mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-12-11 17:41:26 -06:00
Cleanup, add watch capabilities
This commit is contained in:
parent
5b4553d6a5
commit
41aeaae7c1
@ -95,6 +95,7 @@ var harnessCoreSources = [
|
||||
"harness.ts",
|
||||
"collections.ts",
|
||||
"vpath.ts",
|
||||
"events.ts",
|
||||
"vfs.ts",
|
||||
"virtualFileSystemWithWatch.ts",
|
||||
"sourceMapRecorder.ts",
|
||||
|
||||
@ -1,13 +1,23 @@
|
||||
/// <reference path="./harness.ts" />
|
||||
namespace Collections {
|
||||
import compareValues = ts.compareValues;
|
||||
namespace collections {
|
||||
// NOTE: Some of the functions here duplicate functionality from compiler/core.ts. They have been added
|
||||
// to reduce the number of direct dependencies on compiler and services to eventually break away
|
||||
// from depending directly on the compiler to speed up compilation time.
|
||||
|
||||
import binarySearch = ts.binarySearch;
|
||||
import removeAt = ts.orderedRemoveItemAt;
|
||||
|
||||
export function compareValues<T>(a: T, b: T): number {
|
||||
if (a === b) return 0;
|
||||
if (a === undefined) return -1;
|
||||
if (b === undefined) return +1;
|
||||
return a < b ? -1 : +1;
|
||||
}
|
||||
|
||||
const caseInsensitiveComparisonCollator = typeof Intl === "object" ? new Intl.Collator(/*locales*/ undefined, { usage: "sort", sensitivity: "accent" }) : undefined;
|
||||
const caseSensitiveComparisonCollator = typeof Intl === "object" ? new Intl.Collator(/*locales*/ undefined, { usage: "sort", sensitivity: "variant" }) : undefined;
|
||||
|
||||
export function compareStrings(a: string | undefined, b: string | undefined, ignoreCase?: boolean) {
|
||||
export function compareStrings(a: string | undefined, b: string | undefined, ignoreCase: boolean) {
|
||||
if (a === b) return 0;
|
||||
if (a === undefined) return -1;
|
||||
if (b === undefined) return +1;
|
||||
@ -23,13 +33,8 @@ namespace Collections {
|
||||
}
|
||||
|
||||
export namespace compareStrings {
|
||||
export function caseSensitive(a: string | undefined, b: string | undefined) {
|
||||
return compareStrings(a, b, /*ignoreCase*/ false);
|
||||
}
|
||||
|
||||
export function caseInsensitive(a: string | undefined, b: string | undefined) {
|
||||
return compareStrings(a, b, /*ignoreCase*/ true);
|
||||
}
|
||||
export function caseSensitive(a: string | undefined, b: string | undefined) { return compareStrings(a, b, /*ignoreCase*/ false); }
|
||||
export function caseInsensitive(a: string | undefined, b: string | undefined) { return compareStrings(a, b, /*ignoreCase*/ true); }
|
||||
}
|
||||
|
||||
function insertAt<T>(array: T[], index: number, value: T) {
|
||||
@ -50,7 +55,7 @@ namespace Collections {
|
||||
/**
|
||||
* A collection of key/value pairs sorted by key.
|
||||
*/
|
||||
export class KeyedCollection<K, V> {
|
||||
export class SortedCollection<K, V> {
|
||||
private _comparer: (a: K, b: K) => number;
|
||||
private _keys: K[] = [];
|
||||
private _values: V[] = [];
|
||||
@ -145,6 +150,14 @@ namespace Collections {
|
||||
|
||||
const undefinedSentinel = {};
|
||||
|
||||
function escapeKey(text: string) {
|
||||
return (text.length >= 2 && text.charAt(0) === "_" && text.charAt(1) === "_" ? "_" + text : text);
|
||||
}
|
||||
|
||||
function unescapeKey(text: string) {
|
||||
return (text.length >= 3 && text.charAt(0) === "_" && text.charAt(1) === "_" && text.charAt(2) === "_" ? text.slice(1) : text);
|
||||
}
|
||||
|
||||
/**
|
||||
* A collection of metadata that supports inheritance.
|
||||
*/
|
||||
@ -173,24 +186,25 @@ namespace Collections {
|
||||
}
|
||||
|
||||
public has(key: string): boolean {
|
||||
return this._map[key] !== undefined;
|
||||
return this._map[escapeKey(key)] !== undefined;
|
||||
}
|
||||
|
||||
public get(key: string): any {
|
||||
const value = this._map[key];
|
||||
const value = this._map[escapeKey(key)];
|
||||
return value === undefinedSentinel ? undefined : value;
|
||||
}
|
||||
|
||||
public set(key: string, value: any): this {
|
||||
this._map[key] = value === undefined ? undefinedSentinel : value;
|
||||
this._map[escapeKey(key)] = value === undefined ? undefinedSentinel : value;
|
||||
this._size = -1;
|
||||
this._version++;
|
||||
return this;
|
||||
}
|
||||
|
||||
public delete(key: string): boolean {
|
||||
if (this._map[key] !== undefined) {
|
||||
delete this._map[key];
|
||||
const escapedKey = escapeKey(key);
|
||||
if (this._map[escapedKey] !== undefined) {
|
||||
delete this._map[escapedKey];
|
||||
this._size = -1;
|
||||
this._version++;
|
||||
return true;
|
||||
@ -206,7 +220,7 @@ namespace Collections {
|
||||
|
||||
public forEach(callback: (value: any, key: string, map: this) => void) {
|
||||
for (const key in this._map) {
|
||||
callback(this._map[key], key, this);
|
||||
callback(this._map[key], unescapeKey(key), this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
26
src/harness/events.ts
Normal file
26
src/harness/events.ts
Normal file
@ -0,0 +1,26 @@
|
||||
/// <reference path="./harness.ts" />
|
||||
namespace events {
|
||||
const _events = require("events");
|
||||
|
||||
export const EventEmitter: {
|
||||
new (): EventEmitter;
|
||||
prototype: EventEmitter;
|
||||
defaultMaxListeners: number;
|
||||
} = _events.EventEmitter;
|
||||
|
||||
export interface EventEmitter {
|
||||
on(event: string | symbol, listener: (...args: any[]) => void): this;
|
||||
once: this["on"];
|
||||
addListener: this["on"];
|
||||
prependListener: this["on"];
|
||||
prependOnceListener: this["on"];
|
||||
removeListener: this["on"];
|
||||
removeAllListeners(event?: string | symbol): this;
|
||||
setMaxListeners(n: number): this;
|
||||
getMaxListeners(): number;
|
||||
listeners(event: string | symbol): Function[];
|
||||
emit(event: string | symbol, ...args: any[]): boolean;
|
||||
eventNames(): (string | symbol)[];
|
||||
listenerCount(type: string | symbol): number;
|
||||
}
|
||||
}
|
||||
@ -32,7 +32,6 @@
|
||||
// this will work in the browser via browserify
|
||||
var _chai: typeof chai = require("chai");
|
||||
var assert: typeof _chai.assert = _chai.assert;
|
||||
declare var __dirname: string; // Node-specific
|
||||
var global: NodeJS.Global = <any>Function("return this").call(undefined);
|
||||
|
||||
declare var window: {};
|
||||
@ -43,9 +42,13 @@ interface XMLHttpRequest {
|
||||
readonly readyState: number;
|
||||
readonly responseText: string;
|
||||
readonly status: number;
|
||||
readonly statusText: string;
|
||||
open(method: string, url: string, async?: boolean, user?: string, password?: string): void;
|
||||
send(data?: string): void;
|
||||
setRequestHeader(header: string, value: string): void;
|
||||
getAllResponseHeaders(): string;
|
||||
getResponseHeader(header: string): string | null;
|
||||
overrideMimeType(mime: string): void;
|
||||
}
|
||||
/* tslint:enable:no-var-keyword */
|
||||
|
||||
@ -489,16 +492,23 @@ namespace Harness {
|
||||
fileExists(fileName: string): boolean;
|
||||
directoryExists(path: string): boolean;
|
||||
deleteFile(fileName: string): void;
|
||||
listFiles(path: string, filter: RegExp, options?: { recursive?: boolean }): string[];
|
||||
listFiles(path: string, filter?: RegExp, options?: { recursive?: boolean }): string[];
|
||||
log(text: string): void;
|
||||
getMemoryUsage?(): number;
|
||||
args(): string[];
|
||||
getExecutingFilePath(): string;
|
||||
exit(exitCode?: number): void;
|
||||
readDirectory(path: string, extension?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[];
|
||||
getAccessibleFileSystemEntries(dirname: string): FileSystemEntries;
|
||||
tryEnableSourceMapsForHost?(): void;
|
||||
getEnvironmentVariable?(name: string): string;
|
||||
getMemoryUsage?(): number;
|
||||
}
|
||||
|
||||
export interface FileSystemEntries {
|
||||
files: string[];
|
||||
directories: string[];
|
||||
}
|
||||
|
||||
export let IO: IO;
|
||||
|
||||
// harness always uses one kind of new line
|
||||
@ -508,253 +518,380 @@ namespace Harness {
|
||||
// Root for file paths that are stored in a virtual file system
|
||||
export const virtualFileSystemRoot = "/";
|
||||
|
||||
namespace IOImpl {
|
||||
export namespace Node {
|
||||
declare const require: any;
|
||||
let fs: any, pathModule: any;
|
||||
if (require) {
|
||||
fs = require("fs");
|
||||
pathModule = require("path");
|
||||
}
|
||||
else {
|
||||
fs = pathModule = {};
|
||||
}
|
||||
|
||||
export const resolvePath = (path: string) => ts.sys.resolvePath(path);
|
||||
export const getCurrentDirectory = () => ts.sys.getCurrentDirectory();
|
||||
export const newLine = () => harnessNewLine;
|
||||
export const useCaseSensitiveFileNames = () => ts.sys.useCaseSensitiveFileNames;
|
||||
export const args = () => ts.sys.args;
|
||||
export const getExecutingFilePath = () => ts.sys.getExecutingFilePath();
|
||||
export const exit = (exitCode: number) => ts.sys.exit(exitCode);
|
||||
export const getDirectories: typeof IO.getDirectories = path => ts.sys.getDirectories(path);
|
||||
|
||||
export const readFile: typeof IO.readFile = path => ts.sys.readFile(path);
|
||||
export const writeFile: typeof IO.writeFile = (path, content) => ts.sys.writeFile(path, content);
|
||||
export const fileExists: typeof IO.fileExists = fs.existsSync;
|
||||
export const log: typeof IO.log = s => console.log(s);
|
||||
export const getEnvironmentVariable: typeof IO.getEnvironmentVariable = name => ts.sys.getEnvironmentVariable(name);
|
||||
|
||||
export function tryEnableSourceMapsForHost() {
|
||||
if (ts.sys.tryEnableSourceMapsForHost) {
|
||||
ts.sys.tryEnableSourceMapsForHost();
|
||||
}
|
||||
}
|
||||
export const readDirectory: typeof IO.readDirectory = (path, extension, exclude, include, depth) => ts.sys.readDirectory(path, extension, exclude, include, depth);
|
||||
|
||||
export function createDirectory(path: string) {
|
||||
if (!directoryExists(path)) {
|
||||
fs.mkdirSync(path);
|
||||
}
|
||||
}
|
||||
|
||||
export function deleteFile(path: string) {
|
||||
try {
|
||||
fs.unlinkSync(path);
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
export function directoryExists(path: string): boolean {
|
||||
return fs.existsSync(path) && fs.statSync(path).isDirectory();
|
||||
}
|
||||
|
||||
export function directoryName(path: string) {
|
||||
const dirPath = pathModule.dirname(path);
|
||||
// Node will just continue to repeat the root path, rather than return null
|
||||
return dirPath === path ? undefined : dirPath;
|
||||
}
|
||||
|
||||
export let listFiles: typeof IO.listFiles = (path, spec?, options?) => {
|
||||
options = options || {};
|
||||
|
||||
function filesInFolder(folder: string): string[] {
|
||||
let paths: string[] = [];
|
||||
|
||||
const files = fs.readdirSync(folder);
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const pathToFile = pathModule.join(folder, files[i]);
|
||||
const stat = fs.statSync(pathToFile);
|
||||
if (options.recursive && stat.isDirectory()) {
|
||||
paths = paths.concat(filesInFolder(pathToFile));
|
||||
}
|
||||
else if (stat.isFile() && (!spec || files[i].match(spec))) {
|
||||
paths.push(pathToFile);
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
return filesInFolder(path);
|
||||
};
|
||||
|
||||
export let getMemoryUsage: typeof IO.getMemoryUsage = () => {
|
||||
if (global.gc) {
|
||||
global.gc();
|
||||
}
|
||||
return process.memoryUsage().heapUsed;
|
||||
};
|
||||
function createNodeIO(): IO {
|
||||
let fs: any, pathModule: any;
|
||||
if (require) {
|
||||
fs = require("fs");
|
||||
pathModule = require("path");
|
||||
}
|
||||
else {
|
||||
fs = pathModule = {};
|
||||
}
|
||||
|
||||
export namespace Network {
|
||||
const serverRoot = "http://localhost:8888/";
|
||||
|
||||
export const newLine = () => harnessNewLine;
|
||||
export const useCaseSensitiveFileNames = () => false;
|
||||
export const getCurrentDirectory = () => "";
|
||||
export const args = () => <string[]>[];
|
||||
export const getExecutingFilePath = () => "";
|
||||
export const exit = ts.noop;
|
||||
export const getDirectories = () => <string[]>[];
|
||||
|
||||
export let log = (s: string) => console.log(s);
|
||||
|
||||
namespace Http {
|
||||
function waitForXHR(xhr: XMLHttpRequest) {
|
||||
while (xhr.readyState !== 4) { }
|
||||
return { status: xhr.status, responseText: xhr.responseText };
|
||||
}
|
||||
|
||||
/// Ask the server to use node's path.resolve to resolve the given path
|
||||
|
||||
export interface XHRResponse {
|
||||
status: number;
|
||||
responseText: string;
|
||||
}
|
||||
|
||||
/// Ask the server for the contents of the file at the given URL via a simple GET request
|
||||
export function getFileFromServerSync(url: string): XHRResponse {
|
||||
const xhr = new XMLHttpRequest();
|
||||
try {
|
||||
xhr.open("GET", url, /*async*/ false);
|
||||
xhr.send();
|
||||
}
|
||||
catch (e) {
|
||||
return { status: 404, responseText: undefined };
|
||||
}
|
||||
|
||||
return waitForXHR(xhr);
|
||||
}
|
||||
|
||||
/// Submit a POST request to the server to do the given action (ex WRITE, DELETE) on the provided URL
|
||||
export function writeToServerSync(url: string, action: string, contents?: string): XHRResponse {
|
||||
const xhr = new XMLHttpRequest();
|
||||
try {
|
||||
const actionMsg = "?action=" + action;
|
||||
xhr.open("POST", url + actionMsg, /*async*/ false);
|
||||
xhr.setRequestHeader("Access-Control-Allow-Origin", "*");
|
||||
xhr.send(contents);
|
||||
}
|
||||
catch (e) {
|
||||
log(`XHR Error: ${e}`);
|
||||
return { status: 500, responseText: undefined };
|
||||
}
|
||||
|
||||
return waitForXHR(xhr);
|
||||
}
|
||||
function deleteFile(path: string) {
|
||||
try {
|
||||
fs.unlinkSync(path);
|
||||
}
|
||||
|
||||
export function createDirectory() {
|
||||
// Do nothing (?)
|
||||
}
|
||||
|
||||
export function deleteFile(path: string) {
|
||||
Http.writeToServerSync(serverRoot + path, "DELETE");
|
||||
}
|
||||
|
||||
export function directoryExists(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
function directoryNameImpl(path: string) {
|
||||
let dirPath = path;
|
||||
// root of the server
|
||||
if (dirPath.match(/localhost:\d+$/) || dirPath.match(/localhost:\d+\/$/)) {
|
||||
dirPath = undefined;
|
||||
// path + fileName
|
||||
}
|
||||
else if (dirPath.indexOf(".") === -1) {
|
||||
dirPath = dirPath.substring(0, dirPath.lastIndexOf("/"));
|
||||
// path
|
||||
}
|
||||
else {
|
||||
// strip any trailing slash
|
||||
if (dirPath.match(/.*\/$/)) {
|
||||
dirPath = dirPath.substring(0, dirPath.length - 2);
|
||||
}
|
||||
dirPath = dirPath.substring(0, dirPath.lastIndexOf("/"));
|
||||
}
|
||||
|
||||
return dirPath;
|
||||
}
|
||||
export let directoryName: typeof IO.directoryName = Utils.memoize(directoryNameImpl, path => path);
|
||||
|
||||
export function resolvePath(path: string) {
|
||||
const response = Http.getFileFromServerSync(serverRoot + path + "?resolve=true");
|
||||
if (response.status === 200) {
|
||||
return response.responseText;
|
||||
}
|
||||
else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function fileExists(path: string): boolean {
|
||||
const response = Http.getFileFromServerSync(serverRoot + path);
|
||||
return response.status === 200;
|
||||
}
|
||||
|
||||
export const listFiles = Utils.memoize((path: string, spec?: RegExp, options?: { recursive?: boolean }): string[] => {
|
||||
const response = Http.getFileFromServerSync(serverRoot + path);
|
||||
if (response.status === 200) {
|
||||
let results = response.responseText.split(",");
|
||||
if (spec) {
|
||||
results = results.filter(file => spec.test(file));
|
||||
}
|
||||
if (options && !options.recursive) {
|
||||
results = results.filter(file => (ts.getDirectoryPath(ts.normalizeSlashes(file)) === path));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
else {
|
||||
return [""];
|
||||
}
|
||||
}, (path: string, spec?: RegExp, options?: { recursive?: boolean }) => `${path}|${spec}|${options ? options.recursive : undefined}`);
|
||||
|
||||
export function readFile(file: string): string | undefined {
|
||||
const response = Http.getFileFromServerSync(serverRoot + file);
|
||||
if (response.status === 200) {
|
||||
return response.responseText;
|
||||
}
|
||||
else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function writeFile(path: string, contents: string) {
|
||||
Http.writeToServerSync(serverRoot + path, "WRITE", contents);
|
||||
}
|
||||
|
||||
export function readDirectory(path: string, extension?: string[], exclude?: string[], include?: string[], depth?: number) {
|
||||
const fs = new Utils.VirtualFileSystem(path, useCaseSensitiveFileNames());
|
||||
for (const file of listFiles(path)) {
|
||||
fs.addFile(file);
|
||||
}
|
||||
return ts.matchFiles(path, extension, exclude, include, useCaseSensitiveFileNames(), getCurrentDirectory(), depth, path => {
|
||||
const entry = fs.getEntry(path);
|
||||
if (entry instanceof Utils.VirtualDirectory) {
|
||||
const directory = <Utils.VirtualDirectory>entry;
|
||||
return {
|
||||
files: ts.map(directory.getFiles(), f => f.name),
|
||||
directories: ts.map(directory.getDirectories(), d => d.name)
|
||||
};
|
||||
}
|
||||
return { files: [], directories: [] };
|
||||
});
|
||||
catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
function directoryName(path: string) {
|
||||
const dirPath = pathModule.dirname(path);
|
||||
// Node will just continue to repeat the root path, rather than return null
|
||||
return dirPath === path ? undefined : dirPath;
|
||||
}
|
||||
|
||||
function listFiles(path: string, spec: RegExp, options?: { recursive?: boolean }) {
|
||||
options = options || {};
|
||||
|
||||
function filesInFolder(folder: string): string[] {
|
||||
let paths: string[] = [];
|
||||
|
||||
const files = fs.readdirSync(folder);
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const pathToFile = pathModule.join(folder, files[i]);
|
||||
const stat = fs.statSync(pathToFile);
|
||||
if (options.recursive && stat.isDirectory()) {
|
||||
paths = paths.concat(filesInFolder(pathToFile));
|
||||
}
|
||||
else if (stat.isFile() && (!spec || files[i].match(spec))) {
|
||||
paths.push(pathToFile);
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
return filesInFolder(path);
|
||||
}
|
||||
|
||||
function getAccessibleFileSystemEntries(dirname: string): FileSystemEntries {
|
||||
try {
|
||||
const entries: string[] = fs.readdirSync(dirname || ".").sort(ts.sys.useCaseSensitiveFileNames ? ts.compareStrings : ts.compareStringsCaseInsensitive);
|
||||
const files: string[] = [];
|
||||
const directories: string[] = [];
|
||||
for (const entry of entries) {
|
||||
if (entry === "." || entry === "..") continue;
|
||||
const name = vpath.combine(dirname, entry);
|
||||
try {
|
||||
const stat = fs.statSync(name);
|
||||
if (!stat) continue;
|
||||
if (stat.isFile()) {
|
||||
files.push(entry);
|
||||
}
|
||||
else if (stat.isDirectory()) {
|
||||
directories.push(entry);
|
||||
}
|
||||
}
|
||||
catch (e) { }
|
||||
}
|
||||
return { files, directories };
|
||||
}
|
||||
catch (e) {
|
||||
return { files: [], directories: [] };
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
newLine: () => harnessNewLine,
|
||||
getCurrentDirectory: () => ts.sys.getCurrentDirectory(),
|
||||
useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames,
|
||||
resolvePath: (path: string) => ts.sys.resolvePath(path),
|
||||
readFile: path => ts.sys.readFile(path),
|
||||
writeFile: (path, content) => ts.sys.writeFile(path, content),
|
||||
directoryName,
|
||||
getDirectories: path => ts.sys.getDirectories(path),
|
||||
createDirectory: path => ts.sys.createDirectory(path),
|
||||
fileExists: path => ts.sys.fileExists(path),
|
||||
directoryExists: path => ts.sys.directoryExists(path),
|
||||
deleteFile,
|
||||
listFiles,
|
||||
log: s => console.log(s),
|
||||
args: () => ts.sys.args,
|
||||
getExecutingFilePath: () => ts.sys.getExecutingFilePath(),
|
||||
exit: exitCode => ts.sys.exit(exitCode),
|
||||
readDirectory: (path, extension, exclude, include, depth) => ts.sys.readDirectory(path, extension, exclude, include, depth),
|
||||
getAccessibleFileSystemEntries,
|
||||
tryEnableSourceMapsForHost: () => ts.sys.tryEnableSourceMapsForHost && ts.sys.tryEnableSourceMapsForHost(),
|
||||
getMemoryUsage: () => ts.sys.getMemoryUsage && ts.sys.getMemoryUsage(),
|
||||
getEnvironmentVariable: name => ts.sys.getEnvironmentVariable(name),
|
||||
};
|
||||
}
|
||||
|
||||
interface URL {
|
||||
hash: string;
|
||||
host: string;
|
||||
hostname: string;
|
||||
href: string;
|
||||
password: string;
|
||||
pathname: string;
|
||||
port: string;
|
||||
protocol: string;
|
||||
search: string;
|
||||
username: string;
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
declare var URL: {
|
||||
prototype: URL;
|
||||
new(url: string, base?: string | URL): URL;
|
||||
};
|
||||
|
||||
function createBrowserIO(): IO {
|
||||
const serverRoot = new URL("http://localhost:8888/");
|
||||
|
||||
interface HttpHeaders {
|
||||
[key: string]: string | string[] | undefined;
|
||||
}
|
||||
|
||||
const HttpHeaders = {
|
||||
combine(left: HttpHeaders | undefined, right: HttpHeaders | undefined): HttpHeaders {
|
||||
return left && right ? { ...left, ...right } :
|
||||
left ? { ...left } :
|
||||
right ? { ...right } :
|
||||
{};
|
||||
},
|
||||
writeRequestHeaders(xhr: XMLHttpRequest, headers: HttpHeaders) {
|
||||
for (const key in headers) {
|
||||
if (!headers.hasOwnProperty(key)) continue;
|
||||
const keyLower = key.toLowerCase();
|
||||
|
||||
if (keyLower === "access-control-allow-origin" || keyLower === "content-length") continue;
|
||||
const values = headers[key];
|
||||
const value = Array.isArray(values) ? values.join(",") : values;
|
||||
if (keyLower === "content-type") {
|
||||
xhr.overrideMimeType(value);
|
||||
continue;
|
||||
}
|
||||
|
||||
xhr.setRequestHeader(key, value);
|
||||
}
|
||||
},
|
||||
readResponseHeaders(xhr: XMLHttpRequest): HttpHeaders {
|
||||
const allHeaders = xhr.getAllResponseHeaders();
|
||||
const headers: HttpHeaders = {};
|
||||
for (const header of allHeaders.split(/\r\n/g)) {
|
||||
const colonIndex = header.indexOf(":");
|
||||
if (colonIndex >= 0) {
|
||||
const key = header.slice(0, colonIndex).trim();
|
||||
const value = header.slice(colonIndex + 1).trim();
|
||||
const values = value.split(",");
|
||||
headers[key] = values.length > 1 ? values : value;
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
};
|
||||
|
||||
interface HttpContent {
|
||||
headers: HttpHeaders;
|
||||
content: string;
|
||||
}
|
||||
|
||||
const HttpContent = {
|
||||
create(headers: HttpHeaders, content: string): HttpContent {
|
||||
return { headers, content };
|
||||
},
|
||||
fromMediaType(mediaType: string, content: string) {
|
||||
return HttpContent.create({ "Content-Type": mediaType }, content);
|
||||
},
|
||||
text(content: string) {
|
||||
return HttpContent.fromMediaType("text/plain", content);
|
||||
},
|
||||
json(content: object) {
|
||||
return HttpContent.fromMediaType("application/json", JSON.stringify(content));
|
||||
},
|
||||
readResponseContent(xhr: XMLHttpRequest) {
|
||||
if (typeof xhr.responseText === "string") {
|
||||
return HttpContent.create({
|
||||
"Content-Type": xhr.getResponseHeader("Content-Type") || undefined,
|
||||
"Content-Length": xhr.getResponseHeader("Content-Length") || undefined
|
||||
}, xhr.responseText);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
interface HttpRequestMessage {
|
||||
method: string;
|
||||
url: URL;
|
||||
headers: HttpHeaders;
|
||||
content?: HttpContent;
|
||||
}
|
||||
|
||||
const HttpRequestMessage = {
|
||||
create(method: string, url: string | URL, headers: HttpHeaders = {}, content?: HttpContent): HttpRequestMessage {
|
||||
if (typeof url === "string") url = new URL(url);
|
||||
return { method, url, headers, content };
|
||||
},
|
||||
options(url: string | URL) {
|
||||
return HttpRequestMessage.create("OPTIONS", url);
|
||||
},
|
||||
head(url: string | URL) {
|
||||
return HttpRequestMessage.create("HEAD", url);
|
||||
},
|
||||
get(url: string | URL) {
|
||||
return HttpRequestMessage.create("GET", url);
|
||||
},
|
||||
delete(url: string | URL) {
|
||||
return HttpRequestMessage.create("DELETE", url);
|
||||
},
|
||||
put(url: string | URL, content: HttpContent) {
|
||||
return HttpRequestMessage.create("PUT", url, {}, content);
|
||||
},
|
||||
post(url: string | URL, content: HttpContent) {
|
||||
return HttpRequestMessage.create("POST", url, {}, content);
|
||||
},
|
||||
};
|
||||
|
||||
interface HttpResponseMessage {
|
||||
statusCode: number;
|
||||
statusMessage: string;
|
||||
headers: HttpHeaders;
|
||||
content?: HttpContent;
|
||||
}
|
||||
|
||||
const HttpResponseMessage = {
|
||||
create(statusCode: number, statusMessage: string, headers: HttpHeaders = {}, content?: HttpContent): HttpResponseMessage {
|
||||
return { statusCode, statusMessage, headers, content };
|
||||
},
|
||||
notFound(): HttpResponseMessage {
|
||||
return HttpResponseMessage.create(404, "Not Found");
|
||||
},
|
||||
hasSuccessStatusCode(response: HttpResponseMessage) {
|
||||
return response.statusCode === 304 || (response.statusCode >= 200 && response.statusCode < 300);
|
||||
},
|
||||
readResponseMessage(xhr: XMLHttpRequest) {
|
||||
return HttpResponseMessage.create(
|
||||
xhr.status,
|
||||
xhr.statusText,
|
||||
HttpHeaders.readResponseHeaders(xhr),
|
||||
HttpContent.readResponseContent(xhr));
|
||||
}
|
||||
};
|
||||
|
||||
function send(request: HttpRequestMessage): HttpResponseMessage {
|
||||
const xhr = new XMLHttpRequest();
|
||||
try {
|
||||
HttpHeaders.writeRequestHeaders(xhr, request.headers);
|
||||
HttpHeaders.writeRequestHeaders(xhr, request.content && request.content.headers);
|
||||
xhr.setRequestHeader("Access-Control-Allow-Origin", "*");
|
||||
xhr.open(request.method, request.url.toString(), /*async*/ false);
|
||||
xhr.send(request.content && request.content.content);
|
||||
while (xhr.readyState !== 4); // block until ready
|
||||
return HttpResponseMessage.readResponseMessage(xhr);
|
||||
}
|
||||
catch (e) {
|
||||
return HttpResponseMessage.notFound();
|
||||
}
|
||||
}
|
||||
|
||||
let caseSensitivity: "CI" | "CS" | undefined;
|
||||
|
||||
function useCaseSensitiveFileNames() {
|
||||
if (!caseSensitivity) {
|
||||
const response = send(HttpRequestMessage.options(new URL("*", serverRoot)));
|
||||
const xCaseSensitivity = response.headers["X-Case-Sensitivity"];
|
||||
caseSensitivity = xCaseSensitivity === "CS" ? "CS" : "CI";
|
||||
}
|
||||
return caseSensitivity === "CS";
|
||||
}
|
||||
|
||||
function resolvePath(path: string) {
|
||||
const response = send(HttpRequestMessage.post(new URL("/api/resolve", serverRoot), HttpContent.text(path)));
|
||||
return HttpResponseMessage.hasSuccessStatusCode(response) && response.content ? response.content.content : undefined;
|
||||
}
|
||||
|
||||
function readFile(path: string): string | undefined {
|
||||
const response = send(HttpRequestMessage.get(new URL(path, serverRoot)));
|
||||
return HttpResponseMessage.hasSuccessStatusCode(response) && response.content ? response.content.content : undefined;
|
||||
}
|
||||
|
||||
function writeFile(path: string, contents: string) {
|
||||
send(HttpRequestMessage.put(new URL(path, serverRoot), HttpContent.text(contents)));
|
||||
}
|
||||
|
||||
function fileExists(path: string): boolean {
|
||||
const response = send(HttpRequestMessage.head(new URL(path, serverRoot)));
|
||||
return HttpResponseMessage.hasSuccessStatusCode(response);
|
||||
}
|
||||
|
||||
function directoryExists(path: string): boolean {
|
||||
const response = send(HttpRequestMessage.post(new URL("/api/directoryExists", serverRoot), HttpContent.text(path)));
|
||||
return HttpResponseMessage.hasSuccessStatusCode(response)
|
||||
&& (response.content && response.content.content) === "true";
|
||||
}
|
||||
|
||||
function deleteFile(path: string) {
|
||||
send(HttpRequestMessage.delete(new URL(path, serverRoot)));
|
||||
}
|
||||
|
||||
function directoryName(path: string) {
|
||||
const url = new URL(path, serverRoot);
|
||||
return ts.getDirectoryPath(ts.normalizeSlashes(url.pathname || "/"));
|
||||
}
|
||||
|
||||
function listFiles(dirname: string, spec?: RegExp, options?: { recursive?: boolean }): string[] {
|
||||
if (spec || (options && !options.recursive)) {
|
||||
let results = IO.listFiles(dirname);
|
||||
if (spec) {
|
||||
results = results.filter(file => spec.test(file));
|
||||
}
|
||||
if (options && !options.recursive) {
|
||||
results = results.filter(file => ts.getDirectoryPath(ts.normalizeSlashes(file)) === dirname);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
const response = send(HttpRequestMessage.post(new URL("/api/listFiles", serverRoot), HttpContent.text(dirname)));
|
||||
return HttpResponseMessage.hasSuccessStatusCode(response)
|
||||
&& response.content
|
||||
&& response.content.headers["Content-Type"] === "application/json"
|
||||
? JSON.parse(response.content.content)
|
||||
: [];
|
||||
}
|
||||
|
||||
function readDirectory(path: string, extension?: string[], exclude?: string[], include?: string[], depth?: number) {
|
||||
const fs = new vfs.VirtualFileSystem(path, useCaseSensitiveFileNames());
|
||||
fs.addFiles(IO.listFiles(path));
|
||||
return ts.matchFiles(path, extension, exclude, include, useCaseSensitiveFileNames(), /*currentDirectory*/ "", depth, path => getAccessibleVirtualFileSystemEntries(fs, path));
|
||||
}
|
||||
|
||||
function getAccessibleFileSystemEntries(dirname: string): FileSystemEntries {
|
||||
const fs = new vfs.VirtualFileSystem(dirname, useCaseSensitiveFileNames());
|
||||
fs.addFiles(IO.listFiles(dirname));
|
||||
return getAccessibleVirtualFileSystemEntries(fs, dirname);
|
||||
}
|
||||
|
||||
function getAccessibleVirtualFileSystemEntries(fs: vfs.VirtualFileSystem, dirname: string): FileSystemEntries {
|
||||
const directory = fs.getDirectory(dirname);
|
||||
return directory
|
||||
? { files: directory.getFileNames(), directories: directory.getDirectoryNames() }
|
||||
: { files: [], directories: [] };
|
||||
}
|
||||
|
||||
return {
|
||||
newLine: () => harnessNewLine,
|
||||
getCurrentDirectory: () => "",
|
||||
useCaseSensitiveFileNames,
|
||||
resolvePath,
|
||||
readFile,
|
||||
writeFile,
|
||||
directoryName: Utils.memoize(directoryName, path => path),
|
||||
getDirectories: () => [],
|
||||
createDirectory: () => {},
|
||||
fileExists,
|
||||
directoryExists,
|
||||
deleteFile,
|
||||
listFiles: Utils.memoize(listFiles, (path, spec, options) => `${path}|${spec}|${options ? options.recursive === true : true}`),
|
||||
log: s => console.log(s),
|
||||
args: () => [],
|
||||
getExecutingFilePath: () => "",
|
||||
exit: () => {},
|
||||
readDirectory,
|
||||
getAccessibleFileSystemEntries
|
||||
};
|
||||
}
|
||||
|
||||
export function mockHash(s: string): string {
|
||||
@ -764,10 +901,10 @@ namespace Harness {
|
||||
const environment = Utils.getExecutionEnvironment();
|
||||
switch (environment) {
|
||||
case Utils.ExecutionEnvironment.Node:
|
||||
IO = IOImpl.Node;
|
||||
IO = createNodeIO();
|
||||
break;
|
||||
case Utils.ExecutionEnvironment.Browser:
|
||||
IO = IOImpl.Network;
|
||||
IO = createBrowserIO();
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown value '${environment}' for ExecutionEnvironment.`);
|
||||
|
||||
@ -123,7 +123,7 @@ namespace Harness.LanguageService {
|
||||
}
|
||||
|
||||
export class LanguageServiceAdapterHost {
|
||||
protected virtualFileSystem: Utils.VirtualFileSystem = new Utils.VirtualFileSystem(virtualFileSystemRoot, /*useCaseSensitiveFilenames*/ false);
|
||||
protected virtualFileSystem: vfs.VirtualFileSystem = new vfs.VirtualFileSystem(virtualFileSystemRoot, /*useCaseSensitiveFilenames*/ false);
|
||||
|
||||
constructor(protected cancellationToken = DefaultHostCancellationToken.Instance,
|
||||
protected settings = ts.getDefaultCompilerOptions()) {
|
||||
@ -135,8 +135,8 @@ namespace Harness.LanguageService {
|
||||
|
||||
public getFilenames(): string[] {
|
||||
const fileNames: string[] = [];
|
||||
for (const virtualEntry of this.virtualFileSystem.getFiles({ recursive: true })) {
|
||||
const scriptInfo = virtualEntry.metadata.get("scriptInfo");
|
||||
for (const virtualEntry of this.virtualFileSystem.getDirectory("/").getFiles({ recursive: true })) {
|
||||
const scriptInfo = virtualEntry.metadata.get("scriptInfo") as ScriptInfo;
|
||||
if (scriptInfo && 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.
|
||||
@ -160,7 +160,7 @@ namespace Harness.LanguageService {
|
||||
const script = file && file.metadata.get("scriptInfo") as ScriptInfo;
|
||||
if (script) {
|
||||
script.editContent(start, end, newText);
|
||||
file.setContent(script.content);
|
||||
file.content = script.content;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -99,6 +99,7 @@
|
||||
"runner.ts",
|
||||
"collections.ts",
|
||||
"vpath.ts",
|
||||
"events.ts",
|
||||
"vfs.ts",
|
||||
"virtualFileSystemWithWatch.ts",
|
||||
"../server/protocol.ts",
|
||||
|
||||
1707
src/harness/vfs.ts
1707
src/harness/vfs.ts
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,7 @@
|
||||
/// <reference path="harness.ts" />
|
||||
|
||||
// TODO(rbuckton): Migrate this to use vfs.
|
||||
|
||||
namespace ts.TestFSWithWatch {
|
||||
const { content: libFileContent } = Harness.getDefaultLibraryFile(Harness.IO);
|
||||
export const libFile: FileOrFolder = {
|
||||
|
||||
@ -1,10 +1,16 @@
|
||||
/// <reference path="./harness.ts" />
|
||||
|
||||
namespace vpath {
|
||||
import compareStrings = ts.compareStrings;
|
||||
// NOTE: Some of the functions here duplicate functionality from compiler/core.ts. They have been added
|
||||
// to reduce the number of direct dependencies on compiler and services to eventually break away
|
||||
// from depending directly on the compiler to speed up compilation time.
|
||||
|
||||
export function normalizeSlashes(path: string): string {
|
||||
return path.replace(/\s*[\\/]\s*/g, "/").trim();
|
||||
import compareValues = collections.compareValues;
|
||||
import compareStrings = collections.compareStrings;
|
||||
|
||||
export const sep = "/";
|
||||
|
||||
export function normalizeSeparators(path: string): string {
|
||||
return path.replace(/\s*[\\/]\s*/g, sep).trim();
|
||||
}
|
||||
|
||||
const rootRegExp = /^[\\/]([\\/](.*?[\\/](.*?[\\/])?)?)?|^[a-zA-Z]:[\\/]?|^\w+:\/{2}[^\\/]*\/?/;
|
||||
@ -20,10 +26,18 @@ namespace vpath {
|
||||
|
||||
const trailingSeperatorRegExp = /[\\/]$/;
|
||||
|
||||
export function hasTrailingSeperator(path: string) {
|
||||
export function hasTrailingSeparator(path: string) {
|
||||
return trailingSeperatorRegExp.test(path);
|
||||
}
|
||||
|
||||
export function addTrailingSeparator(path: string) {
|
||||
return hasTrailingSeparator(path) ? path : path + "/";
|
||||
}
|
||||
|
||||
export function removeTrailingSeparator(path: string) {
|
||||
return hasTrailingSeparator(path) ? path.slice(0, -1) : path;
|
||||
}
|
||||
|
||||
function reduce(components: string[]) {
|
||||
const normalized = [components[0]];
|
||||
for (let i = 1; i < components.length; i++) {
|
||||
@ -41,20 +55,16 @@ namespace vpath {
|
||||
|
||||
export function normalize(path: string): string {
|
||||
const components = reduce(parse(path));
|
||||
return components.length > 1 && hasTrailingSeperator(path) ? format(components) + "/" : format(components);
|
||||
return components.length > 1 && hasTrailingSeparator(path) ? format(components) + sep : format(components);
|
||||
}
|
||||
|
||||
export function combine(path: string, ...paths: string[]) {
|
||||
path = normalizeSlashes(path);
|
||||
path = normalizeSeparators(path);
|
||||
for (let name of paths) {
|
||||
name = normalizeSlashes(name);
|
||||
name = normalizeSeparators(name);
|
||||
if (name.length === 0) continue;
|
||||
if (path.length === 0 || isAbsolute(name)) {
|
||||
path = name;
|
||||
}
|
||||
else {
|
||||
path = hasTrailingSeperator(path) ? path + name : path + "/" + name;
|
||||
}
|
||||
path = path.length === 0 || isAbsolute(name) ? name :
|
||||
addTrailingSeparator(path) + name;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
@ -89,25 +99,65 @@ namespace vpath {
|
||||
return format(["", ...components]);
|
||||
}
|
||||
|
||||
export namespace relative {
|
||||
export function caseSensitive(from: string, to: string) { return relative(from, to, /*ignoreCase*/ false); }
|
||||
export function caseInsensitive(from: string, to: string) { return relative(from, to, /*ignoreCase*/ true); }
|
||||
}
|
||||
|
||||
export function compare(a: string, b: string, ignoreCase: boolean) {
|
||||
if (!isAbsolute(a)) throw new Error("Path not absolute");
|
||||
if (!isAbsolute(b)) throw new Error("Path not absolute");
|
||||
if (a === b) return 0;
|
||||
a = removeTrailingSeparator(a);
|
||||
b = removeTrailingSeparator(b);
|
||||
if (a === b) return 0;
|
||||
const aComponents = reduce(parse(a));
|
||||
const bComponents = reduce(parse(b));
|
||||
const len = Math.min(aComponents.length, bComponents.length);
|
||||
for (let i = 0; i < len; i++) {
|
||||
const result = compareStrings(aComponents[i], bComponents[i], ignoreCase);
|
||||
if (result !== 0) return result;
|
||||
}
|
||||
return compareValues(aComponents.length, bComponents.length);
|
||||
}
|
||||
|
||||
export namespace compare {
|
||||
export function caseSensitive(a: string, b: string) { return compare(a, b, /*ignoreCase*/ false); }
|
||||
export function caseInsensitive(a: string, b: string) { return compare(a, b, /*ignoreCase*/ true); }
|
||||
}
|
||||
|
||||
export function equals(a: string, b: string, ignoreCase: boolean) {
|
||||
return compare(a, b, ignoreCase) === 0;
|
||||
}
|
||||
|
||||
export namespace equals {
|
||||
export function caseSensitive(a: string, b: string) { return equals(a, b, /*ignoreCase*/ false); }
|
||||
export function caseInsensitive(a: string, b: string) { return equals(a, b, /*ignoreCase*/ true); }
|
||||
}
|
||||
|
||||
export function beneath(ancestor: string, descendant: string, ignoreCase: boolean) {
|
||||
if (!isAbsolute(ancestor)) throw new Error("Path not absolute");
|
||||
if (!isAbsolute(descendant)) throw new Error("Path not absolute");
|
||||
|
||||
const ancestorComponents = reduce(parse(ancestor));
|
||||
const descendantComponents = reduce(parse(descendant));
|
||||
|
||||
const len = Math.min(ancestorComponents.length, descendantComponents.length);
|
||||
let start: number;
|
||||
for (start = 0; start < ancestorComponents.length && start < descendantComponents.length; start++) {
|
||||
for (start = 0; start < len; start++) {
|
||||
if (compareStrings(ancestorComponents[start], descendantComponents[start], ignoreCase)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return start === ancestorComponents.length;
|
||||
}
|
||||
|
||||
export namespace beneath {
|
||||
export function caseSensitive(ancestor: string, descendant: string) { return beneath(ancestor, descendant, /*ignoreCase*/ false); }
|
||||
export function caseInsensitive(ancestor: string, descendant: string) { return beneath(ancestor, descendant, /*ignoreCase*/ true); }
|
||||
}
|
||||
|
||||
export function parse(path: string) {
|
||||
path = normalizeSlashes(path);
|
||||
path = normalizeSeparators(path);
|
||||
const rootLength = getRootLength(path);
|
||||
const root = path.substring(0, rootLength);
|
||||
const rest = path.substring(rootLength).split(/\/+/g);
|
||||
@ -116,19 +166,19 @@ namespace vpath {
|
||||
}
|
||||
|
||||
export function format(components: string[]) {
|
||||
return components.length ? components[0] + components.slice(1).join("/") : "";
|
||||
return components.length ? components[0] + components.slice(1).join(sep) : "";
|
||||
}
|
||||
|
||||
export function dirname(path: string) {
|
||||
path = normalizeSlashes(path);
|
||||
return path.substr(0, Math.max(getRootLength(path), path.lastIndexOf("/")));
|
||||
path = normalizeSeparators(path);
|
||||
return path.substr(0, Math.max(getRootLength(path), path.lastIndexOf(sep)));
|
||||
}
|
||||
|
||||
export function basename(path: string, ext?: string): string;
|
||||
export function basename(path: string, options?: { extensions?: string[], ignoreCase?: boolean }): string;
|
||||
export function basename(path: string, options?: { extensions?: string[], ignoreCase?: boolean } | string) {
|
||||
path = normalizeSlashes(path);
|
||||
const name = path.substr(Math.max(getRootLength(path), path.lastIndexOf("/") + 1));
|
||||
path = normalizeSeparators(path);
|
||||
const name = path.substr(Math.max(getRootLength(path), path.lastIndexOf(sep) + 1));
|
||||
const extension = typeof options === "string" ? options.startsWith(".") ? options : "." + options :
|
||||
options && options.extensions ? extname(name, options) :
|
||||
undefined;
|
||||
@ -136,6 +186,7 @@ namespace vpath {
|
||||
}
|
||||
|
||||
const extRegExp = /\.\w+$/;
|
||||
|
||||
export function extname(path: string, options?: { extensions?: string[], ignoreCase?: boolean }) {
|
||||
if (options && options.extensions) {
|
||||
for (let extension of options.extensions) {
|
||||
@ -153,9 +204,4 @@ namespace vpath {
|
||||
const match = extRegExp.exec(path);
|
||||
return match ? match[0] : "";
|
||||
}
|
||||
|
||||
export function chext(path: string, ext: string, options?: { extensions?: string[], ignoreCase?: boolean }) {
|
||||
const pathext = extname(path, options);
|
||||
return pathext ? path.slice(0, path.length - pathext.length) + (ext.startsWith(".") ? ext : "." + ext) : path;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user