Skip to content

Commit 61fb845

Browse files
author
Andy
authored
Get packageId for relative import within a package (#21130)
* Get packageId for relative import within a package * Code review * Rename things and add comments * Improve documentation * Test for scoped packages
1 parent 2ca688a commit 61fb845

11 files changed

+476
-1
lines changed

src/compiler/moduleNameResolver.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,9 @@ namespace ts {
801801
}
802802
const resolvedFromFile = loadModuleFromFile(extensions, candidate, failedLookupLocations, onlyRecordFailures, state);
803803
if (resolvedFromFile) {
804-
return noPackageId(resolvedFromFile);
804+
const nm = considerPackageJson ? parseNodeModuleFromPath(resolvedFromFile.path) : undefined;
805+
const packageId = nm && getPackageJsonInfo(nm.packageDirectory, nm.subModuleName, failedLookupLocations, /*onlyRecordFailures*/ false, state).packageId;
806+
return withPackageId(packageId, resolvedFromFile);
805807
}
806808
}
807809
if (!onlyRecordFailures) {
@@ -816,6 +818,45 @@ namespace ts {
816818
return loadNodeModuleFromDirectory(extensions, candidate, failedLookupLocations, onlyRecordFailures, state, considerPackageJson);
817819
}
818820

821+
const nodeModulesPathPart = "/node_modules/";
822+
823+
/**
824+
* This will be called on the successfully resolved path from `loadModuleFromFile`.
825+
* (Not neeeded for `loadModuleFromNodeModules` as that looks up the `package.json` as part of resolution.)
826+
*
827+
* packageDirectory is the directory of the package itself.
828+
* subModuleName is the path within the package.
829+
* For `blah/node_modules/foo/index.d.ts` this is { packageDirectory: "foo", subModuleName: "" }. (Part before "/node_modules/" is ignored.)
830+
* For `/node_modules/foo/bar.d.ts` this is { packageDirectory: "foo", subModuleName": "bar" }.
831+
* For `/node_modules/@types/foo/bar/index.d.ts` this is { packageDirectory: "@types/foo", subModuleName: "bar" }.
832+
*/
833+
function parseNodeModuleFromPath(path: string): { packageDirectory: string, subModuleName: string } | undefined {
834+
path = normalizePath(path);
835+
const idx = path.lastIndexOf(nodeModulesPathPart);
836+
if (idx === -1) {
837+
return undefined;
838+
}
839+
840+
const indexAfterNodeModules = idx + nodeModulesPathPart.length;
841+
let indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterNodeModules);
842+
if (path.charCodeAt(indexAfterNodeModules) === CharacterCodes.at) {
843+
indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterPackageName);
844+
}
845+
const packageDirectory = path.slice(0, indexAfterPackageName);
846+
const subModuleName = removeExtensionAndIndex(path.slice(indexAfterPackageName + 1));
847+
return { packageDirectory, subModuleName };
848+
}
849+
850+
function moveToNextDirectorySeparatorIfAvailable(path: string, prevSeparatorIndex: number): number {
851+
const nextSeparatorIndex = path.indexOf(directorySeparator, prevSeparatorIndex + 1);
852+
return nextSeparatorIndex === -1 ? prevSeparatorIndex : nextSeparatorIndex;
853+
}
854+
855+
function removeExtensionAndIndex(path: string): string {
856+
const noExtension = removeFileExtension(path);
857+
return noExtension === "index" ? "" : removeSuffix(noExtension, "/index");
858+
}
859+
819860
/* @internal */
820861
export function directoryProbablyExists(directoryName: string, host: { directoryExists?: (directoryName: string) => boolean }): boolean {
821862
// if host does not support 'directoryExists' assume that directory will exist
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//// [tests/cases/compiler/duplicatePackage_relativeImportWithinPackage.ts] ////
2+
3+
//// [package.json]
4+
{
5+
"name": "foo",
6+
"version": "1.2.3"
7+
}
8+
9+
//// [index.d.ts]
10+
export class C {
11+
private x: number;
12+
}
13+
14+
//// [index.d.ts]
15+
import { C } from "foo";
16+
export const o: C;
17+
18+
//// [use.d.ts]
19+
import { C } from "./index";
20+
export function use(o: C): void;
21+
22+
//// [index.d.ts]
23+
export class C {
24+
private x: number;
25+
}
26+
27+
//// [package.json]
28+
{
29+
"name": "foo",
30+
"version": "1.2.3"
31+
}
32+
33+
//// [index.ts]
34+
import { use } from "foo/use";
35+
import { o } from "a";
36+
37+
use(o);
38+
39+
40+
//// [index.js]
41+
"use strict";
42+
exports.__esModule = true;
43+
var use_1 = require("foo/use");
44+
var a_1 = require("a");
45+
use_1.use(a_1.o);
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
=== /index.ts ===
2+
import { use } from "foo/use";
3+
>use : Symbol(use, Decl(index.ts, 0, 8))
4+
5+
import { o } from "a";
6+
>o : Symbol(o, Decl(index.ts, 1, 8))
7+
8+
use(o);
9+
>use : Symbol(use, Decl(index.ts, 0, 8))
10+
>o : Symbol(o, Decl(index.ts, 1, 8))
11+
12+
=== /node_modules/a/node_modules/foo/index.d.ts ===
13+
export class C {
14+
>C : Symbol(C, Decl(index.d.ts, 0, 0))
15+
16+
private x: number;
17+
>x : Symbol(C.x, Decl(index.d.ts, 0, 16))
18+
}
19+
20+
=== /node_modules/a/index.d.ts ===
21+
import { C } from "foo";
22+
>C : Symbol(C, Decl(index.d.ts, 0, 8))
23+
24+
export const o: C;
25+
>o : Symbol(o, Decl(index.d.ts, 1, 12))
26+
>C : Symbol(C, Decl(index.d.ts, 0, 8))
27+
28+
=== /node_modules/foo/use.d.ts ===
29+
import { C } from "./index";
30+
>C : Symbol(C, Decl(use.d.ts, 0, 8))
31+
32+
export function use(o: C): void;
33+
>use : Symbol(use, Decl(use.d.ts, 0, 28))
34+
>o : Symbol(o, Decl(use.d.ts, 1, 20))
35+
>C : Symbol(C, Decl(use.d.ts, 0, 8))
36+
37+
=== /node_modules/foo/index.d.ts ===
38+
export class C {
39+
>C : Symbol(C, Decl(index.d.ts, 0, 0))
40+
41+
private x: number;
42+
>x : Symbol(C.x, Decl(index.d.ts, 0, 16))
43+
}
44+
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
[
2+
"======== Resolving module 'foo/use' from '/index.ts'. ========",
3+
"Module resolution kind is not specified, using 'NodeJs'.",
4+
"Loading module 'foo/use' from 'node_modules' folder, target file type 'TypeScript'.",
5+
"Found 'package.json' at '/node_modules/foo/package.json'.",
6+
"File '/node_modules/foo/use.ts' does not exist.",
7+
"File '/node_modules/foo/use.tsx' does not exist.",
8+
"File '/node_modules/foo/use.d.ts' exist - use it as a name resolution result.",
9+
"Resolving real path for '/node_modules/foo/use.d.ts', result '/node_modules/foo/use.d.ts'.",
10+
"======== Module name 'foo/use' was successfully resolved to '/node_modules/foo/use.d.ts'. ========",
11+
"======== Resolving module 'a' from '/index.ts'. ========",
12+
"Module resolution kind is not specified, using 'NodeJs'.",
13+
"Loading module 'a' from 'node_modules' folder, target file type 'TypeScript'.",
14+
"File '/node_modules/a/package.json' does not exist.",
15+
"File '/node_modules/a.ts' does not exist.",
16+
"File '/node_modules/a.tsx' does not exist.",
17+
"File '/node_modules/a.d.ts' does not exist.",
18+
"File '/node_modules/a/index.ts' does not exist.",
19+
"File '/node_modules/a/index.tsx' does not exist.",
20+
"File '/node_modules/a/index.d.ts' exist - use it as a name resolution result.",
21+
"Resolving real path for '/node_modules/a/index.d.ts', result '/node_modules/a/index.d.ts'.",
22+
"======== Module name 'a' was successfully resolved to '/node_modules/a/index.d.ts'. ========",
23+
"======== Resolving module './index' from '/node_modules/foo/use.d.ts'. ========",
24+
"Module resolution kind is not specified, using 'NodeJs'.",
25+
"Loading module as file / folder, candidate module location '/node_modules/foo/index', target file type 'TypeScript'.",
26+
"File '/node_modules/foo/index.ts' does not exist.",
27+
"File '/node_modules/foo/index.tsx' does not exist.",
28+
"File '/node_modules/foo/index.d.ts' exist - use it as a name resolution result.",
29+
"Found 'package.json' at '/node_modules/foo/package.json'.",
30+
"======== Module name './index' was successfully resolved to '/node_modules/foo/index.d.ts'. ========",
31+
"======== Resolving module 'foo' from '/node_modules/a/index.d.ts'. ========",
32+
"Module resolution kind is not specified, using 'NodeJs'.",
33+
"Loading module 'foo' from 'node_modules' folder, target file type 'TypeScript'.",
34+
"Found 'package.json' at '/node_modules/a/node_modules/foo/package.json'.",
35+
"File '/node_modules/a/node_modules/foo.ts' does not exist.",
36+
"File '/node_modules/a/node_modules/foo.tsx' does not exist.",
37+
"File '/node_modules/a/node_modules/foo.d.ts' does not exist.",
38+
"'package.json' does not have a 'typings' field.",
39+
"'package.json' does not have a 'types' field.",
40+
"File '/node_modules/a/node_modules/foo/index.ts' does not exist.",
41+
"File '/node_modules/a/node_modules/foo/index.tsx' does not exist.",
42+
"File '/node_modules/a/node_modules/foo/index.d.ts' exist - use it as a name resolution result.",
43+
"Resolving real path for '/node_modules/a/node_modules/foo/index.d.ts', result '/node_modules/a/node_modules/foo/index.d.ts'.",
44+
"======== Module name 'foo' was successfully resolved to '/node_modules/a/node_modules/foo/index.d.ts'. ========"
45+
]
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
=== /index.ts ===
2+
import { use } from "foo/use";
3+
>use : (o: C) => void
4+
5+
import { o } from "a";
6+
>o : C
7+
8+
use(o);
9+
>use(o) : void
10+
>use : (o: C) => void
11+
>o : C
12+
13+
=== /node_modules/a/node_modules/foo/index.d.ts ===
14+
export class C {
15+
>C : C
16+
17+
private x: number;
18+
>x : number
19+
}
20+
21+
=== /node_modules/a/index.d.ts ===
22+
import { C } from "foo";
23+
>C : typeof C
24+
25+
export const o: C;
26+
>o : C
27+
>C : C
28+
29+
=== /node_modules/foo/use.d.ts ===
30+
import { C } from "./index";
31+
>C : typeof C
32+
33+
export function use(o: C): void;
34+
>use : (o: C) => void
35+
>o : C
36+
>C : C
37+
38+
=== /node_modules/foo/index.d.ts ===
39+
export class C {
40+
>C : C
41+
42+
private x: number;
43+
>x : number
44+
}
45+
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//// [tests/cases/compiler/duplicatePackage_relativeImportWithinPackage_scoped.ts] ////
2+
3+
//// [package.json]
4+
{
5+
"name": "@foo/bar",
6+
"version": "1.2.3"
7+
}
8+
9+
//// [index.d.ts]
10+
export class C {
11+
private x: number;
12+
}
13+
14+
//// [index.d.ts]
15+
import { C } from "@foo/bar";
16+
export const o: C;
17+
18+
//// [use.d.ts]
19+
import { C } from "./index";
20+
export function use(o: C): void;
21+
22+
//// [index.d.ts]
23+
export class C {
24+
private x: number;
25+
}
26+
27+
//// [package.json]
28+
{
29+
"name": "@foo/bar",
30+
"version": "1.2.3"
31+
}
32+
33+
//// [index.ts]
34+
import { use } from "@foo/bar/use";
35+
import { o } from "a";
36+
37+
use(o);
38+
39+
40+
//// [index.js]
41+
"use strict";
42+
exports.__esModule = true;
43+
var use_1 = require("@foo/bar/use");
44+
var a_1 = require("a");
45+
use_1.use(a_1.o);
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
=== /index.ts ===
2+
import { use } from "@foo/bar/use";
3+
>use : Symbol(use, Decl(index.ts, 0, 8))
4+
5+
import { o } from "a";
6+
>o : Symbol(o, Decl(index.ts, 1, 8))
7+
8+
use(o);
9+
>use : Symbol(use, Decl(index.ts, 0, 8))
10+
>o : Symbol(o, Decl(index.ts, 1, 8))
11+
12+
=== /node_modules/a/node_modules/@foo/bar/index.d.ts ===
13+
export class C {
14+
>C : Symbol(C, Decl(index.d.ts, 0, 0))
15+
16+
private x: number;
17+
>x : Symbol(C.x, Decl(index.d.ts, 0, 16))
18+
}
19+
20+
=== /node_modules/a/index.d.ts ===
21+
import { C } from "@foo/bar";
22+
>C : Symbol(C, Decl(index.d.ts, 0, 8))
23+
24+
export const o: C;
25+
>o : Symbol(o, Decl(index.d.ts, 1, 12))
26+
>C : Symbol(C, Decl(index.d.ts, 0, 8))
27+
28+
=== /node_modules/@foo/bar/use.d.ts ===
29+
import { C } from "./index";
30+
>C : Symbol(C, Decl(use.d.ts, 0, 8))
31+
32+
export function use(o: C): void;
33+
>use : Symbol(use, Decl(use.d.ts, 0, 28))
34+
>o : Symbol(o, Decl(use.d.ts, 1, 20))
35+
>C : Symbol(C, Decl(use.d.ts, 0, 8))
36+
37+
=== /node_modules/@foo/bar/index.d.ts ===
38+
export class C {
39+
>C : Symbol(C, Decl(index.d.ts, 0, 0))
40+
41+
private x: number;
42+
>x : Symbol(C.x, Decl(index.d.ts, 0, 16))
43+
}
44+
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
[
2+
"======== Resolving module '@foo/bar/use' from '/index.ts'. ========",
3+
"Module resolution kind is not specified, using 'NodeJs'.",
4+
"Loading module '@foo/bar/use' from 'node_modules' folder, target file type 'TypeScript'.",
5+
"Found 'package.json' at '/node_modules/@foo/bar/package.json'.",
6+
"File '/node_modules/@foo/bar/use.ts' does not exist.",
7+
"File '/node_modules/@foo/bar/use.tsx' does not exist.",
8+
"File '/node_modules/@foo/bar/use.d.ts' exist - use it as a name resolution result.",
9+
"Resolving real path for '/node_modules/@foo/bar/use.d.ts', result '/node_modules/@foo/bar/use.d.ts'.",
10+
"======== Module name '@foo/bar/use' was successfully resolved to '/node_modules/@foo/bar/use.d.ts'. ========",
11+
"======== Resolving module 'a' from '/index.ts'. ========",
12+
"Module resolution kind is not specified, using 'NodeJs'.",
13+
"Loading module 'a' from 'node_modules' folder, target file type 'TypeScript'.",
14+
"File '/node_modules/a/package.json' does not exist.",
15+
"File '/node_modules/a.ts' does not exist.",
16+
"File '/node_modules/a.tsx' does not exist.",
17+
"File '/node_modules/a.d.ts' does not exist.",
18+
"File '/node_modules/a/index.ts' does not exist.",
19+
"File '/node_modules/a/index.tsx' does not exist.",
20+
"File '/node_modules/a/index.d.ts' exist - use it as a name resolution result.",
21+
"Resolving real path for '/node_modules/a/index.d.ts', result '/node_modules/a/index.d.ts'.",
22+
"======== Module name 'a' was successfully resolved to '/node_modules/a/index.d.ts'. ========",
23+
"======== Resolving module './index' from '/node_modules/@foo/bar/use.d.ts'. ========",
24+
"Module resolution kind is not specified, using 'NodeJs'.",
25+
"Loading module as file / folder, candidate module location '/node_modules/@foo/bar/index', target file type 'TypeScript'.",
26+
"File '/node_modules/@foo/bar/index.ts' does not exist.",
27+
"File '/node_modules/@foo/bar/index.tsx' does not exist.",
28+
"File '/node_modules/@foo/bar/index.d.ts' exist - use it as a name resolution result.",
29+
"Found 'package.json' at '/node_modules/@foo/bar/package.json'.",
30+
"======== Module name './index' was successfully resolved to '/node_modules/@foo/bar/index.d.ts'. ========",
31+
"======== Resolving module '@foo/bar' from '/node_modules/a/index.d.ts'. ========",
32+
"Module resolution kind is not specified, using 'NodeJs'.",
33+
"Loading module '@foo/bar' from 'node_modules' folder, target file type 'TypeScript'.",
34+
"Found 'package.json' at '/node_modules/a/node_modules/@foo/bar/package.json'.",
35+
"File '/node_modules/a/node_modules/@foo/bar.ts' does not exist.",
36+
"File '/node_modules/a/node_modules/@foo/bar.tsx' does not exist.",
37+
"File '/node_modules/a/node_modules/@foo/bar.d.ts' does not exist.",
38+
"'package.json' does not have a 'typings' field.",
39+
"'package.json' does not have a 'types' field.",
40+
"File '/node_modules/a/node_modules/@foo/bar/index.ts' does not exist.",
41+
"File '/node_modules/a/node_modules/@foo/bar/index.tsx' does not exist.",
42+
"File '/node_modules/a/node_modules/@foo/bar/index.d.ts' exist - use it as a name resolution result.",
43+
"Resolving real path for '/node_modules/a/node_modules/@foo/bar/index.d.ts', result '/node_modules/a/node_modules/@foo/bar/index.d.ts'.",
44+
"======== Module name '@foo/bar' was successfully resolved to '/node_modules/a/node_modules/@foo/bar/index.d.ts'. ========"
45+
]

0 commit comments

Comments
 (0)