Skip to content

Commit f64b8ad

Browse files
author
Andy
authored
Add "preserveSymlinks" option (#16661)
* Add "preserveSymlinks" option * Respond to PR comments
1 parent b3f3f33 commit f64b8ad

File tree

18 files changed

+200
-7
lines changed

18 files changed

+200
-7
lines changed

src/compiler/commandLineParser.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,6 @@ namespace ts {
340340
isTSConfigOnly: true,
341341
category: Diagnostics.Module_Resolution_Options,
342342
description: Diagnostics.A_series_of_entries_which_re_map_imports_to_lookup_locations_relative_to_the_baseUrl
343-
344343
},
345344
{
346345
// this option can only be specified in tsconfig.json
@@ -384,6 +383,12 @@ namespace ts {
384383
category: Diagnostics.Module_Resolution_Options,
385384
description: Diagnostics.Allow_default_imports_from_modules_with_no_default_export_This_does_not_affect_code_emit_just_typechecking
386385
},
386+
{
387+
name: "preserveSymlinks",
388+
type: "boolean",
389+
category: Diagnostics.Module_Resolution_Options,
390+
description: Diagnostics.Do_not_resolve_the_real_path_of_symlinks,
391+
},
387392

388393
// Source Maps
389394
{

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2662,6 +2662,10 @@
26622662
"category": "Message",
26632663
"code": 6012
26642664
},
2665+
"Do not resolve the real path of symlinks.": {
2666+
"category": "Message",
2667+
"code": 6013
2668+
},
26652669
"Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'.": {
26662670
"category": "Message",
26672671
"code": 6015

src/compiler/moduleNameResolver.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,10 @@ namespace ts {
200200

201201
let resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined;
202202
if (resolved) {
203-
resolved = realpath(resolved, host, traceEnabled);
203+
if (!options.preserveSymlinks) {
204+
resolved = realpath(resolved, host, traceEnabled);
205+
}
206+
204207
if (traceEnabled) {
205208
trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2, typeReferenceDirectiveName, resolved, primary);
206209
}
@@ -734,8 +737,14 @@ namespace ts {
734737
trace(host, Diagnostics.Loading_module_0_from_node_modules_folder_target_file_type_1, moduleName, Extensions[extensions]);
735738
}
736739
const resolved = loadModuleFromNodeModules(extensions, moduleName, containingDirectory, failedLookupLocations, state, cache);
740+
if (!resolved) return undefined;
741+
742+
let resolvedValue = resolved.value;
743+
if (!compilerOptions.preserveSymlinks) {
744+
resolvedValue = resolvedValue && { ...resolved.value, path: realpath(resolved.value.path, host, traceEnabled), extension: resolved.value.extension };
745+
}
737746
// For node_modules lookups, get the real path so that multiple accesses to an `npm link`-ed module do not create duplicate files.
738-
return resolved && { value: resolved.value && { resolved: { ...resolved.value, path: realpath(resolved.value.path, host, traceEnabled) }, isExternalLibraryImport: true } };
747+
return { value: resolvedValue && { resolved: resolvedValue, isExternalLibraryImport: true } };
739748
}
740749
else {
741750
const candidate = normalizePath(combinePaths(containingDirectory, moduleName));

src/compiler/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3608,6 +3608,7 @@ namespace ts {
36083608
paths?: MapLike<string[]>;
36093609
/*@internal*/ plugins?: PluginImport[];
36103610
preserveConstEnums?: boolean;
3611+
preserveSymlinks?: boolean;
36113612
project?: string;
36123613
/* @internal */ pretty?: DiagnosticStyle;
36133614
reactNamespace?: string;
@@ -3923,6 +3924,10 @@ namespace ts {
39233924
readFile(fileName: string): string | undefined;
39243925
trace?(s: string): void;
39253926
directoryExists?(directoryName: string): boolean;
3927+
/**
3928+
* Resolve a symbolic link.
3929+
* @see https://nodejs.org/api/fs.html#fs_fs_realpathsync_path_options
3930+
*/
39263931
realpath?(path: string): string;
39273932
getCurrentDirectory?(): string;
39283933
getDirectories?(path: string): string[];

src/harness/unittests/moduleResolution.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,19 @@ namespace ts {
2626
interface File {
2727
name: string;
2828
content?: string;
29+
symlinks?: string[];
2930
}
3031

3132
function createModuleResolutionHost(hasDirectoryExists: boolean, ...files: File[]): ModuleResolutionHost {
32-
const map = arrayToMap(files, f => f.name);
33+
const map = createMap<File>();
34+
for (const file of files) {
35+
map.set(file.name, file);
36+
if (file.symlinks) {
37+
for (const symlink of file.symlinks) {
38+
map.set(symlink, file);
39+
}
40+
}
41+
}
3342

3443
if (hasDirectoryExists) {
3544
const directories = createMap<string>();
@@ -46,6 +55,7 @@ namespace ts {
4655
}
4756
return {
4857
readFile,
58+
realpath,
4959
directoryExists: path => directories.has(path),
5060
fileExists: path => {
5161
assert.isTrue(directories.has(getDirectoryPath(path)), `'fileExists' '${path}' request in non-existing directory`);
@@ -54,12 +64,15 @@ namespace ts {
5464
};
5565
}
5666
else {
57-
return { readFile, fileExists: path => map.has(path) };
67+
return { readFile, realpath, fileExists: path => map.has(path) };
5868
}
5969
function readFile(path: string): string | undefined {
6070
const file = map.get(path);
6171
return file && file.content;
6272
}
73+
function realpath(path: string): string {
74+
return map.get(path).name;
75+
}
6376
}
6477

6578
describe("Node module resolution - relative paths", () => {
@@ -233,8 +246,8 @@ namespace ts {
233246
test(/*hasDirectoryExists*/ true);
234247

235248
function test(hasDirectoryExists: boolean) {
236-
const containingFile = { name: "/a/node_modules/b/c/node_modules/d/e.ts" };
237-
const moduleFile = { name: "/a/node_modules/foo/index.d.ts" };
249+
const containingFile: File = { name: "/a/node_modules/b/c/node_modules/d/e.ts" };
250+
const moduleFile: File = { name: "/a/node_modules/foo/index.d.ts" };
238251
const resolution = nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile));
239252
checkResolvedModuleWithFailedLookupLocations(resolution, createResolvedModule(moduleFile.name, /*isExternalLibraryImport*/ true), [
240253
"/a/node_modules/b/c/node_modules/d/node_modules/foo.ts",
@@ -289,6 +302,19 @@ namespace ts {
289302
]);
290303
}
291304
});
305+
306+
testPreserveSymlinks(/*preserveSymlinks*/ false);
307+
testPreserveSymlinks(/*preserveSymlinks*/ true);
308+
function testPreserveSymlinks(preserveSymlinks: boolean) {
309+
it(`preserveSymlinks: ${preserveSymlinks}`, () => {
310+
const realFileName = "/linked/index.d.ts";
311+
const symlinkFileName = "/app/node_modulex/linked/index.d.ts";
312+
const host = createModuleResolutionHost(/*hasDirectoryExists*/ true, { name: realFileName, symlinks: [symlinkFileName] });
313+
const resolution = nodeModuleNameResolver("linked", "/app/app.ts", { preserveSymlinks }, host);
314+
const resolvedFileName = preserveSymlinks ? symlinkFileName : realFileName;
315+
checkResolvedModule(resolution.resolvedModule, { resolvedFileName, isExternalLibraryImport: true, extension: Extension.Dts });
316+
});
317+
}
292318
});
293319

294320
describe("Module resolution - relative imports", () => {

src/server/protocol.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2429,6 +2429,7 @@ namespace ts.server.protocol {
24292429
paths?: MapLike<string[]>;
24302430
plugins?: PluginImport[];
24312431
preserveConstEnums?: boolean;
2432+
preserveSymlinks?: boolean;
24322433
project?: string;
24332434
reactNamespace?: string;
24342435
removeComments?: boolean;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/app/app.ts(9,1): error TS90010: Type 'C' is not assignable to type 'C'. Two different types with this name exist, but they are unrelated.
2+
Types have separate declarations of a private property 'x'.
3+
4+
5+
==== /app/app.ts (1 errors) ====
6+
// We shouldn't resolve symlinks for references either. See the trace.
7+
/// <reference types="linked" />
8+
9+
import { C as C1 } from "linked";
10+
import { C as C2 } from "linked2";
11+
12+
let x = new C1();
13+
// Should fail. We no longer resolve any symlinks.
14+
x = new C2();
15+
~
16+
!!! error TS90010: Type 'C' is not assignable to type 'C'. Two different types with this name exist, but they are unrelated.
17+
!!! error TS90010: Types have separate declarations of a private property 'x'.
18+
19+
==== /linked/index.d.ts (0 errors) ====
20+
export { real } from "real";
21+
export class C { private x; }
22+
23+
==== /app/node_modules/real/index.d.ts (0 errors) ====
24+
export const real: string;
25+
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//// [tests/cases/compiler/moduleResolutionWithSymlinks_preserveSymlinks.ts] ////
2+
3+
//// [index.d.ts]
4+
export { real } from "real";
5+
export class C { private x; }
6+
7+
//// [index.d.ts]
8+
export const real: string;
9+
10+
//// [app.ts]
11+
// We shouldn't resolve symlinks for references either. See the trace.
12+
/// <reference types="linked" />
13+
14+
import { C as C1 } from "linked";
15+
import { C as C2 } from "linked2";
16+
17+
let x = new C1();
18+
// Should fail. We no longer resolve any symlinks.
19+
x = new C2();
20+
21+
22+
//// [app.js]
23+
"use strict";
24+
// We shouldn't resolve symlinks for references either. See the trace.
25+
/// <reference types="linked" />
26+
exports.__esModule = true;
27+
var linked_1 = require("linked");
28+
var linked2_1 = require("linked2");
29+
var x = new linked_1.C();
30+
// Should fail. We no longer resolve any symlinks.
31+
x = new linked2_1.C();
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
[
2+
"======== Resolving type reference directive 'linked', containing file '/app/app.ts', root directory not set. ========",
3+
"Root directory cannot be determined, skipping primary search paths.",
4+
"Looking up in 'node_modules' folder, initial location '/app'.",
5+
"File '/app/node_modules/linked.d.ts' does not exist.",
6+
"File '/app/node_modules/linked/package.json' does not exist.",
7+
"File '/app/node_modules/linked/index.d.ts' exist - use it as a name resolution result.",
8+
"======== Type reference directive 'linked' was successfully resolved to '/app/node_modules/linked/index.d.ts', primary: false. ========",
9+
"======== Resolving module 'real' from '/app/node_modules/linked/index.d.ts'. ========",
10+
"Explicitly specified module resolution kind: 'NodeJs'.",
11+
"Loading module 'real' from 'node_modules' folder, target file type 'TypeScript'.",
12+
"Directory '/app/node_modules/linked/node_modules' does not exist, skipping all lookups in it.",
13+
"File '/app/node_modules/real.ts' does not exist.",
14+
"File '/app/node_modules/real.tsx' does not exist.",
15+
"File '/app/node_modules/real.d.ts' does not exist.",
16+
"File '/app/node_modules/real/package.json' does not exist.",
17+
"File '/app/node_modules/real/index.ts' does not exist.",
18+
"File '/app/node_modules/real/index.tsx' does not exist.",
19+
"File '/app/node_modules/real/index.d.ts' exist - use it as a name resolution result.",
20+
"======== Module name 'real' was successfully resolved to '/app/node_modules/real/index.d.ts'. ========",
21+
"======== Resolving module 'linked' from '/app/app.ts'. ========",
22+
"Explicitly specified module resolution kind: 'NodeJs'.",
23+
"Loading module 'linked' from 'node_modules' folder, target file type 'TypeScript'.",
24+
"File '/app/node_modules/linked.ts' does not exist.",
25+
"File '/app/node_modules/linked.tsx' does not exist.",
26+
"File '/app/node_modules/linked.d.ts' does not exist.",
27+
"File '/app/node_modules/linked/package.json' does not exist.",
28+
"File '/app/node_modules/linked/index.ts' does not exist.",
29+
"File '/app/node_modules/linked/index.tsx' does not exist.",
30+
"File '/app/node_modules/linked/index.d.ts' exist - use it as a name resolution result.",
31+
"======== Module name 'linked' was successfully resolved to '/app/node_modules/linked/index.d.ts'. ========",
32+
"======== Resolving module 'linked2' from '/app/app.ts'. ========",
33+
"Explicitly specified module resolution kind: 'NodeJs'.",
34+
"Loading module 'linked2' from 'node_modules' folder, target file type 'TypeScript'.",
35+
"File '/app/node_modules/linked2.ts' does not exist.",
36+
"File '/app/node_modules/linked2.tsx' does not exist.",
37+
"File '/app/node_modules/linked2.d.ts' does not exist.",
38+
"File '/app/node_modules/linked2/package.json' does not exist.",
39+
"File '/app/node_modules/linked2/index.ts' does not exist.",
40+
"File '/app/node_modules/linked2/index.tsx' does not exist.",
41+
"File '/app/node_modules/linked2/index.d.ts' exist - use it as a name resolution result.",
42+
"======== Module name 'linked2' was successfully resolved to '/app/node_modules/linked2/index.d.ts'. ========",
43+
"======== Resolving module 'real' from '/app/node_modules/linked2/index.d.ts'. ========",
44+
"Explicitly specified module resolution kind: 'NodeJs'.",
45+
"Loading module 'real' from 'node_modules' folder, target file type 'TypeScript'.",
46+
"Directory '/app/node_modules/linked2/node_modules' does not exist, skipping all lookups in it.",
47+
"File '/app/node_modules/real.ts' does not exist.",
48+
"File '/app/node_modules/real.tsx' does not exist.",
49+
"File '/app/node_modules/real.d.ts' does not exist.",
50+
"File '/app/node_modules/real/package.json' does not exist.",
51+
"File '/app/node_modules/real/index.ts' does not exist.",
52+
"File '/app/node_modules/real/index.tsx' does not exist.",
53+
"File '/app/node_modules/real/index.d.ts' exist - use it as a name resolution result.",
54+
"======== Module name 'real' was successfully resolved to '/app/node_modules/real/index.d.ts'. ========"
55+
]

tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
// "typeRoots": [], /* List of folders to include type definitions from. */
4040
// "types": [], /* Type declaration files to be included in compilation. */
4141
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
42+
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
4243

4344
/* Source Map Options */
4445
// "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */

0 commit comments

Comments
 (0)