mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Initial support for globs in tsconfig.json
This commit is contained in:
@@ -495,46 +495,51 @@ namespace ts {
|
||||
};
|
||||
|
||||
function getFileNames(): string[] {
|
||||
let fileNames: string[] = [];
|
||||
let fileNames: string[];
|
||||
if (hasProperty(json, "files")) {
|
||||
if (json["files"] instanceof Array) {
|
||||
fileNames = map(<string[]>json["files"], s => combinePaths(basePath, s));
|
||||
if (isArray(json["files"])) {
|
||||
fileNames = <string[]>json["files"];
|
||||
}
|
||||
else {
|
||||
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "files", "Array"));
|
||||
}
|
||||
}
|
||||
else {
|
||||
const filesSeen: Map<boolean> = {};
|
||||
const exclude = json["exclude"] instanceof Array ? map(<string[]>json["exclude"], normalizeSlashes) : undefined;
|
||||
const supportedExtensions = getSupportedExtensions(options);
|
||||
Debug.assert(indexOf(supportedExtensions, ".ts") < indexOf(supportedExtensions, ".d.ts"), "Changed priority of extensions to pick");
|
||||
|
||||
// Get files of supported extensions in their order of resolution
|
||||
for (const extension of supportedExtensions) {
|
||||
const filesInDirWithExtension = host.readDirectory(basePath, extension, exclude);
|
||||
for (const fileName of filesInDirWithExtension) {
|
||||
// .ts extension would read the .d.ts extension files too but since .d.ts is lower priority extension,
|
||||
// lets pick them when its turn comes up
|
||||
if (extension === ".ts" && fileExtensionIs(fileName, ".d.ts")) {
|
||||
continue;
|
||||
}
|
||||
let includeSpecs: string[];
|
||||
if (hasProperty(json, "include")) {
|
||||
if (isArray(json["include"])) {
|
||||
includeSpecs = <string[]>json["include"];
|
||||
}
|
||||
else {
|
||||
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "include", "Array"));
|
||||
}
|
||||
}
|
||||
|
||||
// If this is one of the output extension (which would be .d.ts and .js if we are allowing compilation of js files)
|
||||
// do not include this file if we included .ts or .tsx file with same base name as it could be output of the earlier compilation
|
||||
if (extension === ".d.ts" || (options.allowJs && contains(supportedJavascriptExtensions, extension))) {
|
||||
const baseName = fileName.substr(0, fileName.length - extension.length);
|
||||
if (hasProperty(filesSeen, baseName + ".ts") || hasProperty(filesSeen, baseName + ".tsx")) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let excludeSpecs: string[];
|
||||
if (hasProperty(json, "exclude")) {
|
||||
if (isArray(json["exclude"])) {
|
||||
excludeSpecs = <string[]>json["exclude"];
|
||||
}
|
||||
else {
|
||||
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "exclude", "Array"));
|
||||
}
|
||||
}
|
||||
|
||||
filesSeen[fileName] = true;
|
||||
fileNames.push(fileName);
|
||||
if (fileNames === undefined && includeSpecs === undefined) {
|
||||
includeSpecs = ["**/*.ts"];
|
||||
if (options.jsx) {
|
||||
includeSpecs.push("**/*.tsx");
|
||||
}
|
||||
|
||||
if (options.allowJs) {
|
||||
includeSpecs.push("**/*.js");
|
||||
if (options.jsx) {
|
||||
includeSpecs.push("**/*.jsx");
|
||||
}
|
||||
}
|
||||
}
|
||||
return fileNames;
|
||||
|
||||
return expandFiles(fileNames, includeSpecs, excludeSpecs, basePath, options, host, errors);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,4 +589,514 @@ namespace ts {
|
||||
|
||||
return { options, errors };
|
||||
}
|
||||
|
||||
// Simplified whitelist, forces escaping of any non-word (or digit), non-whitespace character.
|
||||
const reservedCharacterPattern = /[^\w\s]/g;
|
||||
|
||||
const enum ExpandResult {
|
||||
Ok,
|
||||
Error
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands an array of file specifications.
|
||||
*
|
||||
* @param fileNames The literal file names to include.
|
||||
* @param includeSpecs The file specifications to expand.
|
||||
* @param excludeSpecs The file specifications to exclude.
|
||||
* @param basePath The base path for any relative file specifications.
|
||||
* @param options Compiler options.
|
||||
* @param host The host used to resolve files and directories.
|
||||
* @param errors An array for diagnostic reporting.
|
||||
*/
|
||||
export function expandFiles(fileNames: string[], includeSpecs: string[], excludeSpecs: string[], basePath: string, options: CompilerOptions, host: ParseConfigHost, errors?: Diagnostic[]): string[] {
|
||||
basePath = normalizePath(basePath);
|
||||
basePath = removeTrailingDirectorySeparator(basePath);
|
||||
|
||||
const excludePattern = includeSpecs ? createExcludeRegularExpression(excludeSpecs, basePath, options, host, errors) : undefined;
|
||||
const fileSet = createFileMap<Path>(host.useCaseSensitiveFileNames ? caseSensitiveKeyMapper : caseInsensitiveKeyMapper);
|
||||
|
||||
// include every literal file.
|
||||
if (fileNames) {
|
||||
for (const fileName of fileNames) {
|
||||
const path = toPath(fileName, basePath, caseSensitiveKeyMapper);
|
||||
if (!fileSet.contains(path)) {
|
||||
fileSet.set(path, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// expand and include the provided files into the file set.
|
||||
if (includeSpecs) {
|
||||
for (let includeSpec of includeSpecs) {
|
||||
includeSpec = normalizePath(includeSpec);
|
||||
includeSpec = removeTrailingDirectorySeparator(includeSpec);
|
||||
expandFileSpec(basePath, includeSpec, 0, excludePattern, options, host, errors, fileSet);
|
||||
}
|
||||
}
|
||||
|
||||
const output = fileSet.reduce(addFileToOutput, []);
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands a file specification with wildcards.
|
||||
*
|
||||
* @param basePath The directory to expand.
|
||||
* @param fileSpec The original file specification.
|
||||
* @param start The starting offset in the file specification.
|
||||
* @param excludePattern A pattern used to exclude a file specification.
|
||||
* @param options Compiler options.
|
||||
* @param host The host used to resolve files and directories.
|
||||
* @param errors An array for diagnostic reporting.
|
||||
* @param fileSet The set of matching files.
|
||||
* @param isExpandingRecursiveDirectory A value indicating whether the file specification includes a recursive directory wildcard prior to the start of this segment.
|
||||
*/
|
||||
function expandFileSpec(basePath: string, fileSpec: string, start: number, excludePattern: RegExp, options: CompilerOptions, host: ParseConfigHost, errors: Diagnostic[], fileSet: FileMap<Path>, isExpandingRecursiveDirectory?: boolean): ExpandResult {
|
||||
// Skip expansion if the base path matches an exclude pattern.
|
||||
if (isExcludedPath(excludePattern, basePath)) {
|
||||
return ExpandResult.Ok;
|
||||
}
|
||||
|
||||
// Find the offset of the next wildcard in the file specification
|
||||
let offset = indexOfWildcard(fileSpec, start);
|
||||
if (offset < 0) {
|
||||
// There were no more wildcards, so include the file.
|
||||
const path = toPath(fileSpec.substring(start), basePath, caseSensitiveKeyMapper);
|
||||
includeFile(path, excludePattern, options, host, fileSet);
|
||||
return ExpandResult.Ok;
|
||||
}
|
||||
|
||||
// Find the last directory separator before the wildcard to get the leading path.
|
||||
offset = fileSpec.lastIndexOf(directorySeparator, offset);
|
||||
if (offset > start) {
|
||||
// The wildcard occurs in a later segment, include remaining path up to
|
||||
// wildcard in prefix.
|
||||
basePath = combinePaths(basePath, fileSpec.substring(start, offset));
|
||||
|
||||
// Skip this wildcard path if the base path now matches an exclude pattern.
|
||||
if (isExcludedPath(excludePattern, basePath)) {
|
||||
return ExpandResult.Ok;
|
||||
}
|
||||
|
||||
start = offset + 1;
|
||||
}
|
||||
|
||||
// Find the offset of the next directory separator to extract the wildcard path segment.
|
||||
offset = getEndOfPathSegment(fileSpec, start);
|
||||
|
||||
// Check if the current offset is the beginning of a recursive directory pattern.
|
||||
if (isRecursiveDirectoryWildcard(fileSpec, start, offset)) {
|
||||
if (offset >= fileSpec.length) {
|
||||
// If there is no file specification following the recursive directory pattern
|
||||
// we cannot match any files, so we will ignore this pattern.
|
||||
return ExpandResult.Ok;
|
||||
}
|
||||
|
||||
// Stop expansion if a file specification contains more than one recursive directory pattern.
|
||||
if (isExpandingRecursiveDirectory) {
|
||||
if (errors) {
|
||||
errors.push(createCompilerDiagnostic(Diagnostics.File_specification_cannot_contain_multiple_recursive_directory_wildcards_Asterisk_Asterisk_Colon_0, fileSpec));
|
||||
}
|
||||
|
||||
return ExpandResult.Error;
|
||||
}
|
||||
|
||||
// Expand the recursive directory pattern.
|
||||
return expandRecursiveDirectory(basePath, fileSpec, offset + 1, excludePattern, options, host, errors, fileSet);
|
||||
}
|
||||
|
||||
// Match the entries in the directory against the wildcard pattern.
|
||||
const pattern = createRegularExpressionFromWildcard(fileSpec, start, offset, host);
|
||||
|
||||
// If there are no more directory separators (the offset is at the end of the file specification), then
|
||||
// this must be a file.
|
||||
if (offset >= fileSpec.length) {
|
||||
const files = host.readFileNames(basePath);
|
||||
for (const extension of getSupportedExtensions(options)) {
|
||||
for (const file of files) {
|
||||
if (fileExtensionIs(file, extension)) {
|
||||
const path = toPath(file, basePath, caseSensitiveKeyMapper);
|
||||
|
||||
// .ts extension would read the .d.ts extension files too but since .d.ts is lower priority extension,
|
||||
// lets pick them when its turn comes up.
|
||||
if (extension === ".ts" && fileExtensionIs(file, ".d.ts")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this is one of the output extension (which would be .d.ts and .js if we are allowing compilation of js files)
|
||||
// do not include this file if we included .ts or .tsx file with same base name as it could be output of the earlier compilation
|
||||
if (extension === ".d.ts" || (options.allowJs && contains(supportedJavascriptExtensions, extension))) {
|
||||
if (fileSet.contains(changeExtension(path, ".ts")) || fileSet.contains(changeExtension(path, ".tsx"))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// This wildcard has no further directory to process, so include the file.
|
||||
includeFile(path, excludePattern, options, host, fileSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
const directories = host.readDirectoryNames(basePath);
|
||||
for (const directory of directories) {
|
||||
// If this was a directory, process the directory.
|
||||
const path = toPath(directory, basePath, caseSensitiveKeyMapper);
|
||||
if (expandFileSpec(path, fileSpec, offset + 1, excludePattern, options, host, errors, fileSet, isExpandingRecursiveDirectory) === ExpandResult.Error) {
|
||||
return ExpandResult.Error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ExpandResult.Ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands a `**` recursive directory wildcard.
|
||||
*
|
||||
* @param basePath The directory to recursively expand.
|
||||
* @param fileSpec The original file specification.
|
||||
* @param start The starting offset in the file specification.
|
||||
* @param excludePattern A pattern used to exclude a file specification.
|
||||
* @param options Compiler options.
|
||||
* @param host The host used to resolve files and directories.
|
||||
* @param errors An array for diagnostic reporting.
|
||||
* @param fileSet The set of matching files.
|
||||
*/
|
||||
function expandRecursiveDirectory(basePath: string, fileSpec: string, start: number, excludePattern: RegExp, options: CompilerOptions, host: ParseConfigHost, errors: Diagnostic[], fileSet: FileMap<Path>): ExpandResult {
|
||||
// expand the non-recursive part of the file specification against the prefix path.
|
||||
if (expandFileSpec(basePath, fileSpec, start, excludePattern, options, host, errors, fileSet, /*isExpandingRecursiveDirectory*/ true) === ExpandResult.Error) {
|
||||
return ExpandResult.Error;
|
||||
}
|
||||
|
||||
// Recursively expand each subdirectory.
|
||||
const directories = host.readDirectoryNames(basePath);
|
||||
for (const entry of directories) {
|
||||
const path = combinePaths(basePath, entry);
|
||||
if (expandRecursiveDirectory(path, fileSpec, start, excludePattern, options, host, errors, fileSet) === ExpandResult.Error) {
|
||||
return ExpandResult.Error;
|
||||
}
|
||||
}
|
||||
|
||||
return ExpandResult.Ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to include a file in a file set.
|
||||
*
|
||||
* @param file The file to include.
|
||||
* @param excludePattern A pattern used to exclude a file specification.
|
||||
* @param options Compiler options.
|
||||
* @param host The host used to resolve files and directories.
|
||||
* @param fileSet The set of matching files.
|
||||
*/
|
||||
function includeFile(file: Path, excludePattern: RegExp, options: CompilerOptions, host: ParseConfigHost, fileSet: FileMap<Path>): void {
|
||||
// Ignore the file if it should be excluded.
|
||||
if (isExcludedPath(excludePattern, file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore the file if it doesn't exist.
|
||||
if (!host.fileExists(file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore the file if it does not have a supported extension.
|
||||
if (!options.allowNonTsExtensions && !isSupportedSourceFileName(file, options)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fileSet.contains(file)) {
|
||||
fileSet.set(file, file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a file to an array of files.
|
||||
*
|
||||
* @param output The output array.
|
||||
* @param file The file path.
|
||||
*/
|
||||
function addFileToOutput(output: string[], file: string) {
|
||||
output.push(file);
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a path should be excluded.
|
||||
*
|
||||
* @param excludePattern A pattern used to exclude a file specification.
|
||||
* @param path The path to test for exclusion.
|
||||
*/
|
||||
function isExcludedPath(excludePattern: RegExp, path: string) {
|
||||
return excludePattern ? excludePattern.test(path) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a regular expression from a glob-style wildcard.
|
||||
*
|
||||
* @param fileSpec The file specification.
|
||||
* @param start The starting offset in the file specification.
|
||||
* @param end The end offset in the file specification.
|
||||
* @param host The host used to resolve files and directories.
|
||||
*/
|
||||
function createRegularExpressionFromWildcard(fileSpec: string, start: number, end: number, host: ParseConfigHost): RegExp {
|
||||
const pattern = createPatternFromWildcard(fileSpec, start, end);
|
||||
return new RegExp("^" + pattern + "$", host.useCaseSensitiveFileNames ? "" : "i");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pattern from a wildcard segment.
|
||||
*
|
||||
* @param fileSpec The file specification.
|
||||
* @param start The starting offset in the file specification.
|
||||
* @param end The end offset in the file specification.
|
||||
*/
|
||||
function createPatternFromWildcard(fileSpec: string, start: number, end: number): string {
|
||||
let pattern = "";
|
||||
let offset = indexOfWildcard(fileSpec, start);
|
||||
while (offset >= 0 && offset < end) {
|
||||
if (offset > start) {
|
||||
// Escape and append the non-wildcard portion to the regular expression
|
||||
pattern += escapeRegularExpressionText(fileSpec, start, offset);
|
||||
}
|
||||
|
||||
const charCode = fileSpec.charCodeAt(offset);
|
||||
if (charCode === CharacterCodes.asterisk) {
|
||||
// Append a multi-character (zero or more characters) pattern to the regular expression
|
||||
pattern += "[^/]*";
|
||||
}
|
||||
else if (charCode === CharacterCodes.question) {
|
||||
// Append a single-character (zero or one character) pattern to the regular expression
|
||||
pattern += "[^/]";
|
||||
}
|
||||
|
||||
start = offset + 1;
|
||||
offset = indexOfWildcard(fileSpec, start);
|
||||
}
|
||||
|
||||
// Escape and append any remaining non-wildcard portion.
|
||||
if (start < end) {
|
||||
pattern += escapeRegularExpressionText(fileSpec, start, end);
|
||||
}
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a regular expression from a glob-style wildcard used to exclude a file.
|
||||
*
|
||||
* @param excludeSpecs The file specifications to exclude.
|
||||
* @param basePath The prefix path.
|
||||
* @param options Compiler options.
|
||||
* @param host The host used to resolve files and directories.
|
||||
* @param errors An array for diagnostic reporting.
|
||||
*/
|
||||
function createExcludeRegularExpression(excludeSpecs: string[], basePath: string, options: CompilerOptions, host: ParseConfigHost, errors: Diagnostic[]): RegExp {
|
||||
// Ignore an empty exclusion list
|
||||
if (!excludeSpecs || excludeSpecs.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
basePath = escapeRegularExpressionText(basePath, 0, basePath.length);
|
||||
|
||||
let pattern = "";
|
||||
for (const excludeSpec of excludeSpecs) {
|
||||
const excludePattern = createExcludePattern(excludeSpec, basePath, options, host, errors);
|
||||
if (excludePattern) {
|
||||
if (pattern.length > 0) {
|
||||
pattern += "|";
|
||||
}
|
||||
|
||||
pattern += "(" + excludePattern + ")";
|
||||
}
|
||||
}
|
||||
|
||||
if (pattern.length > 0) {
|
||||
return new RegExp("^(" + pattern + ")($|/)", host.useCaseSensitiveFileNames ? "" : "i");
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pattern for used to exclude a file.
|
||||
*
|
||||
* @param excludeSpec The file specification to exclude.
|
||||
* @param basePath The base path for the exclude pattern.
|
||||
* @param options Compiler options.
|
||||
* @param host The host used to resolve files and directories.
|
||||
* @param errors An array for diagnostic reporting.
|
||||
*/
|
||||
function createExcludePattern(excludeSpec: string, basePath: string, options: CompilerOptions, host: ParseConfigHost, errors: Diagnostic[]): string {
|
||||
if (!excludeSpec) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
excludeSpec = normalizePath(excludeSpec);
|
||||
excludeSpec = removeTrailingDirectorySeparator(excludeSpec);
|
||||
|
||||
let pattern = isRootedDiskPath(excludeSpec) ? "" : basePath;
|
||||
let hasRecursiveDirectoryWildcard = false;
|
||||
let segmentStart = 0;
|
||||
let segmentEnd = getEndOfPathSegment(excludeSpec, segmentStart);
|
||||
while (segmentStart < segmentEnd) {
|
||||
if (isRecursiveDirectoryWildcard(excludeSpec, segmentStart, segmentEnd)) {
|
||||
if (hasRecursiveDirectoryWildcard) {
|
||||
if (errors) {
|
||||
errors.push(createCompilerDiagnostic(Diagnostics.File_specification_cannot_contain_multiple_recursive_directory_wildcards_Asterisk_Asterisk_Colon_0, excludeSpec));
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// As an optimization, if the recursive directory is the last
|
||||
// wildcard, or is followed by only `*` or `*.ts`, don't add the
|
||||
// remaining pattern and exit the loop.
|
||||
if (canElideRecursiveDirectorySegment(excludeSpec, segmentEnd, options, host)) {
|
||||
break;
|
||||
}
|
||||
|
||||
hasRecursiveDirectoryWildcard = true;
|
||||
pattern += "(/.+)?";
|
||||
}
|
||||
else {
|
||||
if (pattern) {
|
||||
pattern += directorySeparator;
|
||||
}
|
||||
|
||||
pattern += createPatternFromWildcard(excludeSpec, segmentStart, segmentEnd);
|
||||
}
|
||||
|
||||
segmentStart = segmentEnd + 1;
|
||||
segmentEnd = getEndOfPathSegment(excludeSpec, segmentStart);
|
||||
}
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a recursive directory segment can be elided when
|
||||
* building a regular expression to exclude a path.
|
||||
*
|
||||
* @param excludeSpec The file specification used to exclude a path.
|
||||
* @param segmentEnd The end position of the recursive directory segment.
|
||||
* @param options Compiler options.
|
||||
* @param host The host used to resolve files and directories.
|
||||
*/
|
||||
function canElideRecursiveDirectorySegment(excludeSpec: string, segmentEnd: number, options: CompilerOptions, host: ParseConfigHost) {
|
||||
// If there are no segments after this segment, the pattern for this segment may be elided.
|
||||
if (segmentEnd + 1 >= excludeSpec.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the following segment is a wildcard that may be elided, the pattern for this segment may be elided.
|
||||
return canElideWildcardSegment(excludeSpec, segmentEnd + 1, options, host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a wildcard segment can be elided when building a
|
||||
* regular expression to exclude a path.
|
||||
*
|
||||
* @param excludeSpec The file specification used to exclude a path.
|
||||
* @param segmentStart The starting position of the segment.
|
||||
* @param options Compiler options.
|
||||
* @param host The host used to resolve files and directories.
|
||||
*/
|
||||
function canElideWildcardSegment(excludeSpec: string, segmentStart: number, options: CompilerOptions, host: ParseConfigHost) {
|
||||
const charCode = excludeSpec.charCodeAt(segmentStart);
|
||||
if (charCode === CharacterCodes.asterisk) {
|
||||
const end = excludeSpec.length;
|
||||
|
||||
// If the segment consists only of `*`, we may elide this segment.
|
||||
if (segmentStart + 1 === end) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the segment consists only of `*.ts`, and we do not allow
|
||||
// any other extensions for source files, we may elide this segment.
|
||||
if (!options.allowNonTsExtensions && !options.jsx && !options.allowJs && segmentStart + 4 === end) {
|
||||
const segment = excludeSpec.substr(segmentStart);
|
||||
return fileExtensionIs(host.useCaseSensitiveFileNames ? segment : segment.toLowerCase(), ".ts");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape regular expression reserved tokens.
|
||||
*
|
||||
* @param text The text to escape.
|
||||
* @param start The starting offset in the string.
|
||||
* @param end The ending offset in the string.
|
||||
*/
|
||||
function escapeRegularExpressionText(text: string, start: number, end: number) {
|
||||
return text.substring(start, end).replace(reservedCharacterPattern, "\\$&");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the wildcard at the current offset is a recursive directory wildcard.
|
||||
*
|
||||
* @param fileSpec The file specification.
|
||||
* @param segmentStart The starting offset of a segment in the file specification.
|
||||
* @param segmentEnd The ending offset of a segment in the file specification.
|
||||
*/
|
||||
function isRecursiveDirectoryWildcard(fileSpec: string, segmentStart: number, segmentEnd: number) {
|
||||
return segmentEnd - segmentStart === 2 &&
|
||||
fileSpec.charCodeAt(segmentStart) === CharacterCodes.asterisk &&
|
||||
fileSpec.charCodeAt(segmentStart + 1) === CharacterCodes.asterisk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the index of the next wildcard character in a file specification.
|
||||
*
|
||||
* @param fileSpec The file specification.
|
||||
* @param start The starting offset in the file specification.
|
||||
*/
|
||||
function indexOfWildcard(fileSpec: string, start: number): number {
|
||||
for (let i = start; i < fileSpec.length; ++i) {
|
||||
const ch = fileSpec.charCodeAt(i);
|
||||
if (ch === CharacterCodes.asterisk || ch === CharacterCodes.question) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end position of a path segment, either the index of the next directory separator or
|
||||
* the provided end position.
|
||||
*
|
||||
* @param fileSpec The file specification.
|
||||
* @param segmentStart The start offset in the file specification.
|
||||
*/
|
||||
function getEndOfPathSegment(fileSpec: string, segmentStart: number): number {
|
||||
const end = fileSpec.length;
|
||||
if (segmentStart >= end) {
|
||||
return end;
|
||||
}
|
||||
|
||||
const offset = fileSpec.indexOf(directorySeparator, segmentStart);
|
||||
return offset < 0 ? end : offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a case sensitive key.
|
||||
*
|
||||
* @param key The original key.
|
||||
*/
|
||||
function caseSensitiveKeyMapper(key: string) {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a case insensitive key.
|
||||
*
|
||||
* @param key The original key.
|
||||
*/
|
||||
function caseInsensitiveKeyMapper(key: string) {
|
||||
return key.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace ts {
|
||||
contains,
|
||||
remove,
|
||||
forEachValue: forEachValueInMap,
|
||||
reduce,
|
||||
clear
|
||||
};
|
||||
|
||||
@@ -34,6 +35,10 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function reduce<U>(callback: (memo: U, value: T, key: Path) => U, initial: U) {
|
||||
return reduceProperties(files, callback, initial);
|
||||
}
|
||||
|
||||
// path should already be well-formed so it does not need to be normalized
|
||||
function get(path: Path): T {
|
||||
return files[toKey(path)];
|
||||
@@ -490,6 +495,24 @@ namespace ts {
|
||||
return a < b ? Comparison.LessThan : Comparison.GreaterThan;
|
||||
}
|
||||
|
||||
export function compareStrings(a: string, b: string, ignoreCase?: boolean): Comparison {
|
||||
if (a === b) return Comparison.EqualTo;
|
||||
if (a === undefined) return Comparison.LessThan;
|
||||
if (b === undefined) return Comparison.GreaterThan;
|
||||
if (ignoreCase) {
|
||||
if (String.prototype.localeCompare) {
|
||||
const result = a.localeCompare(b, /*locales*/ undefined, { usage: "sort", sensitivity: "accent" });
|
||||
return result < 0 ? Comparison.LessThan : result > 0 ? Comparison.GreaterThan : Comparison.EqualTo;
|
||||
}
|
||||
|
||||
a = a.toUpperCase();
|
||||
b = b.toUpperCase();
|
||||
if (a === b) return Comparison.EqualTo;
|
||||
}
|
||||
|
||||
return a < b ? Comparison.LessThan : Comparison.GreaterThan;
|
||||
}
|
||||
|
||||
function getDiagnosticFileName(diagnostic: Diagnostic): string {
|
||||
return diagnostic.file ? diagnostic.file.fileName : undefined;
|
||||
}
|
||||
@@ -756,6 +779,49 @@ namespace ts {
|
||||
return path1 + directorySeparator + path2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a trailing directory separator from a path.
|
||||
* @param path The path.
|
||||
*/
|
||||
export function removeTrailingDirectorySeparator(path: string) {
|
||||
if (path.charAt(path.length - 1) === directorySeparator) {
|
||||
return path.substr(0, path.length - 1);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a trailing directory separator to a path, if it does not already have one.
|
||||
* @param path The path.
|
||||
*/
|
||||
export function ensureTrailingDirectorySeparator(path: string) {
|
||||
if (path.charAt(path.length - 1) !== directorySeparator) {
|
||||
return path + directorySeparator;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
export function comparePaths(a: string, b: string, currentDirectory: string, ignoreCase?: boolean) {
|
||||
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 sharedLength = Math.min(aComponents.length, bComponents.length);
|
||||
for (let i = 0; i < sharedLength; ++i) {
|
||||
const result = compareStrings(aComponents[i], bComponents[i], ignoreCase);
|
||||
if (result !== Comparison.EqualTo) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return compareValues(aComponents.length, bComponents.length);
|
||||
}
|
||||
|
||||
export function fileExtensionIs(path: string, extension: string): boolean {
|
||||
const pathLen = path.length;
|
||||
const extLen = extension.length;
|
||||
@@ -794,6 +860,10 @@ namespace ts {
|
||||
return path;
|
||||
}
|
||||
|
||||
export function changeExtension<T extends string | Path>(path: T, newExtension: string): T {
|
||||
return <T>(removeFileExtension(path) + newExtension);
|
||||
}
|
||||
|
||||
const backslashOrDoubleQuote = /[\"\\]/g;
|
||||
const escapedCharsRegExp = /[\u0000-\u001f\t\v\f\b\r\n\u2028\u2029\u0085]/g;
|
||||
const escapedCharsMap: Map<string> = {
|
||||
|
||||
@@ -2020,6 +2020,10 @@
|
||||
"category": "Error",
|
||||
"code": 5009
|
||||
},
|
||||
"File specification cannot contain multiple recursive directory wildcards ('**'): '{0}'.": {
|
||||
"category": "Error",
|
||||
"code": 5011
|
||||
},
|
||||
"Cannot read file '{0}': {1}": {
|
||||
"category": "Error",
|
||||
"code": 5012
|
||||
|
||||
+43
-3
@@ -17,6 +17,8 @@ namespace ts {
|
||||
getExecutingFilePath(): string;
|
||||
getCurrentDirectory(): string;
|
||||
readDirectory(path: string, extension?: string, exclude?: string[]): string[];
|
||||
readFileNames(path: string): string[];
|
||||
readDirectoryNames(path: string): string[];
|
||||
getMemoryUsage?(): number;
|
||||
exit(exitCode?: number): void;
|
||||
}
|
||||
@@ -155,6 +157,16 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function readFileNames(path: string): string[] {
|
||||
const folder = fso.GetFolder(path || ".");
|
||||
return getNames(folder.files);
|
||||
}
|
||||
|
||||
function readDirectoryNames(path: string): string[] {
|
||||
const folder = fso.GetFolder(path || ".");
|
||||
return getNames(folder.directories);
|
||||
}
|
||||
|
||||
return {
|
||||
args,
|
||||
newLine: "\r\n",
|
||||
@@ -185,6 +197,8 @@ namespace ts {
|
||||
return new ActiveXObject("WScript.Shell").CurrentDirectory;
|
||||
},
|
||||
readDirectory,
|
||||
readFileNames,
|
||||
readDirectoryNames,
|
||||
exit(exitCode?: number): void {
|
||||
try {
|
||||
WScript.Quit(exitCode);
|
||||
@@ -281,7 +295,7 @@ namespace ts {
|
||||
// REVIEW: for now this implementation uses polling.
|
||||
// The advantage of polling is that it works reliably
|
||||
// on all os and with network mounted files.
|
||||
// For 90 referenced files, the average time to detect
|
||||
// For 90 referenced files, the average time to detect
|
||||
// changes is 2*msInterval (by default 5 seconds).
|
||||
// The overhead of this is .04 percent (1/2500) with
|
||||
// average pause of < 1 millisecond (and max
|
||||
@@ -381,6 +395,30 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function readFileNames(path: string): string[] {
|
||||
const entries = _fs.readdirSync(path || ".");
|
||||
const files: string[] = [];
|
||||
for (const entry of entries) {
|
||||
const stat = _fs.statSync(combinePaths(path, entry));
|
||||
if (stat.isFile()) {
|
||||
files.push(entry);
|
||||
}
|
||||
}
|
||||
return files.sort();
|
||||
}
|
||||
|
||||
function readDirectoryNames(path: string): string[] {
|
||||
const entries = _fs.readdirSync(path || ".");
|
||||
const directories: string[] = [];
|
||||
for (const entry of entries) {
|
||||
const stat = _fs.statSync(combinePaths(path, entry));
|
||||
if (stat.isDirectory()) {
|
||||
directories.push(entry);
|
||||
}
|
||||
}
|
||||
return directories.sort();
|
||||
}
|
||||
|
||||
return {
|
||||
args: process.argv.slice(2),
|
||||
newLine: _os.EOL,
|
||||
@@ -406,7 +444,7 @@ namespace ts {
|
||||
};
|
||||
},
|
||||
watchDirectory: (path, callback, recursive) => {
|
||||
// Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows
|
||||
// Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows
|
||||
// (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643)
|
||||
return _fs.watch(
|
||||
path,
|
||||
@@ -426,7 +464,7 @@ namespace ts {
|
||||
return _path.resolve(path);
|
||||
},
|
||||
fileExists(path: string): boolean {
|
||||
return _fs.existsSync(path);
|
||||
return _fs.existsSync(path) && _fs.statSync(path).isFile();
|
||||
},
|
||||
directoryExists(path: string) {
|
||||
return _fs.existsSync(path) && _fs.statSync(path).isDirectory();
|
||||
@@ -443,6 +481,8 @@ namespace ts {
|
||||
return process.cwd();
|
||||
},
|
||||
readDirectory,
|
||||
readFileNames,
|
||||
readDirectoryNames,
|
||||
getMemoryUsage() {
|
||||
if (global.gc) {
|
||||
global.gc();
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace ts {
|
||||
remove(fileName: Path): void;
|
||||
|
||||
forEachValue(f: (key: Path, v: T) => void): void;
|
||||
reduce<U>(f: (memo: U, value: T, key: Path) => U, initial: U): U;
|
||||
clear(): void;
|
||||
}
|
||||
|
||||
@@ -1582,7 +1583,27 @@ namespace ts {
|
||||
}
|
||||
|
||||
export interface ParseConfigHost {
|
||||
useCaseSensitiveFileNames: boolean;
|
||||
|
||||
readDirectory(rootDir: string, extension: string, exclude: string[]): string[];
|
||||
|
||||
/**
|
||||
* Gets a value indicating whether the specified path exists.
|
||||
* @param path The path to test.
|
||||
*/
|
||||
fileExists(path: string): boolean;
|
||||
|
||||
/**
|
||||
* Reads the files names in the directory.
|
||||
* @param rootDir The directory path.
|
||||
*/
|
||||
readFileNames(rootDir: string): string[];
|
||||
|
||||
/**
|
||||
* Reads the directory names in the directory.
|
||||
* @param rootDir The directory path.
|
||||
*/
|
||||
readDirectoryNames(rootDir: string): string[];
|
||||
}
|
||||
|
||||
export interface WriteFileCallback {
|
||||
|
||||
Vendored
+3
@@ -167,6 +167,9 @@ declare module chai {
|
||||
module assert {
|
||||
function equal(actual: any, expected: any, message?: string): void;
|
||||
function notEqual(actual: any, expected: any, message?: string): void;
|
||||
function deepEqual(actual: any, expected: any, message?: string): void;
|
||||
function notDeepEqual(actual: any, expected: any, message?: string): void;
|
||||
function lengthOf(object: any[], length: number, message?: string): void;
|
||||
function isTrue(value: any, message?: string): void;
|
||||
function isFalse(value: any, message?: string): void;
|
||||
function isNull(value: any, message?: string): void;
|
||||
|
||||
+21
-7
@@ -1,7 +1,7 @@
|
||||
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
@@ -282,7 +282,7 @@ namespace Utils {
|
||||
break;
|
||||
|
||||
case "parserContextFlags":
|
||||
// Clear the flag that are produced by aggregating child values.. That is ephemeral
|
||||
// Clear the flag that are produced by aggregating child values.. That is ephemeral
|
||||
// data we don't care about in the dump. We only care what the parser set directly
|
||||
// on the ast.
|
||||
let value = n.parserContextFlags & ts.ParserContextFlags.ParserGeneratedFlags;
|
||||
@@ -356,7 +356,7 @@ namespace Utils {
|
||||
assert.equal(node1.kind, node2.kind, "node1.kind !== node2.kind");
|
||||
assert.equal(node1.flags, node2.flags, "node1.flags !== node2.flags");
|
||||
|
||||
// call this on both nodes to ensure all propagated flags have been set (and thus can be
|
||||
// call this on both nodes to ensure all propagated flags have been set (and thus can be
|
||||
// compared).
|
||||
assert.equal(ts.containsParseError(node1), ts.containsParseError(node2));
|
||||
assert.equal(node1.parserContextFlags, node2.parserContextFlags, "node1.parserContextFlags !== node2.parserContextFlags");
|
||||
@@ -436,6 +436,8 @@ namespace Harness {
|
||||
getExecutingFilePath(): string;
|
||||
exit(exitCode?: number): void;
|
||||
readDirectory(path: string, extension?: string, exclude?: string[]): string[];
|
||||
readDirectoryNames(path: string): string[];
|
||||
readFileNames(path: string): string[];
|
||||
}
|
||||
export var IO: IO;
|
||||
|
||||
@@ -474,6 +476,8 @@ namespace Harness {
|
||||
export const fileExists: typeof IO.fileExists = fso.FileExists;
|
||||
export const log: typeof IO.log = global.WScript && global.WScript.StdOut.WriteLine;
|
||||
export const readDirectory: typeof IO.readDirectory = (path, extension, exclude) => ts.sys.readDirectory(path, extension, exclude);
|
||||
export const readDirectoryNames: typeof IO.readDirectoryNames = path => ts.sys.readDirectoryNames(path);
|
||||
export const readFileNames: typeof IO.readFileNames = path => ts.sys.readFileNames(path);
|
||||
|
||||
export function createDirectory(path: string) {
|
||||
if (directoryExists(path)) {
|
||||
@@ -544,6 +548,8 @@ namespace Harness {
|
||||
export const log: typeof IO.log = s => console.log(s);
|
||||
|
||||
export const readDirectory: typeof IO.readDirectory = (path, extension, exclude) => ts.sys.readDirectory(path, extension, exclude);
|
||||
export const readDirectoryNames: typeof IO.readDirectoryNames = path => ts.sys.readDirectoryNames(path);
|
||||
export const readFileNames: typeof IO.readFileNames = path => ts.sys.readFileNames(path);
|
||||
|
||||
export function createDirectory(path: string) {
|
||||
if (!directoryExists(path)) {
|
||||
@@ -752,6 +758,14 @@ namespace Harness {
|
||||
export function readDirectory(path: string, extension?: string, exclude?: string[]) {
|
||||
return listFiles(path).filter(f => !extension || ts.fileExtensionIs(f, extension));
|
||||
}
|
||||
|
||||
export function readDirectoryNames(path: string): string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
export function readFileNames(path: string) {
|
||||
return readDirectory(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -777,7 +791,7 @@ namespace Harness {
|
||||
(emittedFile: string, emittedLine: number, emittedColumn: number, sourceFile: string, sourceLine: number, sourceColumn: number, sourceName: string): void;
|
||||
}
|
||||
|
||||
// Settings
|
||||
// Settings
|
||||
export let userSpecifiedRoot = "";
|
||||
export let lightMode = false;
|
||||
|
||||
@@ -816,7 +830,7 @@ namespace Harness {
|
||||
fileName: string,
|
||||
sourceText: string,
|
||||
languageVersion: ts.ScriptTarget) {
|
||||
// We'll only assert invariants outside of light mode.
|
||||
// We'll only assert invariants outside of light mode.
|
||||
const shouldAssertInvariants = !Harness.lightMode;
|
||||
|
||||
// Only set the parent nodes if we're asserting invariants. We don't need them otherwise.
|
||||
@@ -911,7 +925,7 @@ namespace Harness {
|
||||
libFiles?: string;
|
||||
}
|
||||
|
||||
// Additional options not already in ts.optionDeclarations
|
||||
// Additional options not already in ts.optionDeclarations
|
||||
const harnessOptionDeclarations: ts.CommandLineOption[] = [
|
||||
{ name: "allowNonTsExtensions", type: "boolean" },
|
||||
{ name: "useCaseSensitiveFileNames", type: "boolean" },
|
||||
@@ -1149,7 +1163,7 @@ namespace Harness {
|
||||
errLines.forEach(e => outputLines.push(e));
|
||||
|
||||
// do not count errors from lib.d.ts here, they are computed separately as numLibraryDiagnostics
|
||||
// if lib.d.ts is explicitly included in input files and there are some errors in it (i.e. because of duplicate identifiers)
|
||||
// if lib.d.ts is explicitly included in input files and there are some errors in it (i.e. because of duplicate identifiers)
|
||||
// then they will be added twice thus triggering 'total errors' assertion with condition
|
||||
// 'totalErrorsReportedInNonLibraryFiles + numLibraryDiagnostics + numTest262HarnessDiagnostics, diagnostics.length
|
||||
|
||||
|
||||
@@ -259,6 +259,12 @@ namespace Harness.LanguageService {
|
||||
readDirectory(rootDir: string, extension: string): string {
|
||||
throw new Error("NYI");
|
||||
}
|
||||
readDirectoryNames(path: string): string {
|
||||
throw new Error("Not implemented.");
|
||||
}
|
||||
readFileNames(path: string): string {
|
||||
throw new Error("Not implemented.");
|
||||
}
|
||||
fileExists(fileName: string) { return this.getScriptInfo(fileName) !== undefined; }
|
||||
readFile(fileName: string) {
|
||||
const snapshot = this.nativeHost.getScriptSnapshot(fileName);
|
||||
@@ -572,6 +578,14 @@ namespace Harness.LanguageService {
|
||||
throw new Error("Not implemented Yet.");
|
||||
}
|
||||
|
||||
readDirectoryNames(path: string): string[] {
|
||||
throw new Error("Not implemented.");
|
||||
}
|
||||
|
||||
readFileNames(path: string): string[] {
|
||||
throw new Error("Not implemented.");
|
||||
}
|
||||
|
||||
watchFile(fileName: string, callback: (fileName: string) => void): ts.FileWatcher {
|
||||
return { close() { } };
|
||||
}
|
||||
@@ -644,4 +658,3 @@ namespace Harness.LanguageService {
|
||||
getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { throw new Error("getPreProcessedFileInfo is not available using the server interface."); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +210,14 @@ class ProjectRunner extends RunnerBase {
|
||||
}
|
||||
|
||||
const configObject = result.config;
|
||||
const configParseResult = ts.parseJsonConfigFileContent(configObject, { readDirectory }, ts.getDirectoryPath(configFileName), compilerOptions);
|
||||
const configParseHost: ts.ParseConfigHost = {
|
||||
useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(),
|
||||
fileExists,
|
||||
readDirectory,
|
||||
readDirectoryNames,
|
||||
readFileNames
|
||||
};
|
||||
const configParseResult = ts.parseJsonConfigFileContent(configObject, configParseHost, ts.getDirectoryPath(configFileName), compilerOptions);
|
||||
if (configParseResult.errors.length > 0) {
|
||||
return {
|
||||
moduleKind,
|
||||
@@ -237,7 +244,7 @@ class ProjectRunner extends RunnerBase {
|
||||
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
|
||||
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.Map<ts.CommandLineOption> = {};
|
||||
@@ -278,6 +285,14 @@ class ProjectRunner extends RunnerBase {
|
||||
return result;
|
||||
}
|
||||
|
||||
function readDirectoryNames(path: string) {
|
||||
return Harness.IO.readDirectoryNames(getFileNameInTheProjectTest(path));
|
||||
}
|
||||
|
||||
function readFileNames(path: string) {
|
||||
return Harness.IO.readFileNames(getFileNameInTheProjectTest(path));
|
||||
}
|
||||
|
||||
function fileExists(fileName: string): boolean {
|
||||
return Harness.IO.fileExists(getFileNameInTheProjectTest(fileName));
|
||||
}
|
||||
|
||||
@@ -76,7 +76,14 @@ namespace RWC {
|
||||
if (tsconfigFile) {
|
||||
const tsconfigFileContents = getHarnessCompilerInputUnit(tsconfigFile.path);
|
||||
const parsedTsconfigFileContents = ts.parseConfigFileTextToJson(tsconfigFile.path, tsconfigFileContents.content);
|
||||
const configParseResult = ts.parseJsonConfigFileContent(parsedTsconfigFileContents.config, Harness.IO, ts.getDirectoryPath(tsconfigFile.path));
|
||||
const configParseHost: ts.ParseConfigHost = {
|
||||
useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(),
|
||||
fileExists: Harness.IO.fileExists,
|
||||
readDirectory: Harness.IO.readDirectory,
|
||||
readDirectoryNames: Harness.IO.readDirectoryNames,
|
||||
readFileNames: Harness.IO.readFileNames,
|
||||
};
|
||||
const configParseResult = ts.parseJsonConfigFileContent(parsedTsconfigFileContents.config, configParseHost, ts.getDirectoryPath(tsconfigFile.path));
|
||||
fileNames = configParseResult.fileNames;
|
||||
opts.options = ts.extend(opts.options, configParseResult.options);
|
||||
}
|
||||
|
||||
+53
-9
@@ -60,7 +60,7 @@ namespace ts {
|
||||
getNewLine?(): string;
|
||||
getProjectVersion?(): string;
|
||||
useCaseSensitiveFileNames?(): boolean;
|
||||
|
||||
|
||||
getModuleResolutionsForFile?(fileName: string): string;
|
||||
}
|
||||
|
||||
@@ -73,6 +73,9 @@ namespace ts {
|
||||
* when enumerating the directory.
|
||||
*/
|
||||
readDirectory(rootDir: string, extension: string, exclude?: string): string;
|
||||
readDirectoryNames?(rootDir: string): string;
|
||||
readFileNames?(rootDir: string): string;
|
||||
useCaseSensitiveFileNames?: boolean;
|
||||
}
|
||||
|
||||
///
|
||||
@@ -272,12 +275,12 @@ namespace ts {
|
||||
private files: string[];
|
||||
private loggingEnabled = false;
|
||||
private tracingEnabled = false;
|
||||
|
||||
|
||||
public resolveModuleNames: (moduleName: string[], containingFile: string) => ResolvedModule[];
|
||||
|
||||
|
||||
constructor(private shimHost: LanguageServiceShimHost) {
|
||||
// if shimHost is a COM object then property check will become method call with no arguments.
|
||||
// 'in' does not have this effect.
|
||||
// 'in' does not have this effect.
|
||||
if ("getModuleResolutionsForFile" in this.shimHost) {
|
||||
this.resolveModuleNames = (moduleNames: string[], containingFile: string) => {
|
||||
let resolutionsInFile = <Map<string>>JSON.parse(this.shimHost.getModuleResolutionsForFile(containingFile));
|
||||
@@ -407,7 +410,18 @@ namespace ts {
|
||||
|
||||
export class CoreServicesShimHostAdapter implements ParseConfigHost {
|
||||
|
||||
public useCaseSensitiveFileNames: boolean;
|
||||
|
||||
constructor(private shimHost: CoreServicesShimHost) {
|
||||
if (typeof shimHost.useCaseSensitiveFileNames === "boolean") {
|
||||
this.useCaseSensitiveFileNames = shimHost.useCaseSensitiveFileNames;
|
||||
}
|
||||
else if (sys) {
|
||||
this.useCaseSensitiveFileNames = sys.useCaseSensitiveFileNames;
|
||||
}
|
||||
else {
|
||||
this.useCaseSensitiveFileNames = true;
|
||||
}
|
||||
}
|
||||
|
||||
public readDirectory(rootDir: string, extension: string, exclude: string[]): string[] {
|
||||
@@ -424,11 +438,41 @@ namespace ts {
|
||||
}
|
||||
return JSON.parse(encoded);
|
||||
}
|
||||
|
||||
|
||||
public readDirectoryNames(path: string): string[] {
|
||||
if (this.shimHost.readDirectory) {
|
||||
const encoded = this.shimHost.readDirectoryNames(path);
|
||||
return JSON.parse(encoded);
|
||||
}
|
||||
|
||||
if (sys) {
|
||||
path = normalizePath(path);
|
||||
path = ensureTrailingDirectorySeparator(path);
|
||||
return sys.readDirectoryNames(path);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public readFileNames(path: string): string[] {
|
||||
if (this.shimHost.readFileNames) {
|
||||
const encoded = this.shimHost.readFileNames(path);
|
||||
return JSON.parse(encoded);
|
||||
}
|
||||
|
||||
if (sys) {
|
||||
path = normalizePath(path);
|
||||
path = ensureTrailingDirectorySeparator(path);
|
||||
return sys.readFileNames(path);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public fileExists(fileName: string): boolean {
|
||||
return this.shimHost.fileExists(fileName);
|
||||
}
|
||||
|
||||
|
||||
public readFile(fileName: string): string {
|
||||
return this.shimHost.readFile(fileName);
|
||||
}
|
||||
@@ -940,7 +984,7 @@ namespace ts {
|
||||
private forwardJSONCall(actionDescription: string, action: () => any): any {
|
||||
return forwardJSONCall(this.logger, actionDescription, action, this.logPerformance);
|
||||
}
|
||||
|
||||
|
||||
public resolveModuleName(fileName: string, moduleName: string, compilerOptionsJson: string): string {
|
||||
return this.forwardJSONCall(`resolveModuleName('${fileName}')`, () => {
|
||||
let compilerOptions = <CompilerOptions>JSON.parse(compilerOptionsJson);
|
||||
@@ -949,14 +993,14 @@ namespace ts {
|
||||
resolvedFileName: result.resolvedModule ? result.resolvedModule.resolvedFileName: undefined,
|
||||
failedLookupLocations: result.failedLookupLocations
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public getPreProcessedFileInfo(fileName: string, sourceTextSnapshot: IScriptSnapshot): string {
|
||||
return this.forwardJSONCall(
|
||||
"getPreProcessedFileInfo('" + fileName + "')",
|
||||
() => {
|
||||
// for now treat files as JavaScript
|
||||
// for now treat files as JavaScript
|
||||
var result = preProcessFile(sourceTextSnapshot.getText(0, sourceTextSnapshot.getLength()), /* readImportFiles */ true, /* detectJavaScriptImports */ true);
|
||||
var convertResult = {
|
||||
referencedFiles: <IFileReference[]>[],
|
||||
|
||||
@@ -39,6 +39,8 @@ module ts {
|
||||
readDirectory: (path: string, extension?: string, exclude?: string[]): string[] => {
|
||||
throw new Error("NYI");
|
||||
},
|
||||
readDirectoryNames: (path: string): string[] => { throw new Error("NYI"); },
|
||||
readFileNames: (path: string): string[] => { throw new Error("NYI"); },
|
||||
exit: (exitCode?: number) => {
|
||||
},
|
||||
watchFile: (path, callback) => {
|
||||
@@ -69,8 +71,8 @@ module ts {
|
||||
let projectService = new server.ProjectService(serverHost, logger);
|
||||
let rootScriptInfo = projectService.openFile(rootFile, /* openedByClient */true);
|
||||
let project = projectService.createInferredProject(rootScriptInfo);
|
||||
project.setProjectOptions( {files: [rootScriptInfo.fileName], compilerOptions: {module: ts.ModuleKind.AMD} } );
|
||||
return {
|
||||
project.setProjectOptions( {files: [rootScriptInfo.fileName], compilerOptions: {module: ts.ModuleKind.AMD} } );
|
||||
return {
|
||||
project,
|
||||
rootScriptInfo
|
||||
};
|
||||
@@ -87,22 +89,22 @@ module ts {
|
||||
name: "c:/f1.ts",
|
||||
content: `foo()`
|
||||
};
|
||||
|
||||
|
||||
let serverHost = createDefaultServerHost({ [root.name]: root, [imported.name]: imported });
|
||||
let { project, rootScriptInfo } = createProject(root.name, serverHost);
|
||||
|
||||
// ensure that imported file was found
|
||||
let diags = project.compilerService.languageService.getSemanticDiagnostics(imported.name);
|
||||
assert.equal(diags.length, 1);
|
||||
|
||||
|
||||
let originalFileExists = serverHost.fileExists;
|
||||
{
|
||||
// patch fileExists to make sure that disk is not touched
|
||||
serverHost.fileExists = (fileName): boolean => {
|
||||
assert.isTrue(false, "fileExists should not be called");
|
||||
assert.isTrue(false, "fileExists should not be called");
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
let newContent = `import {x} from "f1"
|
||||
var x: string = 1;`;
|
||||
rootScriptInfo.editContent(0, rootScriptInfo.content.length, newContent);
|
||||
@@ -123,7 +125,7 @@ module ts {
|
||||
};
|
||||
let newContent = `import {x} from "f2"`;
|
||||
rootScriptInfo.editContent(0, rootScriptInfo.content.length, newContent);
|
||||
|
||||
|
||||
try {
|
||||
// trigger synchronization to make sure that LSHost will try to find 'f2' module on disk
|
||||
project.compilerService.languageService.getSemanticDiagnostics(imported.name);
|
||||
@@ -132,7 +134,7 @@ module ts {
|
||||
catch(e) {
|
||||
assert.isTrue(e.message.indexOf(`Could not find file: '${imported.name}'.`) === 0);
|
||||
}
|
||||
|
||||
|
||||
assert.isTrue(fileExistsIsCalled);
|
||||
}
|
||||
{
|
||||
@@ -140,45 +142,45 @@ module ts {
|
||||
serverHost.fileExists = (fileName): boolean => {
|
||||
if (fileName === "lib.d.ts") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
fileExistsCalled = true;
|
||||
assert.isTrue(fileName.indexOf('/f1.') !== -1);
|
||||
return originalFileExists(fileName);
|
||||
};
|
||||
|
||||
|
||||
let newContent = `import {x} from "f1"`;
|
||||
rootScriptInfo.editContent(0, rootScriptInfo.content.length, newContent);
|
||||
project.compilerService.languageService.getSemanticDiagnostics(imported.name);
|
||||
assert.isTrue(fileExistsCalled);
|
||||
|
||||
|
||||
// setting compiler options discards module resolution cache
|
||||
fileExistsCalled = false;
|
||||
|
||||
|
||||
let opts = ts.clone(project.projectOptions);
|
||||
opts.compilerOptions = ts.clone(opts.compilerOptions);
|
||||
opts.compilerOptions.target = ts.ScriptTarget.ES5;
|
||||
project.setProjectOptions(opts);
|
||||
|
||||
|
||||
project.compilerService.languageService.getSemanticDiagnostics(imported.name);
|
||||
assert.isTrue(fileExistsCalled);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
it("loads missing files from disk", () => {
|
||||
let root: File = {
|
||||
name: 'c:/foo.ts',
|
||||
content: `import {x} from "bar"`
|
||||
};
|
||||
|
||||
|
||||
let imported: File = {
|
||||
name: 'c:/bar.d.ts',
|
||||
content: `export var y = 1`
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
let fileMap: Map<File> = { [root.name]: root };
|
||||
let serverHost = createDefaultServerHost(fileMap);
|
||||
let originalFileExists = serverHost.fileExists;
|
||||
|
||||
|
||||
let fileExistsCalledForBar = false;
|
||||
serverHost.fileExists = fileName => {
|
||||
if (fileName === "lib.d.ts") {
|
||||
@@ -187,22 +189,22 @@ module ts {
|
||||
if (!fileExistsCalledForBar) {
|
||||
fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1;
|
||||
}
|
||||
|
||||
|
||||
return originalFileExists(fileName);
|
||||
};
|
||||
|
||||
|
||||
let { project, rootScriptInfo } = createProject(root.name, serverHost);
|
||||
|
||||
let diags = project.compilerService.languageService.getSemanticDiagnostics(root.name);
|
||||
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
|
||||
assert.isTrue(diags.length === 1, "one diagnostic expected");
|
||||
assert.isTrue(typeof diags[0].messageText === "string" && ((<string>diags[0].messageText).indexOf("Cannot find module") === 0), "should be 'cannot find module' message");
|
||||
|
||||
|
||||
// assert that import will success once file appear on disk
|
||||
fileMap[imported.name] = imported;
|
||||
fileExistsCalledForBar = false;
|
||||
rootScriptInfo.editContent(0, rootScriptInfo.content.length, `import {y} from "bar"`)
|
||||
|
||||
|
||||
diags = project.compilerService.languageService.getSemanticDiagnostics(root.name);
|
||||
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
|
||||
assert.isTrue(diags.length === 0);
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
/// <reference path="..\..\..\src\harness\external\mocha.d.ts" />
|
||||
/// <reference path="..\..\..\src\harness\harness.ts" />
|
||||
|
||||
describe("expandFiles", () => {
|
||||
it("fail", () => {
|
||||
assert.isTrue(false, "just checking");
|
||||
});
|
||||
|
||||
const basePath = "c:/dev/";
|
||||
const caseInsensitiveHost = createMockParseConfigHost(
|
||||
basePath,
|
||||
/*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"
|
||||
],
|
||||
/*ignoreCase*/ true);
|
||||
|
||||
const caseSensitiveHost = createMockParseConfigHost(
|
||||
basePath,
|
||||
/*files*/ [
|
||||
"c:/dev/a.ts",
|
||||
"c:/dev/a.d.ts",
|
||||
"c:/dev/a.js",
|
||||
"c:/dev/b.ts",
|
||||
"c:/dev/b.js",
|
||||
"c:/dev/A.ts",
|
||||
"c:/dev/B.ts",
|
||||
"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/b.ts",
|
||||
"c:/dev/x/y/a.ts",
|
||||
"c:/dev/x/y/b.ts",
|
||||
],
|
||||
/*ignoreCase*/ false);
|
||||
|
||||
const expect = _chai.expect;
|
||||
describe("with literal file list", () => {
|
||||
it("without exclusions", () => {
|
||||
const fileNames = ["a.ts", "b.ts"];
|
||||
const results = ts.expandFiles(fileNames, /*includeSpecs*/ undefined, /*excludeSpecs*/ undefined, basePath, {}, caseInsensitiveHost);
|
||||
assert.deepEqual(results, ["c:/dev/a.ts", "c:/dev/b.ts"]);
|
||||
});
|
||||
it("missing files are still present", () => {
|
||||
const fileNames = ["z.ts", "x.ts"];
|
||||
const results = ts.expandFiles(fileNames, /*includeSpecs*/ undefined, /*excludeSpecs*/ undefined, basePath, {}, caseInsensitiveHost);
|
||||
assert.deepEqual(results, ["c:/dev/z.ts", "c:/dev/x.ts"]);
|
||||
});
|
||||
it("are not removed due to excludes", () => {
|
||||
const fileNames = ["a.ts", "b.ts"];
|
||||
const excludeSpecs = ["b.ts"];
|
||||
const results = ts.expandFiles(fileNames, /*includeSpecs*/ undefined, excludeSpecs, basePath, {}, caseInsensitiveHost);
|
||||
assert.deepEqual(results, ["c:/dev/a.ts", "c:/dev/b.ts"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with literal include list", () => {
|
||||
it("without exclusions", () => {
|
||||
const includeSpecs = ["a.ts", "b.ts"];
|
||||
const results = ts.expandFiles(/*fileNames*/ undefined, includeSpecs, /*excludeSpecs*/ undefined, basePath, {}, caseInsensitiveHost);
|
||||
assert.deepEqual(results, ["c:/dev/a.ts", "c:/dev/b.ts"]);
|
||||
});
|
||||
it("with non .ts file extensions are excluded", () => {
|
||||
const includeSpecs = ["a.js", "b.js"];
|
||||
const results = ts.expandFiles(/*fileNames*/ undefined, includeSpecs, /*excludeSpecs*/ undefined, basePath, {}, caseInsensitiveHost);
|
||||
assert.deepEqual(results, []);
|
||||
});
|
||||
it("with missing files are excluded", () => {
|
||||
const includeSpecs = ["z.ts", "x.ts"];
|
||||
const results = ts.expandFiles(/*fileNames*/ undefined, includeSpecs, /*excludeSpecs*/ undefined, basePath, {}, caseInsensitiveHost);
|
||||
assert.deepEqual(results, []);
|
||||
});
|
||||
it("with literal excludes", () => {
|
||||
const includeSpecs = ["a.ts", "b.ts"];
|
||||
const excludeSpecs = ["b.ts"];
|
||||
const results = ts.expandFiles(/*fileNames*/ undefined, includeSpecs, excludeSpecs, basePath, {}, caseInsensitiveHost);
|
||||
assert.deepEqual(results, ["c:/dev/a.ts"]);
|
||||
});
|
||||
it("with wildcard excludes", () => {
|
||||
const includeSpecs = ["a.ts", "b.ts", "z/a.ts", "z/abz.ts", "z/aba.ts", "x/b.ts"];
|
||||
const excludeSpecs = ["*.ts", "z/??z.ts", "*/b.ts"];
|
||||
const results = ts.expandFiles(/*fileNames*/ undefined, includeSpecs, excludeSpecs, basePath, {}, caseInsensitiveHost);
|
||||
assert.deepEqual(results, ["c:/dev/z/a.ts", "c:/dev/z/aba.ts"]);
|
||||
});
|
||||
it("with recursive excludes", () => {
|
||||
const includeSpecs = ["a.ts", "b.ts", "x/a.ts", "x/b.ts", "x/y/a.ts", "x/y/b.ts"];
|
||||
const excludeSpecs = ["**/b.ts"];
|
||||
const results = ts.expandFiles(/*fileNames*/ undefined, includeSpecs, excludeSpecs, basePath, {}, caseInsensitiveHost);
|
||||
assert.deepEqual(results, ["c:/dev/a.ts", "c:/dev/x/a.ts", "c:/dev/x/y/a.ts"]);
|
||||
});
|
||||
it("with case sensitive exclude", () => {
|
||||
const includeSpecs = ["B.ts"];
|
||||
const excludeSpecs = ["**/b.ts"];
|
||||
const results = ts.expandFiles(/*fileNames*/ undefined, includeSpecs, excludeSpecs, basePath, {}, caseSensitiveHost);
|
||||
assert.deepEqual(results, ["c:/dev/B.ts"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with wildcard include list", () => {
|
||||
it("same named declarations are excluded", () => {
|
||||
const includeSpecs = ["*.ts"];
|
||||
const results = ts.expandFiles(/*fileNames*/ undefined, includeSpecs, /*excludeSpecs*/ undefined, basePath, {}, caseInsensitiveHost);
|
||||
assert.deepEqual(results, ["c:/dev/a.ts", "c:/dev/b.ts", "c:/dev/c.d.ts"]);
|
||||
});
|
||||
it("`*` matches only ts files", () => {
|
||||
const includeSpecs = ["*"];
|
||||
const results = ts.expandFiles(/*fileNames*/ undefined, includeSpecs, /*excludeSpecs*/ undefined, basePath, {}, caseInsensitiveHost);
|
||||
assert.deepEqual(results, ["c:/dev/a.ts", "c:/dev/b.ts", "c:/dev/c.d.ts"]);
|
||||
});
|
||||
it("`?` matches only a single character", () => {
|
||||
const includeSpecs = ["x/?.ts"];
|
||||
const results = ts.expandFiles(/*fileNames*/ undefined, includeSpecs, /*excludeSpecs*/ undefined, basePath, {}, caseInsensitiveHost);
|
||||
assert.deepEqual(results, ["c:/dev/x/a.ts", "c:/dev/x/b.ts"]);
|
||||
});
|
||||
it("with recursive directory", () => {
|
||||
const includeSpecs = ["**/a.ts"];
|
||||
const results = ts.expandFiles(/*fileNames*/ undefined, includeSpecs, /*excludeSpecs*/ undefined, basePath, {}, caseInsensitiveHost);
|
||||
assert.deepEqual(results, ["c:/dev/a.ts", "c:/dev/x/a.ts", "c:/dev/x/y/a.ts", "c:/dev/z/a.ts"]);
|
||||
});
|
||||
it("case sensitive", () => {
|
||||
const includeSpecs = ["**/A.ts"];
|
||||
const results = ts.expandFiles(/*fileNames*/ undefined, includeSpecs, /*excludeSpecs*/ undefined, basePath, {}, caseSensitiveHost);
|
||||
assert.deepEqual(results, ["c:/dev/A.ts"]);
|
||||
});
|
||||
it("with missing files are excluded", () => {
|
||||
const includeSpecs = ["*/z.ts"];
|
||||
const results = ts.expandFiles(/*fileNames*/ undefined, includeSpecs, /*excludeSpecs*/ undefined, basePath, {}, caseInsensitiveHost);
|
||||
assert.deepEqual(results, []);
|
||||
});
|
||||
it("always include literal files", () => {
|
||||
const fileNames = ["a.ts"];
|
||||
const includeSpecs = ["*/z.ts"];
|
||||
const excludeSpecs = ["**/a.ts"];
|
||||
const results = ts.expandFiles(fileNames, includeSpecs, excludeSpecs, basePath, {}, caseInsensitiveHost);
|
||||
assert.deepEqual(results, ["c:/dev/a.ts"]);
|
||||
});
|
||||
});
|
||||
|
||||
function createMockParseConfigHost(basePath: string, files: string[], ignoreCase: boolean): ts.ParseConfigHost {
|
||||
const fileSet: ts.Map<string> = {};
|
||||
const directorySet: ts.Map<{ files: ts.Map<string>; directories: ts.Map<string>; }> = {};
|
||||
|
||||
files.sort((a, b) => ts.comparePaths(a, b, basePath, ignoreCase));
|
||||
for (const file of files) {
|
||||
addFile(ts.getNormalizedAbsolutePath(file, basePath));
|
||||
}
|
||||
|
||||
return {
|
||||
useCaseSensitiveFileNames: !ignoreCase,
|
||||
fileExists,
|
||||
readDirectory,
|
||||
readFileNames,
|
||||
readDirectoryNames
|
||||
};
|
||||
|
||||
function fileExists(path: string): boolean {
|
||||
const fileKey = ignoreCase ? path.toLowerCase() : path;
|
||||
return ts.hasProperty(fileSet, fileKey);
|
||||
}
|
||||
|
||||
function directoryExists(path: string): boolean {
|
||||
const directoryKey = ignoreCase ? path.toLowerCase() : path;
|
||||
return ts.hasProperty(directorySet, directoryKey);
|
||||
}
|
||||
|
||||
function readDirectory(rootDir: string, extension?: string, exclude?: string[]): string[] {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
function readFileNames(path: string) {
|
||||
const files = getDirectoryEntry(path).files;
|
||||
const result: string[] = [];
|
||||
ts.forEachKey(files, key => { result.push(key); });
|
||||
result.sort((a, b) => ts.compareStrings(a, b, ignoreCase));
|
||||
return result;
|
||||
}
|
||||
|
||||
function readDirectoryNames(path: string) {
|
||||
const directories = getDirectoryEntry(path).directories;
|
||||
const result: string[] = [];
|
||||
ts.forEachKey(directories, key => { result.push(key); });
|
||||
result.sort((a, b) => ts.compareStrings(a, b, ignoreCase));
|
||||
return result;
|
||||
}
|
||||
|
||||
function getDirectoryEntry(path: string) {
|
||||
path = ts.getNormalizedAbsolutePath(path, basePath);
|
||||
path = ts.removeTrailingDirectorySeparator(path);
|
||||
const directoryKey = ignoreCase ? path.toLowerCase() : path;
|
||||
return ts.getProperty(directorySet, directoryKey);
|
||||
}
|
||||
|
||||
function addFile(file: string) {
|
||||
const fileKey = ignoreCase ? file.toLowerCase() : file;
|
||||
if (!ts.hasProperty(fileSet, fileKey)) {
|
||||
fileSet[fileKey] = file;
|
||||
const name = ts.getBaseFileName(file);
|
||||
const parent = ts.getDirectoryPath(file);
|
||||
addToDirectory(parent, name, "file");
|
||||
}
|
||||
}
|
||||
|
||||
function addDirectory(directory: string) {
|
||||
directory = ts.removeTrailingDirectorySeparator(directory);
|
||||
const directoryKey = ignoreCase ? directory.toLowerCase() : directory;
|
||||
if (!ts.hasProperty(directorySet, directoryKey)) {
|
||||
directorySet[directoryKey] = { files: {}, directories: {} };
|
||||
const name = ts.getBaseFileName(directory);
|
||||
const parent = ts.getDirectoryPath(directory);
|
||||
if (parent !== directory) {
|
||||
addToDirectory(parent, name, "directory");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addToDirectory(directory: string, entry: string, type: "file" | "directory") {
|
||||
addDirectory(directory);
|
||||
directory = ts.removeTrailingDirectorySeparator(directory);
|
||||
const directoryKey = ignoreCase ? directory.toLowerCase() : directory;
|
||||
const entryKey = ignoreCase ? entry.toLowerCase() : entry;
|
||||
const directoryEntry = directorySet[directoryKey];
|
||||
const entries = type === "file" ? directoryEntry.files : directoryEntry.directories;
|
||||
if (!ts.hasProperty(entries, entryKey)) {
|
||||
entries[entryKey] = entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -16,6 +16,8 @@ namespace ts.server {
|
||||
getExecutingFilePath(): string { return void 0; },
|
||||
getCurrentDirectory(): string { return void 0; },
|
||||
readDirectory(): string[] { return []; },
|
||||
readDirectoryNames(): string[] { return []; },
|
||||
readFileNames(): string[] { return []; },
|
||||
exit(): void {}
|
||||
};
|
||||
const mockLogger: Logger = {
|
||||
@@ -28,7 +30,7 @@ namespace ts.server {
|
||||
endGroup(): void {},
|
||||
msg(s: string, type?: string): void {},
|
||||
};
|
||||
|
||||
|
||||
describe("the Session class", () => {
|
||||
let session: Session;
|
||||
let lastSent: protocol.Message;
|
||||
@@ -204,7 +206,7 @@ namespace ts.server {
|
||||
.to.throw(`Protocol handler already exists for command "${command}"`);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("event", () => {
|
||||
it("can format event responses and send them", () => {
|
||||
const evt = "notify-test";
|
||||
@@ -315,7 +317,7 @@ namespace ts.server {
|
||||
responseRequired: true
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
send(msg: protocol.Message) {
|
||||
this.client.handle(msg);
|
||||
}
|
||||
@@ -323,7 +325,7 @@ namespace ts.server {
|
||||
enqueue(msg: protocol.Request) {
|
||||
this.queue.unshift(msg);
|
||||
}
|
||||
|
||||
|
||||
handleRequest(msg: protocol.Request) {
|
||||
let response: protocol.Response;
|
||||
try {
|
||||
@@ -345,7 +347,7 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class InProcClient {
|
||||
private server: InProcSession;
|
||||
private seq = 0;
|
||||
@@ -379,7 +381,7 @@ namespace ts.server {
|
||||
connect(session: InProcSession): void {
|
||||
this.server = session;
|
||||
}
|
||||
|
||||
|
||||
execute(command: string, args: any, callback: (resp: protocol.Response) => void): void {
|
||||
if (!this.server) {
|
||||
return;
|
||||
@@ -394,7 +396,7 @@ namespace ts.server {
|
||||
this.callbacks[this.seq] = callback;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
it("can be constructed and respond to commands", (done) => {
|
||||
const cli = new InProcClient();
|
||||
const session = new InProcSession(cli);
|
||||
@@ -402,23 +404,23 @@ namespace ts.server {
|
||||
data: true
|
||||
};
|
||||
const toEvent = {
|
||||
data: false
|
||||
data: false
|
||||
};
|
||||
let responses = 0;
|
||||
|
||||
// Connect the client
|
||||
cli.connect(session);
|
||||
|
||||
|
||||
// Add an event handler
|
||||
cli.on("testevent", (eventinfo) => {
|
||||
expect(eventinfo).to.equal(toEvent);
|
||||
responses++;
|
||||
expect(responses).to.equal(1);
|
||||
});
|
||||
|
||||
|
||||
// Trigger said event from the server
|
||||
session.event(toEvent, "testevent");
|
||||
|
||||
|
||||
// Queue an echo command
|
||||
cli.execute("echo", toEcho, (resp) => {
|
||||
assert(resp.success, resp.message);
|
||||
@@ -426,7 +428,7 @@ namespace ts.server {
|
||||
expect(responses).to.equal(2);
|
||||
expect(resp.body).to.deep.equal(toEcho);
|
||||
});
|
||||
|
||||
|
||||
// Queue a configure command
|
||||
cli.execute("configure", {
|
||||
hostInfo: "unit test",
|
||||
@@ -436,10 +438,10 @@ namespace ts.server {
|
||||
}, (resp) => {
|
||||
assert(resp.success, resp.message);
|
||||
responses++;
|
||||
expect(responses).to.equal(3);
|
||||
expect(responses).to.equal(3);
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
// Consume the queue and trigger the callbacks
|
||||
session.consumeQueue();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user