-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Forbid unused property renaming in destructuring binding in function types #41044
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
4aec8ef
a37d6d4
67732d3
1d114e6
4abcfa1
39e90ce
79ef4fd
69b8a58
4ddd9ae
e210484
2797958
cb326d2
17a29ff
c4c80db
1e3dc2e
32c340e
9c9fe5e
f0e5112
7aa2b6e
755c7c4
c7768cf
bdb42e1
6d4d147
bc92945
e2710b7
1e4c14b
4e519ff
07f162f
6c8a95b
bd0da30
600b614
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 |
---|---|---|
|
@@ -1026,6 +1026,7 @@ namespace ts { | |
const potentialNewTargetCollisions: Node[] = []; | ||
const potentialWeakMapSetCollisions: Node[] = []; | ||
const potentialReflectCollisions: Node[] = []; | ||
const potentialUnusedRenamedBindingElementsInTypes: BindingElement[] = []; | ||
const awaitedTypeStack: number[] = []; | ||
|
||
const diagnostics = createDiagnosticCollection(); | ||
|
@@ -5964,19 +5965,29 @@ namespace ts { | |
return parameterNode; | ||
|
||
function cloneBindingName(node: BindingName): BindingName { | ||
return elideInitializerAndSetEmitFlags(node) as BindingName; | ||
function elideInitializerAndSetEmitFlags(node: Node): Node { | ||
return elideInitializerAndPropertyRenamingAndSetEmitFlags(node) as BindingName; | ||
function elideInitializerAndPropertyRenamingAndSetEmitFlags(node: Node): Node { | ||
if (context.tracker.trackSymbol && isComputedPropertyName(node) && isLateBindableName(node)) { | ||
trackComputedName(node.expression, context.enclosingDeclaration, context); | ||
} | ||
let visited = visitEachChild(node, elideInitializerAndSetEmitFlags, nullTransformationContext, /*nodesVisitor*/ undefined, elideInitializerAndSetEmitFlags)!; | ||
let visited = visitEachChild(node, elideInitializerAndPropertyRenamingAndSetEmitFlags, nullTransformationContext, /*nodesVisitor*/ undefined, elideInitializerAndPropertyRenamingAndSetEmitFlags)!; | ||
if (isBindingElement(visited)) { | ||
visited = factory.updateBindingElement( | ||
visited, | ||
visited.dotDotDotToken, | ||
visited.propertyName, | ||
visited.name, | ||
/*initializer*/ undefined); | ||
if (visited.propertyName && isIdentifier(visited.propertyName) && isIdentifier(visited.name)) { | ||
visited = factory.updateBindingElement( | ||
visited, | ||
visited.dotDotDotToken, | ||
/* propertyName*/ undefined, | ||
visited.propertyName, | ||
/*initializer*/ undefined); | ||
} | ||
else { | ||
visited = factory.updateBindingElement( | ||
visited, | ||
visited.dotDotDotToken, | ||
visited.propertyName, | ||
visited.name, | ||
/*initializer*/ undefined); | ||
} | ||
} | ||
if (!nodeIsSynthesized(visited)) { | ||
visited = factory.cloneNode(visited); | ||
|
@@ -37564,6 +37575,24 @@ namespace ts { | |
}); | ||
} | ||
|
||
function checkPotentialUncheckedRenamedBindingElementsInTypes() { | ||
for (const node of potentialUnusedRenamedBindingElementsInTypes) { | ||
if (!getSymbolOfNode(node)?.isReferenced) { | ||
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. Huh, 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. Apparently just checking truthiness of |
||
const wrappingDeclaration = walkUpBindingElementsAndPatterns(node); | ||
Debug.assert(isParameterDeclaration(wrappingDeclaration), "Only parameter declaration should be checked here"); | ||
const diagnostic = createDiagnosticForNode(node.name, Diagnostics._0_is_an_unused_renaming_of_1_Did_you_intend_to_use_it_as_a_type_annotation, declarationNameToString(node.name), declarationNameToString(node.propertyName)); | ||
if (!wrappingDeclaration.type) { | ||
// entire parameter does not have type annotation, suggest adding an annotation | ||
addRelatedInfo( | ||
diagnostic, | ||
createFileDiagnostic(getSourceFileOfNode(wrappingDeclaration), wrappingDeclaration.end, 1, Diagnostics.We_can_only_write_a_type_for_0_by_adding_a_type_for_the_entire_parameter_here, declarationNameToString(node.propertyName)) | ||
); | ||
} | ||
diagnostics.add(diagnostic); | ||
} | ||
} | ||
} | ||
|
||
function bindingNameText(name: BindingName): string { | ||
switch (name.kind) { | ||
case SyntaxKind.Identifier: | ||
|
@@ -37905,6 +37934,19 @@ namespace ts { | |
} | ||
|
||
if (isBindingElement(node)) { | ||
if ( | ||
node.propertyName && | ||
isIdentifier(node.name) && | ||
isParameterDeclaration(node) && | ||
nodeIsMissing((getContainingFunction(node) as FunctionLikeDeclaration).body)) { | ||
// type F = ({a: string}) => void; | ||
// ^^^^^^ | ||
// variable renaming in function type notation is confusing, | ||
// so we forbid it even if noUnusedLocals is not enabled | ||
potentialUnusedRenamedBindingElementsInTypes.push(node); | ||
return; | ||
} | ||
|
||
if (isObjectBindingPattern(node.parent) && node.dotDotDotToken && languageVersion < ScriptTarget.ES2018) { | ||
checkExternalEmitHelpers(node, ExternalEmitHelpers.Rest); | ||
} | ||
|
@@ -41748,6 +41790,7 @@ namespace ts { | |
clear(potentialNewTargetCollisions); | ||
clear(potentialWeakMapSetCollisions); | ||
clear(potentialReflectCollisions); | ||
clear(potentialUnusedRenamedBindingElementsInTypes); | ||
|
||
forEach(node.statements, checkSourceElement); | ||
checkSourceElement(node.endOfFileToken); | ||
|
@@ -41767,6 +41810,9 @@ namespace ts { | |
} | ||
}); | ||
} | ||
if (!node.isDeclarationFile) { | ||
checkPotentialUncheckedRenamedBindingElementsInTypes(); | ||
} | ||
}); | ||
|
||
if (compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error && | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
tests/cases/conformance/es6/destructuring/destructuringInFunctionType.ts(12,18): error TS2842: 'b' is an unused renaming of 'a'. Did you intend to use it as a type annotation? | ||
tests/cases/conformance/es6/destructuring/destructuringInFunctionType.ts(12,28): error TS2842: 'a' is an unused renaming of 'b'. Did you intend to use it as a type annotation? | ||
|
||
|
||
==== tests/cases/conformance/es6/destructuring/destructuringInFunctionType.ts (2 errors) ==== | ||
interface a { a } | ||
interface b { b } | ||
interface c { c } | ||
|
||
type T1 = ([a, b, c]); | ||
type F1 = ([a, b, c]) => void; | ||
|
||
type T2 = ({ a }); | ||
type F2 = ({ a }) => void; | ||
|
||
type T3 = ([{ a: b }, { b: a }]); | ||
type F3 = ([{ a: b }, { b: a }]) => void; | ||
~ | ||
!!! error TS2842: 'b' is an unused renaming of 'a'. Did you intend to use it as a type annotation? | ||
!!! related TS2843 tests/cases/conformance/es6/destructuring/destructuringInFunctionType.ts:12:32: We can only write a type for 'a' by adding a type for the entire parameter here. | ||
~ | ||
!!! error TS2842: 'a' is an unused renaming of 'b'. Did you intend to use it as a type annotation? | ||
!!! related TS2843 tests/cases/conformance/es6/destructuring/destructuringInFunctionType.ts:12:32: We can only write a type for 'b' by adding a type for the entire parameter here. | ||
|
||
type T4 = ([{ a: [b, c] }]); | ||
type F4 = ([{ a: [b, c] }]) => void; | ||
|
||
type C1 = new ([{ a: [b, c] }]) => void; | ||
|
||
var v1 = ([a, b, c]) => "hello"; | ||
var v2: ([a, b, c]) => string; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
tests/cases/compiler/excessPropertyCheckWithSpread.ts(1,25): error TS2842: 'number' is an unused renaming of 'a'. Did you intend to use it as a type annotation? | ||
|
||
|
||
==== tests/cases/compiler/excessPropertyCheckWithSpread.ts (1 errors) ==== | ||
declare function f({ a: number }): void | ||
~~~~~~ | ||
!!! error TS2842: 'number' is an unused renaming of 'a'. Did you intend to use it as a type annotation? | ||
!!! related TS2843 tests/cases/compiler/excessPropertyCheckWithSpread.ts:1:33: We can only write a type for 'a' by adding a type for the entire parameter here. | ||
interface I { | ||
readonly n: number; | ||
} | ||
declare let i: I; | ||
f({ a: 1, ...i }); | ||
|
||
interface R { | ||
opt?: number | ||
} | ||
interface L { | ||
opt: string | ||
} | ||
declare let l: L; | ||
declare let r: R; | ||
f({ a: 1, ...l, ...r }); | ||
|
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.
It looks like
name
anddiagnostic
are always predictable / derivable fromnode
, so this can just beNode[]
like the others. (I don’t think there’s a need to try to make it general-purpose for future use.) I also don’t like the name but it’s extremely hard to come up with anything better...potentialUnusedRenamedBindingElementsInTypes
? 😅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.
Thank you for feedback, fixed 🙂