diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index be8f4d6f0cb46..125c4f91f0edd 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -1,4 +1,4 @@ -/// +/// /// namespace ts { @@ -76,13 +76,7 @@ namespace ts { return tryReadFromField("typings") || tryReadFromField("types"); case Extensions.JavaScript: - if (typeof jsonContent.main === "string") { - if (state.traceEnabled) { - trace(state.host, Diagnostics.No_types_specified_in_package_json_so_returning_main_value_of_0, jsonContent.main); - } - return normalizePath(combinePaths(baseDirectory, jsonContent.main)); - } - return undefined; + return tryReadFromField("main"); } function tryReadFromField(fieldName: string) { @@ -830,6 +824,16 @@ namespace ts { if (resolved) { return resolved; } + + if (extensions === Extensions.JavaScript) { + // A package.json "main" may specify an exact filename, or may choose to omit an extension. + // We tried the ts extensions erlier, now try the js extensions. + // tryReadPackageJsonMainOrTypes returns main iff extensions is Extensions.JavaScript. + const resolved = tryAddingExtensions(mainOrTypesFile, Extensions.JavaScript, failedLookupLocations, onlyRecordFailures, state); + if (resolved) { + return resolved; + } + } } else { if (state.traceEnabled) { diff --git a/src/harness/unittests/moduleResolution.ts b/src/harness/unittests/moduleResolution.ts index 35313e153082c..88594dc6f0594 100644 --- a/src/harness/unittests/moduleResolution.ts +++ b/src/harness/unittests/moduleResolution.ts @@ -1,4 +1,4 @@ -/// +/// namespace ts { export function checkResolvedModule(expected: ResolvedModuleFull, actual: ResolvedModuleFull): boolean { @@ -408,7 +408,7 @@ export = C; "/a/b/c.ts": `/// `, "/a/b/d.ts": "var x" }); - test(files, { module: ts.ModuleKind.AMD }, "/a/b", /*useCaseSensitiveFileNames*/ false, ["c.ts", "/a/b/d.ts"], []); + test(files, { module: ts.ModuleKind.AMD }, "/a/b", /*useCaseSensitiveFileNames*/ false, ["c.ts", "/a/b/d.ts"], []); }); it("should fail when two files used in program differ only in casing (tripleslash references)", () => { @@ -416,7 +416,7 @@ export = C; "/a/b/c.ts": `/// `, "/a/b/d.ts": "var x" }); - test(files, { module: ts.ModuleKind.AMD, forceConsistentCasingInFileNames: true }, "/a/b", /*useCaseSensitiveFileNames*/ false, ["c.ts", "d.ts"], [1149]); + test(files, { module: ts.ModuleKind.AMD, forceConsistentCasingInFileNames: true }, "/a/b", /*useCaseSensitiveFileNames*/ false, ["c.ts", "d.ts"], [1149]); }); it("should fail when two files used in program differ only in casing (imports)", () => { @@ -424,7 +424,7 @@ export = C; "/a/b/c.ts": `import {x} from "D"`, "/a/b/d.ts": "export var x" }); - test(files, { module: ts.ModuleKind.AMD, forceConsistentCasingInFileNames: true }, "/a/b", /*useCaseSensitiveFileNames*/ false, ["c.ts", "d.ts"], [1149]); + test(files, { module: ts.ModuleKind.AMD, forceConsistentCasingInFileNames: true }, "/a/b", /*useCaseSensitiveFileNames*/ false, ["c.ts", "d.ts"], [1149]); }); it("should fail when two files used in program differ only in casing (imports, relative module names)", () => { @@ -432,7 +432,7 @@ export = C; "moduleA.ts": `import {x} from "./ModuleB"`, "moduleB.ts": "export var x" }); - test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "", /*useCaseSensitiveFileNames*/ false, ["moduleA.ts", "moduleB.ts"], [1149]); + test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "", /*useCaseSensitiveFileNames*/ false, ["moduleA.ts", "moduleB.ts"], [1149]); }); it("should fail when two files exist on disk that differs only in casing", () => { @@ -441,7 +441,7 @@ export = C; "/a/b/D.ts": "export var x", "/a/b/d.ts": "export var y" }); - test(files, { module: ts.ModuleKind.AMD }, "/a/b", /*useCaseSensitiveFileNames*/ true, ["c.ts", "d.ts"], [1149]); + test(files, { module: ts.ModuleKind.AMD }, "/a/b", /*useCaseSensitiveFileNames*/ true, ["c.ts", "d.ts"], [1149]); }); it("should fail when module name in 'require' calls has inconsistent casing", () => { @@ -450,7 +450,7 @@ export = C; "moduleB.ts": `import a = require("./moduleC")`, "moduleC.ts": "export var x" }); - test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "", /*useCaseSensitiveFileNames*/ false, ["moduleA.ts", "moduleB.ts", "moduleC.ts"], [1149, 1149]); + test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "", /*useCaseSensitiveFileNames*/ false, ["moduleA.ts", "moduleB.ts", "moduleC.ts"], [1149, 1149]); }); it("should fail when module names in 'require' calls has inconsistent casing and current directory has uppercase chars", () => { @@ -463,7 +463,7 @@ import a = require("./moduleA"); import b = require("./moduleB"); ` }); - test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "/a/B/c", /*useCaseSensitiveFileNames*/ false, ["moduleD.ts"], [1149]); + test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "/a/B/c", /*useCaseSensitiveFileNames*/ false, ["moduleD.ts"], [1149]); }); it("should not fail when module names in 'require' calls has consistent casing and current directory has uppercase chars", () => { const files = createMap({ @@ -475,7 +475,7 @@ import a = require("./moduleA"); import b = require("./moduleB"); ` }); - test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "/a/B/c", /*useCaseSensitiveFileNames*/ false, ["moduleD.ts"], []); + test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "/a/B/c", /*useCaseSensitiveFileNames*/ false, ["moduleD.ts"], []); }); }); @@ -490,7 +490,7 @@ import b = require("./moduleB"); const file2: File = { name: "/root/folder2/file2.ts" }; const file3: File = { name: "/root/folder2/file3.ts" }; const host = createModuleResolutionHost(hasDirectoryExists, file1, file2, file3); - for (const moduleResolution of [ ModuleResolutionKind.NodeJs, ModuleResolutionKind.Classic ]) { + for (const moduleResolution of [ModuleResolutionKind.NodeJs, ModuleResolutionKind.Classic]) { const options: CompilerOptions = { moduleResolution, baseUrl: "/root" }; { const result = resolveModuleName("folder2/file2", file1.name, options, host); @@ -703,7 +703,7 @@ import b = require("./moduleB"); } }); - it ("classic + baseUrl + path mappings", () => { + it("classic + baseUrl + path mappings", () => { // classic mode does not use directoryExists test(/*hasDirectoryExists*/ false); @@ -762,7 +762,7 @@ import b = require("./moduleB"); } }); - it ("node + rootDirs", () => { + it("node + rootDirs", () => { test(/*hasDirectoryExists*/ false); test(/*hasDirectoryExists*/ true); @@ -835,7 +835,7 @@ import b = require("./moduleB"); } }); - it ("classic + rootDirs", () => { + it("classic + rootDirs", () => { test(/*hasDirectoryExists*/ false); function test(hasDirectoryExists: boolean) { @@ -889,7 +889,7 @@ import b = require("./moduleB"); } }); - it ("nested node module", () => { + it("nested node module", () => { test(/*hasDirectoryExists*/ false); test(/*hasDirectoryExists*/ true); @@ -902,9 +902,9 @@ import b = require("./moduleB"); moduleResolution: ModuleResolutionKind.NodeJs, baseUrl: "/root", paths: { - "libs/guid": [ "src/libs/guid" ] + "libs/guid": ["src/libs/guid"] } - }; + }; const result = resolveModuleName("libs/guid", app.name, options, host); checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(libsTypings.name), [ // first try to load module as file @@ -1020,7 +1020,7 @@ import b = require("./moduleB"); const names = map(files, f => f.name); const sourceFiles = arrayToMap(map(files, f => createSourceFile(f.name, f.content, ScriptTarget.ES2015)), f => f.fileName); const compilerHost: CompilerHost = { - fileExists : fileName => fileName in sourceFiles, + fileExists: fileName => fileName in sourceFiles, getSourceFile: fileName => sourceFiles[fileName], getDefaultLibFileName: () => "lib.d.ts", writeFile: notImplemented, @@ -1042,7 +1042,7 @@ import b = require("./moduleB"); assert.equal(diagnostics1[0].messageText, diagnostics2[0].messageText, "expected one diagnostic"); }); - it ("Modules in the same .d.ts file are preferred to external files", () => { + it("Modules in the same .d.ts file are preferred to external files", () => { const f = { name: "/a/b/c/c/app.d.ts", content: ` @@ -1056,7 +1056,7 @@ import b = require("./moduleB"); }; const file = createSourceFile(f.name, f.content, ScriptTarget.ES2015); const compilerHost: CompilerHost = { - fileExists : fileName => fileName === file.fileName, + fileExists: fileName => fileName === file.fileName, getSourceFile: fileName => fileName === file.fileName ? file : undefined, getDefaultLibFileName: () => "lib.d.ts", writeFile: notImplemented, @@ -1074,7 +1074,7 @@ import b = require("./moduleB"); createProgram([f.name], {}, compilerHost); }); - it ("Modules in .ts file are not checked in the same file", () => { + it("Modules in .ts file are not checked in the same file", () => { const f = { name: "/a/b/c/c/app.ts", content: ` @@ -1088,7 +1088,7 @@ import b = require("./moduleB"); }; const file = createSourceFile(f.name, f.content, ScriptTarget.ES2015); const compilerHost: CompilerHost = { - fileExists : fileName => fileName === file.fileName, + fileExists: fileName => fileName === file.fileName, getSourceFile: fileName => fileName === file.fileName ? file : undefined, getDefaultLibFileName: () => "lib.d.ts", writeFile: notImplemented, @@ -1105,5 +1105,31 @@ import b = require("./moduleB"); }; createProgram([f.name], {}, compilerHost); }); + + it("Module name as directory - load js from 'main'", () => { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); + + function test(hasDirectoryExists: boolean) { + const containingFile = { name: "/a/b.ts" }; + const packageJson = { name: "/a/c/package.json", content: JSON.stringify({ main: "d/e" }) }; + const indexFile = { name: "/a/c/d/e.js" }; + const resolution = nodeModuleNameResolver("./c", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, packageJson, indexFile)); + checkResolvedModuleWithFailedLookupLocations(resolution, createResolvedModule(indexFile.name), [ + "/a/c.ts", + "/a/c.tsx", + "/a/c.d.ts", + "/a/c/index.ts", + "/a/c/index.tsx", + "/a/c/index.d.ts", + "/a/c.js", + "/a/c.jsx", + "/a/c/d/e", + "/a/c/d/e.ts", + "/a/c/d/e.tsx", + "/a/c/d/e.d.ts", + ]); + } + }); }); } diff --git a/tests/baselines/reference/moduleResolutionWithExtensions_unexpected.trace.json b/tests/baselines/reference/moduleResolutionWithExtensions_unexpected.trace.json index 84532e8db7b22..c4489ac057daf 100644 --- a/tests/baselines/reference/moduleResolutionWithExtensions_unexpected.trace.json +++ b/tests/baselines/reference/moduleResolutionWithExtensions_unexpected.trace.json @@ -17,12 +17,14 @@ "File '/node_modules/normalize.css.js' does not exist.", "File '/node_modules/normalize.css.jsx' does not exist.", "Found 'package.json' at '/node_modules/normalize.css/package.json'.", - "No types specified in 'package.json', so returning 'main' value of 'normalize.css'", + "'package.json' has 'main' field 'normalize.css' that references '/node_modules/normalize.css/normalize.css'.", "File '/node_modules/normalize.css/normalize.css' exist - use it as a name resolution result.", "File '/node_modules/normalize.css/normalize.css' has an unsupported extension, so skipping it.", "File '/node_modules/normalize.css/normalize.css.ts' does not exist.", "File '/node_modules/normalize.css/normalize.css.tsx' does not exist.", "File '/node_modules/normalize.css/normalize.css.d.ts' does not exist.", + "File '/node_modules/normalize.css/normalize.css.js' does not exist.", + "File '/node_modules/normalize.css/normalize.css.jsx' does not exist.", "File '/node_modules/normalize.css/index.js' does not exist.", "File '/node_modules/normalize.css/index.jsx' does not exist.", "======== Module name 'normalize.css' was not resolved. ========" diff --git a/tests/baselines/reference/untypedModuleImport_MainInPackageJson.js b/tests/baselines/reference/untypedModuleImport_MainInPackageJson.js new file mode 100644 index 0000000000000..3b4c84a7b4372 --- /dev/null +++ b/tests/baselines/reference/untypedModuleImport_MainInPackageJson.js @@ -0,0 +1,22 @@ +//// [tests/cases/conformance/moduleResolution/untypedModuleImport_MainInPackageJson.ts] //// + +//// [foo.js] +// This tests that importing from a JS file globally works in an untyped way. +// (Assuming we don't have `--noImplicitAny` or `--allowJs`.) + +This file is not processed. + +//// [package.json] +{ + "main": "lib/foo" +} + +//// [a.ts] +import * as foo from "foo"; +foo.bar(); + + +//// [a.js] +"use strict"; +var foo = require("foo"); +foo.bar(); diff --git a/tests/baselines/reference/untypedModuleImport_MainInPackageJson.symbols b/tests/baselines/reference/untypedModuleImport_MainInPackageJson.symbols new file mode 100644 index 0000000000000..f5703f555ab0d --- /dev/null +++ b/tests/baselines/reference/untypedModuleImport_MainInPackageJson.symbols @@ -0,0 +1,7 @@ +=== /a.ts === +import * as foo from "foo"; +>foo : Symbol(foo, Decl(a.ts, 0, 6)) + +foo.bar(); +>foo : Symbol(foo, Decl(a.ts, 0, 6)) + diff --git a/tests/baselines/reference/untypedModuleImport_MainInPackageJson.types b/tests/baselines/reference/untypedModuleImport_MainInPackageJson.types new file mode 100644 index 0000000000000..9d4e2e909c90c --- /dev/null +++ b/tests/baselines/reference/untypedModuleImport_MainInPackageJson.types @@ -0,0 +1,10 @@ +=== /a.ts === +import * as foo from "foo"; +>foo : any + +foo.bar(); +>foo.bar() : any +>foo.bar : any +>foo : any +>bar : any + diff --git a/tests/baselines/reference/untypedModuleImport_noImplicitAny2.errors.txt b/tests/baselines/reference/untypedModuleImport_noImplicitAny2.errors.txt new file mode 100644 index 0000000000000..f912decd6e8d0 --- /dev/null +++ b/tests/baselines/reference/untypedModuleImport_noImplicitAny2.errors.txt @@ -0,0 +1,18 @@ +/a.ts(1,22): error TS7016: Could not find a declaration file for module 'foo'. '/node_modules/foo/lib/foo.js' implicitly has an 'any' type. + + +==== /a.ts (1 errors) ==== + import * as foo from "foo"; + ~~~~~ +!!! error TS7016: Could not find a declaration file for module 'foo'. '/node_modules/foo/lib/foo.js' implicitly has an 'any' type. + +==== /node_modules/foo/lib/foo.js (0 errors) ==== + // This tests that `--noImplicitAny` disables untyped modules. + + This file is not processed. + +==== /node_modules/foo/package.json (0 errors) ==== + { + "main": "lib/foo" + } + \ No newline at end of file diff --git a/tests/baselines/reference/untypedModuleImport_noImplicitAny2.js b/tests/baselines/reference/untypedModuleImport_noImplicitAny2.js new file mode 100644 index 0000000000000..76f33d33132a2 --- /dev/null +++ b/tests/baselines/reference/untypedModuleImport_noImplicitAny2.js @@ -0,0 +1,18 @@ +//// [tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny2.ts] //// + +//// [foo.js] +// This tests that `--noImplicitAny` disables untyped modules. + +This file is not processed. + +//// [package.json] +{ + "main": "lib/foo" +} + +//// [a.ts] +import * as foo from "foo"; + + +//// [a.js] +"use strict"; diff --git a/tests/cases/conformance/moduleResolution/untypedModuleImport_MainInPackageJson.ts b/tests/cases/conformance/moduleResolution/untypedModuleImport_MainInPackageJson.ts new file mode 100644 index 0000000000000..4d6fcc0249b9e --- /dev/null +++ b/tests/cases/conformance/moduleResolution/untypedModuleImport_MainInPackageJson.ts @@ -0,0 +1,16 @@ +// @noImplicitReferences: true +// @currentDirectory: / +// This tests that importing from a JS file globally works in an untyped way. +// (Assuming we don't have `--noImplicitAny` or `--allowJs`.) + +// @filename: /node_modules/foo/lib/foo.js +This file is not processed. + +// @filename: /node_modules/foo/package.json +{ + "main": "lib/foo" +} + +// @filename: /a.ts +import * as foo from "foo"; +foo.bar(); diff --git a/tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny2.ts b/tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny2.ts new file mode 100644 index 0000000000000..1293a8812257b --- /dev/null +++ b/tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny2.ts @@ -0,0 +1,15 @@ +// @noImplicitReferences: true +// @currentDirectory: / +// @noImplicitAny: true +// This tests that `--noImplicitAny` disables untyped modules. + +// @filename: /node_modules/foo/lib/foo.js +This file is not processed. + +// @filename: /node_modules/foo/package.json +{ + "main": "lib/foo" +} + +// @filename: /a.ts +import * as foo from "foo";