mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-14 19:16:17 -06:00
Merge branch 'master' into tscWatchExportUpdate
This commit is contained in:
commit
c426fc6868
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
@ -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
@ -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>;
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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}"`);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 };
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -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()}`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
|
||||
@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@ -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)));
|
||||
|
||||
@ -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 = [
|
||||
|
||||
@ -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";', "") },
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
namespace ts {
|
||||
describe("compilerCore", () => {
|
||||
describe("unittests:: compilerCore", () => {
|
||||
describe("equalOwnProperties", () => {
|
||||
it("correctly equates objects", () => {
|
||||
assert.isTrue(equalOwnProperties({}, {}));
|
||||
|
||||
@ -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);
|
||||
@ -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]
|
||||
@ -1,5 +1,5 @@
|
||||
namespace ts {
|
||||
describe("convertCompilerOptionsFromJson", () => {
|
||||
describe("unittests:: config:: convertCompilerOptionsFromJson", () => {
|
||||
const formatDiagnosticHost: FormatDiagnosticsHost = {
|
||||
getCurrentDirectory: () => "/apath/",
|
||||
getCanonicalFileName: createGetCanonicalFileName(/*useCaseSensitiveFileNames*/ true),
|
||||
@ -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);
|
||||
@ -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"]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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 = {
|
||||
@ -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": {
|
||||
@ -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`;
|
||||
@ -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));
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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 }
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
describe("forAwaitOfEvaluation", () => {
|
||||
describe("unittests:: evaluation:: forAwaitOfEvaluation", () => {
|
||||
it("sync (es5)", async () => {
|
||||
const result = evaluator.evaluateTypeScript(`
|
||||
let i = 0;
|
||||
|
||||
@ -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]}`);
|
||||
}
|
||||
|
||||
@ -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" +
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
namespace ts {
|
||||
describe("JSDocParsing", () => {
|
||||
describe("unittests:: JSDocParsing", () => {
|
||||
describe("TypeExpressions", () => {
|
||||
function parsesCorrectly(name: string, content: string) {
|
||||
it(name, () => {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 {
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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:/");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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, () => {
|
||||
|
||||
@ -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";');
|
||||
@ -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");
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -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[],
|
||||
|
||||
@ -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 {
|
||||
]);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
namespace ts {
|
||||
describe("cancellableLanguageServiceOperations", () => {
|
||||
describe("unittests:: services:: cancellableLanguageServiceOperations", () => {
|
||||
const file = `
|
||||
function foo(): void;
|
||||
function foo<T>(x: T): T;
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
describe("DocumentRegistry", () => {
|
||||
describe("unittests:: services:: DocumentRegistry", () => {
|
||||
it("documents are shared between projects", () => {
|
||||
const documentRegistry = ts.createDocumentRegistry();
|
||||
const defaultCompilerOptions = ts.getDefaultCompilerOptions();
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
namespace ts {
|
||||
describe("extractConstants", () => {
|
||||
describe("unittests:: services:: extract:: extractConstants", () => {
|
||||
testExtractConstant("extractConstant_TopLevel",
|
||||
`let x = [#|1|];`);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
namespace ts {
|
||||
describe("extractFunctions", () => {
|
||||
describe("unittests:: services:: extract:: extractFunctions", () => {
|
||||
testExtractFunction("extractFunction1",
|
||||
`namespace A {
|
||||
let x = 1;
|
||||
@ -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]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
`);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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",
|
||||
@ -1,4 +1,4 @@
|
||||
describe("PatternMatcher", () => {
|
||||
describe("unittests:: services:: PatternMatcher", () => {
|
||||
describe("BreakIntoCharacterSpans", () => {
|
||||
it("EmptyIdentifier", () => {
|
||||
verifyBreakIntoCharacterSpans("");
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
namespace ts {
|
||||
describe("Transpile", () => {
|
||||
describe("unittests:: services:: Transpile", () => {
|
||||
|
||||
interface TranspileTestSettings {
|
||||
options?: TranspileOptions;
|
||||
@ -1,5 +1,5 @@
|
||||
namespace ts {
|
||||
describe("TransformAPI", () => {
|
||||
describe("unittests:: TransformAPI", () => {
|
||||
function replaceUndefinedWithVoid0(context: TransformationContext) {
|
||||
const previousOnSubstituteNode = context.onSubstituteNode;
|
||||
context.enableSubstitution(SyntaxKind.Identifier);
|
||||
|
||||
@ -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"],
|
||||
|
||||
@ -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);
|
||||
|
||||
97
src/testRunner/unittests/tscWatch/consoleClearing.ts
Normal file
97
src/testRunner/unittests/tscWatch/consoleClearing.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
718
src/testRunner/unittests/tscWatch/emit.ts
Normal file
718
src/testRunner/unittests/tscWatch/emit.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
307
src/testRunner/unittests/tscWatch/emitAndErrorUpdates.ts
Normal file
307
src/testRunner/unittests/tscWatch/emitAndErrorUpdates.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
203
src/testRunner/unittests/tscWatch/helpers.ts
Normal file
203
src/testRunner/unittests/tscWatch/helpers.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
1287
src/testRunner/unittests/tscWatch/programUpdates.ts
Normal file
1287
src/testRunner/unittests/tscWatch/programUpdates.ts
Normal file
File diff suppressed because it is too large
Load Diff
448
src/testRunner/unittests/tscWatch/resolutionCache.ts
Normal file
448
src/testRunner/unittests/tscWatch/resolutionCache.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
40
src/testRunner/unittests/tscWatch/watchApi.ts
Normal file
40
src/testRunner/unittests/tscWatch/watchApi.ts
Normal 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]);
|
||||
});
|
||||
});
|
||||
}
|
||||
178
src/testRunner/unittests/tscWatch/watchEnvironment.ts
Normal file
178
src/testRunner/unittests/tscWatch/watchEnvironment.ts
Normal 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
271
src/testRunner/unittests/tsserver/cancellationToken.ts
Normal file
271
src/testRunner/unittests/tsserver/cancellationToken.ts
Normal 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));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
122
src/testRunner/unittests/tsserver/completions.ts
Normal file
122
src/testRunner/unittests/tsserver/completions.ts
Normal 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,
|
||||
}
|
||||
]);
|
||||
});
|
||||
});
|
||||
}
|
||||
174
src/testRunner/unittests/tsserver/configFileSearch.ts
Normal file
174
src/testRunner/unittests/tsserver/configFileSearch.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
1010
src/testRunner/unittests/tsserver/configuredProjects.ts
Normal file
1010
src/testRunner/unittests/tsserver/configuredProjects.ts
Normal file
File diff suppressed because it is too large
Load Diff
566
src/testRunner/unittests/tsserver/declarationFileMaps.ts
Normal file
566
src/testRunner/unittests/tsserver/declarationFileMaps.ts
Normal 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
Loading…
x
Reference in New Issue
Block a user