Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

In JS, assignment to an undeclared instance property should act as its declaration #22896

Closed
mohsen1 opened this issue Mar 27, 2018 · 9 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Domain: JavaScript The issue relates to JavaScript specifically Suggestion An idea for TypeScript

Comments

@mohsen1
Copy link
Contributor

mohsen1 commented Mar 27, 2018

TypeScript Version: 2.7.0-dev.201xxxxx

Search Terms:
Salsa, Class member, instance

TypeScript Version
2.7.2

Code

// tsconfig
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "allowJs": true,
    "checkJs": true,
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true
  }
}
// index.js
class Foo {};
let f = new Foo();
f.bar = 1

Expected behavior:
No error

Actual behavior:

$ tsc --pretty
index.js:3:3 - error TS2339: Property 'bar' does not exist on type 'Foo'.

3 f.bar = 1
    ~~~

@marvinhagemeister
Copy link

Note that this is about checking js files, not ts guess where one would declare class properties anyways

@Jessidhia
Copy link

I think it's a correct error, because it's an instance variable.

class Foo { constructor () { this.f = undefined } } should be enough to declare f as an instance variable; however, its type may end up being undefined instead of any or some other appropriate type. It'd be better to annotate the this.f assignment with @type.

@mohsen1
Copy link
Contributor Author

mohsen1 commented Mar 27, 2018

@Kovensky Not sure what design goals of Salsa is but this.f = undefinedin constructor seems unlike usual JavaScript.

@DanielRosenwasser
Copy link
Member

To be honest, I think adding properties to instances after the fact isn't something we can do under our current design.

@RyanCavanaugh
Copy link
Member

What's being proposed? Would a set of a property on a class instance never be an error? That seems like going too far.

@mohsen1
Copy link
Contributor Author

mohsen1 commented Mar 27, 2018

At least in Webpack they are fine with initiating the member with undefined in constructor.

See this comment: webpack/webpack#6869 (comment)

@mhegazy
Copy link
Contributor

mhegazy commented Mar 27, 2018

I think a solution here is to support /** @property */ and/or /** @member */tags on classes. this way the code does not need to be changed with a this.prop = undefined, just a comment added along with a type.

similar to #15715.

@sandersn sandersn changed the title Salsa enforce class member declaration In JS, assignment to an undeclared instance property should act as its declaration Mar 28, 2018
@sandersn sandersn added the Salsa label Mar 28, 2018
@sandersn
Copy link
Member

A @property jsdoc may be the right solution for some code bases, but I don't think webpack is an example of one. For webpack to compile with checkJs, you have to add (at least) one line of code to fix the "Property does not exist" error. The question is which line you want to write:

class C {
  constructor() {
    this.upfront = 1
    // here?: this.late = undefined
  }
}
var c = new C();
// here?: /** @property */
c.late = 2;

Of the two, this.late = undefined is clearly better since it improves the definition of C both at edit time and compile time by making it easier for the compiler and the VM, respectively, to understand.

The only reason I can think of to prefer /** @property */ in this case is if you want to check a code base without changing anything except comments. But in that case I think you are better off inserting // @ts-ignore FIXME everywhere late is referenced, the way Google does with errors it wants to ignore. For most projects, the whole point of introducing Typescript is to make changes based on the errors it produces. If some project authors aren't on board, then they won't be happy with any changes that Typescript precipitates — some are guaranteed to be non-idiomatic Javascript.

One place where /** @property */ may make sense is an initializer that is not part of a class (or constructor function):

function C(full = true) {
  this.basics = 'just the basics'
  if (full) initC(this)
}
function initC(c) {
  c.other = 'stuff goes here'
  c.db = somedb.connect()
  c.launchMissiles = function() { /* CLASSIFIED but definitely IRREVOCABLE */ }
}
C.prototype.afterInitialisation = function() {
  // refer multiple times to c.other, c.db, c.launchMissiles
}

Since initC initialises a lot of properties, and presumably does so during construction much of the time, it wouldn't provide a runtime advantage to have a bunch of assignments like this.other = undefined in the constructor function, and the duplication would stand out in the source. Note that to get this to work, we'd need to type the parameter c as well.

/** @param {C} c */
function initC(c) {
  /** @property */
  c.other = 'stuff goes here'
  /** @property */
  c.db = somedb.connect()
  /** @property */
  c.launchMissiles = function() { /* CLASSIFIED but definitely IRREVOCABLE */ }
}

Even in this scenario, the duplication in the constructor might be just as good as the additional doc comments in initC.

My vote is to look for some other motivating example than webpack, since it looks like the webpack changes just required one or two additions per class. Plus, those additions are good for the runtime performance of webpack.

@mhegazy
Copy link
Contributor

mhegazy commented Mar 28, 2018

I was thinking of adding it on the class/function declaration, e.g.:

/**
  * @property {number} a
  */
class C {
    m() {
         this.a;
    }
}

or

/**
 * @class
 * @property {number} a
 */
function F() { 
    this.m = function m () { 
        this.a;
    }
}

@weswigham weswigham added Domain: JavaScript The issue relates to JavaScript specifically and removed Salsa labels Nov 29, 2018
@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature labels Mar 7, 2019
@mohsen1 mohsen1 closed this as completed Apr 1, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Domain: JavaScript The issue relates to JavaScript specifically Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

8 participants