diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 79b6249539dbc..8a2040a83df07 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 af8dd8ceb6971..373e5fc3388c1 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2768,6 +2768,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 bab554927e726..5440b1a782b2d 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/src/harness/harness.ts b/src/harness/harness.ts index d6959c2a54fdd..56d66e753969b 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1,7 +1,7 @@ // // Copyright (c) Microsoft Corporation. All rights reserved. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -350,7 +350,7 @@ namespace Utils { assert.equal(node1.end, node2.end, "node1.end !== node2.end"); assert.equal(node1.kind, node2.kind, "node1.kind !== node2.kind"); - // call this on both nodes to ensure all propagated flags have been set (and thus can be + // call this on both nodes to ensure all propagated flags have been set (and thus can be // compared). assert.equal(ts.containsParseError(node1), ts.containsParseError(node2)); assert.equal(node1.flags, node2.flags, "node1.flags !== node2.flags"); @@ -751,7 +751,7 @@ namespace Harness { (emittedFile: string, emittedLine: number, emittedColumn: number, sourceFile: string, sourceLine: number, sourceColumn: number, sourceName: string): void; } - // Settings + // Settings export let userSpecifiedRoot = ""; export let lightMode = false; @@ -790,7 +790,7 @@ namespace Harness { fileName: string, sourceText: string, languageVersion: ts.ScriptTarget) { - // We'll only assert invariants outside of light mode. + // We'll only assert invariants outside of light mode. const shouldAssertInvariants = !Harness.lightMode; // Only set the parent nodes if we're asserting invariants. We don't need them otherwise. @@ -935,7 +935,7 @@ namespace Harness { libFiles?: string; } - // Additional options not already in ts.optionDeclarations + // Additional options not already in ts.optionDeclarations const harnessOptionDeclarations: ts.CommandLineOption[] = [ { name: "allowNonTsExtensions", type: "boolean" }, { name: "useCaseSensitiveFileNames", type: "boolean" }, @@ -1187,7 +1187,7 @@ namespace Harness { errLines.forEach(e => outputLines.push(e)); // do not count errors from lib.d.ts here, they are computed separately as numLibraryDiagnostics - // if lib.d.ts is explicitly included in input files and there are some errors in it (i.e. because of duplicate identifiers) + // if lib.d.ts is explicitly included in input files and there are some errors in it (i.e. because of duplicate identifiers) // then they will be added twice thus triggering 'total errors' assertion with condition // 'totalErrorsReportedInNonLibraryFiles + numLibraryDiagnostics + numTest262HarnessDiagnostics, diagnostics.length @@ -1497,7 +1497,7 @@ namespace Harness { }; testUnitData.push(newTestFile2); - // unit tests always list files explicitly + // unit tests always list files explicitly const parseConfigHost: ts.ParseConfigHost = { readDirectory: (name) => [] }; diff --git a/tests/baselines/reference/moduleResolutionWithExtensions.js b/tests/baselines/reference/moduleResolutionWithExtensions.js new file mode 100644 index 0000000000000..df12a3531bcfa --- /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 0000000000000..e9934946a64fd --- /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 0000000000000..6e6589d7c1f8a --- /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 0000000000000..fbc0091ee3b95 --- /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 2aa341271cddb..0000000000000 --- 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 2d487ede24572..8a7506c2d4298 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 0000000000000..61e6a768ea6aa --- /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 0000000000000..a0fca3d456219 --- /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 7c071fdc208c2..e9f8842d5cbb5 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 7c071fdc208c2..e9f8842d5cbb5 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 0000000000000..6ad3565862337 --- /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 a8a29852ac267..064f777c5512e 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");