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:
Nathan Shively-Sanders
2018-06-20 16:28:30 -07:00
committed by GitHub
parent 3eeb36bd22
commit 0f55566cf4
18 changed files with 60 additions and 25 deletions
+12 -17
View File
@@ -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)) {
+1 -1
View File
@@ -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
+1 -1
View File
@@ -770,7 +770,7 @@ namespace ts {
enableSubstitutionsForBlockScopedBindings();
}
const extendsClauseElement = getClassExtendsHeritageClauseElement(node);
const extendsClauseElement = getEffectiveBaseTypeNode(node);
const classFunction = createFunctionExpression(
/*modifiers*/ undefined,
/*asteriskToken*/ undefined,
+1 -1
View File
@@ -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;
+13 -2
View File
@@ -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
View File
@@ -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 {}