Fix default property assigned prototype (#40836)

* Fix default-property-assignment decls+prototype property decls

The check in getAssignedClassSymbol forgot to allow for default-property
assignment declarations, in part because it wasn't using a utility
function to do so.

* small cleanup

* make allowDeclaration parameter required
This commit is contained in:
Nathan Shively-Sanders 2020-09-30 08:36:52 -07:00 committed by GitHub
parent df33dd593f
commit f615e229d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 154 additions and 66 deletions

View file

@ -507,6 +507,7 @@ namespace ts {
},
getAugmentedPropertiesOfType,
getRootSymbols,
getSymbolOfExpando,
getContextualType: (nodeIn: Expression, contextFlags?: ContextFlags) => {
const node = getParseTreeNode(nodeIn, isExpression);
if (!node) {
@ -8728,9 +8729,9 @@ namespace ts {
let links = getSymbolLinks(symbol);
const originalLinks = links;
if (!links.type) {
const jsDeclaration = symbol.valueDeclaration && getDeclarationOfExpando(symbol.valueDeclaration);
if (jsDeclaration) {
const merged = mergeJSSymbols(symbol, getSymbolOfNode(jsDeclaration));
const expando = symbol.valueDeclaration && getSymbolOfExpando(symbol.valueDeclaration, /*allowDeclaration*/ false);
if (expando) {
const merged = mergeJSSymbols(symbol, expando);
if (merged) {
// note:we overwrite links because we just cloned the symbol
symbol = links = merged;
@ -24828,7 +24829,7 @@ namespace ts {
const exprType = checkJsxAttribute(attributeDecl, checkMode);
objectFlags |= getObjectFlags(exprType) & ObjectFlags.PropagatingFlags;
const attributeSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient | member.flags, member.escapedName);
const attributeSymbol = createSymbol(SymbolFlags.Property | member.flags, member.escapedName);
attributeSymbol.declarations = member.declarations;
attributeSymbol.parent = member.parent;
if (member.valueDeclaration) {
@ -24887,7 +24888,7 @@ namespace ts {
const contextualType = getApparentTypeOfContextualType(openingLikeElement.attributes);
const childrenContextualType = contextualType && getTypeOfPropertyOfContextualType(contextualType, jsxChildrenPropertyName);
// If there are children in the body of JSX element, create dummy attribute "children" with the union of children types so that it will pass the attribute checking process
const childrenPropSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, jsxChildrenPropertyName);
const childrenPropSymbol = createSymbol(SymbolFlags.Property, jsxChildrenPropertyName);
childrenPropSymbol.type = childrenTypes.length === 1 ? childrenTypes[0] :
childrenContextualType && forEachType(childrenContextualType, isTupleLikeType) ? createTupleType(childrenTypes) :
createArrayType(getUnionType(childrenTypes));
@ -28075,15 +28076,59 @@ namespace ts {
}
function getAssignedClassSymbol(decl: Declaration): Symbol | undefined {
const assignmentSymbol = decl && decl.parent &&
(isFunctionDeclaration(decl) && getSymbolOfNode(decl) ||
isBinaryExpression(decl.parent) && getSymbolOfNode(decl.parent.left) ||
isVariableDeclaration(decl.parent) && getSymbolOfNode(decl.parent));
const prototype = assignmentSymbol && assignmentSymbol.exports && assignmentSymbol.exports.get("prototype" as __String);
const init = prototype && prototype.valueDeclaration && getAssignedJSPrototype(prototype.valueDeclaration);
const assignmentSymbol = decl && getSymbolOfExpando(decl, /*allowDeclaration*/ true);
const prototype = assignmentSymbol?.exports?.get("prototype" as __String);
const init = prototype?.valueDeclaration && getAssignedJSPrototype(prototype.valueDeclaration);
return init ? getSymbolOfNode(init) : undefined;
}
function getSymbolOfExpando(node: Node, allowDeclaration: boolean): Symbol | undefined {
if (!node.parent) {
return undefined;
}
let name: Expression | BindingName | undefined;
let decl: Node | undefined;
if (isVariableDeclaration(node.parent) && node.parent.initializer === node) {
if (!isInJSFile(node) && !isVarConst(node.parent)) {
return undefined;
}
name = node.parent.name;
decl = node.parent;
}
else if (isBinaryExpression(node.parent)) {
const parentNode = node.parent;
const parentNodeOperator = node.parent.operatorToken.kind;
if (parentNodeOperator === SyntaxKind.EqualsToken && (allowDeclaration || parentNode.right === node)) {
name = parentNode.left;
decl = name;
}
else if (parentNodeOperator === SyntaxKind.BarBarToken || parentNodeOperator === SyntaxKind.QuestionQuestionToken) {
if (isVariableDeclaration(parentNode.parent) && parentNode.parent.initializer === parentNode) {
name = parentNode.parent.name;
decl = parentNode.parent;
}
else if (isBinaryExpression(parentNode.parent) && parentNode.parent.operatorToken.kind === SyntaxKind.EqualsToken && (allowDeclaration || parentNode.parent.right === parentNode)) {
name = parentNode.parent.left;
decl = name;
}
if (!name || !isBindableStaticNameExpression(name) || !isSameEntityName(name, parentNode.left)) {
return undefined;
}
}
}
else if (allowDeclaration && isFunctionDeclaration(node)) {
name = node.name;
decl = node;
}
if (!decl || !name || (!allowDeclaration && !getExpandoInitializer(node, isPrototypeAccess(name)))) {
return undefined;
}
return getSymbolOfNode(decl);
}
function getAssignedJSPrototype(node: Node) {
if (!node.parent) {
return false;
@ -28160,14 +28205,11 @@ namespace ts {
}
if (isInJSFile(node)) {
const decl = getDeclarationOfExpando(node);
if (decl) {
const jsSymbol = getSymbolOfNode(decl);
if (jsSymbol?.exports?.size) {
const jsAssignmentType = createAnonymousType(jsSymbol, jsSymbol.exports, emptyArray, emptyArray, undefined, undefined);
jsAssignmentType.objectFlags |= ObjectFlags.JSLiteral;
return getIntersectionType([returnType, jsAssignmentType]);
}
const jsSymbol = getSymbolOfExpando(node, /*allowDeclaration*/ false);
if (jsSymbol?.exports?.size) {
const jsAssignmentType = createAnonymousType(jsSymbol, jsSymbol.exports, emptyArray, emptyArray, undefined, undefined);
jsAssignmentType.objectFlags |= ObjectFlags.JSLiteral;
return getIntersectionType([returnType, jsAssignmentType]);
}
}

View file

@ -4004,6 +4004,7 @@ namespace ts {
getAugmentedPropertiesOfType(type: Type): Symbol[];
getRootSymbols(symbol: Symbol): readonly Symbol[];
getSymbolOfExpando(node: Node, allowDeclaration: boolean): Symbol | undefined;
getContextualType(node: Expression): Type | undefined;
/* @internal */ getContextualType(node: Expression, contextFlags?: ContextFlags): Type | undefined; // eslint-disable-line @typescript-eslint/unified-signatures
/* @internal */ getContextualTypeForObjectLiteralElement(element: ObjectLiteralElementLike): Type | undefined;

View file

@ -1937,48 +1937,6 @@ namespace ts {
return getSourceTextOfNodeFromSourceFile(sourceFile, str).charCodeAt(0) === CharacterCodes.doubleQuote;
}
export function getDeclarationOfExpando(node: Node): Node | undefined {
if (!node.parent) {
return undefined;
}
let name: Expression | BindingName | undefined;
let decl: Node | undefined;
if (isVariableDeclaration(node.parent) && node.parent.initializer === node) {
if (!isInJSFile(node) && !isVarConst(node.parent)) {
return undefined;
}
name = node.parent.name;
decl = node.parent;
}
else if (isBinaryExpression(node.parent)) {
const parentNode = node.parent;
const parentNodeOperator = node.parent.operatorToken.kind;
if (parentNodeOperator === SyntaxKind.EqualsToken && parentNode.right === node) {
name = parentNode.left;
decl = name;
}
else if (parentNodeOperator === SyntaxKind.BarBarToken || parentNodeOperator === SyntaxKind.QuestionQuestionToken) {
if (isVariableDeclaration(parentNode.parent) && parentNode.parent.initializer === parentNode) {
name = parentNode.parent.name;
decl = parentNode.parent;
}
else if (isBinaryExpression(parentNode.parent) && parentNode.parent.operatorToken.kind === SyntaxKind.EqualsToken && parentNode.parent.right === parentNode) {
name = parentNode.parent.left;
decl = name;
}
if (!name || !isBindableStaticNameExpression(name) || !isSameEntityName(name, parentNode.left)) {
return undefined;
}
}
}
if (!name || !getExpandoInitializer(node, isPrototypeAccess(name))) {
return undefined;
}
return decl;
}
export function isAssignmentDeclaration(decl: Declaration) {
return isBinaryExpression(decl) || isAccessExpression(decl) || isIdentifier(decl) || isCallExpression(decl);
}
@ -2098,7 +2056,7 @@ namespace ts {
* var min = window.min || {}
* my.app = self.my.app || class { }
*/
function isSameEntityName(name: Expression, initializer: Expression): boolean {
export function isSameEntityName(name: Expression, initializer: Expression): boolean {
if (isPropertyNameLiteral(name) && isPropertyNameLiteral(initializer)) {
return getTextOfIdentifierOrLiteral(name) === getTextOfIdentifierOrLiteral(initializer);
}

View file

@ -37,7 +37,7 @@ namespace ts {
function check(node: Node) {
if (isJsFile) {
if (canBeConvertedToClass(node)) {
if (canBeConvertedToClass(node, checker)) {
diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_constructor_function_may_be_converted_to_a_class_declaration));
}
}
@ -190,14 +190,13 @@ namespace ts {
return `${exp.pos.toString()}:${exp.end.toString()}`;
}
function canBeConvertedToClass(node: Node): boolean {
function canBeConvertedToClass(node: Node, checker: TypeChecker): boolean {
if (node.kind === SyntaxKind.FunctionExpression) {
if (isVariableDeclaration(node.parent) && node.symbol.members?.size) {
return true;
}
const decl = getDeclarationOfExpando(node);
const symbol = decl?.symbol;
const symbol = checker.getSymbolOfExpando(node, /*allowDeclaration*/ false);
return !!(symbol && (symbol.exports?.size || symbol.members?.size));
}

View file

@ -2207,6 +2207,7 @@ declare namespace ts {
getFullyQualifiedName(symbol: Symbol): string;
getAugmentedPropertiesOfType(type: Type): Symbol[];
getRootSymbols(symbol: Symbol): readonly Symbol[];
getSymbolOfExpando(node: Node, allowDeclaration: boolean): Symbol | undefined;
getContextualType(node: Expression): Type | undefined;
/**
* returns unknownSignature in the case of an error.

View file

@ -2207,6 +2207,7 @@ declare namespace ts {
getFullyQualifiedName(symbol: Symbol): string;
getAugmentedPropertiesOfType(type: Type): Symbol[];
getRootSymbols(symbol: Symbol): readonly Symbol[];
getSymbolOfExpando(node: Node, allowDeclaration: boolean): Symbol | undefined;
getContextualType(node: Expression): Type | undefined;
/**
* returns unknownSignature in the case of an error.

View file

@ -0,0 +1,33 @@
=== tests/cases/conformance/salsa/bug39167.js ===
var test = {};
>test : Symbol(test, Decl(bug39167.js, 0, 3), Decl(bug39167.js, 0, 14), Decl(bug39167.js, 2, 18))
test.K = test.K ||
>test.K : Symbol(test.K, Decl(bug39167.js, 0, 14), Decl(bug39167.js, 4, 5))
>test : Symbol(test, Decl(bug39167.js, 0, 3), Decl(bug39167.js, 0, 14), Decl(bug39167.js, 2, 18))
>K : Symbol(test.K, Decl(bug39167.js, 0, 14), Decl(bug39167.js, 4, 5))
>test.K : Symbol(test.K, Decl(bug39167.js, 0, 14), Decl(bug39167.js, 4, 5))
>test : Symbol(test, Decl(bug39167.js, 0, 3), Decl(bug39167.js, 0, 14), Decl(bug39167.js, 2, 18))
>K : Symbol(test.K, Decl(bug39167.js, 0, 14), Decl(bug39167.js, 4, 5))
function () {}
test.K.prototype = {
>test.K.prototype : Symbol(test.K.prototype, Decl(bug39167.js, 2, 18))
>test.K : Symbol(test.K, Decl(bug39167.js, 0, 14), Decl(bug39167.js, 4, 5))
>test : Symbol(test, Decl(bug39167.js, 0, 3), Decl(bug39167.js, 0, 14), Decl(bug39167.js, 2, 18))
>K : Symbol(test.K, Decl(bug39167.js, 0, 14), Decl(bug39167.js, 4, 5))
>prototype : Symbol(test.K.prototype, Decl(bug39167.js, 2, 18))
add() {}
>add : Symbol(add, Decl(bug39167.js, 4, 20))
};
new test.K().add;
>new test.K().add : Symbol(add, Decl(bug39167.js, 4, 20))
>test.K : Symbol(test.K, Decl(bug39167.js, 0, 14), Decl(bug39167.js, 4, 5))
>test : Symbol(test, Decl(bug39167.js, 0, 3), Decl(bug39167.js, 0, 14), Decl(bug39167.js, 2, 18))
>K : Symbol(test.K, Decl(bug39167.js, 0, 14), Decl(bug39167.js, 4, 5))
>add : Symbol(add, Decl(bug39167.js, 4, 20))

View file

@ -0,0 +1,40 @@
=== tests/cases/conformance/salsa/bug39167.js ===
var test = {};
>test : typeof test
>{} : {}
test.K = test.K ||
>test.K = test.K || function () {} : typeof K
>test.K : typeof K
>test : typeof test
>K : typeof K
>test.K || function () {} : typeof K
>test.K : typeof K
>test : typeof test
>K : typeof K
function () {}
>function () {} : typeof K
test.K.prototype = {
>test.K.prototype = { add() {}} : { add(): void; }
>test.K.prototype : { add(): void; }
>test.K : typeof K
>test : typeof test
>K : typeof K
>prototype : { add(): void; }
>{ add() {}} : { add(): void; }
add() {}
>add : () => void
};
new test.K().add;
>new test.K().add : () => void
>new test.K() : K
>test.K : typeof K
>test : typeof test
>K : typeof K
>add : () => void

View file

@ -0,0 +1,13 @@
// @noEmit: true
// @allowJs: true
// @checkJs: true
// @Filename: bug39167.js
var test = {};
test.K = test.K ||
function () {}
test.K.prototype = {
add() {}
};
new test.K().add;