diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 8175730159b0b..f647480979b3f 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2210,6 +2210,13 @@ namespace ts { * Path must have a valid extension. */ export function extensionFromPath(path: string): Extension { + const ext = tryGetExtensionFromPath(path); + if (ext !== undefined) { + return ext; + } + Debug.fail(`File ${path} has unknown extension.`); + } + export function tryGetExtensionFromPath(path: string): Extension | undefined { if (fileExtensionIs(path, ".d.ts")) { return Extension.Dts; } @@ -2225,7 +2232,5 @@ namespace ts { if (fileExtensionIs(path, ".jsx")) { return Extension.Jsx; } - Debug.fail(`File ${path} has unknown extension.`); - return Extension.Js; } } diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index b467278cf334d..2d7f1277776a1 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -515,18 +515,21 @@ namespace ts { if (state.traceEnabled) { trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText); } - for (const subst of state.compilerOptions.paths[matchedPatternText]) { + return forEach(state.compilerOptions.paths[matchedPatternText], subst => { const path = matchedStar ? subst.replace("*", matchedStar) : subst; const candidate = normalizePath(combinePaths(state.compilerOptions.baseUrl, path)); if (state.traceEnabled) { trace(state.host, Diagnostics.Trying_substitution_0_candidate_module_location_Colon_1, subst, path); } - const resolved = loader(extensions, candidate, failedLookupLocations, !directoryProbablyExists(getDirectoryPath(candidate), state.host), state); - if (resolved) { - return resolved; + // A path mapping may have a ".ts" extension; in contrast to an import, which should omit it. + const tsExtension = tryGetExtensionFromPath(candidate); + if (tsExtension !== undefined) { + const path = tryFile(candidate, failedLookupLocations, /*onlyRecordFailures*/false, state); + return path && { path, extension: tsExtension }; } - } - return undefined; + + return loader(extensions, candidate, failedLookupLocations, !directoryProbablyExists(getDirectoryPath(candidate), state.host), state); + }); } else { const candidate = normalizePath(combinePaths(state.compilerOptions.baseUrl, moduleName)); diff --git a/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension.js b/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension.js new file mode 100644 index 0000000000000..06f6b50e4a003 --- /dev/null +++ b/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension.js @@ -0,0 +1,24 @@ +//// [tests/cases/compiler/pathMappingBasedModuleResolution_withExtension.ts] //// + +//// [foo.ts] + +export function foo() {} + +//// [bar.js] +export function bar() {} + +//// [a.ts] +import { foo } from "foo"; +import { bar } from "bar"; + + +//// [foo.js] +"use strict"; +function foo() { } +exports.foo = foo; +//// [bar.js] +"use strict"; +function bar() { } +exports.bar = bar; +//// [a.js] +"use strict"; diff --git a/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension.symbols b/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension.symbols new file mode 100644 index 0000000000000..717660067ecc0 --- /dev/null +++ b/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension.symbols @@ -0,0 +1,16 @@ +=== /a.ts === +import { foo } from "foo"; +>foo : Symbol(foo, Decl(a.ts, 0, 8)) + +import { bar } from "bar"; +>bar : Symbol(bar, Decl(a.ts, 1, 8)) + +=== /foo/foo.ts === + +export function foo() {} +>foo : Symbol(foo, Decl(foo.ts, 0, 0)) + +=== /bar/bar.js === +export function bar() {} +>bar : Symbol(bar, Decl(bar.js, 0, 0)) + diff --git a/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension.trace.json b/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension.trace.json new file mode 100644 index 0000000000000..a761414eb4226 --- /dev/null +++ b/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension.trace.json @@ -0,0 +1,18 @@ +[ + "======== Resolving module 'foo' from '/a.ts'. ========", + "Module resolution kind is not specified, using 'NodeJs'.", + "'baseUrl' option is set to '/', using this value to resolve non-relative module name 'foo'", + "'paths' option is specified, looking for a pattern to match module name 'foo'.", + "Module name 'foo', matched pattern 'foo'.", + "Trying substitution 'foo/foo.ts', candidate module location: 'foo/foo.ts'.", + "File '/foo/foo.ts' exist - use it as a name resolution result.", + "======== Module name 'foo' was successfully resolved to '/foo/foo.ts'. ========", + "======== Resolving module 'bar' from '/a.ts'. ========", + "Module resolution kind is not specified, using 'NodeJs'.", + "'baseUrl' option is set to '/', using this value to resolve non-relative module name 'bar'", + "'paths' option is specified, looking for a pattern to match module name 'bar'.", + "Module name 'bar', matched pattern 'bar'.", + "Trying substitution 'bar/bar.js', candidate module location: 'bar/bar.js'.", + "File '/bar/bar.js' exist - use it as a name resolution result.", + "======== Module name 'bar' was successfully resolved to '/bar/bar.js'. ========" +] \ No newline at end of file diff --git a/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension.types b/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension.types new file mode 100644 index 0000000000000..8d7c57b1d2701 --- /dev/null +++ b/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension.types @@ -0,0 +1,16 @@ +=== /a.ts === +import { foo } from "foo"; +>foo : () => void + +import { bar } from "bar"; +>bar : () => void + +=== /foo/foo.ts === + +export function foo() {} +>foo : () => void + +=== /bar/bar.js === +export function bar() {} +>bar : () => void + diff --git a/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension_failedLookup.errors.txt b/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension_failedLookup.errors.txt new file mode 100644 index 0000000000000..7578f730c334c --- /dev/null +++ b/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension_failedLookup.errors.txt @@ -0,0 +1,9 @@ +/a.ts(2,21): error TS2307: Cannot find module 'foo'. + + +==== /a.ts (1 errors) ==== + + import { foo } from "foo"; + ~~~~~ +!!! error TS2307: Cannot find module 'foo'. + \ No newline at end of file diff --git a/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension_failedLookup.js b/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension_failedLookup.js new file mode 100644 index 0000000000000..1774876911f26 --- /dev/null +++ b/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension_failedLookup.js @@ -0,0 +1,7 @@ +//// [a.ts] + +import { foo } from "foo"; + + +//// [a.js] +"use strict"; diff --git a/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension_failedLookup.trace.json b/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension_failedLookup.trace.json new file mode 100644 index 0000000000000..face88d225e3f --- /dev/null +++ b/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension_failedLookup.trace.json @@ -0,0 +1,32 @@ +[ + "======== Resolving module 'foo' from '/a.ts'. ========", + "Module resolution kind is not specified, using 'NodeJs'.", + "'baseUrl' option is set to '/', using this value to resolve non-relative module name 'foo'", + "'paths' option is specified, looking for a pattern to match module name 'foo'.", + "Module name 'foo', matched pattern 'foo'.", + "Trying substitution 'foo/foo.ts', candidate module location: 'foo/foo.ts'.", + "File '/foo/foo.ts' does not exist.", + "Loading module 'foo' from 'node_modules' folder.", + "File '/node_modules/foo.ts' does not exist.", + "File '/node_modules/foo.tsx' does not exist.", + "File '/node_modules/foo.d.ts' does not exist.", + "File '/node_modules/foo/package.json' does not exist.", + "File '/node_modules/foo/index.ts' does not exist.", + "File '/node_modules/foo/index.tsx' does not exist.", + "File '/node_modules/foo/index.d.ts' does not exist.", + "File '/node_modules/@types/foo.d.ts' does not exist.", + "File '/node_modules/@types/foo/package.json' does not exist.", + "File '/node_modules/@types/foo/index.d.ts' does not exist.", + "'baseUrl' option is set to '/', using this value to resolve non-relative module name 'foo'", + "'paths' option is specified, looking for a pattern to match module name 'foo'.", + "Module name 'foo', matched pattern 'foo'.", + "Trying substitution 'foo/foo.ts', candidate module location: 'foo/foo.ts'.", + "File '/foo/foo.ts' does not exist.", + "Loading module 'foo' from 'node_modules' folder.", + "File '/node_modules/foo.js' does not exist.", + "File '/node_modules/foo.jsx' does not exist.", + "File '/node_modules/foo/package.json' does not exist.", + "File '/node_modules/foo/index.js' does not exist.", + "File '/node_modules/foo/index.jsx' does not exist.", + "======== Module name 'foo' was not resolved. ========" +] \ No newline at end of file diff --git a/tests/cases/compiler/pathMappingBasedModuleResolution_withExtension.ts b/tests/cases/compiler/pathMappingBasedModuleResolution_withExtension.ts new file mode 100644 index 0000000000000..e40c67e4ea38c --- /dev/null +++ b/tests/cases/compiler/pathMappingBasedModuleResolution_withExtension.ts @@ -0,0 +1,26 @@ +// @noImplicitReferences: true +// @traceResolution: true +// @allowJs: true + +// @Filename: /foo/foo.ts +export function foo() {} + +// @Filename: /bar/bar.js +export function bar() {} + +// @Filename: /a.ts +import { foo } from "foo"; +import { bar } from "bar"; + +// @Filename: /tsconfig.json +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "foo": ["foo/foo.ts"], + "bar": ["bar/bar.js"] + }, + "allowJs": true, + "outDir": "bin" + } +} diff --git a/tests/cases/compiler/pathMappingBasedModuleResolution_withExtension_failedLookup.ts b/tests/cases/compiler/pathMappingBasedModuleResolution_withExtension_failedLookup.ts new file mode 100644 index 0000000000000..a983b6c482503 --- /dev/null +++ b/tests/cases/compiler/pathMappingBasedModuleResolution_withExtension_failedLookup.ts @@ -0,0 +1,15 @@ +// @noImplicitReferences: true +// @traceResolution: true + +// @Filename: /a.ts +import { foo } from "foo"; + +// @Filename: /tsconfig.json +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "foo": ["foo/foo.ts"] + } + } +}