-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Fix use-before-def errors for ESNext property declarations #36465
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1326,12 +1326,14 @@ namespace ts { | |
} | ||
else if (isPropertyDeclaration(declaration)) { | ||
// still might be illegal if a self-referencing property initializer (eg private x = this.x) | ||
return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage); | ||
return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ false); | ||
} | ||
else if (isParameterPropertyDeclaration(declaration, declaration.parent)) { | ||
const container = getEnclosingBlockScopeContainer(declaration.parent); | ||
// foo = this.bar is illegal in esnext+useDefineForClassFields when bar is a parameter property | ||
return !(compilerOptions.target === ScriptTarget.ESNext && !!compilerOptions.useDefineForClassFields | ||
&& isUsedInFunctionOrInstanceProperty(usage, declaration)); | ||
&& getContainingClass(declaration) === getContainingClass(usage) | ||
&& isUsedInFunctionOrInstanceProperty(usage, declaration, container)); | ||
} | ||
return true; | ||
} | ||
|
@@ -1357,10 +1359,19 @@ namespace ts { | |
} | ||
|
||
const container = getEnclosingBlockScopeContainer(declaration); | ||
return !!(usage.flags & NodeFlags.JSDoc) | ||
|| isInTypeQuery(usage) | ||
|| isUsedInFunctionOrInstanceProperty(usage, declaration, container) | ||
&& !(compilerOptions.target === ScriptTarget.ESNext && !!compilerOptions.useDefineForClassFields); | ||
if (!!(usage.flags & NodeFlags.JSDoc) || isInTypeQuery(usage)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I bet this condition actually could be the same as |
||
return true; | ||
} | ||
if (isUsedInFunctionOrInstanceProperty(usage, declaration, container)) { | ||
if (compilerOptions.target === ScriptTarget.ESNext && !!compilerOptions.useDefineForClassFields) { | ||
return (isPropertyDeclaration(declaration) || isParameterPropertyDeclaration(declaration, declaration.parent)) && | ||
!isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ true); | ||
} | ||
else { | ||
return true; | ||
} | ||
} | ||
return false; | ||
|
||
function isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration: VariableDeclaration, usage: Node): boolean { | ||
const container = getEnclosingBlockScopeContainer(declaration); | ||
|
@@ -1412,7 +1423,8 @@ namespace ts { | |
}); | ||
} | ||
|
||
function isPropertyImmediatelyReferencedWithinDeclaration(declaration: PropertyDeclaration, usage: Node) { | ||
/** stopAtAnyPropertyDeclaration is used for detecting ES-standard class field use-before-def errors */ | ||
function isPropertyImmediatelyReferencedWithinDeclaration(declaration: PropertyDeclaration | ParameterPropertyDeclaration, usage: Node, stopAtAnyPropertyDeclaration: boolean) { | ||
// always legal if usage is after declaration | ||
if (usage.end > declaration.end) { | ||
return false; | ||
|
@@ -1427,8 +1439,13 @@ namespace ts { | |
|
||
switch (node.kind) { | ||
case SyntaxKind.ArrowFunction: | ||
case SyntaxKind.PropertyDeclaration: | ||
return true; | ||
case SyntaxKind.PropertyDeclaration: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can’t quite figure out what this means without stepping through one case that needs class Foo {
Bar = class extends Foo {
p2 = this.p1
}
p1 = 0
} (I don’t know, maybe there’s a more creative combo of these horrors) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is one: class B2<V> {
anon = class extends A<V> { }
} Your example works on 3.7, so needs to keep working on 3.8: Back to the drawing board I guess. What a test case! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Commit to fix this looks good 👍 |
||
// even when stopping at any property declaration, they need to come from the same class | ||
return stopAtAnyPropertyDeclaration && | ||
(isPropertyDeclaration(declaration) && node.parent === declaration.parent | ||
|| isParameterPropertyDeclaration(declaration, declaration.parent) && node.parent === declaration.parent.parent) | ||
? "quit": true; | ||
case SyntaxKind.Block: | ||
switch (node.parent.kind) { | ||
case SyntaxKind.GetAccessor: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For some reason my brain wants this expression to be De Morgan’d, or maybe for whole function itself to return use-before-declared, not declared-before-used. Oh well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the WHOLE time I was working on this, except every change made things harder to read.