• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import * as ts from "typescript";
17import { isValidIndex } from "../expression/memberAccessExpression";
18import * as jshelpers from "../jshelpers";
19
20export enum PropertyKind {
21    VARIABLE,
22    CONSTANT,
23    COMPUTED, // Property with computed value (execution time).
24    PROTOTYPE,
25    ACCESSOR,
26    SPREAD
27}
28
29export class Property {
30    private propKind: PropertyKind;
31    private valueNode: ts.Node | undefined;
32    private setterNode: ts.SetAccessorDeclaration | undefined;
33    private getterNode: ts.GetAccessorDeclaration | undefined;
34    private compiled: boolean = false;
35    private redeclared: boolean = false;
36    private name: string | number | ts.ComputedPropertyName | undefined;
37
38    constructor(propKind: PropertyKind, name: string | number | ts.ComputedPropertyName | undefined) {
39        this.propKind = propKind;
40        if (typeof (name) != 'undefined') {
41            this.name = name;
42        }
43    }
44
45    setCompiled(): void {
46        this.compiled = true;
47    }
48
49    setRedeclared(): void {
50        this.redeclared = true;
51    }
52
53    isCompiled(): boolean {
54        return this.compiled;
55    }
56
57    isRedeclared(): boolean {
58        return this.redeclared;
59    }
60
61    getName(): string | number | ts.ComputedPropertyName {
62        if (typeof (this.name) === 'undefined') {
63            throw new Error("this property doesn't have a name");
64        }
65        return this.name;
66    }
67
68    getKind(): PropertyKind {
69        return this.propKind;
70    }
71
72    getValue(): ts.Node {
73        if (this.propKind === PropertyKind.ACCESSOR) {
74            throw new Error("Accessor doesn't have valueNode");
75        }
76        return this.valueNode!;
77    }
78
79    getGetter(): ts.GetAccessorDeclaration {
80        return this.getterNode;
81    }
82
83    getSetter(): ts.SetAccessorDeclaration {
84        return this.setterNode;
85    }
86
87    setValue(valueNode: ts.Node): void {
88        this.valueNode = valueNode;
89        this.getterNode = undefined;
90        this.setterNode = undefined;
91    }
92
93    setGetter(getter: ts.GetAccessorDeclaration): void {
94        if (this.propKind != PropertyKind.ACCESSOR) {
95            this.valueNode = undefined;
96            this.setterNode = undefined;
97            this.propKind = PropertyKind.ACCESSOR;
98        }
99        this.getterNode = getter;
100    }
101
102    setSetter(setter: ts.SetAccessorDeclaration): void {
103        if (this.propKind != PropertyKind.ACCESSOR) {
104            this.valueNode = undefined;
105            this.getterNode = undefined;
106            this.propKind = PropertyKind.ACCESSOR;
107        }
108        this.setterNode = setter;
109    }
110
111    setKind(propKind: PropertyKind): void {
112        this.propKind = propKind;
113    }
114}
115
116
117export function generatePropertyFromExpr(expr: ts.ObjectLiteralExpression): Property[] {
118    let hasProto: boolean = false;
119    let properties: Property[] = [];
120    let namedPropertyMap: Map<string, number> = new Map<string, number>();
121
122    expr.properties.forEach(property => {
123        switch (property.kind) {
124            case ts.SyntaxKind.PropertyAssignment: {
125                if (property.name.kind === ts.SyntaxKind.ComputedPropertyName) {
126                    defineProperty(property.name, property, PropertyKind.COMPUTED, properties, namedPropertyMap);
127                    break;
128                }
129
130                let propName: number | string = <number | string>getPropName(property.name);
131                if (propName === "__proto__") {
132                    if (!hasProto) {
133                        defineProperty(propName, property.initializer, PropertyKind.PROTOTYPE, properties, namedPropertyMap);
134                        hasProto = true;
135                        break;
136                    } else {
137                        throw new Error("__proto__ was set multiple times in the object definition.");
138                    }
139                }
140
141                if (isConstantExpr(property.initializer)) {
142                    defineProperty(propName, property.initializer, PropertyKind.CONSTANT, properties, namedPropertyMap);
143                } else {
144                    defineProperty(propName, property.initializer, PropertyKind.VARIABLE, properties, namedPropertyMap);
145                }
146                break;
147            }
148            case ts.SyntaxKind.ShorthandPropertyAssignment: {
149                // ShorthandProperty's name always be Identifier
150                let propName = jshelpers.getTextOfIdentifierOrLiteral(property.name);
151                defineProperty(propName, property.name, PropertyKind.VARIABLE, properties, namedPropertyMap);
152                break;
153            }
154            case ts.SyntaxKind.SpreadAssignment: {
155                defineProperty(undefined, property.expression, PropertyKind.SPREAD, properties, namedPropertyMap);
156                break;
157            }
158            case ts.SyntaxKind.MethodDeclaration: {
159                let propName = getPropName(property.name);
160                if (typeof (propName) === 'string' || typeof (propName) === 'number') {
161                    defineProperty(propName, property, PropertyKind.VARIABLE, properties, namedPropertyMap);
162                } else {
163                    defineProperty(propName, property, PropertyKind.COMPUTED, properties, namedPropertyMap);
164                }
165                break;
166            }
167            case ts.SyntaxKind.GetAccessor:
168            case ts.SyntaxKind.SetAccessor: {
169                let propName = getPropName(property.name);
170                if (typeof (propName) === 'string' || typeof (propName) === 'number') {
171                    defineProperty(propName, property, PropertyKind.ACCESSOR, properties, namedPropertyMap);
172                } else {
173                    defineProperty(propName, property, PropertyKind.COMPUTED, properties, namedPropertyMap);
174                }
175                break;
176            }
177            default:
178                throw new Error("Unreachable Kind");
179        }
180    });
181
182    return properties;
183}
184
185function defineProperty(
186    propName: string | number | ts.ComputedPropertyName | undefined,
187    propValue: ts.Node,
188    propKind: PropertyKind,
189    properties: Property[],
190    namedPropertyMap: Map<string, number>): void {
191    if (propKind === PropertyKind.COMPUTED || propKind === PropertyKind.SPREAD) {
192        let prop = new Property(propKind, <ts.ComputedPropertyName | undefined>propName);
193        prop.setValue(propValue);
194        properties.push(prop);
195    } else {
196        let prop = new Property(propKind, propName);
197        let name_str = propertyKeyAsString(<string | number>propName);
198
199        if (namedPropertyMap.has(name_str)) {
200            let prevProp = properties[namedPropertyMap.get(name_str)!];
201
202            if ((prevProp.getKind() === PropertyKind.ACCESSOR || prevProp.getKind() === PropertyKind.CONSTANT) &&
203                (propKind === PropertyKind.ACCESSOR || propKind === PropertyKind.CONSTANT)) {
204                if (propKind === PropertyKind.ACCESSOR) {
205                    if (ts.isGetAccessorDeclaration(propValue)) {
206                        prevProp!.setGetter(propValue);
207                    } else if (ts.isSetAccessorDeclaration(propValue)) {
208                        prevProp!.setSetter(propValue);
209                    }
210                } else {
211                    prevProp!.setValue(propValue);
212                    prevProp!.setKind(PropertyKind.CONSTANT);
213                }
214                return;
215            }
216
217            prop.setRedeclared();
218        }
219
220        namedPropertyMap.set(name_str, properties.length);
221        if (propKind === PropertyKind.ACCESSOR) {
222            if (ts.isGetAccessorDeclaration(propValue)) {
223                prop.setGetter(propValue);
224            } else if (ts.isSetAccessorDeclaration(propValue)) {
225                prop.setSetter(propValue);
226            }
227        } else {
228            prop.setValue(propValue);
229        }
230        properties.push(prop);
231    }
232}
233
234export function isConstantExpr(node: ts.Node): boolean {
235    switch (node.kind) {
236        case ts.SyntaxKind.StringLiteral:
237        case ts.SyntaxKind.NumericLiteral:
238        case ts.SyntaxKind.NullKeyword:
239        case ts.SyntaxKind.TrueKeyword:
240        case ts.SyntaxKind.FalseKeyword:
241            return true;
242        default:
243            return false;
244    }
245}
246
247export function propertyKeyAsString(propName: string | number): string {
248    if (typeof (propName) === 'number') {
249        return propName.toString();
250    }
251    return propName;
252}
253
254export function getPropName(propertyName: ts.PropertyName): string | number | ts.ComputedPropertyName {
255    if (ts.isComputedPropertyName(propertyName)) {
256        return propertyName;
257    }
258
259    let propName: number | string = jshelpers.getTextOfIdentifierOrLiteral(propertyName);
260
261    if (propertyName.kind === ts.SyntaxKind.NumericLiteral) {
262        propName = Number.parseFloat(propName);
263        if (!isValidIndex(propName)) {
264            propName = propName.toString();
265        }
266    } else if (propertyName.kind === ts.SyntaxKind.StringLiteral) {
267        let temp = Number(propName);
268        if (!isNaN(Number.parseFloat(propName)) && !isNaN(temp) && isValidIndex(temp) && String(temp) === propName) {
269            propName = temp;
270        }
271    }
272
273    return propName;
274}