Skip to content

Commit

Permalink
feat: support __proto__ in object literal
Browse files Browse the repository at this point in the history
  • Loading branch information
Jack-Works committed Jan 15, 2021
1 parent 22f8fa4 commit 6f3ef5d
Show file tree
Hide file tree
Showing 25 changed files with 617 additions and 96 deletions.
38 changes: 38 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25103,6 +25103,26 @@ namespace ts {
return links.immediateTarget;
}

/**
* If the node is a `__proto__` literal declaration.
* According to ECMA262 B.3.1 only this syntax counted in
* PropertyName ":" AssignmentExpression
*
* It works in thoes forms:
* { __proto__: value }; { "__proto__": value }; { __proto_\u005f: value };
* It does not work in thoes forms:
* { ["__proto__"]: value }; { __proto__ }
*/
function getProtoLiteralDeclarationInitializer(node: ObjectLiteralElementLike): false | Expression {
if (node.kind !== SyntaxKind.PropertyAssignment) return false;
const name = node.name;
if (!name) return false;
// Identifier.escapedText says if the identifier starts with __, it will starts with 3 "_".
if (name.kind === SyntaxKind.Identifier && name.escapedText === "___proto__") return node.initializer;
if (name.kind === SyntaxKind.StringLiteral && name.text === "__proto__") return node.initializer;
return false;
}

function checkObjectLiteral(node: ObjectLiteralExpression, checkMode?: CheckMode): Type {
const inDestructuringPattern = isAssignmentTarget(node);
// Grammar checking
Expand Down Expand Up @@ -25135,8 +25155,26 @@ namespace ts {
}
}

const skippedProperties = new Set<ObjectLiteralElementLike>();
// It should be the left-most spread element so we loop this first
for (const memberDecl of node.properties) {
const item = getProtoLiteralDeclarationInitializer(memberDecl);
if (!item) continue;
// Don't add __proto__ literal to the properties table
skippedProperties.add(memberDecl);
// it's prototype is null so it works like normal object (despite Object.prototype things).
if (item.kind === SyntaxKind.NullKeyword) continue;
const type = getReducedType(checkExpression(item));
if (!(type.flags & TypeFlags.Object)) {
error(memberDecl, Diagnostics.Type_0_is_not_assignable_to_type_1, typeToString(type), "object | null");
}
else {
spread = getSpreadType(spread, type, node.symbol, objectFlags, inConstContext);
}
}
let offset = 0;
for (const memberDecl of node.properties) {
if (skippedProperties.has(memberDecl)) continue;
let member = getSymbolOfNode(memberDecl);
const computedNameType = memberDecl.name && memberDecl.name.kind === SyntaxKind.ComputedPropertyName && !isWellKnownSymbolSyntactically(memberDecl.name.expression) ?
checkComputedPropertyName(memberDecl.name) : undefined;
Expand Down
38 changes: 38 additions & 0 deletions tests/baselines/reference/__proto__literal.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
tests/cases/compiler/__proto__literal.ts(10,28): error TS2300: Duplicate identifier '__proto_\u005f'.
tests/cases/compiler/__proto__literal.ts(20,13): error TS2322: Type '1' is not assignable to type 'object | null'.


==== tests/cases/compiler/__proto__literal.ts (2 errors) ====
const o = { a: 1 }
const __proto__ = o
// Should
const x1 = { __proto__: o }
const x2 = { __proto_\u005f: o }
const x3 = { "__proto__": o }
const x4 = { "__proto_\u005f": o }

// Duplicate
const y1 = { __proto__: o, __proto_\u005f: o }
~~~~~~~~~~~~~~
!!! error TS2300: Duplicate identifier '__proto_\u005f'.

// Spread order
const z1 = { ...({a: ''}), __proto__: o }
const z2 = { __proto__: o, ...({a: ''}) }

// Null
const w = { __proto__: null }

// Non-object
const q = { __proto__: 1, x: 1 }
~~~~~~~~~~~~
!!! error TS2322: Type '1' is not assignable to type 'object | null'.

// Should not
const x5 = { ["__proto__"]: o }
const x6 = { __proto__ }
const x7 = { __proto__() {} }
enum e { __proto__ = 1 }
{
const { __proto__ } = { ['__proto__']: 1 }
}
71 changes: 71 additions & 0 deletions tests/baselines/reference/__proto__literal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//// [__proto__literal.ts]
const o = { a: 1 }
const __proto__ = o
// Should
const x1 = { __proto__: o }
const x2 = { __proto_\u005f: o }
const x3 = { "__proto__": o }
const x4 = { "__proto_\u005f": o }

// Duplicate
const y1 = { __proto__: o, __proto_\u005f: o }

// Spread order
const z1 = { ...({a: ''}), __proto__: o }
const z2 = { __proto__: o, ...({a: ''}) }

// Null
const w = { __proto__: null }

// Non-object
const q = { __proto__: 1, x: 1 }

// Should not
const x5 = { ["__proto__"]: o }
const x6 = { __proto__ }
const x7 = { __proto__() {} }
enum e { __proto__ = 1 }
{
const { __proto__ } = { ['__proto__']: 1 }
}

//// [__proto__literal.js]
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var _a, _b;
var o = { a: 1 };
var __proto__ = o;
// Should
var x1 = { __proto__: o };
var x2 = { __proto_\u005f: o };
var x3 = { "__proto__": o };
var x4 = { "__proto_\u005f": o };
// Duplicate
var y1 = { __proto__: o, __proto_\u005f: o };
// Spread order
var z1 = __assign(__assign({}, ({ a: '' })), { __proto__: o });
var z2 = __assign({ __proto__: o }, ({ a: '' }));
// Null
var w = { __proto__: null };
// Non-object
var q = { __proto__: 1, x: 1 };
// Should not
var x5 = (_a = {}, _a["__proto__"] = o, _a);
var x6 = { __proto__: __proto__ };
var x7 = { __proto__: function () { } };
var e;
(function (e) {
e[e["__proto__"] = 1] = "__proto__";
})(e || (e = {}));
{
var __proto__1 = (_b = {}, _b['__proto__'] = 1, _b).__proto__;
}
86 changes: 86 additions & 0 deletions tests/baselines/reference/__proto__literal.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
=== tests/cases/compiler/__proto__literal.ts ===
const o = { a: 1 }
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))
>a : Symbol(a, Decl(__proto__literal.ts, 0, 11))

const __proto__ = o
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 1, 5))
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))

// Should
const x1 = { __proto__: o }
>x1 : Symbol(x1, Decl(__proto__literal.ts, 3, 5))
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 3, 12))
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))

const x2 = { __proto_\u005f: o }
>x2 : Symbol(x2, Decl(__proto__literal.ts, 4, 5))
>__proto_\u005f : Symbol(__proto_\u005f, Decl(__proto__literal.ts, 4, 12))
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))

const x3 = { "__proto__": o }
>x3 : Symbol(x3, Decl(__proto__literal.ts, 5, 5))
>"__proto__" : Symbol("__proto__", Decl(__proto__literal.ts, 5, 12))
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))

const x4 = { "__proto_\u005f": o }
>x4 : Symbol(x4, Decl(__proto__literal.ts, 6, 5))
>"__proto_\u005f" : Symbol("__proto_\u005f", Decl(__proto__literal.ts, 6, 12))
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))

// Duplicate
const y1 = { __proto__: o, __proto_\u005f: o }
>y1 : Symbol(y1, Decl(__proto__literal.ts, 9, 5))
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 9, 12), Decl(__proto__literal.ts, 9, 26))
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))
>__proto_\u005f : Symbol(__proto__, Decl(__proto__literal.ts, 9, 12), Decl(__proto__literal.ts, 9, 26))
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))

// Spread order
const z1 = { ...({a: ''}), __proto__: o }
>z1 : Symbol(z1, Decl(__proto__literal.ts, 12, 5))
>a : Symbol(a, Decl(__proto__literal.ts, 12, 18))
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 12, 26))
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))

const z2 = { __proto__: o, ...({a: ''}) }
>z2 : Symbol(z2, Decl(__proto__literal.ts, 13, 5))
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 13, 12))
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))
>a : Symbol(a, Decl(__proto__literal.ts, 13, 32))

// Null
const w = { __proto__: null }
>w : Symbol(w, Decl(__proto__literal.ts, 16, 5))
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 16, 11))

// Non-object
const q = { __proto__: 1, x: 1 }
>q : Symbol(q, Decl(__proto__literal.ts, 19, 5))
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 19, 11))
>x : Symbol(x, Decl(__proto__literal.ts, 19, 25))

// Should not
const x5 = { ["__proto__"]: o }
>x5 : Symbol(x5, Decl(__proto__literal.ts, 22, 5))
>["__proto__"] : Symbol(["__proto__"], Decl(__proto__literal.ts, 22, 12))
>"__proto__" : Symbol(["__proto__"], Decl(__proto__literal.ts, 22, 12))
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))

const x6 = { __proto__ }
>x6 : Symbol(x6, Decl(__proto__literal.ts, 23, 5))
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 23, 12))

const x7 = { __proto__() {} }
>x7 : Symbol(x7, Decl(__proto__literal.ts, 24, 5))
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 24, 12))

enum e { __proto__ = 1 }
>e : Symbol(e, Decl(__proto__literal.ts, 24, 29))
>__proto__ : Symbol(e.__proto__, Decl(__proto__literal.ts, 25, 8))
{
const { __proto__ } = { ['__proto__']: 1 }
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 27, 11))
>['__proto__'] : Symbol(['__proto__'], Decl(__proto__literal.ts, 27, 27))
>'__proto__' : Symbol(['__proto__'], Decl(__proto__literal.ts, 27, 27))
}
112 changes: 112 additions & 0 deletions tests/baselines/reference/__proto__literal.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
=== tests/cases/compiler/__proto__literal.ts ===
const o = { a: 1 }
>o : { a: number; }
>{ a: 1 } : { a: number; }
>a : number
>1 : 1

const __proto__ = o
>__proto__ : { a: number; }
>o : { a: number; }

// Should
const x1 = { __proto__: o }
>x1 : { a: number; }
>{ __proto__: o } : { a: number; }
>__proto__ : { a: number; }
>o : { a: number; }

const x2 = { __proto_\u005f: o }
>x2 : { a: number; }
>{ __proto_\u005f: o } : { a: number; }
>__proto_\u005f : { a: number; }
>o : { a: number; }

const x3 = { "__proto__": o }
>x3 : { a: number; }
>{ "__proto__": o } : { a: number; }
>"__proto__" : { a: number; }
>o : { a: number; }

const x4 = { "__proto_\u005f": o }
>x4 : { a: number; }
>{ "__proto_\u005f": o } : { a: number; }
>"__proto_\u005f" : { a: number; }
>o : { a: number; }

// Duplicate
const y1 = { __proto__: o, __proto_\u005f: o }
>y1 : { a: number; }
>{ __proto__: o, __proto_\u005f: o } : { a: number; }
>__proto__ : { a: number; }
>o : { a: number; }
>__proto_\u005f : { a: number; }
>o : { a: number; }

// Spread order
const z1 = { ...({a: ''}), __proto__: o }
>z1 : { a: string; }
>{ ...({a: ''}), __proto__: o } : { a: string; }
>({a: ''}) : { a: string; }
>{a: ''} : { a: string; }
>a : string
>'' : ""
>__proto__ : { a: number; }
>o : { a: number; }

const z2 = { __proto__: o, ...({a: ''}) }
>z2 : { a: string; }
>{ __proto__: o, ...({a: ''}) } : { a: string; }
>__proto__ : { a: number; }
>o : { a: number; }
>({a: ''}) : { a: string; }
>{a: ''} : { a: string; }
>a : string
>'' : ""

// Null
const w = { __proto__: null }
>w : {}
>{ __proto__: null } : {}
>__proto__ : null
>null : null

// Non-object
const q = { __proto__: 1, x: 1 }
>q : { x: number; }
>{ __proto__: 1, x: 1 } : { x: number; }
>__proto__ : number
>1 : 1
>x : number
>1 : 1

// Should not
const x5 = { ["__proto__"]: o }
>x5 : { __proto__: { a: number; }; }
>{ ["__proto__"]: o } : { __proto__: { a: number; }; }
>["__proto__"] : { a: number; }
>"__proto__" : "__proto__"
>o : { a: number; }

const x6 = { __proto__ }
>x6 : { __proto__: { a: number; }; }
>{ __proto__ } : { __proto__: { a: number; }; }
>__proto__ : { a: number; }

const x7 = { __proto__() {} }
>x7 : { __proto__(): void; }
>{ __proto__() {} } : { __proto__(): void; }
>__proto__ : () => void

enum e { __proto__ = 1 }
>e : e
>__proto__ : e.__proto__
>1 : 1
{
const { __proto__ } = { ['__proto__']: 1 }
>__proto__ : number
>{ ['__proto__']: 1 } : { __proto__: number; }
>['__proto__'] : number
>'__proto__' : "__proto__"
>1 : 1
}
Loading

0 comments on commit 6f3ef5d

Please sign in to comment.