From a20470e005278ba7409a8f7dc749d3dd0e0a5fb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E7=92=90?= Date: Sun, 18 Feb 2018 18:33:22 +0800 Subject: [PATCH 01/11] disallow nan and infinity in enum member --- src/compiler/checker.ts | 8 +- .../reference/enumConstantMembers.errors.txt | 85 +++++++++++++++++++ .../reference/enumConstantMembers.js | 33 ++++++- .../reference/enumConstantMembers.symbols | 57 +++++++++++++ .../reference/enumConstantMembers.types | 83 ++++++++++++++++++ .../conformance/enums/enumConstantMembers.ts | 22 ++++- 6 files changed, 284 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/enumConstantMembers.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e8b31f7c9a4..ff8be6a0ba8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -24311,7 +24311,7 @@ namespace ts { const initializer = member.initializer; const value = enumKind === EnumKind.Literal && !isLiteralEnumMember(member) ? undefined : evaluate(initializer); if (value !== undefined) { - if (isConstEnum && typeof value === "number" && !isFinite(value)) { + if (typeof value === "number" && !isFinite(value)) { error(initializer, isNaN(value) ? Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN : Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value); @@ -24376,7 +24376,11 @@ namespace ts { case SyntaxKind.ParenthesizedExpression: return evaluate((expr).expression); case SyntaxKind.Identifier: - return nodeIsMissing(expr) ? 0 : evaluateEnumMember(expr, getSymbolOfNode(member.parent), (expr).escapedText); + const identifier = expr; + if (isInfinityOrNaNString(identifier.escapedText)) { + return +(identifier.escapedText); + } + return nodeIsMissing(expr) ? 0 : evaluateEnumMember(expr, getSymbolOfNode(member.parent), identifier.escapedText); case SyntaxKind.ElementAccessExpression: case SyntaxKind.PropertyAccessExpression: const ex = expr; diff --git a/tests/baselines/reference/enumConstantMembers.errors.txt b/tests/baselines/reference/enumConstantMembers.errors.txt new file mode 100644 index 00000000000..16bfa92a761 --- /dev/null +++ b/tests/baselines/reference/enumConstantMembers.errors.txt @@ -0,0 +1,85 @@ +tests/cases/conformance/enums/enumConstantMembers.ts(22,9): error TS2477: 'const' enum member initializer was evaluated to a non-finite value. +tests/cases/conformance/enums/enumConstantMembers.ts(23,9): error TS2477: 'const' enum member initializer was evaluated to a non-finite value. +tests/cases/conformance/enums/enumConstantMembers.ts(24,9): error TS2477: 'const' enum member initializer was evaluated to a non-finite value. +tests/cases/conformance/enums/enumConstantMembers.ts(25,9): error TS2478: 'const' enum member initializer was evaluated to disallowed value 'NaN'. +tests/cases/conformance/enums/enumConstantMembers.ts(26,9): error TS2478: 'const' enum member initializer was evaluated to disallowed value 'NaN'. +tests/cases/conformance/enums/enumConstantMembers.ts(27,9): error TS2477: 'const' enum member initializer was evaluated to a non-finite value. +tests/cases/conformance/enums/enumConstantMembers.ts(28,9): error TS2477: 'const' enum member initializer was evaluated to a non-finite value. +tests/cases/conformance/enums/enumConstantMembers.ts(32,9): error TS2477: 'const' enum member initializer was evaluated to a non-finite value. +tests/cases/conformance/enums/enumConstantMembers.ts(33,9): error TS2477: 'const' enum member initializer was evaluated to a non-finite value. +tests/cases/conformance/enums/enumConstantMembers.ts(34,9): error TS2477: 'const' enum member initializer was evaluated to a non-finite value. +tests/cases/conformance/enums/enumConstantMembers.ts(35,9): error TS2478: 'const' enum member initializer was evaluated to disallowed value 'NaN'. +tests/cases/conformance/enums/enumConstantMembers.ts(36,9): error TS2478: 'const' enum member initializer was evaluated to disallowed value 'NaN'. +tests/cases/conformance/enums/enumConstantMembers.ts(37,9): error TS2477: 'const' enum member initializer was evaluated to a non-finite value. +tests/cases/conformance/enums/enumConstantMembers.ts(38,9): error TS2477: 'const' enum member initializer was evaluated to a non-finite value. + + +==== tests/cases/conformance/enums/enumConstantMembers.ts (14 errors) ==== + // Constant members allow negatives, but not decimals. Also hex literals are allowed + enum E1 { + a = 1, + b + } + enum E2 { + a = - 1, + b + } + enum E3 { + a = 0.1, + b // Error because 0.1 is not a constant + } + + declare enum E4 { + a = 1, + b = -1, + c = 0.1 // Not a constant + } + + enum E5 { + a = 1 / 0, + ~~~~~ +!!! error TS2477: 'const' enum member initializer was evaluated to a non-finite value. + b = 2 / 0.0, + ~~~~~~~ +!!! error TS2477: 'const' enum member initializer was evaluated to a non-finite value. + c = 1.0 / 0.0, + ~~~~~~~~~ +!!! error TS2477: 'const' enum member initializer was evaluated to a non-finite value. + d = 0.0 / 0.0, + ~~~~~~~~~ +!!! error TS2478: 'const' enum member initializer was evaluated to disallowed value 'NaN'. + e = NaN, + ~~~ +!!! error TS2478: 'const' enum member initializer was evaluated to disallowed value 'NaN'. + f = Infinity, + ~~~~~~~~ +!!! error TS2477: 'const' enum member initializer was evaluated to a non-finite value. + g = -Infinity + ~~~~~~~~~ +!!! error TS2477: 'const' enum member initializer was evaluated to a non-finite value. + } + + const enum E6 { + a = 1 / 0, + ~~~~~ +!!! error TS2477: 'const' enum member initializer was evaluated to a non-finite value. + b = 2 / 0.0, + ~~~~~~~ +!!! error TS2477: 'const' enum member initializer was evaluated to a non-finite value. + c = 1.0 / 0.0, + ~~~~~~~~~ +!!! error TS2477: 'const' enum member initializer was evaluated to a non-finite value. + d = 0.0 / 0.0, + ~~~~~~~~~ +!!! error TS2478: 'const' enum member initializer was evaluated to disallowed value 'NaN'. + e = NaN, + ~~~ +!!! error TS2478: 'const' enum member initializer was evaluated to disallowed value 'NaN'. + f = Infinity, + ~~~~~~~~ +!!! error TS2477: 'const' enum member initializer was evaluated to a non-finite value. + g = -Infinity + ~~~~~~~~~ +!!! error TS2477: 'const' enum member initializer was evaluated to a non-finite value. + } + \ No newline at end of file diff --git a/tests/baselines/reference/enumConstantMembers.js b/tests/baselines/reference/enumConstantMembers.js index f58c7d253af..a74d0570639 100644 --- a/tests/baselines/reference/enumConstantMembers.js +++ b/tests/baselines/reference/enumConstantMembers.js @@ -17,7 +17,28 @@ declare enum E4 { a = 1, b = -1, c = 0.1 // Not a constant -} +} + +enum E5 { + a = 1 / 0, + b = 2 / 0.0, + c = 1.0 / 0.0, + d = 0.0 / 0.0, + e = NaN, + f = Infinity, + g = -Infinity +} + +const enum E6 { + a = 1 / 0, + b = 2 / 0.0, + c = 1.0 / 0.0, + d = 0.0 / 0.0, + e = NaN, + f = Infinity, + g = -Infinity +} + //// [enumConstantMembers.js] // Constant members allow negatives, but not decimals. Also hex literals are allowed @@ -36,3 +57,13 @@ var E3; E3[E3["a"] = 0.1] = "a"; E3[E3["b"] = 1.1] = "b"; // Error because 0.1 is not a constant })(E3 || (E3 = {})); +var E5; +(function (E5) { + E5[E5["a"] = Infinity] = "a"; + E5[E5["b"] = Infinity] = "b"; + E5[E5["c"] = Infinity] = "c"; + E5[E5["d"] = NaN] = "d"; + E5[E5["e"] = NaN] = "e"; + E5[E5["f"] = Infinity] = "f"; + E5[E5["g"] = -Infinity] = "g"; +})(E5 || (E5 = {})); diff --git a/tests/baselines/reference/enumConstantMembers.symbols b/tests/baselines/reference/enumConstantMembers.symbols index 6bf516cceef..54a62d8911b 100644 --- a/tests/baselines/reference/enumConstantMembers.symbols +++ b/tests/baselines/reference/enumConstantMembers.symbols @@ -40,3 +40,60 @@ declare enum E4 { c = 0.1 // Not a constant >c : Symbol(E4.c, Decl(enumConstantMembers.ts, 16, 11)) } + +enum E5 { +>E5 : Symbol(E5, Decl(enumConstantMembers.ts, 18, 1)) + + a = 1 / 0, +>a : Symbol(E5.a, Decl(enumConstantMembers.ts, 20, 9)) + + b = 2 / 0.0, +>b : Symbol(E5.b, Decl(enumConstantMembers.ts, 21, 14)) + + c = 1.0 / 0.0, +>c : Symbol(E5.c, Decl(enumConstantMembers.ts, 22, 16)) + + d = 0.0 / 0.0, +>d : Symbol(E5.d, Decl(enumConstantMembers.ts, 23, 18)) + + e = NaN, +>e : Symbol(E5.e, Decl(enumConstantMembers.ts, 24, 18)) +>NaN : Symbol(NaN, Decl(lib.d.ts, --, --)) + + f = Infinity, +>f : Symbol(E5.f, Decl(enumConstantMembers.ts, 25, 12)) +>Infinity : Symbol(Infinity, Decl(lib.d.ts, --, --)) + + g = -Infinity +>g : Symbol(E5.g, Decl(enumConstantMembers.ts, 26, 17)) +>Infinity : Symbol(Infinity, Decl(lib.d.ts, --, --)) +} + +const enum E6 { +>E6 : Symbol(E6, Decl(enumConstantMembers.ts, 28, 1)) + + a = 1 / 0, +>a : Symbol(E6.a, Decl(enumConstantMembers.ts, 30, 15)) + + b = 2 / 0.0, +>b : Symbol(E6.b, Decl(enumConstantMembers.ts, 31, 14)) + + c = 1.0 / 0.0, +>c : Symbol(E6.c, Decl(enumConstantMembers.ts, 32, 16)) + + d = 0.0 / 0.0, +>d : Symbol(E6.d, Decl(enumConstantMembers.ts, 33, 18)) + + e = NaN, +>e : Symbol(E6.e, Decl(enumConstantMembers.ts, 34, 18)) +>NaN : Symbol(NaN, Decl(lib.d.ts, --, --)) + + f = Infinity, +>f : Symbol(E6.f, Decl(enumConstantMembers.ts, 35, 12)) +>Infinity : Symbol(Infinity, Decl(lib.d.ts, --, --)) + + g = -Infinity +>g : Symbol(E6.g, Decl(enumConstantMembers.ts, 36, 17)) +>Infinity : Symbol(Infinity, Decl(lib.d.ts, --, --)) +} + diff --git a/tests/baselines/reference/enumConstantMembers.types b/tests/baselines/reference/enumConstantMembers.types index bfbf71a6c17..0015a6debb3 100644 --- a/tests/baselines/reference/enumConstantMembers.types +++ b/tests/baselines/reference/enumConstantMembers.types @@ -48,3 +48,86 @@ declare enum E4 { >c : E4.c >0.1 : 0.1 } + +enum E5 { +>E5 : E5 + + a = 1 / 0, +>a : E5 +>1 / 0 : number +>1 : 1 +>0 : 0 + + b = 2 / 0.0, +>b : E5 +>2 / 0.0 : number +>2 : 2 +>0.0 : 0 + + c = 1.0 / 0.0, +>c : E5 +>1.0 / 0.0 : number +>1.0 : 1 +>0.0 : 0 + + d = 0.0 / 0.0, +>d : E5 +>0.0 / 0.0 : number +>0.0 : 0 +>0.0 : 0 + + e = NaN, +>e : E5 +>NaN : number + + f = Infinity, +>f : E5 +>Infinity : number + + g = -Infinity +>g : E5 +>-Infinity : number +>Infinity : number +} + +const enum E6 { +>E6 : E6 + + a = 1 / 0, +>a : E6 +>1 / 0 : number +>1 : 1 +>0 : 0 + + b = 2 / 0.0, +>b : E6 +>2 / 0.0 : number +>2 : 2 +>0.0 : 0 + + c = 1.0 / 0.0, +>c : E6 +>1.0 / 0.0 : number +>1.0 : 1 +>0.0 : 0 + + d = 0.0 / 0.0, +>d : E6 +>0.0 / 0.0 : number +>0.0 : 0 +>0.0 : 0 + + e = NaN, +>e : E6 +>NaN : number + + f = Infinity, +>f : E6 +>Infinity : number + + g = -Infinity +>g : E6 +>-Infinity : number +>Infinity : number +} + diff --git a/tests/cases/conformance/enums/enumConstantMembers.ts b/tests/cases/conformance/enums/enumConstantMembers.ts index e56828db064..4970879b9c5 100644 --- a/tests/cases/conformance/enums/enumConstantMembers.ts +++ b/tests/cases/conformance/enums/enumConstantMembers.ts @@ -16,4 +16,24 @@ declare enum E4 { a = 1, b = -1, c = 0.1 // Not a constant -} \ No newline at end of file +} + +enum E5 { + a = 1 / 0, + b = 2 / 0.0, + c = 1.0 / 0.0, + d = 0.0 / 0.0, + e = NaN, + f = Infinity, + g = -Infinity +} + +const enum E6 { + a = 1 / 0, + b = 2 / 0.0, + c = 1.0 / 0.0, + d = 0.0 / 0.0, + e = NaN, + f = Infinity, + g = -Infinity +} From 45e6df955e6584db10f113c08ff596380006d024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E7=92=90?= Date: Fri, 4 May 2018 10:33:12 +0800 Subject: [PATCH 02/11] only disallow infinite or nan in const enum --- src/compiler/checker.ts | 2 +- .../reference/enumConstantMembers.errors.txt | 23 +------------------ 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ff8be6a0ba8..5e799376e37 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -24311,7 +24311,7 @@ namespace ts { const initializer = member.initializer; const value = enumKind === EnumKind.Literal && !isLiteralEnumMember(member) ? undefined : evaluate(initializer); if (value !== undefined) { - if (typeof value === "number" && !isFinite(value)) { + if (isConstEnum && typeof value === "number" && !isFinite(value)) { error(initializer, isNaN(value) ? Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN : Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value); diff --git a/tests/baselines/reference/enumConstantMembers.errors.txt b/tests/baselines/reference/enumConstantMembers.errors.txt index 16bfa92a761..446f09040f9 100644 --- a/tests/baselines/reference/enumConstantMembers.errors.txt +++ b/tests/baselines/reference/enumConstantMembers.errors.txt @@ -1,10 +1,3 @@ -tests/cases/conformance/enums/enumConstantMembers.ts(22,9): error TS2477: 'const' enum member initializer was evaluated to a non-finite value. -tests/cases/conformance/enums/enumConstantMembers.ts(23,9): error TS2477: 'const' enum member initializer was evaluated to a non-finite value. -tests/cases/conformance/enums/enumConstantMembers.ts(24,9): error TS2477: 'const' enum member initializer was evaluated to a non-finite value. -tests/cases/conformance/enums/enumConstantMembers.ts(25,9): error TS2478: 'const' enum member initializer was evaluated to disallowed value 'NaN'. -tests/cases/conformance/enums/enumConstantMembers.ts(26,9): error TS2478: 'const' enum member initializer was evaluated to disallowed value 'NaN'. -tests/cases/conformance/enums/enumConstantMembers.ts(27,9): error TS2477: 'const' enum member initializer was evaluated to a non-finite value. -tests/cases/conformance/enums/enumConstantMembers.ts(28,9): error TS2477: 'const' enum member initializer was evaluated to a non-finite value. tests/cases/conformance/enums/enumConstantMembers.ts(32,9): error TS2477: 'const' enum member initializer was evaluated to a non-finite value. tests/cases/conformance/enums/enumConstantMembers.ts(33,9): error TS2477: 'const' enum member initializer was evaluated to a non-finite value. tests/cases/conformance/enums/enumConstantMembers.ts(34,9): error TS2477: 'const' enum member initializer was evaluated to a non-finite value. @@ -14,7 +7,7 @@ tests/cases/conformance/enums/enumConstantMembers.ts(37,9): error TS2477: 'const tests/cases/conformance/enums/enumConstantMembers.ts(38,9): error TS2477: 'const' enum member initializer was evaluated to a non-finite value. -==== tests/cases/conformance/enums/enumConstantMembers.ts (14 errors) ==== +==== tests/cases/conformance/enums/enumConstantMembers.ts (7 errors) ==== // Constant members allow negatives, but not decimals. Also hex literals are allowed enum E1 { a = 1, @@ -37,26 +30,12 @@ tests/cases/conformance/enums/enumConstantMembers.ts(38,9): error TS2477: 'const enum E5 { a = 1 / 0, - ~~~~~ -!!! error TS2477: 'const' enum member initializer was evaluated to a non-finite value. b = 2 / 0.0, - ~~~~~~~ -!!! error TS2477: 'const' enum member initializer was evaluated to a non-finite value. c = 1.0 / 0.0, - ~~~~~~~~~ -!!! error TS2477: 'const' enum member initializer was evaluated to a non-finite value. d = 0.0 / 0.0, - ~~~~~~~~~ -!!! error TS2478: 'const' enum member initializer was evaluated to disallowed value 'NaN'. e = NaN, - ~~~ -!!! error TS2478: 'const' enum member initializer was evaluated to disallowed value 'NaN'. f = Infinity, - ~~~~~~~~ -!!! error TS2477: 'const' enum member initializer was evaluated to a non-finite value. g = -Infinity - ~~~~~~~~~ -!!! error TS2477: 'const' enum member initializer was evaluated to a non-finite value. } const enum E6 { From 46eaf62abb99800a8f9d8ce326852c13fa51e0ba Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Tue, 22 May 2018 12:20:34 -0700 Subject: [PATCH 03/11] getEditsForFileRename: Handle old file still being present --- src/harness/tsconfig.json | 1 + src/server/tsconfig.json | 1 + src/server/tsconfig.library.json | 1 + src/services/getEditsForFileRename.ts | 9 ++---- src/services/tsconfig.json | 1 + .../cases/fourslash/getEditsForFileRename.ts | 2 ++ ...tEditsForFileRename_oldFileStillPresent.ts | 32 +++++++++++++++++++ 7 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 tests/cases/fourslash/getEditsForFileRename_oldFileStillPresent.ts diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index 359701a02cb..ba068cbc85a 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -63,6 +63,7 @@ "../services/documentRegistry.ts", "../services/importTracker.ts", "../services/findAllReferences.ts", + "../services/getEditsForFileRename.ts", "../services/goToDefinition.ts", "../services/jsDoc.ts", "../services/semver.ts", diff --git a/src/server/tsconfig.json b/src/server/tsconfig.json index 8ae6974baf0..f8ea769d92c 100644 --- a/src/server/tsconfig.json +++ b/src/server/tsconfig.json @@ -59,6 +59,7 @@ "../services/documentRegistry.ts", "../services/importTracker.ts", "../services/findAllReferences.ts", + "../services/getEditsForFileRename.ts", "../services/goToDefinition.ts", "../services/jsDoc.ts", "../services/semver.ts", diff --git a/src/server/tsconfig.library.json b/src/server/tsconfig.library.json index 922af11e879..8ba0a6edf62 100644 --- a/src/server/tsconfig.library.json +++ b/src/server/tsconfig.library.json @@ -65,6 +65,7 @@ "../services/documentRegistry.ts", "../services/importTracker.ts", "../services/findAllReferences.ts", + "../services/getEditsForFileRename.ts", "../services/goToDefinition.ts", "../services/jsDoc.ts", "../services/semver.ts", diff --git a/src/services/getEditsForFileRename.ts b/src/services/getEditsForFileRename.ts index d05d22a98d9..a1c74380f5e 100644 --- a/src/services/getEditsForFileRename.ts +++ b/src/services/getEditsForFileRename.ts @@ -31,23 +31,20 @@ namespace ts { } function getImportsToUpdate(program: Program, oldFilePath: string, host: LanguageServiceHost): ReadonlyArray { - const checker = program.getTypeChecker(); const result: ToUpdate[] = []; for (const sourceFile of program.getSourceFiles()) { for (const ref of sourceFile.referencedFiles) { - if (!program.getSourceFileFromReference(sourceFile, ref) && resolveTripleslashReference(ref.fileName, sourceFile.fileName) === oldFilePath) { + if (resolveTripleslashReference(ref.fileName, sourceFile.fileName) === oldFilePath) { result.push({ sourceFile, toUpdate: ref }); } } for (const importStringLiteral of sourceFile.imports) { - // If it resolved to something already, ignore. - if (checker.getSymbolAtLocation(importStringLiteral)) continue; - const resolved = host.resolveModuleNames ? host.getResolvedModuleWithFailedLookupLocationsFromCache && host.getResolvedModuleWithFailedLookupLocationsFromCache(importStringLiteral.text, sourceFile.fileName) : program.getResolvedModuleWithFailedLookupLocationsFromCache(importStringLiteral.text, sourceFile.fileName); - if (resolved && contains(resolved.failedLookupLocations, oldFilePath)) { + // We may or may not have picked up on the file being renamed, so maybe successfully resolved to oldFilePath, or maybe that's in failedLookupLocations + if (resolved && contains([resolved.resolvedModule && resolved.resolvedModule.resolvedFileName, ...resolved.failedLookupLocations], oldFilePath)) { result.push({ sourceFile, toUpdate: importStringLiteral }); } } diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 7e1ccc9c3af..05d4831b08f 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -56,6 +56,7 @@ "documentRegistry.ts", "importTracker.ts", "findAllReferences.ts", + "getEditsForFileRename.ts", "goToDefinition.ts", "jsDoc.ts", "semver.ts", diff --git a/tests/cases/fourslash/getEditsForFileRename.ts b/tests/cases/fourslash/getEditsForFileRename.ts index f2694818d1d..8e53883b37e 100644 --- a/tests/cases/fourslash/getEditsForFileRename.ts +++ b/tests/cases/fourslash/getEditsForFileRename.ts @@ -1,5 +1,7 @@ /// +// See also `getEditsForFileRename_oldFileStillPresent.ts` + // @Filename: /a.ts /////// ////import old from "./src/old"; diff --git a/tests/cases/fourslash/getEditsForFileRename_oldFileStillPresent.ts b/tests/cases/fourslash/getEditsForFileRename_oldFileStillPresent.ts new file mode 100644 index 00000000000..c4ebf341a5e --- /dev/null +++ b/tests/cases/fourslash/getEditsForFileRename_oldFileStillPresent.ts @@ -0,0 +1,32 @@ +/// + +// Same test as `getEditsForFileRename.ts`, but with the old file not yet renamed. + +// @Filename: /src/old.ts +////stuff + +// @Filename: /a.ts +/////// +////import old from "./src/old"; + +// @Filename: /src/a.ts +/////// +////import old from "./old"; + +// @Filename: /src/foo/a.ts +/////// +////import old from "../old"; + +// @Filename: /tsconfig.json +////{ "files": ["/a.ts", "/src/a.ts", "/src/foo/a.ts", "/src/old.ts"] } + +verify.getEditsForFileRename({ + oldPath: "/src/old.ts", + newPath: "/src/new.ts", + newFileContents: { + "/a.ts": '/// \nimport old from "./src/new";', + "/src/a.ts": '/// \nimport old from "./new";', + "/src/foo/a.ts": '/// \nimport old from "../new";', + "/tsconfig.json": '{ "files": ["/a.ts", "/src/a.ts", "/src/foo/a.ts", "/src/new.ts"] }', + }, +}); From 900e269872d566422a63440e05205b0e620925ff Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Tue, 22 May 2018 13:39:30 -0700 Subject: [PATCH 04/11] Don't need to check failedLookupLocations if resolved successfully --- src/services/getEditsForFileRename.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/getEditsForFileRename.ts b/src/services/getEditsForFileRename.ts index a1c74380f5e..b35e73a5ce0 100644 --- a/src/services/getEditsForFileRename.ts +++ b/src/services/getEditsForFileRename.ts @@ -44,7 +44,7 @@ namespace ts { ? host.getResolvedModuleWithFailedLookupLocationsFromCache && host.getResolvedModuleWithFailedLookupLocationsFromCache(importStringLiteral.text, sourceFile.fileName) : program.getResolvedModuleWithFailedLookupLocationsFromCache(importStringLiteral.text, sourceFile.fileName); // We may or may not have picked up on the file being renamed, so maybe successfully resolved to oldFilePath, or maybe that's in failedLookupLocations - if (resolved && contains([resolved.resolvedModule && resolved.resolvedModule.resolvedFileName, ...resolved.failedLookupLocations], oldFilePath)) { + if (resolved && contains(resolved.resolvedModule ? [resolved.resolvedModule.resolvedFileName] : resolved.failedLookupLocations, oldFilePath)) { result.push({ sourceFile, toUpdate: importStringLiteral }); } } From a0d4e4f059bfbca3f134f1167a9f61c2dca8eb42 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 22 May 2018 17:10:56 -0700 Subject: [PATCH 05/11] Fix getter of local symbol for export= when it is json module Fixes #24341 --- src/services/importTracker.ts | 3 +++ .../fourslash/findAllReferencesOfJsonModule.ts | 14 ++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 tests/cases/fourslash/findAllReferencesOfJsonModule.ts diff --git a/src/services/importTracker.ts b/src/services/importTracker.ts index 75b96c48caf..f53b7ffad49 100644 --- a/src/services/importTracker.ts +++ b/src/services/importTracker.ts @@ -579,6 +579,9 @@ namespace ts.FindAllReferences { else if (isBinaryExpression(decl)) { // `module.exports = class {}` return Debug.assertDefined(decl.right.symbol); } + else if (isSourceFile(decl)) { // json module + return Debug.assertDefined(decl.symbol); + } return Debug.fail(); } diff --git a/tests/cases/fourslash/findAllReferencesOfJsonModule.ts b/tests/cases/fourslash/findAllReferencesOfJsonModule.ts new file mode 100644 index 00000000000..36f700b09e6 --- /dev/null +++ b/tests/cases/fourslash/findAllReferencesOfJsonModule.ts @@ -0,0 +1,14 @@ +/// + +// @resolveJsonModule: true +// @module: commonjs +// @esModuleInterop: true + +// @Filename: /foo.ts +////import [|{| "isWriteAccess": true, "isDefinition": true |}settings|] from "./settings.json"; +////[|settings|]; + +// @Filename: /settings.json +//// {} + +verify.singleReferenceGroup("import settings"); From fb4caadaec42f26034c4049d87c020adb766d8b5 Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 23 May 2018 08:28:09 -0700 Subject: [PATCH 06/11] Support signature help for partially-filled-in type arguments `f<` (#24138) * Support signature help for partially-filled-in type arguments `f<` * Use `isPossiblyTypeArgumentPosition` and support new expressions --- src/services/completions.ts | 10 +- src/services/signatureHelp.ts | 155 ++++++++++-------- src/services/utilities.ts | 29 ++-- .../fourslash/signatureHelpTypeArguments.ts | 62 +++++++ 4 files changed, 174 insertions(+), 82 deletions(-) create mode 100644 tests/cases/fourslash/signatureHelpTypeArguments.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index e7039806762..56833298a64 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -422,7 +422,7 @@ namespace ts.Completions { case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: if (!isRequireCall(node.parent, /*checkArgumentIsStringLiteralLike*/ false) && !isImportCall(node.parent)) { - const argumentInfo = SignatureHelp.getImmediatelyContainingArgumentInfo(node, position, sourceFile); + const argumentInfo = SignatureHelp.getArgumentInfoForCompletions(node, position, sourceFile); // Get string literal completions from specialized signatures of the target // i.e. declare function f(a: 'A'); // f("/*completion position*/") @@ -452,7 +452,7 @@ namespace ts.Completions { } } - function getStringLiteralCompletionsFromSignature(argumentInfo: SignatureHelp.ArgumentListInfo, checker: TypeChecker): StringLiteralCompletionsFromTypes { + function getStringLiteralCompletionsFromSignature(argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker): StringLiteralCompletionsFromTypes { let isNewIdentifier = false; const uniques = createMap(); @@ -460,7 +460,7 @@ namespace ts.Completions { checker.getResolvedSignature(argumentInfo.invocation, candidates, argumentInfo.argumentCount); const types = flatMap(candidates, candidate => { if (!candidate.hasRestParameter && argumentInfo.argumentCount > candidate.parameters.length) return; - const type = checker.getParameterType(candidate, argumentInfo.argumentIndex!); // TODO: GH#18217 + const type = checker.getParameterType(candidate, argumentInfo.argumentIndex); isNewIdentifier = isNewIdentifier || !!(type.flags & TypeFlags.String); return getStringLiteralTypes(type, checker, uniques); }); @@ -720,10 +720,10 @@ namespace ts.Completions { case SyntaxKind.OpenBraceToken: return isJsxExpression(parent) && parent.parent.kind !== SyntaxKind.JsxElement ? checker.getContextualTypeForJsxAttribute(parent.parent) : undefined; default: - const argInfo = SignatureHelp.getImmediatelyContainingArgumentInfo(currentToken, position, sourceFile); + const argInfo = SignatureHelp.getArgumentInfoForCompletions(currentToken, position, sourceFile); return argInfo // At `,`, treat this as the next argument after the comma. - ? checker.getContextualTypeForArgumentAtIndex(argInfo.invocation, argInfo.argumentIndex! + (currentToken.kind === SyntaxKind.CommaToken ? 1 : 0)) // TODO: GH#18217 + ? checker.getContextualTypeForArgumentAtIndex(argInfo.invocation, argInfo.argumentIndex + (currentToken.kind === SyntaxKind.CommaToken ? 1 : 0)) : isEqualityOperatorKind(currentToken.kind) && isBinaryExpression(parent) && isEqualityOperatorKind(parent.operatorToken.kind) // completion at `x ===/**/` should be for the right side ? checker.getTypeAtLocation(parent.left) diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index 61330757f08..5fcf3e4866b 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -1,17 +1,20 @@ /* @internal */ namespace ts.SignatureHelp { - export const enum ArgumentListKind { + const enum ArgumentListKind { TypeArguments, CallArguments, TaggedTemplateArguments, JSXAttributesArguments } - export interface ArgumentListInfo { + const enum InvocationKind { Call, TypeArgs } + type Invocation = { kind: InvocationKind.Call, node: CallLikeExpression } | { kind: InvocationKind.TypeArgs, called: Expression }; + + interface ArgumentListInfo { kind: ArgumentListKind; - invocation: CallLikeExpression; + invocation: Invocation; argumentsSpan: TextSpan; - argumentIndex?: number; + argumentIndex: number; /** argumentCount is the *apparent* number of arguments. */ argumentCount: number; } @@ -32,32 +35,39 @@ namespace ts.SignatureHelp { cancellationToken.throwIfCancellationRequested(); // Semantic filtering of signature help - const call = argumentInfo.invocation; - const candidates: Signature[] = []; - const resolvedSignature = typeChecker.getResolvedSignature(call, candidates, argumentInfo.argumentCount); + const candidateInfo = getCandidateInfo(argumentInfo, typeChecker); cancellationToken.throwIfCancellationRequested(); - if (!candidates.length) { + if (!candidateInfo) { // We didn't have any sig help items produced by the TS compiler. If this is a JS // file, then see if we can figure out anything better. if (isSourceFileJavaScript(sourceFile)) { return createJavaScriptSignatureHelpItems(argumentInfo, program, cancellationToken); } - return undefined; } - return typeChecker.runWithCancellationToken(cancellationToken, typeChecker => createSignatureHelpItems(candidates, resolvedSignature!, argumentInfo, typeChecker)); + return typeChecker.runWithCancellationToken(cancellationToken, typeChecker => createSignatureHelpItems(candidateInfo.candidates, candidateInfo.resolvedSignature, argumentInfo, sourceFile, typeChecker)); + } + + function getCandidateInfo(argumentInfo: ArgumentListInfo, checker: TypeChecker): { readonly candidates: ReadonlyArray, readonly resolvedSignature: Signature } | undefined { + const { invocation } = argumentInfo; + if (invocation.kind === InvocationKind.Call) { + const candidates: Signature[] = []; + const resolvedSignature = checker.getResolvedSignature(invocation.node, candidates, argumentInfo.argumentCount)!; // TODO: GH#18217 + return candidates.length === 0 ? undefined : { candidates, resolvedSignature }; + } + else { + const type = checker.getTypeAtLocation(invocation.called)!; // TODO: GH#18217 + const signatures = isNewExpression(invocation.called.parent) ? type.getConstructSignatures() : type.getCallSignatures(); + const candidates = signatures.filter(candidate => !!candidate.typeParameters && candidate.typeParameters.length >= argumentInfo.argumentCount); + return candidates.length === 0 ? undefined : { candidates, resolvedSignature: first(candidates) }; + } } function createJavaScriptSignatureHelpItems(argumentInfo: ArgumentListInfo, program: Program, cancellationToken: CancellationToken): SignatureHelpItems | undefined { - if (argumentInfo.invocation.kind !== SyntaxKind.CallExpression) { - return undefined; - } - // See if we can find some symbol with the call expression name that has call signatures. - const callExpression = argumentInfo.invocation; - const expression = callExpression.expression; + const expression = getExpressionFromInvocation(argumentInfo.invocation); const name = isIdentifier(expression) ? expression : isPropertyAccessExpression(expression) ? expression.name : undefined; if (!name || !name.escapedText) { return undefined; @@ -76,7 +86,7 @@ namespace ts.SignatureHelp { if (type) { const callSignatures = type.getCallSignatures(); if (callSignatures && callSignatures.length) { - return typeChecker.runWithCancellationToken(cancellationToken, typeChecker => createSignatureHelpItems(callSignatures, callSignatures[0], argumentInfo, typeChecker)); + return typeChecker.runWithCancellationToken(cancellationToken, typeChecker => createSignatureHelpItems(callSignatures, callSignatures[0], argumentInfo, sourceFile, typeChecker)); } } } @@ -85,13 +95,25 @@ namespace ts.SignatureHelp { } } + export interface ArgumentInfoForCompletions { + readonly invocation: CallLikeExpression; + readonly argumentIndex: number; + readonly argumentCount: number; + } + export function getArgumentInfoForCompletions(node: Node, position: number, sourceFile: SourceFile): ArgumentInfoForCompletions | undefined { + const info = getImmediatelyContainingArgumentInfo(node, position, sourceFile); + return !info || info.kind === ArgumentListKind.TypeArguments || info.invocation.kind === InvocationKind.TypeArgs ? undefined + : { invocation: info.invocation.node, argumentCount: info.argumentCount, argumentIndex: info.argumentIndex }; + } + /** * Returns relevant information for the argument list and the current argument if we are * in the argument of an invocation; returns undefined otherwise. */ - export function getImmediatelyContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo | undefined { + function getImmediatelyContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo | undefined { const { parent } = node; if (isCallOrNewExpression(parent)) { + const invocation = parent; let list: Node | undefined; let argumentIndex: number; @@ -134,56 +156,63 @@ namespace ts.SignatureHelp { Debug.assertLessThan(argumentIndex, argumentCount); } const argumentsSpan = getApplicableSpanForArguments(list, sourceFile); - return { kind, invocation: parent, argumentsSpan, argumentIndex, argumentCount }; + return { kind, invocation: { kind: InvocationKind.Call, node: invocation }, argumentsSpan, argumentIndex, argumentCount }; } - else if (node.kind === SyntaxKind.NoSubstitutionTemplateLiteral && parent.kind === SyntaxKind.TaggedTemplateExpression) { + else if (isNoSubstitutionTemplateLiteral(node) && isTaggedTemplateExpression(parent)) { // Check if we're actually inside the template; // otherwise we'll fall out and return undefined. - if (isInsideTemplateLiteral(node, position)) { - return getArgumentListInfoForTemplate(node.parent, /*argumentIndex*/ 0, sourceFile); + if (isInsideTemplateLiteral(node, position)) { + return getArgumentListInfoForTemplate(parent, /*argumentIndex*/ 0, sourceFile); } } - else if (node.kind === SyntaxKind.TemplateHead && parent.parent.kind === SyntaxKind.TaggedTemplateExpression) { - const templateExpression = node.parent; + else if (isTemplateHead(node) && parent.parent.kind === SyntaxKind.TaggedTemplateExpression) { + const templateExpression = parent; const tagExpression = templateExpression.parent; Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression); - const argumentIndex = isInsideTemplateLiteral(node, position) ? 0 : 1; + const argumentIndex = isInsideTemplateLiteral(node, position) ? 0 : 1; return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile); } - else if (parent.kind === SyntaxKind.TemplateSpan && parent.parent.parent.kind === SyntaxKind.TaggedTemplateExpression) { - const templateSpan = node.parent; - const templateExpression = templateSpan.parent; - const tagExpression = templateExpression.parent; - Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression); + else if (isTemplateSpan(parent) && isTaggedTemplateExpression(parent.parent.parent)) { + const templateSpan = parent; + const tagExpression = parent.parent.parent; // If we're just after a template tail, don't show signature help. if (node.kind === SyntaxKind.TemplateTail && !isInsideTemplateLiteral(node, position)) { return undefined; } - const spanIndex = templateExpression.templateSpans.indexOf(templateSpan); + const spanIndex = templateSpan.parent.templateSpans.indexOf(templateSpan); const argumentIndex = getArgumentIndexForTemplatePiece(spanIndex, node, position); return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile); } - else if (node.parent && isJsxOpeningLikeElement(node.parent)) { + else if (isJsxOpeningLikeElement(parent)) { // Provide a signature help for JSX opening element or JSX self-closing element. // This is not guarantee that JSX tag-name is resolved into stateless function component. (that is done in "getSignatureHelpItems") // i.e // export function MainButton(props: ButtonProps, context: any): JSX.Element { ... } // n.parent.end) { - Debug.fail("Node of kind " + n.kind + " is not a subspan of its parent of kind " + n.parent.kind); - } - + Debug.assert(rangeContainsRange(n.parent, n), "Not a subspan", () => `Child: ${Debug.showSyntaxKind(n)}, parent: ${Debug.showSyntaxKind(n.parent)}`); const argumentInfo = getImmediatelyContainingArgumentInfo(n, position, sourceFile); if (argumentInfo) { return argumentInfo; } - - - // TODO: Handle generic call with incomplete syntax } return undefined; } @@ -343,16 +362,20 @@ namespace ts.SignatureHelp { return children[indexOfOpenerToken + 1]; } + function getExpressionFromInvocation(invocation: Invocation): Expression { + return invocation.kind === InvocationKind.Call ? getInvokedExpression(invocation.node) : invocation.called; + } + const signatureHelpNodeBuilderFlags = NodeBuilderFlags.OmitParameterModifiers | NodeBuilderFlags.IgnoreErrors; - function createSignatureHelpItems(candidates: Signature[], resolvedSignature: Signature, argumentListInfo: ArgumentListInfo, typeChecker: TypeChecker): SignatureHelpItems { + function createSignatureHelpItems(candidates: ReadonlyArray, resolvedSignature: Signature, argumentListInfo: ArgumentListInfo, sourceFile: SourceFile, typeChecker: TypeChecker): SignatureHelpItems { const { argumentCount, argumentsSpan: applicableSpan, invocation, argumentIndex } = argumentListInfo; const isTypeParameterList = argumentListInfo.kind === ArgumentListKind.TypeArguments; - const callTarget = getInvokedExpression(invocation); - const callTargetSymbol = typeChecker.getSymbolAtLocation(callTarget); + const enclosingDeclaration = invocation.kind === InvocationKind.Call ? invocation.node : invocation.called; + const callTargetSymbol = typeChecker.getSymbolAtLocation(getExpressionFromInvocation(invocation)); const callTargetDisplayParts = callTargetSymbol && symbolToDisplayParts(typeChecker, callTargetSymbol, /*enclosingDeclaration*/ undefined, /*meaning*/ undefined); const printer = createPrinter({ removeComments: true }); - const items: SignatureHelpItem[] = map(candidates, candidateSignature => { + const items = candidates.map(candidateSignature => { let signatureHelpParameters: SignatureHelpParameter[]; const prefixDisplayParts: SymbolDisplayPart[] = []; const suffixDisplayParts: SymbolDisplayPart[] = []; @@ -369,9 +392,9 @@ namespace ts.SignatureHelp { signatureHelpParameters = typeParameters && typeParameters.length > 0 ? map(typeParameters, createSignatureHelpParameterForTypeParameter) : emptyArray; suffixDisplayParts.push(punctuationPart(SyntaxKind.GreaterThanToken)); const parameterParts = mapToDisplayParts(writer => { - const thisParameter = candidateSignature.thisParameter ? [typeChecker.symbolToParameterDeclaration(candidateSignature.thisParameter, invocation, signatureHelpNodeBuilderFlags)!] : []; - const params = createNodeArray([...thisParameter, ...map(candidateSignature.parameters, param => typeChecker.symbolToParameterDeclaration(param, invocation, signatureHelpNodeBuilderFlags)!)]); - printer.writeList(ListFormat.CallExpressionArguments, params, getSourceFileOfNode(getParseTreeNode(invocation)), writer); + const thisParameter = candidateSignature.thisParameter ? [typeChecker.symbolToParameterDeclaration(candidateSignature.thisParameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!] : []; + const params = createNodeArray([...thisParameter, ...candidateSignature.parameters.map(param => typeChecker.symbolToParameterDeclaration(param, enclosingDeclaration, signatureHelpNodeBuilderFlags)!)]); + printer.writeList(ListFormat.CallExpressionArguments, params, sourceFile, writer); }); addRange(suffixDisplayParts, parameterParts); } @@ -379,8 +402,8 @@ namespace ts.SignatureHelp { isVariadic = candidateSignature.hasRestParameter; const typeParameterParts = mapToDisplayParts(writer => { if (candidateSignature.typeParameters && candidateSignature.typeParameters.length) { - const args = createNodeArray(map(candidateSignature.typeParameters, p => typeChecker.typeParameterToDeclaration(p, invocation)!)); - printer.writeList(ListFormat.TypeParameters, args, getSourceFileOfNode(getParseTreeNode(invocation)), writer); + const args = createNodeArray(candidateSignature.typeParameters.map(p => typeChecker.typeParameterToDeclaration(p, enclosingDeclaration)!)); + printer.writeList(ListFormat.TypeParameters, args, sourceFile, writer); } }); addRange(prefixDisplayParts, typeParameterParts); @@ -395,10 +418,10 @@ namespace ts.SignatureHelp { writer.writeSpace(" "); const predicate = typeChecker.getTypePredicateOfSignature(candidateSignature); if (predicate) { - typeChecker.writeTypePredicate(predicate, invocation, /*flags*/ undefined, writer); + typeChecker.writeTypePredicate(predicate, enclosingDeclaration, /*flags*/ undefined, writer); } else { - typeChecker.writeType(typeChecker.getReturnTypeOfSignature(candidateSignature), invocation, /*flags*/ undefined, writer); + typeChecker.writeType(typeChecker.getReturnTypeOfSignature(candidateSignature), enclosingDeclaration, /*flags*/ undefined, writer); } }); addRange(suffixDisplayParts, returnTypeParts); @@ -415,18 +438,18 @@ namespace ts.SignatureHelp { }); if (argumentIndex !== 0) { - Debug.assertLessThan(argumentIndex!, argumentCount); // TODO: GH#18217 + Debug.assertLessThan(argumentIndex, argumentCount); } const selectedItemIndex = candidates.indexOf(resolvedSignature); Debug.assert(selectedItemIndex !== -1); // If candidates is non-empty it should always include bestSignature. We check for an empty candidates before calling this function. - return { items, applicableSpan, selectedItemIndex, argumentIndex: argumentIndex!, argumentCount }; // TODO: GH#18217 + return { items, applicableSpan, selectedItemIndex, argumentIndex, argumentCount }; function createSignatureHelpParameterForParameter(parameter: Symbol): SignatureHelpParameter { const displayParts = mapToDisplayParts(writer => { - const param = typeChecker.symbolToParameterDeclaration(parameter, invocation, signatureHelpNodeBuilderFlags)!; - printer.writeNode(EmitHint.Unspecified, param, getSourceFileOfNode(getParseTreeNode(invocation)), writer); + const param = typeChecker.symbolToParameterDeclaration(parameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!; + printer.writeNode(EmitHint.Unspecified, param, sourceFile, writer); }); return { @@ -439,8 +462,8 @@ namespace ts.SignatureHelp { function createSignatureHelpParameterForTypeParameter(typeParameter: TypeParameter): SignatureHelpParameter { const displayParts = mapToDisplayParts(writer => { - const param = typeChecker.typeParameterToDeclaration(typeParameter, invocation)!; - printer.writeNode(EmitHint.Unspecified, param, getSourceFileOfNode(getParseTreeNode(invocation)), writer); + const param = typeChecker.typeParameterToDeclaration(typeParameter, enclosingDeclaration)!; + printer.writeNode(EmitHint.Unspecified, param, sourceFile, writer); }); return { diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 54f88646071..e6ebdb78447 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -920,7 +920,11 @@ namespace ts { } } - export function isPossiblyTypeArgumentPosition(tokenIn: Node, sourceFile: SourceFile): boolean { + export interface PossibleTypeArgumentInfo { + readonly called: Identifier; + readonly nTypeArguments: number; + } + export function isPossiblyTypeArgumentPosition(tokenIn: Node, sourceFile: SourceFile): PossibleTypeArgumentInfo | undefined { let token: Node | undefined = tokenIn; // This function determines if the node could be type argument position // Since during editing, when type argument list is not complete, @@ -928,15 +932,15 @@ namespace ts { // scanning of the previous identifier followed by "<" before current node would give us better result // Note that we also balance out the already provided type arguments, arrays, object literals while doing so let remainingLessThanTokens = 0; + let nTypeArguments = 0; while (token) { switch (token.kind) { case SyntaxKind.LessThanToken: // Found the beginning of the generic argument expression token = findPrecedingToken(token.getFullStart(), sourceFile); - if (!token) return false; - const tokenIsIdentifier = isIdentifier(token); - if (!remainingLessThanTokens || !tokenIsIdentifier) { - return tokenIsIdentifier; + if (!token || !isIdentifier(token)) return undefined; + if (!remainingLessThanTokens) { + return { called: token, nTypeArguments }; } remainingLessThanTokens--; break; @@ -957,25 +961,28 @@ namespace ts { // This can be object type, skip until we find the matching open brace token // Skip until the matching open brace token token = findPrecedingMatchingToken(token, SyntaxKind.OpenBraceToken, sourceFile); - if (!token) return false; + if (!token) return undefined; break; case SyntaxKind.CloseParenToken: // This can be object type, skip until we find the matching open brace token // Skip until the matching open brace token token = findPrecedingMatchingToken(token, SyntaxKind.OpenParenToken, sourceFile); - if (!token) return false; + if (!token) return undefined; break; case SyntaxKind.CloseBracketToken: // This can be object type, skip until we find the matching open brace token // Skip until the matching open brace token token = findPrecedingMatchingToken(token, SyntaxKind.OpenBracketToken, sourceFile); - if (!token) return false; + if (!token) return undefined; break; // Valid tokens in a type name. Skip. case SyntaxKind.CommaToken: + nTypeArguments++; + break; + case SyntaxKind.EqualsGreaterThanToken: case SyntaxKind.Identifier: @@ -999,13 +1006,13 @@ namespace ts { } // Invalid token in type - return false; + return undefined; } token = findPrecedingToken(token.getFullStart(), sourceFile); } - return false; + return undefined; } /** @@ -1086,7 +1093,7 @@ namespace ts { return SyntaxKind.FirstPunctuation <= kind && kind <= SyntaxKind.LastPunctuation; } - export function isInsideTemplateLiteral(node: LiteralExpression, position: number) { + export function isInsideTemplateLiteral(node: LiteralExpression | TemplateHead, position: number) { return isTemplateLiteralKind(node.kind) && (node.getStart() < position && position < node.getEnd()) || (!!node.isUnterminated && position === node.getEnd()); } diff --git a/tests/cases/fourslash/signatureHelpTypeArguments.ts b/tests/cases/fourslash/signatureHelpTypeArguments.ts new file mode 100644 index 00000000000..d08e9fc5ec5 --- /dev/null +++ b/tests/cases/fourslash/signatureHelpTypeArguments.ts @@ -0,0 +1,62 @@ +/// + +////declare function f(a: number, b: string, c: boolean): void; // ignored, not generic +////declare function f(): void; +////declare function f(): void; +////declare function f(): void; +////f(): void; +//// new(): void; +//// new(): void; +////}; +////new C(): void", + parameterName: "T", + parameterSpan: "T extends number", + }, + { + marker: "f1", + overloadsCount: 2, + text: "f(): void", + parameterName: "U", + parameterSpan: "U", + }, + { + marker: "f2", + text: "f(): void", + parameterName: "V", + parameterSpan: "V extends string", + }, + + { + marker: "C0", + overloadsCount: 3, + text: "C(): void", + parameterName: "T", + parameterSpan: "T extends number", + }, + { + marker: "C1", + overloadsCount: 2, + text: "C(): void", + parameterName: "U", + parameterSpan: "U", + }, + { + marker: "C2", + text: "C(): void", + parameterName: "V", + parameterSpan: "V extends string", + }, +); From 5983c45e2430dfa8e345a987a1dd561f03532f37 Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 23 May 2018 09:36:17 -0700 Subject: [PATCH 07/11] Fix typo: seperate -> separate (#24338) * Fix typo: seperate -> separate * update tests --- src/compiler/checker.ts | 2 +- src/compiler/core.ts | 2 +- src/compiler/scanner.ts | 2 +- src/services/codefixes/importFixes.ts | 2 +- src/services/pathCompletions.ts | 6 +++--- ...rs.txt => parseCommaSeparatedNewlineNew.errors.txt} | 8 ++++---- .../reference/parseCommaSeparatedNewlineNew.js | 7 +++++++ ...w.symbols => parseCommaSeparatedNewlineNew.symbols} | 2 +- .../reference/parseCommaSeparatedNewlineNew.types | 10 ++++++++++ ...txt => parseCommaSeparatedNewlineNumber.errors.txt} | 6 +++--- .../reference/parseCommaSeparatedNewlineNumber.js | 7 +++++++ ...ymbols => parseCommaSeparatedNewlineNumber.symbols} | 2 +- .../reference/parseCommaSeparatedNewlineNumber.types | 9 +++++++++ ...txt => parseCommaSeparatedNewlineString.errors.txt} | 6 +++--- .../reference/parseCommaSeparatedNewlineString.js | 7 +++++++ ...ymbols => parseCommaSeparatedNewlineString.symbols} | 2 +- .../reference/parseCommaSeparatedNewlineString.types | 9 +++++++++ .../reference/parseCommaSeperatedNewlineNew.js | 7 ------- .../reference/parseCommaSeperatedNewlineNew.types | 10 ---------- .../reference/parseCommaSeperatedNewlineNumber.js | 7 ------- .../reference/parseCommaSeperatedNewlineNumber.types | 9 --------- .../reference/parseCommaSeperatedNewlineString.js | 7 ------- .../reference/parseCommaSeperatedNewlineString.types | 9 --------- ...dNewlineNew.ts => parseCommaSeparatedNewlineNew.ts} | 0 ...neNumber.ts => parseCommaSeparatedNewlineNumber.ts} | 0 ...neString.ts => parseCommaSeparatedNewlineString.ts} | 0 26 files changed, 69 insertions(+), 69 deletions(-) rename tests/baselines/reference/{parseCommaSeperatedNewlineNew.errors.txt => parseCommaSeparatedNewlineNew.errors.txt} (56%) create mode 100644 tests/baselines/reference/parseCommaSeparatedNewlineNew.js rename tests/baselines/reference/{parseCommaSeperatedNewlineNew.symbols => parseCommaSeparatedNewlineNew.symbols} (54%) create mode 100644 tests/baselines/reference/parseCommaSeparatedNewlineNew.types rename tests/baselines/reference/{parseCommaSeperatedNewlineNumber.errors.txt => parseCommaSeparatedNewlineNumber.errors.txt} (57%) create mode 100644 tests/baselines/reference/parseCommaSeparatedNewlineNumber.js rename tests/baselines/reference/{parseCommaSeperatedNewlineNumber.symbols => parseCommaSeparatedNewlineNumber.symbols} (53%) create mode 100644 tests/baselines/reference/parseCommaSeparatedNewlineNumber.types rename tests/baselines/reference/{parseCommaSeperatedNewlineString.errors.txt => parseCommaSeparatedNewlineString.errors.txt} (57%) create mode 100644 tests/baselines/reference/parseCommaSeparatedNewlineString.js rename tests/baselines/reference/{parseCommaSeperatedNewlineString.symbols => parseCommaSeparatedNewlineString.symbols} (53%) create mode 100644 tests/baselines/reference/parseCommaSeparatedNewlineString.types delete mode 100644 tests/baselines/reference/parseCommaSeperatedNewlineNew.js delete mode 100644 tests/baselines/reference/parseCommaSeperatedNewlineNew.types delete mode 100644 tests/baselines/reference/parseCommaSeperatedNewlineNumber.js delete mode 100644 tests/baselines/reference/parseCommaSeperatedNewlineNumber.types delete mode 100644 tests/baselines/reference/parseCommaSeperatedNewlineString.js delete mode 100644 tests/baselines/reference/parseCommaSeperatedNewlineString.types rename tests/cases/compiler/{parseCommaSeperatedNewlineNew.ts => parseCommaSeparatedNewlineNew.ts} (100%) rename tests/cases/compiler/{parseCommaSeperatedNewlineNumber.ts => parseCommaSeparatedNewlineNumber.ts} (100%) rename tests/cases/compiler/{parseCommaSeperatedNewlineString.ts => parseCommaSeparatedNewlineString.ts} (100%) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 80168823b3c..ff55b99801e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -27061,7 +27061,7 @@ namespace ts { } } - // We do global augmentations seperately from module augmentations (and before creating global types) because they + // We do global augmentations separately from module augmentations (and before creating global types) because they // 1. Affect global types. We won't have the correct global types until global augmentations are merged. Also, // 2. Module augmentation instantiation requires creating the type of a module, which, in turn, can require // checking for an export or property on the module (if export=) which, in turn, can fall back to the diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 90572217d43..cf07ee26b8b 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2653,7 +2653,7 @@ namespace ts { /** * Matches any single directory segment unless it is the last segment and a .min.js file * Breakdown: - * [^./] # matches everything up to the first . character (excluding directory seperators) + * [^./] # matches everything up to the first . character (excluding directory separators) * (\\.(?!min\\.js$))? # matches . characters but not if they are part of the .min.js file extension */ singleAsteriskRegexFragment: "([^./]|(\\.(?!min\\.js$))?)*", diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 8963285dc7a..41b65c67864 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -1308,7 +1308,7 @@ namespace ts { let isPreviousTokenSeparator = false; while (true) { const ch = text.charCodeAt(pos); - // Numeric seperators are allowed anywhere within a numeric literal, except not at the beginning, or following another separator + // Numeric separators are allowed anywhere within a numeric literal, except not at the beginning, or following another separator if (ch === CharacterCodes._) { tokenFlags |= TokenFlags.ContainsSeparator; if (separatorAllowed) { diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index 01fbc26403f..cc526a53b14 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -211,7 +211,7 @@ namespace ts.codefix { // if this file doesn't have any import statements, insert an import statement and then insert a new line // between the only import statement and user code. Otherwise just insert the statement because chances - // are there are already a new line seperating code and import statements. + // are there are already a new line separating code and import statements. return createCodeAction(Diagnostics.Import_0_from_module_1, [symbolName, moduleSpecifierWithoutQuotes], changes); } diff --git a/src/services/pathCompletions.ts b/src/services/pathCompletions.ts index afa1d76bbac..06cc7a71821 100644 --- a/src/services/pathCompletions.ts +++ b/src/services/pathCompletions.ts @@ -297,8 +297,8 @@ namespace ts.Completions.PathCompletions { // after the last '/' that appears in the fragment because that's where the replacement span // starts if (fragmentDirectory !== undefined) { - const moduleNameWithSeperator = ensureTrailingDirectorySeparator(fragmentDirectory); - return nonRelativeModuleNames.map(nonRelativeModuleName => removePrefix(nonRelativeModuleName, moduleNameWithSeperator)); + const moduleNameWithSeparator = ensureTrailingDirectorySeparator(fragmentDirectory); + return nonRelativeModuleNames.map(nonRelativeModuleName => removePrefix(nonRelativeModuleName, moduleNameWithSeparator)); } return nonRelativeModuleNames; } @@ -410,7 +410,7 @@ namespace ts.Completions.PathCompletions { return result; } - // Replace everything after the last directory seperator that appears + // Replace everything after the last directory separator that appears function getDirectoryFragmentTextSpan(text: string, textStart: number): TextSpan | undefined { const index = Math.max(text.lastIndexOf(directorySeparator), text.lastIndexOf("\\")); const offset = index !== -1 ? index + 1 : 0; diff --git a/tests/baselines/reference/parseCommaSeperatedNewlineNew.errors.txt b/tests/baselines/reference/parseCommaSeparatedNewlineNew.errors.txt similarity index 56% rename from tests/baselines/reference/parseCommaSeperatedNewlineNew.errors.txt rename to tests/baselines/reference/parseCommaSeparatedNewlineNew.errors.txt index ff3ff1a033d..6b779e15dab 100644 --- a/tests/baselines/reference/parseCommaSeperatedNewlineNew.errors.txt +++ b/tests/baselines/reference/parseCommaSeparatedNewlineNew.errors.txt @@ -1,9 +1,9 @@ -tests/cases/compiler/parseCommaSeperatedNewlineNew.ts(1,2): error TS2304: Cannot find name 'a'. -tests/cases/compiler/parseCommaSeperatedNewlineNew.ts(1,2): error TS2695: Left side of comma operator is unused and has no side effects. -tests/cases/compiler/parseCommaSeperatedNewlineNew.ts(2,4): error TS1109: Expression expected. +tests/cases/compiler/parseCommaSeparatedNewlineNew.ts(1,2): error TS2304: Cannot find name 'a'. +tests/cases/compiler/parseCommaSeparatedNewlineNew.ts(1,2): error TS2695: Left side of comma operator is unused and has no side effects. +tests/cases/compiler/parseCommaSeparatedNewlineNew.ts(2,4): error TS1109: Expression expected. -==== tests/cases/compiler/parseCommaSeperatedNewlineNew.ts (3 errors) ==== +==== tests/cases/compiler/parseCommaSeparatedNewlineNew.ts (3 errors) ==== (a, ~ !!! error TS2304: Cannot find name 'a'. diff --git a/tests/baselines/reference/parseCommaSeparatedNewlineNew.js b/tests/baselines/reference/parseCommaSeparatedNewlineNew.js new file mode 100644 index 00000000000..04ef5d6e73f --- /dev/null +++ b/tests/baselines/reference/parseCommaSeparatedNewlineNew.js @@ -0,0 +1,7 @@ +//// [parseCommaSeparatedNewlineNew.ts] +(a, +new) + +//// [parseCommaSeparatedNewlineNew.js] +(a, + new ); diff --git a/tests/baselines/reference/parseCommaSeperatedNewlineNew.symbols b/tests/baselines/reference/parseCommaSeparatedNewlineNew.symbols similarity index 54% rename from tests/baselines/reference/parseCommaSeperatedNewlineNew.symbols rename to tests/baselines/reference/parseCommaSeparatedNewlineNew.symbols index 757444b907d..d912adbb5e1 100644 --- a/tests/baselines/reference/parseCommaSeperatedNewlineNew.symbols +++ b/tests/baselines/reference/parseCommaSeparatedNewlineNew.symbols @@ -1,4 +1,4 @@ -=== tests/cases/compiler/parseCommaSeperatedNewlineNew.ts === +=== tests/cases/compiler/parseCommaSeparatedNewlineNew.ts === (a, No type information for this code.new) No type information for this code. \ No newline at end of file diff --git a/tests/baselines/reference/parseCommaSeparatedNewlineNew.types b/tests/baselines/reference/parseCommaSeparatedNewlineNew.types new file mode 100644 index 00000000000..d787e889846 --- /dev/null +++ b/tests/baselines/reference/parseCommaSeparatedNewlineNew.types @@ -0,0 +1,10 @@ +=== tests/cases/compiler/parseCommaSeparatedNewlineNew.ts === +(a, +>(a,new) : any +>a,new : any +>a : any + +new) +>new : any +> : any + diff --git a/tests/baselines/reference/parseCommaSeperatedNewlineNumber.errors.txt b/tests/baselines/reference/parseCommaSeparatedNewlineNumber.errors.txt similarity index 57% rename from tests/baselines/reference/parseCommaSeperatedNewlineNumber.errors.txt rename to tests/baselines/reference/parseCommaSeparatedNewlineNumber.errors.txt index 73f087cc1e6..f059aaf633e 100644 --- a/tests/baselines/reference/parseCommaSeperatedNewlineNumber.errors.txt +++ b/tests/baselines/reference/parseCommaSeparatedNewlineNumber.errors.txt @@ -1,8 +1,8 @@ -tests/cases/compiler/parseCommaSeperatedNewlineNumber.ts(1,2): error TS2304: Cannot find name 'a'. -tests/cases/compiler/parseCommaSeperatedNewlineNumber.ts(1,2): error TS2695: Left side of comma operator is unused and has no side effects. +tests/cases/compiler/parseCommaSeparatedNewlineNumber.ts(1,2): error TS2304: Cannot find name 'a'. +tests/cases/compiler/parseCommaSeparatedNewlineNumber.ts(1,2): error TS2695: Left side of comma operator is unused and has no side effects. -==== tests/cases/compiler/parseCommaSeperatedNewlineNumber.ts (2 errors) ==== +==== tests/cases/compiler/parseCommaSeparatedNewlineNumber.ts (2 errors) ==== (a, ~ !!! error TS2304: Cannot find name 'a'. diff --git a/tests/baselines/reference/parseCommaSeparatedNewlineNumber.js b/tests/baselines/reference/parseCommaSeparatedNewlineNumber.js new file mode 100644 index 00000000000..3105c4258e6 --- /dev/null +++ b/tests/baselines/reference/parseCommaSeparatedNewlineNumber.js @@ -0,0 +1,7 @@ +//// [parseCommaSeparatedNewlineNumber.ts] +(a, +1) + +//// [parseCommaSeparatedNewlineNumber.js] +(a, + 1); diff --git a/tests/baselines/reference/parseCommaSeperatedNewlineNumber.symbols b/tests/baselines/reference/parseCommaSeparatedNewlineNumber.symbols similarity index 53% rename from tests/baselines/reference/parseCommaSeperatedNewlineNumber.symbols rename to tests/baselines/reference/parseCommaSeparatedNewlineNumber.symbols index 6fca007a89a..2775ffef173 100644 --- a/tests/baselines/reference/parseCommaSeperatedNewlineNumber.symbols +++ b/tests/baselines/reference/parseCommaSeparatedNewlineNumber.symbols @@ -1,4 +1,4 @@ -=== tests/cases/compiler/parseCommaSeperatedNewlineNumber.ts === +=== tests/cases/compiler/parseCommaSeparatedNewlineNumber.ts === (a, No type information for this code.1) No type information for this code. \ No newline at end of file diff --git a/tests/baselines/reference/parseCommaSeparatedNewlineNumber.types b/tests/baselines/reference/parseCommaSeparatedNewlineNumber.types new file mode 100644 index 00000000000..a6f107bbd4b --- /dev/null +++ b/tests/baselines/reference/parseCommaSeparatedNewlineNumber.types @@ -0,0 +1,9 @@ +=== tests/cases/compiler/parseCommaSeparatedNewlineNumber.ts === +(a, +>(a,1) : 1 +>a,1 : 1 +>a : any + +1) +>1 : 1 + diff --git a/tests/baselines/reference/parseCommaSeperatedNewlineString.errors.txt b/tests/baselines/reference/parseCommaSeparatedNewlineString.errors.txt similarity index 57% rename from tests/baselines/reference/parseCommaSeperatedNewlineString.errors.txt rename to tests/baselines/reference/parseCommaSeparatedNewlineString.errors.txt index 2c70bfbfc30..d8e7d5efc7a 100644 --- a/tests/baselines/reference/parseCommaSeperatedNewlineString.errors.txt +++ b/tests/baselines/reference/parseCommaSeparatedNewlineString.errors.txt @@ -1,8 +1,8 @@ -tests/cases/compiler/parseCommaSeperatedNewlineString.ts(1,2): error TS2304: Cannot find name 'a'. -tests/cases/compiler/parseCommaSeperatedNewlineString.ts(1,2): error TS2695: Left side of comma operator is unused and has no side effects. +tests/cases/compiler/parseCommaSeparatedNewlineString.ts(1,2): error TS2304: Cannot find name 'a'. +tests/cases/compiler/parseCommaSeparatedNewlineString.ts(1,2): error TS2695: Left side of comma operator is unused and has no side effects. -==== tests/cases/compiler/parseCommaSeperatedNewlineString.ts (2 errors) ==== +==== tests/cases/compiler/parseCommaSeparatedNewlineString.ts (2 errors) ==== (a, ~ !!! error TS2304: Cannot find name 'a'. diff --git a/tests/baselines/reference/parseCommaSeparatedNewlineString.js b/tests/baselines/reference/parseCommaSeparatedNewlineString.js new file mode 100644 index 00000000000..27df178f4d7 --- /dev/null +++ b/tests/baselines/reference/parseCommaSeparatedNewlineString.js @@ -0,0 +1,7 @@ +//// [parseCommaSeparatedNewlineString.ts] +(a, +'') + +//// [parseCommaSeparatedNewlineString.js] +(a, + ''); diff --git a/tests/baselines/reference/parseCommaSeperatedNewlineString.symbols b/tests/baselines/reference/parseCommaSeparatedNewlineString.symbols similarity index 53% rename from tests/baselines/reference/parseCommaSeperatedNewlineString.symbols rename to tests/baselines/reference/parseCommaSeparatedNewlineString.symbols index 32e753e7f71..c5e111f98e3 100644 --- a/tests/baselines/reference/parseCommaSeperatedNewlineString.symbols +++ b/tests/baselines/reference/parseCommaSeparatedNewlineString.symbols @@ -1,4 +1,4 @@ -=== tests/cases/compiler/parseCommaSeperatedNewlineString.ts === +=== tests/cases/compiler/parseCommaSeparatedNewlineString.ts === (a, No type information for this code.'') No type information for this code. \ No newline at end of file diff --git a/tests/baselines/reference/parseCommaSeparatedNewlineString.types b/tests/baselines/reference/parseCommaSeparatedNewlineString.types new file mode 100644 index 00000000000..5e6c1e4bcc0 --- /dev/null +++ b/tests/baselines/reference/parseCommaSeparatedNewlineString.types @@ -0,0 +1,9 @@ +=== tests/cases/compiler/parseCommaSeparatedNewlineString.ts === +(a, +>(a,'') : "" +>a,'' : "" +>a : any + +'') +>'' : "" + diff --git a/tests/baselines/reference/parseCommaSeperatedNewlineNew.js b/tests/baselines/reference/parseCommaSeperatedNewlineNew.js deleted file mode 100644 index c1326c47802..00000000000 --- a/tests/baselines/reference/parseCommaSeperatedNewlineNew.js +++ /dev/null @@ -1,7 +0,0 @@ -//// [parseCommaSeperatedNewlineNew.ts] -(a, -new) - -//// [parseCommaSeperatedNewlineNew.js] -(a, - new ); diff --git a/tests/baselines/reference/parseCommaSeperatedNewlineNew.types b/tests/baselines/reference/parseCommaSeperatedNewlineNew.types deleted file mode 100644 index a3c1cb2d641..00000000000 --- a/tests/baselines/reference/parseCommaSeperatedNewlineNew.types +++ /dev/null @@ -1,10 +0,0 @@ -=== tests/cases/compiler/parseCommaSeperatedNewlineNew.ts === -(a, ->(a,new) : any ->a,new : any ->a : any - -new) ->new : any -> : any - diff --git a/tests/baselines/reference/parseCommaSeperatedNewlineNumber.js b/tests/baselines/reference/parseCommaSeperatedNewlineNumber.js deleted file mode 100644 index 167e604665a..00000000000 --- a/tests/baselines/reference/parseCommaSeperatedNewlineNumber.js +++ /dev/null @@ -1,7 +0,0 @@ -//// [parseCommaSeperatedNewlineNumber.ts] -(a, -1) - -//// [parseCommaSeperatedNewlineNumber.js] -(a, - 1); diff --git a/tests/baselines/reference/parseCommaSeperatedNewlineNumber.types b/tests/baselines/reference/parseCommaSeperatedNewlineNumber.types deleted file mode 100644 index 2b80be0b66f..00000000000 --- a/tests/baselines/reference/parseCommaSeperatedNewlineNumber.types +++ /dev/null @@ -1,9 +0,0 @@ -=== tests/cases/compiler/parseCommaSeperatedNewlineNumber.ts === -(a, ->(a,1) : 1 ->a,1 : 1 ->a : any - -1) ->1 : 1 - diff --git a/tests/baselines/reference/parseCommaSeperatedNewlineString.js b/tests/baselines/reference/parseCommaSeperatedNewlineString.js deleted file mode 100644 index f7f96541c77..00000000000 --- a/tests/baselines/reference/parseCommaSeperatedNewlineString.js +++ /dev/null @@ -1,7 +0,0 @@ -//// [parseCommaSeperatedNewlineString.ts] -(a, -'') - -//// [parseCommaSeperatedNewlineString.js] -(a, - ''); diff --git a/tests/baselines/reference/parseCommaSeperatedNewlineString.types b/tests/baselines/reference/parseCommaSeperatedNewlineString.types deleted file mode 100644 index 4c4030f56eb..00000000000 --- a/tests/baselines/reference/parseCommaSeperatedNewlineString.types +++ /dev/null @@ -1,9 +0,0 @@ -=== tests/cases/compiler/parseCommaSeperatedNewlineString.ts === -(a, ->(a,'') : "" ->a,'' : "" ->a : any - -'') ->'' : "" - diff --git a/tests/cases/compiler/parseCommaSeperatedNewlineNew.ts b/tests/cases/compiler/parseCommaSeparatedNewlineNew.ts similarity index 100% rename from tests/cases/compiler/parseCommaSeperatedNewlineNew.ts rename to tests/cases/compiler/parseCommaSeparatedNewlineNew.ts diff --git a/tests/cases/compiler/parseCommaSeperatedNewlineNumber.ts b/tests/cases/compiler/parseCommaSeparatedNewlineNumber.ts similarity index 100% rename from tests/cases/compiler/parseCommaSeperatedNewlineNumber.ts rename to tests/cases/compiler/parseCommaSeparatedNewlineNumber.ts diff --git a/tests/cases/compiler/parseCommaSeperatedNewlineString.ts b/tests/cases/compiler/parseCommaSeparatedNewlineString.ts similarity index 100% rename from tests/cases/compiler/parseCommaSeperatedNewlineString.ts rename to tests/cases/compiler/parseCommaSeparatedNewlineString.ts From e59559e24e3a759d2f0b39334c0ca25a629bd09f Mon Sep 17 00:00:00 2001 From: csigs Date: Wed, 23 May 2018 17:55:46 +0000 Subject: [PATCH 08/11] LEGO: check in for master to temporary branch. --- .../diagnosticMessages.generated.json.lcl | 45 +++++++++++++++++++ .../diagnosticMessages.generated.json.lcl | 45 +++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl index f08745d21c6..bbedf489c37 100644 --- a/src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -1008,6 +1008,15 @@ + + + + + + + + + @@ -1125,6 +1134,15 @@ + + + + + + + + + @@ -6564,6 +6582,15 @@ + + + + + + + + + @@ -6603,6 +6630,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl index bdc5b984f48..5ec3b07f2ef 100644 --- a/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -1007,6 +1007,15 @@ + + + + + + + + + @@ -1124,6 +1133,15 @@ + + + + + + + + + @@ -6563,6 +6581,15 @@ + + + + + + + + + @@ -6602,6 +6629,24 @@ + + + + + + + + + + + + + + + + + + From 4606709672af666955f2385f01caf545450600c3 Mon Sep 17 00:00:00 2001 From: Wenlu Wang Date: Thu, 24 May 2018 05:09:49 +0800 Subject: [PATCH 09/11] add code fix convert to mapped object type (#24286) * add code fix convert to mapped object type * add support for type literal and improve test * fix typo * add support for heritageClauses * only determine declaration is not class --- src/compiler/diagnosticMessages.json | 4 + src/harness/tsconfig.json | 1 + src/server/tsconfig.json | 1 + src/server/tsconfig.library.json | 1 + .../codefixes/convertToMappedObjectType.ts | 94 +++++++++++++++++++ .../generateGetAccessorAndSetAccessor.ts | 4 +- src/services/tsconfig.json | 1 + .../codeFixConvertToMappedObjectType1.ts | 17 ++++ .../codeFixConvertToMappedObjectType10.ts | 23 +++++ .../codeFixConvertToMappedObjectType11.ts | 23 +++++ .../codeFixConvertToMappedObjectType12.ts | 12 +++ .../codeFixConvertToMappedObjectType2.ts | 17 ++++ .../codeFixConvertToMappedObjectType3.ts | 14 +++ .../codeFixConvertToMappedObjectType4.ts | 14 +++ .../codeFixConvertToMappedObjectType5.ts | 8 ++ .../codeFixConvertToMappedObjectType6.ts | 16 ++++ .../codeFixConvertToMappedObjectType7.ts | 18 ++++ .../codeFixConvertToMappedObjectType8.ts | 21 +++++ .../codeFixConvertToMappedObjectType9.ts | 21 +++++ 19 files changed, 308 insertions(+), 2 deletions(-) create mode 100644 src/services/codefixes/convertToMappedObjectType.ts create mode 100644 tests/cases/fourslash/codeFixConvertToMappedObjectType1.ts create mode 100644 tests/cases/fourslash/codeFixConvertToMappedObjectType10.ts create mode 100644 tests/cases/fourslash/codeFixConvertToMappedObjectType11.ts create mode 100644 tests/cases/fourslash/codeFixConvertToMappedObjectType12.ts create mode 100644 tests/cases/fourslash/codeFixConvertToMappedObjectType2.ts create mode 100644 tests/cases/fourslash/codeFixConvertToMappedObjectType3.ts create mode 100644 tests/cases/fourslash/codeFixConvertToMappedObjectType4.ts create mode 100644 tests/cases/fourslash/codeFixConvertToMappedObjectType5.ts create mode 100644 tests/cases/fourslash/codeFixConvertToMappedObjectType6.ts create mode 100644 tests/cases/fourslash/codeFixConvertToMappedObjectType7.ts create mode 100644 tests/cases/fourslash/codeFixConvertToMappedObjectType8.ts create mode 100644 tests/cases/fourslash/codeFixConvertToMappedObjectType9.ts diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 59007e610b4..be96d3de07d 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4280,5 +4280,9 @@ "Remove all unused labels": { "category": "Message", "code": 95054 + }, + "Convert '{0}' to mapped object type": { + "category": "Message", + "code": 95055 } } diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index 359701a02cb..08b8acadc93 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -118,6 +118,7 @@ "../services/codefixes/requireInTs.ts", "../services/codefixes/useDefaultImport.ts", "../services/codefixes/fixAddModuleReferTypeMissingTypeof.ts", + "../services/codefixes/convertToMappedObjectType.ts", "../services/refactors/extractSymbol.ts", "../services/refactors/generateGetAccessorAndSetAccessor.ts", "../services/refactors/moveToNewFile.ts", diff --git a/src/server/tsconfig.json b/src/server/tsconfig.json index 8ae6974baf0..806721c9c56 100644 --- a/src/server/tsconfig.json +++ b/src/server/tsconfig.json @@ -114,6 +114,7 @@ "../services/codefixes/requireInTs.ts", "../services/codefixes/useDefaultImport.ts", "../services/codefixes/fixAddModuleReferTypeMissingTypeof.ts", + "../services/codefixes/convertToMappedObjectType.ts", "../services/refactors/extractSymbol.ts", "../services/refactors/generateGetAccessorAndSetAccessor.ts", "../services/refactors/moveToNewFile.ts", diff --git a/src/server/tsconfig.library.json b/src/server/tsconfig.library.json index 922af11e879..cabcd8ff104 100644 --- a/src/server/tsconfig.library.json +++ b/src/server/tsconfig.library.json @@ -120,6 +120,7 @@ "../services/codefixes/requireInTs.ts", "../services/codefixes/useDefaultImport.ts", "../services/codefixes/fixAddModuleReferTypeMissingTypeof.ts", + "../services/codefixes/convertToMappedObjectType.ts", "../services/refactors/extractSymbol.ts", "../services/refactors/generateGetAccessorAndSetAccessor.ts", "../services/refactors/moveToNewFile.ts", diff --git a/src/services/codefixes/convertToMappedObjectType.ts b/src/services/codefixes/convertToMappedObjectType.ts new file mode 100644 index 00000000000..b8315ce6d96 --- /dev/null +++ b/src/services/codefixes/convertToMappedObjectType.ts @@ -0,0 +1,94 @@ +/* @internal */ +namespace ts.codefix { + const fixIdAddMissingTypeof = "fixConvertToMappedObjectType"; + const fixId = fixIdAddMissingTypeof; + const errorCodes = [Diagnostics.An_index_signature_parameter_type_cannot_be_a_union_type_Consider_using_a_mapped_object_type_instead.code]; + + type FixableDeclaration = InterfaceDeclaration | TypeAliasDeclaration; + + interface Info { + indexSignature: IndexSignatureDeclaration; + container: FixableDeclaration; + otherMembers: ReadonlyArray; + parameterName: Identifier; + parameterType: TypeNode; + } + + registerCodeFix({ + errorCodes, + getCodeActions: context => { + const { sourceFile, span } = context; + const info = getFixableSignatureAtPosition(sourceFile, span.start); + if (!info) return; + const { indexSignature, container, otherMembers, parameterName, parameterType } = info; + + const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, indexSignature, container, otherMembers, parameterName, parameterType)); + return [createCodeFixAction(fixId, changes, [Diagnostics.Convert_0_to_mapped_object_type, idText(container.name)], fixId, [Diagnostics.Convert_0_to_mapped_object_type, idText(container.name)])]; + }, + fixIds: [fixId], + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { + const info = getFixableSignatureAtPosition(diag.file, diag.start); + if (!info) return; + const { indexSignature, container, otherMembers, parameterName, parameterType } = info; + + doChange(changes, context.sourceFile, indexSignature, container, otherMembers, parameterName, parameterType); + }) + }); + + function isFixableParameterName(node: Node): boolean { + return node && node.parent && node.parent.parent && node.parent.parent.parent && !isClassDeclaration(node.parent.parent.parent); + } + + function getFixableSignatureAtPosition(sourceFile: SourceFile, pos: number): Info | undefined { + const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false); + if (!isFixableParameterName(token)) return undefined; + + const indexSignature = token.parent.parent; + const container = isInterfaceDeclaration(indexSignature.parent) ? indexSignature.parent : indexSignature.parent.parent; + const members = isInterfaceDeclaration(container) ? container.members : (container.type).members; + const otherMembers = filter(members, member => !isIndexSignatureDeclaration(member)); + const parameter = first(indexSignature.parameters); + + return { + indexSignature, + container, + otherMembers, + parameterName: parameter.name, + parameterType: parameter.type! + }; + } + + function getInterfaceHeritageClauses(declaration: FixableDeclaration): NodeArray | undefined { + if (!isInterfaceDeclaration(declaration)) return undefined; + + const heritageClause = getHeritageClause(declaration.heritageClauses, SyntaxKind.ExtendsKeyword); + return heritageClause && heritageClause.types; + } + + function createTypeAliasFromInterface(indexSignature: IndexSignatureDeclaration, declaration: FixableDeclaration, otherMembers: ReadonlyArray, parameterName: Identifier, parameterType: TypeNode) { + const heritageClauses = getInterfaceHeritageClauses(declaration); + const mappedTypeParameter = createTypeParameterDeclaration(parameterName, parameterType); + const mappedIntersectionType = createMappedTypeNode( + hasReadonlyModifier(indexSignature) ? createModifier(SyntaxKind.ReadonlyKeyword) : undefined, + mappedTypeParameter, + indexSignature.questionToken, + indexSignature.type); + + return createTypeAliasDeclaration( + declaration.decorators, + declaration.modifiers, + declaration.name, + declaration.typeParameters, + createIntersectionTypeNode( + concatenate( + heritageClauses, + append([mappedIntersectionType], otherMembers.length ? createTypeLiteralNode(otherMembers) : undefined) + ) + ) + ); + } + + function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, indexSignature: IndexSignatureDeclaration, declaration: FixableDeclaration, otherMembers: ReadonlyArray, parameterName: Identifier, parameterType: TypeNode) { + changes.replaceNode(sourceFile, declaration, createTypeAliasFromInterface(indexSignature, declaration, otherMembers, parameterName, parameterType)); + } +} diff --git a/src/services/refactors/generateGetAccessorAndSetAccessor.ts b/src/services/refactors/generateGetAccessorAndSetAccessor.ts index 6b49b84d4de..07104f8e620 100644 --- a/src/services/refactors/generateGetAccessorAndSetAccessor.ts +++ b/src/services/refactors/generateGetAccessorAndSetAccessor.ts @@ -88,7 +88,7 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor { return { renameFilename, renameLocation, edits }; } - function isConvertableName (name: DeclarationName): name is AcceptedNameType { + function isConvertibleName (name: DeclarationName): name is AcceptedNameType { return isIdentifier(name) || isStringLiteral(name); } @@ -125,7 +125,7 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor { // make sure declaration have AccessibilityModifier or Static Modifier or Readonly Modifier const meaning = ModifierFlags.AccessibilityModifier | ModifierFlags.Static | ModifierFlags.Readonly; if (!declaration || !rangeOverlapsWithStartEnd(declaration.name, startPosition, endPosition!) // TODO: GH#18217 - || !isConvertableName(declaration.name) || (getModifierFlags(declaration) | meaning) !== meaning) return undefined; + || !isConvertibleName(declaration.name) || (getModifierFlags(declaration) | meaning) !== meaning) return undefined; const name = declaration.name.text; const startWithUnderscore = startsWithUnderscore(name); diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 7e1ccc9c3af..960e46fb367 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -111,6 +111,7 @@ "codefixes/requireInTs.ts", "codefixes/useDefaultImport.ts", "codefixes/fixAddModuleReferTypeMissingTypeof.ts", + "codefixes/convertToMappedObjectType.ts", "refactors/extractSymbol.ts", "refactors/generateGetAccessorAndSetAccessor.ts", "refactors/moveToNewFile.ts", diff --git a/tests/cases/fourslash/codeFixConvertToMappedObjectType1.ts b/tests/cases/fourslash/codeFixConvertToMappedObjectType1.ts new file mode 100644 index 00000000000..f00702e7bdc --- /dev/null +++ b/tests/cases/fourslash/codeFixConvertToMappedObjectType1.ts @@ -0,0 +1,17 @@ +/// + +//// type K = "foo" | "bar"; +//// interface SomeType { +//// a: string; +//// [prop: K]: any; +//// } + +verify.codeFix({ + description: `Convert 'SomeType' to mapped object type`, + newFileContent: `type K = "foo" | "bar"; +type SomeType = { + [prop in K]: any; +} & { + a: string; +};` +}) diff --git a/tests/cases/fourslash/codeFixConvertToMappedObjectType10.ts b/tests/cases/fourslash/codeFixConvertToMappedObjectType10.ts new file mode 100644 index 00000000000..073b20d9160 --- /dev/null +++ b/tests/cases/fourslash/codeFixConvertToMappedObjectType10.ts @@ -0,0 +1,23 @@ +/// + +//// type K = "foo" | "bar"; +//// interface Foo { } +//// interface Bar { bar: T; } +//// interface SomeType extends Foo, Bar { +//// a: number; +//// b: T; +//// [prop: K]: any; +//// } + +verify.codeFix({ + description: `Convert 'SomeType' to mapped object type`, + newFileContent: `type K = "foo" | "bar"; +interface Foo { } +interface Bar { bar: T; } +type SomeType = Foo & Bar & { + [prop in K]: any; +} & { + a: number; + b: T; +};` +}) diff --git a/tests/cases/fourslash/codeFixConvertToMappedObjectType11.ts b/tests/cases/fourslash/codeFixConvertToMappedObjectType11.ts new file mode 100644 index 00000000000..b82ee1ac179 --- /dev/null +++ b/tests/cases/fourslash/codeFixConvertToMappedObjectType11.ts @@ -0,0 +1,23 @@ +/// + +//// type K = "foo" | "bar"; +//// interface Foo { } +//// interface Bar { bar: T; } +//// interface SomeType extends Foo, Bar { +//// a: number; +//// b: T; +//// readonly [prop: K]: any; +//// } + +verify.codeFix({ + description: `Convert 'SomeType' to mapped object type`, + newFileContent: `type K = "foo" | "bar"; +interface Foo { } +interface Bar { bar: T; } +type SomeType = Foo & Bar & { + readonly [prop in K]: any; +} & { + a: number; + b: T; +};` +}) diff --git a/tests/cases/fourslash/codeFixConvertToMappedObjectType12.ts b/tests/cases/fourslash/codeFixConvertToMappedObjectType12.ts new file mode 100644 index 00000000000..6374dc43dac --- /dev/null +++ b/tests/cases/fourslash/codeFixConvertToMappedObjectType12.ts @@ -0,0 +1,12 @@ +/// + +//// type K = "foo" | "bar"; +//// interface Foo { } +//// interface Bar { bar: T; } +//// interface SomeType extends Foo, Bar { +//// a: number; +//// b: T; +//// readonly [prop: K]?: any; +//// } + +verify.not.codeFixAvailable() diff --git a/tests/cases/fourslash/codeFixConvertToMappedObjectType2.ts b/tests/cases/fourslash/codeFixConvertToMappedObjectType2.ts new file mode 100644 index 00000000000..27dfc7cab0d --- /dev/null +++ b/tests/cases/fourslash/codeFixConvertToMappedObjectType2.ts @@ -0,0 +1,17 @@ +/// + +//// type K = "foo" | "bar"; +//// type SomeType = { +//// a: string; +//// [prop: K]: any; +//// } + +verify.codeFix({ + description: `Convert 'SomeType' to mapped object type`, + newFileContent: `type K = "foo" | "bar"; +type SomeType = { + [prop in K]: any; +} & { + a: string; +};` +}) diff --git a/tests/cases/fourslash/codeFixConvertToMappedObjectType3.ts b/tests/cases/fourslash/codeFixConvertToMappedObjectType3.ts new file mode 100644 index 00000000000..b916eaca9b6 --- /dev/null +++ b/tests/cases/fourslash/codeFixConvertToMappedObjectType3.ts @@ -0,0 +1,14 @@ +/// + +//// type K = "foo" | "bar"; +//// type SomeType = { +//// [prop: K]: any; +//// } + +verify.codeFix({ + description: `Convert 'SomeType' to mapped object type`, + newFileContent: `type K = "foo" | "bar"; +type SomeType = { + [prop in K]: any; +};` +}) diff --git a/tests/cases/fourslash/codeFixConvertToMappedObjectType4.ts b/tests/cases/fourslash/codeFixConvertToMappedObjectType4.ts new file mode 100644 index 00000000000..4077a7c04af --- /dev/null +++ b/tests/cases/fourslash/codeFixConvertToMappedObjectType4.ts @@ -0,0 +1,14 @@ +/// + +//// type K = "foo" | "bar"; +//// interface SomeType { +//// [prop: K]: any; +//// } + +verify.codeFix({ + description: `Convert 'SomeType' to mapped object type`, + newFileContent: `type K = "foo" | "bar"; +type SomeType = { + [prop in K]: any; +};` +}) diff --git a/tests/cases/fourslash/codeFixConvertToMappedObjectType5.ts b/tests/cases/fourslash/codeFixConvertToMappedObjectType5.ts new file mode 100644 index 00000000000..869974f9ad2 --- /dev/null +++ b/tests/cases/fourslash/codeFixConvertToMappedObjectType5.ts @@ -0,0 +1,8 @@ +/// + +//// type K = "foo" | "bar"; +//// class SomeType { +//// [prop: K]: any; +//// } + +verify.not.codeFixAvailable() diff --git a/tests/cases/fourslash/codeFixConvertToMappedObjectType6.ts b/tests/cases/fourslash/codeFixConvertToMappedObjectType6.ts new file mode 100644 index 00000000000..b00f356b48b --- /dev/null +++ b/tests/cases/fourslash/codeFixConvertToMappedObjectType6.ts @@ -0,0 +1,16 @@ +/// + +//// type K = "foo" | "bar"; +//// interface Foo { } +//// interface SomeType extends Foo { +//// [prop: K]: any; +//// } + +verify.codeFix({ + description: `Convert 'SomeType' to mapped object type`, + newFileContent: `type K = "foo" | "bar"; +interface Foo { } +type SomeType = Foo & { + [prop in K]: any; +};` +}) diff --git a/tests/cases/fourslash/codeFixConvertToMappedObjectType7.ts b/tests/cases/fourslash/codeFixConvertToMappedObjectType7.ts new file mode 100644 index 00000000000..a0a1e4eeccb --- /dev/null +++ b/tests/cases/fourslash/codeFixConvertToMappedObjectType7.ts @@ -0,0 +1,18 @@ +/// + +//// type K = "foo" | "bar"; +//// interface Foo { } +//// interface Bar { } +//// interface SomeType extends Foo, Bar { +//// [prop: K]: any; +//// } + +verify.codeFix({ + description: `Convert 'SomeType' to mapped object type`, + newFileContent: `type K = "foo" | "bar"; +interface Foo { } +interface Bar { } +type SomeType = Foo & Bar & { + [prop in K]: any; +};` +}) diff --git a/tests/cases/fourslash/codeFixConvertToMappedObjectType8.ts b/tests/cases/fourslash/codeFixConvertToMappedObjectType8.ts new file mode 100644 index 00000000000..6ee6c9f0aad --- /dev/null +++ b/tests/cases/fourslash/codeFixConvertToMappedObjectType8.ts @@ -0,0 +1,21 @@ +/// + +//// type K = "foo" | "bar"; +//// interface Foo { } +//// interface Bar { } +//// interface SomeType extends Foo, Bar { +//// a: number; +//// [prop: K]: any; +//// } + +verify.codeFix({ + description: `Convert 'SomeType' to mapped object type`, + newFileContent: `type K = "foo" | "bar"; +interface Foo { } +interface Bar { } +type SomeType = Foo & Bar & { + [prop in K]: any; +} & { + a: number; +};` +}) diff --git a/tests/cases/fourslash/codeFixConvertToMappedObjectType9.ts b/tests/cases/fourslash/codeFixConvertToMappedObjectType9.ts new file mode 100644 index 00000000000..095296481f5 --- /dev/null +++ b/tests/cases/fourslash/codeFixConvertToMappedObjectType9.ts @@ -0,0 +1,21 @@ +/// + +//// type K = "foo" | "bar"; +//// interface Foo { } +//// interface Bar { bar: T; } +//// interface SomeType extends Foo, Bar { +//// a: number; +//// [prop: K]: any; +//// } + +verify.codeFix({ + description: `Convert 'SomeType' to mapped object type`, + newFileContent: `type K = "foo" | "bar"; +interface Foo { } +interface Bar { bar: T; } +type SomeType = Foo & Bar & { + [prop in K]: any; +} & { + a: number; +};` +}) From e9981b100bf90548034a6a23b22e2ee6507e956d Mon Sep 17 00:00:00 2001 From: csigs Date: Wed, 23 May 2018 22:10:36 +0000 Subject: [PATCH 10/11] LEGO: check in for master to temporary branch. --- .../diagnosticMessages.generated.json.lcl | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl index e880c4493a9..5604802bcb3 100644 --- a/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -1019,10 +1019,13 @@ - + - + + + + @@ -1143,6 +1146,15 @@ + + + + + + + + + @@ -6582,6 +6594,15 @@ + + + + + + + + + @@ -6621,6 +6642,24 @@ + + + + + + + + + + + + + + + + + + From b61d4858d43c2c0241543653197d9ff53bfdb9b7 Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 23 May 2018 16:01:52 -0700 Subject: [PATCH 11/11] Simplify convertToMappedObjectType (#24360) --- .../codefixes/convertToMappedObjectType.ts | 92 ++++++------------- 1 file changed, 27 insertions(+), 65 deletions(-) diff --git a/src/services/codefixes/convertToMappedObjectType.ts b/src/services/codefixes/convertToMappedObjectType.ts index b8315ce6d96..ff86a9872bb 100644 --- a/src/services/codefixes/convertToMappedObjectType.ts +++ b/src/services/codefixes/convertToMappedObjectType.ts @@ -6,89 +6,51 @@ namespace ts.codefix { type FixableDeclaration = InterfaceDeclaration | TypeAliasDeclaration; - interface Info { - indexSignature: IndexSignatureDeclaration; - container: FixableDeclaration; - otherMembers: ReadonlyArray; - parameterName: Identifier; - parameterType: TypeNode; - } - registerCodeFix({ errorCodes, getCodeActions: context => { const { sourceFile, span } = context; - const info = getFixableSignatureAtPosition(sourceFile, span.start); - if (!info) return; - const { indexSignature, container, otherMembers, parameterName, parameterType } = info; - - const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, indexSignature, container, otherMembers, parameterName, parameterType)); - return [createCodeFixAction(fixId, changes, [Diagnostics.Convert_0_to_mapped_object_type, idText(container.name)], fixId, [Diagnostics.Convert_0_to_mapped_object_type, idText(container.name)])]; + const info = getInfo(sourceFile, span.start); + if (!info) return undefined; + const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, info)); + const name = idText(info.container.name); + return [createCodeFixAction(fixId, changes, [Diagnostics.Convert_0_to_mapped_object_type, name], fixId, [Diagnostics.Convert_0_to_mapped_object_type, name])]; }, fixIds: [fixId], getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { - const info = getFixableSignatureAtPosition(diag.file, diag.start); - if (!info) return; - const { indexSignature, container, otherMembers, parameterName, parameterType } = info; - - doChange(changes, context.sourceFile, indexSignature, container, otherMembers, parameterName, parameterType); + const info = getInfo(diag.file, diag.start); + if (info) doChange(changes, diag.file, info); }) }); - function isFixableParameterName(node: Node): boolean { - return node && node.parent && node.parent.parent && node.parent.parent.parent && !isClassDeclaration(node.parent.parent.parent); - } - - function getFixableSignatureAtPosition(sourceFile: SourceFile, pos: number): Info | undefined { + interface Info { readonly indexSignature: IndexSignatureDeclaration; readonly container: FixableDeclaration; } + function getInfo(sourceFile: SourceFile, pos: number): Info | undefined { const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false); - if (!isFixableParameterName(token)) return undefined; + const indexSignature = cast(token.parent.parent, isIndexSignatureDeclaration); + if (isClassDeclaration(indexSignature.parent)) return undefined; + const container = isInterfaceDeclaration(indexSignature.parent) ? indexSignature.parent : cast(indexSignature.parent.parent, isTypeAliasDeclaration); + return { indexSignature, container }; + } - const indexSignature = token.parent.parent; - const container = isInterfaceDeclaration(indexSignature.parent) ? indexSignature.parent : indexSignature.parent.parent; + function createTypeAliasFromInterface(declaration: FixableDeclaration, type: TypeNode): TypeAliasDeclaration { + return createTypeAliasDeclaration(declaration.decorators, declaration.modifiers, declaration.name, declaration.typeParameters, type); + } + + function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { indexSignature, container }: Info): void { const members = isInterfaceDeclaration(container) ? container.members : (container.type).members; - const otherMembers = filter(members, member => !isIndexSignatureDeclaration(member)); + const otherMembers = members.filter(member => !isIndexSignatureDeclaration(member)); const parameter = first(indexSignature.parameters); - - return { - indexSignature, - container, - otherMembers, - parameterName: parameter.name, - parameterType: parameter.type! - }; - } - - function getInterfaceHeritageClauses(declaration: FixableDeclaration): NodeArray | undefined { - if (!isInterfaceDeclaration(declaration)) return undefined; - - const heritageClause = getHeritageClause(declaration.heritageClauses, SyntaxKind.ExtendsKeyword); - return heritageClause && heritageClause.types; - } - - function createTypeAliasFromInterface(indexSignature: IndexSignatureDeclaration, declaration: FixableDeclaration, otherMembers: ReadonlyArray, parameterName: Identifier, parameterType: TypeNode) { - const heritageClauses = getInterfaceHeritageClauses(declaration); - const mappedTypeParameter = createTypeParameterDeclaration(parameterName, parameterType); + const mappedTypeParameter = createTypeParameterDeclaration(cast(parameter.name, isIdentifier), parameter.type); const mappedIntersectionType = createMappedTypeNode( hasReadonlyModifier(indexSignature) ? createModifier(SyntaxKind.ReadonlyKeyword) : undefined, mappedTypeParameter, indexSignature.questionToken, indexSignature.type); - - return createTypeAliasDeclaration( - declaration.decorators, - declaration.modifiers, - declaration.name, - declaration.typeParameters, - createIntersectionTypeNode( - concatenate( - heritageClauses, - append([mappedIntersectionType], otherMembers.length ? createTypeLiteralNode(otherMembers) : undefined) - ) - ) - ); - } - - function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, indexSignature: IndexSignatureDeclaration, declaration: FixableDeclaration, otherMembers: ReadonlyArray, parameterName: Identifier, parameterType: TypeNode) { - changes.replaceNode(sourceFile, declaration, createTypeAliasFromInterface(indexSignature, declaration, otherMembers, parameterName, parameterType)); + const intersectionType = createIntersectionTypeNode([ + ...getAllSuperTypeNodes(container), + mappedIntersectionType, + ...(otherMembers.length ? [createTypeLiteralNode(otherMembers)] : emptyArray), + ]); + changes.replaceNode(sourceFile, container, createTypeAliasFromInterface(container, intersectionType)); } }