Merge branch 'master' of https://github.com/Microsoft/TypeScript into bug/24542-bad-error-message-for-import-ing-an-export

This commit is contained in:
Alexander T
2019-01-08 10:55:11 +02:00
273 changed files with 30502 additions and 25466 deletions
+1 -5
View File
@@ -16,11 +16,7 @@ matrix:
branches:
only:
- master
- release-2.7
- release-2.8
- release-2.9
- release-3.0
- release-3.1
- /^release-.*/
install:
- npm uninstall typescript --no-save
+9 -3
View File
@@ -683,11 +683,17 @@ function ensureCompileTask(projectGraph, options) {
}
});
}
const js = (projectGraphConfig.resolvedOptions.js ? projectGraphConfig.resolvedOptions.js(stream.js) : stream.js)
const additionalJsOutputs = projectGraphConfig.resolvedOptions.js ? projectGraphConfig.resolvedOptions.js(stream.js)
.pipe(gulpif(sourceMap || inlineSourceMap, sourcemaps.write(sourceMapPath, sourceMapOptions))) : undefined;
const additionalDtsOutputs = projectGraphConfig.resolvedOptions.dts ? projectGraphConfig.resolvedOptions.dts(stream.dts)
.pipe(gulpif(declarationMap, sourcemaps.write(sourceMapPath, sourceMapOptions))) : undefined;
const js = stream.js
.pipe(gulpif(sourceMap || inlineSourceMap, sourcemaps.write(sourceMapPath, sourceMapOptions)));
const dts = (projectGraphConfig.resolvedOptions.dts ? projectGraphConfig.resolvedOptions.dts(stream.dts) : stream.dts)
const dts = stream.dts
.pipe(gulpif(declarationMap, sourcemaps.write(sourceMapPath, sourceMapOptions)));
return merge2([js, dts])
return merge2([js, dts, additionalJsOutputs, additionalDtsOutputs].filter(x => !!x))
.pipe(gulp.dest(destPath));
});
}
+1
View File
@@ -12,6 +12,7 @@
"module": "commonjs",
"outDir": "../../built/local/tslint",
"baseUrl": "../..",
"types": ["node"],
"paths": {
"typescript": ["lib/typescript.d.ts"]
}
+6 -1
View File
@@ -143,7 +143,7 @@ namespace ts {
let symbolCount = 0;
let Symbol: { new (flags: SymbolFlags, name: __String): Symbol }; // tslint:disable-line variable-name
let Symbol: new (flags: SymbolFlags, name: __String) => Symbol; // tslint:disable-line variable-name
let classifiableNames: UnderscoreEscapedMap<true>;
const unreachableFlow: FlowNode = { flags: FlowFlags.Unreachable };
@@ -233,6 +233,11 @@ namespace ts {
symbol.members = createSymbolTable();
}
// On merge of const enum module with class or function, reset const enum only flag (namespaces will already recalculate)
if (symbol.constEnumOnlyModule && (symbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.RegularEnum))) {
symbol.constEnumOnlyModule = false;
}
if (symbolFlags & SymbolFlags.Value) {
setValueDeclaration(symbol, node);
}
+22 -7
View File
@@ -64,7 +64,9 @@ namespace ts {
const state = BuilderState.create(newProgram, getCanonicalFileName, oldState) as BuilderProgramState;
state.program = newProgram;
const compilerOptions = newProgram.getCompilerOptions();
if (!compilerOptions.outFile && !compilerOptions.out) {
// With --out or --outFile, any change affects all semantic diagnostics so no need to cache them
// With --isolatedModules, emitting changed file doesnt emit dependent files so we cant know of dependent files to retrieve errors so dont cache the errors
if (!compilerOptions.outFile && !compilerOptions.out && !compilerOptions.isolatedModules) {
state.semanticDiagnosticsPerFile = createMap<ReadonlyArray<Diagnostic>>();
}
state.changedFilesSet = createMap<true>();
@@ -281,10 +283,19 @@ namespace ts {
}
// If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected
return !!forEachEntry(state.exportedModulesMap!, (exportedModules, exportedFromPath) =>
if (forEachEntry(state.exportedModulesMap!, (exportedModules, exportedFromPath) =>
!state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it
exportedModules.has(filePath) &&
removeSemanticDiagnosticsOfFileAndExportsOfFile(state, exportedFromPath as Path, seenFileAndExportsOfFile)
)) {
return true;
}
// Remove diagnostics of files that import this file (without going to exports of referencing files)
return !!forEachEntry(state.referencedMap!, (referencesInFile, referencingFilePath) =>
referencesInFile.has(filePath) &&
!seenFileAndExportsOfFile.has(referencingFilePath) && // Not already removed diagnostic file
removeSemanticDiagnosticsOf(state, referencingFilePath as Path) // Dont add to seen since this is not yet done with the export removal
);
}
@@ -329,15 +340,19 @@ namespace ts {
*/
function getSemanticDiagnosticsOfFile(state: BuilderProgramState, sourceFile: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic> {
const path = sourceFile.path;
const cachedDiagnostics = state.semanticDiagnosticsPerFile!.get(path);
// Report the semantic diagnostics from the cache if we already have those diagnostics present
if (cachedDiagnostics) {
return cachedDiagnostics;
if (state.semanticDiagnosticsPerFile) {
const cachedDiagnostics = state.semanticDiagnosticsPerFile.get(path);
// Report the semantic diagnostics from the cache if we already have those diagnostics present
if (cachedDiagnostics) {
return cachedDiagnostics;
}
}
// Diagnostics werent cached, get them from program, and cache the result
const diagnostics = state.program.getSemanticDiagnostics(sourceFile, cancellationToken);
state.semanticDiagnosticsPerFile!.set(path, diagnostics);
if (state.semanticDiagnosticsPerFile) {
state.semanticDiagnosticsPerFile.set(path, diagnostics);
}
return diagnostics;
}
+387 -243
View File
@@ -4433,6 +4433,22 @@ namespace ts {
return location.kind === SyntaxKind.SourceFile || isAmbientModule(location);
}
function getNameOfSymbolFromNameType(symbol: Symbol, context?: NodeBuilderContext) {
const nameType = symbol.nameType;
if (nameType) {
if (nameType.flags & TypeFlags.StringOrNumberLiteral) {
const name = "" + (<StringLiteralType | NumberLiteralType>nameType).value;
if (!isIdentifierText(name, compilerOptions.target) && !isNumericLiteralName(name)) {
return `"${escapeString(name, CharacterCodes.doubleQuote)}"`;
}
return name;
}
if (nameType.flags & TypeFlags.UniqueESSymbol) {
return `[${getNameOfSymbolAsWritten((<UniqueESSymbolType>nameType).symbol, context)}]`;
}
}
}
/**
* Gets a human-readable name for a symbol.
* Should *not* be used for the right-hand side of a `.` -- use `symbolName(symbol)` for that instead.
@@ -4457,6 +4473,13 @@ namespace ts {
if (isCallExpression(declaration) && isBindableObjectDefinePropertyCall(declaration)) {
return symbolName(symbol);
}
if (isComputedPropertyName(name) && !(getCheckFlags(symbol) & CheckFlags.Late) && symbol.nameType && symbol.nameType.flags & TypeFlags.StringOrNumberLiteral) {
// Computed property name isn't late bound, but has a well-known name type - use name type to generate a symbol name
const result = getNameOfSymbolFromNameType(symbol, context);
if (result !== undefined) {
return result;
}
}
return declarationNameToString(name);
}
if (declaration.parent && declaration.parent.kind === SyntaxKind.VariableDeclaration) {
@@ -4472,16 +4495,8 @@ namespace ts {
return declaration.kind === SyntaxKind.ClassExpression ? "(Anonymous class)" : "(Anonymous function)";
}
}
const nameType = symbol.nameType;
if (nameType) {
if (nameType.flags & TypeFlags.StringLiteral && !isIdentifierText((<StringLiteralType>nameType).value, compilerOptions.target)) {
return `"${escapeString((<StringLiteralType>nameType).value, CharacterCodes.doubleQuote)}"`;
}
if (nameType && nameType.flags & TypeFlags.UniqueESSymbol) {
return `[${getNameOfSymbolAsWritten((<UniqueESSymbolType>nameType).symbol, context)}]`;
}
}
return symbolName(symbol);
const name = getNameOfSymbolFromNameType(symbol, context);
return name !== undefined ? name : symbolName(symbol);
}
function isDeclarationVisible(node: Node): boolean {
@@ -4775,6 +4790,69 @@ namespace ts {
return createAnonymousType(symbol, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo);
}
// Determine the control flow type associated with a destructuring declaration or assignment. The following
// forms of destructuring are possible:
// let { x } = obj; // BindingElement
// let [ x ] = obj; // BindingElement
// { x } = obj; // ShorthandPropertyAssignment
// { x: v } = obj; // PropertyAssignment
// [ x ] = obj; // Expression
// We construct a synthetic element access expression corresponding to 'obj.x' such that the control
// flow analyzer doesn't have to handle all the different syntactic forms.
function getFlowTypeOfDestructuring(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression, declaredType: Type) {
const reference = getSyntheticElementAccess(node);
return reference ? getFlowTypeOfReference(reference, declaredType) : declaredType;
}
function getSyntheticElementAccess(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression): ElementAccessExpression | undefined {
const parentAccess = getParentElementAccess(node);
if (parentAccess && parentAccess.flowNode) {
const propName = getDestructuringPropertyName(node);
if (propName) {
const result = <ElementAccessExpression>createNode(SyntaxKind.ElementAccessExpression, node.pos, node.end);
result.parent = node;
result.expression = <LeftHandSideExpression>parentAccess;
const literal = <StringLiteral>createNode(SyntaxKind.StringLiteral, node.pos, node.end);
literal.parent = result;
literal.text = propName;
result.argumentExpression = literal;
result.flowNode = parentAccess.flowNode;
return result;
}
}
}
function getParentElementAccess(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression) {
const ancestor = node.parent.parent;
switch (ancestor.kind) {
case SyntaxKind.BindingElement:
case SyntaxKind.PropertyAssignment:
return getSyntheticElementAccess(<BindingElement | PropertyAssignment>ancestor);
case SyntaxKind.ArrayLiteralExpression:
return getSyntheticElementAccess(<Expression>node.parent);
case SyntaxKind.VariableDeclaration:
return (<VariableDeclaration>ancestor).initializer;
case SyntaxKind.BinaryExpression:
return (<BinaryExpression>ancestor).right;
}
}
function getDestructuringPropertyName(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression) {
const parent = node.parent;
if (node.kind === SyntaxKind.BindingElement && parent.kind === SyntaxKind.ObjectBindingPattern) {
return getLiteralPropertyNameText((<BindingElement>node).propertyName || <Identifier>(<BindingElement>node).name);
}
if (node.kind === SyntaxKind.PropertyAssignment || node.kind === SyntaxKind.ShorthandPropertyAssignment) {
return getLiteralPropertyNameText((<PropertyAssignment | ShorthandPropertyAssignment>node).name);
}
return "" + (<NodeArray<Node>>(<BindingPattern | ArrayLiteralExpression>parent).elements).indexOf(node);
}
function getLiteralPropertyNameText(name: PropertyName) {
const type = getLiteralTypeFromPropertyName(name);
return type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral) ? "" + (<LiteralType>type).value : undefined;
}
/** Return the inferred type for a binding element */
function getTypeForBindingElement(declaration: BindingElement): Type | undefined {
const pattern = declaration.parent;
@@ -4815,9 +4893,9 @@ namespace ts {
else {
// Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form)
const name = declaration.propertyName || <Identifier>declaration.name;
const exprType = getLiteralTypeFromPropertyName(name);
const declaredType = checkIndexedAccessIndexType(getIndexedAccessType(parentType, exprType, name), name);
type = getFlowTypeOfReference(declaration, getConstraintForLocation(declaredType, declaration.name));
const indexType = getLiteralTypeFromPropertyName(name);
const declaredType = getConstraintForLocation(getIndexedAccessType(parentType, indexType, name), declaration.name);
type = getFlowTypeOfDestructuring(declaration, declaredType);
}
}
else {
@@ -4834,21 +4912,13 @@ namespace ts {
mapType(parentType, t => sliceTupleType(<TupleTypeReference>t, index)) :
createArrayType(elementType);
}
else if (isArrayLikeType(parentType)) {
const indexType = getLiteralType(index);
const declaredType = getConstraintForLocation(getIndexedAccessType(parentType, indexType, declaration.name), declaration.name);
type = getFlowTypeOfDestructuring(declaration, declaredType);
}
else {
// Use specific property type when parent is a tuple or numeric index type when parent is an array
const index = pattern.elements.indexOf(declaration);
type = everyType(parentType, isTupleLikeType) ?
getTupleElementType(parentType, index) || declaration.initializer && checkDeclarationInitializer(declaration) :
elementType;
if (!type) {
if (isTupleType(parentType)) {
error(declaration, Diagnostics.Tuple_type_0_with_length_1_cannot_be_assigned_to_tuple_with_length_2, typeToString(parentType), getTypeReferenceArity(<TypeReference>parentType), pattern.elements.length);
}
else {
error(declaration, Diagnostics.Type_0_has_no_property_1, typeToString(parentType), "" + index);
}
return errorType;
}
type = elementType;
}
}
// In strict null checking mode, if a default value of a non-undefined type is specified, remove
@@ -4856,7 +4926,7 @@ namespace ts {
if (strictNullChecks && declaration.initializer && !(getFalsyFlags(checkDeclarationInitializer(declaration)) & TypeFlags.Undefined)) {
type = getTypeWithFacts(type, TypeFacts.NEUndefined);
}
return declaration.initializer && !getContextualTypeForVariableLikeDeclaration(walkUpBindingElementsAndPatterns(declaration)) ?
return declaration.initializer && !getEffectiveTypeAnnotationNode(walkUpBindingElementsAndPatterns(declaration)) ?
getUnionType([type, checkDeclarationInitializer(declaration)], UnionReduction.Subtype) :
type;
}
@@ -6736,7 +6806,12 @@ namespace ts {
// type is the union of the constituent return types.
function getUnionSignatures(signatureLists: ReadonlyArray<ReadonlyArray<Signature>>): Signature[] {
let result: Signature[] | undefined;
let indexWithLengthOverOne: number | undefined;
for (let i = 0; i < signatureLists.length; i++) {
if (signatureLists[i].length === 0) return emptyArray;
if (signatureLists[i].length > 1) {
indexWithLengthOverOne = indexWithLengthOverOne === undefined ? i : -1; // -1 is a signal there are multiple overload sets
}
for (const signature of signatureLists[i]) {
// Only process signatures with parameter lists that aren't already in the result list
if (!result || !findMatchingSignature(result, signature, /*partialMatch*/ false, /*ignoreThisTypes*/ true, /*ignoreReturnTypes*/ true)) {
@@ -6760,9 +6835,91 @@ namespace ts {
}
}
}
if (!length(result) && indexWithLengthOverOne !== -1) {
// No sufficiently similar signature existed to subsume all the other signatures in the union - time to see if we can make a single
// signature that handles all over them. We only do this when there are overloads in only one constituent.
// (Overloads are conditional in nature and having overloads in multiple constituents would necessitate making a power set of
// signatures from the type, whose ordering would be non-obvious)
const masterList = signatureLists[indexWithLengthOverOne !== undefined ? indexWithLengthOverOne : 0];
let results: Signature[] | undefined = masterList.slice();
for (const signatures of signatureLists) {
if (signatures !== masterList) {
const signature = signatures[0];
Debug.assert(!!signature, "getUnionSignatures bails early on empty signature lists and should not have empty lists on second pass");
results = signature.typeParameters && some(results, s => !!s.typeParameters) ? undefined : map(results, sig => combineSignaturesOfUnionMembers(sig, signature));
if (!results) {
break;
}
}
}
result = results;
}
return result || emptyArray;
}
function combineUnionThisParam(left: Symbol | undefined, right: Symbol | undefined): Symbol | undefined {
if (!left || !right) {
return left || right;
}
// A signature `this` type might be a read or a write position... It's very possible that it should be invariant
// and we should refuse to merge signatures if there are `this` types and they do not match. However, so as to be
// permissive when calling, for now, we'll union the `this` types just like the overlapping-union-signature check does
const thisType = getUnionType([getTypeOfSymbol(left), getTypeOfSymbol(right)], UnionReduction.Subtype);
return createSymbolWithType(left, thisType);
}
function combineUnionParameters(left: Signature, right: Signature) {
const longest = getParameterCount(left) >= getParameterCount(right) ? left : right;
const shorter = longest === left ? right : left;
const longestCount = getParameterCount(longest);
const eitherHasEffectiveRest = (hasEffectiveRestParameter(left) || hasEffectiveRestParameter(right));
const needsExtraRestElement = eitherHasEffectiveRest && !hasEffectiveRestParameter(longest);
const params = new Array<Symbol>(longestCount + (needsExtraRestElement ? 1 : 0));
for (let i = 0; i < longestCount; i++) {
const longestParamType = tryGetTypeAtPosition(longest, i)!;
const shorterParamType = tryGetTypeAtPosition(shorter, i) || unknownType;
const unionParamType = getIntersectionType([longestParamType, shorterParamType]);
const isRestParam = eitherHasEffectiveRest && !needsExtraRestElement && i === (longestCount - 1);
const isOptional = i >= getMinArgumentCount(longest) && i >= getMinArgumentCount(shorter);
const leftName = getParameterNameAtPosition(left, i);
const rightName = getParameterNameAtPosition(right, i);
const paramSymbol = createSymbol(
SymbolFlags.FunctionScopedVariable | (isOptional && !isRestParam ? SymbolFlags.Optional : 0),
leftName === rightName ? leftName : `arg${i}` as __String
);
paramSymbol.type = isRestParam ? createArrayType(unionParamType) : unionParamType;
params[i] = paramSymbol;
}
if (needsExtraRestElement) {
const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String);
restParamSymbol.type = createArrayType(getTypeAtPosition(shorter, longestCount));
params[longestCount] = restParamSymbol;
}
return params;
}
function combineSignaturesOfUnionMembers(left: Signature, right: Signature): Signature {
const declaration = left.declaration;
const params = combineUnionParameters(left, right);
const thisParam = combineUnionThisParam(left.thisParameter, right.thisParameter);
const minArgCount = Math.max(left.minArgumentCount, right.minArgumentCount);
const hasRestParam = left.hasRestParameter || right.hasRestParameter;
const hasLiteralTypes = left.hasLiteralTypes || right.hasLiteralTypes;
const result = createSignature(
declaration,
left.typeParameters || right.typeParameters,
thisParam,
params,
/*resolvedReturnType*/ undefined,
/*resolvedTypePredicate*/ undefined,
minArgCount,
hasRestParam,
hasLiteralTypes
);
result.unionSignatures = concatenate(left.unionSignatures || [left], [right]);
return result;
}
function getUnionIndexInfo(types: ReadonlyArray<Type>, kind: IndexKind): IndexInfo | undefined {
const indexTypes: Type[] = [];
let isAnyReadonly = false;
@@ -6934,6 +7091,39 @@ namespace ts {
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined);
}
// Return the lower bound of the key type in a mapped type. Intuitively, the lower
// bound includes those keys that are known to always be present, for example because
// because of constraints on type parameters (e.g. 'keyof T' for a constrained T).
function getLowerBoundOfKeyType(type: Type): Type {
if (type.flags & (TypeFlags.Any | TypeFlags.Primitive)) {
return type;
}
if (type.flags & TypeFlags.Index) {
return getIndexType(getApparentType((<IndexType>type).type));
}
if (type.flags & TypeFlags.Conditional) {
return getLowerBoundOfConditionalType(<ConditionalType>type);
}
if (type.flags & TypeFlags.Union) {
return getUnionType(sameMap((<UnionType>type).types, getLowerBoundOfKeyType));
}
if (type.flags & TypeFlags.Intersection) {
return getIntersectionType(sameMap((<UnionType>type).types, getLowerBoundOfKeyType));
}
return neverType;
}
function getLowerBoundOfConditionalType(type: ConditionalType) {
if (type.root.isDistributive) {
const constraint = getLowerBoundOfKeyType(type.checkType);
if (constraint !== type.checkType) {
const mapper = makeUnaryTypeMapper(type.root.checkType, constraint);
return getConditionalTypeInstantiation(type, combineTypeMappers(mapper, type.mapper));
}
}
return type;
}
/** Resolve the members of a mapped type { [P in K]: T } */
function resolveMappedTypeMembers(type: MappedType) {
const members: SymbolTable = createSymbolTable();
@@ -6962,10 +7152,7 @@ namespace ts {
}
}
else {
// If the key type is a 'keyof X', obtain 'keyof C' where C is the base constraint of X.
// Then iterate over the constituents of the key type.
const iterationType = constraintType.flags & TypeFlags.Index ? getIndexType(getApparentType((<IndexType>constraintType).type)) : constraintType;
forEachType(iterationType, addMemberForKeyType);
forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType);
}
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo);
@@ -7519,38 +7706,37 @@ namespace ts {
return props[0];
}
let declarations: Declaration[] | undefined;
let commonType: Type | undefined;
let firstType: Type | undefined;
let nameType: Type | undefined;
const propTypes: Type[] = [];
let first = true;
let commonValueDeclaration: Declaration | undefined;
let firstValueDeclaration: Declaration | undefined;
let hasNonUniformValueDeclaration = false;
for (const prop of props) {
if (!commonValueDeclaration) {
commonValueDeclaration = prop.valueDeclaration;
if (!firstValueDeclaration) {
firstValueDeclaration = prop.valueDeclaration;
}
else if (prop.valueDeclaration !== commonValueDeclaration) {
else if (prop.valueDeclaration !== firstValueDeclaration) {
hasNonUniformValueDeclaration = true;
}
declarations = addRange(declarations, prop.declarations);
const type = getTypeOfSymbol(prop);
if (first) {
commonType = type;
if (!firstType) {
firstType = type;
nameType = prop.nameType;
first = false;
}
else {
if (type !== commonType) {
checkFlags |= CheckFlags.HasNonUniformType;
}
else if (type !== firstType) {
checkFlags |= CheckFlags.HasNonUniformType;
}
if (isLiteralType(type)) {
checkFlags |= CheckFlags.HasLiteralType;
}
propTypes.push(type);
}
addRange(propTypes, indexTypes);
const result = createSymbol(SymbolFlags.Property | commonFlags, name, syntheticFlag | checkFlags);
result.containingType = containingType;
if (!hasNonUniformValueDeclaration && commonValueDeclaration) {
result.valueDeclaration = commonValueDeclaration;
if (!hasNonUniformValueDeclaration && firstValueDeclaration) {
result.valueDeclaration = firstValueDeclaration;
}
result.declarations = declarations!;
result.nameType = nameType;
@@ -7778,22 +7964,17 @@ namespace ts {
const numTypeArguments = length(typeArguments);
if (isJavaScriptImplicitAny || (numTypeArguments >= minTypeArgumentCount && numTypeArguments <= numTypeParameters)) {
const result = typeArguments ? typeArguments.slice() : [];
// Map an unsatisfied type parameter with a default type.
// If a type parameter does not have a default type, or if the default type
// is a forward reference, the empty object type is used.
const baseDefaultType = getDefaultTypeArgumentType(isJavaScriptImplicitAny);
const circularityMapper = createTypeMapper(typeParameters!, map(typeParameters!, () => baseDefaultType));
// Map invalid forward references in default types to the error type
for (let i = numTypeArguments; i < numTypeParameters; i++) {
result[i] = instantiateType(getConstraintFromTypeParameter(typeParameters![i]) || baseDefaultType, circularityMapper);
result[i] = errorType;
}
const baseDefaultType = getDefaultTypeArgumentType(isJavaScriptImplicitAny);
for (let i = numTypeArguments; i < numTypeParameters; i++) {
const mapper = createTypeMapper(typeParameters!, result);
let defaultType = getDefaultFromTypeParameter(typeParameters![i]);
if (isJavaScriptImplicitAny && defaultType && isTypeIdenticalTo(defaultType, emptyObjectType)) {
defaultType = anyType;
}
result[i] = defaultType ? instantiateType(defaultType, mapper) : baseDefaultType;
result[i] = defaultType ? instantiateType(defaultType, createTypeMapper(typeParameters!, result)) : baseDefaultType;
}
result.length = typeParameters!.length;
return result;
@@ -9530,16 +9711,16 @@ namespace ts {
return false;
}
function getPropertyTypeForIndexType(objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | undefined, cacheSymbol: boolean, missingType: Type) {
function getPropertyTypeForIndexType(objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, cacheSymbol: boolean, missingType: Type) {
const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined;
const propName = isTypeUsableAsLateBoundName(indexType)
? getLateBoundNameFromType(indexType)
: accessExpression && checkThatExpressionIsProperSymbolReference(accessExpression.argumentExpression, indexType, /*reportError*/ false)
? getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>accessExpression.argumentExpression).name))
: accessNode && isPropertyName(accessNode)
const propName = isTypeUsableAsLateBoundName(indexType) ?
getLateBoundNameFromType(indexType) :
accessExpression && checkThatExpressionIsProperSymbolReference(accessExpression.argumentExpression, indexType, /*reportError*/ false) ?
getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>accessExpression.argumentExpression).name)) :
accessNode && isPropertyName(accessNode) ?
// late bound names are handled in the first branch, so here we only need to handle normal names
? getPropertyNameForPropertyNameNode(accessNode)
: undefined;
getPropertyNameForPropertyNameNode(accessNode) :
undefined;
if (propName !== undefined) {
const prop = getPropertyOfType(objectType, propName);
if (prop) {
@@ -9561,7 +9742,13 @@ namespace ts {
if (everyType(objectType, isTupleType) && isNumericLiteralName(propName) && +propName >= 0) {
if (accessNode && everyType(objectType, t => !(<TupleTypeReference>t).target.hasRestElement)) {
const indexNode = getIndexNodeForAccessExpression(accessNode);
error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType));
if (isTupleType(objectType)) {
error(indexNode, Diagnostics.Tuple_type_0_of_length_1_has_no_element_at_index_2,
typeToString(objectType), getTypeReferenceArity(objectType), unescapeLeadingUnderscores(propName));
}
else {
error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType));
}
}
return mapType(objectType, t => getRestTypeOfTupleType(<TupleTypeReference>t) || undefinedType);
}
@@ -9633,7 +9820,7 @@ namespace ts {
return missingType;
}
function getIndexNodeForAccessExpression(accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName) {
function getIndexNodeForAccessExpression(accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression) {
return accessNode.kind === SyntaxKind.ElementAccessExpression
? accessNode.argumentExpression
: accessNode.kind === SyntaxKind.IndexedAccessType
@@ -9702,7 +9889,7 @@ namespace ts {
return type.simplified = type;
}
function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName, missingType = accessNode ? errorType : unknownType): Type {
function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, missingType = accessNode ? errorType : unknownType): Type {
if (objectType === wildcardType || indexType === wildcardType) {
return wildcardType;
}
@@ -10860,8 +11047,7 @@ namespace ts {
function hasContextSensitiveReturnExpression(node: FunctionLikeDeclaration) {
// TODO(anhans): A block should be context-sensitive if it has a context-sensitive return value.
const body = node.body!;
return body.kind === SyntaxKind.Block ? false : isContextSensitive(body);
return !!node.body && node.body.kind !== SyntaxKind.Block && isContextSensitive(node.body);
}
function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | ArrowFunction | MethodDeclaration {
@@ -13604,6 +13790,9 @@ namespace ts {
// union includes empty object types (e.g. reducing {} | string to just {}).
return getUnionType(widenedTypes, some(widenedTypes, isEmptyObjectType) ? UnionReduction.Subtype : UnionReduction.Literal);
}
if (type.flags & TypeFlags.Intersection) {
return getIntersectionType(sameMap((<IntersectionType>type).types, getWidenedType));
}
if (isArrayType(type) || isTupleType(type)) {
return createTypeReference((<TypeReference>type).target, sameMap((<TypeReference>type).typeArguments, getWidenedType));
}
@@ -14576,49 +14765,23 @@ namespace ts {
// occurring in an apparent type position with '@' because the control flow type
// of such nodes may be based on the apparent type instead of the declared type.
function getFlowCacheKey(node: Node): string | undefined {
if (node.kind === SyntaxKind.Identifier) {
const symbol = getResolvedSymbol(<Identifier>node);
return symbol !== unknownSymbol ? (isConstraintPosition(node) ? "@" : "") + getSymbolId(symbol) : undefined;
}
if (node.kind === SyntaxKind.ThisKeyword) {
return "0";
}
if (node.kind === SyntaxKind.PropertyAccessExpression) {
const key = getFlowCacheKey((<PropertyAccessExpression>node).expression);
return key && key + "." + idText((<PropertyAccessExpression>node).name);
}
if (node.kind === SyntaxKind.BindingElement) {
const container = (node as BindingElement).parent.parent;
const key = container.kind === SyntaxKind.BindingElement ? getFlowCacheKey(container) : (container.initializer && getFlowCacheKey(container.initializer));
const text = getBindingElementNameText(node as BindingElement);
const result = key && text && (key + "." + text);
return result;
switch (node.kind) {
case SyntaxKind.Identifier:
const symbol = getResolvedSymbol(<Identifier>node);
return symbol !== unknownSymbol ? (isConstraintPosition(node) ? "@" : "") + getSymbolId(symbol) : undefined;
case SyntaxKind.ThisKeyword:
return "0";
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
const propName = getAccessedPropertyName(<AccessExpression>node);
if (propName !== undefined) {
const key = getFlowCacheKey((<AccessExpression>node).expression);
return key && key + "." + propName;
}
}
return undefined;
}
function getBindingElementNameText(element: BindingElement): string | undefined {
const parent = element.parent;
if (parent.kind === SyntaxKind.ObjectBindingPattern) {
const name = element.propertyName || element.name;
switch (name.kind) {
case SyntaxKind.Identifier:
return idText(name);
case SyntaxKind.ComputedPropertyName:
return isStringOrNumericLiteralLike(name.expression) ? name.expression.text : undefined;
case SyntaxKind.StringLiteral:
case SyntaxKind.NumericLiteral:
return name.text;
default:
// Per types, array and object binding patterns remain, however they should never be present if propertyName is not defined
Debug.fail("Unexpected name kind for binding element name");
}
}
else {
return "" + parent.elements.indexOf(element);
}
}
function isMatchingReference(source: Node, target: Node): boolean {
switch (source.kind) {
case SyntaxKind.Identifier:
@@ -14631,49 +14794,27 @@ namespace ts {
return target.kind === SyntaxKind.SuperKeyword;
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
return (isPropertyAccessExpression(target) || isElementAccessExpression(target)) &&
getAccessedPropertyName(source as PropertyAccessExpression | ElementAccessExpression) === getAccessedPropertyName(target) &&
isMatchingReference((source as PropertyAccessExpression | ElementAccessExpression).expression, target.expression);
case SyntaxKind.BindingElement:
if (target.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>target).name.escapedText === getBindingElementNameText(<BindingElement>source)) {
const ancestor = source.parent.parent;
if (ancestor.kind === SyntaxKind.BindingElement) {
return isMatchingReference(ancestor, (<PropertyAccessExpression>target).expression);
}
if (ancestor.kind === SyntaxKind.VariableDeclaration) {
const initializer = (<VariableDeclaration>ancestor).initializer;
return !!initializer && isMatchingReference(initializer, (<PropertyAccessExpression>target).expression);
}
}
return isAccessExpression(target) &&
getAccessedPropertyName(<AccessExpression>source) === getAccessedPropertyName(target) &&
isMatchingReference((<AccessExpression>source).expression, target.expression);
}
return false;
}
function getAccessedPropertyName(access: PropertyAccessExpression | ElementAccessExpression): __String | undefined {
return isPropertyAccessExpression(access) ? access.name.escapedText :
function getAccessedPropertyName(access: AccessExpression): __String | undefined {
return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText :
isStringLiteral(access.argumentExpression) || isNumericLiteral(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) :
undefined;
}
function getReferenceParent(source: Node) {
if (source.kind === SyntaxKind.PropertyAccessExpression) {
return (<PropertyAccessExpression>source).expression;
}
if (source.kind === SyntaxKind.BindingElement) {
const ancestor = source.parent.parent;
return ancestor.kind === SyntaxKind.VariableDeclaration ? (<VariableDeclaration>ancestor).initializer : ancestor;
}
return undefined;
}
function containsMatchingReference(source: Node, target: Node) {
let parent = getReferenceParent(source);
while (parent) {
if (isMatchingReference(parent, target)) {
while (isAccessExpression(source)) {
source = source.expression;
if (isMatchingReference(source, target)) {
return true;
}
parent = getReferenceParent(parent);
}
return false;
}
// Return true if target is a property access xxx.yyy, source is a property access xxx.zzz, the declared
@@ -14681,34 +14822,30 @@ namespace ts {
// a possible discriminant if its type differs in the constituents of containing union type, and if every
// choice is a unit type or a union of unit types.
function containsMatchingReferenceDiscriminant(source: Node, target: Node) {
return target.kind === SyntaxKind.PropertyAccessExpression &&
containsMatchingReference(source, (<PropertyAccessExpression>target).expression) &&
isDiscriminantProperty(getDeclaredTypeOfReference((<PropertyAccessExpression>target).expression), (<PropertyAccessExpression>target).name.escapedText);
let name;
return isAccessExpression(target) &&
containsMatchingReference(source, target.expression) &&
(name = getAccessedPropertyName(target)) !== undefined &&
isDiscriminantProperty(getDeclaredTypeOfReference(target.expression), name);
}
function getDeclaredTypeOfReference(expr: Node): Type | undefined {
if (expr.kind === SyntaxKind.Identifier) {
return getTypeOfSymbol(getResolvedSymbol(<Identifier>expr));
}
if (expr.kind === SyntaxKind.PropertyAccessExpression) {
const type = getDeclaredTypeOfReference((<PropertyAccessExpression>expr).expression);
return type && getTypeOfPropertyOfType(type, (<PropertyAccessExpression>expr).name.escapedText);
if (isAccessExpression(expr)) {
const type = getDeclaredTypeOfReference(expr.expression);
if (type) {
const propName = getAccessedPropertyName(expr);
return propName !== undefined ? getTypeOfPropertyOfType(type, propName) : undefined;
}
}
return undefined;
}
function isDiscriminantType(type: Type): boolean {
if (type.flags & TypeFlags.Union) {
if (type.flags & (TypeFlags.Boolean | TypeFlags.EnumLiteral)) {
return true;
}
let combined = 0;
for (const t of (<UnionType>type).types) combined |= t.flags;
if (combined & TypeFlags.Unit && !(combined & TypeFlags.Instantiable)) {
return true;
}
}
return false;
return !!(type.flags & TypeFlags.Union &&
(type.flags & (TypeFlags.Boolean | TypeFlags.EnumLiteral) || !isGenericIndexType(type)));
}
function isDiscriminantProperty(type: Type | undefined, name: __String) {
@@ -14716,7 +14853,9 @@ namespace ts {
const prop = getUnionOrIntersectionProperty(<UnionType>type, name);
if (prop && getCheckFlags(prop) & CheckFlags.SyntheticProperty) {
if ((<TransientSymbol>prop).isDiscriminantProperty === undefined) {
(<TransientSymbol>prop).isDiscriminantProperty = !!((<TransientSymbol>prop).checkFlags & CheckFlags.HasNonUniformType) && isDiscriminantType(getTypeOfSymbol(prop));
(<TransientSymbol>prop).isDiscriminantProperty =
((<TransientSymbol>prop).checkFlags & CheckFlags.Discriminant) === CheckFlags.Discriminant &&
isDiscriminantType(getTypeOfSymbol(prop));
}
return !!(<TransientSymbol>prop).isDiscriminantProperty;
}
@@ -15518,7 +15657,7 @@ namespace ts {
else if (isMatchingReferenceDiscriminant(expr, type)) {
type = narrowTypeByDiscriminant(
type,
expr as PropertyAccessExpression | ElementAccessExpression,
expr as AccessExpression,
t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd));
}
else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) {
@@ -15638,21 +15777,19 @@ namespace ts {
}
function isMatchingReferenceDiscriminant(expr: Expression, computedType: Type) {
if (!(computedType.flags & TypeFlags.Union) ||
expr.kind !== SyntaxKind.PropertyAccessExpression && expr.kind !== SyntaxKind.ElementAccessExpression) {
if (!(computedType.flags & TypeFlags.Union) || !isAccessExpression(expr)) {
return false;
}
const access = expr as PropertyAccessExpression | ElementAccessExpression;
const name = getAccessedPropertyName(access);
if (!name) {
const name = getAccessedPropertyName(expr);
if (name === undefined) {
return false;
}
return isMatchingReference(reference, access.expression) && isDiscriminantProperty(computedType, name);
return isMatchingReference(reference, expr.expression) && isDiscriminantProperty(computedType, name);
}
function narrowTypeByDiscriminant(type: Type, access: PropertyAccessExpression | ElementAccessExpression, narrowType: (t: Type) => Type): Type {
function narrowTypeByDiscriminant(type: Type, access: AccessExpression, narrowType: (t: Type) => Type): Type {
const propName = getAccessedPropertyName(access);
if (!propName) {
if (propName === undefined) {
return type;
}
const propType = getTypeOfPropertyOfType(type, propName);
@@ -15665,7 +15802,7 @@ namespace ts {
return getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy);
}
if (isMatchingReferenceDiscriminant(expr, declaredType)) {
return narrowTypeByDiscriminant(type, <PropertyAccessExpression | ElementAccessExpression>expr, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy));
return narrowTypeByDiscriminant(type, <AccessExpression>expr, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy));
}
if (containsMatchingReferenceDiscriminant(reference, expr)) {
return declaredType;
@@ -15716,10 +15853,10 @@ namespace ts {
return narrowTypeByEquality(type, operator, left, assumeTrue);
}
if (isMatchingReferenceDiscriminant(left, declaredType)) {
return narrowTypeByDiscriminant(type, <PropertyAccessExpression | ElementAccessExpression>left, t => narrowTypeByEquality(t, operator, right, assumeTrue));
return narrowTypeByDiscriminant(type, <AccessExpression>left, t => narrowTypeByEquality(t, operator, right, assumeTrue));
}
if (isMatchingReferenceDiscriminant(right, declaredType)) {
return narrowTypeByDiscriminant(type, <PropertyAccessExpression | ElementAccessExpression>right, t => narrowTypeByEquality(t, operator, left, assumeTrue));
return narrowTypeByDiscriminant(type, <AccessExpression>right, t => narrowTypeByEquality(t, operator, left, assumeTrue));
}
if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) {
return declaredType;
@@ -16024,9 +16161,8 @@ namespace ts {
}
else {
const invokedExpression = skipParentheses(callExpression.expression);
if (invokedExpression.kind === SyntaxKind.ElementAccessExpression || invokedExpression.kind === SyntaxKind.PropertyAccessExpression) {
const accessExpression = invokedExpression as ElementAccessExpression | PropertyAccessExpression;
const possibleReference = skipParentheses(accessExpression.expression);
if (isAccessExpression(invokedExpression)) {
const possibleReference = skipParentheses(invokedExpression.expression);
if (isMatchingReference(reference, possibleReference)) {
return getNarrowedType(type, predicate.type, assumeTrue, isTypeSubtypeOf);
}
@@ -16969,7 +17105,7 @@ namespace ts {
if (parent.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>parent).operatorToken.kind === SyntaxKind.EqualsToken) {
const target = (<BinaryExpression>parent).left;
if (target.kind === SyntaxKind.PropertyAccessExpression || target.kind === SyntaxKind.ElementAccessExpression) {
const { expression } = target as PropertyAccessExpression | ElementAccessExpression;
const { expression } = target as AccessExpression;
// Don't contextually type `this` as `exports` in `exports.Point = function(x, y) { this.x = x; this.y = y; }`
if (inJs && isIdentifier(expression)) {
const sourceFile = getSourceFileOfNode(parent);
@@ -17540,6 +17676,26 @@ namespace ts {
}
function getJsxPropsTypeForSignatureFromMember(sig: Signature, forcedLookupLocation: __String) {
if (sig.unionSignatures) {
// JSX Elements using the legacy `props`-field based lookup (eg, react class components) need to treat the `props` member as an input
// instead of an output position when resolving the signature. We need to go back to the input signatures of the composite signature,
// get the type of `props` on each return type individually, and then _intersect them_, rather than union them (as would normally occur
// for a union signature). It's an unfortunate quirk of looking in the output of the signature for the type we want to use for the input.
// The default behavior of `getTypeOfFirstParameterOfSignatureWithFallback` when no `props` member name is defined is much more sane.
const results: Type[] = [];
for (const signature of sig.unionSignatures) {
const instance = getReturnTypeOfSignature(signature);
if (isTypeAny(instance)) {
return instance;
}
const propType = getTypeOfPropertyOfType(instance, forcedLookupLocation);
if (!propType) {
return;
}
results.push(propType);
}
return getIntersectionType(results);
}
const instanceType = getReturnTypeOfSignature(sig);
return isTypeAny(instanceType) ? instanceType : getTypeOfPropertyOfType(instanceType, forcedLookupLocation);
}
@@ -19130,8 +19286,8 @@ namespace ts {
}
}
function isValidPropertyAccessForCompletions(node: PropertyAccessExpression | ImportTypeNode, type: Type, property: Symbol): boolean {
return isValidPropertyAccessWithType(node, node.kind !== SyntaxKind.ImportType && node.expression.kind === SyntaxKind.SuperKeyword, property.escapedName, type)
function isValidPropertyAccessForCompletions(node: PropertyAccessExpression | ImportTypeNode | QualifiedName, type: Type, property: Symbol): boolean {
return isValidPropertyAccessWithType(node, node.kind === SyntaxKind.PropertyAccessExpression && node.expression.kind === SyntaxKind.SuperKeyword, property.escapedName, type)
&& (!(property.flags & SymbolFlags.Method) || isValidMethodAccess(property, type));
}
function isValidMethodAccess(method: Symbol, actualThisType: Type): boolean {
@@ -19725,7 +19881,7 @@ namespace ts {
if (node.kind === SyntaxKind.CallExpression) {
const callee = skipOuterExpressions(node.expression);
if (callee.kind === SyntaxKind.PropertyAccessExpression || callee.kind === SyntaxKind.ElementAccessExpression) {
return (callee as PropertyAccessExpression | ElementAccessExpression).expression;
return (callee as AccessExpression).expression;
}
}
}
@@ -21757,7 +21913,7 @@ namespace ts {
// Allow assignments to readonly properties within constructors of the same class declaration.
if (symbol.flags & SymbolFlags.Property &&
(expr.kind === SyntaxKind.PropertyAccessExpression || expr.kind === SyntaxKind.ElementAccessExpression) &&
(expr as PropertyAccessExpression | ElementAccessExpression).expression.kind === SyntaxKind.ThisKeyword) {
(expr as AccessExpression).expression.kind === SyntaxKind.ThisKeyword) {
// Look for if this is the constructor for the class that `symbol` is a property of.
const func = getContainingFunction(expr);
if (!(func && func.kind === SyntaxKind.Constructor)) {
@@ -21775,7 +21931,7 @@ namespace ts {
function isReferenceThroughNamespaceImport(expr: Expression): boolean {
if (expr.kind === SyntaxKind.PropertyAccessExpression || expr.kind === SyntaxKind.ElementAccessExpression) {
const node = skipParentheses((expr as PropertyAccessExpression | ElementAccessExpression).expression);
const node = skipParentheses((expr as AccessExpression).expression);
if (node.kind === SyntaxKind.Identifier) {
const symbol = getNodeLinks(node).resolvedSymbol!;
if (symbol.flags & SymbolFlags.Alias) {
@@ -22024,21 +22180,18 @@ namespace ts {
function checkObjectLiteralDestructuringPropertyAssignment(objectLiteralType: Type, property: ObjectLiteralElementLike, allProperties?: NodeArray<ObjectLiteralElementLike>, rightIsThis = false) {
if (property.kind === SyntaxKind.PropertyAssignment || property.kind === SyntaxKind.ShorthandPropertyAssignment) {
const name = property.name;
if (name.kind === SyntaxKind.ComputedPropertyName) {
checkComputedPropertyName(name);
}
if (isComputedNonLiteralName(name)) {
return undefined;
}
const type = getTypeOfObjectLiteralDestructuringProperty(objectLiteralType, name, property, rightIsThis);
if (type) {
// non-shorthand property assignments should always have initializers
return checkDestructuringAssignment(property.kind === SyntaxKind.ShorthandPropertyAssignment ? property : property.initializer, type);
}
else {
error(name, Diagnostics.Type_0_has_no_property_1_and_no_string_index_signature, typeToString(objectLiteralType), declarationNameToString(name));
const text = getTextOfPropertyName(name);
if (text) {
const prop = getPropertyOfType(objectLiteralType, text);
if (prop) {
markPropertyAsReferenced(prop, property, rightIsThis);
checkPropertyAccessibility(property, /*isSuper*/ false, objectLiteralType, prop);
}
}
const exprType = getLiteralTypeFromPropertyName(name);
const elementType = getIndexedAccessType(objectLiteralType, exprType, name);
const type = getFlowTypeOfDestructuring(property, elementType);
return checkDestructuringAssignment(property.kind === SyntaxKind.ShorthandPropertyAssignment ? property : property.initializer, type);
}
else if (property.kind === SyntaxKind.SpreadAssignment) {
if (languageVersion < ScriptTarget.ESNext) {
@@ -22059,31 +22212,11 @@ namespace ts {
}
}
function getTypeOfObjectLiteralDestructuringProperty(objectLiteralType: Type, name: PropertyName, property: PropertyAssignment | ShorthandPropertyAssignment, rightIsThis: boolean) {
if (isTypeAny(objectLiteralType)) {
return objectLiteralType;
}
let type: Type | undefined;
const text = getTextOfPropertyName(name);
if (text) { // TODO: GH#26379
const prop = getPropertyOfType(objectLiteralType, text);
if (prop) {
markPropertyAsReferenced(prop, property, rightIsThis);
checkPropertyAccessibility(property, /*isSuper*/ false, objectLiteralType, prop);
type = getTypeOfSymbol(prop);
}
type = type || (isNumericLiteralName(text) ? getIndexTypeOfType(objectLiteralType, IndexKind.Number) : undefined);
}
return type || getIndexTypeOfType(objectLiteralType, IndexKind.String);
}
function checkArrayLiteralAssignment(node: ArrayLiteralExpression, sourceType: Type, checkMode?: CheckMode): Type {
const elements = node.elements;
if (languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) {
checkExternalEmitHelpers(node, ExternalEmitHelpers.Read);
}
// This elementType will be used if the specific property corresponding to this index is not
// present (aka the tuple element property). This call also checks that the parentType is in
// fact an iterable or array (depending on target language).
@@ -22100,39 +22233,30 @@ namespace ts {
const element = elements[elementIndex];
if (element.kind !== SyntaxKind.OmittedExpression) {
if (element.kind !== SyntaxKind.SpreadElement) {
const propName = "" + elementIndex as __String;
const type = isTypeAny(sourceType) ? sourceType :
everyType(sourceType, isTupleLikeType) ? getTupleElementType(sourceType, elementIndex) :
elementType;
if (type) {
const indexType = getLiteralType(elementIndex);
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);
return checkDestructuringAssignment(element, type, checkMode);
}
// We still need to check element expression here because we may need to set appropriate flag on the expression
// such as NodeCheckFlags.LexicalThis on "this"expression.
checkExpression(element);
if (isTupleType(sourceType)) {
error(element, Diagnostics.Tuple_type_0_with_length_1_cannot_be_assigned_to_tuple_with_length_2, typeToString(sourceType), getTypeReferenceArity(<TypeReference>sourceType), elements.length);
}
else {
error(element, Diagnostics.Type_0_has_no_property_1, typeToString(sourceType), propName as string);
}
return checkDestructuringAssignment(element, elementType, checkMode);
}
if (elementIndex < elements.length - 1) {
error(element, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern);
}
else {
if (elementIndex < elements.length - 1) {
error(element, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern);
const restExpression = (<SpreadElement>element).expression;
if (restExpression.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>restExpression).operatorToken.kind === SyntaxKind.EqualsToken) {
error((<BinaryExpression>restExpression).operatorToken, Diagnostics.A_rest_element_cannot_have_an_initializer);
}
else {
const restExpression = (<SpreadElement>element).expression;
if (restExpression.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>restExpression).operatorToken.kind === SyntaxKind.EqualsToken) {
error((<BinaryExpression>restExpression).operatorToken, Diagnostics.A_rest_element_cannot_have_an_initializer);
}
else {
checkGrammarForDisallowedTrailingComma(node.elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma);
const type = everyType(sourceType, isTupleType) ?
mapType(sourceType, t => sliceTupleType(<TupleTypeReference>t, elementIndex)) :
createArrayType(elementType);
return checkDestructuringAssignment(restExpression, type, checkMode);
}
checkGrammarForDisallowedTrailingComma(node.elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma);
const type = everyType(sourceType, isTupleType) ?
mapType(sourceType, t => sliceTupleType(<TupleTypeReference>t, elementIndex)) :
createArrayType(elementType);
return checkDestructuringAssignment(restExpression, type, checkMode);
}
}
}
@@ -22709,7 +22833,8 @@ namespace ts {
const type = getTypeOfExpression(initializer, /*cache*/ true);
const widened = getCombinedNodeFlags(declaration) & NodeFlags.Const ||
isDeclarationReadonly(declaration) ||
isTypeAssertion(initializer) ? type : getWidenedLiteralType(type);
isTypeAssertion(initializer) ||
isLiteralOfContextualType(type, getContextualType(initializer)) ? type : getWidenedLiteralType(type);
if (isInJSFile(declaration)) {
if (widened.flags & TypeFlags.Nullable) {
reportImplicitAny(declaration, anyType);
@@ -26364,6 +26489,7 @@ namespace ts {
if (produceDiagnostics) {
if (node.default) {
seenDefault = true;
checkTypeParametersNotReferenced(node.default, typeParameterDeclarations, i);
}
else if (seenDefault) {
error(node, Diagnostics.Required_type_parameters_may_not_follow_optional_type_parameters);
@@ -26378,6 +26504,24 @@ namespace ts {
}
}
/** Check that type parameter defaults only reference previously declared type parameters */
function checkTypeParametersNotReferenced(root: TypeNode, typeParameters: ReadonlyArray<TypeParameterDeclaration>, index: number) {
visit(root);
function visit(node: Node) {
if (node.kind === SyntaxKind.TypeReference) {
const type = getTypeFromTypeReference(<TypeReferenceNode>node);
if (type.flags & TypeFlags.TypeParameter) {
for (let i = index; i < typeParameters.length; i++) {
if (type.symbol === getSymbolOfNode(typeParameters[i])) {
error(node, Diagnostics.Type_parameter_defaults_can_only_reference_previously_declared_type_parameters);
}
}
}
}
forEachChild(node, visit);
}
}
/** Check that type parameter lists are identical across multiple declarations */
function checkTypeParameterListsIdentical(symbol: Symbol) {
if (symbol.declarations.length === 1) {
@@ -26971,7 +27115,7 @@ namespace ts {
return nodeIsMissing(expr) ? 0 : evaluateEnumMember(expr, getSymbolOfNode(member.parent), identifier.escapedText);
case SyntaxKind.ElementAccessExpression:
case SyntaxKind.PropertyAccessExpression:
const ex = <PropertyAccessExpression | ElementAccessExpression>expr;
const ex = <AccessExpression>expr;
if (isConstantMemberAccess(ex)) {
const type = getTypeOfExpression(ex.expression);
if (type.symbol && type.symbol.flags & SymbolFlags.Enum) {
@@ -28945,7 +29089,7 @@ namespace ts {
return getNodeLinks(node).enumMemberValue;
}
function canHaveConstantValue(node: Node): node is EnumMember | PropertyAccessExpression | ElementAccessExpression {
function canHaveConstantValue(node: Node): node is EnumMember | AccessExpression {
switch (node.kind) {
case SyntaxKind.EnumMember:
case SyntaxKind.PropertyAccessExpression:
@@ -28955,7 +29099,7 @@ namespace ts {
return false;
}
function getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number | undefined {
function getConstantValue(node: EnumMember | AccessExpression): string | number | undefined {
if (node.kind === SyntaxKind.EnumMember) {
return getEnumMemberValue(node);
}
+2 -2
View File
@@ -112,13 +112,13 @@ namespace ts {
}
// The global Map object. This may not be available, so we must test for it.
declare const Map: { new <T>(): Map<T> } | undefined;
declare const Map: (new <T>() => Map<T>) | undefined;
// Internet Explorer's Map doesn't support iteration, so don't use it.
// tslint:disable-next-line no-in-operator variable-name
export const MapCtr = typeof Map !== "undefined" && "entries" in Map.prototype ? Map : shimMap();
// Keep the class inside a function so it doesn't get compiled if it's not used.
function shimMap(): { new <T>(): Map<T> } {
function shimMap(): new <T>() => Map<T> {
class MapIterator<T, U extends (string | T | [string, T])> {
private data: MapLike<T>;
+13 -9
View File
@@ -1015,6 +1015,14 @@
"category": "Error",
"code": 1351
},
"A bigint literal cannot use exponential notation.": {
"category": "Error",
"code": 1352
},
"A bigint literal must be an integer.": {
"category": "Error",
"code": 1353
},
"Duplicate identifier '{0}'.": {
"category": "Error",
@@ -1636,14 +1644,6 @@
"category": "Error",
"code": 2458
},
"Type '{0}' has no property '{1}' and no string index signature.": {
"category": "Error",
"code": 2459
},
"Type '{0}' has no property '{1}'.": {
"category": "Error",
"code": 2460
},
"Type '{0}' is not an array type.": {
"category": "Error",
"code": 2461
@@ -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",
+15 -6
View File
@@ -787,7 +787,13 @@ namespace ts {
// Key is a file name. Value is the (non-empty, or undefined) list of files that redirect to it.
let redirectTargetsMap = createMultiMap<string>();
const filesByName = createMap<SourceFile | undefined>();
/**
* map with
* - SourceFile if present
* - false if sourceFile missing for source of project reference redirect
* - undefined otherwise
*/
const filesByName = createMap<SourceFile | false | undefined>();
let missingFilePaths: ReadonlyArray<Path> | undefined;
// stores 'filename -> file association' ignoring case
// used to track cases when two file names differ only in casing
@@ -854,7 +860,7 @@ namespace ts {
}
}
missingFilePaths = arrayFrom(filesByName.keys(), p => <Path>p).filter(p => !filesByName.get(p));
missingFilePaths = arrayFrom(mapDefinedIterator(filesByName.entries(), ([path, file]) => file === undefined ? path as Path : undefined));
files = stableSort(processingDefaultLibFiles, compareDefaultLibFiles).concat(processingOtherFiles);
processingDefaultLibFiles = undefined;
processingOtherFiles = undefined;
@@ -1567,7 +1573,7 @@ namespace ts {
}
function getSourceFileByPath(path: Path): SourceFile | undefined {
return filesByName.get(path);
return filesByName.get(path) || undefined;
}
function getDiagnosticsHelper<T extends Diagnostic>(
@@ -2104,7 +2110,7 @@ namespace ts {
/** This should have similar behavior to 'processSourceFile' without diagnostics or mutation. */
function getSourceFileFromReference(referencingFile: SourceFile, ref: FileReference): SourceFile | undefined {
return getSourceFileFromReferenceWorker(resolveTripleslashReference(ref.fileName, referencingFile.fileName), fileName => filesByName.get(toPath(fileName)));
return getSourceFileFromReferenceWorker(resolveTripleslashReference(ref.fileName, referencingFile.fileName), fileName => filesByName.get(toPath(fileName)) || undefined);
}
function getSourceFileFromReferenceWorker(
@@ -2235,7 +2241,7 @@ namespace ts {
}
}
return file;
return file || undefined;
}
let redirectedPath: Path | undefined;
@@ -2327,9 +2333,12 @@ namespace ts {
}
function addFileToFilesByName(file: SourceFile | undefined, path: Path, redirectedPath: Path | undefined) {
filesByName.set(path, file);
if (redirectedPath) {
filesByName.set(redirectedPath, file);
filesByName.set(path, file || false);
}
else {
filesByName.set(path, file);
}
}
+23 -11
View File
@@ -337,13 +337,14 @@ namespace ts {
return result;
}
export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number): number {
return computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character, sourceFile.text);
}
export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number): number;
/* @internal */
export function getPositionOfLineAndCharacterWithEdits(sourceFile: SourceFileLike, line: number, character: number): number {
return computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character, sourceFile.text, /*allowEdits*/ true);
// tslint:disable-next-line:unified-signatures
export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number, allowEdits?: true): number;
export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number, allowEdits?: true): number {
return sourceFile.getPositionOfLineAndCharacter ?
sourceFile.getPositionOfLineAndCharacter(line, character, allowEdits) :
computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character, sourceFile.text, allowEdits);
}
/* @internal */
@@ -976,7 +977,7 @@ namespace ts {
}
if (decimalFragment !== undefined || tokenFlags & TokenFlags.Scientific) {
checkForIdentifierStartAfterNumericLiteral();
checkForIdentifierStartAfterNumericLiteral(start, decimalFragment === undefined && !!(tokenFlags & TokenFlags.Scientific));
return {
type: SyntaxKind.NumericLiteral,
value: "" + +result // if value is not an integer, it can be safely coerced to a number
@@ -985,20 +986,31 @@ namespace ts {
else {
tokenValue = result;
const type = checkBigIntSuffix(); // if value is an integer, check whether it is a bigint
checkForIdentifierStartAfterNumericLiteral();
checkForIdentifierStartAfterNumericLiteral(start);
return { type, value: tokenValue };
}
}
function checkForIdentifierStartAfterNumericLiteral() {
function checkForIdentifierStartAfterNumericLiteral(numericStart: number, isScientific?: boolean) {
if (!isIdentifierStart(text.charCodeAt(pos), languageVersion)) {
return;
}
const identifierStart = pos;
const { length } = scanIdentifierParts();
error(Diagnostics.An_identifier_or_keyword_cannot_immediately_follow_a_numeric_literal, identifierStart, length);
pos = identifierStart;
if (length === 1 && text[identifierStart] === "n") {
if (isScientific) {
error(Diagnostics.A_bigint_literal_cannot_use_exponential_notation, numericStart, identifierStart - numericStart + 1);
}
else {
error(Diagnostics.A_bigint_literal_must_be_an_integer, numericStart, identifierStart - numericStart + 1);
}
}
else {
error(Diagnostics.An_identifier_or_keyword_cannot_immediately_follow_a_numeric_literal, identifierStart, length);
pos = identifierStart;
}
}
function scanOctalDigits(): number {
+24 -14
View File
@@ -266,14 +266,24 @@ namespace ts {
const sourceMapCommentRegExp = /^\/\/[@#] source[M]appingURL=(.+)\s*$/;
const whitespaceOrMapCommentRegExp = /^\s*(\/\/[@#] .*)?$/;
export interface LineInfo {
getLineCount(): number;
getLineText(line: number): string;
}
export function getLineInfo(text: string, lineStarts: ReadonlyArray<number>): LineInfo {
return {
getLineCount: () => lineStarts.length,
getLineText: line => text.substring(lineStarts[line], lineStarts[line + 1])
};
}
/**
* Tries to find the sourceMappingURL comment at the end of a file.
* @param text The source text of the file.
* @param lineStarts The line starts of the file.
*/
export function tryGetSourceMappingURL(text: string, lineStarts: ReadonlyArray<number> = computeLineStarts(text)) {
for (let index = lineStarts.length - 1; index >= 0; index--) {
const line = text.substring(lineStarts[index], lineStarts[index + 1]);
export function tryGetSourceMappingURL(lineInfo: LineInfo) {
for (let index = lineInfo.getLineCount() - 1; index >= 0; index--) {
const line = lineInfo.getLineText(index);
const comment = sourceMapCommentRegExp.exec(line);
if (comment) {
return comment[1];
@@ -573,7 +583,10 @@ namespace ts {
}
function compareSourcePositions(left: SourceMappedPosition, right: SourceMappedPosition) {
return compareValues(left.sourceIndex, right.sourceIndex);
// Compares sourcePosition without comparing sourceIndex
// since the mappings are grouped by sourceIndex
Debug.assert(left.sourceIndex === right.sourceIndex);
return compareValues(left.sourcePosition, right.sourcePosition);
}
function compareGeneratedPositions(left: MappedPosition, right: MappedPosition) {
@@ -592,11 +605,9 @@ namespace ts {
const mapDirectory = getDirectoryPath(mapPath);
const sourceRoot = map.sourceRoot ? getNormalizedAbsolutePath(map.sourceRoot, mapDirectory) : mapDirectory;
const generatedAbsoluteFilePath = getNormalizedAbsolutePath(map.file, mapDirectory);
const generatedCanonicalFilePath = host.getCanonicalFileName(generatedAbsoluteFilePath) as Path;
const generatedFile = host.getSourceFileLike(generatedCanonicalFilePath);
const generatedFile = host.getSourceFileLike(generatedAbsoluteFilePath);
const sourceFileAbsolutePaths = map.sources.map(source => getNormalizedAbsolutePath(source, sourceRoot));
const sourceFileCanonicalPaths = sourceFileAbsolutePaths.map(source => host.getCanonicalFileName(source) as Path);
const sourceToSourceIndexMap = createMapFromEntries(sourceFileCanonicalPaths.map((source, i) => [source, i] as [string, number]));
const sourceToSourceIndexMap = createMapFromEntries(sourceFileAbsolutePaths.map((source, i) => [host.getCanonicalFileName(source), i] as [string, number]));
let decodedMappings: ReadonlyArray<MappedPosition> | undefined;
let generatedMappings: SortedReadonlyArray<MappedPosition> | undefined;
let sourceMappings: ReadonlyArray<SortedReadonlyArray<SourceMappedPosition>> | undefined;
@@ -608,16 +619,15 @@ namespace ts {
function processMapping(mapping: Mapping): MappedPosition {
const generatedPosition = generatedFile !== undefined
? getPositionOfLineAndCharacterWithEdits(generatedFile, mapping.generatedLine, mapping.generatedCharacter)
? getPositionOfLineAndCharacter(generatedFile, mapping.generatedLine, mapping.generatedCharacter, /*allowEdits*/ true)
: -1;
let source: string | undefined;
let sourcePosition: number | undefined;
if (isSourceMapping(mapping)) {
const sourceFilePath = sourceFileCanonicalPaths[mapping.sourceIndex];
const sourceFile = host.getSourceFileLike(sourceFilePath);
const sourceFile = host.getSourceFileLike(sourceFileAbsolutePaths[mapping.sourceIndex]);
source = map.sources[mapping.sourceIndex];
sourcePosition = sourceFile !== undefined
? getPositionOfLineAndCharacterWithEdits(sourceFile, mapping.sourceLine, mapping.sourceCharacter)
? getPositionOfLineAndCharacter(sourceFile, mapping.sourceLine, mapping.sourceCharacter, /*allowEdits*/ true)
: -1;
}
return {
+2 -1
View File
@@ -1100,7 +1100,8 @@ namespace ts {
if (extendsClause && !isEntityNameExpression(extendsClause.expression) && extendsClause.expression.kind !== SyntaxKind.NullKeyword) {
// We must add a temporary declaration for the extends clause expression
const newId = createOptimisticUniqueName(`${unescapeLeadingUnderscores(input.name!.escapedText)}_base`); // TODO: GH#18217
const oldId = input.name ? unescapeLeadingUnderscores(input.name.escapedText) : "default";
const newId = createOptimisticUniqueName(`${oldId}_base`);
getSymbolAccessibilityDiagnostic = () => ({
diagnosticMessage: Diagnostics.extends_clause_of_exported_class_0_has_or_is_using_private_name_1,
errorNode: extendsClause,
+1 -1
View File
@@ -780,7 +780,7 @@ namespace ts {
enableSubstitutionsForBlockScopedBindings();
}
const extendsClauseElement = getEffectiveBaseTypeNode(node);
const extendsClauseElement = getClassExtendsHeritageElement(node);
const classFunction = createFunctionExpression(
/*modifiers*/ undefined,
/*asteriskToken*/ undefined,
+19 -12
View File
@@ -1745,6 +1745,9 @@ namespace ts {
export type EntityNameExpression = Identifier | PropertyAccessEntityNameExpression;
export type EntityNameOrEntityNameExpression = EntityName | EntityNameExpression;
/* @internal */
export type AccessExpression = PropertyAccessExpression | ElementAccessExpression;
export interface PropertyAccessExpression extends MemberExpression, NamedDeclaration {
kind: SyntaxKind.PropertyAccessExpression;
expression: LeftHandSideExpression;
@@ -2614,6 +2617,8 @@ namespace ts {
export interface SourceFileLike {
readonly text: string;
lineMap?: ReadonlyArray<number>;
/* @internal */
getPositionOfLineAndCharacter?(line: number, character: number, allowEdits?: true): number;
}
@@ -3103,7 +3108,7 @@ namespace ts {
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number | undefined;
isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName | ImportTypeNode, propertyName: string): boolean;
/** Exclude accesses to private properties or methods with a `this` parameter that `type` doesn't satisfy. */
/* @internal */ isValidPropertyAccessForCompletions(node: PropertyAccessExpression | ImportTypeNode, type: Type, property: Symbol): boolean;
/* @internal */ isValidPropertyAccessForCompletions(node: PropertyAccessExpression | ImportTypeNode | QualifiedName, type: Type, property: Symbol): boolean;
/** Follow all aliases to get the original symbol. */
getAliasedSymbol(symbol: Symbol): Symbol;
/** Follow a *single* alias to get the immediately aliased symbol. */
@@ -3672,15 +3677,17 @@ namespace ts {
Readonly = 1 << 3, // Readonly transient symbol
Partial = 1 << 4, // Synthetic property present in some but not all constituents
HasNonUniformType = 1 << 5, // Synthetic property with non-uniform type in constituents
ContainsPublic = 1 << 6, // Synthetic property with public constituent(s)
ContainsProtected = 1 << 7, // Synthetic property with protected constituent(s)
ContainsPrivate = 1 << 8, // Synthetic property with private constituent(s)
ContainsStatic = 1 << 9, // Synthetic property with static constituent(s)
Late = 1 << 10, // Late-bound symbol for a computed property with a dynamic name
ReverseMapped = 1 << 11, // Property of reverse-inferred homomorphic mapped type
OptionalParameter = 1 << 12, // Optional parameter
RestParameter = 1 << 13, // Rest parameter
Synthetic = SyntheticProperty | SyntheticMethod
HasLiteralType = 1 << 6, // Synthetic property with at least one literal type in constituents
ContainsPublic = 1 << 7, // Synthetic property with public constituent(s)
ContainsProtected = 1 << 8, // Synthetic property with protected constituent(s)
ContainsPrivate = 1 << 9, // Synthetic property with private constituent(s)
ContainsStatic = 1 << 10, // Synthetic property with static constituent(s)
Late = 1 << 11, // Late-bound symbol for a computed property with a dynamic name
ReverseMapped = 1 << 12, // Property of reverse-inferred homomorphic mapped type
OptionalParameter = 1 << 13, // Optional parameter
RestParameter = 1 << 14, // Rest parameter
Synthetic = SyntheticProperty | SyntheticMethod,
Discriminant = HasNonUniformType | HasLiteralType
}
/* @internal */
@@ -5528,9 +5535,9 @@ namespace ts {
/* @internal */
export interface DocumentPositionMapperHost {
getSourceFileLike(path: Path): SourceFileLike | undefined;
getSourceFileLike(fileName: string): SourceFileLike | undefined;
getCanonicalFileName(path: string): string;
log?(text: string): void;
log(text: string): void;
}
/**
+6 -2
View File
@@ -2121,7 +2121,7 @@ namespace ts {
: undefined;
}
function getSingleInitializerOfVariableStatementOrPropertyDeclaration(node: Node): Expression | undefined {
export function getSingleInitializerOfVariableStatementOrPropertyDeclaration(node: Node): Expression | undefined {
switch (node.kind) {
case SyntaxKind.VariableStatement:
const v = getSingleVariableOfVariableStatement(node);
@@ -4590,6 +4590,10 @@ namespace ts {
|| kind === SyntaxKind.JSDocFunctionType
|| kind === SyntaxKind.JSDocVariadicType;
}
export function isAccessExpression(node: Node): node is AccessExpression {
return node.kind === SyntaxKind.PropertyAccessExpression || node.kind === SyntaxKind.ElementAccessExpression;
}
}
namespace ts {
@@ -6925,7 +6929,7 @@ namespace ts {
export interface ObjectAllocator {
getNodeConstructor(): new (kind: SyntaxKind, pos?: number, end?: number) => Node;
getTokenConstructor(): { new <TKind extends SyntaxKind>(kind: TKind, pos?: number, end?: number): Token<TKind> };
getTokenConstructor(): new <TKind extends SyntaxKind>(kind: TKind, pos?: number, end?: number) => Token<TKind>;
getIdentifierConstructor(): new (kind: SyntaxKind.Identifier, pos?: number, end?: number) => Identifier;
getSourceFileConstructor(): new (kind: SyntaxKind.SourceFile, pos?: number, end?: number) => SourceFile;
getSymbolConstructor(): new (flags: SymbolFlags, name: __String) => Symbol;
+1 -1
View File
@@ -600,7 +600,7 @@ namespace FourSlash {
throw new Error("Expected exactly one output from emit of " + this.activeFile.fileName);
}
const evaluation = new Function(`${emit.outputFiles[0].text};\r\nreturn (${expr});`)();
const evaluation = new Function(`${emit.outputFiles[0].text};\r\nreturn (${expr});`)(); // tslint:disable-line:function-constructor
if (evaluation !== value) {
this.raiseError(`Expected evaluation of expression "${expr}" to equal "${value}", but got "${evaluation}"`);
}
+2 -4
View File
@@ -28,12 +28,10 @@ var assert: typeof _chai.assert = _chai.assert;
};
}
var global: NodeJS.Global = Function("return this").call(undefined);
var global: NodeJS.Global = Function("return this").call(undefined); // tslint:disable-line:function-constructor
declare var window: {};
declare var XMLHttpRequest: {
new(): XMLHttpRequest;
};
declare var XMLHttpRequest: new() => XMLHttpRequest;
interface XMLHttpRequest {
readonly readyState: number;
readonly responseText: string;
+1 -1
View File
@@ -69,7 +69,7 @@ namespace Harness.SourceMapRecorder {
SourceMapDecoder.initializeSourceMapDecoding(sourceMapData);
sourceMapRecorder.WriteLine("===================================================================");
sourceMapRecorder.WriteLine("JsFile: " + sourceMapData.sourceMap.file);
sourceMapRecorder.WriteLine("mapUrl: " + ts.tryGetSourceMappingURL(jsFile.text, jsLineMap));
sourceMapRecorder.WriteLine("mapUrl: " + ts.tryGetSourceMappingURL(ts.getLineInfo(jsFile.text, jsLineMap)));
sourceMapRecorder.WriteLine("sourceRoot: " + sourceMapData.sourceMap.sourceRoot);
sourceMapRecorder.WriteLine("sources: " + sourceMapData.sourceMap.sources);
if (sourceMapData.sourceMap.sourcesContent) {
@@ -2083,7 +2083,7 @@
<Str Cat="Text">
<Val><![CDATA[Cannot find the common subdirectory path for the input files.]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[找不到輸入檔案的一般子目錄路徑。]]></Val>
<Val><![CDATA[找不到輸入檔的一般子目錄路徑。]]></Val>
</Tgt>
</Str>
<Disp Icon="Str" />
@@ -7567,7 +7567,7 @@
<Str Cat="Text">
<Val><![CDATA[Specify the root directory of input files. Use to control the output directory structure with --outDir.]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[指定輸入檔案的根目錄。用以控制具有 --outDir 的輸出目錄結構。]]></Val>
<Val><![CDATA[指定輸入檔的根目錄。用以控制具有 --outDir 的輸出目錄結構。]]></Val>
</Tgt>
</Str>
<Disp Icon="Str" />
@@ -9172,7 +9172,7 @@
<Str Cat="Text">
<Val><![CDATA[Watch input files.]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[監看輸入檔案。]]></Val>
<Val><![CDATA[監看輸入檔。]]></Val>
</Tgt>
</Str>
<Disp Icon="Str" />
File diff suppressed because it is too large Load Diff
+243 -10
View File
@@ -342,6 +342,7 @@ namespace ts.server {
FailedLookupLocation = "Directory of Failed lookup locations in module resolution",
TypeRoots = "Type root directory",
NodeModulesForClosedScriptInfo = "node_modules for closed script infos in them",
MissingSourceMapFile = "Missing source map file"
}
const enum ConfigFileWatcherStatus {
@@ -437,7 +438,8 @@ namespace ts.server {
/**
* Container of all known scripts
*/
private readonly filenameToScriptInfo = createMap<ScriptInfo>();
/*@internal*/
readonly filenameToScriptInfo = createMap<ScriptInfo>();
private readonly scriptInfoInNodeModulesWatchers = createMap <ScriptInfoInNodeModulesWatcher>();
/**
* Contains all the deleted script info's version information so that
@@ -944,10 +946,42 @@ namespace ts.server {
// this file and set of inferred projects
info.delayReloadNonMixedContentFile();
this.delayUpdateProjectGraphs(info.containingProjects);
this.handleSourceMapProjects(info);
}
}
}
private handleSourceMapProjects(info: ScriptInfo) {
// Change in d.ts, update source projects as well
if (info.sourceMapFilePath) {
if (isString(info.sourceMapFilePath)) {
const sourceMapFileInfo = this.getScriptInfoForPath(info.sourceMapFilePath);
this.delayUpdateSourceInfoProjects(sourceMapFileInfo && sourceMapFileInfo.sourceInfos);
}
else {
this.delayUpdateSourceInfoProjects(info.sourceMapFilePath.sourceInfos);
}
}
// Change in mapInfo, update declarationProjects and source projects
this.delayUpdateSourceInfoProjects(info.sourceInfos);
if (info.declarationInfoPath) {
this.delayUpdateProjectsOfScriptInfoPath(info.declarationInfoPath);
}
}
private delayUpdateSourceInfoProjects(sourceInfos: Map<true> | undefined) {
if (sourceInfos) {
sourceInfos.forEach((_value, path) => this.delayUpdateProjectsOfScriptInfoPath(path as Path));
}
}
private delayUpdateProjectsOfScriptInfoPath(path: Path) {
const info = this.getScriptInfoForPath(path);
if (info) {
this.delayUpdateProjectGraphs(info.containingProjects);
}
}
private handleDeletedFile(info: ScriptInfo) {
this.stopWatchingScriptInfo(info);
@@ -961,6 +995,15 @@ namespace ts.server {
// update projects to make sure that set of referenced files is correct
this.delayUpdateProjectGraphs(containingProjects);
this.handleSourceMapProjects(info);
info.closeSourceMapFileWatcher();
// need to recalculate source map from declaration file
if (info.declarationInfoPath) {
const declarationInfo = this.getScriptInfoForPath(info.declarationInfoPath);
if (declarationInfo) {
declarationInfo.sourceMapFilePath = undefined;
}
}
}
}
@@ -1234,7 +1277,8 @@ namespace ts.server {
private setConfigFileExistenceByNewConfiguredProject(project: ConfiguredProject) {
const configFileExistenceInfo = this.getConfigFileExistenceInfo(project);
if (configFileExistenceInfo) {
Debug.assert(configFileExistenceInfo.exists);
// The existance might not be set if the file watcher is not invoked by the time config project is created by external project
configFileExistenceInfo.exists = true;
// close existing watcher
if (configFileExistenceInfo.configFileWatcherForRootOfInferredProject) {
const configFileName = project.getConfigFilePath();
@@ -2195,6 +2239,150 @@ namespace ts.server {
return this.filenameToScriptInfo.get(fileName);
}
/*@internal*/
getDocumentPositionMapper(project: Project, generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined {
// Since declaration info and map file watches arent updating project's directory structure host (which can cache file structure) use host
const declarationInfo = this.getOrCreateScriptInfoNotOpenedByClient(generatedFileName, project.currentDirectory, this.host);
if (!declarationInfo) return undefined;
// Try to get from cache
declarationInfo.getSnapshot(); // Ensure synchronized
if (isString(declarationInfo.sourceMapFilePath)) {
// Ensure mapper is synchronized
const sourceMapFileInfo = this.getScriptInfoForPath(declarationInfo.sourceMapFilePath);
if (sourceMapFileInfo) {
sourceMapFileInfo.getSnapshot();
if (sourceMapFileInfo.documentPositionMapper !== undefined) {
sourceMapFileInfo.sourceInfos = this.addSourceInfoToSourceMap(sourceFileName, project, sourceMapFileInfo.sourceInfos);
return sourceMapFileInfo.documentPositionMapper ? sourceMapFileInfo.documentPositionMapper : undefined;
}
}
declarationInfo.sourceMapFilePath = undefined;
}
else if (declarationInfo.sourceMapFilePath) {
declarationInfo.sourceMapFilePath.sourceInfos = this.addSourceInfoToSourceMap(sourceFileName, project, declarationInfo.sourceMapFilePath.sourceInfos);
return undefined;
}
else if (declarationInfo.sourceMapFilePath !== undefined) {
// Doesnt have sourceMap
return undefined;
}
// Create the mapper
let sourceMapFileInfo: ScriptInfo | undefined;
let mapFileNameFromDeclarationInfo: string | undefined;
let readMapFile: ReadMapFile | undefined = (mapFileName, mapFileNameFromDts) => {
const mapInfo = this.getOrCreateScriptInfoNotOpenedByClient(mapFileName, project.currentDirectory, this.host);
if (!mapInfo) {
mapFileNameFromDeclarationInfo = mapFileNameFromDts;
return undefined;
}
sourceMapFileInfo = mapInfo;
const snap = mapInfo.getSnapshot();
if (mapInfo.documentPositionMapper !== undefined) return mapInfo.documentPositionMapper;
return snap.getText(0, snap.getLength());
};
const projectName = project.projectName;
const documentPositionMapper = getDocumentPositionMapper(
{ getCanonicalFileName: this.toCanonicalFileName, log: s => this.logger.info(s), getSourceFileLike: f => this.getSourceFileLike(f, projectName, declarationInfo) },
declarationInfo.fileName,
declarationInfo.getLineInfo(),
readMapFile
);
readMapFile = undefined; // Remove ref to project
if (sourceMapFileInfo) {
declarationInfo.sourceMapFilePath = sourceMapFileInfo.path;
sourceMapFileInfo.declarationInfoPath = declarationInfo.path;
sourceMapFileInfo.documentPositionMapper = documentPositionMapper || false;
sourceMapFileInfo.sourceInfos = this.addSourceInfoToSourceMap(sourceFileName, project, sourceMapFileInfo.sourceInfos);
}
else if (mapFileNameFromDeclarationInfo) {
declarationInfo.sourceMapFilePath = {
watcher: this.addMissingSourceMapFile(
project.currentDirectory === this.currentDirectory ?
mapFileNameFromDeclarationInfo :
getNormalizedAbsolutePath(mapFileNameFromDeclarationInfo, project.currentDirectory),
declarationInfo.path
),
sourceInfos: this.addSourceInfoToSourceMap(sourceFileName, project)
};
}
else {
declarationInfo.sourceMapFilePath = false;
}
return documentPositionMapper;
}
private addSourceInfoToSourceMap(sourceFileName: string | undefined, project: Project, sourceInfos?: Map<true>) {
if (sourceFileName) {
// Attach as source
const sourceInfo = this.getOrCreateScriptInfoNotOpenedByClient(sourceFileName, project.currentDirectory, project.directoryStructureHost)!;
(sourceInfos || (sourceInfos = createMap())).set(sourceInfo.path, true);
}
return sourceInfos;
}
private addMissingSourceMapFile(mapFileName: string, declarationInfoPath: Path) {
const fileWatcher = this.watchFactory.watchFile(
this.host,
mapFileName,
() => {
const declarationInfo = this.getScriptInfoForPath(declarationInfoPath);
if (declarationInfo && declarationInfo.sourceMapFilePath && !isString(declarationInfo.sourceMapFilePath)) {
// Update declaration and source projects
this.delayUpdateProjectGraphs(declarationInfo.containingProjects);
this.delayUpdateSourceInfoProjects(declarationInfo.sourceMapFilePath.sourceInfos);
declarationInfo.closeSourceMapFileWatcher();
}
},
PollingInterval.High,
WatchType.MissingSourceMapFile,
);
return fileWatcher;
}
/*@internal*/
getSourceFileLike(fileName: string, projectNameOrProject: string | Project, declarationInfo?: ScriptInfo) {
const project = (projectNameOrProject as Project).projectName ? projectNameOrProject as Project : this.findProject(projectNameOrProject as string);
if (project) {
const path = project.toPath(fileName);
const sourceFile = project.getSourceFile(path);
if (sourceFile && sourceFile.resolvedPath === path) return sourceFile;
}
// Need to look for other files.
const info = this.getOrCreateScriptInfoNotOpenedByClient(fileName, (project || this).currentDirectory, project ? project.directoryStructureHost : this.host);
if (!info) return undefined;
// Attach as source
if (declarationInfo && isString(declarationInfo.sourceMapFilePath) && info !== declarationInfo) {
const sourceMapInfo = this.getScriptInfoForPath(declarationInfo.sourceMapFilePath);
if (sourceMapInfo) {
(sourceMapInfo.sourceInfos || (sourceMapInfo.sourceInfos = createMap())).set(info.path, true);
}
}
// Key doesnt matter since its only for text and lines
if (info.cacheSourceFile) return info.cacheSourceFile.sourceFile;
// Create sourceFileLike
if (!info.sourceFileLike) {
info.sourceFileLike = {
get text() {
Debug.fail("shouldnt need text");
return "";
},
getLineAndCharacterOfPosition: pos => {
const lineOffset = info.positionToLineOffset(pos);
return { line: lineOffset.line - 1, character: lineOffset.offset - 1 };
},
getPositionOfLineAndCharacter: (line, character, allowEdits) => info.lineOffsetToPosition(line + 1, character + 1, allowEdits)
};
}
return info.sourceFileLike;
}
setHostConfiguration(args: protocol.ConfigureRequestArguments) {
if (args.file) {
const info = this.getScriptInfoForNormalizedPath(toNormalizedPath(args.file));
@@ -2416,7 +2604,7 @@ namespace ts.server {
/** @internal */
fileExists(fileName: NormalizedPath): boolean {
return this.filenameToScriptInfo.has(fileName) || this.host.fileExists(fileName);
return !!this.getScriptInfoForNormalizedPath(fileName) || this.host.fileExists(fileName);
}
private findExternalProjectContainingOpenScriptInfo(info: ScriptInfo): ExternalProject | undefined {
@@ -2490,13 +2678,7 @@ namespace ts.server {
// when some file/s were closed which resulted in project removal.
// It was then postponed to cleanup these script infos so that they can be reused if
// the file from that old project is reopened because of opening file from here.
this.filenameToScriptInfo.forEach(info => {
if (!info.isScriptOpen() && info.isOrphan()) {
// if there are not projects that include this script info - delete it
this.stopWatchingScriptInfo(info);
this.deleteScriptInfo(info);
}
});
this.removeOrphanScriptInfos();
this.printProjects();
@@ -2539,6 +2721,57 @@ namespace ts.server {
}
}
private removeOrphanScriptInfos() {
const toRemoveScriptInfos = cloneMap(this.filenameToScriptInfo);
this.filenameToScriptInfo.forEach(info => {
// If script info is open or orphan, retain it and its dependencies
if (!info.isScriptOpen() && info.isOrphan()) {
// Otherwise if there is any source info that is alive, this alive too
if (!info.sourceMapFilePath) return;
let sourceInfos: Map<true> | undefined;
if (isString(info.sourceMapFilePath)) {
const sourceMapInfo = this.getScriptInfoForPath(info.sourceMapFilePath);
sourceInfos = sourceMapInfo && sourceMapInfo.sourceInfos;
}
else {
sourceInfos = info.sourceMapFilePath.sourceInfos;
}
if (!sourceInfos) return;
if (!forEachKey(sourceInfos, path => {
const info = this.getScriptInfoForPath(path as Path);
return !!info && (info.isScriptOpen() || !info.isOrphan());
})) {
return;
}
}
// Retain this script info
toRemoveScriptInfos.delete(info.path);
if (info.sourceMapFilePath) {
let sourceInfos: Map<true> | undefined;
if (isString(info.sourceMapFilePath)) {
// And map file info and source infos
toRemoveScriptInfos.delete(info.sourceMapFilePath);
const sourceMapInfo = this.getScriptInfoForPath(info.sourceMapFilePath);
sourceInfos = sourceMapInfo && sourceMapInfo.sourceInfos;
}
else {
sourceInfos = info.sourceMapFilePath.sourceInfos;
}
if (sourceInfos) {
sourceInfos.forEach((_value, path) => toRemoveScriptInfos.delete(path));
}
}
});
toRemoveScriptInfos.forEach(info => {
// if there are not projects that include this script info - delete it
this.stopWatchingScriptInfo(info);
this.deleteScriptInfo(info);
info.closeSourceMapFileWatcher();
});
}
private telemetryOnOpenFile(scriptInfo: ScriptInfo): void {
if (this.syntaxOnly || !this.eventHandler || !scriptInfo.isJavaScript() || !addToSeen(this.allJsFilesForOpenFileTelemetry, scriptInfo.path)) {
return;
+14 -1
View File
@@ -503,6 +503,16 @@ namespace ts.server {
return this.getLanguageService().getSourceMapper();
}
/*@internal*/
getDocumentPositionMapper(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined {
return this.projectService.getDocumentPositionMapper(this, generatedFileName, sourceFileName);
}
/*@internal*/
getSourceFileLike(fileName: string) {
return this.projectService.getSourceFileLike(fileName, this);
}
private shouldEmitFile(scriptInfo: ScriptInfo) {
return scriptInfo && !scriptInfo.isDynamicOrHasMixedContent();
}
@@ -749,7 +759,10 @@ namespace ts.server {
}
containsScriptInfo(info: ScriptInfo): boolean {
return this.isRoot(info) || (!!this.program && this.program.getSourceFileByPath(info.path) !== undefined);
if (this.isRoot(info)) return true;
if (!this.program) return false;
const file = this.program.getSourceFileByPath(info.path);
return !!file && file.resolvedPath === info.path;
}
containsFile(filename: NormalizedPath, requireOpen?: boolean): boolean {
+67 -8
View File
@@ -64,12 +64,22 @@ namespace ts.server {
this.switchToScriptVersionCache();
}
private resetSourceMapInfo() {
this.info.sourceFileLike = undefined;
this.info.closeSourceMapFileWatcher();
this.info.sourceMapFilePath = undefined;
this.info.declarationInfoPath = undefined;
this.info.sourceInfos = undefined;
this.info.documentPositionMapper = undefined;
}
/** Public for testing */
public useText(newText?: string) {
this.svc = undefined;
this.text = newText;
this.lineMap = undefined;
this.fileSize = undefined;
this.resetSourceMapInfo();
this.version.text++;
}
@@ -79,6 +89,7 @@ namespace ts.server {
this.text = undefined;
this.lineMap = undefined;
this.fileSize = undefined;
this.resetSourceMapInfo();
}
/**
@@ -156,8 +167,8 @@ namespace ts.server {
: ScriptSnapshot.fromString(this.getOrLoadText());
}
public getLineInfo(line: number): AbsolutePositionAndLineText {
return this.switchToScriptVersionCache().getLineInfo(line);
public getAbsolutePositionAndLineText(line: number): AbsolutePositionAndLineText {
return this.switchToScriptVersionCache().getAbsolutePositionAndLineText(line);
}
/**
* @param line 0 based index
@@ -176,9 +187,9 @@ namespace ts.server {
* @param line 1 based index
* @param offset 1 based index
*/
lineOffsetToPosition(line: number, offset: number): number {
lineOffsetToPosition(line: number, offset: number, allowEdits?: true): number {
if (!this.useScriptVersionCacheIfValidOrOpen()) {
return computePositionOfLineAndCharacter(this.getLineMap(), line - 1, offset - 1, this.text);
return computePositionOfLineAndCharacter(this.getLineMap(), line - 1, offset - 1, this.text, allowEdits);
}
// TODO: assert this offset is actually on the line
@@ -246,6 +257,17 @@ namespace ts.server {
Debug.assert(!this.svc, "ScriptVersionCache should not be set");
return this.lineMap || (this.lineMap = computeLineStarts(this.getOrLoadText()));
}
getLineInfo(): LineInfo {
if (this.svc) {
return {
getLineCount: () => this.svc!.getLineCount(),
getLineText: line => this.svc!.getAbsolutePositionAndLineText(line + 1).lineText!
};
}
const lineMap = this.getLineMap();
return getLineInfo(this.text!, lineMap);
}
}
/*@internal*/
@@ -259,6 +281,12 @@ namespace ts.server {
sourceFile: SourceFile;
}
/*@internal*/
export interface SourceMapFileWatcher {
watcher: FileWatcher;
sourceInfos?: Map<true>;
}
export class ScriptInfo {
/**
* All projects that include this file
@@ -284,6 +312,20 @@ namespace ts.server {
/*@internal*/
mTime?: number;
/*@internal*/
sourceFileLike?: SourceFileLike;
/*@internal*/
sourceMapFilePath?: Path | SourceMapFileWatcher | false;
// Present on sourceMapFile info
/*@internal*/
declarationInfoPath?: Path;
/*@internal*/
sourceInfos?: Map<true>;
/*@internal*/
documentPositionMapper?: DocumentPositionMapper | false;
constructor(
private readonly host: ServerHost,
readonly fileName: NormalizedPath,
@@ -521,8 +563,8 @@ namespace ts.server {
}
/*@internal*/
getLineInfo(line: number): AbsolutePositionAndLineText {
return this.textStorage.getLineInfo(line);
getAbsolutePositionAndLineText(line: number): AbsolutePositionAndLineText {
return this.textStorage.getAbsolutePositionAndLineText(line);
}
editContent(start: number, end: number, newText: string): void {
@@ -551,8 +593,12 @@ namespace ts.server {
* @param line 1 based index
* @param offset 1 based index
*/
lineOffsetToPosition(line: number, offset: number): number {
return this.textStorage.lineOffsetToPosition(line, offset);
lineOffsetToPosition(line: number, offset: number): number;
/*@internal*/
// tslint:disable-next-line:unified-signatures
lineOffsetToPosition(line: number, offset: number, allowEdits?: true): number;
lineOffsetToPosition(line: number, offset: number, allowEdits?: true): number {
return this.textStorage.lineOffsetToPosition(line, offset, allowEdits);
}
positionToLineOffset(position: number): protocol.Location {
@@ -562,5 +608,18 @@ namespace ts.server {
public isJavaScript() {
return this.scriptKind === ScriptKind.JS || this.scriptKind === ScriptKind.JSX;
}
/*@internal*/
getLineInfo(): LineInfo {
return this.textStorage.getLineInfo();
}
/*@internal*/
closeSourceMapFileWatcher() {
if (this.sourceMapFilePath && !isString(this.sourceMapFilePath)) {
closeFileWatcherOf(this.sourceMapFilePath);
this.sourceMapFilePath = undefined;
}
}
}
}
+11 -3
View File
@@ -308,8 +308,8 @@ namespace ts.server {
return this._getSnapshot().version;
}
getLineInfo(line: number): AbsolutePositionAndLineText {
return this._getSnapshot().index.lineNumberToInfo(line);
getAbsolutePositionAndLineText(oneBasedLine: number): AbsolutePositionAndLineText {
return this._getSnapshot().index.lineNumberToInfo(oneBasedLine);
}
lineOffsetToPosition(line: number, column: number): number {
@@ -348,6 +348,10 @@ namespace ts.server {
}
}
getLineCount() {
return this._getSnapshot().index.getLineCount();
}
static fromString(script: string) {
const svc = new ScriptVersionCache();
const snap = new LineIndexSnapshot(0, svc, new LineIndex());
@@ -400,8 +404,12 @@ namespace ts.server {
return this.root.charOffsetToLineInfo(1, position);
}
getLineCount() {
return this.root.lineCount();
}
lineNumberToInfo(oneBasedLine: number): AbsolutePositionAndLineText {
const lineCount = this.root.lineCount();
const lineCount = this.getLineCount();
if (oneBasedLine <= lineCount) {
const { position, leaf } = this.root.lineNumberToInfo(oneBasedLine, 0);
return { absolutePosition: position, lineText: leaf && leaf.text };
+1 -1
View File
@@ -1474,7 +1474,7 @@ namespace ts.server {
// only to the previous line. If all this is true, then
// add edits necessary to properly indent the current line.
if ((args.key === "\n") && ((!edits) || (edits.length === 0) || allEditsBeforePos(edits, position))) {
const { lineText, absolutePosition } = scriptInfo.getLineInfo(args.line);
const { lineText, absolutePosition } = scriptInfo.getAbsolutePositionAndLineText(args.line);
if (lineText && lineText.search("\\S") < 0) {
const preferredIndent = languageService.getIndentationAtPosition(file, position, formatOptions);
let hasIndent = 0;
+1 -1
View File
@@ -375,7 +375,7 @@ namespace ts.codefix {
const symbolName = isJsxOpeningLikeElement(symbolToken.parent)
&& symbolToken.parent.tagName === symbolToken
&& (isIntrinsicJsxName(symbolToken.text) || checker.resolveName(symbolToken.text, symbolToken, SymbolFlags.All, /*excludeGlobals*/ false))
? checker.getJsxNamespace()
? checker.getJsxNamespace(sourceFile)
: symbolToken.text;
// "default" is a keyword and not a legal identifier for the import, so we don't expect it here
Debug.assert(symbolName !== InternalSymbolName.Default);
+1 -1
View File
@@ -913,7 +913,7 @@ namespace ts.Completions {
}
else {
for (const symbol of type.getApparentProperties()) {
if (typeChecker.isValidPropertyAccessForCompletions(node.kind === SyntaxKind.ImportType ? <ImportTypeNode>node : <PropertyAccessExpression>node.parent, type, symbol)) {
if (typeChecker.isValidPropertyAccessForCompletions(node.kind === SyntaxKind.ImportType ? <ImportTypeNode>node : <PropertyAccessExpression | QualifiedName>node.parent, type, symbol)) {
addPropertySymbol(symbol);
}
}
+1 -1
View File
@@ -660,7 +660,7 @@ namespace ts.NavigationBar {
else if (isCallExpression(parent)) {
const name = getCalledExpressionName(parent.expression);
if (name !== undefined) {
const args = mapDefined(parent.arguments, a => isStringLiteral(a) ? a.getText(curSourceFile) : undefined).join(", ");
const args = mapDefined(parent.arguments, a => isStringLiteralLike(a) ? a.getText(curSourceFile) : undefined).join(", ");
return `${name}(${args}) callback`;
}
}
+4 -9
View File
@@ -28,12 +28,12 @@ namespace ts.OrganizeImports {
organizeImportsWorker(topLevelExportDecls, coalesceExports);
for (const ambientModule of sourceFile.statements.filter(isAmbientModule)) {
const ambientModuleBody = getModuleBlock(ambientModule as ModuleDeclaration)!; // TODO: GH#18217
if (!ambientModule.body) { continue; }
const ambientModuleImportDecls = ambientModuleBody.statements.filter(isImportDeclaration);
const ambientModuleImportDecls = ambientModule.body.statements.filter(isImportDeclaration);
organizeImportsWorker(ambientModuleImportDecls, coalesceAndOrganizeImports);
const ambientModuleExportDecls = ambientModuleBody.statements.filter(isExportDeclaration);
const ambientModuleExportDecls = ambientModule.body.statements.filter(isExportDeclaration);
organizeImportsWorker(ambientModuleExportDecls, coalesceExports);
}
@@ -81,14 +81,9 @@ 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();
const jsxNamespace = typeChecker.getJsxNamespace(sourceFile);
const jsxElementsPresent = !!(sourceFile.transformFlags & TransformFlags.ContainsJsx);
const usedImports: ImportDeclaration[] = [];
+15 -2
View File
@@ -37,6 +37,10 @@ namespace ts.OutliningElementsCollector {
addOutliningForLeadingCommentsForNode(n, sourceFile, cancellationToken, out);
}
if (isFunctionExpressionAssignedToVariable(n)) {
addOutliningForLeadingCommentsForNode(n.parent.parent.parent, sourceFile, cancellationToken, out);
}
const span = getOutliningSpanForNode(n, sourceFile);
if (span) out.push(span);
@@ -54,6 +58,14 @@ namespace ts.OutliningElementsCollector {
}
depthRemaining++;
}
function isFunctionExpressionAssignedToVariable(n: Node) {
if (!isFunctionExpression(n) && !isArrowFunction(n)) {
return false;
}
const ancestor = findAncestor(n, isVariableStatement);
return !!ancestor && getSingleInitializerOfVariableStatementOrPropertyDeclaration(ancestor) === n;
}
}
function addRegionOutliningSpans(sourceFile: SourceFile, out: Push<OutliningSpan>): void {
@@ -175,6 +187,7 @@ namespace ts.OutliningElementsCollector {
case SyntaxKind.ModuleBlock:
return spanForNode(n.parent);
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ClassExpression:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.CaseBlock:
@@ -206,10 +219,10 @@ namespace ts.OutliningElementsCollector {
}
function spanForObjectOrArrayLiteral(node: Node, open: SyntaxKind.OpenBraceToken | SyntaxKind.OpenBracketToken = SyntaxKind.OpenBraceToken): OutliningSpan | undefined {
// If the block has no leading keywords and is inside an array literal,
// If the block has no leading keywords and is inside an array literal or call expression,
// we only want to collapse the span of the block.
// Otherwise, the collapsed section will include the end of the previous line.
return spanForNode(node, /*autoCollapse*/ false, /*useFullStart*/ !isArrayLiteralExpression(node.parent), open);
return spanForNode(node, /*autoCollapse*/ false, /*useFullStart*/ !isArrayLiteralExpression(node.parent) && !isCallExpression(node.parent), open);
}
function spanForNode(hintSpanNode: Node, autoCollapse = false, useFullStart = true, open: SyntaxKind.OpenBraceToken | SyntaxKind.OpenBracketToken = SyntaxKind.OpenBraceToken): OutliningSpan | undefined {
+2 -4
View File
@@ -449,8 +449,7 @@ namespace ts.refactor.extractSymbol {
case SyntaxKind.ThisKeyword:
rangeFacts |= RangeFacts.UsesThis;
break;
case SyntaxKind.LabeledStatement:
{
case SyntaxKind.LabeledStatement: {
const label = (<LabeledStatement>node).label;
(seenLabels || (seenLabels = [])).push(label.escapedText);
forEachChild(node, visit);
@@ -458,8 +457,7 @@ namespace ts.refactor.extractSymbol {
break;
}
case SyntaxKind.BreakStatement:
case SyntaxKind.ContinueStatement:
{
case SyntaxKind.ContinueStatement: {
const label = (<BreakStatement | ContinueStatement>node).label;
if (label) {
if (!contains(seenLabels, label.escapedText)) {
+23 -12
View File
@@ -602,8 +602,8 @@ namespace ts {
return getLineStarts(this);
}
public getPositionOfLineAndCharacter(line: number, character: number): number {
return getPositionOfLineAndCharacter(this, line, character);
public getPositionOfLineAndCharacter(line: number, character: number, allowEdits?: true): number {
return computePositionOfLineAndCharacter(getLineStarts(this), line, character, this.text, allowEdits);
}
public getLineEndOfPosition(pos: number): number {
@@ -1139,7 +1139,16 @@ namespace ts {
const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host);
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
const sourceMapper = getSourceMapper(useCaseSensitiveFileNames, currentDirectory, log, host, () => program);
const sourceMapper = getSourceMapper({
useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
getCurrentDirectory: () => currentDirectory,
getProgram,
fileExists: host.fileExists && (f => host.fileExists!(f)),
readFile: host.readFile && ((f, encoding) => host.readFile!(f, encoding)),
getDocumentPositionMapper: host.getDocumentPositionMapper && ((generatedFileName, sourceFileName) => host.getDocumentPositionMapper!(generatedFileName, sourceFileName)),
getSourceFileLike: host.getSourceFileLike && (f => host.getSourceFileLike!(f)),
log
});
function getValidSourceFile(fileName: string): SourceFile {
const sourceFile = program.getSourceFile(fileName);
@@ -1203,15 +1212,7 @@ namespace ts {
writeFile: noop,
getCurrentDirectory: () => currentDirectory,
fileExists,
readFile(fileName) {
// stub missing host functionality
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
const entry = hostCache && hostCache.getEntryByPath(path);
if (entry) {
return isString(entry) ? undefined : getSnapshotText(entry.scriptSnapshot);
}
return host.readFile && host.readFile(fileName);
},
readFile,
realpath: host.realpath && (path => host.realpath!(path)),
directoryExists: directoryName => {
return directoryProbablyExists(directoryName, host);
@@ -1272,6 +1273,16 @@ namespace ts {
(!!host.fileExists && host.fileExists(fileName));
}
function readFile(fileName: string) {
// stub missing host functionality
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
const entry = hostCache && hostCache.getEntryByPath(path);
if (entry) {
return isString(entry) ? undefined : getSnapshotText(entry.scriptSnapshot);
}
return host.readFile && host.readFile(fileName);
}
// Release any files we have acquired in the old program but are
// not part of the new program.
function onReleaseOldSourceFile(oldSourceFile: SourceFile, oldOptions: CompilerOptions) {
+134 -103
View File
@@ -9,151 +9,182 @@ namespace ts {
clearCache(): void;
}
export function getSourceMapper(
useCaseSensitiveFileNames: boolean,
currentDirectory: string,
log: (message: string) => void,
host: LanguageServiceHost,
getProgram: () => Program,
): SourceMapper {
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
let sourcemappedFileCache: SourceFileLikeCache;
export interface SourceMapperHost {
useCaseSensitiveFileNames(): boolean;
getCurrentDirectory(): string;
getProgram(): Program | undefined;
fileExists?(path: string): boolean;
readFile?(path: string, encoding?: string): string | undefined;
getSourceFileLike?(fileName: string): SourceFileLike | undefined;
getDocumentPositionMapper?(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined;
log(s: string): void;
}
export function getSourceMapper(host: SourceMapperHost): SourceMapper {
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
const currentDirectory = host.getCurrentDirectory();
const sourceFileLike = createMap<SourceFileLike | false>();
const documentPositionMappers = createMap<DocumentPositionMapper>();
return { tryGetSourcePosition, tryGetGeneratedPosition, toLineColumnOffset, clearCache };
function toPath(fileName: string) {
return ts.toPath(fileName, currentDirectory, getCanonicalFileName);
}
function scanForSourcemapURL(fileName: string) {
const mappedFile = sourcemappedFileCache.get(toPath(fileName));
if (!mappedFile) {
return;
}
function getDocumentPositionMapper(generatedFileName: string, sourceFileName?: string) {
const path = toPath(generatedFileName);
const value = documentPositionMappers.get(path);
if (value) return value;
return tryGetSourceMappingURL(mappedFile.text, getLineStarts(mappedFile));
}
function convertDocumentToSourceMapper(file: { sourceMapper?: DocumentPositionMapper }, contents: string, mapFileName: string) {
const map = tryParseRawSourceMap(contents);
if (!map || !map.sources || !map.file || !map.mappings) {
// obviously invalid map
return file.sourceMapper = identitySourceMapConsumer;
let mapper: DocumentPositionMapper | undefined;
if (host.getDocumentPositionMapper) {
mapper = host.getDocumentPositionMapper(generatedFileName, sourceFileName);
}
const program = getProgram();
return file.sourceMapper = createDocumentPositionMapper({
getSourceFileLike: s => {
// Lookup file in program, if provided
const file = program && program.getSourceFileByPath(s);
// file returned here could be .d.ts when asked for .ts file if projectReferences and module resolution created this source file
if (file === undefined || file.resolvedPath !== s) {
// Otherwise check the cache (which may hit disk)
return sourcemappedFileCache.get(s);
}
return file;
},
getCanonicalFileName,
log,
}, map, mapFileName);
}
function getSourceMapper(fileName: string, file: SourceFileLike): DocumentPositionMapper {
if (!host.readFile || !host.fileExists) {
return file.sourceMapper = identitySourceMapConsumer;
else if (host.readFile) {
const file = getSourceFileLike(generatedFileName);
mapper = file && ts.getDocumentPositionMapper(
{ getSourceFileLike, getCanonicalFileName, log: s => host.log(s) },
generatedFileName,
getLineInfo(file.text, getLineStarts(file)),
f => !host.fileExists || host.fileExists(f) ? host.readFile!(f) : undefined
);
}
if (file.sourceMapper) {
return file.sourceMapper;
}
let mapFileName = scanForSourcemapURL(fileName);
if (mapFileName) {
const match = base64UrlRegExp.exec(mapFileName);
if (match) {
if (match[1]) {
const base64Object = match[1];
return convertDocumentToSourceMapper(file, base64decode(sys, base64Object), fileName);
}
// Not a data URL we can parse, skip it
mapFileName = undefined;
}
}
const possibleMapLocations: string[] = [];
if (mapFileName) {
possibleMapLocations.push(mapFileName);
}
possibleMapLocations.push(fileName + ".map");
for (const location of possibleMapLocations) {
const mapPath = ts.toPath(location, getDirectoryPath(fileName), getCanonicalFileName);
if (host.fileExists(mapPath)) {
return convertDocumentToSourceMapper(file, host.readFile(mapPath)!, mapPath); // TODO: GH#18217
}
}
return file.sourceMapper = identitySourceMapConsumer;
documentPositionMappers.set(path, mapper || identitySourceMapConsumer);
return mapper || identitySourceMapConsumer;
}
function tryGetSourcePosition(info: DocumentPosition): DocumentPosition | undefined {
if (!isDeclarationFileName(info.fileName)) return undefined;
const file = getFile(info.fileName);
const file = getSourceFile(info.fileName);
if (!file) return undefined;
const newLoc = getSourceMapper(info.fileName, file).getSourcePosition(info);
return newLoc === info ? undefined : tryGetSourcePosition(newLoc) || newLoc;
const newLoc = getDocumentPositionMapper(info.fileName).getSourcePosition(info);
return !newLoc || newLoc === info ? undefined : tryGetSourcePosition(newLoc) || newLoc;
}
function tryGetGeneratedPosition(info: DocumentPosition): DocumentPosition | undefined {
const program = getProgram();
if (isDeclarationFileName(info.fileName)) return undefined;
const sourceFile = getSourceFile(info.fileName);
if (!sourceFile) return undefined;
const program = host.getProgram()!;
const options = program.getCompilerOptions();
const outPath = options.outFile || options.out;
const declarationPath = outPath ?
removeFileExtension(outPath) + Extension.Dts :
getDeclarationEmitOutputFilePathWorker(info.fileName, program.getCompilerOptions(), currentDirectory, program.getCommonSourceDirectory(), getCanonicalFileName);
if (declarationPath === undefined) return undefined;
const declarationFile = getFile(declarationPath);
if (!declarationFile) return undefined;
const newLoc = getSourceMapper(declarationPath, declarationFile).getGeneratedPosition(info);
const newLoc = getDocumentPositionMapper(declarationPath, info.fileName).getGeneratedPosition(info);
return newLoc === info ? undefined : newLoc;
}
function getFile(fileName: string): SourceFileLike | undefined {
function getSourceFile(fileName: string) {
const program = host.getProgram();
if (!program) return undefined;
const path = toPath(fileName);
const file = getProgram().getSourceFileByPath(path);
if (file && file.resolvedPath === path) {
return file;
// file returned here could be .d.ts when asked for .ts file if projectReferences and module resolution created this source file
const file = program.getSourceFileByPath(path);
return file && file.resolvedPath === path ? file : undefined;
}
function getOrCreateSourceFileLike(fileName: string): SourceFileLike | undefined {
const path = toPath(fileName);
const fileFromCache = sourceFileLike.get(path);
if (fileFromCache !== undefined) return fileFromCache ? fileFromCache : undefined;
if (!host.readFile || host.fileExists && !host.fileExists(path)) {
sourceFileLike.set(path, false);
return undefined;
}
return sourcemappedFileCache.get(path);
// And failing that, check the disk
const text = host.readFile(path);
const file = text ? createSourceFileLike(text) : false;
sourceFileLike.set(path, file);
return file ? file : undefined;
}
// This can be called from source mapper in either source program or program that includes generated file
function getSourceFileLike(fileName: string) {
return !host.getSourceFileLike ?
getSourceFile(fileName) || getOrCreateSourceFileLike(fileName) :
host.getSourceFileLike(fileName);
}
function toLineColumnOffset(fileName: string, position: number): LineAndCharacter {
const file = getFile(fileName)!; // TODO: GH#18217
const file = getSourceFileLike(fileName)!; // TODO: GH#18217
return file.getLineAndCharacterOfPosition(position);
}
function clearCache(): void {
sourcemappedFileCache = createSourceFileLikeCache(host);
sourceFileLike.clear();
documentPositionMappers.clear();
}
}
interface SourceFileLikeCache {
get(path: Path): SourceFileLike | undefined;
/**
* string | undefined to contents of map file to create DocumentPositionMapper from it
* DocumentPositionMapper | false to give back cached DocumentPositionMapper
*/
export type ReadMapFile = (mapFileName: string, mapFileNameFromDts: string | undefined) => string | undefined | DocumentPositionMapper | false;
export function getDocumentPositionMapper(
host: DocumentPositionMapperHost,
generatedFileName: string,
generatedFileLineInfo: LineInfo,
readMapFile: ReadMapFile) {
let mapFileName = tryGetSourceMappingURL(generatedFileLineInfo);
if (mapFileName) {
const match = base64UrlRegExp.exec(mapFileName);
if (match) {
if (match[1]) {
const base64Object = match[1];
return convertDocumentToSourceMapper(host, base64decode(sys, base64Object), generatedFileName);
}
// Not a data URL we can parse, skip it
mapFileName = undefined;
}
}
const possibleMapLocations: string[] = [];
if (mapFileName) {
possibleMapLocations.push(mapFileName);
}
possibleMapLocations.push(generatedFileName + ".map");
const originalMapFileName = mapFileName && getNormalizedAbsolutePath(mapFileName, getDirectoryPath(generatedFileName));
for (const location of possibleMapLocations) {
const mapFileName = getNormalizedAbsolutePath(location, getDirectoryPath(generatedFileName));
const mapFileContents = readMapFile(mapFileName, originalMapFileName);
if (isString(mapFileContents)) {
return convertDocumentToSourceMapper(host, mapFileContents, mapFileName);
}
if (mapFileContents !== undefined) {
return mapFileContents || undefined;
}
}
return undefined;
}
function createSourceFileLikeCache(host: { readFile?: (path: string) => string | undefined, fileExists?: (path: string) => boolean }): SourceFileLikeCache {
const cached = createMap<SourceFileLike>();
function convertDocumentToSourceMapper(host: DocumentPositionMapperHost, contents: string, mapFileName: string) {
const map = tryParseRawSourceMap(contents);
if (!map || !map.sources || !map.file || !map.mappings) {
// obviously invalid map
return undefined;
}
return createDocumentPositionMapper(host, map, mapFileName);
}
function createSourceFileLike(text: string, lineMap?: SourceFileLike["lineMap"]): SourceFileLike {
return {
get(path: Path) {
if (cached.has(path)) {
return cached.get(path);
}
if (!host.fileExists || !host.readFile || !host.fileExists(path)) return;
// And failing that, check the disk
const text = host.readFile(path)!; // TODO: GH#18217
const file = {
text,
lineMap: undefined,
getLineAndCharacterOfPosition(pos: number) {
return computeLineAndCharacterOfPosition(getLineStarts(this), pos);
}
} as SourceFileLike;
cached.set(path, file);
return file;
text,
lineMap,
getLineAndCharacterOfPosition(pos: number) {
return computeLineAndCharacterOfPosition(getLineStarts(this), pos);
}
};
}
+21 -7
View File
@@ -1,5 +1,7 @@
/* @internal */
namespace ts {
const visitedNestedConvertibleFunctions = createMap<true>();
export function computeSuggestionDiagnostics(sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): DiagnosticWithLocation[] {
program.getSemanticDiagnostics(sourceFile, cancellationToken);
const diags: DiagnosticWithLocation[] = [];
@@ -13,6 +15,7 @@ namespace ts {
const isJsFile = isSourceFileJS(sourceFile);
visitedNestedConvertibleFunctions.clear();
check(sourceFile);
if (getAllowSyntheticDefaultImports(program.getCompilerOptions())) {
@@ -114,17 +117,22 @@ namespace ts {
}
function addConvertToAsyncFunctionDiagnostics(node: FunctionLikeDeclaration, checker: TypeChecker, diags: Push<DiagnosticWithLocation>): void {
if (!isAsyncFunction(node) &&
node.body &&
isBlock(node.body) &&
hasReturnStatementWithPromiseHandler(node.body) &&
returnsPromise(node, checker)) {
// need to check function before checking map so that deeper levels of nested callbacks are checked
if (isConvertibleFunction(node, checker) && !visitedNestedConvertibleFunctions.has(getKeyFromNode(node))) {
diags.push(createDiagnosticForNode(
!node.name && isVariableDeclaration(node.parent) && isIdentifier(node.parent.name) ? node.parent.name : node,
Diagnostics.This_may_be_converted_to_an_async_function));
}
}
function isConvertibleFunction(node: FunctionLikeDeclaration, checker: TypeChecker) {
return !isAsyncFunction(node) &&
node.body &&
isBlock(node.body) &&
hasReturnStatementWithPromiseHandler(node.body) &&
returnsPromise(node, checker);
}
function returnsPromise(node: FunctionLikeDeclaration, checker: TypeChecker): boolean {
const functionType = checker.getTypeAtLocation(node);
const callSignatures = checker.getSignaturesOfType(functionType, SignatureKind.Call);
@@ -169,14 +177,20 @@ namespace ts {
// should be kept up to date with getTransformationBody in convertToAsyncFunction.ts
function isFixablePromiseArgument(arg: Expression): boolean {
switch (arg.kind) {
case SyntaxKind.NullKeyword:
case SyntaxKind.Identifier: // identifier includes undefined
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
visitedNestedConvertibleFunctions.set(getKeyFromNode(arg as FunctionLikeDeclaration), true);
/* falls through */
case SyntaxKind.NullKeyword:
case SyntaxKind.Identifier: // identifier includes undefined
return true;
default:
return false;
}
}
function getKeyFromNode(exp: FunctionLikeDeclaration) {
return `${exp.pos.toString()}:${exp.end.toString()}`;
}
}
+5 -1
View File
@@ -91,7 +91,6 @@ namespace ts {
export interface SourceFileLike {
getLineAndCharacterOfPosition(pos: number): LineAndCharacter;
/*@internal*/ sourceMapper?: DocumentPositionMapper;
}
export interface SourceMapSource {
@@ -233,6 +232,11 @@ namespace ts {
installPackage?(options: InstallPackageOptions): Promise<ApplyCodeActionCommandResult>;
/* @internal */ inspectValue?(options: InspectValueOptions): Promise<ValueInfo>;
writeFile?(fileName: string, content: string): void;
/* @internal */
getDocumentPositionMapper?(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined;
/* @internal */
getSourceFileLike?(fileName: string): SourceFileLike | undefined;
}
/* @internal */
+2 -2
View File
@@ -1282,11 +1282,11 @@ namespace ts {
return !!compilerOptions.module || compilerOptions.target! >= ScriptTarget.ES2015 || !!compilerOptions.noEmit;
}
export function hostUsesCaseSensitiveFileNames(host: LanguageServiceHost): boolean {
export function hostUsesCaseSensitiveFileNames(host: { useCaseSensitiveFileNames?(): boolean; }): boolean {
return host.useCaseSensitiveFileNames ? host.useCaseSensitiveFileNames() : false;
}
export function hostGetCanonicalFileName(host: LanguageServiceHost): GetCanonicalFileName {
export function hostGetCanonicalFileName(host: { useCaseSensitiveFileNames?(): boolean; }): GetCanonicalFileName {
return createGetCanonicalFileName(hostUsesCaseSensitiveFileNames(host));
}
+78 -33
View File
@@ -36,64 +36,109 @@
"runner.ts",
"unittests/extractTestHelpers.ts",
"unittests/tsserverProjectSystem.ts",
"unittests/typingsInstaller.ts",
"unittests/services/extract/helpers.ts",
"unittests/tscWatch/helpers.ts",
"unittests/tsserver/helpers.ts",
"unittests/asserts.ts",
"unittests/base64.ts",
"unittests/builder.ts",
"unittests/cancellableLanguageServiceOperations.ts",
"unittests/commandLineParsing.ts",
"unittests/compileOnSave.ts",
"unittests/compilerCore.ts",
"unittests/configurationExtension.ts",
"unittests/convertCompilerOptionsFromJson.ts",
"unittests/convertToAsyncFunction.ts",
"unittests/convertToBase64.ts",
"unittests/convertTypeAcquisitionFromJson.ts",
"unittests/customTransforms.ts",
"unittests/extractConstants.ts",
"unittests/extractFunctions.ts",
"unittests/extractRanges.ts",
"unittests/factory.ts",
"unittests/hostNewLineSupport.ts",
"unittests/incrementalParser.ts",
"unittests/initializeTSConfig.ts",
"unittests/jsDocParsing.ts",
"unittests/languageService.ts",
"unittests/matchFiles.ts",
"unittests/moduleResolution.ts",
"unittests/organizeImports.ts",
"unittests/parsePseudoBigInt.ts",
"unittests/paths.ts",
"unittests/printer.ts",
"unittests/programMissingFiles.ts",
"unittests/programNoParseFalsyFileNames.ts",
"unittests/projectErrors.ts",
"unittests/projectReferences.ts",
"unittests/programApi.ts",
"unittests/publicApi.ts",
"unittests/reuseProgramStructure.ts",
"unittests/session.ts",
"unittests/semver.ts",
"unittests/showConfig.ts",
"unittests/symbolWalker.ts",
"unittests/telemetry.ts",
"unittests/textChanges.ts",
"unittests/textStorage.ts",
"unittests/transform.ts",
"unittests/transpile.ts",
"unittests/tsbuild.ts",
"unittests/tsbuildWatchMode.ts",
"unittests/tsconfigParsing.ts",
"unittests/tscWatchMode.ts",
"unittests/versionCache.ts",
"unittests/config/commandLineParsing.ts",
"unittests/config/configurationExtension.ts",
"unittests/config/convertCompilerOptionsFromJson.ts",
"unittests/config/convertTypeAcquisitionFromJson.ts",
"unittests/config/initializeTSConfig.ts",
"unittests/config/matchFiles.ts",
"unittests/config/projectReferences.ts",
"unittests/config/showConfig.ts",
"unittests/config/tsconfigParsing.ts",
"unittests/evaluation/asyncArrow.ts",
"unittests/evaluation/asyncGenerator.ts",
"unittests/evaluation/forAwaitOf.ts",
"unittests/services/cancellableLanguageServiceOperations.ts",
"unittests/services/colorization.ts",
"unittests/services/convertToAsyncFunction.ts",
"unittests/services/documentRegistry.ts",
"unittests/services/extract/constants.ts",
"unittests/services/extract/functions.ts",
"unittests/services/extract/symbolWalker.ts",
"unittests/services/extract/ranges.ts",
"unittests/services/hostNewLineSupport.ts",
"unittests/services/languageService.ts",
"unittests/services/organizeImports.ts",
"unittests/services/patternMatcher.ts",
"unittests/services/preProcessFile.ts"
"unittests/services/preProcessFile.ts",
"unittests/services/textChanges.ts",
"unittests/services/transpile.ts",
"unittests/tscWatch/consoleClearing.ts",
"unittests/tscWatch/emit.ts",
"unittests/tscWatch/emitAndErrorUpdates.ts",
"unittests/tscWatch/programUpdates.ts",
"unittests/tscWatch/resolutionCache.ts",
"unittests/tscWatch/watchEnvironment.ts",
"unittests/tscWatch/watchApi.ts",
"unittests/tsserver/cachingFileSystemInformation.ts",
"unittests/tsserver/cancellationToken.ts",
"unittests/tsserver/compileOnSave.ts",
"unittests/tsserver/completions.ts",
"unittests/tsserver/configFileSearch.ts",
"unittests/tsserver/configuredProjects.ts",
"unittests/tsserver/declarationFileMaps.ts",
"unittests/tsserver/documentRegistry.ts",
"unittests/tsserver/duplicatePackages.ts",
"unittests/tsserver/events/largeFileReferenced.ts",
"unittests/tsserver/events/projectLanguageServiceState.ts",
"unittests/tsserver/events/projectLoading.ts",
"unittests/tsserver/events/projectUpdatedInBackground.ts",
"unittests/tsserver/events/surveyReady.ts",
"unittests/tsserver/externalProjects.ts",
"unittests/tsserver/forceConsistentCasingInFileNames.ts",
"unittests/tsserver/formatSettings.ts",
"unittests/tsserver/getApplicableRefactors.ts",
"unittests/tsserver/getEditsForFileRename.ts",
"unittests/tsserver/importHelpers.ts",
"unittests/tsserver/inferredProjects.ts",
"unittests/tsserver/languageService.ts",
"unittests/tsserver/maxNodeModuleJsDepth.ts",
"unittests/tsserver/metadataInResponse.ts",
"unittests/tsserver/navTo.ts",
"unittests/tsserver/occurences.ts",
"unittests/tsserver/openFile.ts",
"unittests/tsserver/projectErrors.ts",
"unittests/tsserver/projectReferences.ts",
"unittests/tsserver/projects.ts",
"unittests/tsserver/refactors.ts",
"unittests/tsserver/reload.ts",
"unittests/tsserver/rename.ts",
"unittests/tsserver/resolutionCache.ts",
"unittests/tsserver/session.ts",
"unittests/tsserver/skipLibCheck.ts",
"unittests/tsserver/symLinks.ts",
"unittests/tsserver/syntaxOperations.ts",
"unittests/tsserver/textStorage.ts",
"unittests/tsserver/telemetry.ts",
"unittests/tsserver/typeAquisition.ts",
"unittests/tsserver/typeReferenceDirectives.ts",
"unittests/tsserver/typingsInstaller.ts",
"unittests/tsserver/untitledFiles.ts",
"unittests/tsserver/versionCache.ts",
"unittests/tsserver/watchEnvironment.ts"
]
}
+1 -1
View File
@@ -1,5 +1,5 @@
namespace ts {
describe("assert", () => {
describe("unittests:: assert", () => {
it("deepEqual", () => {
assert.throws(() => assert.deepEqual(createNodeArray([createIdentifier("A")]), createNodeArray([createIdentifier("B")])));
assert.throws(() => assert.deepEqual(createNodeArray([], /*hasTrailingComma*/ true), createNodeArray([], /*hasTrailingComma*/ false)));
+1 -1
View File
@@ -1,5 +1,5 @@
namespace ts {
describe("base64", () => {
describe("unittests:: base64", () => {
describe("base64decode", () => {
it("can decode input strings correctly without needing a host implementation", () => {
const tests = [
+1 -1
View File
@@ -1,5 +1,5 @@
namespace ts {
describe("builder", () => {
describe("unittests:: builder", () => {
it("emits dependent files", () => {
const files: NamedSourceText[] = [
{ name: "/a.ts", text: SourceText.New("", 'import { b } from "./b";', "") },
+1 -1
View File
@@ -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 -1
View File
@@ -1,5 +1,5 @@
namespace ts {
describe("convertToBase64", () => {
describe("unittests:: convertToBase64", () => {
function runTest(input: string): void {
const actual = convertToBase64(input);
const expected = sys.base64encode!(input);
+2 -2
View File
@@ -1,5 +1,5 @@
namespace ts {
describe("customTransforms", () => {
describe("unittests:: customTransforms", () => {
function emitsCorrectly(name: string, sources: { file: string, text: string }[], customTransformers: CustomTransformers, options: CompilerOptions = {}) {
it(name, () => {
const roots = sources.map(source => createSourceFile(source.file, source.text, ScriptTarget.ES2015));
@@ -97,4 +97,4 @@ namespace ts {
experimentalDecorators: true
});
});
}
}
@@ -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 -1
View File
@@ -1,5 +1,5 @@
namespace ts {
describe("FactoryAPI", () => {
describe("unittests:: FactoryAPI", () => {
function assertSyntaxKind(node: Node, expected: SyntaxKind) {
assert.strictEqual(node.kind, expected, `Actual: ${Debug.showSyntaxKind(node)} Expected: ${(ts as any).SyntaxKind[expected]}`);
}
@@ -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 -1
View File
@@ -1,5 +1,5 @@
namespace ts {
describe("JSDocParsing", () => {
describe("unittests:: JSDocParsing", () => {
describe("TypeExpressions", () => {
function parsesCorrectly(name: string, content: string) {
it(name, () => {
+7 -7
View File
@@ -80,7 +80,7 @@ namespace ts {
}
}
describe("Node module resolution - relative paths", () => {
describe("unittests:: moduleResolution:: Node module resolution - relative paths", () => {
function testLoadAsFile(containingFileName: string, moduleFileNameNoExt: string, moduleName: string): void {
for (const ext of supportedTSExtensions) {
@@ -200,7 +200,7 @@ namespace ts {
});
});
describe("Node module resolution - non-relative paths", () => {
describe("unittests:: moduleResolution:: Node module resolution - non-relative paths", () => {
it("computes correct commonPrefix for moduleName cache", () => {
const resolutionCache = createModuleResolutionCache("/", (f) => f);
let cache = resolutionCache.getOrCreateCacheForModuleName("a");
@@ -457,7 +457,7 @@ namespace ts {
});
});
describe("Module resolution - relative imports", () => {
describe("unittests:: moduleResolution:: Relative imports", () => {
function test(files: Map<string>, currentDirectory: string, rootFiles: string[], expectedFilesCount: number, relativeNamesToCheck: string[]) {
const options: CompilerOptions = { module: ModuleKind.CommonJS };
const host: CompilerHost = {
@@ -530,7 +530,7 @@ export = C;
});
});
describe("Files with different casing", () => {
describe("unittests:: moduleResolution:: Files with different casing", () => {
let library: SourceFile;
function test(files: Map<string>, options: CompilerOptions, currentDirectory: string, useCaseSensitiveFileNames: boolean, rootFiles: string[], diagnosticCodes: number[]): void {
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
@@ -651,7 +651,7 @@ import b = require("./moduleB");
});
});
describe("baseUrl augmented module resolution", () => {
describe("unittests:: moduleResolution:: baseUrl augmented module resolution", () => {
it("module resolution without path mappings/rootDirs", () => {
test(/*hasDirectoryExists*/ false);
@@ -1098,7 +1098,7 @@ import b = require("./moduleB");
});
});
describe("ModuleResolutionHost.directoryExists", () => {
describe("unittests:: moduleResolution:: ModuleResolutionHost.directoryExists", () => {
it("No 'fileExists' calls if containing directory is missing", () => {
const host: ModuleResolutionHost = {
readFile: notImplemented,
@@ -1111,7 +1111,7 @@ import b = require("./moduleB");
});
});
describe("Type reference directive resolution: ", () => {
describe("unittests:: moduleResolution:: Type reference directive resolution: ", () => {
function testWorker(hasDirectoryExists: boolean, typesRoot: string | undefined, typeDirective: string, primary: boolean, initialFile: File, targetFile: File, ...otherFiles: File[]) {
const host = createModuleResolutionHost(hasDirectoryExists, ...[initialFile, targetFile].concat(...otherFiles));
const result = resolveTypeReferenceDirective(typeDirective, initialFile.name, typesRoot ? { typeRoots: [typesRoot] } : {}, host);
@@ -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 {
});
});
});
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
describe("core paths", () => {
describe("unittests:: core paths", () => {
it("normalizeSlashes", () => {
assert.strictEqual(ts.normalizeSlashes("a"), "a");
assert.strictEqual(ts.normalizeSlashes("a/b"), "a/b");
@@ -289,4 +289,4 @@ describe("core paths", () => {
assert.strictEqual(ts.getRelativePathFromDirectory("file:///a/b/c", "file:///a/b", /*ignoreCase*/ false), "..");
assert.strictEqual(ts.getRelativePathFromDirectory("file:///c:", "file:///d:", /*ignoreCase*/ false), "file:///d:/");
});
});
});
+1 -1
View File
@@ -1,5 +1,5 @@
namespace ts {
describe("PrinterAPI", () => {
describe("unittests:: PrinterAPI", () => {
function makePrintsCorrectly(prefix: string) {
return function printsCorrectly(name: string, options: PrinterOptions, printCallback: (printer: Printer) => string) {
it(name, () => {
@@ -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");
});
});
}
-202
View File
@@ -1,202 +0,0 @@
namespace ts.projectSystem {
describe("Project errors", () => {
function checkProjectErrors(projectFiles: server.ProjectFilesWithTSDiagnostics, expectedErrors: ReadonlyArray<string>): void {
assert.isTrue(projectFiles !== undefined, "missing project files");
checkProjectErrorsWorker(projectFiles.projectErrors, expectedErrors);
}
function checkProjectErrorsWorker(errors: ReadonlyArray<Diagnostic>, expectedErrors: ReadonlyArray<string>): void {
assert.equal(errors ? errors.length : 0, expectedErrors.length, `expected ${expectedErrors.length} error in the list`);
if (expectedErrors.length) {
for (let i = 0; i < errors.length; i++) {
const actualMessage = flattenDiagnosticMessageText(errors[i].messageText, "\n");
const expectedMessage = expectedErrors[i];
assert.isTrue(actualMessage.indexOf(expectedMessage) === 0, `error message does not match, expected ${actualMessage} to start with ${expectedMessage}`);
}
}
}
function checkDiagnosticsWithLinePos(errors: server.protocol.DiagnosticWithLinePosition[], expectedErrors: string[]) {
assert.equal(errors ? errors.length : 0, expectedErrors.length, `expected ${expectedErrors.length} error in the list`);
if (expectedErrors.length) {
zipWith(errors, expectedErrors, ({ message: actualMessage }, expectedMessage) => {
assert.isTrue(startsWith(actualMessage, actualMessage), `error message does not match, expected ${actualMessage} to start with ${expectedMessage}`);
});
}
}
it("external project - diagnostics for missing files", () => {
const file1 = {
path: "/a/b/app.ts",
content: ""
};
const file2 = {
path: "/a/b/applib.ts",
content: ""
};
const host = createServerHost([file1, libFile]);
const session = createSession(host);
const projectService = session.getProjectService();
const projectFileName = "/a/b/test.csproj";
const compilerOptionsRequest: server.protocol.CompilerOptionsDiagnosticsRequest = {
type: "request",
command: server.CommandNames.CompilerOptionsDiagnosticsFull,
seq: 2,
arguments: { projectFileName }
};
{
projectService.openExternalProject({
projectFileName,
options: {},
rootFiles: toExternalFiles([file1.path, file2.path])
});
checkNumberOfProjects(projectService, { externalProjects: 1 });
const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
// only file1 exists - expect error
checkDiagnosticsWithLinePos(diags, ["File '/a/b/applib.ts' not found."]);
}
host.reloadFS([file2, libFile]);
{
// only file2 exists - expect error
checkNumberOfProjects(projectService, { externalProjects: 1 });
const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
checkDiagnosticsWithLinePos(diags, ["File '/a/b/app.ts' not found."]);
}
host.reloadFS([file1, file2, libFile]);
{
// both files exist - expect no errors
checkNumberOfProjects(projectService, { externalProjects: 1 });
const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
checkDiagnosticsWithLinePos(diags, []);
}
});
it("configured projects - diagnostics for missing files", () => {
const file1 = {
path: "/a/b/app.ts",
content: ""
};
const file2 = {
path: "/a/b/applib.ts",
content: ""
};
const config = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) })
};
const host = createServerHost([file1, config, libFile]);
const session = createSession(host);
const projectService = session.getProjectService();
openFilesForSession([file1], session);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const project = configuredProjectAt(projectService, 0);
const compilerOptionsRequest: server.protocol.CompilerOptionsDiagnosticsRequest = {
type: "request",
command: server.CommandNames.CompilerOptionsDiagnosticsFull,
seq: 2,
arguments: { projectFileName: project.getProjectName() }
};
let diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
checkDiagnosticsWithLinePos(diags, ["File '/a/b/applib.ts' not found."]);
host.reloadFS([file1, file2, config, libFile]);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
checkDiagnosticsWithLinePos(diags, []);
});
it("configured projects - diagnostics for corrupted config 1", () => {
const file1 = {
path: "/a/b/app.ts",
content: ""
};
const file2 = {
path: "/a/b/lib.ts",
content: ""
};
const correctConfig = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) })
};
const corruptedConfig = {
path: correctConfig.path,
content: correctConfig.content.substr(1)
};
const host = createServerHost([file1, file2, corruptedConfig]);
const projectService = createProjectService(host);
projectService.openClientFile(file1.path);
{
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject = find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!;
assert.isTrue(configuredProject !== undefined, "should find configured project");
checkProjectErrors(configuredProject, []);
const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors();
checkProjectErrorsWorker(projectErrors, [
"'{' expected."
]);
assert.isNotNull(projectErrors[0].file);
assert.equal(projectErrors[0].file!.fileName, corruptedConfig.path);
}
// fix config and trigger watcher
host.reloadFS([file1, file2, correctConfig]);
{
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject = find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!;
assert.isTrue(configuredProject !== undefined, "should find configured project");
checkProjectErrors(configuredProject, []);
const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors();
checkProjectErrorsWorker(projectErrors, []);
}
});
it("configured projects - diagnostics for corrupted config 2", () => {
const file1 = {
path: "/a/b/app.ts",
content: ""
};
const file2 = {
path: "/a/b/lib.ts",
content: ""
};
const correctConfig = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) })
};
const corruptedConfig = {
path: correctConfig.path,
content: correctConfig.content.substr(1)
};
const host = createServerHost([file1, file2, correctConfig]);
const projectService = createProjectService(host);
projectService.openClientFile(file1.path);
{
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject = find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!;
assert.isTrue(configuredProject !== undefined, "should find configured project");
checkProjectErrors(configuredProject, []);
const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors();
checkProjectErrorsWorker(projectErrors, []);
}
// break config and trigger watcher
host.reloadFS([file1, file2, corruptedConfig]);
{
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject = find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!;
assert.isTrue(configuredProject !== undefined, "should find configured project");
checkProjectErrors(configuredProject, []);
const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors();
checkProjectErrorsWorker(projectErrors, [
"'{' expected."
]);
assert.isNotNull(projectErrors[0].file);
assert.equal(projectErrors[0].file!.fileName, corruptedConfig.path);
}
});
});
}
@@ -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[],
+2 -2
View File
@@ -1,6 +1,6 @@
namespace ts {
import theory = utils.theory;
describe("semver", () => {
describe("unittests:: semver", () => {
describe("Version", () => {
function assertVersion(version: Version, [major, minor, patch, prerelease, build]: [number, number, number, string[]?, string[]?]) {
assert.strictEqual(version.major, major);
@@ -225,4 +225,4 @@ namespace ts {
]);
});
});
}
}
@@ -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",
@@ -586,6 +596,34 @@ import { React, Other } from "react";
},
reactLibFile);
testOrganizeImports("JsxPragmaTsx",
{
path: "/test.tsx",
content: `/** @jsx jsx */
import { Global, jsx } from '@emotion/core';
import * as React from 'react';
export const App: React.FunctionComponent = _ => <Global><h1>Hello!</h1></Global>
`,
},
{
path: "/@emotion/core/index.d.ts",
content: `import { createElement } from 'react'
export const jsx: typeof createElement;
export function Global(props: any): ReactElement<any>;`
},
{
path: reactLibFile.path,
content: `${reactLibFile.content}
export namespace React {
interface FunctionComponent {
}
}
`
}
);
describe("Exports", () => {
testOrganizeExports("MoveToTop",
@@ -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 -1
View File
@@ -1,5 +1,5 @@
namespace ts {
describe("TransformAPI", () => {
describe("unittests:: TransformAPI", () => {
function replaceUndefinedWithVoid0(context: TransformationContext) {
const previousOnSubstituteNode = context.onSubstituteNode;
context.enableSubstitution(SyntaxKind.Identifier);
+15 -15
View File
@@ -8,7 +8,7 @@ namespace ts {
"/src/core/index.js", "/src/core/index.d.ts", "/src/core/index.d.ts.map",
"/src/logic/index.js", "/src/logic/index.js.map", "/src/logic/index.d.ts"];
describe("tsbuild - sanity check of clean build of 'sample1' project", () => {
describe("unittests:: tsbuild - sanity check of clean build of 'sample1' project", () => {
it("can build the sample project 'sample1' without error", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
@@ -61,7 +61,7 @@ namespace ts {
});
});
describe("tsbuild - dry builds", () => {
describe("unittests:: tsbuild - dry builds", () => {
it("doesn't write any files in a dry build", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
@@ -90,7 +90,7 @@ namespace ts {
});
});
describe("tsbuild - clean builds", () => {
describe("unittests:: tsbuild - clean builds", () => {
it("removes all files it built", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
@@ -111,7 +111,7 @@ namespace ts {
});
});
describe("tsbuild - force builds", () => {
describe("unittests:: tsbuild - force builds", () => {
it("always builds under --force", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
@@ -137,7 +137,7 @@ namespace ts {
});
});
describe("tsbuild - can detect when and what to rebuild", () => {
describe("unittests:: tsbuild - can detect when and what to rebuild", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: true });
@@ -200,7 +200,7 @@ namespace ts {
});
});
describe("tsbuild - downstream-blocked compilations", () => {
describe("unittests:: tsbuild - downstream-blocked compilations", () => {
it("won't build downstream projects if upstream projects have errors", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
@@ -222,7 +222,7 @@ namespace ts {
});
});
describe("tsbuild - project invalidation", () => {
describe("unittests:: tsbuild - project invalidation", () => {
it("invalidates projects correctly", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
@@ -270,7 +270,7 @@ export class cNew {}`);
});
});
describe("tsbuild - with resolveJsonModule option", () => {
describe("unittests:: tsbuild - with resolveJsonModule option", () => {
const projFs = loadProjectFromDisk("tests/projects/resolveJsonModuleAndComposite");
const allExpectedOutputs = ["/src/tests/dist/src/index.js", "/src/tests/dist/src/index.d.ts", "/src/tests/dist/src/hello.json"];
@@ -320,7 +320,7 @@ export default hello.hello`);
});
});
describe("tsbuild - lists files", () => {
describe("unittests:: tsbuild - lists files", () => {
it("listFiles", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
@@ -369,7 +369,7 @@ export default hello.hello`);
});
});
describe("tsbuild - with rootDir of project reference in parentDirectory", () => {
describe("unittests:: tsbuild - with rootDir of project reference in parentDirectory", () => {
const projFs = loadProjectFromDisk("tests/projects/projectReferenceWithRootDirInParent");
const allExpectedOutputs = [
"/src/dist/other/other.js", "/src/dist/other/other.d.ts",
@@ -388,7 +388,7 @@ export default hello.hello`);
});
});
describe("tsbuild - when project reference is referenced transitively", () => {
describe("unittests:: tsbuild - when project reference is referenced transitively", () => {
const projFs = loadProjectFromDisk("tests/projects/transitiveReferences");
const allExpectedOutputs = [
"/src/a.js", "/src/a.d.ts",
@@ -460,7 +460,7 @@ export const b = new A();`);
export namespace OutFile {
const outFileFs = loadProjectFromDisk("tests/projects/outfile-concat");
describe("tsbuild - baseline sectioned sourcemaps", () => {
describe("unittests:: tsbuild - baseline sectioned sourcemaps", () => {
let fs: vfs.FileSystem | undefined;
before(() => {
fs = outFileFs.shadow();
@@ -480,7 +480,7 @@ export const b = new A();`);
});
});
describe("tsbuild - downstream prepend projects always get rebuilt", () => {
describe("unittests:: tsbuild - downstream prepend projects always get rebuilt", () => {
it("", () => {
const fs = outFileFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
@@ -508,7 +508,7 @@ export const b = new A();`);
"/src/core/index.d.ts.map",
];
describe("tsbuild - empty files option in tsconfig", () => {
describe("unittests:: tsbuild - empty files option in tsconfig", () => {
it("has empty files diagnostic when files is empty and no references are provided", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
@@ -541,7 +541,7 @@ export const b = new A();`);
});
}
describe("tsbuild - graph-ordering", () => {
describe("unittests:: tsbuild - graph-ordering", () => {
let host: fakes.SolutionBuilderHost | undefined;
const deps: [string, string][] = [
["A", "B"],
+5 -8
View File
@@ -1,5 +1,4 @@
namespace ts.tscWatch {
export import libFile = TestFSWithWatch.libFile;
import projectsLocation = TestFSWithWatch.tsbuildProjectsLocation;
import getFilePathInProject = TestFSWithWatch.getTsBuildProjectFilePath;
import getFileFromProject = TestFSWithWatch.getTsBuildProjectFile;
@@ -15,7 +14,7 @@ namespace ts.tscWatch {
return solutionBuilder;
}
describe("tsbuild-watch program updates", () => {
describe("unittests:: tsbuild-watch program updates", () => {
const project = "sample1";
const enum SubProject {
core = "core",
@@ -480,7 +479,7 @@ let x: string = 10;`);
const { host, solutionBuilder } = createSolutionOfProject(allFiles, currentDirectory, solutionBuilderconfig, getOutputFileStamps);
// Build in watch mode
const watch = createWatchOfConfigFileReturningBuilder(watchConfig, host);
const watch = createWatchOfConfigFile(watchConfig, host);
checkOutputErrorsInitial(host, emptyArray);
return { host, solutionBuilder, watch };
@@ -506,10 +505,8 @@ let x: string = 10;`);
projectSystem.checkProjectActualFiles(service.configuredProjects.get(configFile.toLowerCase())!, expectedFiles);
}
type Watch = () => BuilderProgram;
function verifyDependencies(watch: Watch, filePath: string, expected: ReadonlyArray<string>) {
checkArray(`${filePath} dependencies`, watch().getAllDependencies(watch().getSourceFile(filePath)!), expected);
checkArray(`${filePath} dependencies`, watch.getBuilderProgram().getAllDependencies(watch().getSourceFile(filePath)!), expected);
}
describe("on sample project", () => {
@@ -543,7 +540,7 @@ let x: string = 10;`);
host.checkTimeoutQueueLengthAndRun(1);
checkOutputErrorsIncremental(host, emptyArray);
checkProgramActualFiles(watch().getProgram(), expectedFilesAfterEdit);
checkProgramActualFiles(watch(), expectedFilesAfterEdit);
});
@@ -643,7 +640,7 @@ export function gfoo() {
expectedWatchedDirectoriesRecursive: ReadonlyArray<string>,
dependencies: ReadonlyArray<[string, ReadonlyArray<string>]>,
expectedWatchedDirectories?: ReadonlyArray<string>) {
checkProgramActualFiles(watch().getProgram(), expectedProgramFiles);
checkProgramActualFiles(watch(), expectedProgramFiles);
verifyWatchesOfProject(host, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, expectedWatchedDirectories);
for (const [file, deps] of dependencies) {
verifyDependencies(watch, file, deps);
@@ -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
View File
@@ -0,0 +1,718 @@
namespace ts.tscWatch {
function getEmittedLineForMultiFileOutput(file: File, host: WatchedSystem) {
return `TSFILE: ${file.path.replace(".ts", ".js")}${host.newLine}`;
}
function getEmittedLineForSingleFileOutput(filename: string, host: WatchedSystem) {
return `TSFILE: ${filename}${host.newLine}`;
}
interface FileOrFolderEmit extends File {
output?: string;
}
function getFileOrFolderEmit(file: File, getOutput?: (file: File) => string): FileOrFolderEmit {
const result = file as FileOrFolderEmit;
if (getOutput) {
result.output = getOutput(file);
}
return result;
}
function getEmittedLines(files: FileOrFolderEmit[]) {
const seen = createMap<true>();
const result: string[] = [];
for (const { output } of files) {
if (output && !seen.has(output)) {
seen.set(output, true);
result.push(output);
}
}
return result;
}
function checkAffectedLines(host: WatchedSystem, affectedFiles: FileOrFolderEmit[], allEmittedFiles: string[]) {
const expectedAffectedFiles = getEmittedLines(affectedFiles);
const expectedNonAffectedFiles = mapDefined(allEmittedFiles, line => contains(expectedAffectedFiles, line) ? undefined : line);
checkOutputContains(host, expectedAffectedFiles);
checkOutputDoesNotContain(host, expectedNonAffectedFiles);
}
describe("unittests:: tsc-watch:: emit with outFile or out setting", () => {
function createWatchForOut(out?: string, outFile?: string) {
const host = createWatchedSystem([]);
const config: FileOrFolderEmit = {
path: "/a/tsconfig.json",
content: JSON.stringify({
compilerOptions: { listEmittedFiles: true }
})
};
let getOutput: (file: File) => string;
if (out) {
config.content = JSON.stringify({
compilerOptions: { listEmittedFiles: true, out }
});
getOutput = __ => getEmittedLineForSingleFileOutput(out, host);
}
else if (outFile) {
config.content = JSON.stringify({
compilerOptions: { listEmittedFiles: true, outFile }
});
getOutput = __ => getEmittedLineForSingleFileOutput(outFile, host);
}
else {
getOutput = file => getEmittedLineForMultiFileOutput(file, host);
}
const f1 = getFileOrFolderEmit({
path: "/a/a.ts",
content: "let x = 1"
}, getOutput);
const f2 = getFileOrFolderEmit({
path: "/a/b.ts",
content: "let y = 1"
}, getOutput);
const files = [f1, f2, config, libFile];
host.reloadFS(files);
createWatchOfConfigFile(config.path, host);
const allEmittedLines = getEmittedLines(files);
checkOutputContains(host, allEmittedLines);
host.clearOutput();
f1.content = "let x = 11";
host.reloadFS(files);
host.runQueuedTimeoutCallbacks();
checkAffectedLines(host, [f1], allEmittedLines);
}
it("projectUsesOutFile should not be returned if not set", () => {
createWatchForOut();
});
it("projectUsesOutFile should be true if out is set", () => {
const outJs = "/a/out.js";
createWatchForOut(outJs);
});
it("projectUsesOutFile should be true if outFile is set", () => {
const outJs = "/a/out.js";
createWatchForOut(/*out*/ undefined, outJs);
});
function verifyFilesEmittedOnce(useOutFile: boolean) {
const file1: File = {
path: "/a/b/output/AnotherDependency/file1.d.ts",
content: "declare namespace Common.SomeComponent.DynamicMenu { enum Z { Full = 0, Min = 1, Average = 2, } }"
};
const file2: File = {
path: "/a/b/dependencies/file2.d.ts",
content: "declare namespace Dependencies.SomeComponent { export class SomeClass { version: string; } }"
};
const file3: File = {
path: "/a/b/project/src/main.ts",
content: "namespace Main { export function fooBar() {} }"
};
const file4: File = {
path: "/a/b/project/src/main2.ts",
content: "namespace main.file4 { import DynamicMenu = Common.SomeComponent.DynamicMenu; export function foo(a: DynamicMenu.z) { } }"
};
const configFile: File = {
path: "/a/b/project/tsconfig.json",
content: JSON.stringify({
compilerOptions: useOutFile ?
{ outFile: "../output/common.js", target: "es5" } :
{ outDir: "../output", target: "es5" },
files: [file1.path, file2.path, file3.path, file4.path]
})
};
const files = [file1, file2, file3, file4];
const allfiles = files.concat(configFile);
const host = createWatchedSystem(allfiles);
const originalWriteFile = host.writeFile.bind(host);
const mapOfFilesWritten = createMap<number>();
host.writeFile = (p: string, content: string) => {
const count = mapOfFilesWritten.get(p);
mapOfFilesWritten.set(p, count ? count + 1 : 1);
return originalWriteFile(p, content);
};
createWatchOfConfigFile(configFile.path, host);
if (useOutFile) {
// Only out file
assert.equal(mapOfFilesWritten.size, 1);
}
else {
// main.js and main2.js
assert.equal(mapOfFilesWritten.size, 2);
}
mapOfFilesWritten.forEach((value, key) => {
assert.equal(value, 1, "Key: " + key);
});
}
it("with --outFile and multiple declaration files in the program", () => {
verifyFilesEmittedOnce(/*useOutFile*/ true);
});
it("without --outFile and multiple declaration files in the program", () => {
verifyFilesEmittedOnce(/*useOutFile*/ false);
});
});
describe("unittests:: tsc-watch:: emit for configured projects", () => {
const file1Consumer1Path = "/a/b/file1Consumer1.ts";
const moduleFile1Path = "/a/b/moduleFile1.ts";
const configFilePath = "/a/b/tsconfig.json";
interface InitialStateParams {
/** custom config file options */
configObj?: any;
/** list of the files that will be emitted for first compilation */
firstCompilationEmitFiles?: string[];
/** get the emit file for file - default is multi file emit line */
getEmitLine?(file: File, host: WatchedSystem): string;
/** Additional files and folders to add */
getAdditionalFileOrFolder?(): File[];
/** initial list of files to emit if not the default list */
firstReloadFileList?: string[];
}
function getInitialState({ configObj = {}, firstCompilationEmitFiles, getEmitLine, getAdditionalFileOrFolder, firstReloadFileList }: InitialStateParams = {}) {
const host = createWatchedSystem([]);
const getOutputName = getEmitLine ? (file: File) => getEmitLine(file, host) :
(file: File) => getEmittedLineForMultiFileOutput(file, host);
const moduleFile1 = getFileOrFolderEmit({
path: moduleFile1Path,
content: "export function Foo() { };",
}, getOutputName);
const file1Consumer1 = getFileOrFolderEmit({
path: file1Consumer1Path,
content: `import {Foo} from "./moduleFile1"; export var y = 10;`,
}, getOutputName);
const file1Consumer2 = getFileOrFolderEmit({
path: "/a/b/file1Consumer2.ts",
content: `import {Foo} from "./moduleFile1"; let z = 10;`,
}, getOutputName);
const moduleFile2 = getFileOrFolderEmit({
path: "/a/b/moduleFile2.ts",
content: `export var Foo4 = 10;`,
}, getOutputName);
const globalFile3 = getFileOrFolderEmit({
path: "/a/b/globalFile3.ts",
content: `interface GlobalFoo { age: number }`
});
const additionalFiles = getAdditionalFileOrFolder ?
map(getAdditionalFileOrFolder(), file => getFileOrFolderEmit(file, getOutputName)) :
[];
(configObj.compilerOptions || (configObj.compilerOptions = {})).listEmittedFiles = true;
const configFile = getFileOrFolderEmit({
path: configFilePath,
content: JSON.stringify(configObj)
});
const files = [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile, ...additionalFiles];
let allEmittedFiles = getEmittedLines(files);
host.reloadFS(firstReloadFileList ? getFiles(firstReloadFileList) : files);
// Initial compile
createWatchOfConfigFile(configFile.path, host);
if (firstCompilationEmitFiles) {
checkAffectedLines(host, getFiles(firstCompilationEmitFiles), allEmittedFiles);
}
else {
checkOutputContains(host, allEmittedFiles);
}
host.clearOutput();
return {
moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile,
files,
getFile,
verifyAffectedFiles,
verifyAffectedAllFiles,
getOutputName
};
function getFiles(filelist: string[]) {
return map(filelist, getFile);
}
function getFile(fileName: string) {
return find(files, file => file.path === fileName)!;
}
function verifyAffectedAllFiles() {
host.reloadFS(files);
host.checkTimeoutQueueLengthAndRun(1);
checkOutputContains(host, allEmittedFiles);
host.clearOutput();
}
function verifyAffectedFiles(expected: FileOrFolderEmit[], filesToReload?: FileOrFolderEmit[]) {
if (!filesToReload) {
filesToReload = files;
}
else if (filesToReload.length > files.length) {
allEmittedFiles = getEmittedLines(filesToReload);
}
host.reloadFS(filesToReload);
host.checkTimeoutQueueLengthAndRun(1);
checkAffectedLines(host, expected, allEmittedFiles);
host.clearOutput();
}
}
it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => {
const {
moduleFile1, file1Consumer1, file1Consumer2,
verifyAffectedFiles
} = getInitialState();
// Change the content of moduleFile1 to `export var T: number;export function Foo() { };`
moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2]);
// Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };`
moduleFile1.content = `export var T: number;export function Foo() { console.log('hi'); };`;
verifyAffectedFiles([moduleFile1]);
});
it("should be up-to-date with the reference map changes", () => {
const {
moduleFile1, file1Consumer1, file1Consumer2,
verifyAffectedFiles
} = getInitialState();
// Change file1Consumer1 content to `export let y = Foo();`
file1Consumer1.content = `export let y = Foo();`;
verifyAffectedFiles([file1Consumer1]);
// Change the content of moduleFile1 to `export var T: number;export function Foo() { };`
moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyAffectedFiles([moduleFile1, file1Consumer2]);
// Add the import statements back to file1Consumer1
file1Consumer1.content = `import {Foo} from "./moduleFile1";let y = Foo();`;
verifyAffectedFiles([file1Consumer1]);
// Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };`
moduleFile1.content = `export var T: number;export var T2: string;export function Foo() { };`;
verifyAffectedFiles([moduleFile1, file1Consumer2, file1Consumer1]);
// Multiple file edits in one go:
// Change file1Consumer1 content to `export let y = Foo();`
// Change the content of moduleFile1 to `export var T: number;export function Foo() { };`
file1Consumer1.content = `export let y = Foo();`;
moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2]);
});
it("should be up-to-date with deleted files", () => {
const {
moduleFile1, file1Consumer1, file1Consumer2,
files,
verifyAffectedFiles
} = getInitialState();
// Change the content of moduleFile1 to `export var T: number;export function Foo() { };`
moduleFile1.content = `export var T: number;export function Foo() { };`;
// Delete file1Consumer2
const filesToLoad = mapDefined(files, file => file === file1Consumer2 ? undefined : file);
verifyAffectedFiles([moduleFile1, file1Consumer1], filesToLoad);
});
it("should be up-to-date with newly created files", () => {
const {
moduleFile1, file1Consumer1, file1Consumer2,
files,
verifyAffectedFiles,
getOutputName
} = getInitialState();
const file1Consumer3 = getFileOrFolderEmit({
path: "/a/b/file1Consumer3.ts",
content: `import {Foo} from "./moduleFile1"; let y = Foo();`
}, getOutputName);
moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer3, file1Consumer2], files.concat(file1Consumer3));
});
it("should detect changes in non-root files", () => {
const {
moduleFile1, file1Consumer1,
verifyAffectedFiles
} = getInitialState({ configObj: { files: [file1Consumer1Path] }, firstCompilationEmitFiles: [file1Consumer1Path, moduleFile1Path] });
moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyAffectedFiles([moduleFile1, file1Consumer1]);
// change file1 internal, and verify only file1 is affected
moduleFile1.content += "var T1: number;";
verifyAffectedFiles([moduleFile1]);
});
it("should return all files if a global file changed shape", () => {
const {
globalFile3, verifyAffectedAllFiles
} = getInitialState();
globalFile3.content += "var T2: string;";
verifyAffectedAllFiles();
});
it("should always return the file itself if '--isolatedModules' is specified", () => {
const {
moduleFile1, verifyAffectedFiles
} = getInitialState({ configObj: { compilerOptions: { isolatedModules: true } } });
moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyAffectedFiles([moduleFile1]);
});
it("should always return the file itself if '--out' or '--outFile' is specified", () => {
const outFilePath = "/a/b/out.js";
const {
moduleFile1, verifyAffectedFiles
} = getInitialState({
configObj: { compilerOptions: { module: "system", outFile: outFilePath } },
getEmitLine: (_, host) => getEmittedLineForSingleFileOutput(outFilePath, host)
});
moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyAffectedFiles([moduleFile1]);
});
it("should return cascaded affected file list", () => {
const file1Consumer1Consumer1: File = {
path: "/a/b/file1Consumer1Consumer1.ts",
content: `import {y} from "./file1Consumer1";`
};
const {
moduleFile1, file1Consumer1, file1Consumer2, verifyAffectedFiles, getFile
} = getInitialState({
getAdditionalFileOrFolder: () => [file1Consumer1Consumer1]
});
const file1Consumer1Consumer1Emit = getFile(file1Consumer1Consumer1.path);
file1Consumer1.content += "export var T: number;";
verifyAffectedFiles([file1Consumer1, file1Consumer1Consumer1Emit]);
// Doesnt change the shape of file1Consumer1
moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2]);
// Change both files before the timeout
file1Consumer1.content += "export var T2: number;";
moduleFile1.content = `export var T2: number;export function Foo() { };`;
verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2, file1Consumer1Consumer1Emit]);
});
it("should work fine for files with circular references", () => {
// TODO: do not exit on such errors? Just continue to watch the files for update in watch mode
const file1: File = {
path: "/a/b/file1.ts",
content: `
/// <reference path="./file2.ts" />
export var t1 = 10;`
};
const file2: File = {
path: "/a/b/file2.ts",
content: `
/// <reference path="./file1.ts" />
export var t2 = 10;`
};
const {
configFile,
getFile,
verifyAffectedFiles
} = getInitialState({
firstCompilationEmitFiles: [file1.path, file2.path],
getAdditionalFileOrFolder: () => [file1, file2],
firstReloadFileList: [libFile.path, file1.path, file2.path, configFilePath]
});
const file1Emit = getFile(file1.path), file2Emit = getFile(file2.path);
file1Emit.content += "export var t3 = 10;";
verifyAffectedFiles([file1Emit, file2Emit], [file1, file2, libFile, configFile]);
});
it("should detect removed code file", () => {
const referenceFile1: File = {
path: "/a/b/referenceFile1.ts",
content: `
/// <reference path="./moduleFile1.ts" />
export var x = Foo();`
};
const {
configFile,
getFile,
verifyAffectedFiles
} = getInitialState({
firstCompilationEmitFiles: [referenceFile1.path, moduleFile1Path],
getAdditionalFileOrFolder: () => [referenceFile1],
firstReloadFileList: [libFile.path, referenceFile1.path, moduleFile1Path, configFilePath]
});
const referenceFile1Emit = getFile(referenceFile1.path);
verifyAffectedFiles([referenceFile1Emit], [libFile, referenceFile1Emit, configFile]);
});
it("should detect non-existing code file", () => {
const referenceFile1: File = {
path: "/a/b/referenceFile1.ts",
content: `
/// <reference path="./moduleFile2.ts" />
export var x = Foo();`
};
const {
configFile,
moduleFile2,
getFile,
verifyAffectedFiles
} = getInitialState({
firstCompilationEmitFiles: [referenceFile1.path],
getAdditionalFileOrFolder: () => [referenceFile1],
firstReloadFileList: [libFile.path, referenceFile1.path, configFilePath]
});
const referenceFile1Emit = getFile(referenceFile1.path);
referenceFile1Emit.content += "export var yy = Foo();";
verifyAffectedFiles([referenceFile1Emit], [libFile, referenceFile1Emit, configFile]);
// Create module File2 and see both files are saved
verifyAffectedFiles([referenceFile1Emit, moduleFile2], [libFile, moduleFile2, referenceFile1Emit, configFile]);
});
});
describe("unittests:: tsc-watch:: emit file content", () => {
interface EmittedFile extends File {
shouldBeWritten: boolean;
}
function getEmittedFiles(files: FileOrFolderEmit[], contents: string[]): EmittedFile[] {
return map(contents, (content, index) => {
return {
content,
path: changeExtension(files[index].path, Extension.Js),
shouldBeWritten: true
};
}
);
}
function verifyEmittedFiles(host: WatchedSystem, emittedFiles: EmittedFile[]) {
for (const { path, content, shouldBeWritten } of emittedFiles) {
if (shouldBeWritten) {
assert.isTrue(host.fileExists(path), `Expected file ${path} to be present`);
assert.equal(host.readFile(path), content, `Contents of file ${path} do not match`);
}
else {
assert.isNotTrue(host.fileExists(path), `Expected file ${path} to be absent`);
}
}
}
function verifyEmittedFileContents(newLine: string, inputFiles: File[], initialEmittedFileContents: string[],
modifyFiles: (files: FileOrFolderEmit[], emitedFiles: EmittedFile[]) => FileOrFolderEmit[], configFile?: File) {
const host = createWatchedSystem([], { newLine });
const files = concatenate(
map(inputFiles, file => getFileOrFolderEmit(file, fileToConvert => getEmittedLineForMultiFileOutput(fileToConvert, host))),
configFile ? [libFile, configFile] : [libFile]
);
const allEmittedFiles = getEmittedLines(files);
host.reloadFS(files);
// Initial compile
if (configFile) {
createWatchOfConfigFile(configFile.path, host);
}
else {
// First file as the root
createWatchOfFilesAndCompilerOptions([files[0].path], host, { listEmittedFiles: true });
}
checkOutputContains(host, allEmittedFiles);
const emittedFiles = getEmittedFiles(files, initialEmittedFileContents);
verifyEmittedFiles(host, emittedFiles);
host.clearOutput();
const affectedFiles = modifyFiles(files, emittedFiles);
host.reloadFS(files);
host.checkTimeoutQueueLengthAndRun(1);
checkAffectedLines(host, affectedFiles, allEmittedFiles);
verifyEmittedFiles(host, emittedFiles);
}
function verifyNewLine(newLine: string) {
const lines = ["var x = 1;", "var y = 2;"];
const fileContent = lines.join(newLine);
const f = {
path: "/a/app.ts",
content: fileContent
};
verifyEmittedFileContents(newLine, [f], [fileContent + newLine], modifyFiles);
function modifyFiles(files: FileOrFolderEmit[], emittedFiles: EmittedFile[]) {
files[0].content = fileContent + newLine + "var z = 3;";
emittedFiles[0].content = files[0].content + newLine;
return [files[0]];
}
}
it("handles new lines: \\n", () => {
verifyNewLine("\n");
});
it("handles new lines: \\r\\n", () => {
verifyNewLine("\r\n");
});
it("should emit specified file", () => {
const file1 = {
path: "/a/b/f1.ts",
content: `export function Foo() { return 10; }`
};
const file2 = {
path: "/a/b/f2.ts",
content: `import {Foo} from "./f1"; export let y = Foo();`
};
const file3 = {
path: "/a/b/f3.ts",
content: `import {y} from "./f2"; let x = y;`
};
const configFile = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({ compilerOptions: { listEmittedFiles: true } })
};
verifyEmittedFileContents("\r\n", [file1, file2, file3], [
`"use strict";\r\nexports.__esModule = true;\r\nfunction Foo() { return 10; }\r\nexports.Foo = Foo;\r\n`,
`"use strict";\r\nexports.__esModule = true;\r\nvar f1_1 = require("./f1");\r\nexports.y = f1_1.Foo();\r\n`,
`"use strict";\r\nexports.__esModule = true;\r\nvar f2_1 = require("./f2");\r\nvar x = f2_1.y;\r\n`
], modifyFiles, configFile);
function modifyFiles(files: FileOrFolderEmit[], emittedFiles: EmittedFile[]) {
files[0].content += `export function foo2() { return 2; }`;
emittedFiles[0].content += `function foo2() { return 2; }\r\nexports.foo2 = foo2;\r\n`;
emittedFiles[2].shouldBeWritten = false;
return files.slice(0, 2);
}
});
it("Elides const enums correctly in incremental compilation", () => {
const currentDirectory = "/user/someone/projects/myproject";
const file1: File = {
path: `${currentDirectory}/file1.ts`,
content: "export const enum E1 { V = 1 }"
};
const file2: File = {
path: `${currentDirectory}/file2.ts`,
content: `import { E1 } from "./file1"; export const enum E2 { V = E1.V }`
};
const file3: File = {
path: `${currentDirectory}/file3.ts`,
content: `import { E2 } from "./file2"; const v: E2 = E2.V;`
};
const strictAndEsModule = `"use strict";\nexports.__esModule = true;\n`;
verifyEmittedFileContents("\n", [file3, file2, file1], [
`${strictAndEsModule}var v = 1 /* V */;\n`,
strictAndEsModule,
strictAndEsModule
], modifyFiles);
function modifyFiles(files: FileOrFolderEmit[], emittedFiles: EmittedFile[]) {
files[0].content += `function foo2() { return 2; }`;
emittedFiles[0].content += `function foo2() { return 2; }\n`;
emittedFiles[1].shouldBeWritten = false;
emittedFiles[2].shouldBeWritten = false;
return [files[0]];
}
});
it("file is deleted and created as part of change", () => {
const projectLocation = "/home/username/project";
const file: File = {
path: `${projectLocation}/app/file.ts`,
content: "var a = 10;"
};
const fileJs = `${projectLocation}/app/file.js`;
const configFile: File = {
path: `${projectLocation}/tsconfig.json`,
content: JSON.stringify({
include: [
"app/**/*.ts"
]
})
};
const files = [file, configFile, libFile];
const host = createWatchedSystem(files, { currentDirectory: projectLocation, useCaseSensitiveFileNames: true });
createWatchOfConfigFile("tsconfig.json", host);
verifyProgram();
file.content += "\nvar b = 10;";
host.reloadFS(files, { invokeFileDeleteCreateAsPartInsteadOfChange: true });
host.runQueuedTimeoutCallbacks();
verifyProgram();
function verifyProgram() {
assert.isTrue(host.fileExists(fileJs));
assert.equal(host.readFile(fileJs), file.content + "\n");
}
});
});
describe("unittests:: tsc-watch:: emit with when module emit is specified as node", () => {
it("when instead of filechanged recursive directory watcher is invoked", () => {
const configFile: File = {
path: "/a/rootFolder/project/tsconfig.json",
content: JSON.stringify({
compilerOptions: {
module: "none",
allowJs: true,
outDir: "Static/scripts/"
},
include: [
"Scripts/**/*"
],
})
};
const outputFolder = "/a/rootFolder/project/Static/scripts/";
const file1: File = {
path: "/a/rootFolder/project/Scripts/TypeScript.ts",
content: "var z = 10;"
};
const file2: File = {
path: "/a/rootFolder/project/Scripts/Javascript.js",
content: "var zz = 10;"
};
const files = [configFile, file1, file2, libFile];
const host = createWatchedSystem(files);
const watch = createWatchOfConfigFile(configFile.path, host);
checkProgramActualFiles(watch(), mapDefined(files, f => f === configFile ? undefined : f.path));
file1.content = "var zz30 = 100;";
host.reloadFS(files, { invokeDirectoryWatcherInsteadOfFileChanged: true });
host.runQueuedTimeoutCallbacks();
checkProgramActualFiles(watch(), mapDefined(files, f => f === configFile ? undefined : f.path));
const outputFile1 = changeExtension((outputFolder + getBaseFileName(file1.path)), ".js");
assert.isTrue(host.fileExists(outputFile1));
assert.equal(host.readFile(outputFile1), file1.content + host.newLine);
});
});
}
@@ -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);
});
});
});
}
@@ -0,0 +1,203 @@
namespace ts.tscWatch {
export import WatchedSystem = TestFSWithWatch.TestServerHost;
export type File = TestFSWithWatch.File;
export type SymLink = TestFSWithWatch.SymLink;
export import libFile = TestFSWithWatch.libFile;
export import createWatchedSystem = TestFSWithWatch.createWatchedSystem;
export import checkArray = TestFSWithWatch.checkArray;
export import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles;
export import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed;
export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories;
export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed;
export import checkOutputContains = TestFSWithWatch.checkOutputContains;
export import checkOutputDoesNotContain = TestFSWithWatch.checkOutputDoesNotContain;
export const commonFile1: File = {
path: "/a/b/commonFile1.ts",
content: "let x = 1"
};
export const commonFile2: File = {
path: "/a/b/commonFile2.ts",
content: "let y = 1"
};
export function checkProgramActualFiles(program: Program, expectedFiles: ReadonlyArray<string>) {
checkArray(`Program actual files`, program.getSourceFiles().map(file => file.fileName), expectedFiles);
}
export function checkProgramRootFiles(program: Program, expectedFiles: ReadonlyArray<string>) {
checkArray(`Program rootFileNames`, program.getRootFileNames(), expectedFiles);
}
export interface Watch {
(): Program;
getBuilderProgram(): EmitAndSemanticDiagnosticsBuilderProgram;
}
export function createWatchOfConfigFile(configFileName: string, host: WatchedSystem, maxNumberOfFilesToIterateForInvalidation?: number) {
const compilerHost = createWatchCompilerHostOfConfigFile(configFileName, {}, host);
compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation;
const watch = createWatchProgram(compilerHost);
const result = (() => watch.getCurrentProgram().getProgram()) as Watch;
result.getBuilderProgram = () => watch.getCurrentProgram();
return result;
}
export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], host: WatchedSystem, options: CompilerOptions = {}, maxNumberOfFilesToIterateForInvalidation?: number) {
const compilerHost = createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles, options, host);
compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation;
const watch = createWatchProgram(compilerHost);
return () => watch.getCurrentProgram().getProgram();
}
const elapsedRegex = /^Elapsed:: [0-9]+ms/;
function checkOutputErrors(
host: WatchedSystem,
logsBeforeWatchDiagnostic: string[] | undefined,
preErrorsWatchDiagnostic: Diagnostic,
logsBeforeErrors: string[] | undefined,
errors: ReadonlyArray<Diagnostic> | ReadonlyArray<string>,
disableConsoleClears?: boolean | undefined,
...postErrorsWatchDiagnostics: Diagnostic[]
) {
let screenClears = 0;
const outputs = host.getOutput();
const expectedOutputCount = 1 + errors.length + postErrorsWatchDiagnostics.length +
(logsBeforeWatchDiagnostic ? logsBeforeWatchDiagnostic.length : 0) + (logsBeforeErrors ? logsBeforeErrors.length : 0);
assert.equal(outputs.length, expectedOutputCount, JSON.stringify(outputs));
let index = 0;
forEach(logsBeforeWatchDiagnostic, log => assertLog("logsBeforeWatchDiagnostic", log));
assertWatchDiagnostic(preErrorsWatchDiagnostic);
forEach(logsBeforeErrors, log => assertLog("logBeforeError", log));
// Verify errors
forEach(errors, assertDiagnostic);
forEach(postErrorsWatchDiagnostics, assertWatchDiagnostic);
assert.equal(host.screenClears.length, screenClears, "Expected number of screen clears");
host.clearOutput();
function isDiagnostic(diagnostic: Diagnostic | string): diagnostic is Diagnostic {
return !!(diagnostic as Diagnostic).messageText;
}
function assertDiagnostic(diagnostic: Diagnostic | string) {
const expected = isDiagnostic(diagnostic) ? formatDiagnostic(diagnostic, host) : diagnostic;
assert.equal(outputs[index], expected, getOutputAtFailedMessage("Diagnostic", expected));
index++;
}
function assertLog(caption: string, expected: string) {
const actual = outputs[index];
assert.equal(actual.replace(elapsedRegex, ""), expected.replace(elapsedRegex, ""), getOutputAtFailedMessage(caption, expected));
index++;
}
function assertWatchDiagnostic(diagnostic: Diagnostic) {
const expected = getWatchDiagnosticWithoutDate(diagnostic);
if (!disableConsoleClears && contains(screenStartingMessageCodes, diagnostic.code)) {
assert.equal(host.screenClears[screenClears], index, `Expected screen clear at this diagnostic: ${expected}`);
screenClears++;
}
assert.isTrue(endsWith(outputs[index], expected), getOutputAtFailedMessage("Watch diagnostic", expected));
index++;
}
function getOutputAtFailedMessage(caption: string, expectedOutput: string) {
return `Expected ${caption}: ${JSON.stringify(expectedOutput)} at ${index} in ${JSON.stringify(outputs)}`;
}
function getWatchDiagnosticWithoutDate(diagnostic: Diagnostic) {
const newLines = contains(screenStartingMessageCodes, diagnostic.code)
? `${host.newLine}${host.newLine}`
: host.newLine;
return ` - ${flattenDiagnosticMessageText(diagnostic.messageText, host.newLine)}${newLines}`;
}
}
function createErrorsFoundCompilerDiagnostic(errors: ReadonlyArray<Diagnostic> | ReadonlyArray<string>) {
return errors.length === 1
? createCompilerDiagnostic(Diagnostics.Found_1_error_Watching_for_file_changes)
: createCompilerDiagnostic(Diagnostics.Found_0_errors_Watching_for_file_changes, errors.length);
}
export function checkOutputErrorsInitial(host: WatchedSystem, errors: ReadonlyArray<Diagnostic> | ReadonlyArray<string>, disableConsoleClears?: boolean, logsBeforeErrors?: string[]) {
checkOutputErrors(
host,
/*logsBeforeWatchDiagnostic*/ undefined,
createCompilerDiagnostic(Diagnostics.Starting_compilation_in_watch_mode),
logsBeforeErrors,
errors,
disableConsoleClears,
createErrorsFoundCompilerDiagnostic(errors));
}
export function checkOutputErrorsIncremental(host: WatchedSystem, errors: ReadonlyArray<Diagnostic> | ReadonlyArray<string>, disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) {
checkOutputErrors(
host,
logsBeforeWatchDiagnostic,
createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation),
logsBeforeErrors,
errors,
disableConsoleClears,
createErrorsFoundCompilerDiagnostic(errors));
}
export function checkOutputErrorsIncrementalWithExit(host: WatchedSystem, errors: ReadonlyArray<Diagnostic> | ReadonlyArray<string>, expectedExitCode: ExitStatus, disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) {
checkOutputErrors(
host,
logsBeforeWatchDiagnostic,
createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation),
logsBeforeErrors,
errors,
disableConsoleClears);
assert.equal(host.exitCode, expectedExitCode);
}
function isDiagnosticMessageChain(message: DiagnosticMessage | DiagnosticMessageChain): message is DiagnosticMessageChain {
return !!(message as DiagnosticMessageChain).messageText;
}
export function getDiagnosticOfFileFrom(file: SourceFile | undefined, start: number | undefined, length: number | undefined, message: DiagnosticMessage | DiagnosticMessageChain, ..._args: (string | number)[]): Diagnostic {
let text: DiagnosticMessageChain | string;
if (isDiagnosticMessageChain(message)) {
text = message;
}
else {
text = getLocaleSpecificMessage(message);
if (arguments.length > 4) {
text = formatStringFromArgs(text, arguments, 4);
}
}
return {
file,
start,
length,
messageText: text,
category: message.category,
code: message.code,
};
}
export function getDiagnosticWithoutFile(message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic {
return getDiagnosticOfFileFrom(/*file*/ undefined, /*start*/ undefined, /*length*/ undefined, message, ...args);
}
export function getDiagnosticOfFile(file: SourceFile, start: number, length: number, message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic {
return getDiagnosticOfFileFrom(file, start, length, message, ...args);
}
export function getDiagnosticOfFileFromProgram(program: Program, filePath: string, start: number, length: number, message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic {
return getDiagnosticOfFileFrom(program.getSourceFileByPath(toPath(filePath, program.getCurrentDirectory(), s => s.toLowerCase()))!,
start, length, message, ...args);
}
export function getUnknownCompilerOption(program: Program, configFile: File, option: string) {
const quotedOption = `"${option}"`;
return getDiagnosticOfFile(program.getCompilerOptions().configFile!, configFile.content.indexOf(quotedOption), quotedOption.length, Diagnostics.Unknown_compiler_option_0, option);
}
export function getDiagnosticModuleNotFoundOfFile(program: Program, file: File, moduleName: string) {
const quotedModuleName = `"${moduleName}"`;
return getDiagnosticOfFileFromProgram(program, file.path, file.content.indexOf(quotedModuleName), quotedModuleName.length, Diagnostics.Cannot_find_module_0, moduleName);
}
}
File diff suppressed because it is too large Load Diff
@@ -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);
});
});
}
@@ -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]);
});
});
}
@@ -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
@@ -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);
});
});
}
@@ -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,
}
]);
});
});
}
@@ -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);
});
});
});
}

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