Since VSC uses TypeScript under the hood to provide IntelliSense, there is a workaround with some trade-offs: using @this JSDoc tag if you don't mind some duplication.
You have to define the type as an intersection of the constructor type and the assigned object type for the constructor and/or any field defined on the class you want additional this IntelliSense for:
/**
* @typedef {{
* foo: string;
* bar: string;
* }} MyAdditions
*/
class MyClass {
/**
* @param {MyAdditions} options
* @this {MyClass & MyAdditions}
*/
constructor({ foo, bar }) {
Object.assign(this, { foo, bar });
// all ok
this.foo;
this.bar;
}
/**
* @this {MyClass & MyAdditions}
*/
doSomethingWithMyAdditions() {
const { foo, bar } = this; // all ok too
console.log(`${foo}${bar}`);
}
}
The caveat, however, is referring to the properties from class instances. Unfortunately, overriding this type of a constructor does not change its return type (given the example above, the return type is still MyClass), so this is still illegal:
const c = new MyClass({ foo: "bar", bar: "foo" });
c.foo; //Property 'foo' does not exist on type 'MyClass'
c.bar; //Property 'bar' does not exist on type 'MyClass'
A cookie smart enough to try to add a @returns tag to the constructor will be greeted with a TS1903 error "Type annotation cannot appear on a constructor declaration". Using @type annotation (i.e. @type {new () => MyClass & MyAdditions}) as an alternative simply results in a no-op.
That said, nothing stops you from casting an instance type (after all, instantiation should not happen too often) with the @type annotation (note the parenthesis around the new):
const d = /** @type {MyClass & MyAdditions} */ (new MyClass({ foo: "bar", bar: "foo" }));
d.foo; // ok now
d.bar; // ok now
Playground
In any case, this is only a workaround, and the original issue is still in the backlog.