mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
In JS, always check the extends tag of a class before its heritage clause (#25111)
* Check extends tag first in getClassExtendsHeritageClauseElement Previously getBaseTypeNodeOfClass checked, but this is only used in a few places. * Fix names and add test * Update API baseline * Move jsdocAugments tests to conformance/jsdoc * Better naming in checkClassLikeDeclaration
This commit is contained in:
committed by
GitHub
parent
3eeb36bd22
commit
0f55566cf4
+12
-17
@@ -5392,16 +5392,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
function getBaseTypeNodeOfClass(type: InterfaceType): ExpressionWithTypeArguments | undefined {
|
||||
const decl = <ClassLikeDeclaration>type.symbol.valueDeclaration;
|
||||
if (isInJavaScriptFile(decl)) {
|
||||
// Prefer an @augments tag because it may have type parameters.
|
||||
const tag = getJSDocAugmentsTag(decl);
|
||||
if (tag) {
|
||||
return tag.class;
|
||||
}
|
||||
}
|
||||
|
||||
return getClassExtendsHeritageClauseElement(decl);
|
||||
return getEffectiveBaseTypeNode(type.symbol.valueDeclaration as ClassLikeDeclaration);
|
||||
}
|
||||
|
||||
function getConstructorsForTypeArguments(type: Type, typeArgumentNodes: ReadonlyArray<TypeNode> | undefined, location: Node): Signature[] {
|
||||
@@ -5428,7 +5419,7 @@ namespace ts {
|
||||
function getBaseConstructorTypeOfClass(type: InterfaceType): Type {
|
||||
if (!type.resolvedBaseConstructorType) {
|
||||
const decl = <ClassLikeDeclaration>type.symbol.valueDeclaration;
|
||||
const extended = getClassExtendsHeritageClauseElement(decl);
|
||||
const extended = getEffectiveBaseTypeNode(decl);
|
||||
const baseTypeNode = getBaseTypeNodeOfClass(type);
|
||||
if (!baseTypeNode) {
|
||||
return type.resolvedBaseConstructorType = undefinedType;
|
||||
@@ -14764,7 +14755,7 @@ namespace ts {
|
||||
|
||||
function checkThisBeforeSuper(node: Node, container: Node, diagnosticMessage: DiagnosticMessage) {
|
||||
const containingClassDecl = <ClassDeclaration>container.parent;
|
||||
const baseTypeNode = getClassExtendsHeritageClauseElement(containingClassDecl);
|
||||
const baseTypeNode = getEffectiveBaseTypeNode(containingClassDecl);
|
||||
|
||||
// If a containing class does not have extends clause or the class extends null
|
||||
// skip checking whether super statement is called before "this" accessing.
|
||||
@@ -15040,7 +15031,7 @@ namespace ts {
|
||||
|
||||
// at this point the only legal case for parent is ClassLikeDeclaration
|
||||
const classLikeDeclaration = <ClassLikeDeclaration>container.parent;
|
||||
if (!getClassExtendsHeritageClauseElement(classLikeDeclaration)) {
|
||||
if (!getEffectiveBaseTypeNode(classLikeDeclaration)) {
|
||||
error(node, Diagnostics.super_can_only_be_referenced_in_a_derived_class);
|
||||
return errorType;
|
||||
}
|
||||
@@ -18678,7 +18669,7 @@ namespace ts {
|
||||
if (superType !== errorType) {
|
||||
// In super call, the candidate signatures are the matching arity signatures of the base constructor function instantiated
|
||||
// with the type arguments specified in the extends clause.
|
||||
const baseTypeNode = getClassExtendsHeritageClauseElement(getContainingClass(node)!);
|
||||
const baseTypeNode = getEffectiveBaseTypeNode(getContainingClass(node)!);
|
||||
if (baseTypeNode) {
|
||||
const baseConstructors = getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.typeArguments, baseTypeNode);
|
||||
return resolveCall(node, baseConstructors, candidatesOutArray);
|
||||
@@ -21474,7 +21465,7 @@ namespace ts {
|
||||
// Constructors of classes with no extends clause may not contain super calls, whereas
|
||||
// constructors of derived classes must contain at least one super call somewhere in their function body.
|
||||
const containingClassDecl = <ClassDeclaration>node.parent;
|
||||
if (getClassExtendsHeritageClauseElement(containingClassDecl)) {
|
||||
if (getEffectiveBaseTypeNode(containingClassDecl)) {
|
||||
captureLexicalThis(node.parent, containingClassDecl);
|
||||
const classExtendsNull = classDeclarationExtendsNull(containingClassDecl);
|
||||
const superCall = getSuperCallInConstructor(node);
|
||||
@@ -22651,7 +22642,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
const name = getIdentifierFromEntityNameExpression(node.class.expression);
|
||||
const extend = getClassExtendsHeritageClauseElement(classLike);
|
||||
const extend = getClassExtendsHeritageElement(classLike);
|
||||
if (extend) {
|
||||
const className = getIdentifierFromEntityNameExpression(extend.expression);
|
||||
if (className && name.escapedText !== className.escapedText) {
|
||||
@@ -24426,7 +24417,7 @@ namespace ts {
|
||||
checkClassForStaticPropertyNameConflicts(node);
|
||||
}
|
||||
|
||||
const baseTypeNode = getClassExtendsHeritageClauseElement(node);
|
||||
const baseTypeNode = getEffectiveBaseTypeNode(node);
|
||||
if (baseTypeNode) {
|
||||
if (languageVersion < ScriptTarget.ES2015) {
|
||||
checkExternalEmitHelpers(baseTypeNode.parent, ExternalEmitHelpers.Extends);
|
||||
@@ -24439,6 +24430,10 @@ namespace ts {
|
||||
const staticBaseType = getApparentType(baseConstructorType);
|
||||
checkBaseTypeAccessibility(staticBaseType, baseTypeNode);
|
||||
checkSourceElement(baseTypeNode.expression);
|
||||
const extendsNode = getClassExtendsHeritageElement(node);
|
||||
if (extendsNode && extendsNode !== baseTypeNode) {
|
||||
checkExpression(extendsNode.expression);
|
||||
}
|
||||
if (some(baseTypeNode.typeArguments)) {
|
||||
forEach(baseTypeNode.typeArguments, checkSourceElement);
|
||||
for (const constructor of getConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode)) {
|
||||
|
||||
@@ -1022,7 +1022,7 @@ namespace ts {
|
||||
}
|
||||
const members = createNodeArray(concatenate(parameterProperties, visitNodes(input.members, visitDeclarationSubtree)));
|
||||
|
||||
const extendsClause = getClassExtendsHeritageClauseElement(input);
|
||||
const extendsClause = getEffectiveBaseTypeNode(input);
|
||||
if (extendsClause && !isEntityNameExpression(extendsClause.expression) && extendsClause.expression.kind !== SyntaxKind.NullKeyword) {
|
||||
// We must add a temporary declaration for the extends clause expression
|
||||
|
||||
|
||||
@@ -770,7 +770,7 @@ namespace ts {
|
||||
enableSubstitutionsForBlockScopedBindings();
|
||||
}
|
||||
|
||||
const extendsClauseElement = getClassExtendsHeritageClauseElement(node);
|
||||
const extendsClauseElement = getEffectiveBaseTypeNode(node);
|
||||
const classFunction = createFunctionExpression(
|
||||
/*modifiers*/ undefined,
|
||||
/*asteriskToken*/ undefined,
|
||||
|
||||
@@ -586,7 +586,7 @@ namespace ts {
|
||||
function getClassFacts(node: ClassDeclaration, staticProperties: ReadonlyArray<PropertyDeclaration>) {
|
||||
let facts = ClassFacts.None;
|
||||
if (some(staticProperties)) facts |= ClassFacts.HasStaticInitializedProperties;
|
||||
const extendsClauseElement = getClassExtendsHeritageClauseElement(node);
|
||||
const extendsClauseElement = getEffectiveBaseTypeNode(node);
|
||||
if (extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword) facts |= ClassFacts.IsDerivedClass;
|
||||
if (shouldEmitDecorateCallForClass(node)) facts |= ClassFacts.HasConstructorDecorators;
|
||||
if (childIsDecorated(node)) facts |= ClassFacts.HasMemberDecorators;
|
||||
|
||||
@@ -2413,7 +2413,18 @@ namespace ts {
|
||||
return isEntityNameExpression(e) || isClassExpression(e);
|
||||
}
|
||||
|
||||
export function getClassExtendsHeritageClauseElement(node: ClassLikeDeclaration | InterfaceDeclaration) {
|
||||
export function getEffectiveBaseTypeNode(node: ClassLikeDeclaration | InterfaceDeclaration) {
|
||||
if (isInJavaScriptFile(node)) {
|
||||
// Prefer an @augments tag because it may have type parameters.
|
||||
const tag = getJSDocAugmentsTag(node);
|
||||
if (tag) {
|
||||
return tag.class;
|
||||
}
|
||||
}
|
||||
return getClassExtendsHeritageElement(node);
|
||||
}
|
||||
|
||||
export function getClassExtendsHeritageElement(node: ClassLikeDeclaration | InterfaceDeclaration) {
|
||||
const heritageClause = getHeritageClause(node.heritageClauses, SyntaxKind.ExtendsKeyword);
|
||||
return heritageClause && heritageClause.types.length > 0 ? heritageClause.types[0] : undefined;
|
||||
}
|
||||
@@ -2426,7 +2437,7 @@ namespace ts {
|
||||
/** Returns the node in an `extends` or `implements` clause of a class or interface. */
|
||||
export function getAllSuperTypeNodes(node: Node): ReadonlyArray<TypeNode> {
|
||||
return isInterfaceDeclaration(node) ? getInterfaceBaseTypeNodes(node) || emptyArray
|
||||
: isClassLike(node) ? concatenate(singleElementArray(getClassExtendsHeritageClauseElement(node)), getClassImplementsHeritageClauseElements(node)) || emptyArray
|
||||
: isClassLike(node) ? concatenate(singleElementArray(getEffectiveBaseTypeNode(node)), getClassImplementsHeritageClauseElements(node)) || emptyArray
|
||||
: emptyArray;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace ts.codefix {
|
||||
}
|
||||
|
||||
function addMissingMembers(classDeclaration: ClassLikeDeclaration, sourceFile: SourceFile, checker: TypeChecker, changeTracker: textChanges.ChangeTracker, preferences: UserPreferences): void {
|
||||
const extendsNode = getClassExtendsHeritageClauseElement(classDeclaration)!;
|
||||
const extendsNode = getEffectiveBaseTypeNode(classDeclaration)!;
|
||||
const instantiatedExtendsType = checker.getTypeAtLocation(extendsNode)!;
|
||||
|
||||
// Note that this is ultimately derived from a map indexed by symbol names,
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace ts.codefix {
|
||||
}
|
||||
|
||||
function getHeritageClauseSymbolTable (classDeclaration: ClassLikeDeclaration, checker: TypeChecker): SymbolTable {
|
||||
const heritageClauseNode = getClassExtendsHeritageClauseElement(classDeclaration);
|
||||
const heritageClauseNode = getEffectiveBaseTypeNode(classDeclaration);
|
||||
if (!heritageClauseNode) return createSymbolTable();
|
||||
const heritageClauseType = checker.getTypeAtLocation(heritageClauseNode) as InterfaceType;
|
||||
const heritageClauseTypeSymbols = checker.getPropertiesOfType(heritageClauseType);
|
||||
|
||||
+2
-1
@@ -6293,7 +6293,8 @@ declare namespace ts {
|
||||
function isIdentifierName(node: Identifier): boolean;
|
||||
function isAliasSymbolDeclaration(node: Node): boolean;
|
||||
function exportAssignmentIsAlias(node: ExportAssignment | BinaryExpression): boolean;
|
||||
function getClassExtendsHeritageClauseElement(node: ClassLikeDeclaration | InterfaceDeclaration): ExpressionWithTypeArguments | undefined;
|
||||
function getEffectiveBaseTypeNode(node: ClassLikeDeclaration | InterfaceDeclaration): ExpressionWithTypeArguments | undefined;
|
||||
function getClassExtendsHeritageElement(node: ClassLikeDeclaration | InterfaceDeclaration): ExpressionWithTypeArguments | undefined;
|
||||
function getClassImplementsHeritageClauseElements(node: ClassLikeDeclaration): NodeArray<ExpressionWithTypeArguments> | undefined;
|
||||
/** Returns the node in an `extends` or `implements` clause of a class or interface. */
|
||||
function getAllSuperTypeNodes(node: Node): ReadonlyArray<TypeNode>;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
=== tests/cases/conformance/jsdoc/bug25101.js ===
|
||||
/**
|
||||
* @template T
|
||||
* @extends {Set<T>} Should prefer this Set<T>, not the Set in the heritage clause
|
||||
*/
|
||||
class My extends Set {}
|
||||
>My : Symbol(My, Decl(bug25101.js, 0, 0))
|
||||
>Set : Symbol(Set, Decl(lib.es2015.collection.d.ts, --, --), Decl(lib.es2015.collection.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
=== tests/cases/conformance/jsdoc/bug25101.js ===
|
||||
/**
|
||||
* @template T
|
||||
* @extends {Set<T>} Should prefer this Set<T>, not the Set in the heritage clause
|
||||
*/
|
||||
class My extends Set {}
|
||||
>My : My<T>
|
||||
>Set : Set<T>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// @noEmit: true
|
||||
// @allowJs: true
|
||||
// @checkJs: true
|
||||
// @lib: esnext
|
||||
// @Filename: bug25101.js
|
||||
/**
|
||||
* @template T
|
||||
* @extends {Set<T>} Should prefer this Set<T>, not the Set in the heritage clause
|
||||
*/
|
||||
class My extends Set {}
|
||||
Reference in New Issue
Block a user