diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3e3c3964b9455..2650e50a04ef3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5165,7 +5165,7 @@ namespace ts { // Appends the type parameters given by a list of declarations to a set of type parameters and returns the resulting set. // The function allocates a new array if the input type parameter set is undefined, but otherwise it modifies the set // in-place and returns the same array. - function appendTypeParameters(typeParameters: TypeParameter[], declarations: ReadonlyArray): TypeParameter[] { + function appendTypeParameters(typeParameters: TypeParameter[] | undefined, declarations: ReadonlyArray): TypeParameter[] { for (const declaration of declarations) { typeParameters = appendIfUnique(typeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode(declaration))); } @@ -5206,7 +5206,7 @@ namespace ts { else if (node.kind === SyntaxKind.ConditionalType) { return concatenate(outerTypeParameters, getInferTypeParameters(node)); } - const outerAndOwnTypeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(node) || emptyArray); + const outerAndOwnTypeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(node)); const thisType = includeThisTypes && (node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.InterfaceDeclaration) && getDeclaredTypeOfClassOrInterface(getSymbolOfNode(node)).thisType; @@ -5224,17 +5224,14 @@ namespace ts { // The local type parameters are the combined set of type parameters from all declarations of the class, // interface, or type alias. function getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol: Symbol): TypeParameter[] { - let result: TypeParameter[]; + let result: TypeParameter[] | undefined; for (const node of symbol.declarations) { if (node.kind === SyntaxKind.InterfaceDeclaration || node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression || isTypeAlias(node)) { const declaration = node; - const typeParameters = getEffectiveTypeParameterDeclarations(declaration); - if (typeParameters) { - result = appendTypeParameters(result, typeParameters); - } + result = appendTypeParameters(result, getEffectiveTypeParameterDeclarations(declaration)); } } return result; @@ -5744,7 +5741,7 @@ namespace ts { const typeParameters = getEffectiveTypeParameterDeclarations(node); return (node.kind === SyntaxKind.Constructor || (returnType && isThislessType(returnType))) && node.parameters.every(isThislessVariableLikeDeclaration) && - (!typeParameters || typeParameters.every(isThislessTypeParameter)); + typeParameters.every(isThislessTypeParameter); } /** @@ -7071,11 +7068,11 @@ namespace ts { // Return list of type parameters with duplicates removed (duplicate identifier errors are generated in the actual // type checking functions). - function getTypeParametersFromDeclaration(declaration: DeclarationWithTypeParameters): TypeParameter[] { - let result: TypeParameter[]; - forEach(getEffectiveTypeParameterDeclarations(declaration), node => { + function getTypeParametersFromDeclaration(declaration: DeclarationWithTypeParameters): TypeParameter[] | undefined { + let result: TypeParameter[] | undefined; + for (const node of getEffectiveTypeParameterDeclarations(declaration)) { result = appendIfUnique(result, getDeclaredTypeOfTypeParameter(node.symbol)); - }); + } return result; } @@ -22679,7 +22676,7 @@ namespace ts { // 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) && typeParameters && last(getSymbolOfNode(node)!.declarations) === node) { + if (!(node.flags & NodeFlags.Ambient) && last(getSymbolOfNode(node)!.declarations) === node) { for (const typeParameter of typeParameters) { if (!(getMergedSymbol(typeParameter.symbol).isReferenced & SymbolFlags.TypeParameter) && !isIdentifierThatStartsWithUnderScore(typeParameter.name)) { addDiagnostic(UnusedKind.Parameter, createDiagnosticForNode(typeParameter.name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(typeParameter.symbol))); @@ -24047,7 +24044,7 @@ namespace ts { /** * Check each type parameter and check that type parameters have no duplicate type parameter declarations */ - function checkTypeParameters(typeParameterDeclarations: ReadonlyArray) { + function checkTypeParameters(typeParameterDeclarations: ReadonlyArray | undefined) { if (typeParameterDeclarations) { let seenDefault = false; for (let i = 0; i < typeParameterDeclarations.length; i++) { @@ -24103,7 +24100,7 @@ namespace ts { for (const declaration of declarations) { // If this declaration has too few or too many type parameters, we report an error const sourceParameters = getEffectiveTypeParameterDeclarations(declaration); - const numTypeParameters = length(sourceParameters); + const numTypeParameters = sourceParameters.length; if (numTypeParameters < minTypeArgumentCount || numTypeParameters > maxTypeArgumentCount) { return false; } @@ -27371,7 +27368,7 @@ namespace ts { } } - function checkGrammarTypeParameterList(typeParameters: NodeArray, file: SourceFile): boolean { + function checkGrammarTypeParameterList(typeParameters: NodeArray | undefined, file: SourceFile): boolean { if (typeParameters && typeParameters.length === 0) { const start = typeParameters.pos - "<".length; const end = skipTrivia(file.text, typeParameters.end) + ">".length; @@ -27427,7 +27424,7 @@ namespace ts { function checkGrammarClassLikeDeclaration(node: ClassLikeDeclaration): boolean { const file = getSourceFileOfNode(node); - return checkGrammarClassDeclarationHeritageClauses(node) || checkGrammarTypeParameterList(getEffectiveTypeParameterDeclarations(node), file); + return checkGrammarClassDeclarationHeritageClauses(node) || checkGrammarTypeParameterList(node.typeParameters, file); } function checkGrammarArrowFunction(node: Node, file: SourceFile): boolean { @@ -28199,8 +28196,8 @@ namespace ts { function checkGrammarConstructorTypeParameters(node: ConstructorDeclaration) { const typeParameters = getEffectiveTypeParameterDeclarations(node); - if (typeParameters) { - const { pos, end } = isNodeArray(typeParameters) ? typeParameters : first(typeParameters); + if (isNodeArray(typeParameters)) { + const { pos, end } = typeParameters; return grammarErrorAtPos(node, pos, end - pos, Diagnostics.Type_parameters_cannot_appear_on_a_constructor_declaration); } } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 8fab560850db2..fa6f65640da1a 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3101,9 +3101,9 @@ namespace ts { * Gets the effective type parameters. If the node was parsed in a * JavaScript file, gets the type parameters from the `@template` tag from JSDoc. */ - export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters) { + export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters): ReadonlyArray { if (isJSDocSignature(node)) { - return undefined; + return emptyArray; } if (isJSDocTypeAlias(node)) { Debug.assert(node.parent.kind === SyntaxKind.JSDocComment); @@ -3114,17 +3114,14 @@ namespace ts { templateTagNodes.hasTrailingComma = false; return templateTagNodes; } - return node.typeParameters || (isInJavaScriptFile(node) ? getJSDocTypeParameterDeclarations(node) : undefined); + return node.typeParameters || (isInJavaScriptFile(node) ? getJSDocTypeParameterDeclarations(node) : emptyArray); } - export function getJSDocTypeParameterDeclarations(node: DeclarationWithTypeParameters) { + export function getJSDocTypeParameterDeclarations(node: DeclarationWithTypeParameters): ReadonlyArray { const tags = filter(getJSDocTags(node), isJSDocTemplateTag); - for (const tag of tags) { - if (!(tag.parent.kind === SyntaxKind.JSDocComment && find(tag.parent.tags, isJSDocTypeAlias))) { - // template tags are only available when a typedef isn't already using them - return tag.typeParameters; - } - } + // template tags are only available when a typedef isn't already using them + const tag = find(tags, tag => !(tag.parent.kind === SyntaxKind.JSDocComment && find(tag.parent.tags, isJSDocTypeAlias))); + return (tag && tag.typeParameters) || emptyArray; } /** diff --git a/src/services/codefixes/annotateWithTypeFromJSDoc.ts b/src/services/codefixes/annotateWithTypeFromJSDoc.ts index 5b809452677d5..7b66467843650 100644 --- a/src/services/codefixes/annotateWithTypeFromJSDoc.ts +++ b/src/services/codefixes/annotateWithTypeFromJSDoc.ts @@ -43,7 +43,7 @@ namespace ts.codefix { if (isFunctionLikeDeclaration(decl) && (getJSDocReturnType(decl) || decl.parameters.some(p => !!getJSDocType(p)))) { if (!decl.typeParameters) { const typeParameters = getJSDocTypeParameterDeclarations(decl); - if (typeParameters) changes.insertTypeParameters(sourceFile, decl, typeParameters); + if (typeParameters.length) changes.insertTypeParameters(sourceFile, decl, typeParameters); } const needParens = isArrowFunction(decl) && !findChildOfKind(decl, SyntaxKind.OpenParenToken, sourceFile); if (needParens) changes.insertNodeBefore(sourceFile, first(decl.parameters), createToken(SyntaxKind.OpenParenToken)); diff --git a/src/services/codefixes/fixUnusedIdentifier.ts b/src/services/codefixes/fixUnusedIdentifier.ts index e15407b625bcd..f22a87f102e81 100644 --- a/src/services/codefixes/fixUnusedIdentifier.ts +++ b/src/services/codefixes/fixUnusedIdentifier.ts @@ -166,8 +166,9 @@ namespace ts.codefix { case SyntaxKind.TypeParameter: const typeParameters = getEffectiveTypeParameterDeclarations(parent.parent); if (typeParameters.length === 1) { - const previousToken = getTokenAtPosition(sourceFile, typeParameters.pos - 1, /*includeJsDocComment*/ false); - const nextToken = getTokenAtPosition(sourceFile, typeParameters.end, /*includeJsDocComment*/ false); + const { pos, end } = cast(typeParameters, isNodeArray); + const previousToken = getTokenAtPosition(sourceFile, pos - 1, /*includeJsDocComment*/ false); + const nextToken = getTokenAtPosition(sourceFile, end, /*includeJsDocComment*/ false); Debug.assert(previousToken.kind === SyntaxKind.LessThanToken); Debug.assert(nextToken.kind === SyntaxKind.GreaterThanToken); diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index 4c5d7ff6f0961..32a39c8f9ad86 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -1464,7 +1464,7 @@ namespace ts.refactor.extractSymbol { } // Note that we add the current node's type parameters *after* updating the corresponding scope. - if (isDeclarationWithTypeParameters(curr) && getEffectiveTypeParameterDeclarations(curr)) { + if (isDeclarationWithTypeParameters(curr)) { for (const typeParameterDecl of getEffectiveTypeParameterDeclarations(curr)) { const typeParameter = checker.getTypeAtLocation(typeParameterDecl) as TypeParameter; if (allTypeParameterUsages.has(typeParameter.id.toString())) { @@ -1534,20 +1534,8 @@ namespace ts.refactor.extractSymbol { return { target, usagesPerScope, functionErrorsPerScope, constantErrorsPerScope, exposedVariableDeclarations }; - function hasTypeParameters(node: Node) { - return isDeclarationWithTypeParameters(node) && - getEffectiveTypeParameterDeclarations(node) && - getEffectiveTypeParameterDeclarations(node).length > 0; - } - function isInGenericContext(node: Node) { - for (; node; node = node.parent) { - if (hasTypeParameters(node)) { - return true; - } - } - - return false; + return !!findAncestor(node, n => isDeclarationWithTypeParameters(n) && getEffectiveTypeParameterDeclarations(n).length !== 0); } function recordTypeParameterUsages(type: Type) {