UNPKG

15 kBJavaScriptView Raw
1"use strict";
2/**
3 * @license
4 * Copyright Google LLC All Rights Reserved.
5 *
6 * Use of this source code is governed by an MIT-style license that can be
7 * found in the LICENSE file at https://angular.io/license
8 */
9var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10 if (k2 === undefined) k2 = k;
11 Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
12}) : (function(o, m, k, k2) {
13 if (k2 === undefined) k2 = k;
14 o[k2] = m[k];
15}));
16var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17 Object.defineProperty(o, "default", { enumerable: true, value: v });
18}) : function(o, v) {
19 o["default"] = v;
20});
21var __importStar = (this && this.__importStar) || function (mod) {
22 if (mod && mod.__esModule) return mod;
23 var result = {};
24 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
25 __setModuleDefault(result, mod);
26 return result;
27};
28Object.defineProperty(exports, "__esModule", { value: true });
29exports.getWrapEnumsTransformer = void 0;
30const ts = __importStar(require("typescript"));
31const ast_utils_1 = require("../helpers/ast-utils");
32function isBlockLike(node) {
33 return (node.kind === ts.SyntaxKind.Block ||
34 node.kind === ts.SyntaxKind.ModuleBlock ||
35 node.kind === ts.SyntaxKind.CaseClause ||
36 node.kind === ts.SyntaxKind.DefaultClause ||
37 node.kind === ts.SyntaxKind.SourceFile);
38}
39function getWrapEnumsTransformer() {
40 return (context) => {
41 const transformer = (sf) => {
42 const result = visitBlockStatements(sf.statements, context);
43 return context.factory.updateSourceFile(sf, ts.setTextRange(result, sf.statements));
44 };
45 return transformer;
46 };
47}
48exports.getWrapEnumsTransformer = getWrapEnumsTransformer;
49function visitBlockStatements(statements, context) {
50 // copy of statements to modify; lazy initialized
51 let updatedStatements;
52 const nodeFactory = context.factory;
53 const visitor = (node) => {
54 if (isBlockLike(node)) {
55 let result = visitBlockStatements(node.statements, context);
56 if (result === node.statements) {
57 return node;
58 }
59 result = ts.setTextRange(result, node.statements);
60 switch (node.kind) {
61 case ts.SyntaxKind.Block:
62 return nodeFactory.updateBlock(node, result);
63 case ts.SyntaxKind.ModuleBlock:
64 return nodeFactory.updateModuleBlock(node, result);
65 case ts.SyntaxKind.CaseClause:
66 return nodeFactory.updateCaseClause(node, node.expression, result);
67 case ts.SyntaxKind.DefaultClause:
68 return nodeFactory.updateDefaultClause(node, result);
69 default:
70 return node;
71 }
72 }
73 else {
74 return node;
75 }
76 };
77 // 'oIndex' is the original statement index; 'uIndex' is the updated statement index
78 for (let oIndex = 0, uIndex = 0; oIndex < statements.length - 1; oIndex++, uIndex++) {
79 const currentStatement = statements[oIndex];
80 let newStatement;
81 let oldStatementsLength = 0;
82 // these can't contain an enum declaration
83 if (currentStatement.kind === ts.SyntaxKind.ImportDeclaration) {
84 continue;
85 }
86 // enum declarations must:
87 // * not be last statement
88 // * be a variable statement
89 // * have only one declaration
90 // * have an identifer as a declaration name
91 // ClassExpression declarations must:
92 // * not be last statement
93 // * be a variable statement
94 // * have only one declaration
95 // * have an ClassExpression or BinaryExpression and a right
96 // of kind ClassExpression as a initializer
97 if (ts.isVariableStatement(currentStatement) &&
98 currentStatement.declarationList.declarations.length === 1) {
99 const variableDeclaration = currentStatement.declarationList.declarations[0];
100 const initializer = variableDeclaration.initializer;
101 if (ts.isIdentifier(variableDeclaration.name)) {
102 const name = variableDeclaration.name.text;
103 if (!initializer) {
104 const iife = findEnumIife(name, statements[oIndex + 1]);
105 if (iife) {
106 // update IIFE and replace variable statement and old IIFE
107 oldStatementsLength = 2;
108 newStatement = updateEnumIife(nodeFactory, currentStatement, iife[0], iife[1]);
109 // skip IIFE statement
110 oIndex++;
111 }
112 }
113 else if (ts.isClassExpression(initializer) ||
114 (ts.isBinaryExpression(initializer) && ts.isClassExpression(initializer.right))) {
115 const classStatements = findStatements(name, statements, oIndex);
116 if (!classStatements) {
117 continue;
118 }
119 oldStatementsLength = classStatements.length;
120 newStatement = createWrappedClass(nodeFactory, variableDeclaration, classStatements);
121 oIndex += classStatements.length - 1;
122 }
123 }
124 }
125 else if (ts.isClassDeclaration(currentStatement)) {
126 const name = currentStatement.name.text;
127 const classStatements = findStatements(name, statements, oIndex);
128 if (!classStatements) {
129 continue;
130 }
131 oldStatementsLength = classStatements.length;
132 newStatement = createWrappedClass(nodeFactory, currentStatement, classStatements);
133 oIndex += oldStatementsLength - 1;
134 }
135 if (newStatement && newStatement.length > 0) {
136 if (!updatedStatements) {
137 updatedStatements = [...statements];
138 }
139 updatedStatements.splice(uIndex, oldStatementsLength, ...newStatement);
140 // When having more than a single new statement
141 // we need to update the update Index
142 uIndex += newStatement ? newStatement.length - 1 : 0;
143 }
144 const result = ts.visitNode(currentStatement, visitor);
145 if (result !== currentStatement) {
146 if (!updatedStatements) {
147 updatedStatements = statements.slice();
148 }
149 updatedStatements[uIndex] = result;
150 }
151 }
152 // if changes, return updated statements
153 // otherwise, return original array instance
154 return updatedStatements ? nodeFactory.createNodeArray(updatedStatements) : statements;
155}
156// TS 2.3 enums have statements that are inside a IIFE.
157function findEnumIife(name, statement) {
158 if (!ts.isExpressionStatement(statement)) {
159 return null;
160 }
161 const expression = statement.expression;
162 if (!expression || !ts.isCallExpression(expression) || expression.arguments.length !== 1) {
163 return null;
164 }
165 const callExpression = expression;
166 let exportExpression;
167 if (!ts.isParenthesizedExpression(callExpression.expression)) {
168 return null;
169 }
170 const functionExpression = callExpression.expression.expression;
171 if (!ts.isFunctionExpression(functionExpression)) {
172 return null;
173 }
174 // The name of the parameter can be different than the name of the enum if it was renamed
175 // due to scope hoisting.
176 const parameter = functionExpression.parameters[0];
177 if (!ts.isIdentifier(parameter.name)) {
178 return null;
179 }
180 const parameterName = parameter.name.text;
181 let argument = callExpression.arguments[0];
182 if (!ts.isBinaryExpression(argument) ||
183 !ts.isIdentifier(argument.left) ||
184 argument.left.text !== name) {
185 return null;
186 }
187 let potentialExport = false;
188 if (argument.operatorToken.kind === ts.SyntaxKind.FirstAssignment) {
189 if (ts.isBinaryExpression(argument.right) &&
190 argument.right.operatorToken.kind !== ts.SyntaxKind.BarBarToken) {
191 return null;
192 }
193 potentialExport = true;
194 argument = argument.right;
195 }
196 if (!ts.isBinaryExpression(argument)) {
197 return null;
198 }
199 if (argument.operatorToken.kind !== ts.SyntaxKind.BarBarToken) {
200 return null;
201 }
202 if (potentialExport && !ts.isIdentifier(argument.left)) {
203 exportExpression = argument.left;
204 }
205 // Go through all the statements and check that all match the name
206 for (const statement of functionExpression.body.statements) {
207 if (!ts.isExpressionStatement(statement) ||
208 !ts.isBinaryExpression(statement.expression) ||
209 !ts.isElementAccessExpression(statement.expression.left)) {
210 return null;
211 }
212 const leftExpression = statement.expression.left.expression;
213 if (!ts.isIdentifier(leftExpression) || leftExpression.text !== parameterName) {
214 return null;
215 }
216 }
217 return [callExpression, exportExpression];
218}
219function updateHostNode(nodeFactory, hostNode, expression) {
220 // Update existing host node with the pure comment before the variable declaration initializer.
221 const variableDeclaration = hostNode.declarationList.declarations[0];
222 const outerVarStmt = nodeFactory.updateVariableStatement(hostNode, hostNode.modifiers, nodeFactory.updateVariableDeclarationList(hostNode.declarationList, [
223 nodeFactory.updateVariableDeclaration(variableDeclaration, variableDeclaration.name, variableDeclaration.exclamationToken, variableDeclaration.type, expression),
224 ]));
225 return outerVarStmt;
226}
227/**
228 * Find enums, class expression or declaration statements.
229 *
230 * The classExpressions block to wrap in an iife must
231 * - end with an ExpressionStatement
232 * - it's expression must be a BinaryExpression
233 * - have the same name
234 *
235 * ```
236 let Foo = class Foo {};
237 Foo = __decorate([]);
238 ```
239 */
240function findStatements(name, statements, statementIndex, offset = 0) {
241 let count = 1;
242 for (let index = statementIndex + 1; index < statements.length; ++index) {
243 const statement = statements[index];
244 if (!ts.isExpressionStatement(statement)) {
245 break;
246 }
247 const expression = statement.expression;
248 if (ts.isCallExpression(expression)) {
249 // Ex:
250 // setClassMetadata(FooClass, [{}], void 0);
251 // __decorate([propDecorator()], FooClass.prototype, "propertyName", void 0);
252 // __decorate([propDecorator()], FooClass, "propertyName", void 0);
253 // __decorate$1([propDecorator()], FooClass, "propertyName", void 0);
254 const args = expression.arguments;
255 if (args.length > 2) {
256 const isReferenced = args.some((arg) => {
257 const potentialIdentifier = ts.isPropertyAccessExpression(arg) ? arg.expression : arg;
258 return ts.isIdentifier(potentialIdentifier) && potentialIdentifier.text === name;
259 });
260 if (isReferenced) {
261 count++;
262 continue;
263 }
264 }
265 }
266 else if (ts.isBinaryExpression(expression)) {
267 const node = ts.isBinaryExpression(expression.left) ? expression.left.left : expression.left;
268 const leftExpression = ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)
269 ? // Static Properties // Ex: Foo.bar = 'value';
270 // ENUM Property // Ex: ChangeDetectionStrategy[ChangeDetectionStrategy.Default] = "Default";
271 node.expression
272 : // Ex: FooClass = __decorate([Component()], FooClass);
273 node;
274 if (ts.isIdentifier(leftExpression) && leftExpression.text === name) {
275 count++;
276 continue;
277 }
278 }
279 break;
280 }
281 if (count > 1) {
282 return statements.slice(statementIndex + offset, statementIndex + count);
283 }
284 return undefined;
285}
286function updateEnumIife(nodeFactory, hostNode, iife, exportAssignment) {
287 if (!ts.isParenthesizedExpression(iife.expression) ||
288 !ts.isFunctionExpression(iife.expression.expression)) {
289 throw new Error('Invalid IIFE Structure');
290 }
291 // Ignore export assignment if variable is directly exported
292 if (hostNode.modifiers &&
293 hostNode.modifiers.findIndex((m) => m.kind == ts.SyntaxKind.ExportKeyword) != -1) {
294 exportAssignment = undefined;
295 }
296 const expression = iife.expression.expression;
297 const updatedFunction = nodeFactory.updateFunctionExpression(expression, expression.modifiers, expression.asteriskToken, expression.name, expression.typeParameters, expression.parameters, expression.type, nodeFactory.updateBlock(expression.body, [
298 ...expression.body.statements,
299 nodeFactory.createReturnStatement(expression.parameters[0].name),
300 ]));
301 let arg = nodeFactory.createObjectLiteralExpression();
302 if (exportAssignment) {
303 arg = nodeFactory.createBinaryExpression(exportAssignment, ts.SyntaxKind.BarBarToken, arg);
304 }
305 const updatedIife = nodeFactory.updateCallExpression(iife, nodeFactory.updateParenthesizedExpression(iife.expression, updatedFunction), iife.typeArguments, [arg]);
306 let value = ast_utils_1.addPureComment(updatedIife);
307 if (exportAssignment) {
308 value = nodeFactory.createBinaryExpression(exportAssignment, ts.SyntaxKind.FirstAssignment, updatedIife);
309 }
310 return [updateHostNode(nodeFactory, hostNode, value)];
311}
312function createWrappedClass(nodeFactory, hostNode, statements) {
313 const name = hostNode.name.text;
314 const updatedStatements = [...statements];
315 if (ts.isClassDeclaration(hostNode)) {
316 updatedStatements[0] = nodeFactory.createClassDeclaration(hostNode.decorators, undefined, hostNode.name, hostNode.typeParameters, hostNode.heritageClauses, hostNode.members);
317 }
318 const pureIife = ast_utils_1.addPureComment(nodeFactory.createImmediatelyInvokedArrowFunction([
319 ...updatedStatements,
320 nodeFactory.createReturnStatement(nodeFactory.createIdentifier(name)),
321 ]));
322 const modifiers = hostNode.modifiers;
323 const isDefault = !!modifiers && modifiers.some((x) => x.kind === ts.SyntaxKind.DefaultKeyword);
324 const newStatement = [];
325 newStatement.push(nodeFactory.createVariableStatement(isDefault ? undefined : modifiers, nodeFactory.createVariableDeclarationList([nodeFactory.createVariableDeclaration(name, undefined, undefined, pureIife)], ts.NodeFlags.Let)));
326 if (isDefault) {
327 newStatement.push(nodeFactory.createExportAssignment(undefined, undefined, false, nodeFactory.createIdentifier(name)));
328 }
329 return newStatement;
330}