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}