From 2d4978d715fdcd12c04c70315f79b00a20089fba Mon Sep 17 00:00:00 2001 From: Joscha Feth Date: Sat, 1 Feb 2020 00:38:51 +1100 Subject: [PATCH 1/2] fix: imports/exports/require with template literal tokens --- src/services/preProcess.ts | 43 +++++-- .../unittests/services/preProcessFile.ts | 111 ++++++++++++++++++ 2 files changed, 144 insertions(+), 10 deletions(-) diff --git a/src/services/preProcess.ts b/src/services/preProcess.ts index fc22b274ddbbd..e7ddec87e2229 100644 --- a/src/services/preProcess.ts +++ b/src/services/preProcess.ts @@ -57,6 +57,11 @@ namespace ts { } } + function isStringLiteralLike(token: SyntaxKind) { + return token === SyntaxKind.StringLiteral + || token === SyntaxKind.NoSubstitutionTemplateLiteral; + } + /** * Returns true if at least one token was consumed from the stream */ @@ -81,7 +86,13 @@ namespace ts { * Returns true if at least one token was consumed from the stream */ function tryConsumeImport(): boolean { - if (lastToken === SyntaxKind.DotToken) { + if (lastToken === SyntaxKind.DotToken + // the following is used to prevent matches of + // `import * as doh from 'ooops';\n`; + // however this is only best-effort as it is possible to have a template + // literal ending right before, not only opening, which would prevent this + // import from being detected. + || lastToken === SyntaxKind.NoSubstitutionTemplateLiteral) { return false; } let token = scanner.getToken(); @@ -89,14 +100,16 @@ namespace ts { token = nextToken(); if (token === SyntaxKind.OpenParenToken) { token = nextToken(); - if (token === SyntaxKind.StringLiteral) { + if (isStringLiteralLike(token)) { // import("mod"); + // import(`mod`); recordModuleName(); return true; } } - else if (token === SyntaxKind.StringLiteral) { + else if (isStringLiteralLike(token)) { // import "mod"; + // import `mod`; recordModuleName(); return true; } @@ -105,8 +118,9 @@ namespace ts { token = nextToken(); if (token === SyntaxKind.FromKeyword) { token = nextToken(); - if (token === SyntaxKind.StringLiteral) { + if (isStringLiteralLike(token)) { // import d from "mod"; + // import d from `mod` recordModuleName(); return true; } @@ -138,9 +152,10 @@ namespace ts { token = nextToken(); if (token === SyntaxKind.FromKeyword) { token = nextToken(); - if (token === SyntaxKind.StringLiteral) { + if (isStringLiteralLike(token)) { // import {a as A} from "mod"; // import d, {a, b as B} from "mod" + // import d, {a, b as B} from `mod` recordModuleName(); } } @@ -154,9 +169,10 @@ namespace ts { token = nextToken(); if (token === SyntaxKind.FromKeyword) { token = nextToken(); - if (token === SyntaxKind.StringLiteral) { + if (isStringLiteralLike(token)) { // import * as NS from "mod" // import d, * as NS from "mod" + // import d, * as NS from `mod` recordModuleName(); } } @@ -188,9 +204,10 @@ namespace ts { token = nextToken(); if (token === SyntaxKind.FromKeyword) { token = nextToken(); - if (token === SyntaxKind.StringLiteral) { - // export {a as A} from "mod"; + if (isStringLiteralLike(token)) { + // export {a as A} from "mod" // export {a, b as B} from "mod" + // export {a, b as B} from `mod` recordModuleName(); } } @@ -200,8 +217,9 @@ namespace ts { token = nextToken(); if (token === SyntaxKind.FromKeyword) { token = nextToken(); - if (token === SyntaxKind.StringLiteral) { + if (isStringLiteralLike(token)) { // export * from "mod" + // export * from `mod` recordModuleName(); } } @@ -230,8 +248,9 @@ namespace ts { token = nextToken(); if (token === SyntaxKind.OpenParenToken) { token = nextToken(); - if (token === SyntaxKind.StringLiteral) { + if (isStringLiteralLike(token)) { // require("mod"); + // require(`mod`); recordModuleName(); } } @@ -294,15 +313,19 @@ namespace ts { // import d, {a, b as B} from "mod" // import i = require("mod"); // import("mod"); + // import(`mod`); + // export * from "mod" // export {a as b} from "mod" // export import i = require("mod") // (for JavaScript files) require("mod") + // (for JavaScript files) require(`mod`); // Do not look for: // AnySymbol.import("mod") // AnySymbol.nested.import("mod") + // `import * as doh from 'ooops';`; while (true) { if (scanner.getToken() === SyntaxKind.EndOfFileToken) { diff --git a/src/testRunner/unittests/services/preProcessFile.ts b/src/testRunner/unittests/services/preProcessFile.ts index 766c7d7078beb..3ce5937a88675 100644 --- a/src/testRunner/unittests/services/preProcessFile.ts +++ b/src/testRunner/unittests/services/preProcessFile.ts @@ -88,6 +88,117 @@ describe("unittests:: services:: PreProcessFile:", () => { }); }); + // https://github.com/microsoft/TypeScript/issues/30878#issuecomment-540698189 + it.skip("Do not return reference path of imports in string literals", () => { + test("`${}`;\n`import * as doh from 'ooops';\n`;", + /*readImportFile*/ true, + /*detectJavaScriptImports*/ false, + { + referencedFiles: [], + importedFiles: [], + typeReferenceDirectives: [], + libReferenceDirectives: [], + ambientExternalModules: undefined, + isLibFile: false + }); + }); + + // https://github.com/microsoft/TypeScript/issues/30878#issue-432369315 + it.skip("Correctly return statically imported files after string literals", () => { + test("`${foo}`; import './r1.ts';", + /*readImportFile*/ true, + /*detectJavaScriptImports*/ false, + { + referencedFiles: [], + typeReferenceDirectives: [], + libReferenceDirectives: [], + importedFiles: [{ fileName: "r1.ts", pos: 17, end: 24 }], + ambientExternalModules: undefined, + isLibFile: false + }); + }); + + // https://github.com/microsoft/TypeScript/issues/30878#issue-432369315 + it("Correctly return dynamically imported files after string literals", () => { + test("`${foo}`; import('./r1.ts');", + /*readImportFile*/ true, + /*detectJavaScriptImports*/ false, + { + referencedFiles: [], + typeReferenceDirectives: [], + libReferenceDirectives: [], + importedFiles: [{ fileName: "r1.ts", pos: 17, end: 25 }], + ambientExternalModules: undefined, + isLibFile: false + }); + }); + + // https://github.com/microsoft/TypeScript/issues/33680#issue-500399194 + it("Correctly return dynamically imported files using string literals", () => { + test("import(`r1.ts`);", + /*readImportFile*/ true, + /*detectJavaScriptImports*/ false, + { + referencedFiles: [], + typeReferenceDirectives: [], + libReferenceDirectives: [], + importedFiles: [{ fileName: "r1.ts", pos: 7, end: 12 }], + ambientExternalModules: undefined, + isLibFile: false + }); + }); + + // https://github.com/microsoft/TypeScript/issues/33680#issue-500399194 + it("Correctly return required files using string literals in JS", () => { + test("require(`r1.ts`);", + /*readImportFile*/ true, + /*detectJavaScriptImports*/ true, + { + referencedFiles: [], + typeReferenceDirectives: [], + libReferenceDirectives: [], + importedFiles: [{ fileName: "r1.ts", pos: 8, end: 13 }], + ambientExternalModules: undefined, + isLibFile: false + }); + }); + + // https://github.com/microsoft/TypeScript/issues/33680#issue-500399194 + it("Correctly return required files using string literals", () => { + test("import i = require(`r1.ts`);", + /*readImportFile*/ true, + /*detectJavaScriptImports*/ false, + { + referencedFiles: [], + typeReferenceDirectives: [], + libReferenceDirectives: [], + importedFiles: [{ fileName: "r1.ts", pos: 19, end: 24 }], + ambientExternalModules: undefined, + isLibFile: false + }); + }); + + // https://github.com/microsoft/TypeScript/issues/33680#issue-500399194 + it("Correctly return static imports using string literals", () => { + test("import d from `r1.ts`; import d, {a, b as B} from `r2.ts`; import d, * as NS from `r3.ts`; export {a, b as B} from `r4.ts`; export * from `r5.ts`;", + /*readImportFile*/ true, + /*detectJavaScriptImports*/ false, + { + referencedFiles: [], + typeReferenceDirectives: [], + libReferenceDirectives: [], + importedFiles: [ + { fileName: "r1.ts", pos: 14, end: 19 }, + { fileName: "r2.ts", pos: 50, end: 55 }, + { fileName: "r3.ts", pos: 82, end: 87 }, + { fileName: "r4.ts", pos: 115, end:120 }, + { fileName: "r5.ts", pos: 138, end:143 } + ], + ambientExternalModules: undefined, + isLibFile: false + }); + }); + it("Correctly return imported files", () => { test("import i1 = require(\"r1.ts\"); import i2 =require(\"r2.ts\"); import i3= require(\"r3.ts\"); import i4=require(\"r4.ts\"); import i5 = require (\"r5.ts\");", /*readImportFile*/ true, From 847da1b5a4aaaedbc39f703bdbc075787a3f1cfd Mon Sep 17 00:00:00 2001 From: Joscha Feth Date: Sat, 1 Feb 2020 00:54:22 +1100 Subject: [PATCH 2/2] remove skipped tests & best-effort string literal detection --- src/services/preProcess.ts | 8 +--- .../unittests/services/preProcessFile.ts | 45 ------------------- 2 files changed, 1 insertion(+), 52 deletions(-) diff --git a/src/services/preProcess.ts b/src/services/preProcess.ts index e7ddec87e2229..db7478288068e 100644 --- a/src/services/preProcess.ts +++ b/src/services/preProcess.ts @@ -86,13 +86,7 @@ namespace ts { * Returns true if at least one token was consumed from the stream */ function tryConsumeImport(): boolean { - if (lastToken === SyntaxKind.DotToken - // the following is used to prevent matches of - // `import * as doh from 'ooops';\n`; - // however this is only best-effort as it is possible to have a template - // literal ending right before, not only opening, which would prevent this - // import from being detected. - || lastToken === SyntaxKind.NoSubstitutionTemplateLiteral) { + if (lastToken === SyntaxKind.DotToken) { return false; } let token = scanner.getToken(); diff --git a/src/testRunner/unittests/services/preProcessFile.ts b/src/testRunner/unittests/services/preProcessFile.ts index 3ce5937a88675..3ee95be47d6ff 100644 --- a/src/testRunner/unittests/services/preProcessFile.ts +++ b/src/testRunner/unittests/services/preProcessFile.ts @@ -88,51 +88,6 @@ describe("unittests:: services:: PreProcessFile:", () => { }); }); - // https://github.com/microsoft/TypeScript/issues/30878#issuecomment-540698189 - it.skip("Do not return reference path of imports in string literals", () => { - test("`${}`;\n`import * as doh from 'ooops';\n`;", - /*readImportFile*/ true, - /*detectJavaScriptImports*/ false, - { - referencedFiles: [], - importedFiles: [], - typeReferenceDirectives: [], - libReferenceDirectives: [], - ambientExternalModules: undefined, - isLibFile: false - }); - }); - - // https://github.com/microsoft/TypeScript/issues/30878#issue-432369315 - it.skip("Correctly return statically imported files after string literals", () => { - test("`${foo}`; import './r1.ts';", - /*readImportFile*/ true, - /*detectJavaScriptImports*/ false, - { - referencedFiles: [], - typeReferenceDirectives: [], - libReferenceDirectives: [], - importedFiles: [{ fileName: "r1.ts", pos: 17, end: 24 }], - ambientExternalModules: undefined, - isLibFile: false - }); - }); - - // https://github.com/microsoft/TypeScript/issues/30878#issue-432369315 - it("Correctly return dynamically imported files after string literals", () => { - test("`${foo}`; import('./r1.ts');", - /*readImportFile*/ true, - /*detectJavaScriptImports*/ false, - { - referencedFiles: [], - typeReferenceDirectives: [], - libReferenceDirectives: [], - importedFiles: [{ fileName: "r1.ts", pos: 17, end: 25 }], - ambientExternalModules: undefined, - isLibFile: false - }); - }); - // https://github.com/microsoft/TypeScript/issues/33680#issue-500399194 it("Correctly return dynamically imported files using string literals", () => { test("import(`r1.ts`);",