fix symlink tag, support arbitrary (ie, directory) links via @link

Introduce indirect symlink lookup to specifier deriver

Use fileset, move exec

vfs path resolution :shakes fist:

Apply files symlink relative to dirname

Use directory function
This commit is contained in:
Wesley Wigham 2018-06-11 13:06:34 -07:00
parent 14da064754
commit 97c3c6056d
No known key found for this signature in database
GPG Key ID: D59F87F60C5400C9
11 changed files with 350 additions and 19 deletions

View File

@ -4093,7 +4093,14 @@ namespace ts {
}
else {
const contextFile = getSourceFileOfNode(getOriginalNode(context!.enclosingDeclaration))!;
return `"${file.moduleName || moduleSpecifiers.getModuleSpecifier(compilerOptions, contextFile, contextFile.path, file.path, context!.tracker.moduleResolverHost!)}"`;
return `"${file.moduleName || moduleSpecifiers.getModuleSpecifiers(
symbol,
compilerOptions,
contextFile,
context!.tracker.moduleResolverHost!,
context!.tracker.moduleResolverHost!.getSourceFiles!(),
{ importModuleSpecifierPreference: "non-relative" }
)[0]}"`;
}
}
const declaration = symbol.declarations[0];

View File

@ -15,17 +15,20 @@ namespace ts.moduleSpecifiers {
// For each symlink/original for a module, returns a list of ways to import that file.
export function getModuleSpecifiers(
moduleSymbol: Symbol,
program: Program,
compilerOptions: CompilerOptions,
importingSourceFile: SourceFile,
host: ModuleSpecifierResolutionHost,
files: ReadonlyArray<SourceFile>,
preferences: ModuleSpecifierPreferences,
): ReadonlyArray<ReadonlyArray<string>> {
const ambient = tryGetModuleNameFromAmbientModule(moduleSymbol);
if (ambient) return [[ambient]];
const compilerOptions = program.getCompilerOptions();
const info = getInfo(compilerOptions, importingSourceFile, importingSourceFile.fileName, host);
const modulePaths = getAllModulePaths(program, getSourceFileOfNode(moduleSymbol.valueDeclaration));
const info = getInfo(compilerOptions, importingSourceFile, importingSourceFile.path, host);
if (!files) {
return Debug.fail("Files list must be present to resolve symlinks in specifier resolution");
}
const modulePaths = getAllModulePaths(files, getSourceFileOfNode(moduleSymbol.valueDeclaration), info.getCanonicalFileName, host);
const global = mapDefined(modulePaths, moduleFileName => getGlobalModuleSpecifier(moduleFileName, info, host, compilerOptions));
return global.length ? global.map(g => [g]) : modulePaths.map(moduleFileName =>
@ -130,15 +133,57 @@ namespace ts.moduleSpecifiers {
return firstDefined(imports, ({ text }) => pathIsRelative(text) ? fileExtensionIs(text, Extension.Js) : undefined) || false;
}
function discoverProbableSymlinks(files: ReadonlyArray<SourceFile>) {
const symlinks = mapDefined(files, sf =>
sf.resolvedModules && firstDefinedIterator(sf.resolvedModules.values(), res =>
res && res.originalPath && res.resolvedFileName !== res.originalPath ? [res.resolvedFileName, res.originalPath] : undefined));
const result = createMap<string>();
if (symlinks) {
for (const [resolvedPath, originalPath] of symlinks) {
const resolvedParts = getPathComponents(resolvedPath);
const originalParts = getPathComponents(originalPath);
while (resolvedParts[resolvedParts.length - 1] === originalParts[originalParts.length - 1]) {
resolvedParts.pop();
originalParts.pop();
}
result.set(getPathFromPathComponents(originalParts), getPathFromPathComponents(resolvedParts));
}
}
return result;
}
function getAllModulePathsUsingIndirectSymlinks(files: ReadonlyArray<SourceFile>, target: string, getCanonicalFileName: (file: string) => string, host: ModuleSpecifierResolutionHost) {
const links = discoverProbableSymlinks(files);
const paths = arrayFrom(links.keys());
let options: string[] | undefined;
for (const path of paths) {
const resolved = links.get(path)!;
if (startsWith(target, resolved + "/")) {
const relative = getRelativePathFromDirectory(resolved, target, getCanonicalFileName);
const option = resolvePath(path, relative);
if (!host.fileExists || host.fileExists(option)) {
if (!options) options = [];
options.push(option);
}
}
}
const resolvedtarget = host.getCurrentDirectory ? resolvePath(host.getCurrentDirectory(), target) : target;
if (options) {
options.push(resolvedtarget); // Since these are speculative, we also include the original resolved name as a possibility
return options;
}
return [resolvedtarget];
}
/**
* Looks for a existing imports that use symlinks to this module.
* Only if no symlink is available, the real path will be used.
*/
function getAllModulePaths(program: Program, { fileName }: SourceFile): ReadonlyArray<string> {
const symlinks = mapDefined(program.getSourceFiles(), sf =>
function getAllModulePaths(files: ReadonlyArray<SourceFile>, { fileName }: SourceFile, getCanonicalFileName: (file: string) => string, host: ModuleSpecifierResolutionHost): ReadonlyArray<string> {
const symlinks = mapDefined(files, sf =>
sf.resolvedModules && firstDefinedIterator(sf.resolvedModules.values(), res =>
res && res.resolvedFileName === fileName ? res.originalPath : undefined));
return symlinks.length === 0 ? [fileName] : symlinks;
return symlinks.length === 0 ? getAllModulePathsUsingIndirectSymlinks(files, fileName, getCanonicalFileName, host) : symlinks;
}
function getRelativePathNParents(relativePath: string): number {

View File

@ -5284,6 +5284,7 @@ namespace ts {
useCaseSensitiveFileNames?(): boolean;
fileExists?(path: string): boolean;
readFile?(path: string): string | undefined;
getSourceFiles?(): ReadonlyArray<SourceFile>; // Used for cached resolutions to find symlinks without traversing the fs (again)
}
/** @deprecated See comment on SymbolWriter */

View File

@ -187,7 +187,9 @@ class CompilerTest {
this.otherFiles,
this.harnessSettings,
/*options*/ tsConfigOptions,
/*currentDirectory*/ this.harnessSettings.currentDirectory);
/*currentDirectory*/ this.harnessSettings.currentDirectory,
testCaseContent.symlinks
);
this.options = this.result.options;
}

View File

@ -1138,6 +1138,7 @@ namespace Harness {
{ name: "noImplicitReferences", type: "boolean" },
{ name: "currentDirectory", type: "string" },
{ name: "symlink", type: "string" },
{ name: "link", type: "string" },
// Emitted js baseline will print full paths for every output file
{ name: "fullEmitPaths", type: "boolean" }
];
@ -1209,7 +1210,9 @@ namespace Harness {
harnessSettings: TestCaseParser.CompilerSettings,
compilerOptions: ts.CompilerOptions,
// Current directory is needed for rwcRunner to be able to use currentDirectory defined in json file
currentDirectory: string): compiler.CompilationResult {
currentDirectory: string | undefined,
symlinks?: vfs.FileSet
): compiler.CompilationResult {
const options: ts.CompilerOptions & HarnessOptions = compilerOptions ? ts.cloneCompilerOptions(compilerOptions) : { noResolve: false };
options.target = options.target || ts.ScriptTarget.ES3;
options.newLine = options.newLine || ts.NewLineKind.CarriageReturnLineFeed;
@ -1246,6 +1249,9 @@ namespace Harness {
const docs = inputFiles.concat(otherFiles).map(documents.TextDocument.fromTestFile);
const fs = vfs.createFromFileSystem(IO, !useCaseSensitiveFileNames, { documents: docs, cwd: currentDirectory });
if (symlinks) {
fs.apply(symlinks);
}
const host = new fakes.CompilerHost(fs, options);
return compiler.compileFiles(host, programFileNames, options);
}
@ -1858,6 +1864,7 @@ namespace Harness {
// Regex for parsing options in the format "@Alpha: Value of any sort"
const optionRegex = /^[\/]{2}\s*@(\w+)\s*:\s*([^\r\n]*)/gm; // multiple matches on multiple lines
const linkRegex = /^[\/]{2}\s*@link\s*:\s*([^\r\n]*)\s*->\s*([^\r\n]*)/gm; // multiple matches on multiple lines
export function extractCompilerSettings(content: string): CompilerSettings {
const opts: CompilerSettings = {};
@ -1875,8 +1882,9 @@ namespace Harness {
export interface TestCaseContent {
settings: CompilerSettings;
testUnitData: TestUnitData[];
tsConfig: ts.ParsedCommandLine;
tsConfigFileUnitData: TestUnitData;
tsConfig: ts.ParsedCommandLine | undefined;
tsConfigFileUnitData: TestUnitData | undefined;
symlinks?: vfs.FileSet;
}
/** Given a test file containing // @FileName directives, return an array of named units of code to be added to an existing compiler instance */
@ -1891,10 +1899,16 @@ namespace Harness {
let currentFileOptions: any = {};
let currentFileName: any;
let refs: string[] = [];
let symlinks: vfs.FileSet | undefined;
for (const line of lines) {
const testMetaData = optionRegex.exec(line);
if (testMetaData) {
let testMetaData: RegExpExecArray | null;
const linkMetaData = linkRegex.exec(line);
if (linkMetaData) {
if (!symlinks) symlinks = {};
symlinks[linkMetaData[2].trim()] = new vfs.Symlink(linkMetaData[1].trim());
}
else if (testMetaData = optionRegex.exec(line)) {
// Comment line, check for global/file @options and record them
optionRegex.lastIndex = 0;
const metaDataName = testMetaData[1].toLowerCase();
@ -1983,7 +1997,7 @@ namespace Harness {
break;
}
}
return { settings, testUnitData, tsConfig, tsConfigFileUnitData };
return { settings, testUnitData, tsConfig, tsConfigFileUnitData, symlinks };
}
}

View File

@ -883,7 +883,7 @@ namespace vfs {
if (this.stringComparer(vpath.dirname(path), path) === 0) {
throw new TypeError("Roots cannot be symbolic links.");
}
this.symlinkSync(entry.symlink, path);
this.symlinkSync(vpath.resolve(dirname, entry.symlink), path);
this._applyFileExtendedOptions(path, entry);
}
else if (entry instanceof Link) {
@ -1050,8 +1050,7 @@ namespace vfs {
if (symlink) {
for (const link of symlink.split(",").map(link => link.trim())) {
fs.mkdirpSync(vpath.dirname(link));
fs.symlinkSync(document.file, link);
fs.filemeta(link).set("document", document);
fs.symlinkSync(vpath.resolve(fs.cwd(), document.file), link);
}
}
}

View File

@ -237,7 +237,7 @@ namespace ts.codefix {
preferences: UserPreferences,
): ReadonlyArray<NewImportInfo> {
const choicesForEachExportingModule = flatMap<SymbolExportInfo, NewImportInfo[]>(moduleSymbols, ({ moduleSymbol, importKind }) => {
const modulePathsGroups = moduleSpecifiers.getModuleSpecifiers(moduleSymbol, program, sourceFile, host, preferences);
const modulePathsGroups = moduleSpecifiers.getModuleSpecifiers(moduleSymbol, program.getCompilerOptions(), sourceFile, host, program.getSourceFiles(), preferences);
return modulePathsGroups.map(group => group.map(moduleSpecifier => ({ moduleSpecifier, importKind })));
});
// Sort to keep the shortest paths first, but keep [relativePath, importRelativeToBaseUrl] groups together

View File

@ -0,0 +1,116 @@
//// [tests/cases/compiler/symbolLinkDeclarationEmitModuleNames.ts] ////
//// [application.ts]
import { Constructor } from "@loopback/context";
export type ControllerClass = Constructor<any>;
//// [usage.ts]
import { ControllerClass } from './application';
import { BindingKey } from '@loopback/context';
export const CONTROLLER_CLASS = BindingKey.create<ControllerClass>(null as any); // line in question
//// [value-promise.ts]
export type Constructor<T> = (...args: any[]) => T;
//// [bindingkey.ts]
import { Constructor } from "@loopback/context"
export class BindingKey<T> {
readonly __type: T;
static create<T extends Constructor<any>>(ctor: T) {
return new BindingKey<T>();
}
}
//// [index.ts]
export * from "./src/value-promise";
export * from "./src/bindingkey";
//// [value-promise.js]
"use strict";
exports.__esModule = true;
//// [bindingkey.js]
"use strict";
exports.__esModule = true;
var BindingKey = /** @class */ (function () {
function BindingKey() {
}
BindingKey.create = function (ctor) {
return new BindingKey();
};
return BindingKey;
}());
exports.BindingKey = BindingKey;
//// [index.js]
"use strict";
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
exports.__esModule = true;
__export(require("./src/bindingkey"));
//// [application.js]
"use strict";
exports.__esModule = true;
//// [usage.js]
"use strict";
exports.__esModule = true;
var context_1 = require("@loopback/context");
exports.CONTROLLER_CLASS = context_1.BindingKey.create(null); // line in question
//// [value-promise.d.ts]
export declare type Constructor<T> = (...args: any[]) => T;
//// [bindingkey.d.ts]
import { Constructor } from "@loopback/context";
export declare class BindingKey<T> {
readonly __type: T;
static create<T extends Constructor<any>>(ctor: T): BindingKey<T>;
}
//// [index.d.ts]
export * from "./src/value-promise";
export * from "./src/bindingkey";
//// [application.d.ts]
import { Constructor } from "@loopback/context";
export declare type ControllerClass = Constructor<any>;
//// [usage.d.ts]
import { BindingKey } from '@loopback/context';
export declare const CONTROLLER_CLASS: BindingKey<import("@loopback/context/src/value-promise").Constructor<any>>;
//// [DtsFileErrors]
tests/cases/compiler/monorepo/context/src/bindingkey.d.ts(1,29): error TS2307: Cannot find module '@loopback/context'.
tests/cases/compiler/monorepo/core/src/application.d.ts(1,29): error TS2307: Cannot find module '@loopback/context'.
tests/cases/compiler/monorepo/core/src/usage.d.ts(1,28): error TS2307: Cannot find module '@loopback/context'.
tests/cases/compiler/monorepo/core/src/usage.d.ts(2,51): error TS2307: Cannot find module '@loopback/context/src/value-promise'.
==== tests/cases/compiler/monorepo/core/src/application.d.ts (1 errors) ====
import { Constructor } from "@loopback/context";
~~~~~~~~~~~~~~~~~~~
!!! error TS2307: Cannot find module '@loopback/context'.
export declare type ControllerClass = Constructor<any>;
==== tests/cases/compiler/monorepo/core/src/usage.d.ts (2 errors) ====
import { BindingKey } from '@loopback/context';
~~~~~~~~~~~~~~~~~~~
!!! error TS2307: Cannot find module '@loopback/context'.
export declare const CONTROLLER_CLASS: BindingKey<import("@loopback/context/src/value-promise").Constructor<any>>;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2307: Cannot find module '@loopback/context/src/value-promise'.
==== /.src/tests/cases/compiler/monorepo/context/src/value-promise.d.ts (0 errors) ====
export declare type Constructor<T> = (...args: any[]) => T;
==== /.src/tests/cases/compiler/monorepo/context/src/bindingkey.d.ts (1 errors) ====
import { Constructor } from "@loopback/context";
~~~~~~~~~~~~~~~~~~~
!!! error TS2307: Cannot find module '@loopback/context'.
export declare class BindingKey<T> {
readonly __type: T;
static create<T extends Constructor<any>>(ctor: T): BindingKey<T>;
}
==== /.src/tests/cases/compiler/monorepo/context/index.d.ts (0 errors) ====
export * from "./src/value-promise";
export * from "./src/bindingkey";

View File

@ -0,0 +1,59 @@
=== tests/cases/compiler/monorepo/core/src/application.ts ===
import { Constructor } from "@loopback/context";
>Constructor : Symbol(Constructor, Decl(application.ts, 0, 8))
export type ControllerClass = Constructor<any>;
>ControllerClass : Symbol(ControllerClass, Decl(application.ts, 0, 48))
>Constructor : Symbol(Constructor, Decl(application.ts, 0, 8))
=== tests/cases/compiler/monorepo/core/src/usage.ts ===
import { ControllerClass } from './application';
>ControllerClass : Symbol(ControllerClass, Decl(usage.ts, 0, 8))
import { BindingKey } from '@loopback/context';
>BindingKey : Symbol(BindingKey, Decl(usage.ts, 1, 8))
export const CONTROLLER_CLASS = BindingKey.create<ControllerClass>(null as any); // line in question
>CONTROLLER_CLASS : Symbol(CONTROLLER_CLASS, Decl(usage.ts, 3, 12))
>BindingKey.create : Symbol(BindingKey.create, Decl(bindingkey.ts, 2, 21))
>BindingKey : Symbol(BindingKey, Decl(usage.ts, 1, 8))
>create : Symbol(BindingKey.create, Decl(bindingkey.ts, 2, 21))
>ControllerClass : Symbol(ControllerClass, Decl(usage.ts, 0, 8))
=== tests/cases/compiler/monorepo/context/src/value-promise.ts ===
export type Constructor<T> = (...args: any[]) => T;
>Constructor : Symbol(Constructor, Decl(value-promise.ts, 0, 0))
>T : Symbol(T, Decl(value-promise.ts, 0, 24))
>args : Symbol(args, Decl(value-promise.ts, 0, 30))
>T : Symbol(T, Decl(value-promise.ts, 0, 24))
=== tests/cases/compiler/monorepo/context/src/bindingkey.ts ===
import { Constructor } from "@loopback/context"
>Constructor : Symbol(Constructor, Decl(bindingkey.ts, 0, 8))
export class BindingKey<T> {
>BindingKey : Symbol(BindingKey, Decl(bindingkey.ts, 0, 47))
>T : Symbol(T, Decl(bindingkey.ts, 1, 24))
readonly __type: T;
>__type : Symbol(BindingKey.__type, Decl(bindingkey.ts, 1, 28))
>T : Symbol(T, Decl(bindingkey.ts, 1, 24))
static create<T extends Constructor<any>>(ctor: T) {
>create : Symbol(BindingKey.create, Decl(bindingkey.ts, 2, 21))
>T : Symbol(T, Decl(bindingkey.ts, 3, 16))
>Constructor : Symbol(Constructor, Decl(bindingkey.ts, 0, 8))
>ctor : Symbol(ctor, Decl(bindingkey.ts, 3, 44))
>T : Symbol(T, Decl(bindingkey.ts, 3, 16))
return new BindingKey<T>();
>BindingKey : Symbol(BindingKey, Decl(bindingkey.ts, 0, 47))
>T : Symbol(T, Decl(bindingkey.ts, 3, 16))
}
}
=== tests/cases/compiler/monorepo/context/index.ts ===
export * from "./src/value-promise";
No type information for this code.export * from "./src/bindingkey";
No type information for this code.
No type information for this code.

View File

@ -0,0 +1,63 @@
=== tests/cases/compiler/monorepo/core/src/application.ts ===
import { Constructor } from "@loopback/context";
>Constructor : any
export type ControllerClass = Constructor<any>;
>ControllerClass : Constructor<any>
>Constructor : Constructor<T>
=== tests/cases/compiler/monorepo/core/src/usage.ts ===
import { ControllerClass } from './application';
>ControllerClass : any
import { BindingKey } from '@loopback/context';
>BindingKey : typeof BindingKey
export const CONTROLLER_CLASS = BindingKey.create<ControllerClass>(null as any); // line in question
>CONTROLLER_CLASS : BindingKey<import("tests/cases/compiler/monorepo/context/src/value-promise").Constructor<any>>
>BindingKey.create<ControllerClass>(null as any) : BindingKey<import("tests/cases/compiler/monorepo/context/src/value-promise").Constructor<any>>
>BindingKey.create : <T extends import("tests/cases/compiler/monorepo/context/src/value-promise").Constructor<any>>(ctor: T) => BindingKey<T>
>BindingKey : typeof BindingKey
>create : <T extends import("tests/cases/compiler/monorepo/context/src/value-promise").Constructor<any>>(ctor: T) => BindingKey<T>
>ControllerClass : import("tests/cases/compiler/monorepo/context/src/value-promise").Constructor<any>
>null as any : any
>null : null
=== tests/cases/compiler/monorepo/context/src/value-promise.ts ===
export type Constructor<T> = (...args: any[]) => T;
>Constructor : Constructor<T>
>T : T
>args : any[]
>T : T
=== tests/cases/compiler/monorepo/context/src/bindingkey.ts ===
import { Constructor } from "@loopback/context"
>Constructor : any
export class BindingKey<T> {
>BindingKey : BindingKey<T>
>T : T
readonly __type: T;
>__type : T
>T : T
static create<T extends Constructor<any>>(ctor: T) {
>create : <T extends Constructor<any>>(ctor: T) => BindingKey<T>
>T : T
>Constructor : Constructor<T>
>ctor : T
>T : T
return new BindingKey<T>();
>new BindingKey<T>() : BindingKey<T>
>BindingKey : typeof BindingKey
>T : T
}
}
=== tests/cases/compiler/monorepo/context/index.ts ===
export * from "./src/value-promise";
No type information for this code.export * from "./src/bindingkey";
No type information for this code.
No type information for this code.

View File

@ -0,0 +1,25 @@
// @declaration: true
// @filename: monorepo/core/src/application.ts
import { Constructor } from "@loopback/context";
export type ControllerClass = Constructor<any>;
// @filename: monorepo/core/src/usage.ts
import { ControllerClass } from './application';
import { BindingKey } from '@loopback/context';
export const CONTROLLER_CLASS = BindingKey.create<ControllerClass>(null as any); // line in question
// @filename: monorepo/context/src/value-promise.ts
export type Constructor<T> = (...args: any[]) => T;
// @filename: monorepo/context/src/bindingkey.ts
import { Constructor } from "@loopback/context"
export class BindingKey<T> {
readonly __type: T;
static create<T extends Constructor<any>>(ctor: T) {
return new BindingKey<T>();
}
}
// @filename: monorepo/context/index.ts
export * from "./src/value-promise";
export * from "./src/bindingkey";
// @link: tests/cases/compiler/monorepo/context -> tests/cases/compiler/monorepo/core/node_modules/@loopback/context