Merge branch 'master' of github.com:Microsoft/TypeScript into rest-param-destructuring

This commit is contained in:
Klaus Meinhardt
2018-08-01 10:05:11 +02:00
3966 changed files with 6840 additions and 59992 deletions
+4 -1
View File
@@ -2072,7 +2072,10 @@ namespace ts {
if (isSpecialPropertyDeclaration(node as PropertyAccessExpression)) {
bindSpecialPropertyDeclaration(node as PropertyAccessExpression);
}
if (isInJavaScriptFile(node) && isModuleExportsPropertyAccessExpression(node as PropertyAccessExpression)) {
if (isInJavaScriptFile(node) &&
file.commonJsModuleIndicator &&
isModuleExportsPropertyAccessExpression(node as PropertyAccessExpression) &&
!lookupSymbolForNameWorker(container, "module" as __String)) {
declareSymbol(container.locals!, /*parent*/ undefined, (node as PropertyAccessExpression).expression as Identifier,
SymbolFlags.FunctionScopedVariable | SymbolFlags.ModuleExports, SymbolFlags.FunctionScopedVariableExcludes);
}
+352 -235
View File
@@ -198,13 +198,10 @@ namespace ts {
},
isContextSensitive,
getFullyQualifiedName,
getResolvedSignature: (nodeIn, candidatesOutArray, theArgumentCount) => {
const node = getParseTreeNode(nodeIn, isCallLikeExpression);
apparentArgumentCount = theArgumentCount;
const res = node ? getResolvedSignature(node, candidatesOutArray) : undefined;
apparentArgumentCount = undefined;
return res;
},
getResolvedSignature: (node, candidatesOutArray, agumentCount) =>
getResolvedSignatureWorker(node, candidatesOutArray, agumentCount, /*isForSignatureHelp*/ false),
getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, agumentCount) =>
getResolvedSignatureWorker(node, candidatesOutArray, agumentCount, /*isForSignatureHelp*/ true),
getConstantValue: nodeIn => {
const node = getParseTreeNode(nodeIn, canHaveConstantValue);
return node ? getConstantValue(node) : undefined;
@@ -354,6 +351,14 @@ namespace ts {
}
};
function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, isForSignatureHelp: boolean): Signature | undefined {
const node = getParseTreeNode(nodeIn, isCallLikeExpression);
apparentArgumentCount = argumentCount;
const res = node ? getResolvedSignature(node, candidatesOutArray, isForSignatureHelp) : undefined;
apparentArgumentCount = undefined;
return res;
}
const tupleTypes = createMap<GenericType>();
const unionTypes = createMap<UnionType>();
const intersectionTypes = createMap<IntersectionType>();
@@ -1834,7 +1839,7 @@ namespace ts {
if (symbol.flags & SymbolFlags.Variable) {
const typeAnnotation = (<VariableDeclaration>symbol.valueDeclaration).type;
if (typeAnnotation) {
return resolveSymbol(getPropertyOfType(getTypeFromTypeNode(typeAnnotation), name)); // TODO: GH#18217
return resolveSymbol(getPropertyOfType(getTypeFromTypeNode(typeAnnotation), name));
}
}
}
@@ -3883,14 +3888,15 @@ namespace ts {
const links = getSymbolLinks(symbol);
let specifier = links.specifierCache && links.specifierCache.get(contextFile.path);
if (!specifier) {
specifier = flatten(moduleSpecifiers.getModuleSpecifiers(
specifier = moduleSpecifiers.getModuleSpecifierForDeclarationFile(
symbol,
compilerOptions,
contextFile,
context.tracker.moduleResolverHost,
context.tracker.moduleResolverHost.getSourceFiles!(), // TODO: GH#18217
{ importModuleSpecifierPreference: "non-relative" }
))[0];
{ importModuleSpecifierPreference: "non-relative" },
host.redirectTargetsMap,
);
links.specifierCache = links.specifierCache || createMap();
links.specifierCache.set(contextFile.path, specifier);
}
@@ -4988,86 +4994,87 @@ namespace ts {
function getTypeOfVariableOrParameterOrProperty(symbol: Symbol): Type {
const links = getSymbolLinks(symbol);
if (!links.type) {
// Handle prototype property
if (symbol.flags & SymbolFlags.Prototype) {
return links.type = getTypeOfPrototypeProperty(symbol);
}
// CommonsJS require and module both have type any.
if (symbol === requireSymbol) {
return links.type = anyType;
}
if (symbol.flags & SymbolFlags.ModuleExports) {
const fileSymbol = getSymbolOfNode(getSourceFileOfNode(symbol.valueDeclaration));
const members = createSymbolTable();
members.set("exports" as __String, fileSymbol);
return links.type = createAnonymousType(symbol, members, emptyArray, emptyArray, undefined, undefined);
}
// Handle catch clause variables
const declaration = symbol.valueDeclaration;
if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) {
return links.type = anyType;
}
// Handle export default expressions
if (isSourceFile(declaration)) {
const jsonSourceFile = cast(declaration, isJsonSourceFile);
return links.type = jsonSourceFile.statements.length ? checkExpression(jsonSourceFile.statements[0].expression) : emptyObjectType;
}
if (declaration.kind === SyntaxKind.ExportAssignment) {
return links.type = checkExpression((<ExportAssignment>declaration).expression);
}
// Handle variable, parameter or property
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
return errorType;
}
let type = getJSSpecialType(symbol, declaration);
if (!type) {
if (isJSDocPropertyLikeTag(declaration)
|| isPropertyAccessExpression(declaration)
|| isIdentifier(declaration)
|| isClassDeclaration(declaration)
|| isFunctionDeclaration(declaration)
|| (isMethodDeclaration(declaration) && !isObjectLiteralMethod(declaration))
|| isMethodSignature(declaration)) {
// Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty`
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) {
return getTypeOfFuncClassEnumModule(symbol);
}
type = tryGetTypeFromEffectiveTypeNode(declaration) || anyType;
}
else if (isPropertyAssignment(declaration)) {
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkPropertyAssignment(declaration);
}
else if (isJsxAttribute(declaration)) {
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkJsxAttribute(declaration);
}
else if (isShorthandPropertyAssignment(declaration)) {
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionForMutableLocation(declaration.name, CheckMode.Normal);
}
else if (isObjectLiteralMethod(declaration)) {
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkObjectLiteralMethod(declaration, CheckMode.Normal);
}
else if (isParameter(declaration)
|| isPropertyDeclaration(declaration)
|| isPropertySignature(declaration)
|| isVariableDeclaration(declaration)
|| isBindingElement(declaration)) {
type = getWidenedTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true);
}
else {
return Debug.fail("Unhandled declaration kind! " + Debug.showSyntaxKind(declaration) + " for " + Debug.showSymbol(symbol));
}
}
if (!popTypeResolution()) {
type = reportCircularityError(symbol);
}
links.type = type;
}
return links.type;
return links.type || (links.type = getTypeOfVariableOrParameterOrPropertyWorker(symbol));
}
function getTypeOfVariableOrParameterOrPropertyWorker(symbol: Symbol) {
// Handle prototype property
if (symbol.flags & SymbolFlags.Prototype) {
return getTypeOfPrototypeProperty(symbol);
}
// CommonsJS require and module both have type any.
if (symbol === requireSymbol) {
return anyType;
}
if (symbol.flags & SymbolFlags.ModuleExports) {
const fileSymbol = getSymbolOfNode(getSourceFileOfNode(symbol.valueDeclaration));
const members = createSymbolTable();
members.set("exports" as __String, fileSymbol);
return createAnonymousType(symbol, members, emptyArray, emptyArray, undefined, undefined);
}
// Handle catch clause variables
const declaration = symbol.valueDeclaration;
if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) {
return anyType;
}
// Handle export default expressions
if (isSourceFile(declaration)) {
const jsonSourceFile = cast(declaration, isJsonSourceFile);
return jsonSourceFile.statements.length ? checkExpression(jsonSourceFile.statements[0].expression) : emptyObjectType;
}
if (declaration.kind === SyntaxKind.ExportAssignment) {
return checkExpression((<ExportAssignment>declaration).expression);
}
// Handle variable, parameter or property
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
return errorType;
}
let type = getJSSpecialType(symbol, declaration);
if (!type) {
if (isJSDocPropertyLikeTag(declaration)
|| isPropertyAccessExpression(declaration)
|| isIdentifier(declaration)
|| isClassDeclaration(declaration)
|| isFunctionDeclaration(declaration)
|| (isMethodDeclaration(declaration) && !isObjectLiteralMethod(declaration))
|| isMethodSignature(declaration)) {
// Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty`
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) {
return getTypeOfFuncClassEnumModule(symbol);
}
type = tryGetTypeFromEffectiveTypeNode(declaration) || anyType;
}
else if (isPropertyAssignment(declaration)) {
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkPropertyAssignment(declaration);
}
else if (isJsxAttribute(declaration)) {
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkJsxAttribute(declaration);
}
else if (isShorthandPropertyAssignment(declaration)) {
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionForMutableLocation(declaration.name, CheckMode.Normal);
}
else if (isObjectLiteralMethod(declaration)) {
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkObjectLiteralMethod(declaration, CheckMode.Normal);
}
else if (isParameter(declaration)
|| isPropertyDeclaration(declaration)
|| isPropertySignature(declaration)
|| isVariableDeclaration(declaration)
|| isBindingElement(declaration)) {
type = getWidenedTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true);
}
else {
return Debug.fail("Unhandled declaration kind! " + Debug.showSyntaxKind(declaration) + " for " + Debug.showSymbol(symbol));
}
}
if (!popTypeResolution()) {
type = reportCircularityError(symbol);
}
return type;
}
function getJSSpecialType(symbol: Symbol, decl: Declaration): Type | undefined {
if (!isInJavaScriptFile(decl)) {
return undefined;
@@ -5118,20 +5125,25 @@ namespace ts {
}
}
function getAnnotatedAccessorType(accessor: AccessorDeclaration | undefined): Type | undefined {
function getAnnotatedAccessorTypeNode(accessor: AccessorDeclaration | undefined): TypeNode | undefined {
if (accessor) {
if (accessor.kind === SyntaxKind.GetAccessor) {
const getterTypeAnnotation = getEffectiveReturnTypeNode(accessor);
return getterTypeAnnotation && getTypeFromTypeNode(getterTypeAnnotation);
return getterTypeAnnotation;
}
else {
const setterTypeAnnotation = getEffectiveSetAccessorTypeAnnotationNode(accessor);
return setterTypeAnnotation && getTypeFromTypeNode(setterTypeAnnotation);
return setterTypeAnnotation;
}
}
return undefined;
}
function getAnnotatedAccessorType(accessor: AccessorDeclaration | undefined): Type | undefined {
const node = getAnnotatedAccessorTypeNode(accessor);
return node && getTypeFromTypeNode(node);
}
function getAnnotatedAccessorThisParameter(accessor: AccessorDeclaration): Symbol | undefined {
const parameter = getAccessorThisParameter(accessor);
return parameter && parameter.symbol;
@@ -5143,64 +5155,65 @@ namespace ts {
function getTypeOfAccessors(symbol: Symbol): Type {
const links = getSymbolLinks(symbol);
if (!links.type) {
const getter = getDeclarationOfKind<AccessorDeclaration>(symbol, SyntaxKind.GetAccessor);
const setter = getDeclarationOfKind<AccessorDeclaration>(symbol, SyntaxKind.SetAccessor);
return links.type || (links.type = getTypeOfAccessorsWorker(symbol));
}
if (getter && isInJavaScriptFile(getter)) {
const jsDocType = getTypeForDeclarationFromJSDocComment(getter);
if (jsDocType) {
return links.type = jsDocType;
}
function getTypeOfAccessorsWorker(symbol: Symbol): Type {
const getter = getDeclarationOfKind<AccessorDeclaration>(symbol, SyntaxKind.GetAccessor);
const setter = getDeclarationOfKind<AccessorDeclaration>(symbol, SyntaxKind.SetAccessor);
if (getter && isInJavaScriptFile(getter)) {
const jsDocType = getTypeForDeclarationFromJSDocComment(getter);
if (jsDocType) {
return jsDocType;
}
}
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
return errorType;
}
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
return errorType;
}
let type: Type;
let type: Type;
// First try to see if the user specified a return type on the get-accessor.
const getterReturnType = getAnnotatedAccessorType(getter);
if (getterReturnType) {
type = getterReturnType;
// First try to see if the user specified a return type on the get-accessor.
const getterReturnType = getAnnotatedAccessorType(getter);
if (getterReturnType) {
type = getterReturnType;
}
else {
// If the user didn't specify a return type, try to use the set-accessor's parameter type.
const setterParameterType = getAnnotatedAccessorType(setter);
if (setterParameterType) {
type = setterParameterType;
}
else {
// If the user didn't specify a return type, try to use the set-accessor's parameter type.
const setterParameterType = getAnnotatedAccessorType(setter);
if (setterParameterType) {
type = setterParameterType;
// If there are no specified types, try to infer it from the body of the get accessor if it exists.
if (getter && getter.body) {
type = getReturnTypeFromBody(getter);
}
// Otherwise, fall back to 'any'.
else {
// If there are no specified types, try to infer it from the body of the get accessor if it exists.
if (getter && getter.body) {
type = getReturnTypeFromBody(getter);
}
// Otherwise, fall back to 'any'.
else {
if (noImplicitAny) {
if (setter) {
error(setter, Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation, symbolToString(symbol));
}
else {
Debug.assert(!!getter, "there must existed getter as we are current checking either setter or getter in this function");
error(getter, Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation, symbolToString(symbol));
}
if (noImplicitAny) {
if (setter) {
error(setter, Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation, symbolToString(symbol));
}
else {
Debug.assert(!!getter, "there must existed getter as we are current checking either setter or getter in this function");
error(getter, Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation, symbolToString(symbol));
}
type = anyType;
}
type = anyType;
}
}
if (!popTypeResolution()) {
type = anyType;
if (noImplicitAny) {
const getter = getDeclarationOfKind<AccessorDeclaration>(symbol, SyntaxKind.GetAccessor);
error(getter, Diagnostics._0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, symbolToString(symbol));
}
}
links.type = type;
}
return links.type;
if (!popTypeResolution()) {
type = anyType;
if (noImplicitAny) {
const getter = getDeclarationOfKind<AccessorDeclaration>(symbol, SyntaxKind.GetAccessor);
error(getter, Diagnostics._0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, symbolToString(symbol));
}
}
return type;
}
function getBaseTypeVariableOfClass(symbol: Symbol) {
@@ -5210,6 +5223,7 @@ namespace ts {
function getTypeOfFuncClassEnumModule(symbol: Symbol): Type {
let links = getSymbolLinks(symbol);
const originalLinks = links;
if (!links.type) {
const jsDeclaration = getDeclarationOfJSInitializer(symbol.valueDeclaration);
if (jsDeclaration) {
@@ -5228,48 +5242,47 @@ namespace ts {
}
}
}
if (symbol.flags & SymbolFlags.Module && isShorthandAmbientModuleSymbol(symbol)) {
links.type = anyType;
}
else if (symbol.valueDeclaration.kind === SyntaxKind.BinaryExpression ||
symbol.valueDeclaration.kind === SyntaxKind.PropertyAccessExpression && symbol.valueDeclaration.parent.kind === SyntaxKind.BinaryExpression) {
links.type = getWidenedTypeFromJSSpecialPropertyDeclarations(symbol);
}
else {
if (symbol.flags & SymbolFlags.ValueModule && symbol.valueDeclaration && isSourceFile(symbol.valueDeclaration) && symbol.valueDeclaration.commonJsModuleIndicator) {
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
return errorType;
}
const resolvedModule = resolveExternalModuleSymbol(symbol);
if (resolvedModule !== symbol) {
const exportEquals = getMergedSymbol(symbol.exports!.get(InternalSymbolName.ExportEquals)!);
links.type = getWidenedTypeFromJSSpecialPropertyDeclarations(exportEquals, exportEquals === resolvedModule ? undefined : resolvedModule);
}
if (!popTypeResolution()) {
links.type = reportCircularityError(symbol);
}
}
if (!links.type) {
const type = createObjectType(ObjectFlags.Anonymous, symbol);
if (symbol.flags & SymbolFlags.Class) {
const baseTypeVariable = getBaseTypeVariableOfClass(symbol);
links.type = baseTypeVariable ? getIntersectionType([type, baseTypeVariable]) : type;
}
else {
links.type = strictNullChecks && symbol.flags & SymbolFlags.Optional ? getOptionalType(type) : type;
}
}
}
originalLinks.type = links.type = getTypeOfFuncClassEnumModuleWorker(symbol);
}
return links.type;
}
function getTypeOfFuncClassEnumModuleWorker(symbol: Symbol): Type {
const declaration = symbol.valueDeclaration;
if (symbol.flags & SymbolFlags.Module && isShorthandAmbientModuleSymbol(symbol)) {
return anyType;
}
else if (declaration.kind === SyntaxKind.BinaryExpression ||
declaration.kind === SyntaxKind.PropertyAccessExpression && declaration.parent.kind === SyntaxKind.BinaryExpression) {
return getWidenedTypeFromJSSpecialPropertyDeclarations(symbol);
}
else if (symbol.flags & SymbolFlags.ValueModule && declaration && isSourceFile(declaration) && declaration.commonJsModuleIndicator) {
const resolvedModule = resolveExternalModuleSymbol(symbol);
if (resolvedModule !== symbol) {
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
return errorType;
}
const exportEquals = getMergedSymbol(symbol.exports!.get(InternalSymbolName.ExportEquals)!);
const type = getWidenedTypeFromJSSpecialPropertyDeclarations(exportEquals, exportEquals === resolvedModule ? undefined : resolvedModule);
if (!popTypeResolution()) {
return reportCircularityError(symbol);
}
return type;
}
}
const type = createObjectType(ObjectFlags.Anonymous, symbol);
if (symbol.flags & SymbolFlags.Class) {
const baseTypeVariable = getBaseTypeVariableOfClass(symbol);
return baseTypeVariable ? getIntersectionType([type, baseTypeVariable]) : type;
}
else {
return strictNullChecks && symbol.flags & SymbolFlags.Optional ? getOptionalType(type) : type;
}
}
function getTypeOfEnumMember(symbol: Symbol): Type {
const links = getSymbolLinks(symbol);
if (!links.type) {
links.type = getDeclaredTypeOfEnumMember(symbol);
}
return links.type;
return links.type || (links.type = getDeclaredTypeOfEnumMember(symbol));
}
function getTypeOfAlias(symbol: Symbol): Type {
@@ -5298,7 +5311,7 @@ namespace ts {
}
else {
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
return errorType;
return links.type = errorType;
}
symbolInstantiationDepth++;
let type = instantiateType(getTypeOfSymbol(links.target!), links.mapper!);
@@ -6853,11 +6866,7 @@ namespace ts {
return obj.properties.some(property => {
const name = property.name && getTextOfPropertyName(property.name);
const expected = name === undefined ? undefined : getTypeOfPropertyOfType(contextualType, name);
if (expected && typeIsLiteralType(expected)) {
const actual = getTypeOfNode(property);
return !!actual && !isTypeIdenticalTo(actual, expected);
}
return false;
return !!expected && typeIsLiteralType(expected) && !isTypeIdenticalTo(getTypeOfNode(property), expected);
});
}
@@ -8128,6 +8137,10 @@ namespace ts {
// TODO: GH#18217 (should the `|| assignedType` be at a lower precedence?)
return (referenceType && assignedType ? getIntersectionType([assignedType, referenceType]) : referenceType || assignedType)!;
}
const enumTag = getJSDocEnumTag(symbol.valueDeclaration);
if (enumTag && enumTag.typeExpression) {
return getTypeFromTypeNode(enumTag.typeExpression);
}
}
function getTypeReferenceTypeWorker(node: NodeWithTypeArguments, symbol: Symbol, typeArguments: Type[] | undefined): Type | undefined {
@@ -8185,7 +8198,7 @@ namespace ts {
}
function isJSDocTypeReference(node: Node): node is TypeReferenceNode {
return !!(node.flags & NodeFlags.JSDoc) && node.kind === SyntaxKind.TypeReference;
return !!(node.flags & NodeFlags.JSDoc) && (node.kind === SyntaxKind.TypeReference || node.kind === SyntaxKind.ImportType);
}
function checkNoTypeArguments(node: NodeWithTypeArguments, symbol?: Symbol) {
@@ -8432,6 +8445,10 @@ namespace ts {
return createTypeFromGenericGlobalType(globalArrayType, [elementType]);
}
function createReadonlyArrayType(elementType: Type): ObjectType {
return createTypeFromGenericGlobalType(globalReadonlyArrayType, [elementType]);
}
function getTypeFromArrayTypeNode(node: ArrayTypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
@@ -9454,7 +9471,7 @@ namespace ts {
links.resolvedSymbol = unknownSymbol;
return links.resolvedType = errorType;
}
const targetMeaning = node.isTypeOf ? SymbolFlags.Value : SymbolFlags.Type;
const targetMeaning = node.isTypeOf ? SymbolFlags.Value : node.flags & NodeFlags.JSDoc ? SymbolFlags.Value | SymbolFlags.Type : SymbolFlags.Type;
// TODO: Future work: support unions/generics/whatever via a deferred import-type
const innerModuleSymbol = resolveExternalModuleName(node, node.argument.literal);
if (!innerModuleSymbol) {
@@ -9821,7 +9838,7 @@ namespace ts {
case SyntaxKind.Identifier:
case SyntaxKind.QualifiedName:
const symbol = getSymbolAtLocation(node);
return (symbol && getDeclaredTypeOfSymbol(symbol))!; // TODO: GH#18217
return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType;
default:
return errorType;
}
@@ -10087,11 +10104,16 @@ namespace ts {
}
function instantiateMappedType(type: MappedType, mapper: TypeMapper): Type {
// Check if we have a homomorphic mapped type, i.e. a type of the form { [P in keyof T]: X } for some
// type variable T. If so, the mapped type is distributive over a union type and when T is instantiated
// to a union type A | B, we produce { [P in keyof A]: X } | { [P in keyof B]: X }. Furthermore, for
// homomorphic mapped types we leave primitive types alone. For example, when T is instantiated to a
// union type A | undefined, we produce { [P in keyof A]: X } | undefined.
// For a momomorphic mapped type { [P in keyof T]: X }, where T is some type variable, the mapping
// operation depends on T as follows:
// * If T is a primitive type no mapping is performed and the result is simply T.
// * If T is a union type we distribute the mapped type over the union.
// * If T is an array we map to an array where the element type has been transformed.
// * If T is a tuple we map to a tuple where the element types have been transformed.
// * Otherwise we map to an object type where the type of each property has been transformed.
// For example, when T is instantiated to a union type A | B, we produce { [P in keyof A]: X } |
// { [P in keyof B]: X }, and when when T is instantiated to a union type A | undefined, we produce
// { [P in keyof A]: X } | undefined.
const constraintType = getConstraintTypeFromMappedType(type);
if (constraintType.flags & TypeFlags.Index) {
const typeVariable = (<IndexType>constraintType).type;
@@ -10100,7 +10122,11 @@ namespace ts {
if (typeVariable !== mappedTypeVariable) {
return mapType(mappedTypeVariable, t => {
if (isMappableType(t)) {
return instantiateAnonymousType(type, createReplacementMapper(typeVariable, t, mapper));
const replacementMapper = createReplacementMapper(typeVariable, t, mapper);
return isArrayType(t) ? createArrayType(instantiateMappedTypeTemplate(type, numberType, /*isOptional*/ true, replacementMapper)) :
isReadonlyArrayType(t) ? createReadonlyArrayType(instantiateMappedTypeTemplate(type, numberType, /*isOptional*/ true, replacementMapper)) :
isTupleType(t) ? instantiateMappedTupleType(t, type, replacementMapper) :
instantiateAnonymousType(type, replacementMapper);
}
return t;
});
@@ -10114,6 +10140,26 @@ namespace ts {
return type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection);
}
function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, mapper: TypeMapper) {
const minLength = tupleType.target.minLength;
const elementTypes = map(tupleType.typeArguments || emptyArray, (_, i) =>
instantiateMappedTypeTemplate(mappedType, getLiteralType("" + i), i >= minLength, mapper));
const modifiers = getMappedTypeModifiers(mappedType);
const newMinLength = modifiers & MappedTypeModifiers.IncludeOptional ? 0 :
modifiers & MappedTypeModifiers.ExcludeOptional ? getTypeReferenceArity(tupleType) - (tupleType.target.hasRestElement ? 1 : 0) :
minLength;
return createTupleType(elementTypes, newMinLength, tupleType.target.hasRestElement, tupleType.target.associatedNames);
}
function instantiateMappedTypeTemplate(type: MappedType, key: Type, isOptional: boolean, mapper: TypeMapper) {
const templateMapper = combineTypeMappers(mapper, createTypeMapper([getTypeParameterFromMappedType(type)], [key]));
const propType = instantiateType(getTemplateTypeFromMappedType(<MappedType>type.target || type), templateMapper);
const modifiers = getMappedTypeModifiers(type);
return strictNullChecks && modifiers & MappedTypeModifiers.IncludeOptional && !isTypeAssignableTo(undefinedType, propType) ? getOptionalType(propType) :
strictNullChecks && modifiers & MappedTypeModifiers.ExcludeOptional && isOptional ? getTypeWithFacts(propType, TypeFacts.NEUndefined) :
propType;
}
function instantiateAnonymousType(type: AnonymousType, mapper: TypeMapper): AnonymousType {
const result = <AnonymousType>createObjectType(type.objectFlags | ObjectFlags.Instantiated, type.symbol);
if (type.objectFlags & ObjectFlags.Mapped) {
@@ -12441,6 +12487,10 @@ namespace ts {
return !!(getObjectFlags(type) & ObjectFlags.Reference) && (<TypeReference>type).target === globalArrayType;
}
function isReadonlyArrayType(type: Type): boolean {
return !!(getObjectFlags(type) & ObjectFlags.Reference) && (<TypeReference>type).target === globalReadonlyArrayType;
}
function isArrayLikeType(type: Type): boolean {
// A type is array-like if it is a reference to the global Array or global ReadonlyArray type,
// or if it is not the undefined or null type and if it is assignable to ReadonlyArray<any>
@@ -12996,6 +13046,22 @@ namespace ts {
return undefined;
}
}
// For arrays and tuples we infer new arrays and tuples where the reverse mapping has been
// applied to the element type(s).
if (isArrayType(source)) {
return createArrayType(inferReverseMappedType((<TypeReference>source).typeArguments![0], target));
}
if (isReadonlyArrayType(source)) {
return createReadonlyArrayType(inferReverseMappedType((<TypeReference>source).typeArguments![0], target));
}
if (isTupleType(source)) {
const elementTypes = map(source.typeArguments || emptyArray, t => inferReverseMappedType(t, target));
const minLength = getMappedTypeModifiers(target) & MappedTypeModifiers.IncludeOptional ?
getTypeReferenceArity(source) - (source.target.hasRestElement ? 1 : 0) : source.target.minLength;
return createTupleType(elementTypes, minLength, source.target.hasRestElement, source.target.associatedNames);
}
// For all other object types we infer a new object type where the reverse mapping has been
// applied to the type of each property.
const reversed = createObjectType(ObjectFlags.ReverseMapped | ObjectFlags.Anonymous, /*symbol*/ undefined) as ReverseMappedType;
reversed.source = source;
reversed.mappedType = target;
@@ -15340,8 +15406,8 @@ namespace ts {
(!isInParameterInitializerBeforeContainingFunction(node) || getThisParameter(container))) {
// Note: a parameter initializer should refer to class-this unless function-this is explicitly annotated.
// If this is a function in a JS file, it might be a class method. Check if it's the RHS
// of a x.prototype.y = function [name]() { .... }
// If this is a function in a JS file, it might be a class method.
// Check if it's the RHS of a x.prototype.y = function [name]() { .... }
if (container.kind === SyntaxKind.FunctionExpression &&
container.parent.kind === SyntaxKind.BinaryExpression &&
getSpecialPropertyAssignmentKind(container.parent as BinaryExpression) === SpecialPropertyAssignmentKind.PrototypeProperty) {
@@ -15355,6 +15421,17 @@ namespace ts {
return getFlowTypeOfReference(node, getInferredClassType(classSymbol));
}
}
// Check if it's a constructor definition, can be either a variable decl or function decl
// i.e.
// * /** @constructor */ function [name]() { ... }
// * /** @constructor */ var x = function() { ... }
else if ((container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.FunctionDeclaration) &&
getJSDocClassTag(container)) {
const classType = getJavaScriptClassType(container.symbol);
if (classType) {
return getFlowTypeOfReference(node, classType);
}
}
const thisType = getThisTypeOfDeclaration(container) || getContextualThisParameterType(container);
if (thisType) {
@@ -16563,7 +16640,8 @@ namespace ts {
const contextualTypeHasPattern = contextualType && contextualType.pattern &&
(contextualType.pattern.kind === SyntaxKind.ObjectBindingPattern || contextualType.pattern.kind === SyntaxKind.ObjectLiteralExpression);
const isInJSFile = isInJavaScriptFile(node) && !isInJsonFile(node);
const isJSObjectLiteral = !contextualType && isInJSFile;
const enumTag = getJSDocEnumTag(node);
const isJSObjectLiteral = !contextualType && isInJSFile && !enumTag;
let typeFlags: TypeFlags = 0;
let patternWithComputedProperties = false;
let hasComputedStringProperty = false;
@@ -16588,6 +16666,9 @@ namespace ts {
checkTypeAssignableTo(type, jsDocType, memberDecl);
type = jsDocType;
}
else if (enumTag && enumTag.typeExpression) {
checkTypeAssignableTo(type, getTypeFromTypeNode(enumTag.typeExpression), memberDecl);
}
}
typeFlags |= type.flags;
const nameType = computedNameType && computedNameType.flags & TypeFlags.StringOrNumberLiteralOrUnique ?
@@ -16622,6 +16703,7 @@ namespace ts {
symbolToString(member), typeToString(contextualType!));
}
}
prop.declarations = member.declarations;
prop.parent = member.parent;
if (member.valueDeclaration) {
@@ -17132,7 +17214,7 @@ namespace ts {
const jsxStatelessElementType = getJsxStatelessElementTypeAt(openingLikeElement);
if (jsxStatelessElementType) {
// We don't call getResolvedSignature here because we have already resolve the type of JSX Element.
const callSignature = getResolvedJsxStatelessFunctionSignature(openingLikeElement, elementType, /*candidatesOutArray*/ undefined);
const callSignature = getResolvedJsxStatelessFunctionSignature(openingLikeElement, elementType, /*candidatesOutArray*/ undefined, /*isForSignatureHelp*/ false);
if (callSignature !== unknownSignature) {
const callReturnType = callSignature && getReturnTypeOfSignature(callSignature);
let paramType = callReturnType && (callSignature!.parameters.length === 0 ? emptyObjectType : getTypeOfSymbol(callSignature!.parameters[0]));
@@ -17169,7 +17251,7 @@ namespace ts {
if (jsxStatelessElementType) {
// We don't call getResolvedSignature because here we have already resolve the type of JSX Element.
const candidatesOutArray: Signature[] = [];
getResolvedJsxStatelessFunctionSignature(openingLikeElement, elementType, candidatesOutArray);
getResolvedJsxStatelessFunctionSignature(openingLikeElement, elementType, candidatesOutArray, /*isForSignatureHelp*/ false);
let result: Type | undefined;
let allMatchingAttributesType: Type | undefined;
for (const candidate of candidatesOutArray) {
@@ -19032,7 +19114,7 @@ namespace ts {
return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, paramCount, typeArguments.length);
}
function resolveCall(node: CallLikeExpression, signatures: ReadonlyArray<Signature>, candidatesOutArray: Signature[] | undefined, fallbackError?: DiagnosticMessage): Signature {
function resolveCall(node: CallLikeExpression, signatures: ReadonlyArray<Signature>, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean, fallbackError?: DiagnosticMessage): Signature {
const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression;
const isDecorator = node.kind === SyntaxKind.Decorator;
const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node);
@@ -19118,7 +19200,7 @@ namespace ts {
// If we are in signature help, a trailing comma indicates that we intend to provide another argument,
// so we will only accept overloads with arity at least 1 higher than the current number of provided arguments.
const signatureHelpTrailingComma =
candidatesOutArray && node.kind === SyntaxKind.CallExpression && node.arguments.hasTrailingComma;
isForSignatureHelp && node.kind === SyntaxKind.CallExpression && node.arguments.hasTrailingComma;
// Section 4.12.1:
// if the candidate list contains one or more signatures for which the type of each argument
@@ -19329,7 +19411,7 @@ namespace ts {
}
const typeArgumentNodes: ReadonlyArray<TypeNode> = callLikeExpressionMayHaveTypeArguments(node) ? node.typeArguments || emptyArray : emptyArray;
const typeArguments = typeArgumentNodes.map(n => getTypeOfNode(n) || anyType);
const typeArguments = typeArgumentNodes.map(getTypeOfNode);
while (typeArguments.length > typeParameters.length) {
typeArguments.pop();
}
@@ -19360,7 +19442,7 @@ namespace ts {
return maxParamsIndex;
}
function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[] | undefined): Signature {
function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature {
if (node.expression.kind === SyntaxKind.SuperKeyword) {
const superType = checkSuperExpression(node.expression);
if (isTypeAny(superType)) {
@@ -19375,7 +19457,7 @@ namespace ts {
const baseTypeNode = getEffectiveBaseTypeNode(getContainingClass(node)!);
if (baseTypeNode) {
const baseConstructors = getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.typeArguments, baseTypeNode);
return resolveCall(node, baseConstructors, candidatesOutArray);
return resolveCall(node, baseConstructors, candidatesOutArray, isForSignatureHelp);
}
}
return resolveUntypedCall(node);
@@ -19428,7 +19510,12 @@ namespace ts {
}
return resolveErrorCall(node);
}
return resolveCall(node, callSignatures, candidatesOutArray);
// If the function is explicitly marked with `@class`, then it must be constructed.
if (callSignatures.some(sig => isInJavaScriptFile(sig.declaration) && !!getJSDocClassTag(sig.declaration!))) {
error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType));
return resolveErrorCall(node);
}
return resolveCall(node, callSignatures, candidatesOutArray, isForSignatureHelp);
}
/**
@@ -19442,7 +19529,7 @@ namespace ts {
!numCallSignatures && !numConstructSignatures && !(apparentFuncType.flags & (TypeFlags.Union | TypeFlags.Never)) && isTypeAssignableTo(funcType, globalFunctionType);
}
function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[] | undefined): Signature {
function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature {
if (node.arguments && languageVersion < ScriptTarget.ES5) {
const spreadIndex = getSpreadArgumentIndex(node.arguments);
if (spreadIndex >= 0) {
@@ -19495,7 +19582,7 @@ namespace ts {
return resolveErrorCall(node);
}
return resolveCall(node, constructSignatures, candidatesOutArray);
return resolveCall(node, constructSignatures, candidatesOutArray, isForSignatureHelp);
}
// If expressionType's apparent type is an object type with no construct signatures but
@@ -19504,7 +19591,7 @@ namespace ts {
// operation is Any. It is an error to have a Void this type.
const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call);
if (callSignatures.length) {
const signature = resolveCall(node, callSignatures, candidatesOutArray);
const signature = resolveCall(node, callSignatures, candidatesOutArray, isForSignatureHelp);
if (!isJavaScriptConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) {
error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword);
}
@@ -19611,7 +19698,7 @@ namespace ts {
}
}
function resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: Signature[] | undefined): Signature {
function resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature {
const tagType = checkExpression(node.tag);
const apparentType = getApparentType(tagType);
@@ -19632,7 +19719,7 @@ namespace ts {
return resolveErrorCall(node);
}
return resolveCall(node, callSignatures, candidatesOutArray);
return resolveCall(node, callSignatures, candidatesOutArray, isForSignatureHelp);
}
/**
@@ -19663,7 +19750,7 @@ namespace ts {
/**
* Resolves a decorator as if it were a call expression.
*/
function resolveDecorator(node: Decorator, candidatesOutArray: Signature[] | undefined): Signature {
function resolveDecorator(node: Decorator, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature {
const funcType = checkExpression(node.expression);
const apparentType = getApparentType(funcType);
if (apparentType === errorType) {
@@ -19692,7 +19779,7 @@ namespace ts {
return resolveErrorCall(node);
}
return resolveCall(node, callSignatures, candidatesOutArray, headMessage);
return resolveCall(node, callSignatures, candidatesOutArray, isForSignatureHelp, headMessage);
}
/**
@@ -19717,32 +19804,32 @@ namespace ts {
* @param candidatesOutArray an array of signature to be filled in by the function. It is passed by signature help in the language service;
* the function will fill it up with appropriate candidate signatures
*/
function getResolvedJsxStatelessFunctionSignature(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[] | undefined): Signature | undefined {
function getResolvedJsxStatelessFunctionSignature(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature | undefined {
Debug.assert(!(elementType.flags & TypeFlags.Union));
const callSignatures = elementType && getSignaturesOfType(elementType, SignatureKind.Call);
if (callSignatures && callSignatures.length > 0) {
return resolveCall(openingLikeElement, callSignatures, candidatesOutArray);
return resolveCall(openingLikeElement, callSignatures, candidatesOutArray, isForSignatureHelp);
}
return undefined;
}
function resolveSignature(node: CallLikeExpression, candidatesOutArray?: Signature[]): Signature {
function resolveSignature(node: CallLikeExpression, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature {
switch (node.kind) {
case SyntaxKind.CallExpression:
return resolveCallExpression(node, candidatesOutArray);
return resolveCallExpression(node, candidatesOutArray, isForSignatureHelp);
case SyntaxKind.NewExpression:
return resolveNewExpression(node, candidatesOutArray);
return resolveNewExpression(node, candidatesOutArray, isForSignatureHelp);
case SyntaxKind.TaggedTemplateExpression:
return resolveTaggedTemplateExpression(node, candidatesOutArray);
return resolveTaggedTemplateExpression(node, candidatesOutArray, isForSignatureHelp);
case SyntaxKind.Decorator:
return resolveDecorator(node, candidatesOutArray);
return resolveDecorator(node, candidatesOutArray, isForSignatureHelp);
case SyntaxKind.JsxOpeningElement:
case SyntaxKind.JsxSelfClosingElement:
// This code-path is called by language service
const exprTypes = checkExpression(node.tagName);
return forEachType(exprTypes, exprType => {
const sfcResult = getResolvedJsxStatelessFunctionSignature(node, exprType, candidatesOutArray);
const sfcResult = getResolvedJsxStatelessFunctionSignature(node, exprType, candidatesOutArray, isForSignatureHelp);
if (sfcResult && sfcResult !== unknownSignature) {
return sfcResult;
}
@@ -19763,7 +19850,7 @@ namespace ts {
* the function will fill it up with appropriate candidate signatures
* @return a signature of the call-like expression or undefined if one can't be found
*/
function getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[]): Signature {
function getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[] | undefined, isForSignatureHelp = false): Signature {
const links = getNodeLinks(node);
// If getResolvedSignature has already been called, we will have cached the resolvedSignature.
// However, it is possible that either candidatesOutArray was not passed in the first time,
@@ -19774,7 +19861,7 @@ namespace ts {
return cached;
}
links.resolvedSignature = resolvingSignature;
const result = resolveSignature(node, candidatesOutArray);
const result = resolveSignature(node, candidatesOutArray, isForSignatureHelp);
// If signature resolution originated in control flow type analysis (for example to compute the
// assigned type in a flow assignment) we don't cache the result as it may be based on temporary
// types from the control flow analysis.
@@ -22613,6 +22700,7 @@ namespace ts {
grammarErrorOnNode(node, Diagnostics.infer_declarations_are_only_permitted_in_the_extends_clause_of_a_conditional_type);
}
checkSourceElement(node.typeParameter);
registerForUnusedIdentifiersCheck(node);
}
function checkImportType(node: ImportTypeNode) {
@@ -23433,9 +23521,13 @@ namespace ts {
}
break;
case SyntaxKind.MethodDeclaration:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
const otherKind = node.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor;
const otherAccessor = getDeclarationOfKind<AccessorDeclaration>(getSymbolOfNode(node as AccessorDeclaration), otherKind);
markDecoratorMedataDataTypeNodeAsReferenced(getAnnotatedAccessorTypeNode(node as AccessorDeclaration) || otherAccessor && getAnnotatedAccessorTypeNode(otherAccessor));
break;
case SyntaxKind.MethodDeclaration:
for (const parameter of (<FunctionLikeDeclaration>node).parameters) {
markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter));
}
@@ -23641,7 +23733,8 @@ namespace ts {
type PotentiallyUnusedIdentifier =
| SourceFile | ModuleDeclaration | ClassLikeDeclaration | InterfaceDeclaration
| Block | CaseBlock | ForStatement | ForInStatement | ForOfStatement
| Exclude<SignatureDeclaration, IndexSignatureDeclaration | JSDocFunctionType> | TypeAliasDeclaration;
| Exclude<SignatureDeclaration, IndexSignatureDeclaration | JSDocFunctionType> | TypeAliasDeclaration
| InferTypeNode;
function checkUnusedIdentifiers(potentiallyUnusedIdentifiers: ReadonlyArray<PotentiallyUnusedIdentifier>, addDiagnostic: AddUnusedDiagnostic) {
for (const node of potentiallyUnusedIdentifiers) {
@@ -23681,6 +23774,7 @@ namespace ts {
case SyntaxKind.FunctionType:
case SyntaxKind.ConstructorType:
case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.InferType:
checkUnusedTypeParameters(node, addDiagnostic);
break;
default:
@@ -23734,21 +23828,48 @@ namespace ts {
}
}
function checkUnusedTypeParameters(
node: ClassDeclaration | ClassExpression | FunctionDeclaration | MethodDeclaration | FunctionExpression | ArrowFunction | ConstructorDeclaration | SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration,
addDiagnostic: AddUnusedDiagnostic,
): void {
function checkUnusedTypeParameters(node: ClassLikeDeclaration | SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration | InferTypeNode, addDiagnostic: AddUnusedDiagnostic): void {
// Only report errors on the last declaration for the type parameter container;
// this ensures that all uses have been accounted for.
const typeParameters = getEffectiveTypeParameterDeclarations(node);
if (!(node.flags & NodeFlags.Ambient) && last(getSymbolOfNode(node).declarations) === node) {
if (node.flags & NodeFlags.Ambient || node.kind !== SyntaxKind.InferType && last(getSymbolOfNode(node).declarations) !== node) return;
if (node.kind === SyntaxKind.InferType) {
const { typeParameter } = node;
if (isTypeParameterUnused(typeParameter)) {
addDiagnostic(node, UnusedKind.Parameter, createDiagnosticForNode(node, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(typeParameter.name)));
}
}
else {
const typeParameters = getEffectiveTypeParameterDeclarations(node);
const seenParentsWithEveryUnused = new NodeSet<DeclarationWithTypeParameterChildren>();
for (const typeParameter of typeParameters) {
if (!(getMergedSymbol(typeParameter.symbol).isReferenced! & SymbolFlags.TypeParameter) && !isIdentifierThatStartsWithUnderscore(typeParameter.name)) {
addDiagnostic(typeParameter, UnusedKind.Parameter, createDiagnosticForNode(typeParameter.name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(typeParameter.symbol)));
if (!isTypeParameterUnused(typeParameter)) continue;
const name = idText(typeParameter.name);
const { parent } = typeParameter;
if (parent.kind !== SyntaxKind.InferType && parent.typeParameters!.every(isTypeParameterUnused)) {
if (seenParentsWithEveryUnused.tryAdd(parent)) {
const range = isJSDocTemplateTag(parent)
// Whole @template tag
? rangeOfNode(parent)
// Include the `<>` in the error message
: rangeOfTypeParameters(parent.typeParameters!);
const only = typeParameters.length === 1;
const message = only ? Diagnostics._0_is_declared_but_its_value_is_never_read : Diagnostics.All_type_parameters_are_unused;
const arg0 = only ? name : undefined;
addDiagnostic(typeParameter, UnusedKind.Parameter, createFileDiagnostic(getSourceFileOfNode(parent), range.pos, range.end - range.pos, message, arg0));
}
}
else {
addDiagnostic(typeParameter, UnusedKind.Parameter, createDiagnosticForNode(typeParameter, Diagnostics._0_is_declared_but_its_value_is_never_read, name));
}
}
}
}
function isTypeParameterUnused(typeParameter: TypeParameterDeclaration): boolean {
return !(getMergedSymbol(typeParameter.symbol).isReferenced! & SymbolFlags.TypeParameter) && !isIdentifierThatStartsWithUnderscore(typeParameter.name);
}
function addToGroup<K, V>(map: Map<[K, V[]]>, key: K, value: V, getKey: (key: K) => number | string): void {
const keyString = String(getKey(key));
@@ -27143,7 +27264,7 @@ namespace ts {
grandParent.kind === SyntaxKind.ObjectBindingPattern &&
node === (<BindingElement>parent).propertyName) {
const typeOfPattern = getTypeOfNode(grandParent);
const propertyDeclaration = typeOfPattern && getPropertyOfType(typeOfPattern, (<Identifier>node).escapedText);
const propertyDeclaration = getPropertyOfType(typeOfPattern, (<Identifier>node).escapedText);
if (propertyDeclaration) {
return propertyDeclaration;
@@ -27243,7 +27364,7 @@ namespace ts {
if (isPartOfTypeNode(node)) {
let typeFromTypeNode = getTypeFromTypeNode(<TypeNode>node);
if (typeFromTypeNode && isExpressionWithTypeArgumentsInClassImplementsClause(node)) {
if (isExpressionWithTypeArgumentsInClassImplementsClause(node)) {
const containingClass = getContainingClass(node)!;
const classType = getTypeOfNode(containingClass) as InterfaceType;
typeFromTypeNode = getTypeWithThisArgument(typeFromTypeNode, classType.thisType);
@@ -27261,8 +27382,8 @@ namespace ts {
// extends clause of a class. We handle that case here.
const classNode = getContainingClass(node)!;
const classType = getDeclaredTypeOfSymbol(getSymbolOfNode(classNode)) as InterfaceType;
const baseType = getBaseTypes(classType)[0];
return baseType && getTypeWithThisArgument(baseType, classType.thisType);
const baseType = firstOrUndefined(getBaseTypes(classType));
return baseType ? getTypeWithThisArgument(baseType, classType.thisType) : errorType;
}
if (isTypeDeclaration(node)) {
@@ -27959,8 +28080,8 @@ namespace ts {
const otherAccessor = getDeclarationOfKind<AccessorDeclaration>(getSymbolOfNode(accessor), otherKind);
const firstAccessor = otherAccessor && (otherAccessor.pos < accessor.pos) ? otherAccessor : accessor;
const secondAccessor = otherAccessor && (otherAccessor.pos < accessor.pos) ? accessor : otherAccessor;
const setAccessor = accessor.kind === SyntaxKind.SetAccessor ? accessor : otherAccessor;
const getAccessor = accessor.kind === SyntaxKind.GetAccessor ? accessor : otherAccessor;
const setAccessor = accessor.kind === SyntaxKind.SetAccessor ? accessor : otherAccessor as SetAccessorDeclaration;
const getAccessor = accessor.kind === SyntaxKind.GetAccessor ? accessor : otherAccessor as GetAccessorDeclaration;
return {
firstAccessor,
secondAccessor,
@@ -29184,10 +29305,6 @@ namespace ts {
}
checkGrammarForDisallowedTrailingComma(elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma);
if (node.name.kind === SyntaxKind.ArrayBindingPattern || node.name.kind === SyntaxKind.ObjectBindingPattern) {
return grammarErrorOnNode(node.name, Diagnostics.A_rest_element_cannot_contain_a_binding_pattern);
}
if (node.propertyName) {
return grammarErrorOnNode(node.name, Diagnostics.A_rest_element_cannot_have_a_property_name);
}
+1 -1
View File
@@ -2093,7 +2093,7 @@ namespace ts {
return arg => f(arg) || g(arg);
}
export function assertTypeIsNever(_: never): void { } // tslint:disable-line no-empty
export function assertType<T>(_: T): void { } // tslint:disable-line no-empty
export function singleElementArray<T>(t: T | undefined): T[] | undefined {
return t === undefined ? undefined : [t];
+28
View File
@@ -3655,6 +3655,10 @@
"category": "Message",
"code": 6204
},
"All type parameters are unused": {
"category": "Error",
"code": 6205
},
"Projects to reference": {
"category": "Message",
@@ -4208,6 +4212,14 @@
"category": "Message",
"code": 90010
},
"Remove template tag": {
"category": "Message",
"code": 90011
},
"Remove type parameters": {
"category": "Message",
"code": 90012
},
"Import '{0}' from module \"{1}\"": {
"category": "Message",
"code": 90013
@@ -4276,6 +4288,22 @@
"category": "Message",
"code": 90029
},
"Replace 'infer {0}' with 'unknown'": {
"category": "Message",
"code": 90030
},
"Replace all unused 'infer' with 'unknown'": {
"category": "Message",
"code": 90031
},
"Import default '{0}' from module \"{1}\"": {
"category": "Message",
"code": 90032
},
"Add default import '{0}' to existing import declaration from \"{1}\"": {
"category": "Message",
"code": 90033
},
"Convert function to an ES2015 class": {
"category": "Message",
"code": 95001
+13 -48
View File
@@ -1058,7 +1058,7 @@ namespace ts {
: node;
}
export function createTaggedTemplate(tag: Expression, template: TemplateLiteral): TaggedTemplateExpression;
/** @deprecated */ export function createTaggedTemplate(tag: Expression, template: TemplateLiteral): TaggedTemplateExpression;
export function createTaggedTemplate(tag: Expression, typeArguments: ReadonlyArray<TypeNode> | undefined, template: TemplateLiteral): TaggedTemplateExpression;
/** @internal */
export function createTaggedTemplate(tag: Expression, typeArgumentsOrTemplate: ReadonlyArray<TypeNode> | TemplateLiteral | undefined, template?: TemplateLiteral): TaggedTemplateExpression;
@@ -1076,7 +1076,7 @@ namespace ts {
return node;
}
export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, template: TemplateLiteral): TaggedTemplateExpression;
/** @deprecated */ export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, template: TemplateLiteral): TaggedTemplateExpression;
export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, typeArguments: ReadonlyArray<TypeNode> | undefined, template: TemplateLiteral): TaggedTemplateExpression;
export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, typeArgumentsOrTemplate: ReadonlyArray<TypeNode> | TemplateLiteral | undefined, template?: TemplateLiteral) {
return node.tag !== tag
@@ -1168,14 +1168,6 @@ namespace ts {
node.body = parenthesizeConciseBody(body);
return node;
}
/** @deprecated */ export function updateArrowFunction(
node: ArrowFunction,
modifiers: ReadonlyArray<Modifier> | undefined,
typeParameters: ReadonlyArray<TypeParameterDeclaration> | undefined,
parameters: ReadonlyArray<ParameterDeclaration>,
type: TypeNode | undefined,
body: ConciseBody): ArrowFunction;
export function updateArrowFunction(
node: ArrowFunction,
modifiers: ReadonlyArray<Modifier> | undefined,
@@ -1183,28 +1175,8 @@ namespace ts {
parameters: ReadonlyArray<ParameterDeclaration>,
type: TypeNode | undefined,
equalsGreaterThanToken: Token<SyntaxKind.EqualsGreaterThanToken>,
body: ConciseBody): ArrowFunction;
export function updateArrowFunction(
node: ArrowFunction,
modifiers: ReadonlyArray<Modifier> | undefined,
typeParameters: ReadonlyArray<TypeParameterDeclaration> | undefined,
parameters: ReadonlyArray<ParameterDeclaration>,
type: TypeNode | undefined,
equalsGreaterThanTokenOrBody: Token<SyntaxKind.EqualsGreaterThanToken> | ConciseBody,
bodyOrUndefined?: ConciseBody,
body: ConciseBody
): ArrowFunction {
let equalsGreaterThanToken: Token<SyntaxKind.EqualsGreaterThanToken>;
let body: ConciseBody;
if (bodyOrUndefined === undefined) {
equalsGreaterThanToken = node.equalsGreaterThanToken;
body = cast(equalsGreaterThanTokenOrBody, isConciseBody);
}
else {
equalsGreaterThanToken = cast(equalsGreaterThanTokenOrBody, (n): n is Token<SyntaxKind.EqualsGreaterThanToken> =>
n.kind === SyntaxKind.EqualsGreaterThanToken);
body = bodyOrUndefined;
}
return node.modifiers !== modifiers
|| node.typeParameters !== typeParameters
|| node.parameters !== parameters
@@ -1306,7 +1278,7 @@ namespace ts {
: node;
}
export function createConditional(condition: Expression, whenTrue: Expression, whenFalse: Expression): ConditionalExpression;
/** @deprecated */ export function createConditional(condition: Expression, whenTrue: Expression, whenFalse: Expression): ConditionalExpression;
export function createConditional(condition: Expression, questionToken: QuestionToken, whenTrue: Expression, colonToken: ColonToken, whenFalse: Expression): ConditionalExpression;
export function createConditional(condition: Expression, questionTokenOrWhenTrue: QuestionToken | Expression, whenTrueOrWhenFalse: Expression, colonToken?: ColonToken, whenFalse?: Expression) {
const node = <ConditionalExpression>createSynthesizedNode(SyntaxKind.ConditionalExpression);
@@ -1317,26 +1289,14 @@ namespace ts {
node.whenFalse = parenthesizeSubexpressionOfConditionalExpression(whenFalse ? whenFalse : whenTrueOrWhenFalse);
return node;
}
/** @deprecated */ export function updateConditional(
node: ConditionalExpression,
condition: Expression,
whenTrue: Expression,
whenFalse: Expression): ConditionalExpression;
export function updateConditional(
node: ConditionalExpression,
condition: Expression,
questionToken: Token<SyntaxKind.QuestionToken>,
whenTrue: Expression,
colonToken: Token<SyntaxKind.ColonToken>,
whenFalse: Expression): ConditionalExpression;
export function updateConditional(node: ConditionalExpression, condition: Expression, ...args: any[]) {
if (args.length === 2) {
const [whenTrue, whenFalse] = args;
return updateConditional(node, condition, node.questionToken, whenTrue, node.colonToken, whenFalse);
}
Debug.assert(args.length === 4);
const [questionToken, whenTrue, colonToken, whenFalse] = args;
whenFalse: Expression
): ConditionalExpression {
return node.condition !== condition
|| node.questionToken !== questionToken
|| node.whenTrue !== whenTrue
@@ -2231,7 +2191,7 @@ namespace ts {
export function createJsxSelfClosingElement(tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes) {
const node = <JsxSelfClosingElement>createSynthesizedNode(SyntaxKind.JsxSelfClosingElement);
node.tagName = tagName;
node.typeArguments = typeArguments && createNodeArray(typeArguments);
node.typeArguments = asNodeArray(typeArguments);
node.attributes = attributes;
return node;
}
@@ -2247,7 +2207,7 @@ namespace ts {
export function createJsxOpeningElement(tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes) {
const node = <JsxOpeningElement>createSynthesizedNode(SyntaxKind.JsxOpeningElement);
node.tagName = tagName;
node.typeArguments = typeArguments && createNodeArray(typeArguments);
node.typeArguments = asNodeArray(typeArguments);
node.attributes = attributes;
return node;
}
@@ -4024,6 +3984,11 @@ namespace ts {
const binaryOperatorPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, binaryOperator);
const binaryOperatorAssociativity = getOperatorAssociativity(SyntaxKind.BinaryExpression, binaryOperator);
const emittedOperand = skipPartiallyEmittedExpressions(operand);
if (!isLeftSideOfBinary && operand.kind === SyntaxKind.ArrowFunction && binaryOperatorPrecedence > 4) {
// We need to parenthesize arrow functions on the right side to avoid it being
// parsed as parenthesized expression: `a && (() => {})`
return true;
}
const operandPrecedence = getExpressionPrecedence(emittedOperand);
switch (compareValues(operandPrecedence, binaryOperatorPrecedence)) {
case Comparison.LessThan:
+70 -50
View File
@@ -1,4 +1,4 @@
// Used by importFixes to synthesize import module specifiers.
// Used by importFixes, getEditsForFileRename, and declaration emit to synthesize import module specifiers.
/* @internal */
namespace ts.moduleSpecifiers {
export interface ModuleSpecifierPreferences {
@@ -14,13 +14,26 @@ namespace ts.moduleSpecifiers {
host: ModuleSpecifierResolutionHost,
files: ReadonlyArray<SourceFile>,
preferences: ModuleSpecifierPreferences = {},
redirectTargetsMap: RedirectTargetsMap,
): string {
const info = getInfo(compilerOptions, importingSourceFile, importingSourceFileName, host);
const modulePaths = getAllModulePaths(files, toFileName, info.getCanonicalFileName, host);
const modulePaths = getAllModulePaths(files, importingSourceFileName, toFileName, info.getCanonicalFileName, host, redirectTargetsMap);
return firstDefined(modulePaths, moduleFileName => getGlobalModuleSpecifier(moduleFileName, info, host, compilerOptions)) ||
first(getLocalModuleSpecifiers(toFileName, info, compilerOptions, preferences));
}
export function getModuleSpecifierForDeclarationFile(
moduleSymbol: Symbol,
compilerOptions: CompilerOptions,
importingSourceFile: SourceFile,
host: ModuleSpecifierResolutionHost,
files: ReadonlyArray<SourceFile>,
preferences: ModuleSpecifierPreferences,
redirectTargetsMap: RedirectTargetsMap,
): string {
return first(first(getModuleSpecifiers(moduleSymbol, compilerOptions, importingSourceFile, host, files, preferences, redirectTargetsMap)));
}
// For each symlink/original for a module, returns a list of ways to import that file.
export function getModuleSpecifiers(
moduleSymbol: Symbol,
@@ -29,6 +42,7 @@ namespace ts.moduleSpecifiers {
host: ModuleSpecifierResolutionHost,
files: ReadonlyArray<SourceFile>,
preferences: ModuleSpecifierPreferences,
redirectTargetsMap: RedirectTargetsMap,
): ReadonlyArray<ReadonlyArray<string>> {
const ambient = tryGetModuleNameFromAmbientModule(moduleSymbol);
if (ambient) return [[ambient]];
@@ -37,7 +51,8 @@ namespace ts.moduleSpecifiers {
if (!files) {
return Debug.fail("Files list must be present to resolve symlinks in specifier resolution");
}
const modulePaths = getAllModulePaths(files, getSourceFileOfNode(moduleSymbol.valueDeclaration).fileName, info.getCanonicalFileName, host);
const moduleSourceFile = getSourceFileOfNode(moduleSymbol.valueDeclaration);
const modulePaths = getAllModulePaths(files, importingSourceFile.path, moduleSourceFile.fileName, info.getCanonicalFileName, host, redirectTargetsMap);
const global = mapDefined(modulePaths, moduleFileName => getGlobalModuleSpecifier(moduleFileName, info, host, compilerOptions));
return global.length ? global.map(g => [g]) : modulePaths.map(moduleFileName =>
@@ -142,64 +157,69 @@ namespace ts.moduleSpecifiers {
return firstDefined(imports, ({ text }) => pathIsRelative(text) ? fileExtensionIs(text, Extension.Js) : undefined) || false;
}
function discoverProbableSymlinks(files: ReadonlyArray<SourceFile>, getCanonicalFileName: (file: string) => string, host: ModuleSpecifierResolutionHost) {
function stringsEqual(a: string, b: string, getCanonicalFileName: GetCanonicalFileName): boolean {
return getCanonicalFileName(a) === getCanonicalFileName(b);
}
// KLUDGE: Don't assume one 'node_modules' links to another. More likely a single directory inside the node_modules is the symlink.
// ALso, don't assume that an `@foo` directory is linked. More likely the contents of that are linked.
function isNodeModulesOrScopedPackageDirectory(s: string, getCanonicalFileName: GetCanonicalFileName): boolean {
return getCanonicalFileName(s) === "node_modules" || startsWith(s, "@");
}
function guessDirectorySymlink(a: string, b: string, cwd: string, getCanonicalFileName: GetCanonicalFileName): [string, string] {
const aParts = getPathComponents(toPath(a, cwd, getCanonicalFileName));
const bParts = getPathComponents(toPath(b, cwd, getCanonicalFileName));
while (!isNodeModulesOrScopedPackageDirectory(aParts[aParts.length - 2], getCanonicalFileName) &&
!isNodeModulesOrScopedPackageDirectory(bParts[bParts.length - 2], getCanonicalFileName) &&
stringsEqual(aParts[aParts.length - 1], bParts[bParts.length - 1], getCanonicalFileName)) {
aParts.pop();
bParts.pop();
}
return [getPathFromPathComponents(aParts), getPathFromPathComponents(bParts)];
}
function discoverProbableSymlinks(files: ReadonlyArray<SourceFile>, getCanonicalFileName: GetCanonicalFileName, cwd: string): ReadonlyMap<string> {
const result = createMap<string>();
const symlinks = mapDefined(files, sf =>
sf.resolvedModules && firstDefinedIterator(sf.resolvedModules.values(), res =>
res && res.originalPath && res.resolvedFileName !== res.originalPath ? [res.resolvedFileName, res.originalPath] : undefined));
const result = createMap<string>();
if (symlinks) {
const currentDirectory = host.getCurrentDirectory ? host.getCurrentDirectory() : "";
const compareStrings = (!host.useCaseSensitiveFileNames || host.useCaseSensitiveFileNames()) ? compareStringsCaseSensitive : compareStringsCaseInsensitive;
for (const [resolvedPath, originalPath] of symlinks) {
const resolvedParts = getPathComponents(toPath(resolvedPath, currentDirectory, getCanonicalFileName));
const originalParts = getPathComponents(toPath(originalPath, currentDirectory, getCanonicalFileName));
while (compareStrings(resolvedParts[resolvedParts.length - 1], originalParts[originalParts.length - 1]) === Comparison.EqualTo) {
resolvedParts.pop();
originalParts.pop();
}
result.set(getPathFromPathComponents(originalParts), getPathFromPathComponents(resolvedParts));
}
for (const [resolvedPath, originalPath] of symlinks) {
const [commonResolved, commonOriginal] = guessDirectorySymlink(resolvedPath, originalPath, cwd, getCanonicalFileName);
result.set(commonOriginal, commonResolved);
}
return result;
}
function getAllModulePathsUsingIndirectSymlinks(files: ReadonlyArray<SourceFile>, target: string, getCanonicalFileName: (file: string) => string, host: ModuleSpecifierResolutionHost) {
const links = discoverProbableSymlinks(files, getCanonicalFileName, host);
const paths = arrayFrom(links.keys());
let options: string[] | undefined;
const compareStrings = (!host.useCaseSensitiveFileNames || host.useCaseSensitiveFileNames()) ? compareStringsCaseSensitive : compareStringsCaseInsensitive;
for (const path of paths) {
const resolved = links.get(path)!;
if (compareStrings(target.slice(0, resolved.length + 1), resolved + "/") === Comparison.EqualTo) {
const relative = getRelativePathFromDirectory(resolved, target, getCanonicalFileName);
const option = resolvePath(path, relative);
if (!host.fileExists || host.fileExists(option)) {
if (!options) options = [];
options.push(option);
}
}
}
if (options) {
options.push(target); // Since these are speculative, we also include the original resolved name as a possibility
return options;
}
return [target];
}
/**
* Looks for existing imports that use symlinks to this module.
* Only if no symlink is available, the real path will be used.
* Symlinks will be returned first so they are preferred over the real path.
*/
function getAllModulePaths(files: ReadonlyArray<SourceFile>, importedFileName: string, getCanonicalFileName: (file: string) => string, host: ModuleSpecifierResolutionHost): ReadonlyArray<string> {
const symlinks = mapDefined(files, sf =>
sf.resolvedModules && firstDefinedIterator(sf.resolvedModules.values(), res =>
res && res.resolvedFileName === importedFileName ? res.originalPath : undefined));
function getAllModulePaths(files: ReadonlyArray<SourceFile>, importingFileName: string, importedFileName: string, getCanonicalFileName: GetCanonicalFileName, host: ModuleSpecifierResolutionHost, redirectTargetsMap: RedirectTargetsMap): ReadonlyArray<string> {
const redirects = redirectTargetsMap.get(importedFileName);
const importedFileNames = redirects ? [...redirects, importedFileName] : [importedFileName];
const cwd = host.getCurrentDirectory ? host.getCurrentDirectory() : "";
const baseOptions = getAllModulePathsUsingIndirectSymlinks(files, getNormalizedAbsolutePath(importedFileName, cwd), getCanonicalFileName, host);
if (symlinks.length === 0) {
return baseOptions;
}
return deduplicate(concatenate(baseOptions, flatMap(symlinks, importedFileName => getAllModulePathsUsingIndirectSymlinks(files, getNormalizedAbsolutePath(importedFileName, cwd), getCanonicalFileName, host))));
const targets = importedFileNames.map(f => getNormalizedAbsolutePath(f, cwd));
const links = discoverProbableSymlinks(files, getCanonicalFileName, cwd);
const result: string[] = [];
const compareStrings = (!host.useCaseSensitiveFileNames || host.useCaseSensitiveFileNames()) ? compareStringsCaseSensitive : compareStringsCaseInsensitive;
links.forEach((resolved, path) => {
if (startsWithDirectory(importingFileName, resolved, getCanonicalFileName)) {
return; // Don't want to a package to globally import from itself
}
const target = targets.find(t => compareStrings(t.slice(0, resolved.length + 1), resolved + "/") === Comparison.EqualTo);
if (target === undefined) return;
const relative = getRelativePathFromDirectory(resolved, target, getCanonicalFileName);
const option = resolvePath(path, relative);
if (!host.fileExists || host.fileExists(option)) {
result.push(option);
}
});
result.push(...targets);
return result;
}
function getRelativePathNParents(relativePath: string): number {
+20 -6
View File
@@ -491,6 +491,8 @@ namespace ts {
visitNode(cbNode, (node as JSDocCallbackTag).typeExpression);
case SyntaxKind.JSDocThisTag:
return visitNode(cbNode, (node as JSDocThisTag).typeExpression);
case SyntaxKind.JSDocEnumTag:
return visitNode(cbNode, (node as JSDocEnumTag).typeExpression);
case SyntaxKind.JSDocSignature:
return visitNodes(cbNode, cbNodes, node.decorators) ||
visitNodes(cbNode, cbNodes, node.modifiers) ||
@@ -6398,7 +6400,7 @@ namespace ts {
switch (token()) {
case SyntaxKind.AtToken:
if (state === JSDocState.BeginningOfLine || state === JSDocState.SawAsterisk) {
removeTrailingNewlines(comments);
removeTrailingWhitespace(comments);
addTag(parseTag(indent));
// NOTE: According to usejsdoc.org, a tag goes to end of line, except the last tag.
// Real-world comments may break this rule, so "BeginningOfLine" will not be a real line beginning
@@ -6458,7 +6460,7 @@ namespace ts {
nextJSDocToken();
}
removeLeadingNewlines(comments);
removeTrailingNewlines(comments);
removeTrailingWhitespace(comments);
return createJSDocComment();
});
@@ -6468,8 +6470,8 @@ namespace ts {
}
}
function removeTrailingNewlines(comments: string[]) {
while (comments.length && (comments[comments.length - 1] === "\n" || comments[comments.length - 1] === "\r")) {
function removeTrailingWhitespace(comments: string[]) {
while (comments.length && comments[comments.length - 1].trim() === "") {
comments.pop();
}
}
@@ -6527,6 +6529,9 @@ namespace ts {
case "this":
tag = parseThisTag(atToken, tagName);
break;
case "enum":
tag = parseEnumTag(atToken, tagName);
break;
case "arg":
case "argument":
case "param":
@@ -6627,7 +6632,7 @@ namespace ts {
}
removeLeadingNewlines(comments);
removeTrailingNewlines(comments);
removeTrailingWhitespace(comments);
return comments.length === 0 ? undefined : comments.join("");
}
@@ -6817,6 +6822,15 @@ namespace ts {
return finishNode(tag);
}
function parseEnumTag(atToken: AtToken, tagName: Identifier): JSDocEnumTag {
const tag = <JSDocEnumTag>createNode(SyntaxKind.JSDocEnumTag, atToken.pos);
tag.atToken = atToken;
tag.tagName = tagName;
tag.typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true);
skipWhitespace();
return finishNode(tag);
}
function parseTypedefTag(atToken: AtToken, tagName: Identifier, indent: number): JSDocTypedefTag {
const typeExpression = tryParseTypeExpression();
skipWhitespace();
@@ -7030,8 +7044,8 @@ namespace ts {
skipWhitespace();
const typeParameter = <TypeParameterDeclaration>createNode(SyntaxKind.TypeParameter);
typeParameter.name = parseJSDocIdentifierName(Diagnostics.Unexpected_token_A_type_parameter_name_was_expected_without_curly_braces);
skipWhitespace();
finishNode(typeParameter);
skipWhitespace();
typeParameters.push(typeParameter);
} while (parseOptionalJsdoc(SyntaxKind.CommaToken));
+11 -19
View File
@@ -634,7 +634,8 @@ namespace ts {
const packageIdToSourceFile = createMap<SourceFile>();
// Maps from a SourceFile's `.path` to the name of the package it was imported with.
let sourceFileToPackageName = createMap<string>();
let redirectTargetsSet = createMap<true>();
// 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>();
let missingFilePaths: ReadonlyArray<Path> | undefined;
@@ -752,7 +753,7 @@ namespace ts {
getSourceFileFromReference,
getLibFileFromReference,
sourceFileToPackageName,
redirectTargetsSet,
redirectTargetsMap,
isEmittedFile,
getConfigFileParsingDiagnostics,
getResolvedModuleWithFailedLookupLocationsFromCache,
@@ -1073,7 +1074,7 @@ namespace ts {
fileChanged = false;
newSourceFile = oldSourceFile; // Use the redirect.
}
else if (oldProgram.redirectTargetsSet.has(oldSourceFile.path)) {
else if (oldProgram.redirectTargetsMap.has(oldSourceFile.path)) {
// If a redirected-to source file changes, the redirect may be broken.
if (newSourceFile !== oldSourceFile) {
return oldProgram.structureIsReused = StructureIsReused.Not;
@@ -1220,7 +1221,7 @@ namespace ts {
resolvedProjectReferences = oldProgram.getProjectReferences();
sourceFileToPackageName = oldProgram.sourceFileToPackageName;
redirectTargetsSet = oldProgram.redirectTargetsSet;
redirectTargetsMap = oldProgram.redirectTargetsMap;
return oldProgram.structureIsReused = StructureIsReused.Completely;
}
@@ -2079,7 +2080,7 @@ namespace ts {
// Some other SourceFile already exists with this package name and version.
// Instead of creating a duplicate, just redirect to the existing one.
const dupFile = createRedirectSourceFile(fileFromPackageId, file!, fileName, path); // TODO: GH#18217
redirectTargetsSet.set(fileFromPackageId.path, true);
redirectTargetsMap.add(fileFromPackageId.path, fileName);
filesByName.set(path, dupFile);
sourceFileToPackageName.set(path, packageId.name);
processingOtherFiles!.push(dupFile);
@@ -2711,12 +2712,12 @@ namespace ts {
function createDiagnosticForReference(index: number, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number) {
const referencesSyntax = getProjectReferencesSyntax();
if (referencesSyntax) {
if (createOptionDiagnosticInArrayLiteralSyntax(referencesSyntax, index, message, arg0, arg1)) {
return;
}
if (referencesSyntax && referencesSyntax.elements.length > index) {
programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, referencesSyntax.elements[index], message, arg0, arg1));
}
else {
programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1));
}
programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1));
}
function createDiagnosticForOption(onKey: boolean, option1: string, option2: string | undefined, message: DiagnosticMessage, arg0: string | number, arg1?: string | number, arg2?: string | number) {
@@ -2769,15 +2770,6 @@ namespace ts {
return !!props.length;
}
function createOptionDiagnosticInArrayLiteralSyntax(arrayLiteral: ArrayLiteralExpression, index: number, message: DiagnosticMessage, arg0: string | number | undefined, arg1?: string | number, arg2?: string | number): boolean {
if (arrayLiteral.elements.length <= index) {
// Out-of-bounds
return false;
}
programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, arrayLiteral.elements[index], message, arg0, arg1, arg2));
return false; // TODO: GH#18217 This function always returns `false`!`
}
function blockEmittingOfFile(emitFileName: string, diag: Diagnostic) {
hasEmitBlockingDiagnostics.set(toPath(emitFileName), true);
programDiagnostics.add(diag);
+17 -6
View File
@@ -52,7 +52,7 @@ namespace ts {
getGlobalCache?(): string | undefined;
writeLog(s: string): void;
maxNumberOfFilesToIterateForInvalidation?: number;
getCurrentProgram(): Program;
getCurrentProgram(): Program | undefined;
}
interface DirectoryWatchesOfFailedLookup {
@@ -398,8 +398,17 @@ namespace ts {
function getDirectoryToWatchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path): DirectoryOfFailedLookupWatch {
if (isInDirectoryPath(rootPath, failedLookupLocationPath)) {
// Always watch root directory recursively
return { dir: rootDir!, dirPath: rootPath }; // TODO: GH#18217
failedLookupLocation = isRootedDiskPath(failedLookupLocation) ? failedLookupLocation : getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory());
Debug.assert(failedLookupLocation.length === failedLookupLocationPath.length, `FailedLookup: ${failedLookupLocation} failedLookupLocationPath: ${failedLookupLocationPath}`); // tslint:disable-line
const subDirectoryInRoot = failedLookupLocationPath.indexOf(directorySeparator, rootPath.length + 1);
if (subDirectoryInRoot !== -1) {
// Instead of watching root, watch directory in root to avoid watching excluded directories not needed for module resolution
return { dir: failedLookupLocation.substr(0, subDirectoryInRoot), dirPath: failedLookupLocationPath.substr(0, subDirectoryInRoot) as Path };
}
else {
// Always watch root directory non recursively
return { dir: rootDir!, dirPath: rootPath, nonRecursive: false }; // TODO: GH#18217
}
}
return getDirectoryToWatchFromFailedLookupLocationDirectory(
@@ -478,6 +487,7 @@ namespace ts {
customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1);
}
if (dirPath === rootPath) {
Debug.assert(!nonRecursive);
setAtRoot = true;
}
else {
@@ -487,8 +497,8 @@ namespace ts {
}
if (setAtRoot) {
// This is always recursive
setDirectoryWatcher(rootDir!, rootPath); // TODO: GH#18217
// This is always non recursive
setDirectoryWatcher(rootDir!, rootPath, /*nonRecursive*/ true); // TODO: GH#18217
}
}
@@ -497,7 +507,8 @@ namespace ts {
}
function watchFailedLookupLocationOfNonRelativeModuleResolutions(resolutions: ResolutionWithFailedLookupLocations[], name: string) {
const updateResolution = resolutionHost.getCurrentProgram().getTypeChecker().tryFindAmbientModuleWithoutAugmentations(name) ?
const program = resolutionHost.getCurrentProgram();
const updateResolution = program && program.getTypeChecker().tryFindAmbientModuleWithoutAugmentations(name) ?
setRefCountToUndefined : watchFailedLookupLocationOfResolution;
resolutions.forEach(updateResolution);
}
+8 -2
View File
@@ -1752,6 +1752,12 @@ namespace ts {
type SerializedEntityNameAsExpression = Identifier | BinaryExpression | PropertyAccessExpression;
type SerializedTypeNode = SerializedEntityNameAsExpression | VoidExpression | ConditionalExpression;
function getAccessorTypeNode(node: AccessorDeclaration) {
const accessors = resolver.getAllAccessorDeclarations(node);
return accessors.setAccessor && getSetAccessorTypeAnnotationNode(accessors.setAccessor)
|| accessors.getAccessor && getEffectiveReturnTypeNode(accessors.getAccessor);
}
/**
* Serializes the type of a node for use with decorator type metadata.
*
@@ -1761,10 +1767,10 @@ namespace ts {
switch (node.kind) {
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.Parameter:
case SyntaxKind.GetAccessor:
return serializeTypeNode((<PropertyDeclaration | ParameterDeclaration | GetAccessorDeclaration>node).type);
case SyntaxKind.SetAccessor:
return serializeTypeNode(getSetAccessorTypeAnnotationNode(<SetAccessorDeclaration>node));
case SyntaxKind.GetAccessor:
return serializeTypeNode(getAccessorTypeNode(node as AccessorDeclaration));
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ClassExpression:
case SyntaxKind.MethodDeclaration:
+1 -1
View File
@@ -1268,7 +1268,7 @@ namespace ts {
// Don't report status on "solution" projects
break;
default:
assertTypeIsNever(status);
assertType<never>(status);
}
}
}
+19 -5
View File
@@ -372,6 +372,7 @@ namespace ts {
JSDocAugmentsTag,
JSDocClassTag,
JSDocCallbackTag,
JSDocEnumTag,
JSDocParameterTag,
JSDocReturnTag,
JSDocThisTag,
@@ -581,6 +582,7 @@ namespace ts {
| FunctionTypeNode
| ConstructorTypeNode
| JSDocFunctionType
| ExportDeclaration
| EndOfFileToken;
export type HasType =
@@ -858,6 +860,7 @@ namespace ts {
name?: PropertyName;
}
/** Unlike ObjectLiteralElement, excludes JSXAttribute and JSXSpreadAttribute. */
export type ObjectLiteralElementLike
= PropertyAssignment
| ShorthandPropertyAssignment
@@ -2202,7 +2205,7 @@ namespace ts {
name: Identifier;
}
export interface ExportDeclaration extends DeclarationStatement {
export interface ExportDeclaration extends DeclarationStatement, JSDocContainer {
kind: SyntaxKind.ExportDeclaration;
parent: SourceFile | ModuleBlock;
/** Will not be assigned in the case of `export * from "foo";` */
@@ -2348,6 +2351,11 @@ namespace ts {
kind: SyntaxKind.JSDocClassTag;
}
export interface JSDocEnumTag extends JSDocTag {
kind: SyntaxKind.JSDocEnumTag;
typeExpression?: JSDocTypeExpression;
}
export interface JSDocThisTag extends JSDocTag {
kind: SyntaxKind.JSDocThisTag;
typeExpression?: JSDocTypeExpression;
@@ -2543,7 +2551,7 @@ namespace ts {
/**
* If two source files are for the same version of the same package, one will redirect to the other.
* (See `createRedirectSourceFile` in program.ts.)
* The redirect will have this set. The redirected-to source file will be in `redirectTargetsSet`.
* The redirect will have this set. The redirected-to source file will be in `redirectTargetsMap`.
*/
/* @internal */ redirectInfo?: RedirectInfo;
@@ -2790,7 +2798,7 @@ namespace ts {
/** Given a source file, get the name of the package it was imported from. */
/* @internal */ sourceFileToPackageName: Map<string>;
/** Set of all source files that some other source file redirects to. */
/* @internal */ redirectTargetsSet: Map<true>;
/* @internal */ redirectTargetsMap: MultiMap<string>;
/** Is the file emitted file */
/* @internal */ isEmittedFile(file: string): boolean;
@@ -2799,6 +2807,9 @@ namespace ts {
getProjectReferences(): (ResolvedProjectReference | undefined)[] | undefined;
}
/* @internal */
export type RedirectTargetsMap = ReadonlyMap<ReadonlyArray<string>>;
export interface ResolvedProjectReference {
commandLine: ParsedCommandLine;
sourceFile: SourceFile;
@@ -2876,6 +2887,8 @@ namespace ts {
getSourceFiles(): ReadonlyArray<SourceFile>;
getSourceFile(fileName: string): SourceFile | undefined;
getResolvedTypeReferenceDirectives(): ReadonlyMap<ResolvedTypeReferenceDirective>;
readonly redirectTargetsMap: RedirectTargetsMap;
}
export interface TypeChecker {
@@ -2964,6 +2977,7 @@ namespace ts {
* @param argumentCount Apparent number of arguments, passed in case of a possibly incomplete call. This should come from an ArgumentListInfo. See `signatureHelp.ts`.
*/
getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined;
/* @internal */ getResolvedSignatureForSignatureHelp(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined;
getSignatureFromDeclaration(declaration: SignatureDeclaration): Signature | undefined;
isImplementationOfOverload(node: SignatureDeclaration): boolean | undefined;
isUndefinedSymbol(symbol: Symbol): boolean;
@@ -3306,8 +3320,8 @@ namespace ts {
export interface AllAccessorDeclarations {
firstAccessor: AccessorDeclaration;
secondAccessor: AccessorDeclaration | undefined;
getAccessor: AccessorDeclaration | undefined;
setAccessor: AccessorDeclaration | undefined;
getAccessor: GetAccessorDeclaration | undefined;
setAccessor: SetAccessorDeclaration | undefined;
}
/** Indicates how to serialize the name for a TypeReferenceNode when emitting decorator metadata */
+111 -26
View File
@@ -563,11 +563,6 @@ namespace ts {
return isString(value) ? '"' + escapeNonAsciiString(value) + '"' : "" + value;
}
// Add an extra underscore to identifiers that start with two underscores to avoid issues with magic names like '__proto__'
export function escapeLeadingUnderscores(identifier: string): __String {
return (identifier.length >= 2 && identifier.charCodeAt(0) === CharacterCodes._ && identifier.charCodeAt(1) === CharacterCodes._ ? "_" + identifier : identifier) as __String;
}
// Make an identifier from an external module name by extracting the string after the last "/" and replacing
// all non-alphanumeric characters with underscores
export function makeIdentifierFromModuleName(moduleName: string): string {
@@ -676,6 +671,19 @@ namespace ts {
export function isDeclarationWithTypeParameters(node: Node): node is DeclarationWithTypeParameters;
export function isDeclarationWithTypeParameters(node: DeclarationWithTypeParameters): node is DeclarationWithTypeParameters {
switch (node.kind) {
case SyntaxKind.JSDocCallbackTag:
case SyntaxKind.JSDocTypedefTag:
case SyntaxKind.JSDocSignature:
return true;
default:
assertType<DeclarationWithTypeParameterChildren>(node);
return isDeclarationWithTypeParameterChildren(node);
}
}
export function isDeclarationWithTypeParameterChildren(node: Node): node is DeclarationWithTypeParameterChildren;
export function isDeclarationWithTypeParameterChildren(node: DeclarationWithTypeParameterChildren): node is DeclarationWithTypeParameterChildren {
switch (node.kind) {
case SyntaxKind.CallSignature:
case SyntaxKind.ConstructSignature:
@@ -696,12 +704,9 @@ namespace ts {
case SyntaxKind.SetAccessor:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.JSDocCallbackTag:
case SyntaxKind.JSDocTypedefTag:
case SyntaxKind.JSDocSignature:
return true;
default:
assertTypeIsNever(node);
assertType<never>(node);
return false;
}
}
@@ -786,7 +791,7 @@ namespace ts {
return createDiagnosticForNodeInSourceFile(sourceFile, node, message, arg0, arg1, arg2, arg3);
}
export function createDiagnosticForNodeArray(sourceFile: SourceFile, nodes: NodeArray<Node>, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic {
export function createDiagnosticForNodeArray(sourceFile: SourceFile, nodes: NodeArray<Node>, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): DiagnosticWithLocation {
const start = skipTrivia(sourceFile.text, nodes.pos);
return createFileDiagnostic(sourceFile, start, nodes.end - start, message, arg0, arg1, arg2, arg3);
}
@@ -3305,8 +3310,8 @@ namespace ts {
// TODO: GH#18217
let firstAccessor!: AccessorDeclaration;
let secondAccessor!: AccessorDeclaration;
let getAccessor!: AccessorDeclaration;
let setAccessor!: AccessorDeclaration;
let getAccessor!: GetAccessorDeclaration;
let setAccessor!: SetAccessorDeclaration;
if (hasDynamicName(accessor)) {
firstAccessor = accessor;
if (accessor.kind === SyntaxKind.GetAccessor) {
@@ -3334,11 +3339,11 @@ namespace ts {
}
if (member.kind === SyntaxKind.GetAccessor && !getAccessor) {
getAccessor = <AccessorDeclaration>member;
getAccessor = <GetAccessorDeclaration>member;
}
if (member.kind === SyntaxKind.SetAccessor && !setAccessor) {
setAccessor = <AccessorDeclaration>member;
setAccessor = <SetAccessorDeclaration>member;
}
}
}
@@ -4794,6 +4799,11 @@ namespace ts {
return undefined;
}
/** Add an extra underscore to identifiers that start with two underscores to avoid issues with magic names like '__proto__' */
export function escapeLeadingUnderscores(identifier: string): __String {
return (identifier.length >= 2 && identifier.charCodeAt(0) === CharacterCodes._ && identifier.charCodeAt(1) === CharacterCodes._ ? "_" + identifier : identifier) as __String;
}
/**
* Remove extra underscore from escaped identifier text content.
*
@@ -4985,6 +4995,11 @@ namespace ts {
return getFirstJSDocTag(node, isJSDocClassTag);
}
/** Gets the JSDoc enum tag for the node if present */
export function getJSDocEnumTag(node: Node): JSDocEnumTag | undefined {
return getFirstJSDocTag(node, isJSDocEnumTag);
}
/** Gets the JSDoc this tag for the node if present */
export function getJSDocThisTag(node: Node): JSDocThisTag | undefined {
return getFirstJSDocTag(node, isJSDocThisTag);
@@ -5749,6 +5764,10 @@ namespace ts {
return node.kind === SyntaxKind.JSDocClassTag;
}
export function isJSDocEnumTag(node: Node): node is JSDocEnumTag {
return node.kind === SyntaxKind.JSDocEnumTag;
}
export function isJSDocThisTag(node: Node): node is JSDocThisTag {
return node.kind === SyntaxKind.JSDocThisTag;
}
@@ -6622,18 +6641,7 @@ namespace ts {
}
export function isObjectLiteralElement(node: Node): node is ObjectLiteralElement {
switch (node.kind) {
case SyntaxKind.JsxAttribute:
case SyntaxKind.JsxSpreadAttribute:
case SyntaxKind.PropertyAssignment:
case SyntaxKind.ShorthandPropertyAssignment:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
return true;
default:
return false;
}
return node.kind === SyntaxKind.JsxAttribute || node.kind === SyntaxKind.JsxSpreadAttribute || isObjectLiteralElementLike(node);
}
/* @internal */
@@ -7149,6 +7157,12 @@ namespace ts {
return path.slice(0, Math.max(rootLength, path.lastIndexOf(directorySeparator)));
}
export function startsWithDirectory(fileName: string, directoryName: string, getCanonicalFileName: GetCanonicalFileName): boolean {
const canonicalFileName = getCanonicalFileName(fileName);
const canonicalDirectoryName = getCanonicalFileName(directoryName);
return startsWith(canonicalFileName, canonicalDirectoryName + "/") || startsWith(canonicalFileName, canonicalDirectoryName + "\\");
}
export function isUrl(path: string) {
return getEncodedRootLength(path) < 0;
}
@@ -8128,4 +8142,75 @@ namespace ts {
}
return { min, max };
}
export interface ReadonlyNodeSet<TNode extends Node> {
has(node: TNode): boolean;
forEach(cb: (node: TNode) => void): void;
some(pred: (node: TNode) => boolean): boolean;
}
export class NodeSet<TNode extends Node> implements ReadonlyNodeSet<TNode> {
private map = createMap<TNode>();
add(node: TNode): void {
this.map.set(String(getNodeId(node)), node);
}
tryAdd(node: TNode): boolean {
if (this.has(node)) return false;
this.add(node);
return true;
}
has(node: TNode): boolean {
return this.map.has(String(getNodeId(node)));
}
forEach(cb: (node: TNode) => void): void {
this.map.forEach(cb);
}
some(pred: (node: TNode) => boolean): boolean {
return forEachEntry(this.map, pred) || false;
}
}
export interface ReadonlyNodeMap<TNode extends Node, TValue> {
get(node: TNode): TValue | undefined;
has(node: TNode): boolean;
}
export class NodeMap<TNode extends Node, TValue> implements ReadonlyNodeMap<TNode, TValue> {
private map = createMap<{ node: TNode, value: TValue }>();
get(node: TNode): TValue | undefined {
const res = this.map.get(String(getNodeId(node)));
return res && res.value;
}
getOrUpdate(node: TNode, setValue: () => TValue): TValue {
const res = this.get(node);
if (res) return res;
const value = setValue();
this.set(node, value);
return value;
}
set(node: TNode, value: TValue): void {
this.map.set(String(getNodeId(node)), { node, value });
}
has(node: TNode): boolean {
return this.map.has(String(getNodeId(node)));
}
forEach(cb: (value: TValue, node: TNode) => void): void {
this.map.forEach(({ node, value }) => cb(value, node));
}
}
export function rangeOfNode(node: Node): TextRange {
return { pos: getTokenPosOfNode(node), end: node.end };
}
export function rangeOfTypeParameters(typeParameters: NodeArray<TypeParameterDeclaration>): TextRange {
// Include the `<>`
return { pos: typeParameters.pos - 1, end: typeParameters.end + 1 };
}
}
+2 -2
View File
@@ -1496,8 +1496,8 @@ Actual: ${stringify(fullActual)}`);
const actualTags = selectedItem.tags;
assert.equal(actualTags.length, (options.tags || ts.emptyArray).length, this.assertionMessageAtLastKnownMarker("signature help tags"));
ts.zipWith((options.tags || ts.emptyArray), actualTags, (expectedTag, actualTag) => {
assert.equal(expectedTag.name, actualTag.name);
assert.equal(expectedTag.text, actualTag.text, this.assertionMessageAtLastKnownMarker("signature help tag " + actualTag.name));
assert.equal(actualTag.name, expectedTag.name);
assert.equal(actualTag.text, expectedTag.text, this.assertionMessageAtLastKnownMarker("signature help tag " + actualTag.name));
});
const allKeys: ReadonlyArray<keyof FourSlashInterface.VerifySignatureHelpOptions> = [
+9 -2
View File
@@ -75,10 +75,17 @@ class TypeWriterWalker {
const sourceText = ts.getSourceTextOfNodeFromSourceFile(this.currentSourceFile, node);
if (!isSymbolWalk) {
// Don't try to get the type of something that's already a type.
// Exception for `T` in `type T = something` because that may evaluate to some interesting type.
if (ts.isPartOfTypeNode(node) || ts.isIdentifier(node) && !(ts.getMeaningFromDeclaration(node.parent) & ts.SemanticMeaning.Value) && !(ts.isTypeAlias(node.parent) && node.parent.name === node)) {
return undefined;
}
// Workaround to ensure we output 'C' instead of 'typeof C' for base class expressions
// let type = this.checker.getTypeAtLocation(node);
const type = node.parent && ts.isExpressionWithTypeArgumentsInClassExtendsClause(node.parent) && this.checker.getTypeAtLocation(node.parent) || this.checker.getTypeAtLocation(node);
const typeString = type ? this.checker.typeToString(type, node.parent, ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.AllowUniqueESSymbolType) : "No type information available!";
let type = ts.isExpressionWithTypeArgumentsInClassExtendsClause(node.parent) ? this.checker.getTypeAtLocation(node.parent) : undefined;
if (!type || type.flags & ts.TypeFlags.Any) type = this.checker.getTypeAtLocation(node);
const typeString = this.checker.typeToString(type, node.parent, ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.AllowUniqueESSymbolType);
return {
line: lineAndCharacter.line,
syntaxKind: node.kind,
+6 -3
View File
@@ -583,6 +583,10 @@ interface Array<T> {}`
return;
}
this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Created);
if (isFsFolder(fileOrDirectory)) {
this.invokeDirectoryWatcher(fileOrDirectory.fullPath, "");
this.invokeWatchedDirectoriesRecursiveCallback(fileOrDirectory.fullPath, "");
}
this.invokeDirectoryWatcher(folder.fullPath, fileOrDirectory.fullPath);
}
@@ -599,12 +603,11 @@ interface Array<T> {}`
this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted);
if (isFsFolder(fileOrDirectory)) {
Debug.assert(fileOrDirectory.entries.length === 0 || isRenaming);
const relativePath = this.getRelativePathToDirectory(fileOrDirectory.fullPath, fileOrDirectory.fullPath);
// Invoke directory and recursive directory watcher for the folder
// Here we arent invoking recursive directory watchers for the base folders
// since that is something we would want to do for both file as well as folder we are deleting
this.invokeWatchedDirectoriesCallback(fileOrDirectory.fullPath, relativePath);
this.invokeWatchedDirectoriesRecursiveCallback(fileOrDirectory.fullPath, relativePath);
this.invokeWatchedDirectoriesCallback(fileOrDirectory.fullPath, "");
this.invokeWatchedDirectoriesRecursiveCallback(fileOrDirectory.fullPath, "");
}
if (basePath !== fileOrDirectory.path) {
+2313 -1900
View File
File diff suppressed because it is too large Load Diff
+32 -4
View File
@@ -31,10 +31,13 @@ interface DOMStringList {
interface DOMTokenList {
[Symbol.iterator](): IterableIterator<string>;
entries(): IterableIterator<[number, string]>;
keys(): IterableIterator<number>;
values(): IterableIterator<string>;
}
interface DataTransferItemList {
[Symbol.iterator](): IterableIterator<File>;
[Symbol.iterator](): IterableIterator<DataTransferItem>;
}
interface FileList {
@@ -67,9 +70,10 @@ interface HTMLCollectionBase {
interface HTMLCollectionOf<T extends Element> {
[Symbol.iterator](): IterableIterator<T>;
entries(): IterableIterator<[number, T]>;
keys(): IterableIterator<number>;
values(): IterableIterator<T>;
}
interface HTMLFormElement {
[Symbol.iterator](): IterableIterator<Element>;
}
interface HTMLSelectElement {
@@ -147,10 +151,34 @@ interface PluginArray {
interface RTCStatsReport extends ReadonlyMap<string, any> {
}
interface SVGLengthList {
[Symbol.iterator](): IterableIterator<SVGLength>;
}
interface SVGNumberList {
[Symbol.iterator](): IterableIterator<SVGNumber>;
}
interface SVGStringList {
[Symbol.iterator](): IterableIterator<string>;
}
interface SourceBufferList {
[Symbol.iterator](): IterableIterator<SourceBuffer>;
}
interface SpeechGrammarList {
[Symbol.iterator](): IterableIterator<SpeechGrammar>;
}
interface SpeechRecognitionResult {
[Symbol.iterator](): IterableIterator<SpeechRecognitionAlternative>;
}
interface SpeechRecognitionResultList {
[Symbol.iterator](): IterableIterator<SpeechRecognitionResult>;
}
interface StyleSheetList {
[Symbol.iterator](): IterableIterator<StyleSheet>;
}
+477 -94
View File
File diff suppressed because it is too large Load Diff
+87 -32
View File
@@ -326,6 +326,13 @@ namespace ts.server {
syntaxOnly?: boolean;
}
interface OriginalFileInfo { fileName: NormalizedPath; path: Path; }
type OpenScriptInfoOrClosedFileInfo = ScriptInfo | OriginalFileInfo;
function isOpenScriptInfo(infoOrFileName: OpenScriptInfoOrClosedFileInfo): infoOrFileName is ScriptInfo {
return !!(infoOrFileName as ScriptInfo).containingProjects;
}
function getDetailWatchInfo(watchType: WatchType, project: Project | undefined) {
return `Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType}`;
}
@@ -700,7 +707,7 @@ namespace ts.server {
/* @internal */
private forEachProject(cb: (project: Project) => void) {
for (const p of this.inferredProjects) cb(p);
this.inferredProjects.forEach(cb);
this.configuredProjects.forEach(cb);
this.externalProjects.forEach(cb);
}
@@ -1044,12 +1051,12 @@ namespace ts.server {
}
}
private configFileExists(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) {
private configFileExists(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: OpenScriptInfoOrClosedFileInfo) {
let configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath);
if (configFileExistenceInfo) {
// By default the info would get impacted by presence of config file since its in the detection path
// Only adding the info as a root to inferred project will need the existence to be watched by file watcher
if (!configFileExistenceInfo.openFilesImpactedByConfigFile.has(info.path)) {
if (isOpenScriptInfo(info) && !configFileExistenceInfo.openFilesImpactedByConfigFile.has(info.path)) {
configFileExistenceInfo.openFilesImpactedByConfigFile.set(info.path, false);
this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.OpenFilesImpactedByConfigFileAdd);
}
@@ -1066,9 +1073,11 @@ namespace ts.server {
// Or the whole chain of config files for the roots of the inferred projects
// Cache the host value of file exists and add the info to map of open files impacted by this config file
const openFilesImpactedByConfigFile = createMap<boolean>();
openFilesImpactedByConfigFile.set(info.path, false);
const exists = this.host.fileExists(configFileName);
const openFilesImpactedByConfigFile = createMap<boolean>();
if (isOpenScriptInfo(info)) {
openFilesImpactedByConfigFile.set(info.path, false);
}
configFileExistenceInfo = { exists, openFilesImpactedByConfigFile };
this.configFileExistenceInfoCache.set(canonicalConfigFilePath, configFileExistenceInfo);
this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.OpenFilesImpactedByConfigFileAdd);
@@ -1180,7 +1189,7 @@ namespace ts.server {
*/
private stopWatchingConfigFilesForClosedScriptInfo(info: ScriptInfo) {
Debug.assert(!info.isScriptOpen());
this.forEachConfigFileLocation(info, /*infoShouldBeOpen*/ true, (configFileName, canonicalConfigFilePath) => {
this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => {
const configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath);
if (configFileExistenceInfo) {
const infoIsRootOfInferredProject = configFileExistenceInfo.openFilesImpactedByConfigFile.get(info.path);
@@ -1214,7 +1223,7 @@ namespace ts.server {
/* @internal */
startWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo) {
Debug.assert(info.isScriptOpen());
this.forEachConfigFileLocation(info, /*infoShouldBeOpen*/ true, (configFileName, canonicalConfigFilePath) => {
this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => {
let configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath);
if (!configFileExistenceInfo) {
// Create the cache
@@ -1242,7 +1251,7 @@ namespace ts.server {
*/
/* @internal */
stopWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo) {
this.forEachConfigFileLocation(info, /*infoShouldBeOpen*/ true, (configFileName, canonicalConfigFilePath) => {
this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => {
const configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath);
if (configFileExistenceInfo && configFileExistenceInfo.openFilesImpactedByConfigFile.has(info.path)) {
Debug.assert(info.isScriptOpen());
@@ -1265,12 +1274,12 @@ namespace ts.server {
* The server must start searching from the directory containing
* the newly opened file.
*/
private forEachConfigFileLocation(info: ScriptInfo, infoShouldBeOpen: boolean, action: (configFileName: NormalizedPath, canonicalConfigFilePath: string) => boolean | void) {
private forEachConfigFileLocation(info: OpenScriptInfoOrClosedFileInfo, action: (configFileName: NormalizedPath, canonicalConfigFilePath: string) => boolean | void) {
if (this.syntaxOnly) {
return undefined;
}
Debug.assert(!infoShouldBeOpen || this.openFiles.has(info.path));
Debug.assert(!isOpenScriptInfo(info) || this.openFiles.has(info.path));
const projectRootPath = this.openFiles.get(info.path);
let searchPath = asNormalizedPath(getDirectoryPath(info.fileName));
@@ -1309,11 +1318,13 @@ namespace ts.server {
* current directory (the directory in which tsc was invoked).
* The server must start searching from the directory containing
* the newly opened file.
* If script info is passed in, it is asserted to be open script info
* otherwise just file name
*/
private getConfigFileNameForFile(info: ScriptInfo, infoShouldBeOpen: boolean) {
if (infoShouldBeOpen) Debug.assert(info.isScriptOpen());
private getConfigFileNameForFile(info: OpenScriptInfoOrClosedFileInfo) {
if (isOpenScriptInfo(info)) Debug.assert(info.isScriptOpen());
this.logger.info(`Search path: ${getDirectoryPath(info.fileName)}`);
const configFileName = this.forEachConfigFileLocation(info, infoShouldBeOpen, (configFileName, canonicalConfigFilePath) =>
const configFileName = this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) =>
this.configFileExists(configFileName, canonicalConfigFilePath, info));
if (configFileName) {
this.logger.info(`For info: ${info.fileName} :: Config file name: ${configFileName}`);
@@ -1683,10 +1694,12 @@ namespace ts.server {
if (!this.eventHandler || this.suppressDiagnosticEvents) {
return;
}
const diagnostics = project.getLanguageService().getCompilerOptionsDiagnostics();
diagnostics.push(...project.getAllProjectErrors());
this.eventHandler(<ConfigFileDiagEvent>{
eventName: ConfigFileDiagEvent,
data: { configFileName: project.getConfigFilePath(), diagnostics: project.getAllProjectErrors(), triggerFile }
data: { configFileName: project.getConfigFilePath(), diagnostics, triggerFile }
});
}
@@ -2003,7 +2016,7 @@ namespace ts.server {
// we first detect if there is already a configured project created for it: if so,
// we re- read the tsconfig file content and update the project only if we havent already done so
// otherwise we create a new one.
const configFileName = this.getConfigFileNameForFile(info, /*infoShouldBeOpen*/ true);
const configFileName = this.getConfigFileNameForFile(info);
if (configFileName) {
const project = this.findConfiguredProjectByProjectName(configFileName);
if (!project) {
@@ -2091,17 +2104,40 @@ namespace ts.server {
return this.openClientFileWithNormalizedPath(toNormalizedPath(fileName), fileContent, scriptKind, /*hasMixedContent*/ false, projectRootPath ? toNormalizedPath(projectRootPath) : undefined);
}
/** @internal */
getProjectForFileWithoutOpening(fileName: NormalizedPath): { readonly scriptInfo: ScriptInfo, readonly projects: ReadonlyArray<Project> } | undefined {
const scriptInfo = this.filenameToScriptInfo.get(fileName) ||
this.getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(fileName, this.currentDirectory, /*fileContent*/ undefined, /*scriptKind*/ undefined, /*hasMixedContent*/ undefined);
if (!scriptInfo) return undefined;
if (scriptInfo.containingProjects.length) {
return { scriptInfo, projects: scriptInfo.containingProjects };
/*@internal*/
getOriginalLocationEnsuringConfiguredProject(project: Project, location: sourcemaps.SourceMappableLocation): sourcemaps.SourceMappableLocation | undefined {
const originalLocation = project.getSourceMapper().tryGetOriginalLocation(location);
if (!originalLocation) return undefined;
const { fileName } = originalLocation;
if (!this.getScriptInfo(fileName) && !this.host.fileExists(fileName)) return undefined;
const originalFileInfo: OriginalFileInfo = { fileName: toNormalizedPath(fileName), path: this.toPath(fileName) };
const configFileName = this.getConfigFileNameForFile(originalFileInfo);
if (!configFileName) return undefined;
const configuredProject = this.findConfiguredProjectByProjectName(configFileName) || this.createConfiguredProject(configFileName);
updateProjectIfDirty(configuredProject);
// Keep this configured project as referenced from project
addOriginalConfiguredProject(configuredProject);
const originalScriptInfo = this.getScriptInfo(fileName);
if (!originalScriptInfo || !originalScriptInfo.containingProjects.length) return undefined;
// Add configured projects as referenced
originalScriptInfo.containingProjects.forEach(project => {
if (project.projectKind === ProjectKind.Configured) {
addOriginalConfiguredProject(project as ConfiguredProject);
}
});
return originalLocation;
function addOriginalConfiguredProject(originalProject: ConfiguredProject) {
if (!project.originalConfiguredProjects) {
project.originalConfiguredProjects = createMap<true>();
}
project.originalConfiguredProjects.set(originalProject.canonicalConfigFilePath, true);
}
const configFileName = this.getConfigFileNameForFile(scriptInfo, /*infoShouldBeOpen*/ false);
const project = configFileName === undefined ? undefined : this.findConfiguredProjectByProjectName(configFileName) || this.createConfiguredProject(configFileName);
return project && project.containsScriptInfo(scriptInfo) ? { scriptInfo, projects: [project] } : undefined;
}
/** @internal */
@@ -2126,7 +2162,7 @@ namespace ts.server {
this.openFiles.set(info.path, projectRootPath);
let project: ConfiguredProject | ExternalProject | undefined = this.findExternalProjectContainingOpenScriptInfo(info);
if (!project && !this.syntaxOnly) { // Checking syntaxOnly is an optimization
configFileName = this.getConfigFileNameForFile(info, /*infoShouldBeOpen*/ true);
configFileName = this.getConfigFileNameForFile(info);
if (configFileName) {
project = this.findConfiguredProjectByProjectName(configFileName);
if (!project) {
@@ -2164,14 +2200,9 @@ namespace ts.server {
}
Debug.assert(!info.isOrphan());
// Remove the configured projects that have zero references from open files.
// This was postponed from closeOpenFile to after opening next file,
// so that we can reuse the project if we need to right away
this.configuredProjects.forEach(project => {
if (!project.hasOpenRef()) {
this.removeProject(project);
}
});
this.removeOrphanConfiguredProjects();
// Remove orphan inferred projects now that we have reused projects
// We need to create a duplicate because we cant guarantee order after removal
@@ -2199,6 +2230,30 @@ namespace ts.server {
return { configFileName, configFileErrors };
}
private removeOrphanConfiguredProjects() {
const toRemoveConfiguredProjects = cloneMap(this.configuredProjects);
// Do not remove configured projects that are used as original projects of other
this.inferredProjects.forEach(markOriginalProjectsAsUsed);
this.externalProjects.forEach(markOriginalProjectsAsUsed);
this.configuredProjects.forEach(project => {
// If project has open ref (there are more than zero references from external project/open file), keep it alive as well as any project it references
if (project.hasOpenRef()) {
toRemoveConfiguredProjects.delete(project.canonicalConfigFilePath);
markOriginalProjectsAsUsed(project);
}
});
// Remove all the non marked projects
toRemoveConfiguredProjects.forEach(project => this.removeProject(project));
function markOriginalProjectsAsUsed(project: Project) {
if (!project.isOrphan() && project.originalConfiguredProjects) {
project.originalConfiguredProjects.forEach((_value, configuredProjectPath) => toRemoveConfiguredProjects.delete(configuredProjectPath));
}
}
}
private telemetryOnOpenFile(scriptInfo: ScriptInfo): void {
if (this.syntaxOnly || !this.eventHandler || !scriptInfo.isJavaScript() || !addToSeen(this.allJsFilesForOpenFileTelemetry, scriptInfo.path)) {
return;
+4 -1
View File
@@ -158,6 +158,9 @@ namespace ts.server {
/*@internal*/
typingFiles: SortedReadonlyArray<string> = emptyArray;
/*@internal*/
originalConfiguredProjects: Map<true> | undefined;
private readonly cancellationToken: ThrottledCancellationToken;
public isNonTsProject() {
@@ -711,7 +714,7 @@ namespace ts.server {
}
containsFile(filename: NormalizedPath, requireOpen?: boolean): boolean {
const info = this.projectService.getScriptInfoForPath(this.toPath(filename));
const info = this.projectService.getScriptInfoForNormalizedPath(filename);
if (info && (info.isScriptOpen() || !requireOpen)) {
return this.containsScriptInfo(info);
}
+25 -29
View File
@@ -258,10 +258,6 @@ namespace ts.server {
readonly symLinkedProjects: MultiMap<Project>;
};
function isProjectsArray(projects: Projects): projects is ReadonlyArray<Project> {
return !!(<ReadonlyArray<Project>>projects).length;
}
/**
* This helper function processes a list of projects and return the concatenated, sortd and deduplicated output of processing each project.
*/
@@ -273,8 +269,8 @@ namespace ts.server {
comparer?: (a: U, b: U) => number,
areEqual?: (a: U, b: U) => boolean,
): U[] {
const outputs = flatMap(isProjectsArray(projects) ? projects : projects.projects, project => action(project, defaultValue));
if (!isProjectsArray(projects) && projects.symLinkedProjects) {
const outputs = flatMap(isArray(projects) ? projects : projects.projects, project => action(project, defaultValue));
if (!isArray(projects) && projects.symLinkedProjects) {
projects.symLinkedProjects.forEach((projects, path) => {
const value = getValue(path as Path);
outputs.push(...flatMap(projects, project => action(project, value)));
@@ -370,7 +366,7 @@ namespace ts.server {
}
function forEachProjectInProjects(projects: Projects, path: string | undefined, cb: (project: Project, path: string | undefined) => void): void {
for (const project of isProjectsArray(projects) ? projects : projects.projects) {
for (const project of isArray(projects) ? projects : projects.projects) {
cb(project, path);
}
if (!isArray(projects) && projects.symLinkedProjects) {
@@ -431,23 +427,20 @@ namespace ts.server {
if (projectAndLocation.project.getCancellationToken().isCancellationRequested()) return undefined; // Skip rest of toDo if cancelled
cb(projectAndLocation, (project, location) => {
seenProjects.set(projectAndLocation.project.projectName, true);
const originalLocation = project.getSourceMapper().tryGetOriginalLocation(location);
const originalLocation = projectService.getOriginalLocationEnsuringConfiguredProject(project, location);
if (!originalLocation) return false;
const originalProjectAndScriptInfo = projectService.getProjectForFileWithoutOpening(toNormalizedPath(originalLocation.fileName));
if (!originalProjectAndScriptInfo) return false;
if (originalProjectAndScriptInfo) {
toDo = toDo || [];
const originalScriptInfo = projectService.getScriptInfo(originalLocation.fileName)!;
toDo = toDo || [];
for (const project of originalProjectAndScriptInfo.projects) {
addToTodo({ project, location: originalLocation as TLocation }, toDo, seenProjects);
}
const symlinkedProjectsMap = projectService.getSymlinkedProjects(originalProjectAndScriptInfo.scriptInfo);
if (symlinkedProjectsMap) {
symlinkedProjectsMap.forEach((symlinkedProjects) => {
for (const symlinkedProject of symlinkedProjects) addToTodo({ project: symlinkedProject, location: originalLocation as TLocation }, toDo!, seenProjects);
});
}
for (const project of originalScriptInfo.containingProjects) {
addToTodo({ project, location: originalLocation as TLocation }, toDo, seenProjects);
}
const symlinkedProjectsMap = projectService.getSymlinkedProjects(originalScriptInfo);
if (symlinkedProjectsMap) {
symlinkedProjectsMap.forEach((symlinkedProjects) => {
for (const symlinkedProject of symlinkedProjects) addToTodo({ project: symlinkedProject, location: originalLocation as TLocation }, toDo!, seenProjects);
});
}
return true;
});
@@ -1106,7 +1099,7 @@ namespace ts.server {
return project.getLanguageService().getRenameInfo(file, position);
}
private getProjects(args: protocol.FileRequestArgs): Projects {
private getProjects(args: protocol.FileRequestArgs, getScriptInfoEnsuringProjectsUptoDate?: boolean, ignoreNoProjectError?: boolean): Projects {
let projects: ReadonlyArray<Project> | undefined;
let symLinkedProjects: MultiMap<Project> | undefined;
if (args.projectFileName) {
@@ -1116,13 +1109,18 @@ namespace ts.server {
}
}
else {
const scriptInfo = this.projectService.getScriptInfo(args.file)!;
const scriptInfo = getScriptInfoEnsuringProjectsUptoDate ?
this.projectService.getScriptInfoEnsuringProjectsUptoDate(args.file) :
this.projectService.getScriptInfo(args.file);
if (!scriptInfo) {
return ignoreNoProjectError ? emptyArray : Errors.ThrowNoProject();
}
projects = scriptInfo.containingProjects;
symLinkedProjects = this.projectService.getSymlinkedProjects(scriptInfo);
}
// filter handles case when 'projects' is undefined
projects = filter(projects, p => p.languageServiceEnabled && !p.isOrphan());
if ((!projects || !projects.length) && !symLinkedProjects) {
if (!ignoreNoProjectError && (!projects || !projects.length) && !symLinkedProjects) {
return Errors.ThrowNoProject();
}
return symLinkedProjects ? { projects: projects!, symLinkedProjects } : projects!; // TODO: GH#18217
@@ -1465,18 +1463,16 @@ namespace ts.server {
}
private getCompileOnSaveAffectedFileList(args: protocol.FileRequestArgs): ReadonlyArray<protocol.CompileOnSaveAffectedFileListSingleProject> {
const info = this.projectService.getScriptInfoEnsuringProjectsUptoDate(args.file);
const projects = this.getProjects(args, /*getScriptInfoEnsuringProjectsUptoDate*/ true, /*ignoreNoProjectError*/ true);
const info = this.projectService.getScriptInfo(args.file);
if (!info) {
return emptyArray;
}
// if specified a project, we only return affected file list in this project
const projects = args.projectFileName ? [this.projectService.findProject(args.projectFileName)!] : info.containingProjects;
const symLinkedProjects = !args.projectFileName && this.projectService.getSymlinkedProjects(info);
return combineProjectOutput(
info,
path => this.projectService.getScriptInfoForPath(path)!,
symLinkedProjects ? { projects, symLinkedProjects } : projects,
projects,
(project, info) => {
let result: protocol.CompileOnSaveAffectedFileListSingleProject | undefined;
if (project.compileOnSaveEnabled && project.languageServiceEnabled && !project.isOrphan() && !project.getCompilationSettings().noEmit) {
+56 -13
View File
@@ -3,6 +3,7 @@ namespace ts.codefix {
const fixName = "unusedIdentifier";
const fixIdPrefix = "unusedIdentifier_prefix";
const fixIdDelete = "unusedIdentifier_delete";
const fixIdInfer = "unusedIdentifier_infer";
const errorCodes = [
Diagnostics._0_is_declared_but_its_value_is_never_read.code,
Diagnostics._0_is_declared_but_never_used.code,
@@ -10,6 +11,7 @@ namespace ts.codefix {
Diagnostics.All_imports_in_import_declaration_are_unused.code,
Diagnostics.All_destructured_elements_are_unused.code,
Diagnostics.All_variables_are_unused.code,
Diagnostics.All_type_parameters_are_unused.code,
];
registerCodeFix({
@@ -20,28 +22,42 @@ namespace ts.codefix {
const sourceFiles = program.getSourceFiles();
const token = getTokenAtPosition(sourceFile, context.span.start);
if (isJSDocTemplateTag(token)) {
return [createDeleteFix(textChanges.ChangeTracker.with(context, t => t.delete(sourceFile, token)), Diagnostics.Remove_template_tag)];
}
if (token.kind === SyntaxKind.LessThanToken) {
const changes = textChanges.ChangeTracker.with(context, t => deleteTypeParameters(t, sourceFile, token));
return [createDeleteFix(changes, Diagnostics.Remove_type_parameters)];
}
const importDecl = tryGetFullImport(token);
if (importDecl) {
const changes = textChanges.ChangeTracker.with(context, t => t.delete(sourceFile, importDecl));
return [createCodeFixAction(fixName, changes, [Diagnostics.Remove_import_from_0, showModuleSpecifier(importDecl)], fixIdDelete, Diagnostics.Delete_all_unused_declarations)];
return [createDeleteFix(changes, [Diagnostics.Remove_import_from_0, showModuleSpecifier(importDecl)])];
}
const delDestructure = textChanges.ChangeTracker.with(context, t =>
tryDeleteFullDestructure(token, t, sourceFile, checker, sourceFiles, /*isFixAll*/ false));
if (delDestructure.length) {
return [createCodeFixAction(fixName, delDestructure, Diagnostics.Remove_destructuring, fixIdDelete, Diagnostics.Delete_all_unused_declarations)];
return [createDeleteFix(delDestructure, Diagnostics.Remove_destructuring)];
}
const delVar = textChanges.ChangeTracker.with(context, t => tryDeleteFullVariableStatement(sourceFile, token, t));
if (delVar.length) {
return [createCodeFixAction(fixName, delVar, Diagnostics.Remove_variable_statement, fixIdDelete, Diagnostics.Delete_all_unused_declarations)];
return [createDeleteFix(delVar, Diagnostics.Remove_variable_statement)];
}
const result: CodeFixAction[] = [];
const deletion = textChanges.ChangeTracker.with(context, t =>
tryDeleteDeclaration(sourceFile, token, t, checker, sourceFiles, /*isFixAll*/ false));
if (deletion.length) {
const name = isComputedPropertyName(token.parent) ? token.parent : token;
result.push(createCodeFixAction(fixName, deletion, [Diagnostics.Remove_declaration_for_Colon_0, name.getText(sourceFile)], fixIdDelete, Diagnostics.Delete_all_unused_declarations));
if (token.kind === SyntaxKind.InferKeyword) {
const changes = textChanges.ChangeTracker.with(context, t => changeInferToUnknown(t, sourceFile, token));
const name = cast(token.parent, isInferTypeNode).typeParameter.name.text;
result.push(createCodeFixAction(fixName, changes, [Diagnostics.Replace_infer_0_with_unknown, name], fixIdInfer, Diagnostics.Replace_all_unused_infer_with_unknown));
}
else {
const deletion = textChanges.ChangeTracker.with(context, t =>
tryDeleteDeclaration(sourceFile, token, t, checker, sourceFiles, /*isFixAll*/ false));
if (deletion.length) {
const name = isComputedPropertyName(token.parent) ? token.parent : token;
result.push(createDeleteFix(deletion, [Diagnostics.Remove_declaration_for_Colon_0, name.getText(sourceFile)]));
}
}
const prefix = textChanges.ChangeTracker.with(context, t => tryPrefixDeclaration(t, errorCode, sourceFile, token));
@@ -51,7 +67,7 @@ namespace ts.codefix {
return result;
},
fixIds: [fixIdPrefix, fixIdDelete],
fixIds: [fixIdPrefix, fixIdDelete, fixIdInfer],
getAllCodeActions: context => {
const { sourceFile, program } = context;
const checker = program.getTypeChecker();
@@ -60,21 +76,31 @@ namespace ts.codefix {
const token = getTokenAtPosition(sourceFile, diag.start);
switch (context.fixId) {
case fixIdPrefix:
if (isIdentifier(token) && canPrefix(token)) {
tryPrefixDeclaration(changes, diag.code, sourceFile, token);
}
tryPrefixDeclaration(changes, diag.code, sourceFile, token);
break;
case fixIdDelete: {
if (token.kind === SyntaxKind.InferKeyword) break; // Can't delete
const importDecl = tryGetFullImport(token);
if (importDecl) {
changes.delete(sourceFile, importDecl);
}
else if (isJSDocTemplateTag(token)) {
changes.delete(sourceFile, token);
}
else if (token.kind === SyntaxKind.LessThanToken) {
deleteTypeParameters(changes, sourceFile, token);
}
else if (!tryDeleteFullDestructure(token, changes, sourceFile, checker, sourceFiles, /*isFixAll*/ true) &&
!tryDeleteFullVariableStatement(sourceFile, token, changes)) {
tryDeleteDeclaration(sourceFile, token, changes, checker, sourceFiles, /*isFixAll*/ true);
}
break;
}
case fixIdInfer:
if (token.kind === SyntaxKind.InferKeyword) {
changeInferToUnknown(changes, sourceFile, token);
}
break;
default:
Debug.fail(JSON.stringify(context.fixId));
}
@@ -82,6 +108,18 @@ namespace ts.codefix {
},
});
function changeInferToUnknown(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node): void {
changes.replaceNode(sourceFile, token.parent, createKeywordTypeNode(SyntaxKind.UnknownKeyword));
}
function createDeleteFix(changes: FileTextChanges[], diag: DiagnosticAndArguments): CodeFixAction {
return createCodeFixAction(fixName, changes, diag, fixIdDelete, Diagnostics.Delete_all_unused_declarations);
}
function deleteTypeParameters(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node): void {
changes.delete(sourceFile, Debug.assertDefined(cast(token.parent, isDeclarationWithTypeParameterChildren).typeParameters));
}
// Sometimes the diagnostic span is an entire ImportDeclaration, so we should remove the whole thing.
function tryGetFullImport(token: Node): ImportDeclaration | undefined {
return token.kind === SyntaxKind.ImportKeyword ? tryCast(token.parent, isImportDeclaration) : undefined;
@@ -110,7 +148,11 @@ namespace ts.codefix {
function tryPrefixDeclaration(changes: textChanges.ChangeTracker, errorCode: number, sourceFile: SourceFile, token: Node): void {
// Don't offer to prefix a property.
if (errorCode !== Diagnostics.Property_0_is_declared_but_its_value_is_never_read.code && isIdentifier(token) && canPrefix(token)) {
if (errorCode === Diagnostics.Property_0_is_declared_but_its_value_is_never_read.code) return;
if (token.kind === SyntaxKind.InferKeyword) {
token = cast(token.parent, isInferTypeNode).typeParameter.name;
}
if (isIdentifier(token) && canPrefix(token)) {
changes.replaceNode(sourceFile, token, createIdentifier(`_${token.text}`));
}
}
@@ -118,6 +160,7 @@ namespace ts.codefix {
function canPrefix(token: Identifier): boolean {
switch (token.parent.kind) {
case SyntaxKind.Parameter:
case SyntaxKind.TypeParameter:
return true;
case SyntaxKind.VariableDeclaration: {
const varDecl = token.parent as VariableDeclaration;
+3 -3
View File
@@ -275,7 +275,7 @@ namespace ts.codefix {
): ReadonlyArray<FixAddNewImport | FixUseImportType> {
const isJs = isSourceFileJavaScript(sourceFile);
const choicesForEachExportingModule = flatMap<SymbolExportInfo, ReadonlyArray<FixAddNewImport | FixUseImportType>>(moduleSymbols, ({ moduleSymbol, importKind, exportedSymbolIsTypeOnly }) => {
const modulePathsGroups = moduleSpecifiers.getModuleSpecifiers(moduleSymbol, program.getCompilerOptions(), sourceFile, host, program.getSourceFiles(), preferences);
const modulePathsGroups = moduleSpecifiers.getModuleSpecifiers(moduleSymbol, program.getCompilerOptions(), sourceFile, host, program.getSourceFiles(), preferences, program.redirectTargetsMap);
return modulePathsGroups.map(group => group.map((moduleSpecifier): FixAddNewImport | FixUseImportType =>
// `position` should only be undefined at a missing jsx namespace, in which case we shouldn't be looking for pure types.
exportedSymbolIsTypeOnly && isJs ? { kind: ImportFixKind.ImportType, moduleSpecifier, position: Debug.assertDefined(position) } : { kind: ImportFixKind.AddNew, moduleSpecifier, importKind }));
@@ -465,14 +465,14 @@ namespace ts.codefix {
const { importClause, importKind } = fix;
doAddExistingFix(changes, sourceFile, importClause, importKind === ImportKind.Default ? symbolName : undefined, importKind === ImportKind.Named ? [symbolName] : emptyArray);
const moduleSpecifierWithoutQuotes = stripQuotes(importClause.parent.moduleSpecifier.getText());
return [Diagnostics.Add_0_to_existing_import_declaration_from_1, symbolName, moduleSpecifierWithoutQuotes];
return [importKind === ImportKind.Default ? Diagnostics.Add_default_import_0_to_existing_import_declaration_from_1 : Diagnostics.Add_0_to_existing_import_declaration_from_1, symbolName, moduleSpecifierWithoutQuotes]; // you too!
}
case ImportFixKind.AddNew: {
const { importKind, moduleSpecifier } = fix;
addNewImports(changes, sourceFile, moduleSpecifier, quotePreference, importKind === ImportKind.Default ? { defaultImport: symbolName, namedImports: emptyArray, namespaceLikeImport: undefined }
: importKind === ImportKind.Named ? { defaultImport: undefined, namedImports: [symbolName], namespaceLikeImport: undefined }
: { defaultImport: undefined, namedImports: emptyArray, namespaceLikeImport: { importKind, name: symbolName } });
return [Diagnostics.Import_0_from_module_1, symbolName, moduleSpecifier];
return [importKind === ImportKind.Default ? Diagnostics.Import_default_0_from_module_1 : Diagnostics.Import_0_from_module_1, symbolName, moduleSpecifier];
}
default:
return Debug.assertNever(fix);
+1 -1
View File
@@ -28,7 +28,7 @@ namespace ts.DocumentHighlights {
const map = arrayToMultiMap(referenceEntries.map(FindAllReferences.toHighlightSpan), e => e.fileName, e => e.span);
return arrayFrom(map.entries(), ([fileName, highlightSpans]) => {
if (!sourceFilesSet.has(fileName)) {
Debug.assert(program.redirectTargetsSet.has(fileName));
Debug.assert(program.redirectTargetsMap.has(fileName));
const redirectTarget = program.getSourceFile(fileName);
const redirect = find(sourceFilesToSearch, f => !!f.redirectInfo && f.redirectInfo.redirectTarget === redirectTarget)!;
fileName = redirect.fileName;
+6 -3
View File
@@ -354,12 +354,15 @@ namespace ts.formatting {
case SyntaxKind.ClassExpression:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.TypeAliasDeclaration:
return getListIfStartEndIsInListRange((<ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeAliasDeclaration>node.parent).typeParameters, node.getStart(sourceFile), end);
case SyntaxKind.JSDocTemplateTag: {
const { typeParameters } = <ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag>node.parent;
return getListIfStartEndIsInListRange(typeParameters, node.getStart(sourceFile), end);
}
case SyntaxKind.NewExpression:
case SyntaxKind.CallExpression: {
const start = node.getStart(sourceFile);
return getListIfStartEndIsInListRange((<CallExpression>node.parent).typeArguments, start, end) ||
getListIfStartEndIsInListRange((<CallExpression>node.parent).arguments, start, end);
return getListIfStartEndIsInListRange((<CallExpression | NewExpression>node.parent).typeArguments, start, end) ||
getListIfStartEndIsInListRange((<CallExpression | NewExpression>node.parent).arguments, start, end);
}
case SyntaxKind.VariableDeclarationList:
return getListIfStartEndIsInListRange((<VariableDeclarationList>node.parent).declarations, node.getStart(sourceFile), end);
+1 -1
View File
@@ -156,7 +156,7 @@ namespace ts {
// Need an update if the imported file moved, or the importing file moved and was using a relative path.
return toImport !== undefined && (toImport.updated || (importingSourceFileMoved && pathIsRelative(importLiteral.text)))
? moduleSpecifiers.getModuleSpecifier(program.getCompilerOptions(), sourceFile, newImportFromPath, toImport.newFileName, host, allFiles, preferences)
? moduleSpecifiers.getModuleSpecifier(program.getCompilerOptions(), sourceFile, newImportFromPath, toImport.newFileName, host, allFiles, preferences, program.redirectTargetsMap)
: undefined;
});
}
+6 -4
View File
@@ -272,14 +272,16 @@ namespace ts.JsDoc {
*/
export function getDocCommentTemplateAtPosition(newLine: string, sourceFile: SourceFile, position: number): TextInsertion | undefined {
// Check if in a context where we don't want to perform any insertion
if (isInString(sourceFile, position) || isInComment(sourceFile, position) || hasDocComment(sourceFile, position)) {
const tokenAtPos = getTokenAtPosition(sourceFile, position);
const existingDocComment = findAncestor(tokenAtPos, isJSDoc);
if (existingDocComment && (existingDocComment.comment !== undefined || length(existingDocComment.tags))) {
// Non-empty comment already exists.
return undefined;
}
const tokenAtPos = getTokenAtPosition(sourceFile, position);
const tokenStart = tokenAtPos.getStart(sourceFile);
if (!tokenAtPos || tokenStart < position) {
// Don't provide a doc comment template based on a *previous* node. (But an existing empty jsdoc comment will likely start before `position`.)
if (!existingDocComment && tokenStart < position) {
return undefined;
}
+1 -1
View File
@@ -1240,7 +1240,7 @@ namespace ts.refactor.extractSymbol {
return scope.members;
}
else {
assertTypeIsNever(scope);
assertType<never>(scope);
}
return emptyArray;
+9 -24
View File
@@ -66,7 +66,7 @@ namespace ts.SignatureHelp {
return undefined;
}
const candidates: Signature[] = [];
const resolvedSignature = checker.getResolvedSignature(invocation.node, candidates, argumentInfo.argumentCount)!; // TODO: GH#18217
const resolvedSignature = checker.getResolvedSignatureForSignatureHelp(invocation.node, candidates, argumentInfo.argumentCount)!; // TODO: GH#18217
return candidates.length === 0 ? undefined : { candidates, resolvedSignature };
}
else if (invocation.kind === InvocationKind.TypeArgs) {
@@ -101,31 +101,16 @@ namespace ts.SignatureHelp {
function createJavaScriptSignatureHelpItems(argumentInfo: ArgumentListInfo, program: Program, cancellationToken: CancellationToken): SignatureHelpItems | undefined {
// See if we can find some symbol with the call expression name that has call signatures.
const expression = getExpressionFromInvocation(argumentInfo.invocation);
const name = isIdentifier(expression) ? expression : isPropertyAccessExpression(expression) ? expression.name : undefined;
if (!name || !name.escapedText) {
return undefined;
}
const name = isIdentifier(expression) ? expression.text : isPropertyAccessExpression(expression) ? expression.name.text : undefined;
const typeChecker = program.getTypeChecker();
for (const sourceFile of program.getSourceFiles()) {
const nameToDeclarations = sourceFile.getNamedDeclarations();
const declarations = nameToDeclarations.get(name.text);
if (declarations) {
for (const declaration of declarations) {
const symbol = declaration.symbol;
if (symbol) {
const type = typeChecker.getTypeOfSymbolAtLocation(symbol, declaration);
if (type) {
const callSignatures = type.getCallSignatures();
if (callSignatures && callSignatures.length) {
return typeChecker.runWithCancellationToken(cancellationToken, typeChecker => createSignatureHelpItems(callSignatures, callSignatures[0], argumentInfo, sourceFile, typeChecker));
}
}
}
return name === undefined ? undefined : firstDefined(program.getSourceFiles(), sourceFile =>
firstDefined(sourceFile.getNamedDeclarations().get(name), declaration => {
const type = declaration.symbol && typeChecker.getTypeOfSymbolAtLocation(declaration.symbol, declaration);
const callSignatures = type && type.getCallSignatures();
if (callSignatures && callSignatures.length) {
return typeChecker.runWithCancellationToken(cancellationToken, typeChecker => createSignatureHelpItems(callSignatures, callSignatures[0], argumentInfo, sourceFile, typeChecker));
}
}
}
}));
}
function lessThanFollowsCalledExpression(startingToken: Node, sourceFile: SourceFile, calledExpression: Expression) {
+14 -39
View File
@@ -213,7 +213,7 @@ namespace ts.textChanges {
private readonly changes: Change[] = [];
private readonly newFiles: { readonly oldFile: SourceFile, readonly fileName: string, readonly statements: ReadonlyArray<Statement> }[] = [];
private readonly classesWithNodesInsertedAtStart = createMap<ClassDeclaration>(); // Set<ClassDeclaration> implemented as Map<node id, ClassDeclaration>
private readonly deletedNodes: { readonly sourceFile: SourceFile, readonly node: Node }[] = [];
private readonly deletedNodes: { readonly sourceFile: SourceFile, readonly node: Node | NodeArray<TypeParameterDeclaration> }[] = [];
public static fromContext(context: TextChangesContext): ChangeTracker {
return new ChangeTracker(getNewLineOrDefaultFromHost(context.host, context.formatContext.options), context.formatContext);
@@ -233,8 +233,8 @@ namespace ts.textChanges {
return this;
}
delete(sourceFile: SourceFile, node: Node): void {
this.deletedNodes.push({ sourceFile, node, });
delete(sourceFile: SourceFile, node: Node | NodeArray<TypeParameterDeclaration>): void {
this.deletedNodes.push({ sourceFile, node });
}
public deleteModifier(sourceFile: SourceFile, modifier: Modifier): void {
@@ -661,7 +661,12 @@ namespace ts.textChanges {
const deletedNodesInLists = new NodeSet(); // Stores ids of nodes in lists that we already deleted. Used to avoid deleting `, ` twice in `a, b`.
for (const { sourceFile, node } of this.deletedNodes) {
if (!this.deletedNodes.some(d => d.sourceFile === sourceFile && rangeContainsRangeExclusive(d.node, node))) {
deleteDeclaration.deleteDeclaration(this, deletedNodesInLists, sourceFile, node);
if (isArray(node)) {
this.deleteRange(sourceFile, rangeOfTypeParameters(node));
}
else {
deleteDeclaration.deleteDeclaration(this, deletedNodesInLists, sourceFile, node);
}
}
}
@@ -1001,7 +1006,7 @@ namespace ts.textChanges {
}
namespace deleteDeclaration {
export function deleteDeclaration(changes: ChangeTracker, deletedNodesInLists: NodeSet, sourceFile: SourceFile, node: Node): void {
export function deleteDeclaration(changes: ChangeTracker, deletedNodesInLists: NodeSet<Node>, sourceFile: SourceFile, node: Node): void {
switch (node.kind) {
case SyntaxKind.Parameter: {
const oldFunction = node.parent;
@@ -1052,33 +1057,9 @@ namespace ts.textChanges {
deleteVariableDeclaration(changes, deletedNodesInLists, sourceFile, node as VariableDeclaration);
break;
case SyntaxKind.TypeParameter: {
const typeParam = node as TypeParameterDeclaration;
switch (typeParam.parent.kind) {
case SyntaxKind.JSDocTemplateTag:
changes.deleteRange(sourceFile, getRangeToDeleteJsDocTag(typeParam.parent, sourceFile));
break;
case SyntaxKind.InferType:
// TODO: GH#25594
break;
default: {
const typeParameters = getEffectiveTypeParameterDeclarations(typeParam.parent);
if (typeParameters.length === 1) {
const { pos, end } = cast(typeParameters, isNodeArray);
const previousToken = getTokenAtPosition(sourceFile, pos - 1);
const nextToken = getTokenAtPosition(sourceFile, end);
Debug.assert(previousToken.kind === SyntaxKind.LessThanToken);
Debug.assert(nextToken.kind === SyntaxKind.GreaterThanToken);
changes.deleteNodeRange(sourceFile, previousToken, nextToken);
}
else {
deleteNodeInList(changes, deletedNodesInLists, sourceFile, node);
}
}
}
case SyntaxKind.TypeParameter:
deleteNodeInList(changes, deletedNodesInLists, sourceFile, node);
break;
}
case SyntaxKind.ImportSpecifier:
const namedImports = (node as ImportSpecifier).parent;
@@ -1144,7 +1125,7 @@ namespace ts.textChanges {
}
}
function deleteVariableDeclaration(changes: ChangeTracker, deletedNodesInLists: NodeSet, sourceFile: SourceFile, node: VariableDeclaration): void {
function deleteVariableDeclaration(changes: ChangeTracker, deletedNodesInLists: NodeSet<Node>, sourceFile: SourceFile, node: VariableDeclaration): void {
const { parent } = node;
if (parent.kind === SyntaxKind.CatchClause) {
@@ -1177,12 +1158,6 @@ namespace ts.textChanges {
Debug.assertNever(gp);
}
}
function getRangeToDeleteJsDocTag(node: JSDocTag, sourceFile: SourceFile): TextRange {
const { parent } = node;
const toDelete = parent.kind === SyntaxKind.JSDocComment && parent.comment === undefined && parent.tags!.length === 1 ? parent : node;
return createTextRangeFromNode(toDelete, sourceFile);
}
}
/** Warning: This deletes comments too. See `copyComments` in `convertFunctionToEs6Class`. */
@@ -1193,7 +1168,7 @@ namespace ts.textChanges {
changes.deleteRange(sourceFile, { pos: startPosition, end: endPosition });
}
function deleteNodeInList(changes: ChangeTracker, deletedNodesInLists: NodeSet, sourceFile: SourceFile, node: Node): void {
function deleteNodeInList(changes: ChangeTracker, deletedNodesInLists: NodeSet<Node>, sourceFile: SourceFile, node: Node): void {
const containingList = Debug.assertDefined(formatting.SmartIndenter.getContainingList(node, sourceFile));
const index = indexOfNode(containingList, node);
Debug.assert(index !== -1);
+1 -58
View File
@@ -374,7 +374,7 @@ namespace ts {
case SpecialPropertyAssignmentKind.Prototype:
return ScriptElementKind.localClassElement;
default: {
assertTypeIsNever(kind);
assertType<never>(kind);
return ScriptElementKind.unknown;
}
}
@@ -1358,63 +1358,6 @@ namespace ts {
return getPropertySymbolsFromBaseTypes(memberSymbol.parent!, memberSymbol.name, checker, _ => true) || false;
}
export interface ReadonlyNodeSet {
has(node: Node): boolean;
forEach(cb: (node: Node) => void): void;
some(pred: (node: Node) => boolean): boolean;
}
export class NodeSet implements ReadonlyNodeSet {
private map = createMap<Node>();
add(node: Node): void {
this.map.set(String(getNodeId(node)), node);
}
has(node: Node): boolean {
return this.map.has(String(getNodeId(node)));
}
forEach(cb: (node: Node) => void): void {
this.map.forEach(cb);
}
some(pred: (node: Node) => boolean): boolean {
return forEachEntry(this.map, pred) || false;
}
}
export interface ReadonlyNodeMap<TNode extends Node, TValue> {
get(node: TNode): TValue | undefined;
has(node: TNode): boolean;
}
export class NodeMap<TNode extends Node, TValue> implements ReadonlyNodeMap<TNode, TValue> {
private map = createMap<{ node: TNode, value: TValue }>();
get(node: TNode): TValue | undefined {
const res = this.map.get(String(getNodeId(node)));
return res && res.value;
}
getOrUpdate(node: TNode, setValue: () => TValue): TValue {
const res = this.get(node);
if (res) return res;
const value = setValue();
this.set(node, value);
return value;
}
set(node: TNode, value: TValue): void {
this.map.set(String(getNodeId(node)), { node, value });
}
has(node: TNode): boolean {
return this.map.has(String(getNodeId(node)));
}
forEach(cb: (value: TValue, node: TNode) => void): void {
this.map.forEach(({ node, value }) => cb(value, node));
}
}
export function getParentNodeInSpan(node: Node | undefined, file: SourceFile, span: TextSpan): Node | undefined {
if (!node) return undefined;
+30 -2
View File
@@ -1,5 +1,8 @@
namespace ts {
describe("FactoryAPI", () => {
function assertSyntaxKind(node: Node, expected: SyntaxKind) {
assert.strictEqual(node.kind, expected, `Actual: ${Debug.showSyntaxKind(node)} Expected: ${(ts as any).SyntaxKind[expected]}`);
}
describe("createExportAssignment", () => {
it("parenthesizes default export if necessary", () => {
function checkExpression(expression: Expression) {
@@ -9,7 +12,7 @@ namespace ts {
/*isExportEquals*/ false,
expression,
);
assert.strictEqual(node.expression.kind, SyntaxKind.ParenthesizedExpression);
assertSyntaxKind(node.expression, SyntaxKind.ParenthesizedExpression);
}
const clazz = createClassExpression(/*modifiers*/ undefined, "C", /*typeParameters*/ undefined, /*heritageClauses*/ undefined, [
@@ -39,7 +42,7 @@ namespace ts {
/*equalsGreaterThanToken*/ undefined,
body,
);
assert.strictEqual(node.body.kind, SyntaxKind.ParenthesizedExpression);
assertSyntaxKind(node.body, SyntaxKind.ParenthesizedExpression);
}
checkBody(createObjectLiteral());
@@ -50,5 +53,30 @@ namespace ts {
checkBody(createBinary(createLiteral("a"), SyntaxKind.CommaToken, createLiteral("b")));
});
});
describe("createBinaryExpression", () => {
it("parenthesizes arrow function in RHS if necessary", () => {
const lhs = createIdentifier("foo");
const rhs = createArrowFunction(
/*modifiers*/ undefined,
/*typeParameters*/ undefined,
[],
/*type*/ undefined,
/*equalsGreaterThanToken*/ undefined,
createBlock([]),
);
function checkRhs(operator: BinaryOperator, expectParens: boolean) {
const node = createBinary(lhs, operator, rhs);
assertSyntaxKind(node.right, expectParens ? SyntaxKind.ParenthesizedExpression : SyntaxKind.ArrowFunction);
}
checkRhs(SyntaxKind.CommaToken, /*expectParens*/ false);
checkRhs(SyntaxKind.EqualsToken, /*expectParens*/ false);
checkRhs(SyntaxKind.PlusEqualsToken, /*expectParens*/ false);
checkRhs(SyntaxKind.BarBarToken, /*expectParens*/ true);
checkRhs(SyntaxKind.AmpersandAmpersandToken, /*expectParens*/ true);
checkRhs(SyntaxKind.EqualsEqualsToken, /*expectParens*/ true);
});
});
});
}
+4 -1
View File
@@ -2262,8 +2262,11 @@ declare module "fs" {
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();
@@ -2622,7 +2625,7 @@ declare module "fs" {
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, linkedPackageRoot, `${mainPackageRoot}/node_modules/@types`, `${projectRoot}/node_modules/@types`], 1, /*recursive*/ true);
checkWatchedDirectoriesDetailed(host, [`${mainPackageRoot}/@scoped`, `${mainPackageRoot}/node_modules`, linkedPackageRoot, `${mainPackageRoot}/node_modules/@types`, `${projectRoot}/node_modules/@types`], 1, /*recursive*/ true);
});
});
+385 -88
View File
@@ -176,6 +176,18 @@ namespace ts.projectSystem {
return { ts: 0, tsx: 0, dts: 0, js: 0, jsx: 0, deferred: 0, ...nonZeroStats };
}
export interface ConfigFileDiagnostic {
fileName: string | undefined;
start: number | undefined;
length: number | undefined;
messageText: string;
category: DiagnosticCategory;
code: number;
reportsUnnecessary?: {};
source?: string;
relatedInformation?: DiagnosticRelatedInformation[];
}
export class TestServerEventManager {
private events: server.ProjectServiceEvent[] = [];
readonly session: TestSession;
@@ -216,10 +228,14 @@ namespace ts.projectSystem {
this.events.forEach(event => assert.notEqual(event.eventName, eventName));
}
checkSingleConfigFileDiagEvent(configFileName: string, triggerFile: string) {
checkSingleConfigFileDiagEvent(configFileName: string, triggerFile: string, errors: ReadonlyArray<ConfigFileDiagnostic>) {
const eventData = this.getEvent<server.ConfigFileDiagEvent>(server.ConfigFileDiagEvent);
assert.equal(eventData.configFileName, configFileName);
assert.equal(eventData.triggerFile, triggerFile);
const actual = eventData.diagnostics.map(({ file, messageText, ...rest }) => ({ fileName: file && file.fileName, messageText: isString(messageText) ? messageText : "", ...rest }));
if (errors) {
assert.deepEqual(actual, errors);
}
}
assertProjectInfoTelemetryEvent(partial: Partial<server.ProjectInfoTelemetryEventData>, configFile = "/tsconfig.json"): void {
@@ -622,8 +638,8 @@ namespace ts.projectSystem {
const configFileLocations = ["/a/b/c/", "/a/b/", "/a/", "/"];
const configFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]);
checkWatchedFiles(host, configFiles.concat(libFile.path, moduleFile.path));
checkWatchedDirectories(host, [], /*recursive*/ false);
checkWatchedDirectories(host, ["/a/b/c", combinePaths(getDirectoryPath(appFile.path), nodeModulesAtTypes)], /*recursive*/ true);
checkWatchedDirectories(host, ["/a/b/c"], /*recursive*/ false);
checkWatchedDirectories(host, [combinePaths(getDirectoryPath(appFile.path), nodeModulesAtTypes)], /*recursive*/ true);
});
it("can handle tsconfig file name with difference casing", () => {
@@ -3035,21 +3051,18 @@ namespace ts.projectSystem {
assert.isDefined(project);
checkProjectActualFiles(project, map(files, file => file.path));
checkWatchedFiles(host, mapDefined(files, file => file === file1 ? undefined : file.path));
checkWatchedDirectories(host, [], /*recursive*/ false);
const watchedRecursiveDirectories = ["/a/b/node_modules/@types"];
watchedRecursiveDirectories.push("/a/b");
checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true);
checkWatchedDirectoriesDetailed(host, ["/a/b"], 1, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, ["/a/b/node_modules/@types"], 1, /*recursive*/ true);
files.push(file2);
host.reloadFS(files);
host.runQueuedTimeoutCallbacks();
watchedRecursiveDirectories.pop();
checkNumberOfProjects(projectService, { configuredProjects: 1 });
assert.strictEqual(projectService.configuredProjects.get(configFile.path), project);
checkProjectActualFiles(project, mapDefined(files, file => file === file2a ? undefined : file.path));
checkWatchedFiles(host, mapDefined(files, file => file === file1 ? undefined : file.path));
checkWatchedDirectories(host, [], /*recursive*/ false);
checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true);
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, ["/a/b/node_modules/@types"], 1, /*recursive*/ true);
// On next file open the files file2a should be closed and not watched any more
projectService.openClientFile(file2.path);
@@ -3057,8 +3070,8 @@ namespace ts.projectSystem {
assert.strictEqual(projectService.configuredProjects.get(configFile.path), project);
checkProjectActualFiles(project, mapDefined(files, file => file === file2a ? undefined : file.path));
checkWatchedFiles(host, [libFile.path, configFile.path]);
checkWatchedDirectories(host, [], /*recursive*/ false);
checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true);
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, ["/a/b/node_modules/@types"], 1, /*recursive*/ true);
});
@@ -3098,7 +3111,7 @@ namespace ts.projectSystem {
checkWatchedFiles(host, [libFile.path, module1.path, module2.path, configFile.path]);
checkWatchedDirectories(host, [], /*recursive*/ false);
const watchedRecursiveDirectories = getTypeRootsFromLocation(root + "/a/b/src");
watchedRecursiveDirectories.push(`${root}/a/b/src`, `${root}/a/b/node_modules`);
watchedRecursiveDirectories.push(`${root}/a/b/src/node_modules`, `${root}/a/b/node_modules`);
checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true);
});
@@ -3479,6 +3492,102 @@ namespace ts.projectSystem {
openFilesForSession([{ file, projectRootPath }], session);
}
});
describe("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);
});
});
});
describe("tsserverProjectSystem Proper errors", () => {
@@ -4698,13 +4807,41 @@ namespace ts.projectSystem {
});
describe("tsserverProjectSystem Configure file diagnostics events", () => {
function getUnknownCompilerOptionDiagnostic(configFile: File, prop: string): ConfigFileDiagnostic {
const d = Diagnostics.Unknown_compiler_option_0;
const start = configFile.content.indexOf(prop) - 1; // start at "prop"
return {
fileName: configFile.path,
start,
length: prop.length + 2,
messageText: formatStringFromArgs(d.message, [prop]),
category: d.category,
code: d.code,
reportsUnnecessary: undefined
};
}
function getFileNotFoundDiagnostic(configFile: File, relativeFileName: string): ConfigFileDiagnostic {
const findString = `{"path":"./${relativeFileName}"}`;
const d = Diagnostics.File_0_does_not_exist;
const start = configFile.content.indexOf(findString);
return {
fileName: configFile.path,
start,
length: findString.length,
messageText: formatStringFromArgs(d.message, [`${getDirectoryPath(configFile.path)}/${relativeFileName}`]),
category: d.category,
code: d.code,
reportsUnnecessary: undefined
};
}
it("are generated when the config file has errors", () => {
const file = {
const file: File = {
path: "/a/b/app.ts",
content: "let x = 10"
};
const configFile = {
const configFile: File = {
path: "/a/b/tsconfig.json",
content: `{
"compilerOptions": {
@@ -4713,29 +4850,32 @@ namespace ts.projectSystem {
}
}`
};
const serverEventManager = new TestServerEventManager([file, configFile]);
const serverEventManager = new TestServerEventManager([file, libFile, configFile]);
openFilesForSession([file], serverEventManager.session);
serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, file.path);
serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, file.path, [
getUnknownCompilerOptionDiagnostic(configFile, "foo"),
getUnknownCompilerOptionDiagnostic(configFile, "allowJS")
]);
});
it("are generated when the config file doesn't have errors", () => {
const file = {
const file: File = {
path: "/a/b/app.ts",
content: "let x = 10"
};
const configFile = {
const configFile: File = {
path: "/a/b/tsconfig.json",
content: `{
"compilerOptions": {}
}`
};
const serverEventManager = new TestServerEventManager([file, configFile]);
const serverEventManager = new TestServerEventManager([file, libFile, configFile]);
openFilesForSession([file], serverEventManager.session);
serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, file.path);
serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, file.path, emptyArray);
});
it("are generated when the config file changes", () => {
const file = {
const file: File = {
path: "/a/b/app.ts",
content: "let x = 10"
};
@@ -4746,37 +4886,40 @@ namespace ts.projectSystem {
}`
};
const serverEventManager = new TestServerEventManager([file, configFile]);
const files = [file, libFile, configFile];
const serverEventManager = new TestServerEventManager(files);
openFilesForSession([file], serverEventManager.session);
serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, file.path);
serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, file.path, emptyArray);
configFile.content = `{
"compilerOptions": {
"haha": 123
}
}`;
serverEventManager.host.reloadFS([file, configFile]);
serverEventManager.host.reloadFS(files);
serverEventManager.host.runQueuedTimeoutCallbacks();
serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, configFile.path);
serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, configFile.path, [
getUnknownCompilerOptionDiagnostic(configFile, "haha")
]);
configFile.content = `{
"compilerOptions": {}
}`;
serverEventManager.host.reloadFS([file, configFile]);
serverEventManager.host.reloadFS(files);
serverEventManager.host.runQueuedTimeoutCallbacks();
serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, configFile.path);
serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, configFile.path, emptyArray);
});
it("are not generated when the config file does not include file opened and config file has errors", () => {
const file = {
const file: File = {
path: "/a/b/app.ts",
content: "let x = 10"
};
const file2 = {
const file2: File = {
path: "/a/b/test.ts",
content: "let x = 10"
};
const configFile = {
const configFile: File = {
path: "/a/b/tsconfig.json",
content: `{
"compilerOptions": {
@@ -4792,11 +4935,11 @@ namespace ts.projectSystem {
});
it("are not generated when the config file has errors but suppressDiagnosticEvents is true", () => {
const file = {
const file: File = {
path: "/a/b/app.ts",
content: "let x = 10"
};
const configFile = {
const configFile: File = {
path: "/a/b/tsconfig.json",
content: `{
"compilerOptions": {
@@ -4805,21 +4948,21 @@ namespace ts.projectSystem {
}
}`
};
const serverEventManager = new TestServerEventManager([file, configFile], /*suppressDiagnosticEvents*/ true);
const serverEventManager = new TestServerEventManager([file, libFile, configFile], /*suppressDiagnosticEvents*/ true);
openFilesForSession([file], serverEventManager.session);
serverEventManager.hasZeroEvent("configFileDiag");
});
it("are not generated when the config file does not include file opened and doesnt contain any errors", () => {
const file = {
const file: File = {
path: "/a/b/app.ts",
content: "let x = 10"
};
const file2 = {
const file2: File = {
path: "/a/b/test.ts",
content: "let x = 10"
};
const configFile = {
const configFile: File = {
path: "/a/b/tsconfig.json",
content: `{
"files": ["app.ts"]
@@ -4830,6 +4973,27 @@ namespace ts.projectSystem {
openFilesForSession([file2], serverEventManager.session);
serverEventManager.hasZeroEvent("configFileDiag");
});
it("contains the project reference errors", () => {
const file: File = {
path: "/a/b/app.ts",
content: "let x = 10"
};
const noSuchTsconfig = "no-such-tsconfig.json";
const configFile: File = {
path: "/a/b/tsconfig.json",
content: `{
"files": ["app.ts"],
"references": [{"path":"./${noSuchTsconfig}"}]
}`
};
const serverEventManager = new TestServerEventManager([file, libFile, configFile]);
openFilesForSession([file], serverEventManager.session);
serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, file.path, [
getFileNotFoundDiagnostic(configFile, noSuchTsconfig)
]);
});
});
describe("tsserverProjectSystem skipLibCheck", () => {
@@ -6780,7 +6944,7 @@ namespace ts.projectSystem {
const { configFileName } = projectService.openClientFile(file1.path);
assert.equal(configFileName, tsconfigFile.path as server.NormalizedPath, `should find config`); // tslint:disable-line no-unnecessary-type-assertion (TODO: GH#18217)
checkNumberOfConfiguredProjects(projectService, 1);
const watchingRecursiveDirectories = [`${canonicalFrontendDir}/src`, canonicalFrontendDir].concat(getNodeModuleDirectories(getDirectoryPath(canonicalFrontendDir)));
const watchingRecursiveDirectories = [`${canonicalFrontendDir}/src`, `${canonicalFrontendDir}/types`, `${canonicalFrontendDir}/node_modules`].concat(getNodeModuleDirectories(getDirectoryPath(canonicalFrontendDir)));
const project = projectService.configuredProjects.get(canonicalConfigPath)!;
verifyProjectAndWatchedDirectories();
@@ -6934,7 +7098,7 @@ namespace ts.projectSystem {
const projectService = createProjectService(host);
const { configFileName } = projectService.openClientFile(app.path);
assert.equal(configFileName, tsconfigJson.path as server.NormalizedPath, `should find config`); // TODO: GH#18217
const recursiveWatchedDirectories: string[] = [appFolder].concat(getNodeModuleDirectories(getDirectoryPath(appFolder)));
const recursiveWatchedDirectories: string[] = [`${appFolder}`, `${appFolder}/node_modules`].concat(getNodeModuleDirectories(getDirectoryPath(appFolder)));
verifyProject();
let timeoutAfterReloadFs = timeoutDuringPartialInstallation;
@@ -7014,7 +7178,7 @@ namespace ts.projectSystem {
const lodashIndexPath = root + "/a/b/node_modules/@types/lodash/index.d.ts";
projectFiles.push(find(filesAndFoldersToAdd, f => f.path === lodashIndexPath)!);
// we would now not have failed lookup in the parent of appFolder since lodash is available
recursiveWatchedDirectories.length = 1;
recursiveWatchedDirectories.length = 2;
// npm installation complete, timeout after reload fs
timeoutAfterReloadFs = true;
verifyAfterPartialOrCompleteNpmInstall(2);
@@ -7534,9 +7698,9 @@ namespace ts.projectSystem {
const openFiles = [file1.path];
const watchedRecursiveDirectories = useSlashRootAsSomeNotRootFolderInUserDirectory ?
// Folders of node_modules lookup not in changedRoot
["a/b/project", "a/b/node_modules", "a/node_modules", "node_modules"].map(v => rootFolder + v) :
["a/b/project", "a/b/project/node_modules", "a/b/node_modules", "a/node_modules", "node_modules"].map(v => rootFolder + v) :
// Folder of tsconfig
["/a/b/project"];
["/a/b/project", "/a/b/project/node_modules"];
const host = createServerHost(projectFiles);
const { session, verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host);
const projectService = session.getProjectService();
@@ -7565,7 +7729,7 @@ namespace ts.projectSystem {
host.reloadFS(projectFiles);
host.runQueuedTimeoutCallbacks();
if (useSlashRootAsSomeNotRootFolderInUserDirectory) {
watchedRecursiveDirectories.length = 2;
watchedRecursiveDirectories.length = 3;
}
else {
// file2 addition wont be detected
@@ -7993,10 +8157,10 @@ new C();`
checkCompleteEvent(session, 2, expectedSequenceId);
}
function verifyWatchedFilesAndDirectories(host: TestServerHost, files: string[], recursiveDirectories: string[], nonRecursiveDirectories: string[]) {
function verifyWatchedFilesAndDirectories(host: TestServerHost, files: string[], recursiveDirectories: ReadonlyMap<number>, nonRecursiveDirectories: string[]) {
checkWatchedFilesDetailed(host, files.filter(f => f !== recognizersDateTimeSrcFile.path), 1);
checkWatchedDirectoriesDetailed(host, nonRecursiveDirectories, 1, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, recursiveDirectories, 1, /*recursive*/ true);
checkWatchedDirectoriesDetailed(host, nonRecursiveDirectories, 1, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, recursiveDirectories, /*recursive*/ true);
}
function createSessionAndOpenFile(host: TestServerHost) {
@@ -8017,8 +8181,16 @@ new C();`
const filesWithNodeModulesSetup = [...filesWithSources, nodeModulesRecorgnizersText];
const filesAfterCompilation = [...filesWithNodeModulesSetup, recongnizerTextDistTypingFile];
const watchedDirectoriesWithResolvedModule = [`${recognizersDateTime}/src`, ...(withPathMapping ? emptyArray : [recognizersDateTime]), ...getTypeRootsFromLocation(recognizersDateTime)];
const watchedDirectoriesWithUnresolvedModule = [recognizersDateTime, ...(withPathMapping ? [recognizersText] : emptyArray), ...watchedDirectoriesWithResolvedModule, ...getNodeModuleDirectories(packages)];
const watchedDirectoriesWithResolvedModule = arrayToMap(getTypeRootsFromLocation(recognizersDateTime), k => k, () => 1);
watchedDirectoriesWithResolvedModule.set(`${recognizersDateTime}/src`, withPathMapping ? 1 : 2); // wild card + failed lookups
if (!withPathMapping) {
watchedDirectoriesWithResolvedModule.set(`${recognizersDateTime}/node_modules`, 1); // failed lookups
}
const watchedDirectoriesWithUnresolvedModule = cloneMap(watchedDirectoriesWithResolvedModule);
watchedDirectoriesWithUnresolvedModule.set(`${recognizersDateTime}/src`, 2); // wild card + failed lookups
[`${recognizersDateTime}/node_modules`, ...(withPathMapping ? [recognizersText] : emptyArray), ...getNodeModuleDirectories(packages)].forEach(d => {
watchedDirectoriesWithUnresolvedModule.set(d, 1);
});
const nonRecursiveWatchedDirectories = withPathMapping ? [packages] : emptyArray;
function verifyProjectWithResolvedModule(session: TestSession) {
@@ -8251,11 +8423,10 @@ new C();`
return `Reusing resolution of module '${moduleName}' to file '${file.path}' from old program.`;
}
function verifyWatchesWithConfigFile(host: TestServerHost, files: File[], openFile: File) {
function verifyWatchesWithConfigFile(host: TestServerHost, files: File[], openFile: File, extraExpectedDirectories?: ReadonlyArray<string>) {
checkWatchedFiles(host, mapDefined(files, f => f === openFile ? undefined : f.path));
checkWatchedDirectories(host, [], /*recursive*/ false);
const configDirectory = getDirectoryPath(configFile.path);
checkWatchedDirectories(host, [configDirectory, `${configDirectory}/${nodeModulesAtTypes}`], /*recursive*/ true);
checkWatchedDirectories(host, [projectLocation, `${projectLocation}/${nodeModulesAtTypes}`, ...(extraExpectedDirectories || emptyArray)], /*recursive*/ true);
}
describe("from files in same folder", () => {
@@ -8299,6 +8470,7 @@ new C();`
});
it("non relative module name", () => {
const expectedNonRelativeDirectories = [`${projectLocation}/node_modules`, `${projectLocation}/src`];
const module1Name = "module1";
const module2Name = "module2";
const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`;
@@ -8312,7 +8484,7 @@ new C();`
const expectedTrace = getExpectedNonRelativeModuleResolutionTrace(host, file1, module1, module1Name);
getExpectedNonRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace);
verifyTrace(resolutionTrace, expectedTrace);
verifyWatchesWithConfigFile(host, files, file1);
verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories);
file1.content += fileContent;
file2.content += fileContent;
@@ -8322,7 +8494,7 @@ new C();`
getExpectedReusingResolutionFromOldProgram(file1, module1Name),
getExpectedReusingResolutionFromOldProgram(file1, module2Name)
]);
verifyWatchesWithConfigFile(host, files, file1);
verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories);
});
});
@@ -8391,6 +8563,7 @@ new C();`
});
it("non relative module name", () => {
const expectedNonRelativeDirectories = [`${projectLocation}/node_modules`, `${projectLocation}/product`];
const module1Name = "module1";
const module2Name = "module2";
const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`;
@@ -8410,7 +8583,7 @@ new C();`
getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module1, module1Name, getDirectoryPath(file4.path), expectedTrace);
getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module2, module2Name, getDirectoryPath(file4.path), expectedTrace);
verifyTrace(resolutionTrace, expectedTrace);
verifyWatchesWithConfigFile(host, files, file1);
verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories);
file1.content += fileContent;
file2.content += fileContent;
@@ -8423,7 +8596,7 @@ new C();`
getExpectedReusingResolutionFromOldProgram(file1, module1Name),
getExpectedReusingResolutionFromOldProgram(file1, module2Name)
]);
verifyWatchesWithConfigFile(host, files, file1);
verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories);
});
it("non relative module name from inferred project", () => {
@@ -8460,7 +8633,7 @@ new C();`
watchedFiles.push(combinePaths(d, "tsconfig.json"), combinePaths(d, "jsconfig.json"));
});
const watchedRecursiveDirectories = getTypeRootsFromLocation(currentDirectory).concat([
currentDirectory, `${projectLocation}/product/${nodeModules}`,
`${currentDirectory}/node_modules`, `${currentDirectory}/feature`, `${projectLocation}/product/${nodeModules}`,
`${projectLocation}/${nodeModules}`, `${projectLocation}/product/test/${nodeModules}`,
`${projectLocation}/product/test/src/${nodeModules}`
]);
@@ -8530,7 +8703,6 @@ export const x = 10;`
outDir: "../out",
baseUrl: "./",
typeRoots: ["typings"]
}
})
};
@@ -8546,13 +8718,15 @@ export const x = 10;`
checkWatchedDirectories(host, emptyArray, /*recursive*/ false); // since fs resolves to ambient module, shouldnt watch failed lookup
}
else {
checkWatchedDirectoriesDetailed(host, [`${projectRoot}`], 1, /*recursive*/ false); // failed lookup for fs
checkWatchedDirectoriesDetailed(host, [`${projectRoot}`, `${projectRoot}/src`], 1, /*recursive*/ false); // failed lookup for fs
}
const expectedWatchedDirectories = createMap<number>();
expectedWatchedDirectories.set(`${projectRoot}/src`, 2); // Wild card and failed lookup
expectedWatchedDirectories.set(`${projectRoot}/src`, 1); // Wild card
expectedWatchedDirectories.set(`${projectRoot}/src/somefolder`, 1); // failedLookup for somefolder/module2
expectedWatchedDirectories.set(`${projectRoot}/src/node_modules`, 1); // failed lookup for somefolder/module2
expectedWatchedDirectories.set(`${projectRoot}/somefolder`, 1); // failed lookup for somefolder/module2
expectedWatchedDirectories.set(`${projectRoot}/node_modules`, 1); // failed lookup for with node_modules/@types/fs
expectedWatchedDirectories.set(`${projectRoot}/src/typings`, 1); // typeroot directory
expectedWatchedDirectories.set(`${projectRoot}/src/typings`, useNodeFile ? 1 : 2); // typeroot directory + failed lookup if not using node file
checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ true);
}
@@ -8943,7 +9117,7 @@ export function Test2() {
});
});
function makeSampleProjects() {
describe("tsserverProjectSystem project references", () => {
const aTs: File = {
path: "/a/a.ts",
content: "export function fnA() {}\nexport interface IfaceA {}\nexport const instanceA: IfaceA = {};",
@@ -8999,62 +9173,121 @@ export function Test2() {
content: `export declare function fnB(): void;\n//# source${""}MappingURL=b.d.ts.map`,
};
const dummyFile: File = {
path: "/dummy/dummy.ts",
content: "let a = 10;"
};
const userTs: File = {
path: "/user/user.ts",
content: 'import { fnA, instanceA } from "../a/bin/a";\nimport { fnB } from "../b/bin/b";\nexport function fnUser() { fnA(); fnB(); instanceA; }',
};
const host = createServerHost([aTs, aTsconfig, aDtsMap, aDts, bTsconfig, bTs, bDtsMap, bDts, userTs]);
const session = createSession(host);
function makeSampleProjects() {
const host = createServerHost([aTs, aTsconfig, aDtsMap, aDts, bTsconfig, bTs, bDtsMap, bDts, userTs, dummyFile]);
const session = createSession(host);
checkDeclarationFiles(aTs, session, [aDtsMap, aDts]);
checkDeclarationFiles(bTs, session, [bDtsMap, bDts]);
checkDeclarationFiles(aTs, session, [aDtsMap, aDts]);
checkDeclarationFiles(bTs, session, [bDtsMap, bDts]);
// Testing what happens if we delete the original sources.
host.removeFile(bTs.path);
// Testing what happens if we delete the original sources.
host.removeFile(bTs.path);
openFilesForSession([userTs], session);
openFilesForSession([userTs], session);
const service = session.getProjectService();
checkNumberOfProjects(service, { inferredProjects: 1 });
return session;
}
return { session, aTs, bDts, userTs };
}
function verifyInferredProjectUnchanged(session: TestSession) {
checkProjectActualFiles(session.getProjectService().inferredProjects[0], [userTs.path, aDts.path, bDts.path]);
}
function verifyDummyProject(session: TestSession) {
checkProjectActualFiles(session.getProjectService().inferredProjects[0], [dummyFile.path]);
}
function verifyOnlyOrphanInferredProject(session: TestSession) {
openFilesForSession([dummyFile], session);
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 });
verifyDummyProject(session);
}
function verifySingleInferredProject(session: TestSession) {
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 });
verifyInferredProjectUnchanged(session);
// Close user file should close all the projects after opening dummy file
closeFilesForSession([userTs], session);
verifyOnlyOrphanInferredProject(session);
}
function verifyATsConfigProject(session: TestSession) {
checkProjectActualFiles(session.getProjectService().configuredProjects.get(aTsconfig.path)!, [aTs.path, aTsconfig.path]);
}
function verifyATsConfigOriginalProject(session: TestSession) {
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 });
verifyInferredProjectUnchanged(session);
verifyATsConfigProject(session);
// Close user file should close all the projects
closeFilesForSession([userTs], session);
verifyOnlyOrphanInferredProject(session);
}
function verifyATsConfigWhenOpened(session: TestSession) {
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 });
verifyInferredProjectUnchanged(session);
verifyATsConfigProject(session);
closeFilesForSession([userTs], session);
openFilesForSession([dummyFile], session);
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 });
verifyDummyProject(session);
verifyATsConfigProject(session); // ATsConfig should still be alive
}
describe("tsserverProjectSystem project references", () => {
it("goToDefinition", () => {
const { session, aTs, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.DefinitionRequest, protocol.DefinitionResponse>(session, protocol.CommandTypes.Definition, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual(response, [protocolFileSpanFromSubstring(aTs, "fnA")]);
verifySingleInferredProject(session);
});
it("getDefinitionAndBoundSpan", () => {
const { session, aTs, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.DefinitionAndBoundSpanRequest, protocol.DefinitionAndBoundSpanResponse>(session, protocol.CommandTypes.DefinitionAndBoundSpan, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual(response, {
textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA", { index: 1 }),
definitions: [protocolFileSpanFromSubstring(aTs, "fnA")],
});
verifySingleInferredProject(session);
});
it("goToType", () => {
const { session, aTs, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.TypeDefinitionRequest, protocol.TypeDefinitionResponse>(session, protocol.CommandTypes.TypeDefinition, protocolFileLocationFromSubstring(userTs, "instanceA"));
assert.deepEqual(response, [protocolFileSpanFromSubstring(aTs, "IfaceA")]);
verifySingleInferredProject(session);
});
it("goToImplementation", () => {
const { session, aTs, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.ImplementationRequest, protocol.ImplementationResponse>(session, protocol.CommandTypes.Implementation, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual(response, [protocolFileSpanFromSubstring(aTs, "fnA")]);
verifySingleInferredProject(session);
});
it("goToDefinition -- target does not exist", () => {
const { session, bDts, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.DefinitionRequest, protocol.DefinitionResponse>(session, CommandNames.Definition, protocolFileLocationFromSubstring(userTs, "fnB()"));
// bTs does not exist, so stick with bDts
assert.deepEqual(response, [protocolFileSpanFromSubstring(bDts, "fnB")]);
verifySingleInferredProject(session);
});
it("navigateTo", () => {
const { session, aTs, bDts, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.NavtoRequest, protocol.NavtoResponse>(session, CommandNames.Navto, { file: userTs.path, searchValue: "fn" });
assert.deepEqual<ReadonlyArray<protocol.NavtoItem> | undefined>(response, [
{
@@ -9082,6 +9315,8 @@ export function Test2() {
kindModifiers: "export",
},
]);
verifyATsConfigOriginalProject(session);
});
const referenceATs = (aTs: File): protocol.ReferencesResponseItem => makeReferenceItem(aTs, /*isDefinition*/ true, "fnA", "export function fnA() {}");
@@ -9091,7 +9326,7 @@ export function Test2() {
];
it("findAllReferences", () => {
const { session, aTs, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.ReferencesRequest, protocol.ReferencesResponse>(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual<protocol.ReferencesResponseBody | undefined>(response, {
@@ -9100,10 +9335,12 @@ export function Test2() {
symbolStartOffset: protocolLocationFromSubstring(userTs.content, "fnA()").offset,
symbolDisplayString: "(alias) fnA(): void\nimport fnA",
});
verifyATsConfigOriginalProject(session);
});
it("findAllReferences -- starting at definition", () => {
const { session, aTs, userTs } = makeSampleProjects();
const session = makeSampleProjects();
openFilesForSession([aTs], session); // If it's not opened, the reference isn't found.
const response = executeSessionRequest<protocol.ReferencesRequest, protocol.ReferencesResponse>(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(aTs, "fnA"));
assert.deepEqual<protocol.ReferencesResponseBody | undefined>(response, {
@@ -9112,10 +9349,11 @@ export function Test2() {
symbolStartOffset: protocolLocationFromSubstring(aTs.content, "fnA").offset,
symbolDisplayString: "function fnA(): void",
});
verifyATsConfigWhenOpened(session);
});
it("findAllReferencesFull", () => {
const { session, aTs, userTs } = makeSampleProjects();
const session = makeSampleProjects();
interface ReferencesFullRequest extends protocol.FileLocationRequest { command: protocol.CommandTypes.ReferencesFull; }
interface ReferencesFullResponse extends protocol.Response { body: ReadonlyArray<ReferencedSymbol>; }
@@ -9172,10 +9410,11 @@ export function Test2() {
],
}
]);
verifyATsConfigOriginalProject(session);
});
it("findAllReferences -- target does not exist", () => {
const { session, bDts, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.ReferencesRequest, protocol.ReferencesResponse>(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(userTs, "fnB()"));
assert.deepEqual<protocol.ReferencesResponseBody | undefined>(response, {
@@ -9188,6 +9427,7 @@ export function Test2() {
symbolStartOffset: protocolLocationFromSubstring(userTs.content, "fnB()").offset,
symbolDisplayString: "(alias) fnB(): void\nimport fnB",
});
verifySingleInferredProject(session);
});
const renameATs = (aTs: File): protocol.SpanGroup => ({
@@ -9203,7 +9443,7 @@ export function Test2() {
});
it("renameLocations", () => {
const { session, aTs, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual<protocol.RenameResponseBody | undefined>(response, {
info: {
@@ -9216,10 +9456,11 @@ export function Test2() {
},
locs: [renameUserTs(userTs), renameATs(aTs)],
});
verifyATsConfigOriginalProject(session);
});
it("renameLocations -- starting at definition", () => {
const { session, aTs, userTs } = makeSampleProjects();
const session = makeSampleProjects();
openFilesForSession([aTs], session); // If it's not opened, the reference isn't found.
const response = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "fnA"));
assert.deepEqual<protocol.RenameResponseBody | undefined>(response, {
@@ -9233,20 +9474,22 @@ export function Test2() {
},
locs: [renameATs(aTs), renameUserTs(userTs)],
});
verifyATsConfigWhenOpened(session);
});
it("renameLocationsFull", () => {
const { session, aTs, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.RenameFullRequest, protocol.RenameFullResponse>(session, protocol.CommandTypes.RenameLocationsFull, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual<ReadonlyArray<RenameLocation>>(response, [
documentSpanFromSubstring(userTs, "fnA"),
documentSpanFromSubstring(userTs, "fnA", { index: 1 }),
documentSpanFromSubstring(aTs, "fnA"),
]);
verifyATsConfigOriginalProject(session);
});
it("renameLocations -- target does not exist", () => {
const { session, bDts, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(userTs, "fnB()"));
assert.deepEqual<protocol.RenameResponseBody | undefined>(response, {
info: {
@@ -9271,11 +9514,11 @@ export function Test2() {
}
],
});
verifySingleInferredProject(session);
});
it("getEditsForFileRename", () => {
const { session, aTs, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.GetEditsForFileRenameRequest, protocol.GetEditsForFileRenameResponse>(session, protocol.CommandTypes.GetEditsForFileRename, {
oldFilePath: aTs.path,
newFilePath: "/a/aNew.ts",
@@ -9288,10 +9531,64 @@ export function Test2() {
],
},
]);
verifySingleInferredProject(session);
});
});
describe("Untitled files", () => {
describe("duplicate packages", () => {
// Tests that 'moduleSpecifiers.ts' will import from the redirecting file, and not from the file it redirects to, if that can provide a global module specifier.
it("works with import fixes", () => {
const packageContent = "export const foo: number;";
const packageJsonContent = JSON.stringify({ name: "foo", version: "1.2.3" });
const aFooIndex: File = { path: "/a/node_modules/foo/index.d.ts", content: packageContent };
const aFooPackage: File = { path: "/a/node_modules/foo/package.json", content: packageJsonContent };
const bFooIndex: File = { path: "/b/node_modules/foo/index.d.ts", content: packageContent };
const bFooPackage: File = { path: "/b/node_modules/foo/package.json", content: packageJsonContent };
const userContent = 'import("foo");\nfoo';
const aUser: File = { path: "/a/user.ts", content: userContent };
const bUser: File = { path: "/b/user.ts", content: userContent };
const tsconfig: File = {
path: "/tsconfig.json",
content: "{}",
};
const host = createServerHost([aFooIndex, aFooPackage, bFooIndex, bFooPackage, aUser, bUser, tsconfig]);
const session = createSession(host);
openFilesForSession([aUser, bUser], session);
for (const user of [aUser, bUser]) {
const response = executeSessionRequest<protocol.CodeFixRequest, protocol.CodeFixResponse>(session, protocol.CommandTypes.GetCodeFixes, {
file: user.path,
startLine: 2,
startOffset: 1,
endLine: 2,
endOffset: 4,
errorCodes: [Diagnostics.Cannot_find_name_0.code],
});
assert.deepEqual<ReadonlyArray<protocol.CodeFixAction> | undefined>(response, [
{
description: `Import 'foo' from module "foo"`,
fixName: "import",
fixId: "fixMissingImport",
fixAllDescription: "Add all missing imports",
changes: [{
fileName: user.path,
textChanges: [{
start: { line: 1, offset: 1 },
end: { line: 1, offset: 1 },
newText: 'import { foo } from "foo";\n\n',
}],
}],
commands: undefined,
},
]);
}
});
});
describe("tsserverProjectSystem Untitled files", () => {
it("Can convert positions to locations", () => {
const aTs: File = { path: "/proj/a.ts", content: "" };
const tsconfig: File = { path: "/proj/tsconfig.json", content: "{}" };
+4 -4
View File
@@ -146,8 +146,10 @@ namespace ts.projectSystem {
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
const expectedWatchedDirectoriesRecursive = createMap<number>();
expectedWatchedDirectoriesRecursive.set("/a/b", 2); // TypingInstaller and wild card
expectedWatchedDirectoriesRecursive.set("/a/b", 1); // wild card
expectedWatchedDirectoriesRecursive.set("/a/b/node_modules/@types", 1); // type root watch
expectedWatchedDirectoriesRecursive.set("/a/b/node_modules", 1); // TypingInstaller
expectedWatchedDirectoriesRecursive.set("/a/b/bower_components", 1); // TypingInstaller
checkWatchedDirectoriesDetailed(host, expectedWatchedDirectoriesRecursive, /*recursive*/ true);
installer.installAll(/*expectedCount*/ 1);
@@ -844,9 +846,7 @@ namespace ts.projectSystem {
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
const watchedRecursiveDirectoriesExpected = createMap<number>();
watchedRecursiveDirectoriesExpected.set("/", 2); // wild card + type installer
checkWatchedDirectoriesDetailed(host, watchedRecursiveDirectoriesExpected, /*recursive*/ true);
checkWatchedDirectoriesDetailed(host, ["/", "/node_modules", "/bower_components"], 1, /*recursive*/ true);
installer.installAll(/*expectedCount*/ 1);
+1 -1
View File
@@ -455,7 +455,7 @@ namespace ts.server {
break;
}
default:
assertTypeIsNever(response);
assertType<never>(response);
}
}
+66 -65
View File
@@ -64,18 +64,24 @@ namespace ts.server.typingsInstaller {
onRequestCompleted: RequestCompletedAction;
}
function isPackageOrBowerJson(fileName: string) {
const base = getBaseFileName(fileName);
return base === "package.json" || base === "bower.json";
function endsWith(str: string, suffix: string, caseSensitive: boolean): boolean {
const expectedPos = str.length - suffix.length;
return expectedPos >= 0 &&
(str.indexOf(suffix, expectedPos) === expectedPos ||
(!caseSensitive && compareStringsCaseInsensitive(str.substr(expectedPos), suffix) === Comparison.EqualTo));
}
function getDirectoryExcludingNodeModulesOrBowerComponents(f: string) {
const indexOfNodeModules = f.indexOf("/node_modules/");
const indexOfBowerComponents = f.indexOf("/bower_components/");
const subStrLength = indexOfNodeModules === -1 || indexOfBowerComponents === -1 ?
Math.max(indexOfNodeModules, indexOfBowerComponents) :
Math.min(indexOfNodeModules, indexOfBowerComponents);
return subStrLength === -1 ? f : f.substr(0, subStrLength);
function isPackageOrBowerJson(fileName: string, caseSensitive: boolean) {
return endsWith(fileName, "/package.json", caseSensitive) || endsWith(fileName, "/bower.json", caseSensitive);
}
function sameFiles(a: string, b: string, caseSensitive: boolean) {
return a === b || (!caseSensitive && compareStringsCaseInsensitive(a, b) === Comparison.EqualTo);
}
const enum ProjectWatcherType {
FileWatcher = "FileWatcher",
DirectoryWatcher = "DirectoryWatcher"
}
type ProjectWatchers = Map<FileWatcher> & { isInvoked?: boolean; };
@@ -88,7 +94,7 @@ namespace ts.server.typingsInstaller {
private safeList: JsTyping.SafeList | undefined;
readonly pendingRunRequests: PendingRequest[] = [];
private readonly toCanonicalFileName: GetCanonicalFileName;
private readonly globalCacheCanonicalPackageJsonPath: string;
private readonly globalCachePackageJsonPath: string;
private installRunCount = 1;
private inFlightRequestCount = 0;
@@ -103,7 +109,7 @@ namespace ts.server.typingsInstaller {
private readonly throttleLimit: number,
protected readonly log = nullLog) {
this.toCanonicalFileName = createGetCanonicalFileName(installTypingHost.useCaseSensitiveFileNames);
this.globalCacheCanonicalPackageJsonPath = combinePaths(this.toCanonicalFileName(globalCachePath), "package.json");
this.globalCachePackageJsonPath = combinePaths(globalCachePath, "package.json");
if (this.log.isEnabled()) {
this.log.writeLine(`Global cache location '${globalCachePath}', safe file path '${safeListPath}', types map path ${typesMapLocation}`);
}
@@ -406,84 +412,79 @@ namespace ts.server.typingsInstaller {
watchers.isInvoked = false;
const isLoggingEnabled = this.log.isEnabled();
const createProjectWatcher = (path: string, createWatch: (path: string) => FileWatcher) => {
toRemove.delete(path);
if (watchers.has(path)) {
const createProjectWatcher = (path: string, projectWatcherType: ProjectWatcherType) => {
const canonicalPath = this.toCanonicalFileName(path);
toRemove.delete(canonicalPath);
if (watchers.has(canonicalPath)) {
return;
}
watchers.set(path, createWatch(path));
};
const createProjectFileWatcher = (file: string): FileWatcher => {
if (isLoggingEnabled) {
this.log.writeLine(`FileWatcher:: Added:: WatchInfo: ${file}`);
this.log.writeLine(`${projectWatcherType}:: Added:: WatchInfo: ${path}`);
}
const watcher = this.installTypingHost.watchFile!(file, (f, eventKind) => { // TODO: GH#18217
if (isLoggingEnabled) {
this.log.writeLine(`FileWatcher:: Triggered with ${f} eventKind: ${FileWatcherEventKind[eventKind]}:: WatchInfo: ${file}:: handler is already invoked '${watchers.isInvoked}'`);
}
if (!watchers.isInvoked) {
watchers.isInvoked = true;
this.sendResponse({ projectName, kind: ActionInvalidate });
}
}, /*pollingInterval*/ 2000);
const watcher = projectWatcherType === ProjectWatcherType.FileWatcher ?
this.installTypingHost.watchFile!(path, (f, eventKind) => { // TODO: GH#18217
if (isLoggingEnabled) {
this.log.writeLine(`FileWatcher:: Triggered with ${f} eventKind: ${FileWatcherEventKind[eventKind]}:: WatchInfo: ${path}:: handler is already invoked '${watchers.isInvoked}'`);
}
if (!watchers.isInvoked) {
watchers.isInvoked = true;
this.sendResponse({ projectName, kind: ActionInvalidate });
}
}, /*pollingInterval*/ 2000) :
this.installTypingHost.watchDirectory!(path, f => { // TODO: GH#18217
if (isLoggingEnabled) {
this.log.writeLine(`DirectoryWatcher:: Triggered with ${f} :: WatchInfo: ${path} recursive :: handler is already invoked '${watchers.isInvoked}'`);
}
if (watchers.isInvoked || !fileExtensionIs(f, Extension.Json)) {
return;
}
return isLoggingEnabled ? {
if (isPackageOrBowerJson(f, this.installTypingHost.useCaseSensitiveFileNames) &&
!sameFiles(f, this.globalCachePackageJsonPath, this.installTypingHost.useCaseSensitiveFileNames)) {
watchers.isInvoked = true;
this.sendResponse({ projectName, kind: ActionInvalidate });
}
}, /*recursive*/ true);
watchers.set(canonicalPath, isLoggingEnabled ? {
close: () => {
this.log.writeLine(`FileWatcher:: Closed:: WatchInfo: ${file}`);
this.log.writeLine(`${projectWatcherType}:: Closed:: WatchInfo: ${path}`);
watcher.close();
}
} : watcher;
};
const createProjectDirectoryWatcher = (dir: string): FileWatcher => {
if (isLoggingEnabled) {
this.log.writeLine(`DirectoryWatcher:: Added:: WatchInfo: ${dir} recursive`);
}
const watcher = this.installTypingHost.watchDirectory!(dir, f => { // TODO: GH#18217
if (isLoggingEnabled) {
this.log.writeLine(`DirectoryWatcher:: Triggered with ${f} :: WatchInfo: ${dir} recursive :: handler is already invoked '${watchers.isInvoked}'`);
}
if (watchers.isInvoked) {
return;
}
f = this.toCanonicalFileName(f);
if (f !== this.globalCacheCanonicalPackageJsonPath && isPackageOrBowerJson(f)) {
watchers.isInvoked = true;
this.sendResponse({ projectName, kind: ActionInvalidate });
}
}, /*recursive*/ true);
return isLoggingEnabled ? {
close: () => {
this.log.writeLine(`DirectoryWatcher:: Closed:: WatchInfo: ${dir} recursive`);
watcher.close();
}
} : watcher;
} : watcher);
};
// Create watches from list of files
for (const file of files) {
const filePath = this.toCanonicalFileName(file);
if (isPackageOrBowerJson(filePath)) {
if (file.endsWith("/package.json") || file.endsWith("/bower.json")) {
// package.json or bower.json exists, watch the file to detect changes and update typings
createProjectWatcher(filePath, createProjectFileWatcher);
createProjectWatcher(file, ProjectWatcherType.FileWatcher);
continue;
}
// path in projectRoot, watch project root
if (containsPath(projectRootPath, filePath, projectRootPath, !this.installTypingHost.useCaseSensitiveFileNames)) {
createProjectWatcher(projectRootPath, createProjectDirectoryWatcher);
if (containsPath(projectRootPath, file, projectRootPath, !this.installTypingHost.useCaseSensitiveFileNames)) {
const subDirectory = file.indexOf(directorySeparator, projectRootPath.length + 1);
if (subDirectory !== -1) {
// Watch subDirectory
createProjectWatcher(file.substr(0, subDirectory), ProjectWatcherType.DirectoryWatcher);
}
else {
// Watch the directory itself
createProjectWatcher(file, ProjectWatcherType.DirectoryWatcher);
}
continue;
}
// path in global cache, watch global cache
if (containsPath(this.globalCachePath, filePath, projectRootPath, !this.installTypingHost.useCaseSensitiveFileNames)) {
createProjectWatcher(this.globalCachePath, createProjectDirectoryWatcher);
if (containsPath(this.globalCachePath, file, projectRootPath, !this.installTypingHost.useCaseSensitiveFileNames)) {
createProjectWatcher(this.globalCachePath, ProjectWatcherType.DirectoryWatcher);
continue;
}
// Get path without node_modules and bower_components
createProjectWatcher(getDirectoryExcludingNodeModulesOrBowerComponents(getDirectoryPath(filePath)), createProjectDirectoryWatcher);
// watch node_modules or bower_components
createProjectWatcher(file, ProjectWatcherType.DirectoryWatcher);
}
// Remove unused watches