mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-15 04:43:37 -05:00
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:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
@@ -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))
|
||||
|
||||
@@ -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'. ========"
|
||||
]
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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))
|
||||
|
||||
@@ -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'. ========"
|
||||
]
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -0,0 +1,4 @@
|
||||
=== /a.ts ===
|
||||
import { x } from "foo/bar";
|
||||
>x : Symbol(x, Decl(a.ts, 0, 8))
|
||||
|
||||
@@ -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'. ========"
|
||||
]
|
||||
@@ -0,0 +1,4 @@
|
||||
=== /a.ts ===
|
||||
import { x } from "foo/bar";
|
||||
>x : any
|
||||
|
||||
@@ -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;
|
||||
@@ -0,0 +1,4 @@
|
||||
=== /a.ts ===
|
||||
import { x } from "foo/@bar";
|
||||
>x : Symbol(x, Decl(a.ts, 0, 8))
|
||||
|
||||
@@ -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'. ========"
|
||||
]
|
||||
@@ -0,0 +1,4 @@
|
||||
=== /a.ts ===
|
||||
import { x } from "foo/@bar";
|
||||
>x : any
|
||||
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
Reference in New Issue
Block a user