Skip to content

Commit 6f3ef5d

Browse files
committed
feat: support __proto__ in object literal
1 parent 22f8fa4 commit 6f3ef5d

25 files changed

+617
-96
lines changed

src/compiler/checker.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25103,6 +25103,26 @@ namespace ts {
2510325103
return links.immediateTarget;
2510425104
}
2510525105

25106+
/**
25107+
* If the node is a `__proto__` literal declaration.
25108+
* According to ECMA262 B.3.1 only this syntax counted in
25109+
* PropertyName ":" AssignmentExpression
25110+
*
25111+
* It works in thoes forms:
25112+
* { __proto__: value }; { "__proto__": value }; { __proto_\u005f: value };
25113+
* It does not work in thoes forms:
25114+
* { ["__proto__"]: value }; { __proto__ }
25115+
*/
25116+
function getProtoLiteralDeclarationInitializer(node: ObjectLiteralElementLike): false | Expression {
25117+
if (node.kind !== SyntaxKind.PropertyAssignment) return false;
25118+
const name = node.name;
25119+
if (!name) return false;
25120+
// Identifier.escapedText says if the identifier starts with __, it will starts with 3 "_".
25121+
if (name.kind === SyntaxKind.Identifier && name.escapedText === "___proto__") return node.initializer;
25122+
if (name.kind === SyntaxKind.StringLiteral && name.text === "__proto__") return node.initializer;
25123+
return false;
25124+
}
25125+
2510625126
function checkObjectLiteral(node: ObjectLiteralExpression, checkMode?: CheckMode): Type {
2510725127
const inDestructuringPattern = isAssignmentTarget(node);
2510825128
// Grammar checking
@@ -25135,8 +25155,26 @@ namespace ts {
2513525155
}
2513625156
}
2513725157

25158+
const skippedProperties = new Set<ObjectLiteralElementLike>();
25159+
// It should be the left-most spread element so we loop this first
25160+
for (const memberDecl of node.properties) {
25161+
const item = getProtoLiteralDeclarationInitializer(memberDecl);
25162+
if (!item) continue;
25163+
// Don't add __proto__ literal to the properties table
25164+
skippedProperties.add(memberDecl);
25165+
// it's prototype is null so it works like normal object (despite Object.prototype things).
25166+
if (item.kind === SyntaxKind.NullKeyword) continue;
25167+
const type = getReducedType(checkExpression(item));
25168+
if (!(type.flags & TypeFlags.Object)) {
25169+
error(memberDecl, Diagnostics.Type_0_is_not_assignable_to_type_1, typeToString(type), "object | null");
25170+
}
25171+
else {
25172+
spread = getSpreadType(spread, type, node.symbol, objectFlags, inConstContext);
25173+
}
25174+
}
2513825175
let offset = 0;
2513925176
for (const memberDecl of node.properties) {
25177+
if (skippedProperties.has(memberDecl)) continue;
2514025178
let member = getSymbolOfNode(memberDecl);
2514125179
const computedNameType = memberDecl.name && memberDecl.name.kind === SyntaxKind.ComputedPropertyName && !isWellKnownSymbolSyntactically(memberDecl.name.expression) ?
2514225180
checkComputedPropertyName(memberDecl.name) : undefined;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
tests/cases/compiler/__proto__literal.ts(10,28): error TS2300: Duplicate identifier '__proto_\u005f'.
2+
tests/cases/compiler/__proto__literal.ts(20,13): error TS2322: Type '1' is not assignable to type 'object | null'.
3+
4+
5+
==== tests/cases/compiler/__proto__literal.ts (2 errors) ====
6+
const o = { a: 1 }
7+
const __proto__ = o
8+
// Should
9+
const x1 = { __proto__: o }
10+
const x2 = { __proto_\u005f: o }
11+
const x3 = { "__proto__": o }
12+
const x4 = { "__proto_\u005f": o }
13+
14+
// Duplicate
15+
const y1 = { __proto__: o, __proto_\u005f: o }
16+
~~~~~~~~~~~~~~
17+
!!! error TS2300: Duplicate identifier '__proto_\u005f'.
18+
19+
// Spread order
20+
const z1 = { ...({a: ''}), __proto__: o }
21+
const z2 = { __proto__: o, ...({a: ''}) }
22+
23+
// Null
24+
const w = { __proto__: null }
25+
26+
// Non-object
27+
const q = { __proto__: 1, x: 1 }
28+
~~~~~~~~~~~~
29+
!!! error TS2322: Type '1' is not assignable to type 'object | null'.
30+
31+
// Should not
32+
const x5 = { ["__proto__"]: o }
33+
const x6 = { __proto__ }
34+
const x7 = { __proto__() {} }
35+
enum e { __proto__ = 1 }
36+
{
37+
const { __proto__ } = { ['__proto__']: 1 }
38+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//// [__proto__literal.ts]
2+
const o = { a: 1 }
3+
const __proto__ = o
4+
// Should
5+
const x1 = { __proto__: o }
6+
const x2 = { __proto_\u005f: o }
7+
const x3 = { "__proto__": o }
8+
const x4 = { "__proto_\u005f": o }
9+
10+
// Duplicate
11+
const y1 = { __proto__: o, __proto_\u005f: o }
12+
13+
// Spread order
14+
const z1 = { ...({a: ''}), __proto__: o }
15+
const z2 = { __proto__: o, ...({a: ''}) }
16+
17+
// Null
18+
const w = { __proto__: null }
19+
20+
// Non-object
21+
const q = { __proto__: 1, x: 1 }
22+
23+
// Should not
24+
const x5 = { ["__proto__"]: o }
25+
const x6 = { __proto__ }
26+
const x7 = { __proto__() {} }
27+
enum e { __proto__ = 1 }
28+
{
29+
const { __proto__ } = { ['__proto__']: 1 }
30+
}
31+
32+
//// [__proto__literal.js]
33+
var __assign = (this && this.__assign) || function () {
34+
__assign = Object.assign || function(t) {
35+
for (var s, i = 1, n = arguments.length; i < n; i++) {
36+
s = arguments[i];
37+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
38+
t[p] = s[p];
39+
}
40+
return t;
41+
};
42+
return __assign.apply(this, arguments);
43+
};
44+
var _a, _b;
45+
var o = { a: 1 };
46+
var __proto__ = o;
47+
// Should
48+
var x1 = { __proto__: o };
49+
var x2 = { __proto_\u005f: o };
50+
var x3 = { "__proto__": o };
51+
var x4 = { "__proto_\u005f": o };
52+
// Duplicate
53+
var y1 = { __proto__: o, __proto_\u005f: o };
54+
// Spread order
55+
var z1 = __assign(__assign({}, ({ a: '' })), { __proto__: o });
56+
var z2 = __assign({ __proto__: o }, ({ a: '' }));
57+
// Null
58+
var w = { __proto__: null };
59+
// Non-object
60+
var q = { __proto__: 1, x: 1 };
61+
// Should not
62+
var x5 = (_a = {}, _a["__proto__"] = o, _a);
63+
var x6 = { __proto__: __proto__ };
64+
var x7 = { __proto__: function () { } };
65+
var e;
66+
(function (e) {
67+
e[e["__proto__"] = 1] = "__proto__";
68+
})(e || (e = {}));
69+
{
70+
var __proto__1 = (_b = {}, _b['__proto__'] = 1, _b).__proto__;
71+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
=== tests/cases/compiler/__proto__literal.ts ===
2+
const o = { a: 1 }
3+
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))
4+
>a : Symbol(a, Decl(__proto__literal.ts, 0, 11))
5+
6+
const __proto__ = o
7+
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 1, 5))
8+
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))
9+
10+
// Should
11+
const x1 = { __proto__: o }
12+
>x1 : Symbol(x1, Decl(__proto__literal.ts, 3, 5))
13+
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 3, 12))
14+
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))
15+
16+
const x2 = { __proto_\u005f: o }
17+
>x2 : Symbol(x2, Decl(__proto__literal.ts, 4, 5))
18+
>__proto_\u005f : Symbol(__proto_\u005f, Decl(__proto__literal.ts, 4, 12))
19+
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))
20+
21+
const x3 = { "__proto__": o }
22+
>x3 : Symbol(x3, Decl(__proto__literal.ts, 5, 5))
23+
>"__proto__" : Symbol("__proto__", Decl(__proto__literal.ts, 5, 12))
24+
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))
25+
26+
const x4 = { "__proto_\u005f": o }
27+
>x4 : Symbol(x4, Decl(__proto__literal.ts, 6, 5))
28+
>"__proto_\u005f" : Symbol("__proto_\u005f", Decl(__proto__literal.ts, 6, 12))
29+
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))
30+
31+
// Duplicate
32+
const y1 = { __proto__: o, __proto_\u005f: o }
33+
>y1 : Symbol(y1, Decl(__proto__literal.ts, 9, 5))
34+
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 9, 12), Decl(__proto__literal.ts, 9, 26))
35+
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))
36+
>__proto_\u005f : Symbol(__proto__, Decl(__proto__literal.ts, 9, 12), Decl(__proto__literal.ts, 9, 26))
37+
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))
38+
39+
// Spread order
40+
const z1 = { ...({a: ''}), __proto__: o }
41+
>z1 : Symbol(z1, Decl(__proto__literal.ts, 12, 5))
42+
>a : Symbol(a, Decl(__proto__literal.ts, 12, 18))
43+
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 12, 26))
44+
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))
45+
46+
const z2 = { __proto__: o, ...({a: ''}) }
47+
>z2 : Symbol(z2, Decl(__proto__literal.ts, 13, 5))
48+
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 13, 12))
49+
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))
50+
>a : Symbol(a, Decl(__proto__literal.ts, 13, 32))
51+
52+
// Null
53+
const w = { __proto__: null }
54+
>w : Symbol(w, Decl(__proto__literal.ts, 16, 5))
55+
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 16, 11))
56+
57+
// Non-object
58+
const q = { __proto__: 1, x: 1 }
59+
>q : Symbol(q, Decl(__proto__literal.ts, 19, 5))
60+
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 19, 11))
61+
>x : Symbol(x, Decl(__proto__literal.ts, 19, 25))
62+
63+
// Should not
64+
const x5 = { ["__proto__"]: o }
65+
>x5 : Symbol(x5, Decl(__proto__literal.ts, 22, 5))
66+
>["__proto__"] : Symbol(["__proto__"], Decl(__proto__literal.ts, 22, 12))
67+
>"__proto__" : Symbol(["__proto__"], Decl(__proto__literal.ts, 22, 12))
68+
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))
69+
70+
const x6 = { __proto__ }
71+
>x6 : Symbol(x6, Decl(__proto__literal.ts, 23, 5))
72+
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 23, 12))
73+
74+
const x7 = { __proto__() {} }
75+
>x7 : Symbol(x7, Decl(__proto__literal.ts, 24, 5))
76+
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 24, 12))
77+
78+
enum e { __proto__ = 1 }
79+
>e : Symbol(e, Decl(__proto__literal.ts, 24, 29))
80+
>__proto__ : Symbol(e.__proto__, Decl(__proto__literal.ts, 25, 8))
81+
{
82+
const { __proto__ } = { ['__proto__']: 1 }
83+
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 27, 11))
84+
>['__proto__'] : Symbol(['__proto__'], Decl(__proto__literal.ts, 27, 27))
85+
>'__proto__' : Symbol(['__proto__'], Decl(__proto__literal.ts, 27, 27))
86+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
=== tests/cases/compiler/__proto__literal.ts ===
2+
const o = { a: 1 }
3+
>o : { a: number; }
4+
>{ a: 1 } : { a: number; }
5+
>a : number
6+
>1 : 1
7+
8+
const __proto__ = o
9+
>__proto__ : { a: number; }
10+
>o : { a: number; }
11+
12+
// Should
13+
const x1 = { __proto__: o }
14+
>x1 : { a: number; }
15+
>{ __proto__: o } : { a: number; }
16+
>__proto__ : { a: number; }
17+
>o : { a: number; }
18+
19+
const x2 = { __proto_\u005f: o }
20+
>x2 : { a: number; }
21+
>{ __proto_\u005f: o } : { a: number; }
22+
>__proto_\u005f : { a: number; }
23+
>o : { a: number; }
24+
25+
const x3 = { "__proto__": o }
26+
>x3 : { a: number; }
27+
>{ "__proto__": o } : { a: number; }
28+
>"__proto__" : { a: number; }
29+
>o : { a: number; }
30+
31+
const x4 = { "__proto_\u005f": o }
32+
>x4 : { a: number; }
33+
>{ "__proto_\u005f": o } : { a: number; }
34+
>"__proto_\u005f" : { a: number; }
35+
>o : { a: number; }
36+
37+
// Duplicate
38+
const y1 = { __proto__: o, __proto_\u005f: o }
39+
>y1 : { a: number; }
40+
>{ __proto__: o, __proto_\u005f: o } : { a: number; }
41+
>__proto__ : { a: number; }
42+
>o : { a: number; }
43+
>__proto_\u005f : { a: number; }
44+
>o : { a: number; }
45+
46+
// Spread order
47+
const z1 = { ...({a: ''}), __proto__: o }
48+
>z1 : { a: string; }
49+
>{ ...({a: ''}), __proto__: o } : { a: string; }
50+
>({a: ''}) : { a: string; }
51+
>{a: ''} : { a: string; }
52+
>a : string
53+
>'' : ""
54+
>__proto__ : { a: number; }
55+
>o : { a: number; }
56+
57+
const z2 = { __proto__: o, ...({a: ''}) }
58+
>z2 : { a: string; }
59+
>{ __proto__: o, ...({a: ''}) } : { a: string; }
60+
>__proto__ : { a: number; }
61+
>o : { a: number; }
62+
>({a: ''}) : { a: string; }
63+
>{a: ''} : { a: string; }
64+
>a : string
65+
>'' : ""
66+
67+
// Null
68+
const w = { __proto__: null }
69+
>w : {}
70+
>{ __proto__: null } : {}
71+
>__proto__ : null
72+
>null : null
73+
74+
// Non-object
75+
const q = { __proto__: 1, x: 1 }
76+
>q : { x: number; }
77+
>{ __proto__: 1, x: 1 } : { x: number; }
78+
>__proto__ : number
79+
>1 : 1
80+
>x : number
81+
>1 : 1
82+
83+
// Should not
84+
const x5 = { ["__proto__"]: o }
85+
>x5 : { __proto__: { a: number; }; }
86+
>{ ["__proto__"]: o } : { __proto__: { a: number; }; }
87+
>["__proto__"] : { a: number; }
88+
>"__proto__" : "__proto__"
89+
>o : { a: number; }
90+
91+
const x6 = { __proto__ }
92+
>x6 : { __proto__: { a: number; }; }
93+
>{ __proto__ } : { __proto__: { a: number; }; }
94+
>__proto__ : { a: number; }
95+
96+
const x7 = { __proto__() {} }
97+
>x7 : { __proto__(): void; }
98+
>{ __proto__() {} } : { __proto__(): void; }
99+
>__proto__ : () => void
100+
101+
enum e { __proto__ = 1 }
102+
>e : e
103+
>__proto__ : e.__proto__
104+
>1 : 1
105+
{
106+
const { __proto__ } = { ['__proto__']: 1 }
107+
>__proto__ : number
108+
>{ ['__proto__']: 1 } : { __proto__: number; }
109+
>['__proto__'] : number
110+
>'__proto__' : "__proto__"
111+
>1 : 1
112+
}

0 commit comments

Comments
 (0)