diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 79b6249539d..8a2040a83df 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -849,13 +849,22 @@ namespace ts { const extensionsToRemove = [".d.ts", ".ts", ".js", ".tsx", ".jsx"]; export function removeFileExtension(path: string): string { for (const ext of extensionsToRemove) { - if (fileExtensionIs(path, ext)) { - return path.substr(0, path.length - ext.length); + const extensionless = tryRemoveExtension(path, ext); + if (extensionless !== undefined) { + return extensionless; } } return path; } + export function tryRemoveExtension(path: string, extension: string): string { + return fileExtensionIs(path, extension) ? path.substring(0, path.length - extension.length) : undefined; + } + + export function isJsxOrTsxExtension(ext: string): boolean { + return ext === ".jsx" || ext === ".tsx"; + } + export interface ObjectAllocator { getNodeConstructor(): new (kind: SyntaxKind, pos?: number, end?: number) => Node; getSourceFileConstructor(): new (kind: SyntaxKind, pos?: number, end?: number) => SourceFile; diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 034bd27b147..44062885677 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2772,6 +2772,10 @@ "category": "Error", "code": 6131 }, + "File name '{0}' has a '{1}' extension - stripping it": { + "category": "Message", + "code": 6132 + }, "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 5b64d066da1..abc3652abe3 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -619,8 +619,25 @@ namespace ts { * 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; + } + // 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" + 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); + } + } + + function loadModuleFromFileWorker(candidate: string, extensions: string[], failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string { if (!onlyRecordFailures) { - // check if containig folder exists - if it doesn't then just record failures for all supported extensions without disk probing + // check if containing folder exists - if it doesn't then just record failures for all supported extensions without disk probing const directory = getDirectoryPath(candidate); if (directory) { onlyRecordFailures = !directoryProbablyExists(directory, state.host); @@ -629,7 +646,7 @@ namespace ts { return forEach(extensions, tryLoad); function tryLoad(ext: string): string { - if (ext === ".tsx" && state.skipTsx) { + if (state.skipTsx && isJsxOrTsxExtension(ext)) { return undefined; } const fileName = fileExtensionIs(candidate, ext) ? candidate : candidate + ext; diff --git a/tests/baselines/reference/moduleResolutionWithExtensions.js b/tests/baselines/reference/moduleResolutionWithExtensions.js new file mode 100644 index 00000000000..df12a3531bc --- /dev/null +++ b/tests/baselines/reference/moduleResolutionWithExtensions.js @@ -0,0 +1,46 @@ +//// [tests/cases/conformance/externalModules/moduleResolutionWithExtensions.ts] //// + +//// [a.ts] + +export default 0; + +// No extension: '.ts' added +//// [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'; + +//// [jquery.d.ts] +declare var x: number; +export default x; + +// No extension: '.d.ts' added +//// [jquery_user_1.ts] +import j from "./jquery"; + +// '.js' extension: stripped and replaced with '.d.ts' +//// [jquery_user_1.ts] +import j from "./jquery.js" + + +//// [a.js] +"use strict"; +exports.__esModule = true; +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"; +//// [jquery_user_1.js] +"use strict"; diff --git a/tests/baselines/reference/moduleResolutionWithExtensions.symbols b/tests/baselines/reference/moduleResolutionWithExtensions.symbols new file mode 100644 index 00000000000..e9934946a64 --- /dev/null +++ b/tests/baselines/reference/moduleResolutionWithExtensions.symbols @@ -0,0 +1,37 @@ +=== /src/a.ts === + +No type information for this code.export default 0; +No type information for this code. +No type information for this code.// No extension: '.ts' added +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'; +>a : Symbol(a, Decl(d.ts, 0, 6)) + +=== /src/jquery.d.ts === +declare var x: number; +>x : Symbol(x, Decl(jquery.d.ts, 0, 11)) + +export default x; +>x : Symbol(x, Decl(jquery.d.ts, 0, 11)) + +// No extension: '.d.ts' added +=== /src/jquery_user_1.ts === +import j from "./jquery"; +>j : Symbol(j, Decl(jquery_user_1.ts, 0, 6)) + +// '.js' extension: stripped and replaced with '.d.ts' +=== /src/jquery_user_1.ts === +import j from "./jquery.js" +>j : Symbol(j, Decl(jquery_user_1.ts, 0, 6)) +>j : Symbol(j, Decl(jquery_user_1.ts, 0, 6)) + diff --git a/tests/baselines/reference/moduleResolutionWithExtensions.trace.json b/tests/baselines/reference/moduleResolutionWithExtensions.trace.json new file mode 100644 index 00000000000..6e6589d7c1f --- /dev/null +++ b/tests/baselines/reference/moduleResolutionWithExtensions.trace.json @@ -0,0 +1,32 @@ +[ + "======== Resolving module './a' from '/src/b.ts'. ========", + "Module resolution kind is not specified, using 'NodeJs'.", + "Loading module as file / folder, candidate module location '/src/a'.", + "File '/src/a.ts' exist - use it as a name resolution result.", + "======== 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.", + "======== 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'.", + "File '/src/a.js.ts' does not exist.", + "File '/src/a.js.tsx' does not exist.", + "File '/src/a.js.d.ts' does not exist.", + "File name '/src/a.js' has a '.js' extension - stripping it", + "File '/src/a.ts' exist - use it as a name resolution result.", + "======== Module name './a.js' was successfully resolved to '/src/a.ts'. ========", + "======== Resolving module './jquery.js' from '/src/jquery_user_1.ts'. ========", + "Module resolution kind is not specified, using 'NodeJs'.", + "Loading module as file / folder, candidate module location '/src/jquery.js'.", + "File '/src/jquery.js.ts' does not exist.", + "File '/src/jquery.js.tsx' does not exist.", + "File '/src/jquery.js.d.ts' does not exist.", + "File name '/src/jquery.js' has a '.js' extension - stripping it", + "File '/src/jquery.ts' does not exist.", + "File '/src/jquery.tsx' does not exist.", + "File '/src/jquery.d.ts' exist - use it as a name resolution result.", + "======== Module name './jquery.js' was successfully resolved to '/src/jquery.d.ts'. ========" +] \ No newline at end of file diff --git a/tests/baselines/reference/moduleResolutionWithExtensions.types b/tests/baselines/reference/moduleResolutionWithExtensions.types new file mode 100644 index 00000000000..fbc0091ee3b --- /dev/null +++ b/tests/baselines/reference/moduleResolutionWithExtensions.types @@ -0,0 +1,37 @@ +=== /src/a.ts === + +No type information for this code.export default 0; +No type information for this code. +No type information for this code.// No extension: '.ts' added +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'; +>a : number + +=== /src/jquery.d.ts === +declare var x: number; +>x : number + +export default x; +>x : number + +// No extension: '.d.ts' added +=== /src/jquery_user_1.ts === +import j from "./jquery"; +>j : number + +// '.js' extension: stripped and replaced with '.d.ts' +=== /src/jquery_user_1.ts === +import j from "./jquery.js" +>j : number +>j : number + diff --git a/tests/baselines/reference/nameWithFileExtension.errors.txt b/tests/baselines/reference/nameWithFileExtension.errors.txt deleted file mode 100644 index 2aa341271cd..00000000000 --- a/tests/baselines/reference/nameWithFileExtension.errors.txt +++ /dev/null @@ -1,12 +0,0 @@ -tests/cases/conformance/externalModules/foo_1.ts(1,22): error TS2307: Cannot find module './foo_0.js'. - - -==== tests/cases/conformance/externalModules/foo_1.ts (1 errors) ==== - import foo = require('./foo_0.js'); - ~~~~~~~~~~~~ -!!! error TS2307: Cannot find module './foo_0.js'. - var x = foo.foo + 42; - -==== tests/cases/conformance/externalModules/foo_0.ts (0 errors) ==== - export var foo = 42; - \ No newline at end of file diff --git a/tests/baselines/reference/nameWithFileExtension.js b/tests/baselines/reference/nameWithFileExtension.js index 2d487ede245..8a7506c2d42 100644 --- a/tests/baselines/reference/nameWithFileExtension.js +++ b/tests/baselines/reference/nameWithFileExtension.js @@ -8,6 +8,9 @@ import foo = require('./foo_0.js'); var x = foo.foo + 42; +//// [foo_0.js] +"use strict"; +exports.foo = 42; //// [foo_1.js] "use strict"; var foo = require('./foo_0.js'); diff --git a/tests/baselines/reference/nameWithFileExtension.symbols b/tests/baselines/reference/nameWithFileExtension.symbols new file mode 100644 index 00000000000..61e6a768ea6 --- /dev/null +++ b/tests/baselines/reference/nameWithFileExtension.symbols @@ -0,0 +1,14 @@ +=== tests/cases/conformance/externalModules/foo_1.ts === +import foo = require('./foo_0.js'); +>foo : Symbol(foo, Decl(foo_1.ts, 0, 0)) + +var x = foo.foo + 42; +>x : Symbol(x, Decl(foo_1.ts, 1, 3)) +>foo.foo : Symbol(foo.foo, Decl(foo_0.ts, 0, 10)) +>foo : Symbol(foo, Decl(foo_1.ts, 0, 0)) +>foo : Symbol(foo.foo, Decl(foo_0.ts, 0, 10)) + +=== tests/cases/conformance/externalModules/foo_0.ts === +export var foo = 42; +>foo : Symbol(foo, Decl(foo_0.ts, 0, 10)) + diff --git a/tests/baselines/reference/nameWithFileExtension.types b/tests/baselines/reference/nameWithFileExtension.types new file mode 100644 index 00000000000..a0fca3d4562 --- /dev/null +++ b/tests/baselines/reference/nameWithFileExtension.types @@ -0,0 +1,17 @@ +=== tests/cases/conformance/externalModules/foo_1.ts === +import foo = require('./foo_0.js'); +>foo : typeof foo + +var x = foo.foo + 42; +>x : number +>foo.foo + 42 : number +>foo.foo : number +>foo : typeof foo +>foo : number +>42 : number + +=== tests/cases/conformance/externalModules/foo_0.ts === +export var foo = 42; +>foo : number +>42 : number + diff --git a/tests/baselines/reference/project/cantFindTheModule/amd/cantFindTheModule.errors.txt b/tests/baselines/reference/project/cantFindTheModule/amd/cantFindTheModule.errors.txt index 7c071fdc208..e9f8842d5cb 100644 --- a/tests/baselines/reference/project/cantFindTheModule/amd/cantFindTheModule.errors.txt +++ b/tests/baselines/reference/project/cantFindTheModule/amd/cantFindTheModule.errors.txt @@ -1,12 +1,12 @@ -decl.ts(1,26): error TS2307: Cannot find module './foo/bar.js'. +decl.ts(1,26): error TS2307: Cannot find module './foo/bar.tx'. decl.ts(2,26): error TS2307: Cannot find module 'baz'. decl.ts(3,26): error TS2307: Cannot find module './baz'. ==== decl.ts (3 errors) ==== - import modErr = require("./foo/bar.js"); + import modErr = require("./foo/bar.tx"); ~~~~~~~~~~~~~~ -!!! error TS2307: Cannot find module './foo/bar.js'. +!!! error TS2307: Cannot find module './foo/bar.tx'. import modErr1 = require("baz"); ~~~~~ !!! error TS2307: Cannot find module 'baz'. diff --git a/tests/baselines/reference/project/cantFindTheModule/node/cantFindTheModule.errors.txt b/tests/baselines/reference/project/cantFindTheModule/node/cantFindTheModule.errors.txt index 7c071fdc208..e9f8842d5cb 100644 --- a/tests/baselines/reference/project/cantFindTheModule/node/cantFindTheModule.errors.txt +++ b/tests/baselines/reference/project/cantFindTheModule/node/cantFindTheModule.errors.txt @@ -1,12 +1,12 @@ -decl.ts(1,26): error TS2307: Cannot find module './foo/bar.js'. +decl.ts(1,26): error TS2307: Cannot find module './foo/bar.tx'. decl.ts(2,26): error TS2307: Cannot find module 'baz'. decl.ts(3,26): error TS2307: Cannot find module './baz'. ==== decl.ts (3 errors) ==== - import modErr = require("./foo/bar.js"); + import modErr = require("./foo/bar.tx"); ~~~~~~~~~~~~~~ -!!! error TS2307: Cannot find module './foo/bar.js'. +!!! error TS2307: Cannot find module './foo/bar.tx'. import modErr1 = require("baz"); ~~~~~ !!! error TS2307: Cannot find module 'baz'. diff --git a/tests/cases/conformance/externalModules/moduleResolutionWithExtensions.ts b/tests/cases/conformance/externalModules/moduleResolutionWithExtensions.ts new file mode 100644 index 00000000000..6ad35658623 --- /dev/null +++ b/tests/cases/conformance/externalModules/moduleResolutionWithExtensions.ts @@ -0,0 +1,28 @@ +// @traceResolution: true + +// @Filename: /src/a.ts +export default 0; + +// No extension: '.ts' added +// @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'; + +// @Filename: /src/jquery.d.ts +declare var x: number; +export default x; + +// No extension: '.d.ts' added +// @Filename: /src/jquery_user_1.ts +import j from "./jquery"; + +// '.js' extension: stripped and replaced with '.d.ts' +// @Filename: /src/jquery_user_1.ts +import j from "./jquery.js" diff --git a/tests/cases/projects/NoModule/decl.ts b/tests/cases/projects/NoModule/decl.ts index a8a29852ac2..064f777c551 100644 --- a/tests/cases/projects/NoModule/decl.ts +++ b/tests/cases/projects/NoModule/decl.ts @@ -1,4 +1,4 @@ -import modErr = require("./foo/bar.js"); +import modErr = require("./foo/bar.tx"); import modErr1 = require("baz"); import modErr2 = require("./baz");