Skip to content

Commit f615e22

Browse files
authored
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
1 parent df33dd5 commit f615e22

9 files changed

+154
-66
lines changed

src/compiler/checker.ts

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,7 @@ namespace ts {
507507
},
508508
getAugmentedPropertiesOfType,
509509
getRootSymbols,
510+
getSymbolOfExpando,
510511
getContextualType: (nodeIn: Expression, contextFlags?: ContextFlags) => {
511512
const node = getParseTreeNode(nodeIn, isExpression);
512513
if (!node) {
@@ -8728,9 +8729,9 @@ namespace ts {
87288729
let links = getSymbolLinks(symbol);
87298730
const originalLinks = links;
87308731
if (!links.type) {
8731-
const jsDeclaration = symbol.valueDeclaration && getDeclarationOfExpando(symbol.valueDeclaration);
8732-
if (jsDeclaration) {
8733-
const merged = mergeJSSymbols(symbol, getSymbolOfNode(jsDeclaration));
8732+
const expando = symbol.valueDeclaration && getSymbolOfExpando(symbol.valueDeclaration, /*allowDeclaration*/ false);
8733+
if (expando) {
8734+
const merged = mergeJSSymbols(symbol, expando);
87348735
if (merged) {
87358736
// note:we overwrite links because we just cloned the symbol
87368737
symbol = links = merged;
@@ -24828,7 +24829,7 @@ namespace ts {
2482824829
const exprType = checkJsxAttribute(attributeDecl, checkMode);
2482924830
objectFlags |= getObjectFlags(exprType) & ObjectFlags.PropagatingFlags;
2483024831

24831-
const attributeSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient | member.flags, member.escapedName);
24832+
const attributeSymbol = createSymbol(SymbolFlags.Property | member.flags, member.escapedName);
2483224833
attributeSymbol.declarations = member.declarations;
2483324834
attributeSymbol.parent = member.parent;
2483424835
if (member.valueDeclaration) {
@@ -24887,7 +24888,7 @@ namespace ts {
2488724888
const contextualType = getApparentTypeOfContextualType(openingLikeElement.attributes);
2488824889
const childrenContextualType = contextualType && getTypeOfPropertyOfContextualType(contextualType, jsxChildrenPropertyName);
2488924890
// 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
24890-
const childrenPropSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, jsxChildrenPropertyName);
24891+
const childrenPropSymbol = createSymbol(SymbolFlags.Property, jsxChildrenPropertyName);
2489124892
childrenPropSymbol.type = childrenTypes.length === 1 ? childrenTypes[0] :
2489224893
childrenContextualType && forEachType(childrenContextualType, isTupleLikeType) ? createTupleType(childrenTypes) :
2489324894
createArrayType(getUnionType(childrenTypes));
@@ -28075,15 +28076,59 @@ namespace ts {
2807528076
}
2807628077

2807728078
function getAssignedClassSymbol(decl: Declaration): Symbol | undefined {
28078-
const assignmentSymbol = decl && decl.parent &&
28079-
(isFunctionDeclaration(decl) && getSymbolOfNode(decl) ||
28080-
isBinaryExpression(decl.parent) && getSymbolOfNode(decl.parent.left) ||
28081-
isVariableDeclaration(decl.parent) && getSymbolOfNode(decl.parent));
28082-
const prototype = assignmentSymbol && assignmentSymbol.exports && assignmentSymbol.exports.get("prototype" as __String);
28083-
const init = prototype && prototype.valueDeclaration && getAssignedJSPrototype(prototype.valueDeclaration);
28079+
const assignmentSymbol = decl && getSymbolOfExpando(decl, /*allowDeclaration*/ true);
28080+
const prototype = assignmentSymbol?.exports?.get("prototype" as __String);
28081+
const init = prototype?.valueDeclaration && getAssignedJSPrototype(prototype.valueDeclaration);
2808428082
return init ? getSymbolOfNode(init) : undefined;
2808528083
}
2808628084

28085+
function getSymbolOfExpando(node: Node, allowDeclaration: boolean): Symbol | undefined {
28086+
if (!node.parent) {
28087+
return undefined;
28088+
}
28089+
let name: Expression | BindingName | undefined;
28090+
let decl: Node | undefined;
28091+
if (isVariableDeclaration(node.parent) && node.parent.initializer === node) {
28092+
if (!isInJSFile(node) && !isVarConst(node.parent)) {
28093+
return undefined;
28094+
}
28095+
name = node.parent.name;
28096+
decl = node.parent;
28097+
}
28098+
else if (isBinaryExpression(node.parent)) {
28099+
const parentNode = node.parent;
28100+
const parentNodeOperator = node.parent.operatorToken.kind;
28101+
if (parentNodeOperator === SyntaxKind.EqualsToken && (allowDeclaration || parentNode.right === node)) {
28102+
name = parentNode.left;
28103+
decl = name;
28104+
}
28105+
else if (parentNodeOperator === SyntaxKind.BarBarToken || parentNodeOperator === SyntaxKind.QuestionQuestionToken) {
28106+
if (isVariableDeclaration(parentNode.parent) && parentNode.parent.initializer === parentNode) {
28107+
name = parentNode.parent.name;
28108+
decl = parentNode.parent;
28109+
}
28110+
else if (isBinaryExpression(parentNode.parent) && parentNode.parent.operatorToken.kind === SyntaxKind.EqualsToken && (allowDeclaration || parentNode.parent.right === parentNode)) {
28111+
name = parentNode.parent.left;
28112+
decl = name;
28113+
}
28114+
28115+
if (!name || !isBindableStaticNameExpression(name) || !isSameEntityName(name, parentNode.left)) {
28116+
return undefined;
28117+
}
28118+
}
28119+
}
28120+
else if (allowDeclaration && isFunctionDeclaration(node)) {
28121+
name = node.name;
28122+
decl = node;
28123+
}
28124+
28125+
if (!decl || !name || (!allowDeclaration && !getExpandoInitializer(node, isPrototypeAccess(name)))) {
28126+
return undefined;
28127+
}
28128+
return getSymbolOfNode(decl);
28129+
}
28130+
28131+
2808728132
function getAssignedJSPrototype(node: Node) {
2808828133
if (!node.parent) {
2808928134
return false;
@@ -28160,14 +28205,11 @@ namespace ts {
2816028205
}
2816128206

2816228207
if (isInJSFile(node)) {
28163-
const decl = getDeclarationOfExpando(node);
28164-
if (decl) {
28165-
const jsSymbol = getSymbolOfNode(decl);
28166-
if (jsSymbol?.exports?.size) {
28167-
const jsAssignmentType = createAnonymousType(jsSymbol, jsSymbol.exports, emptyArray, emptyArray, undefined, undefined);
28168-
jsAssignmentType.objectFlags |= ObjectFlags.JSLiteral;
28169-
return getIntersectionType([returnType, jsAssignmentType]);
28170-
}
28208+
const jsSymbol = getSymbolOfExpando(node, /*allowDeclaration*/ false);
28209+
if (jsSymbol?.exports?.size) {
28210+
const jsAssignmentType = createAnonymousType(jsSymbol, jsSymbol.exports, emptyArray, emptyArray, undefined, undefined);
28211+
jsAssignmentType.objectFlags |= ObjectFlags.JSLiteral;
28212+
return getIntersectionType([returnType, jsAssignmentType]);
2817128213
}
2817228214
}
2817328215

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4004,6 +4004,7 @@ namespace ts {
40044004
getAugmentedPropertiesOfType(type: Type): Symbol[];
40054005

40064006
getRootSymbols(symbol: Symbol): readonly Symbol[];
4007+
getSymbolOfExpando(node: Node, allowDeclaration: boolean): Symbol | undefined;
40074008
getContextualType(node: Expression): Type | undefined;
40084009
/* @internal */ getContextualType(node: Expression, contextFlags?: ContextFlags): Type | undefined; // eslint-disable-line @typescript-eslint/unified-signatures
40094010
/* @internal */ getContextualTypeForObjectLiteralElement(element: ObjectLiteralElementLike): Type | undefined;

src/compiler/utilities.ts

Lines changed: 1 addition & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1937,48 +1937,6 @@ namespace ts {
19371937
return getSourceTextOfNodeFromSourceFile(sourceFile, str).charCodeAt(0) === CharacterCodes.doubleQuote;
19381938
}
19391939

1940-
export function getDeclarationOfExpando(node: Node): Node | undefined {
1941-
if (!node.parent) {
1942-
return undefined;
1943-
}
1944-
let name: Expression | BindingName | undefined;
1945-
let decl: Node | undefined;
1946-
if (isVariableDeclaration(node.parent) && node.parent.initializer === node) {
1947-
if (!isInJSFile(node) && !isVarConst(node.parent)) {
1948-
return undefined;
1949-
}
1950-
name = node.parent.name;
1951-
decl = node.parent;
1952-
}
1953-
else if (isBinaryExpression(node.parent)) {
1954-
const parentNode = node.parent;
1955-
const parentNodeOperator = node.parent.operatorToken.kind;
1956-
if (parentNodeOperator === SyntaxKind.EqualsToken && parentNode.right === node) {
1957-
name = parentNode.left;
1958-
decl = name;
1959-
}
1960-
else if (parentNodeOperator === SyntaxKind.BarBarToken || parentNodeOperator === SyntaxKind.QuestionQuestionToken) {
1961-
if (isVariableDeclaration(parentNode.parent) && parentNode.parent.initializer === parentNode) {
1962-
name = parentNode.parent.name;
1963-
decl = parentNode.parent;
1964-
}
1965-
else if (isBinaryExpression(parentNode.parent) && parentNode.parent.operatorToken.kind === SyntaxKind.EqualsToken && parentNode.parent.right === parentNode) {
1966-
name = parentNode.parent.left;
1967-
decl = name;
1968-
}
1969-
1970-
if (!name || !isBindableStaticNameExpression(name) || !isSameEntityName(name, parentNode.left)) {
1971-
return undefined;
1972-
}
1973-
}
1974-
}
1975-
1976-
if (!name || !getExpandoInitializer(node, isPrototypeAccess(name))) {
1977-
return undefined;
1978-
}
1979-
return decl;
1980-
}
1981-
19821940
export function isAssignmentDeclaration(decl: Declaration) {
19831941
return isBinaryExpression(decl) || isAccessExpression(decl) || isIdentifier(decl) || isCallExpression(decl);
19841942
}
@@ -2098,7 +2056,7 @@ namespace ts {
20982056
* var min = window.min || {}
20992057
* my.app = self.my.app || class { }
21002058
*/
2101-
function isSameEntityName(name: Expression, initializer: Expression): boolean {
2059+
export function isSameEntityName(name: Expression, initializer: Expression): boolean {
21022060
if (isPropertyNameLiteral(name) && isPropertyNameLiteral(initializer)) {
21032061
return getTextOfIdentifierOrLiteral(name) === getTextOfIdentifierOrLiteral(initializer);
21042062
}

src/services/suggestionDiagnostics.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ namespace ts {
3737

3838
function check(node: Node) {
3939
if (isJsFile) {
40-
if (canBeConvertedToClass(node)) {
40+
if (canBeConvertedToClass(node, checker)) {
4141
diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_constructor_function_may_be_converted_to_a_class_declaration));
4242
}
4343
}
@@ -190,14 +190,13 @@ namespace ts {
190190
return `${exp.pos.toString()}:${exp.end.toString()}`;
191191
}
192192

193-
function canBeConvertedToClass(node: Node): boolean {
193+
function canBeConvertedToClass(node: Node, checker: TypeChecker): boolean {
194194
if (node.kind === SyntaxKind.FunctionExpression) {
195195
if (isVariableDeclaration(node.parent) && node.symbol.members?.size) {
196196
return true;
197197
}
198198

199-
const decl = getDeclarationOfExpando(node);
200-
const symbol = decl?.symbol;
199+
const symbol = checker.getSymbolOfExpando(node, /*allowDeclaration*/ false);
201200
return !!(symbol && (symbol.exports?.size || symbol.members?.size));
202201
}
203202

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2207,6 +2207,7 @@ declare namespace ts {
22072207
getFullyQualifiedName(symbol: Symbol): string;
22082208
getAugmentedPropertiesOfType(type: Type): Symbol[];
22092209
getRootSymbols(symbol: Symbol): readonly Symbol[];
2210+
getSymbolOfExpando(node: Node, allowDeclaration: boolean): Symbol | undefined;
22102211
getContextualType(node: Expression): Type | undefined;
22112212
/**
22122213
* returns unknownSignature in the case of an error.

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2207,6 +2207,7 @@ declare namespace ts {
22072207
getFullyQualifiedName(symbol: Symbol): string;
22082208
getAugmentedPropertiesOfType(type: Type): Symbol[];
22092209
getRootSymbols(symbol: Symbol): readonly Symbol[];
2210+
getSymbolOfExpando(node: Node, allowDeclaration: boolean): Symbol | undefined;
22102211
getContextualType(node: Expression): Type | undefined;
22112212
/**
22122213
* returns unknownSignature in the case of an error.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
=== tests/cases/conformance/salsa/bug39167.js ===
2+
var test = {};
3+
>test : Symbol(test, Decl(bug39167.js, 0, 3), Decl(bug39167.js, 0, 14), Decl(bug39167.js, 2, 18))
4+
5+
test.K = test.K ||
6+
>test.K : Symbol(test.K, Decl(bug39167.js, 0, 14), Decl(bug39167.js, 4, 5))
7+
>test : Symbol(test, Decl(bug39167.js, 0, 3), Decl(bug39167.js, 0, 14), Decl(bug39167.js, 2, 18))
8+
>K : Symbol(test.K, Decl(bug39167.js, 0, 14), Decl(bug39167.js, 4, 5))
9+
>test.K : Symbol(test.K, Decl(bug39167.js, 0, 14), Decl(bug39167.js, 4, 5))
10+
>test : Symbol(test, Decl(bug39167.js, 0, 3), Decl(bug39167.js, 0, 14), Decl(bug39167.js, 2, 18))
11+
>K : Symbol(test.K, Decl(bug39167.js, 0, 14), Decl(bug39167.js, 4, 5))
12+
13+
function () {}
14+
15+
test.K.prototype = {
16+
>test.K.prototype : Symbol(test.K.prototype, Decl(bug39167.js, 2, 18))
17+
>test.K : Symbol(test.K, Decl(bug39167.js, 0, 14), Decl(bug39167.js, 4, 5))
18+
>test : Symbol(test, Decl(bug39167.js, 0, 3), Decl(bug39167.js, 0, 14), Decl(bug39167.js, 2, 18))
19+
>K : Symbol(test.K, Decl(bug39167.js, 0, 14), Decl(bug39167.js, 4, 5))
20+
>prototype : Symbol(test.K.prototype, Decl(bug39167.js, 2, 18))
21+
22+
add() {}
23+
>add : Symbol(add, Decl(bug39167.js, 4, 20))
24+
25+
};
26+
27+
new test.K().add;
28+
>new test.K().add : Symbol(add, Decl(bug39167.js, 4, 20))
29+
>test.K : Symbol(test.K, Decl(bug39167.js, 0, 14), Decl(bug39167.js, 4, 5))
30+
>test : Symbol(test, Decl(bug39167.js, 0, 3), Decl(bug39167.js, 0, 14), Decl(bug39167.js, 2, 18))
31+
>K : Symbol(test.K, Decl(bug39167.js, 0, 14), Decl(bug39167.js, 4, 5))
32+
>add : Symbol(add, Decl(bug39167.js, 4, 20))
33+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
=== tests/cases/conformance/salsa/bug39167.js ===
2+
var test = {};
3+
>test : typeof test
4+
>{} : {}
5+
6+
test.K = test.K ||
7+
>test.K = test.K || function () {} : typeof K
8+
>test.K : typeof K
9+
>test : typeof test
10+
>K : typeof K
11+
>test.K || function () {} : typeof K
12+
>test.K : typeof K
13+
>test : typeof test
14+
>K : typeof K
15+
16+
function () {}
17+
>function () {} : typeof K
18+
19+
test.K.prototype = {
20+
>test.K.prototype = { add() {}} : { add(): void; }
21+
>test.K.prototype : { add(): void; }
22+
>test.K : typeof K
23+
>test : typeof test
24+
>K : typeof K
25+
>prototype : { add(): void; }
26+
>{ add() {}} : { add(): void; }
27+
28+
add() {}
29+
>add : () => void
30+
31+
};
32+
33+
new test.K().add;
34+
>new test.K().add : () => void
35+
>new test.K() : K
36+
>test.K : typeof K
37+
>test : typeof test
38+
>K : typeof K
39+
>add : () => void
40+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// @noEmit: true
2+
// @allowJs: true
3+
// @checkJs: true
4+
// @Filename: bug39167.js
5+
var test = {};
6+
test.K = test.K ||
7+
function () {}
8+
9+
test.K.prototype = {
10+
add() {}
11+
};
12+
13+
new test.K().add;

0 commit comments

Comments
 (0)