From 3c576f108c950e43634cece77eaff0d4ef7fc3b0 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Thu, 11 Mar 2021 16:39:20 +0200 Subject: [PATCH] fix(41027): handle unused static members (#41103) --- src/compiler/checker.ts | 17 ++- .../unusedPrivateStaticMembers.errors.txt | 58 ++++++++ .../reference/unusedPrivateStaticMembers.js | 82 +++++++++++ .../unusedPrivateStaticMembers.symbols | 102 ++++++++++++++ .../unusedPrivateStaticMembers.types | 130 ++++++++++++++++++ .../compiler/unusedPrivateStaticMembers.ts | 45 ++++++ 6 files changed, 427 insertions(+), 7 deletions(-) create mode 100644 tests/baselines/reference/unusedPrivateStaticMembers.errors.txt create mode 100644 tests/baselines/reference/unusedPrivateStaticMembers.js create mode 100644 tests/baselines/reference/unusedPrivateStaticMembers.symbols create mode 100644 tests/baselines/reference/unusedPrivateStaticMembers.types create mode 100644 tests/cases/compiler/unusedPrivateStaticMembers.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b34869f7882..d6dbdf2908a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14334,7 +14334,7 @@ namespace ts { addDeprecatedSuggestion(deprecatedNode, prop.declarations, propName as string); } if (accessExpression) { - markPropertyAsReferenced(prop, accessExpression, /*isThisAccess*/ accessExpression.expression.kind === SyntaxKind.ThisKeyword); + markPropertyAsReferenced(prop, accessExpression, isSelfTypeAccess(accessExpression.expression, objectType.symbol)); if (isAssignmentToReadonlyEntity(accessExpression, prop, getAssignmentTargetKind(accessExpression))) { error(accessExpression.argumentExpression, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(prop)); return undefined; @@ -26796,7 +26796,7 @@ namespace ts { addDeprecatedSuggestion(right, prop.declarations, right.escapedText as string); } checkPropertyNotUsedBeforeDeclaration(prop, node, right); - markPropertyAsReferenced(prop, node, left.kind === SyntaxKind.ThisKeyword); + markPropertyAsReferenced(prop, node, isSelfTypeAccess(left, parentSymbol)); getNodeLinks(node).resolvedSymbol = prop; checkPropertyAccessibility(node, left.kind === SyntaxKind.SuperKeyword, apparentType, prop); if (isAssignmentToReadonlyEntity(node as Expression, prop, assignmentKind)) { @@ -27125,7 +27125,7 @@ namespace ts { } } - function markPropertyAsReferenced(prop: Symbol, nodeForCheckWriteOnly: Node | undefined, isThisAccess: boolean) { + function markPropertyAsReferenced(prop: Symbol, nodeForCheckWriteOnly: Node | undefined, isSelfTypeAccess: boolean) { const valueDeclaration = prop && (prop.flags & SymbolFlags.ClassMember) && prop.valueDeclaration; if (!valueDeclaration) { return; @@ -27138,8 +27138,7 @@ namespace ts { if (nodeForCheckWriteOnly && isWriteOnlyAccess(nodeForCheckWriteOnly) && !(prop.flags & SymbolFlags.SetAccessor)) { return; } - - if (isThisAccess) { + if (isSelfTypeAccess) { // Find any FunctionLikeDeclaration because those create a new 'this' binding. But this should only matter for methods (or getters/setters). const containingMethod = findAncestor(nodeForCheckWriteOnly, isFunctionLikeDeclaration); if (containingMethod && containingMethod.symbol === prop) { @@ -27150,6 +27149,11 @@ namespace ts { (getCheckFlags(prop) & CheckFlags.Instantiated ? getSymbolLinks(prop).target : prop)!.isReferenced = SymbolFlags.All; } + function isSelfTypeAccess(name: Expression | QualifiedName, parent: Symbol | undefined) { + return name.kind === SyntaxKind.ThisKeyword + || !!parent && isEntityNameExpression(name) && parent === getResolvedSymbol(getFirstIdentifier(name)); + } + function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName | ImportTypeNode, propertyName: __String): boolean { switch (node.kind) { case SyntaxKind.PropertyAccessExpression: @@ -34700,7 +34704,7 @@ namespace ts { const nameText = getPropertyNameFromType(exprType); const property = getPropertyOfType(parentType, nameText); if (property) { - markPropertyAsReferenced(property, /*nodeForCheckWriteOnly*/ undefined, /*isThisAccess*/ false); // A destructuring is never a write-only reference. + markPropertyAsReferenced(property, /*nodeForCheckWriteOnly*/ undefined, /*isSelfTypeAccess*/ false); // A destructuring is never a write-only reference. checkPropertyAccessibility(node, !!parent.initializer && parent.initializer.kind === SyntaxKind.SuperKeyword, parentType, property); } } @@ -41504,5 +41508,4 @@ namespace ts { export function signatureHasLiteralTypes(s: Signature) { return !!(s.flags & SignatureFlags.HasLiteralTypes); } - } diff --git a/tests/baselines/reference/unusedPrivateStaticMembers.errors.txt b/tests/baselines/reference/unusedPrivateStaticMembers.errors.txt new file mode 100644 index 00000000000..a3e972377fd --- /dev/null +++ b/tests/baselines/reference/unusedPrivateStaticMembers.errors.txt @@ -0,0 +1,58 @@ +tests/cases/compiler/unusedPrivateStaticMembers.ts(16,20): error TS6133: 'p1' is declared but its value is never read. +tests/cases/compiler/unusedPrivateStaticMembers.ts(17,20): error TS6133: 'm1' is declared but its value is never read. +tests/cases/compiler/unusedPrivateStaticMembers.ts(21,20): error TS6133: 'm1' is declared but its value is never read. +tests/cases/compiler/unusedPrivateStaticMembers.ts(25,20): error TS6133: 'm2' is declared but its value is never read. + + +==== tests/cases/compiler/unusedPrivateStaticMembers.ts (4 errors) ==== + class Test1 { + private static m1() {} + public static test() { + Test1.m1(); + } + } + + class Test2 { + private static p1 = 0 + public static test() { + Test2.p1; + } + } + + class Test3 { + private static p1 = 0; + ~~ +!!! error TS6133: 'p1' is declared but its value is never read. + private static m1() {} + ~~ +!!! error TS6133: 'm1' is declared but its value is never read. + } + + class Test4 { + private static m1(n: number): number { + ~~ +!!! error TS6133: 'm1' is declared but its value is never read. + return (n === 0) ? 1 : (n * Test4.m1(n - 1)); + } + + private static m2(n: number): number { + ~~ +!!! error TS6133: 'm2' is declared but its value is never read. + return (n === 0) ? 1 : (n * Test4["m2"](n - 1)); + } + } + + class Test5 { + private static m1() {} + public static test() { + Test5["m1"](); + } + } + + class Test6 { + private static p1 = 0; + public static test() { + Test6["p1"]; + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/unusedPrivateStaticMembers.js b/tests/baselines/reference/unusedPrivateStaticMembers.js new file mode 100644 index 00000000000..8fda032e8bd --- /dev/null +++ b/tests/baselines/reference/unusedPrivateStaticMembers.js @@ -0,0 +1,82 @@ +//// [unusedPrivateStaticMembers.ts] +class Test1 { + private static m1() {} + public static test() { + Test1.m1(); + } +} + +class Test2 { + private static p1 = 0 + public static test() { + Test2.p1; + } +} + +class Test3 { + private static p1 = 0; + private static m1() {} +} + +class Test4 { + private static m1(n: number): number { + return (n === 0) ? 1 : (n * Test4.m1(n - 1)); + } + + private static m2(n: number): number { + return (n === 0) ? 1 : (n * Test4["m2"](n - 1)); + } +} + +class Test5 { + private static m1() {} + public static test() { + Test5["m1"](); + } +} + +class Test6 { + private static p1 = 0; + public static test() { + Test6["p1"]; + } +} + + +//// [unusedPrivateStaticMembers.js] +class Test1 { + static m1() { } + static test() { + Test1.m1(); + } +} +class Test2 { + static test() { + Test2.p1; + } +} +Test2.p1 = 0; +class Test3 { + static m1() { } +} +Test3.p1 = 0; +class Test4 { + static m1(n) { + return (n === 0) ? 1 : (n * Test4.m1(n - 1)); + } + static m2(n) { + return (n === 0) ? 1 : (n * Test4["m2"](n - 1)); + } +} +class Test5 { + static m1() { } + static test() { + Test5["m1"](); + } +} +class Test6 { + static test() { + Test6["p1"]; + } +} +Test6.p1 = 0; diff --git a/tests/baselines/reference/unusedPrivateStaticMembers.symbols b/tests/baselines/reference/unusedPrivateStaticMembers.symbols new file mode 100644 index 00000000000..bacd889a27d --- /dev/null +++ b/tests/baselines/reference/unusedPrivateStaticMembers.symbols @@ -0,0 +1,102 @@ +=== tests/cases/compiler/unusedPrivateStaticMembers.ts === +class Test1 { +>Test1 : Symbol(Test1, Decl(unusedPrivateStaticMembers.ts, 0, 0)) + + private static m1() {} +>m1 : Symbol(Test1.m1, Decl(unusedPrivateStaticMembers.ts, 0, 13)) + + public static test() { +>test : Symbol(Test1.test, Decl(unusedPrivateStaticMembers.ts, 1, 26)) + + Test1.m1(); +>Test1.m1 : Symbol(Test1.m1, Decl(unusedPrivateStaticMembers.ts, 0, 13)) +>Test1 : Symbol(Test1, Decl(unusedPrivateStaticMembers.ts, 0, 0)) +>m1 : Symbol(Test1.m1, Decl(unusedPrivateStaticMembers.ts, 0, 13)) + } +} + +class Test2 { +>Test2 : Symbol(Test2, Decl(unusedPrivateStaticMembers.ts, 5, 1)) + + private static p1 = 0 +>p1 : Symbol(Test2.p1, Decl(unusedPrivateStaticMembers.ts, 7, 13)) + + public static test() { +>test : Symbol(Test2.test, Decl(unusedPrivateStaticMembers.ts, 8, 25)) + + Test2.p1; +>Test2.p1 : Symbol(Test2.p1, Decl(unusedPrivateStaticMembers.ts, 7, 13)) +>Test2 : Symbol(Test2, Decl(unusedPrivateStaticMembers.ts, 5, 1)) +>p1 : Symbol(Test2.p1, Decl(unusedPrivateStaticMembers.ts, 7, 13)) + } +} + +class Test3 { +>Test3 : Symbol(Test3, Decl(unusedPrivateStaticMembers.ts, 12, 1)) + + private static p1 = 0; +>p1 : Symbol(Test3.p1, Decl(unusedPrivateStaticMembers.ts, 14, 13)) + + private static m1() {} +>m1 : Symbol(Test3.m1, Decl(unusedPrivateStaticMembers.ts, 15, 26)) +} + +class Test4 { +>Test4 : Symbol(Test4, Decl(unusedPrivateStaticMembers.ts, 17, 1)) + + private static m1(n: number): number { +>m1 : Symbol(Test4.m1, Decl(unusedPrivateStaticMembers.ts, 19, 13)) +>n : Symbol(n, Decl(unusedPrivateStaticMembers.ts, 20, 22)) + + return (n === 0) ? 1 : (n * Test4.m1(n - 1)); +>n : Symbol(n, Decl(unusedPrivateStaticMembers.ts, 20, 22)) +>n : Symbol(n, Decl(unusedPrivateStaticMembers.ts, 20, 22)) +>Test4.m1 : Symbol(Test4.m1, Decl(unusedPrivateStaticMembers.ts, 19, 13)) +>Test4 : Symbol(Test4, Decl(unusedPrivateStaticMembers.ts, 17, 1)) +>m1 : Symbol(Test4.m1, Decl(unusedPrivateStaticMembers.ts, 19, 13)) +>n : Symbol(n, Decl(unusedPrivateStaticMembers.ts, 20, 22)) + } + + private static m2(n: number): number { +>m2 : Symbol(Test4.m2, Decl(unusedPrivateStaticMembers.ts, 22, 5)) +>n : Symbol(n, Decl(unusedPrivateStaticMembers.ts, 24, 22)) + + return (n === 0) ? 1 : (n * Test4["m2"](n - 1)); +>n : Symbol(n, Decl(unusedPrivateStaticMembers.ts, 24, 22)) +>n : Symbol(n, Decl(unusedPrivateStaticMembers.ts, 24, 22)) +>Test4 : Symbol(Test4, Decl(unusedPrivateStaticMembers.ts, 17, 1)) +>"m2" : Symbol(Test4.m2, Decl(unusedPrivateStaticMembers.ts, 22, 5)) +>n : Symbol(n, Decl(unusedPrivateStaticMembers.ts, 24, 22)) + } +} + +class Test5 { +>Test5 : Symbol(Test5, Decl(unusedPrivateStaticMembers.ts, 27, 1)) + + private static m1() {} +>m1 : Symbol(Test5.m1, Decl(unusedPrivateStaticMembers.ts, 29, 13)) + + public static test() { +>test : Symbol(Test5.test, Decl(unusedPrivateStaticMembers.ts, 30, 26)) + + Test5["m1"](); +>Test5 : Symbol(Test5, Decl(unusedPrivateStaticMembers.ts, 27, 1)) +>"m1" : Symbol(Test5.m1, Decl(unusedPrivateStaticMembers.ts, 29, 13)) + } +} + +class Test6 { +>Test6 : Symbol(Test6, Decl(unusedPrivateStaticMembers.ts, 34, 1)) + + private static p1 = 0; +>p1 : Symbol(Test6.p1, Decl(unusedPrivateStaticMembers.ts, 36, 13)) + + public static test() { +>test : Symbol(Test6.test, Decl(unusedPrivateStaticMembers.ts, 37, 26)) + + Test6["p1"]; +>Test6 : Symbol(Test6, Decl(unusedPrivateStaticMembers.ts, 34, 1)) +>"p1" : Symbol(Test6.p1, Decl(unusedPrivateStaticMembers.ts, 36, 13)) + } +} + diff --git a/tests/baselines/reference/unusedPrivateStaticMembers.types b/tests/baselines/reference/unusedPrivateStaticMembers.types new file mode 100644 index 00000000000..9202659ffe2 --- /dev/null +++ b/tests/baselines/reference/unusedPrivateStaticMembers.types @@ -0,0 +1,130 @@ +=== tests/cases/compiler/unusedPrivateStaticMembers.ts === +class Test1 { +>Test1 : Test1 + + private static m1() {} +>m1 : () => void + + public static test() { +>test : () => void + + Test1.m1(); +>Test1.m1() : void +>Test1.m1 : () => void +>Test1 : typeof Test1 +>m1 : () => void + } +} + +class Test2 { +>Test2 : Test2 + + private static p1 = 0 +>p1 : number +>0 : 0 + + public static test() { +>test : () => void + + Test2.p1; +>Test2.p1 : number +>Test2 : typeof Test2 +>p1 : number + } +} + +class Test3 { +>Test3 : Test3 + + private static p1 = 0; +>p1 : number +>0 : 0 + + private static m1() {} +>m1 : () => void +} + +class Test4 { +>Test4 : Test4 + + private static m1(n: number): number { +>m1 : (n: number) => number +>n : number + + return (n === 0) ? 1 : (n * Test4.m1(n - 1)); +>(n === 0) ? 1 : (n * Test4.m1(n - 1)) : number +>(n === 0) : boolean +>n === 0 : boolean +>n : number +>0 : 0 +>1 : 1 +>(n * Test4.m1(n - 1)) : number +>n * Test4.m1(n - 1) : number +>n : number +>Test4.m1(n - 1) : number +>Test4.m1 : (n: number) => number +>Test4 : typeof Test4 +>m1 : (n: number) => number +>n - 1 : number +>n : number +>1 : 1 + } + + private static m2(n: number): number { +>m2 : (n: number) => number +>n : number + + return (n === 0) ? 1 : (n * Test4["m2"](n - 1)); +>(n === 0) ? 1 : (n * Test4["m2"](n - 1)) : number +>(n === 0) : boolean +>n === 0 : boolean +>n : number +>0 : 0 +>1 : 1 +>(n * Test4["m2"](n - 1)) : number +>n * Test4["m2"](n - 1) : number +>n : number +>Test4["m2"](n - 1) : number +>Test4["m2"] : (n: number) => number +>Test4 : typeof Test4 +>"m2" : "m2" +>n - 1 : number +>n : number +>1 : 1 + } +} + +class Test5 { +>Test5 : Test5 + + private static m1() {} +>m1 : () => void + + public static test() { +>test : () => void + + Test5["m1"](); +>Test5["m1"]() : void +>Test5["m1"] : () => void +>Test5 : typeof Test5 +>"m1" : "m1" + } +} + +class Test6 { +>Test6 : Test6 + + private static p1 = 0; +>p1 : number +>0 : 0 + + public static test() { +>test : () => void + + Test6["p1"]; +>Test6["p1"] : number +>Test6 : typeof Test6 +>"p1" : "p1" + } +} + diff --git a/tests/cases/compiler/unusedPrivateStaticMembers.ts b/tests/cases/compiler/unusedPrivateStaticMembers.ts new file mode 100644 index 00000000000..baae4c0bdba --- /dev/null +++ b/tests/cases/compiler/unusedPrivateStaticMembers.ts @@ -0,0 +1,45 @@ +// @noUnusedLocals: true +// @target: esnext + +class Test1 { + private static m1() {} + public static test() { + Test1.m1(); + } +} + +class Test2 { + private static p1 = 0 + public static test() { + Test2.p1; + } +} + +class Test3 { + private static p1 = 0; + private static m1() {} +} + +class Test4 { + private static m1(n: number): number { + return (n === 0) ? 1 : (n * Test4.m1(n - 1)); + } + + private static m2(n: number): number { + return (n === 0) ? 1 : (n * Test4["m2"](n - 1)); + } +} + +class Test5 { + private static m1() {} + public static test() { + Test5["m1"](); + } +} + +class Test6 { + private static p1 = 0; + public static test() { + Test6["p1"]; + } +}