From cfdef9de7235ff9eb80b99be86850a65899d7f81 Mon Sep 17 00:00:00 2001 From: TypeScript Bot Date: Wed, 16 Mar 2022 11:57:09 -0700 Subject: [PATCH] Cherry-pick PR #47657 into release-4.6 (#48223) Component commits: 4516fa8421 fix(47597): ignore commented imports following template expression Co-authored-by: Oleksandr T --- src/services/preProcess.ts | 36 ++++ .../unittests/services/preProcessFile.ts | 171 ++++++++++++++++++ 2 files changed, 207 insertions(+) diff --git a/src/services/preProcess.ts b/src/services/preProcess.ts index 41845616bbe4b..d5c384e9161cc 100644 --- a/src/services/preProcess.ts +++ b/src/services/preProcess.ts @@ -345,6 +345,42 @@ namespace ts { break; } + if (scanner.getToken() === SyntaxKind.TemplateHead) { + const stack = [scanner.getToken()]; + let token = scanner.scan(); + loop: while (length(stack)) { + switch (token) { + case SyntaxKind.EndOfFileToken: + break loop; + case SyntaxKind.ImportKeyword: + tryConsumeImport(); + break; + case SyntaxKind.TemplateHead: + stack.push(token); + break; + case SyntaxKind.OpenBraceToken: + if (length(stack)) { + stack.push(token); + } + break; + case SyntaxKind.CloseBraceToken: + if (length(stack)) { + if (lastOrUndefined(stack) === SyntaxKind.TemplateHead) { + if (scanner.reScanTemplateToken(/* isTaggedTemplate */ false) === SyntaxKind.TemplateTail) { + stack.pop(); + } + } + else { + stack.pop(); + } + } + break; + } + token = scanner.scan(); + } + nextToken(); + } + // check if at least one of alternative have moved scanner forward if (tryConsumeDeclare() || tryConsumeImport() || diff --git a/src/testRunner/unittests/services/preProcessFile.ts b/src/testRunner/unittests/services/preProcessFile.ts index fb7a7e62649d4..a6369d6e4d4cf 100644 --- a/src/testRunner/unittests/services/preProcessFile.ts +++ b/src/testRunner/unittests/services/preProcessFile.ts @@ -176,6 +176,177 @@ describe("unittests:: services:: PreProcessFile:", () => { }); }); + it("Correctly ignore commented imports following template expression", () => { + /* eslint-disable no-template-curly-in-string */ + test("/**" + "\n" + + " * Before" + "\n" + + " * ```" + "\n" + + " * import * as a from \"a\";" + "\n" + + " * ```" + "\n" + + " */" + "\n" + + "type Foo = `${string}`;" + "\n" + + "/**" + "\n" + + " * After" + "\n" + + " * ```" + "\n" + + " * import { B } from \"b\";" + "\n" + + " * import * as c from \"c\";" + "\n" + + " * ```" + "\n" + + " */", + /*readImportFile*/ true, + /*detectJavaScriptImports*/ true, + { + referencedFiles: [], + typeReferenceDirectives: [], + libReferenceDirectives: [], + importedFiles: [], + ambientExternalModules: undefined, + isLibFile: false + }); + /* eslint-enable no-template-curly-in-string */ + }); + + it("Correctly returns imports after a template expression", () => { + /* eslint-disable no-template-curly-in-string */ + test("`${foo}`; import \"./foo\";", + /*readImportFile*/ true, + /*detectJavaScriptImports*/ true, + { + referencedFiles: [], + typeReferenceDirectives: [], + libReferenceDirectives: [], + importedFiles: [ + { fileName: "./foo", pos: 17, end: 22 } + ], + ambientExternalModules: undefined, + isLibFile: false + }); + /* eslint-enable no-template-curly-in-string */ + }); + + it("Correctly returns dynamic imports from template expression", () => { + /* eslint-disable no-template-curly-in-string */ + test("`${(
Text `` ${} text {} " + "\n" + + "${import(\"a\")} {import(\"b\")} " + "\n" + + "${/* A comment */} ${/* import(\"ignored\") */}
)}`", + /*readImportFile*/ true, + /*detectJavaScriptImports*/ true, + { + referencedFiles: [], + typeReferenceDirectives: [], + libReferenceDirectives: [], + importedFiles: [ + { fileName: "a", pos: 39, end: 40 }, + { fileName: "b", pos: 53, end: 54 } + ], + ambientExternalModules: undefined, + isLibFile: false + }); + /* eslint-enable no-template-curly-in-string */ + }); + + it("Correctly returns dynamic imports from nested template expression", () => { + /* eslint-disable no-template-curly-in-string */ + test("`${foo(`${bar(`${import(\"a\")} ${import(\"b\")}`, `${baz(`${import(\"c\") ${import(\"d\")}`)}`)}`)}`", + /*readImportFile*/ true, + /*detectJavaScriptImports*/ true, + { + referencedFiles: [], + typeReferenceDirectives: [], + libReferenceDirectives: [], + importedFiles: [ + { fileName: "a", pos: 24, end: 25 }, + { fileName: "b", pos: 39, end: 40 }, + { fileName: "c", pos: 64, end: 65 }, + { fileName: "d", pos: 78, end: 79 }, + ], + ambientExternalModules: undefined, + isLibFile: false + }); + /* eslint-enable no-template-curly-in-string */ + }); + + it("Correctly returns dynamic imports from tagged template expression", () => { + /* eslint-disable no-template-curly-in-string */ + test("foo`${ fn({ a: 100 }, import(\"a\"), `${import(\"b\")}`, import(\"c\"), `${import(\"d\")} foo`, import(\"e\")) }`", + /*readImportFile*/ true, + /*detectJavaScriptImports*/ true, + { + referencedFiles: [], + typeReferenceDirectives: [], + libReferenceDirectives: [], + importedFiles: [ + { fileName: "a", pos: 29, end: 30 }, + { fileName: "b", pos: 45, end: 46 }, + { fileName: "c", pos: 60, end: 61 }, + { fileName: "d", pos: 76, end: 77 }, + { fileName: "e", pos: 95, end: 96 }, + ], + ambientExternalModules: undefined, + isLibFile: false + }); + /* eslint-enable no-template-curly-in-string */ + }); + + it("Correctly returns dynamic imports from template expression and imports following it", () => { + /* eslint-disable no-template-curly-in-string */ + test("const x = `hello ${await import(\"a\").default}`;" + "\n\n" + + "import { y } from \"b\";", + /*readImportFile*/ true, + /*detectJavaScriptImports*/ true, + { + referencedFiles: [], + typeReferenceDirectives: [], + libReferenceDirectives: [], + importedFiles: [ + { fileName: "a", pos: 32, end: 33 }, + { fileName: "b", pos: 67, end: 68 }, + ], + ambientExternalModules: undefined, + isLibFile: false + }); + /* eslint-enable no-template-curly-in-string */ + }); + + it("Correctly returns dynamic imports from template expressions and other imports", () => { + /* eslint-disable no-template-curly-in-string */ + test("const x = `x ${await import(\"a\").default}`;" + "\n\n" + + "import { y } from \"b\";" + "\n" + + "const y = `y ${import(\"c\")}`;" + "\n\n" + + "import { d } from \"d\";", + /*readImportFile*/ true, + /*detectJavaScriptImports*/ true, + { + referencedFiles: [], + typeReferenceDirectives: [], + libReferenceDirectives: [], + importedFiles: [ + { fileName: "a", pos: 28, end: 29 }, + { fileName: "b", pos: 63, end: 64 }, + { fileName: "c", pos: 90, end: 91 }, + { fileName: "d", pos: 117, end: 118 }, + ], + ambientExternalModules: undefined, + isLibFile: false + }); + /* eslint-enable no-template-curly-in-string */ + }); + + it("Correctly returns empty importedFiles with incorrect template expression", () => { + /* eslint-disable no-template-curly-in-string */ + test("const foo = `${", + /*readImportFile*/ true, + /*detectJavaScriptImports*/ true, + { + referencedFiles: [], + typeReferenceDirectives: [], + libReferenceDirectives: [], + importedFiles: [], + ambientExternalModules: undefined, + isLibFile: false + }); + /* eslint-enable no-template-curly-in-string */ + }); + it("Correctly return ES6 exports", () => { test("export * from \"m1\";" + "\n" + "export {a} from \"m2\";" + "\n" +