Merge pull request #20763 from Microsoft/vfs

Update harness to use single robust virtual file system for tests.
This commit is contained in:
Ron Buckton 2018-05-03 10:25:20 -07:00 committed by GitHub
commit 56648ad0f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
62 changed files with 5807 additions and 2388 deletions

48
.vscode/tasks.json vendored
View file

@ -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"]
}
]
}

View file

@ -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())

View file

@ -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";
@ -244,7 +244,7 @@ function compileFile(outFile, sources, prereqs, prefixes, useBuiltCompiler, opts
if (opts.stripInternal) {
options += " --stripInternal";
}
options += " --target es5";
options += " --target es5";
if (opts.lib) {
options += " --lib " + opts.lib;
}
@ -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"));

View file

@ -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",

View file

@ -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,

View file

@ -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];
}
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);
}
/**
* 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);
}
return normalizedPathComponents(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) {
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);
}
/**
* 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;
}
/**
* 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;
}
/**
* 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;
}
}
// 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;
}
}
return relativePath + relativePathComponents.join(directorySeparator);
}
// Cant find the relative path, get the absolute path
let absolutePath = getNormalizedPathFromPathComponents(pathComponents);
if (isAbsolutePathAnUrl && isRootedDiskPath(absolutePath)) {
absolutePath = "file:///" + absolutePath;
}
return absolutePath;
return path;
}
export function getRelativePath(path: string, directoryPath: string, getCanonicalFileName: GetCanonicalFileName) {
const relativePath = getRelativePathToDirectoryOrUrl(directoryPath, path, directoryPath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
return ensurePathIsRelative(relativePath);
/**
* 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 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));
}
// 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 {
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.
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) {

View file

@ -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)) {

View file

@ -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)) {

View file

@ -57,7 +57,7 @@ namespace ts {
return currentDirectory;
}
return getNormalizedPathFromPathComponents(commonPathComponents);
return getPathFromPathComponents(commonPathComponents);
}
interface OutputFingerprint {

View file

@ -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
View 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
View 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);
}
}

View file

@ -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,19 +52,33 @@ 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);
});
}
else {
this.tests.forEach(test => this.checkTestCodeOutput(test));
}
const files = this.tests.length > 0 ? this.tests : this.enumerateTestFiles();
files.forEach(file => { this.checkTestCodeOutput(vpath.normalizeSeparators(file)); });
});
}
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) {
this.emit = false;
@ -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
View 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;
}
}
}

View file

@ -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
View 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;
}
}
}

View file

@ -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

View file

@ -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);

View file

@ -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);

View file

@ -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) {

View file

@ -1,107 +1,306 @@
///<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 {
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
resolveMapRoot?: boolean; // should we resolve this map root and give compiler the absolute disk path as map root?
resolveSourceRoot?: boolean; // should we resolve this source root and give compiler the absolute disk path as map root?
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 {
// 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 {
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 {
public enumerateTestFiles() {
return this.enumerateFiles("tests/cases/project", /\.json$/, { recursive: true });
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
resolveMapRoot?: boolean; // should we resolve this map root and give compiler the absolute disk path as map root?
resolveSourceRoot?: boolean; // should we resolve this source root and give compiler the absolute disk path as map root?
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
}
public kind(): TestRunnerKind {
return "project";
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
}
public initializeTests() {
if (this.tests.length === 0) {
const testFiles = this.enumerateTestFiles();
testFiles.forEach(fn => {
this.runProjectTestCase(fn);
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?: ReadonlyArray<documents.TextDocument>;
}
export class ProjectRunner extends RunnerBase {
public enumerateTestFiles() {
return this.enumerateFiles("tests/cases/project", /\.json$/, { recursive: true });
}
public kind(): TestRunnerKind {
return "project";
}
public initializeTests() {
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; });
});
}
}
}
private runProjectTestCase(testCaseFileName: string) {
let testCase: ProjectRunnerTestCase & ts.CompilerOptions;
class ProjectCompilerHost extends fakes.CompilerHost {
private _testCase: ProjectRunnerTestCase & ts.CompilerOptions;
private _projectParseConfigHost: ProjectParseConfigHost;
let testFileText: string;
try {
testFileText = Harness.IO.readFile(testCaseFileName);
}
catch (e) {
assert(false, "Unable to open testcase file: " + testCaseFileName + ": " + e.message);
constructor(sys: fakes.System | vfs.FileSystem, compilerOptions: ts.CompilerOptions, _testCaseJustName: string, testCase: ProjectRunnerTestCase & ts.CompilerOptions, _moduleKind: ts.ModuleKind) {
super(sys, compilerOptions);
this._testCase = testCase;
}
try {
testCase = <ProjectRunnerTestCase & ts.CompilerOptions>JSON.parse(testFileText);
public get parseConfigHost(): fakes.ParseConfigHost {
return this._projectParseConfigHost || (this._projectParseConfigHost = new ProjectParseConfigHost(this.sys, this._testCase));
}
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";
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;
try {
testFileText = Harness.IO.readFile(testCaseFileName);
}
catch (e) {
assert(false, "Unable to open testcase file: " + testCaseFileName + ": " + e.message);
}
try {
testCase = <ProjectRunnerTestCase & ts.CompilerOptions>JSON.parse(testFileText);
}
catch (e) {
assert(false, "Testcase: " + testCaseFileName + " does not contain valid json format: " + e.message);
}
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,153 +390,90 @@ 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);
}
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;
return ts.forEach(compilerResult.outputFiles, outputFile => outputFile.meta.get("fileName") === fileName ? outputFile : undefined);
}
}
function getErrorsBaseline(compilerResult: CompileProjectFilesResult) {
const inputSourceFiles = compilerResult.configFileSourceFiles.slice();
if (compilerResult.program) {
for (const sourceFile of compilerResult.program.getSourceFiles()) {
if (!Harness.isDefaultLibraryFile(sourceFile.fileName)) {
inputSourceFiles.push(sourceFile);
}
}
}
const inputFiles = inputSourceFiles.map<Harness.Compiler.TestFile>(sourceFile => ({
unitName: ts.isRootedDiskPath(sourceFile.fileName) ?
RunnerBase.removeFullPaths(sourceFile.fileName) :
sourceFile.fileName,
content: sourceFile.text
}));
return Harness.Compiler.getErrorBaseline(inputFiles, compilerResult.errors);
}
const name = "Compiling project for " + testCase.scenario + ": testcase " + testCaseFileName;
describe("projects tests", () => {
describe(name, () => {
function verifyCompilerResults(moduleKind: ts.ModuleKind) {
let compilerResult: BatchCompileProjectTestCaseResult;
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 "));
}
}
});
// 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);
});
}
}
});
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;
});
});
});
}
}
function moduleNameToString(moduleKind: ts.ModuleKind) {
return moduleKind === ts.ModuleKind.AMD
? "amd"
: moduleKind === ts.ModuleKind.CommonJS
? "node"
: "none";
}
function getErrorsBaseline(compilerResult: CompileProjectFilesResult) {
const inputSourceFiles = compilerResult.configFileSourceFiles.slice();
if (compilerResult.program) {
for (const sourceFile of compilerResult.program.getSourceFiles()) {
if (!Harness.isDefaultLibraryFile(sourceFile.fileName)) {
inputSourceFiles.push(sourceFile);
}
}
}
const inputFiles = inputSourceFiles.map<Harness.Compiler.TestFile>(sourceFile => ({
unitName: ts.isRootedDiskPath(sourceFile.fileName) ?
RunnerBase.removeFullPaths(sourceFile.fileName) :
sourceFile.fileName,
content: sourceFile.text
}));
return Harness.Compiler.getErrorBaseline(inputFiles, compilerResult.errors);
}
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,
sourceRoot: testCase.resolveSourceRoot && testCase.sourceRoot
? vpath.resolve(vfs.srcFolder, testCase.sourceRoot)
: testCase.sourceRoot
};
// 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;
}
}

View file

@ -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

View file

@ -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);
}
});

View file

@ -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

View file

@ -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;
}

View file

@ -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",

View file

@ -1,116 +1,123 @@
/// <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": {
extends: "./configs/base",
files: [
"main.ts",
"supplemental.ts"
]
},
"/dev/tsconfig.nostrictnull.json": {
extends: "./tsconfig",
compilerOptions: {
strictNullChecks: false
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": JSON.stringify({
extends: "./tsconfig",
compilerOptions: {
strictNullChecks: false
}
}),
"dev/configs/base.json": JSON.stringify({
compilerOptions: {
allowJs: true,
noImplicitAny: true,
strictNullChecks: true
}
}),
"dev/configs/tests.json": JSON.stringify({
compilerOptions: {
preserveConstEnums: true,
removeComments: false,
sourceMap: true
},
exclude: [
"../tests/baselines",
"../tests/scenarios"
],
include: [
"../tests/**/*.ts"
]
}),
"dev/circular.json": JSON.stringify({
extends: "./circular2",
compilerOptions: {
module: "amd"
}
}),
"dev/circular2.json": JSON.stringify({
extends: "./circular",
compilerOptions: {
module: "commonjs"
}
}),
"dev/missing.json": JSON.stringify({
extends: "./missing2",
compilerOptions: {
types: []
}
}),
"dev/failure.json": JSON.stringify({
extends: "./failure2.json",
compilerOptions: {
typeRoots: []
}
}),
"dev/failure2.json": JSON.stringify({
excludes: ["*.js"]
}),
"dev/configs/first.json": JSON.stringify({
extends: "./base",
compilerOptions: {
module: "commonjs"
},
files: ["../main.ts"]
}),
"dev/configs/second.json": JSON.stringify({
extends: "./base",
compilerOptions: {
module: "amd"
},
include: ["../supplemental.*"]
}),
"dev/configs/third.json": JSON.stringify({
extends: "./second",
compilerOptions: {
// tslint:disable-next-line:no-null-keyword
module: null
},
include: ["../supplemental.*"]
}),
"dev/configs/fourth.json": JSON.stringify({
extends: "./third",
compilerOptions: {
module: "system"
},
// tslint:disable-next-line:no-null-keyword
include: null,
files: ["../main.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": ""
}
}
},
"/dev/configs/base.json": {
compilerOptions: {
allowJs: true,
noImplicitAny: true,
strictNullChecks: true
}
},
"/dev/configs/tests.json": {
compilerOptions: {
preserveConstEnums: true,
removeComments: false,
sourceMap: true
},
exclude: [
"../tests/baselines",
"../tests/scenarios"
],
include: [
"../tests/**/*.ts"
]
},
"/dev/circular.json": {
extends: "./circular2",
compilerOptions: {
module: "amd"
}
},
"/dev/circular2.json": {
extends: "./circular",
compilerOptions: {
module: "commonjs"
}
},
"/dev/missing.json": {
extends: "./missing2",
compilerOptions: {
types: []
}
},
"/dev/failure.json": {
extends: "./failure2.json",
compilerOptions: {
typeRoots: []
}
},
"/dev/failure2.json": {
excludes: ["*.js"]
},
"/dev/configs/first.json": {
extends: "./base",
compilerOptions: {
module: "commonjs"
},
files: ["../main.ts"]
},
"/dev/configs/second.json": {
extends: "./base",
compilerOptions: {
module: "amd"
},
include: ["../supplemental.*"]
},
"/dev/configs/third.json": {
extends: "./second",
compilerOptions: {
// tslint:disable-next-line:no-null-keyword
module: null
},
include: ["../supplemental.*"]
},
"/dev/configs/fourth.json": {
extends: "./third",
compilerOptions: {
module: "system"
},
// 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": ""
});
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]) => {

View file

@ -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;

View file

@ -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);

View file

@ -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);

View file

@ -1,5 +1,5 @@
/// <reference path="..\..\..\src\harness\harness.ts" />
/// <reference path="..\..\..\src\harness\virtualFileSystem.ts" />
/// <reference path="..\..\..\src\harness\vfs.ts" />
namespace ts {

View 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:/");
});
});

View file

@ -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:
"/// <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 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 = 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();

View file

@ -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));
});
}

View file

@ -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;
});
}

View file

@ -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);
}
@ -109,7 +113,7 @@ namespace ts {
"xx//file.d.ts"
]
}`, { config: { exclude: ["xx//file.d.ts"] } });
assertParseResult(
assertParseResult(
`{
"exclude": [
"xx/*file.d.ts*/"

114
src/harness/utils.ts Normal file
View 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

File diff suppressed because it is too large Load diff

View file

@ -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
View 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.");
}
}

View file

@ -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;

View file

@ -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));
}
}
}

View file

@ -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)));
};
}

View file

@ -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.
*/
fragment = getDirectoryPath(fragment);
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

View file

@ -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);

View file

@ -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;

View file

@ -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'.",

View file

@ -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) ====

View file

@ -5,7 +5,7 @@
//@allowSyntheticDefaultImports: true
//@allowJs: true
//@jsx: react
//@outDir: "build"
//@outDir: build
//@filename: react.d.ts
export = React;

View file

@ -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

View file

@ -1,3 +1,4 @@
// @lib: es2015
// @strict: true
// @target: es2015

View file

@ -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 });

View file

@ -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 });

View file

@ -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 });

View file

@ -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,
});

View file

@ -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,
});

View file

@ -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 });

View file

@ -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();

View file

@ -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": [
{

View file

@ -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