Merge branch 'master' into report-multiple-overload-errors

This commit is contained in:
Nathan Shively-Sanders
2019-07-03 09:28:44 -07:00
140 changed files with 2330 additions and 630 deletions
+3
View File
@@ -274,6 +274,9 @@ namespace ts {
if (isStringOrNumericLiteralLike(nameExpression)) {
return escapeLeadingUnderscores(nameExpression.text);
}
if (isSignedNumericLiteral(nameExpression)) {
return tokenToString(nameExpression.operator) + nameExpression.operand.text as __String;
}
Debug.assert(isWellKnownSymbolSyntactically(nameExpression));
return getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>nameExpression).name));
+84 -65
View File
@@ -65,6 +65,7 @@ namespace ts {
let typeCount = 0;
let symbolCount = 0;
let enumCount = 0;
let instantiationCount = 0;
let instantiationDepth = 0;
let constraintDepth = 0;
let currentNode: Node | undefined;
@@ -722,6 +723,7 @@ namespace ts {
NoIndexSignatures = 1 << 0,
Writing = 1 << 1,
CacheSymbol = 1 << 2,
NoTupleBoundsCheck = 1 << 3,
}
const enum CallbackCheck {
@@ -4652,6 +4654,9 @@ namespace ts {
if (!isIdentifierText(name, compilerOptions.target) && !isNumericLiteralName(name)) {
return `"${escapeString(name, CharacterCodes.doubleQuote)}"`;
}
if (isNumericLiteralName(name) && startsWith(name, "-")) {
return `[${name}]`;
}
return name;
}
if (nameType.flags & TypeFlags.UniqueESSymbol) {
@@ -5115,21 +5120,25 @@ namespace ts {
}
else if (isArrayLikeType(parentType)) {
const indexType = getLiteralType(index);
const declaredType = getConstraintForLocation(getIndexedAccessType(parentType, indexType, declaration.name), declaration.name);
const accessFlags = hasDefaultValue(declaration) ? AccessFlags.NoTupleBoundsCheck : 0;
const declaredType = getConstraintForLocation(getIndexedAccessTypeOrUndefined(parentType, indexType, declaration.name, accessFlags) || errorType, declaration.name);
type = getFlowTypeOfDestructuring(declaration, declaredType);
}
else {
type = elementType;
}
}
// In strict null checking mode, if a default value of a non-undefined type is specified, remove
// undefined from the final type.
if (strictNullChecks && declaration.initializer && !(getFalsyFlags(checkDeclarationInitializer(declaration)) & TypeFlags.Undefined)) {
type = getTypeWithFacts(type, TypeFacts.NEUndefined);
if (!declaration.initializer) {
return type;
}
return declaration.initializer && !getEffectiveTypeAnnotationNode(walkUpBindingElementsAndPatterns(declaration)) ?
getUnionType([type, checkDeclarationInitializer(declaration)], UnionReduction.Subtype) :
type;
if (getEffectiveTypeAnnotationNode(walkUpBindingElementsAndPatterns(declaration))) {
// In strict null checking mode, if a default value of a non-undefined type is specified, remove
// undefined from the final type.
return strictNullChecks && !(getFalsyFlags(checkDeclarationInitializer(declaration)) & TypeFlags.Undefined) ?
getTypeWithFacts(type, TypeFacts.NEUndefined) :
type;
}
return getUnionType([getTypeWithFacts(type, TypeFacts.NEUndefined), checkDeclarationInitializer(declaration)], UnionReduction.Subtype);
}
function getTypeForDeclarationFromJSDocComment(declaration: Node) {
@@ -10140,7 +10149,7 @@ namespace ts {
propType;
}
if (everyType(objectType, isTupleType) && isNumericLiteralName(propName) && +propName >= 0) {
if (accessNode && everyType(objectType, t => !(<TupleTypeReference>t).target.hasRestElement)) {
if (accessNode && everyType(objectType, t => !(<TupleTypeReference>t).target.hasRestElement) && !(accessFlags & AccessFlags.NoTupleBoundsCheck)) {
const indexNode = getIndexNodeForAccessExpression(accessNode);
if (isTupleType(objectType)) {
error(indexNode, Diagnostics.Tuple_type_0_of_length_1_has_no_element_at_index_2,
@@ -11422,13 +11431,14 @@ namespace ts {
if (!type || !mapper || mapper === identityMapper) {
return type;
}
if (instantiationDepth === 50) {
if (instantiationDepth === 50 || instantiationCount >= 5000000) {
// We have reached 50 recursive type instantiations and there is a very high likelyhood we're dealing
// with a combination of infinite generic types that perpetually generate new type identities. We stop
// the recursion here by yielding the error type.
error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite);
return errorType;
}
instantiationCount++;
instantiationDepth++;
const result = instantiateTypeWorker(type, mapper);
instantiationDepth--;
@@ -13287,14 +13297,14 @@ namespace ts {
if (!isGenericMappedType(source)) {
const targetConstraint = getConstraintTypeFromMappedType(target);
const sourceKeys = getIndexType(source, /*stringsOnly*/ undefined, /*noIndexSignatures*/ true);
const hasOptionalUnionKeys = modifiers & MappedTypeModifiers.IncludeOptional && targetConstraint.flags & TypeFlags.Union;
const filteredByApplicability = hasOptionalUnionKeys ? filterType(targetConstraint, t => !!isRelatedTo(t, sourceKeys)) : undefined;
const includeOptional = modifiers & MappedTypeModifiers.IncludeOptional;
const filteredByApplicability = includeOptional ? intersectTypes(targetConstraint, sourceKeys) : undefined;
// A source type T is related to a target type { [P in Q]: X } if Q is related to keyof T and T[Q] is related to X.
// A source type T is related to a target type { [P in Q]?: X } if some constituent Q' of Q is related to keyof T and T[Q'] is related to X.
if (hasOptionalUnionKeys
if (includeOptional
? !(filteredByApplicability!.flags & TypeFlags.Never)
: isRelatedTo(targetConstraint, sourceKeys)) {
const indexingType = hasOptionalUnionKeys ? filteredByApplicability! : getTypeParameterFromMappedType(target);
const indexingType = filteredByApplicability || getTypeParameterFromMappedType(target);
const indexedAccessType = getIndexedAccessType(source, indexingType);
const templateType = getTemplateTypeFromMappedType(target);
if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) {
@@ -15429,36 +15439,11 @@ namespace ts {
inferFromTypes(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(<ConditionalType>target));
}
else if (target.flags & TypeFlags.Conditional && !contravariant) {
inferFromTypes(source, getTrueTypeFromConditionalType(<ConditionalType>target));
inferFromTypes(source, getFalseTypeFromConditionalType(<ConditionalType>target));
const targetTypes = [getTrueTypeFromConditionalType(<ConditionalType>target), getFalseTypeFromConditionalType(<ConditionalType>target)];
inferToMultipleTypes(source, targetTypes, /*isIntersection*/ false);
}
else if (target.flags & TypeFlags.UnionOrIntersection) {
// We infer from types that are not naked type variables first so that inferences we
// make from nested naked type variables and given slightly higher priority by virtue
// of being first in the candidates array.
let typeVariableCount = 0;
for (const t of (<UnionOrIntersectionType>target).types) {
if (getInferenceInfoForType(t)) {
typeVariableCount++;
}
else {
inferFromTypes(source, t);
}
}
// Inferences directly to naked type variables are given lower priority as they are
// less specific. For example, when inferring from Promise<string> to T | Promise<T>,
// we want to infer string for T, not Promise<string> | string. For intersection types
// we only infer to single naked type variables.
if (target.flags & TypeFlags.Union ? typeVariableCount !== 0 : typeVariableCount === 1) {
const savePriority = priority;
priority |= InferencePriority.NakedTypeVariable;
for (const t of (<UnionOrIntersectionType>target).types) {
if (getInferenceInfoForType(t)) {
inferFromTypes(source, t);
}
}
priority = savePriority;
}
inferToMultipleTypes(source, (<UnionOrIntersectionType>target).types, !!(target.flags & TypeFlags.Intersection));
}
else if (source.flags & TypeFlags.Union) {
// Source is a union or intersection type, infer from each constituent type
@@ -15556,6 +15541,35 @@ namespace ts {
return undefined;
}
function inferToMultipleTypes(source: Type, targets: Type[], isIntersection: boolean) {
// We infer from types that are not naked type variables first so that inferences we
// make from nested naked type variables and given slightly higher priority by virtue
// of being first in the candidates array.
let typeVariableCount = 0;
for (const t of targets) {
if (getInferenceInfoForType(t)) {
typeVariableCount++;
}
else {
inferFromTypes(source, t);
}
}
// Inferences directly to naked type variables are given lower priority as they are
// less specific. For example, when inferring from Promise<string> to T | Promise<T>,
// we want to infer string for T, not Promise<string> | string. For intersection types
// we only infer to single naked type variables.
if (isIntersection ? typeVariableCount === 1 : typeVariableCount !== 0) {
const savePriority = priority;
priority |= InferencePriority.NakedTypeVariable;
for (const t of targets) {
if (getInferenceInfoForType(t)) {
inferFromTypes(source, t);
}
}
priority = savePriority;
}
}
function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean {
if (constraintType.flags & TypeFlags.Union) {
let result = false;
@@ -19287,26 +19301,7 @@ namespace ts {
function getArrayLiteralTupleTypeIfApplicable(elementTypes: Type[], contextualType: Type | undefined, hasRestElement: boolean, elementCount = elementTypes.length, readonly = false) {
// Infer a tuple type when the contextual type is or contains a tuple-like type
if (readonly || (contextualType && forEachType(contextualType, isTupleLikeType))) {
const minLength = elementCount - (hasRestElement ? 1 : 0);
const pattern = contextualType && contextualType.pattern;
// If array literal is contextually typed by a binding pattern or an assignment pattern, pad the resulting
// tuple type with the corresponding binding or assignment element types to make the lengths equal.
if (!hasRestElement && pattern && (pattern.kind === SyntaxKind.ArrayBindingPattern || pattern.kind === SyntaxKind.ArrayLiteralExpression)) {
const patternElements = (<BindingPattern | ArrayLiteralExpression>pattern).elements;
for (let i = elementCount; i < patternElements.length; i++) {
const e = patternElements[i];
if (hasDefaultValue(e)) {
elementTypes.push((<TypeReference>contextualType).typeArguments![i]);
}
else if (i < patternElements.length - 1 || !(e.kind === SyntaxKind.BindingElement && (<BindingElement>e).dotDotDotToken || e.kind === SyntaxKind.SpreadElement)) {
if (e.kind !== SyntaxKind.OmittedExpression) {
error(e, Diagnostics.Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value);
}
elementTypes.push(strictNullChecks ? implicitNeverType : undefinedWideningType);
}
}
}
return createTupleType(elementTypes, minLength, hasRestElement, readonly);
return createTupleType(elementTypes, elementCount - (hasRestElement ? 1 : 0), hasRestElement, readonly);
}
}
@@ -23906,8 +23901,10 @@ namespace ts {
if (isArrayLikeType(sourceType)) {
// We create a synthetic expression so that getIndexedAccessType doesn't get confused
// when the element is a SyntaxKind.ElementAccessExpression.
const elementType = getIndexedAccessType(sourceType, indexType, createSyntheticExpression(element, indexType));
const type = getFlowTypeOfDestructuring(element, elementType);
const accessFlags = hasDefaultValue(element) ? AccessFlags.NoTupleBoundsCheck : 0;
const elementType = getIndexedAccessTypeOrUndefined(sourceType, indexType, createSyntheticExpression(element, indexType), accessFlags) || errorType;
const assignedType = hasDefaultValue(element) ? getTypeWithFacts(elementType, TypeFacts.NEUndefined) : elementType;
const type = getFlowTypeOfDestructuring(element, assignedType);
return checkDestructuringAssignment(element, type, checkMode);
}
return checkDestructuringAssignment(element, elementType, checkMode);
@@ -24496,10 +24493,13 @@ namespace ts {
function checkDeclarationInitializer(declaration: HasExpressionInitializer) {
const initializer = getEffectiveInitializer(declaration)!;
const type = getTypeOfExpression(initializer, /*cache*/ true);
const padded = isParameter(declaration) && declaration.name.kind === SyntaxKind.ArrayBindingPattern &&
isTupleType(type) && !type.target.hasRestElement && getTypeReferenceArity(type) < declaration.name.elements.length ?
padTupleType(type, declaration.name) : type;
const widened = getCombinedNodeFlags(declaration) & NodeFlags.Const ||
isDeclarationReadonly(declaration) ||
isTypeAssertion(initializer) ||
isLiteralOfContextualType(type, getContextualType(initializer)) ? type : getWidenedLiteralType(type);
isLiteralOfContextualType(padded, getContextualType(initializer)) ? padded : getWidenedLiteralType(padded);
if (isInJSFile(declaration)) {
if (widened.flags & TypeFlags.Nullable) {
reportImplicitAny(declaration, anyType);
@@ -24513,6 +24513,22 @@ namespace ts {
return widened;
}
function padTupleType(type: TupleTypeReference, pattern: ArrayBindingPattern) {
const patternElements = pattern.elements;
const arity = getTypeReferenceArity(type);
const elementTypes = arity ? type.typeArguments!.slice() : [];
for (let i = arity; i < patternElements.length; i++) {
const e = patternElements[i];
if (i < patternElements.length - 1 || !(e.kind === SyntaxKind.BindingElement && e.dotDotDotToken)) {
elementTypes.push(!isOmittedExpression(e) && hasDefaultValue(e) ? getTypeFromBindingElement(e, /*includePatternInType*/ false, /*reportErrors*/ false) : anyType);
if (!isOmittedExpression(e) && !hasDefaultValue(e)) {
reportImplicitAny(e, anyType);
}
}
}
return createTupleType(elementTypes, type.target.minLength, /*hasRestElement*/ false, type.target.readonly);
}
function isLiteralOfContextualType(candidateType: Type, contextualType: Type | undefined): boolean {
if (contextualType) {
if (contextualType.flags & TypeFlags.UnionOrIntersection) {
@@ -24761,6 +24777,7 @@ namespace ts {
function checkExpression(node: Expression | QualifiedName, checkMode?: CheckMode, forceTuple?: boolean): Type {
const saveCurrentNode = currentNode;
currentNode = node;
instantiationCount = 0;
const uninstantiatedType = checkExpressionWorker(node, checkMode, forceTuple);
const type = instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode);
if (isConstEnumObjectType(type)) {
@@ -29482,6 +29499,7 @@ namespace ts {
if (node) {
const saveCurrentNode = currentNode;
currentNode = node;
instantiationCount = 0;
checkSourceElementWorker(node);
currentNode = saveCurrentNode;
}
@@ -29753,6 +29771,7 @@ namespace ts {
function checkDeferredNode(node: Node) {
const saveCurrentNode = currentNode;
currentNode = node;
instantiationCount = 0;
switch (node.kind) {
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
+15 -9
View File
@@ -1544,8 +1544,8 @@ namespace ts {
export function createIf(expression: Expression, thenStatement: Statement, elseStatement?: Statement) {
const node = <IfStatement>createSynthesizedNode(SyntaxKind.IfStatement);
node.expression = expression;
node.thenStatement = thenStatement;
node.elseStatement = elseStatement;
node.thenStatement = asEmbeddedStatement(thenStatement);
node.elseStatement = asEmbeddedStatement(elseStatement);
return node;
}
@@ -1559,7 +1559,7 @@ namespace ts {
export function createDo(statement: Statement, expression: Expression) {
const node = <DoStatement>createSynthesizedNode(SyntaxKind.DoStatement);
node.statement = statement;
node.statement = asEmbeddedStatement(statement);
node.expression = expression;
return node;
}
@@ -1574,7 +1574,7 @@ namespace ts {
export function createWhile(expression: Expression, statement: Statement) {
const node = <WhileStatement>createSynthesizedNode(SyntaxKind.WhileStatement);
node.expression = expression;
node.statement = statement;
node.statement = asEmbeddedStatement(statement);
return node;
}
@@ -1590,7 +1590,7 @@ namespace ts {
node.initializer = initializer;
node.condition = condition;
node.incrementor = incrementor;
node.statement = statement;
node.statement = asEmbeddedStatement(statement);
return node;
}
@@ -1607,7 +1607,7 @@ namespace ts {
const node = <ForInStatement>createSynthesizedNode(SyntaxKind.ForInStatement);
node.initializer = initializer;
node.expression = expression;
node.statement = statement;
node.statement = asEmbeddedStatement(statement);
return node;
}
@@ -1624,7 +1624,7 @@ namespace ts {
node.awaitModifier = awaitModifier;
node.initializer = initializer;
node.expression = expression;
node.statement = statement;
node.statement = asEmbeddedStatement(statement);
return node;
}
@@ -1676,7 +1676,7 @@ namespace ts {
export function createWith(expression: Expression, statement: Statement) {
const node = <WithStatement>createSynthesizedNode(SyntaxKind.WithStatement);
node.expression = expression;
node.statement = statement;
node.statement = asEmbeddedStatement(statement);
return node;
}
@@ -1704,7 +1704,7 @@ namespace ts {
export function createLabel(label: string | Identifier, statement: Statement) {
const node = <LabeledStatement>createSynthesizedNode(SyntaxKind.LabeledStatement);
node.label = asName(label);
node.statement = statement;
node.statement = asEmbeddedStatement(statement);
return node;
}
@@ -3080,6 +3080,12 @@ namespace ts {
return typeof value === "number" ? createToken(value) : value;
}
function asEmbeddedStatement<T extends Node>(statement: T): T | EmptyStatement;
function asEmbeddedStatement<T extends Node>(statement: T | undefined): T | EmptyStatement | undefined;
function asEmbeddedStatement<T extends Node>(statement: T | undefined): T | EmptyStatement | undefined {
return statement && isNotEmittedStatement(statement) ? setTextRange(setOriginalNode(createEmptyStatement(), statement), statement) : statement;
}
/**
* Clears any EmitNode entries from parse-tree nodes.
* @param sourceFile A source file.
+1 -1
View File
@@ -1395,7 +1395,7 @@ namespace ts {
const filePath = newSourceFile.path;
addFileToFilesByName(newSourceFile, filePath, newSourceFile.resolvedPath);
// Set the file as found during node modules search if it was found that way in old progra,
if (oldProgram.isSourceFileFromExternalLibrary(oldProgram.getSourceFileByPath(filePath)!)) {
if (oldProgram.isSourceFileFromExternalLibrary(oldProgram.getSourceFileByPath(newSourceFile.resolvedPath)!)) {
sourceFilesFoundSearchingNodeModules.set(filePath, true);
}
}
+13 -4
View File
@@ -2705,11 +2705,19 @@ namespace ts {
return isStringLiteralLike(node) || isNumericLiteral(node);
}
export function isSignedNumericLiteral(node: Node): node is PrefixUnaryExpression & { operand: NumericLiteral } {
return isPrefixUnaryExpression(node) && (node.operator === SyntaxKind.PlusToken || node.operator === SyntaxKind.MinusToken) && isNumericLiteral(node.operand);
}
/**
* A declaration has a dynamic name if both of the following are true:
* 1. The declaration has a computed property name
* 2. The computed name is *not* expressed as Symbol.<name>, where name
* is a property of the Symbol constructor that denotes a built in
* A declaration has a dynamic name if all of the following are true:
* 1. The declaration has a computed property name.
* 2. The computed name is *not* expressed as a StringLiteral.
* 3. The computed name is *not* expressed as a NumericLiteral.
* 4. The computed name is *not* expressed as a PlusToken or MinusToken
* immediately followed by a NumericLiteral.
* 5. The computed name is *not* expressed as `Symbol.<name>`, where `<name>`
* is a property of the Symbol constructor that denotes a built-in
* Symbol.
*/
export function hasDynamicName(declaration: Declaration): declaration is DynamicNamedDeclaration {
@@ -2720,6 +2728,7 @@ namespace ts {
export function isDynamicName(name: DeclarationName): boolean {
return name.kind === SyntaxKind.ComputedPropertyName &&
!isStringOrNumericLiteralLike(name.expression) &&
!isSignedNumericLiteral(name.expression) &&
!isWellKnownSymbolSyntactically(name.expression);
}
+8 -1
View File
@@ -501,7 +501,7 @@ namespace Harness {
}
function enumerateTestFiles(runner: RunnerBase) {
return runner.enumerateTestFiles();
return runner.getTestFiles();
}
function listFiles(path: string, spec: RegExp, options: { recursive?: boolean } = {}) {
@@ -1311,6 +1311,13 @@ namespace Harness {
if (jsCode.length && jsCode.charCodeAt(jsCode.length - 1) !== ts.CharacterCodes.lineFeed) {
jsCode += "\r\n";
}
if (!result.diagnostics.length && !ts.endsWith(file.file, ts.Extension.Json)) {
const fileParseResult = ts.createSourceFile(file.file, file.text, options.target || ts.ScriptTarget.ES3, /*parentNodes*/ false, ts.endsWith(file.file, "x") ? ts.ScriptKind.JSX : ts.ScriptKind.JS);
if (ts.length(fileParseResult.parseDiagnostics)) {
jsCode += getErrorBaseline([file.asTestFile()], fileParseResult.parseDiagnostics);
return;
}
}
jsCode += fileOutput(file, harnessSettings);
});
+11
View File
@@ -2,6 +2,9 @@ type TestRunnerKind = CompilerTestKind | FourslashTestKind | "project" | "rwc" |
type CompilerTestKind = "conformance" | "compiler";
type FourslashTestKind = "fourslash" | "fourslash-shims" | "fourslash-shims-pp" | "fourslash-server";
let shards = 1;
let shardId = 1;
abstract class RunnerBase {
// contains the tests to run
public tests: (string | Harness.FileBasedTest)[] = [];
@@ -19,6 +22,14 @@ abstract class RunnerBase {
abstract enumerateTestFiles(): (string | Harness.FileBasedTest)[];
getTestFiles(): ReturnType<this["enumerateTestFiles"]> {
const all = this.enumerateTestFiles();
if (shards === 1) {
return all as ReturnType<this["enumerateTestFiles"]>;
}
return all.filter((_val, idx) => idx % shards === (shardId - 1)) as ReturnType<this["enumerateTestFiles"]>;
}
/** The working directory where tests are found. Needed for batch testing where the input path will differ from the output path inside baselines */
public workingDirectory = "";
+7 -1
View File
@@ -2234,7 +2234,13 @@ namespace ts.server {
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;
if (!declarationInfo) {
if (sourceFileName) {
// Project contains source file and it generates the generated file name
project.addGeneratedFileWatch(generatedFileName, sourceFileName);
}
return undefined;
}
// Try to get from cache
declarationInfo.getSnapshot(); // Ensure synchronized
+100
View File
@@ -109,12 +109,22 @@ namespace ts.server {
return value instanceof ScriptInfo;
}
interface GeneratedFileWatcher {
generatedFilePath: Path;
watcher: FileWatcher;
}
type GeneratedFileWatcherMap = GeneratedFileWatcher | Map<GeneratedFileWatcher>;
function isGeneratedFileWatcher(watch: GeneratedFileWatcherMap): watch is GeneratedFileWatcher {
return (watch as GeneratedFileWatcher).generatedFilePath !== undefined;
}
export abstract class Project implements LanguageServiceHost, ModuleResolutionHost {
private rootFiles: ScriptInfo[] = [];
private rootFilesMap: Map<ProjectRoot> = createMap<ProjectRoot>();
private program: Program | undefined;
private externalFiles: SortedReadonlyArray<string> | undefined;
private missingFilesMap: Map<FileWatcher> | undefined;
private generatedFilesMap: GeneratedFileWatcherMap | undefined;
private plugins: PluginModuleWithName[] = [];
/*@internal*/
@@ -568,6 +578,7 @@ namespace ts.server {
this.lastFileExceededProgramSize = lastFileExceededProgramSize;
this.builderState = undefined;
this.resolutionCache.closeTypeRootsWatch();
this.clearGeneratedFileWatch();
this.projectService.onUpdateLanguageServiceStateForProject(this, /*languageServiceEnabled*/ false);
}
@@ -649,6 +660,7 @@ namespace ts.server {
clearMap(this.missingFilesMap, closeFileWatcher);
this.missingFilesMap = undefined!;
}
this.clearGeneratedFileWatch();
// signal language service to release source files acquired from document registry
this.languageService.dispose();
@@ -942,6 +954,39 @@ namespace ts.server {
missingFilePath => this.addMissingFileWatcher(missingFilePath)
);
if (this.generatedFilesMap) {
const outPath = this.compilerOptions.outFile && this.compilerOptions.out;
if (isGeneratedFileWatcher(this.generatedFilesMap)) {
// --out
if (!outPath || !this.isValidGeneratedFileWatcher(
removeFileExtension(outPath) + Extension.Dts,
this.generatedFilesMap,
)) {
this.clearGeneratedFileWatch();
}
}
else {
// MultiFile
if (outPath) {
this.clearGeneratedFileWatch();
}
else {
this.generatedFilesMap.forEach((watcher, source) => {
const sourceFile = this.program!.getSourceFileByPath(source as Path);
if (!sourceFile ||
sourceFile.resolvedPath !== source ||
!this.isValidGeneratedFileWatcher(
getDeclarationEmitOutputFilePathWorker(sourceFile.fileName, this.compilerOptions, this.currentDirectory, this.program!.getCommonSourceDirectory(), this.getCanonicalFileName),
watcher
)) {
closeFileWatcherOf(watcher);
(this.generatedFilesMap as Map<GeneratedFileWatcher>).delete(source);
}
});
}
}
}
// Watch the type locations that would be added to program as part of automatic type resolutions
if (this.languageServiceEnabled) {
this.resolutionCache.updateTypeRootsWatch();
@@ -1006,6 +1051,61 @@ namespace ts.server {
return !!this.missingFilesMap && this.missingFilesMap.has(path);
}
/* @internal */
addGeneratedFileWatch(generatedFile: string, sourceFile: string) {
if (this.compilerOptions.outFile || this.compilerOptions.out) {
// Single watcher
if (!this.generatedFilesMap) {
this.generatedFilesMap = this.createGeneratedFileWatcher(generatedFile);
}
}
else {
// Map
const path = this.toPath(sourceFile);
if (this.generatedFilesMap) {
if (isGeneratedFileWatcher(this.generatedFilesMap)) {
Debug.fail(`${this.projectName} Expected to not have --out watcher for generated file with options: ${JSON.stringify(this.compilerOptions)}`);
return;
}
if (this.generatedFilesMap.has(path)) return;
}
else {
this.generatedFilesMap = createMap();
}
this.generatedFilesMap.set(path, this.createGeneratedFileWatcher(generatedFile));
}
}
private createGeneratedFileWatcher(generatedFile: string): GeneratedFileWatcher {
return {
generatedFilePath: this.toPath(generatedFile),
watcher: this.projectService.watchFactory.watchFile(
this.projectService.host,
generatedFile,
() => this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this),
PollingInterval.High,
WatchType.MissingGeneratedFile,
this
)
};
}
private isValidGeneratedFileWatcher(generateFile: string, watcher: GeneratedFileWatcher) {
return this.toPath(generateFile) === watcher.generatedFilePath;
}
private clearGeneratedFileWatch() {
if (this.generatedFilesMap) {
if (isGeneratedFileWatcher(this.generatedFilesMap)) {
closeFileWatcherOf(this.generatedFilesMap);
}
else {
clearMap(this.generatedFilesMap, closeFileWatcherOf);
}
this.generatedFilesMap = undefined;
}
}
getScriptInfoForNormalizedPath(fileName: NormalizedPath): ScriptInfo | undefined {
const scriptInfo = this.projectService.getScriptInfoForPath(this.toPath(fileName));
if (scriptInfo && !scriptInfo.isAttached(this)) {
+1
View File
@@ -227,5 +227,6 @@ namespace ts {
NodeModulesForClosedScriptInfo = "node_modules for closed script infos in them",
MissingSourceMapFile = "Missing source map file",
NoopConfigFileForInferredRoot = "Noop Config file for the inferred project root",
MissingGeneratedFile = "Missing generated file"
}
}
+6 -2
View File
@@ -72,9 +72,13 @@ namespace ts.SmartSelectionRange {
function pushSelectionRange(start: number, end: number): void {
// Skip empty ranges
if (start !== end) {
// Skip ranges that are identical to the parent
const textSpan = createTextSpanFromBounds(start, end);
if (!selectionRange || !textSpansEqual(textSpan, selectionRange.textSpan)) {
if (!selectionRange || (
// Skip ranges that are identical to the parent
!textSpansEqual(textSpan, selectionRange.textSpan) &&
// Skip ranges that don’t contain the original position
textSpanIntersectsWithPosition(textSpan, pos)
)) {
selectionRange = { textSpan, ...selectionRange && { parent: selectionRange } };
}
}
+130 -104
View File
@@ -847,9 +847,10 @@ namespace ts.textChanges {
/** Note: output node may be mutated input node. */
export function getNonformattedText(node: Node, sourceFile: SourceFile | undefined, newLineCharacter: string): { text: string, node: Node } {
const writer = new Writer(newLineCharacter);
const omitTrailingSemicolon = !!sourceFile && !probablyUsesSemicolons(sourceFile);
const writer = createWriter(newLineCharacter, omitTrailingSemicolon);
const newLine = newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed;
createPrinter({ newLine, neverAsciiEscape: true }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer);
createPrinter({ newLine, neverAsciiEscape: true, omitTrailingSemicolon }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer);
return { text: writer.getText(), node: assignPositionsToNode(node) };
}
}
@@ -887,143 +888,168 @@ namespace ts.textChanges {
return nodeArray;
}
class Writer implements EmitTextWriter, PrintHandlers {
private lastNonTriviaPosition = 0;
private readonly writer: EmitTextWriter;
interface TextChangesWriter extends EmitTextWriter, PrintHandlers {}
public readonly onEmitNode: PrintHandlers["onEmitNode"];
public readonly onBeforeEmitNodeArray: PrintHandlers["onBeforeEmitNodeArray"];
public readonly onAfterEmitNodeArray: PrintHandlers["onAfterEmitNodeArray"];
public readonly onBeforeEmitToken: PrintHandlers["onBeforeEmitToken"];
public readonly onAfterEmitToken: PrintHandlers["onAfterEmitToken"];
function createWriter(newLine: string, omitTrailingSemicolon?: boolean): TextChangesWriter {
let lastNonTriviaPosition = 0;
constructor(newLine: string) {
this.writer = createTextWriter(newLine);
this.onEmitNode = (hint, node, printCallback) => {
if (node) {
setPos(node, this.lastNonTriviaPosition);
}
printCallback(hint, node);
if (node) {
setEnd(node, this.lastNonTriviaPosition);
}
};
this.onBeforeEmitNodeArray = nodes => {
if (nodes) {
setPos(nodes, this.lastNonTriviaPosition);
}
};
this.onAfterEmitNodeArray = nodes => {
if (nodes) {
setEnd(nodes, this.lastNonTriviaPosition);
}
};
this.onBeforeEmitToken = node => {
if (node) {
setPos(node, this.lastNonTriviaPosition);
}
};
this.onAfterEmitToken = node => {
if (node) {
setEnd(node, this.lastNonTriviaPosition);
}
};
}
private setLastNonTriviaPosition(s: string, force: boolean) {
const writer = omitTrailingSemicolon ? getTrailingSemicolonOmittingWriter(createTextWriter(newLine)) : createTextWriter(newLine);
const onEmitNode: PrintHandlers["onEmitNode"] = (hint, node, printCallback) => {
if (node) {
setPos(node, lastNonTriviaPosition);
}
printCallback(hint, node);
if (node) {
setEnd(node, lastNonTriviaPosition);
}
};
const onBeforeEmitNodeArray: PrintHandlers["onBeforeEmitNodeArray"] = nodes => {
if (nodes) {
setPos(nodes, lastNonTriviaPosition);
}
};
const onAfterEmitNodeArray: PrintHandlers["onAfterEmitNodeArray"] = nodes => {
if (nodes) {
setEnd(nodes, lastNonTriviaPosition);
}
};
const onBeforeEmitToken: PrintHandlers["onBeforeEmitToken"] = node => {
if (node) {
setPos(node, lastNonTriviaPosition);
}
};
const onAfterEmitToken: PrintHandlers["onAfterEmitToken"] = node => {
if (node) {
setEnd(node, lastNonTriviaPosition);
}
};
function setLastNonTriviaPosition(s: string, force: boolean) {
if (force || !isTrivia(s)) {
this.lastNonTriviaPosition = this.writer.getTextPos();
lastNonTriviaPosition = writer.getTextPos();
let i = 0;
while (isWhiteSpaceLike(s.charCodeAt(s.length - i - 1))) {
i++;
}
// trim trailing whitespaces
this.lastNonTriviaPosition -= i;
lastNonTriviaPosition -= i;
}
}
write(s: string): void {
this.writer.write(s);
this.setLastNonTriviaPosition(s, /*force*/ false);
function write(s: string): void {
writer.write(s);
setLastNonTriviaPosition(s, /*force*/ false);
}
writeComment(s: string): void {
this.writer.writeComment(s);
function writeComment(s: string): void {
writer.writeComment(s);
}
writeKeyword(s: string): void {
this.writer.writeKeyword(s);
this.setLastNonTriviaPosition(s, /*force*/ false);
function writeKeyword(s: string): void {
writer.writeKeyword(s);
setLastNonTriviaPosition(s, /*force*/ false);
}
writeOperator(s: string): void {
this.writer.writeOperator(s);
this.setLastNonTriviaPosition(s, /*force*/ false);
function writeOperator(s: string): void {
writer.writeOperator(s);
setLastNonTriviaPosition(s, /*force*/ false);
}
writePunctuation(s: string): void {
this.writer.writePunctuation(s);
this.setLastNonTriviaPosition(s, /*force*/ false);
function writePunctuation(s: string): void {
writer.writePunctuation(s);
setLastNonTriviaPosition(s, /*force*/ false);
}
writeTrailingSemicolon(s: string): void {
this.writer.writeTrailingSemicolon(s);
this.setLastNonTriviaPosition(s, /*force*/ false);
function writeTrailingSemicolon(s: string): void {
writer.writeTrailingSemicolon(s);
setLastNonTriviaPosition(s, /*force*/ false);
}
writeParameter(s: string): void {
this.writer.writeParameter(s);
this.setLastNonTriviaPosition(s, /*force*/ false);
function writeParameter(s: string): void {
writer.writeParameter(s);
setLastNonTriviaPosition(s, /*force*/ false);
}
writeProperty(s: string): void {
this.writer.writeProperty(s);
this.setLastNonTriviaPosition(s, /*force*/ false);
function writeProperty(s: string): void {
writer.writeProperty(s);
setLastNonTriviaPosition(s, /*force*/ false);
}
writeSpace(s: string): void {
this.writer.writeSpace(s);
this.setLastNonTriviaPosition(s, /*force*/ false);
function writeSpace(s: string): void {
writer.writeSpace(s);
setLastNonTriviaPosition(s, /*force*/ false);
}
writeStringLiteral(s: string): void {
this.writer.writeStringLiteral(s);
this.setLastNonTriviaPosition(s, /*force*/ false);
function writeStringLiteral(s: string): void {
writer.writeStringLiteral(s);
setLastNonTriviaPosition(s, /*force*/ false);
}
writeSymbol(s: string, sym: Symbol): void {
this.writer.writeSymbol(s, sym);
this.setLastNonTriviaPosition(s, /*force*/ false);
function writeSymbol(s: string, sym: Symbol): void {
writer.writeSymbol(s, sym);
setLastNonTriviaPosition(s, /*force*/ false);
}
writeLine(): void {
this.writer.writeLine();
function writeLine(): void {
writer.writeLine();
}
increaseIndent(): void {
this.writer.increaseIndent();
function increaseIndent(): void {
writer.increaseIndent();
}
decreaseIndent(): void {
this.writer.decreaseIndent();
function decreaseIndent(): void {
writer.decreaseIndent();
}
getText(): string {
return this.writer.getText();
function getText(): string {
return writer.getText();
}
rawWrite(s: string): void {
this.writer.rawWrite(s);
this.setLastNonTriviaPosition(s, /*force*/ false);
function rawWrite(s: string): void {
writer.rawWrite(s);
setLastNonTriviaPosition(s, /*force*/ false);
}
writeLiteral(s: string): void {
this.writer.writeLiteral(s);
this.setLastNonTriviaPosition(s, /*force*/ true);
function writeLiteral(s: string): void {
writer.writeLiteral(s);
setLastNonTriviaPosition(s, /*force*/ true);
}
getTextPos(): number {
return this.writer.getTextPos();
function getTextPos(): number {
return writer.getTextPos();
}
getLine(): number {
return this.writer.getLine();
function getLine(): number {
return writer.getLine();
}
getColumn(): number {
return this.writer.getColumn();
function getColumn(): number {
return writer.getColumn();
}
getIndent(): number {
return this.writer.getIndent();
function getIndent(): number {
return writer.getIndent();
}
isAtStartOfLine(): boolean {
return this.writer.isAtStartOfLine();
function isAtStartOfLine(): boolean {
return writer.isAtStartOfLine();
}
clear(): void {
this.writer.clear();
this.lastNonTriviaPosition = 0;
function clear(): void {
writer.clear();
lastNonTriviaPosition = 0;
}
return {
onEmitNode,
onBeforeEmitNodeArray,
onAfterEmitNodeArray,
onBeforeEmitToken,
onAfterEmitToken,
write,
writeComment,
writeKeyword,
writeOperator,
writePunctuation,
writeTrailingSemicolon,
writeParameter,
writeProperty,
writeSpace,
writeStringLiteral,
writeSymbol,
writeLine,
increaseIndent,
decreaseIndent,
getText,
rawWrite,
writeLiteral,
getTextPos,
getLine,
getColumn,
getIndent,
isAtStartOfLine,
clear
};
}
function getInsertionPositionAtSourceFileTop(sourceFile: SourceFile): number {
+47
View File
@@ -1991,4 +1991,51 @@ namespace ts {
});
return typeIsAccessible ? res : undefined;
}
export function syntaxUsuallyHasTrailingSemicolon(kind: SyntaxKind) {
return kind === SyntaxKind.VariableStatement
|| kind === SyntaxKind.ExpressionStatement
|| kind === SyntaxKind.DoStatement
|| kind === SyntaxKind.ContinueStatement
|| kind === SyntaxKind.BreakStatement
|| kind === SyntaxKind.ReturnStatement
|| kind === SyntaxKind.ThrowStatement
|| kind === SyntaxKind.DebuggerStatement
|| kind === SyntaxKind.PropertyDeclaration
|| kind === SyntaxKind.TypeAliasDeclaration
|| kind === SyntaxKind.ImportDeclaration
|| kind === SyntaxKind.ImportEqualsDeclaration
|| kind === SyntaxKind.ExportDeclaration;
}
export function probablyUsesSemicolons(sourceFile: SourceFile): boolean {
let withSemicolon = 0;
let withoutSemicolon = 0;
const nStatementsToObserve = 5;
forEachChild(sourceFile, function visit(node): boolean | undefined {
if (syntaxUsuallyHasTrailingSemicolon(node.kind)) {
const lastToken = node.getLastToken(sourceFile);
if (lastToken && lastToken.kind === SyntaxKind.SemicolonToken) {
withSemicolon++;
}
else {
withoutSemicolon++;
}
}
if (withSemicolon + withoutSemicolon >= nStatementsToObserve) {
return true;
}
return forEachChild(node, visit);
});
// One statement missing a semicolon isn’t sufficient evidence to say the user
// doesn’t want semicolons, because they may not even be done writing that statement.
if (withSemicolon === 0 && withoutSemicolon <= 1) {
return true;
}
// If even 2/5 places have a semicolon, the user probably wants semicolons
return withSemicolon / withoutSemicolon > 1 / nStatementsToObserve;
}
}
+3 -3
View File
@@ -24,7 +24,7 @@ abstract class ExternalCompileRunnerBase extends RunnerBase {
*/
initializeTests(): void {
// Read in and evaluate the test list
const testList = this.tests && this.tests.length ? this.tests : this.enumerateTestFiles();
const testList = this.tests && this.tests.length ? this.tests : this.getTestFiles();
// tslint:disable-next-line:no-this-assignment
const cls = this;
@@ -113,7 +113,7 @@ class DockerfileRunner extends ExternalCompileRunnerBase {
}
initializeTests(): void {
// Read in and evaluate the test list
const testList = this.tests && this.tests.length ? this.tests : this.enumerateTestFiles();
const testList = this.tests && this.tests.length ? this.tests : this.getTestFiles();
// tslint:disable-next-line:no-this-assignment
const cls = this;
@@ -201,7 +201,7 @@ function sanitizeTimestamps(result: string): string {
function sanitizeVersionSpecifiers(result: string): string {
return result
.replace(/\d+.\d+.\d+-insiders.\d\d\d\d\d\d\d\d/g, "X.X.X-insiders.xxxxxxxx")
.replace(/([@v])\d+\.\d+\.\d+/g, "$1X.X.X");
.replace(/([@v\()])\d+\.\d+\.\d+/g, "$1X.X.X");
}
/**
+1 -1
View File
@@ -222,7 +222,7 @@ namespace Harness.Parallel.Host {
console.log("Discovering runner-based tests...");
const discoverStart = +(new Date());
for (const runner of runners) {
for (const test of runner.enumerateTestFiles()) {
for (const test of runner.getTestFiles()) {
const file = typeof test === "string" ? test : test.file;
let size: number;
if (!perfData) {
+5 -1
View File
@@ -32,7 +32,11 @@ namespace project {
export class ProjectRunner extends RunnerBase {
public enumerateTestFiles() {
return this.enumerateFiles("tests/cases/project", /\.json$/, { recursive: true });
const all = this.enumerateFiles("tests/cases/project", /\.json$/, { recursive: true });
if (shards === 1) {
return all;
}
return all.filter((_val, idx) => idx % shards === (shardId - 1));
}
public kind(): TestRunnerKind {
+11
View File
@@ -80,6 +80,8 @@ interface TestConfig {
timeout?: number;
keepFailed?: boolean;
skipPercent?: number;
shardId?: number;
shards?: number;
}
interface TaskSet {
@@ -114,6 +116,12 @@ function handleTestConfig() {
if (testConfig.skipPercent !== undefined) {
skipPercent = testConfig.skipPercent;
}
if (testConfig.shardId) {
shardId = testConfig.shardId;
}
if (testConfig.shards) {
shards = testConfig.shards;
}
if (testConfig.stackTraceLimit === "full") {
(<any>Error).stackTraceLimit = Infinity;
@@ -129,6 +137,9 @@ function handleTestConfig() {
const runnerConfig = testConfig.runners || testConfig.test;
if (runnerConfig && runnerConfig.length > 0) {
if (testConfig.runners) {
runUnitTests = runnerConfig.indexOf("unittest") !== -1;
}
for (const option of runnerConfig) {
if (!option) {
continue;
+1 -1
View File
@@ -225,7 +225,7 @@ class RWCRunner extends RunnerBase {
*/
public initializeTests(): void {
// Read in and evaluate the test list
for (const test of this.tests && this.tests.length ? this.tests : this.enumerateTestFiles()) {
for (const test of this.tests && this.tests.length ? this.tests : this.getTestFiles()) {
this.runTest(typeof test === "string" ? test : test.file);
}
}
+1 -1
View File
@@ -97,7 +97,7 @@ class Test262BaselineRunner extends RunnerBase {
public initializeTests() {
// this will set up a series of describe/it blocks to run between the setup and cleanup phases
if (this.tests.length === 0) {
const testFiles = this.enumerateTestFiles();
const testFiles = this.getTestFiles();
testFiles.forEach(fn => {
this.runTest(fn);
});
@@ -94,6 +94,7 @@ namespace ts.projectSystem {
describe("with main and depedency project", () => {
const projectLocation = "/user/username/projects/myproject";
const dependecyLocation = `${projectLocation}/dependency`;
const dependecyDeclsLocation = `${projectLocation}/decls`;
const mainLocation = `${projectLocation}/main`;
const dependencyTs: File = {
path: `${dependecyLocation}/FnS.ts`,
@@ -106,7 +107,7 @@ export function fn5() { }
};
const dependencyConfig: File = {
path: `${dependecyLocation}/tsconfig.json`,
content: JSON.stringify({ compilerOptions: { composite: true, declarationMap: true } })
content: JSON.stringify({ compilerOptions: { composite: true, declarationMap: true, declarationDir: "../decls" } })
};
const mainTs: File = {
@@ -117,7 +118,7 @@ export function fn5() { }
fn3,
fn4,
fn5
} from '../dependency/fns'
} from '../decls/fns'
fn1();
fn2();
@@ -142,9 +143,9 @@ fn5();
path: `${projectLocation}/random/tsconfig.json`,
content: "{}"
};
const dtsLocation = `${dependecyLocation}/FnS.d.ts`;
const dtsLocation = `${dependecyDeclsLocation}/FnS.d.ts`;
const dtsPath = dtsLocation.toLowerCase() as Path;
const dtsMapLocation = `${dtsLocation}.map`;
const dtsMapLocation = `${dependecyDeclsLocation}/FnS.d.ts.map`;
const dtsMapPath = dtsMapLocation.toLowerCase() as Path;
const files = [dependencyTs, dependencyConfig, mainTs, mainConfig, libFile, randomFile, randomConfig];
@@ -217,7 +218,7 @@ fn5();
start: { line: fn + 1, offset: 5 },
end: { line: fn + 1, offset: 8 },
contextStart: { line: 1, offset: 1 },
contextEnd: { line: 7, offset: 27 }
contextEnd: { line: 7, offset: 22 }
};
}
function usageSpan(fn: number): protocol.TextSpan {
@@ -287,19 +288,25 @@ fn5();
function verifyDocumentPositionMapperUpdates(
mainScenario: string,
verifier: ReadonlyArray<DocumentPositionMapperVerifier>,
closedInfos: ReadonlyArray<string>) {
closedInfos: ReadonlyArray<string>,
withRefs: boolean) {
const openFiles = verifier.map(v => v.openFile);
const expectedProjectActualFiles = verifier.map(v => v.expectedProjectActualFiles);
const actionGetters = verifier.map(v => v.actionGetter);
const openFileLastLines = verifier.map(v => v.openFileLastLine);
const configFiles = openFiles.map(openFile => `${getDirectoryPath(openFile.path)}/tsconfig.json`);
const openInfos = openFiles.map(f => f.path);
// When usage and dependency are used, dependency config is part of closedInfo so ignore
const otherWatchedFiles = verifier.length > 1 ? [configFiles[0]] : configFiles;
const otherWatchedFiles = withRefs && verifier.length > 1 ? [configFiles[0]] : configFiles;
function openTsFile(onHostCreate?: (host: TestServerHost) => void) {
const host = createHost(files, [mainConfig.path]);
if (!withRefs) {
// Erase project reference
host.writeFile(mainConfig.path, JSON.stringify({
compilerOptions: { composite: true, declarationMap: true }
}));
}
if (onHostCreate) {
onHostCreate(host);
}
@@ -336,7 +343,7 @@ fn5();
);
}
function verifyInfosWhenNoDtsFile(session: TestSession, host: TestServerHost, dependencyTsAndMapOk?: true) {
function verifyInfosWhenNoDtsFile(session: TestSession, host: TestServerHost, watchDts: boolean, dependencyTsAndMapOk?: true) {
const dtsMapClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsMapPath ? f : undefined);
const dtsClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsPath ? f : undefined);
verifyInfosWithRandom(
@@ -344,8 +351,7 @@ fn5();
host,
openInfos,
closedInfos.filter(f => (dependencyTsAndMapOk || f !== dtsMapClosedInfo) && f !== dtsClosedInfo && (dependencyTsAndMapOk || f !== dependencyTs.path)),
// When project actual file contains dts, it needs to be watched
dtsClosedInfo && expectedProjectActualFiles.some(expectedProjectActualFiles => expectedProjectActualFiles.some(f => f.toLowerCase() === dtsPath)) ?
dtsClosedInfo && watchDts ?
otherWatchedFiles.concat(dtsClosedInfo) :
otherWatchedFiles
);
@@ -361,22 +367,22 @@ fn5();
}
}
function action(actionGetter: SessionActionGetter, fn: number, session: TestSession) {
const { reqName, request, expectedResponse, expectedResponseNoMap, expectedResponseNoDts } = actionGetter(fn);
function action(verifier: DocumentPositionMapperVerifier, fn: number, session: TestSession) {
const { reqName, request, expectedResponse, expectedResponseNoMap, expectedResponseNoDts } = verifier.actionGetter(fn);
const { response } = session.executeCommandSeq(request);
return { reqName, response, expectedResponse, expectedResponseNoMap, expectedResponseNoDts };
return { reqName, response, expectedResponse, expectedResponseNoMap, expectedResponseNoDts, verifier };
}
function firstAction(session: TestSession) {
actionGetters.forEach(actionGetter => action(actionGetter, 1, session));
verifier.forEach(v => action(v, 1, session));
}
function verifyAllFnActionWorker(session: TestSession, verifyAction: (result: ReturnType<typeof action>, dtsInfo: server.ScriptInfo | undefined, isFirst: boolean) => void, dtsAbsent?: true) {
// action
let isFirst = true;
for (const actionGetter of actionGetters) {
for (const v of verifier) {
for (let fn = 1; fn <= 5; fn++) {
const result = action(actionGetter, fn, session);
const result = action(v, fn, session);
const dtsInfo = session.getProjectService().filenameToScriptInfo.get(dtsPath);
if (dtsAbsent) {
assert.isUndefined(dtsInfo);
@@ -449,9 +455,17 @@ fn5();
dependencyTsAndMapOk?: true
) {
// action
verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseNoDts }) => {
verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseNoDts, verifier }) => {
assert.deepEqual(response, expectedResponseNoDts || expectedResponse, `Failed on ${reqName}`);
verifyInfosWhenNoDtsFile(session, host, dependencyTsAndMapOk);
verifyInfosWhenNoDtsFile(
session,
host,
// Even when project actual file contains dts, its not watched because the dts is in another folder and module resolution just fails
// instead of succeeding to source file and then mapping using project reference (When using usage location)
// But watched if sourcemapper is in source project since we need to keep track of dts to update the source mapper for any potential usages
verifier.expectedProjectActualFiles.every(f => f.toLowerCase() !== dtsPath),
dependencyTsAndMapOk,
);
}, /*dtsAbsent*/ true);
}
@@ -535,7 +549,11 @@ fn5();
// Collecting at this point retains dependency.d.ts and map watcher
closeFilesForSession([randomFile], session);
openFilesForSession([randomFile], session);
verifyInfosWhenNoDtsFile(session, host);
verifyInfosWhenNoDtsFile(
session,
host,
!!forEach(verifier, v => v.expectedProjectActualFiles.every(f => f.toLowerCase() !== dtsPath))
);
// Closing open file, removes dependencies too
closeFilesForSession([...openFiles, randomFile], session);
@@ -616,7 +634,7 @@ fn5();
"when dependency file's map changes",
host => host.writeFile(
dtsMapLocation,
`{"version":3,"file":"FnS.d.ts","sourceRoot":"","sources":["FnS.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,eAAO,MAAM,CAAC,KAAK,CAAC"}`
`{"version":3,"file":"FnS.d.ts","sourceRoot":"","sources":["../dependency/FnS.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,eAAO,MAAM,CAAC,KAAK,CAAC"}`
),
/*afterActionDocumentPositionMapperNotEquals*/ true
);
@@ -635,44 +653,133 @@ fn5();
);
}
const usageVerifier: DocumentPositionMapperVerifier = {
openFile: mainTs,
expectedProjectActualFiles: [mainTs.path, libFile.path, mainConfig.path, dtsPath],
actionGetter: gotoDefintinionFromMainTs,
openFileLastLine: 14
};
describe("from project that uses dependency", () => {
const closedInfos = [dependencyTs.path, dependencyConfig.path, libFile.path, dtsPath, dtsMapLocation];
verifyDocumentPositionMapperUpdates(
"can go to definition correctly",
[usageVerifier],
closedInfos
);
});
function verifyScenarios(withRefs: boolean) {
describe(withRefs ? "when main tsconfig has project reference" : "when main tsconfig doesnt have project reference", () => {
const usageVerifier: DocumentPositionMapperVerifier = {
openFile: mainTs,
expectedProjectActualFiles: [mainTs.path, libFile.path, mainConfig.path, dtsPath],
actionGetter: gotoDefintinionFromMainTs,
openFileLastLine: 14
};
describe("from project that uses dependency", () => {
const closedInfos = withRefs ?
[dependencyTs.path, dependencyConfig.path, libFile.path, dtsPath, dtsMapLocation] :
[dependencyTs.path, libFile.path, dtsPath, dtsMapLocation];
verifyDocumentPositionMapperUpdates(
"can go to definition correctly",
[usageVerifier],
closedInfos,
withRefs
);
});
const definingVerifier: DocumentPositionMapperVerifier = {
openFile: dependencyTs,
expectedProjectActualFiles: [dependencyTs.path, libFile.path, dependencyConfig.path],
actionGetter: renameFromDependencyTs,
openFileLastLine: 6
};
describe("from defining project", () => {
const closedInfos = [libFile.path, dtsLocation, dtsMapLocation];
verifyDocumentPositionMapperUpdates(
"rename locations from dependency",
[definingVerifier],
closedInfos
);
});
const definingVerifier: DocumentPositionMapperVerifier = {
openFile: dependencyTs,
expectedProjectActualFiles: [dependencyTs.path, libFile.path, dependencyConfig.path],
actionGetter: renameFromDependencyTs,
openFileLastLine: 6,
};
describe("from defining project", () => {
const closedInfos = [libFile.path, dtsLocation, dtsMapLocation];
verifyDocumentPositionMapperUpdates(
"rename locations from dependency",
[definingVerifier],
closedInfos,
withRefs
);
});
describe("when opening depedency and usage project", () => {
const closedInfos = [libFile.path, dtsPath, dtsMapLocation, dependencyConfig.path];
verifyDocumentPositionMapperUpdates(
"goto Definition in usage and rename locations from defining project",
[usageVerifier, { ...definingVerifier, actionGetter: renameFromDependencyTsWithBothProjectsOpen }],
closedInfos
);
});
describe("when opening depedency and usage project", () => {
const closedInfos = withRefs ?
[libFile.path, dtsPath, dtsMapLocation, dependencyConfig.path] :
[libFile.path, dtsPath, dtsMapLocation];
verifyDocumentPositionMapperUpdates(
"goto Definition in usage and rename locations from defining project",
[usageVerifier, { ...definingVerifier, actionGetter: renameFromDependencyTsWithBothProjectsOpen }],
closedInfos,
withRefs
);
});
});
}
verifyScenarios(/*withRefs*/ false);
verifyScenarios(/*withRefs*/ true);
});
it("reusing d.ts files from composite and non composite projects", () => {
const projectLocation = "/user/username/projects/myproject";
const configA: File = {
path: `${projectLocation}/compositea/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
composite: true,
outDir: "../dist/",
rootDir: "../",
baseUrl: "../",
paths: { "@ref/*": ["./dist/*"] }
}
})
};
const aTs: File = {
path: `${projectLocation}/compositea/a.ts`,
content: `import { b } from "@ref/compositeb/b";`
};
const a2Ts: File = {
path: `${projectLocation}/compositea/a2.ts`,
content: `export const x = 10;`
};
const configB: File = {
path: `${projectLocation}/compositeb/tsconfig.json`,
content: configA.content
};
const bTs: File = {
path: `${projectLocation}/compositeb/b.ts`,
content: "export function b() {}"
};
const bDts: File = {
path: `${projectLocation}/dist/compositeb/b.d.ts`,
content: "export declare function b(): void;"
};
const configC: File = {
path: `${projectLocation}/compositec/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
composite: true,
outDir: "../dist/",
rootDir: "../",
baseUrl: "../",
paths: { "@ref/*": ["./*"] }
},
references: [{ path: "../compositeb" }]
})
};
const cTs: File = {
path: `${projectLocation}/compositec/c.ts`,
content: aTs.content
};
const files = [libFile, aTs, a2Ts, configA, bDts, bTs, configB, cTs, configC];
const host = createServerHost(files);
const service = createProjectService(host);
service.openClientFile(aTs.path);
service.checkNumberOfProjects({ configuredProjects: 1 });
// project A referencing b.d.ts without project reference
const projectA = service.configuredProjects.get(configA.path)!;
assert.isDefined(projectA);
checkProjectActualFiles(projectA, [aTs.path, a2Ts.path, bDts.path, libFile.path, configA.path]);
// reuses b.d.ts but sets the path and resolved path since projectC has project references
// as the real resolution was to b.ts
service.openClientFile(cTs.path);
service.checkNumberOfProjects({ configuredProjects: 2 });
const projectC = service.configuredProjects.get(configC.path)!;
checkProjectActualFiles(projectC, [cTs.path, bDts.path, libFile.path, configC.path]);
// Now new project for project A tries to reuse b but there is no filesByName mapping for b's source location
host.writeFile(a2Ts.path, `${a2Ts.content}export const y = 30;`);
assert.isTrue(projectA.dirty);
projectA.updateGraph();
});
});
}