I'm not using typescript (and, right now I don't intend to) but the default set up for VSCode seems to have put some checking in for me. It's kind of handy, kind of a pain. I want to stick with it for now.
My code that causes this issue is:
let {
localApiVersion, localDate, remoteVersion, remoteDate,
} = new Proxy({}, { get: () => null });
Which I took from my answer here. I'm doing this because I didn't feel like doing let localApiVersion = null four times.
However VSCode is giving me this error:
Property 'localApiVersion' does not exist on type '{}'.ts(2339)
The check (I think) is because my jscongig.json looks like this:
{
"compilerOptions": {
"module": "commonjs",
"target": "es2019",
"checkJs": true
},
"exclude": [
"node_modules",
"**/node_modules/*"
]
}
I know I can write // @ts-ignore above that line, but don't want to get into that habit.
Is there a way, without removing the check (or adding typescript to the project), to inform VSCode that these are ok?
Here is what worked for me (please upvote the other answers too, they were instrumental in me getting here):
let {
localApiVersion, localDate, remoteVersion, remoteDate,
} = /** @type {Object.<string, (null|string)>} */ (new Proxy({}, { get: () => null }));
Note the @type annotation goes before the new and the whole constructor part is wrapped in parens. I'm using (null|string) instead of unknown a) because I know it will be reassigned to a string and b) so I still get some type checking, even though before reassignment you can make type mistakes. I may switch it to just null, and when changing type explicitly use this sort of thing: @type {string} (localApiVersion).
And here is with the tool tip in VSCode:
Using typedef will also work:
/**
* @typedef {Object.<string, (null|string)>} InitProxy
*/
let {
localApiVersion, localDate, remoteVersion, remoteDate,
} = /** @type {InitProxy} */ (new Proxy({}, { get: () => null }));
But this is getting into the levels of verbosity I'm trying to avoid.
The {} type comes from T extends object constraint in ProxyConstructor; in the absence of an explicit type, T is resolved to an empty object, or {}.
However, {} does not fit the bill, since it doesn't have any properties, while your code requires it to have localApiVersion, localDate, etc. Yes, JavaScript knows about these properties (or rather it doesn't care), but TypeScript doesn't know about them, and it cannot communicate with runtime code.
The solution is to type-cast at least something in this picture, so that TypeScript can infer the correct typings. In a .ts file it is trivial. In a .js file, however, this is what can be done:
// @ts-check
/** @type {Record<string, unknown>} */
const target = {};
let { foo, bar, baz } = new Proxy(target, { get: () => null });
It seems quite surprising you're getting a TypeScript error on JavaScript code, but you can provide type information via JSDoc annotations (see here, here, and here), which are just comments so they don't require using the TypeScript compiler before running the code in a JavaScript environment.
Having said that, I just cannot get it working with JSDoc annotations. I would expect this to work, because the third link above says you can do type assertions this way:
// @ts-check
/**
* @typedef {Object} Stuff - comment here
* @property {any} localApiVersion - comment here
* @property {any} localDate - comment here
* @property {any} remoteVersion - comment here
* @property {any} remoteDate - comment here
*/
let {
localApiVersion, localDate, remoteVersion, remoteDate,
} = new Proxy(/** @type {Stuff} */ {}, { get: () => null });;
But that doesn't work. :-(
Just for what it's worth, this is easy in TypeScript itself:
interface Stuff {
localApiVersion: any;
localDate: any;
remoteVersion: any;
remoteDate: any;
}
let {
localApiVersion, localDate, remoteVersion, remoteDate,
} = new Proxy({} as Stuff, { get: () => null });
// ^^^^^^^^−−−−−−− type assertion
// I normally avoid type assertions, but this use of the Proxy with get
// trap is a very special case.
(Example on the TypeScript playground.)
I would expect it to be possible with JSDoc annotations, since I know people use TypeScript type checking that way, but, well, I just use TypeScript and haven't been able to figure out a JSDoc equivalent.