From 448a480aa8d592378732b1052c645b5c681fa7e3 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Tue, 12 Jul 2016 08:37:52 -0700 Subject: [PATCH] Don't allow `.ts` to appear in an import --- src/compiler/program.ts | 61 +++++++++++-------- src/harness/unittests/moduleResolution.ts | 8 +-- .../missingFunctionImplementation2.errors.txt | 2 +- .../missingFunctionImplementation2.js | 2 +- .../reference/moduleResolutionNoTs.errors.txt | 11 ++++ .../reference/moduleResolutionNoTs.js | 15 +++++ .../moduleResolutionWithExtensions.js | 7 --- .../moduleResolutionWithExtensions.symbols | 5 -- .../moduleResolutionWithExtensions.trace.json | 6 -- .../moduleResolutionWithExtensions.types | 5 -- .../staticInstanceResolution5.errors.txt | 2 +- .../reference/staticInstanceResolution5.js | 2 +- .../missingFunctionImplementation2.ts | 2 +- tests/cases/compiler/moduleResolutionNoTs.ts | 5 ++ .../compiler/staticInstanceResolution5.ts | 2 +- .../moduleResolutionWithExtensions.ts | 4 -- 16 files changed, 77 insertions(+), 62 deletions(-) create mode 100644 tests/baselines/reference/moduleResolutionNoTs.errors.txt create mode 100644 tests/baselines/reference/moduleResolutionNoTs.js create mode 100644 tests/cases/compiler/moduleResolutionNoTs.ts diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 988714be2ab..5ddf5f7d065 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -668,24 +668,37 @@ namespace ts { * @param {boolean} onlyRecordFailures - if true then function won't try to actually load files but instead record all attempts as failures. This flag is necessary * in cases when we know upfront that all load attempts will fail (because containing folder does not exists) however we still need to record all failed lookup locations. */ - function loadModuleFromFile(candidate: string, extensions: string[], failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string { - // First try to keep/add an extension: importing "./foo.ts" can be matched by a file "./foo.ts", and "./foo" by "./foo.d.ts" - const resolvedByAddingOrKeepingExtension = loadModuleFromFileWorker(candidate, extensions, failedLookupLocation, onlyRecordFailures, state); - if (resolvedByAddingOrKeepingExtension) { - return resolvedByAddingOrKeepingExtension; + function loadModuleFromFile(candidate: string, extensions: string[], failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined { + // If the candidate already has an extension load that or quit. + if (hasTypeScriptFileExtension(candidate)) { + // Don't allow `.ts` to appear at the end + if (!fileExtensionIs(candidate, ".d.ts")) { + return undefined; + } + return tryFile(candidate, failedLookupLocation, onlyRecordFailures, state); } - // Then try stripping a ".js" or ".jsx" extension and replacing it with a TypeScript one, e.g. "./foo.js" can be matched by "./foo.ts" or "./foo.d.ts" + + // Next, try adding an extension. + // We don't allow an import of "foo.ts" to be matched by "foo.ts.ts", but we do allow "foo.js" to be matched by "foo.js.ts". + const resolvedByAddingExtension = tryAddingExtensions(candidate, extensions, failedLookupLocation, onlyRecordFailures, state); + if (resolvedByAddingExtension) { + return resolvedByAddingExtension; + } + + // If that didn't work, try stripping a ".js" or ".jsx" extension and replacing it with a TypeScript one; + // e.g. "./foo.js" can be matched by "./foo.ts" or "./foo.d.ts" if (hasJavaScriptFileExtension(candidate)) { const extensionless = removeFileExtension(candidate); if (state.traceEnabled) { const extension = candidate.substring(extensionless.length); trace(state.host, Diagnostics.File_name_0_has_a_1_extension_stripping_it, candidate, extension); } - return loadModuleFromFileWorker(extensionless, extensions, failedLookupLocation, onlyRecordFailures, state); + return tryAddingExtensions(extensionless, extensions, failedLookupLocation, onlyRecordFailures, state); } } - function loadModuleFromFileWorker(candidate: string, extensions: string[], failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string { + /** Try to return an existing file that adds one of the `extensions` to `candidate`. */ + function tryAddingExtensions(candidate: string, extensions: string[], failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined { if (!onlyRecordFailures) { // check if containing folder exists - if it doesn't then just record failures for all supported extensions without disk probing const directory = getDirectoryPath(candidate); @@ -693,26 +706,24 @@ namespace ts { onlyRecordFailures = !directoryProbablyExists(directory, state.host); } } - return forEach(extensions, tryLoad); + return forEach(extensions, ext => + !(state.skipTsx && isJsxOrTsxExtension(ext)) && tryFile(candidate + ext, failedLookupLocation, onlyRecordFailures, state)); + } - function tryLoad(ext: string): string { - if (state.skipTsx && isJsxOrTsxExtension(ext)) { - return undefined; + /** Return the file if it exists. */ + function tryFile(fileName: string, failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined { + if (!onlyRecordFailures && state.host.fileExists(fileName)) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.File_0_exist_use_it_as_a_name_resolution_result, fileName); } - const fileName = fileExtensionIs(candidate, ext) ? candidate : candidate + ext; - if (!onlyRecordFailures && state.host.fileExists(fileName)) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.File_0_exist_use_it_as_a_name_resolution_result, fileName); - } - return fileName; - } - else { - if (state.traceEnabled) { - trace(state.host, Diagnostics.File_0_does_not_exist, fileName); - } - failedLookupLocation.push(fileName); - return undefined; + return fileName; + } + else { + if (state.traceEnabled) { + trace(state.host, Diagnostics.File_0_does_not_exist, fileName); } + failedLookupLocation.push(fileName); + return undefined; } } diff --git a/src/harness/unittests/moduleResolution.ts b/src/harness/unittests/moduleResolution.ts index b3f2102d903..0c312f4701a 100644 --- a/src/harness/unittests/moduleResolution.ts +++ b/src/harness/unittests/moduleResolution.ts @@ -465,8 +465,8 @@ export = C; "/a/B/c/moduleB.ts": `import a = require("./moduleC")`, "/a/B/c/moduleC.ts": "export var x", "/a/B/c/moduleD.ts": ` -import a = require("./moduleA.ts"); -import b = require("./moduleB.ts"); +import a = require("./moduleA"); +import b = require("./moduleB"); ` }; test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "/a/B/c", /*useCaseSensitiveFileNames*/ false, ["moduleD.ts"], [1149]); @@ -477,8 +477,8 @@ import b = require("./moduleB.ts"); "/a/B/c/moduleB.ts": `import a = require("./moduleC")`, "/a/B/c/moduleC.ts": "export var x", "/a/B/c/moduleD.ts": ` -import a = require("./moduleA.ts"); -import b = require("./moduleB.ts"); +import a = require("./moduleA"); +import b = require("./moduleB"); ` }; test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "/a/B/c", /*useCaseSensitiveFileNames*/ false, ["moduleD.ts"], []); diff --git a/tests/baselines/reference/missingFunctionImplementation2.errors.txt b/tests/baselines/reference/missingFunctionImplementation2.errors.txt index a695482e1fb..86d8baba6f7 100644 --- a/tests/baselines/reference/missingFunctionImplementation2.errors.txt +++ b/tests/baselines/reference/missingFunctionImplementation2.errors.txt @@ -4,7 +4,7 @@ tests/cases/compiler/missingFunctionImplementation2_b.ts(1,17): error TS2391: Fu ==== tests/cases/compiler/missingFunctionImplementation2_a.ts (1 errors) ==== export {}; - declare module "./missingFunctionImplementation2_b.ts" { + declare module "./missingFunctionImplementation2_b" { export function f(a, b): void; ~ !!! error TS2384: Overload signatures must all be ambient or non-ambient. diff --git a/tests/baselines/reference/missingFunctionImplementation2.js b/tests/baselines/reference/missingFunctionImplementation2.js index 104b6dd9443..7b3456015c8 100644 --- a/tests/baselines/reference/missingFunctionImplementation2.js +++ b/tests/baselines/reference/missingFunctionImplementation2.js @@ -2,7 +2,7 @@ //// [missingFunctionImplementation2_a.ts] export {}; -declare module "./missingFunctionImplementation2_b.ts" { +declare module "./missingFunctionImplementation2_b" { export function f(a, b): void; } diff --git a/tests/baselines/reference/moduleResolutionNoTs.errors.txt b/tests/baselines/reference/moduleResolutionNoTs.errors.txt new file mode 100644 index 00000000000..d1d6270ae13 --- /dev/null +++ b/tests/baselines/reference/moduleResolutionNoTs.errors.txt @@ -0,0 +1,11 @@ +tests/cases/compiler/user.ts(1,15): error TS2307: Cannot find module './m.ts'. + + +==== tests/cases/compiler/m.ts (0 errors) ==== + export default 0; + +==== tests/cases/compiler/user.ts (1 errors) ==== + import x from "./m.ts"; + ~~~~~~~~ +!!! error TS2307: Cannot find module './m.ts'. + \ No newline at end of file diff --git a/tests/baselines/reference/moduleResolutionNoTs.js b/tests/baselines/reference/moduleResolutionNoTs.js new file mode 100644 index 00000000000..2a56947ce19 --- /dev/null +++ b/tests/baselines/reference/moduleResolutionNoTs.js @@ -0,0 +1,15 @@ +//// [tests/cases/compiler/moduleResolutionNoTs.ts] //// + +//// [m.ts] +export default 0; + +//// [user.ts] +import x from "./m.ts"; + + +//// [m.js] +"use strict"; +exports.__esModule = true; +exports["default"] = 0; +//// [user.js] +"use strict"; diff --git a/tests/baselines/reference/moduleResolutionWithExtensions.js b/tests/baselines/reference/moduleResolutionWithExtensions.js index df12a3531bc..16d176872ca 100644 --- a/tests/baselines/reference/moduleResolutionWithExtensions.js +++ b/tests/baselines/reference/moduleResolutionWithExtensions.js @@ -8,10 +8,6 @@ export default 0; //// [b.ts] import a from './a'; -// Matching extension -//// [c.ts] -import a from './a.ts'; - // '.js' extension: stripped and replaced with '.ts' //// [d.ts] import a from './a.js'; @@ -36,9 +32,6 @@ exports["default"] = 0; // No extension: '.ts' added //// [b.js] "use strict"; -// Matching extension -//// [c.js] -"use strict"; // '.js' extension: stripped and replaced with '.ts' //// [d.js] "use strict"; diff --git a/tests/baselines/reference/moduleResolutionWithExtensions.symbols b/tests/baselines/reference/moduleResolutionWithExtensions.symbols index e9934946a64..eaaa1d51cc0 100644 --- a/tests/baselines/reference/moduleResolutionWithExtensions.symbols +++ b/tests/baselines/reference/moduleResolutionWithExtensions.symbols @@ -7,11 +7,6 @@ No type information for this code.=== /src/b.ts === import a from './a'; >a : Symbol(a, Decl(b.ts, 0, 6)) -// Matching extension -=== /src/c.ts === -import a from './a.ts'; ->a : Symbol(a, Decl(c.ts, 0, 6)) - // '.js' extension: stripped and replaced with '.ts' === /src/d.ts === import a from './a.js'; diff --git a/tests/baselines/reference/moduleResolutionWithExtensions.trace.json b/tests/baselines/reference/moduleResolutionWithExtensions.trace.json index 7dc9e8c104b..5060006ac56 100644 --- a/tests/baselines/reference/moduleResolutionWithExtensions.trace.json +++ b/tests/baselines/reference/moduleResolutionWithExtensions.trace.json @@ -5,12 +5,6 @@ "File '/src/a.ts' exist - use it as a name resolution result.", "Resolving real path for '/src/a.ts', result '/src/a.ts'", "======== Module name './a' was successfully resolved to '/src/a.ts'. ========", - "======== Resolving module './a.ts' from '/src/c.ts'. ========", - "Module resolution kind is not specified, using 'NodeJs'.", - "Loading module as file / folder, candidate module location '/src/a.ts'.", - "File '/src/a.ts' exist - use it as a name resolution result.", - "Resolving real path for '/src/a.ts', result '/src/a.ts'", - "======== Module name './a.ts' was successfully resolved to '/src/a.ts'. ========", "======== Resolving module './a.js' from '/src/d.ts'. ========", "Module resolution kind is not specified, using 'NodeJs'.", "Loading module as file / folder, candidate module location '/src/a.js'.", diff --git a/tests/baselines/reference/moduleResolutionWithExtensions.types b/tests/baselines/reference/moduleResolutionWithExtensions.types index fbc0091ee3b..f59eda1a81e 100644 --- a/tests/baselines/reference/moduleResolutionWithExtensions.types +++ b/tests/baselines/reference/moduleResolutionWithExtensions.types @@ -7,11 +7,6 @@ No type information for this code.=== /src/b.ts === import a from './a'; >a : number -// Matching extension -=== /src/c.ts === -import a from './a.ts'; ->a : number - // '.js' extension: stripped and replaced with '.ts' === /src/d.ts === import a from './a.js'; diff --git a/tests/baselines/reference/staticInstanceResolution5.errors.txt b/tests/baselines/reference/staticInstanceResolution5.errors.txt index da6f0fde143..f36851b2b97 100644 --- a/tests/baselines/reference/staticInstanceResolution5.errors.txt +++ b/tests/baselines/reference/staticInstanceResolution5.errors.txt @@ -4,7 +4,7 @@ tests/cases/compiler/staticInstanceResolution5_1.ts(6,16): error TS2304: Cannot ==== tests/cases/compiler/staticInstanceResolution5_1.ts (3 errors) ==== - import WinJS = require('staticInstanceResolution5_0.ts'); + import WinJS = require('staticInstanceResolution5_0'); // these 3 should be errors var x = (w1: WinJS) => { }; diff --git a/tests/baselines/reference/staticInstanceResolution5.js b/tests/baselines/reference/staticInstanceResolution5.js index 0809d1af5f0..b3514e00aad 100644 --- a/tests/baselines/reference/staticInstanceResolution5.js +++ b/tests/baselines/reference/staticInstanceResolution5.js @@ -8,7 +8,7 @@ export class Promise { } //// [staticInstanceResolution5_1.ts] -import WinJS = require('staticInstanceResolution5_0.ts'); +import WinJS = require('staticInstanceResolution5_0'); // these 3 should be errors var x = (w1: WinJS) => { }; diff --git a/tests/cases/compiler/missingFunctionImplementation2.ts b/tests/cases/compiler/missingFunctionImplementation2.ts index 25909b6add4..1717bc663f2 100644 --- a/tests/cases/compiler/missingFunctionImplementation2.ts +++ b/tests/cases/compiler/missingFunctionImplementation2.ts @@ -1,6 +1,6 @@ // @Filename: missingFunctionImplementation2_a.ts export {}; -declare module "./missingFunctionImplementation2_b.ts" { +declare module "./missingFunctionImplementation2_b" { export function f(a, b): void; } diff --git a/tests/cases/compiler/moduleResolutionNoTs.ts b/tests/cases/compiler/moduleResolutionNoTs.ts new file mode 100644 index 00000000000..18b042d905f --- /dev/null +++ b/tests/cases/compiler/moduleResolutionNoTs.ts @@ -0,0 +1,5 @@ +// @filename: m.ts +export default 0; + +// @filename: user.ts +import x from "./m.ts"; diff --git a/tests/cases/compiler/staticInstanceResolution5.ts b/tests/cases/compiler/staticInstanceResolution5.ts index 3adb8667be5..ed34dbd535f 100644 --- a/tests/cases/compiler/staticInstanceResolution5.ts +++ b/tests/cases/compiler/staticInstanceResolution5.ts @@ -7,7 +7,7 @@ export class Promise { } // @Filename: staticInstanceResolution5_1.ts -import WinJS = require('staticInstanceResolution5_0.ts'); +import WinJS = require('staticInstanceResolution5_0'); // these 3 should be errors var x = (w1: WinJS) => { }; diff --git a/tests/cases/conformance/externalModules/moduleResolutionWithExtensions.ts b/tests/cases/conformance/externalModules/moduleResolutionWithExtensions.ts index 6ad35658623..429289546c5 100644 --- a/tests/cases/conformance/externalModules/moduleResolutionWithExtensions.ts +++ b/tests/cases/conformance/externalModules/moduleResolutionWithExtensions.ts @@ -7,10 +7,6 @@ export default 0; // @Filename: /src/b.ts import a from './a'; -// Matching extension -// @Filename: /src/c.ts -import a from './a.ts'; - // '.js' extension: stripped and replaced with '.ts' // @Filename: /src/d.ts import a from './a.js';