Support 'package.json' not in package root (#19133)

* Support 'package.json' not in package root

* Test "foo/@bar"

* More tests, and don't use "types" from the root package.json if not loading the root module
This commit is contained in:
Andy
2017-10-16 13:02:15 -07:00
committed by GitHub
parent 40222d1a77
commit 2cb0403e2d
21 changed files with 288 additions and 12 deletions

View File

@@ -77,16 +77,20 @@ namespace ts {
traceEnabled: boolean;
}
interface PackageJson {
name?: string;
version?: string;
/** Just the fields that we use for module resolution. */
interface PackageJsonPathFields {
typings?: string;
types?: string;
main?: string;
}
interface PackageJson extends PackageJsonPathFields {
name?: string;
version?: string;
}
/** Reads from "main" or "types"/"typings" depending on `extensions`. */
function tryReadPackageJsonFields(readTypes: boolean, jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState): string | undefined {
function tryReadPackageJsonFields(readTypes: boolean, jsonContent: PackageJsonPathFields, baseDirectory: string, state: ModuleResolutionState): string | undefined {
return readTypes ? tryReadFromField("typings") || tryReadFromField("types") : tryReadFromField("main");
function tryReadFromField(fieldName: "typings" | "types" | "main"): string | undefined {
@@ -886,7 +890,7 @@ namespace ts {
return withPackageId(packageId, loadNodeModuleFromDirectoryWorker(extensions, candidate, failedLookupLocations, onlyRecordFailures, state, packageJsonContent));
}
function loadNodeModuleFromDirectoryWorker(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState, packageJsonContent: PackageJson | undefined): PathAndExtension | undefined {
function loadNodeModuleFromDirectoryWorker(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState, packageJsonContent: PackageJsonPathFields | undefined): PathAndExtension | undefined {
const fromPackageJson = packageJsonContent && loadModuleFromPackageJson(packageJsonContent, extensions, candidate, failedLookupLocations, state);
if (fromPackageJson) {
return fromPackageJson;
@@ -901,7 +905,7 @@ namespace ts {
failedLookupLocations: Push<string>,
onlyRecordFailures: boolean,
{ host, traceEnabled }: ModuleResolutionState,
): { packageJsonContent: PackageJson | undefined, packageId: PackageId | undefined } {
): { found: boolean, packageJsonContent: PackageJsonPathFields | undefined, packageId: PackageId | undefined } {
const directoryExists = !onlyRecordFailures && directoryProbablyExists(nodeModuleDirectory, host);
const packageJsonPath = pathToPackageJson(nodeModuleDirectory);
if (directoryExists && host.fileExists(packageJsonPath)) {
@@ -912,7 +916,7 @@ namespace ts {
const packageId: PackageId = typeof packageJsonContent.name === "string" && typeof packageJsonContent.version === "string"
? { name: packageJsonContent.name, subModuleName, version: packageJsonContent.version }
: undefined;
return { packageJsonContent, packageId };
return { found: true, packageJsonContent, packageId };
}
else {
if (directoryExists && traceEnabled) {
@@ -920,11 +924,11 @@ namespace ts {
}
// record package json as one of failed lookup locations - in the future if this file will appear it will invalidate resolution results
failedLookupLocations.push(packageJsonPath);
return { packageJsonContent: undefined, packageId: undefined };
return { found: false, packageJsonContent: undefined, packageId: undefined };
}
}
function loadModuleFromPackageJson(jsonContent: PackageJson, extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, state: ModuleResolutionState): PathAndExtension | undefined {
function loadModuleFromPackageJson(jsonContent: PackageJsonPathFields, extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, state: ModuleResolutionState): PathAndExtension | undefined {
const file = tryReadPackageJsonFields(extensions !== Extensions.JavaScript, jsonContent, candidate, state);
if (!file) {
return undefined;
@@ -976,10 +980,22 @@ namespace ts {
}
function loadModuleFromNodeModulesFolder(extensions: Extensions, moduleName: string, nodeModulesFolder: string, nodeModulesFolderExists: boolean, failedLookupLocations: Push<string>, state: ModuleResolutionState): Resolved | undefined {
const { packageName, rest } = getPackageName(moduleName);
const packageRootPath = combinePaths(nodeModulesFolder, packageName);
const { packageJsonContent, packageId } = getPackageJsonInfo(packageRootPath, rest, failedLookupLocations, !nodeModulesFolderExists, state);
const candidate = normalizePath(combinePaths(nodeModulesFolder, moduleName));
// First look for a nested package.json, as in `node_modules/foo/bar/package.json`.
let packageJsonContent: PackageJsonPathFields | undefined;
let packageId: PackageId | undefined;
const packageInfo = getPackageJsonInfo(candidate, "", failedLookupLocations, /*onlyRecordFailures*/ !nodeModulesFolderExists, state);
if (packageInfo.found) {
({ packageJsonContent, packageId } = packageInfo);
}
else {
const { packageName, rest } = getPackageName(moduleName);
if (rest !== "") { // If "rest" is empty, we just did this search above.
const packageRootPath = combinePaths(nodeModulesFolder, packageName);
// Don't use a "types" or "main" from here because we're not loading the root, but a subdirectory -- just here for the packageId.
packageId = getPackageJsonInfo(packageRootPath, rest, failedLookupLocations, !nodeModulesFolderExists, state).packageId;
}
}
const pathAndExtension = loadModuleFromFile(extensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state) ||
loadNodeModuleFromDirectoryWorker(extensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state, packageJsonContent);
return withPackageId(packageId, pathAndExtension);

View File

@@ -0,0 +1,20 @@
//// [tests/cases/compiler/moduleResolution_packageJson_notAtPackageRoot.ts] ////
//// [package.json]
// Loads from a "fake" nested package.json, not from the one at the root.
{ "types": "types.d.ts" }
//// [package.json]
{}
//// [types.d.ts]
export const x: number;
//// [a.ts]
import { x } from "foo/bar";
//// [a.js]
"use strict";
exports.__esModule = true;

View File

@@ -0,0 +1,8 @@
=== /a.ts ===
import { x } from "foo/bar";
>x : Symbol(x, Decl(a.ts, 0, 8))
=== /node_modules/foo/bar/types.d.ts ===
export const x: number;
>x : Symbol(x, Decl(types.d.ts, 0, 12))

View File

@@ -0,0 +1,14 @@
[
"======== Resolving module 'foo/bar' from '/a.ts'. ========",
"Module resolution kind is not specified, using 'NodeJs'.",
"Loading module 'foo/bar' from 'node_modules' folder, target file type 'TypeScript'.",
"Found 'package.json' at '/node_modules/foo/bar/package.json'.",
"File '/node_modules/foo/bar.ts' does not exist.",
"File '/node_modules/foo/bar.tsx' does not exist.",
"File '/node_modules/foo/bar.d.ts' does not exist.",
"'package.json' does not have a 'typings' field.",
"'package.json' has 'types' field 'types.d.ts' that references '/node_modules/foo/bar/types.d.ts'.",
"File '/node_modules/foo/bar/types.d.ts' exist - use it as a name resolution result.",
"Resolving real path for '/node_modules/foo/bar/types.d.ts', result '/node_modules/foo/bar/types.d.ts'.",
"======== Module name 'foo/bar' was successfully resolved to '/node_modules/foo/bar/types.d.ts'. ========"
]

View File

@@ -0,0 +1,8 @@
=== /a.ts ===
import { x } from "foo/bar";
>x : number
=== /node_modules/foo/bar/types.d.ts ===
export const x: number;
>x : number

View File

@@ -0,0 +1,20 @@
//// [tests/cases/compiler/moduleResolution_packageJson_notAtPackageRoot_fakeScopedPackage.ts] ////
//// [package.json]
// Copy of `moduleResolution_packageJson_notAtPackageRoot` with `foo/@bar` instead of `foo/bar`. Should behave identically.
{ "types": "types.d.ts" }
//// [package.json]
{}
//// [types.d.ts]
export const x: number;
//// [a.ts]
import { x } from "foo/@bar";
//// [a.js]
"use strict";
exports.__esModule = true;

View File

@@ -0,0 +1,8 @@
=== /a.ts ===
import { x } from "foo/@bar";
>x : Symbol(x, Decl(a.ts, 0, 8))
=== /node_modules/foo/@bar/types.d.ts ===
export const x: number;
>x : Symbol(x, Decl(types.d.ts, 0, 12))

View File

@@ -0,0 +1,14 @@
[
"======== Resolving module 'foo/@bar' from '/a.ts'. ========",
"Module resolution kind is not specified, using 'NodeJs'.",
"Loading module 'foo/@bar' from 'node_modules' folder, target file type 'TypeScript'.",
"Found 'package.json' at '/node_modules/foo/@bar/package.json'.",
"File '/node_modules/foo/@bar.ts' does not exist.",
"File '/node_modules/foo/@bar.tsx' does not exist.",
"File '/node_modules/foo/@bar.d.ts' does not exist.",
"'package.json' does not have a 'typings' field.",
"'package.json' has 'types' field 'types.d.ts' that references '/node_modules/foo/@bar/types.d.ts'.",
"File '/node_modules/foo/@bar/types.d.ts' exist - use it as a name resolution result.",
"Resolving real path for '/node_modules/foo/@bar/types.d.ts', result '/node_modules/foo/@bar/types.d.ts'.",
"======== Module name 'foo/@bar' was successfully resolved to '/node_modules/foo/@bar/types.d.ts'. ========"
]

View File

@@ -0,0 +1,8 @@
=== /a.ts ===
import { x } from "foo/@bar";
>x : number
=== /node_modules/foo/@bar/types.d.ts ===
export const x: number;
>x : number

View File

@@ -0,0 +1,18 @@
//// [tests/cases/compiler/moduleResolution_packageJson_yesAtPackageRoot.ts] ////
//// [index.js]
not read
//// [package.json]
{ "name": "foo", "version": "1.2.3", "types": "types.d.ts" }
//// [types.d.ts]
export const x = 0;
//// [a.ts]
import { x } from "foo/bar";
//// [a.js]
"use strict";
exports.__esModule = true;

View File

@@ -0,0 +1,4 @@
=== /a.ts ===
import { x } from "foo/bar";
>x : Symbol(x, Decl(a.ts, 0, 8))

View File

@@ -0,0 +1,22 @@
[
"======== Resolving module 'foo/bar' from '/a.ts'. ========",
"Module resolution kind is not specified, using 'NodeJs'.",
"Loading module 'foo/bar' from 'node_modules' folder, target file type 'TypeScript'.",
"File '/node_modules/foo/bar/package.json' does not exist.",
"Found 'package.json' at '/node_modules/foo/package.json'.",
"File '/node_modules/foo/bar.ts' does not exist.",
"File '/node_modules/foo/bar.tsx' does not exist.",
"File '/node_modules/foo/bar.d.ts' does not exist.",
"File '/node_modules/foo/bar/index.ts' does not exist.",
"File '/node_modules/foo/bar/index.tsx' does not exist.",
"File '/node_modules/foo/bar/index.d.ts' does not exist.",
"Directory '/node_modules/@types' does not exist, skipping all lookups in it.",
"Loading module 'foo/bar' from 'node_modules' folder, target file type 'JavaScript'.",
"File '/node_modules/foo/bar/package.json' does not exist.",
"Found 'package.json' at '/node_modules/foo/package.json'.",
"File '/node_modules/foo/bar.js' does not exist.",
"File '/node_modules/foo/bar.jsx' does not exist.",
"File '/node_modules/foo/bar/index.js' exist - use it as a name resolution result.",
"Resolving real path for '/node_modules/foo/bar/index.js', result '/node_modules/foo/bar/index.js'.",
"======== Module name 'foo/bar' was successfully resolved to '/node_modules/foo/bar/index.js'. ========"
]

View File

@@ -0,0 +1,4 @@
=== /a.ts ===
import { x } from "foo/bar";
>x : any

View File

@@ -0,0 +1,20 @@
//// [tests/cases/compiler/moduleResolution_packageJson_yesAtPackageRoot_fakeScopedPackage.ts] ////
//// [index.js]
// Copy of `moduleResolution_packageJson_notAtPackageRoot` with `foo/@bar` instead of `foo/bar`. Should behave identically.
not read
//// [package.json]
{ "name": "foo", "version": "1.2.3", "types": "types.d.ts" }
//// [types.d.ts]
export const x = 0;
//// [a.ts]
import { x } from "foo/@bar";
//// [a.js]
"use strict";
exports.__esModule = true;

View File

@@ -0,0 +1,4 @@
=== /a.ts ===
import { x } from "foo/@bar";
>x : Symbol(x, Decl(a.ts, 0, 8))

View File

@@ -0,0 +1,22 @@
[
"======== Resolving module 'foo/@bar' from '/a.ts'. ========",
"Module resolution kind is not specified, using 'NodeJs'.",
"Loading module 'foo/@bar' from 'node_modules' folder, target file type 'TypeScript'.",
"File '/node_modules/foo/@bar/package.json' does not exist.",
"Found 'package.json' at '/node_modules/foo/package.json'.",
"File '/node_modules/foo/@bar.ts' does not exist.",
"File '/node_modules/foo/@bar.tsx' does not exist.",
"File '/node_modules/foo/@bar.d.ts' does not exist.",
"File '/node_modules/foo/@bar/index.ts' does not exist.",
"File '/node_modules/foo/@bar/index.tsx' does not exist.",
"File '/node_modules/foo/@bar/index.d.ts' does not exist.",
"Directory '/node_modules/@types' does not exist, skipping all lookups in it.",
"Loading module 'foo/@bar' from 'node_modules' folder, target file type 'JavaScript'.",
"File '/node_modules/foo/@bar/package.json' does not exist.",
"Found 'package.json' at '/node_modules/foo/package.json'.",
"File '/node_modules/foo/@bar.js' does not exist.",
"File '/node_modules/foo/@bar.jsx' does not exist.",
"File '/node_modules/foo/@bar/index.js' exist - use it as a name resolution result.",
"Resolving real path for '/node_modules/foo/@bar/index.js', result '/node_modules/foo/@bar/index.js'.",
"======== Module name 'foo/@bar' was successfully resolved to '/node_modules/foo/@bar/index.js'. ========"
]

View File

@@ -0,0 +1,4 @@
=== /a.ts ===
import { x } from "foo/@bar";
>x : any

View File

@@ -0,0 +1,16 @@
// @noImplicitReferences: true
// @traceResolution: true
// Loads from a "fake" nested package.json, not from the one at the root.
// @Filename: /node_modules/foo/bar/package.json
{ "types": "types.d.ts" }
// @Filename: /node_modules/foo/package.json
{}
// @Filename: /node_modules/foo/bar/types.d.ts
export const x: number;
// @Filename: /a.ts
import { x } from "foo/bar";

View File

@@ -0,0 +1,16 @@
// @noImplicitReferences: true
// @traceResolution: true
// Copy of `moduleResolution_packageJson_notAtPackageRoot` with `foo/@bar` instead of `foo/bar`. Should behave identically.
// @Filename: /node_modules/foo/@bar/package.json
{ "types": "types.d.ts" }
// @Filename: /node_modules/foo/package.json
{}
// @Filename: /node_modules/foo/@bar/types.d.ts
export const x: number;
// @Filename: /a.ts
import { x } from "foo/@bar";

View File

@@ -0,0 +1,14 @@
// @noImplicitReferences: true
// @traceResolution: true
// @Filename: /node_modules/foo/bar/index.js
not read
// @Filename: /node_modules/foo/package.json
{ "name": "foo", "version": "1.2.3", "types": "types.d.ts" }
// @Filename: /node_modules/foo/types.d.ts
export const x = 0;
// @Filename: /a.ts
import { x } from "foo/bar";

View File

@@ -0,0 +1,16 @@
// @noImplicitReferences: true
// @traceResolution: true
// Copy of `moduleResolution_packageJson_notAtPackageRoot` with `foo/@bar` instead of `foo/bar`. Should behave identically.
// @Filename: /node_modules/foo/@bar/index.js
not read
// @Filename: /node_modules/foo/package.json
{ "name": "foo", "version": "1.2.3", "types": "types.d.ts" }
// @Filename: /node_modules/foo/types.d.ts
export const x = 0;
// @Filename: /a.ts
import { x } from "foo/@bar";