Skip to content

Commit

Permalink
Merge pull request #8486 from Microsoft/symlinked-modules
Browse files Browse the repository at this point in the history
use CompilerHost.realpath to resolve actual location for symlinks
  • Loading branch information
vladima committed May 5, 2016
2 parents bbbb56b + 0a93768 commit cd1af12
Show file tree
Hide file tree
Showing 12 changed files with 230 additions and 28 deletions.
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2748,6 +2748,10 @@
"category": "Error",
"code": 6129
},
"Resolving real path for '{0}', result '{1}'": {
"category": "Message",
"code": 6130
},
"Variable '{0}' implicitly has an '{1}' type.": {
"category": "Error",
"code": 7005
Expand Down
33 changes: 21 additions & 12 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -570,22 +570,29 @@ namespace ts {
let resolvedFileName = tryLoadModuleUsingOptionalResolutionSettings(moduleName, containingDirectory, nodeLoadModuleByRelativeName,
failedLookupLocations, supportedExtensions, state);

if (resolvedFileName) {
return createResolvedModule(resolvedFileName, /*isExternalLibraryImport*/false, failedLookupLocations);
let isExternalLibraryImport = false;
if (!resolvedFileName) {
if (moduleHasNonRelativeName(moduleName)) {
if (traceEnabled) {
trace(host, Diagnostics.Loading_module_0_from_node_modules_folder, moduleName);
}
resolvedFileName = loadModuleFromNodeModules(moduleName, containingDirectory, failedLookupLocations, state);
isExternalLibraryImport = resolvedFileName !== undefined;
}
else {
const candidate = normalizePath(combinePaths(containingDirectory, moduleName));
resolvedFileName = nodeLoadModuleByRelativeName(candidate, supportedExtensions, failedLookupLocations, /*onlyRecordFailures*/ false, state);
}
}

let isExternalLibraryImport = false;
if (moduleHasNonRelativeName(moduleName)) {
if (resolvedFileName && host.realpath) {
const originalFileName = resolvedFileName;
resolvedFileName = normalizePath(host.realpath(resolvedFileName));
if (traceEnabled) {
trace(host, Diagnostics.Loading_module_0_from_node_modules_folder, moduleName);
trace(host, Diagnostics.Resolving_real_path_for_0_result_1, originalFileName, resolvedFileName);
}
resolvedFileName = loadModuleFromNodeModules(moduleName, containingDirectory, failedLookupLocations, state);
isExternalLibraryImport = resolvedFileName !== undefined;
}
else {
const candidate = normalizePath(combinePaths(containingDirectory, moduleName));
resolvedFileName = nodeLoadModuleByRelativeName(candidate, supportedExtensions, failedLookupLocations, /*onlyRecordFailures*/ false, state);
}

return createResolvedModule(resolvedFileName, isExternalLibraryImport, failedLookupLocations);
}

Expand Down Expand Up @@ -873,6 +880,7 @@ namespace ts {
}

const newLine = getNewLineCharacter(options);
const realpath = sys.realpath && ((path: string) => sys.realpath(path));

return {
getSourceFile,
Expand All @@ -886,7 +894,8 @@ namespace ts {
fileExists: fileName => sys.fileExists(fileName),
readFile: fileName => sys.readFile(fileName),
trace: (s: string) => sys.write(s + newLine),
directoryExists: directoryName => sys.directoryExists(directoryName)
directoryExists: directoryName => sys.directoryExists(directoryName),
realpath
};
}

Expand Down
8 changes: 7 additions & 1 deletion src/compiler/sys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ namespace ts {
createHash?(data: string): string;
getMemoryUsage?(): number;
exit(exitCode?: number): void;
realpath?(path: string): string;
}

export interface FileWatcher {
Expand Down Expand Up @@ -73,6 +74,7 @@ namespace ts {
readDirectory(path: string, extension?: string, exclude?: string[]): string[];
watchFile?(path: string, callback: FileWatcherCallback): FileWatcher;
watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
realpath(path: string): string;
};

export var sys: System = (function () {
Expand Down Expand Up @@ -527,12 +529,15 @@ namespace ts {
},
exit(exitCode?: number): void {
process.exit(exitCode);
},
realpath(path: string): string {
return _fs.realpathSync(path);
}
};
}

function getChakraSystem(): System {

const realpath = ChakraHost.realpath && ((path: string) => ChakraHost.realpath(path));
return {
newLine: ChakraHost.newLine || "\r\n",
args: ChakraHost.args,
Expand All @@ -558,6 +563,7 @@ namespace ts {
getCurrentDirectory: () => ChakraHost.currentDirectory,
readDirectory: ChakraHost.readDirectory,
exit: ChakraHost.quit,
realpath
};
}

Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2779,6 +2779,7 @@ namespace ts {
readFile(fileName: string): string;
trace?(s: string): void;
directoryExists?(directoryName: string): boolean;
realpath?(path: string): string;
}

export interface ResolvedModule {
Expand Down
6 changes: 3 additions & 3 deletions src/harness/compilerRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,16 @@ class CompilerBaselineRunner extends RunnerBase {
otherFiles = [];

if (testCaseContent.settings["noImplicitReferences"] || /require\(/.test(lastUnit.content) || /reference\spath/.test(lastUnit.content)) {
toBeCompiled.push({ unitName: this.makeUnitName(lastUnit.name, rootDir), content: lastUnit.content });
toBeCompiled.push({ unitName: this.makeUnitName(lastUnit.name, rootDir), content: lastUnit.content, fileOptions: lastUnit.fileOptions });
units.forEach(unit => {
if (unit.name !== lastUnit.name) {
otherFiles.push({ unitName: this.makeUnitName(unit.name, rootDir), content: unit.content });
otherFiles.push({ unitName: this.makeUnitName(unit.name, rootDir), content: unit.content, fileOptions: unit.fileOptions });
}
});
}
else {
toBeCompiled = units.map(unit => {
return { unitName: this.makeUnitName(unit.name, rootDir), content: unit.content };
return { unitName: this.makeUnitName(unit.name, rootDir), content: unit.content, fileOptions: unit.fileOptions };
});
}

Expand Down
39 changes: 27 additions & 12 deletions src/harness/harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -855,21 +855,31 @@ namespace Harness {
// Local get canonical file name function, that depends on passed in parameter for useCaseSensitiveFileNames
const getCanonicalFileName = ts.createGetCanonicalFileName(useCaseSensitiveFileNames);

const fileMap: ts.FileMap<ts.SourceFile> = ts.createFileMap<ts.SourceFile>();
let realPathMap: ts.FileMap<string>;
const fileMap: ts.FileMap<() => ts.SourceFile> = ts.createFileMap<() => ts.SourceFile>();
for (const file of inputFiles) {
if (file.content !== undefined) {
const fileName = ts.normalizePath(file.unitName);
const sourceFile = createSourceFileAndAssertInvariants(fileName, file.content, scriptTarget);
const path = ts.toPath(file.unitName, currentDirectory, getCanonicalFileName);
fileMap.set(path, sourceFile);
if (file.fileOptions && file.fileOptions["symlink"]) {
const link = file.fileOptions["symlink"];
const linkPath = ts.toPath(link, currentDirectory, getCanonicalFileName);
if (!realPathMap) {
realPathMap = ts.createFileMap<string>();
}
realPathMap.set(linkPath, fileName);
fileMap.set(path, (): ts.SourceFile => { throw new Error("Symlinks should always be resolved to a realpath first"); });
}
const sourceFile = createSourceFileAndAssertInvariants(fileName, file.content, scriptTarget);
fileMap.set(path, () => sourceFile);
}
}

function getSourceFile(fileName: string, languageVersion: ts.ScriptTarget) {
fileName = ts.normalizePath(fileName);
const path = ts.toPath(fileName, currentDirectory, getCanonicalFileName);
if (fileMap.contains(path)) {
return fileMap.get(path);
return fileMap.get(path)();
}
else if (fileName === fourslashFileName) {
const tsFn = "tests/cases/fourslash/" + fourslashFileName;
Expand Down Expand Up @@ -898,11 +908,16 @@ namespace Harness {
useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
getNewLine: () => newLine,
fileExists: fileName => {
return fileMap.contains(ts.toPath(fileName, currentDirectory, getCanonicalFileName));
const path = ts.toPath(fileName, currentDirectory, getCanonicalFileName);
return fileMap.contains(path) || (realPathMap && realPathMap.contains(path));
},
readFile: (fileName: string): string => {
return fileMap.get(ts.toPath(fileName, currentDirectory, getCanonicalFileName)).getText();
}
return fileMap.get(ts.toPath(fileName, currentDirectory, getCanonicalFileName))().getText();
},
realpath: realPathMap && ((f: string) => {
const path = ts.toPath(f, currentDirectory, getCanonicalFileName);
return realPathMap.contains(path) ? realPathMap.get(path) : path;
})
};
}

Expand All @@ -923,7 +938,8 @@ namespace Harness {
{ name: "libFiles", type: "string" },
{ name: "noErrorTruncation", type: "boolean" },
{ name: "suppressOutputPathCheck", type: "boolean" },
{ name: "noImplicitReferences", type: "boolean" }
{ name: "noImplicitReferences", type: "boolean" },
{ name: "symlink", type: "string" }
];

let optionsIndex: ts.Map<ts.CommandLineOption>;
Expand Down Expand Up @@ -978,6 +994,7 @@ namespace Harness {
export interface TestFile {
unitName: string;
content: string;
fileOptions?: any;
}

export interface CompilationOutput {
Expand Down Expand Up @@ -1415,10 +1432,8 @@ namespace Harness {
// Comment line, check for global/file @options and record them
optionRegex.lastIndex = 0;
const metaDataName = testMetaData[1].toLowerCase();
if (metaDataName === "filename") {
currentFileOptions[testMetaData[1]] = testMetaData[2];
}
else {
currentFileOptions[testMetaData[1]] = testMetaData[2];
if (metaDataName !== "filename") {
continue;
}

Expand Down
4 changes: 4 additions & 0 deletions src/services/shims.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,11 +431,15 @@ namespace ts {
export class CoreServicesShimHostAdapter implements ParseConfigHost, ModuleResolutionHost {

public directoryExists: (directoryName: string) => boolean;
public realpath: (path: string) => string;

constructor(private shimHost: CoreServicesShimHost) {
if ("directoryExists" in this.shimHost) {
this.directoryExists = directoryName => this.shimHost.directoryExists(directoryName);
}
if ("realpath" in this.shimHost) {
this.realpath = path => this.shimHost.realpath(path);
}
}

public readDirectory(rootDir: string, extension: string, exclude: string[], depth?: number): string[] {
Expand Down
37 changes: 37 additions & 0 deletions tests/baselines/reference/moduleResolutionWithSymlinks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//// [tests/cases/compiler/moduleResolutionWithSymlinks.ts] ////

//// [index.ts]

export class MyClass{}

//// [index.ts]
import {MyClass} from "library-a";
export { MyClass as MyClass2 }

//// [app.ts]
import { MyClass } from "./library-a";
import { MyClass2 } from "./library-b";

let x: MyClass;
let y: MyClass2;
x = y;
y = x;

//// [index.js]
"use strict";
var MyClass = (function () {
function MyClass() {
}
return MyClass;
}());
exports.MyClass = MyClass;
//// [index.js]
"use strict";
var library_a_1 = require("library-a");
exports.MyClass2 = library_a_1.MyClass;
//// [app.js]
"use strict";
var x;
var y;
x = y;
y = x;
36 changes: 36 additions & 0 deletions tests/baselines/reference/moduleResolutionWithSymlinks.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
=== /src/app.ts ===
import { MyClass } from "./library-a";
>MyClass : Symbol(MyClass, Decl(app.ts, 0, 8))

import { MyClass2 } from "./library-b";
>MyClass2 : Symbol(MyClass2, Decl(app.ts, 1, 8))

let x: MyClass;
>x : Symbol(x, Decl(app.ts, 3, 3))
>MyClass : Symbol(MyClass, Decl(app.ts, 0, 8))

let y: MyClass2;
>y : Symbol(y, Decl(app.ts, 4, 3))
>MyClass2 : Symbol(MyClass2, Decl(app.ts, 1, 8))

x = y;
>x : Symbol(x, Decl(app.ts, 3, 3))
>y : Symbol(y, Decl(app.ts, 4, 3))

y = x;
>y : Symbol(y, Decl(app.ts, 4, 3))
>x : Symbol(x, Decl(app.ts, 3, 3))

=== /src/library-a/index.ts ===

export class MyClass{}
>MyClass : Symbol(MyClass, Decl(index.ts, 0, 0))

=== /src/library-b/index.ts ===
import {MyClass} from "library-a";
>MyClass : Symbol(MyClass, Decl(index.ts, 0, 8))

export { MyClass as MyClass2 }
>MyClass : Symbol(MyClass2, Decl(index.ts, 1, 8))
>MyClass2 : Symbol(MyClass2, Decl(index.ts, 1, 8))

32 changes: 32 additions & 0 deletions tests/baselines/reference/moduleResolutionWithSymlinks.trace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[
"======== Resolving module './library-a' from '/src/app.ts'. ========",
"Module resolution kind is not specified, using 'NodeJs'.",
"Loading module as file / folder, candidate module location '/src/library-a'.",
"File '/src/library-a.ts' does not exist.",
"File '/src/library-a.tsx' does not exist.",
"File '/src/library-a.d.ts' does not exist.",
"File '/src/library-a/package.json' does not exist.",
"File '/src/library-a/index.ts' exist - use it as a name resolution result.",
"Resolving real path for '/src/library-a/index.ts', result '/src/library-a/index.ts'",
"======== Module name './library-a' was successfully resolved to '/src/library-a/index.ts'. ========",
"======== Resolving module './library-b' from '/src/app.ts'. ========",
"Module resolution kind is not specified, using 'NodeJs'.",
"Loading module as file / folder, candidate module location '/src/library-b'.",
"File '/src/library-b.ts' does not exist.",
"File '/src/library-b.tsx' does not exist.",
"File '/src/library-b.d.ts' does not exist.",
"File '/src/library-b/package.json' does not exist.",
"File '/src/library-b/index.ts' exist - use it as a name resolution result.",
"Resolving real path for '/src/library-b/index.ts', result '/src/library-b/index.ts'",
"======== Module name './library-b' was successfully resolved to '/src/library-b/index.ts'. ========",
"======== Resolving module 'library-a' from '/src/library-b/index.ts'. ========",
"Module resolution kind is not specified, using 'NodeJs'.",
"Loading module 'library-a' from 'node_modules' folder.",
"File '/src/library-b/node_modules/library-a.ts' does not exist.",
"File '/src/library-b/node_modules/library-a.tsx' does not exist.",
"File '/src/library-b/node_modules/library-a.d.ts' does not exist.",
"File '/src/library-b/node_modules/library-a/package.json' does not exist.",
"File '/src/library-b/node_modules/library-a/index.ts' exist - use it as a name resolution result.",
"Resolving real path for '/src/library-b/node_modules/library-a/index.ts', result '/src/library-a/index.ts'",
"======== Module name 'library-a' was successfully resolved to '/src/library-a/index.ts'. ========"
]
38 changes: 38 additions & 0 deletions tests/baselines/reference/moduleResolutionWithSymlinks.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
=== /src/app.ts ===
import { MyClass } from "./library-a";
>MyClass : typeof MyClass

import { MyClass2 } from "./library-b";
>MyClass2 : typeof MyClass

let x: MyClass;
>x : MyClass
>MyClass : MyClass

let y: MyClass2;
>y : MyClass
>MyClass2 : MyClass

x = y;
>x = y : MyClass
>x : MyClass
>y : MyClass

y = x;
>y = x : MyClass
>y : MyClass
>x : MyClass

=== /src/library-a/index.ts ===

export class MyClass{}
>MyClass : MyClass

=== /src/library-b/index.ts ===
import {MyClass} from "library-a";
>MyClass : typeof MyClass

export { MyClass as MyClass2 }
>MyClass : typeof MyClass
>MyClass2 : typeof MyClass

Loading

0 comments on commit cd1af12

Please sign in to comment.