Merge pull request #20763 from Microsoft/vfs
Update harness to use single robust virtual file system for tests.
This commit is contained in:
commit
56648ad0f1
48
.vscode/tasks.json
vendored
48
.vscode/tasks.json
vendored
|
@ -1,30 +1,34 @@
|
|||
// Available variables which can be used inside of strings.
|
||||
// ${workspaceRoot}: the root folder of the team
|
||||
// ${file}: the current opened file
|
||||
// ${fileBasename}: the current opened file's basename
|
||||
// ${fileDirname}: the current opened file's dirname
|
||||
// ${fileExtname}: the current opened file's extension
|
||||
// ${cwd}: the current working directory of the spawned process
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"command": "gulp",
|
||||
"isShellCommand": true,
|
||||
"showOutput": "silent",
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"taskName": "local",
|
||||
"isBuildCommand": true,
|
||||
"showOutput": "silent",
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
]
|
||||
"type": "shell",
|
||||
"identifier": "local",
|
||||
"label": "gulp: local",
|
||||
"command": "gulp",
|
||||
"args": ["local"],
|
||||
"group": { "kind": "build", "isDefault": true },
|
||||
"problemMatcher": ["$gulp-tsc"]
|
||||
},
|
||||
{
|
||||
"taskName": "tests",
|
||||
"showOutput": "silent",
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
]
|
||||
"type": "shell",
|
||||
"identifier": "tsc",
|
||||
"label": "gulp: tsc",
|
||||
"command": "gulp",
|
||||
"args": ["tsc"],
|
||||
"group": "build",
|
||||
"problemMatcher": ["$gulp-tsc"]
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"identifier": "tests",
|
||||
"label": "gulp: tests",
|
||||
"command": "gulp",
|
||||
"args": ["tests"],
|
||||
"group": "build",
|
||||
"problemMatcher": ["$gulp-tsc"]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -761,7 +761,7 @@ const nodeServerOutFile = "tests/webTestServer.js";
|
|||
const nodeServerInFile = "tests/webTestServer.ts";
|
||||
gulp.task(nodeServerOutFile, /*help*/ false, [servicesFile], () => {
|
||||
/** @type {tsc.Settings} */
|
||||
const settings = getCompilerSettings({ module: "commonjs" }, /*useBuiltCompiler*/ true);
|
||||
const settings = getCompilerSettings({ module: "commonjs", target: "es2015" }, /*useBuiltCompiler*/ true);
|
||||
return gulp.src(nodeServerInFile)
|
||||
.pipe(newer(nodeServerOutFile))
|
||||
.pipe(sourcemaps.init())
|
||||
|
|
55
Jakefile.js
55
Jakefile.js
|
@ -1,5 +1,6 @@
|
|||
// This file contains the build logic for the public repo
|
||||
// @ts-check
|
||||
/// <reference types="jake" />
|
||||
|
||||
var fs = require("fs");
|
||||
var os = require("os");
|
||||
|
@ -171,35 +172,34 @@ var compilerFilename = "tsc.js";
|
|||
var LKGCompiler = path.join(LKGDirectory, compilerFilename);
|
||||
var builtLocalCompiler = path.join(builtLocalDirectory, compilerFilename);
|
||||
|
||||
/* Compiles a file from a list of sources
|
||||
* @param outFile: the target file name
|
||||
* @param sources: an array of the names of the source files
|
||||
* @param prereqs: prerequisite tasks to compiling the file
|
||||
* @param prefixes: a list of files to prepend to the target file
|
||||
* @param useBuiltCompiler: true to use the built compiler, false to use the LKG
|
||||
* @parap {Object} opts - property bag containing auxiliary options
|
||||
* @param {boolean} opts.noOutFile: true to compile without using --out
|
||||
* @param {boolean} opts.generateDeclarations: true to compile using --declaration
|
||||
* @param {string} opts.outDir: value for '--outDir' command line option
|
||||
* @param {boolean} opts.keepComments: false to compile using --removeComments
|
||||
* @param {boolean} opts.preserveConstEnums: true if compiler should keep const enums in code
|
||||
* @param {boolean} opts.noResolve: true if compiler should not include non-rooted files in compilation
|
||||
* @param {boolean} opts.stripInternal: true if compiler should remove declarations marked as @internal
|
||||
* @param {boolean} opts.inlineSourceMap: true if compiler should inline sourceMap
|
||||
* @param {Array} opts.types: array of types to include in compilation
|
||||
* @param callback: a function to execute after the compilation process ends
|
||||
/**
|
||||
* Compiles a file from a list of sources
|
||||
* @param {string} outFile the target file name
|
||||
* @param {string[]} sources an array of the names of the source files
|
||||
* @param {string[]} prereqs prerequisite tasks to compiling the file
|
||||
* @param {string[]} prefixes a list of files to prepend to the target file
|
||||
* @param {boolean} useBuiltCompiler true to use the built compiler, false to use the LKG
|
||||
* @param {object} [opts] property bag containing auxiliary options
|
||||
* @param {boolean} [opts.noOutFile] true to compile without using --out
|
||||
* @param {boolean} [opts.generateDeclarations] true to compile using --declaration
|
||||
* @param {string} [opts.outDir] value for '--outDir' command line option
|
||||
* @param {boolean} [opts.keepComments] false to compile using --removeComments
|
||||
* @param {boolean} [opts.preserveConstEnums] true if compiler should keep const enums in code
|
||||
* @param {boolean} [opts.noResolve] true if compiler should not include non-rooted files in compilation
|
||||
* @param {boolean} [opts.stripInternal] true if compiler should remove declarations marked as internal
|
||||
* @param {boolean} [opts.inlineSourceMap] true if compiler should inline sourceMap
|
||||
* @param {string[]} [opts.types] array of types to include in compilation
|
||||
* @param {string} [opts.lib] explicit libs to include.
|
||||
* @param {function(): void} [callback] a function to execute after the compilation process ends
|
||||
*/
|
||||
function compileFile(outFile, sources, prereqs, prefixes, useBuiltCompiler, opts, callback) {
|
||||
file(outFile, prereqs, function() {
|
||||
if (process.env.USE_TRANSFORMS === "false") {
|
||||
useBuiltCompiler = false;
|
||||
}
|
||||
var startCompileTime = mark();
|
||||
opts = opts || {};
|
||||
var compilerPath = useBuiltCompiler ? builtLocalCompiler : LKGCompiler;
|
||||
var options = "--noImplicitAny --noImplicitThis --alwaysStrict --noEmitOnError --types ";
|
||||
var options = "--noImplicitAny --noImplicitThis --alwaysStrict --noEmitOnError";
|
||||
if (opts.types) {
|
||||
options += opts.types.join(",");
|
||||
options += " --types " + opts.types.join(",");
|
||||
}
|
||||
options += " --pretty";
|
||||
// Keep comments when specifically requested
|
||||
|
@ -236,7 +236,7 @@ function compileFile(outFile, sources, prereqs, prefixes, useBuiltCompiler, opts
|
|||
options += " --inlineSourceMap --inlineSources";
|
||||
}
|
||||
else {
|
||||
options += " -sourcemap";
|
||||
options += " --sourcemap";
|
||||
}
|
||||
}
|
||||
options += " --newLine LF";
|
||||
|
@ -362,7 +362,6 @@ var buildProtocolTs = path.join(scriptsDirectory, "buildProtocol.ts");
|
|||
var buildProtocolJs = path.join(scriptsDirectory, "buildProtocol.js");
|
||||
var buildProtocolDts = path.join(builtLocalDirectory, "protocol.d.ts");
|
||||
var typescriptServicesDts = path.join(builtLocalDirectory, "typescriptServices.d.ts");
|
||||
var typesMapJson = path.join(builtLocalDirectory, "typesMap.json");
|
||||
|
||||
file(buildProtocolTs);
|
||||
|
||||
|
@ -540,7 +539,7 @@ var serverFile = path.join(builtLocalDirectory, "tsserver.js");
|
|||
compileFile(serverFile, serverSources, [builtLocalDirectory, copyright, cancellationTokenFile, typingsInstallerFile, watchGuardFile].concat(serverSources).concat(servicesSources), /*prefixes*/ [copyright], /*useBuiltCompiler*/ true, { types: ["node"], preserveConstEnums: true, lib: "es6" });
|
||||
var tsserverLibraryFile = path.join(builtLocalDirectory, "tsserverlibrary.js");
|
||||
var tsserverLibraryDefinitionFile = path.join(builtLocalDirectory, "tsserverlibrary.d.ts");
|
||||
file(typesMapOutputPath, function() {
|
||||
file(typesMapOutputPath, /** @type {*} */(function() {
|
||||
var content = fs.readFileSync(path.join(serverDirectory, 'typesMap.json'));
|
||||
// Validate that it's valid JSON
|
||||
try {
|
||||
|
@ -549,7 +548,7 @@ file(typesMapOutputPath, function() {
|
|||
console.log("Parse error in typesMap.json: " + e);
|
||||
}
|
||||
fs.writeFileSync(typesMapOutputPath, content);
|
||||
});
|
||||
}));
|
||||
compileFile(
|
||||
tsserverLibraryFile,
|
||||
languageServiceLibrarySources,
|
||||
|
@ -693,7 +692,7 @@ desc("Builds the test infrastructure using the built compiler");
|
|||
task("tests", ["local", run].concat(libraryTargets));
|
||||
|
||||
function exec(cmd, completeHandler, errorHandler) {
|
||||
var ex = jake.createExec([cmd], { windowsVerbatimArguments: true, interactive: true });
|
||||
var ex = jake.createExec([cmd], /** @type {jake.ExecOptions} */({ windowsVerbatimArguments: true, interactive: true }));
|
||||
// Add listeners for output and error
|
||||
ex.addListener("stdout", function (output) {
|
||||
process.stdout.write(output);
|
||||
|
@ -1170,7 +1169,7 @@ task("lint", ["build-rules"], () => {
|
|||
function lint(project, cb) {
|
||||
const cmd = `node node_modules/tslint/bin/tslint --project ${project} --formatters-dir ./built/local/tslint/formatters --format autolinkableStylish`;
|
||||
console.log("Linting: " + cmd);
|
||||
jake.exec([cmd], { interactive: true, windowsVerbatimArguments: true }, cb);
|
||||
jake.exec([cmd], cb, /** @type {jake.ExecOptions} */({ interactive: true, windowsVerbatimArguments: true }));
|
||||
}
|
||||
lint("scripts/tslint/tsconfig.json", () => lint("src/tsconfig-base.json", () => {
|
||||
if (fold.isTravis()) console.log(fold.end("lint"));
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
"@types/gulp-help": "latest",
|
||||
"@types/gulp-newer": "latest",
|
||||
"@types/gulp-sourcemaps": "latest",
|
||||
"@types/jake": "latest",
|
||||
"@types/merge2": "latest",
|
||||
"@types/minimatch": "latest",
|
||||
"@types/minimist": "latest",
|
||||
|
@ -47,6 +48,7 @@
|
|||
"@types/node": "8.5.5",
|
||||
"@types/q": "latest",
|
||||
"@types/run-sequence": "latest",
|
||||
"@types/source-map-support": "latest",
|
||||
"@types/through2": "latest",
|
||||
"@types/travis-fold": "latest",
|
||||
"@types/xml2js": "^0.4.0",
|
||||
|
|
|
@ -1820,7 +1820,7 @@ namespace ts {
|
|||
}
|
||||
|
||||
function getDefaultCompilerOptions(configFileName?: string) {
|
||||
const options: CompilerOptions = getBaseFileName(configFileName) === "jsconfig.json"
|
||||
const options: CompilerOptions = configFileName && getBaseFileName(configFileName) === "jsconfig.json"
|
||||
? { allowJs: true, maxNodeModuleJsDepth: 2, allowSyntheticDefaultImports: true, skipLibCheck: true, noEmit: true }
|
||||
: {};
|
||||
return options;
|
||||
|
@ -1835,7 +1835,7 @@ namespace ts {
|
|||
}
|
||||
|
||||
function getDefaultTypeAcquisition(configFileName?: string): TypeAcquisition {
|
||||
return { enable: getBaseFileName(configFileName) === "jsconfig.json", include: [], exclude: [] };
|
||||
return { enable: configFileName && getBaseFileName(configFileName) === "jsconfig.json", include: [], exclude: [] };
|
||||
}
|
||||
|
||||
function convertTypeAcquisitionFromJsonWorker(jsonOptions: any,
|
||||
|
|
|
@ -1890,12 +1890,12 @@ namespace ts {
|
|||
comparer(a[key], b[key]);
|
||||
}
|
||||
|
||||
function getDiagnosticFileName(diagnostic: Diagnostic): string {
|
||||
return diagnostic.file ? diagnostic.file.fileName : undefined;
|
||||
function getDiagnosticFilePath(diagnostic: Diagnostic): string {
|
||||
return diagnostic.file ? diagnostic.file.path : undefined;
|
||||
}
|
||||
|
||||
export function compareDiagnostics(d1: Diagnostic, d2: Diagnostic): Comparison {
|
||||
return compareStringsCaseSensitive(getDiagnosticFileName(d1), getDiagnosticFileName(d2)) ||
|
||||
return compareStringsCaseSensitive(getDiagnosticFilePath(d1), getDiagnosticFilePath(d2)) ||
|
||||
compareValues(d1.start, d2.start) ||
|
||||
compareValues(d1.length, d2.length) ||
|
||||
compareValues(d1.code, d2.code) ||
|
||||
|
@ -1932,110 +1932,6 @@ namespace ts {
|
|||
return text1 ? Comparison.GreaterThan : Comparison.LessThan;
|
||||
}
|
||||
|
||||
export function normalizeSlashes(path: string): string {
|
||||
return path.replace(/\\/g, "/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns length of path root (i.e. length of "/", "x:/", "//server/share/, file:///user/files")
|
||||
*/
|
||||
export function getRootLength(path: string): number {
|
||||
if (path.charCodeAt(0) === CharacterCodes.slash) {
|
||||
if (path.charCodeAt(1) !== CharacterCodes.slash) return 1;
|
||||
const p1 = path.indexOf("/", 2);
|
||||
if (p1 < 0) return 2;
|
||||
const p2 = path.indexOf("/", p1 + 1);
|
||||
if (p2 < 0) return p1 + 1;
|
||||
return p2 + 1;
|
||||
}
|
||||
if (path.charCodeAt(1) === CharacterCodes.colon) {
|
||||
if (path.charCodeAt(2) === CharacterCodes.slash || path.charCodeAt(2) === CharacterCodes.backslash) return 3;
|
||||
}
|
||||
// Per RFC 1738 'file' URI schema has the shape file://<host>/<path>
|
||||
// if <host> is omitted then it is assumed that host value is 'localhost',
|
||||
// however slash after the omitted <host> is not removed.
|
||||
// file:///folder1/file1 - this is a correct URI
|
||||
// file://folder2/file2 - this is an incorrect URI
|
||||
if (path.lastIndexOf("file:///", 0) === 0) {
|
||||
return "file:///".length;
|
||||
}
|
||||
const idx = path.indexOf("://");
|
||||
if (idx !== -1) {
|
||||
return idx + "://".length;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internally, we represent paths as strings with '/' as the directory separator.
|
||||
* When we make system calls (eg: LanguageServiceHost.getDirectory()),
|
||||
* we expect the host to correctly handle paths in our specified format.
|
||||
*/
|
||||
export const directorySeparator = "/";
|
||||
const directorySeparatorCharCode = CharacterCodes.slash;
|
||||
function getNormalizedParts(normalizedSlashedPath: string, rootLength: number): string[] {
|
||||
const parts = normalizedSlashedPath.substr(rootLength).split(directorySeparator);
|
||||
const normalized: string[] = [];
|
||||
for (const part of parts) {
|
||||
if (part !== ".") {
|
||||
if (part === ".." && normalized.length > 0 && lastOrUndefined(normalized) !== "..") {
|
||||
normalized.pop();
|
||||
}
|
||||
else {
|
||||
// A part may be an empty string (which is 'falsy') if the path had consecutive slashes,
|
||||
// e.g. "path//file.ts". Drop these before re-joining the parts.
|
||||
if (part) {
|
||||
normalized.push(part);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function normalizePath(path: string): string {
|
||||
return normalizePathAndParts(path).path;
|
||||
}
|
||||
|
||||
export function normalizePathAndParts(path: string): { path: string, parts: string[] } {
|
||||
path = normalizeSlashes(path);
|
||||
const rootLength = getRootLength(path);
|
||||
const root = path.substr(0, rootLength);
|
||||
const parts = getNormalizedParts(path, rootLength);
|
||||
if (parts.length) {
|
||||
const joinedParts = root + parts.join(directorySeparator);
|
||||
return { path: pathEndsWithDirectorySeparator(path) ? joinedParts + directorySeparator : joinedParts, parts };
|
||||
}
|
||||
else {
|
||||
return { path: root, parts };
|
||||
}
|
||||
}
|
||||
|
||||
/** A path ending with '/' refers to a directory only, never a file. */
|
||||
export function pathEndsWithDirectorySeparator(path: string): boolean {
|
||||
return path.charCodeAt(path.length - 1) === directorySeparatorCharCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path except for its basename. Eg:
|
||||
*
|
||||
* /path/to/file.ext -> /path/to
|
||||
*/
|
||||
export function getDirectoryPath(path: Path): Path;
|
||||
export function getDirectoryPath(path: string): string;
|
||||
export function getDirectoryPath(path: string): string {
|
||||
return path.substr(0, Math.max(getRootLength(path), path.lastIndexOf(directorySeparator)));
|
||||
}
|
||||
|
||||
export function isUrl(path: string) {
|
||||
return path && !isRootedDiskPath(path) && stringContains(path, "://");
|
||||
}
|
||||
|
||||
export function pathIsRelative(path: string): boolean {
|
||||
return /^\.\.?($|[\\/])/.test(path);
|
||||
}
|
||||
|
||||
export function getEmitScriptTarget(compilerOptions: CompilerOptions) {
|
||||
return compilerOptions.target || ScriptTarget.ES3;
|
||||
}
|
||||
|
@ -2089,8 +1985,208 @@ namespace ts {
|
|||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Paths
|
||||
//
|
||||
|
||||
|
||||
/**
|
||||
* Internally, we represent paths as strings with '/' as the directory separator.
|
||||
* When we make system calls (eg: LanguageServiceHost.getDirectory()),
|
||||
* we expect the host to correctly handle paths in our specified format.
|
||||
*/
|
||||
export const directorySeparator = "/";
|
||||
const altDirectorySeparator = "\\";
|
||||
const urlSchemeSeparator = "://";
|
||||
|
||||
const backslashRegExp = /\\/g;
|
||||
|
||||
/**
|
||||
* Normalize path separators.
|
||||
*/
|
||||
export function normalizeSlashes(path: string): string {
|
||||
return path.replace(backslashRegExp, directorySeparator);
|
||||
}
|
||||
|
||||
function isVolumeCharacter(charCode: number) {
|
||||
return (charCode >= CharacterCodes.a && charCode <= CharacterCodes.z) ||
|
||||
(charCode >= CharacterCodes.A && charCode <= CharacterCodes.Z);
|
||||
}
|
||||
|
||||
function getFileUrlVolumeSeparatorEnd(url: string, start: number) {
|
||||
const ch0 = url.charCodeAt(start);
|
||||
if (ch0 === CharacterCodes.colon) return start + 1;
|
||||
if (ch0 === CharacterCodes.percent && url.charCodeAt(start + 1) === CharacterCodes._3) {
|
||||
const ch2 = url.charCodeAt(start + 2);
|
||||
if (ch2 === CharacterCodes.a || ch2 === CharacterCodes.A) return start + 3;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files").
|
||||
* If the root is part of a URL, the twos-complement of the root length is returned.
|
||||
*/
|
||||
function getEncodedRootLength(path: string): number {
|
||||
if (!path) return 0;
|
||||
const ch0 = path.charCodeAt(0);
|
||||
|
||||
// POSIX or UNC
|
||||
if (ch0 === CharacterCodes.slash || ch0 === CharacterCodes.backslash) {
|
||||
if (path.charCodeAt(1) !== ch0) return 1; // POSIX: "/" (or non-normalized "\")
|
||||
|
||||
const p1 = path.indexOf(ch0 === CharacterCodes.slash ? directorySeparator : altDirectorySeparator, 2);
|
||||
if (p1 < 0) return path.length; // UNC: "//server" or "\\server"
|
||||
|
||||
return p1 + 1; // UNC: "//server/" or "\\server\"
|
||||
}
|
||||
|
||||
// DOS
|
||||
if (isVolumeCharacter(ch0) && path.charCodeAt(1) === CharacterCodes.colon) {
|
||||
const ch2 = path.charCodeAt(2);
|
||||
if (ch2 === CharacterCodes.slash || ch2 === CharacterCodes.backslash) return 3; // DOS: "c:/" or "c:\"
|
||||
if (path.length === 2) return 2; // DOS: "c:" (but not "c:d")
|
||||
}
|
||||
|
||||
// URL
|
||||
const schemeEnd = path.indexOf(urlSchemeSeparator);
|
||||
if (schemeEnd !== -1) {
|
||||
const authorityStart = schemeEnd + urlSchemeSeparator.length;
|
||||
const authorityEnd = path.indexOf(directorySeparator, authorityStart);
|
||||
if (authorityEnd !== -1) { // URL: "file:///", "file://server/", "file://server/path"
|
||||
// For local "file" URLs, include the leading DOS volume (if present).
|
||||
// Per https://www.ietf.org/rfc/rfc1738.txt, a host of "" or "localhost" is a
|
||||
// special case interpreted as "the machine from which the URL is being interpreted".
|
||||
const scheme = path.slice(0, schemeEnd);
|
||||
const authority = path.slice(authorityStart, authorityEnd);
|
||||
if (scheme === "file" && (authority === "" || authority === "localhost") &&
|
||||
isVolumeCharacter(path.charCodeAt(authorityEnd + 1))) {
|
||||
const volumeSeparatorEnd = getFileUrlVolumeSeparatorEnd(path, authorityEnd + 2);
|
||||
if (volumeSeparatorEnd !== -1) {
|
||||
if (path.charCodeAt(volumeSeparatorEnd) === CharacterCodes.slash) {
|
||||
// URL: "file:///c:/", "file://localhost/c:/", "file:///c%3a/", "file://localhost/c%3a/"
|
||||
return ~(volumeSeparatorEnd + 1);
|
||||
}
|
||||
if (volumeSeparatorEnd === path.length) {
|
||||
// URL: "file:///c:", "file://localhost/c:", "file:///c$3a", "file://localhost/c%3a"
|
||||
// but not "file:///c:d" or "file:///c%3ad"
|
||||
return ~volumeSeparatorEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ~(authorityEnd + 1); // URL: "file://server/", "http://server/"
|
||||
}
|
||||
return ~path.length; // URL: "file://server", "http://server"
|
||||
}
|
||||
|
||||
// relative
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files").
|
||||
*
|
||||
* For example:
|
||||
* ```ts
|
||||
* getRootLength("a") === 0 // ""
|
||||
* getRootLength("/") === 1 // "/"
|
||||
* getRootLength("c:") === 2 // "c:"
|
||||
* getRootLength("c:d") === 0 // ""
|
||||
* getRootLength("c:/") === 3 // "c:/"
|
||||
* getRootLength("c:\\") === 3 // "c:\\"
|
||||
* getRootLength("//server") === 7 // "//server"
|
||||
* getRootLength("//server/share") === 8 // "//server/"
|
||||
* getRootLength("\\\\server") === 7 // "\\\\server"
|
||||
* getRootLength("\\\\server\\share") === 8 // "\\\\server\\"
|
||||
* getRootLength("file:///path") === 8 // "file:///"
|
||||
* getRootLength("file:///c:") === 10 // "file:///c:"
|
||||
* getRootLength("file:///c:d") === 8 // "file:///"
|
||||
* getRootLength("file:///c:/path") === 11 // "file:///c:/"
|
||||
* getRootLength("file://server") === 13 // "file://server"
|
||||
* getRootLength("file://server/path") === 14 // "file://server/"
|
||||
* getRootLength("http://server") === 13 // "http://server"
|
||||
* getRootLength("http://server/path") === 14 // "http://server/"
|
||||
* ```
|
||||
*/
|
||||
export function getRootLength(path: string) {
|
||||
const rootLength = getEncodedRootLength(path);
|
||||
return rootLength < 0 ? ~rootLength : rootLength;
|
||||
}
|
||||
|
||||
// TODO(rbuckton): replace references with `resolvePath`
|
||||
export function normalizePath(path: string): string {
|
||||
return resolvePath(path);
|
||||
}
|
||||
|
||||
export function normalizePathAndParts(path: string): { path: string, parts: string[] } {
|
||||
path = normalizeSlashes(path);
|
||||
const [root, ...parts] = reducePathComponents(getPathComponents(path));
|
||||
if (parts.length) {
|
||||
const joinedParts = root + parts.join(directorySeparator);
|
||||
return { path: hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(joinedParts) : joinedParts, parts };
|
||||
}
|
||||
else {
|
||||
return { path: root, parts };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path except for its basename. Semantics align with NodeJS's `path.dirname`
|
||||
* except that we support URL's as well.
|
||||
*
|
||||
* ```ts
|
||||
* getDirectoryPath("/path/to/file.ext") === "/path/to"
|
||||
* getDirectoryPath("/path/to/") === "/path"
|
||||
* getDirectoryPath("/") === "/"
|
||||
* ```
|
||||
*/
|
||||
export function getDirectoryPath(path: Path): Path;
|
||||
/**
|
||||
* Returns the path except for its basename. Semantics align with NodeJS's `path.dirname`
|
||||
* except that we support URL's as well.
|
||||
*
|
||||
* ```ts
|
||||
* getDirectoryPath("/path/to/file.ext") === "/path/to"
|
||||
* getDirectoryPath("/path/to/") === "/path"
|
||||
* getDirectoryPath("/") === "/"
|
||||
* ```
|
||||
*/
|
||||
export function getDirectoryPath(path: string): string;
|
||||
export function getDirectoryPath(path: string): string {
|
||||
path = normalizeSlashes(path);
|
||||
|
||||
// If the path provided is itself the root, then return it.
|
||||
const rootLength = getRootLength(path);
|
||||
if (rootLength === path.length) return path;
|
||||
|
||||
// return the leading portion of the path up to the last (non-terminal) directory separator
|
||||
// but not including any trailing directory separator.
|
||||
path = removeTrailingDirectorySeparator(path);
|
||||
return path.slice(0, Math.max(rootLength, path.lastIndexOf(directorySeparator)));
|
||||
}
|
||||
|
||||
export function isUrl(path: string) {
|
||||
return getEncodedRootLength(path) < 0;
|
||||
}
|
||||
|
||||
export function pathIsRelative(path: string): boolean {
|
||||
return /^\.\.?($|[\\/])/.test(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a path is an absolute path (e.g. starts with `/`, or a dos path
|
||||
* like `c:`, `c:\` or `c:/`).
|
||||
*/
|
||||
export function isRootedDiskPath(path: string) {
|
||||
return path && getRootLength(path) !== 0;
|
||||
return getEncodedRootLength(path) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a path consists only of a path root.
|
||||
*/
|
||||
export function isDiskPathRoot(path: string) {
|
||||
const rootLength = getEncodedRootLength(path);
|
||||
return rootLength > 0 && rootLength === path.length;
|
||||
}
|
||||
|
||||
export function convertToRelativePath(absoluteOrRelativePath: string, basePath: string, getCanonicalFileName: (path: string) => string): string {
|
||||
|
@ -2099,146 +2195,214 @@ namespace ts {
|
|||
: getRelativePathToDirectoryOrUrl(basePath, absoluteOrRelativePath, basePath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
|
||||
}
|
||||
|
||||
function normalizedPathComponents(path: string, rootLength: number) {
|
||||
const normalizedParts = getNormalizedParts(path, rootLength);
|
||||
return [path.substr(0, rootLength)].concat(normalizedParts);
|
||||
function pathComponents(path: string, rootLength: number) {
|
||||
const root = path.substring(0, rootLength);
|
||||
const rest = path.substring(rootLength).split(directorySeparator);
|
||||
if (rest.length && !lastOrUndefined(rest)) rest.pop();
|
||||
return [root, ...rest];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a path into an array containing a root component (at index 0) and zero or more path
|
||||
* components (at indices > 0). The result is not normalized.
|
||||
* If the path is relative, the root component is `""`.
|
||||
* If the path is absolute, the root component includes the first path separator (`/`).
|
||||
*/
|
||||
export function getPathComponents(path: string, currentDirectory = "") {
|
||||
path = combinePaths(currentDirectory, path);
|
||||
const rootLength = getRootLength(path);
|
||||
return pathComponents(path, rootLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce an array of path components to a more simplified path by navigating any
|
||||
* `"."` or `".."` entries in the path.
|
||||
*/
|
||||
export function reducePathComponents(components: ReadonlyArray<string>) {
|
||||
if (!some(components)) return [];
|
||||
const reduced = [components[0]];
|
||||
for (let i = 1; i < components.length; i++) {
|
||||
const component = components[i];
|
||||
if (component === ".") continue;
|
||||
if (component === "..") {
|
||||
if (reduced.length > 1) {
|
||||
if (reduced[reduced.length - 1] !== "..") {
|
||||
reduced.pop();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (reduced[0]) continue;
|
||||
}
|
||||
reduced.push(component);
|
||||
}
|
||||
return reduced;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a path into an array containing a root component (at index 0) and zero or more path
|
||||
* components (at indices > 0). The result is normalized.
|
||||
* If the path is relative, the root component is `""`.
|
||||
* If the path is absolute, the root component includes the first path separator (`/`).
|
||||
*/
|
||||
export function getNormalizedPathComponents(path: string, currentDirectory: string) {
|
||||
path = normalizeSlashes(path);
|
||||
let rootLength = getRootLength(path);
|
||||
if (rootLength === 0) {
|
||||
// If the path is not rooted it is relative to current directory
|
||||
path = combinePaths(normalizeSlashes(currentDirectory), path);
|
||||
rootLength = getRootLength(path);
|
||||
}
|
||||
|
||||
return normalizedPathComponents(path, rootLength);
|
||||
return reducePathComponents(getPathComponents(path, currentDirectory));
|
||||
}
|
||||
|
||||
export function getNormalizedAbsolutePath(fileName: string, currentDirectory: string) {
|
||||
return getNormalizedPathFromPathComponents(getNormalizedPathComponents(fileName, currentDirectory));
|
||||
return getPathFromPathComponents(getNormalizedPathComponents(fileName, currentDirectory));
|
||||
}
|
||||
|
||||
export function getNormalizedPathFromPathComponents(pathComponents: ReadonlyArray<string>) {
|
||||
if (pathComponents && pathComponents.length) {
|
||||
return pathComponents[0] + pathComponents.slice(1).join(directorySeparator);
|
||||
}
|
||||
/**
|
||||
* Formats a parsed path consisting of a root component (at index 0) and zero or more path
|
||||
* segments (at indices > 0).
|
||||
*/
|
||||
export function getPathFromPathComponents(pathComponents: ReadonlyArray<string>) {
|
||||
if (pathComponents.length === 0) return "";
|
||||
|
||||
const root = pathComponents[0] && ensureTrailingDirectorySeparator(pathComponents[0]);
|
||||
if (pathComponents.length === 1) return root;
|
||||
|
||||
return root + pathComponents.slice(1).join(directorySeparator);
|
||||
}
|
||||
|
||||
function getNormalizedPathComponentsOfUrl(url: string) {
|
||||
// Get root length of http://www.website.com/folder1/folder2/
|
||||
// In this example the root is: http://www.website.com/
|
||||
// normalized path components should be ["http://www.website.com/", "folder1", "folder2"]
|
||||
function getPathComponentsRelativeTo(from: string, to: string, stringEqualityComparer: (a: string, b: string) => boolean, getCanonicalFileName: GetCanonicalFileName) {
|
||||
const fromComponents = reducePathComponents(getPathComponents(from));
|
||||
const toComponents = reducePathComponents(getPathComponents(to));
|
||||
|
||||
const urlLength = url.length;
|
||||
// Initial root length is http:// part
|
||||
let rootLength = url.indexOf("://") + "://".length;
|
||||
while (rootLength < urlLength) {
|
||||
// Consume all immediate slashes in the protocol
|
||||
// eg.initial rootlength is just file:// but it needs to consume another "/" in file:///
|
||||
if (url.charCodeAt(rootLength) === CharacterCodes.slash) {
|
||||
rootLength++;
|
||||
}
|
||||
else {
|
||||
// non slash character means we continue proceeding to next component of root search
|
||||
break;
|
||||
}
|
||||
let start: number;
|
||||
for (start = 0; start < fromComponents.length && start < toComponents.length; start++) {
|
||||
const fromComponent = getCanonicalFileName(fromComponents[start]);
|
||||
const toComponent = getCanonicalFileName(toComponents[start]);
|
||||
const comparer = start === 0 ? equateStringsCaseInsensitive : stringEqualityComparer;
|
||||
if (!comparer(fromComponent, toComponent)) break;
|
||||
}
|
||||
|
||||
// there are no parts after http:// just return current string as the pathComponent
|
||||
if (rootLength === urlLength) {
|
||||
return [url];
|
||||
if (start === 0) {
|
||||
return toComponents;
|
||||
}
|
||||
|
||||
// Find the index of "/" after website.com so the root can be http://www.website.com/ (from existing http://)
|
||||
const indexOfNextSlash = url.indexOf(directorySeparator, rootLength);
|
||||
if (indexOfNextSlash !== -1) {
|
||||
// Found the "/" after the website.com so the root is length of http://www.website.com/
|
||||
// and get components after the root normally like any other folder components
|
||||
rootLength = indexOfNextSlash + 1;
|
||||
return normalizedPathComponents(url, rootLength);
|
||||
}
|
||||
else {
|
||||
// Can't find the host assume the rest of the string as component
|
||||
// but make sure we append "/" to it as root is not joined using "/"
|
||||
// eg. if url passed in was http://website.com we want to use root as [http://website.com/]
|
||||
// so that other path manipulations will be correct and it can be merged with relative paths correctly
|
||||
return [url + directorySeparator];
|
||||
const components = toComponents.slice(start);
|
||||
const relative: string[] = [];
|
||||
for (; start < fromComponents.length; start++) {
|
||||
relative.push("..");
|
||||
}
|
||||
return ["", ...relative, ...components];
|
||||
}
|
||||
|
||||
function getNormalizedPathOrUrlComponents(pathOrUrl: string, currentDirectory: string) {
|
||||
if (isUrl(pathOrUrl)) {
|
||||
return getNormalizedPathComponentsOfUrl(pathOrUrl);
|
||||
}
|
||||
else {
|
||||
return getNormalizedPathComponents(pathOrUrl, currentDirectory);
|
||||
}
|
||||
/**
|
||||
* Gets a relative path that can be used to traverse between `from` and `to`.
|
||||
*/
|
||||
export function getRelativePath(from: string, to: string, ignoreCase: boolean): string;
|
||||
/**
|
||||
* Gets a relative path that can be used to traverse between `from` and `to`.
|
||||
*/
|
||||
// tslint:disable-next-line:unified-signatures
|
||||
export function getRelativePath(from: string, to: string, getCanonicalFileName: GetCanonicalFileName): string;
|
||||
export function getRelativePath(from: string, to: string, getCanonicalFileNameOrIgnoreCase: GetCanonicalFileName | boolean) {
|
||||
Debug.assert((getRootLength(from) > 0) === (getRootLength(to) > 0), "Paths must either both be absolute or both be relative");
|
||||
const getCanonicalFileName = typeof getCanonicalFileNameOrIgnoreCase === "function" ? getCanonicalFileNameOrIgnoreCase : identity;
|
||||
const ignoreCase = typeof getCanonicalFileNameOrIgnoreCase === "boolean" ? getCanonicalFileNameOrIgnoreCase : false;
|
||||
const pathComponents = getPathComponentsRelativeTo(from, to, ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive, getCanonicalFileName);
|
||||
return getPathFromPathComponents(pathComponents);
|
||||
}
|
||||
|
||||
export function getRelativePathToDirectoryOrUrl(directoryPathOrUrl: string, relativeOrAbsolutePath: string, currentDirectory: string, getCanonicalFileName: GetCanonicalFileName, isAbsolutePathAnUrl: boolean) {
|
||||
const pathComponents = getNormalizedPathOrUrlComponents(relativeOrAbsolutePath, currentDirectory);
|
||||
const directoryComponents = getNormalizedPathOrUrlComponents(directoryPathOrUrl, currentDirectory);
|
||||
if (directoryComponents.length > 1 && lastOrUndefined(directoryComponents) === "") {
|
||||
// If the directory path given was of type test/cases/ then we really need components of directory to be only till its name
|
||||
// that is ["test", "cases", ""] needs to be actually ["test", "cases"]
|
||||
directoryComponents.pop();
|
||||
const pathComponents = getPathComponentsRelativeTo(
|
||||
resolvePath(currentDirectory, directoryPathOrUrl),
|
||||
resolvePath(currentDirectory, relativeOrAbsolutePath),
|
||||
equateStringsCaseSensitive,
|
||||
getCanonicalFileName
|
||||
);
|
||||
|
||||
const firstComponent = pathComponents[0];
|
||||
if (isAbsolutePathAnUrl && isRootedDiskPath(firstComponent)) {
|
||||
const prefix = firstComponent.charAt(0) === directorySeparator ? "file://" : "file:///";
|
||||
pathComponents[0] = prefix + firstComponent;
|
||||
}
|
||||
|
||||
// Find the component that differs
|
||||
let joinStartIndex: number;
|
||||
for (joinStartIndex = 0; joinStartIndex < pathComponents.length && joinStartIndex < directoryComponents.length; joinStartIndex++) {
|
||||
if (getCanonicalFileName(directoryComponents[joinStartIndex]) !== getCanonicalFileName(pathComponents[joinStartIndex])) {
|
||||
break;
|
||||
}
|
||||
return getPathFromPathComponents(pathComponents);
|
||||
}
|
||||
|
||||
// Get the relative path
|
||||
if (joinStartIndex) {
|
||||
let relativePath = "";
|
||||
const relativePathComponents = pathComponents.slice(joinStartIndex, pathComponents.length);
|
||||
for (; joinStartIndex < directoryComponents.length; joinStartIndex++) {
|
||||
if (directoryComponents[joinStartIndex] !== "") {
|
||||
relativePath = relativePath + ".." + directorySeparator;
|
||||
}
|
||||
/**
|
||||
* Ensures a path is either absolute (prefixed with `/` or `c:`) or dot-relative (prefixed
|
||||
* with `./` or `../`) so as not to be confused with an unprefixed module name.
|
||||
*/
|
||||
export function ensurePathIsNonModuleName(path: string): string {
|
||||
return getRootLength(path) === 0 && !pathIsRelative(path) ? "./" + path : path;
|
||||
}
|
||||
|
||||
return relativePath + relativePathComponents.join(directorySeparator);
|
||||
/**
|
||||
* Returns the path except for its containing directory name.
|
||||
* Semantics align with NodeJS's `path.basename` except that we support URL's as well.
|
||||
*
|
||||
* ```ts
|
||||
* getBaseFileName("/path/to/file.ext") === "file.ext"
|
||||
* getBaseFileName("/path/to/") === "to"
|
||||
* getBaseFileName("/") === ""
|
||||
* ```
|
||||
*/
|
||||
export function getBaseFileName(path: string): string;
|
||||
/**
|
||||
* Gets the portion of a path following the last (non-terminal) separator (`/`).
|
||||
* Semantics align with NodeJS's `path.basename` except that we support URL's as well.
|
||||
* If the base name has any one of the provided extensions, it is removed.
|
||||
*
|
||||
* ```ts
|
||||
* getBaseFileName("/path/to/file.ext", ".ext", true) === "file"
|
||||
* getBaseFileName("/path/to/file.js", ".ext", true) === "file.js"
|
||||
* ```
|
||||
*/
|
||||
export function getBaseFileName(path: string, extensions: string | ReadonlyArray<string>, ignoreCase: boolean): string;
|
||||
export function getBaseFileName(path: string, extensions?: string | ReadonlyArray<string>, ignoreCase?: boolean) {
|
||||
path = normalizeSlashes(path);
|
||||
|
||||
// if the path provided is itself the root, then it has not file name.
|
||||
const rootLength = getRootLength(path);
|
||||
if (rootLength === path.length) return "";
|
||||
|
||||
// return the trailing portion of the path starting after the last (non-terminal) directory
|
||||
// separator but not including any trailing directory separator.
|
||||
path = removeTrailingDirectorySeparator(path);
|
||||
const name = path.slice(Math.max(getRootLength(path), path.lastIndexOf(directorySeparator) + 1));
|
||||
const extension = extensions !== undefined && ignoreCase !== undefined ? getAnyExtensionFromPath(name, extensions, ignoreCase) : undefined;
|
||||
return extension ? name.slice(0, name.length - extension.length) : name;
|
||||
}
|
||||
|
||||
// Cant find the relative path, get the absolute path
|
||||
let absolutePath = getNormalizedPathFromPathComponents(pathComponents);
|
||||
if (isAbsolutePathAnUrl && isRootedDiskPath(absolutePath)) {
|
||||
absolutePath = "file:///" + absolutePath;
|
||||
/**
|
||||
* Combines paths. If a path is absolute, it replaces any previous path.
|
||||
*/
|
||||
export function combinePaths(path: string, ...paths: string[]): string {
|
||||
if (path) path = normalizeSlashes(path);
|
||||
for (let relativePath of paths) {
|
||||
if (!relativePath) continue;
|
||||
relativePath = normalizeSlashes(relativePath);
|
||||
if (!path || getRootLength(relativePath) !== 0) {
|
||||
path = relativePath;
|
||||
}
|
||||
else {
|
||||
path = ensureTrailingDirectorySeparator(path) + relativePath;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
return absolutePath;
|
||||
/**
|
||||
* Combines and resolves paths. If a path is absolute, it replaces any previous path. Any
|
||||
* `.` and `..` path components are resolved.
|
||||
*/
|
||||
export function resolvePath(path: string, ...paths: string[]): string {
|
||||
const combined = some(paths) ? combinePaths(path, ...paths) : normalizeSlashes(path);
|
||||
const normalized = getPathFromPathComponents(reducePathComponents(getPathComponents(combined)));
|
||||
return normalized && hasTrailingDirectorySeparator(combined) ? ensureTrailingDirectorySeparator(normalized) : normalized;
|
||||
}
|
||||
|
||||
export function getRelativePath(path: string, directoryPath: string, getCanonicalFileName: GetCanonicalFileName) {
|
||||
const relativePath = getRelativePathToDirectoryOrUrl(directoryPath, path, directoryPath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
|
||||
return ensurePathIsRelative(relativePath);
|
||||
}
|
||||
|
||||
export function ensurePathIsRelative(path: string): string {
|
||||
return !pathIsRelative(path) ? "./" + path : path;
|
||||
}
|
||||
|
||||
export function getBaseFileName(path: string) {
|
||||
if (path === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const i = path.lastIndexOf(directorySeparator);
|
||||
return i < 0 ? path : path.substring(i + 1);
|
||||
}
|
||||
|
||||
export function combinePaths(path1: string, path2: string): string {
|
||||
if (!(path1 && path1.length)) return path2;
|
||||
if (!(path2 && path2.length)) return path1;
|
||||
if (getRootLength(path2) !== 0) return path2;
|
||||
if (path1.charAt(path1.length - 1) === directorySeparator) return path1 + path2;
|
||||
return path1 + directorySeparator + path2;
|
||||
/**
|
||||
* Determines whether a path has a trailing separator (`/` or `\\`).
|
||||
*/
|
||||
export function hasTrailingDirectorySeparator(path: string) {
|
||||
if (path.length === 0) return false;
|
||||
const ch = path.charCodeAt(path.length - 1);
|
||||
return ch === CharacterCodes.slash || ch === CharacterCodes.backslash;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2248,7 +2412,7 @@ namespace ts {
|
|||
export function removeTrailingDirectorySeparator(path: Path): Path;
|
||||
export function removeTrailingDirectorySeparator(path: string): string;
|
||||
export function removeTrailingDirectorySeparator(path: string) {
|
||||
if (path.charAt(path.length - 1) === directorySeparator) {
|
||||
if (hasTrailingDirectorySeparator(path)) {
|
||||
return path.substr(0, path.length - 1);
|
||||
}
|
||||
|
||||
|
@ -2262,47 +2426,78 @@ namespace ts {
|
|||
export function ensureTrailingDirectorySeparator(path: Path): Path;
|
||||
export function ensureTrailingDirectorySeparator(path: string): string;
|
||||
export function ensureTrailingDirectorySeparator(path: string) {
|
||||
if (path.charAt(path.length - 1) !== directorySeparator) {
|
||||
if (!hasTrailingDirectorySeparator(path)) {
|
||||
return path + directorySeparator;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
export function comparePaths(a: string, b: string, currentDirectory: string, ignoreCase?: boolean) {
|
||||
function comparePathsWorker(a: string, b: string, componentComparer: (a: string, b: string) => Comparison) {
|
||||
if (a === b) return Comparison.EqualTo;
|
||||
if (a === undefined) return Comparison.LessThan;
|
||||
if (b === undefined) return Comparison.GreaterThan;
|
||||
a = removeTrailingDirectorySeparator(a);
|
||||
b = removeTrailingDirectorySeparator(b);
|
||||
const aComponents = getNormalizedPathComponents(a, currentDirectory);
|
||||
const bComponents = getNormalizedPathComponents(b, currentDirectory);
|
||||
const aComponents = reducePathComponents(getPathComponents(a));
|
||||
const bComponents = reducePathComponents(getPathComponents(b));
|
||||
const sharedLength = Math.min(aComponents.length, bComponents.length);
|
||||
const comparer = getStringComparer(ignoreCase);
|
||||
for (let i = 0; i < sharedLength; i++) {
|
||||
const result = comparer(aComponents[i], bComponents[i]);
|
||||
const stringComparer = i === 0 ? compareStringsCaseInsensitive : componentComparer;
|
||||
const result = stringComparer(aComponents[i], bComponents[i]);
|
||||
if (result !== Comparison.EqualTo) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return compareValues(aComponents.length, bComponents.length);
|
||||
}
|
||||
|
||||
export function containsPath(parent: string, child: string, currentDirectory: string, ignoreCase?: boolean) {
|
||||
/**
|
||||
* Performs a case-sensitive comparison of two paths.
|
||||
*/
|
||||
export function comparePathsCaseSensitive(a: string, b: string) {
|
||||
return comparePathsWorker(a, b, compareStringsCaseSensitive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a case-insensitive comparison of two paths.
|
||||
*/
|
||||
export function comparePathsCaseInsensitive(a: string, b: string) {
|
||||
return comparePathsWorker(a, b, compareStringsCaseInsensitive);
|
||||
}
|
||||
|
||||
export function comparePaths(a: string, b: string, ignoreCase?: boolean): Comparison;
|
||||
export function comparePaths(a: string, b: string, currentDirectory: string, ignoreCase?: boolean): Comparison;
|
||||
export function comparePaths(a: string, b: string, currentDirectory?: string | boolean, ignoreCase?: boolean) {
|
||||
if (typeof currentDirectory === "string") {
|
||||
a = combinePaths(currentDirectory, a);
|
||||
b = combinePaths(currentDirectory, b);
|
||||
}
|
||||
else if (typeof currentDirectory === "boolean") {
|
||||
ignoreCase = currentDirectory;
|
||||
}
|
||||
return comparePathsWorker(a, b, getStringComparer(ignoreCase));
|
||||
}
|
||||
|
||||
export function containsPath(parent: string, child: string, ignoreCase?: boolean): boolean;
|
||||
export function containsPath(parent: string, child: string, currentDirectory: string, ignoreCase?: boolean): boolean;
|
||||
export function containsPath(parent: string, child: string, currentDirectory?: string | boolean, ignoreCase?: boolean) {
|
||||
if (typeof currentDirectory === "string") {
|
||||
parent = combinePaths(currentDirectory, parent);
|
||||
child = combinePaths(currentDirectory, child);
|
||||
}
|
||||
else if (typeof currentDirectory === "boolean") {
|
||||
ignoreCase = currentDirectory;
|
||||
}
|
||||
if (parent === undefined || child === undefined) return false;
|
||||
if (parent === child) return true;
|
||||
parent = removeTrailingDirectorySeparator(parent);
|
||||
child = removeTrailingDirectorySeparator(child);
|
||||
if (parent === child) return true;
|
||||
const parentComponents = getNormalizedPathComponents(parent, currentDirectory);
|
||||
const childComponents = getNormalizedPathComponents(child, currentDirectory);
|
||||
const parentComponents = reducePathComponents(getPathComponents(parent));
|
||||
const childComponents = reducePathComponents(getPathComponents(child));
|
||||
if (childComponents.length < parentComponents.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const equalityComparer = ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive;
|
||||
const componentEqualityComparer = ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive;
|
||||
for (let i = 0; i < parentComponents.length; i++) {
|
||||
const equalityComparer = i === 0 ? equateStringsCaseInsensitive : componentEqualityComparer;
|
||||
if (!equalityComparer(parentComponents[i], childComponents[i])) {
|
||||
return false;
|
||||
}
|
||||
|
@ -2791,7 +2986,14 @@ namespace ts {
|
|||
}
|
||||
|
||||
export function changeExtension<T extends string | Path>(path: T, newExtension: string): T {
|
||||
return <T>(removeFileExtension(path) + newExtension);
|
||||
return <T>changeAnyExtension(path, newExtension, extensionsToRemove, /*ignoreCase*/ false);
|
||||
}
|
||||
|
||||
export function changeAnyExtension(path: string, ext: string): string;
|
||||
export function changeAnyExtension(path: string, ext: string, extensions: string | ReadonlyArray<string>, ignoreCase: boolean): string;
|
||||
export function changeAnyExtension(path: string, ext: string, extensions?: string | ReadonlyArray<string>, ignoreCase?: boolean) {
|
||||
const pathext = extensions !== undefined && ignoreCase !== undefined ? getAnyExtensionFromPath(path, extensions, ignoreCase) : getAnyExtensionFromPath(path);
|
||||
return pathext ? path.slice(0, path.length - pathext.length) + (startsWith(ext, ".") ? ext : "." + ext) : path;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3125,14 +3327,40 @@ namespace ts {
|
|||
return find<Extension>(supportedTypescriptExtensionsForExtractExtension, e => fileExtensionIs(path, e)) || find(supportedJavascriptExtensions, e => fileExtensionIs(path, e));
|
||||
}
|
||||
|
||||
function getAnyExtensionFromPathWorker(path: string, extensions: string | ReadonlyArray<string>, stringEqualityComparer: (a: string, b: string) => boolean) {
|
||||
if (typeof extensions === "string") extensions = [extensions];
|
||||
for (let extension of extensions) {
|
||||
if (!startsWith(extension, ".")) extension = "." + extension;
|
||||
if (path.length >= extension.length && path.charAt(path.length - extension.length) === ".") {
|
||||
const pathExtension = path.slice(path.length - extension.length);
|
||||
if (stringEqualityComparer(pathExtension, extension)) {
|
||||
return pathExtension;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file extension for a path.
|
||||
*/
|
||||
export function getAnyExtensionFromPath(path: string): string;
|
||||
/**
|
||||
* Gets the file extension for a path, provided it is one of the provided extensions.
|
||||
*/
|
||||
export function getAnyExtensionFromPath(path: string, extensions: string | ReadonlyArray<string>, ignoreCase: boolean): string;
|
||||
export function getAnyExtensionFromPath(path: string, extensions?: string | ReadonlyArray<string>, ignoreCase?: boolean): string {
|
||||
// Retrieves any string from the final "." onwards from a base file name.
|
||||
// Unlike extensionFromPath, which throws an exception on unrecognized extensions.
|
||||
export function getAnyExtensionFromPath(path: string): string | undefined {
|
||||
if (extensions) {
|
||||
return getAnyExtensionFromPathWorker(path, extensions, ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive);
|
||||
}
|
||||
const baseFileName = getBaseFileName(path);
|
||||
const extensionIndex = baseFileName.lastIndexOf(".");
|
||||
if (extensionIndex >= 0) {
|
||||
return baseFileName.substring(extensionIndex);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export function isCheckJsEnabledForFile(sourceFile: SourceFile, compilerOptions: CompilerOptions) {
|
||||
|
|
|
@ -65,7 +65,8 @@ namespace ts {
|
|||
// JavaScript files are always LanguageVariant.JSX, as JSX syntax is allowed in .js files also.
|
||||
// So for JavaScript files, '.jsx' is only emitted if the input was '.jsx', and JsxEmit.Preserve.
|
||||
// For TypeScript, the only time to emit with a '.jsx' extension, is on JSX input, and JsxEmit.Preserve
|
||||
function getOutputExtension(sourceFile: SourceFile, options: CompilerOptions): Extension {
|
||||
/* @internal */
|
||||
export function getOutputExtension(sourceFile: SourceFile, options: CompilerOptions): Extension {
|
||||
if (options.jsx === JsxEmit.Preserve) {
|
||||
if (isSourceFileJavaScript(sourceFile)) {
|
||||
if (fileExtensionIs(sourceFile.fileName, Extension.Jsx)) {
|
||||
|
|
|
@ -806,7 +806,7 @@ namespace ts {
|
|||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Loading_module_as_file_Slash_folder_candidate_module_location_0_target_file_type_1, candidate, Extensions[extensions]);
|
||||
}
|
||||
if (!pathEndsWithDirectorySeparator(candidate)) {
|
||||
if (!hasTrailingDirectorySeparator(candidate)) {
|
||||
if (!onlyRecordFailures) {
|
||||
const parentOfCandidate = getDirectoryPath(candidate);
|
||||
if (!directoryProbablyExists(parentOfCandidate, state.host)) {
|
||||
|
|
|
@ -57,7 +57,7 @@ namespace ts {
|
|||
return currentDirectory;
|
||||
}
|
||||
|
||||
return getNormalizedPathFromPathComponents(commonPathComponents);
|
||||
return getPathFromPathComponents(commonPathComponents);
|
||||
}
|
||||
|
||||
interface OutputFingerprint {
|
||||
|
|
|
@ -63,7 +63,7 @@ namespace ts {
|
|||
}
|
||||
|
||||
function getCachedFileSystemEntries(rootDirPath: Path): MutableFileSystemEntries | undefined {
|
||||
return cachedReadDirectoryResult.get(rootDirPath);
|
||||
return cachedReadDirectoryResult.get(ensureTrailingDirectorySeparator(rootDirPath));
|
||||
}
|
||||
|
||||
function getCachedFileSystemEntriesForBaseDir(path: Path): MutableFileSystemEntries | undefined {
|
||||
|
@ -80,7 +80,7 @@ namespace ts {
|
|||
directories: host.getDirectories(rootDir) || []
|
||||
};
|
||||
|
||||
cachedReadDirectoryResult.set(rootDirPath, resultFromHost);
|
||||
cachedReadDirectoryResult.set(ensureTrailingDirectorySeparator(rootDirPath), resultFromHost);
|
||||
return resultFromHost;
|
||||
}
|
||||
|
||||
|
@ -90,6 +90,7 @@ namespace ts {
|
|||
* The host request is done under try catch block to avoid caching incorrect result
|
||||
*/
|
||||
function tryReadDirectory(rootDir: string, rootDirPath: Path): MutableFileSystemEntries | undefined {
|
||||
rootDirPath = ensureTrailingDirectorySeparator(rootDirPath);
|
||||
const cachedResult = getCachedFileSystemEntries(rootDirPath);
|
||||
if (cachedResult) {
|
||||
return cachedResult;
|
||||
|
@ -100,7 +101,7 @@ namespace ts {
|
|||
}
|
||||
catch (_e) {
|
||||
// If there is exception to read directories, dont cache the result and direct the calls to host
|
||||
Debug.assert(!cachedReadDirectoryResult.has(rootDirPath));
|
||||
Debug.assert(!cachedReadDirectoryResult.has(ensureTrailingDirectorySeparator(rootDirPath)));
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
@ -142,7 +143,7 @@ namespace ts {
|
|||
|
||||
function directoryExists(dirPath: string): boolean {
|
||||
const path = toPath(dirPath);
|
||||
return cachedReadDirectoryResult.has(path) || host.directoryExists(dirPath);
|
||||
return cachedReadDirectoryResult.has(ensureTrailingDirectorySeparator(path)) || host.directoryExists(dirPath);
|
||||
}
|
||||
|
||||
function createDirectory(dirPath: string) {
|
||||
|
|
321
src/harness/collections.ts
Normal file
321
src/harness/collections.ts
Normal file
|
@ -0,0 +1,321 @@
|
|||
namespace collections {
|
||||
export interface SortOptions<T> {
|
||||
comparer: (a: T, b: T) => number;
|
||||
sort: "insertion" | "comparison";
|
||||
}
|
||||
|
||||
export class SortedMap<K, V> {
|
||||
private _comparer: (a: K, b: K) => number;
|
||||
private _keys: K[] = [];
|
||||
private _values: V[] = [];
|
||||
private _order: number[] | undefined;
|
||||
private _version = 0;
|
||||
private _copyOnWrite = false;
|
||||
|
||||
constructor(comparer: ((a: K, b: K) => number) | SortOptions<K>, iterable?: Iterable<[K, V]>) {
|
||||
this._comparer = typeof comparer === "object" ? comparer.comparer : comparer;
|
||||
this._order = typeof comparer === "object" && comparer.sort === "insertion" ? [] : undefined;
|
||||
if (iterable) {
|
||||
const iterator = getIterator(iterable);
|
||||
try {
|
||||
for (let i = nextResult(iterator); i; i = nextResult(iterator)) {
|
||||
const [key, value] = i.value;
|
||||
this.set(key, value);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
closeIterator(iterator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public get size() {
|
||||
return this._keys.length;
|
||||
}
|
||||
|
||||
public get comparer() {
|
||||
return this._comparer;
|
||||
}
|
||||
|
||||
public get [Symbol.toStringTag]() {
|
||||
return "SortedMap";
|
||||
}
|
||||
|
||||
public has(key: K) {
|
||||
return ts.binarySearch(this._keys, key, ts.identity, this._comparer) >= 0;
|
||||
}
|
||||
|
||||
public get(key: K) {
|
||||
const index = ts.binarySearch(this._keys, key, ts.identity, this._comparer);
|
||||
return index >= 0 ? this._values[index] : undefined;
|
||||
}
|
||||
|
||||
public set(key: K, value: V) {
|
||||
const index = ts.binarySearch(this._keys, key, ts.identity, this._comparer);
|
||||
if (index >= 0) {
|
||||
this._values[index] = value;
|
||||
}
|
||||
else {
|
||||
this.writePreamble();
|
||||
insertAt(this._keys, ~index, key);
|
||||
insertAt(this._values, ~index, value);
|
||||
if (this._order) insertAt(this._order, ~index, this._version);
|
||||
this.writePostScript();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public delete(key: K) {
|
||||
const index = ts.binarySearch(this._keys, key, ts.identity, this._comparer);
|
||||
if (index >= 0) {
|
||||
this.writePreamble();
|
||||
ts.orderedRemoveItemAt(this._keys, index);
|
||||
ts.orderedRemoveItemAt(this._values, index);
|
||||
if (this._order) ts.orderedRemoveItemAt(this._order, index);
|
||||
this.writePostScript();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public clear() {
|
||||
if (this.size > 0) {
|
||||
this.writePreamble();
|
||||
this._keys.length = 0;
|
||||
this._values.length = 0;
|
||||
if (this._order) this._order.length = 0;
|
||||
this.writePostScript();
|
||||
}
|
||||
}
|
||||
|
||||
public forEach(callback: (value: V, key: K, collection: this) => void, thisArg?: any) {
|
||||
const keys = this._keys;
|
||||
const values = this._values;
|
||||
const indices = this.getIterationOrder();
|
||||
const version = this._version;
|
||||
this._copyOnWrite = true;
|
||||
try {
|
||||
if (indices) {
|
||||
for (const i of indices) {
|
||||
callback.call(thisArg, values[i], keys[i], this);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
callback.call(thisArg, values[i], keys[i], this);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (version === this._version) {
|
||||
this._copyOnWrite = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public * keys() {
|
||||
const keys = this._keys;
|
||||
const indices = this.getIterationOrder();
|
||||
const version = this._version;
|
||||
this._copyOnWrite = true;
|
||||
try {
|
||||
if (indices) {
|
||||
for (const i of indices) {
|
||||
yield keys[i];
|
||||
}
|
||||
}
|
||||
else {
|
||||
yield* keys;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (version === this._version) {
|
||||
this._copyOnWrite = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public * values() {
|
||||
const values = this._values;
|
||||
const indices = this.getIterationOrder();
|
||||
const version = this._version;
|
||||
this._copyOnWrite = true;
|
||||
try {
|
||||
if (indices) {
|
||||
for (const i of indices) {
|
||||
yield values[i];
|
||||
}
|
||||
}
|
||||
else {
|
||||
yield* values;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (version === this._version) {
|
||||
this._copyOnWrite = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public * entries() {
|
||||
const keys = this._keys;
|
||||
const values = this._values;
|
||||
const indices = this.getIterationOrder();
|
||||
const version = this._version;
|
||||
this._copyOnWrite = true;
|
||||
try {
|
||||
if (indices) {
|
||||
for (const i of indices) {
|
||||
yield [keys[i], values[i]] as [K, V];
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
yield [keys[i], values[i]] as [K, V];
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (version === this._version) {
|
||||
this._copyOnWrite = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public [Symbol.iterator]() {
|
||||
return this.entries();
|
||||
}
|
||||
|
||||
private writePreamble() {
|
||||
if (this._copyOnWrite) {
|
||||
this._keys = this._keys.slice();
|
||||
this._values = this._values.slice();
|
||||
if (this._order) this._order = this._order.slice();
|
||||
this._copyOnWrite = false;
|
||||
}
|
||||
}
|
||||
|
||||
private writePostScript() {
|
||||
this._version++;
|
||||
}
|
||||
|
||||
private getIterationOrder() {
|
||||
if (this._order) {
|
||||
const order = this._order;
|
||||
return this._order
|
||||
.map((_, i) => i)
|
||||
.sort((x, y) => order[x] - order[y]);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function insertAt<T>(array: T[], index: number, value: T): void {
|
||||
if (index === 0) {
|
||||
array.unshift(value);
|
||||
}
|
||||
else if (index === array.length) {
|
||||
array.push(value);
|
||||
}
|
||||
else {
|
||||
for (let i = array.length; i > index; i--) {
|
||||
array[i] = array[i - 1];
|
||||
}
|
||||
array[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
export function getIterator<T>(iterable: Iterable<T>): Iterator<T> {
|
||||
return iterable[Symbol.iterator]();
|
||||
}
|
||||
|
||||
export function nextResult<T>(iterator: Iterator<T>): IteratorResult<T> | undefined {
|
||||
const result = iterator.next();
|
||||
return result.done ? undefined : result;
|
||||
}
|
||||
|
||||
export function closeIterator<T>(iterator: Iterator<T>) {
|
||||
const fn = iterator.return;
|
||||
if (typeof fn === "function") fn.call(iterator);
|
||||
}
|
||||
|
||||
/**
|
||||
* A collection of metadata that supports inheritance.
|
||||
*/
|
||||
export class Metadata {
|
||||
private static readonly _undefinedValue = {};
|
||||
private _parent: Metadata | undefined;
|
||||
private _map: { [key: string]: any };
|
||||
private _version = 0;
|
||||
private _size = -1;
|
||||
private _parentVersion: number | undefined;
|
||||
|
||||
constructor(parent?: Metadata) {
|
||||
this._parent = parent;
|
||||
this._map = Object.create(parent ? parent._map : null); // tslint:disable-line:no-null-keyword
|
||||
}
|
||||
|
||||
public get size(): number {
|
||||
if (this._size === -1 || (this._parent && this._parent._version !== this._parentVersion)) {
|
||||
let size = 0;
|
||||
for (const _ in this._map) size++;
|
||||
this._size = size;
|
||||
if (this._parent) {
|
||||
this._parentVersion = this._parent._version;
|
||||
}
|
||||
}
|
||||
return this._size;
|
||||
}
|
||||
|
||||
public get parent() {
|
||||
return this._parent;
|
||||
}
|
||||
|
||||
public has(key: string): boolean {
|
||||
return this._map[Metadata._escapeKey(key)] !== undefined;
|
||||
}
|
||||
|
||||
public get(key: string): any {
|
||||
const value = this._map[Metadata._escapeKey(key)];
|
||||
return value === Metadata._undefinedValue ? undefined : value;
|
||||
}
|
||||
|
||||
public set(key: string, value: any): this {
|
||||
this._map[Metadata._escapeKey(key)] = value === undefined ? Metadata._undefinedValue : value;
|
||||
this._size = -1;
|
||||
this._version++;
|
||||
return this;
|
||||
}
|
||||
|
||||
public delete(key: string): boolean {
|
||||
const escapedKey = Metadata._escapeKey(key);
|
||||
if (this._map[escapedKey] !== undefined) {
|
||||
delete this._map[escapedKey];
|
||||
this._size = -1;
|
||||
this._version++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._map = Object.create(this._parent ? this._parent._map : null); // tslint:disable-line:no-null-keyword
|
||||
this._size = -1;
|
||||
this._version++;
|
||||
}
|
||||
|
||||
public forEach(callback: (value: any, key: string, map: this) => void) {
|
||||
for (const key in this._map) {
|
||||
callback(this._map[key], Metadata._unescapeKey(key), this);
|
||||
}
|
||||
}
|
||||
|
||||
private static _escapeKey(text: string) {
|
||||
return (text.length >= 2 && text.charAt(0) === "_" && text.charAt(1) === "_" ? "_" + text : text);
|
||||
}
|
||||
|
||||
private static _unescapeKey(text: string) {
|
||||
return (text.length >= 3 && text.charAt(0) === "_" && text.charAt(1) === "_" && text.charAt(2) === "_" ? text.slice(1) : text);
|
||||
}
|
||||
}
|
||||
}
|
248
src/harness/compiler.ts
Normal file
248
src/harness/compiler.ts
Normal file
|
@ -0,0 +1,248 @@
|
|||
/**
|
||||
* Test harness compiler functionality.
|
||||
*/
|
||||
namespace compiler {
|
||||
export interface Project {
|
||||
file: string;
|
||||
config?: ts.ParsedCommandLine;
|
||||
errors?: ts.Diagnostic[];
|
||||
}
|
||||
|
||||
export function readProject(host: fakes.ParseConfigHost, project: string | undefined, existingOptions?: ts.CompilerOptions): Project | undefined {
|
||||
if (project) {
|
||||
project = host.vfs.stringComparer(vpath.basename(project), "tsconfig.json") === 0 ? project :
|
||||
vpath.combine(project, "tsconfig.json");
|
||||
}
|
||||
else {
|
||||
[project] = host.vfs.scanSync(".", "ancestors-or-self", {
|
||||
accept: (path, stats) => stats.isFile() && host.vfs.stringComparer(vpath.basename(path), "tsconfig.json") === 0
|
||||
});
|
||||
}
|
||||
|
||||
if (project) {
|
||||
// TODO(rbuckton): Do we need to resolve this? Resolving breaks projects tests.
|
||||
// project = vpath.resolve(host.vfs.currentDirectory, project);
|
||||
|
||||
// read the config file
|
||||
const readResult = ts.readConfigFile(project, path => host.readFile(path));
|
||||
if (readResult.error) {
|
||||
return { file: project, errors: [readResult.error] };
|
||||
}
|
||||
|
||||
// parse the config file
|
||||
const config = ts.parseJsonConfigFileContent(readResult.config, host, vpath.dirname(project), existingOptions);
|
||||
return { file: project, errors: config.errors, config };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Correlates compilation inputs and outputs
|
||||
*/
|
||||
export interface CompilationOutput {
|
||||
readonly inputs: ReadonlyArray<documents.TextDocument>;
|
||||
readonly js: documents.TextDocument | undefined;
|
||||
readonly dts: documents.TextDocument | undefined;
|
||||
readonly map: documents.TextDocument | undefined;
|
||||
}
|
||||
|
||||
export class CompilationResult {
|
||||
public readonly host: fakes.CompilerHost;
|
||||
public readonly program: ts.Program | undefined;
|
||||
public readonly result: ts.EmitResult | undefined;
|
||||
public readonly options: ts.CompilerOptions;
|
||||
public readonly diagnostics: ReadonlyArray<ts.Diagnostic>;
|
||||
public readonly js: ReadonlyMap<string, documents.TextDocument>;
|
||||
public readonly dts: ReadonlyMap<string, documents.TextDocument>;
|
||||
public readonly maps: ReadonlyMap<string, documents.TextDocument>;
|
||||
|
||||
private _inputs: documents.TextDocument[] = [];
|
||||
private _inputsAndOutputs: collections.SortedMap<string, CompilationOutput>;
|
||||
|
||||
constructor(host: fakes.CompilerHost, options: ts.CompilerOptions, program: ts.Program | undefined, result: ts.EmitResult | undefined, diagnostics: ts.Diagnostic[]) {
|
||||
this.host = host;
|
||||
this.program = program;
|
||||
this.result = result;
|
||||
this.diagnostics = diagnostics;
|
||||
this.options = program ? program.getCompilerOptions() : options;
|
||||
|
||||
// collect outputs
|
||||
const js = this.js = new collections.SortedMap<string, documents.TextDocument>({ comparer: this.vfs.stringComparer, sort: "insertion" });
|
||||
const dts = this.dts = new collections.SortedMap<string, documents.TextDocument>({ comparer: this.vfs.stringComparer, sort: "insertion" });
|
||||
const maps = this.maps = new collections.SortedMap<string, documents.TextDocument>({ comparer: this.vfs.stringComparer, sort: "insertion" });
|
||||
for (const document of this.host.outputs) {
|
||||
if (vpath.isJavaScript(document.file)) {
|
||||
js.set(document.file, document);
|
||||
}
|
||||
else if (vpath.isDeclaration(document.file)) {
|
||||
dts.set(document.file, document);
|
||||
}
|
||||
else if (vpath.isSourceMap(document.file)) {
|
||||
maps.set(document.file, document);
|
||||
}
|
||||
}
|
||||
|
||||
// correlate inputs and outputs
|
||||
this._inputsAndOutputs = new collections.SortedMap<string, CompilationOutput>({ comparer: this.vfs.stringComparer, sort: "insertion" });
|
||||
if (program) {
|
||||
if (this.options.out || this.options.outFile) {
|
||||
const outFile = vpath.resolve(this.vfs.cwd(), this.options.outFile || this.options.out);
|
||||
const inputs: documents.TextDocument[] = [];
|
||||
for (const sourceFile of program.getSourceFiles()) {
|
||||
if (sourceFile) {
|
||||
const input = new documents.TextDocument(sourceFile.fileName, sourceFile.text);
|
||||
this._inputs.push(input);
|
||||
if (!vpath.isDeclaration(sourceFile.fileName)) {
|
||||
inputs.push(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const outputs: CompilationOutput = {
|
||||
inputs,
|
||||
js: js.get(outFile),
|
||||
dts: dts.get(vpath.changeExtension(outFile, ".d.ts")),
|
||||
map: maps.get(outFile + ".map")
|
||||
};
|
||||
|
||||
if (outputs.js) this._inputsAndOutputs.set(outputs.js.file, outputs);
|
||||
if (outputs.dts) this._inputsAndOutputs.set(outputs.dts.file, outputs);
|
||||
if (outputs.map) this._inputsAndOutputs.set(outputs.map.file, outputs);
|
||||
|
||||
for (const input of inputs) {
|
||||
this._inputsAndOutputs.set(input.file, outputs);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const sourceFile of program.getSourceFiles()) {
|
||||
if (sourceFile) {
|
||||
const input = new documents.TextDocument(sourceFile.fileName, sourceFile.text);
|
||||
this._inputs.push(input);
|
||||
if (!vpath.isDeclaration(sourceFile.fileName)) {
|
||||
const extname = ts.getOutputExtension(sourceFile, this.options);
|
||||
const outputs: CompilationOutput = {
|
||||
inputs: [input],
|
||||
js: js.get(this.getOutputPath(sourceFile.fileName, extname)),
|
||||
dts: dts.get(this.getOutputPath(sourceFile.fileName, ".d.ts")),
|
||||
map: maps.get(this.getOutputPath(sourceFile.fileName, extname + ".map"))
|
||||
};
|
||||
|
||||
this._inputsAndOutputs.set(sourceFile.fileName, outputs);
|
||||
if (outputs.js) this._inputsAndOutputs.set(outputs.js.file, outputs);
|
||||
if (outputs.dts) this._inputsAndOutputs.set(outputs.dts.file, outputs);
|
||||
if (outputs.map) this._inputsAndOutputs.set(outputs.map.file, outputs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.diagnostics = diagnostics;
|
||||
}
|
||||
|
||||
public get vfs(): vfs.FileSystem {
|
||||
return this.host.vfs;
|
||||
}
|
||||
|
||||
public get inputs(): ReadonlyArray<documents.TextDocument> {
|
||||
return this._inputs;
|
||||
}
|
||||
|
||||
public get outputs(): ReadonlyArray<documents.TextDocument> {
|
||||
return this.host.outputs;
|
||||
}
|
||||
|
||||
public get traces(): ReadonlyArray<string> {
|
||||
return this.host.traces;
|
||||
}
|
||||
|
||||
public get emitSkipped(): boolean {
|
||||
return this.result && this.result.emitSkipped || false;
|
||||
}
|
||||
|
||||
public get singleFile(): boolean {
|
||||
return !!this.options.outFile || !!this.options.out;
|
||||
}
|
||||
|
||||
public get commonSourceDirectory(): string {
|
||||
const common = this.program && this.program.getCommonSourceDirectory() || "";
|
||||
return common && vpath.combine(this.vfs.cwd(), common);
|
||||
}
|
||||
|
||||
public getInputsAndOutputs(path: string): CompilationOutput | undefined {
|
||||
return this._inputsAndOutputs.get(vpath.resolve(this.vfs.cwd(), path));
|
||||
}
|
||||
|
||||
public getInputs(path: string): ReadonlyArray<documents.TextDocument> | undefined {
|
||||
const outputs = this.getInputsAndOutputs(path);
|
||||
return outputs && outputs.inputs;
|
||||
}
|
||||
|
||||
public getOutput(path: string, kind: "js" | "dts" | "map"): documents.TextDocument | undefined {
|
||||
const outputs = this.getInputsAndOutputs(path);
|
||||
return outputs && outputs[kind];
|
||||
}
|
||||
|
||||
public getSourceMapRecord(): string | undefined {
|
||||
if (this.result.sourceMaps && this.result.sourceMaps.length > 0) {
|
||||
return Harness.SourceMapRecorder.getSourceMapRecord(this.result.sourceMaps, this.program, Array.from(this.js.values()), Array.from(this.dts.values()));
|
||||
}
|
||||
}
|
||||
|
||||
public getSourceMap(path: string): documents.SourceMap | undefined {
|
||||
if (this.options.noEmit || vpath.isDeclaration(path)) return undefined;
|
||||
if (this.options.inlineSourceMap) {
|
||||
const document = this.getOutput(path, "js");
|
||||
return document && documents.SourceMap.fromSource(document.text);
|
||||
}
|
||||
if (this.options.sourceMap) {
|
||||
const document = this.getOutput(path, "map");
|
||||
return document && new documents.SourceMap(document.file, document.text);
|
||||
}
|
||||
}
|
||||
|
||||
public getOutputPath(path: string, ext: string): string {
|
||||
if (this.options.outFile || this.options.out) {
|
||||
path = vpath.resolve(this.vfs.cwd(), this.options.outFile || this.options.out);
|
||||
}
|
||||
else {
|
||||
path = vpath.resolve(this.vfs.cwd(), path);
|
||||
const outDir = ext === ".d.ts" ? this.options.declarationDir || this.options.outDir : this.options.outDir;
|
||||
if (outDir) {
|
||||
const common = this.commonSourceDirectory;
|
||||
if (common) {
|
||||
path = vpath.relative(common, path, this.vfs.ignoreCase);
|
||||
path = vpath.combine(vpath.resolve(this.vfs.cwd(), this.options.outDir), path);
|
||||
}
|
||||
}
|
||||
}
|
||||
return vpath.changeExtension(path, ext);
|
||||
}
|
||||
}
|
||||
|
||||
export function compileFiles(host: fakes.CompilerHost, rootFiles: string[] | undefined, compilerOptions: ts.CompilerOptions): CompilationResult {
|
||||
if (compilerOptions.project || !rootFiles || rootFiles.length === 0) {
|
||||
const project = readProject(host.parseConfigHost, compilerOptions.project, compilerOptions);
|
||||
if (project) {
|
||||
if (project.errors && project.errors.length > 0) {
|
||||
return new CompilationResult(host, compilerOptions, /*program*/ undefined, /*result*/ undefined, project.errors);
|
||||
}
|
||||
if (project.config) {
|
||||
rootFiles = project.config.fileNames;
|
||||
compilerOptions = project.config.options;
|
||||
}
|
||||
}
|
||||
delete compilerOptions.project;
|
||||
}
|
||||
|
||||
// establish defaults (aligns with old harness)
|
||||
if (compilerOptions.target === undefined) compilerOptions.target = ts.ScriptTarget.ES3;
|
||||
if (compilerOptions.newLine === undefined) compilerOptions.newLine = ts.NewLineKind.CarriageReturnLineFeed;
|
||||
if (compilerOptions.skipDefaultLibCheck === undefined) compilerOptions.skipDefaultLibCheck = true;
|
||||
if (compilerOptions.noErrorTruncation === undefined) compilerOptions.noErrorTruncation = true;
|
||||
|
||||
const program = ts.createProgram(rootFiles || [], compilerOptions, host);
|
||||
const emitResult = program.emit();
|
||||
const errors = ts.getPreEmitDiagnostics(program);
|
||||
return new CompilationResult(host, compilerOptions, program, emitResult, errors);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,10 @@
|
|||
/// <reference path="harness.ts" />
|
||||
/// <reference path="runnerbase.ts" />
|
||||
/// <reference path="typeWriter.ts" />
|
||||
/// <reference path="./vpath.ts" />
|
||||
/// <reference path="./vfs.ts" />
|
||||
/// <reference path="./compiler.ts" />
|
||||
/// <reference path="./documents.ts" />
|
||||
|
||||
const enum CompilerTestType {
|
||||
Conformance,
|
||||
|
@ -41,151 +45,6 @@ class CompilerBaselineRunner extends RunnerBase {
|
|||
return this.enumerateFiles(this.basePath, /\.tsx?$/, { recursive: true });
|
||||
}
|
||||
|
||||
private makeUnitName(name: string, root: string) {
|
||||
const path = ts.toPath(name, root, (fileName) => Harness.Compiler.getCanonicalFileName(fileName));
|
||||
const pathStart = ts.toPath(Harness.IO.getCurrentDirectory(), "", (fileName) => Harness.Compiler.getCanonicalFileName(fileName));
|
||||
return pathStart ? path.replace(pathStart, "/") : path;
|
||||
}
|
||||
|
||||
public checkTestCodeOutput(fileName: string) {
|
||||
describe(`${this.testSuiteName} tests for ${fileName}`, () => {
|
||||
// Mocha holds onto the closure environment of the describe callback even after the test is done.
|
||||
// Everything declared here should be cleared out in the "after" callback.
|
||||
let justName: string;
|
||||
let lastUnit: Harness.TestCaseParser.TestUnitData;
|
||||
let harnessSettings: Harness.TestCaseParser.CompilerSettings;
|
||||
let hasNonDtsFiles: boolean;
|
||||
|
||||
let result: Harness.Compiler.CompilerResult;
|
||||
let options: ts.CompilerOptions;
|
||||
let tsConfigFiles: Harness.Compiler.TestFile[];
|
||||
// equivalent to the files that will be passed on the command line
|
||||
let toBeCompiled: Harness.Compiler.TestFile[];
|
||||
// equivalent to other files on the file system not directly passed to the compiler (ie things that are referenced by other files)
|
||||
let otherFiles: Harness.Compiler.TestFile[];
|
||||
|
||||
before(() => {
|
||||
justName = fileName.replace(/^.*[\\\/]/, ""); // strips the fileName from the path.
|
||||
const content = Harness.IO.readFile(fileName);
|
||||
const rootDir = fileName.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(fileName) + "/";
|
||||
const testCaseContent = Harness.TestCaseParser.makeUnitsFromTest(content, fileName, rootDir);
|
||||
const units = testCaseContent.testUnitData;
|
||||
harnessSettings = testCaseContent.settings;
|
||||
let tsConfigOptions: ts.CompilerOptions;
|
||||
tsConfigFiles = [];
|
||||
if (testCaseContent.tsConfig) {
|
||||
assert.equal(testCaseContent.tsConfig.fileNames.length, 0, `list of files in tsconfig is not currently supported`);
|
||||
|
||||
tsConfigOptions = ts.cloneCompilerOptions(testCaseContent.tsConfig.options);
|
||||
tsConfigFiles.push(this.createHarnessTestFile(testCaseContent.tsConfigFileUnitData, rootDir, ts.combinePaths(rootDir, tsConfigOptions.configFilePath)));
|
||||
}
|
||||
else {
|
||||
const baseUrl = harnessSettings.baseUrl;
|
||||
if (baseUrl !== undefined && !ts.isRootedDiskPath(baseUrl)) {
|
||||
harnessSettings.baseUrl = ts.getNormalizedAbsolutePath(baseUrl, rootDir);
|
||||
}
|
||||
}
|
||||
|
||||
lastUnit = units[units.length - 1];
|
||||
hasNonDtsFiles = ts.forEach(units, unit => !ts.fileExtensionIs(unit.name, ts.Extension.Dts));
|
||||
// We need to assemble the list of input files for the compiler and other related files on the 'filesystem' (ie in a multi-file test)
|
||||
// If the last file in a test uses require or a triple slash reference we'll assume all other files will be brought in via references,
|
||||
// otherwise, assume all files are just meant to be in the same compilation session without explicit references to one another.
|
||||
toBeCompiled = [];
|
||||
otherFiles = [];
|
||||
|
||||
if (testCaseContent.settings.noImplicitReferences || /require\(/.test(lastUnit.content) || /reference\spath/.test(lastUnit.content)) {
|
||||
toBeCompiled.push(this.createHarnessTestFile(lastUnit, rootDir));
|
||||
units.forEach(unit => {
|
||||
if (unit.name !== lastUnit.name) {
|
||||
otherFiles.push(this.createHarnessTestFile(unit, rootDir));
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
toBeCompiled = units.map(unit => {
|
||||
return this.createHarnessTestFile(unit, rootDir);
|
||||
});
|
||||
}
|
||||
|
||||
if (tsConfigOptions && tsConfigOptions.configFilePath !== undefined) {
|
||||
tsConfigOptions.configFilePath = ts.combinePaths(rootDir, tsConfigOptions.configFilePath);
|
||||
tsConfigOptions.configFile.fileName = tsConfigOptions.configFilePath;
|
||||
}
|
||||
|
||||
const output = Harness.Compiler.compileFiles(
|
||||
toBeCompiled, otherFiles, harnessSettings, /*options*/ tsConfigOptions, /*currentDirectory*/ harnessSettings.currentDirectory);
|
||||
|
||||
options = output.options;
|
||||
result = output.result;
|
||||
});
|
||||
|
||||
after(() => {
|
||||
// Mocha holds onto the closure environment of the describe callback even after the test is done.
|
||||
// Therefore we have to clean out large objects after the test is done.
|
||||
justName = undefined;
|
||||
lastUnit = undefined;
|
||||
hasNonDtsFiles = undefined;
|
||||
result = undefined;
|
||||
options = undefined;
|
||||
toBeCompiled = undefined;
|
||||
otherFiles = undefined;
|
||||
tsConfigFiles = undefined;
|
||||
});
|
||||
|
||||
// check errors
|
||||
it("Correct errors for " + fileName, () => {
|
||||
Harness.Compiler.doErrorBaseline(justName, tsConfigFiles.concat(toBeCompiled, otherFiles), result.errors, !!options.pretty);
|
||||
});
|
||||
|
||||
it (`Correct module resolution tracing for ${fileName}`, () => {
|
||||
if (options.traceResolution) {
|
||||
Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".trace.json"), () => {
|
||||
return JSON.stringify(result.traceResults || [], undefined, 4);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Source maps?
|
||||
it("Correct sourcemap content for " + fileName, () => {
|
||||
if (options.sourceMap || options.inlineSourceMap || options.declarationMap) {
|
||||
Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".sourcemap.txt"), () => {
|
||||
const record = result.getSourceMapRecord();
|
||||
if ((options.noEmitOnError && result.errors.length !== 0) || record === undefined) {
|
||||
// Because of the noEmitOnError option no files are created. We need to return null because baselining isn't required.
|
||||
/* tslint:disable:no-null-keyword */
|
||||
return null;
|
||||
/* tslint:enable:no-null-keyword */
|
||||
}
|
||||
return record;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("Correct JS output for " + fileName, () => {
|
||||
if (hasNonDtsFiles && this.emit) {
|
||||
Harness.Compiler.doJsEmitBaseline(justName, fileName, options, result, tsConfigFiles, toBeCompiled, otherFiles, harnessSettings);
|
||||
}
|
||||
});
|
||||
|
||||
it("Correct Sourcemap output for " + fileName, () => {
|
||||
Harness.Compiler.doSourcemapBaseline(justName, options, result, harnessSettings);
|
||||
});
|
||||
|
||||
it("Correct type/symbol baselines for " + fileName, () => {
|
||||
if (fileName.indexOf("APISample") >= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Harness.Compiler.doTypeAndSymbolBaseline(justName, result.program, toBeCompiled.concat(otherFiles).filter(file => !!result.program.getSourceFile(file.unitName)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private createHarnessTestFile(lastUnit: Harness.TestCaseParser.TestUnitData, rootDir: string, unitName?: string): Harness.Compiler.TestFile {
|
||||
return { unitName: unitName || this.makeUnitName(lastUnit.name, rootDir), content: lastUnit.content, fileOptions: lastUnit.fileOptions };
|
||||
}
|
||||
|
||||
public initializeTests() {
|
||||
describe(this.testSuiteName + " tests", () => {
|
||||
describe("Setup compiler for compiler baselines", () => {
|
||||
|
@ -193,18 +52,32 @@ class CompilerBaselineRunner extends RunnerBase {
|
|||
});
|
||||
|
||||
// this will set up a series of describe/it blocks to run between the setup and cleanup phases
|
||||
if (this.tests.length === 0) {
|
||||
const testFiles = this.enumerateTestFiles();
|
||||
testFiles.forEach(fn => {
|
||||
fn = fn.replace(/\\/g, "/");
|
||||
this.checkTestCodeOutput(fn);
|
||||
const files = this.tests.length > 0 ? this.tests : this.enumerateTestFiles();
|
||||
files.forEach(file => { this.checkTestCodeOutput(vpath.normalizeSeparators(file)); });
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.tests.forEach(test => this.checkTestCodeOutput(test));
|
||||
}
|
||||
|
||||
public checkTestCodeOutput(fileName: string) {
|
||||
for (const { name, payload } of CompilerTest.getConfigurations(fileName)) {
|
||||
describe(`${this.testSuiteName} tests for ${fileName}${name ? ` (${name})` : ``}`, () => {
|
||||
this.runSuite(fileName, payload);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private runSuite(fileName: string, testCaseContent: Harness.TestCaseParser.TestCaseContent) {
|
||||
// Mocha holds onto the closure environment of the describe callback even after the test is done.
|
||||
// Everything declared here should be cleared out in the "after" callback.
|
||||
let compilerTest: CompilerTest | undefined;
|
||||
before(() => { compilerTest = new CompilerTest(fileName, testCaseContent); });
|
||||
it(`Correct errors for ${fileName}`, () => { compilerTest.verifyDiagnostics(); });
|
||||
it(`Correct module resolution tracing for ${fileName}`, () => { compilerTest.verifyModuleResolution(); });
|
||||
it(`Correct sourcemap content for ${fileName}`, () => { compilerTest.verifySourceMapRecord(); });
|
||||
it(`Correct JS output for ${fileName}`, () => { if (this.emit) compilerTest.verifyJavaScriptOutput(); });
|
||||
it(`Correct Sourcemap output for ${fileName}`, () => { compilerTest.verifySourceMapOutput(); });
|
||||
it(`Correct type/symbol baselines for ${fileName}`, () => { compilerTest.verifyTypesAndSymbols(); });
|
||||
after(() => { compilerTest = undefined; });
|
||||
}
|
||||
|
||||
private parseOptions() {
|
||||
if (this.options && this.options.length > 0) {
|
||||
|
@ -223,3 +96,189 @@ class CompilerBaselineRunner extends RunnerBase {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface CompilerTestConfiguration {
|
||||
name: string;
|
||||
payload: Harness.TestCaseParser.TestCaseContent;
|
||||
}
|
||||
|
||||
class CompilerTest {
|
||||
private fileName: string;
|
||||
private justName: string;
|
||||
private lastUnit: Harness.TestCaseParser.TestUnitData;
|
||||
private harnessSettings: Harness.TestCaseParser.CompilerSettings;
|
||||
private hasNonDtsFiles: boolean;
|
||||
private result: compiler.CompilationResult;
|
||||
private options: ts.CompilerOptions;
|
||||
private tsConfigFiles: Harness.Compiler.TestFile[];
|
||||
// equivalent to the files that will be passed on the command line
|
||||
private toBeCompiled: Harness.Compiler.TestFile[];
|
||||
// equivalent to other files on the file system not directly passed to the compiler (ie things that are referenced by other files)
|
||||
private otherFiles: Harness.Compiler.TestFile[];
|
||||
|
||||
constructor(fileName: string, testCaseContent: Harness.TestCaseParser.TestCaseContent) {
|
||||
this.fileName = fileName;
|
||||
this.justName = vpath.basename(fileName);
|
||||
const rootDir = fileName.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(fileName) + "/";
|
||||
const units = testCaseContent.testUnitData;
|
||||
this.harnessSettings = testCaseContent.settings;
|
||||
let tsConfigOptions: ts.CompilerOptions;
|
||||
this.tsConfigFiles = [];
|
||||
if (testCaseContent.tsConfig) {
|
||||
assert.equal(testCaseContent.tsConfig.fileNames.length, 0, `list of files in tsconfig is not currently supported`);
|
||||
|
||||
tsConfigOptions = ts.cloneCompilerOptions(testCaseContent.tsConfig.options);
|
||||
this.tsConfigFiles.push(this.createHarnessTestFile(testCaseContent.tsConfigFileUnitData, rootDir, ts.combinePaths(rootDir, tsConfigOptions.configFilePath)));
|
||||
}
|
||||
else {
|
||||
const baseUrl = this.harnessSettings.baseUrl;
|
||||
if (baseUrl !== undefined && !ts.isRootedDiskPath(baseUrl)) {
|
||||
this.harnessSettings.baseUrl = ts.getNormalizedAbsolutePath(baseUrl, rootDir);
|
||||
}
|
||||
}
|
||||
|
||||
this.lastUnit = units[units.length - 1];
|
||||
this.hasNonDtsFiles = ts.forEach(units, unit => !ts.fileExtensionIs(unit.name, ts.Extension.Dts));
|
||||
// We need to assemble the list of input files for the compiler and other related files on the 'filesystem' (ie in a multi-file test)
|
||||
// If the last file in a test uses require or a triple slash reference we'll assume all other files will be brought in via references,
|
||||
// otherwise, assume all files are just meant to be in the same compilation session without explicit references to one another.
|
||||
this.toBeCompiled = [];
|
||||
this.otherFiles = [];
|
||||
|
||||
if (testCaseContent.settings.noImplicitReferences || /require\(/.test(this.lastUnit.content) || /reference\spath/.test(this.lastUnit.content)) {
|
||||
this.toBeCompiled.push(this.createHarnessTestFile(this.lastUnit, rootDir));
|
||||
units.forEach(unit => {
|
||||
if (unit.name !== this.lastUnit.name) {
|
||||
this.otherFiles.push(this.createHarnessTestFile(unit, rootDir));
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.toBeCompiled = units.map(unit => {
|
||||
return this.createHarnessTestFile(unit, rootDir);
|
||||
});
|
||||
}
|
||||
|
||||
if (tsConfigOptions && tsConfigOptions.configFilePath !== undefined) {
|
||||
tsConfigOptions.configFilePath = ts.combinePaths(rootDir, tsConfigOptions.configFilePath);
|
||||
tsConfigOptions.configFile.fileName = tsConfigOptions.configFilePath;
|
||||
}
|
||||
|
||||
this.result = Harness.Compiler.compileFiles(
|
||||
this.toBeCompiled,
|
||||
this.otherFiles,
|
||||
this.harnessSettings,
|
||||
/*options*/ tsConfigOptions,
|
||||
/*currentDirectory*/ this.harnessSettings.currentDirectory);
|
||||
|
||||
this.options = this.result.options;
|
||||
}
|
||||
|
||||
public static getConfigurations(fileName: string) {
|
||||
const content = Harness.IO.readFile(fileName);
|
||||
const rootDir = fileName.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(fileName) + "/";
|
||||
const testCaseContent = Harness.TestCaseParser.makeUnitsFromTest(content, fileName, rootDir);
|
||||
const configurations: CompilerTestConfiguration[] = [];
|
||||
const scriptTargets = this._split(testCaseContent.settings.target);
|
||||
const moduleKinds = this._split(testCaseContent.settings.module);
|
||||
for (const scriptTarget of scriptTargets) {
|
||||
for (const moduleKind of moduleKinds) {
|
||||
let name = "";
|
||||
if (moduleKinds.length > 1) {
|
||||
name += `@module: ${moduleKind || "none"}`;
|
||||
}
|
||||
if (scriptTargets.length > 1) {
|
||||
if (name) name += ", ";
|
||||
name += `@target: ${scriptTarget || "none"}`;
|
||||
}
|
||||
|
||||
const settings = { ...testCaseContent.settings };
|
||||
if (scriptTarget) settings.target = scriptTarget;
|
||||
if (moduleKind) settings.module = moduleKind;
|
||||
configurations.push({ name, payload: { ...testCaseContent, settings } });
|
||||
}
|
||||
}
|
||||
|
||||
return configurations;
|
||||
}
|
||||
|
||||
public verifyDiagnostics() {
|
||||
// check errors
|
||||
Harness.Compiler.doErrorBaseline(
|
||||
this.justName,
|
||||
this.tsConfigFiles.concat(this.toBeCompiled, this.otherFiles),
|
||||
this.result.diagnostics,
|
||||
!!this.options.pretty);
|
||||
}
|
||||
|
||||
public verifyModuleResolution() {
|
||||
if (this.options.traceResolution) {
|
||||
Harness.Baseline.runBaseline(this.justName.replace(/\.tsx?$/, ".trace.json"), () => {
|
||||
return utils.removeTestPathPrefixes(JSON.stringify(this.result.traces, undefined, 4));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public verifySourceMapRecord() {
|
||||
if (this.options.sourceMap || this.options.inlineSourceMap || this.options.declarationMap) {
|
||||
Harness.Baseline.runBaseline(this.justName.replace(/\.tsx?$/, ".sourcemap.txt"), () => {
|
||||
const record = utils.removeTestPathPrefixes(this.result.getSourceMapRecord());
|
||||
if ((this.options.noEmitOnError && this.result.diagnostics.length !== 0) || record === undefined) {
|
||||
// Because of the noEmitOnError option no files are created. We need to return null because baselining isn't required.
|
||||
/* tslint:disable:no-null-keyword */
|
||||
return null;
|
||||
/* tslint:enable:no-null-keyword */
|
||||
}
|
||||
return record;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public verifyJavaScriptOutput() {
|
||||
if (this.hasNonDtsFiles) {
|
||||
Harness.Compiler.doJsEmitBaseline(
|
||||
this.justName,
|
||||
this.fileName,
|
||||
this.options,
|
||||
this.result,
|
||||
this.tsConfigFiles,
|
||||
this.toBeCompiled,
|
||||
this.otherFiles,
|
||||
this.harnessSettings);
|
||||
}
|
||||
}
|
||||
|
||||
public verifySourceMapOutput() {
|
||||
Harness.Compiler.doSourcemapBaseline(
|
||||
this.justName,
|
||||
this.options,
|
||||
this.result,
|
||||
this.harnessSettings);
|
||||
}
|
||||
|
||||
public verifyTypesAndSymbols() {
|
||||
if (this.fileName.indexOf("APISample") >= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Harness.Compiler.doTypeAndSymbolBaseline(
|
||||
this.justName,
|
||||
this.result.program,
|
||||
this.toBeCompiled.concat(this.otherFiles).filter(file => !!this.result.program.getSourceFile(file.unitName)));
|
||||
}
|
||||
|
||||
private static _split(text: string) {
|
||||
const entries = text && text.split(",").map(s => s.toLowerCase().trim()).filter(s => s.length > 0);
|
||||
return entries && entries.length > 0 ? entries : [""];
|
||||
}
|
||||
|
||||
private makeUnitName(name: string, root: string) {
|
||||
const path = ts.toPath(name, root, ts.identity);
|
||||
const pathStart = ts.toPath(Harness.IO.getCurrentDirectory(), "", ts.identity);
|
||||
return pathStart ? path.replace(pathStart, "/") : path;
|
||||
}
|
||||
|
||||
private createHarnessTestFile(lastUnit: Harness.TestCaseParser.TestUnitData, rootDir: string, unitName?: string): Harness.Compiler.TestFile {
|
||||
return { unitName: unitName || this.makeUnitName(lastUnit.name, rootDir), content: lastUnit.content, fileOptions: lastUnit.fileOptions };
|
||||
}
|
||||
}
|
187
src/harness/documents.ts
Normal file
187
src/harness/documents.ts
Normal file
|
@ -0,0 +1,187 @@
|
|||
// NOTE: The contents of this file are all exported from the namespace 'documents'. This is to
|
||||
// support the eventual conversion of harness into a modular system.
|
||||
|
||||
namespace documents {
|
||||
export class TextDocument {
|
||||
public readonly meta: Map<string, string>;
|
||||
public readonly file: string;
|
||||
public readonly text: string;
|
||||
|
||||
private _lineStarts: ReadonlyArray<number> | undefined;
|
||||
private _testFile: Harness.Compiler.TestFile | undefined;
|
||||
|
||||
constructor(file: string, text: string, meta?: Map<string, string>) {
|
||||
this.file = file;
|
||||
this.text = text;
|
||||
this.meta = meta || new Map<string, string>();
|
||||
}
|
||||
|
||||
public get lineStarts(): ReadonlyArray<number> {
|
||||
return this._lineStarts || (this._lineStarts = ts.computeLineStarts(this.text));
|
||||
}
|
||||
|
||||
public static fromTestFile(file: Harness.Compiler.TestFile) {
|
||||
return new TextDocument(
|
||||
file.unitName,
|
||||
file.content,
|
||||
file.fileOptions && Object.keys(file.fileOptions)
|
||||
.reduce((meta, key) => meta.set(key, file.fileOptions[key]), new Map<string, string>()));
|
||||
}
|
||||
|
||||
public asTestFile() {
|
||||
return this._testFile || (this._testFile = {
|
||||
unitName: this.file,
|
||||
content: this.text,
|
||||
fileOptions: Array.from(this.meta)
|
||||
.reduce((obj, [key, value]) => (obj[key] = value, obj), {} as Record<string, string>)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export interface RawSourceMap {
|
||||
version: number;
|
||||
file: string;
|
||||
sourceRoot?: string;
|
||||
sources: string[];
|
||||
sourcesContent?: string[];
|
||||
names: string[];
|
||||
mappings: string;
|
||||
}
|
||||
|
||||
export interface Mapping {
|
||||
mappingIndex: number;
|
||||
emittedLine: number;
|
||||
emittedColumn: number;
|
||||
sourceIndex: number;
|
||||
sourceLine: number;
|
||||
sourceColumn: number;
|
||||
nameIndex?: number;
|
||||
}
|
||||
|
||||
export class SourceMap {
|
||||
public readonly raw: RawSourceMap;
|
||||
public readonly mapFile: string | undefined;
|
||||
public readonly version: number;
|
||||
public readonly file: string;
|
||||
public readonly sourceRoot: string | undefined;
|
||||
public readonly sources: ReadonlyArray<string> = [];
|
||||
public readonly sourcesContent: ReadonlyArray<string> | undefined;
|
||||
public readonly mappings: ReadonlyArray<Mapping> = [];
|
||||
public readonly names: ReadonlyArray<string> | undefined;
|
||||
|
||||
private static readonly _mappingRegExp = /([A-Za-z0-9+/]+),?|(;)|./g;
|
||||
private static readonly _sourceMappingURLRegExp = /^\/\/[#@]\s*sourceMappingURL\s*=\s*(.*?)\s*$/mig;
|
||||
private static readonly _dataURLRegExp = /^data:application\/json;base64,([a-z0-9+/=]+)$/i;
|
||||
private static readonly _base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
private _emittedLineMappings: Mapping[][] = [];
|
||||
private _sourceLineMappings: Mapping[][][] = [];
|
||||
|
||||
constructor(mapFile: string | undefined, data: string | RawSourceMap) {
|
||||
this.raw = typeof data === "string" ? JSON.parse(data) as RawSourceMap : data;
|
||||
this.mapFile = mapFile;
|
||||
this.version = this.raw.version;
|
||||
this.file = this.raw.file;
|
||||
this.sourceRoot = this.raw.sourceRoot;
|
||||
this.sources = this.raw.sources;
|
||||
this.sourcesContent = this.raw.sourcesContent;
|
||||
this.names = this.raw.names;
|
||||
|
||||
// populate mappings
|
||||
const mappings: Mapping[] = [];
|
||||
let emittedLine = 0;
|
||||
let emittedColumn = 0;
|
||||
let sourceIndex = 0;
|
||||
let sourceLine = 0;
|
||||
let sourceColumn = 0;
|
||||
let nameIndex = 0;
|
||||
let match: RegExpExecArray | null;
|
||||
while (match = SourceMap._mappingRegExp.exec(this.raw.mappings)) {
|
||||
if (match[1]) {
|
||||
const segment = SourceMap._decodeVLQ(match[1]);
|
||||
if (segment.length !== 1 && segment.length !== 4 && segment.length !== 5) {
|
||||
throw new Error("Invalid VLQ");
|
||||
}
|
||||
|
||||
emittedColumn += segment[0];
|
||||
if (segment.length >= 4) {
|
||||
sourceIndex += segment[1];
|
||||
sourceLine += segment[2];
|
||||
sourceColumn += segment[3];
|
||||
}
|
||||
|
||||
const mapping: Mapping = { mappingIndex: mappings.length, emittedLine, emittedColumn, sourceIndex, sourceLine, sourceColumn };
|
||||
if (segment.length === 5) {
|
||||
nameIndex += segment[4];
|
||||
mapping.nameIndex = nameIndex;
|
||||
}
|
||||
|
||||
mappings.push(mapping);
|
||||
|
||||
const mappingsForEmittedLine = this._emittedLineMappings[mapping.emittedLine] || (this._emittedLineMappings[mapping.emittedLine] = []);
|
||||
mappingsForEmittedLine.push(mapping);
|
||||
|
||||
const mappingsForSource = this._sourceLineMappings[mapping.sourceIndex] || (this._sourceLineMappings[mapping.sourceIndex] = []);
|
||||
const mappingsForSourceLine = mappingsForSource[mapping.sourceLine] || (mappingsForSource[mapping.sourceLine] = []);
|
||||
mappingsForSourceLine.push(mapping);
|
||||
}
|
||||
else if (match[2]) {
|
||||
emittedLine++;
|
||||
emittedColumn = 0;
|
||||
}
|
||||
else {
|
||||
throw new Error(`Unrecognized character '${match[0]}'.`);
|
||||
}
|
||||
}
|
||||
|
||||
this.mappings = mappings;
|
||||
}
|
||||
|
||||
public static getUrl(text: string) {
|
||||
let match: RegExpExecArray | null;
|
||||
let lastMatch: RegExpExecArray | undefined;
|
||||
while (match = SourceMap._sourceMappingURLRegExp.exec(text)) {
|
||||
lastMatch = match;
|
||||
}
|
||||
return lastMatch ? lastMatch[1] : undefined;
|
||||
}
|
||||
|
||||
public static fromUrl(url: string) {
|
||||
const match = SourceMap._dataURLRegExp.exec(url);
|
||||
return match ? new SourceMap(/*mapFile*/ undefined, new Buffer(match[1], "base64").toString("utf8")) : undefined;
|
||||
}
|
||||
|
||||
public static fromSource(text: string) {
|
||||
const url = this.getUrl(text);
|
||||
return url && this.fromUrl(url);
|
||||
}
|
||||
|
||||
public getMappingsForEmittedLine(emittedLine: number): ReadonlyArray<Mapping> | undefined {
|
||||
return this._emittedLineMappings[emittedLine];
|
||||
}
|
||||
|
||||
public getMappingsForSourceLine(sourceIndex: number, sourceLine: number): ReadonlyArray<Mapping> | undefined {
|
||||
const mappingsForSource = this._sourceLineMappings[sourceIndex];
|
||||
return mappingsForSource && mappingsForSource[sourceLine];
|
||||
}
|
||||
|
||||
private static _decodeVLQ(text: string): number[] {
|
||||
const vlq: number[] = [];
|
||||
let shift = 0;
|
||||
let value = 0;
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const currentByte = SourceMap._base64Chars.indexOf(text.charAt(i));
|
||||
value += (currentByte & 31) << shift;
|
||||
if ((currentByte & 32) === 0) {
|
||||
vlq.push(value & 1 ? -(value >>> 1) : value >>> 1);
|
||||
shift = 0;
|
||||
value = 0;
|
||||
}
|
||||
else {
|
||||
shift += 5;
|
||||
}
|
||||
}
|
||||
return vlq;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -41,7 +41,7 @@ abstract class ExternalCompileRunnerBase extends RunnerBase {
|
|||
const cp = require("child_process");
|
||||
|
||||
it("should build successfully", () => {
|
||||
let cwd = path.join(__dirname, "../../", cls.testDir, directoryName);
|
||||
let cwd = path.join(Harness.IO.getWorkspaceRoot(), cls.testDir, directoryName);
|
||||
const stdio = isWorker ? "pipe" : "inherit";
|
||||
let types: string[];
|
||||
if (fs.existsSync(path.join(cwd, "test.json"))) {
|
||||
|
@ -69,7 +69,7 @@ abstract class ExternalCompileRunnerBase extends RunnerBase {
|
|||
const install = cp.spawnSync(`npm`, ["i", "--ignore-scripts"], { cwd, timeout: timeout / 2, shell: true, stdio }); // NPM shouldn't take the entire timeout - if it takes a long time, it should be terminated and we should log the failure
|
||||
if (install.status !== 0) throw new Error(`NPM Install for ${directoryName} failed: ${install.stderr.toString()}`);
|
||||
}
|
||||
const args = [path.join(__dirname, "tsc.js")];
|
||||
const args = [path.join(Harness.IO.getWorkspaceRoot(), "built/local/tsc.js")];
|
||||
if (types) {
|
||||
args.push("--types", types.join(","));
|
||||
}
|
||||
|
|
376
src/harness/fakes.ts
Normal file
376
src/harness/fakes.ts
Normal file
|
@ -0,0 +1,376 @@
|
|||
/**
|
||||
* Fake implementations of various compiler dependencies.
|
||||
*/
|
||||
namespace fakes {
|
||||
const processExitSentinel = new Error("System exit");
|
||||
|
||||
export interface SystemOptions {
|
||||
executingFilePath?: string;
|
||||
newLine?: "\r\n" | "\n";
|
||||
env?: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A fake `ts.System` that leverages a virtual file system.
|
||||
*/
|
||||
export class System implements ts.System {
|
||||
public readonly vfs: vfs.FileSystem;
|
||||
public readonly args: string[] = [];
|
||||
public readonly output: string[] = [];
|
||||
public readonly newLine: string;
|
||||
public readonly useCaseSensitiveFileNames: boolean;
|
||||
public exitCode: number;
|
||||
|
||||
private readonly _executingFilePath: string | undefined;
|
||||
private readonly _env: Record<string, string> | undefined;
|
||||
|
||||
constructor(vfs: vfs.FileSystem, { executingFilePath, newLine = "\r\n", env }: SystemOptions = {}) {
|
||||
this.vfs = vfs.isReadonly ? vfs.shadow() : vfs;
|
||||
this.useCaseSensitiveFileNames = !this.vfs.ignoreCase;
|
||||
this.newLine = newLine;
|
||||
this._executingFilePath = executingFilePath;
|
||||
this._env = env;
|
||||
}
|
||||
|
||||
public write(message: string) {
|
||||
this.output.push(message);
|
||||
}
|
||||
|
||||
public readFile(path: string) {
|
||||
try {
|
||||
const content = this.vfs.readFileSync(path, "utf8");
|
||||
return content === undefined ? undefined :
|
||||
vpath.extname(path) === ".json" ? utils.removeComments(utils.removeByteOrderMark(content), utils.CommentRemoval.leadingAndTrailing) :
|
||||
utils.removeByteOrderMark(content);
|
||||
}
|
||||
catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public writeFile(path: string, data: string, writeByteOrderMark?: boolean): void {
|
||||
this.vfs.mkdirpSync(vpath.dirname(path));
|
||||
this.vfs.writeFileSync(path, writeByteOrderMark ? utils.addUTF8ByteOrderMark(data) : data);
|
||||
}
|
||||
|
||||
public fileExists(path: string) {
|
||||
const stats = this._getStats(path);
|
||||
return stats ? stats.isFile() : false;
|
||||
}
|
||||
|
||||
public directoryExists(path: string) {
|
||||
const stats = this._getStats(path);
|
||||
return stats ? stats.isDirectory() : false;
|
||||
}
|
||||
|
||||
public createDirectory(path: string): void {
|
||||
this.vfs.mkdirpSync(path);
|
||||
}
|
||||
|
||||
public getCurrentDirectory() {
|
||||
return this.vfs.cwd();
|
||||
}
|
||||
|
||||
public getDirectories(path: string) {
|
||||
const result: string[] = [];
|
||||
try {
|
||||
for (const file of this.vfs.readdirSync(path)) {
|
||||
if (this.vfs.statSync(vpath.combine(path, file)).isDirectory()) {
|
||||
result.push(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { /*ignore*/ }
|
||||
return result;
|
||||
}
|
||||
|
||||
public readDirectory(path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[] {
|
||||
return ts.matchFiles(path, extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, path => this.getAccessibleFileSystemEntries(path));
|
||||
}
|
||||
|
||||
public getAccessibleFileSystemEntries(path: string): ts.FileSystemEntries {
|
||||
const files: string[] = [];
|
||||
const directories: string[] = [];
|
||||
try {
|
||||
for (const file of this.vfs.readdirSync(path)) {
|
||||
try {
|
||||
const stats = this.vfs.statSync(vpath.combine(path, file));
|
||||
if (stats.isFile()) {
|
||||
files.push(file);
|
||||
}
|
||||
else if (stats.isDirectory()) {
|
||||
directories.push(file);
|
||||
}
|
||||
}
|
||||
catch { /*ignored*/ }
|
||||
}
|
||||
}
|
||||
catch { /*ignored*/ }
|
||||
return { files, directories };
|
||||
}
|
||||
|
||||
public exit(exitCode?: number) {
|
||||
this.exitCode = exitCode;
|
||||
throw processExitSentinel;
|
||||
}
|
||||
|
||||
public getFileSize(path: string) {
|
||||
const stats = this._getStats(path);
|
||||
return stats && stats.isFile() ? stats.size : 0;
|
||||
}
|
||||
|
||||
public resolvePath(path: string) {
|
||||
return vpath.resolve(this.vfs.cwd(), path);
|
||||
}
|
||||
|
||||
public getExecutingFilePath() {
|
||||
if (this._executingFilePath === undefined) return ts.notImplemented();
|
||||
return this._executingFilePath;
|
||||
}
|
||||
|
||||
public getModifiedTime(path: string) {
|
||||
const stats = this._getStats(path);
|
||||
return stats ? stats.mtime : undefined;
|
||||
}
|
||||
|
||||
public createHash(data: string): string {
|
||||
return data;
|
||||
}
|
||||
|
||||
public realpath(path: string) {
|
||||
try {
|
||||
return this.vfs.realpathSync(path);
|
||||
}
|
||||
catch {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
public getEnvironmentVariable(name: string): string | undefined {
|
||||
return this._env && this._env[name];
|
||||
}
|
||||
|
||||
private _getStats(path: string) {
|
||||
try {
|
||||
return this.vfs.statSync(path);
|
||||
}
|
||||
catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A fake `ts.ParseConfigHost` that leverages a virtual file system.
|
||||
*/
|
||||
export class ParseConfigHost implements ts.ParseConfigHost {
|
||||
public readonly sys: System;
|
||||
|
||||
constructor(sys: System | vfs.FileSystem) {
|
||||
if (sys instanceof vfs.FileSystem) sys = new System(sys);
|
||||
this.sys = sys;
|
||||
}
|
||||
|
||||
public get vfs() {
|
||||
return this.sys.vfs;
|
||||
}
|
||||
|
||||
public get useCaseSensitiveFileNames() {
|
||||
return this.sys.useCaseSensitiveFileNames;
|
||||
}
|
||||
|
||||
public fileExists(fileName: string): boolean {
|
||||
return this.sys.fileExists(fileName);
|
||||
}
|
||||
|
||||
public directoryExists(directoryName: string): boolean {
|
||||
return this.sys.directoryExists(directoryName);
|
||||
}
|
||||
|
||||
public readFile(path: string): string | undefined {
|
||||
return this.sys.readFile(path);
|
||||
}
|
||||
|
||||
public readDirectory(path: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] {
|
||||
return this.sys.readDirectory(path, extensions, excludes, includes, depth);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A fake `ts.CompilerHost` that leverages a virtual file system.
|
||||
*/
|
||||
export class CompilerHost implements ts.CompilerHost {
|
||||
public readonly sys: System;
|
||||
public readonly defaultLibLocation: string;
|
||||
public readonly outputs: documents.TextDocument[] = [];
|
||||
public readonly traces: string[] = [];
|
||||
public readonly shouldAssertInvariants = !Harness.lightMode;
|
||||
|
||||
private _setParentNodes: boolean;
|
||||
private _sourceFiles: collections.SortedMap<string, ts.SourceFile>;
|
||||
private _parseConfigHost: ParseConfigHost;
|
||||
private _newLine: string;
|
||||
|
||||
constructor(sys: System | vfs.FileSystem, options = ts.getDefaultCompilerOptions(), setParentNodes = false) {
|
||||
if (sys instanceof vfs.FileSystem) sys = new System(sys);
|
||||
this.sys = sys;
|
||||
this.defaultLibLocation = sys.vfs.meta.get("defaultLibLocation") || "";
|
||||
this._newLine = ts.getNewLineCharacter(options, () => this.sys.newLine);
|
||||
this._sourceFiles = new collections.SortedMap<string, ts.SourceFile>({ comparer: sys.vfs.stringComparer, sort: "insertion" });
|
||||
this._setParentNodes = setParentNodes;
|
||||
}
|
||||
|
||||
public get vfs() {
|
||||
return this.sys.vfs;
|
||||
}
|
||||
|
||||
public get parseConfigHost() {
|
||||
return this._parseConfigHost || (this._parseConfigHost = new ParseConfigHost(this.sys));
|
||||
}
|
||||
|
||||
public getCurrentDirectory(): string {
|
||||
return this.sys.getCurrentDirectory();
|
||||
}
|
||||
|
||||
public useCaseSensitiveFileNames(): boolean {
|
||||
return this.sys.useCaseSensitiveFileNames;
|
||||
}
|
||||
|
||||
public getNewLine(): string {
|
||||
return this._newLine;
|
||||
}
|
||||
|
||||
public getCanonicalFileName(fileName: string): string {
|
||||
return this.sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
|
||||
}
|
||||
|
||||
public fileExists(fileName: string): boolean {
|
||||
return this.sys.fileExists(fileName);
|
||||
}
|
||||
|
||||
public directoryExists(directoryName: string): boolean {
|
||||
return this.sys.directoryExists(directoryName);
|
||||
}
|
||||
|
||||
public getDirectories(path: string): string[] {
|
||||
return this.sys.getDirectories(path);
|
||||
}
|
||||
|
||||
public readFile(path: string): string | undefined {
|
||||
return this.sys.readFile(path);
|
||||
}
|
||||
|
||||
public writeFile(fileName: string, content: string, writeByteOrderMark: boolean) {
|
||||
if (writeByteOrderMark) content = utils.addUTF8ByteOrderMark(content);
|
||||
this.sys.writeFile(fileName, content);
|
||||
|
||||
const document = new documents.TextDocument(fileName, content);
|
||||
document.meta.set("fileName", fileName);
|
||||
this.vfs.filemeta(fileName).set("document", document);
|
||||
const index = this.outputs.findIndex(output => this.vfs.stringComparer(document.file, output.file) === 0);
|
||||
if (index < 0) {
|
||||
this.outputs.push(document);
|
||||
}
|
||||
else {
|
||||
this.outputs[index] = document;
|
||||
}
|
||||
}
|
||||
|
||||
public trace(s: string): void {
|
||||
this.traces.push(s);
|
||||
}
|
||||
|
||||
public realpath(path: string): string {
|
||||
return this.sys.realpath(path);
|
||||
}
|
||||
|
||||
public getDefaultLibLocation(): string {
|
||||
return vpath.resolve(this.getCurrentDirectory(), this.defaultLibLocation);
|
||||
}
|
||||
|
||||
public getDefaultLibFileName(options: ts.CompilerOptions): string {
|
||||
// return vpath.resolve(this.getDefaultLibLocation(), ts.getDefaultLibFileName(options));
|
||||
|
||||
// TODO(rbuckton): This patches the baseline to replace lib.es5.d.ts with lib.d.ts.
|
||||
// This is only to make the PR for this change easier to read. A follow-up PR will
|
||||
// revert this change and accept the new baselines.
|
||||
// See https://github.com/Microsoft/TypeScript/pull/20763#issuecomment-352553264
|
||||
return vpath.resolve(this.getDefaultLibLocation(), getDefaultLibFileName(options));
|
||||
function getDefaultLibFileName(options: ts.CompilerOptions) {
|
||||
switch (options.target) {
|
||||
case ts.ScriptTarget.ESNext:
|
||||
case ts.ScriptTarget.ES2017:
|
||||
return "lib.es2017.d.ts";
|
||||
case ts.ScriptTarget.ES2016:
|
||||
return "lib.es2016.d.ts";
|
||||
case ts.ScriptTarget.ES2015:
|
||||
return "lib.es2015.d.ts";
|
||||
|
||||
default:
|
||||
return "lib.d.ts";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getSourceFile(fileName: string, languageVersion: number): ts.SourceFile | undefined {
|
||||
const canonicalFileName = this.getCanonicalFileName(vpath.resolve(this.getCurrentDirectory(), fileName));
|
||||
const existing = this._sourceFiles.get(canonicalFileName);
|
||||
if (existing) return existing;
|
||||
|
||||
const content = this.readFile(canonicalFileName);
|
||||
if (content === undefined) return undefined;
|
||||
|
||||
// A virtual file system may shadow another existing virtual file system. This
|
||||
// allows us to reuse a common virtual file system structure across multiple
|
||||
// tests. If a virtual file is a shadow, it is likely that the file will be
|
||||
// reused across multiple tests. In that case, we cache the SourceFile we parse
|
||||
// so that it can be reused across multiple tests to avoid the cost of
|
||||
// repeatedly parsing the same file over and over (such as lib.d.ts).
|
||||
const cacheKey = this.vfs.shadowRoot && `SourceFile[languageVersion=${languageVersion},setParentNodes=${this._setParentNodes}]`;
|
||||
if (cacheKey) {
|
||||
const meta = this.vfs.filemeta(canonicalFileName);
|
||||
const sourceFileFromMetadata = meta.get(cacheKey) as ts.SourceFile | undefined;
|
||||
if (sourceFileFromMetadata) {
|
||||
this._sourceFiles.set(canonicalFileName, sourceFileFromMetadata);
|
||||
return sourceFileFromMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
const parsed = ts.createSourceFile(fileName, content, languageVersion, this._setParentNodes || this.shouldAssertInvariants);
|
||||
if (this.shouldAssertInvariants) {
|
||||
Utils.assertInvariants(parsed, /*parent*/ undefined);
|
||||
}
|
||||
|
||||
this._sourceFiles.set(canonicalFileName, parsed);
|
||||
|
||||
if (cacheKey) {
|
||||
// store the cached source file on the unshadowed file with the same version.
|
||||
const stats = this.vfs.statSync(canonicalFileName);
|
||||
|
||||
let fs = this.vfs;
|
||||
while (fs.shadowRoot) {
|
||||
try {
|
||||
const shadowRootStats = fs.shadowRoot.statSync(canonicalFileName);
|
||||
if (shadowRootStats.dev !== stats.dev ||
|
||||
shadowRootStats.ino !== stats.ino ||
|
||||
shadowRootStats.mtimeMs !== stats.mtimeMs) {
|
||||
break;
|
||||
}
|
||||
|
||||
fs = fs.shadowRoot;
|
||||
}
|
||||
catch {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fs !== this.vfs) {
|
||||
fs.filemeta(canonicalFileName).set(cacheKey, parsed);
|
||||
}
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
/// <reference path="harnessLanguageService.ts" />
|
||||
/// <reference path="harness.ts" />
|
||||
/// <reference path="fourslashRunner.ts" />
|
||||
/// <reference path="./compiler.ts" />
|
||||
|
||||
namespace FourSlash {
|
||||
ts.disableIncrementalParsing = false;
|
||||
|
@ -277,7 +278,13 @@ namespace FourSlash {
|
|||
|
||||
if (configFileName) {
|
||||
const baseDir = ts.normalizePath(ts.getDirectoryPath(configFileName));
|
||||
const host = new Utils.MockParseConfigHost(baseDir, /*ignoreCase*/ false, this.inputFiles);
|
||||
const files: vfs.FileSet = { [baseDir]: {} };
|
||||
this.inputFiles.forEach((data, path) => {
|
||||
const scriptInfo = new Harness.LanguageService.ScriptInfo(path, undefined, /*isRootFile*/ false);
|
||||
files[path] = new vfs.File(data, { meta: { scriptInfo } });
|
||||
});
|
||||
const fs = new vfs.FileSystem(/*ignoreCase*/ true, { cwd: baseDir, files });
|
||||
const host = new fakes.ParseConfigHost(fs);
|
||||
const jsonSourceFile = ts.parseJsonText(configFileName, this.inputFiles.get(configFileName));
|
||||
compilationOptions = ts.parseJsonSourceFileConfigFileContent(jsonSourceFile, host, baseDir, compilationOptions, configFileName).options;
|
||||
}
|
||||
|
@ -333,7 +340,10 @@ namespace FourSlash {
|
|||
}
|
||||
|
||||
for (const file of testData.files) {
|
||||
ts.forEach(file.symlinks, link => this.languageServiceAdapterHost.addSymlink(link, file.fileName));
|
||||
ts.forEach(file.symlinks, link => {
|
||||
this.languageServiceAdapterHost.vfs.mkdirpSync(vpath.dirname(link));
|
||||
this.languageServiceAdapterHost.vfs.symlinkSync(file.fileName, link);
|
||||
});
|
||||
}
|
||||
|
||||
this.formatCodeSettings = {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -117,11 +117,17 @@ namespace Harness.LanguageService {
|
|||
}
|
||||
|
||||
export abstract class LanguageServiceAdapterHost {
|
||||
public readonly sys = new fakes.System(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: virtualFileSystemRoot }));
|
||||
public typesRegistry: ts.Map<void> | undefined;
|
||||
protected virtualFileSystem: Utils.VirtualFileSystem = new Utils.VirtualFileSystem(virtualFileSystemRoot, /*useCaseSensitiveFilenames*/false);
|
||||
private scriptInfos: collections.SortedMap<string, ScriptInfo>;
|
||||
|
||||
constructor(protected cancellationToken = DefaultHostCancellationToken.instance,
|
||||
protected settings = ts.getDefaultCompilerOptions()) {
|
||||
this.scriptInfos = new collections.SortedMap({ comparer: this.vfs.stringComparer, sort: "insertion" });
|
||||
}
|
||||
|
||||
public get vfs() {
|
||||
return this.sys.vfs;
|
||||
}
|
||||
|
||||
public getNewLine(): string {
|
||||
|
@ -130,38 +136,38 @@ namespace Harness.LanguageService {
|
|||
|
||||
public getFilenames(): string[] {
|
||||
const fileNames: string[] = [];
|
||||
for (const virtualEntry of this.virtualFileSystem.getAllFileEntries()) {
|
||||
const scriptInfo = virtualEntry.content;
|
||||
this.scriptInfos.forEach(scriptInfo => {
|
||||
if (scriptInfo.isRootFile) {
|
||||
// only include root files here
|
||||
// usually it means that we won't include lib.d.ts in the list of root files so it won't mess the computation of compilation root dir.
|
||||
fileNames.push(scriptInfo.fileName);
|
||||
}
|
||||
}
|
||||
});
|
||||
return fileNames;
|
||||
}
|
||||
|
||||
public getScriptInfo(fileName: string): ScriptInfo {
|
||||
const fileEntry = this.virtualFileSystem.traversePath(fileName);
|
||||
return fileEntry && fileEntry.isFile() ? fileEntry.content : undefined;
|
||||
return this.scriptInfos.get(vpath.resolve(this.vfs.cwd(), fileName));
|
||||
}
|
||||
|
||||
public addScript(fileName: string, content: string, isRootFile: boolean): void {
|
||||
this.virtualFileSystem.addFile(fileName, new ScriptInfo(fileName, content, isRootFile));
|
||||
this.vfs.mkdirpSync(vpath.dirname(fileName));
|
||||
this.vfs.writeFileSync(fileName, content);
|
||||
this.scriptInfos.set(vpath.resolve(this.vfs.cwd(), fileName), new ScriptInfo(fileName, content, isRootFile));
|
||||
}
|
||||
|
||||
public editScript(fileName: string, start: number, end: number, newText: string) {
|
||||
const script = this.getScriptInfo(fileName);
|
||||
if (script !== undefined) {
|
||||
if (script) {
|
||||
script.editContent(start, end, newText);
|
||||
this.vfs.mkdirpSync(vpath.dirname(fileName));
|
||||
this.vfs.writeFileSync(fileName, script.content);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error("No script with name '" + fileName + "'");
|
||||
}
|
||||
|
||||
public abstract addSymlink(from: string, target: string): void;
|
||||
|
||||
public openFile(_fileName: string, _content?: string, _scriptKindName?: string): void { /*overridden*/ }
|
||||
|
||||
/**
|
||||
|
@ -178,62 +184,60 @@ namespace Harness.LanguageService {
|
|||
|
||||
/// Native adapter
|
||||
class NativeLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceHost, LanguageServiceAdapterHost {
|
||||
symlinks = ts.createMap<string>();
|
||||
|
||||
isKnownTypesPackageName(name: string): boolean {
|
||||
return this.typesRegistry && this.typesRegistry.has(name);
|
||||
}
|
||||
|
||||
installPackage = ts.notImplemented;
|
||||
|
||||
getCompilationSettings() { return this.settings; }
|
||||
|
||||
getCancellationToken() { return this.cancellationToken; }
|
||||
|
||||
getDirectories(path: string): string[] {
|
||||
const dir = this.virtualFileSystem.traversePath(path);
|
||||
return dir && dir.isDirectory() ? dir.getDirectories().map(d => d.name) : [];
|
||||
return this.sys.getDirectories(path);
|
||||
}
|
||||
|
||||
getCurrentDirectory(): string { return virtualFileSystemRoot; }
|
||||
|
||||
getDefaultLibFileName(): string { return Compiler.defaultLibFileName; }
|
||||
|
||||
getScriptFileNames(): string[] {
|
||||
return this.getFilenames().filter(ts.isAnySupportedFileExtension);
|
||||
}
|
||||
|
||||
getScriptSnapshot(fileName: string): ts.IScriptSnapshot {
|
||||
const script = this.getScriptInfo(fileName);
|
||||
return script ? new ScriptSnapshot(script) : undefined;
|
||||
}
|
||||
|
||||
getScriptKind(): ts.ScriptKind { return ts.ScriptKind.Unknown; }
|
||||
|
||||
getScriptVersion(fileName: string): string {
|
||||
const script = this.getScriptInfo(fileName);
|
||||
return script ? script.version.toString() : undefined;
|
||||
}
|
||||
|
||||
directoryExists(dirName: string): boolean {
|
||||
if (ts.forEachEntry(this.symlinks, (_, key) => ts.forSomeAncestorDirectory(key, ancestor => ancestor === dirName))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const fileEntry = this.virtualFileSystem.traversePath(dirName);
|
||||
return fileEntry && fileEntry.isDirectory();
|
||||
return this.sys.directoryExists(dirName);
|
||||
}
|
||||
|
||||
fileExists(fileName: string): boolean {
|
||||
return this.symlinks.has(fileName) || this.getScriptSnapshot(fileName) !== undefined;
|
||||
return this.sys.fileExists(fileName);
|
||||
}
|
||||
|
||||
readDirectory(path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[] {
|
||||
return ts.matchFiles(path, extensions, exclude, include,
|
||||
/*useCaseSensitiveFileNames*/ false,
|
||||
this.getCurrentDirectory(),
|
||||
depth,
|
||||
(p) => this.virtualFileSystem.getAccessibleFileSystemEntries(p));
|
||||
return this.sys.readDirectory(path, extensions, exclude, include, depth);
|
||||
}
|
||||
|
||||
readFile(path: string): string | undefined {
|
||||
const target = this.symlinks.get(path);
|
||||
return target !== undefined ? this.readFile(target) : ts.getSnapshotText(this.getScriptSnapshot(path));
|
||||
return this.sys.readFile(path);
|
||||
}
|
||||
addSymlink(from: string, target: string) { this.symlinks.set(from, target); }
|
||||
|
||||
realpath(path: string): string {
|
||||
const target = this.symlinks.get(path);
|
||||
return target === undefined ? path : target;
|
||||
return this.sys.realpath(path);
|
||||
}
|
||||
|
||||
getTypeRootsVersion() {
|
||||
return 0;
|
||||
}
|
||||
|
@ -262,8 +266,6 @@ namespace Harness.LanguageService {
|
|||
public getModuleResolutionsForFile: (fileName: string) => string;
|
||||
public getTypeReferenceDirectiveResolutionsForFile: (fileName: string) => string;
|
||||
|
||||
addSymlink() { return ts.notImplemented(); }
|
||||
|
||||
constructor(preprocessToResolve: boolean, cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) {
|
||||
super(cancellationToken, options);
|
||||
this.nativeHost = new NativeLanguageServiceHost(cancellationToken, options);
|
||||
|
|
|
@ -110,7 +110,7 @@ namespace Playback {
|
|||
return run;
|
||||
}
|
||||
|
||||
export interface PlaybackIO extends Harness.Io, PlaybackControl { }
|
||||
export interface PlaybackIO extends Harness.IO, PlaybackControl { }
|
||||
|
||||
export interface PlaybackSystem extends ts.System, PlaybackControl { }
|
||||
|
||||
|
@ -134,7 +134,7 @@ namespace Playback {
|
|||
};
|
||||
}
|
||||
|
||||
export function newStyleLogIntoOldStyleLog(log: IoLog, host: ts.System | Harness.Io, baseName: string) {
|
||||
export function newStyleLogIntoOldStyleLog(log: IoLog, host: ts.System | Harness.IO, baseName: string) {
|
||||
for (const file of log.filesAppended) {
|
||||
if (file.contentsPath) {
|
||||
file.contents = host.readFile(ts.combinePaths(baseName, file.contentsPath));
|
||||
|
@ -210,8 +210,8 @@ namespace Playback {
|
|||
}
|
||||
|
||||
function initWrapper(wrapper: PlaybackSystem, underlying: ts.System): void;
|
||||
function initWrapper(wrapper: PlaybackIO, underlying: Harness.Io): void;
|
||||
function initWrapper(wrapper: PlaybackSystem | PlaybackIO, underlying: ts.System | Harness.Io): void {
|
||||
function initWrapper(wrapper: PlaybackIO, underlying: Harness.IO): void;
|
||||
function initWrapper(wrapper: PlaybackSystem | PlaybackIO, underlying: ts.System | Harness.IO): void {
|
||||
ts.forEach(Object.keys(underlying), prop => {
|
||||
(<any>wrapper)[prop] = (<any>underlying)[prop];
|
||||
});
|
||||
|
@ -427,7 +427,7 @@ namespace Playback {
|
|||
// console.log("Swallowed write operation during replay: " + name);
|
||||
}
|
||||
|
||||
export function wrapIO(underlying: Harness.Io): PlaybackIO {
|
||||
export function wrapIO(underlying: Harness.IO): PlaybackIO {
|
||||
const wrapper: PlaybackIO = <any>{};
|
||||
initWrapper(wrapper, underlying);
|
||||
|
||||
|
|
|
@ -28,12 +28,14 @@ namespace Harness.Parallel.Worker {
|
|||
(global as any).describe = ((name, callback) => {
|
||||
testList.push({ name, callback, kind: "suite" });
|
||||
}) as Mocha.IContextDefinition;
|
||||
(global as any).describe.skip = ts.noop;
|
||||
(global as any).it = ((name, callback) => {
|
||||
if (!testList) {
|
||||
throw new Error("Tests must occur within a describe block");
|
||||
}
|
||||
testList.push({ name, callback, kind: "test" });
|
||||
}) as Mocha.ITestDefinition;
|
||||
(global as any).it.skip = ts.noop;
|
||||
}
|
||||
|
||||
function setTimeoutAndExecute(timeout: number | undefined, f: () => void) {
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
///<reference path="harness.ts" />
|
||||
///<reference path="runnerbase.ts" />
|
||||
/// <reference path="harness.ts" />
|
||||
/// <reference path="runnerbase.ts" />
|
||||
/// <reference path="./vpath.ts" />
|
||||
/// <reference path="./vfs.ts" />
|
||||
/// <reference path="./documents.ts" />
|
||||
/// <reference path="./compiler.ts" />
|
||||
/// <reference path="./fakes.ts" />
|
||||
|
||||
// Test case is json of below type in tests/cases/project/
|
||||
interface ProjectRunnerTestCase {
|
||||
namespace project {
|
||||
// Test case is json of below type in tests/cases/project/
|
||||
interface ProjectRunnerTestCase {
|
||||
scenario: string;
|
||||
projectRoot: string; // project where it lives - this also is the current directory when compiling
|
||||
inputFiles: ReadonlyArray<string>; // list of input files to be given to program
|
||||
|
@ -11,33 +17,28 @@ interface ProjectRunnerTestCase {
|
|||
baselineCheck?: boolean; // Verify the baselines of output files, if this is false, we will write to output to the disk but there is no verification of baselines
|
||||
runTest?: boolean; // Run the resulting test
|
||||
bug?: string; // If there is any bug associated with this test case
|
||||
}
|
||||
}
|
||||
|
||||
interface ProjectRunnerTestCaseResolutionInfo extends ProjectRunnerTestCase {
|
||||
interface ProjectRunnerTestCaseResolutionInfo extends ProjectRunnerTestCase {
|
||||
// Apart from actual test case the results of the resolution
|
||||
resolvedInputFiles: ReadonlyArray<string>; // List of files that were asked to read by compiler
|
||||
emittedFiles: ReadonlyArray<string>; // List of files that were emitted by the compiler
|
||||
}
|
||||
}
|
||||
|
||||
interface BatchCompileProjectTestCaseEmittedFile extends Harness.Compiler.GeneratedFile {
|
||||
emittedFileName: string;
|
||||
}
|
||||
|
||||
interface CompileProjectFilesResult {
|
||||
interface CompileProjectFilesResult {
|
||||
configFileSourceFiles: ReadonlyArray<ts.SourceFile>;
|
||||
moduleKind: ts.ModuleKind;
|
||||
program?: ts.Program;
|
||||
compilerOptions?: ts.CompilerOptions;
|
||||
errors: ReadonlyArray<ts.Diagnostic>;
|
||||
sourceMapData?: ReadonlyArray<ts.SourceMapData>;
|
||||
}
|
||||
}
|
||||
|
||||
interface BatchCompileProjectTestCaseResult extends CompileProjectFilesResult {
|
||||
outputFiles?: BatchCompileProjectTestCaseEmittedFile[];
|
||||
}
|
||||
|
||||
class ProjectRunner extends RunnerBase {
|
||||
interface BatchCompileProjectTestCaseResult extends CompileProjectFilesResult {
|
||||
outputFiles?: ReadonlyArray<documents.TextDocument>;
|
||||
}
|
||||
|
||||
export class ProjectRunner extends RunnerBase {
|
||||
public enumerateTestFiles() {
|
||||
return this.enumerateFiles("tests/cases/project", /\.json$/, { recursive: true });
|
||||
}
|
||||
|
@ -47,18 +48,134 @@ class ProjectRunner extends RunnerBase {
|
|||
}
|
||||
|
||||
public initializeTests() {
|
||||
if (this.tests.length === 0) {
|
||||
const testFiles = this.enumerateTestFiles();
|
||||
testFiles.forEach(fn => {
|
||||
this.runProjectTestCase(fn);
|
||||
describe("projects tests", () => {
|
||||
const tests = this.tests.length === 0 ? this.enumerateTestFiles() : this.tests;
|
||||
for (const test of tests) {
|
||||
this.runProjectTestCase(test);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.tests.forEach(test => this.runProjectTestCase(test));
|
||||
}
|
||||
}
|
||||
|
||||
private runProjectTestCase(testCaseFileName: string) {
|
||||
for (const { name, payload } of ProjectTestCase.getConfigurations(testCaseFileName)) {
|
||||
describe("Compiling project for " + payload.testCase.scenario + ": testcase " + testCaseFileName + (name ? ` (${name})` : ``), () => {
|
||||
let projectTestCase: ProjectTestCase | undefined;
|
||||
before(() => { projectTestCase = new ProjectTestCase(testCaseFileName, payload); });
|
||||
it(`Correct module resolution tracing for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifyResolution());
|
||||
it(`Correct errors for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifyDiagnostics());
|
||||
it(`Correct JS output for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifyJavaScriptOutput());
|
||||
// NOTE: This check was commented out in previous code. Leaving this here to eventually be restored if needed.
|
||||
// it(`Correct sourcemap content for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifySourceMapRecord());
|
||||
it(`Correct declarations for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifyDeclarations());
|
||||
after(() => { projectTestCase = undefined; });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ProjectCompilerHost extends fakes.CompilerHost {
|
||||
private _testCase: ProjectRunnerTestCase & ts.CompilerOptions;
|
||||
private _projectParseConfigHost: ProjectParseConfigHost;
|
||||
|
||||
constructor(sys: fakes.System | vfs.FileSystem, compilerOptions: ts.CompilerOptions, _testCaseJustName: string, testCase: ProjectRunnerTestCase & ts.CompilerOptions, _moduleKind: ts.ModuleKind) {
|
||||
super(sys, compilerOptions);
|
||||
this._testCase = testCase;
|
||||
}
|
||||
|
||||
public get parseConfigHost(): fakes.ParseConfigHost {
|
||||
return this._projectParseConfigHost || (this._projectParseConfigHost = new ProjectParseConfigHost(this.sys, this._testCase));
|
||||
}
|
||||
|
||||
public getDefaultLibFileName(_options: ts.CompilerOptions) {
|
||||
return vpath.resolve(this.getDefaultLibLocation(), "lib.es5.d.ts");
|
||||
}
|
||||
}
|
||||
|
||||
class ProjectParseConfigHost extends fakes.ParseConfigHost {
|
||||
private _testCase: ProjectRunnerTestCase & ts.CompilerOptions;
|
||||
|
||||
constructor(sys: fakes.System, testCase: ProjectRunnerTestCase & ts.CompilerOptions) {
|
||||
super(sys);
|
||||
this._testCase = testCase;
|
||||
}
|
||||
|
||||
public readDirectory(path: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] {
|
||||
const result = super.readDirectory(path, extensions, excludes, includes, depth);
|
||||
const projectRoot = vpath.resolve(vfs.srcFolder, this._testCase.projectRoot);
|
||||
return result.map(item => vpath.relative(
|
||||
projectRoot,
|
||||
vpath.resolve(projectRoot, item),
|
||||
this.vfs.ignoreCase
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
interface ProjectTestConfiguration {
|
||||
name: string;
|
||||
payload: ProjectTestPayload;
|
||||
}
|
||||
|
||||
interface ProjectTestPayload {
|
||||
testCase: ProjectRunnerTestCase & ts.CompilerOptions;
|
||||
moduleKind: ts.ModuleKind;
|
||||
vfs: vfs.FileSystem;
|
||||
}
|
||||
|
||||
class ProjectTestCase {
|
||||
private testCase: ProjectRunnerTestCase & ts.CompilerOptions;
|
||||
private testCaseJustName: string;
|
||||
private sys: fakes.System;
|
||||
private compilerOptions: ts.CompilerOptions;
|
||||
private compilerResult: BatchCompileProjectTestCaseResult;
|
||||
|
||||
constructor(testCaseFileName: string, { testCase, moduleKind, vfs }: ProjectTestPayload) {
|
||||
this.testCase = testCase;
|
||||
this.testCaseJustName = testCaseFileName.replace(/^.*[\\\/]/, "").replace(/\.json/, "");
|
||||
this.compilerOptions = createCompilerOptions(testCase, moduleKind);
|
||||
this.sys = new fakes.System(vfs);
|
||||
|
||||
let configFileName: string;
|
||||
let inputFiles = testCase.inputFiles;
|
||||
if (this.compilerOptions.project) {
|
||||
// Parse project
|
||||
configFileName = ts.normalizePath(ts.combinePaths(this.compilerOptions.project, "tsconfig.json"));
|
||||
assert(!inputFiles || inputFiles.length === 0, "cannot specify input files and project option together");
|
||||
}
|
||||
else if (!inputFiles || inputFiles.length === 0) {
|
||||
configFileName = ts.findConfigFile("", path => this.sys.fileExists(path));
|
||||
}
|
||||
|
||||
let errors: ts.Diagnostic[];
|
||||
const configFileSourceFiles: ts.SourceFile[] = [];
|
||||
if (configFileName) {
|
||||
const result = ts.readJsonConfigFile(configFileName, path => this.sys.readFile(path));
|
||||
configFileSourceFiles.push(result);
|
||||
const configParseHost = new ProjectParseConfigHost(this.sys, this.testCase);
|
||||
const configParseResult = ts.parseJsonSourceFileConfigFileContent(result, configParseHost, ts.getDirectoryPath(configFileName), this.compilerOptions);
|
||||
inputFiles = configParseResult.fileNames;
|
||||
this.compilerOptions = configParseResult.options;
|
||||
errors = result.parseDiagnostics.concat(configParseResult.errors);
|
||||
}
|
||||
|
||||
const compilerHost = new ProjectCompilerHost(this.sys, this.compilerOptions, this.testCaseJustName, this.testCase, moduleKind);
|
||||
const projectCompilerResult = this.compileProjectFiles(moduleKind, configFileSourceFiles, () => inputFiles, compilerHost, this.compilerOptions);
|
||||
|
||||
this.compilerResult = {
|
||||
configFileSourceFiles,
|
||||
moduleKind,
|
||||
program: projectCompilerResult.program,
|
||||
compilerOptions: this.compilerOptions,
|
||||
sourceMapData: projectCompilerResult.sourceMapData,
|
||||
outputFiles: compilerHost.outputs,
|
||||
errors: errors ? ts.concatenate(errors, projectCompilerResult.errors) : projectCompilerResult.errors,
|
||||
};
|
||||
}
|
||||
|
||||
private get vfs() {
|
||||
return this.sys.vfs;
|
||||
}
|
||||
|
||||
public static getConfigurations(testCaseFileName: string): ProjectTestConfiguration[] {
|
||||
let testCase: ProjectRunnerTestCase & ts.CompilerOptions;
|
||||
|
||||
let testFileText: string;
|
||||
|
@ -75,33 +192,115 @@ class ProjectRunner extends RunnerBase {
|
|||
catch (e) {
|
||||
assert(false, "Testcase: " + testCaseFileName + " does not contain valid json format: " + e.message);
|
||||
}
|
||||
let testCaseJustName = testCaseFileName.replace(/^.*[\\\/]/, "").replace(/\.json/, "");
|
||||
|
||||
function moduleNameToString(moduleKind: ts.ModuleKind) {
|
||||
return moduleKind === ts.ModuleKind.AMD
|
||||
? "amd"
|
||||
: moduleKind === ts.ModuleKind.CommonJS
|
||||
? "node"
|
||||
: "none";
|
||||
const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false);
|
||||
fs.mountSync(vpath.resolve(Harness.IO.getWorkspaceRoot(), "tests"), vpath.combine(vfs.srcFolder, "tests"), vfs.createResolver(Harness.IO));
|
||||
fs.mkdirpSync(vpath.combine(vfs.srcFolder, testCase.projectRoot));
|
||||
fs.chdir(vpath.combine(vfs.srcFolder, testCase.projectRoot));
|
||||
fs.makeReadonly();
|
||||
|
||||
return [
|
||||
{ name: `@module: commonjs`, payload: { testCase, moduleKind: ts.ModuleKind.CommonJS, vfs: fs } },
|
||||
{ name: `@module: amd`, payload: { testCase, moduleKind: ts.ModuleKind.AMD, vfs: fs } }
|
||||
];
|
||||
}
|
||||
|
||||
public verifyResolution() {
|
||||
const cwd = this.vfs.cwd();
|
||||
const ignoreCase = this.vfs.ignoreCase;
|
||||
const resolutionInfo: ProjectRunnerTestCaseResolutionInfo & ts.CompilerOptions = JSON.parse(JSON.stringify(this.testCase));
|
||||
resolutionInfo.resolvedInputFiles = this.compilerResult.program.getSourceFiles()
|
||||
.map(({ fileName: input }) => vpath.beneath(vfs.builtFolder, input, this.vfs.ignoreCase) || vpath.beneath(vfs.testLibFolder, input, this.vfs.ignoreCase) ? utils.removeTestPathPrefixes(input) :
|
||||
vpath.isAbsolute(input) ? vpath.relative(cwd, input, ignoreCase) :
|
||||
input);
|
||||
|
||||
resolutionInfo.emittedFiles = this.compilerResult.outputFiles
|
||||
.map(output => output.meta.get("fileName") || output.file)
|
||||
.map(output => utils.removeTestPathPrefixes(vpath.isAbsolute(output) ? vpath.relative(cwd, output, ignoreCase) : output));
|
||||
|
||||
const content = JSON.stringify(resolutionInfo, undefined, " ");
|
||||
|
||||
// TODO(rbuckton): This patches the baseline to replace lib.es5.d.ts with lib.d.ts.
|
||||
// This is only to make the PR for this change easier to read. A follow-up PR will
|
||||
// revert this change and accept the new baselines.
|
||||
// See https://github.com/Microsoft/TypeScript/pull/20763#issuecomment-352553264
|
||||
const patchedContent = content.replace(/lib\.es5\.d\.ts/g, "lib.d.ts");
|
||||
Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + this.testCaseJustName + ".json", () => patchedContent);
|
||||
}
|
||||
|
||||
public verifyDiagnostics() {
|
||||
if (this.compilerResult.errors.length) {
|
||||
Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + this.testCaseJustName + ".errors.txt", () => {
|
||||
return getErrorsBaseline(this.compilerResult);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public verifyJavaScriptOutput() {
|
||||
if (this.testCase.baselineCheck) {
|
||||
const errs: Error[] = [];
|
||||
let nonSubfolderDiskFiles = 0;
|
||||
for (const output of this.compilerResult.outputFiles) {
|
||||
try {
|
||||
// convert file name to rooted name
|
||||
// if filename is not rooted - concat it with project root and then expand project root relative to current directory
|
||||
const fileName = output.meta.get("fileName") || output.file;
|
||||
const diskFileName = vpath.isAbsolute(fileName) ? fileName : vpath.resolve(this.vfs.cwd(), fileName);
|
||||
|
||||
// compute file name relative to current directory (expanded project root)
|
||||
let diskRelativeName = vpath.relative(this.vfs.cwd(), diskFileName, this.vfs.ignoreCase);
|
||||
if (vpath.isAbsolute(diskRelativeName) || diskRelativeName.startsWith("../")) {
|
||||
// If the generated output file resides in the parent folder or is rooted path,
|
||||
// we need to instead create files that can live in the project reference folder
|
||||
// but make sure extension of these files matches with the fileName the compiler asked to write
|
||||
diskRelativeName = `diskFile${nonSubfolderDiskFiles}${vpath.extname(fileName, [".js.map", ".js", ".d.ts"], this.vfs.ignoreCase)}`;
|
||||
nonSubfolderDiskFiles++;
|
||||
}
|
||||
|
||||
const content = utils.removeTestPathPrefixes(output.text, /*retainTrailingDirectorySeparator*/ true);
|
||||
Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + diskRelativeName, () => content);
|
||||
}
|
||||
catch (e) {
|
||||
errs.push(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (errs.length) {
|
||||
throw Error(errs.join("\n "));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public verifySourceMapRecord() {
|
||||
// NOTE: This check was commented out in previous code. Leaving this here to eventually be restored if needed.
|
||||
// if (compilerResult.sourceMapData) {
|
||||
// Harness.Baseline.runBaseline(getBaselineFolder(compilerResult.moduleKind) + testCaseJustName + ".sourcemap.txt", () => {
|
||||
// return Harness.SourceMapRecorder.getSourceMapRecord(compilerResult.sourceMapData, compilerResult.program,
|
||||
// ts.filter(compilerResult.outputFiles, outputFile => Harness.Compiler.isJS(outputFile.emittedFileName)));
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
public verifyDeclarations() {
|
||||
if (!this.compilerResult.errors.length && this.testCase.declaration) {
|
||||
const dTsCompileResult = this.compileDeclarations(this.compilerResult);
|
||||
if (dTsCompileResult && dTsCompileResult.errors.length) {
|
||||
Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + this.testCaseJustName + ".dts.errors.txt", () => {
|
||||
return getErrorsBaseline(dTsCompileResult);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Project baselines verified go in project/testCaseName/moduleKind/
|
||||
function getBaselineFolder(moduleKind: ts.ModuleKind) {
|
||||
return "project/" + testCaseJustName + "/" + moduleNameToString(moduleKind) + "/";
|
||||
private getBaselineFolder(moduleKind: ts.ModuleKind) {
|
||||
return "project/" + this.testCaseJustName + "/" + moduleNameToString(moduleKind) + "/";
|
||||
}
|
||||
|
||||
// When test case output goes to tests/baselines/local/projectOutput/testCaseName/moduleKind/
|
||||
// We have these two separate locations because when comparing baselines the baseline verifier will delete the existing file
|
||||
// so even if it was created by compiler in that location, the file will be deleted by verified before we can read it
|
||||
// so lets keep these two locations separate
|
||||
function getProjectOutputFolder(fileName: string, moduleKind: ts.ModuleKind) {
|
||||
return Harness.Baseline.localPath("projectOutput/" + testCaseJustName + "/" + moduleNameToString(moduleKind) + "/" + fileName);
|
||||
}
|
||||
|
||||
function cleanProjectUrl(url: string) {
|
||||
let diskProjectPath = ts.normalizeSlashes(Harness.IO.resolvePath(testCase.projectRoot));
|
||||
private cleanProjectUrl(url: string) {
|
||||
let diskProjectPath = ts.normalizeSlashes(Harness.IO.resolvePath(this.testCase.projectRoot));
|
||||
let projectRootUrl = "file:///" + diskProjectPath;
|
||||
const normalizedProjectRoot = ts.normalizeSlashes(testCase.projectRoot);
|
||||
const normalizedProjectRoot = ts.normalizeSlashes(this.testCase.projectRoot);
|
||||
diskProjectPath = diskProjectPath.substr(0, diskProjectPath.lastIndexOf(normalizedProjectRoot));
|
||||
projectRootUrl = projectRootUrl.substr(0, projectRootUrl.lastIndexOf(normalizedProjectRoot));
|
||||
if (url && url.length) {
|
||||
|
@ -121,17 +320,12 @@ class ProjectRunner extends RunnerBase {
|
|||
return url;
|
||||
}
|
||||
|
||||
function getCurrentDirectory() {
|
||||
return Harness.IO.resolvePath(testCase.projectRoot);
|
||||
}
|
||||
|
||||
function compileProjectFiles(moduleKind: ts.ModuleKind, configFileSourceFiles: ReadonlyArray<ts.SourceFile>,
|
||||
private compileProjectFiles(moduleKind: ts.ModuleKind, configFileSourceFiles: ReadonlyArray<ts.SourceFile>,
|
||||
getInputFiles: () => ReadonlyArray<string>,
|
||||
getSourceFileTextImpl: (fileName: string) => string,
|
||||
writeFile: (fileName: string, data: string, writeByteOrderMark: boolean) => void,
|
||||
compilerHost: ts.CompilerHost,
|
||||
compilerOptions: ts.CompilerOptions): CompileProjectFilesResult {
|
||||
|
||||
const program = ts.createProgram(getInputFiles(), compilerOptions, createCompilerHost());
|
||||
const program = ts.createProgram(getInputFiles(), compilerOptions, compilerHost);
|
||||
const errors = ts.getPreEmitDiagnostics(program);
|
||||
|
||||
const emitResult = program.emit();
|
||||
|
@ -142,10 +336,10 @@ class ProjectRunner extends RunnerBase {
|
|||
if (sourceMapData) {
|
||||
for (const data of sourceMapData) {
|
||||
for (let j = 0; j < data.sourceMapSources.length; j++) {
|
||||
data.sourceMapSources[j] = cleanProjectUrl(data.sourceMapSources[j]);
|
||||
data.sourceMapSources[j] = this.cleanProjectUrl(data.sourceMapSources[j]);
|
||||
}
|
||||
data.jsSourceMappingURL = cleanProjectUrl(data.jsSourceMappingURL);
|
||||
data.sourceMapSourceRoot = cleanProjectUrl(data.sourceMapSourceRoot);
|
||||
data.jsSourceMappingURL = this.cleanProjectUrl(data.jsSourceMappingURL);
|
||||
data.sourceMapSourceRoot = this.cleanProjectUrl(data.sourceMapSourceRoot);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,218 +350,22 @@ class ProjectRunner extends RunnerBase {
|
|||
errors,
|
||||
sourceMapData
|
||||
};
|
||||
|
||||
function getSourceFileText(fileName: string): string {
|
||||
const text = getSourceFileTextImpl(fileName);
|
||||
return text !== undefined ? text : getSourceFileTextImpl(ts.getNormalizedAbsolutePath(fileName, getCurrentDirectory()));
|
||||
}
|
||||
|
||||
function getSourceFile(fileName: string, languageVersion: ts.ScriptTarget): ts.SourceFile {
|
||||
let sourceFile: ts.SourceFile;
|
||||
if (fileName === Harness.Compiler.defaultLibFileName) {
|
||||
sourceFile = Harness.Compiler.getDefaultLibrarySourceFile(Harness.Compiler.getDefaultLibFileName(compilerOptions));
|
||||
}
|
||||
else {
|
||||
const text = getSourceFileText(fileName);
|
||||
if (text !== undefined) {
|
||||
sourceFile = Harness.Compiler.createSourceFileAndAssertInvariants(fileName, text, languageVersion);
|
||||
}
|
||||
}
|
||||
|
||||
return sourceFile;
|
||||
}
|
||||
|
||||
function createCompilerHost(): ts.CompilerHost {
|
||||
return {
|
||||
getSourceFile,
|
||||
getDefaultLibFileName: () => Harness.Compiler.defaultLibFileName,
|
||||
writeFile,
|
||||
getCurrentDirectory,
|
||||
getCanonicalFileName: Harness.Compiler.getCanonicalFileName,
|
||||
useCaseSensitiveFileNames: () => Harness.IO.useCaseSensitiveFileNames(),
|
||||
getNewLine: () => Harness.IO.newLine(),
|
||||
fileExists: fileName => fileName === Harness.Compiler.defaultLibFileName || getSourceFileText(fileName) !== undefined,
|
||||
readFile: fileName => Harness.IO.readFile(fileName),
|
||||
getDirectories: path => Harness.IO.getDirectories(path)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function batchCompilerProjectTestCase(moduleKind: ts.ModuleKind): BatchCompileProjectTestCaseResult {
|
||||
let nonSubfolderDiskFiles = 0;
|
||||
|
||||
const outputFiles: BatchCompileProjectTestCaseEmittedFile[] = [];
|
||||
let inputFiles = testCase.inputFiles;
|
||||
let compilerOptions = createCompilerOptions();
|
||||
const configFileSourceFiles: ts.SourceFile[] = [];
|
||||
|
||||
let configFileName: string;
|
||||
if (compilerOptions.project) {
|
||||
// Parse project
|
||||
configFileName = ts.normalizePath(ts.combinePaths(compilerOptions.project, "tsconfig.json"));
|
||||
assert(!inputFiles || inputFiles.length === 0, "cannot specify input files and project option together");
|
||||
}
|
||||
else if (!inputFiles || inputFiles.length === 0) {
|
||||
configFileName = ts.findConfigFile("", fileExists);
|
||||
}
|
||||
|
||||
let errors: ts.Diagnostic[];
|
||||
if (configFileName) {
|
||||
const result = ts.readJsonConfigFile(configFileName, getSourceFileText);
|
||||
configFileSourceFiles.push(result);
|
||||
const configParseHost: ts.ParseConfigHost = {
|
||||
useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(),
|
||||
fileExists,
|
||||
readDirectory,
|
||||
readFile
|
||||
};
|
||||
const configParseResult = ts.parseJsonSourceFileConfigFileContent(result, configParseHost, ts.getDirectoryPath(configFileName), compilerOptions);
|
||||
inputFiles = configParseResult.fileNames;
|
||||
compilerOptions = configParseResult.options;
|
||||
errors = result.parseDiagnostics.concat(configParseResult.errors);
|
||||
}
|
||||
|
||||
const projectCompilerResult = compileProjectFiles(moduleKind, configFileSourceFiles, () => inputFiles, getSourceFileText, writeFile, compilerOptions);
|
||||
return {
|
||||
configFileSourceFiles,
|
||||
moduleKind,
|
||||
program: projectCompilerResult.program,
|
||||
compilerOptions,
|
||||
sourceMapData: projectCompilerResult.sourceMapData,
|
||||
outputFiles,
|
||||
errors: errors ? ts.concatenate(errors, projectCompilerResult.errors) : projectCompilerResult.errors,
|
||||
};
|
||||
|
||||
function createCompilerOptions() {
|
||||
// Set the special options that depend on other testcase options
|
||||
const compilerOptions: ts.CompilerOptions = {
|
||||
mapRoot: testCase.resolveMapRoot && testCase.mapRoot ? Harness.IO.resolvePath(testCase.mapRoot) : testCase.mapRoot,
|
||||
sourceRoot: testCase.resolveSourceRoot && testCase.sourceRoot ? Harness.IO.resolvePath(testCase.sourceRoot) : testCase.sourceRoot,
|
||||
module: moduleKind,
|
||||
moduleResolution: ts.ModuleResolutionKind.Classic, // currently all tests use classic module resolution kind, this will change in the future
|
||||
};
|
||||
// Set the values specified using json
|
||||
const optionNameMap = ts.arrayToMap(ts.optionDeclarations, option => option.name);
|
||||
for (const name in testCase) {
|
||||
if (name !== "mapRoot" && name !== "sourceRoot") {
|
||||
const option = optionNameMap.get(name);
|
||||
if (option) {
|
||||
const optType = option.type;
|
||||
let value = <any>testCase[name];
|
||||
if (!ts.isString(optType)) {
|
||||
const key = value.toLowerCase();
|
||||
const optTypeValue = optType.get(key);
|
||||
if (optTypeValue) {
|
||||
value = optTypeValue;
|
||||
}
|
||||
}
|
||||
compilerOptions[option.name] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return compilerOptions;
|
||||
}
|
||||
|
||||
function getFileNameInTheProjectTest(fileName: string): string {
|
||||
return ts.isRootedDiskPath(fileName)
|
||||
? fileName
|
||||
: ts.normalizeSlashes(testCase.projectRoot) + "/" + ts.normalizeSlashes(fileName);
|
||||
}
|
||||
|
||||
function readDirectory(rootDir: string, extension: string[], exclude: string[], include: string[], depth: number): string[] {
|
||||
const harnessReadDirectoryResult = Harness.IO.readDirectory(getFileNameInTheProjectTest(rootDir), extension, exclude, include, depth);
|
||||
const result: string[] = [];
|
||||
for (let i = 0; i < harnessReadDirectoryResult.length; i++) {
|
||||
result[i] = ts.getRelativePathToDirectoryOrUrl(testCase.projectRoot, harnessReadDirectoryResult[i],
|
||||
getCurrentDirectory(), Harness.Compiler.getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function fileExists(fileName: string): boolean {
|
||||
return Harness.IO.fileExists(getFileNameInTheProjectTest(fileName));
|
||||
}
|
||||
|
||||
function readFile(fileName: string): string | undefined {
|
||||
return Harness.IO.readFile(getFileNameInTheProjectTest(fileName));
|
||||
}
|
||||
|
||||
function getSourceFileText(fileName: string): string {
|
||||
let text: string;
|
||||
try {
|
||||
text = Harness.IO.readFile(getFileNameInTheProjectTest(fileName));
|
||||
}
|
||||
catch (e) {
|
||||
// text doesn't get defined.
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function writeFile(fileName: string, data: string, writeByteOrderMark: boolean) {
|
||||
// convert file name to rooted name
|
||||
// if filename is not rooted - concat it with project root and then expand project root relative to current directory
|
||||
const diskFileName = ts.isRootedDiskPath(fileName)
|
||||
? fileName
|
||||
: Harness.IO.resolvePath(ts.normalizeSlashes(testCase.projectRoot) + "/" + ts.normalizeSlashes(fileName));
|
||||
|
||||
const currentDirectory = getCurrentDirectory();
|
||||
// compute file name relative to current directory (expanded project root)
|
||||
let diskRelativeName = ts.getRelativePathToDirectoryOrUrl(currentDirectory, diskFileName, currentDirectory, Harness.Compiler.getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
|
||||
if (ts.isRootedDiskPath(diskRelativeName) || diskRelativeName.substr(0, 3) === "../") {
|
||||
// If the generated output file resides in the parent folder or is rooted path,
|
||||
// we need to instead create files that can live in the project reference folder
|
||||
// but make sure extension of these files matches with the fileName the compiler asked to write
|
||||
diskRelativeName = "diskFile" + nonSubfolderDiskFiles +
|
||||
(Harness.Compiler.isDTS(fileName) ? ts.Extension.Dts :
|
||||
Harness.Compiler.isJS(fileName) ? ts.Extension.Js : ".js.map");
|
||||
nonSubfolderDiskFiles++;
|
||||
}
|
||||
|
||||
if (Harness.Compiler.isJS(fileName)) {
|
||||
// Make sure if there is URl we have it cleaned up
|
||||
const indexOfSourceMapUrl = data.lastIndexOf(`//# ${"sourceMappingURL"}=`); // This line can be seen as a sourceMappingURL comment
|
||||
if (indexOfSourceMapUrl !== -1) {
|
||||
data = data.substring(0, indexOfSourceMapUrl + 21) + cleanProjectUrl(data.substring(indexOfSourceMapUrl + 21));
|
||||
}
|
||||
}
|
||||
else if (Harness.Compiler.isJSMap(fileName)) {
|
||||
// Make sure sources list is cleaned
|
||||
const sourceMapData = JSON.parse(data);
|
||||
for (let i = 0; i < sourceMapData.sources.length; i++) {
|
||||
sourceMapData.sources[i] = cleanProjectUrl(sourceMapData.sources[i]);
|
||||
}
|
||||
sourceMapData.sourceRoot = cleanProjectUrl(sourceMapData.sourceRoot);
|
||||
data = JSON.stringify(sourceMapData);
|
||||
}
|
||||
|
||||
const outputFilePath = getProjectOutputFolder(diskRelativeName, moduleKind);
|
||||
// Actual writing of file as in tc.ts
|
||||
function ensureDirectoryStructure(directoryname: string) {
|
||||
if (directoryname) {
|
||||
if (!Harness.IO.directoryExists(directoryname)) {
|
||||
ensureDirectoryStructure(ts.getDirectoryPath(directoryname));
|
||||
Harness.IO.createDirectory(directoryname);
|
||||
}
|
||||
}
|
||||
}
|
||||
ensureDirectoryStructure(ts.getDirectoryPath(ts.normalizePath(outputFilePath)));
|
||||
Harness.IO.writeFile(outputFilePath, data);
|
||||
|
||||
outputFiles.push({ emittedFileName: fileName, code: data, fileName: diskRelativeName, writeByteOrderMark });
|
||||
}
|
||||
}
|
||||
|
||||
function compileCompileDTsFiles(compilerResult: BatchCompileProjectTestCaseResult) {
|
||||
const allInputFiles: { emittedFileName: string; code: string; }[] = [];
|
||||
private compileDeclarations(compilerResult: BatchCompileProjectTestCaseResult) {
|
||||
if (!compilerResult.program) {
|
||||
return;
|
||||
}
|
||||
const compilerOptions = compilerResult.program.getCompilerOptions();
|
||||
|
||||
const compilerOptions = compilerResult.program.getCompilerOptions();
|
||||
const allInputFiles: documents.TextDocument[] = [];
|
||||
const rootFiles: string[] = [];
|
||||
ts.forEach(compilerResult.program.getSourceFiles(), sourceFile => {
|
||||
if (sourceFile.isDeclarationFile) {
|
||||
allInputFiles.unshift({ emittedFileName: sourceFile.fileName, code: sourceFile.text });
|
||||
if (!vpath.isDefaultLibrary(sourceFile.fileName)) {
|
||||
allInputFiles.unshift(new documents.TextDocument(sourceFile.fileName, sourceFile.text));
|
||||
}
|
||||
rootFiles.unshift(sourceFile.fileName);
|
||||
}
|
||||
else if (!(compilerOptions.outFile || compilerOptions.out)) {
|
||||
let emitOutputFilePathWithoutExtension: string;
|
||||
|
@ -384,6 +382,7 @@ class ProjectRunner extends RunnerBase {
|
|||
const file = findOutputDtsFile(outputDtsFileName);
|
||||
if (file) {
|
||||
allInputFiles.unshift(file);
|
||||
rootFiles.unshift(file.meta.get("fileName") || file.file);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -391,31 +390,32 @@ class ProjectRunner extends RunnerBase {
|
|||
const outputDtsFile = findOutputDtsFile(outputDtsFileName);
|
||||
if (!ts.contains(allInputFiles, outputDtsFile)) {
|
||||
allInputFiles.unshift(outputDtsFile);
|
||||
rootFiles.unshift(outputDtsFile.meta.get("fileName") || outputDtsFile.file);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const _vfs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, {
|
||||
documents: allInputFiles,
|
||||
cwd: vpath.combine(vfs.srcFolder, this.testCase.projectRoot)
|
||||
});
|
||||
|
||||
// Dont allow config files since we are compiling existing source options
|
||||
return compileProjectFiles(compilerResult.moduleKind, compilerResult.configFileSourceFiles, getInputFiles, getSourceFileText, /*writeFile*/ ts.noop, compilerResult.compilerOptions);
|
||||
const compilerHost = new ProjectCompilerHost(_vfs, compilerResult.compilerOptions, this.testCaseJustName, this.testCase, compilerResult.moduleKind);
|
||||
return this.compileProjectFiles(compilerResult.moduleKind, compilerResult.configFileSourceFiles, () => rootFiles, compilerHost, compilerResult.compilerOptions);
|
||||
|
||||
function findOutputDtsFile(fileName: string) {
|
||||
return ts.forEach(compilerResult.outputFiles, outputFile => outputFile.emittedFileName === fileName ? outputFile : undefined);
|
||||
return ts.forEach(compilerResult.outputFiles, outputFile => outputFile.meta.get("fileName") === fileName ? outputFile : undefined);
|
||||
}
|
||||
}
|
||||
function getInputFiles() {
|
||||
return ts.map(allInputFiles, outputFile => outputFile.emittedFileName);
|
||||
}
|
||||
function getSourceFileText(fileName: string): string {
|
||||
for (const inputFile of allInputFiles) {
|
||||
const isMatchingFile = ts.isRootedDiskPath(fileName)
|
||||
? ts.getNormalizedAbsolutePath(inputFile.emittedFileName, getCurrentDirectory()) === fileName
|
||||
: inputFile.emittedFileName === fileName;
|
||||
|
||||
if (isMatchingFile) {
|
||||
return inputFile.code;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
function moduleNameToString(moduleKind: ts.ModuleKind) {
|
||||
return moduleKind === ts.ModuleKind.AMD
|
||||
? "amd"
|
||||
: moduleKind === ts.ModuleKind.CommonJS
|
||||
? "node"
|
||||
: "none";
|
||||
}
|
||||
|
||||
function getErrorsBaseline(compilerResult: CompileProjectFilesResult) {
|
||||
|
@ -438,106 +438,42 @@ class ProjectRunner extends RunnerBase {
|
|||
return Harness.Compiler.getErrorBaseline(inputFiles, compilerResult.errors);
|
||||
}
|
||||
|
||||
const name = "Compiling project for " + testCase.scenario + ": testcase " + testCaseFileName;
|
||||
function createCompilerOptions(testCase: ProjectRunnerTestCase & ts.CompilerOptions, moduleKind: ts.ModuleKind) {
|
||||
// Set the special options that depend on other testcase options
|
||||
const compilerOptions: ts.CompilerOptions = {
|
||||
noErrorTruncation: false,
|
||||
skipDefaultLibCheck: false,
|
||||
moduleResolution: ts.ModuleResolutionKind.Classic,
|
||||
module: moduleKind,
|
||||
mapRoot: testCase.resolveMapRoot && testCase.mapRoot
|
||||
? vpath.resolve(vfs.srcFolder, testCase.mapRoot)
|
||||
: testCase.mapRoot,
|
||||
|
||||
describe("projects tests", () => {
|
||||
describe(name, () => {
|
||||
function verifyCompilerResults(moduleKind: ts.ModuleKind) {
|
||||
let compilerResult: BatchCompileProjectTestCaseResult;
|
||||
sourceRoot: testCase.resolveSourceRoot && testCase.sourceRoot
|
||||
? vpath.resolve(vfs.srcFolder, testCase.sourceRoot)
|
||||
: testCase.sourceRoot
|
||||
};
|
||||
|
||||
function getCompilerResolutionInfo() {
|
||||
const resolutionInfo: ProjectRunnerTestCaseResolutionInfo & ts.CompilerOptions = JSON.parse(JSON.stringify(testCase));
|
||||
resolutionInfo.resolvedInputFiles = ts.map(compilerResult.program.getSourceFiles(), inputFile => {
|
||||
return ts.convertToRelativePath(inputFile.fileName, getCurrentDirectory(), path => Harness.Compiler.getCanonicalFileName(path));
|
||||
});
|
||||
resolutionInfo.emittedFiles = ts.map(compilerResult.outputFiles, outputFile => {
|
||||
return ts.convertToRelativePath(outputFile.emittedFileName, getCurrentDirectory(), path => Harness.Compiler.getCanonicalFileName(path));
|
||||
});
|
||||
return resolutionInfo;
|
||||
}
|
||||
|
||||
it(name + ": " + moduleNameToString(moduleKind), () => {
|
||||
// Compile using node
|
||||
compilerResult = batchCompilerProjectTestCase(moduleKind);
|
||||
});
|
||||
|
||||
it("Resolution information of (" + moduleNameToString(moduleKind) + "): " + testCaseFileName, () => {
|
||||
Harness.Baseline.runBaseline(getBaselineFolder(compilerResult.moduleKind) + testCaseJustName + ".json", () => {
|
||||
return JSON.stringify(getCompilerResolutionInfo(), undefined, " ");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("Errors for (" + moduleNameToString(moduleKind) + "): " + testCaseFileName, () => {
|
||||
if (compilerResult.errors.length) {
|
||||
Harness.Baseline.runBaseline(getBaselineFolder(compilerResult.moduleKind) + testCaseJustName + ".errors.txt", () => {
|
||||
return getErrorsBaseline(compilerResult);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("Baseline of emitted result (" + moduleNameToString(moduleKind) + "): " + testCaseFileName, () => {
|
||||
if (testCase.baselineCheck) {
|
||||
const errs: Error[] = [];
|
||||
ts.forEach(compilerResult.outputFiles, outputFile => {
|
||||
// There may be multiple files with different baselines. Run all and report at the end, else
|
||||
// it stops copying the remaining emitted files from 'local/projectOutput' to 'local/project'.
|
||||
try {
|
||||
Harness.Baseline.runBaseline(getBaselineFolder(compilerResult.moduleKind) + outputFile.fileName, () => {
|
||||
try {
|
||||
return Harness.IO.readFile(getProjectOutputFolder(outputFile.fileName, compilerResult.moduleKind));
|
||||
}
|
||||
catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
errs.push(e);
|
||||
}
|
||||
});
|
||||
if (errs.length) {
|
||||
throw Error(errs.join("\n "));
|
||||
// Set the values specified using json
|
||||
const optionNameMap = ts.arrayToMap(ts.optionDeclarations, option => option.name);
|
||||
for (const name in testCase) {
|
||||
if (name !== "mapRoot" && name !== "sourceRoot") {
|
||||
const option = optionNameMap.get(name);
|
||||
if (option) {
|
||||
const optType = option.type;
|
||||
let value = <any>testCase[name];
|
||||
if (!ts.isString(optType)) {
|
||||
const key = value.toLowerCase();
|
||||
const optTypeValue = optType.get(key);
|
||||
if (optTypeValue) {
|
||||
value = optTypeValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// it("SourceMapRecord for (" + moduleNameToString(moduleKind) + "): " + testCaseFileName, () => {
|
||||
// if (compilerResult.sourceMapData) {
|
||||
// Harness.Baseline.runBaseline(getBaselineFolder(compilerResult.moduleKind) + testCaseJustName + ".sourcemap.txt", () => {
|
||||
// return Harness.SourceMapRecorder.getSourceMapRecord(compilerResult.sourceMapData, compilerResult.program,
|
||||
// ts.filter(compilerResult.outputFiles, outputFile => Harness.Compiler.isJS(outputFile.emittedFileName)));
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
||||
// Verify that all the generated .d.ts files compile
|
||||
it("Errors in generated Dts files for (" + moduleNameToString(moduleKind) + "): " + testCaseFileName, () => {
|
||||
if (!compilerResult.errors.length && testCase.declaration) {
|
||||
const dTsCompileResult = compileCompileDTsFiles(compilerResult);
|
||||
if (dTsCompileResult && dTsCompileResult.errors.length) {
|
||||
Harness.Baseline.runBaseline(getBaselineFolder(compilerResult.moduleKind) + testCaseJustName + ".dts.errors.txt", () => {
|
||||
return getErrorsBaseline(dTsCompileResult);
|
||||
});
|
||||
compilerOptions[option.name] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
after(() => {
|
||||
compilerResult = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
verifyCompilerResults(ts.ModuleKind.CommonJS);
|
||||
verifyCompilerResults(ts.ModuleKind.AMD);
|
||||
|
||||
after(() => {
|
||||
// Mocha holds onto the closure environment of the describe callback even after the test is done.
|
||||
// Therefore we have to clean out large objects after the test is done.
|
||||
testCase = undefined;
|
||||
testFileText = undefined;
|
||||
testCaseJustName = undefined;
|
||||
});
|
||||
});
|
||||
});
|
||||
return compilerOptions;
|
||||
}
|
||||
}
|
|
@ -55,7 +55,7 @@ function createRunner(kind: TestRunnerKind): RunnerBase {
|
|||
case "fourslash-server":
|
||||
return new FourSlashRunner(FourSlashTestType.Server);
|
||||
case "project":
|
||||
return new ProjectRunner();
|
||||
return new project.ProjectRunner();
|
||||
case "rwc":
|
||||
return new RWCRunner();
|
||||
case "test262":
|
||||
|
@ -68,10 +68,6 @@ function createRunner(kind: TestRunnerKind): RunnerBase {
|
|||
ts.Debug.fail(`Unknown runner kind ${kind}`);
|
||||
}
|
||||
|
||||
if (Harness.IO.tryEnableSourceMapsForHost && /^development$/i.test(Harness.IO.getEnvironmentVariable("NODE_ENV"))) {
|
||||
Harness.IO.tryEnableSourceMapsForHost();
|
||||
}
|
||||
|
||||
// users can define tests to run in mytest.config that will override cmd line args, otherwise use cmd line args (test.config), otherwise no options
|
||||
|
||||
const mytestconfigFileName = "mytest.config";
|
||||
|
@ -161,13 +157,13 @@ function handleTestConfig() {
|
|||
case "compiler":
|
||||
runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance));
|
||||
runners.push(new CompilerBaselineRunner(CompilerTestType.Regressions));
|
||||
runners.push(new ProjectRunner());
|
||||
runners.push(new project.ProjectRunner());
|
||||
break;
|
||||
case "conformance":
|
||||
runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance));
|
||||
break;
|
||||
case "project":
|
||||
runners.push(new ProjectRunner());
|
||||
runners.push(new project.ProjectRunner());
|
||||
break;
|
||||
case "fourslash":
|
||||
runners.push(new FourSlashRunner(FourSlashTestType.Native));
|
||||
|
@ -208,7 +204,7 @@ function handleTestConfig() {
|
|||
|
||||
// TODO: project tests don"t work in the browser yet
|
||||
if (Utils.getExecutionEnvironment() !== Utils.ExecutionEnvironment.Browser) {
|
||||
runners.push(new ProjectRunner());
|
||||
runners.push(new project.ProjectRunner());
|
||||
}
|
||||
|
||||
// language services
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
/* tslint:disable:no-null-keyword */
|
||||
|
||||
namespace RWC {
|
||||
function runWithIOLog(ioLog: IoLog, fn: (oldIO: Harness.Io) => void) {
|
||||
function runWithIOLog(ioLog: IoLog, fn: (oldIO: Harness.IO) => void) {
|
||||
const oldIO = Harness.IO;
|
||||
|
||||
const wrappedIO = Playback.wrapIO(oldIO);
|
||||
|
@ -30,7 +30,7 @@ namespace RWC {
|
|||
let inputFiles: Harness.Compiler.TestFile[] = [];
|
||||
let otherFiles: Harness.Compiler.TestFile[] = [];
|
||||
let tsconfigFiles: Harness.Compiler.TestFile[] = [];
|
||||
let compilerResult: Harness.Compiler.CompilerResult;
|
||||
let compilerResult: compiler.CompilationResult;
|
||||
let compilerOptions: ts.CompilerOptions;
|
||||
const baselineOpts: Harness.Baseline.BaselineOptions = {
|
||||
Subfolder: "rwc",
|
||||
|
@ -142,8 +142,7 @@ namespace RWC {
|
|||
opts.options.noLib = true;
|
||||
|
||||
// Emit the results
|
||||
compilerOptions = undefined;
|
||||
const output = Harness.Compiler.compileFiles(
|
||||
compilerResult = Harness.Compiler.compileFiles(
|
||||
inputFiles,
|
||||
otherFiles,
|
||||
/* harnessOptions */ undefined,
|
||||
|
@ -151,9 +150,7 @@ namespace RWC {
|
|||
// Since each RWC json file specifies its current directory in its json file, we need
|
||||
// to pass this information in explicitly instead of acquiring it from the process.
|
||||
currentDirectory);
|
||||
|
||||
compilerOptions = output.options;
|
||||
compilerResult = output.result;
|
||||
compilerOptions = compilerResult.options;
|
||||
});
|
||||
|
||||
function getHarnessCompilerInputUnit(fileName: string): Harness.Compiler.TestFile {
|
||||
|
@ -173,38 +170,38 @@ namespace RWC {
|
|||
it("has the expected emitted code", function(this: Mocha.ITestCallbackContext) {
|
||||
this.timeout(100_000); // Allow longer timeouts for RWC js verification
|
||||
Harness.Baseline.runMultifileBaseline(baseName, "", () => {
|
||||
return Harness.Compiler.iterateOutputs(compilerResult.files);
|
||||
return Harness.Compiler.iterateOutputs(compilerResult.js.values());
|
||||
}, baselineOpts, [".js", ".jsx"]);
|
||||
});
|
||||
|
||||
it("has the expected declaration file content", () => {
|
||||
Harness.Baseline.runMultifileBaseline(baseName, "", () => {
|
||||
if (!compilerResult.declFilesCode.length) {
|
||||
if (!compilerResult.dts.size) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Harness.Compiler.iterateOutputs(compilerResult.declFilesCode);
|
||||
return Harness.Compiler.iterateOutputs(compilerResult.dts.values());
|
||||
}, baselineOpts, [".d.ts"]);
|
||||
});
|
||||
|
||||
it("has the expected source maps", () => {
|
||||
Harness.Baseline.runMultifileBaseline(baseName, "", () => {
|
||||
if (!compilerResult.sourceMaps.length) {
|
||||
if (!compilerResult.maps.size) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Harness.Compiler.iterateOutputs(compilerResult.sourceMaps);
|
||||
return Harness.Compiler.iterateOutputs(compilerResult.maps.values());
|
||||
}, baselineOpts, [".map"]);
|
||||
});
|
||||
|
||||
it("has the expected errors", () => {
|
||||
Harness.Baseline.runMultifileBaseline(baseName, ".errors.txt", () => {
|
||||
if (compilerResult.errors.length === 0) {
|
||||
if (compilerResult.diagnostics.length === 0) {
|
||||
return null;
|
||||
}
|
||||
// Do not include the library in the baselines to avoid noise
|
||||
const baselineFiles = tsconfigFiles.concat(inputFiles, otherFiles).filter(f => !Harness.isDefaultLibraryFile(f.unitName));
|
||||
const errors = compilerResult.errors.filter(e => !e.file || !Harness.isDefaultLibraryFile(e.file.fileName));
|
||||
const errors = compilerResult.diagnostics.filter(e => !e.file || !Harness.isDefaultLibraryFile(e.file.fileName));
|
||||
return Harness.Compiler.iterateErrorBaseline(baselineFiles, errors);
|
||||
}, baselineOpts);
|
||||
});
|
||||
|
@ -212,9 +209,9 @@ namespace RWC {
|
|||
// Ideally, a generated declaration file will have no errors. But we allow generated
|
||||
// declaration file errors as part of the baseline.
|
||||
it("has the expected errors in generated declaration files", () => {
|
||||
if (compilerOptions.declaration && !compilerResult.errors.length) {
|
||||
if (compilerOptions.declaration && !compilerResult.diagnostics.length) {
|
||||
Harness.Baseline.runMultifileBaseline(baseName, ".dts.errors.txt", () => {
|
||||
if (compilerResult.errors.length === 0) {
|
||||
if (compilerResult.diagnostics.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -225,7 +222,7 @@ namespace RWC {
|
|||
compilerResult = undefined;
|
||||
const declFileCompilationResult = Harness.Compiler.compileDeclarationFiles(declContext);
|
||||
|
||||
return Harness.Compiler.iterateErrorBaseline(tsconfigFiles.concat(declFileCompilationResult.declInputFiles, declFileCompilationResult.declOtherFiles), declFileCompilationResult.declResult.errors);
|
||||
return Harness.Compiler.iterateErrorBaseline(tsconfigFiles.concat(declFileCompilationResult.declInputFiles, declFileCompilationResult.declOtherFiles), declFileCompilationResult.declResult.diagnostics);
|
||||
}, baselineOpts);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -206,8 +206,8 @@ namespace Harness.SourceMapRecorder {
|
|||
let sourceMapSources: string[];
|
||||
let sourceMapNames: string[];
|
||||
|
||||
let jsFile: Compiler.GeneratedFile;
|
||||
let jsLineMap: number[];
|
||||
let jsFile: documents.TextDocument;
|
||||
let jsLineMap: ReadonlyArray<number>;
|
||||
let tsCode: string;
|
||||
let tsLineMap: number[];
|
||||
|
||||
|
@ -216,13 +216,13 @@ namespace Harness.SourceMapRecorder {
|
|||
let prevWrittenJsLine: number;
|
||||
let spanMarkerContinues: boolean;
|
||||
|
||||
export function initializeSourceMapSpanWriter(sourceMapRecordWriter: Compiler.WriterAggregator, sourceMapData: ts.SourceMapData, currentJsFile: Compiler.GeneratedFile) {
|
||||
export function initializeSourceMapSpanWriter(sourceMapRecordWriter: Compiler.WriterAggregator, sourceMapData: ts.SourceMapData, currentJsFile: documents.TextDocument) {
|
||||
sourceMapRecorder = sourceMapRecordWriter;
|
||||
sourceMapSources = sourceMapData.sourceMapSources;
|
||||
sourceMapNames = sourceMapData.sourceMapNames;
|
||||
|
||||
jsFile = currentJsFile;
|
||||
jsLineMap = ts.computeLineStarts(jsFile.code);
|
||||
jsLineMap = jsFile.lineStarts;
|
||||
|
||||
spansOnSingleLine = [];
|
||||
prevWrittenSourcePos = 0;
|
||||
|
@ -290,7 +290,7 @@ namespace Harness.SourceMapRecorder {
|
|||
|
||||
assert.isTrue(spansOnSingleLine.length === 1);
|
||||
sourceMapRecorder.WriteLine("-------------------------------------------------------------------");
|
||||
sourceMapRecorder.WriteLine("emittedFile:" + jsFile.fileName);
|
||||
sourceMapRecorder.WriteLine("emittedFile:" + jsFile.file);
|
||||
sourceMapRecorder.WriteLine("sourceFile:" + sourceMapSources[spansOnSingleLine[0].sourceMapSpan.sourceIndex]);
|
||||
sourceMapRecorder.WriteLine("-------------------------------------------------------------------");
|
||||
|
||||
|
@ -313,15 +313,16 @@ namespace Harness.SourceMapRecorder {
|
|||
writeJsFileLines(jsLineMap.length);
|
||||
}
|
||||
|
||||
function getTextOfLine(line: number, lineMap: number[], code: string) {
|
||||
function getTextOfLine(line: number, lineMap: ReadonlyArray<number>, code: string) {
|
||||
const startPos = lineMap[line];
|
||||
const endPos = lineMap[line + 1];
|
||||
return code.substring(startPos, endPos);
|
||||
const text = code.substring(startPos, endPos);
|
||||
return line === 0 ? utils.removeByteOrderMark(text) : text;
|
||||
}
|
||||
|
||||
function writeJsFileLines(endJsLine: number) {
|
||||
for (; prevWrittenJsLine < endJsLine; prevWrittenJsLine++) {
|
||||
sourceMapRecorder.Write(">>>" + getTextOfLine(prevWrittenJsLine, jsLineMap, jsFile.code));
|
||||
sourceMapRecorder.Write(">>>" + getTextOfLine(prevWrittenJsLine, jsLineMap, jsFile.text));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -417,7 +418,7 @@ namespace Harness.SourceMapRecorder {
|
|||
// Emit markers
|
||||
iterateSpans(writeSourceMapMarker);
|
||||
|
||||
const jsFileText = getTextOfLine(currentJsLine, jsLineMap, jsFile.code);
|
||||
const jsFileText = getTextOfLine(currentJsLine, jsLineMap, jsFile.text);
|
||||
if (prevEmittedCol < jsFileText.length) {
|
||||
// There is remaining text on this line that will be part of next source span so write marker that continues
|
||||
writeSourceMapMarker(/*currentSpan*/ undefined, spansOnSingleLine.length, /*endColumn*/ jsFileText.length, /*endContinues*/ true);
|
||||
|
@ -434,13 +435,13 @@ namespace Harness.SourceMapRecorder {
|
|||
}
|
||||
}
|
||||
|
||||
export function getSourceMapRecord(sourceMapDataList: ts.SourceMapData[], program: ts.Program, jsFiles: Compiler.GeneratedFile[], declarationFiles: Compiler.GeneratedFile[]) {
|
||||
export function getSourceMapRecord(sourceMapDataList: ReadonlyArray<ts.SourceMapData>, program: ts.Program, jsFiles: ReadonlyArray<documents.TextDocument>, declarationFiles: ReadonlyArray<documents.TextDocument>) {
|
||||
const sourceMapRecorder = new Compiler.WriterAggregator();
|
||||
|
||||
for (let i = 0; i < sourceMapDataList.length; i++) {
|
||||
const sourceMapData = sourceMapDataList[i];
|
||||
let prevSourceFile: ts.SourceFile;
|
||||
let currentFile: Compiler.GeneratedFile;
|
||||
let currentFile: documents.TextDocument;
|
||||
if (ts.endsWith(sourceMapData.sourceMapFile, ts.Extension.Dts)) {
|
||||
if (sourceMapDataList.length > jsFiles.length) {
|
||||
currentFile = declarationFiles[Math.floor(i / 2)]; // When both kinds of source map are present, they alternate js/dts
|
||||
|
|
|
@ -31,7 +31,7 @@ class Test262BaselineRunner extends RunnerBase {
|
|||
// Everything declared here should be cleared out in the "after" callback.
|
||||
let testState: {
|
||||
filename: string;
|
||||
compilerResult: Harness.Compiler.CompilerResult;
|
||||
compilerResult: compiler.CompilationResult;
|
||||
inputFiles: Harness.Compiler.TestFile[];
|
||||
};
|
||||
|
||||
|
@ -52,14 +52,12 @@ class Test262BaselineRunner extends RunnerBase {
|
|||
compilerResult: undefined,
|
||||
};
|
||||
|
||||
const output = Harness.Compiler.compileFiles(
|
||||
testState.compilerResult = Harness.Compiler.compileFiles(
|
||||
[Test262BaselineRunner.helperFile].concat(inputFiles),
|
||||
/*otherFiles*/ [],
|
||||
/* harnessOptions */ undefined,
|
||||
Test262BaselineRunner.options,
|
||||
/* currentDirectory */ undefined
|
||||
);
|
||||
testState.compilerResult = output.result;
|
||||
/* currentDirectory */ undefined);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
|
@ -68,14 +66,14 @@ class Test262BaselineRunner extends RunnerBase {
|
|||
|
||||
it("has the expected emitted code", () => {
|
||||
Harness.Baseline.runBaseline(testState.filename + ".output.js", () => {
|
||||
const files = testState.compilerResult.files.filter(f => f.fileName !== Test262BaselineRunner.helpersFilePath);
|
||||
const files = Array.from(testState.compilerResult.js.values()).filter(f => f.file !== Test262BaselineRunner.helpersFilePath);
|
||||
return Harness.Compiler.collateOutputs(files);
|
||||
}, Test262BaselineRunner.baselineOptions);
|
||||
});
|
||||
|
||||
it("has the expected errors", () => {
|
||||
Harness.Baseline.runBaseline(testState.filename + ".errors.txt", () => {
|
||||
const errors = testState.compilerResult.errors;
|
||||
const errors = testState.compilerResult.diagnostics;
|
||||
if (errors.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -135,12 +135,19 @@
|
|||
"../server/session.ts",
|
||||
"../server/scriptVersionCache.ts",
|
||||
|
||||
"collections.ts",
|
||||
"utils.ts",
|
||||
"documents.ts",
|
||||
"vpath.ts",
|
||||
"vfs.ts",
|
||||
"compiler.ts",
|
||||
"fakes.ts",
|
||||
|
||||
"sourceMapRecorder.ts",
|
||||
"runnerbase.ts",
|
||||
"virtualFileSystem.ts",
|
||||
"harness.ts",
|
||||
"virtualFileSystemWithWatch.ts",
|
||||
"harnessLanguageService.ts",
|
||||
"virtualFileSystemWithWatch.ts",
|
||||
"fourslashRunner.ts",
|
||||
"fourslash.ts",
|
||||
"typeWriter.ts",
|
||||
|
|
|
@ -1,29 +1,34 @@
|
|||
/// <reference path="..\harness.ts" />
|
||||
/// <reference path="..\virtualFileSystem.ts" />
|
||||
/// <reference path="../harness.ts" />
|
||||
/// <reference path="../vfs.ts" />
|
||||
/// <reference path="../compiler.ts" />
|
||||
|
||||
namespace ts {
|
||||
const testContentsJson = createMapFromTemplate({
|
||||
"/dev/tsconfig.json": {
|
||||
function createFileSystem(ignoreCase: boolean, cwd: string, root: string) {
|
||||
return new vfs.FileSystem(ignoreCase, {
|
||||
cwd,
|
||||
files: {
|
||||
[root]: {
|
||||
"dev/tsconfig.json": JSON.stringify({
|
||||
extends: "./configs/base",
|
||||
files: [
|
||||
"main.ts",
|
||||
"supplemental.ts"
|
||||
]
|
||||
},
|
||||
"/dev/tsconfig.nostrictnull.json": {
|
||||
}),
|
||||
"dev/tsconfig.nostrictnull.json": JSON.stringify({
|
||||
extends: "./tsconfig",
|
||||
compilerOptions: {
|
||||
strictNullChecks: false
|
||||
}
|
||||
},
|
||||
"/dev/configs/base.json": {
|
||||
}),
|
||||
"dev/configs/base.json": JSON.stringify({
|
||||
compilerOptions: {
|
||||
allowJs: true,
|
||||
noImplicitAny: true,
|
||||
strictNullChecks: true
|
||||
}
|
||||
},
|
||||
"/dev/configs/tests.json": {
|
||||
}),
|
||||
"dev/configs/tests.json": JSON.stringify({
|
||||
compilerOptions: {
|
||||
preserveConstEnums: true,
|
||||
removeComments: false,
|
||||
|
@ -36,57 +41,57 @@ namespace ts {
|
|||
include: [
|
||||
"../tests/**/*.ts"
|
||||
]
|
||||
},
|
||||
"/dev/circular.json": {
|
||||
}),
|
||||
"dev/circular.json": JSON.stringify({
|
||||
extends: "./circular2",
|
||||
compilerOptions: {
|
||||
module: "amd"
|
||||
}
|
||||
},
|
||||
"/dev/circular2.json": {
|
||||
}),
|
||||
"dev/circular2.json": JSON.stringify({
|
||||
extends: "./circular",
|
||||
compilerOptions: {
|
||||
module: "commonjs"
|
||||
}
|
||||
},
|
||||
"/dev/missing.json": {
|
||||
}),
|
||||
"dev/missing.json": JSON.stringify({
|
||||
extends: "./missing2",
|
||||
compilerOptions: {
|
||||
types: []
|
||||
}
|
||||
},
|
||||
"/dev/failure.json": {
|
||||
}),
|
||||
"dev/failure.json": JSON.stringify({
|
||||
extends: "./failure2.json",
|
||||
compilerOptions: {
|
||||
typeRoots: []
|
||||
}
|
||||
},
|
||||
"/dev/failure2.json": {
|
||||
}),
|
||||
"dev/failure2.json": JSON.stringify({
|
||||
excludes: ["*.js"]
|
||||
},
|
||||
"/dev/configs/first.json": {
|
||||
}),
|
||||
"dev/configs/first.json": JSON.stringify({
|
||||
extends: "./base",
|
||||
compilerOptions: {
|
||||
module: "commonjs"
|
||||
},
|
||||
files: ["../main.ts"]
|
||||
},
|
||||
"/dev/configs/second.json": {
|
||||
}),
|
||||
"dev/configs/second.json": JSON.stringify({
|
||||
extends: "./base",
|
||||
compilerOptions: {
|
||||
module: "amd"
|
||||
},
|
||||
include: ["../supplemental.*"]
|
||||
},
|
||||
"/dev/configs/third.json": {
|
||||
}),
|
||||
"dev/configs/third.json": JSON.stringify({
|
||||
extends: "./second",
|
||||
compilerOptions: {
|
||||
// tslint:disable-next-line:no-null-keyword
|
||||
module: null
|
||||
},
|
||||
include: ["../supplemental.*"]
|
||||
},
|
||||
"/dev/configs/fourth.json": {
|
||||
}),
|
||||
"dev/configs/fourth.json": JSON.stringify({
|
||||
extends: "./third",
|
||||
compilerOptions: {
|
||||
module: "system"
|
||||
|
@ -94,23 +99,25 @@ namespace ts {
|
|||
// tslint:disable-next-line:no-null-keyword
|
||||
include: null,
|
||||
files: ["../main.ts"]
|
||||
},
|
||||
"/dev/extends.json": { extends: 42 },
|
||||
"/dev/extends2.json": { extends: "configs/base" },
|
||||
"/dev/main.ts": "",
|
||||
"/dev/supplemental.ts": "",
|
||||
"/dev/tests/unit/spec.ts": "",
|
||||
"/dev/tests/utils.ts": "",
|
||||
"/dev/tests/scenarios/first.json": "",
|
||||
"/dev/tests/baselines/first/output.ts": ""
|
||||
}),
|
||||
"dev/extends.json": JSON.stringify({ extends: 42 }),
|
||||
"dev/extends2.json": JSON.stringify({ extends: "configs/base" }),
|
||||
"dev/main.ts": "",
|
||||
"dev/supplemental.ts": "",
|
||||
"dev/tests/unit/spec.ts": "",
|
||||
"dev/tests/utils.ts": "",
|
||||
"dev/tests/scenarios/first.json": "",
|
||||
"dev/tests/baselines/first/output.ts": ""
|
||||
}
|
||||
}
|
||||
});
|
||||
const testContents = mapEntries(testContentsJson, (k, v) => [k, isString(v) ? v : JSON.stringify(v)]);
|
||||
}
|
||||
|
||||
const caseInsensitiveBasePath = "c:/dev/";
|
||||
const caseInsensitiveHost = new Utils.MockParseConfigHost(caseInsensitiveBasePath, /*useCaseSensitiveFileNames*/ false, mapEntries(testContents, (key, content) => [`c:${key}`, content]));
|
||||
const caseInsensitiveHost = new fakes.ParseConfigHost(createFileSystem(/*ignoreCase*/ true, caseInsensitiveBasePath, "c:/"));
|
||||
|
||||
const caseSensitiveBasePath = "/dev/";
|
||||
const caseSensitiveHost = new Utils.MockParseConfigHost(caseSensitiveBasePath, /*useCaseSensitiveFileNames*/ true, testContents);
|
||||
const caseSensitiveHost = new fakes.ParseConfigHost(createFileSystem(/*ignoreCase*/ false, caseSensitiveBasePath, "/"));
|
||||
|
||||
function verifyDiagnostics(actual: Diagnostic[], expected: {code: number, category: DiagnosticCategory, messageText: string}[]) {
|
||||
assert.isTrue(expected.length === actual.length, `Expected error: ${JSON.stringify(expected)}. Actual error: ${JSON.stringify(actual)}.`);
|
||||
|
@ -124,7 +131,7 @@ namespace ts {
|
|||
}
|
||||
|
||||
describe("configurationExtension", () => {
|
||||
forEach<[string, string, Utils.MockParseConfigHost], void>([
|
||||
forEach<[string, string, fakes.ParseConfigHost], void>([
|
||||
["under a case insensitive host", caseInsensitiveBasePath, caseInsensitiveHost],
|
||||
["under a case sensitive host", caseSensitiveBasePath, caseSensitiveHost]
|
||||
], ([testName, basePath, host]) => {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
/// <reference path="..\harness.ts" />
|
||||
/// <reference path="..\..\compiler\commandLineParser.ts" />
|
||||
/// <reference path="../compiler.ts" />
|
||||
/// <reference path="../vfs.ts" />
|
||||
|
||||
namespace ts {
|
||||
describe("convertCompilerOptionsFromJson", () => {
|
||||
|
@ -31,7 +33,7 @@ namespace ts {
|
|||
const result = parseJsonText(configFileName, fileText);
|
||||
assert(!result.parseDiagnostics.length);
|
||||
assert(!!result.endOfFileToken);
|
||||
const host: ParseConfigHost = new Utils.MockParseConfigHost("/apath/", true, []);
|
||||
const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: "/apath/" }));
|
||||
const { options: actualCompilerOptions, errors: actualParseErrors } = parseJsonSourceFileConfigFileContent(result, host, "/apath/", /*existingOptions*/ undefined, configFileName);
|
||||
expectedResult.compilerOptions.configFilePath = configFileName;
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
/// <reference path="..\harness.ts" />
|
||||
/// <reference path="..\..\compiler\commandLineParser.ts" />
|
||||
/// <reference path="../compiler.ts" />
|
||||
/// <reference path="../vfs.ts" />
|
||||
|
||||
namespace ts {
|
||||
interface ExpectedResult { typeAcquisition: TypeAcquisition; errors: Diagnostic[]; }
|
||||
|
@ -43,7 +45,7 @@ namespace ts {
|
|||
const result = parseJsonText(configFileName, fileText);
|
||||
assert(!result.parseDiagnostics.length);
|
||||
assert(!!result.endOfFileToken);
|
||||
const host: ParseConfigHost = new Utils.MockParseConfigHost("/apath/", true, []);
|
||||
const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: "/apath/" }));
|
||||
const { typeAcquisition: actualTypeAcquisition, errors: actualParseErrors } = parseJsonSourceFileConfigFileContent(result, host, "/apath/", /*existingOptions*/ undefined, configFileName);
|
||||
verifyAcquisition(actualTypeAcquisition, expectedResult);
|
||||
|
||||
|
|
|
@ -1,107 +1,108 @@
|
|||
/// <reference path="..\harness.ts" />
|
||||
/// <reference path="..\virtualFileSystem.ts" />
|
||||
/// <reference path="../harness.ts" />
|
||||
/// <reference path="../compiler.ts" />
|
||||
/// <reference path="../vfs.ts" />
|
||||
|
||||
namespace ts {
|
||||
const caseInsensitiveBasePath = "c:/dev/";
|
||||
const caseInsensitiveTsconfigPath = "c:/dev/tsconfig.json";
|
||||
const caseInsensitiveHost = new Utils.MockParseConfigHost(caseInsensitiveBasePath, /*useCaseSensitiveFileNames*/ false, [
|
||||
"c:/dev/a.ts",
|
||||
"c:/dev/a.d.ts",
|
||||
"c:/dev/a.js",
|
||||
"c:/dev/b.ts",
|
||||
"c:/dev/b.js",
|
||||
"c:/dev/c.d.ts",
|
||||
"c:/dev/z/a.ts",
|
||||
"c:/dev/z/abz.ts",
|
||||
"c:/dev/z/aba.ts",
|
||||
"c:/dev/z/b.ts",
|
||||
"c:/dev/z/bbz.ts",
|
||||
"c:/dev/z/bba.ts",
|
||||
"c:/dev/x/a.ts",
|
||||
"c:/dev/x/aa.ts",
|
||||
"c:/dev/x/b.ts",
|
||||
"c:/dev/x/y/a.ts",
|
||||
"c:/dev/x/y/b.ts",
|
||||
"c:/dev/js/a.js",
|
||||
"c:/dev/js/b.js",
|
||||
"c:/dev/js/d.min.js",
|
||||
"c:/dev/js/ab.min.js",
|
||||
"c:/ext/ext.ts",
|
||||
"c:/ext/b/a..b.ts"
|
||||
]);
|
||||
const caseInsensitiveHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: {
|
||||
"c:/dev/a.ts": "",
|
||||
"c:/dev/a.d.ts": "",
|
||||
"c:/dev/a.js": "",
|
||||
"c:/dev/b.ts": "",
|
||||
"c:/dev/b.js": "",
|
||||
"c:/dev/c.d.ts": "",
|
||||
"c:/dev/z/a.ts": "",
|
||||
"c:/dev/z/abz.ts": "",
|
||||
"c:/dev/z/aba.ts": "",
|
||||
"c:/dev/z/b.ts": "",
|
||||
"c:/dev/z/bbz.ts": "",
|
||||
"c:/dev/z/bba.ts": "",
|
||||
"c:/dev/x/a.ts": "",
|
||||
"c:/dev/x/aa.ts": "",
|
||||
"c:/dev/x/b.ts": "",
|
||||
"c:/dev/x/y/a.ts": "",
|
||||
"c:/dev/x/y/b.ts": "",
|
||||
"c:/dev/js/a.js": "",
|
||||
"c:/dev/js/b.js": "",
|
||||
"c:/dev/js/d.min.js": "",
|
||||
"c:/dev/js/ab.min.js": "",
|
||||
"c:/ext/ext.ts": "",
|
||||
"c:/ext/b/a..b.ts": "",
|
||||
}}));
|
||||
|
||||
const caseSensitiveBasePath = "/dev/";
|
||||
const caseSensitiveHost = new Utils.MockParseConfigHost(caseSensitiveBasePath, /*useCaseSensitiveFileNames*/ true, [
|
||||
"/dev/a.ts",
|
||||
"/dev/a.d.ts",
|
||||
"/dev/a.js",
|
||||
"/dev/b.ts",
|
||||
"/dev/b.js",
|
||||
"/dev/A.ts",
|
||||
"/dev/B.ts",
|
||||
"/dev/c.d.ts",
|
||||
"/dev/z/a.ts",
|
||||
"/dev/z/abz.ts",
|
||||
"/dev/z/aba.ts",
|
||||
"/dev/z/b.ts",
|
||||
"/dev/z/bbz.ts",
|
||||
"/dev/z/bba.ts",
|
||||
"/dev/x/a.ts",
|
||||
"/dev/x/b.ts",
|
||||
"/dev/x/y/a.ts",
|
||||
"/dev/x/y/b.ts",
|
||||
"/dev/q/a/c/b/d.ts",
|
||||
"/dev/js/a.js",
|
||||
"/dev/js/b.js",
|
||||
]);
|
||||
const caseSensitiveHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: caseSensitiveBasePath, files: {
|
||||
"/dev/a.ts": "",
|
||||
"/dev/a.d.ts": "",
|
||||
"/dev/a.js": "",
|
||||
"/dev/b.ts": "",
|
||||
"/dev/b.js": "",
|
||||
"/dev/A.ts": "",
|
||||
"/dev/B.ts": "",
|
||||
"/dev/c.d.ts": "",
|
||||
"/dev/z/a.ts": "",
|
||||
"/dev/z/abz.ts": "",
|
||||
"/dev/z/aba.ts": "",
|
||||
"/dev/z/b.ts": "",
|
||||
"/dev/z/bbz.ts": "",
|
||||
"/dev/z/bba.ts": "",
|
||||
"/dev/x/a.ts": "",
|
||||
"/dev/x/b.ts": "",
|
||||
"/dev/x/y/a.ts": "",
|
||||
"/dev/x/y/b.ts": "",
|
||||
"/dev/q/a/c/b/d.ts": "",
|
||||
"/dev/js/a.js": "",
|
||||
"/dev/js/b.js": "",
|
||||
}}));
|
||||
|
||||
const caseInsensitiveMixedExtensionHost = new Utils.MockParseConfigHost(caseInsensitiveBasePath, /*useCaseSensitiveFileNames*/ false, [
|
||||
"c:/dev/a.ts",
|
||||
"c:/dev/a.d.ts",
|
||||
"c:/dev/a.js",
|
||||
"c:/dev/b.tsx",
|
||||
"c:/dev/b.d.ts",
|
||||
"c:/dev/b.jsx",
|
||||
"c:/dev/c.tsx",
|
||||
"c:/dev/c.js",
|
||||
"c:/dev/d.js",
|
||||
"c:/dev/e.jsx",
|
||||
"c:/dev/f.other"
|
||||
]);
|
||||
const caseInsensitiveMixedExtensionHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: {
|
||||
"c:/dev/a.ts": "",
|
||||
"c:/dev/a.d.ts": "",
|
||||
"c:/dev/a.js": "",
|
||||
"c:/dev/b.tsx": "",
|
||||
"c:/dev/b.d.ts": "",
|
||||
"c:/dev/b.jsx": "",
|
||||
"c:/dev/c.tsx": "",
|
||||
"c:/dev/c.js": "",
|
||||
"c:/dev/d.js": "",
|
||||
"c:/dev/e.jsx": "",
|
||||
"c:/dev/f.other": "",
|
||||
}}));
|
||||
|
||||
const caseInsensitiveCommonFoldersHost = new Utils.MockParseConfigHost(caseInsensitiveBasePath, /*useCaseSensitiveFileNames*/ false, [
|
||||
"c:/dev/a.ts",
|
||||
"c:/dev/a.d.ts",
|
||||
"c:/dev/a.js",
|
||||
"c:/dev/b.ts",
|
||||
"c:/dev/x/a.ts",
|
||||
"c:/dev/node_modules/a.ts",
|
||||
"c:/dev/bower_components/a.ts",
|
||||
"c:/dev/jspm_packages/a.ts"
|
||||
]);
|
||||
const caseInsensitiveCommonFoldersHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: {
|
||||
"c:/dev/a.ts": "",
|
||||
"c:/dev/a.d.ts": "",
|
||||
"c:/dev/a.js": "",
|
||||
"c:/dev/b.ts": "",
|
||||
"c:/dev/x/a.ts": "",
|
||||
"c:/dev/node_modules/a.ts": "",
|
||||
"c:/dev/bower_components/a.ts": "",
|
||||
"c:/dev/jspm_packages/a.ts": "",
|
||||
}}));
|
||||
|
||||
const caseInsensitiveDottedFoldersHost = new Utils.MockParseConfigHost(caseInsensitiveBasePath, /*useCaseSensitiveFileNames*/ false, [
|
||||
"c:/dev/x/d.ts",
|
||||
"c:/dev/x/y/d.ts",
|
||||
"c:/dev/x/y/.e.ts",
|
||||
"c:/dev/x/.y/a.ts",
|
||||
"c:/dev/.z/.b.ts",
|
||||
"c:/dev/.z/c.ts",
|
||||
"c:/dev/w/.u/e.ts",
|
||||
"c:/dev/g.min.js/.g/g.ts"
|
||||
]);
|
||||
const caseInsensitiveDottedFoldersHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: {
|
||||
"c:/dev/x/d.ts": "",
|
||||
"c:/dev/x/y/d.ts": "",
|
||||
"c:/dev/x/y/.e.ts": "",
|
||||
"c:/dev/x/.y/a.ts": "",
|
||||
"c:/dev/.z/.b.ts": "",
|
||||
"c:/dev/.z/c.ts": "",
|
||||
"c:/dev/w/.u/e.ts": "",
|
||||
"c:/dev/g.min.js/.g/g.ts": "",
|
||||
}}));
|
||||
|
||||
const caseInsensitiveOrderingDiffersWithCaseHost = new Utils.MockParseConfigHost(caseInsensitiveBasePath, /*useCaseSensitiveFileNames*/ false, [
|
||||
"c:/dev/xylophone.ts",
|
||||
"c:/dev/Yosemite.ts",
|
||||
"c:/dev/zebra.ts",
|
||||
]);
|
||||
const caseInsensitiveOrderingDiffersWithCaseHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: {
|
||||
"c:/dev/xylophone.ts": "",
|
||||
"c:/dev/Yosemite.ts": "",
|
||||
"c:/dev/zebra.ts": "",
|
||||
}}));
|
||||
|
||||
const caseSensitiveOrderingDiffersWithCaseHost = new Utils.MockParseConfigHost(caseSensitiveBasePath, /*useCaseSensitiveFileNames*/ true, [
|
||||
"/dev/xylophone.ts",
|
||||
"/dev/Yosemite.ts",
|
||||
"/dev/zebra.ts",
|
||||
]);
|
||||
const caseSensitiveOrderingDiffersWithCaseHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: caseSensitiveBasePath, files: {
|
||||
"/dev/xylophone.ts": "",
|
||||
"/dev/Yosemite.ts": "",
|
||||
"/dev/zebra.ts": "",
|
||||
}}));
|
||||
|
||||
function assertParsed(actual: ParsedCommandLine, expected: ParsedCommandLine): void {
|
||||
assert.deepEqual(actual.fileNames, expected.fileNames);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/// <reference path="..\..\..\src\harness\harness.ts" />
|
||||
/// <reference path="..\..\..\src\harness\virtualFileSystem.ts" />
|
||||
/// <reference path="..\..\..\src\harness\vfs.ts" />
|
||||
|
||||
|
||||
namespace ts {
|
||||
|
|
292
src/harness/unittests/paths.ts
Normal file
292
src/harness/unittests/paths.ts
Normal file
|
@ -0,0 +1,292 @@
|
|||
describe("core paths", () => {
|
||||
it("normalizeSlashes", () => {
|
||||
assert.strictEqual(ts.normalizeSlashes("a"), "a");
|
||||
assert.strictEqual(ts.normalizeSlashes("a/b"), "a/b");
|
||||
assert.strictEqual(ts.normalizeSlashes("a\\b"), "a/b");
|
||||
assert.strictEqual(ts.normalizeSlashes("\\\\server\\path"), "//server/path");
|
||||
});
|
||||
it("getRootLength", () => {
|
||||
assert.strictEqual(ts.getRootLength("a"), 0);
|
||||
assert.strictEqual(ts.getRootLength("/"), 1);
|
||||
assert.strictEqual(ts.getRootLength("/path"), 1);
|
||||
assert.strictEqual(ts.getRootLength("c:"), 2);
|
||||
assert.strictEqual(ts.getRootLength("c:d"), 0);
|
||||
assert.strictEqual(ts.getRootLength("c:/"), 3);
|
||||
assert.strictEqual(ts.getRootLength("c:\\"), 3);
|
||||
assert.strictEqual(ts.getRootLength("//server"), 8);
|
||||
assert.strictEqual(ts.getRootLength("//server/share"), 9);
|
||||
assert.strictEqual(ts.getRootLength("\\\\server"), 8);
|
||||
assert.strictEqual(ts.getRootLength("\\\\server\\share"), 9);
|
||||
assert.strictEqual(ts.getRootLength("file:///"), 8);
|
||||
assert.strictEqual(ts.getRootLength("file:///path"), 8);
|
||||
assert.strictEqual(ts.getRootLength("file:///c:"), 10);
|
||||
assert.strictEqual(ts.getRootLength("file:///c:d"), 8);
|
||||
assert.strictEqual(ts.getRootLength("file:///c:/path"), 11);
|
||||
assert.strictEqual(ts.getRootLength("file:///c%3a"), 12);
|
||||
assert.strictEqual(ts.getRootLength("file:///c%3ad"), 8);
|
||||
assert.strictEqual(ts.getRootLength("file:///c%3a/path"), 13);
|
||||
assert.strictEqual(ts.getRootLength("file:///c%3A"), 12);
|
||||
assert.strictEqual(ts.getRootLength("file:///c%3Ad"), 8);
|
||||
assert.strictEqual(ts.getRootLength("file:///c%3A/path"), 13);
|
||||
assert.strictEqual(ts.getRootLength("file://localhost"), 16);
|
||||
assert.strictEqual(ts.getRootLength("file://localhost/"), 17);
|
||||
assert.strictEqual(ts.getRootLength("file://localhost/path"), 17);
|
||||
assert.strictEqual(ts.getRootLength("file://localhost/c:"), 19);
|
||||
assert.strictEqual(ts.getRootLength("file://localhost/c:d"), 17);
|
||||
assert.strictEqual(ts.getRootLength("file://localhost/c:/path"), 20);
|
||||
assert.strictEqual(ts.getRootLength("file://localhost/c%3a"), 21);
|
||||
assert.strictEqual(ts.getRootLength("file://localhost/c%3ad"), 17);
|
||||
assert.strictEqual(ts.getRootLength("file://localhost/c%3a/path"), 22);
|
||||
assert.strictEqual(ts.getRootLength("file://localhost/c%3A"), 21);
|
||||
assert.strictEqual(ts.getRootLength("file://localhost/c%3Ad"), 17);
|
||||
assert.strictEqual(ts.getRootLength("file://localhost/c%3A/path"), 22);
|
||||
assert.strictEqual(ts.getRootLength("file://server"), 13);
|
||||
assert.strictEqual(ts.getRootLength("file://server/"), 14);
|
||||
assert.strictEqual(ts.getRootLength("file://server/path"), 14);
|
||||
assert.strictEqual(ts.getRootLength("file://server/c:"), 14);
|
||||
assert.strictEqual(ts.getRootLength("file://server/c:d"), 14);
|
||||
assert.strictEqual(ts.getRootLength("file://server/c:/d"), 14);
|
||||
assert.strictEqual(ts.getRootLength("file://server/c%3a"), 14);
|
||||
assert.strictEqual(ts.getRootLength("file://server/c%3ad"), 14);
|
||||
assert.strictEqual(ts.getRootLength("file://server/c%3a/d"), 14);
|
||||
assert.strictEqual(ts.getRootLength("file://server/c%3A"), 14);
|
||||
assert.strictEqual(ts.getRootLength("file://server/c%3Ad"), 14);
|
||||
assert.strictEqual(ts.getRootLength("file://server/c%3A/d"), 14);
|
||||
assert.strictEqual(ts.getRootLength("http://server"), 13);
|
||||
assert.strictEqual(ts.getRootLength("http://server/path"), 14);
|
||||
});
|
||||
it("isUrl", () => {
|
||||
assert.isFalse(ts.isUrl("a"));
|
||||
assert.isFalse(ts.isUrl("/"));
|
||||
assert.isFalse(ts.isUrl("c:"));
|
||||
assert.isFalse(ts.isUrl("c:d"));
|
||||
assert.isFalse(ts.isUrl("c:/"));
|
||||
assert.isFalse(ts.isUrl("c:\\"));
|
||||
assert.isFalse(ts.isUrl("//server"));
|
||||
assert.isFalse(ts.isUrl("//server/share"));
|
||||
assert.isFalse(ts.isUrl("\\\\server"));
|
||||
assert.isFalse(ts.isUrl("\\\\server\\share"));
|
||||
assert.isTrue(ts.isUrl("file:///path"));
|
||||
assert.isTrue(ts.isUrl("file:///c:"));
|
||||
assert.isTrue(ts.isUrl("file:///c:d"));
|
||||
assert.isTrue(ts.isUrl("file:///c:/path"));
|
||||
assert.isTrue(ts.isUrl("file://server"));
|
||||
assert.isTrue(ts.isUrl("file://server/path"));
|
||||
assert.isTrue(ts.isUrl("http://server"));
|
||||
assert.isTrue(ts.isUrl("http://server/path"));
|
||||
});
|
||||
it("isRootedDiskPath", () => {
|
||||
assert.isFalse(ts.isRootedDiskPath("a"));
|
||||
assert.isTrue(ts.isRootedDiskPath("/"));
|
||||
assert.isTrue(ts.isRootedDiskPath("c:"));
|
||||
assert.isFalse(ts.isRootedDiskPath("c:d"));
|
||||
assert.isTrue(ts.isRootedDiskPath("c:/"));
|
||||
assert.isTrue(ts.isRootedDiskPath("c:\\"));
|
||||
assert.isTrue(ts.isRootedDiskPath("//server"));
|
||||
assert.isTrue(ts.isRootedDiskPath("//server/share"));
|
||||
assert.isTrue(ts.isRootedDiskPath("\\\\server"));
|
||||
assert.isTrue(ts.isRootedDiskPath("\\\\server\\share"));
|
||||
assert.isFalse(ts.isRootedDiskPath("file:///path"));
|
||||
assert.isFalse(ts.isRootedDiskPath("file:///c:"));
|
||||
assert.isFalse(ts.isRootedDiskPath("file:///c:d"));
|
||||
assert.isFalse(ts.isRootedDiskPath("file:///c:/path"));
|
||||
assert.isFalse(ts.isRootedDiskPath("file://server"));
|
||||
assert.isFalse(ts.isRootedDiskPath("file://server/path"));
|
||||
assert.isFalse(ts.isRootedDiskPath("http://server"));
|
||||
assert.isFalse(ts.isRootedDiskPath("http://server/path"));
|
||||
});
|
||||
it("getDirectoryPath", () => {
|
||||
assert.strictEqual(ts.getDirectoryPath(""), "");
|
||||
assert.strictEqual(ts.getDirectoryPath("a"), "");
|
||||
assert.strictEqual(ts.getDirectoryPath("a/b"), "a");
|
||||
assert.strictEqual(ts.getDirectoryPath("/"), "/");
|
||||
assert.strictEqual(ts.getDirectoryPath("/a"), "/");
|
||||
assert.strictEqual(ts.getDirectoryPath("/a/"), "/");
|
||||
assert.strictEqual(ts.getDirectoryPath("/a/b"), "/a");
|
||||
assert.strictEqual(ts.getDirectoryPath("/a/b/"), "/a");
|
||||
assert.strictEqual(ts.getDirectoryPath("c:"), "c:");
|
||||
assert.strictEqual(ts.getDirectoryPath("c:d"), "");
|
||||
assert.strictEqual(ts.getDirectoryPath("c:/"), "c:/");
|
||||
assert.strictEqual(ts.getDirectoryPath("c:/path"), "c:/");
|
||||
assert.strictEqual(ts.getDirectoryPath("c:/path/"), "c:/");
|
||||
assert.strictEqual(ts.getDirectoryPath("//server"), "//server");
|
||||
assert.strictEqual(ts.getDirectoryPath("//server/"), "//server/");
|
||||
assert.strictEqual(ts.getDirectoryPath("//server/share"), "//server/");
|
||||
assert.strictEqual(ts.getDirectoryPath("//server/share/"), "//server/");
|
||||
assert.strictEqual(ts.getDirectoryPath("\\\\server"), "//server");
|
||||
assert.strictEqual(ts.getDirectoryPath("\\\\server\\"), "//server/");
|
||||
assert.strictEqual(ts.getDirectoryPath("\\\\server\\share"), "//server/");
|
||||
assert.strictEqual(ts.getDirectoryPath("\\\\server\\share\\"), "//server/");
|
||||
assert.strictEqual(ts.getDirectoryPath("file:///"), "file:///");
|
||||
assert.strictEqual(ts.getDirectoryPath("file:///path"), "file:///");
|
||||
assert.strictEqual(ts.getDirectoryPath("file:///path/"), "file:///");
|
||||
assert.strictEqual(ts.getDirectoryPath("file:///c:"), "file:///c:");
|
||||
assert.strictEqual(ts.getDirectoryPath("file:///c:d"), "file:///");
|
||||
assert.strictEqual(ts.getDirectoryPath("file:///c:/"), "file:///c:/");
|
||||
assert.strictEqual(ts.getDirectoryPath("file:///c:/path"), "file:///c:/");
|
||||
assert.strictEqual(ts.getDirectoryPath("file:///c:/path/"), "file:///c:/");
|
||||
assert.strictEqual(ts.getDirectoryPath("file://server"), "file://server");
|
||||
assert.strictEqual(ts.getDirectoryPath("file://server/"), "file://server/");
|
||||
assert.strictEqual(ts.getDirectoryPath("file://server/path"), "file://server/");
|
||||
assert.strictEqual(ts.getDirectoryPath("file://server/path/"), "file://server/");
|
||||
assert.strictEqual(ts.getDirectoryPath("http://server"), "http://server");
|
||||
assert.strictEqual(ts.getDirectoryPath("http://server/"), "http://server/");
|
||||
assert.strictEqual(ts.getDirectoryPath("http://server/path"), "http://server/");
|
||||
assert.strictEqual(ts.getDirectoryPath("http://server/path/"), "http://server/");
|
||||
});
|
||||
it("getBaseFileName", () => {
|
||||
assert.strictEqual(ts.getBaseFileName(""), "");
|
||||
assert.strictEqual(ts.getBaseFileName("a"), "a");
|
||||
assert.strictEqual(ts.getBaseFileName("a/"), "a");
|
||||
assert.strictEqual(ts.getBaseFileName("/"), "");
|
||||
assert.strictEqual(ts.getBaseFileName("/a"), "a");
|
||||
assert.strictEqual(ts.getBaseFileName("/a/"), "a");
|
||||
assert.strictEqual(ts.getBaseFileName("/a/b"), "b");
|
||||
assert.strictEqual(ts.getBaseFileName("c:"), "");
|
||||
assert.strictEqual(ts.getBaseFileName("c:d"), "c:d");
|
||||
assert.strictEqual(ts.getBaseFileName("c:/"), "");
|
||||
assert.strictEqual(ts.getBaseFileName("c:\\"), "");
|
||||
assert.strictEqual(ts.getBaseFileName("c:/path"), "path");
|
||||
assert.strictEqual(ts.getBaseFileName("c:/path/"), "path");
|
||||
assert.strictEqual(ts.getBaseFileName("//server"), "");
|
||||
assert.strictEqual(ts.getBaseFileName("//server/"), "");
|
||||
assert.strictEqual(ts.getBaseFileName("//server/share"), "share");
|
||||
assert.strictEqual(ts.getBaseFileName("//server/share/"), "share");
|
||||
assert.strictEqual(ts.getBaseFileName("file:///"), "");
|
||||
assert.strictEqual(ts.getBaseFileName("file:///path"), "path");
|
||||
assert.strictEqual(ts.getBaseFileName("file:///path/"), "path");
|
||||
assert.strictEqual(ts.getBaseFileName("file:///c:"), "");
|
||||
assert.strictEqual(ts.getBaseFileName("file:///c:/"), "");
|
||||
assert.strictEqual(ts.getBaseFileName("file:///c:d"), "c:d");
|
||||
assert.strictEqual(ts.getBaseFileName("file:///c:/d"), "d");
|
||||
assert.strictEqual(ts.getBaseFileName("file:///c:/d/"), "d");
|
||||
assert.strictEqual(ts.getBaseFileName("http://server"), "");
|
||||
assert.strictEqual(ts.getBaseFileName("http://server/"), "");
|
||||
assert.strictEqual(ts.getBaseFileName("http://server/a"), "a");
|
||||
assert.strictEqual(ts.getBaseFileName("http://server/a/"), "a");
|
||||
assert.strictEqual(ts.getBaseFileName("/path/a.ext", ".ext", /*ignoreCase*/ false), "a");
|
||||
assert.strictEqual(ts.getBaseFileName("/path/a.ext", ".EXT", /*ignoreCase*/ true), "a");
|
||||
assert.strictEqual(ts.getBaseFileName("/path/a.ext", "ext", /*ignoreCase*/ false), "a");
|
||||
assert.strictEqual(ts.getBaseFileName("/path/a.b", ".ext", /*ignoreCase*/ false), "a.b");
|
||||
assert.strictEqual(ts.getBaseFileName("/path/a.b", [".b", ".c"], /*ignoreCase*/ false), "a");
|
||||
assert.strictEqual(ts.getBaseFileName("/path/a.c", [".b", ".c"], /*ignoreCase*/ false), "a");
|
||||
assert.strictEqual(ts.getBaseFileName("/path/a.d", [".b", ".c"], /*ignoreCase*/ false), "a.d");
|
||||
});
|
||||
it("getAnyExtensionFromPath", () => {
|
||||
assert.strictEqual(ts.getAnyExtensionFromPath(""), "");
|
||||
assert.strictEqual(ts.getAnyExtensionFromPath(".ext"), ".ext");
|
||||
assert.strictEqual(ts.getAnyExtensionFromPath("a.ext"), ".ext");
|
||||
assert.strictEqual(ts.getAnyExtensionFromPath("/a.ext"), ".ext");
|
||||
assert.strictEqual(ts.getAnyExtensionFromPath("a.ext/"), ".ext");
|
||||
assert.strictEqual(ts.getAnyExtensionFromPath("a.ext", ".ext", /*ignoreCase*/ false), ".ext");
|
||||
assert.strictEqual(ts.getAnyExtensionFromPath("a.ext", ".EXT", /*ignoreCase*/ true), ".ext");
|
||||
assert.strictEqual(ts.getAnyExtensionFromPath("a.ext", "ext", /*ignoreCase*/ false), ".ext");
|
||||
assert.strictEqual(ts.getAnyExtensionFromPath("a.b", ".ext", /*ignoreCase*/ false), "");
|
||||
assert.strictEqual(ts.getAnyExtensionFromPath("a.b", [".b", ".c"], /*ignoreCase*/ false), ".b");
|
||||
assert.strictEqual(ts.getAnyExtensionFromPath("a.c", [".b", ".c"], /*ignoreCase*/ false), ".c");
|
||||
assert.strictEqual(ts.getAnyExtensionFromPath("a.d", [".b", ".c"], /*ignoreCase*/ false), "");
|
||||
});
|
||||
it("getPathComponents", () => {
|
||||
assert.deepEqual(ts.getPathComponents(""), [""]);
|
||||
assert.deepEqual(ts.getPathComponents("a"), ["", "a"]);
|
||||
assert.deepEqual(ts.getPathComponents("./a"), ["", ".", "a"]);
|
||||
assert.deepEqual(ts.getPathComponents("/"), ["/"]);
|
||||
assert.deepEqual(ts.getPathComponents("/a"), ["/", "a"]);
|
||||
assert.deepEqual(ts.getPathComponents("/a/"), ["/", "a"]);
|
||||
assert.deepEqual(ts.getPathComponents("c:"), ["c:"]);
|
||||
assert.deepEqual(ts.getPathComponents("c:d"), ["", "c:d"]);
|
||||
assert.deepEqual(ts.getPathComponents("c:/"), ["c:/"]);
|
||||
assert.deepEqual(ts.getPathComponents("c:/path"), ["c:/", "path"]);
|
||||
assert.deepEqual(ts.getPathComponents("//server"), ["//server"]);
|
||||
assert.deepEqual(ts.getPathComponents("//server/"), ["//server/"]);
|
||||
assert.deepEqual(ts.getPathComponents("//server/share"), ["//server/", "share"]);
|
||||
assert.deepEqual(ts.getPathComponents("file:///"), ["file:///"]);
|
||||
assert.deepEqual(ts.getPathComponents("file:///path"), ["file:///", "path"]);
|
||||
assert.deepEqual(ts.getPathComponents("file:///c:"), ["file:///c:"]);
|
||||
assert.deepEqual(ts.getPathComponents("file:///c:d"), ["file:///", "c:d"]);
|
||||
assert.deepEqual(ts.getPathComponents("file:///c:/"), ["file:///c:/"]);
|
||||
assert.deepEqual(ts.getPathComponents("file:///c:/path"), ["file:///c:/", "path"]);
|
||||
assert.deepEqual(ts.getPathComponents("file://server"), ["file://server"]);
|
||||
assert.deepEqual(ts.getPathComponents("file://server/"), ["file://server/"]);
|
||||
assert.deepEqual(ts.getPathComponents("file://server/path"), ["file://server/", "path"]);
|
||||
assert.deepEqual(ts.getPathComponents("http://server"), ["http://server"]);
|
||||
assert.deepEqual(ts.getPathComponents("http://server/"), ["http://server/"]);
|
||||
assert.deepEqual(ts.getPathComponents("http://server/path"), ["http://server/", "path"]);
|
||||
});
|
||||
it("reducePathComponents", () => {
|
||||
assert.deepEqual(ts.reducePathComponents([]), []);
|
||||
assert.deepEqual(ts.reducePathComponents([""]), [""]);
|
||||
assert.deepEqual(ts.reducePathComponents(["", "."]), [""]);
|
||||
assert.deepEqual(ts.reducePathComponents(["", ".", "a"]), ["", "a"]);
|
||||
assert.deepEqual(ts.reducePathComponents(["", "a", "."]), ["", "a"]);
|
||||
assert.deepEqual(ts.reducePathComponents(["", ".."]), ["", ".."]);
|
||||
assert.deepEqual(ts.reducePathComponents(["", "..", ".."]), ["", "..", ".."]);
|
||||
assert.deepEqual(ts.reducePathComponents(["", "..", ".", ".."]), ["", "..", ".."]);
|
||||
assert.deepEqual(ts.reducePathComponents(["", "a", ".."]), [""]);
|
||||
assert.deepEqual(ts.reducePathComponents(["", "..", "a"]), ["", "..", "a"]);
|
||||
assert.deepEqual(ts.reducePathComponents(["/"]), ["/"]);
|
||||
assert.deepEqual(ts.reducePathComponents(["/", "."]), ["/"]);
|
||||
assert.deepEqual(ts.reducePathComponents(["/", ".."]), ["/"]);
|
||||
assert.deepEqual(ts.reducePathComponents(["/", "a", ".."]), ["/"]);
|
||||
});
|
||||
it("combinePaths", () => {
|
||||
assert.strictEqual(ts.combinePaths("/", "/node_modules/@types"), "/node_modules/@types");
|
||||
assert.strictEqual(ts.combinePaths("/a/..", ""), "/a/..");
|
||||
assert.strictEqual(ts.combinePaths("/a/..", "b"), "/a/../b");
|
||||
assert.strictEqual(ts.combinePaths("/a/..", "b/"), "/a/../b/");
|
||||
assert.strictEqual(ts.combinePaths("/a/..", "/"), "/");
|
||||
assert.strictEqual(ts.combinePaths("/a/..", "/b"), "/b");
|
||||
});
|
||||
it("resolvePath", () => {
|
||||
assert.strictEqual(ts.resolvePath(""), "");
|
||||
assert.strictEqual(ts.resolvePath("."), "");
|
||||
assert.strictEqual(ts.resolvePath("./"), "");
|
||||
assert.strictEqual(ts.resolvePath(".."), "..");
|
||||
assert.strictEqual(ts.resolvePath("../"), "../");
|
||||
assert.strictEqual(ts.resolvePath("/"), "/");
|
||||
assert.strictEqual(ts.resolvePath("/."), "/");
|
||||
assert.strictEqual(ts.resolvePath("/./"), "/");
|
||||
assert.strictEqual(ts.resolvePath("/../"), "/");
|
||||
assert.strictEqual(ts.resolvePath("/a"), "/a");
|
||||
assert.strictEqual(ts.resolvePath("/a/"), "/a/");
|
||||
assert.strictEqual(ts.resolvePath("/a/."), "/a");
|
||||
assert.strictEqual(ts.resolvePath("/a/./"), "/a/");
|
||||
assert.strictEqual(ts.resolvePath("/a/./b"), "/a/b");
|
||||
assert.strictEqual(ts.resolvePath("/a/./b/"), "/a/b/");
|
||||
assert.strictEqual(ts.resolvePath("/a/.."), "/");
|
||||
assert.strictEqual(ts.resolvePath("/a/../"), "/");
|
||||
assert.strictEqual(ts.resolvePath("/a/../b"), "/b");
|
||||
assert.strictEqual(ts.resolvePath("/a/../b/"), "/b/");
|
||||
assert.strictEqual(ts.resolvePath("/a/..", "b"), "/b");
|
||||
assert.strictEqual(ts.resolvePath("/a/..", "/"), "/");
|
||||
assert.strictEqual(ts.resolvePath("/a/..", "b/"), "/b/");
|
||||
assert.strictEqual(ts.resolvePath("/a/..", "/b"), "/b");
|
||||
assert.strictEqual(ts.resolvePath("/a/.", "b"), "/a/b");
|
||||
assert.strictEqual(ts.resolvePath("/a/.", "."), "/a");
|
||||
assert.strictEqual(ts.resolvePath("a", "b", "c"), "a/b/c");
|
||||
assert.strictEqual(ts.resolvePath("a", "b", "/c"), "/c");
|
||||
assert.strictEqual(ts.resolvePath("a", "b", "../c"), "a/c");
|
||||
});
|
||||
it("getPathRelativeTo", () => {
|
||||
assert.strictEqual(ts.getRelativePath("/", "/", /*ignoreCase*/ false), "");
|
||||
assert.strictEqual(ts.getRelativePath("/a", "/a", /*ignoreCase*/ false), "");
|
||||
assert.strictEqual(ts.getRelativePath("/a/", "/a", /*ignoreCase*/ false), "");
|
||||
assert.strictEqual(ts.getRelativePath("/a", "/", /*ignoreCase*/ false), "..");
|
||||
assert.strictEqual(ts.getRelativePath("/a", "/b", /*ignoreCase*/ false), "../b");
|
||||
assert.strictEqual(ts.getRelativePath("/a/b", "/b", /*ignoreCase*/ false), "../../b");
|
||||
assert.strictEqual(ts.getRelativePath("/a/b/c", "/b", /*ignoreCase*/ false), "../../../b");
|
||||
assert.strictEqual(ts.getRelativePath("/a/b/c", "/b/c", /*ignoreCase*/ false), "../../../b/c");
|
||||
assert.strictEqual(ts.getRelativePath("/a/b/c", "/a/b", /*ignoreCase*/ false), "..");
|
||||
assert.strictEqual(ts.getRelativePath("c:", "d:", /*ignoreCase*/ false), "d:/");
|
||||
assert.strictEqual(ts.getRelativePath("file:///", "file:///", /*ignoreCase*/ false), "");
|
||||
assert.strictEqual(ts.getRelativePath("file:///a", "file:///a", /*ignoreCase*/ false), "");
|
||||
assert.strictEqual(ts.getRelativePath("file:///a/", "file:///a", /*ignoreCase*/ false), "");
|
||||
assert.strictEqual(ts.getRelativePath("file:///a", "file:///", /*ignoreCase*/ false), "..");
|
||||
assert.strictEqual(ts.getRelativePath("file:///a", "file:///b", /*ignoreCase*/ false), "../b");
|
||||
assert.strictEqual(ts.getRelativePath("file:///a/b", "file:///b", /*ignoreCase*/ false), "../../b");
|
||||
assert.strictEqual(ts.getRelativePath("file:///a/b/c", "file:///b", /*ignoreCase*/ false), "../../../b");
|
||||
assert.strictEqual(ts.getRelativePath("file:///a/b/c", "file:///b/c", /*ignoreCase*/ false), "../../../b/c");
|
||||
assert.strictEqual(ts.getRelativePath("file:///a/b/c", "file:///a/b", /*ignoreCase*/ false), "..");
|
||||
assert.strictEqual(ts.getRelativePath("file:///c:", "file:///d:", /*ignoreCase*/ false), "file:///d:/");
|
||||
});
|
||||
});
|
|
@ -1,4 +1,6 @@
|
|||
/// <reference path="..\harness.ts" />
|
||||
/// <reference path="../documents.ts" />
|
||||
/// <reference path="../vfs.ts" />
|
||||
|
||||
namespace ts {
|
||||
function verifyMissingFilePaths(missingPaths: ReadonlyArray<Path>, expected: ReadonlyArray<string>) {
|
||||
|
@ -22,33 +24,25 @@ namespace ts {
|
|||
const emptyFileName = "empty.ts";
|
||||
const emptyFileRelativePath = "./" + emptyFileName;
|
||||
|
||||
const emptyFile: Harness.Compiler.TestFile = {
|
||||
unitName: emptyFileName,
|
||||
content: ""
|
||||
};
|
||||
const emptyFile = new documents.TextDocument(emptyFileName, "");
|
||||
|
||||
const referenceFileName = "reference.ts";
|
||||
const referenceFileRelativePath = "./" + referenceFileName;
|
||||
|
||||
const referenceFile: Harness.Compiler.TestFile = {
|
||||
unitName: referenceFileName,
|
||||
content:
|
||||
const referenceFile = new documents.TextDocument(referenceFileName,
|
||||
"/// <reference path=\"d:/imaginary/nonexistent1.ts\"/>\n" + // Absolute
|
||||
"/// <reference path=\"./nonexistent2.ts\"/>\n" + // Relative
|
||||
"/// <reference path=\"nonexistent3.ts\"/>\n" + // Unqualified
|
||||
"/// <reference path=\"nonexistent4\"/>\n" // No extension
|
||||
};
|
||||
|
||||
const testCompilerHost = Harness.Compiler.createCompilerHost(
|
||||
/*inputFiles*/ [emptyFile, referenceFile],
|
||||
/*writeFile*/ undefined,
|
||||
/*scriptTarget*/ undefined,
|
||||
/*useCaseSensitiveFileNames*/ false,
|
||||
/*currentDirectory*/ "d:\\pretend\\",
|
||||
/*newLineKind*/ NewLineKind.LineFeed,
|
||||
/*libFiles*/ undefined
|
||||
);
|
||||
|
||||
const testCompilerHost = new fakes.CompilerHost(
|
||||
vfs.createFromFileSystem(
|
||||
Harness.IO,
|
||||
/*ignoreCase*/ true,
|
||||
{ documents: [emptyFile, referenceFile], cwd: "d:\\pretend\\" }),
|
||||
{ newLine: NewLineKind.LineFeed });
|
||||
|
||||
it("handles no missing root files", () => {
|
||||
const program = createProgram([emptyFileRelativePath], options, testCompilerHost);
|
||||
const missing = program.getMissingFilePaths();
|
||||
|
|
|
@ -14,13 +14,12 @@ describe("Public APIs", () => {
|
|||
});
|
||||
|
||||
it("should compile", () => {
|
||||
const testFile: Harness.Compiler.TestFile = {
|
||||
unitName: builtFile,
|
||||
content: fileContent
|
||||
};
|
||||
const inputFiles = [testFile];
|
||||
const output = Harness.Compiler.compileFiles(inputFiles, [], /*harnessSettings*/ undefined, /*options*/ {}, /*currentDirectory*/ undefined);
|
||||
assert(!output.result.errors || !output.result.errors.length, Harness.Compiler.minimalDiagnosticsToString(output.result.errors, /*pretty*/ true));
|
||||
const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false);
|
||||
fs.linkSync(`${vfs.builtFolder}/${fileName}`, `${vfs.srcFolder}/${fileName}`);
|
||||
const sys = new fakes.System(fs);
|
||||
const host = new fakes.CompilerHost(sys);
|
||||
const result = compiler.compileFiles(host, [`${vfs.srcFolder}/${fileName}`], {});
|
||||
assert(!result.diagnostics || !result.diagnostics.length, Harness.Compiler.minimalDiagnosticsToString(result.diagnostics, /*pretty*/ true));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -4,17 +4,13 @@ namespace ts {
|
|||
describe("Symbol Walker", () => {
|
||||
function test(description: string, source: string, verifier: (file: SourceFile, checker: TypeChecker) => void) {
|
||||
it(description, () => {
|
||||
let {result} = Harness.Compiler.compileFiles([{
|
||||
const result = Harness.Compiler.compileFiles([{
|
||||
unitName: "main.ts",
|
||||
content: source
|
||||
}], [], {}, {}, "/");
|
||||
let file = result.program.getSourceFile("main.ts");
|
||||
let checker = result.program.getTypeChecker();
|
||||
const file = result.program.getSourceFile("main.ts");
|
||||
const checker = result.program.getTypeChecker();
|
||||
verifier(file, checker);
|
||||
|
||||
result = undefined;
|
||||
file = undefined;
|
||||
checker = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
/// <reference path="..\harness.ts" />
|
||||
/// <reference path="..\..\compiler\commandLineParser.ts" />
|
||||
/// <reference path="../compiler.ts" />
|
||||
/// <reference path="../vfs.ts" />
|
||||
|
||||
namespace ts {
|
||||
describe("parseConfigFileTextToJson", () => {
|
||||
|
@ -31,13 +33,15 @@ namespace ts {
|
|||
|
||||
function getParsedCommandJson(jsonText: string, configFileName: string, basePath: string, allFileList: string[]) {
|
||||
const parsed = parseConfigFileTextToJson(configFileName, jsonText);
|
||||
const host: ParseConfigHost = new Utils.MockParseConfigHost(basePath, true, allFileList);
|
||||
const files = allFileList.reduce((files, value) => (files[value] = "", files), {} as vfs.FileSet);
|
||||
const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: basePath, files: { "/": {}, ...files } }));
|
||||
return parseJsonConfigFileContent(parsed.config, host, basePath, /*existingOptions*/ undefined, configFileName);
|
||||
}
|
||||
|
||||
function getParsedCommandJsonNode(jsonText: string, configFileName: string, basePath: string, allFileList: string[]) {
|
||||
const parsed = parseJsonText(configFileName, jsonText);
|
||||
const host: ParseConfigHost = new Utils.MockParseConfigHost(basePath, true, allFileList);
|
||||
const files = allFileList.reduce((files, value) => (files[value] = "", files), {} as vfs.FileSet);
|
||||
const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: basePath, files: { "/": {}, ...files } }));
|
||||
return parseJsonSourceFileConfigFileContent(parsed, host, basePath, /*existingOptions*/ undefined, configFileName);
|
||||
}
|
||||
|
||||
|
|
114
src/harness/utils.ts
Normal file
114
src/harness/utils.ts
Normal file
|
@ -0,0 +1,114 @@
|
|||
/**
|
||||
* Common utilities
|
||||
*/
|
||||
namespace utils {
|
||||
const leadingCommentRegExp = /^(\s*\/\*[^]*?\*\/\s*|\s*\/\/[^\r\n\u2028\u2029]*[\r\n\u2028\u2029]*)+/;
|
||||
const trailingCommentRegExp = /(\s*\/\*[^]*?\*\/\s*|\s*\/\/[^\r\n\u2028\u2029]*[\r\n\u2028\u2029]*)+$/;
|
||||
const leadingAndTrailingCommentRegExp = /^(\s*\/\*[^]*?\*\/\s*|\s*\/\/[^\r\n\u2028\u2029]*[\r\n\u2028\u2029]*)+|(\s*\/\*[^]*?\*\/\s*|\s*\/\/[^\r\n\u2028\u2029]*[\r\n\u2028\u2029]*)+$/g;
|
||||
const allCommentRegExp = /(['"])(?:(?!\1).|\\[^])*\1|(\/\*[^]*?\*\/|\/\/[^\r\n\u2028\u2029]*[\r\n\u2028\u2029]*)/g;
|
||||
|
||||
export const enum CommentRemoval {
|
||||
leading,
|
||||
trailing,
|
||||
leadingAndTrailing,
|
||||
all
|
||||
}
|
||||
|
||||
export function removeComments(text: string, removal: CommentRemoval) {
|
||||
switch (removal) {
|
||||
case CommentRemoval.leading:
|
||||
return text.replace(leadingCommentRegExp, "");
|
||||
case CommentRemoval.trailing:
|
||||
return text.replace(trailingCommentRegExp, "");
|
||||
case CommentRemoval.leadingAndTrailing:
|
||||
return text.replace(leadingAndTrailingCommentRegExp, "");
|
||||
case CommentRemoval.all:
|
||||
return text.replace(allCommentRegExp, (match, quote) => quote ? match : "");
|
||||
}
|
||||
}
|
||||
|
||||
const testPathPrefixRegExp = /(?:(file:\/{3})|\/)\.(ts|lib|src)\//g;
|
||||
export function removeTestPathPrefixes(text: string, retainTrailingDirectorySeparator?: boolean) {
|
||||
return text !== undefined ? text.replace(testPathPrefixRegExp, (_, scheme) => scheme || (retainTrailingDirectorySeparator ? "/" : "")) : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes leading indentation from a template literal string.
|
||||
*/
|
||||
export function dedent(array: TemplateStringsArray, ...args: any[]) {
|
||||
let text = array[0];
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
text += args[i];
|
||||
text += array[i + 1];
|
||||
}
|
||||
|
||||
const lineTerminatorRegExp = /\r\n?|\n/g;
|
||||
const lines: string[] = [];
|
||||
const lineTerminators: string[] = [];
|
||||
let match: RegExpExecArray | null;
|
||||
let lineStart = 0;
|
||||
while (match = lineTerminatorRegExp.exec(text)) {
|
||||
if (lineStart !== match.index || lines.length > 0) {
|
||||
lines.push(text.slice(lineStart, match.index));
|
||||
lineTerminators.push(match[0]);
|
||||
}
|
||||
lineStart = match.index + match[0].length;
|
||||
}
|
||||
|
||||
if (lineStart < text.length) {
|
||||
lines.push(text.slice(lineStart));
|
||||
}
|
||||
|
||||
const indentation = guessIndentation(lines);
|
||||
|
||||
let result = "";
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const lineText = lines[i];
|
||||
const line = indentation ? lineText.slice(indentation) : lineText;
|
||||
result += line;
|
||||
if (i < lineTerminators.length) {
|
||||
result += lineTerminators[i];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function guessIndentation(lines: string[]) {
|
||||
let indentation: number;
|
||||
for (const line of lines) {
|
||||
for (let i = 0; i < line.length && (indentation === undefined || i < indentation); i++) {
|
||||
if (!ts.isWhiteSpaceLike(line.charCodeAt(i))) {
|
||||
if (indentation === undefined || i < indentation) {
|
||||
indentation = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return indentation;
|
||||
}
|
||||
|
||||
export function toUtf8(text: string): string {
|
||||
return new Buffer(text).toString("utf8");
|
||||
}
|
||||
|
||||
export function getByteOrderMarkLength(text: string): number {
|
||||
if (text.length >= 1) {
|
||||
const ch0 = text.charCodeAt(0);
|
||||
if (ch0 === 0xfeff) return 1;
|
||||
if (ch0 === 0xfe) return text.length >= 2 && text.charCodeAt(1) === 0xff ? 2 : 0;
|
||||
if (ch0 === 0xff) return text.length >= 2 && text.charCodeAt(1) === 0xfe ? 2 : 0;
|
||||
if (ch0 === 0xef) return text.length >= 3 && text.charCodeAt(1) === 0xbb && text.charCodeAt(2) === 0xbf ? 3 : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function removeByteOrderMark(text: string): string {
|
||||
const length = getByteOrderMarkLength(text);
|
||||
return length ? text.slice(length) : text;
|
||||
}
|
||||
|
||||
export function addUTF8ByteOrderMark(text: string) {
|
||||
return getByteOrderMarkLength(text) === 0 ? "\u00EF\u00BB\u00BF" + text : text;
|
||||
}
|
||||
}
|
1306
src/harness/vfs.ts
Normal file
1306
src/harness/vfs.ts
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,222 +0,0 @@
|
|||
/// <reference path="harness.ts" />
|
||||
/// <reference path="..\compiler\commandLineParser.ts"/>
|
||||
namespace Utils {
|
||||
export class VirtualFileSystemEntry {
|
||||
fileSystem: VirtualFileSystem;
|
||||
name: string;
|
||||
|
||||
constructor(fileSystem: VirtualFileSystem, name: string) {
|
||||
this.fileSystem = fileSystem;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
isDirectory(): this is VirtualDirectory { return false; }
|
||||
isFile(): this is VirtualFile { return false; }
|
||||
isFileSystem(): this is VirtualFileSystem { return false; }
|
||||
}
|
||||
|
||||
export class VirtualFile extends VirtualFileSystemEntry {
|
||||
content?: Harness.LanguageService.ScriptInfo;
|
||||
isFile() { return true; }
|
||||
}
|
||||
|
||||
export abstract class VirtualFileSystemContainer extends VirtualFileSystemEntry {
|
||||
abstract getFileSystemEntries(): VirtualFileSystemEntry[];
|
||||
|
||||
getFileSystemEntry(name: string): VirtualFileSystemEntry {
|
||||
for (const entry of this.getFileSystemEntries()) {
|
||||
if (this.fileSystem.sameName(entry.name, name)) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getDirectories(): VirtualDirectory[] {
|
||||
return <VirtualDirectory[]>ts.filter(this.getFileSystemEntries(), entry => entry.isDirectory());
|
||||
}
|
||||
|
||||
getFiles(): VirtualFile[] {
|
||||
return <VirtualFile[]>ts.filter(this.getFileSystemEntries(), entry => entry.isFile());
|
||||
}
|
||||
|
||||
getDirectory(name: string): VirtualDirectory {
|
||||
const entry = this.getFileSystemEntry(name);
|
||||
return entry.isDirectory() ? entry : undefined;
|
||||
}
|
||||
|
||||
getFile(name: string): VirtualFile {
|
||||
const entry = this.getFileSystemEntry(name);
|
||||
return entry.isFile() ? entry : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export class VirtualDirectory extends VirtualFileSystemContainer {
|
||||
private entries: VirtualFileSystemEntry[] = [];
|
||||
|
||||
isDirectory() { return true; }
|
||||
|
||||
getFileSystemEntries() { return this.entries.slice(); }
|
||||
|
||||
addDirectory(name: string): VirtualDirectory {
|
||||
const entry = this.getFileSystemEntry(name);
|
||||
if (entry === undefined) {
|
||||
const directory = new VirtualDirectory(this.fileSystem, name);
|
||||
this.entries.push(directory);
|
||||
return directory;
|
||||
}
|
||||
else if (entry.isDirectory()) {
|
||||
return entry;
|
||||
}
|
||||
else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
addFile(name: string, content?: Harness.LanguageService.ScriptInfo): VirtualFile {
|
||||
const entry = this.getFileSystemEntry(name);
|
||||
if (entry === undefined) {
|
||||
const file = new VirtualFile(this.fileSystem, name);
|
||||
file.content = content;
|
||||
this.entries.push(file);
|
||||
return file;
|
||||
}
|
||||
else if (entry.isFile()) {
|
||||
entry.content = content;
|
||||
return entry;
|
||||
}
|
||||
else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class VirtualFileSystem extends VirtualFileSystemContainer {
|
||||
private root: VirtualDirectory;
|
||||
|
||||
currentDirectory: string;
|
||||
useCaseSensitiveFileNames: boolean;
|
||||
|
||||
constructor(currentDirectory: string, useCaseSensitiveFileNames: boolean) {
|
||||
super(/*fileSystem*/ undefined, "");
|
||||
this.fileSystem = this;
|
||||
this.root = new VirtualDirectory(this, "");
|
||||
this.currentDirectory = currentDirectory;
|
||||
this.useCaseSensitiveFileNames = useCaseSensitiveFileNames;
|
||||
}
|
||||
|
||||
isFileSystem() { return true; }
|
||||
|
||||
getFileSystemEntries() { return this.root.getFileSystemEntries(); }
|
||||
|
||||
addDirectory(path: string) {
|
||||
path = ts.normalizePath(path);
|
||||
const components = ts.getNormalizedPathComponents(path, this.currentDirectory);
|
||||
let directory: VirtualDirectory = this.root;
|
||||
for (const component of components) {
|
||||
directory = directory.addDirectory(component);
|
||||
if (directory === undefined) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
addFile(path: string, content?: Harness.LanguageService.ScriptInfo) {
|
||||
const absolutePath = ts.normalizePath(ts.getNormalizedAbsolutePath(path, this.currentDirectory));
|
||||
const fileName = ts.getBaseFileName(absolutePath);
|
||||
const directoryPath = ts.getDirectoryPath(absolutePath);
|
||||
const directory = this.addDirectory(directoryPath);
|
||||
return directory ? directory.addFile(fileName, content) : undefined;
|
||||
}
|
||||
|
||||
fileExists(path: string) {
|
||||
const entry = this.traversePath(path);
|
||||
return entry !== undefined && entry.isFile();
|
||||
}
|
||||
|
||||
sameName(a: string, b: string) {
|
||||
return this.useCaseSensitiveFileNames ? a === b : a.toLowerCase() === b.toLowerCase();
|
||||
}
|
||||
|
||||
traversePath(path: string) {
|
||||
path = ts.normalizePath(path);
|
||||
let directory: VirtualDirectory = this.root;
|
||||
for (const component of ts.getNormalizedPathComponents(path, this.currentDirectory)) {
|
||||
const entry = directory.getFileSystemEntry(component);
|
||||
if (entry === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
else if (entry.isDirectory()) {
|
||||
directory = entry;
|
||||
}
|
||||
else {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the directory at the given path and retrieves a list of file names and a list
|
||||
* of directory names within it. Suitable for use with ts.matchFiles()
|
||||
* @param path The path to the directory to be read
|
||||
*/
|
||||
getAccessibleFileSystemEntries(path: string) {
|
||||
const entry = this.traversePath(path);
|
||||
if (entry && entry.isDirectory()) {
|
||||
return {
|
||||
files: ts.map(entry.getFiles(), f => f.name),
|
||||
directories: ts.map(entry.getDirectories(), d => d.name)
|
||||
};
|
||||
}
|
||||
return { files: [], directories: [] };
|
||||
}
|
||||
|
||||
getAllFileEntries() {
|
||||
const fileEntries: VirtualFile[] = [];
|
||||
getFilesRecursive(this.root, fileEntries);
|
||||
return fileEntries;
|
||||
|
||||
function getFilesRecursive(dir: VirtualDirectory, result: VirtualFile[]) {
|
||||
const files = dir.getFiles();
|
||||
const dirs = dir.getDirectories();
|
||||
for (const file of files) {
|
||||
result.push(file);
|
||||
}
|
||||
for (const subDir of dirs) {
|
||||
getFilesRecursive(subDir, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class MockParseConfigHost extends VirtualFileSystem implements ts.ParseConfigHost {
|
||||
constructor(currentDirectory: string, ignoreCase: boolean, files: ts.Map<string> | string[]) {
|
||||
super(currentDirectory, ignoreCase);
|
||||
if (files instanceof Array) {
|
||||
for (const file of files) {
|
||||
this.addFile(file, new Harness.LanguageService.ScriptInfo(file, undefined, /*isRootFile*/false));
|
||||
}
|
||||
}
|
||||
else {
|
||||
files.forEach((fileContent, fileName) => {
|
||||
this.addFile(fileName, new Harness.LanguageService.ScriptInfo(fileName, fileContent, /*isRootFile*/false));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
readFile(path: string): string | undefined {
|
||||
const value = this.traversePath(path);
|
||||
if (value && value.isFile()) {
|
||||
return value.content.content;
|
||||
}
|
||||
}
|
||||
|
||||
readDirectory(path: string, extensions: ReadonlyArray<string>, excludes: ReadonlyArray<string>, includes: ReadonlyArray<string>, depth: number) {
|
||||
return ts.matchFiles(path, extensions, excludes, includes, this.useCaseSensitiveFileNames, this.currentDirectory, depth, (path: string) => this.getAccessibleFileSystemEntries(path));
|
||||
}
|
||||
}
|
||||
}
|
127
src/harness/vpath.ts
Normal file
127
src/harness/vpath.ts
Normal file
|
@ -0,0 +1,127 @@
|
|||
namespace vpath {
|
||||
export import sep = ts.directorySeparator;
|
||||
export import normalizeSeparators = ts.normalizeSlashes;
|
||||
export import isAbsolute = ts.isRootedDiskPath;
|
||||
export import isRoot = ts.isDiskPathRoot;
|
||||
export import hasTrailingSeparator = ts.hasTrailingDirectorySeparator;
|
||||
export import addTrailingSeparator = ts.ensureTrailingDirectorySeparator;
|
||||
export import removeTrailingSeparator = ts.removeTrailingDirectorySeparator;
|
||||
export import normalize = ts.normalizePath;
|
||||
export import combine = ts.combinePaths;
|
||||
export import parse = ts.getPathComponents;
|
||||
export import reduce = ts.reducePathComponents;
|
||||
export import format = ts.getPathFromPathComponents;
|
||||
export import resolve = ts.resolvePath;
|
||||
export import compare = ts.comparePaths;
|
||||
export import compareCaseSensitive = ts.comparePathsCaseSensitive;
|
||||
export import compareCaseInsensitive = ts.comparePathsCaseInsensitive;
|
||||
export import dirname = ts.getDirectoryPath;
|
||||
export import basename = ts.getBaseFileName;
|
||||
export import extname = ts.getAnyExtensionFromPath;
|
||||
export import relative = ts.getRelativePath;
|
||||
export import beneath = ts.containsPath;
|
||||
export import changeExtension = ts.changeAnyExtension;
|
||||
export import isTypeScript = ts.hasTypeScriptFileExtension;
|
||||
export import isJavaScript = ts.hasJavaScriptFileExtension;
|
||||
|
||||
const invalidRootComponentRegExp = /^(?!(\/|\/\/\w+\/|[a-zA-Z]:\/?|)$)/;
|
||||
const invalidNavigableComponentRegExp = /[:*?"<>|]/;
|
||||
const invalidNonNavigableComponentRegExp = /^\.{1,2}$|[:*?"<>|]/;
|
||||
const extRegExp = /\.\w+$/;
|
||||
|
||||
export const enum ValidationFlags {
|
||||
None = 0,
|
||||
|
||||
RequireRoot = 1 << 0,
|
||||
RequireDirname = 1 << 1,
|
||||
RequireBasename = 1 << 2,
|
||||
RequireExtname = 1 << 3,
|
||||
RequireTrailingSeparator = 1 << 4,
|
||||
|
||||
AllowRoot = 1 << 5,
|
||||
AllowDirname = 1 << 6,
|
||||
AllowBasename = 1 << 7,
|
||||
AllowExtname = 1 << 8,
|
||||
AllowTrailingSeparator = 1 << 9,
|
||||
AllowNavigation = 1 << 10,
|
||||
|
||||
/** Path must be a valid directory root */
|
||||
Root = RequireRoot | AllowRoot | AllowTrailingSeparator,
|
||||
|
||||
/** Path must be a absolute */
|
||||
Absolute = RequireRoot | AllowRoot | AllowDirname | AllowBasename | AllowExtname | AllowTrailingSeparator | AllowNavigation,
|
||||
|
||||
/** Path may be relative or absolute */
|
||||
RelativeOrAbsolute = AllowRoot | AllowDirname | AllowBasename | AllowExtname | AllowTrailingSeparator | AllowNavigation,
|
||||
|
||||
/** Path may only be a filename */
|
||||
Basename = RequireBasename | AllowExtname,
|
||||
}
|
||||
|
||||
function validateComponents(components: string[], flags: ValidationFlags, hasTrailingSeparator: boolean) {
|
||||
const hasRoot = !!components[0];
|
||||
const hasDirname = components.length > 2;
|
||||
const hasBasename = components.length > 1;
|
||||
const hasExtname = hasBasename && extRegExp.test(components[components.length - 1]);
|
||||
const invalidComponentRegExp = flags & ValidationFlags.AllowNavigation ? invalidNavigableComponentRegExp : invalidNonNavigableComponentRegExp;
|
||||
|
||||
// Validate required components
|
||||
if (flags & ValidationFlags.RequireRoot && !hasRoot) return false;
|
||||
if (flags & ValidationFlags.RequireDirname && !hasDirname) return false;
|
||||
if (flags & ValidationFlags.RequireBasename && !hasBasename) return false;
|
||||
if (flags & ValidationFlags.RequireExtname && !hasExtname) return false;
|
||||
if (flags & ValidationFlags.RequireTrailingSeparator && !hasTrailingSeparator) return false;
|
||||
|
||||
// Required components indicate allowed components
|
||||
if (flags & ValidationFlags.RequireRoot) flags |= ValidationFlags.AllowRoot;
|
||||
if (flags & ValidationFlags.RequireDirname) flags |= ValidationFlags.AllowDirname;
|
||||
if (flags & ValidationFlags.RequireBasename) flags |= ValidationFlags.AllowBasename;
|
||||
if (flags & ValidationFlags.RequireExtname) flags |= ValidationFlags.AllowExtname;
|
||||
if (flags & ValidationFlags.RequireTrailingSeparator) flags |= ValidationFlags.AllowTrailingSeparator;
|
||||
|
||||
// Validate disallowed components
|
||||
if (~flags & ValidationFlags.AllowRoot && hasRoot) return false;
|
||||
if (~flags & ValidationFlags.AllowDirname && hasDirname) return false;
|
||||
if (~flags & ValidationFlags.AllowBasename && hasBasename) return false;
|
||||
if (~flags & ValidationFlags.AllowExtname && hasExtname) return false;
|
||||
if (~flags & ValidationFlags.AllowTrailingSeparator && hasTrailingSeparator) return false;
|
||||
|
||||
// Validate component strings
|
||||
if (invalidRootComponentRegExp.test(components[0])) return false;
|
||||
for (let i = 1; i < components.length; i++) {
|
||||
if (invalidComponentRegExp.test(components[i])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function validate(path: string, flags: ValidationFlags = ValidationFlags.RelativeOrAbsolute) {
|
||||
const components = parse(path);
|
||||
const trailing = hasTrailingSeparator(path);
|
||||
if (!validateComponents(components, flags, trailing)) throw vfs.createIOError("ENOENT");
|
||||
return components.length > 1 && trailing ? format(reduce(components)) + sep : format(reduce(components));
|
||||
}
|
||||
|
||||
export function isDeclaration(path: string) {
|
||||
return extname(path, ".d.ts", /*ignoreCase*/ false).length > 0;
|
||||
}
|
||||
|
||||
export function isSourceMap(path: string) {
|
||||
return extname(path, ".map", /*ignoreCase*/ false).length > 0;
|
||||
}
|
||||
|
||||
const javaScriptSourceMapExtensions: ReadonlyArray<string> = [".js.map", ".jsx.map"];
|
||||
|
||||
export function isJavaScriptSourceMap(path: string) {
|
||||
return extname(path, javaScriptSourceMapExtensions, /*ignoreCase*/ false).length > 0;
|
||||
}
|
||||
|
||||
export function isJson(path: string) {
|
||||
return extname(path, ".json", /*ignoreCase*/ false).length > 0;
|
||||
}
|
||||
|
||||
export function isDefaultLibrary(path: string) {
|
||||
return isDeclaration(path)
|
||||
&& basename(path).startsWith("lib.");
|
||||
}
|
||||
}
|
|
@ -521,7 +521,11 @@ namespace ts.server {
|
|||
}
|
||||
}
|
||||
|
||||
updateTypingsForProject(response: SetTypings | InvalidateCachedTypings | PackageInstalledResponse): void {
|
||||
updateTypingsForProject(response: SetTypings | InvalidateCachedTypings | PackageInstalledResponse): void;
|
||||
/** @internal */
|
||||
// tslint:disable-next-line:unified-signatures
|
||||
updateTypingsForProject(response: SetTypings | InvalidateCachedTypings | PackageInstalledResponse | BeginInstallTypes | EndInstallTypes): void;
|
||||
updateTypingsForProject(response: SetTypings | InvalidateCachedTypings | PackageInstalledResponse | BeginInstallTypes | EndInstallTypes): void {
|
||||
const project = this.findProject(response.projectName);
|
||||
if (!project) {
|
||||
return;
|
||||
|
|
|
@ -266,7 +266,7 @@ namespace ts.codefix {
|
|||
return [global];
|
||||
}
|
||||
|
||||
const relativePath = removeExtensionAndIndexPostFix(getRelativePath(moduleFileName, sourceDirectory, getCanonicalFileName), moduleResolutionKind, addJsExtension);
|
||||
const relativePath = removeExtensionAndIndexPostFix(ensurePathIsNonModuleName(getRelativePath(sourceDirectory, moduleFileName, getCanonicalFileName)), moduleResolutionKind, addJsExtension);
|
||||
if (!baseUrl || preferences.importModuleSpecifierPreference === "relative") {
|
||||
return [relativePath];
|
||||
}
|
||||
|
@ -321,7 +321,7 @@ namespace ts.codefix {
|
|||
1 < 2 = true
|
||||
In this case we should prefer using the relative path "../a" instead of the baseUrl path "foo/a".
|
||||
*/
|
||||
const pathFromSourceToBaseUrl = getRelativePath(baseUrl, sourceDirectory, getCanonicalFileName);
|
||||
const pathFromSourceToBaseUrl = ensurePathIsNonModuleName(getRelativePath(sourceDirectory, baseUrl, getCanonicalFileName));
|
||||
const relativeFirst = getRelativePathNParents(relativePath) < getRelativePathNParents(pathFromSourceToBaseUrl);
|
||||
return relativeFirst ? [relativePath, importRelativeToBaseUrl] : [importRelativeToBaseUrl, relativePath];
|
||||
});
|
||||
|
@ -343,11 +343,12 @@ namespace ts.codefix {
|
|||
}
|
||||
|
||||
function getRelativePathNParents(relativePath: string): number {
|
||||
let count = 0;
|
||||
for (let i = 0; i + 3 <= relativePath.length && relativePath.slice(i, i + 3) === "../"; i += 3) {
|
||||
count++;
|
||||
const components = getPathComponents(relativePath);
|
||||
if (components[0] || components.length === 1) return 0;
|
||||
for (let i = 1; i < components.length; i++) {
|
||||
if (components[i] !== "..") return i - 1;
|
||||
}
|
||||
return count;
|
||||
return components.length - 1;
|
||||
}
|
||||
|
||||
function tryGetModuleNameFromAmbientModule(moduleSymbol: Symbol): string | undefined {
|
||||
|
@ -389,7 +390,7 @@ namespace ts.codefix {
|
|||
}
|
||||
|
||||
const normalizedSourcePath = getPathRelativeToRootDirs(sourceDirectory, rootDirs, getCanonicalFileName);
|
||||
const relativePath = normalizedSourcePath !== undefined ? getRelativePath(normalizedTargetPath, normalizedSourcePath, getCanonicalFileName) : normalizedTargetPath;
|
||||
const relativePath = normalizedSourcePath !== undefined ? ensurePathIsNonModuleName(getRelativePath(normalizedSourcePath, normalizedTargetPath, getCanonicalFileName)) : normalizedTargetPath;
|
||||
return removeFileExtension(relativePath);
|
||||
}
|
||||
|
||||
|
@ -472,7 +473,7 @@ namespace ts.codefix {
|
|||
return path.substring(parts.topLevelPackageNameIndex + 1);
|
||||
}
|
||||
else {
|
||||
return getRelativePath(path, sourceDirectory, getCanonicalFileName);
|
||||
return ensurePathIsNonModuleName(getRelativePath(sourceDirectory, path, getCanonicalFileName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,10 +63,10 @@ namespace ts {
|
|||
|
||||
function getPathUpdater(oldFilePath: string, newFilePath: string, host: LanguageServiceHost): (oldPath: string) => string | undefined {
|
||||
// Get the relative path from old to new location, and append it on to the end of imports and normalize.
|
||||
const rel = getRelativePath(newFilePath, getDirectoryPath(oldFilePath), createGetCanonicalFileName(hostUsesCaseSensitiveFileNames(host)));
|
||||
const rel = ensurePathIsNonModuleName(getRelativePath(getDirectoryPath(oldFilePath), newFilePath, createGetCanonicalFileName(hostUsesCaseSensitiveFileNames(host))));
|
||||
return oldPath => {
|
||||
if (!pathIsRelative(oldPath)) return;
|
||||
return ensurePathIsRelative(normalizePath(combinePaths(getDirectoryPath(oldPath), rel)));
|
||||
return ensurePathIsNonModuleName(normalizePath(combinePaths(getDirectoryPath(oldPath), rel)));
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -89,7 +89,9 @@ namespace ts.Completions.PathCompletions {
|
|||
* Remove the basename from the path. Note that we don't use the basename to filter completions;
|
||||
* the client is responsible for refining completions.
|
||||
*/
|
||||
if (!hasTrailingDirectorySeparator(fragment)) {
|
||||
fragment = getDirectoryPath(fragment);
|
||||
}
|
||||
|
||||
if (fragment === "") {
|
||||
fragment = "." + directorySeparator;
|
||||
|
@ -97,8 +99,9 @@ namespace ts.Completions.PathCompletions {
|
|||
|
||||
fragment = ensureTrailingDirectorySeparator(fragment);
|
||||
|
||||
const absolutePath = normalizeAndPreserveTrailingSlash(isRootedDiskPath(fragment) ? fragment : combinePaths(scriptPath, fragment));
|
||||
const baseDirectory = getDirectoryPath(absolutePath);
|
||||
// const absolutePath = normalizeAndPreserveTrailingSlash(isRootedDiskPath(fragment) ? fragment : combinePaths(scriptPath, fragment)); // TODO(rbuckton): should use resolvePaths
|
||||
const absolutePath = resolvePath(scriptPath, fragment);
|
||||
const baseDirectory = hasTrailingDirectorySeparator(absolutePath) ? absolutePath : getDirectoryPath(absolutePath);
|
||||
const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames());
|
||||
|
||||
if (tryDirectoryExists(host, baseDirectory)) {
|
||||
|
@ -178,7 +181,7 @@ namespace ts.Completions.PathCompletions {
|
|||
}
|
||||
}
|
||||
|
||||
const fragmentDirectory = containsSlash(fragment) ? getDirectoryPath(fragment) : undefined;
|
||||
const fragmentDirectory = containsSlash(fragment) ? hasTrailingDirectorySeparator(fragment) ? fragment : getDirectoryPath(fragment) : undefined;
|
||||
for (const ambientName of getAmbientModuleCompletions(fragment, fragmentDirectory, typeChecker)) {
|
||||
result.push(nameAndKind(ambientName, ScriptElementKind.externalModuleName));
|
||||
}
|
||||
|
@ -239,14 +242,15 @@ namespace ts.Completions.PathCompletions {
|
|||
|
||||
// The prefix has two effective parts: the directory path and the base component after the filepath that is not a
|
||||
// full directory component. For example: directory/path/of/prefix/base*
|
||||
const normalizedPrefix = normalizeAndPreserveTrailingSlash(parsed.prefix);
|
||||
const normalizedPrefixDirectory = getDirectoryPath(normalizedPrefix);
|
||||
const normalizedPrefixBase = getBaseFileName(normalizedPrefix);
|
||||
const normalizedPrefix = resolvePath(parsed.prefix);
|
||||
const normalizedPrefixDirectory = hasTrailingDirectorySeparator(parsed.prefix) ? normalizedPrefix : getDirectoryPath(normalizedPrefix);
|
||||
const normalizedPrefixBase = hasTrailingDirectorySeparator(parsed.prefix) ? "" : getBaseFileName(normalizedPrefix);
|
||||
|
||||
const fragmentHasPath = containsSlash(fragment);
|
||||
const fragmentDirectory = fragmentHasPath ? hasTrailingDirectorySeparator(fragment) ? fragment : getDirectoryPath(fragment) : undefined;
|
||||
|
||||
// Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call
|
||||
const expandedPrefixDirectory = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + getDirectoryPath(fragment)) : normalizedPrefixDirectory;
|
||||
const expandedPrefixDirectory = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + fragmentDirectory) : normalizedPrefixDirectory;
|
||||
|
||||
const normalizedSuffix = normalizePath(parsed.suffix);
|
||||
// Need to normalize after combining: If we combinePaths("a", "../b"), we want "b" and not "a/../b".
|
||||
|
@ -418,16 +422,6 @@ namespace ts.Completions.PathCompletions {
|
|||
return false;
|
||||
}
|
||||
|
||||
function normalizeAndPreserveTrailingSlash(path: string) {
|
||||
if (normalizeSlashes(path) === "./") {
|
||||
// normalizePath turns "./" into "". "" + "/" would then be a rooted path instead of a relative one, so avoid this particular case.
|
||||
// There is no problem for adding "/" to a non-empty string -- it's only a problem at the beginning.
|
||||
return "";
|
||||
}
|
||||
const norm = normalizePath(path);
|
||||
return hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(norm) : norm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches a triple slash reference directive with an incomplete string literal for its path. Used
|
||||
* to determine if the caret is currently within the string literal and capture the literal fragment
|
||||
|
|
|
@ -1122,11 +1122,6 @@ namespace ts {
|
|||
return false;
|
||||
}
|
||||
|
||||
export function hasTrailingDirectorySeparator(path: string) {
|
||||
const lastCharacter = path.charAt(path.length - 1);
|
||||
return lastCharacter === "/" || lastCharacter === "\\";
|
||||
}
|
||||
|
||||
export function isInReferenceComment(sourceFile: SourceFile, position: number): boolean {
|
||||
return isInComment(sourceFile, position, /*tokenAtPosition*/ undefined, c => {
|
||||
const commentText = sourceFile.text.substring(c.pos, c.end);
|
||||
|
|
|
@ -38,6 +38,6 @@ _2["default"].aIndex;
|
|||
"use strict";
|
||||
exports.__esModule = true;
|
||||
var __1 = require("..");
|
||||
var _1 = require("../");
|
||||
var __2 = require("../");
|
||||
__1["default"].a;
|
||||
_1["default"].aIndex;
|
||||
__2["default"].aIndex;
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"File '/types/jquery/jquery.d.ts' exist - use it as a name resolution result.",
|
||||
"Resolving real path for '/types/jquery/jquery.d.ts', result '/types/jquery/jquery.d.ts'.",
|
||||
"======== Type reference directive 'jquery' was successfully resolved to '/types/jquery/jquery.d.ts', primary: true. ========",
|
||||
"======== Resolving type reference directive 'jquery', containing file 'test/__inferred type names__.ts', root directory '/types'. ========",
|
||||
"======== Resolving type reference directive 'jquery', containing file '/test/__inferred type names__.ts', root directory '/types'. ========",
|
||||
"Resolving with primary search path '/types'.",
|
||||
"'package.json' does not have a 'typings' field.",
|
||||
"'package.json' has 'types' field 'jquery.d.ts' that references '/types/jquery/jquery.d.ts'.",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
lib.d.ts(32,18): error TS2300: Duplicate identifier 'eval'.
|
||||
tests/cases/compiler/variableDeclarationInStrictMode1.ts(2,5): error TS1100: Invalid use of 'eval' in strict mode.
|
||||
tests/cases/compiler/variableDeclarationInStrictMode1.ts(2,5): error TS2300: Duplicate identifier 'eval'.
|
||||
lib.d.ts(32,18): error TS2300: Duplicate identifier 'eval'.
|
||||
|
||||
|
||||
==== tests/cases/compiler/variableDeclarationInStrictMode1.ts (2 errors) ====
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//@allowSyntheticDefaultImports: true
|
||||
//@allowJs: true
|
||||
//@jsx: react
|
||||
//@outDir: "build"
|
||||
//@outDir: build
|
||||
|
||||
//@filename: react.d.ts
|
||||
export = React;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// @noImplicitReferences: true
|
||||
// @traceResolution: true
|
||||
// @typeRoots: /types
|
||||
// @currentDirectory: test
|
||||
// @currentDirectory: /test
|
||||
|
||||
// package.json in a primary reference can refer to another file
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// @lib: es2015
|
||||
// @strict: true
|
||||
// @target: es2015
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
// @Filename: my_typings/some-module/index.d.ts
|
||||
//// export var x = 9;
|
||||
|
||||
verify.completionsAt(["0", "4"], ["someFile1", "sub", "my_typings"], { isNewIdentifierLocation: true });
|
||||
verify.completionsAt(["0", "4"], ["someFile1", "my_typings", "sub"], { isNewIdentifierLocation: true });
|
||||
verify.completionsAt("1", ["someFile2"], { isNewIdentifierLocation: true });
|
||||
verify.completionsAt("2", [{ name: "some-module", replacementSpan: test.ranges()[0] }], { isNewIdentifierLocation: true });
|
||||
verify.completionsAt("3", ["fourslash"], { isNewIdentifierLocation: true });
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
// @Filename: my_typings/some-module/index.d.ts
|
||||
//// export var x = 9;
|
||||
|
||||
verify.completionsAt("0", ["someFile1", "sub", "my_typings"], { isNewIdentifierLocation: true });
|
||||
verify.completionsAt("0", ["someFile1", "my_typings", "sub"], { isNewIdentifierLocation: true });
|
||||
verify.completionsAt("1", ["someFile2"], { isNewIdentifierLocation: true });
|
||||
verify.completionsAt("2", [{ name: "some-module", replacementSpan: test.ranges()[0] }], { isNewIdentifierLocation: true });
|
||||
verify.completionsAt("3", ["fourslash"], { isNewIdentifierLocation: true });
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
// @Filename: my_typings/some-module/index.d.ts
|
||||
//// export var x = 9;
|
||||
|
||||
verify.completionsAt("0", ["someFile.ts", "sub", "my_typings"], { isNewIdentifierLocation: true });
|
||||
verify.completionsAt("0", ["someFile.ts", "my_typings", "sub"], { isNewIdentifierLocation: true });
|
||||
verify.completionsAt("1", ["some-module"], { isNewIdentifierLocation: true });
|
||||
verify.completionsAt("2", ["someOtherFile.ts"], { isNewIdentifierLocation: true });
|
||||
verify.completionsAt("3", ["some-module"], { isNewIdentifierLocation: true });
|
||||
|
|
|
@ -42,6 +42,6 @@
|
|||
|
||||
verify.completions({
|
||||
at: test.markerNames(),
|
||||
are: ["prefix", "prefix-only", "2test", "0test", "1test"],
|
||||
are: ["2test", "prefix", "prefix-only", "0test", "1test"],
|
||||
isNewIdentifierLocation: true,
|
||||
});
|
||||
|
|
|
@ -39,6 +39,6 @@ verify.completions(
|
|||
},
|
||||
{
|
||||
at: kinds.map(k => `${k}2`),
|
||||
are: ["e1", "f1", "f2", "tests", "folder"],
|
||||
are: ["e1", "f1", "f2", "folder", "tests"],
|
||||
isNewIdentifierLocation: true,
|
||||
});
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
// @Filename: my_typings/some-module/index.d.ts
|
||||
//// export var x = 9;
|
||||
|
||||
verify.completionsAt("0", ["someFile1", "sub", "my_typings"], { isNewIdentifierLocation: true });
|
||||
verify.completionsAt("0", ["someFile1", "my_typings", "sub"], { isNewIdentifierLocation: true });
|
||||
verify.completionsAt("1", ["someFile2"], { isNewIdentifierLocation: true });
|
||||
verify.completionsAt("2", [{ name: "some-module", replacementSpan: test.ranges()[0] }], { isNewIdentifierLocation: true });
|
||||
verify.completionsAt("3", ["fourslash"], { isNewIdentifierLocation: true });
|
||||
|
|
|
@ -16,6 +16,6 @@
|
|||
|
||||
const ranges = test.ranges();
|
||||
const [r0, r1, r2] = ranges;
|
||||
verify.referenceGroups(ranges, [{ definition: 'module "/a"', ranges: [r0, r2, r1] }]);
|
||||
verify.referenceGroups(ranges, [{ definition: 'module "/a"', ranges: [r0, r1, r2] }]);
|
||||
// Testing that it works with documentHighlights too
|
||||
verify.rangesAreDocumentHighlights();
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
/// <reference path="fourslash.ts"/>
|
||||
|
||||
// @Filename: test/my fil"e.ts
|
||||
// @Filename: test/my fil e.ts
|
||||
////export class Bar {
|
||||
//// public s: string;
|
||||
////}
|
||||
////export var x: number;
|
||||
|
||||
verify.navigationTree({
|
||||
"text": "\"my fil\\\"e\"",
|
||||
"text": "\"my fil\\te\"",
|
||||
"kind": "module",
|
||||
"childItems": [
|
||||
{
|
||||
|
@ -32,7 +32,7 @@ verify.navigationTree({
|
|||
|
||||
verify.navigationBar([
|
||||
{
|
||||
"text": "\"my fil\\\"e\"",
|
||||
"text": "\"my fil\\te\"",
|
||||
"kind": "module",
|
||||
"childItems": [
|
||||
{
|
||||
|
|
|
@ -21,8 +21,10 @@
|
|||
<button id='runTestBtn' onclick='runTests()' type="button" class="btn">Run Tests</button>
|
||||
</div>
|
||||
<div id="mocha"></div>
|
||||
<script src="../node_modules/source-map-support/browser-source-map-support.js"></script>
|
||||
<script>sourceMapSupport.install();</script>
|
||||
<script src="../node_modules/mocha/mocha.js"></script>
|
||||
<script>mocha.setup('bdd')</script>
|
||||
<script>mocha.setup({ ui: 'bdd', timeout: 0 })</script>
|
||||
<script src="../built/local/bundle.js"></script>
|
||||
|
||||
<script>
|
||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue