Merge branch 'master' into tscWatchExportUpdate

This commit is contained in:
Sheetal Nandi 2019-01-03 11:35:54 -08:00
commit c426fc6868
287 changed files with 21354 additions and 15526 deletions

View File

@ -16,11 +16,7 @@ matrix:
branches:
only:
- master
- release-2.7
- release-2.8
- release-2.9
- release-3.0
- release-3.1
- /^release-.*/
install:
- npm uninstall typescript --no-save

View File

@ -82,19 +82,26 @@ Your pull request should:
* To avoid line ending issues, set `autocrlf = input` and `whitespace = cr-at-eol` in your git configuration
## Contributing `lib.d.ts` fixes
The library sources are in: [src/lib](https://github.com/Microsoft/TypeScript/tree/master/src/lib)
Library files in `built/local/` are updated by running
```Shell
There are three relevant locations to be aware of when it comes to TypeScript's library declaration files:
* `src/lib`: the location of the sources themselves.
* `lib`: the location of the last-known-good (LKG) versions of the files which are updated periodically.
* `built/local`: the build output location, including where `src/lib` files will be copied to.
Any changes should be made to [src/lib](https://github.com/Microsoft/TypeScript/tree/master/src/lib). **Most** of these files can be updated by hand, with the exception of any generated files (see below).
Library files in `built/local/` are updated automatically by running the standard build task:
```sh
jake
```
The files in `lib/` are used to bootstrap compilation and usually do not need to be updated.
The files in `lib/` are used to bootstrap compilation and usually **should not** be updated unless publishing a new version or updating the LKG.
#### `src/lib/dom.generated.d.ts` and `src/lib/webworker.generated.d.ts`
### Modifying generated library files
These two files represent the DOM typings and are auto-generated. To make any modifications to them, please submit a PR to https://github.com/Microsoft/TSJS-lib-generator
The files `src/lib/dom.generated.d.ts` and `src/lib/webworker.generated.d.ts` both represent type declarations for the DOM and are auto-generated. To make any modifications to them, you will have to direct changes to https://github.com/Microsoft/TSJS-lib-generator
## Running the Tests

View File

@ -82,6 +82,7 @@
"mocha": "latest",
"mocha-fivemat-progress-reporter": "latest",
"plugin-error": "latest",
"pretty-hrtime": "^1.0.3",
"prex": "^0.4.3",
"q": "latest",
"remove-internal": "^2.9.2",

View File

@ -683,11 +683,17 @@ function ensureCompileTask(projectGraph, options) {
}
});
}
const js = (projectGraphConfig.resolvedOptions.js ? projectGraphConfig.resolvedOptions.js(stream.js) : stream.js)
const additionalJsOutputs = projectGraphConfig.resolvedOptions.js ? projectGraphConfig.resolvedOptions.js(stream.js)
.pipe(gulpif(sourceMap || inlineSourceMap, sourcemaps.write(sourceMapPath, sourceMapOptions))) : undefined;
const additionalDtsOutputs = projectGraphConfig.resolvedOptions.dts ? projectGraphConfig.resolvedOptions.dts(stream.dts)
.pipe(gulpif(declarationMap, sourcemaps.write(sourceMapPath, sourceMapOptions))) : undefined;
const js = stream.js
.pipe(gulpif(sourceMap || inlineSourceMap, sourcemaps.write(sourceMapPath, sourceMapOptions)));
const dts = (projectGraphConfig.resolvedOptions.dts ? projectGraphConfig.resolvedOptions.dts(stream.dts) : stream.dts)
const dts = stream.dts
.pipe(gulpif(declarationMap, sourcemaps.write(sourceMapPath, sourceMapOptions)));
return merge2([js, dts])
return merge2([js, dts, additionalJsOutputs, additionalDtsOutputs].filter(x => !!x))
.pipe(gulp.dest(destPath));
});
}

View File

@ -143,7 +143,7 @@ namespace ts {
let symbolCount = 0;
let Symbol: { new (flags: SymbolFlags, name: __String): Symbol }; // tslint:disable-line variable-name
let Symbol: new (flags: SymbolFlags, name: __String) => Symbol; // tslint:disable-line variable-name
let classifiableNames: UnderscoreEscapedMap<true>;
const unreachableFlow: FlowNode = { flags: FlowFlags.Unreachable };
@ -233,6 +233,11 @@ namespace ts {
symbol.members = createSymbolTable();
}
// On merge of const enum module with class or function, reset const enum only flag (namespaces will already recalculate)
if (symbol.constEnumOnlyModule && (symbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.RegularEnum))) {
symbol.constEnumOnlyModule = false;
}
if (symbolFlags & SymbolFlags.Value) {
setValueDeclaration(symbol, node);
}

File diff suppressed because it is too large Load Diff

View File

@ -112,13 +112,13 @@ namespace ts {
}
// The global Map object. This may not be available, so we must test for it.
declare const Map: { new <T>(): Map<T> } | undefined;
declare const Map: (new <T>() => Map<T>) | undefined;
// Internet Explorer's Map doesn't support iteration, so don't use it.
// tslint:disable-next-line no-in-operator variable-name
export const MapCtr = typeof Map !== "undefined" && "entries" in Map.prototype ? Map : shimMap();
// Keep the class inside a function so it doesn't get compiled if it's not used.
function shimMap(): { new <T>(): Map<T> } {
function shimMap(): new <T>() => Map<T> {
class MapIterator<T, U extends (string | T | [string, T])> {
private data: MapLike<T>;

View File

@ -1015,6 +1015,14 @@
"category": "Error",
"code": 1351
},
"A bigint literal cannot use exponential notation.": {
"category": "Error",
"code": 1352
},
"A bigint literal must be an integer.": {
"category": "Error",
"code": 1353
},
"Duplicate identifier '{0}'.": {
"category": "Error",
@ -1636,14 +1644,6 @@
"category": "Error",
"code": 2458
},
"Type '{0}' has no property '{1}' and no string index signature.": {
"category": "Error",
"code": 2459
},
"Type '{0}' has no property '{1}'.": {
"category": "Error",
"code": 2460
},
"Type '{0}' is not an array type.": {
"category": "Error",
"code": 2461
@ -1696,7 +1696,7 @@
"category": "Error",
"code": 2473
},
"In 'const' enum declarations member initializer must be constant expression.": {
"const enum member initializers can only contain literal values and other computed enum values.": {
"category": "Error",
"code": 2474
},
@ -1760,7 +1760,7 @@
"category": "Error",
"code": 2492
},
"Tuple type '{0}' with length '{1}' cannot be assigned to tuple with length '{2}'.": {
"Tuple type '{0}' of length '{1}' has no element at index '{2}'.": {
"category": "Error",
"code": 2493
},
@ -2537,6 +2537,10 @@
"category": "Error",
"code": 2743
},
"Type parameter defaults can only reference previously declared type parameters.": {
"category": "Error",
"code": 2744
},
"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",

View File

@ -1651,11 +1651,17 @@ namespace ts {
function emitPropertyAccessExpression(node: PropertyAccessExpression) {
let indentBeforeDot = false;
let indentAfterDot = false;
const dotRangeFirstCommentStart = skipTrivia(
currentSourceFile!.text,
node.expression.end,
/*stopAfterLineBreak*/ false,
/*stopAtComments*/ true
);
const dotRangeStart = skipTrivia(currentSourceFile!.text, dotRangeFirstCommentStart);
const dotRangeEnd = dotRangeStart + 1;
if (!(getEmitFlags(node) & EmitFlags.NoIndentation)) {
const dotRangeStart = node.expression.end;
const dotRangeEnd = skipTrivia(currentSourceFile!.text, node.expression.end) + 1;
const dotToken = createToken(SyntaxKind.DotToken);
dotToken.pos = dotRangeStart;
dotToken.pos = node.expression.end;
dotToken.end = dotRangeEnd;
indentBeforeDot = needsIndentation(node, node.expression, dotToken);
indentAfterDot = needsIndentation(node, dotToken, node.name);
@ -1664,7 +1670,8 @@ namespace ts {
emitExpression(node.expression);
increaseIndentIf(indentBeforeDot, /*writeSpaceIfNotIndenting*/ false);
const shouldEmitDotDot = !indentBeforeDot && needsDotDotForPropertyAccess(node.expression);
const dotHasCommentTrivia = dotRangeFirstCommentStart !== dotRangeStart;
const shouldEmitDotDot = !indentBeforeDot && needsDotDotForPropertyAccess(node.expression, dotHasCommentTrivia);
if (shouldEmitDotDot) {
writePunctuation(".");
}
@ -1677,13 +1684,15 @@ namespace ts {
// 1..toString is a valid property access, emit a dot after the literal
// Also emit a dot if expression is a integer const enum value - it will appear in generated code as numeric literal
function needsDotDotForPropertyAccess(expression: Expression) {
function needsDotDotForPropertyAccess(expression: Expression, dotHasTrivia: boolean) {
expression = skipPartiallyEmittedExpressions(expression);
if (isNumericLiteral(expression)) {
// check if numeric literal is a decimal literal that was originally written with a dot
const text = getLiteralTextOfNode(<LiteralExpression>expression, /*neverAsciiEscape*/ true);
return !expression.numericLiteralFlags
&& !stringContains(text, tokenToString(SyntaxKind.DotToken)!);
// If he number will be printed verbatim and it doesn't already contain a dot, add one
// if the expression doesn't have any comments that will be emitted.
return !expression.numericLiteralFlags && !stringContains(text, tokenToString(SyntaxKind.DotToken)!) &&
(!dotHasTrivia || printerOptions.removeComments);
}
else if (isPropertyAccessExpression(expression) || isElementAccessExpression(expression)) {
// check if constant enum value is integer

View File

@ -787,7 +787,13 @@ namespace ts {
// Key is a file name. Value is the (non-empty, or undefined) list of files that redirect to it.
let redirectTargetsMap = createMultiMap<string>();
const filesByName = createMap<SourceFile | undefined>();
/**
* map with
* - SourceFile if present
* - false if sourceFile missing for source of project reference redirect
* - undefined otherwise
*/
const filesByName = createMap<SourceFile | false | undefined>();
let missingFilePaths: ReadonlyArray<Path> | undefined;
// stores 'filename -> file association' ignoring case
// used to track cases when two file names differ only in casing
@ -854,7 +860,7 @@ namespace ts {
}
}
missingFilePaths = arrayFrom(filesByName.keys(), p => <Path>p).filter(p => !filesByName.get(p));
missingFilePaths = arrayFrom(mapDefinedIterator(filesByName.entries(), ([path, file]) => file === undefined ? path as Path : undefined));
files = stableSort(processingDefaultLibFiles, compareDefaultLibFiles).concat(processingOtherFiles);
processingDefaultLibFiles = undefined;
processingOtherFiles = undefined;
@ -1567,7 +1573,7 @@ namespace ts {
}
function getSourceFileByPath(path: Path): SourceFile | undefined {
return filesByName.get(path);
return filesByName.get(path) || undefined;
}
function getDiagnosticsHelper<T extends Diagnostic>(
@ -2104,7 +2110,7 @@ namespace ts {
/** This should have similar behavior to 'processSourceFile' without diagnostics or mutation. */
function getSourceFileFromReference(referencingFile: SourceFile, ref: FileReference): SourceFile | undefined {
return getSourceFileFromReferenceWorker(resolveTripleslashReference(ref.fileName, referencingFile.fileName), fileName => filesByName.get(toPath(fileName)));
return getSourceFileFromReferenceWorker(resolveTripleslashReference(ref.fileName, referencingFile.fileName), fileName => filesByName.get(toPath(fileName)) || undefined);
}
function getSourceFileFromReferenceWorker(
@ -2235,7 +2241,7 @@ namespace ts {
}
}
return file;
return file || undefined;
}
let redirectedPath: Path | undefined;
@ -2327,9 +2333,12 @@ namespace ts {
}
function addFileToFilesByName(file: SourceFile | undefined, path: Path, redirectedPath: Path | undefined) {
filesByName.set(path, file);
if (redirectedPath) {
filesByName.set(redirectedPath, file);
filesByName.set(path, file || false);
}
else {
filesByName.set(path, file);
}
}

View File

@ -337,13 +337,14 @@ namespace ts {
return result;
}
export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number): number {
return computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character, sourceFile.text);
}
export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number): number;
/* @internal */
export function getPositionOfLineAndCharacterWithEdits(sourceFile: SourceFileLike, line: number, character: number): number {
return computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character, sourceFile.text, /*allowEdits*/ true);
// tslint:disable-next-line:unified-signatures
export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number, allowEdits?: true): number;
export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number, allowEdits?: true): number {
return sourceFile.getPositionOfLineAndCharacter ?
sourceFile.getPositionOfLineAndCharacter(line, character, allowEdits) :
computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character, sourceFile.text, allowEdits);
}
/* @internal */
@ -976,7 +977,7 @@ namespace ts {
}
if (decimalFragment !== undefined || tokenFlags & TokenFlags.Scientific) {
checkForIdentifierStartAfterNumericLiteral();
checkForIdentifierStartAfterNumericLiteral(start, decimalFragment === undefined && !!(tokenFlags & TokenFlags.Scientific));
return {
type: SyntaxKind.NumericLiteral,
value: "" + +result // if value is not an integer, it can be safely coerced to a number
@ -985,20 +986,31 @@ namespace ts {
else {
tokenValue = result;
const type = checkBigIntSuffix(); // if value is an integer, check whether it is a bigint
checkForIdentifierStartAfterNumericLiteral();
checkForIdentifierStartAfterNumericLiteral(start);
return { type, value: tokenValue };
}
}
function checkForIdentifierStartAfterNumericLiteral() {
function checkForIdentifierStartAfterNumericLiteral(numericStart: number, isScientific?: boolean) {
if (!isIdentifierStart(text.charCodeAt(pos), languageVersion)) {
return;
}
const identifierStart = pos;
const { length } = scanIdentifierParts();
error(Diagnostics.An_identifier_or_keyword_cannot_immediately_follow_a_numeric_literal, identifierStart, length);
pos = identifierStart;
if (length === 1 && text[identifierStart] === "n") {
if (isScientific) {
error(Diagnostics.A_bigint_literal_cannot_use_exponential_notation, numericStart, identifierStart - numericStart + 1);
}
else {
error(Diagnostics.A_bigint_literal_must_be_an_integer, numericStart, identifierStart - numericStart + 1);
}
}
else {
error(Diagnostics.An_identifier_or_keyword_cannot_immediately_follow_a_numeric_literal, identifierStart, length);
pos = identifierStart;
}
}
function scanOctalDigits(): number {

View File

@ -266,14 +266,24 @@ namespace ts {
const sourceMapCommentRegExp = /^\/\/[@#] source[M]appingURL=(.+)\s*$/;
const whitespaceOrMapCommentRegExp = /^\s*(\/\/[@#] .*)?$/;
export interface LineInfo {
getLineCount(): number;
getLineText(line: number): string;
}
export function getLineInfo(text: string, lineStarts: ReadonlyArray<number>): LineInfo {
return {
getLineCount: () => lineStarts.length,
getLineText: line => text.substring(lineStarts[line], lineStarts[line + 1])
};
}
/**
* Tries to find the sourceMappingURL comment at the end of a file.
* @param text The source text of the file.
* @param lineStarts The line starts of the file.
*/
export function tryGetSourceMappingURL(text: string, lineStarts: ReadonlyArray<number> = computeLineStarts(text)) {
for (let index = lineStarts.length - 1; index >= 0; index--) {
const line = text.substring(lineStarts[index], lineStarts[index + 1]);
export function tryGetSourceMappingURL(lineInfo: LineInfo) {
for (let index = lineInfo.getLineCount() - 1; index >= 0; index--) {
const line = lineInfo.getLineText(index);
const comment = sourceMapCommentRegExp.exec(line);
if (comment) {
return comment[1];
@ -573,7 +583,10 @@ namespace ts {
}
function compareSourcePositions(left: SourceMappedPosition, right: SourceMappedPosition) {
return compareValues(left.sourceIndex, right.sourceIndex);
// Compares sourcePosition without comparing sourceIndex
// since the mappings are grouped by sourceIndex
Debug.assert(left.sourceIndex === right.sourceIndex);
return compareValues(left.sourcePosition, right.sourcePosition);
}
function compareGeneratedPositions(left: MappedPosition, right: MappedPosition) {
@ -592,11 +605,9 @@ namespace ts {
const mapDirectory = getDirectoryPath(mapPath);
const sourceRoot = map.sourceRoot ? getNormalizedAbsolutePath(map.sourceRoot, mapDirectory) : mapDirectory;
const generatedAbsoluteFilePath = getNormalizedAbsolutePath(map.file, mapDirectory);
const generatedCanonicalFilePath = host.getCanonicalFileName(generatedAbsoluteFilePath) as Path;
const generatedFile = host.getSourceFileLike(generatedCanonicalFilePath);
const generatedFile = host.getSourceFileLike(generatedAbsoluteFilePath);
const sourceFileAbsolutePaths = map.sources.map(source => getNormalizedAbsolutePath(source, sourceRoot));
const sourceFileCanonicalPaths = sourceFileAbsolutePaths.map(source => host.getCanonicalFileName(source) as Path);
const sourceToSourceIndexMap = createMapFromEntries(sourceFileCanonicalPaths.map((source, i) => [source, i] as [string, number]));
const sourceToSourceIndexMap = createMapFromEntries(sourceFileAbsolutePaths.map((source, i) => [host.getCanonicalFileName(source), i] as [string, number]));
let decodedMappings: ReadonlyArray<MappedPosition> | undefined;
let generatedMappings: SortedReadonlyArray<MappedPosition> | undefined;
let sourceMappings: ReadonlyArray<SortedReadonlyArray<SourceMappedPosition>> | undefined;
@ -608,16 +619,15 @@ namespace ts {
function processMapping(mapping: Mapping): MappedPosition {
const generatedPosition = generatedFile !== undefined
? getPositionOfLineAndCharacterWithEdits(generatedFile, mapping.generatedLine, mapping.generatedCharacter)
? getPositionOfLineAndCharacter(generatedFile, mapping.generatedLine, mapping.generatedCharacter, /*allowEdits*/ true)
: -1;
let source: string | undefined;
let sourcePosition: number | undefined;
if (isSourceMapping(mapping)) {
const sourceFilePath = sourceFileCanonicalPaths[mapping.sourceIndex];
const sourceFile = host.getSourceFileLike(sourceFilePath);
const sourceFile = host.getSourceFileLike(sourceFileAbsolutePaths[mapping.sourceIndex]);
source = map.sources[mapping.sourceIndex];
sourcePosition = sourceFile !== undefined
? getPositionOfLineAndCharacterWithEdits(sourceFile, mapping.sourceLine, mapping.sourceCharacter)
? getPositionOfLineAndCharacter(sourceFile, mapping.sourceLine, mapping.sourceCharacter, /*allowEdits*/ true)
: -1;
}
return {

View File

@ -1100,7 +1100,8 @@ namespace ts {
if (extendsClause && !isEntityNameExpression(extendsClause.expression) && extendsClause.expression.kind !== SyntaxKind.NullKeyword) {
// We must add a temporary declaration for the extends clause expression
const newId = createOptimisticUniqueName(`${unescapeLeadingUnderscores(input.name!.escapedText)}_base`); // TODO: GH#18217
const oldId = input.name ? unescapeLeadingUnderscores(input.name.escapedText) : "default";
const newId = createOptimisticUniqueName(`${oldId}_base`);
getSymbolAccessibilityDiagnostic = () => ({
diagnosticMessage: Diagnostics.extends_clause_of_exported_class_0_has_or_is_using_private_name_1,
errorNode: extendsClause,

View File

@ -1745,6 +1745,9 @@ namespace ts {
export type EntityNameExpression = Identifier | PropertyAccessEntityNameExpression;
export type EntityNameOrEntityNameExpression = EntityName | EntityNameExpression;
/* @internal */
export type AccessExpression = PropertyAccessExpression | ElementAccessExpression;
export interface PropertyAccessExpression extends MemberExpression, NamedDeclaration {
kind: SyntaxKind.PropertyAccessExpression;
expression: LeftHandSideExpression;
@ -2614,6 +2617,8 @@ namespace ts {
export interface SourceFileLike {
readonly text: string;
lineMap?: ReadonlyArray<number>;
/* @internal */
getPositionOfLineAndCharacter?(line: number, character: number, allowEdits?: true): number;
}
@ -3103,7 +3108,7 @@ namespace ts {
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number | undefined;
isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName | ImportTypeNode, propertyName: string): boolean;
/** Exclude accesses to private properties or methods with a `this` parameter that `type` doesn't satisfy. */
/* @internal */ isValidPropertyAccessForCompletions(node: PropertyAccessExpression | ImportTypeNode, type: Type, property: Symbol): boolean;
/* @internal */ isValidPropertyAccessForCompletions(node: PropertyAccessExpression | ImportTypeNode | QualifiedName, type: Type, property: Symbol): boolean;
/** Follow all aliases to get the original symbol. */
getAliasedSymbol(symbol: Symbol): Symbol;
/** Follow a *single* alias to get the immediately aliased symbol. */
@ -3672,15 +3677,17 @@ namespace ts {
Readonly = 1 << 3, // Readonly transient symbol
Partial = 1 << 4, // Synthetic property present in some but not all constituents
HasNonUniformType = 1 << 5, // Synthetic property with non-uniform type in constituents
ContainsPublic = 1 << 6, // Synthetic property with public constituent(s)
ContainsProtected = 1 << 7, // Synthetic property with protected constituent(s)
ContainsPrivate = 1 << 8, // Synthetic property with private constituent(s)
ContainsStatic = 1 << 9, // Synthetic property with static constituent(s)
Late = 1 << 10, // Late-bound symbol for a computed property with a dynamic name
ReverseMapped = 1 << 11, // Property of reverse-inferred homomorphic mapped type
OptionalParameter = 1 << 12, // Optional parameter
RestParameter = 1 << 13, // Rest parameter
Synthetic = SyntheticProperty | SyntheticMethod
HasLiteralType = 1 << 6, // Synthetic property with at least one literal type in constituents
ContainsPublic = 1 << 7, // Synthetic property with public constituent(s)
ContainsProtected = 1 << 8, // Synthetic property with protected constituent(s)
ContainsPrivate = 1 << 9, // Synthetic property with private constituent(s)
ContainsStatic = 1 << 10, // Synthetic property with static constituent(s)
Late = 1 << 11, // Late-bound symbol for a computed property with a dynamic name
ReverseMapped = 1 << 12, // Property of reverse-inferred homomorphic mapped type
OptionalParameter = 1 << 13, // Optional parameter
RestParameter = 1 << 14, // Rest parameter
Synthetic = SyntheticProperty | SyntheticMethod,
Discriminant = HasNonUniformType | HasLiteralType
}
/* @internal */
@ -5528,9 +5535,9 @@ namespace ts {
/* @internal */
export interface DocumentPositionMapperHost {
getSourceFileLike(path: Path): SourceFileLike | undefined;
getSourceFileLike(fileName: string): SourceFileLike | undefined;
getCanonicalFileName(path: string): string;
log?(text: string): void;
log(text: string): void;
}
/**

View File

@ -2121,7 +2121,7 @@ namespace ts {
: undefined;
}
function getSingleInitializerOfVariableStatementOrPropertyDeclaration(node: Node): Expression | undefined {
export function getSingleInitializerOfVariableStatementOrPropertyDeclaration(node: Node): Expression | undefined {
switch (node.kind) {
case SyntaxKind.VariableStatement:
const v = getSingleVariableOfVariableStatement(node);
@ -4590,6 +4590,10 @@ namespace ts {
|| kind === SyntaxKind.JSDocFunctionType
|| kind === SyntaxKind.JSDocVariadicType;
}
export function isAccessExpression(node: Node): node is AccessExpression {
return node.kind === SyntaxKind.PropertyAccessExpression || node.kind === SyntaxKind.ElementAccessExpression;
}
}
namespace ts {
@ -6925,7 +6929,7 @@ namespace ts {
export interface ObjectAllocator {
getNodeConstructor(): new (kind: SyntaxKind, pos?: number, end?: number) => Node;
getTokenConstructor(): { new <TKind extends SyntaxKind>(kind: TKind, pos?: number, end?: number): Token<TKind> };
getTokenConstructor(): new <TKind extends SyntaxKind>(kind: TKind, pos?: number, end?: number) => Token<TKind>;
getIdentifierConstructor(): new (kind: SyntaxKind.Identifier, pos?: number, end?: number) => Identifier;
getSourceFileConstructor(): new (kind: SyntaxKind.SourceFile, pos?: number, end?: number) => SourceFile;
getSymbolConstructor(): new (flags: SymbolFlags, name: __String) => Symbol;

View File

@ -600,7 +600,7 @@ namespace FourSlash {
throw new Error("Expected exactly one output from emit of " + this.activeFile.fileName);
}
const evaluation = new Function(`${emit.outputFiles[0].text};\r\nreturn (${expr});`)();
const evaluation = new Function(`${emit.outputFiles[0].text};\r\nreturn (${expr});`)(); // tslint:disable-line:function-constructor
if (evaluation !== value) {
this.raiseError(`Expected evaluation of expression "${expr}" to equal "${value}", but got "${evaluation}"`);
}

View File

@ -28,12 +28,10 @@ var assert: typeof _chai.assert = _chai.assert;
};
}
var global: NodeJS.Global = Function("return this").call(undefined);
var global: NodeJS.Global = Function("return this").call(undefined); // tslint:disable-line:function-constructor
declare var window: {};
declare var XMLHttpRequest: {
new(): XMLHttpRequest;
};
declare var XMLHttpRequest: new() => XMLHttpRequest;
interface XMLHttpRequest {
readonly readyState: number;
readonly responseText: string;

View File

@ -69,7 +69,7 @@ namespace Harness.SourceMapRecorder {
SourceMapDecoder.initializeSourceMapDecoding(sourceMapData);
sourceMapRecorder.WriteLine("===================================================================");
sourceMapRecorder.WriteLine("JsFile: " + sourceMapData.sourceMap.file);
sourceMapRecorder.WriteLine("mapUrl: " + ts.tryGetSourceMappingURL(jsFile.text, jsLineMap));
sourceMapRecorder.WriteLine("mapUrl: " + ts.tryGetSourceMappingURL(ts.getLineInfo(jsFile.text, jsLineMap)));
sourceMapRecorder.WriteLine("sourceRoot: " + sourceMapData.sourceMap.sourceRoot);
sourceMapRecorder.WriteLine("sources: " + sourceMapData.sourceMap.sources);
if (sourceMapData.sourceMap.sourcesContent) {

View File

@ -342,6 +342,7 @@ namespace ts.server {
FailedLookupLocation = "Directory of Failed lookup locations in module resolution",
TypeRoots = "Type root directory",
NodeModulesForClosedScriptInfo = "node_modules for closed script infos in them",
MissingSourceMapFile = "Missing source map file"
}
const enum ConfigFileWatcherStatus {
@ -437,7 +438,8 @@ namespace ts.server {
/**
* Container of all known scripts
*/
private readonly filenameToScriptInfo = createMap<ScriptInfo>();
/*@internal*/
readonly filenameToScriptInfo = createMap<ScriptInfo>();
private readonly scriptInfoInNodeModulesWatchers = createMap <ScriptInfoInNodeModulesWatcher>();
/**
* Contains all the deleted script info's version information so that
@ -944,10 +946,42 @@ namespace ts.server {
// this file and set of inferred projects
info.delayReloadNonMixedContentFile();
this.delayUpdateProjectGraphs(info.containingProjects);
this.handleSourceMapProjects(info);
}
}
}
private handleSourceMapProjects(info: ScriptInfo) {
// Change in d.ts, update source projects as well
if (info.sourceMapFilePath) {
if (isString(info.sourceMapFilePath)) {
const sourceMapFileInfo = this.getScriptInfoForPath(info.sourceMapFilePath);
this.delayUpdateSourceInfoProjects(sourceMapFileInfo && sourceMapFileInfo.sourceInfos);
}
else {
this.delayUpdateSourceInfoProjects(info.sourceMapFilePath.sourceInfos);
}
}
// Change in mapInfo, update declarationProjects and source projects
this.delayUpdateSourceInfoProjects(info.sourceInfos);
if (info.declarationInfoPath) {
this.delayUpdateProjectsOfScriptInfoPath(info.declarationInfoPath);
}
}
private delayUpdateSourceInfoProjects(sourceInfos: Map<true> | undefined) {
if (sourceInfos) {
sourceInfos.forEach((_value, path) => this.delayUpdateProjectsOfScriptInfoPath(path as Path));
}
}
private delayUpdateProjectsOfScriptInfoPath(path: Path) {
const info = this.getScriptInfoForPath(path);
if (info) {
this.delayUpdateProjectGraphs(info.containingProjects);
}
}
private handleDeletedFile(info: ScriptInfo) {
this.stopWatchingScriptInfo(info);
@ -961,6 +995,15 @@ namespace ts.server {
// update projects to make sure that set of referenced files is correct
this.delayUpdateProjectGraphs(containingProjects);
this.handleSourceMapProjects(info);
info.closeSourceMapFileWatcher();
// need to recalculate source map from declaration file
if (info.declarationInfoPath) {
const declarationInfo = this.getScriptInfoForPath(info.declarationInfoPath);
if (declarationInfo) {
declarationInfo.sourceMapFilePath = undefined;
}
}
}
}
@ -1234,7 +1277,8 @@ namespace ts.server {
private setConfigFileExistenceByNewConfiguredProject(project: ConfiguredProject) {
const configFileExistenceInfo = this.getConfigFileExistenceInfo(project);
if (configFileExistenceInfo) {
Debug.assert(configFileExistenceInfo.exists);
// The existance might not be set if the file watcher is not invoked by the time config project is created by external project
configFileExistenceInfo.exists = true;
// close existing watcher
if (configFileExistenceInfo.configFileWatcherForRootOfInferredProject) {
const configFileName = project.getConfigFilePath();
@ -2195,6 +2239,150 @@ namespace ts.server {
return this.filenameToScriptInfo.get(fileName);
}
/*@internal*/
getDocumentPositionMapper(project: Project, generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined {
// Since declaration info and map file watches arent updating project's directory structure host (which can cache file structure) use host
const declarationInfo = this.getOrCreateScriptInfoNotOpenedByClient(generatedFileName, project.currentDirectory, this.host);
if (!declarationInfo) return undefined;
// Try to get from cache
declarationInfo.getSnapshot(); // Ensure synchronized
if (isString(declarationInfo.sourceMapFilePath)) {
// Ensure mapper is synchronized
const sourceMapFileInfo = this.getScriptInfoForPath(declarationInfo.sourceMapFilePath);
if (sourceMapFileInfo) {
sourceMapFileInfo.getSnapshot();
if (sourceMapFileInfo.documentPositionMapper !== undefined) {
sourceMapFileInfo.sourceInfos = this.addSourceInfoToSourceMap(sourceFileName, project, sourceMapFileInfo.sourceInfos);
return sourceMapFileInfo.documentPositionMapper ? sourceMapFileInfo.documentPositionMapper : undefined;
}
}
declarationInfo.sourceMapFilePath = undefined;
}
else if (declarationInfo.sourceMapFilePath) {
declarationInfo.sourceMapFilePath.sourceInfos = this.addSourceInfoToSourceMap(sourceFileName, project, declarationInfo.sourceMapFilePath.sourceInfos);
return undefined;
}
else if (declarationInfo.sourceMapFilePath !== undefined) {
// Doesnt have sourceMap
return undefined;
}
// Create the mapper
let sourceMapFileInfo: ScriptInfo | undefined;
let mapFileNameFromDeclarationInfo: string | undefined;
let readMapFile: ReadMapFile | undefined = (mapFileName, mapFileNameFromDts) => {
const mapInfo = this.getOrCreateScriptInfoNotOpenedByClient(mapFileName, project.currentDirectory, this.host);
if (!mapInfo) {
mapFileNameFromDeclarationInfo = mapFileNameFromDts;
return undefined;
}
sourceMapFileInfo = mapInfo;
const snap = mapInfo.getSnapshot();
if (mapInfo.documentPositionMapper !== undefined) return mapInfo.documentPositionMapper;
return snap.getText(0, snap.getLength());
};
const projectName = project.projectName;
const documentPositionMapper = getDocumentPositionMapper(
{ getCanonicalFileName: this.toCanonicalFileName, log: s => this.logger.info(s), getSourceFileLike: f => this.getSourceFileLike(f, projectName, declarationInfo) },
declarationInfo.fileName,
declarationInfo.getLineInfo(),
readMapFile
);
readMapFile = undefined; // Remove ref to project
if (sourceMapFileInfo) {
declarationInfo.sourceMapFilePath = sourceMapFileInfo.path;
sourceMapFileInfo.declarationInfoPath = declarationInfo.path;
sourceMapFileInfo.documentPositionMapper = documentPositionMapper || false;
sourceMapFileInfo.sourceInfos = this.addSourceInfoToSourceMap(sourceFileName, project, sourceMapFileInfo.sourceInfos);
}
else if (mapFileNameFromDeclarationInfo) {
declarationInfo.sourceMapFilePath = {
watcher: this.addMissingSourceMapFile(
project.currentDirectory === this.currentDirectory ?
mapFileNameFromDeclarationInfo :
getNormalizedAbsolutePath(mapFileNameFromDeclarationInfo, project.currentDirectory),
declarationInfo.path
),
sourceInfos: this.addSourceInfoToSourceMap(sourceFileName, project)
};
}
else {
declarationInfo.sourceMapFilePath = false;
}
return documentPositionMapper;
}
private addSourceInfoToSourceMap(sourceFileName: string | undefined, project: Project, sourceInfos?: Map<true>) {
if (sourceFileName) {
// Attach as source
const sourceInfo = this.getOrCreateScriptInfoNotOpenedByClient(sourceFileName, project.currentDirectory, project.directoryStructureHost)!;
(sourceInfos || (sourceInfos = createMap())).set(sourceInfo.path, true);
}
return sourceInfos;
}
private addMissingSourceMapFile(mapFileName: string, declarationInfoPath: Path) {
const fileWatcher = this.watchFactory.watchFile(
this.host,
mapFileName,
() => {
const declarationInfo = this.getScriptInfoForPath(declarationInfoPath);
if (declarationInfo && declarationInfo.sourceMapFilePath && !isString(declarationInfo.sourceMapFilePath)) {
// Update declaration and source projects
this.delayUpdateProjectGraphs(declarationInfo.containingProjects);
this.delayUpdateSourceInfoProjects(declarationInfo.sourceMapFilePath.sourceInfos);
declarationInfo.closeSourceMapFileWatcher();
}
},
PollingInterval.High,
WatchType.MissingSourceMapFile,
);
return fileWatcher;
}
/*@internal*/
getSourceFileLike(fileName: string, projectNameOrProject: string | Project, declarationInfo?: ScriptInfo) {
const project = (projectNameOrProject as Project).projectName ? projectNameOrProject as Project : this.findProject(projectNameOrProject as string);
if (project) {
const path = project.toPath(fileName);
const sourceFile = project.getSourceFile(path);
if (sourceFile && sourceFile.resolvedPath === path) return sourceFile;
}
// Need to look for other files.
const info = this.getOrCreateScriptInfoNotOpenedByClient(fileName, (project || this).currentDirectory, project ? project.directoryStructureHost : this.host);
if (!info) return undefined;
// Attach as source
if (declarationInfo && isString(declarationInfo.sourceMapFilePath) && info !== declarationInfo) {
const sourceMapInfo = this.getScriptInfoForPath(declarationInfo.sourceMapFilePath);
if (sourceMapInfo) {
(sourceMapInfo.sourceInfos || (sourceMapInfo.sourceInfos = createMap())).set(info.path, true);
}
}
// Key doesnt matter since its only for text and lines
if (info.cacheSourceFile) return info.cacheSourceFile.sourceFile;
// Create sourceFileLike
if (!info.sourceFileLike) {
info.sourceFileLike = {
get text() {
Debug.fail("shouldnt need text");
return "";
},
getLineAndCharacterOfPosition: pos => {
const lineOffset = info.positionToLineOffset(pos);
return { line: lineOffset.line - 1, character: lineOffset.offset - 1 };
},
getPositionOfLineAndCharacter: (line, character, allowEdits) => info.lineOffsetToPosition(line + 1, character + 1, allowEdits)
};
}
return info.sourceFileLike;
}
setHostConfiguration(args: protocol.ConfigureRequestArguments) {
if (args.file) {
const info = this.getScriptInfoForNormalizedPath(toNormalizedPath(args.file));
@ -2416,7 +2604,7 @@ namespace ts.server {
/** @internal */
fileExists(fileName: NormalizedPath): boolean {
return this.filenameToScriptInfo.has(fileName) || this.host.fileExists(fileName);
return !!this.getScriptInfoForNormalizedPath(fileName) || this.host.fileExists(fileName);
}
private findExternalProjectContainingOpenScriptInfo(info: ScriptInfo): ExternalProject | undefined {
@ -2490,13 +2678,7 @@ namespace ts.server {
// when some file/s were closed which resulted in project removal.
// It was then postponed to cleanup these script infos so that they can be reused if
// the file from that old project is reopened because of opening file from here.
this.filenameToScriptInfo.forEach(info => {
if (!info.isScriptOpen() && info.isOrphan()) {
// if there are not projects that include this script info - delete it
this.stopWatchingScriptInfo(info);
this.deleteScriptInfo(info);
}
});
this.removeOrphanScriptInfos();
this.printProjects();
@ -2539,6 +2721,57 @@ namespace ts.server {
}
}
private removeOrphanScriptInfos() {
const toRemoveScriptInfos = cloneMap(this.filenameToScriptInfo);
this.filenameToScriptInfo.forEach(info => {
// If script info is open or orphan, retain it and its dependencies
if (!info.isScriptOpen() && info.isOrphan()) {
// Otherwise if there is any source info that is alive, this alive too
if (!info.sourceMapFilePath) return;
let sourceInfos: Map<true> | undefined;
if (isString(info.sourceMapFilePath)) {
const sourceMapInfo = this.getScriptInfoForPath(info.sourceMapFilePath);
sourceInfos = sourceMapInfo && sourceMapInfo.sourceInfos;
}
else {
sourceInfos = info.sourceMapFilePath.sourceInfos;
}
if (!sourceInfos) return;
if (!forEachKey(sourceInfos, path => {
const info = this.getScriptInfoForPath(path as Path);
return !!info && (info.isScriptOpen() || !info.isOrphan());
})) {
return;
}
}
// Retain this script info
toRemoveScriptInfos.delete(info.path);
if (info.sourceMapFilePath) {
let sourceInfos: Map<true> | undefined;
if (isString(info.sourceMapFilePath)) {
// And map file info and source infos
toRemoveScriptInfos.delete(info.sourceMapFilePath);
const sourceMapInfo = this.getScriptInfoForPath(info.sourceMapFilePath);
sourceInfos = sourceMapInfo && sourceMapInfo.sourceInfos;
}
else {
sourceInfos = info.sourceMapFilePath.sourceInfos;
}
if (sourceInfos) {
sourceInfos.forEach((_value, path) => toRemoveScriptInfos.delete(path));
}
}
});
toRemoveScriptInfos.forEach(info => {
// if there are not projects that include this script info - delete it
this.stopWatchingScriptInfo(info);
this.deleteScriptInfo(info);
info.closeSourceMapFileWatcher();
});
}
private telemetryOnOpenFile(scriptInfo: ScriptInfo): void {
if (this.syntaxOnly || !this.eventHandler || !scriptInfo.isJavaScript() || !addToSeen(this.allJsFilesForOpenFileTelemetry, scriptInfo.path)) {
return;

View File

@ -503,6 +503,16 @@ namespace ts.server {
return this.getLanguageService().getSourceMapper();
}
/*@internal*/
getDocumentPositionMapper(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined {
return this.projectService.getDocumentPositionMapper(this, generatedFileName, sourceFileName);
}
/*@internal*/
getSourceFileLike(fileName: string) {
return this.projectService.getSourceFileLike(fileName, this);
}
private shouldEmitFile(scriptInfo: ScriptInfo) {
return scriptInfo && !scriptInfo.isDynamicOrHasMixedContent();
}
@ -749,7 +759,10 @@ namespace ts.server {
}
containsScriptInfo(info: ScriptInfo): boolean {
return this.isRoot(info) || (!!this.program && this.program.getSourceFileByPath(info.path) !== undefined);
if (this.isRoot(info)) return true;
if (!this.program) return false;
const file = this.program.getSourceFileByPath(info.path);
return !!file && file.resolvedPath === info.path;
}
containsFile(filename: NormalizedPath, requireOpen?: boolean): boolean {

View File

@ -64,12 +64,22 @@ namespace ts.server {
this.switchToScriptVersionCache();
}
private resetSourceMapInfo() {
this.info.sourceFileLike = undefined;
this.info.closeSourceMapFileWatcher();
this.info.sourceMapFilePath = undefined;
this.info.declarationInfoPath = undefined;
this.info.sourceInfos = undefined;
this.info.documentPositionMapper = undefined;
}
/** Public for testing */
public useText(newText?: string) {
this.svc = undefined;
this.text = newText;
this.lineMap = undefined;
this.fileSize = undefined;
this.resetSourceMapInfo();
this.version.text++;
}
@ -79,6 +89,7 @@ namespace ts.server {
this.text = undefined;
this.lineMap = undefined;
this.fileSize = undefined;
this.resetSourceMapInfo();
}
/**
@ -156,8 +167,8 @@ namespace ts.server {
: ScriptSnapshot.fromString(this.getOrLoadText());
}
public getLineInfo(line: number): AbsolutePositionAndLineText {
return this.switchToScriptVersionCache().getLineInfo(line);
public getAbsolutePositionAndLineText(line: number): AbsolutePositionAndLineText {
return this.switchToScriptVersionCache().getAbsolutePositionAndLineText(line);
}
/**
* @param line 0 based index
@ -176,9 +187,9 @@ namespace ts.server {
* @param line 1 based index
* @param offset 1 based index
*/
lineOffsetToPosition(line: number, offset: number): number {
lineOffsetToPosition(line: number, offset: number, allowEdits?: true): number {
if (!this.useScriptVersionCacheIfValidOrOpen()) {
return computePositionOfLineAndCharacter(this.getLineMap(), line - 1, offset - 1, this.text);
return computePositionOfLineAndCharacter(this.getLineMap(), line - 1, offset - 1, this.text, allowEdits);
}
// TODO: assert this offset is actually on the line
@ -246,6 +257,17 @@ namespace ts.server {
Debug.assert(!this.svc, "ScriptVersionCache should not be set");
return this.lineMap || (this.lineMap = computeLineStarts(this.getOrLoadText()));
}
getLineInfo(): LineInfo {
if (this.svc) {
return {
getLineCount: () => this.svc!.getLineCount(),
getLineText: line => this.svc!.getAbsolutePositionAndLineText(line + 1).lineText!
};
}
const lineMap = this.getLineMap();
return getLineInfo(this.text!, lineMap);
}
}
/*@internal*/
@ -259,6 +281,12 @@ namespace ts.server {
sourceFile: SourceFile;
}
/*@internal*/
export interface SourceMapFileWatcher {
watcher: FileWatcher;
sourceInfos?: Map<true>;
}
export class ScriptInfo {
/**
* All projects that include this file
@ -284,6 +312,20 @@ namespace ts.server {
/*@internal*/
mTime?: number;
/*@internal*/
sourceFileLike?: SourceFileLike;
/*@internal*/
sourceMapFilePath?: Path | SourceMapFileWatcher | false;
// Present on sourceMapFile info
/*@internal*/
declarationInfoPath?: Path;
/*@internal*/
sourceInfos?: Map<true>;
/*@internal*/
documentPositionMapper?: DocumentPositionMapper | false;
constructor(
private readonly host: ServerHost,
readonly fileName: NormalizedPath,
@ -521,8 +563,8 @@ namespace ts.server {
}
/*@internal*/
getLineInfo(line: number): AbsolutePositionAndLineText {
return this.textStorage.getLineInfo(line);
getAbsolutePositionAndLineText(line: number): AbsolutePositionAndLineText {
return this.textStorage.getAbsolutePositionAndLineText(line);
}
editContent(start: number, end: number, newText: string): void {
@ -551,8 +593,12 @@ namespace ts.server {
* @param line 1 based index
* @param offset 1 based index
*/
lineOffsetToPosition(line: number, offset: number): number {
return this.textStorage.lineOffsetToPosition(line, offset);
lineOffsetToPosition(line: number, offset: number): number;
/*@internal*/
// tslint:disable-next-line:unified-signatures
lineOffsetToPosition(line: number, offset: number, allowEdits?: true): number;
lineOffsetToPosition(line: number, offset: number, allowEdits?: true): number {
return this.textStorage.lineOffsetToPosition(line, offset, allowEdits);
}
positionToLineOffset(position: number): protocol.Location {
@ -562,5 +608,18 @@ namespace ts.server {
public isJavaScript() {
return this.scriptKind === ScriptKind.JS || this.scriptKind === ScriptKind.JSX;
}
/*@internal*/
getLineInfo(): LineInfo {
return this.textStorage.getLineInfo();
}
/*@internal*/
closeSourceMapFileWatcher() {
if (this.sourceMapFilePath && !isString(this.sourceMapFilePath)) {
closeFileWatcherOf(this.sourceMapFilePath);
this.sourceMapFilePath = undefined;
}
}
}
}

View File

@ -308,8 +308,8 @@ namespace ts.server {
return this._getSnapshot().version;
}
getLineInfo(line: number): AbsolutePositionAndLineText {
return this._getSnapshot().index.lineNumberToInfo(line);
getAbsolutePositionAndLineText(oneBasedLine: number): AbsolutePositionAndLineText {
return this._getSnapshot().index.lineNumberToInfo(oneBasedLine);
}
lineOffsetToPosition(line: number, column: number): number {
@ -348,6 +348,10 @@ namespace ts.server {
}
}
getLineCount() {
return this._getSnapshot().index.getLineCount();
}
static fromString(script: string) {
const svc = new ScriptVersionCache();
const snap = new LineIndexSnapshot(0, svc, new LineIndex());
@ -400,8 +404,12 @@ namespace ts.server {
return this.root.charOffsetToLineInfo(1, position);
}
getLineCount() {
return this.root.lineCount();
}
lineNumberToInfo(oneBasedLine: number): AbsolutePositionAndLineText {
const lineCount = this.root.lineCount();
const lineCount = this.getLineCount();
if (oneBasedLine <= lineCount) {
const { position, leaf } = this.root.lineNumberToInfo(oneBasedLine, 0);
return { absolutePosition: position, lineText: leaf && leaf.text };

View File

@ -1474,7 +1474,7 @@ namespace ts.server {
// only to the previous line. If all this is true, then
// add edits necessary to properly indent the current line.
if ((args.key === "\n") && ((!edits) || (edits.length === 0) || allEditsBeforePos(edits, position))) {
const { lineText, absolutePosition } = scriptInfo.getLineInfo(args.line);
const { lineText, absolutePosition } = scriptInfo.getAbsolutePositionAndLineText(args.line);
if (lineText && lineText.search("\\S") < 0) {
const preferredIndent = languageService.getIndentationAtPosition(file, position, formatOptions);
let hasIndent = 0;

View File

@ -97,7 +97,7 @@ namespace ts.codefix {
optional: boolean,
body: Block | undefined,
): MethodDeclaration | undefined {
const signatureDeclaration = <MethodDeclaration>checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration, NodeBuilderFlags.SuppressAnyReturnType);
const signatureDeclaration = <MethodDeclaration>checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration, NodeBuilderFlags.NoTruncation | NodeBuilderFlags.SuppressAnyReturnType);
if (!signatureDeclaration) {
return undefined;
}

View File

@ -913,7 +913,7 @@ namespace ts.Completions {
}
else {
for (const symbol of type.getApparentProperties()) {
if (typeChecker.isValidPropertyAccessForCompletions(node.kind === SyntaxKind.ImportType ? <ImportTypeNode>node : <PropertyAccessExpression>node.parent, type, symbol)) {
if (typeChecker.isValidPropertyAccessForCompletions(node.kind === SyntaxKind.ImportType ? <ImportTypeNode>node : <PropertyAccessExpression | QualifiedName>node.parent, type, symbol)) {
addPropertySymbol(symbol);
}
}

View File

@ -660,7 +660,7 @@ namespace ts.NavigationBar {
else if (isCallExpression(parent)) {
const name = getCalledExpressionName(parent.expression);
if (name !== undefined) {
const args = mapDefined(parent.arguments, a => isStringLiteral(a) ? a.getText(curSourceFile) : undefined).join(", ");
const args = mapDefined(parent.arguments, a => isStringLiteralLike(a) ? a.getText(curSourceFile) : undefined).join(", ");
return `${name}(${args}) callback`;
}
}

View File

@ -28,12 +28,12 @@ namespace ts.OrganizeImports {
organizeImportsWorker(topLevelExportDecls, coalesceExports);
for (const ambientModule of sourceFile.statements.filter(isAmbientModule)) {
const ambientModuleBody = getModuleBlock(ambientModule as ModuleDeclaration)!; // TODO: GH#18217
if (!ambientModule.body) { continue; }
const ambientModuleImportDecls = ambientModuleBody.statements.filter(isImportDeclaration);
const ambientModuleImportDecls = ambientModule.body.statements.filter(isImportDeclaration);
organizeImportsWorker(ambientModuleImportDecls, coalesceAndOrganizeImports);
const ambientModuleExportDecls = ambientModuleBody.statements.filter(isExportDeclaration);
const ambientModuleExportDecls = ambientModule.body.statements.filter(isExportDeclaration);
organizeImportsWorker(ambientModuleExportDecls, coalesceExports);
}
@ -81,11 +81,6 @@ namespace ts.OrganizeImports {
}
}
function getModuleBlock(moduleDecl: ModuleDeclaration): ModuleBlock | undefined {
const body = moduleDecl.body;
return body && !isIdentifier(body) ? (isModuleBlock(body) ? body : getModuleBlock(body)) : undefined;
}
function removeUnusedImports(oldImports: ReadonlyArray<ImportDeclaration>, sourceFile: SourceFile, program: Program) {
const typeChecker = program.getTypeChecker();
const jsxNamespace = typeChecker.getJsxNamespace();

View File

@ -37,6 +37,10 @@ namespace ts.OutliningElementsCollector {
addOutliningForLeadingCommentsForNode(n, sourceFile, cancellationToken, out);
}
if (isFunctionExpressionAssignedToVariable(n)) {
addOutliningForLeadingCommentsForNode(n.parent.parent.parent, sourceFile, cancellationToken, out);
}
const span = getOutliningSpanForNode(n, sourceFile);
if (span) out.push(span);
@ -54,6 +58,14 @@ namespace ts.OutliningElementsCollector {
}
depthRemaining++;
}
function isFunctionExpressionAssignedToVariable(n: Node) {
if (!isFunctionExpression(n) && !isArrowFunction(n)) {
return false;
}
const ancestor = findAncestor(n, isVariableStatement);
return !!ancestor && getSingleInitializerOfVariableStatementOrPropertyDeclaration(ancestor) === n;
}
}
function addRegionOutliningSpans(sourceFile: SourceFile, out: Push<OutliningSpan>): void {
@ -175,6 +187,7 @@ namespace ts.OutliningElementsCollector {
case SyntaxKind.ModuleBlock:
return spanForNode(n.parent);
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ClassExpression:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.CaseBlock:
@ -206,10 +219,10 @@ namespace ts.OutliningElementsCollector {
}
function spanForObjectOrArrayLiteral(node: Node, open: SyntaxKind.OpenBraceToken | SyntaxKind.OpenBracketToken = SyntaxKind.OpenBraceToken): OutliningSpan | undefined {
// If the block has no leading keywords and is inside an array literal,
// If the block has no leading keywords and is inside an array literal or call expression,
// we only want to collapse the span of the block.
// Otherwise, the collapsed section will include the end of the previous line.
return spanForNode(node, /*autoCollapse*/ false, /*useFullStart*/ !isArrayLiteralExpression(node.parent), open);
return spanForNode(node, /*autoCollapse*/ false, /*useFullStart*/ !isArrayLiteralExpression(node.parent) && !isCallExpression(node.parent), open);
}
function spanForNode(hintSpanNode: Node, autoCollapse = false, useFullStart = true, open: SyntaxKind.OpenBraceToken | SyntaxKind.OpenBracketToken = SyntaxKind.OpenBraceToken): OutliningSpan | undefined {

View File

@ -449,8 +449,7 @@ namespace ts.refactor.extractSymbol {
case SyntaxKind.ThisKeyword:
rangeFacts |= RangeFacts.UsesThis;
break;
case SyntaxKind.LabeledStatement:
{
case SyntaxKind.LabeledStatement: {
const label = (<LabeledStatement>node).label;
(seenLabels || (seenLabels = [])).push(label.escapedText);
forEachChild(node, visit);
@ -458,8 +457,7 @@ namespace ts.refactor.extractSymbol {
break;
}
case SyntaxKind.BreakStatement:
case SyntaxKind.ContinueStatement:
{
case SyntaxKind.ContinueStatement: {
const label = (<BreakStatement | ContinueStatement>node).label;
if (label) {
if (!contains(seenLabels, label.escapedText)) {

View File

@ -602,8 +602,8 @@ namespace ts {
return getLineStarts(this);
}
public getPositionOfLineAndCharacter(line: number, character: number): number {
return getPositionOfLineAndCharacter(this, line, character);
public getPositionOfLineAndCharacter(line: number, character: number, allowEdits?: true): number {
return computePositionOfLineAndCharacter(getLineStarts(this), line, character, this.text, allowEdits);
}
public getLineEndOfPosition(pos: number): number {
@ -1139,7 +1139,16 @@ namespace ts {
const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host);
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
const sourceMapper = getSourceMapper(useCaseSensitiveFileNames, currentDirectory, log, host, () => program);
const sourceMapper = getSourceMapper({
useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
getCurrentDirectory: () => currentDirectory,
getProgram,
fileExists: host.fileExists && (f => host.fileExists!(f)),
readFile: host.readFile && ((f, encoding) => host.readFile!(f, encoding)),
getDocumentPositionMapper: host.getDocumentPositionMapper && ((generatedFileName, sourceFileName) => host.getDocumentPositionMapper!(generatedFileName, sourceFileName)),
getSourceFileLike: host.getSourceFileLike && (f => host.getSourceFileLike!(f)),
log
});
function getValidSourceFile(fileName: string): SourceFile {
const sourceFile = program.getSourceFile(fileName);
@ -1203,15 +1212,7 @@ namespace ts {
writeFile: noop,
getCurrentDirectory: () => currentDirectory,
fileExists,
readFile(fileName) {
// stub missing host functionality
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
const entry = hostCache && hostCache.getEntryByPath(path);
if (entry) {
return isString(entry) ? undefined : getSnapshotText(entry.scriptSnapshot);
}
return host.readFile && host.readFile(fileName);
},
readFile,
realpath: host.realpath && (path => host.realpath!(path)),
directoryExists: directoryName => {
return directoryProbablyExists(directoryName, host);
@ -1272,6 +1273,16 @@ namespace ts {
(!!host.fileExists && host.fileExists(fileName));
}
function readFile(fileName: string) {
// stub missing host functionality
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
const entry = hostCache && hostCache.getEntryByPath(path);
if (entry) {
return isString(entry) ? undefined : getSnapshotText(entry.scriptSnapshot);
}
return host.readFile && host.readFile(fileName);
}
// Release any files we have acquired in the old program but are
// not part of the new program.
function onReleaseOldSourceFile(oldSourceFile: SourceFile, oldOptions: CompilerOptions) {

View File

@ -9,151 +9,182 @@ namespace ts {
clearCache(): void;
}
export function getSourceMapper(
useCaseSensitiveFileNames: boolean,
currentDirectory: string,
log: (message: string) => void,
host: LanguageServiceHost,
getProgram: () => Program,
): SourceMapper {
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
let sourcemappedFileCache: SourceFileLikeCache;
export interface SourceMapperHost {
useCaseSensitiveFileNames(): boolean;
getCurrentDirectory(): string;
getProgram(): Program | undefined;
fileExists?(path: string): boolean;
readFile?(path: string, encoding?: string): string | undefined;
getSourceFileLike?(fileName: string): SourceFileLike | undefined;
getDocumentPositionMapper?(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined;
log(s: string): void;
}
export function getSourceMapper(host: SourceMapperHost): SourceMapper {
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
const currentDirectory = host.getCurrentDirectory();
const sourceFileLike = createMap<SourceFileLike | false>();
const documentPositionMappers = createMap<DocumentPositionMapper>();
return { tryGetSourcePosition, tryGetGeneratedPosition, toLineColumnOffset, clearCache };
function toPath(fileName: string) {
return ts.toPath(fileName, currentDirectory, getCanonicalFileName);
}
function scanForSourcemapURL(fileName: string) {
const mappedFile = sourcemappedFileCache.get(toPath(fileName));
if (!mappedFile) {
return;
}
function getDocumentPositionMapper(generatedFileName: string, sourceFileName?: string) {
const path = toPath(generatedFileName);
const value = documentPositionMappers.get(path);
if (value) return value;
return tryGetSourceMappingURL(mappedFile.text, getLineStarts(mappedFile));
}
function convertDocumentToSourceMapper(file: { sourceMapper?: DocumentPositionMapper }, contents: string, mapFileName: string) {
const map = tryParseRawSourceMap(contents);
if (!map || !map.sources || !map.file || !map.mappings) {
// obviously invalid map
return file.sourceMapper = identitySourceMapConsumer;
let mapper: DocumentPositionMapper | undefined;
if (host.getDocumentPositionMapper) {
mapper = host.getDocumentPositionMapper(generatedFileName, sourceFileName);
}
const program = getProgram();
return file.sourceMapper = createDocumentPositionMapper({
getSourceFileLike: s => {
// Lookup file in program, if provided
const file = program && program.getSourceFileByPath(s);
// file returned here could be .d.ts when asked for .ts file if projectReferences and module resolution created this source file
if (file === undefined || file.resolvedPath !== s) {
// Otherwise check the cache (which may hit disk)
return sourcemappedFileCache.get(s);
}
return file;
},
getCanonicalFileName,
log,
}, map, mapFileName);
}
function getSourceMapper(fileName: string, file: SourceFileLike): DocumentPositionMapper {
if (!host.readFile || !host.fileExists) {
return file.sourceMapper = identitySourceMapConsumer;
else if (host.readFile) {
const file = getSourceFileLike(generatedFileName);
mapper = file && ts.getDocumentPositionMapper(
{ getSourceFileLike, getCanonicalFileName, log: s => host.log(s) },
generatedFileName,
getLineInfo(file.text, getLineStarts(file)),
f => !host.fileExists || host.fileExists(f) ? host.readFile!(f) : undefined
);
}
if (file.sourceMapper) {
return file.sourceMapper;
}
let mapFileName = scanForSourcemapURL(fileName);
if (mapFileName) {
const match = base64UrlRegExp.exec(mapFileName);
if (match) {
if (match[1]) {
const base64Object = match[1];
return convertDocumentToSourceMapper(file, base64decode(sys, base64Object), fileName);
}
// Not a data URL we can parse, skip it
mapFileName = undefined;
}
}
const possibleMapLocations: string[] = [];
if (mapFileName) {
possibleMapLocations.push(mapFileName);
}
possibleMapLocations.push(fileName + ".map");
for (const location of possibleMapLocations) {
const mapPath = ts.toPath(location, getDirectoryPath(fileName), getCanonicalFileName);
if (host.fileExists(mapPath)) {
return convertDocumentToSourceMapper(file, host.readFile(mapPath)!, mapPath); // TODO: GH#18217
}
}
return file.sourceMapper = identitySourceMapConsumer;
documentPositionMappers.set(path, mapper || identitySourceMapConsumer);
return mapper || identitySourceMapConsumer;
}
function tryGetSourcePosition(info: DocumentPosition): DocumentPosition | undefined {
if (!isDeclarationFileName(info.fileName)) return undefined;
const file = getFile(info.fileName);
const file = getSourceFile(info.fileName);
if (!file) return undefined;
const newLoc = getSourceMapper(info.fileName, file).getSourcePosition(info);
return newLoc === info ? undefined : tryGetSourcePosition(newLoc) || newLoc;
const newLoc = getDocumentPositionMapper(info.fileName).getSourcePosition(info);
return !newLoc || newLoc === info ? undefined : tryGetSourcePosition(newLoc) || newLoc;
}
function tryGetGeneratedPosition(info: DocumentPosition): DocumentPosition | undefined {
const program = getProgram();
if (isDeclarationFileName(info.fileName)) return undefined;
const sourceFile = getSourceFile(info.fileName);
if (!sourceFile) return undefined;
const program = host.getProgram()!;
const options = program.getCompilerOptions();
const outPath = options.outFile || options.out;
const declarationPath = outPath ?
removeFileExtension(outPath) + Extension.Dts :
getDeclarationEmitOutputFilePathWorker(info.fileName, program.getCompilerOptions(), currentDirectory, program.getCommonSourceDirectory(), getCanonicalFileName);
if (declarationPath === undefined) return undefined;
const declarationFile = getFile(declarationPath);
if (!declarationFile) return undefined;
const newLoc = getSourceMapper(declarationPath, declarationFile).getGeneratedPosition(info);
const newLoc = getDocumentPositionMapper(declarationPath, info.fileName).getGeneratedPosition(info);
return newLoc === info ? undefined : newLoc;
}
function getFile(fileName: string): SourceFileLike | undefined {
function getSourceFile(fileName: string) {
const program = host.getProgram();
if (!program) return undefined;
const path = toPath(fileName);
const file = getProgram().getSourceFileByPath(path);
if (file && file.resolvedPath === path) {
return file;
// file returned here could be .d.ts when asked for .ts file if projectReferences and module resolution created this source file
const file = program.getSourceFileByPath(path);
return file && file.resolvedPath === path ? file : undefined;
}
function getOrCreateSourceFileLike(fileName: string): SourceFileLike | undefined {
const path = toPath(fileName);
const fileFromCache = sourceFileLike.get(path);
if (fileFromCache !== undefined) return fileFromCache ? fileFromCache : undefined;
if (!host.readFile || host.fileExists && !host.fileExists(path)) {
sourceFileLike.set(path, false);
return undefined;
}
return sourcemappedFileCache.get(path);
// And failing that, check the disk
const text = host.readFile(path);
const file = text ? createSourceFileLike(text) : false;
sourceFileLike.set(path, file);
return file ? file : undefined;
}
// This can be called from source mapper in either source program or program that includes generated file
function getSourceFileLike(fileName: string) {
return !host.getSourceFileLike ?
getSourceFile(fileName) || getOrCreateSourceFileLike(fileName) :
host.getSourceFileLike(fileName);
}
function toLineColumnOffset(fileName: string, position: number): LineAndCharacter {
const file = getFile(fileName)!; // TODO: GH#18217
const file = getSourceFileLike(fileName)!; // TODO: GH#18217
return file.getLineAndCharacterOfPosition(position);
}
function clearCache(): void {
sourcemappedFileCache = createSourceFileLikeCache(host);
sourceFileLike.clear();
documentPositionMappers.clear();
}
}
interface SourceFileLikeCache {
get(path: Path): SourceFileLike | undefined;
/**
* string | undefined to contents of map file to create DocumentPositionMapper from it
* DocumentPositionMapper | false to give back cached DocumentPositionMapper
*/
export type ReadMapFile = (mapFileName: string, mapFileNameFromDts: string | undefined) => string | undefined | DocumentPositionMapper | false;
export function getDocumentPositionMapper(
host: DocumentPositionMapperHost,
generatedFileName: string,
generatedFileLineInfo: LineInfo,
readMapFile: ReadMapFile) {
let mapFileName = tryGetSourceMappingURL(generatedFileLineInfo);
if (mapFileName) {
const match = base64UrlRegExp.exec(mapFileName);
if (match) {
if (match[1]) {
const base64Object = match[1];
return convertDocumentToSourceMapper(host, base64decode(sys, base64Object), generatedFileName);
}
// Not a data URL we can parse, skip it
mapFileName = undefined;
}
}
const possibleMapLocations: string[] = [];
if (mapFileName) {
possibleMapLocations.push(mapFileName);
}
possibleMapLocations.push(generatedFileName + ".map");
const originalMapFileName = mapFileName && getNormalizedAbsolutePath(mapFileName, getDirectoryPath(generatedFileName));
for (const location of possibleMapLocations) {
const mapFileName = getNormalizedAbsolutePath(location, getDirectoryPath(generatedFileName));
const mapFileContents = readMapFile(mapFileName, originalMapFileName);
if (isString(mapFileContents)) {
return convertDocumentToSourceMapper(host, mapFileContents, mapFileName);
}
if (mapFileContents !== undefined) {
return mapFileContents || undefined;
}
}
return undefined;
}
function createSourceFileLikeCache(host: { readFile?: (path: string) => string | undefined, fileExists?: (path: string) => boolean }): SourceFileLikeCache {
const cached = createMap<SourceFileLike>();
function convertDocumentToSourceMapper(host: DocumentPositionMapperHost, contents: string, mapFileName: string) {
const map = tryParseRawSourceMap(contents);
if (!map || !map.sources || !map.file || !map.mappings) {
// obviously invalid map
return undefined;
}
return createDocumentPositionMapper(host, map, mapFileName);
}
function createSourceFileLike(text: string, lineMap?: SourceFileLike["lineMap"]): SourceFileLike {
return {
get(path: Path) {
if (cached.has(path)) {
return cached.get(path);
}
if (!host.fileExists || !host.readFile || !host.fileExists(path)) return;
// And failing that, check the disk
const text = host.readFile(path)!; // TODO: GH#18217
const file = {
text,
lineMap: undefined,
getLineAndCharacterOfPosition(pos: number) {
return computeLineAndCharacterOfPosition(getLineStarts(this), pos);
}
} as SourceFileLike;
cached.set(path, file);
return file;
text,
lineMap,
getLineAndCharacterOfPosition(pos: number) {
return computeLineAndCharacterOfPosition(getLineStarts(this), pos);
}
};
}

View File

@ -1,5 +1,7 @@
/* @internal */
namespace ts {
const visitedNestedConvertibleFunctions = createMap<true>();
export function computeSuggestionDiagnostics(sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): DiagnosticWithLocation[] {
program.getSemanticDiagnostics(sourceFile, cancellationToken);
const diags: DiagnosticWithLocation[] = [];
@ -13,6 +15,7 @@ namespace ts {
const isJsFile = isSourceFileJS(sourceFile);
visitedNestedConvertibleFunctions.clear();
check(sourceFile);
if (getAllowSyntheticDefaultImports(program.getCompilerOptions())) {
@ -114,17 +117,22 @@ namespace ts {
}
function addConvertToAsyncFunctionDiagnostics(node: FunctionLikeDeclaration, checker: TypeChecker, diags: Push<DiagnosticWithLocation>): void {
if (!isAsyncFunction(node) &&
node.body &&
isBlock(node.body) &&
hasReturnStatementWithPromiseHandler(node.body) &&
returnsPromise(node, checker)) {
// need to check function before checking map so that deeper levels of nested callbacks are checked
if (isConvertibleFunction(node, checker) && !visitedNestedConvertibleFunctions.has(getKeyFromNode(node))) {
diags.push(createDiagnosticForNode(
!node.name && isVariableDeclaration(node.parent) && isIdentifier(node.parent.name) ? node.parent.name : node,
Diagnostics.This_may_be_converted_to_an_async_function));
}
}
function isConvertibleFunction(node: FunctionLikeDeclaration, checker: TypeChecker) {
return !isAsyncFunction(node) &&
node.body &&
isBlock(node.body) &&
hasReturnStatementWithPromiseHandler(node.body) &&
returnsPromise(node, checker);
}
function returnsPromise(node: FunctionLikeDeclaration, checker: TypeChecker): boolean {
const functionType = checker.getTypeAtLocation(node);
const callSignatures = checker.getSignaturesOfType(functionType, SignatureKind.Call);
@ -169,14 +177,20 @@ namespace ts {
// should be kept up to date with getTransformationBody in convertToAsyncFunction.ts
function isFixablePromiseArgument(arg: Expression): boolean {
switch (arg.kind) {
case SyntaxKind.NullKeyword:
case SyntaxKind.Identifier: // identifier includes undefined
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
visitedNestedConvertibleFunctions.set(getKeyFromNode(arg as FunctionLikeDeclaration), true);
/* falls through */
case SyntaxKind.NullKeyword:
case SyntaxKind.Identifier: // identifier includes undefined
return true;
default:
return false;
}
}
function getKeyFromNode(exp: FunctionLikeDeclaration) {
return `${exp.pos.toString()}:${exp.end.toString()}`;
}
}

View File

@ -91,7 +91,6 @@ namespace ts {
export interface SourceFileLike {
getLineAndCharacterOfPosition(pos: number): LineAndCharacter;
/*@internal*/ sourceMapper?: DocumentPositionMapper;
}
export interface SourceMapSource {
@ -233,6 +232,11 @@ namespace ts {
installPackage?(options: InstallPackageOptions): Promise<ApplyCodeActionCommandResult>;
/* @internal */ inspectValue?(options: InspectValueOptions): Promise<ValueInfo>;
writeFile?(fileName: string, content: string): void;
/* @internal */
getDocumentPositionMapper?(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined;
/* @internal */
getSourceFileLike?(fileName: string): SourceFileLike | undefined;
}
/* @internal */

View File

@ -1282,11 +1282,11 @@ namespace ts {
return !!compilerOptions.module || compilerOptions.target! >= ScriptTarget.ES2015 || !!compilerOptions.noEmit;
}
export function hostUsesCaseSensitiveFileNames(host: LanguageServiceHost): boolean {
export function hostUsesCaseSensitiveFileNames(host: { useCaseSensitiveFileNames?(): boolean; }): boolean {
return host.useCaseSensitiveFileNames ? host.useCaseSensitiveFileNames() : false;
}
export function hostGetCanonicalFileName(host: LanguageServiceHost): GetCanonicalFileName {
export function hostGetCanonicalFileName(host: { useCaseSensitiveFileNames?(): boolean; }): GetCanonicalFileName {
return createGetCanonicalFileName(hostUsesCaseSensitiveFileNames(host));
}

View File

@ -36,64 +36,109 @@
"runner.ts",
"unittests/extractTestHelpers.ts",
"unittests/tsserverProjectSystem.ts",
"unittests/typingsInstaller.ts",
"unittests/services/extract/helpers.ts",
"unittests/tscWatch/helpers.ts",
"unittests/tsserver/helpers.ts",
"unittests/asserts.ts",
"unittests/base64.ts",
"unittests/builder.ts",
"unittests/cancellableLanguageServiceOperations.ts",
"unittests/commandLineParsing.ts",
"unittests/compileOnSave.ts",
"unittests/compilerCore.ts",
"unittests/configurationExtension.ts",
"unittests/convertCompilerOptionsFromJson.ts",
"unittests/convertToAsyncFunction.ts",
"unittests/convertToBase64.ts",
"unittests/convertTypeAcquisitionFromJson.ts",
"unittests/customTransforms.ts",
"unittests/extractConstants.ts",
"unittests/extractFunctions.ts",
"unittests/extractRanges.ts",
"unittests/factory.ts",
"unittests/hostNewLineSupport.ts",
"unittests/incrementalParser.ts",
"unittests/initializeTSConfig.ts",
"unittests/jsDocParsing.ts",
"unittests/languageService.ts",
"unittests/matchFiles.ts",
"unittests/moduleResolution.ts",
"unittests/organizeImports.ts",
"unittests/parsePseudoBigInt.ts",
"unittests/paths.ts",
"unittests/printer.ts",
"unittests/programMissingFiles.ts",
"unittests/programNoParseFalsyFileNames.ts",
"unittests/projectErrors.ts",
"unittests/projectReferences.ts",
"unittests/programApi.ts",
"unittests/publicApi.ts",
"unittests/reuseProgramStructure.ts",
"unittests/session.ts",
"unittests/semver.ts",
"unittests/showConfig.ts",
"unittests/symbolWalker.ts",
"unittests/telemetry.ts",
"unittests/textChanges.ts",
"unittests/textStorage.ts",
"unittests/transform.ts",
"unittests/transpile.ts",
"unittests/tsbuild.ts",
"unittests/tsbuildWatchMode.ts",
"unittests/tsconfigParsing.ts",
"unittests/tscWatchMode.ts",
"unittests/versionCache.ts",
"unittests/config/commandLineParsing.ts",
"unittests/config/configurationExtension.ts",
"unittests/config/convertCompilerOptionsFromJson.ts",
"unittests/config/convertTypeAcquisitionFromJson.ts",
"unittests/config/initializeTSConfig.ts",
"unittests/config/matchFiles.ts",
"unittests/config/projectReferences.ts",
"unittests/config/showConfig.ts",
"unittests/config/tsconfigParsing.ts",
"unittests/evaluation/asyncArrow.ts",
"unittests/evaluation/asyncGenerator.ts",
"unittests/evaluation/forAwaitOf.ts",
"unittests/services/cancellableLanguageServiceOperations.ts",
"unittests/services/colorization.ts",
"unittests/services/convertToAsyncFunction.ts",
"unittests/services/documentRegistry.ts",
"unittests/services/extract/constants.ts",
"unittests/services/extract/functions.ts",
"unittests/services/extract/symbolWalker.ts",
"unittests/services/extract/ranges.ts",
"unittests/services/hostNewLineSupport.ts",
"unittests/services/languageService.ts",
"unittests/services/organizeImports.ts",
"unittests/services/patternMatcher.ts",
"unittests/services/preProcessFile.ts"
"unittests/services/preProcessFile.ts",
"unittests/services/textChanges.ts",
"unittests/services/transpile.ts",
"unittests/tscWatch/consoleClearing.ts",
"unittests/tscWatch/emit.ts",
"unittests/tscWatch/emitAndErrorUpdates.ts",
"unittests/tscWatch/programUpdates.ts",
"unittests/tscWatch/resolutionCache.ts",
"unittests/tscWatch/watchEnvironment.ts",
"unittests/tscWatch/watchApi.ts",
"unittests/tsserver/cachingFileSystemInformation.ts",
"unittests/tsserver/cancellationToken.ts",
"unittests/tsserver/compileOnSave.ts",
"unittests/tsserver/completions.ts",
"unittests/tsserver/configFileSearch.ts",
"unittests/tsserver/configuredProjects.ts",
"unittests/tsserver/declarationFileMaps.ts",
"unittests/tsserver/documentRegistry.ts",
"unittests/tsserver/duplicatePackages.ts",
"unittests/tsserver/events/largeFileReferenced.ts",
"unittests/tsserver/events/projectLanguageServiceState.ts",
"unittests/tsserver/events/projectLoading.ts",
"unittests/tsserver/events/projectUpdatedInBackground.ts",
"unittests/tsserver/events/surveyReady.ts",
"unittests/tsserver/externalProjects.ts",
"unittests/tsserver/forceConsistentCasingInFileNames.ts",
"unittests/tsserver/formatSettings.ts",
"unittests/tsserver/getApplicableRefactors.ts",
"unittests/tsserver/getEditsForFileRename.ts",
"unittests/tsserver/importHelpers.ts",
"unittests/tsserver/inferredProjects.ts",
"unittests/tsserver/languageService.ts",
"unittests/tsserver/maxNodeModuleJsDepth.ts",
"unittests/tsserver/metadataInResponse.ts",
"unittests/tsserver/navTo.ts",
"unittests/tsserver/occurences.ts",
"unittests/tsserver/openFile.ts",
"unittests/tsserver/projectErrors.ts",
"unittests/tsserver/projectReferences.ts",
"unittests/tsserver/projects.ts",
"unittests/tsserver/refactors.ts",
"unittests/tsserver/reload.ts",
"unittests/tsserver/rename.ts",
"unittests/tsserver/resolutionCache.ts",
"unittests/tsserver/session.ts",
"unittests/tsserver/skipLibCheck.ts",
"unittests/tsserver/symLinks.ts",
"unittests/tsserver/syntaxOperations.ts",
"unittests/tsserver/textStorage.ts",
"unittests/tsserver/telemetry.ts",
"unittests/tsserver/typeAquisition.ts",
"unittests/tsserver/typeReferenceDirectives.ts",
"unittests/tsserver/typingsInstaller.ts",
"unittests/tsserver/untitledFiles.ts",
"unittests/tsserver/versionCache.ts",
"unittests/tsserver/watchEnvironment.ts"
]
}

View File

@ -1,5 +1,5 @@
namespace ts {
describe("assert", () => {
describe("unittests:: assert", () => {
it("deepEqual", () => {
assert.throws(() => assert.deepEqual(createNodeArray([createIdentifier("A")]), createNodeArray([createIdentifier("B")])));
assert.throws(() => assert.deepEqual(createNodeArray([], /*hasTrailingComma*/ true), createNodeArray([], /*hasTrailingComma*/ false)));

View File

@ -1,5 +1,5 @@
namespace ts {
describe("base64", () => {
describe("unittests:: base64", () => {
describe("base64decode", () => {
it("can decode input strings correctly without needing a host implementation", () => {
const tests = [

View File

@ -1,5 +1,5 @@
namespace ts {
describe("builder", () => {
describe("unittests:: builder", () => {
it("emits dependent files", () => {
const files: NamedSourceText[] = [
{ name: "/a.ts", text: SourceText.New("", 'import { b } from "./b";', "") },

View File

@ -1,5 +1,5 @@
namespace ts {
describe("compilerCore", () => {
describe("unittests:: compilerCore", () => {
describe("equalOwnProperties", () => {
it("correctly equates objects", () => {
assert.isTrue(equalOwnProperties({}, {}));

View File

@ -1,5 +1,5 @@
namespace ts {
describe("parseCommandLine", () => {
describe("unittests:: config:: commandLineParsing:: parseCommandLine", () => {
function assertParseResult(commandLine: string[], expectedParsedCommandLine: ParsedCommandLine) {
const parsed = parseCommandLine(commandLine);
@ -367,7 +367,7 @@ namespace ts {
});
});
describe("parseBuildOptions", () => {
describe("unittests:: config:: commandLineParsing:: parseBuildOptions", () => {
function assertParseResult(commandLine: string[], expectedParsedBuildCommand: ParsedBuildCommand) {
const parsed = parseBuildCommand(commandLine);
const parsedBuildOptions = JSON.stringify(parsed.buildOptions);

View File

@ -208,7 +208,7 @@ namespace ts {
}
}
describe("configurationExtension", () => {
describe("unittests:: config:: configurationExtension", () => {
forEach<[string, string, fakes.ParseConfigHost], void>([
["under a case insensitive host", caseInsensitiveBasePath, caseInsensitiveHost],
["under a case sensitive host", caseSensitiveBasePath, caseSensitiveHost]

View File

@ -1,5 +1,5 @@
namespace ts {
describe("convertCompilerOptionsFromJson", () => {
describe("unittests:: config:: convertCompilerOptionsFromJson", () => {
const formatDiagnosticHost: FormatDiagnosticsHost = {
getCurrentDirectory: () => "/apath/",
getCanonicalFileName: createGetCanonicalFileName(/*useCaseSensitiveFileNames*/ true),

View File

@ -1,6 +1,6 @@
namespace ts {
interface ExpectedResult { typeAcquisition: TypeAcquisition; errors: Diagnostic[]; }
describe("convertTypeAcquisitionFromJson", () => {
describe("unittests:: config:: convertTypeAcquisitionFromJson", () => {
function assertTypeAcquisition(json: any, configFileName: string, expectedResult: ExpectedResult) {
assertTypeAcquisitionWithJson(json, configFileName, expectedResult);
assertTypeAcquisitionWithJsonNode(json, configFileName, expectedResult);

View File

@ -1,5 +1,5 @@
namespace ts {
describe("initTSConfig", () => {
describe("unittests:: config:: initTSConfig", () => {
function initTSConfigCorrectly(name: string, commandLinesArgs: string[]) {
describe(name, () => {
const commandLine = parseCommandLine(commandLinesArgs);
@ -30,4 +30,4 @@ namespace ts {
initTSConfigCorrectly("Initialized TSConfig with advanced options", ["--init", "--declaration", "--declarationDir", "lib", "--skipLibCheck", "--noErrorTruncation"]);
});
}
}

View File

@ -143,7 +143,7 @@ namespace ts {
return createFileDiagnostic(file, start, length, diagnosticMessage, arg0);
}
describe("matchFiles", () => {
describe("unittests:: config:: matchFiles", () => {
it("with defaults", () => {
const json = {};
const expected: ParsedCommandLine = {

View File

@ -96,7 +96,7 @@ namespace ts {
checkResult(prog, host);
}
describe("project-references meta check", () => {
describe("unittests:: config:: project-references meta check", () => {
it("default setup was created correctly", () => {
const spec: TestSpecification = {
"/primary": {
@ -118,7 +118,7 @@ namespace ts {
/**
* Validate that we enforce the basic settings constraints for referenced projects
*/
describe("project-references constraint checking for settings", () => {
describe("unittests:: config:: project-references constraint checking for settings", () => {
it("errors when declaration = false", () => {
const spec: TestSpecification = {
"/primary": {
@ -248,7 +248,7 @@ namespace ts {
/**
* Path mapping behavior
*/
describe("project-references path mapping", () => {
describe("unittests:: config:: project-references path mapping", () => {
it("redirects to the output .d.ts file", () => {
const spec: TestSpecification = {
"/alpha": {
@ -268,7 +268,7 @@ namespace ts {
});
});
describe("project-references nice-behavior", () => {
describe("unittests:: config:: project-references nice-behavior", () => {
it("issues a nice error when the input file is missing", () => {
const spec: TestSpecification = {
"/alpha": {
@ -289,7 +289,7 @@ namespace ts {
/**
* 'composite' behavior
*/
describe("project-references behavior changes under composite: true", () => {
describe("unittests:: config:: project-references behavior changes under composite: true", () => {
it("doesn't infer the rootDir from source paths", () => {
const spec: TestSpecification = {
"/alpha": {
@ -308,7 +308,7 @@ namespace ts {
});
});
describe("errors when a file in a composite project occurs outside the root", () => {
describe("unittests:: config:: project-references errors when a file in a composite project occurs outside the root", () => {
it("Errors when a file is outside the rootdir", () => {
const spec: TestSpecification = {
"/alpha": {

View File

@ -1,5 +1,5 @@
namespace ts {
describe("showConfig", () => {
describe("unittests:: config:: showConfig", () => {
function showTSConfigCorrectly(name: string, commandLinesArgs: string[], configJson?: object) {
describe(name, () => {
const outputFileName = `showConfig/${name.replace(/[^a-z0-9\-./ ]/ig, "")}/tsconfig.json`;

View File

@ -1,5 +1,5 @@
namespace ts {
describe("parseConfigFileTextToJson", () => {
describe("unittests:: config:: tsconfigParsing:: parseConfigFileTextToJson", () => {
function assertParseResult(jsonText: string, expectedConfigObject: { config?: any; error?: Diagnostic[] }) {
const parsed = parseConfigFileTextToJson("/apath/tsconfig.json", jsonText);
assert.equal(JSON.stringify(parsed), JSON.stringify(expectedConfigObject));

View File

@ -1,5 +1,5 @@
namespace ts {
describe("convertToBase64", () => {
describe("unittests:: convertToBase64", () => {
function runTest(input: string): void {
const actual = convertToBase64(input);
const expected = sys.base64encode!(input);

View File

@ -1,5 +1,5 @@
namespace ts {
describe("customTransforms", () => {
describe("unittests:: customTransforms", () => {
function emitsCorrectly(name: string, sources: { file: string, text: string }[], customTransformers: CustomTransformers, options: CompilerOptions = {}) {
it(name, () => {
const roots = sources.map(source => createSourceFile(source.file, source.text, ScriptTarget.ES2015));
@ -97,4 +97,4 @@ namespace ts {
experimentalDecorators: true
});
});
}
}

View File

@ -1,4 +1,4 @@
describe("asyncArrowEvaluation", () => {
describe("unittests:: evaluation:: asyncArrowEvaluation", () => {
// https://github.com/Microsoft/TypeScript/issues/24722
it("this capture (es5)", async () => {
const result = evaluator.evaluateTypeScript(`
@ -15,4 +15,4 @@ describe("asyncArrowEvaluation", () => {
await result.main();
assert.instanceOf(result.output[0].a(), result.A);
});
});
});

View File

@ -1,4 +1,4 @@
describe("asyncGeneratorEvaluation", () => {
describe("unittests:: evaluation:: asyncGeneratorEvaluation", () => {
it("return (es5)", async () => {
const result = evaluator.evaluateTypeScript(`
async function * g() {
@ -27,4 +27,4 @@ describe("asyncGeneratorEvaluation", () => {
{ value: 0, done: true }
]);
});
});
});

View File

@ -1,4 +1,4 @@
describe("forAwaitOfEvaluation", () => {
describe("unittests:: evaluation:: forAwaitOfEvaluation", () => {
it("sync (es5)", async () => {
const result = evaluator.evaluateTypeScript(`
let i = 0;

View File

@ -1,5 +1,5 @@
namespace ts {
describe("FactoryAPI", () => {
describe("unittests:: FactoryAPI", () => {
function assertSyntaxKind(node: Node, expected: SyntaxKind) {
assert.strictEqual(node.kind, expected, `Actual: ${Debug.showSyntaxKind(node)} Expected: ${(ts as any).SyntaxKind[expected]}`);
}

View File

@ -120,7 +120,7 @@ namespace ts {
}
}
describe("Incremental", () => {
describe("unittests:: Incremental Parser", () => {
it("Inserting into method", () => {
const source = "class C {\r\n" +
" public foo1() { }\r\n" +

View File

@ -1,5 +1,5 @@
namespace ts {
describe("JSDocParsing", () => {
describe("unittests:: JSDocParsing", () => {
describe("TypeExpressions", () => {
function parsesCorrectly(name: string, content: string) {
it(name, () => {

View File

@ -80,7 +80,7 @@ namespace ts {
}
}
describe("Node module resolution - relative paths", () => {
describe("unittests:: moduleResolution:: Node module resolution - relative paths", () => {
function testLoadAsFile(containingFileName: string, moduleFileNameNoExt: string, moduleName: string): void {
for (const ext of supportedTSExtensions) {
@ -200,7 +200,7 @@ namespace ts {
});
});
describe("Node module resolution - non-relative paths", () => {
describe("unittests:: moduleResolution:: Node module resolution - non-relative paths", () => {
it("computes correct commonPrefix for moduleName cache", () => {
const resolutionCache = createModuleResolutionCache("/", (f) => f);
let cache = resolutionCache.getOrCreateCacheForModuleName("a");
@ -457,7 +457,7 @@ namespace ts {
});
});
describe("Module resolution - relative imports", () => {
describe("unittests:: moduleResolution:: Relative imports", () => {
function test(files: Map<string>, currentDirectory: string, rootFiles: string[], expectedFilesCount: number, relativeNamesToCheck: string[]) {
const options: CompilerOptions = { module: ModuleKind.CommonJS };
const host: CompilerHost = {
@ -530,7 +530,7 @@ export = C;
});
});
describe("Files with different casing", () => {
describe("unittests:: moduleResolution:: Files with different casing", () => {
let library: SourceFile;
function test(files: Map<string>, options: CompilerOptions, currentDirectory: string, useCaseSensitiveFileNames: boolean, rootFiles: string[], diagnosticCodes: number[]): void {
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
@ -651,7 +651,7 @@ import b = require("./moduleB");
});
});
describe("baseUrl augmented module resolution", () => {
describe("unittests:: moduleResolution:: baseUrl augmented module resolution", () => {
it("module resolution without path mappings/rootDirs", () => {
test(/*hasDirectoryExists*/ false);
@ -1098,7 +1098,7 @@ import b = require("./moduleB");
});
});
describe("ModuleResolutionHost.directoryExists", () => {
describe("unittests:: moduleResolution:: ModuleResolutionHost.directoryExists", () => {
it("No 'fileExists' calls if containing directory is missing", () => {
const host: ModuleResolutionHost = {
readFile: notImplemented,
@ -1111,7 +1111,7 @@ import b = require("./moduleB");
});
});
describe("Type reference directive resolution: ", () => {
describe("unittests:: moduleResolution:: Type reference directive resolution: ", () => {
function testWorker(hasDirectoryExists: boolean, typesRoot: string | undefined, typeDirective: string, primary: boolean, initialFile: File, targetFile: File, ...otherFiles: File[]) {
const host = createModuleResolutionHost(hasDirectoryExists, ...[initialFile, targetFile].concat(...otherFiles));
const result = resolveTypeReferenceDirective(typeDirective, initialFile.name, typesRoot ? { typeRoots: [typesRoot] } : {}, host);

View File

@ -1,5 +1,5 @@
namespace ts {
describe("BigInt literal base conversions", () => {
describe("unittests:: BigInt literal base conversions", () => {
describe("parsePseudoBigInt", () => {
const testNumbers: number[] = [];
for (let i = 0; i < 1e3; i++) testNumbers.push(i);
@ -68,4 +68,4 @@ namespace ts {
});
});
});
}
}

View File

@ -1,4 +1,4 @@
describe("core paths", () => {
describe("unittests:: core paths", () => {
it("normalizeSlashes", () => {
assert.strictEqual(ts.normalizeSlashes("a"), "a");
assert.strictEqual(ts.normalizeSlashes("a/b"), "a/b");
@ -289,4 +289,4 @@ describe("core paths", () => {
assert.strictEqual(ts.getRelativePathFromDirectory("file:///a/b/c", "file:///a/b", /*ignoreCase*/ false), "..");
assert.strictEqual(ts.getRelativePathFromDirectory("file:///c:", "file:///d:", /*ignoreCase*/ false), "file:///d:/");
});
});
});

View File

@ -1,5 +1,5 @@
namespace ts {
describe("PrinterAPI", () => {
describe("unittests:: PrinterAPI", () => {
function makePrintsCorrectly(prefix: string) {
return function printsCorrectly(name: string, options: PrinterOptions, printCallback: (printer: Printer) => string) {
it(name, () => {

View File

@ -11,7 +11,7 @@ namespace ts {
assert.equal(notFound.length, 0, `Not found ${notFound} in actual: ${missingPaths} expected: ${expected}`);
}
describe("Program.getMissingFilePaths", () => {
describe("unittests:: Program.getMissingFilePaths", () => {
const options: CompilerOptions = {
noLib: true,
@ -97,9 +97,37 @@ namespace ts {
"d:/pretend/nonexistent4.tsx"
]);
});
it("should not have missing file paths", () => {
const testSource = `
class Foo extends HTMLElement {
bar: string = 'baz';
}`;
const host: CompilerHost = {
getSourceFile: (fileName: string, languageVersion: ScriptTarget, _onError?: (message: string) => void) => {
return fileName === "test.ts" ? createSourceFile(fileName, testSource, languageVersion) : undefined;
},
getDefaultLibFileName: () => "",
writeFile: (_fileName, _content) => { throw new Error("unsupported"); },
getCurrentDirectory: () => sys.getCurrentDirectory(),
getCanonicalFileName: fileName => sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(),
getNewLine: () => sys.newLine,
useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames,
fileExists: fileName => fileName === "test.ts",
readFile: fileName => fileName === "test.ts" ? testSource : undefined,
resolveModuleNames: (_moduleNames: string[], _containingFile: string) => { throw new Error("unsupported"); },
getDirectories: _path => { throw new Error("unsupported"); },
};
const program = createProgram(["test.ts"], { module: ModuleKind.ES2015 }, host);
assert(program.getSourceFiles().length === 1, "expected 'getSourceFiles' length to be 1");
assert(program.getMissingFilePaths().length === 0, "expected 'getMissingFilePaths' length to be 0");
assert(program.getFileProcessingDiagnostics().getDiagnostics().length === 0, "expected 'getFileProcessingDiagnostics' length to be 0");
});
});
describe("Program.isSourceFileFromExternalLibrary", () => {
describe("unittests:: Program.isSourceFileFromExternalLibrary", () => {
it("works on redirect files", () => {
// In this example '/node_modules/foo/index.d.ts' will redirect to '/node_modules/bar/node_modules/foo/index.d.ts'.
const a = new documents.TextDocument("/a.ts", 'import * as bar from "bar"; import * as foo from "foo";');

View File

@ -1,36 +0,0 @@
namespace ts {
describe("programNoParseFalsyFileNames", () => {
let program: Program;
beforeEach(() => {
const testSource = `
class Foo extends HTMLElement {
bar: string = 'baz';
}`;
const host: CompilerHost = {
getSourceFile: (fileName: string, languageVersion: ScriptTarget, _onError?: (message: string) => void) => {
return fileName === "test.ts" ? createSourceFile(fileName, testSource, languageVersion) : undefined;
},
getDefaultLibFileName: () => "",
writeFile: (_fileName, _content) => { throw new Error("unsupported"); },
getCurrentDirectory: () => sys.getCurrentDirectory(),
getCanonicalFileName: fileName => sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(),
getNewLine: () => sys.newLine,
useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames,
fileExists: fileName => fileName === "test.ts",
readFile: fileName => fileName === "test.ts" ? testSource : undefined,
resolveModuleNames: (_moduleNames: string[], _containingFile: string) => { throw new Error("unsupported"); },
getDirectories: _path => { throw new Error("unsupported"); },
};
program = createProgram(["test.ts"], { module: ModuleKind.ES2015 }, host);
});
it("should not have missing file paths", () => {
assert(program.getSourceFiles().length === 1, "expected 'getSourceFiles' length to be 1");
assert(program.getMissingFilePaths().length === 0, "expected 'getMissingFilePaths' length to be 0");
assert(program.getFileProcessingDiagnostics().getDiagnostics().length === 0, "expected 'getFileProcessingDiagnostics' length to be 0");
});
});
}

View File

@ -1,202 +0,0 @@
namespace ts.projectSystem {
describe("Project errors", () => {
function checkProjectErrors(projectFiles: server.ProjectFilesWithTSDiagnostics, expectedErrors: ReadonlyArray<string>): void {
assert.isTrue(projectFiles !== undefined, "missing project files");
checkProjectErrorsWorker(projectFiles.projectErrors, expectedErrors);
}
function checkProjectErrorsWorker(errors: ReadonlyArray<Diagnostic>, expectedErrors: ReadonlyArray<string>): void {
assert.equal(errors ? errors.length : 0, expectedErrors.length, `expected ${expectedErrors.length} error in the list`);
if (expectedErrors.length) {
for (let i = 0; i < errors.length; i++) {
const actualMessage = flattenDiagnosticMessageText(errors[i].messageText, "\n");
const expectedMessage = expectedErrors[i];
assert.isTrue(actualMessage.indexOf(expectedMessage) === 0, `error message does not match, expected ${actualMessage} to start with ${expectedMessage}`);
}
}
}
function checkDiagnosticsWithLinePos(errors: server.protocol.DiagnosticWithLinePosition[], expectedErrors: string[]) {
assert.equal(errors ? errors.length : 0, expectedErrors.length, `expected ${expectedErrors.length} error in the list`);
if (expectedErrors.length) {
zipWith(errors, expectedErrors, ({ message: actualMessage }, expectedMessage) => {
assert.isTrue(startsWith(actualMessage, actualMessage), `error message does not match, expected ${actualMessage} to start with ${expectedMessage}`);
});
}
}
it("external project - diagnostics for missing files", () => {
const file1 = {
path: "/a/b/app.ts",
content: ""
};
const file2 = {
path: "/a/b/applib.ts",
content: ""
};
const host = createServerHost([file1, libFile]);
const session = createSession(host);
const projectService = session.getProjectService();
const projectFileName = "/a/b/test.csproj";
const compilerOptionsRequest: server.protocol.CompilerOptionsDiagnosticsRequest = {
type: "request",
command: server.CommandNames.CompilerOptionsDiagnosticsFull,
seq: 2,
arguments: { projectFileName }
};
{
projectService.openExternalProject({
projectFileName,
options: {},
rootFiles: toExternalFiles([file1.path, file2.path])
});
checkNumberOfProjects(projectService, { externalProjects: 1 });
const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
// only file1 exists - expect error
checkDiagnosticsWithLinePos(diags, ["File '/a/b/applib.ts' not found."]);
}
host.reloadFS([file2, libFile]);
{
// only file2 exists - expect error
checkNumberOfProjects(projectService, { externalProjects: 1 });
const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
checkDiagnosticsWithLinePos(diags, ["File '/a/b/app.ts' not found."]);
}
host.reloadFS([file1, file2, libFile]);
{
// both files exist - expect no errors
checkNumberOfProjects(projectService, { externalProjects: 1 });
const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
checkDiagnosticsWithLinePos(diags, []);
}
});
it("configured projects - diagnostics for missing files", () => {
const file1 = {
path: "/a/b/app.ts",
content: ""
};
const file2 = {
path: "/a/b/applib.ts",
content: ""
};
const config = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) })
};
const host = createServerHost([file1, config, libFile]);
const session = createSession(host);
const projectService = session.getProjectService();
openFilesForSession([file1], session);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const project = configuredProjectAt(projectService, 0);
const compilerOptionsRequest: server.protocol.CompilerOptionsDiagnosticsRequest = {
type: "request",
command: server.CommandNames.CompilerOptionsDiagnosticsFull,
seq: 2,
arguments: { projectFileName: project.getProjectName() }
};
let diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
checkDiagnosticsWithLinePos(diags, ["File '/a/b/applib.ts' not found."]);
host.reloadFS([file1, file2, config, libFile]);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
checkDiagnosticsWithLinePos(diags, []);
});
it("configured projects - diagnostics for corrupted config 1", () => {
const file1 = {
path: "/a/b/app.ts",
content: ""
};
const file2 = {
path: "/a/b/lib.ts",
content: ""
};
const correctConfig = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) })
};
const corruptedConfig = {
path: correctConfig.path,
content: correctConfig.content.substr(1)
};
const host = createServerHost([file1, file2, corruptedConfig]);
const projectService = createProjectService(host);
projectService.openClientFile(file1.path);
{
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject = find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!;
assert.isTrue(configuredProject !== undefined, "should find configured project");
checkProjectErrors(configuredProject, []);
const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors();
checkProjectErrorsWorker(projectErrors, [
"'{' expected."
]);
assert.isNotNull(projectErrors[0].file);
assert.equal(projectErrors[0].file!.fileName, corruptedConfig.path);
}
// fix config and trigger watcher
host.reloadFS([file1, file2, correctConfig]);
{
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject = find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!;
assert.isTrue(configuredProject !== undefined, "should find configured project");
checkProjectErrors(configuredProject, []);
const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors();
checkProjectErrorsWorker(projectErrors, []);
}
});
it("configured projects - diagnostics for corrupted config 2", () => {
const file1 = {
path: "/a/b/app.ts",
content: ""
};
const file2 = {
path: "/a/b/lib.ts",
content: ""
};
const correctConfig = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) })
};
const corruptedConfig = {
path: correctConfig.path,
content: correctConfig.content.substr(1)
};
const host = createServerHost([file1, file2, correctConfig]);
const projectService = createProjectService(host);
projectService.openClientFile(file1.path);
{
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject = find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!;
assert.isTrue(configuredProject !== undefined, "should find configured project");
checkProjectErrors(configuredProject, []);
const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors();
checkProjectErrorsWorker(projectErrors, []);
}
// break config and trigger watcher
host.reloadFS([file1, file2, corruptedConfig]);
{
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject = find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!;
assert.isTrue(configuredProject !== undefined, "should find configured project");
checkProjectErrors(configuredProject, []);
const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors();
checkProjectErrorsWorker(projectErrors, [
"'{' expected."
]);
assert.isNotNull(projectErrors[0].file);
assert.equal(projectErrors[0].file!.fileName, corruptedConfig.path);
}
});
});
}

View File

@ -210,7 +210,7 @@ namespace ts {
checkCache("resolved type directives", program, fileName, expectedContent, f => f.resolvedTypeReferenceDirectiveNames, checkResolvedTypeDirective);
}
describe("Reuse program structure", () => {
describe("unittests:: Reuse program structure:: General", () => {
const target = ScriptTarget.Latest;
const files: NamedSourceText[] = [
{
@ -895,7 +895,7 @@ namespace ts {
});
});
describe("host is optional", () => {
describe("unittests:: Reuse program structure:: host is optional", () => {
it("should work if host is not provided", () => {
createProgram([], {});
});
@ -905,7 +905,7 @@ namespace ts {
import createTestSystem = TestFSWithWatch.createWatchedSystem;
import libFile = TestFSWithWatch.libFile;
describe("isProgramUptoDate should return true when there is no change in compiler options and", () => {
describe("unittests:: Reuse program structure:: isProgramUptoDate should return true when there is no change in compiler options and", () => {
function verifyProgramIsUptoDate(
program: Program,
newRootFileNames: string[],

View File

@ -1,6 +1,6 @@
namespace ts {
import theory = utils.theory;
describe("semver", () => {
describe("unittests:: semver", () => {
describe("Version", () => {
function assertVersion(version: Version, [major, minor, patch, prerelease, build]: [number, number, number, string[]?, string[]?]) {
assert.strictEqual(version.major, major);
@ -225,4 +225,4 @@ namespace ts {
]);
});
});
}
}

View File

@ -1,5 +1,5 @@
namespace ts {
describe("cancellableLanguageServiceOperations", () => {
describe("unittests:: services:: cancellableLanguageServiceOperations", () => {
const file = `
function foo(): void;
function foo<T>(x: T): T;

View File

@ -6,7 +6,7 @@ interface ClassificationEntry {
position?: number;
}
describe("Colorization", () => {
describe("unittests:: services:: Colorization", () => {
// Use the shim adapter to ensure test coverage of the shim layer for the classifier
const languageServiceAdapter = new Harness.LanguageService.ShimLanguageServiceAdapter(/*preprocessToResolve*/ false);
const classifier = languageServiceAdapter.getClassifier();

View File

@ -255,7 +255,7 @@ interface String { charAt: any; }
interface Array<T> {}`
};
function testConvertToAsyncFunction(caption: string, text: string, baselineFolder: string, includeLib?: boolean, expectFailure = false) {
function testConvertToAsyncFunction(caption: string, text: string, baselineFolder: string, includeLib?: boolean, expectFailure = false, onlyProvideAction = false) {
const t = extractTest(text);
const selectionRange = t.ranges.get("selection")!;
if (!selectionRange) {
@ -307,7 +307,7 @@ interface Array<T> {}`
const actions = codefix.getFixes(context);
const action = find(actions, action => action.description === Diagnostics.Convert_to_async_function.message);
if (expectFailure) {
if (expectFailure && !onlyProvideAction) {
assert.isNotTrue(action && action.changes.length > 0);
return;
}
@ -343,7 +343,7 @@ interface Array<T> {}`
}
}
describe("convertToAsyncFunctions", () => {
describe("unittests:: services:: convertToAsyncFunctions", () => {
_testConvertToAsyncFunction("convertToAsyncFunction_basic", `
function [#|f|](): Promise<void>{
return fetch('https://typescriptlang.org').then(result => { console.log(result) });
@ -1151,25 +1151,25 @@ function [#|f|]() {
_testConvertToAsyncFunction("convertToAsyncFunction_simpleFunctionExpression", `
const [#|foo|] = function () {
return fetch('https://typescriptlang.org').then(result => { console.log(result) });
}
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_simpleFunctionExpressionWithName", `
const foo = function [#|f|]() {
return fetch('https://typescriptlang.org').then(result => { console.log(result) });
}
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_simpleFunctionExpressionAssignedToBindingPattern", `
const { length } = [#|function|] () {
return fetch('https://typescriptlang.org').then(result => { console.log(result) });
}
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_catchBlockUniqueParams", `
function [#|f|]() {
return Promise.resolve().then(x => 1).catch(x => "a").then(x => !!x);
}
return Promise.resolve().then(x => 1).catch(x => "a").then(x => !!x);
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_bindingPattern", `
@ -1178,7 +1178,7 @@ function [#|f|]() {
}
function res({ status, trailer }){
console.log(status);
}
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_bindingPatternNameCollision", `
@ -1188,7 +1188,7 @@ function [#|f|]() {
}
function res({ status, trailer }){
console.log(status);
}
}
`);
_testConvertToAsyncFunctionFailed("convertToAsyncFunction_thenArgumentNotFunction", `
@ -1209,7 +1209,7 @@ function [#|f|]() {
}
function res(result) {
return Promise.resolve().then(x => console.log(result));
}
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsPromise", `
@ -1241,7 +1241,7 @@ function [#|f|]() {
return Promise.resolve(1)
.then(x => Promise.reject(x))
.catch(err => console.log(err));
}
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_nestedPromises", `
@ -1266,6 +1266,22 @@ _testConvertToAsyncFunction("convertToAsyncFunction_exportModifier", `
export function [#|foo|]() {
return fetch('https://typescriptlang.org').then(s => console.log(s));
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_OutermostOnlySuccess", `
function [#|foo|]() {
return fetch('a').then(() => {
return fetch('b').then(() => 'c');
})
}
`);
_testConvertToAsyncFunctionFailedSuggestion("convertToAsyncFunction_OutermostOnlyFailure", `
function foo() {
return fetch('a').then([#|() => {|]
return fetch('b').then(() => 'c');
})
}
`);
});
@ -1276,4 +1292,8 @@ export function [#|foo|]() {
function _testConvertToAsyncFunctionFailed(caption: string, text: string) {
testConvertToAsyncFunction(caption, text, "convertToAsyncFunction", /*includeLib*/ true, /*expectFailure*/ true);
}
function _testConvertToAsyncFunctionFailedSuggestion(caption: string, text: string) {
testConvertToAsyncFunction(caption, text, "convertToAsyncFunction", /*includeLib*/ true, /*expectFailure*/ true, /*onlyProvideAction*/ true);
}
}

View File

@ -1,4 +1,4 @@
describe("DocumentRegistry", () => {
describe("unittests:: services:: DocumentRegistry", () => {
it("documents are shared between projects", () => {
const documentRegistry = ts.createDocumentRegistry();
const defaultCompilerOptions = ts.getDefaultCompilerOptions();

View File

@ -1,5 +1,5 @@
namespace ts {
describe("extractConstants", () => {
describe("unittests:: services:: extract:: extractConstants", () => {
testExtractConstant("extractConstant_TopLevel",
`let x = [#|1|];`);

View File

@ -1,5 +1,5 @@
namespace ts {
describe("extractFunctions", () => {
describe("unittests:: services:: extract:: extractFunctions", () => {
testExtractFunction("extractFunction1",
`namespace A {
let x = 1;

View File

@ -42,7 +42,7 @@ namespace ts {
}
}
describe("extractRanges", () => {
describe("unittests:: services:: extract:: extractRanges", () => {
it("get extract range from selection", () => {
testExtractRange(`
[#|
@ -418,4 +418,4 @@ switch (x) {
testExtractRangeFailed("extract-method-not-for-token-expression-statement", `[#|a|]`, [refactor.extractSymbol.Messages.cannotExtractIdentifier.message]);
});
}
}

View File

@ -1,5 +1,5 @@
namespace ts {
describe("Symbol Walker", () => {
describe("unittests:: services:: extract:: Symbol Walker", () => {
function test(description: string, source: string, verifier: (file: SourceFile, checker: TypeChecker) => void) {
it(description, () => {
const result = Harness.Compiler.compileFiles([{
@ -42,4 +42,4 @@ export default function foo(a: number, b: Bar): void {}`, (file, checker) => {
assert.equal(stdLibRefSymbols, 1); // Expect 1 stdlib entry symbol - the implicit Array referenced by Bar.history
});
});
}
}

View File

@ -1,5 +1,5 @@
namespace ts {
describe("hostNewLineSupport", () => {
describe("unittests:: services:: hostNewLineSupport", () => {
function testLSWithFiles(settings: CompilerOptions, files: Harness.Compiler.TestFile[]) {
function snapFor(path: string): IScriptSnapshot | undefined {
if (path === "lib.d.ts") {
@ -46,4 +46,4 @@ namespace ts {
`);
});
});
}
}

View File

@ -1,5 +1,5 @@
namespace ts {
describe("languageService", () => {
describe("unittests:: services:: languageService", () => {
const files: {[index: string]: string} = {
"foo.ts": `import Vue from "./vue";
import Component from "./vue-class-component";
@ -43,4 +43,4 @@ export function Component(x: Config): any;`
expect(definitions).to.exist; // tslint:disable-line no-unused-expression
});
});
}
}

View File

@ -1,5 +1,5 @@
namespace ts {
describe("Organize imports", () => {
describe("unittests:: services:: Organize imports", () => {
describe("Sort imports", () => {
it("Sort - non-relative vs non-relative", () => {
assertSortsBefore(
@ -274,6 +274,16 @@ export const Other = 1;
assert.isEmpty(changes);
});
it("doesn't crash on shorthand ambient module", () => {
const testFile = {
path: "/a.ts",
content: "declare module '*';",
};
const languageService = makeLanguageService(testFile);
const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions);
assert.isEmpty(changes);
});
testOrganizeImports("Renamed_used",
{
path: "/test.ts",

View File

@ -1,4 +1,4 @@
describe("PatternMatcher", () => {
describe("unittests:: services:: PatternMatcher", () => {
describe("BreakIntoCharacterSpans", () => {
it("EmptyIdentifier", () => {
verifyBreakIntoCharacterSpans("");

View File

@ -1,4 +1,4 @@
describe("PreProcessFile:", () => {
describe("unittests:: services:: PreProcessFile:", () => {
function test(sourceText: string, readImportFile: boolean, detectJavaScriptImports: boolean, expectedPreProcess: ts.PreProcessedFileInfo): void {
const resultPreProcess = ts.preProcessFile(sourceText, readImportFile, detectJavaScriptImports);

View File

@ -2,7 +2,7 @@
// tslint:disable trim-trailing-whitespace
namespace ts {
describe("textChanges", () => {
describe("unittests:: services:: textChanges", () => {
function findChild(name: string, n: Node) {
return find(n)!;
@ -753,4 +753,4 @@ let x = foo
});
}
});
}
}

View File

@ -1,5 +1,5 @@
namespace ts {
describe("Transpile", () => {
describe("unittests:: services:: Transpile", () => {
interface TranspileTestSettings {
options?: TranspileOptions;

View File

@ -1,5 +1,5 @@
namespace ts {
describe("TransformAPI", () => {
describe("unittests:: TransformAPI", () => {
function replaceUndefinedWithVoid0(context: TransformationContext) {
const previousOnSubstituteNode = context.onSubstituteNode;
context.enableSubstitution(SyntaxKind.Identifier);

View File

@ -8,7 +8,7 @@ namespace ts {
"/src/core/index.js", "/src/core/index.d.ts", "/src/core/index.d.ts.map",
"/src/logic/index.js", "/src/logic/index.js.map", "/src/logic/index.d.ts"];
describe("tsbuild - sanity check of clean build of 'sample1' project", () => {
describe("unittests:: tsbuild - sanity check of clean build of 'sample1' project", () => {
it("can build the sample project 'sample1' without error", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
@ -61,7 +61,7 @@ namespace ts {
});
});
describe("tsbuild - dry builds", () => {
describe("unittests:: tsbuild - dry builds", () => {
it("doesn't write any files in a dry build", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
@ -90,7 +90,7 @@ namespace ts {
});
});
describe("tsbuild - clean builds", () => {
describe("unittests:: tsbuild - clean builds", () => {
it("removes all files it built", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
@ -111,7 +111,7 @@ namespace ts {
});
});
describe("tsbuild - force builds", () => {
describe("unittests:: tsbuild - force builds", () => {
it("always builds under --force", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
@ -137,7 +137,7 @@ namespace ts {
});
});
describe("tsbuild - can detect when and what to rebuild", () => {
describe("unittests:: tsbuild - can detect when and what to rebuild", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: true });
@ -200,7 +200,7 @@ namespace ts {
});
});
describe("tsbuild - downstream-blocked compilations", () => {
describe("unittests:: tsbuild - downstream-blocked compilations", () => {
it("won't build downstream projects if upstream projects have errors", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
@ -222,7 +222,7 @@ namespace ts {
});
});
describe("tsbuild - project invalidation", () => {
describe("unittests:: tsbuild - project invalidation", () => {
it("invalidates projects correctly", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
@ -270,7 +270,7 @@ export class cNew {}`);
});
});
describe("tsbuild - with resolveJsonModule option", () => {
describe("unittests:: tsbuild - with resolveJsonModule option", () => {
const projFs = loadProjectFromDisk("tests/projects/resolveJsonModuleAndComposite");
const allExpectedOutputs = ["/src/tests/dist/src/index.js", "/src/tests/dist/src/index.d.ts", "/src/tests/dist/src/hello.json"];
@ -320,7 +320,7 @@ export default hello.hello`);
});
});
describe("tsbuild - lists files", () => {
describe("unittests:: tsbuild - lists files", () => {
it("listFiles", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
@ -369,7 +369,7 @@ export default hello.hello`);
});
});
describe("tsbuild - with rootDir of project reference in parentDirectory", () => {
describe("unittests:: tsbuild - with rootDir of project reference in parentDirectory", () => {
const projFs = loadProjectFromDisk("tests/projects/projectReferenceWithRootDirInParent");
const allExpectedOutputs = [
"/src/dist/other/other.js", "/src/dist/other/other.d.ts",
@ -388,7 +388,7 @@ export default hello.hello`);
});
});
describe("tsbuild - when project reference is referenced transitively", () => {
describe("unittests:: tsbuild - when project reference is referenced transitively", () => {
const projFs = loadProjectFromDisk("tests/projects/transitiveReferences");
const allExpectedOutputs = [
"/src/a.js", "/src/a.d.ts",
@ -460,7 +460,7 @@ export const b = new A();`);
export namespace OutFile {
const outFileFs = loadProjectFromDisk("tests/projects/outfile-concat");
describe("tsbuild - baseline sectioned sourcemaps", () => {
describe("unittests:: tsbuild - baseline sectioned sourcemaps", () => {
let fs: vfs.FileSystem | undefined;
before(() => {
fs = outFileFs.shadow();
@ -480,7 +480,7 @@ export const b = new A();`);
});
});
describe("tsbuild - downstream prepend projects always get rebuilt", () => {
describe("unittests:: tsbuild - downstream prepend projects always get rebuilt", () => {
it("", () => {
const fs = outFileFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
@ -508,7 +508,7 @@ export const b = new A();`);
"/src/core/index.d.ts.map",
];
describe("tsbuild - empty files option in tsconfig", () => {
describe("unittests:: tsbuild - empty files option in tsconfig", () => {
it("has empty files diagnostic when files is empty and no references are provided", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
@ -541,7 +541,7 @@ export const b = new A();`);
});
}
describe("tsbuild - graph-ordering", () => {
describe("unittests:: tsbuild - graph-ordering", () => {
let host: fakes.SolutionBuilderHost | undefined;
const deps: [string, string][] = [
["A", "B"],

View File

@ -1,5 +1,4 @@
namespace ts.tscWatch {
export import libFile = TestFSWithWatch.libFile;
import projectsLocation = TestFSWithWatch.tsbuildProjectsLocation;
import getFilePathInProject = TestFSWithWatch.getTsBuildProjectFilePath;
import getFileFromProject = TestFSWithWatch.getTsBuildProjectFile;
@ -15,7 +14,7 @@ namespace ts.tscWatch {
return solutionBuilder;
}
describe("tsbuild-watch program updates", () => {
describe("unittests:: tsbuild-watch program updates", () => {
const project = "sample1";
const enum SubProject {
core = "core",
@ -480,7 +479,7 @@ let x: string = 10;`);
const { host, solutionBuilder } = createSolutionOfProject(allFiles, currentDirectory, solutionBuilderconfig, getOutputFileStamps);
// Build in watch mode
const watch = createWatchOfConfigFileReturningBuilder(watchConfig, host);
const watch = createWatchOfConfigFile(watchConfig, host);
checkOutputErrorsInitial(host, emptyArray);
return { host, solutionBuilder, watch };
@ -506,10 +505,8 @@ let x: string = 10;`);
projectSystem.checkProjectActualFiles(service.configuredProjects.get(configFile.toLowerCase())!, expectedFiles);
}
type Watch = () => BuilderProgram;
function verifyDependencies(watch: Watch, filePath: string, expected: ReadonlyArray<string>) {
checkArray(`${filePath} dependencies`, watch().getAllDependencies(watch().getSourceFile(filePath)!), expected);
checkArray(`${filePath} dependencies`, watch.getBuilderProgram().getAllDependencies(watch().getSourceFile(filePath)!), expected);
}
describe("on sample project", () => {
@ -543,7 +540,7 @@ let x: string = 10;`);
host.checkTimeoutQueueLengthAndRun(1);
checkOutputErrorsIncremental(host, emptyArray);
checkProgramActualFiles(watch().getProgram(), expectedFilesAfterEdit);
checkProgramActualFiles(watch(), expectedFilesAfterEdit);
});
@ -643,7 +640,7 @@ export function gfoo() {
expectedWatchedDirectoriesRecursive: ReadonlyArray<string>,
dependencies: ReadonlyArray<[string, ReadonlyArray<string>]>,
expectedWatchedDirectories?: ReadonlyArray<string>) {
checkProgramActualFiles(watch().getProgram(), expectedProgramFiles);
checkProgramActualFiles(watch(), expectedProgramFiles);
verifyWatchesOfProject(host, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, expectedWatchedDirectories);
for (const [file, deps] of dependencies) {
verifyDependencies(watch, file, deps);

View File

@ -0,0 +1,97 @@
namespace ts.tscWatch {
describe("unittests:: tsc-watch:: console clearing", () => {
const currentDirectoryLog = "Current directory: / CaseSensitiveFileNames: false\n";
const fileWatcherAddedLog = [
"FileWatcher:: Added:: WatchInfo: /f.ts 250 Source file\n",
"FileWatcher:: Added:: WatchInfo: /a/lib/lib.d.ts 250 Source file\n"
];
const file: File = {
path: "/f.ts",
content: ""
};
function getProgramSynchronizingLog(options: CompilerOptions) {
return [
"Synchronizing program\n",
"CreatingProgramWith::\n",
" roots: [\"/f.ts\"]\n",
` options: ${JSON.stringify(options)}\n`
];
}
function isConsoleClearDisabled(options: CompilerOptions) {
return options.diagnostics || options.extendedDiagnostics || options.preserveWatchOutput;
}
function verifyCompilation(host: WatchedSystem, options: CompilerOptions, initialDisableOptions?: CompilerOptions) {
const disableConsoleClear = isConsoleClearDisabled(options);
const hasLog = options.extendedDiagnostics || options.diagnostics;
checkOutputErrorsInitial(host, emptyArray, initialDisableOptions ? isConsoleClearDisabled(initialDisableOptions) : disableConsoleClear, hasLog ? [
currentDirectoryLog,
...getProgramSynchronizingLog(options),
...(options.extendedDiagnostics ? fileWatcherAddedLog : emptyArray)
] : undefined);
host.modifyFile(file.path, "//");
host.runQueuedTimeoutCallbacks();
checkOutputErrorsIncremental(host, emptyArray, disableConsoleClear, hasLog ? [
"FileWatcher:: Triggered with /f.ts 1:: WatchInfo: /f.ts 250 Source file\n",
"Scheduling update\n",
"Elapsed:: 0ms FileWatcher:: Triggered with /f.ts 1:: WatchInfo: /f.ts 250 Source file\n"
] : undefined, hasLog ? getProgramSynchronizingLog(options) : undefined);
}
function checkConsoleClearingUsingCommandLineOptions(options: CompilerOptions = {}) {
const files = [file, libFile];
const host = createWatchedSystem(files);
createWatchOfFilesAndCompilerOptions([file.path], host, options);
verifyCompilation(host, options);
}
it("without --diagnostics or --extendedDiagnostics", () => {
checkConsoleClearingUsingCommandLineOptions();
});
it("with --diagnostics", () => {
checkConsoleClearingUsingCommandLineOptions({
diagnostics: true,
});
});
it("with --extendedDiagnostics", () => {
checkConsoleClearingUsingCommandLineOptions({
extendedDiagnostics: true,
});
});
it("with --preserveWatchOutput", () => {
checkConsoleClearingUsingCommandLineOptions({
preserveWatchOutput: true,
});
});
describe("when preserveWatchOutput is true in config file", () => {
const compilerOptions: CompilerOptions = {
preserveWatchOutput: true
};
const configFile: File = {
path: "/tsconfig.json",
content: JSON.stringify({ compilerOptions })
};
const files = [file, configFile, libFile];
it("using createWatchOfConfigFile ", () => {
const host = createWatchedSystem(files);
createWatchOfConfigFile(configFile.path, host);
// Initially console is cleared if --preserveOutput is not provided since the config file is yet to be parsed
verifyCompilation(host, compilerOptions, {});
});
it("when createWatchProgram is invoked with configFileParseResult on WatchCompilerHostOfConfigFile", () => {
const host = createWatchedSystem(files);
const reportDiagnostic = createDiagnosticReporter(host);
const optionsToExtend: CompilerOptions = {};
const configParseResult = parseConfigFileWithSystem(configFile.path, optionsToExtend, host, reportDiagnostic)!;
const watchCompilerHost = createWatchCompilerHostOfConfigFile(configParseResult.options.configFilePath!, optionsToExtend, host, /*createProgram*/ undefined, reportDiagnostic, createWatchStatusReporter(host));
watchCompilerHost.configFileParsingResult = configParseResult;
createWatchProgram(watchCompilerHost);
verifyCompilation(host, compilerOptions);
});
});
});
}

View File

@ -0,0 +1,718 @@
namespace ts.tscWatch {
function getEmittedLineForMultiFileOutput(file: File, host: WatchedSystem) {
return `TSFILE: ${file.path.replace(".ts", ".js")}${host.newLine}`;
}
function getEmittedLineForSingleFileOutput(filename: string, host: WatchedSystem) {
return `TSFILE: ${filename}${host.newLine}`;
}
interface FileOrFolderEmit extends File {
output?: string;
}
function getFileOrFolderEmit(file: File, getOutput?: (file: File) => string): FileOrFolderEmit {
const result = file as FileOrFolderEmit;
if (getOutput) {
result.output = getOutput(file);
}
return result;
}
function getEmittedLines(files: FileOrFolderEmit[]) {
const seen = createMap<true>();
const result: string[] = [];
for (const { output } of files) {
if (output && !seen.has(output)) {
seen.set(output, true);
result.push(output);
}
}
return result;
}
function checkAffectedLines(host: WatchedSystem, affectedFiles: FileOrFolderEmit[], allEmittedFiles: string[]) {
const expectedAffectedFiles = getEmittedLines(affectedFiles);
const expectedNonAffectedFiles = mapDefined(allEmittedFiles, line => contains(expectedAffectedFiles, line) ? undefined : line);
checkOutputContains(host, expectedAffectedFiles);
checkOutputDoesNotContain(host, expectedNonAffectedFiles);
}
describe("unittests:: tsc-watch:: emit with outFile or out setting", () => {
function createWatchForOut(out?: string, outFile?: string) {
const host = createWatchedSystem([]);
const config: FileOrFolderEmit = {
path: "/a/tsconfig.json",
content: JSON.stringify({
compilerOptions: { listEmittedFiles: true }
})
};
let getOutput: (file: File) => string;
if (out) {
config.content = JSON.stringify({
compilerOptions: { listEmittedFiles: true, out }
});
getOutput = __ => getEmittedLineForSingleFileOutput(out, host);
}
else if (outFile) {
config.content = JSON.stringify({
compilerOptions: { listEmittedFiles: true, outFile }
});
getOutput = __ => getEmittedLineForSingleFileOutput(outFile, host);
}
else {
getOutput = file => getEmittedLineForMultiFileOutput(file, host);
}
const f1 = getFileOrFolderEmit({
path: "/a/a.ts",
content: "let x = 1"
}, getOutput);
const f2 = getFileOrFolderEmit({
path: "/a/b.ts",
content: "let y = 1"
}, getOutput);
const files = [f1, f2, config, libFile];
host.reloadFS(files);
createWatchOfConfigFile(config.path, host);
const allEmittedLines = getEmittedLines(files);
checkOutputContains(host, allEmittedLines);
host.clearOutput();
f1.content = "let x = 11";
host.reloadFS(files);
host.runQueuedTimeoutCallbacks();
checkAffectedLines(host, [f1], allEmittedLines);
}
it("projectUsesOutFile should not be returned if not set", () => {
createWatchForOut();
});
it("projectUsesOutFile should be true if out is set", () => {
const outJs = "/a/out.js";
createWatchForOut(outJs);
});
it("projectUsesOutFile should be true if outFile is set", () => {
const outJs = "/a/out.js";
createWatchForOut(/*out*/ undefined, outJs);
});
function verifyFilesEmittedOnce(useOutFile: boolean) {
const file1: File = {
path: "/a/b/output/AnotherDependency/file1.d.ts",
content: "declare namespace Common.SomeComponent.DynamicMenu { enum Z { Full = 0, Min = 1, Average = 2, } }"
};
const file2: File = {
path: "/a/b/dependencies/file2.d.ts",
content: "declare namespace Dependencies.SomeComponent { export class SomeClass { version: string; } }"
};
const file3: File = {
path: "/a/b/project/src/main.ts",
content: "namespace Main { export function fooBar() {} }"
};
const file4: File = {
path: "/a/b/project/src/main2.ts",
content: "namespace main.file4 { import DynamicMenu = Common.SomeComponent.DynamicMenu; export function foo(a: DynamicMenu.z) { } }"
};
const configFile: File = {
path: "/a/b/project/tsconfig.json",
content: JSON.stringify({
compilerOptions: useOutFile ?
{ outFile: "../output/common.js", target: "es5" } :
{ outDir: "../output", target: "es5" },
files: [file1.path, file2.path, file3.path, file4.path]
})
};
const files = [file1, file2, file3, file4];
const allfiles = files.concat(configFile);
const host = createWatchedSystem(allfiles);
const originalWriteFile = host.writeFile.bind(host);
const mapOfFilesWritten = createMap<number>();
host.writeFile = (p: string, content: string) => {
const count = mapOfFilesWritten.get(p);
mapOfFilesWritten.set(p, count ? count + 1 : 1);
return originalWriteFile(p, content);
};
createWatchOfConfigFile(configFile.path, host);
if (useOutFile) {
// Only out file
assert.equal(mapOfFilesWritten.size, 1);
}
else {
// main.js and main2.js
assert.equal(mapOfFilesWritten.size, 2);
}
mapOfFilesWritten.forEach((value, key) => {
assert.equal(value, 1, "Key: " + key);
});
}
it("with --outFile and multiple declaration files in the program", () => {
verifyFilesEmittedOnce(/*useOutFile*/ true);
});
it("without --outFile and multiple declaration files in the program", () => {
verifyFilesEmittedOnce(/*useOutFile*/ false);
});
});
describe("unittests:: tsc-watch:: emit for configured projects", () => {
const file1Consumer1Path = "/a/b/file1Consumer1.ts";
const moduleFile1Path = "/a/b/moduleFile1.ts";
const configFilePath = "/a/b/tsconfig.json";
interface InitialStateParams {
/** custom config file options */
configObj?: any;
/** list of the files that will be emitted for first compilation */
firstCompilationEmitFiles?: string[];
/** get the emit file for file - default is multi file emit line */
getEmitLine?(file: File, host: WatchedSystem): string;
/** Additional files and folders to add */
getAdditionalFileOrFolder?(): File[];
/** initial list of files to emit if not the default list */
firstReloadFileList?: string[];
}
function getInitialState({ configObj = {}, firstCompilationEmitFiles, getEmitLine, getAdditionalFileOrFolder, firstReloadFileList }: InitialStateParams = {}) {
const host = createWatchedSystem([]);
const getOutputName = getEmitLine ? (file: File) => getEmitLine(file, host) :
(file: File) => getEmittedLineForMultiFileOutput(file, host);
const moduleFile1 = getFileOrFolderEmit({
path: moduleFile1Path,
content: "export function Foo() { };",
}, getOutputName);
const file1Consumer1 = getFileOrFolderEmit({
path: file1Consumer1Path,
content: `import {Foo} from "./moduleFile1"; export var y = 10;`,
}, getOutputName);
const file1Consumer2 = getFileOrFolderEmit({
path: "/a/b/file1Consumer2.ts",
content: `import {Foo} from "./moduleFile1"; let z = 10;`,
}, getOutputName);
const moduleFile2 = getFileOrFolderEmit({
path: "/a/b/moduleFile2.ts",
content: `export var Foo4 = 10;`,
}, getOutputName);
const globalFile3 = getFileOrFolderEmit({
path: "/a/b/globalFile3.ts",
content: `interface GlobalFoo { age: number }`
});
const additionalFiles = getAdditionalFileOrFolder ?
map(getAdditionalFileOrFolder(), file => getFileOrFolderEmit(file, getOutputName)) :
[];
(configObj.compilerOptions || (configObj.compilerOptions = {})).listEmittedFiles = true;
const configFile = getFileOrFolderEmit({
path: configFilePath,
content: JSON.stringify(configObj)
});
const files = [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile, ...additionalFiles];
let allEmittedFiles = getEmittedLines(files);
host.reloadFS(firstReloadFileList ? getFiles(firstReloadFileList) : files);
// Initial compile
createWatchOfConfigFile(configFile.path, host);
if (firstCompilationEmitFiles) {
checkAffectedLines(host, getFiles(firstCompilationEmitFiles), allEmittedFiles);
}
else {
checkOutputContains(host, allEmittedFiles);
}
host.clearOutput();
return {
moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile,
files,
getFile,
verifyAffectedFiles,
verifyAffectedAllFiles,
getOutputName
};
function getFiles(filelist: string[]) {
return map(filelist, getFile);
}
function getFile(fileName: string) {
return find(files, file => file.path === fileName)!;
}
function verifyAffectedAllFiles() {
host.reloadFS(files);
host.checkTimeoutQueueLengthAndRun(1);
checkOutputContains(host, allEmittedFiles);
host.clearOutput();
}
function verifyAffectedFiles(expected: FileOrFolderEmit[], filesToReload?: FileOrFolderEmit[]) {
if (!filesToReload) {
filesToReload = files;
}
else if (filesToReload.length > files.length) {
allEmittedFiles = getEmittedLines(filesToReload);
}
host.reloadFS(filesToReload);
host.checkTimeoutQueueLengthAndRun(1);
checkAffectedLines(host, expected, allEmittedFiles);
host.clearOutput();
}
}
it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => {
const {
moduleFile1, file1Consumer1, file1Consumer2,
verifyAffectedFiles
} = getInitialState();
// Change the content of moduleFile1 to `export var T: number;export function Foo() { };`
moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2]);
// Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };`
moduleFile1.content = `export var T: number;export function Foo() { console.log('hi'); };`;
verifyAffectedFiles([moduleFile1]);
});
it("should be up-to-date with the reference map changes", () => {
const {
moduleFile1, file1Consumer1, file1Consumer2,
verifyAffectedFiles
} = getInitialState();
// Change file1Consumer1 content to `export let y = Foo();`
file1Consumer1.content = `export let y = Foo();`;
verifyAffectedFiles([file1Consumer1]);
// Change the content of moduleFile1 to `export var T: number;export function Foo() { };`
moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyAffectedFiles([moduleFile1, file1Consumer2]);
// Add the import statements back to file1Consumer1
file1Consumer1.content = `import {Foo} from "./moduleFile1";let y = Foo();`;
verifyAffectedFiles([file1Consumer1]);
// Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };`
moduleFile1.content = `export var T: number;export var T2: string;export function Foo() { };`;
verifyAffectedFiles([moduleFile1, file1Consumer2, file1Consumer1]);
// Multiple file edits in one go:
// Change file1Consumer1 content to `export let y = Foo();`
// Change the content of moduleFile1 to `export var T: number;export function Foo() { };`
file1Consumer1.content = `export let y = Foo();`;
moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2]);
});
it("should be up-to-date with deleted files", () => {
const {
moduleFile1, file1Consumer1, file1Consumer2,
files,
verifyAffectedFiles
} = getInitialState();
// Change the content of moduleFile1 to `export var T: number;export function Foo() { };`
moduleFile1.content = `export var T: number;export function Foo() { };`;
// Delete file1Consumer2
const filesToLoad = mapDefined(files, file => file === file1Consumer2 ? undefined : file);
verifyAffectedFiles([moduleFile1, file1Consumer1], filesToLoad);
});
it("should be up-to-date with newly created files", () => {
const {
moduleFile1, file1Consumer1, file1Consumer2,
files,
verifyAffectedFiles,
getOutputName
} = getInitialState();
const file1Consumer3 = getFileOrFolderEmit({
path: "/a/b/file1Consumer3.ts",
content: `import {Foo} from "./moduleFile1"; let y = Foo();`
}, getOutputName);
moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer3, file1Consumer2], files.concat(file1Consumer3));
});
it("should detect changes in non-root files", () => {
const {
moduleFile1, file1Consumer1,
verifyAffectedFiles
} = getInitialState({ configObj: { files: [file1Consumer1Path] }, firstCompilationEmitFiles: [file1Consumer1Path, moduleFile1Path] });
moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyAffectedFiles([moduleFile1, file1Consumer1]);
// change file1 internal, and verify only file1 is affected
moduleFile1.content += "var T1: number;";
verifyAffectedFiles([moduleFile1]);
});
it("should return all files if a global file changed shape", () => {
const {
globalFile3, verifyAffectedAllFiles
} = getInitialState();
globalFile3.content += "var T2: string;";
verifyAffectedAllFiles();
});
it("should always return the file itself if '--isolatedModules' is specified", () => {
const {
moduleFile1, verifyAffectedFiles
} = getInitialState({ configObj: { compilerOptions: { isolatedModules: true } } });
moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyAffectedFiles([moduleFile1]);
});
it("should always return the file itself if '--out' or '--outFile' is specified", () => {
const outFilePath = "/a/b/out.js";
const {
moduleFile1, verifyAffectedFiles
} = getInitialState({
configObj: { compilerOptions: { module: "system", outFile: outFilePath } },
getEmitLine: (_, host) => getEmittedLineForSingleFileOutput(outFilePath, host)
});
moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyAffectedFiles([moduleFile1]);
});
it("should return cascaded affected file list", () => {
const file1Consumer1Consumer1: File = {
path: "/a/b/file1Consumer1Consumer1.ts",
content: `import {y} from "./file1Consumer1";`
};
const {
moduleFile1, file1Consumer1, file1Consumer2, verifyAffectedFiles, getFile
} = getInitialState({
getAdditionalFileOrFolder: () => [file1Consumer1Consumer1]
});
const file1Consumer1Consumer1Emit = getFile(file1Consumer1Consumer1.path);
file1Consumer1.content += "export var T: number;";
verifyAffectedFiles([file1Consumer1, file1Consumer1Consumer1Emit]);
// Doesnt change the shape of file1Consumer1
moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2]);
// Change both files before the timeout
file1Consumer1.content += "export var T2: number;";
moduleFile1.content = `export var T2: number;export function Foo() { };`;
verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2, file1Consumer1Consumer1Emit]);
});
it("should work fine for files with circular references", () => {
// TODO: do not exit on such errors? Just continue to watch the files for update in watch mode
const file1: File = {
path: "/a/b/file1.ts",
content: `
/// <reference path="./file2.ts" />
export var t1 = 10;`
};
const file2: File = {
path: "/a/b/file2.ts",
content: `
/// <reference path="./file1.ts" />
export var t2 = 10;`
};
const {
configFile,
getFile,
verifyAffectedFiles
} = getInitialState({
firstCompilationEmitFiles: [file1.path, file2.path],
getAdditionalFileOrFolder: () => [file1, file2],
firstReloadFileList: [libFile.path, file1.path, file2.path, configFilePath]
});
const file1Emit = getFile(file1.path), file2Emit = getFile(file2.path);
file1Emit.content += "export var t3 = 10;";
verifyAffectedFiles([file1Emit, file2Emit], [file1, file2, libFile, configFile]);
});
it("should detect removed code file", () => {
const referenceFile1: File = {
path: "/a/b/referenceFile1.ts",
content: `
/// <reference path="./moduleFile1.ts" />
export var x = Foo();`
};
const {
configFile,
getFile,
verifyAffectedFiles
} = getInitialState({
firstCompilationEmitFiles: [referenceFile1.path, moduleFile1Path],
getAdditionalFileOrFolder: () => [referenceFile1],
firstReloadFileList: [libFile.path, referenceFile1.path, moduleFile1Path, configFilePath]
});
const referenceFile1Emit = getFile(referenceFile1.path);
verifyAffectedFiles([referenceFile1Emit], [libFile, referenceFile1Emit, configFile]);
});
it("should detect non-existing code file", () => {
const referenceFile1: File = {
path: "/a/b/referenceFile1.ts",
content: `
/// <reference path="./moduleFile2.ts" />
export var x = Foo();`
};
const {
configFile,
moduleFile2,
getFile,
verifyAffectedFiles
} = getInitialState({
firstCompilationEmitFiles: [referenceFile1.path],
getAdditionalFileOrFolder: () => [referenceFile1],
firstReloadFileList: [libFile.path, referenceFile1.path, configFilePath]
});
const referenceFile1Emit = getFile(referenceFile1.path);
referenceFile1Emit.content += "export var yy = Foo();";
verifyAffectedFiles([referenceFile1Emit], [libFile, referenceFile1Emit, configFile]);
// Create module File2 and see both files are saved
verifyAffectedFiles([referenceFile1Emit, moduleFile2], [libFile, moduleFile2, referenceFile1Emit, configFile]);
});
});
describe("unittests:: tsc-watch:: emit file content", () => {
interface EmittedFile extends File {
shouldBeWritten: boolean;
}
function getEmittedFiles(files: FileOrFolderEmit[], contents: string[]): EmittedFile[] {
return map(contents, (content, index) => {
return {
content,
path: changeExtension(files[index].path, Extension.Js),
shouldBeWritten: true
};
}
);
}
function verifyEmittedFiles(host: WatchedSystem, emittedFiles: EmittedFile[]) {
for (const { path, content, shouldBeWritten } of emittedFiles) {
if (shouldBeWritten) {
assert.isTrue(host.fileExists(path), `Expected file ${path} to be present`);
assert.equal(host.readFile(path), content, `Contents of file ${path} do not match`);
}
else {
assert.isNotTrue(host.fileExists(path), `Expected file ${path} to be absent`);
}
}
}
function verifyEmittedFileContents(newLine: string, inputFiles: File[], initialEmittedFileContents: string[],
modifyFiles: (files: FileOrFolderEmit[], emitedFiles: EmittedFile[]) => FileOrFolderEmit[], configFile?: File) {
const host = createWatchedSystem([], { newLine });
const files = concatenate(
map(inputFiles, file => getFileOrFolderEmit(file, fileToConvert => getEmittedLineForMultiFileOutput(fileToConvert, host))),
configFile ? [libFile, configFile] : [libFile]
);
const allEmittedFiles = getEmittedLines(files);
host.reloadFS(files);
// Initial compile
if (configFile) {
createWatchOfConfigFile(configFile.path, host);
}
else {
// First file as the root
createWatchOfFilesAndCompilerOptions([files[0].path], host, { listEmittedFiles: true });
}
checkOutputContains(host, allEmittedFiles);
const emittedFiles = getEmittedFiles(files, initialEmittedFileContents);
verifyEmittedFiles(host, emittedFiles);
host.clearOutput();
const affectedFiles = modifyFiles(files, emittedFiles);
host.reloadFS(files);
host.checkTimeoutQueueLengthAndRun(1);
checkAffectedLines(host, affectedFiles, allEmittedFiles);
verifyEmittedFiles(host, emittedFiles);
}
function verifyNewLine(newLine: string) {
const lines = ["var x = 1;", "var y = 2;"];
const fileContent = lines.join(newLine);
const f = {
path: "/a/app.ts",
content: fileContent
};
verifyEmittedFileContents(newLine, [f], [fileContent + newLine], modifyFiles);
function modifyFiles(files: FileOrFolderEmit[], emittedFiles: EmittedFile[]) {
files[0].content = fileContent + newLine + "var z = 3;";
emittedFiles[0].content = files[0].content + newLine;
return [files[0]];
}
}
it("handles new lines: \\n", () => {
verifyNewLine("\n");
});
it("handles new lines: \\r\\n", () => {
verifyNewLine("\r\n");
});
it("should emit specified file", () => {
const file1 = {
path: "/a/b/f1.ts",
content: `export function Foo() { return 10; }`
};
const file2 = {
path: "/a/b/f2.ts",
content: `import {Foo} from "./f1"; export let y = Foo();`
};
const file3 = {
path: "/a/b/f3.ts",
content: `import {y} from "./f2"; let x = y;`
};
const configFile = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({ compilerOptions: { listEmittedFiles: true } })
};
verifyEmittedFileContents("\r\n", [file1, file2, file3], [
`"use strict";\r\nexports.__esModule = true;\r\nfunction Foo() { return 10; }\r\nexports.Foo = Foo;\r\n`,
`"use strict";\r\nexports.__esModule = true;\r\nvar f1_1 = require("./f1");\r\nexports.y = f1_1.Foo();\r\n`,
`"use strict";\r\nexports.__esModule = true;\r\nvar f2_1 = require("./f2");\r\nvar x = f2_1.y;\r\n`
], modifyFiles, configFile);
function modifyFiles(files: FileOrFolderEmit[], emittedFiles: EmittedFile[]) {
files[0].content += `export function foo2() { return 2; }`;
emittedFiles[0].content += `function foo2() { return 2; }\r\nexports.foo2 = foo2;\r\n`;
emittedFiles[2].shouldBeWritten = false;
return files.slice(0, 2);
}
});
it("Elides const enums correctly in incremental compilation", () => {
const currentDirectory = "/user/someone/projects/myproject";
const file1: File = {
path: `${currentDirectory}/file1.ts`,
content: "export const enum E1 { V = 1 }"
};
const file2: File = {
path: `${currentDirectory}/file2.ts`,
content: `import { E1 } from "./file1"; export const enum E2 { V = E1.V }`
};
const file3: File = {
path: `${currentDirectory}/file3.ts`,
content: `import { E2 } from "./file2"; const v: E2 = E2.V;`
};
const strictAndEsModule = `"use strict";\nexports.__esModule = true;\n`;
verifyEmittedFileContents("\n", [file3, file2, file1], [
`${strictAndEsModule}var v = 1 /* V */;\n`,
strictAndEsModule,
strictAndEsModule
], modifyFiles);
function modifyFiles(files: FileOrFolderEmit[], emittedFiles: EmittedFile[]) {
files[0].content += `function foo2() { return 2; }`;
emittedFiles[0].content += `function foo2() { return 2; }\n`;
emittedFiles[1].shouldBeWritten = false;
emittedFiles[2].shouldBeWritten = false;
return [files[0]];
}
});
it("file is deleted and created as part of change", () => {
const projectLocation = "/home/username/project";
const file: File = {
path: `${projectLocation}/app/file.ts`,
content: "var a = 10;"
};
const fileJs = `${projectLocation}/app/file.js`;
const configFile: File = {
path: `${projectLocation}/tsconfig.json`,
content: JSON.stringify({
include: [
"app/**/*.ts"
]
})
};
const files = [file, configFile, libFile];
const host = createWatchedSystem(files, { currentDirectory: projectLocation, useCaseSensitiveFileNames: true });
createWatchOfConfigFile("tsconfig.json", host);
verifyProgram();
file.content += "\nvar b = 10;";
host.reloadFS(files, { invokeFileDeleteCreateAsPartInsteadOfChange: true });
host.runQueuedTimeoutCallbacks();
verifyProgram();
function verifyProgram() {
assert.isTrue(host.fileExists(fileJs));
assert.equal(host.readFile(fileJs), file.content + "\n");
}
});
});
describe("unittests:: tsc-watch:: emit with when module emit is specified as node", () => {
it("when instead of filechanged recursive directory watcher is invoked", () => {
const configFile: File = {
path: "/a/rootFolder/project/tsconfig.json",
content: JSON.stringify({
compilerOptions: {
module: "none",
allowJs: true,
outDir: "Static/scripts/"
},
include: [
"Scripts/**/*"
],
})
};
const outputFolder = "/a/rootFolder/project/Static/scripts/";
const file1: File = {
path: "/a/rootFolder/project/Scripts/TypeScript.ts",
content: "var z = 10;"
};
const file2: File = {
path: "/a/rootFolder/project/Scripts/Javascript.js",
content: "var zz = 10;"
};
const files = [configFile, file1, file2, libFile];
const host = createWatchedSystem(files);
const watch = createWatchOfConfigFile(configFile.path, host);
checkProgramActualFiles(watch(), mapDefined(files, f => f === configFile ? undefined : f.path));
file1.content = "var zz30 = 100;";
host.reloadFS(files, { invokeDirectoryWatcherInsteadOfFileChanged: true });
host.runQueuedTimeoutCallbacks();
checkProgramActualFiles(watch(), mapDefined(files, f => f === configFile ? undefined : f.path));
const outputFile1 = changeExtension((outputFolder + getBaseFileName(file1.path)), ".js");
assert.isTrue(host.fileExists(outputFile1));
assert.equal(host.readFile(outputFile1), file1.content + host.newLine);
});
});
}

View File

@ -0,0 +1,307 @@
namespace ts.tscWatch {
describe("unittests:: tsc-watch:: Emit times and Error updates in builder after program changes", () => {
const currentDirectory = "/user/username/projects/myproject";
const config: File = {
path: `${currentDirectory}/tsconfig.json`,
content: `{}`
};
function getOutputFileStampAndError(host: WatchedSystem, watch: Watch, file: File) {
const builderProgram = watch.getBuilderProgram();
const state = builderProgram.getState();
return {
file,
fileStamp: host.getModifiedTime(file.path.replace(".ts", ".js")),
errors: builderProgram.getSemanticDiagnostics(watch().getSourceFileByPath(file.path as Path)),
errorsFromOldState: !!state.semanticDiagnosticsFromOldState && state.semanticDiagnosticsFromOldState.has(file.path)
};
}
function getOutputFileStampsAndErrors(host: WatchedSystem, watch: Watch, directoryFiles: ReadonlyArray<File>) {
return directoryFiles.map(d => getOutputFileStampAndError(host, watch, d));
}
function findStampAndErrors(stampsAndErrors: ReadonlyArray<ReturnType<typeof getOutputFileStampAndError>>, file: File) {
return find(stampsAndErrors, info => info.file === file)!;
}
function verifyOutputFileStampsAndErrors(
file: File,
emitExpected: boolean,
errorRefershExpected: boolean,
beforeChangeFileStampsAndErrors: ReadonlyArray<ReturnType<typeof getOutputFileStampAndError>>,
afterChangeFileStampsAndErrors: ReadonlyArray<ReturnType<typeof getOutputFileStampAndError>>
) {
const beforeChange = findStampAndErrors(beforeChangeFileStampsAndErrors, file);
const afterChange = findStampAndErrors(afterChangeFileStampsAndErrors, file);
if (emitExpected) {
assert.notStrictEqual(afterChange.fileStamp, beforeChange.fileStamp, `Expected emit for file ${file.path}`);
}
else {
assert.strictEqual(afterChange.fileStamp, beforeChange.fileStamp, `Did not expect new emit for file ${file.path}`);
}
if (errorRefershExpected) {
if (afterChange.errors !== emptyArray || beforeChange.errors !== emptyArray) {
assert.notStrictEqual(afterChange.errors, beforeChange.errors, `Expected new errors for file ${file.path}`);
}
assert.isFalse(afterChange.errorsFromOldState, `Expected errors to be not copied from old state for file ${file.path}`);
}
else {
assert.strictEqual(afterChange.errors, beforeChange.errors, `Expected errors to not change for file ${file.path}`);
assert.isTrue(afterChange.errorsFromOldState, `Expected errors to be copied from old state for file ${file.path}`);
}
}
interface VerifyEmitAndErrorUpdates {
change: (host: WatchedSystem) => void;
getInitialErrors: (watch: Watch) => ReadonlyArray<Diagnostic> | ReadonlyArray<string>;
getIncrementalErrors: (watch: Watch) => ReadonlyArray<Diagnostic> | ReadonlyArray<string>;
filesWithNewEmit: ReadonlyArray<File>;
filesWithOnlyErrorRefresh: ReadonlyArray<File>;
filesNotTouched: ReadonlyArray<File>;
configFile?: File;
}
function verifyEmitAndErrorUpdates({ filesWithNewEmit, filesWithOnlyErrorRefresh, filesNotTouched, configFile = config, change, getInitialErrors, getIncrementalErrors }: VerifyEmitAndErrorUpdates) {
const nonLibFiles = [...filesWithNewEmit, ...filesWithOnlyErrorRefresh, ...filesNotTouched];
const files = [...nonLibFiles, configFile, libFile];
const host = createWatchedSystem(files, { currentDirectory });
const watch = createWatchOfConfigFile("tsconfig.json", host);
checkProgramActualFiles(watch(), [...nonLibFiles.map(f => f.path), libFile.path]);
checkOutputErrorsInitial(host, getInitialErrors(watch));
const beforeChange = getOutputFileStampsAndErrors(host, watch, nonLibFiles);
change(host);
host.runQueuedTimeoutCallbacks();
checkOutputErrorsIncremental(host, getIncrementalErrors(watch));
const afterChange = getOutputFileStampsAndErrors(host, watch, nonLibFiles);
filesWithNewEmit.forEach(file => verifyOutputFileStampsAndErrors(file, /*emitExpected*/ true, /*errorRefershExpected*/ true, beforeChange, afterChange));
filesWithOnlyErrorRefresh.forEach(file => verifyOutputFileStampsAndErrors(file, /*emitExpected*/ false, /*errorRefershExpected*/ true, beforeChange, afterChange));
filesNotTouched.forEach(file => verifyOutputFileStampsAndErrors(file, /*emitExpected*/ false, /*errorRefershExpected*/ false, beforeChange, afterChange));
}
describe("deep import changes", () => {
const aFile: File = {
path: `${currentDirectory}/a.ts`,
content: `import {B} from './b';
declare var console: any;
let b = new B();
console.log(b.c.d);`
};
function verifyDeepImportChange(bFile: File, cFile: File) {
const filesWithNewEmit: File[] = [];
const filesWithOnlyErrorRefresh = [aFile];
addImportedModule(bFile);
addImportedModule(cFile);
verifyEmitAndErrorUpdates({
filesWithNewEmit,
filesWithOnlyErrorRefresh,
filesNotTouched: emptyArray,
change: host => host.writeFile(cFile.path, cFile.content.replace("d", "d2")),
getInitialErrors: () => emptyArray,
getIncrementalErrors: watch => [
getDiagnosticOfFileFromProgram(watch(), aFile.path, aFile.content.lastIndexOf("d"), 1, Diagnostics.Property_0_does_not_exist_on_type_1, "d", "C")
]
});
function addImportedModule(file: File) {
if (file.path.endsWith(".d.ts")) {
filesWithOnlyErrorRefresh.push(file);
}
else {
filesWithNewEmit.push(file);
}
}
}
it("updates errors when deep import file changes", () => {
const bFile: File = {
path: `${currentDirectory}/b.ts`,
content: `import {C} from './c';
export class B
{
c = new C();
}`
};
const cFile: File = {
path: `${currentDirectory}/c.ts`,
content: `export class C
{
d = 1;
}`
};
verifyDeepImportChange(bFile, cFile);
});
it("updates errors when deep import through declaration file changes", () => {
const bFile: File = {
path: `${currentDirectory}/b.d.ts`,
content: `import {C} from './c';
export class B
{
c: C;
}`
};
const cFile: File = {
path: `${currentDirectory}/c.d.ts`,
content: `export class C
{
d: number;
}`
};
verifyDeepImportChange(bFile, cFile);
});
});
it("updates errors in file not exporting a deep multilevel import that changes", () => {
const aFile: File = {
path: `${currentDirectory}/a.ts`,
content: `export interface Point {
name: string;
c: Coords;
}
export interface Coords {
x2: number;
y: number;
}`
};
const bFile: File = {
path: `${currentDirectory}/b.ts`,
content: `import { Point } from "./a";
export interface PointWrapper extends Point {
}`
};
const cFile: File = {
path: `${currentDirectory}/c.ts`,
content: `import { PointWrapper } from "./b";
export function getPoint(): PointWrapper {
return {
name: "test",
c: {
x: 1,
y: 2
}
}
};`
};
const dFile: File = {
path: `${currentDirectory}/d.ts`,
content: `import { getPoint } from "./c";
getPoint().c.x;`
};
const eFile: File = {
path: `${currentDirectory}/e.ts`,
content: `import "./d";`
};
verifyEmitAndErrorUpdates({
filesWithNewEmit: [aFile, bFile],
filesWithOnlyErrorRefresh: [cFile, dFile],
filesNotTouched: [eFile],
change: host => host.writeFile(aFile.path, aFile.content.replace("x2", "x")),
getInitialErrors: watch => [
getDiagnosticOfFileFromProgram(watch(), cFile.path, cFile.content.indexOf("x: 1"), 4, chainDiagnosticMessages(
chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, "x", "Coords"),
Diagnostics.Type_0_is_not_assignable_to_type_1,
"{ x: number; y: number; }",
"Coords"
)),
getDiagnosticOfFileFromProgram(watch(), dFile.path, dFile.content.lastIndexOf("x"), 1, Diagnostics.Property_0_does_not_exist_on_type_1, "x", "Coords")
],
getIncrementalErrors: () => emptyArray
});
});
describe("updates errors when file transitively exported file changes", () => {
const config: File = {
path: `${currentDirectory}/tsconfig.json`,
content: JSON.stringify({
files: ["app.ts"],
compilerOptions: { baseUrl: "." }
})
};
const app: File = {
path: `${currentDirectory}/app.ts`,
content: `import { Data } from "lib2/public";
export class App {
public constructor() {
new Data().test();
}
}`
};
const lib2Public: File = {
path: `${currentDirectory}/lib2/public.ts`,
content: `export * from "./data";`
};
const lib2Data: File = {
path: `${currentDirectory}/lib2/data.ts`,
content: `import { ITest } from "lib1/public";
export class Data {
public test() {
const result: ITest = {
title: "title"
}
return result;
}
}`
};
const lib1Public: File = {
path: `${currentDirectory}/lib1/public.ts`,
content: `export * from "./tools/public";`
};
const lib1ToolsPublic: File = {
path: `${currentDirectory}/lib1/tools/public.ts`,
content: `export * from "./tools.interface";`
};
const lib1ToolsInterface: File = {
path: `${currentDirectory}/lib1/tools/tools.interface.ts`,
content: `export interface ITest {
title: string;
}`
};
function verifyTransitiveExports(lib2Data: File, lib2Data2?: File) {
const filesWithNewEmit = [lib1ToolsInterface, lib1ToolsPublic];
const filesWithOnlyErrorRefresh = [app, lib2Public, lib1Public, lib2Data];
if (lib2Data2) {
filesWithOnlyErrorRefresh.push(lib2Data2);
}
verifyEmitAndErrorUpdates({
filesWithNewEmit,
filesWithOnlyErrorRefresh,
filesNotTouched: emptyArray,
configFile: config,
change: host => host.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title", "title2")),
getInitialErrors: () => emptyArray,
getIncrementalErrors: () => [
"lib2/data.ts(5,13): error TS2322: Type '{ title: string; }' is not assignable to type 'ITest'.\n Object literal may only specify known properties, but 'title' does not exist in type 'ITest'. Did you mean to write 'title2'?\n"
]
});
}
it("when there are no circular import and exports", () => {
verifyTransitiveExports(lib2Data);
});
it("when there are circular import and exports", () => {
const lib2Data: File = {
path: `${currentDirectory}/lib2/data.ts`,
content: `import { ITest } from "lib1/public"; import { Data2 } from "./data2";
export class Data {
public dat?: Data2; public test() {
const result: ITest = {
title: "title"
}
return result;
}
}`
};
const lib2Data2: File = {
path: `${currentDirectory}/lib2/data2.ts`,
content: `import { Data } from "./data";
export class Data2 {
public dat?: Data;
}`
};
verifyTransitiveExports(lib2Data, lib2Data2);
});
});
});
}

View File

@ -0,0 +1,203 @@
namespace ts.tscWatch {
export import WatchedSystem = TestFSWithWatch.TestServerHost;
export type File = TestFSWithWatch.File;
export type SymLink = TestFSWithWatch.SymLink;
export import libFile = TestFSWithWatch.libFile;
export import createWatchedSystem = TestFSWithWatch.createWatchedSystem;
export import checkArray = TestFSWithWatch.checkArray;
export import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles;
export import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed;
export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories;
export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed;
export import checkOutputContains = TestFSWithWatch.checkOutputContains;
export import checkOutputDoesNotContain = TestFSWithWatch.checkOutputDoesNotContain;
export const commonFile1: File = {
path: "/a/b/commonFile1.ts",
content: "let x = 1"
};
export const commonFile2: File = {
path: "/a/b/commonFile2.ts",
content: "let y = 1"
};
export function checkProgramActualFiles(program: Program, expectedFiles: ReadonlyArray<string>) {
checkArray(`Program actual files`, program.getSourceFiles().map(file => file.fileName), expectedFiles);
}
export function checkProgramRootFiles(program: Program, expectedFiles: ReadonlyArray<string>) {
checkArray(`Program rootFileNames`, program.getRootFileNames(), expectedFiles);
}
export interface Watch {
(): Program;
getBuilderProgram(): EmitAndSemanticDiagnosticsBuilderProgram;
}
export function createWatchOfConfigFile(configFileName: string, host: WatchedSystem, maxNumberOfFilesToIterateForInvalidation?: number) {
const compilerHost = createWatchCompilerHostOfConfigFile(configFileName, {}, host);
compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation;
const watch = createWatchProgram(compilerHost);
const result = (() => watch.getCurrentProgram().getProgram()) as Watch;
result.getBuilderProgram = () => watch.getCurrentProgram();
return result;
}
export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], host: WatchedSystem, options: CompilerOptions = {}, maxNumberOfFilesToIterateForInvalidation?: number) {
const compilerHost = createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles, options, host);
compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation;
const watch = createWatchProgram(compilerHost);
return () => watch.getCurrentProgram().getProgram();
}
const elapsedRegex = /^Elapsed:: [0-9]+ms/;
function checkOutputErrors(
host: WatchedSystem,
logsBeforeWatchDiagnostic: string[] | undefined,
preErrorsWatchDiagnostic: Diagnostic,
logsBeforeErrors: string[] | undefined,
errors: ReadonlyArray<Diagnostic> | ReadonlyArray<string>,
disableConsoleClears?: boolean | undefined,
...postErrorsWatchDiagnostics: Diagnostic[]
) {
let screenClears = 0;
const outputs = host.getOutput();
const expectedOutputCount = 1 + errors.length + postErrorsWatchDiagnostics.length +
(logsBeforeWatchDiagnostic ? logsBeforeWatchDiagnostic.length : 0) + (logsBeforeErrors ? logsBeforeErrors.length : 0);
assert.equal(outputs.length, expectedOutputCount, JSON.stringify(outputs));
let index = 0;
forEach(logsBeforeWatchDiagnostic, log => assertLog("logsBeforeWatchDiagnostic", log));
assertWatchDiagnostic(preErrorsWatchDiagnostic);
forEach(logsBeforeErrors, log => assertLog("logBeforeError", log));
// Verify errors
forEach(errors, assertDiagnostic);
forEach(postErrorsWatchDiagnostics, assertWatchDiagnostic);
assert.equal(host.screenClears.length, screenClears, "Expected number of screen clears");
host.clearOutput();
function isDiagnostic(diagnostic: Diagnostic | string): diagnostic is Diagnostic {
return !!(diagnostic as Diagnostic).messageText;
}
function assertDiagnostic(diagnostic: Diagnostic | string) {
const expected = isDiagnostic(diagnostic) ? formatDiagnostic(diagnostic, host) : diagnostic;
assert.equal(outputs[index], expected, getOutputAtFailedMessage("Diagnostic", expected));
index++;
}
function assertLog(caption: string, expected: string) {
const actual = outputs[index];
assert.equal(actual.replace(elapsedRegex, ""), expected.replace(elapsedRegex, ""), getOutputAtFailedMessage(caption, expected));
index++;
}
function assertWatchDiagnostic(diagnostic: Diagnostic) {
const expected = getWatchDiagnosticWithoutDate(diagnostic);
if (!disableConsoleClears && contains(screenStartingMessageCodes, diagnostic.code)) {
assert.equal(host.screenClears[screenClears], index, `Expected screen clear at this diagnostic: ${expected}`);
screenClears++;
}
assert.isTrue(endsWith(outputs[index], expected), getOutputAtFailedMessage("Watch diagnostic", expected));
index++;
}
function getOutputAtFailedMessage(caption: string, expectedOutput: string) {
return `Expected ${caption}: ${JSON.stringify(expectedOutput)} at ${index} in ${JSON.stringify(outputs)}`;
}
function getWatchDiagnosticWithoutDate(diagnostic: Diagnostic) {
const newLines = contains(screenStartingMessageCodes, diagnostic.code)
? `${host.newLine}${host.newLine}`
: host.newLine;
return ` - ${flattenDiagnosticMessageText(diagnostic.messageText, host.newLine)}${newLines}`;
}
}
function createErrorsFoundCompilerDiagnostic(errors: ReadonlyArray<Diagnostic> | ReadonlyArray<string>) {
return errors.length === 1
? createCompilerDiagnostic(Diagnostics.Found_1_error_Watching_for_file_changes)
: createCompilerDiagnostic(Diagnostics.Found_0_errors_Watching_for_file_changes, errors.length);
}
export function checkOutputErrorsInitial(host: WatchedSystem, errors: ReadonlyArray<Diagnostic> | ReadonlyArray<string>, disableConsoleClears?: boolean, logsBeforeErrors?: string[]) {
checkOutputErrors(
host,
/*logsBeforeWatchDiagnostic*/ undefined,
createCompilerDiagnostic(Diagnostics.Starting_compilation_in_watch_mode),
logsBeforeErrors,
errors,
disableConsoleClears,
createErrorsFoundCompilerDiagnostic(errors));
}
export function checkOutputErrorsIncremental(host: WatchedSystem, errors: ReadonlyArray<Diagnostic> | ReadonlyArray<string>, disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) {
checkOutputErrors(
host,
logsBeforeWatchDiagnostic,
createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation),
logsBeforeErrors,
errors,
disableConsoleClears,
createErrorsFoundCompilerDiagnostic(errors));
}
export function checkOutputErrorsIncrementalWithExit(host: WatchedSystem, errors: ReadonlyArray<Diagnostic> | ReadonlyArray<string>, expectedExitCode: ExitStatus, disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) {
checkOutputErrors(
host,
logsBeforeWatchDiagnostic,
createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation),
logsBeforeErrors,
errors,
disableConsoleClears);
assert.equal(host.exitCode, expectedExitCode);
}
function isDiagnosticMessageChain(message: DiagnosticMessage | DiagnosticMessageChain): message is DiagnosticMessageChain {
return !!(message as DiagnosticMessageChain).messageText;
}
export function getDiagnosticOfFileFrom(file: SourceFile | undefined, start: number | undefined, length: number | undefined, message: DiagnosticMessage | DiagnosticMessageChain, ..._args: (string | number)[]): Diagnostic {
let text: DiagnosticMessageChain | string;
if (isDiagnosticMessageChain(message)) {
text = message;
}
else {
text = getLocaleSpecificMessage(message);
if (arguments.length > 4) {
text = formatStringFromArgs(text, arguments, 4);
}
}
return {
file,
start,
length,
messageText: text,
category: message.category,
code: message.code,
};
}
export function getDiagnosticWithoutFile(message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic {
return getDiagnosticOfFileFrom(/*file*/ undefined, /*start*/ undefined, /*length*/ undefined, message, ...args);
}
export function getDiagnosticOfFile(file: SourceFile, start: number, length: number, message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic {
return getDiagnosticOfFileFrom(file, start, length, message, ...args);
}
export function getDiagnosticOfFileFromProgram(program: Program, filePath: string, start: number, length: number, message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic {
return getDiagnosticOfFileFrom(program.getSourceFileByPath(toPath(filePath, program.getCurrentDirectory(), s => s.toLowerCase()))!,
start, length, message, ...args);
}
export function getUnknownCompilerOption(program: Program, configFile: File, option: string) {
const quotedOption = `"${option}"`;
return getDiagnosticOfFile(program.getCompilerOptions().configFile!, configFile.content.indexOf(quotedOption), quotedOption.length, Diagnostics.Unknown_compiler_option_0, option);
}
export function getDiagnosticModuleNotFoundOfFile(program: Program, file: File, moduleName: string) {
const quotedModuleName = `"${moduleName}"`;
return getDiagnosticOfFileFromProgram(program, file.path, file.content.indexOf(quotedModuleName), quotedModuleName.length, Diagnostics.Cannot_find_module_0, moduleName);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,448 @@
namespace ts.tscWatch {
describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch module resolution caching", () => {
it("works", () => {
const root = {
path: "/a/d/f0.ts",
content: `import {x} from "f1"`
};
const imported = {
path: "/a/f1.ts",
content: `foo()`
};
const files = [root, imported, libFile];
const host = createWatchedSystem(files);
const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD });
const f1IsNotModule = getDiagnosticOfFileFromProgram(watch(), root.path, root.content.indexOf('"f1"'), '"f1"'.length, Diagnostics.File_0_is_not_a_module, imported.path);
const cannotFindFoo = getDiagnosticOfFileFromProgram(watch(), imported.path, imported.content.indexOf("foo"), "foo".length, Diagnostics.Cannot_find_name_0, "foo");
// ensure that imported file was found
checkOutputErrorsInitial(host, [f1IsNotModule, cannotFindFoo]);
const originalFileExists = host.fileExists;
{
const newContent = `import {x} from "f1"
var x: string = 1;`;
root.content = newContent;
host.reloadFS(files);
// patch fileExists to make sure that disk is not touched
host.fileExists = notImplemented;
// trigger synchronization to make sure that import will be fetched from the cache
host.runQueuedTimeoutCallbacks();
// ensure file has correct number of errors after edit
checkOutputErrorsIncremental(host, [
f1IsNotModule,
getDiagnosticOfFileFromProgram(watch(), root.path, newContent.indexOf("var x") + "var ".length, "x".length, Diagnostics.Type_0_is_not_assignable_to_type_1, 1, "string"),
cannotFindFoo
]);
}
{
let fileExistsIsCalled = false;
host.fileExists = (fileName): boolean => {
if (fileName === "lib.d.ts") {
return false;
}
fileExistsIsCalled = true;
assert.isTrue(fileName.indexOf("/f2.") !== -1);
return originalFileExists.call(host, fileName);
};
root.content = `import {x} from "f2"`;
host.reloadFS(files);
// trigger synchronization to make sure that LSHost will try to find 'f2' module on disk
host.runQueuedTimeoutCallbacks();
// ensure file has correct number of errors after edit
checkOutputErrorsIncremental(host, [
getDiagnosticModuleNotFoundOfFile(watch(), root, "f2")
]);
assert.isTrue(fileExistsIsCalled);
}
{
let fileExistsCalled = false;
host.fileExists = (fileName): boolean => {
if (fileName === "lib.d.ts") {
return false;
}
fileExistsCalled = true;
assert.isTrue(fileName.indexOf("/f1.") !== -1);
return originalFileExists.call(host, fileName);
};
const newContent = `import {x} from "f1"`;
root.content = newContent;
host.reloadFS(files);
host.runQueuedTimeoutCallbacks();
checkOutputErrorsIncremental(host, [f1IsNotModule, cannotFindFoo]);
assert.isTrue(fileExistsCalled);
}
});
it("loads missing files from disk", () => {
const root = {
path: `/a/foo.ts`,
content: `import {x} from "bar"`
};
const imported = {
path: `/a/bar.d.ts`,
content: `export const y = 1;`
};
const files = [root, libFile];
const host = createWatchedSystem(files);
const originalFileExists = host.fileExists;
let fileExistsCalledForBar = false;
host.fileExists = fileName => {
if (fileName === "lib.d.ts") {
return false;
}
if (!fileExistsCalledForBar) {
fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1;
}
return originalFileExists.call(host, fileName);
};
const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD });
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
checkOutputErrorsInitial(host, [
getDiagnosticModuleNotFoundOfFile(watch(), root, "bar")
]);
fileExistsCalledForBar = false;
root.content = `import {y} from "bar"`;
host.reloadFS(files.concat(imported));
host.runQueuedTimeoutCallbacks();
checkOutputErrorsIncremental(host, emptyArray);
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called.");
});
it("should compile correctly when resolved module goes missing and then comes back (module is not part of the root)", () => {
const root = {
path: `/a/foo.ts`,
content: `import {x} from "bar"`
};
const imported = {
path: `/a/bar.d.ts`,
content: `export const y = 1;export const x = 10;`
};
const files = [root, libFile];
const filesWithImported = files.concat(imported);
const host = createWatchedSystem(filesWithImported);
const originalFileExists = host.fileExists;
let fileExistsCalledForBar = false;
host.fileExists = fileName => {
if (fileName === "lib.d.ts") {
return false;
}
if (!fileExistsCalledForBar) {
fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1;
}
return originalFileExists.call(host, fileName);
};
const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD });
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
checkOutputErrorsInitial(host, emptyArray);
fileExistsCalledForBar = false;
host.reloadFS(files);
host.runQueuedTimeoutCallbacks();
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called.");
checkOutputErrorsIncremental(host, [
getDiagnosticModuleNotFoundOfFile(watch(), root, "bar")
]);
fileExistsCalledForBar = false;
host.reloadFS(filesWithImported);
host.checkTimeoutQueueLengthAndRun(1);
checkOutputErrorsIncremental(host, emptyArray);
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called.");
});
it("works when module resolution changes to ambient module", () => {
const root = {
path: "/a/b/foo.ts",
content: `import * as fs from "fs";`
};
const packageJson = {
path: "/a/b/node_modules/@types/node/package.json",
content: `
{
"main": ""
}
`
};
const nodeType = {
path: "/a/b/node_modules/@types/node/index.d.ts",
content: `
declare module "fs" {
export interface Stats {
isFile(): boolean;
}
}`
};
const files = [root, libFile];
const filesWithNodeType = files.concat(packageJson, nodeType);
const host = createWatchedSystem(files, { currentDirectory: "/a/b" });
const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { });
checkOutputErrorsInitial(host, [
getDiagnosticModuleNotFoundOfFile(watch(), root, "fs")
]);
host.reloadFS(filesWithNodeType);
host.runQueuedTimeoutCallbacks();
checkOutputErrorsIncremental(host, emptyArray);
});
it("works when included file with ambient module changes", () => {
const root = {
path: "/a/b/foo.ts",
content: `
import * as fs from "fs";
import * as u from "url";
`
};
const file = {
path: "/a/b/bar.d.ts",
content: `
declare module "url" {
export interface Url {
href?: string;
}
}
`
};
const fileContentWithFS = `
declare module "fs" {
export interface Stats {
isFile(): boolean;
}
}
`;
const files = [root, file, libFile];
const host = createWatchedSystem(files, { currentDirectory: "/a/b" });
const watch = createWatchOfFilesAndCompilerOptions([root.path, file.path], host, {});
checkOutputErrorsInitial(host, [
getDiagnosticModuleNotFoundOfFile(watch(), root, "fs")
]);
file.content += fileContentWithFS;
host.reloadFS(files);
host.runQueuedTimeoutCallbacks();
checkOutputErrorsIncremental(host, emptyArray);
});
it("works when reusing program with files from external library", () => {
interface ExpectedFile { path: string; isExpectedToEmit?: boolean; content?: string; }
const configDir = "/a/b/projects/myProject/src/";
const file1: File = {
path: configDir + "file1.ts",
content: 'import module1 = require("module1");\nmodule1("hello");'
};
const file2: File = {
path: configDir + "file2.ts",
content: 'import module11 = require("module1");\nmodule11("hello");'
};
const module1: File = {
path: "/a/b/projects/myProject/node_modules/module1/index.js",
content: "module.exports = options => { return options.toString(); }"
};
const configFile: File = {
path: configDir + "tsconfig.json",
content: JSON.stringify({
compilerOptions: {
allowJs: true,
rootDir: ".",
outDir: "../dist",
moduleResolution: "node",
maxNodeModuleJsDepth: 1
}
})
};
const outDirFolder = "/a/b/projects/myProject/dist/";
const programFiles = [file1, file2, module1, libFile];
const host = createWatchedSystem(programFiles.concat(configFile), { currentDirectory: "/a/b/projects/myProject/" });
const watch = createWatchOfConfigFile(configFile.path, host);
checkProgramActualFiles(watch(), programFiles.map(f => f.path));
checkOutputErrorsInitial(host, emptyArray);
const expectedFiles: ExpectedFile[] = [
createExpectedEmittedFile(file1),
createExpectedEmittedFile(file2),
createExpectedToNotEmitFile("index.js"),
createExpectedToNotEmitFile("src/index.js"),
createExpectedToNotEmitFile("src/file1.js"),
createExpectedToNotEmitFile("src/file2.js"),
createExpectedToNotEmitFile("lib.js"),
createExpectedToNotEmitFile("lib.d.ts")
];
verifyExpectedFiles(expectedFiles);
file1.content += "\n;";
expectedFiles[0].content += ";\n"; // Only emit file1 with this change
expectedFiles[1].isExpectedToEmit = false;
host.reloadFS(programFiles.concat(configFile));
host.runQueuedTimeoutCallbacks();
checkProgramActualFiles(watch(), programFiles.map(f => f.path));
checkOutputErrorsIncremental(host, emptyArray);
verifyExpectedFiles(expectedFiles);
function verifyExpectedFiles(expectedFiles: ExpectedFile[]) {
forEach(expectedFiles, f => {
assert.equal(!!host.fileExists(f.path), f.isExpectedToEmit, "File " + f.path + " is expected to " + (f.isExpectedToEmit ? "emit" : "not emit"));
if (f.isExpectedToEmit) {
assert.equal(host.readFile(f.path), f.content, "Expected contents of " + f.path);
}
});
}
function createExpectedToNotEmitFile(fileName: string): ExpectedFile {
return {
path: outDirFolder + fileName,
isExpectedToEmit: false
};
}
function createExpectedEmittedFile(file: File): ExpectedFile {
return {
path: removeFileExtension(file.path.replace(configDir, outDirFolder)) + Extension.Js,
isExpectedToEmit: true,
content: '"use strict";\nexports.__esModule = true;\n' + file.content.replace("import", "var") + "\n"
};
}
});
it("works when renaming node_modules folder that already contains @types folder", () => {
const currentDirectory = "/user/username/projects/myproject";
const file: File = {
path: `${currentDirectory}/a.ts`,
content: `import * as q from "qqq";`
};
const module: File = {
path: `${currentDirectory}/node_modules2/@types/qqq/index.d.ts`,
content: "export {}"
};
const files = [file, module, libFile];
const host = createWatchedSystem(files, { currentDirectory });
const watch = createWatchOfFilesAndCompilerOptions([file.path], host);
checkProgramActualFiles(watch(), [file.path, libFile.path]);
checkOutputErrorsInitial(host, [getDiagnosticModuleNotFoundOfFile(watch(), file, "qqq")]);
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
checkWatchedDirectories(host, [`${currentDirectory}/node_modules`, `${currentDirectory}/node_modules/@types`], /*recursive*/ true);
host.renameFolder(`${currentDirectory}/node_modules2`, `${currentDirectory}/node_modules`);
host.runQueuedTimeoutCallbacks();
checkProgramActualFiles(watch(), [file.path, libFile.path, `${currentDirectory}/node_modules/@types/qqq/index.d.ts`]);
checkOutputErrorsIncremental(host, emptyArray);
});
describe("ignores files/folder changes in node_modules that start with '.'", () => {
const projectPath = "/user/username/projects/project";
const npmCacheFile: File = {
path: `${projectPath}/node_modules/.cache/babel-loader/89c02171edab901b9926470ba6d5677e.ts`,
content: JSON.stringify({ something: 10 })
};
const file1: File = {
path: `${projectPath}/test.ts`,
content: `import { x } from "somemodule";`
};
const file2: File = {
path: `${projectPath}/node_modules/somemodule/index.d.ts`,
content: `export const x = 10;`
};
const files = [libFile, file1, file2];
const expectedFiles = files.map(f => f.path);
it("when watching node_modules in inferred project for failed lookup", () => {
const host = createWatchedSystem(files);
const watch = createWatchOfFilesAndCompilerOptions([file1.path], host, {}, /*maxNumberOfFilesToIterateForInvalidation*/ 1);
checkProgramActualFiles(watch(), expectedFiles);
host.checkTimeoutQueueLength(0);
host.ensureFileOrFolder(npmCacheFile);
host.checkTimeoutQueueLength(0);
});
it("when watching node_modules as part of wild card directories in config project", () => {
const config: File = {
path: `${projectPath}/tsconfig.json`,
content: "{}"
};
const host = createWatchedSystem(files.concat(config));
const watch = createWatchOfConfigFile(config.path, host);
checkProgramActualFiles(watch(), expectedFiles);
host.checkTimeoutQueueLength(0);
host.ensureFileOrFolder(npmCacheFile);
host.checkTimeoutQueueLength(0);
});
});
});
describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch with modules linked to sibling folder", () => {
const projectRoot = "/user/username/projects/project";
const mainPackageRoot = `${projectRoot}/main`;
const linkedPackageRoot = `${projectRoot}/linked-package`;
const mainFile: File = {
path: `${mainPackageRoot}/index.ts`,
content: "import { Foo } from '@scoped/linked-package'"
};
const config: File = {
path: `${mainPackageRoot}/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { module: "commonjs", moduleResolution: "node", baseUrl: ".", rootDir: "." },
files: ["index.ts"]
})
};
const linkedPackageInMain: SymLink = {
path: `${mainPackageRoot}/node_modules/@scoped/linked-package`,
symLink: `${linkedPackageRoot}`
};
const linkedPackageJson: File = {
path: `${linkedPackageRoot}/package.json`,
content: JSON.stringify({ name: "@scoped/linked-package", version: "0.0.1", types: "dist/index.d.ts", main: "dist/index.js" })
};
const linkedPackageIndex: File = {
path: `${linkedPackageRoot}/dist/index.d.ts`,
content: "export * from './other';"
};
const linkedPackageOther: File = {
path: `${linkedPackageRoot}/dist/other.d.ts`,
content: 'export declare const Foo = "BAR";'
};
it("verify watched directories", () => {
const files = [libFile, mainFile, config, linkedPackageInMain, linkedPackageJson, linkedPackageIndex, linkedPackageOther];
const host = createWatchedSystem(files, { currentDirectory: mainPackageRoot });
createWatchOfConfigFile("tsconfig.json", host);
checkWatchedFilesDetailed(host, [libFile.path, mainFile.path, config.path, linkedPackageIndex.path, linkedPackageOther.path], 1);
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, [`${mainPackageRoot}/@scoped`, `${mainPackageRoot}/node_modules`, linkedPackageRoot, `${mainPackageRoot}/node_modules/@types`, `${projectRoot}/node_modules/@types`], 1, /*recursive*/ true);
});
});
}

View File

@ -0,0 +1,40 @@
namespace ts.tscWatch {
describe("unittests:: tsc-watch:: watchAPI:: tsc-watch with custom module resolution", () => {
const projectRoot = "/user/username/projects/project";
const configFileJson: any = {
compilerOptions: { module: "commonjs", resolveJsonModule: true },
files: ["index.ts"]
};
const mainFile: File = {
path: `${projectRoot}/index.ts`,
content: "import settings from './settings.json';"
};
const config: File = {
path: `${projectRoot}/tsconfig.json`,
content: JSON.stringify(configFileJson)
};
const settingsJson: File = {
path: `${projectRoot}/settings.json`,
content: JSON.stringify({ content: "Print this" })
};
it("verify that module resolution with json extension works when returned without extension", () => {
const files = [libFile, mainFile, config, settingsJson];
const host = createWatchedSystem(files, { currentDirectory: projectRoot });
const compilerHost = createWatchCompilerHostOfConfigFile(config.path, {}, host);
const parsedCommandResult = parseJsonConfigFileContent(configFileJson, host, config.path);
compilerHost.resolveModuleNames = (moduleNames, containingFile) => moduleNames.map(m => {
const result = resolveModuleName(m, containingFile, parsedCommandResult.options, compilerHost);
const resolvedModule = result.resolvedModule!;
return {
resolvedFileName: resolvedModule.resolvedFileName,
isExternalLibraryImport: resolvedModule.isExternalLibraryImport,
originalFileName: resolvedModule.originalPath,
};
});
const watch = createWatchProgram(compilerHost);
const program = watch.getCurrentProgram().getProgram();
checkProgramActualFiles(program, [mainFile.path, libFile.path, settingsJson.path]);
});
});
}

View File

@ -0,0 +1,178 @@
namespace ts.tscWatch {
import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory;
describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different polling/non polling options", () => {
it("watchFile using dynamic priority polling", () => {
const projectFolder = "/a/username/project";
const file1: File = {
path: `${projectFolder}/typescript.ts`,
content: "var z = 10;"
};
const files = [file1, libFile];
const environmentVariables = createMap<string>();
environmentVariables.set("TSC_WATCHFILE", "DynamicPriorityPolling");
const host = createWatchedSystem(files, { environmentVariables });
const watch = createWatchOfFilesAndCompilerOptions([file1.path], host);
const initialProgram = watch();
verifyProgram();
const mediumPollingIntervalThreshold = unchangedPollThresholds[PollingInterval.Medium];
for (let index = 0; index < mediumPollingIntervalThreshold; index++) {
// Transition libFile and file1 to low priority queue
host.checkTimeoutQueueLengthAndRun(1);
assert.deepEqual(watch(), initialProgram);
}
// Make a change to file
file1.content = "var zz30 = 100;";
host.reloadFS(files);
// This should detect change in the file
host.checkTimeoutQueueLengthAndRun(1);
assert.deepEqual(watch(), initialProgram);
// Callbacks: medium priority + high priority queue and scheduled program update
host.checkTimeoutQueueLengthAndRun(3);
// During this timeout the file would be detected as unchanged
let fileUnchangeDetected = 1;
const newProgram = watch();
assert.notStrictEqual(newProgram, initialProgram);
verifyProgram();
const outputFile1 = changeExtension(file1.path, ".js");
assert.isTrue(host.fileExists(outputFile1));
assert.equal(host.readFile(outputFile1), file1.content + host.newLine);
const newThreshold = unchangedPollThresholds[PollingInterval.Low] + mediumPollingIntervalThreshold;
for (; fileUnchangeDetected < newThreshold; fileUnchangeDetected++) {
// For high + Medium/low polling interval
host.checkTimeoutQueueLengthAndRun(2);
assert.deepEqual(watch(), newProgram);
}
// Everything goes in high polling interval queue
host.checkTimeoutQueueLengthAndRun(1);
assert.deepEqual(watch(), newProgram);
function verifyProgram() {
checkProgramActualFiles(watch(), files.map(f => f.path));
checkWatchedFiles(host, []);
checkWatchedDirectories(host, [], /*recursive*/ false);
checkWatchedDirectories(host, [], /*recursive*/ true);
}
});
describe("tsc-watch when watchDirectories implementation", () => {
function verifyRenamingFileInSubFolder(tscWatchDirectory: Tsc_WatchDirectory) {
const projectFolder = "/a/username/project";
const projectSrcFolder = `${projectFolder}/src`;
const configFile: File = {
path: `${projectFolder}/tsconfig.json`,
content: "{}"
};
const file: File = {
path: `${projectSrcFolder}/file1.ts`,
content: ""
};
const programFiles = [file, libFile];
const files = [file, configFile, libFile];
const environmentVariables = createMap<string>();
environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory);
const host = createWatchedSystem(files, { environmentVariables });
const watch = createWatchOfConfigFile(configFile.path, host);
const projectFolders = [projectFolder, projectSrcFolder, `${projectFolder}/node_modules/@types`];
// Watching files config file, file, lib file
const expectedWatchedFiles = files.map(f => f.path);
const expectedWatchedDirectories = tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory ? projectFolders : emptyArray;
if (tscWatchDirectory === Tsc_WatchDirectory.WatchFile) {
expectedWatchedFiles.push(...projectFolders);
}
verifyProgram(checkOutputErrorsInitial);
// Rename the file:
file.path = file.path.replace("file1.ts", "file2.ts");
expectedWatchedFiles[0] = file.path;
host.reloadFS(files);
if (tscWatchDirectory === Tsc_WatchDirectory.DynamicPolling) {
// With dynamic polling the fs change would be detected only by running timeouts
host.runQueuedTimeoutCallbacks();
}
// Delayed update program
host.runQueuedTimeoutCallbacks();
verifyProgram(checkOutputErrorsIncremental);
function verifyProgram(checkOutputErrors: (host: WatchedSystem, errors: ReadonlyArray<Diagnostic>) => void) {
checkProgramActualFiles(watch(), programFiles.map(f => f.path));
checkOutputErrors(host, emptyArray);
const outputFile = changeExtension(file.path, ".js");
assert(host.fileExists(outputFile));
assert.equal(host.readFile(outputFile), file.content);
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
// Watching config file, file, lib file and directories
checkWatchedFilesDetailed(host, expectedWatchedFiles, 1);
checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, 1, /*recursive*/ false);
}
}
it("uses watchFile when renaming file in subfolder", () => {
verifyRenamingFileInSubFolder(Tsc_WatchDirectory.WatchFile);
});
it("uses non recursive watchDirectory when renaming file in subfolder", () => {
verifyRenamingFileInSubFolder(Tsc_WatchDirectory.NonRecursiveWatchDirectory);
});
it("uses non recursive dynamic polling when renaming file in subfolder", () => {
verifyRenamingFileInSubFolder(Tsc_WatchDirectory.DynamicPolling);
});
it("when there are symlinks to folders in recursive folders", () => {
const cwd = "/home/user/projects/myproject";
const file1: File = {
path: `${cwd}/src/file.ts`,
content: `import * as a from "a"`
};
const tsconfig: File = {
path: `${cwd}/tsconfig.json`,
content: `{ "compilerOptions": { "extendedDiagnostics": true, "traceResolution": true }}`
};
const realA: File = {
path: `${cwd}/node_modules/reala/index.d.ts`,
content: `export {}`
};
const realB: File = {
path: `${cwd}/node_modules/realb/index.d.ts`,
content: `export {}`
};
const symLinkA: SymLink = {
path: `${cwd}/node_modules/a`,
symLink: `${cwd}/node_modules/reala`
};
const symLinkB: SymLink = {
path: `${cwd}/node_modules/b`,
symLink: `${cwd}/node_modules/realb`
};
const symLinkBInA: SymLink = {
path: `${cwd}/node_modules/reala/node_modules/b`,
symLink: `${cwd}/node_modules/b`
};
const symLinkAInB: SymLink = {
path: `${cwd}/node_modules/realb/node_modules/a`,
symLink: `${cwd}/node_modules/a`
};
const files = [file1, tsconfig, realA, realB, symLinkA, symLinkB, symLinkBInA, symLinkAInB];
const environmentVariables = createMap<string>();
environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory);
const host = createWatchedSystem(files, { environmentVariables, currentDirectory: cwd });
createWatchOfConfigFile("tsconfig.json", host);
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
checkWatchedDirectories(host, [cwd, `${cwd}/node_modules`, `${cwd}/node_modules/@types`, `${cwd}/node_modules/reala`, `${cwd}/node_modules/realb`,
`${cwd}/node_modules/reala/node_modules`, `${cwd}/node_modules/realb/node_modules`, `${cwd}/src`], /*recursive*/ false);
});
});
});
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,271 @@
namespace ts.projectSystem {
describe("unittests:: tsserver:: cancellationToken", () => {
// Disable sourcemap support for the duration of the test, as sourcemapping the errors generated during this test is slow and not something we care to test
let oldPrepare: AnyFunction;
before(() => {
oldPrepare = (Error as any).prepareStackTrace;
delete (Error as any).prepareStackTrace;
});
after(() => {
(Error as any).prepareStackTrace = oldPrepare;
});
it("is attached to request", () => {
const f1 = {
path: "/a/b/app.ts",
content: "let xyz = 1;"
};
const host = createServerHost([f1]);
let expectedRequestId: number;
const cancellationToken: server.ServerCancellationToken = {
isCancellationRequested: () => false,
setRequest: requestId => {
if (expectedRequestId === undefined) {
assert.isTrue(false, "unexpected call");
}
assert.equal(requestId, expectedRequestId);
},
resetRequest: noop
};
const session = createSession(host, { cancellationToken });
expectedRequestId = session.getNextSeq();
session.executeCommandSeq(<server.protocol.OpenRequest>{
command: "open",
arguments: { file: f1.path }
});
expectedRequestId = session.getNextSeq();
session.executeCommandSeq(<server.protocol.GeterrRequest>{
command: "geterr",
arguments: { files: [f1.path] }
});
expectedRequestId = session.getNextSeq();
session.executeCommandSeq(<server.protocol.OccurrencesRequest>{
command: "occurrences",
arguments: { file: f1.path, line: 1, offset: 6 }
});
expectedRequestId = 2;
host.runQueuedImmediateCallbacks();
expectedRequestId = 2;
host.runQueuedImmediateCallbacks();
});
it("Geterr is cancellable", () => {
const f1 = {
path: "/a/app.ts",
content: "let x = 1"
};
const config = {
path: "/a/tsconfig.json",
content: JSON.stringify({
compilerOptions: {}
})
};
const cancellationToken = new TestServerCancellationToken();
const host = createServerHost([f1, config]);
const session = createSession(host, {
canUseEvents: true,
eventHandler: noop,
cancellationToken
});
{
session.executeCommandSeq(<protocol.OpenRequest>{
command: "open",
arguments: { file: f1.path }
});
// send geterr for missing file
session.executeCommandSeq(<protocol.GeterrRequest>{
command: "geterr",
arguments: { files: ["/a/missing"] }
});
// no files - expect 'completed' event
assert.equal(host.getOutput().length, 1, "expect 1 message");
verifyRequestCompleted(session.getSeq(), 0);
}
{
const getErrId = session.getNextSeq();
// send geterr for a valid file
session.executeCommandSeq(<protocol.GeterrRequest>{
command: "geterr",
arguments: { files: [f1.path] }
});
assert.equal(host.getOutput().length, 0, "expect 0 messages");
// run new request
session.executeCommandSeq(<protocol.ProjectInfoRequest>{
command: "projectInfo",
arguments: { file: f1.path }
});
session.clearMessages();
// cancel previously issued Geterr
cancellationToken.setRequestToCancel(getErrId);
host.runQueuedTimeoutCallbacks();
assert.equal(host.getOutput().length, 1, "expect 1 message");
verifyRequestCompleted(getErrId, 0);
cancellationToken.resetToken();
}
{
const getErrId = session.getNextSeq();
session.executeCommandSeq(<protocol.GeterrRequest>{
command: "geterr",
arguments: { files: [f1.path] }
});
assert.equal(host.getOutput().length, 0, "expect 0 messages");
// run first step
host.runQueuedTimeoutCallbacks();
assert.equal(host.getOutput().length, 1, "expect 1 message");
const e1 = <protocol.Event>getMessage(0);
assert.equal(e1.event, "syntaxDiag");
session.clearMessages();
cancellationToken.setRequestToCancel(getErrId);
host.runQueuedImmediateCallbacks();
assert.equal(host.getOutput().length, 1, "expect 1 message");
verifyRequestCompleted(getErrId, 0);
cancellationToken.resetToken();
}
{
const getErrId = session.getNextSeq();
session.executeCommandSeq(<protocol.GeterrRequest>{
command: "geterr",
arguments: { files: [f1.path] }
});
assert.equal(host.getOutput().length, 0, "expect 0 messages");
// run first step
host.runQueuedTimeoutCallbacks();
assert.equal(host.getOutput().length, 1, "expect 1 message");
const e1 = <protocol.Event>getMessage(0);
assert.equal(e1.event, "syntaxDiag");
session.clearMessages();
// the semanticDiag message
host.runQueuedImmediateCallbacks();
assert.equal(host.getOutput().length, 1);
const e2 = <protocol.Event>getMessage(0);
assert.equal(e2.event, "semanticDiag");
session.clearMessages();
host.runQueuedImmediateCallbacks(1);
assert.equal(host.getOutput().length, 2);
const e3 = <protocol.Event>getMessage(0);
assert.equal(e3.event, "suggestionDiag");
verifyRequestCompleted(getErrId, 1);
cancellationToken.resetToken();
}
{
const getErr1 = session.getNextSeq();
session.executeCommandSeq(<protocol.GeterrRequest>{
command: "geterr",
arguments: { files: [f1.path] }
});
assert.equal(host.getOutput().length, 0, "expect 0 messages");
// run first step
host.runQueuedTimeoutCallbacks();
assert.equal(host.getOutput().length, 1, "expect 1 message");
const e1 = <protocol.Event>getMessage(0);
assert.equal(e1.event, "syntaxDiag");
session.clearMessages();
session.executeCommandSeq(<protocol.GeterrRequest>{
command: "geterr",
arguments: { files: [f1.path] }
});
// make sure that getErr1 is completed
verifyRequestCompleted(getErr1, 0);
}
function verifyRequestCompleted(expectedSeq: number, n: number) {
const event = <protocol.RequestCompletedEvent>getMessage(n);
assert.equal(event.event, "requestCompleted");
assert.equal(event.body.request_seq, expectedSeq, "expectedSeq");
session.clearMessages();
}
function getMessage(n: number) {
return JSON.parse(server.extractMessage(host.getOutput()[n]));
}
});
it("Lower priority tasks are cancellable", () => {
const f1 = {
path: "/a/app.ts",
content: `{ let x = 1; } var foo = "foo"; var bar = "bar"; var fooBar = "fooBar";`
};
const config = {
path: "/a/tsconfig.json",
content: JSON.stringify({
compilerOptions: {}
})
};
const cancellationToken = new TestServerCancellationToken(/*cancelAfterRequest*/ 3);
const host = createServerHost([f1, config]);
const session = createSession(host, {
canUseEvents: true,
eventHandler: noop,
cancellationToken,
throttleWaitMilliseconds: 0
});
{
session.executeCommandSeq(<protocol.OpenRequest>{
command: "open",
arguments: { file: f1.path }
});
// send navbar request (normal priority)
session.executeCommandSeq(<protocol.NavBarRequest>{
command: "navbar",
arguments: { file: f1.path }
});
// ensure the nav bar request can be canceled
verifyExecuteCommandSeqIsCancellable(<protocol.NavBarRequest>{
command: "navbar",
arguments: { file: f1.path }
});
// send outlining spans request (normal priority)
session.executeCommandSeq(<protocol.OutliningSpansRequestFull>{
command: "outliningSpans",
arguments: { file: f1.path }
});
// ensure the outlining spans request can be canceled
verifyExecuteCommandSeqIsCancellable(<protocol.OutliningSpansRequestFull>{
command: "outliningSpans",
arguments: { file: f1.path }
});
}
function verifyExecuteCommandSeqIsCancellable<T extends server.protocol.Request>(request: Partial<T>) {
// Set the next request to be cancellable
// The cancellation token will cancel the request the third time
// isCancellationRequested() is called.
cancellationToken.setRequestToCancel(session.getNextSeq());
let operationCanceledExceptionThrown = false;
try {
session.executeCommandSeq(request);
}
catch (e) {
assert(e instanceof OperationCanceledException);
operationCanceledExceptionThrown = true;
}
assert(operationCanceledExceptionThrown, "Operation Canceled Exception not thrown for request: " + JSON.stringify(request));
}
});
});
}

View File

@ -6,7 +6,7 @@ namespace ts.projectSystem {
return new TestTypingsInstaller("/a/data/", /*throttleLimit*/5, host);
}
describe("CompileOnSave affected list", () => {
describe("unittests:: tsserver:: compileOnSave:: affected list", () => {
function sendAffectedFileRequestAndCheckResult(session: server.Session, request: server.protocol.Request, expectedFileList: { projectFileName: string, files: File[] }[]) {
const response = session.executeCommand(request).response as server.protocol.CompileOnSaveAffectedFileListSingleProject[];
const actualResult = response.sort((list1, list2) => compareStringsCaseSensitive(list1.projectFileName, list2.projectFileName));
@ -502,9 +502,57 @@ namespace ts.projectSystem {
]);
});
});
describe("tsserverProjectSystem emit with outFile or out setting", () => {
function test(opts: CompilerOptions, expectedUsesOutFile: boolean) {
const f1 = {
path: "/a/a.ts",
content: "let x = 1"
};
const f2 = {
path: "/a/b.ts",
content: "let y = 1"
};
const config = {
path: "/a/tsconfig.json",
content: JSON.stringify({
compilerOptions: opts,
compileOnSave: true
})
};
const host = createServerHost([f1, f2, config]);
const session = projectSystem.createSession(host);
session.executeCommand(<protocol.OpenRequest>{
seq: 1,
type: "request",
command: "open",
arguments: { file: f1.path }
});
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 });
const { response } = session.executeCommand(<protocol.CompileOnSaveAffectedFileListRequest>{
seq: 2,
type: "request",
command: "compileOnSaveAffectedFileList",
arguments: { file: f1.path }
});
assert.equal((<protocol.CompileOnSaveAffectedFileListSingleProject[]>response).length, 1, "expected output for 1 project");
assert.equal((<protocol.CompileOnSaveAffectedFileListSingleProject[]>response)[0].fileNames.length, 2, "expected output for 1 project");
assert.equal((<protocol.CompileOnSaveAffectedFileListSingleProject[]>response)[0].projectUsesOutFile, expectedUsesOutFile, "usesOutFile");
}
it("projectUsesOutFile should not be returned if not set", () => {
test({}, /*expectedUsesOutFile*/ false);
});
it("projectUsesOutFile should be true if outFile is set", () => {
test({ outFile: "/a/out.js" }, /*expectedUsesOutFile*/ true);
});
it("projectUsesOutFile should be true if out is set", () => {
test({ out: "/a/out.js" }, /*expectedUsesOutFile*/ true);
});
});
});
describe("EmitFile test", () => {
describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => {
it("should respect line endings", () => {
test("\n");
test("\r\n");
@ -646,4 +694,100 @@ namespace ts.projectSystem {
}
});
});
describe("unittests:: tsserver:: compileOnSave:: CompileOnSaveAffectedFileListRequest with and without projectFileName in request", () => {
const projectRoot = "/user/username/projects/myproject";
const core: File = {
path: `${projectRoot}/core/core.ts`,
content: "let z = 10;"
};
const app1: File = {
path: `${projectRoot}/app1/app.ts`,
content: "let x = 10;"
};
const app2: File = {
path: `${projectRoot}/app2/app.ts`,
content: "let y = 10;"
};
const app1Config: File = {
path: `${projectRoot}/app1/tsconfig.json`,
content: JSON.stringify({
files: ["app.ts", "../core/core.ts"],
compilerOptions: { outFile: "build/output.js" },
compileOnSave: true
})
};
const app2Config: File = {
path: `${projectRoot}/app2/tsconfig.json`,
content: JSON.stringify({
files: ["app.ts", "../core/core.ts"],
compilerOptions: { outFile: "build/output.js" },
compileOnSave: true
})
};
const files = [libFile, core, app1, app2, app1Config, app2Config];
function insertString(session: TestSession, file: File) {
session.executeCommandSeq<protocol.ChangeRequest>({
command: protocol.CommandTypes.Change,
arguments: {
file: file.path,
line: 1,
offset: 1,
endLine: 1,
endOffset: 1,
insertString: "let k = 1"
}
});
}
function getSession() {
const host = createServerHost(files);
const session = createSession(host);
openFilesForSession([app1, app2, core], session);
const service = session.getProjectService();
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 });
const project1 = service.configuredProjects.get(app1Config.path)!;
const project2 = service.configuredProjects.get(app2Config.path)!;
checkProjectActualFiles(project1, [libFile.path, app1.path, core.path, app1Config.path]);
checkProjectActualFiles(project2, [libFile.path, app2.path, core.path, app2Config.path]);
insertString(session, app1);
insertString(session, app2);
assert.equal(project1.dirty, true);
assert.equal(project2.dirty, true);
return session;
}
it("when projectFile is specified", () => {
const session = getSession();
const response = session.executeCommandSeq<protocol.CompileOnSaveAffectedFileListRequest>({
command: protocol.CommandTypes.CompileOnSaveAffectedFileList,
arguments: {
file: core.path,
projectFileName: app1Config.path
}
}).response;
assert.deepEqual(response, [
{ projectFileName: app1Config.path, fileNames: [core.path, app1.path], projectUsesOutFile: true }
]);
assert.equal(session.getProjectService().configuredProjects.get(app1Config.path)!.dirty, false);
assert.equal(session.getProjectService().configuredProjects.get(app2Config.path)!.dirty, true);
});
it("when projectFile is not specified", () => {
const session = getSession();
const response = session.executeCommandSeq<protocol.CompileOnSaveAffectedFileListRequest>({
command: protocol.CommandTypes.CompileOnSaveAffectedFileList,
arguments: {
file: core.path
}
}).response;
assert.deepEqual(response, [
{ projectFileName: app1Config.path, fileNames: [core.path, app1.path], projectUsesOutFile: true },
{ projectFileName: app2Config.path, fileNames: [core.path, app2.path], projectUsesOutFile: true }
]);
assert.equal(session.getProjectService().configuredProjects.get(app1Config.path)!.dirty, false);
assert.equal(session.getProjectService().configuredProjects.get(app2Config.path)!.dirty, false);
});
});
}

View File

@ -0,0 +1,122 @@
namespace ts.projectSystem {
describe("unittests:: tsserver:: completions", () => {
it("works", () => {
const aTs: File = {
path: "/a.ts",
content: "export const foo = 0;",
};
const bTs: File = {
path: "/b.ts",
content: "foo",
};
const tsconfig: File = {
path: "/tsconfig.json",
content: "{}",
};
const session = createSession(createServerHost([aTs, bTs, tsconfig]));
openFilesForSession([aTs, bTs], session);
const requestLocation: protocol.FileLocationRequestArgs = {
file: bTs.path,
line: 1,
offset: 3,
};
const response = executeSessionRequest<protocol.CompletionsRequest, protocol.CompletionInfoResponse>(session, protocol.CommandTypes.CompletionInfo, {
...requestLocation,
includeExternalModuleExports: true,
prefix: "foo",
});
const entry: protocol.CompletionEntry = {
hasAction: true,
insertText: undefined,
isRecommended: undefined,
kind: ScriptElementKind.constElement,
kindModifiers: ScriptElementKindModifier.exportedModifier,
name: "foo",
replacementSpan: undefined,
sortText: "0",
source: "/a",
};
assert.deepEqual<protocol.CompletionInfo | undefined>(response, {
isGlobalCompletion: true,
isMemberCompletion: false,
isNewIdentifierLocation: false,
entries: [entry],
});
const detailsRequestArgs: protocol.CompletionDetailsRequestArgs = {
...requestLocation,
entryNames: [{ name: "foo", source: "/a" }],
};
const detailsResponse = executeSessionRequest<protocol.CompletionDetailsRequest, protocol.CompletionDetailsResponse>(session, protocol.CommandTypes.CompletionDetails, detailsRequestArgs);
const detailsCommon: protocol.CompletionEntryDetails & CompletionEntryDetails = {
displayParts: [
keywordPart(SyntaxKind.ConstKeyword),
spacePart(),
displayPart("foo", SymbolDisplayPartKind.localName),
punctuationPart(SyntaxKind.ColonToken),
spacePart(),
displayPart("0", SymbolDisplayPartKind.stringLiteral),
],
documentation: emptyArray,
kind: ScriptElementKind.constElement,
kindModifiers: ScriptElementKindModifier.exportedModifier,
name: "foo",
source: [{ text: "./a", kind: "text" }],
tags: undefined,
};
assert.deepEqual<ReadonlyArray<protocol.CompletionEntryDetails> | undefined>(detailsResponse, [
{
codeActions: [
{
description: `Import 'foo' from module "./a"`,
changes: [
{
fileName: "/b.ts",
textChanges: [
{
start: { line: 1, offset: 1 },
end: { line: 1, offset: 1 },
newText: 'import { foo } from "./a";\n\n',
},
],
},
],
commands: undefined,
},
],
...detailsCommon,
},
]);
interface CompletionDetailsFullRequest extends protocol.FileLocationRequest {
readonly command: protocol.CommandTypes.CompletionDetailsFull;
readonly arguments: protocol.CompletionDetailsRequestArgs;
}
interface CompletionDetailsFullResponse extends protocol.Response {
readonly body?: ReadonlyArray<CompletionEntryDetails>;
}
const detailsFullResponse = executeSessionRequest<CompletionDetailsFullRequest, CompletionDetailsFullResponse>(session, protocol.CommandTypes.CompletionDetailsFull, detailsRequestArgs);
assert.deepEqual<ReadonlyArray<CompletionEntryDetails> | undefined>(detailsFullResponse, [
{
codeActions: [
{
description: `Import 'foo' from module "./a"`,
changes: [
{
fileName: "/b.ts",
textChanges: [createTextChange(createTextSpan(0, 0), 'import { foo } from "./a";\n\n')],
},
],
commands: undefined,
}
],
...detailsCommon,
}
]);
});
});
}

View File

@ -0,0 +1,174 @@
namespace ts.projectSystem {
describe("unittests:: tsserver:: searching for config file", () => {
it("should stop at projectRootPath if given", () => {
const f1 = {
path: "/a/file1.ts",
content: ""
};
const configFile = {
path: "/tsconfig.json",
content: "{}"
};
const host = createServerHost([f1, configFile]);
const service = createProjectService(host);
service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, "/a");
checkNumberOfConfiguredProjects(service, 0);
checkNumberOfInferredProjects(service, 1);
service.closeClientFile(f1.path);
service.openClientFile(f1.path);
checkNumberOfConfiguredProjects(service, 1);
checkNumberOfInferredProjects(service, 0);
});
it("should use projectRootPath when searching for inferred project again", () => {
const projectDir = "/a/b/projects/project";
const configFileLocation = `${projectDir}/src`;
const f1 = {
path: `${configFileLocation}/file1.ts`,
content: ""
};
const configFile = {
path: `${configFileLocation}/tsconfig.json`,
content: "{}"
};
const configFile2 = {
path: "/a/b/projects/tsconfig.json",
content: "{}"
};
const host = createServerHost([f1, libFile, configFile, configFile2]);
const service = createProjectService(host);
service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir);
checkNumberOfProjects(service, { configuredProjects: 1 });
assert.isDefined(service.configuredProjects.get(configFile.path));
checkWatchedFiles(host, [libFile.path, configFile.path]);
checkWatchedDirectories(host, [], /*recursive*/ false);
const typeRootLocations = getTypeRootsFromLocation(configFileLocation);
checkWatchedDirectories(host, typeRootLocations.concat(configFileLocation), /*recursive*/ true);
// Delete config file - should create inferred project and not configured project
host.reloadFS([f1, libFile, configFile2]);
host.runQueuedTimeoutCallbacks();
checkNumberOfProjects(service, { inferredProjects: 1 });
checkWatchedFiles(host, [libFile.path, configFile.path, `${configFileLocation}/jsconfig.json`, `${projectDir}/tsconfig.json`, `${projectDir}/jsconfig.json`]);
checkWatchedDirectories(host, [], /*recursive*/ false);
checkWatchedDirectories(host, typeRootLocations, /*recursive*/ true);
});
it("should use projectRootPath when searching for inferred project again 2", () => {
const projectDir = "/a/b/projects/project";
const configFileLocation = `${projectDir}/src`;
const f1 = {
path: `${configFileLocation}/file1.ts`,
content: ""
};
const configFile = {
path: `${configFileLocation}/tsconfig.json`,
content: "{}"
};
const configFile2 = {
path: "/a/b/projects/tsconfig.json",
content: "{}"
};
const host = createServerHost([f1, libFile, configFile, configFile2]);
const service = createProjectService(host, { useSingleInferredProject: true }, { useInferredProjectPerProjectRoot: true });
service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir);
checkNumberOfProjects(service, { configuredProjects: 1 });
assert.isDefined(service.configuredProjects.get(configFile.path));
checkWatchedFiles(host, [libFile.path, configFile.path]);
checkWatchedDirectories(host, [], /*recursive*/ false);
checkWatchedDirectories(host, getTypeRootsFromLocation(configFileLocation).concat(configFileLocation), /*recursive*/ true);
// Delete config file - should create inferred project with project root path set
host.reloadFS([f1, libFile, configFile2]);
host.runQueuedTimeoutCallbacks();
checkNumberOfProjects(service, { inferredProjects: 1 });
assert.equal(service.inferredProjects[0].projectRootPath, projectDir);
checkWatchedFiles(host, [libFile.path, configFile.path, `${configFileLocation}/jsconfig.json`, `${projectDir}/tsconfig.json`, `${projectDir}/jsconfig.json`]);
checkWatchedDirectories(host, [], /*recursive*/ false);
checkWatchedDirectories(host, getTypeRootsFromLocation(projectDir), /*recursive*/ true);
});
describe("when the opened file is not from project root", () => {
const projectRoot = "/a/b/projects/project";
const file: File = {
path: `${projectRoot}/src/index.ts`,
content: "let y = 10"
};
const tsconfig: File = {
path: `${projectRoot}/tsconfig.json`,
content: "{}"
};
const files = [file, libFile];
const filesWithConfig = files.concat(tsconfig);
const dirOfFile = getDirectoryPath(file.path);
function openClientFile(files: File[]) {
const host = createServerHost(files);
const projectService = createProjectService(host);
projectService.openClientFile(file.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, "/a/b/projects/proj");
return { host, projectService };
}
function verifyConfiguredProject(host: TestServerHost, projectService: TestProjectService, orphanInferredProject?: boolean) {
projectService.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: orphanInferredProject ? 1 : 0 });
const project = Debug.assertDefined(projectService.configuredProjects.get(tsconfig.path));
if (orphanInferredProject) {
const inferredProject = projectService.inferredProjects[0];
assert.isTrue(inferredProject.isOrphan());
}
checkProjectActualFiles(project, [file.path, libFile.path, tsconfig.path]);
checkWatchedFiles(host, [libFile.path, tsconfig.path]);
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
checkWatchedDirectories(host, (orphanInferredProject ? [projectRoot, `${dirOfFile}/node_modules/@types`] : [projectRoot]).concat(getTypeRootsFromLocation(projectRoot)), /*recursive*/ true);
}
function verifyInferredProject(host: TestServerHost, projectService: TestProjectService) {
projectService.checkNumberOfProjects({ inferredProjects: 1 });
const project = projectService.inferredProjects[0];
assert.isDefined(project);
const filesToWatch = [libFile.path];
forEachAncestorDirectory(dirOfFile, ancestor => {
filesToWatch.push(combinePaths(ancestor, "tsconfig.json"));
filesToWatch.push(combinePaths(ancestor, "jsconfig.json"));
});
checkProjectActualFiles(project, [file.path, libFile.path]);
checkWatchedFiles(host, filesToWatch);
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
checkWatchedDirectories(host, getTypeRootsFromLocation(dirOfFile), /*recursive*/ true);
}
it("tsconfig for the file exists", () => {
const { host, projectService } = openClientFile(filesWithConfig);
verifyConfiguredProject(host, projectService);
host.reloadFS(files);
host.runQueuedTimeoutCallbacks();
verifyInferredProject(host, projectService);
host.reloadFS(filesWithConfig);
host.runQueuedTimeoutCallbacks();
verifyConfiguredProject(host, projectService, /*orphanInferredProject*/ true);
});
it("tsconfig for the file does not exist", () => {
const { host, projectService } = openClientFile(files);
verifyInferredProject(host, projectService);
host.reloadFS(filesWithConfig);
host.runQueuedTimeoutCallbacks();
verifyConfiguredProject(host, projectService, /*orphanInferredProject*/ true);
host.reloadFS(files);
host.runQueuedTimeoutCallbacks();
verifyInferredProject(host, projectService);
});
});
});
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,566 @@
namespace ts.projectSystem {
function protocolFileSpanFromSubstring(file: File, substring: string, options?: SpanFromSubstringOptions): protocol.FileSpan {
return { file: file.path, ...protocolTextSpanFromSubstring(file.content, substring, options) };
}
function documentSpanFromSubstring(file: File, substring: string, options?: SpanFromSubstringOptions): DocumentSpan {
return { fileName: file.path, textSpan: textSpanFromSubstring(file.content, substring, options) };
}
function renameLocation(file: File, substring: string, options?: SpanFromSubstringOptions): RenameLocation {
return documentSpanFromSubstring(file, substring, options);
}
function makeReferenceItem(file: File, isDefinition: boolean, text: string, lineText: string, options?: SpanFromSubstringOptions): protocol.ReferencesResponseItem {
return {
...protocolFileSpanFromSubstring(file, text, options),
isDefinition,
isWriteAccess: isDefinition,
lineText,
};
}
function makeReferenceEntry(file: File, isDefinition: boolean, text: string, options?: SpanFromSubstringOptions): ReferenceEntry {
return {
...documentSpanFromSubstring(file, text, options),
isDefinition,
isWriteAccess: isDefinition,
isInString: undefined,
};
}
function checkDeclarationFiles(file: File, session: TestSession, expectedFiles: ReadonlyArray<File>): void {
openFilesForSession([file], session);
const project = Debug.assertDefined(session.getProjectService().getDefaultProjectForFile(file.path as server.NormalizedPath, /*ensureProject*/ false));
const program = project.getCurrentProgram()!;
const output = getFileEmitOutput(program, Debug.assertDefined(program.getSourceFile(file.path)), /*emitOnlyDtsFiles*/ true);
closeFilesForSession([file], session);
Debug.assert(!output.emitSkipped);
assert.deepEqual(output.outputFiles, expectedFiles.map((e): OutputFile => ({ name: e.path, text: e.content, writeByteOrderMark: false })));
}
describe("unittests:: tsserver:: with declaration file maps:: project references", () => {
const aTs: File = {
path: "/a/a.ts",
content: "export function fnA() {}\nexport interface IfaceA {}\nexport const instanceA: IfaceA = {};",
};
const compilerOptions: CompilerOptions = {
outDir: "bin",
declaration: true,
declarationMap: true,
composite: true,
};
const configContent = JSON.stringify({ compilerOptions });
const aTsconfig: File = { path: "/a/tsconfig.json", content: configContent };
const aDtsMapContent: RawSourceMap = {
version: 3,
file: "a.d.ts",
sourceRoot: "",
sources: ["../a.ts"],
names: [],
mappings: "AAAA,wBAAgB,GAAG,SAAK;AACxB,MAAM,WAAW,MAAM;CAAG;AAC1B,eAAO,MAAM,SAAS,EAAE,MAAW,CAAC"
};
const aDtsMap: File = {
path: "/a/bin/a.d.ts.map",
content: JSON.stringify(aDtsMapContent),
};
const aDts: File = {
path: "/a/bin/a.d.ts",
// Need to mangle the sourceMappingURL part or it breaks the build
content: `export declare function fnA(): void;\nexport interface IfaceA {\n}\nexport declare const instanceA: IfaceA;\n//# source${""}MappingURL=a.d.ts.map`,
};
const bTs: File = {
path: "/b/b.ts",
content: "export function fnB() {}",
};
const bTsconfig: File = { path: "/b/tsconfig.json", content: configContent };
const bDtsMapContent: RawSourceMap = {
version: 3,
file: "b.d.ts",
sourceRoot: "",
sources: ["../b.ts"],
names: [],
mappings: "AAAA,wBAAgB,GAAG,SAAK",
};
const bDtsMap: File = {
path: "/b/bin/b.d.ts.map",
content: JSON.stringify(bDtsMapContent),
};
const bDts: File = {
// Need to mangle the sourceMappingURL part or it breaks the build
path: "/b/bin/b.d.ts",
content: `export declare function fnB(): void;\n//# source${""}MappingURL=b.d.ts.map`,
};
const dummyFile: File = {
path: "/dummy/dummy.ts",
content: "let a = 10;"
};
const userTs: File = {
path: "/user/user.ts",
content: 'import * as a from "../a/bin/a";\nimport * as b from "../b/bin/b";\nexport function fnUser() { a.fnA(); b.fnB(); a.instanceA; }',
};
const userTsForConfigProject: File = {
path: "/user/user.ts",
content: 'import * as a from "../a/a";\nimport * as b from "../b/b";\nexport function fnUser() { a.fnA(); b.fnB(); a.instanceA; }',
};
const userTsconfig: File = {
path: "/user/tsconfig.json",
content: JSON.stringify({
file: ["user.ts"],
references: [{ path: "../a" }, { path: "../b" }]
})
};
function makeSampleProjects(addUserTsConfig?: boolean) {
const host = createServerHost([aTs, aTsconfig, aDtsMap, aDts, bTsconfig, bTs, bDtsMap, bDts, ...(addUserTsConfig ? [userTsForConfigProject, userTsconfig] : [userTs]), dummyFile]);
const session = createSession(host);
checkDeclarationFiles(aTs, session, [aDtsMap, aDts]);
checkDeclarationFiles(bTs, session, [bDtsMap, bDts]);
// Testing what happens if we delete the original sources.
host.deleteFile(bTs.path);
openFilesForSession([userTs], session);
const service = session.getProjectService();
checkNumberOfProjects(service, addUserTsConfig ? { configuredProjects: 1 } : { inferredProjects: 1 });
return session;
}
function verifyInferredProjectUnchanged(session: TestSession) {
checkProjectActualFiles(session.getProjectService().inferredProjects[0], [userTs.path, aDts.path, bDts.path]);
}
function verifyDummyProject(session: TestSession) {
checkProjectActualFiles(session.getProjectService().inferredProjects[0], [dummyFile.path]);
}
function verifyOnlyOrphanInferredProject(session: TestSession) {
openFilesForSession([dummyFile], session);
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 });
verifyDummyProject(session);
}
function verifySingleInferredProject(session: TestSession) {
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 });
verifyInferredProjectUnchanged(session);
// Close user file should close all the projects after opening dummy file
closeFilesForSession([userTs], session);
verifyOnlyOrphanInferredProject(session);
}
function verifyATsConfigProject(session: TestSession) {
checkProjectActualFiles(session.getProjectService().configuredProjects.get(aTsconfig.path)!, [aTs.path, aTsconfig.path]);
}
function verifyATsConfigOriginalProject(session: TestSession) {
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 });
verifyInferredProjectUnchanged(session);
verifyATsConfigProject(session);
// Close user file should close all the projects
closeFilesForSession([userTs], session);
verifyOnlyOrphanInferredProject(session);
}
function verifyATsConfigWhenOpened(session: TestSession) {
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 });
verifyInferredProjectUnchanged(session);
verifyATsConfigProject(session);
closeFilesForSession([userTs], session);
openFilesForSession([dummyFile], session);
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 });
verifyDummyProject(session);
verifyATsConfigProject(session); // ATsConfig should still be alive
}
function verifyUserTsConfigProject(session: TestSession) {
checkProjectActualFiles(session.getProjectService().configuredProjects.get(userTsconfig.path)!, [userTs.path, aDts.path, userTsconfig.path]);
}
it("goToDefinition", () => {
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.DefinitionRequest, protocol.DefinitionResponse>(session, protocol.CommandTypes.Definition, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual(response, [protocolFileSpanFromSubstring(aTs, "fnA")]);
verifySingleInferredProject(session);
});
it("getDefinitionAndBoundSpan", () => {
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.DefinitionAndBoundSpanRequest, protocol.DefinitionAndBoundSpanResponse>(session, protocol.CommandTypes.DefinitionAndBoundSpan, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual(response, {
textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"),
definitions: [protocolFileSpanFromSubstring(aTs, "fnA")],
});
verifySingleInferredProject(session);
});
it("getDefinitionAndBoundSpan with file navigation", () => {
const session = makeSampleProjects(/*addUserTsConfig*/ true);
const response = executeSessionRequest<protocol.DefinitionAndBoundSpanRequest, protocol.DefinitionAndBoundSpanResponse>(session, protocol.CommandTypes.DefinitionAndBoundSpan, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual(response, {
textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"),
definitions: [protocolFileSpanFromSubstring(aTs, "fnA")],
});
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 });
verifyUserTsConfigProject(session);
// Navigate to the definition
closeFilesForSession([userTs], session);
openFilesForSession([aTs], session);
// UserTs configured project should be alive
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 });
verifyUserTsConfigProject(session);
verifyATsConfigProject(session);
closeFilesForSession([aTs], session);
verifyOnlyOrphanInferredProject(session);
});
it("goToType", () => {
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.TypeDefinitionRequest, protocol.TypeDefinitionResponse>(session, protocol.CommandTypes.TypeDefinition, protocolFileLocationFromSubstring(userTs, "instanceA"));
assert.deepEqual(response, [protocolFileSpanFromSubstring(aTs, "IfaceA")]);
verifySingleInferredProject(session);
});
it("goToImplementation", () => {
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.ImplementationRequest, protocol.ImplementationResponse>(session, protocol.CommandTypes.Implementation, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual(response, [protocolFileSpanFromSubstring(aTs, "fnA")]);
verifySingleInferredProject(session);
});
it("goToDefinition -- target does not exist", () => {
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.DefinitionRequest, protocol.DefinitionResponse>(session, CommandNames.Definition, protocolFileLocationFromSubstring(userTs, "fnB()"));
// bTs does not exist, so stick with bDts
assert.deepEqual(response, [protocolFileSpanFromSubstring(bDts, "fnB")]);
verifySingleInferredProject(session);
});
it("navigateTo", () => {
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.NavtoRequest, protocol.NavtoResponse>(session, CommandNames.Navto, { file: userTs.path, searchValue: "fn" });
assert.deepEqual<ReadonlyArray<protocol.NavtoItem> | undefined>(response, [
{
...protocolFileSpanFromSubstring(bDts, "export declare function fnB(): void;"),
name: "fnB",
matchKind: "prefix",
isCaseSensitive: true,
kind: ScriptElementKind.functionElement,
kindModifiers: "export,declare",
},
{
...protocolFileSpanFromSubstring(userTs, "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }"),
name: "fnUser",
matchKind: "prefix",
isCaseSensitive: true,
kind: ScriptElementKind.functionElement,
kindModifiers: "export",
},
{
...protocolFileSpanFromSubstring(aTs, "export function fnA() {}"),
name: "fnA",
matchKind: "prefix",
isCaseSensitive: true,
kind: ScriptElementKind.functionElement,
kindModifiers: "export",
},
]);
verifyATsConfigOriginalProject(session);
});
const referenceATs = (aTs: File): protocol.ReferencesResponseItem => makeReferenceItem(aTs, /*isDefinition*/ true, "fnA", "export function fnA() {}");
const referencesUserTs = (userTs: File): ReadonlyArray<protocol.ReferencesResponseItem> => [
makeReferenceItem(userTs, /*isDefinition*/ false, "fnA", "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }"),
];
it("findAllReferences", () => {
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.ReferencesRequest, protocol.ReferencesResponse>(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual<protocol.ReferencesResponseBody | undefined>(response, {
refs: [...referencesUserTs(userTs), referenceATs(aTs)],
symbolName: "fnA",
symbolStartOffset: protocolLocationFromSubstring(userTs.content, "fnA()").offset,
symbolDisplayString: "function fnA(): void",
});
verifyATsConfigOriginalProject(session);
});
it("findAllReferences -- starting at definition", () => {
const session = makeSampleProjects();
openFilesForSession([aTs], session); // If it's not opened, the reference isn't found.
const response = executeSessionRequest<protocol.ReferencesRequest, protocol.ReferencesResponse>(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(aTs, "fnA"));
assert.deepEqual<protocol.ReferencesResponseBody | undefined>(response, {
refs: [referenceATs(aTs), ...referencesUserTs(userTs)],
symbolName: "fnA",
symbolStartOffset: protocolLocationFromSubstring(aTs.content, "fnA").offset,
symbolDisplayString: "function fnA(): void",
});
verifyATsConfigWhenOpened(session);
});
interface ReferencesFullRequest extends protocol.FileLocationRequest { readonly command: protocol.CommandTypes.ReferencesFull; }
interface ReferencesFullResponse extends protocol.Response { readonly body: ReadonlyArray<ReferencedSymbol>; }
it("findAllReferencesFull", () => {
const session = makeSampleProjects();
const responseFull = executeSessionRequest<ReferencesFullRequest, ReferencesFullResponse>(session, protocol.CommandTypes.ReferencesFull, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual<ReadonlyArray<ReferencedSymbol>>(responseFull, [
{
definition: {
...documentSpanFromSubstring(aTs, "fnA"),
kind: ScriptElementKind.functionElement,
name: "function fnA(): void",
containerKind: ScriptElementKind.unknown,
containerName: "",
displayParts: [
keywordPart(SyntaxKind.FunctionKeyword),
spacePart(),
displayPart("fnA", SymbolDisplayPartKind.functionName),
punctuationPart(SyntaxKind.OpenParenToken),
punctuationPart(SyntaxKind.CloseParenToken),
punctuationPart(SyntaxKind.ColonToken),
spacePart(),
keywordPart(SyntaxKind.VoidKeyword),
],
},
references: [
makeReferenceEntry(userTs, /*isDefinition*/ false, "fnA"),
makeReferenceEntry(aTs, /*isDefinition*/ true, "fnA"),
],
},
]);
verifyATsConfigOriginalProject(session);
});
it("findAllReferencesFull definition is in mapped file", () => {
const aTs: File = { path: "/a/a.ts", content: `function f() {}` };
const aTsconfig: File = {
path: "/a/tsconfig.json",
content: JSON.stringify({ compilerOptions: { declaration: true, declarationMap: true, outFile: "../bin/a.js" } }),
};
const bTs: File = { path: "/b/b.ts", content: `f();` };
const bTsconfig: File = { path: "/b/tsconfig.json", content: JSON.stringify({ references: [{ path: "../a" }] }) };
const aDts: File = { path: "/bin/a.d.ts", content: `declare function f(): void;\n//# sourceMappingURL=a.d.ts.map` };
const aDtsMap: File = {
path: "/bin/a.d.ts.map",
content: JSON.stringify({ version: 3, file: "a.d.ts", sourceRoot: "", sources: ["../a/a.ts"], names: [], mappings: "AAAA,iBAAS,CAAC,SAAK" }),
};
const session = createSession(createServerHost([aTs, aTsconfig, bTs, bTsconfig, aDts, aDtsMap]));
checkDeclarationFiles(aTs, session, [aDtsMap, aDts]);
openFilesForSession([bTs], session);
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 });
const responseFull = executeSessionRequest<ReferencesFullRequest, ReferencesFullResponse>(session, protocol.CommandTypes.ReferencesFull, protocolFileLocationFromSubstring(bTs, "f()"));
assert.deepEqual<ReadonlyArray<ReferencedSymbol>>(responseFull, [
{
definition: {
containerKind: ScriptElementKind.unknown,
containerName: "",
displayParts: [
keywordPart(SyntaxKind.FunctionKeyword),
spacePart(),
displayPart("f", SymbolDisplayPartKind.functionName),
punctuationPart(SyntaxKind.OpenParenToken),
punctuationPart(SyntaxKind.CloseParenToken),
punctuationPart(SyntaxKind.ColonToken),
spacePart(),
keywordPart(SyntaxKind.VoidKeyword),
],
fileName: aTs.path,
kind: ScriptElementKind.functionElement,
name: "function f(): void",
textSpan: { start: 9, length: 1 },
},
references: [
{
fileName: bTs.path,
isDefinition: false,
isInString: undefined,
isWriteAccess: false,
textSpan: { start: 0, length: 1 },
},
{
fileName: aTs.path,
isDefinition: true,
isInString: undefined,
isWriteAccess: true,
textSpan: { start: 9, length: 1 },
},
],
}
]);
});
it("findAllReferences -- target does not exist", () => {
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.ReferencesRequest, protocol.ReferencesResponse>(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(userTs, "fnB()"));
assert.deepEqual<protocol.ReferencesResponseBody | undefined>(response, {
refs: [
makeReferenceItem(bDts, /*isDefinition*/ true, "fnB", "export declare function fnB(): void;"),
makeReferenceItem(userTs, /*isDefinition*/ false, "fnB", "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }"),
],
symbolName: "fnB",
symbolStartOffset: protocolLocationFromSubstring(userTs.content, "fnB()").offset,
symbolDisplayString: "function fnB(): void",
});
verifySingleInferredProject(session);
});
const renameATs = (aTs: File): protocol.SpanGroup => ({
file: aTs.path,
locs: [protocolRenameSpanFromSubstring(aTs.content, "fnA")],
});
const renameUserTs = (userTs: File): protocol.SpanGroup => ({
file: userTs.path,
locs: [protocolRenameSpanFromSubstring(userTs.content, "fnA")],
});
it("renameLocations", () => {
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual<protocol.RenameResponseBody | undefined>(response, {
info: {
canRename: true,
displayName: "fnA",
fileToRename: undefined,
fullDisplayName: '"/a/bin/a".fnA', // Ideally this would use the original source's path instead of the declaration file's path.
kind: ScriptElementKind.functionElement,
kindModifiers: [ScriptElementKindModifier.exportedModifier, ScriptElementKindModifier.ambientModifier].join(","),
triggerSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"),
},
locs: [renameUserTs(userTs), renameATs(aTs)],
});
verifyATsConfigOriginalProject(session);
});
it("renameLocations -- starting at definition", () => {
const session = makeSampleProjects();
openFilesForSession([aTs], session); // If it's not opened, the reference isn't found.
const response = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "fnA"));
assert.deepEqual<protocol.RenameResponseBody | undefined>(response, {
info: {
canRename: true,
displayName: "fnA",
fileToRename: undefined,
fullDisplayName: '"/a/a".fnA',
kind: ScriptElementKind.functionElement,
kindModifiers: ScriptElementKindModifier.exportedModifier,
triggerSpan: protocolTextSpanFromSubstring(aTs.content, "fnA"),
},
locs: [renameATs(aTs), renameUserTs(userTs)],
});
verifyATsConfigWhenOpened(session);
});
it("renameLocationsFull", () => {
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.RenameFullRequest, protocol.RenameFullResponse>(session, protocol.CommandTypes.RenameLocationsFull, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual<ReadonlyArray<RenameLocation>>(response, [
renameLocation(userTs, "fnA"),
renameLocation(aTs, "fnA"),
]);
verifyATsConfigOriginalProject(session);
});
it("renameLocations -- target does not exist", () => {
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(userTs, "fnB()"));
assert.deepEqual<protocol.RenameResponseBody | undefined>(response, {
info: {
canRename: true,
displayName: "fnB",
fileToRename: undefined,
fullDisplayName: '"/b/bin/b".fnB',
kind: ScriptElementKind.functionElement,
kindModifiers: [ScriptElementKindModifier.exportedModifier, ScriptElementKindModifier.ambientModifier].join(","),
triggerSpan: protocolTextSpanFromSubstring(userTs.content, "fnB"),
},
locs: [
{
file: bDts.path,
locs: [protocolRenameSpanFromSubstring(bDts.content, "fnB")],
},
{
file: userTs.path,
locs: [protocolRenameSpanFromSubstring(userTs.content, "fnB")],
},
],
});
verifySingleInferredProject(session);
});
it("getEditsForFileRename", () => {
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.GetEditsForFileRenameRequest, protocol.GetEditsForFileRenameResponse>(session, protocol.CommandTypes.GetEditsForFileRename, {
oldFilePath: aTs.path,
newFilePath: "/a/aNew.ts",
});
assert.deepEqual<ReadonlyArray<protocol.FileCodeEdits>>(response, [
{
fileName: userTs.path,
textChanges: [
{ ...protocolTextSpanFromSubstring(userTs.content, "../a/bin/a"), newText: "../a/bin/aNew" },
],
},
]);
verifySingleInferredProject(session);
});
it("getEditsForFileRename when referencing project doesnt include file and its renamed", () => {
const aTs: File = { path: "/a/src/a.ts", content: "" };
const aTsconfig: File = {
path: "/a/tsconfig.json",
content: JSON.stringify({
compilerOptions: {
composite: true,
declaration: true,
declarationMap: true,
outDir: "./build",
}
}),
};
const bTs: File = { path: "/b/src/b.ts", content: "" };
const bTsconfig: File = {
path: "/b/tsconfig.json",
content: JSON.stringify({
compilerOptions: {
composite: true,
outDir: "./build",
},
include: ["./src"],
references: [{ path: "../a" }],
}),
};
const host = createServerHost([aTs, aTsconfig, bTs, bTsconfig]);
const session = createSession(host);
openFilesForSession([aTs, bTs], session);
const response = executeSessionRequest<protocol.GetEditsForFileRenameRequest, protocol.GetEditsForFileRenameResponse>(session, CommandNames.GetEditsForFileRename, {
oldFilePath: aTs.path,
newFilePath: "/a/src/a1.ts",
});
assert.deepEqual<ReadonlyArray<protocol.FileCodeEdits>>(response, []); // Should not change anything
});
});
}

Some files were not shown because too many files have changed in this diff Show More