Skip to content

Commit 838b76e

Browse files
committed
Merge remote-tracking branch 'origin/master' into fixAPISampleTests
2 parents 1b6f0ea + 61fb222 commit 838b76e

13 files changed

+352
-19
lines changed

src/compiler/checker.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4091,7 +4091,14 @@ namespace ts {
40914091
}
40924092
else {
40934093
const contextFile = getSourceFileOfNode(getOriginalNode(context!.enclosingDeclaration))!;
4094-
return `"${file.moduleName || moduleSpecifiers.getModuleSpecifier(compilerOptions, contextFile, contextFile.path, file.path, context!.tracker.moduleResolverHost!)}"`;
4094+
return `"${file.moduleName || moduleSpecifiers.getModuleSpecifiers(
4095+
symbol,
4096+
compilerOptions,
4097+
contextFile,
4098+
context!.tracker.moduleResolverHost!,
4099+
context!.tracker.moduleResolverHost!.getSourceFiles!(),
4100+
{ importModuleSpecifierPreference: "non-relative" }
4101+
)[0]}"`;
40954102
}
40964103
}
40974104
const declaration = symbol.declarations[0];

src/compiler/moduleSpecifiers.ts

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,20 @@ namespace ts.moduleSpecifiers {
1515
// For each symlink/original for a module, returns a list of ways to import that file.
1616
export function getModuleSpecifiers(
1717
moduleSymbol: Symbol,
18-
program: Program,
18+
compilerOptions: CompilerOptions,
1919
importingSourceFile: SourceFile,
2020
host: ModuleSpecifierResolutionHost,
21+
files: ReadonlyArray<SourceFile>,
2122
preferences: ModuleSpecifierPreferences,
2223
): ReadonlyArray<ReadonlyArray<string>> {
2324
const ambient = tryGetModuleNameFromAmbientModule(moduleSymbol);
2425
if (ambient) return [[ambient]];
2526

26-
const compilerOptions = program.getCompilerOptions();
27-
const info = getInfo(compilerOptions, importingSourceFile, importingSourceFile.fileName, host);
28-
const modulePaths = getAllModulePaths(program, getSourceFileOfNode(moduleSymbol.valueDeclaration));
27+
const info = getInfo(compilerOptions, importingSourceFile, importingSourceFile.path, host);
28+
if (!files) {
29+
return Debug.fail("Files list must be present to resolve symlinks in specifier resolution");
30+
}
31+
const modulePaths = getAllModulePaths(files, getSourceFileOfNode(moduleSymbol.valueDeclaration), info.getCanonicalFileName, host);
2932

3033
const global = mapDefined(modulePaths, moduleFileName => getGlobalModuleSpecifier(moduleFileName, info, host, compilerOptions));
3134
return global.length ? global.map(g => [g]) : modulePaths.map(moduleFileName =>
@@ -130,15 +133,57 @@ namespace ts.moduleSpecifiers {
130133
return firstDefined(imports, ({ text }) => pathIsRelative(text) ? fileExtensionIs(text, Extension.Js) : undefined) || false;
131134
}
132135

136+
function discoverProbableSymlinks(files: ReadonlyArray<SourceFile>) {
137+
const symlinks = mapDefined(files, sf =>
138+
sf.resolvedModules && firstDefinedIterator(sf.resolvedModules.values(), res =>
139+
res && res.originalPath && res.resolvedFileName !== res.originalPath ? [res.resolvedFileName, res.originalPath] : undefined));
140+
const result = createMap<string>();
141+
if (symlinks) {
142+
for (const [resolvedPath, originalPath] of symlinks) {
143+
const resolvedParts = getPathComponents(resolvedPath);
144+
const originalParts = getPathComponents(originalPath);
145+
while (resolvedParts[resolvedParts.length - 1] === originalParts[originalParts.length - 1]) {
146+
resolvedParts.pop();
147+
originalParts.pop();
148+
}
149+
result.set(getPathFromPathComponents(originalParts), getPathFromPathComponents(resolvedParts));
150+
}
151+
}
152+
return result;
153+
}
154+
155+
function getAllModulePathsUsingIndirectSymlinks(files: ReadonlyArray<SourceFile>, target: string, getCanonicalFileName: (file: string) => string, host: ModuleSpecifierResolutionHost) {
156+
const links = discoverProbableSymlinks(files);
157+
const paths = arrayFrom(links.keys());
158+
let options: string[] | undefined;
159+
for (const path of paths) {
160+
const resolved = links.get(path)!;
161+
if (startsWith(target, resolved + "/")) {
162+
const relative = getRelativePathFromDirectory(resolved, target, getCanonicalFileName);
163+
const option = resolvePath(path, relative);
164+
if (!host.fileExists || host.fileExists(option)) {
165+
if (!options) options = [];
166+
options.push(option);
167+
}
168+
}
169+
}
170+
const resolvedtarget = host.getCurrentDirectory ? resolvePath(host.getCurrentDirectory(), target) : target;
171+
if (options) {
172+
options.push(resolvedtarget); // Since these are speculative, we also include the original resolved name as a possibility
173+
return options;
174+
}
175+
return [resolvedtarget];
176+
}
177+
133178
/**
134179
* Looks for a existing imports that use symlinks to this module.
135180
* Only if no symlink is available, the real path will be used.
136181
*/
137-
function getAllModulePaths(program: Program, { fileName }: SourceFile): ReadonlyArray<string> {
138-
const symlinks = mapDefined(program.getSourceFiles(), sf =>
182+
function getAllModulePaths(files: ReadonlyArray<SourceFile>, { fileName }: SourceFile, getCanonicalFileName: (file: string) => string, host: ModuleSpecifierResolutionHost): ReadonlyArray<string> {
183+
const symlinks = mapDefined(files, sf =>
139184
sf.resolvedModules && firstDefinedIterator(sf.resolvedModules.values(), res =>
140185
res && res.resolvedFileName === fileName ? res.originalPath : undefined));
141-
return symlinks.length === 0 ? [fileName] : symlinks;
186+
return symlinks.length === 0 ? getAllModulePathsUsingIndirectSymlinks(files, fileName, getCanonicalFileName, host) : symlinks;
142187
}
143188

144189
function getRelativePathNParents(relativePath: string): number {

src/harness/harness.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,6 +1108,7 @@ namespace Harness {
11081108
{ name: "noImplicitReferences", type: "boolean" },
11091109
{ name: "currentDirectory", type: "string" },
11101110
{ name: "symlink", type: "string" },
1111+
{ name: "link", type: "string" },
11111112
// Emitted js baseline will print full paths for every output file
11121113
{ name: "fullEmitPaths", type: "boolean" }
11131114
];
@@ -1179,7 +1180,9 @@ namespace Harness {
11791180
harnessSettings: TestCaseParser.CompilerSettings | undefined,
11801181
compilerOptions: ts.CompilerOptions | undefined,
11811182
// Current directory is needed for rwcRunner to be able to use currentDirectory defined in json file
1182-
currentDirectory: string | undefined): compiler.CompilationResult {
1183+
currentDirectory: string | undefined,
1184+
symlinks?: vfs.FileSet
1185+
): compiler.CompilationResult {
11831186
const options: ts.CompilerOptions & HarnessOptions = compilerOptions ? ts.cloneCompilerOptions(compilerOptions) : { noResolve: false };
11841187
options.target = options.target || ts.ScriptTarget.ES3;
11851188
options.newLine = options.newLine || ts.NewLineKind.CarriageReturnLineFeed;
@@ -1216,6 +1219,9 @@ namespace Harness {
12161219

12171220
const docs = inputFiles.concat(otherFiles).map(documents.TextDocument.fromTestFile);
12181221
const fs = vfs.createFromFileSystem(IO, !useCaseSensitiveFileNames, { documents: docs, cwd: currentDirectory });
1222+
if (symlinks) {
1223+
fs.apply(symlinks);
1224+
}
12191225
const host = new fakes.CompilerHost(fs, options);
12201226
return compiler.compileFiles(host, programFileNames, options);
12211227
}
@@ -1836,6 +1842,7 @@ namespace Harness {
18361842

18371843
// Regex for parsing options in the format "@Alpha: Value of any sort"
18381844
const optionRegex = /^[\/]{2}\s*@(\w+)\s*:\s*([^\r\n]*)/gm; // multiple matches on multiple lines
1845+
const linkRegex = /^[\/]{2}\s*@link\s*:\s*([^\r\n]*)\s*->\s*([^\r\n]*)/gm; // multiple matches on multiple lines
18391846

18401847
export function extractCompilerSettings(content: string): CompilerSettings {
18411848
const opts: CompilerSettings = {};
@@ -1855,6 +1862,7 @@ namespace Harness {
18551862
testUnitData: TestUnitData[];
18561863
tsConfig: ts.ParsedCommandLine | undefined;
18571864
tsConfigFileUnitData: TestUnitData | undefined;
1865+
symlinks?: vfs.FileSet;
18581866
}
18591867

18601868
/** Given a test file containing // @FileName directives, return an array of named units of code to be added to an existing compiler instance */
@@ -1869,10 +1877,16 @@ namespace Harness {
18691877
let currentFileOptions: any = {};
18701878
let currentFileName: any;
18711879
let refs: string[] = [];
1880+
let symlinks: vfs.FileSet | undefined;
18721881

18731882
for (const line of lines) {
1874-
const testMetaData = optionRegex.exec(line);
1875-
if (testMetaData) {
1883+
let testMetaData: RegExpExecArray | null;
1884+
const linkMetaData = linkRegex.exec(line);
1885+
if (linkMetaData) {
1886+
if (!symlinks) symlinks = {};
1887+
symlinks[linkMetaData[2].trim()] = new vfs.Symlink(linkMetaData[1].trim());
1888+
}
1889+
else if (testMetaData = optionRegex.exec(line)) {
18761890
// Comment line, check for global/file @options and record them
18771891
optionRegex.lastIndex = 0;
18781892
const metaDataName = testMetaData[1].toLowerCase();
@@ -1961,7 +1975,7 @@ namespace Harness {
19611975
break;
19621976
}
19631977
}
1964-
return { settings, testUnitData, tsConfig, tsConfigFileUnitData };
1978+
return { settings, testUnitData, tsConfig, tsConfigFileUnitData, symlinks };
19651979
}
19661980
}
19671981

src/harness/vfs.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -911,7 +911,7 @@ namespace vfs {
911911
if (this.stringComparer(vpath.dirname(path), path) === 0) {
912912
throw new TypeError("Roots cannot be symbolic links.");
913913
}
914-
this.symlinkSync(entry.symlink, path);
914+
this.symlinkSync(vpath.resolve(dirname, entry.symlink), path);
915915
this._applyFileExtendedOptions(path, entry);
916916
}
917917
else if (entry instanceof Link) {
@@ -1078,8 +1078,7 @@ namespace vfs {
10781078
if (symlink) {
10791079
for (const link of symlink.split(",").map(link => link.trim())) {
10801080
fs.mkdirpSync(vpath.dirname(link));
1081-
fs.symlinkSync(document.file, link);
1082-
fs.filemeta(link).set("document", document);
1081+
fs.symlinkSync(vpath.resolve(fs.cwd(), document.file), link);
10831082
}
10841083
}
10851084
}

src/parser/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5255,6 +5255,7 @@ namespace ts {
52555255
useCaseSensitiveFileNames?(): boolean;
52565256
fileExists?(path: string): boolean;
52575257
readFile?(path: string): string | undefined;
5258+
getSourceFiles?(): ReadonlyArray<SourceFile>; // Used for cached resolutions to find symlinks without traversing the fs (again)
52585259
}
52595260

52605261
/** @deprecated See comment on SymbolWriter */

src/services/codefixes/importFixes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ namespace ts.codefix {
247247
preferences: UserPreferences,
248248
): ReadonlyArray<NewImportInfo> {
249249
const choicesForEachExportingModule = flatMap<SymbolExportInfo, NewImportInfo[]>(moduleSymbols, ({ moduleSymbol, importKind }) => {
250-
const modulePathsGroups = moduleSpecifiers.getModuleSpecifiers(moduleSymbol, program, sourceFile, host, preferences);
250+
const modulePathsGroups = moduleSpecifiers.getModuleSpecifiers(moduleSymbol, program.getCompilerOptions(), sourceFile, host, program.getSourceFiles(), preferences);
251251
return modulePathsGroups.map(group => group.map(moduleSpecifier => ({ moduleSpecifier, importKind })));
252252
});
253253
// Sort to keep the shortest paths first, but keep [relativePath, importRelativeToBaseUrl] groups together

src/testRunner/compilerRunner.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,9 @@ class CompilerTest {
179179
this.otherFiles,
180180
this.harnessSettings,
181181
/*options*/ tsConfigOptions,
182-
/*currentDirectory*/ this.harnessSettings.currentDirectory);
182+
/*currentDirectory*/ this.harnessSettings.currentDirectory,
183+
testCaseContent.symlinks
184+
);
183185

184186
this.options = this.result.options;
185187
}

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4467,6 +4467,7 @@ declare namespace ts {
44674467
useCaseSensitiveFileNames?(): boolean;
44684468
fileExists?(path: string): boolean;
44694469
readFile?(path: string): string | undefined;
4470+
getSourceFiles?(): ReadonlyArray<SourceFile>;
44704471
}
44714472
/** @deprecated See comment on SymbolWriter */
44724473
interface SymbolTracker {
@@ -9174,7 +9175,7 @@ declare namespace ts.moduleSpecifiers {
91749175
importModuleSpecifierPreference?: "relative" | "non-relative";
91759176
}
91769177
function getModuleSpecifier(compilerOptions: CompilerOptions, fromSourceFile: SourceFile, fromSourceFileName: string, toFileName: string, host: ModuleSpecifierResolutionHost, preferences?: ModuleSpecifierPreferences): string;
9177-
function getModuleSpecifiers(moduleSymbol: Symbol, program: Program, importingSourceFile: SourceFile, host: ModuleSpecifierResolutionHost, preferences: ModuleSpecifierPreferences): ReadonlyArray<ReadonlyArray<string>>;
9178+
function getModuleSpecifiers(moduleSymbol: Symbol, compilerOptions: CompilerOptions, importingSourceFile: SourceFile, host: ModuleSpecifierResolutionHost, files: ReadonlyArray<SourceFile>, preferences: ModuleSpecifierPreferences): ReadonlyArray<ReadonlyArray<string>>;
91789179
}
91799180
declare namespace ts {
91809181
/**

tests/baselines/reference/api/typescript.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4467,6 +4467,7 @@ declare namespace ts {
44674467
useCaseSensitiveFileNames?(): boolean;
44684468
fileExists?(path: string): boolean;
44694469
readFile?(path: string): string | undefined;
4470+
getSourceFiles?(): ReadonlyArray<SourceFile>;
44704471
}
44714472
/** @deprecated See comment on SymbolWriter */
44724473
interface SymbolTracker {
@@ -9174,7 +9175,7 @@ declare namespace ts.moduleSpecifiers {
91749175
importModuleSpecifierPreference?: "relative" | "non-relative";
91759176
}
91769177
function getModuleSpecifier(compilerOptions: CompilerOptions, fromSourceFile: SourceFile, fromSourceFileName: string, toFileName: string, host: ModuleSpecifierResolutionHost, preferences?: ModuleSpecifierPreferences): string;
9177-
function getModuleSpecifiers(moduleSymbol: Symbol, program: Program, importingSourceFile: SourceFile, host: ModuleSpecifierResolutionHost, preferences: ModuleSpecifierPreferences): ReadonlyArray<ReadonlyArray<string>>;
9178+
function getModuleSpecifiers(moduleSymbol: Symbol, compilerOptions: CompilerOptions, importingSourceFile: SourceFile, host: ModuleSpecifierResolutionHost, files: ReadonlyArray<SourceFile>, preferences: ModuleSpecifierPreferences): ReadonlyArray<ReadonlyArray<string>>;
91789179
}
91799180
declare namespace ts {
91809181
/**
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//// [tests/cases/compiler/symbolLinkDeclarationEmitModuleNames.ts] ////
2+
3+
//// [application.ts]
4+
import { Constructor } from "@loopback/context";
5+
export type ControllerClass = Constructor<any>;
6+
//// [usage.ts]
7+
import { ControllerClass } from './application';
8+
import { BindingKey } from '@loopback/context';
9+
10+
export const CONTROLLER_CLASS = BindingKey.create<ControllerClass>(null as any); // line in question
11+
//// [value-promise.ts]
12+
export type Constructor<T> = (...args: any[]) => T;
13+
//// [bindingkey.ts]
14+
import { Constructor } from "@loopback/context"
15+
export class BindingKey<T> {
16+
readonly __type: T;
17+
static create<T extends Constructor<any>>(ctor: T) {
18+
return new BindingKey<T>();
19+
}
20+
}
21+
22+
//// [index.ts]
23+
export * from "./src/value-promise";
24+
export * from "./src/bindingkey";
25+
26+
27+
//// [value-promise.js]
28+
"use strict";
29+
exports.__esModule = true;
30+
//// [bindingkey.js]
31+
"use strict";
32+
exports.__esModule = true;
33+
var BindingKey = /** @class */ (function () {
34+
function BindingKey() {
35+
}
36+
BindingKey.create = function (ctor) {
37+
return new BindingKey();
38+
};
39+
return BindingKey;
40+
}());
41+
exports.BindingKey = BindingKey;
42+
//// [index.js]
43+
"use strict";
44+
function __export(m) {
45+
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
46+
}
47+
exports.__esModule = true;
48+
__export(require("./src/bindingkey"));
49+
//// [application.js]
50+
"use strict";
51+
exports.__esModule = true;
52+
//// [usage.js]
53+
"use strict";
54+
exports.__esModule = true;
55+
var context_1 = require("@loopback/context");
56+
exports.CONTROLLER_CLASS = context_1.BindingKey.create(null); // line in question
57+
58+
59+
//// [value-promise.d.ts]
60+
export declare type Constructor<T> = (...args: any[]) => T;
61+
//// [bindingkey.d.ts]
62+
import { Constructor } from "@loopback/context";
63+
export declare class BindingKey<T> {
64+
readonly __type: T;
65+
static create<T extends Constructor<any>>(ctor: T): BindingKey<T>;
66+
}
67+
//// [index.d.ts]
68+
export * from "./src/value-promise";
69+
export * from "./src/bindingkey";
70+
//// [application.d.ts]
71+
import { Constructor } from "@loopback/context";
72+
export declare type ControllerClass = Constructor<any>;
73+
//// [usage.d.ts]
74+
import { BindingKey } from '@loopback/context';
75+
export declare const CONTROLLER_CLASS: BindingKey<import("@loopback/context/src/value-promise").Constructor<any>>;
76+
77+
78+
//// [DtsFileErrors]
79+
80+
81+
tests/cases/compiler/monorepo/context/src/bindingkey.d.ts(1,29): error TS2307: Cannot find module '@loopback/context'.
82+
tests/cases/compiler/monorepo/core/src/application.d.ts(1,29): error TS2307: Cannot find module '@loopback/context'.
83+
tests/cases/compiler/monorepo/core/src/usage.d.ts(1,28): error TS2307: Cannot find module '@loopback/context'.
84+
tests/cases/compiler/monorepo/core/src/usage.d.ts(2,51): error TS2307: Cannot find module '@loopback/context/src/value-promise'.
85+
86+
87+
==== tests/cases/compiler/monorepo/core/src/application.d.ts (1 errors) ====
88+
import { Constructor } from "@loopback/context";
89+
~~~~~~~~~~~~~~~~~~~
90+
!!! error TS2307: Cannot find module '@loopback/context'.
91+
export declare type ControllerClass = Constructor<any>;
92+
93+
==== tests/cases/compiler/monorepo/core/src/usage.d.ts (2 errors) ====
94+
import { BindingKey } from '@loopback/context';
95+
~~~~~~~~~~~~~~~~~~~
96+
!!! error TS2307: Cannot find module '@loopback/context'.
97+
export declare const CONTROLLER_CLASS: BindingKey<import("@loopback/context/src/value-promise").Constructor<any>>;
98+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
99+
!!! error TS2307: Cannot find module '@loopback/context/src/value-promise'.
100+
101+
==== /.src/tests/cases/compiler/monorepo/context/src/value-promise.d.ts (0 errors) ====
102+
export declare type Constructor<T> = (...args: any[]) => T;
103+
104+
==== /.src/tests/cases/compiler/monorepo/context/src/bindingkey.d.ts (1 errors) ====
105+
import { Constructor } from "@loopback/context";
106+
~~~~~~~~~~~~~~~~~~~
107+
!!! error TS2307: Cannot find module '@loopback/context'.
108+
export declare class BindingKey<T> {
109+
readonly __type: T;
110+
static create<T extends Constructor<any>>(ctor: T): BindingKey<T>;
111+
}
112+
113+
==== /.src/tests/cases/compiler/monorepo/context/index.d.ts (0 errors) ====
114+
export * from "./src/value-promise";
115+
export * from "./src/bindingkey";
116+

0 commit comments

Comments
 (0)