Skip to content

Commit dc5cd96

Browse files
committed
Switch to paths-like pattern matching
1 parent b9afdad commit dc5cd96

File tree

38 files changed

+872
-354
lines changed

38 files changed

+872
-354
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3671,14 +3671,18 @@
36713671
"category": "Error",
36723672
"code": 6205
36733673
},
3674-
"'package.json' has invalid version '{0}' in 'typesVersions' field.": {
3674+
"'package.json' has a 'typesVersions' field with version-specific path mappings.": {
36753675
"category": "Message",
36763676
"code": 6206
36773677
},
36783678
"'package.json' does not have a 'typesVersions' entry that matches version '{0}'.": {
36793679
"category": "Message",
36803680
"code": 6207
36813681
},
3682+
"'package.json' has a 'typesVersions' entry '{0}' that matches compiler version '{1}', looking for a pattern to match module name '{2}'.": {
3683+
"category": "Message",
3684+
"code": 6208
3685+
},
36823686

36833687
"Projects to reference": {
36843688
"category": "Message",

src/compiler/moduleNameResolver.ts

Lines changed: 289 additions & 254 deletions
Large diffs are not rendered by default.

src/compiler/moduleSpecifiers.ts

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,8 @@ namespace ts.moduleSpecifiers {
260260
const suffix = pattern.substr(indexOfStar + 1);
261261
if (relativeToBaseUrl.length >= prefix.length + suffix.length &&
262262
startsWith(relativeToBaseUrl, prefix) &&
263-
endsWith(relativeToBaseUrl, suffix)) {
263+
endsWith(relativeToBaseUrl, suffix) ||
264+
!suffix && relativeToBaseUrl === removeTrailingDirectorySeparator(prefix)) {
264265
const matchedStar = relativeToBaseUrl.substr(prefix.length, relativeToBaseUrl.length - suffix.length);
265266
return key.replace("*", matchedStar);
266267
}
@@ -318,6 +319,26 @@ namespace ts.moduleSpecifiers {
318319
return undefined;
319320
}
320321

322+
const packageRootPath = moduleFileName.substring(0, parts.packageRootIndex);
323+
const packageJsonPath = combinePaths(packageRootPath, "package.json");
324+
const packageJsonContent = host.fileExists!(packageJsonPath)
325+
? JSON.parse(host.readFile!(packageJsonPath)!)
326+
: undefined;
327+
const versionPaths = packageJsonContent && packageJsonContent.typesVersions
328+
? getPackageJsonTypesVersionsPaths(packageJsonContent.typesVersions)
329+
: undefined;
330+
if (versionPaths) {
331+
const subModuleName = moduleFileName.slice(parts.packageRootIndex + 1);
332+
const fromPaths = tryGetModuleNameFromPaths(
333+
removeFileExtension(subModuleName),
334+
removeExtensionAndIndexPostFix(subModuleName, ModuleResolutionKind.NodeJs, /*addJsExtension*/ false),
335+
versionPaths.paths
336+
);
337+
if (fromPaths !== undefined) {
338+
moduleFileName = combinePaths(moduleFileName.slice(0, parts.packageRootIndex), fromPaths);
339+
}
340+
}
341+
321342
// Simplify the full file path to something that can be resolved by Node.
322343

323344
// If the module could be imported by a directory name, use that directory's name
@@ -330,17 +351,12 @@ namespace ts.moduleSpecifiers {
330351

331352
function getDirectoryOrExtensionlessFileName(path: string): string {
332353
// If the file is the main module, it can be imported by the package name
333-
const packageRootPath = path.substring(0, parts.packageRootIndex);
334-
const packageJsonPath = combinePaths(packageRootPath, "package.json");
335-
if (host.fileExists!(packageJsonPath)) { // TODO: GH#18217
336-
const packageJsonContent = JSON.parse(host.readFile!(packageJsonPath)!);
337-
if (packageJsonContent) {
338-
const mainFileRelative = packageJsonContent.typings || packageJsonContent.types || packageJsonContent.main;
339-
if (mainFileRelative) {
340-
const mainExportFile = toPath(mainFileRelative, packageRootPath, getCanonicalFileName);
341-
if (removeFileExtension(mainExportFile) === removeFileExtension(getCanonicalFileName(path))) {
342-
return packageRootPath;
343-
}
354+
if (packageJsonContent) {
355+
const mainFileRelative = packageJsonContent.typings || packageJsonContent.types || packageJsonContent.main;
356+
if (mainFileRelative) {
357+
const mainExportFile = toPath(mainFileRelative, packageRootPath, getCanonicalFileName);
358+
if (removeFileExtension(mainExportFile) === removeFileExtension(getCanonicalFileName(path))) {
359+
return packageRootPath;
344360
}
345361
}
346362
}

src/compiler/transformers/declarations.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,13 @@ namespace ts {
289289
if (startsWith(fileName, "./") && hasExtension(fileName)) {
290290
fileName = fileName.substring(2);
291291
}
292+
293+
// omit references to files from node_modules (npm may disambiguate module
294+
// references when installing this package, making the path is unreliable).
295+
if (startsWith(fileName, "node_modules/") || fileName.indexOf("/node_modules/") !== -1) {
296+
return;
297+
}
298+
292299
references.push({ pos: -1, end: -1, fileName });
293300
}
294301
};

src/services/pathCompletions.ts

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -107,24 +107,10 @@ namespace ts.Completions.PathCompletions {
107107

108108
// const absolutePath = normalizeAndPreserveTrailingSlash(isRootedDiskPath(fragment) ? fragment : combinePaths(scriptPath, fragment)); // TODO(rbuckton): should use resolvePaths
109109
const absolutePath = resolvePath(scriptPath, fragment);
110-
let baseDirectory = hasTrailingDirectorySeparator(absolutePath) ? absolutePath : getDirectoryPath(absolutePath);
110+
const baseDirectory = hasTrailingDirectorySeparator(absolutePath) ? absolutePath : getDirectoryPath(absolutePath);
111111
const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames());
112112

113113
if (tryDirectoryExists(host, baseDirectory)) {
114-
// check for a version redirect
115-
const packageJsonPath = findPackageJson(baseDirectory, host);
116-
if (packageJsonPath) {
117-
const packageJson = readJson(packageJsonPath, host as { readFile: (filename: string) => string | undefined });
118-
const typesVersions = (packageJson as any).typesVersions;
119-
if (typeof typesVersions === "object") {
120-
const result = getPackageJsonTypesVersionsOverride(typesVersions);
121-
const versionPath = result && result.directory;
122-
if (versionPath) {
123-
baseDirectory = resolvePath(baseDirectory, versionPath);
124-
}
125-
}
126-
}
127-
128114
// Enumerate the available files if possible
129115
const files = tryReadDirectory(host, baseDirectory, extensions, /*exclude*/ undefined, /*include*/ ["./*"]);
130116

@@ -165,11 +151,41 @@ namespace ts.Completions.PathCompletions {
165151
}
166152
}
167153
}
154+
155+
// check for a version redirect
156+
const packageJsonPath = findPackageJson(baseDirectory, host);
157+
if (packageJsonPath) {
158+
const packageJson = readJson(packageJsonPath, host as { readFile: (filename: string) => string | undefined });
159+
const typesVersions = (packageJson as any).typesVersions;
160+
if (typeof typesVersions === "object") {
161+
const versionResult = getPackageJsonTypesVersionsPaths(typesVersions);
162+
const versionPaths = versionResult && versionResult.paths;
163+
const rest = absolutePath.slice(ensureTrailingDirectorySeparator(baseDirectory).length);
164+
if (versionPaths) {
165+
addCompletionEntriesFromPaths(result, rest, baseDirectory, extensions, versionPaths, host);
166+
}
167+
}
168+
}
168169
}
169170

170171
return result;
171172
}
172173

174+
function addCompletionEntriesFromPaths(result: NameAndKind[], fragment: string, baseDirectory: string, fileExtensions: ReadonlyArray<string>, paths: MapLike<string[]>, host: LanguageServiceHost) {
175+
for (const path in paths) {
176+
if (!hasProperty(paths, path)) continue;
177+
const patterns = paths[path];
178+
if (patterns) {
179+
for (const { name, kind } of getCompletionsForPathMapping(path, patterns, fragment, baseDirectory, fileExtensions, host)) {
180+
// Path mappings may provide a duplicate way to get to something we've already added, so don't add again.
181+
if (!result.some(entry => entry.name === name)) {
182+
result.push(nameAndKind(name, kind));
183+
}
184+
}
185+
}
186+
}
187+
}
188+
173189
/**
174190
* Check all of the declared modules and those in node modules. Possible sources of modules:
175191
* Modules that are found by the type checker
@@ -185,19 +201,10 @@ namespace ts.Completions.PathCompletions {
185201
const fileExtensions = getSupportedExtensionsForModuleResolution(compilerOptions);
186202
if (baseUrl) {
187203
const projectDir = compilerOptions.project || host.getCurrentDirectory();
188-
const absolute = isRootedDiskPath(baseUrl) ? baseUrl : combinePaths(projectDir, baseUrl);
189-
getCompletionEntriesForDirectoryFragment(fragment, normalizePath(absolute), fileExtensions, /*includeExtensions*/ false, host, /*exclude*/ undefined, result);
190-
191-
for (const path in paths!) {
192-
const patterns = paths![path];
193-
if (paths!.hasOwnProperty(path) && patterns) {
194-
for (const { name, kind } of getCompletionsForPathMapping(path, patterns, fragment, baseUrl, fileExtensions, host)) {
195-
// Path mappings may provide a duplicate way to get to something we've already added, so don't add again.
196-
if (!result.some(entry => entry.name === name)) {
197-
result.push(nameAndKind(name, kind));
198-
}
199-
}
200-
}
204+
const absolute = normalizePath(combinePaths(projectDir, baseUrl));
205+
getCompletionEntriesForDirectoryFragment(fragment, absolute, fileExtensions, /*includeExtensions*/ false, host, /*exclude*/ undefined, result);
206+
if (paths) {
207+
addCompletionEntriesFromPaths(result, fragment, absolute, fileExtensions, paths, host);
201208
}
202209
}
203210

tests/baselines/reference/duplicatePackage_relativeImportWithinPackage.trace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@
3333
"======== Resolving module 'foo' from '/node_modules/a/index.d.ts'. ========",
3434
"Module resolution kind is not specified, using 'NodeJs'.",
3535
"Loading module 'foo' from 'node_modules' folder, target file type 'TypeScript'.",
36-
"'package.json' does not have a 'typesVersions' field.",
3736
"'package.json' does not have a 'typings' field.",
3837
"'package.json' does not have a 'types' field.",
3938
"'package.json' does not have a 'main' field.",
39+
"'package.json' does not have a 'typesVersions' field.",
4040
"Found 'package.json' at '/node_modules/a/node_modules/foo/package.json'. Package ID is 'foo/[email protected]'.",
4141
"File '/node_modules/a/node_modules/foo.ts' does not exist.",
4242
"File '/node_modules/a/node_modules/foo.tsx' does not exist.",

tests/baselines/reference/duplicatePackage_relativeImportWithinPackage_scoped.trace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@
3333
"======== Resolving module '@foo/bar' from '/node_modules/a/index.d.ts'. ========",
3434
"Module resolution kind is not specified, using 'NodeJs'.",
3535
"Loading module '@foo/bar' from 'node_modules' folder, target file type 'TypeScript'.",
36-
"'package.json' does not have a 'typesVersions' field.",
3736
"'package.json' does not have a 'typings' field.",
3837
"'package.json' does not have a 'types' field.",
3938
"'package.json' does not have a 'main' field.",
39+
"'package.json' does not have a 'typesVersions' field.",
4040
"Found 'package.json' at '/node_modules/a/node_modules/@foo/bar/package.json'. Package ID is '@foo/bar/[email protected]'.",
4141
"File '/node_modules/a/node_modules/@foo/bar.ts' does not exist.",
4242
"File '/node_modules/a/node_modules/@foo/bar.tsx' does not exist.",

tests/baselines/reference/library-reference-10.trace.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
[
22
"======== Resolving type reference directive 'jquery', containing file '/foo/consumer.ts', root directory './types'. ========",
33
"Resolving with primary search path './types'.",
4-
"'package.json' does not have a 'typesVersions' field.",
54
"'package.json' has 'typings' field 'jquery.d.ts' that references 'types/jquery/jquery.d.ts'.",
5+
"'package.json' does not have a 'typesVersions' field.",
66
"Found 'package.json' at './types/jquery/package.json'.",
7+
"'package.json' does not have a 'typesVersions' field.",
78
"'package.json' has 'typings' field 'jquery.d.ts' that references 'types/jquery/jquery.d.ts'.",
89
"File 'types/jquery/jquery.d.ts' exist - use it as a name resolution result.",
910
"Resolving real path for 'types/jquery/jquery.d.ts', result '/foo/types/jquery/jquery.d.ts'.",
1011
"======== Type reference directive 'jquery' was successfully resolved to '/foo/types/jquery/jquery.d.ts', primary: true. ========",
1112
"======== Resolving type reference directive 'jquery', containing file '/foo/__inferred type names__.ts', root directory './types'. ========",
1213
"Resolving with primary search path './types'.",
13-
"'package.json' does not have a 'typesVersions' field.",
1414
"'package.json' has 'typings' field 'jquery.d.ts' that references 'types/jquery/jquery.d.ts'.",
15+
"'package.json' does not have a 'typesVersions' field.",
1516
"Found 'package.json' at './types/jquery/package.json'.",
17+
"'package.json' does not have a 'typesVersions' field.",
1618
"'package.json' has 'typings' field 'jquery.d.ts' that references 'types/jquery/jquery.d.ts'.",
1719
"File 'types/jquery/jquery.d.ts' exist - use it as a name resolution result.",
1820
"Resolving real path for 'types/jquery/jquery.d.ts', result '/foo/types/jquery/jquery.d.ts'.",

tests/baselines/reference/library-reference-11.trace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
"Root directory cannot be determined, skipping primary search paths.",
44
"Looking up in 'node_modules' folder, initial location '/a/b'.",
55
"Directory '/a/b/node_modules' does not exist, skipping all lookups in it.",
6-
"'package.json' does not have a 'typesVersions' field.",
76
"'package.json' has 'typings' field 'jquery.d.ts' that references '/a/node_modules/jquery/jquery.d.ts'.",
7+
"'package.json' does not have a 'typesVersions' field.",
88
"Found 'package.json' at '/a/node_modules/jquery/package.json'.",
99
"File '/a/node_modules/jquery.d.ts' does not exist.",
1010
"'package.json' has 'typings' field 'jquery.d.ts' that references '/a/node_modules/jquery/jquery.d.ts'.",

tests/baselines/reference/library-reference-12.trace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
"Root directory cannot be determined, skipping primary search paths.",
44
"Looking up in 'node_modules' folder, initial location '/a/b'.",
55
"Directory '/a/b/node_modules' does not exist, skipping all lookups in it.",
6-
"'package.json' does not have a 'typesVersions' field.",
76
"'package.json' does not have a 'typings' field.",
87
"'package.json' has 'types' field 'dist/jquery.d.ts' that references '/a/node_modules/jquery/dist/jquery.d.ts'.",
8+
"'package.json' does not have a 'typesVersions' field.",
99
"Found 'package.json' at '/a/node_modules/jquery/package.json'.",
1010
"File '/a/node_modules/jquery.d.ts' does not exist.",
1111
"'package.json' does not have a 'typings' field.",

0 commit comments

Comments
 (0)