UNPKG

18.9 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.expect = exports.createScrubFileTransformerFactory = exports.testScrubFile = void 0;
30const ts = __importStar(require("typescript"));
31const ast_utils_1 = require("../helpers/ast-utils");
32function testScrubFile(content) {
33 const markers = [
34 'decorators',
35 '__decorate',
36 'propDecorators',
37 'ctorParameters',
38 'ɵsetClassMetadata',
39 ];
40 return markers.some((marker) => content.includes(marker));
41}
42exports.testScrubFile = testScrubFile;
43function createScrubFileTransformerFactory(isAngularCoreFile) {
44 return (program) => scrubFileTransformer(program, isAngularCoreFile);
45}
46exports.createScrubFileTransformerFactory = createScrubFileTransformerFactory;
47function scrubFileTransformer(program, isAngularCoreFile) {
48 if (!program) {
49 throw new Error('scrubFileTransformer requires a TypeScript Program.');
50 }
51 const checker = program.getTypeChecker();
52 return (context) => {
53 const transformer = (sf) => {
54 const ngMetadata = findAngularMetadata(sf, isAngularCoreFile);
55 const tslibImports = findTslibImports(sf);
56 const nodes = [];
57 ts.forEachChild(sf, checkNodeForDecorators);
58 function checkNodeForDecorators(node) {
59 var _a;
60 if (!ts.isExpressionStatement(node)) {
61 return ts.forEachChild(node, checkNodeForDecorators);
62 }
63 const exprStmt = node;
64 const iife = (_a = getIifeStatement(exprStmt)) === null || _a === void 0 ? void 0 : _a.expression;
65 // Do checks that don't need the typechecker first and bail early.
66 if (isCtorParamsAssignmentExpression(exprStmt)) {
67 nodes.push(node);
68 }
69 else if (iife && isIvyPrivateCallExpression(iife, 'ɵsetClassMetadata')) {
70 nodes.push(node);
71 }
72 else if (iife &&
73 ts.isBinaryExpression(iife) &&
74 isIvyPrivateCallExpression(iife.right, 'ɵsetClassMetadata')) {
75 nodes.push(node);
76 }
77 else if (isDecoratorAssignmentExpression(exprStmt)) {
78 nodes.push(...pickDecorationNodesToRemove(exprStmt, ngMetadata, checker));
79 }
80 else if (isDecorateAssignmentExpression(exprStmt, tslibImports, checker) ||
81 isAngularDecoratorExpression(exprStmt, ngMetadata, tslibImports, checker)) {
82 nodes.push(...pickDecorateNodesToRemove(exprStmt, tslibImports, ngMetadata, checker));
83 }
84 else if (isPropDecoratorAssignmentExpression(exprStmt)) {
85 nodes.push(...pickPropDecorationNodesToRemove(exprStmt, ngMetadata, checker));
86 }
87 }
88 const visitor = (node) => {
89 // Check if node is a statement to be dropped.
90 if (nodes.find((n) => n === node)) {
91 return undefined;
92 }
93 // Otherwise return node as is.
94 return ts.visitEachChild(node, visitor, context);
95 };
96 return ts.visitNode(sf, visitor);
97 };
98 return transformer;
99 };
100}
101function expect(node, kind) {
102 if (node.kind !== kind) {
103 throw new Error('Invalid node type.');
104 }
105 return node;
106}
107exports.expect = expect;
108function findAngularMetadata(node, isAngularCoreFile) {
109 let specs = [];
110 // Find all specifiers from imports of `@angular/core`.
111 ts.forEachChild(node, (child) => {
112 if (child.kind === ts.SyntaxKind.ImportDeclaration) {
113 const importDecl = child;
114 if (isAngularCoreImport(importDecl, isAngularCoreFile)) {
115 specs.push(...(0, ast_utils_1.collectDeepNodes)(importDecl, ts.SyntaxKind.ImportSpecifier));
116 }
117 }
118 });
119 // If the current module is a Angular core file, we also consider all declarations in it to
120 // potentially be Angular metadata.
121 if (isAngularCoreFile) {
122 const localDecl = findAllDeclarations(node);
123 specs = specs.concat(localDecl);
124 }
125 return specs;
126}
127function findAllDeclarations(node) {
128 const nodes = [];
129 ts.forEachChild(node, (child) => {
130 if (child.kind === ts.SyntaxKind.VariableStatement) {
131 const vStmt = child;
132 vStmt.declarationList.declarations.forEach((decl) => {
133 if (decl.name.kind !== ts.SyntaxKind.Identifier) {
134 return;
135 }
136 nodes.push(decl);
137 });
138 }
139 });
140 return nodes;
141}
142function isAngularCoreImport(node, isAngularCoreFile) {
143 if (!(node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier))) {
144 return false;
145 }
146 const importText = node.moduleSpecifier.text;
147 // Imports to `@angular/core` are always core imports.
148 if (importText === '@angular/core') {
149 return true;
150 }
151 // Relative imports from a Angular core file are also core imports.
152 if (isAngularCoreFile && importText.startsWith('.')) {
153 return true;
154 }
155 return false;
156}
157// Check if assignment is `Clazz.decorators = [...];`.
158function isDecoratorAssignmentExpression(exprStmt) {
159 if (!isAssignmentExpressionTo(exprStmt, 'decorators')) {
160 return false;
161 }
162 const expr = exprStmt.expression;
163 if (!ts.isArrayLiteralExpression(expr.right)) {
164 return false;
165 }
166 return true;
167}
168// Check if assignment is `Clazz = __decorate([...], Clazz)`.
169function isDecorateAssignmentExpression(exprStmt, tslibImports, checker) {
170 if (!ts.isBinaryExpression(exprStmt.expression)) {
171 return false;
172 }
173 const expr = exprStmt.expression;
174 if (!ts.isIdentifier(expr.left)) {
175 return false;
176 }
177 const classIdent = expr.left;
178 let callExpr;
179 if (ts.isCallExpression(expr.right)) {
180 callExpr = expr.right;
181 }
182 else if (ts.isBinaryExpression(expr.right)) {
183 // `Clazz = Clazz_1 = __decorate([...], Clazz)` can be found when there are static property
184 // accesses.
185 const innerExpr = expr.right;
186 if (!ts.isIdentifier(innerExpr.left) || !ts.isCallExpression(innerExpr.right)) {
187 return false;
188 }
189 callExpr = innerExpr.right;
190 }
191 else {
192 return false;
193 }
194 if (!isTslibHelper(callExpr, '__decorate', tslibImports, checker)) {
195 return false;
196 }
197 if (callExpr.arguments.length !== 2) {
198 return false;
199 }
200 const classArg = callExpr.arguments[1];
201 if (!ts.isIdentifier(classArg)) {
202 return false;
203 }
204 if (classIdent.text !== classArg.text) {
205 return false;
206 }
207 if (!ts.isArrayLiteralExpression(callExpr.arguments[0])) {
208 return false;
209 }
210 return true;
211}
212// Check if expression is `__decorate([smt, __metadata("design:type", Object)], ...)`.
213function isAngularDecoratorExpression(exprStmt, ngMetadata, tslibImports, checker) {
214 if (!ts.isCallExpression(exprStmt.expression)) {
215 return false;
216 }
217 const callExpr = exprStmt.expression;
218 if (!isTslibHelper(callExpr, '__decorate', tslibImports, checker)) {
219 return false;
220 }
221 if (callExpr.arguments.length !== 4) {
222 return false;
223 }
224 const decorateArray = callExpr.arguments[0];
225 if (!ts.isArrayLiteralExpression(decorateArray)) {
226 return false;
227 }
228 // Check first array entry for Angular decorators.
229 if (decorateArray.elements.length === 0 || !ts.isCallExpression(decorateArray.elements[0])) {
230 return false;
231 }
232 return decorateArray.elements.some((decoratorCall) => {
233 if (!ts.isCallExpression(decoratorCall) || !ts.isIdentifier(decoratorCall.expression)) {
234 return false;
235 }
236 const decoratorId = decoratorCall.expression;
237 return identifierIsMetadata(decoratorId, ngMetadata, checker);
238 });
239}
240// Check if assignment is `Clazz.propDecorators = [...];`.
241function isPropDecoratorAssignmentExpression(exprStmt) {
242 if (!isAssignmentExpressionTo(exprStmt, 'propDecorators')) {
243 return false;
244 }
245 const expr = exprStmt.expression;
246 if (expr.right.kind !== ts.SyntaxKind.ObjectLiteralExpression) {
247 return false;
248 }
249 return true;
250}
251// Check if assignment is `Clazz.ctorParameters = [...];`.
252function isCtorParamsAssignmentExpression(exprStmt) {
253 if (!isAssignmentExpressionTo(exprStmt, 'ctorParameters')) {
254 return false;
255 }
256 const expr = exprStmt.expression;
257 if (expr.right.kind !== ts.SyntaxKind.FunctionExpression &&
258 expr.right.kind !== ts.SyntaxKind.ArrowFunction) {
259 return false;
260 }
261 return true;
262}
263function isAssignmentExpressionTo(exprStmt, name) {
264 if (!ts.isBinaryExpression(exprStmt.expression)) {
265 return false;
266 }
267 const expr = exprStmt.expression;
268 if (!ts.isPropertyAccessExpression(expr.left)) {
269 return false;
270 }
271 const propAccess = expr.left;
272 if (propAccess.name.text !== name) {
273 return false;
274 }
275 if (!ts.isIdentifier(propAccess.expression)) {
276 return false;
277 }
278 if (expr.operatorToken.kind !== ts.SyntaxKind.FirstAssignment) {
279 return false;
280 }
281 return true;
282}
283// Each Ivy private call expression is inside an IIFE
284function getIifeStatement(exprStmt) {
285 const expression = exprStmt.expression;
286 if (!expression || !ts.isCallExpression(expression) || expression.arguments.length !== 0) {
287 return null;
288 }
289 const parenExpr = expression;
290 if (!ts.isParenthesizedExpression(parenExpr.expression)) {
291 return null;
292 }
293 const funExpr = parenExpr.expression.expression;
294 if (!ts.isFunctionExpression(funExpr)) {
295 return null;
296 }
297 const innerStmts = funExpr.body.statements;
298 if (innerStmts.length !== 1) {
299 return null;
300 }
301 const innerExprStmt = innerStmts[0];
302 if (!ts.isExpressionStatement(innerExprStmt)) {
303 return null;
304 }
305 return innerExprStmt;
306}
307function isIvyPrivateCallExpression(expression, name) {
308 // Now we're in the IIFE and have the inner expression statement. We can check if it matches
309 // a private Ivy call.
310 if (!ts.isCallExpression(expression)) {
311 return false;
312 }
313 const propAccExpr = expression.expression;
314 if (!ts.isPropertyAccessExpression(propAccExpr)) {
315 return false;
316 }
317 if (propAccExpr.name.text != name) {
318 return false;
319 }
320 return true;
321}
322// Remove Angular decorators from`Clazz.decorators = [...];`, or expression itself if all are
323// removed.
324function pickDecorationNodesToRemove(exprStmt, ngMetadata, checker) {
325 const expr = expect(exprStmt.expression, ts.SyntaxKind.BinaryExpression);
326 const literal = expect(expr.right, ts.SyntaxKind.ArrayLiteralExpression);
327 if (!literal.elements.every((elem) => ts.isObjectLiteralExpression(elem))) {
328 return [];
329 }
330 const elements = literal.elements;
331 const ngDecorators = elements.filter((elem) => isAngularDecorator(elem, ngMetadata, checker));
332 return elements.length > ngDecorators.length ? ngDecorators : [exprStmt];
333}
334// Remove Angular decorators from `Clazz = __decorate([...], Clazz)`, or expression itself if all
335// are removed.
336function pickDecorateNodesToRemove(exprStmt, tslibImports, ngMetadata, checker) {
337 let callExpr;
338 if (ts.isCallExpression(exprStmt.expression)) {
339 callExpr = exprStmt.expression;
340 }
341 else if (ts.isBinaryExpression(exprStmt.expression)) {
342 const expr = exprStmt.expression;
343 if (ts.isCallExpression(expr.right)) {
344 callExpr = expr.right;
345 }
346 else if (ts.isBinaryExpression(expr.right) && ts.isCallExpression(expr.right.right)) {
347 callExpr = expr.right.right;
348 }
349 }
350 if (!callExpr) {
351 return [];
352 }
353 const arrLiteral = expect(callExpr.arguments[0], ts.SyntaxKind.ArrayLiteralExpression);
354 if (!arrLiteral.elements.every((elem) => ts.isCallExpression(elem))) {
355 return [];
356 }
357 const elements = arrLiteral.elements;
358 const ngDecoratorCalls = elements.filter((el) => {
359 if (!ts.isIdentifier(el.expression)) {
360 return false;
361 }
362 return identifierIsMetadata(el.expression, ngMetadata, checker);
363 });
364 // Remove __metadata calls of type 'design:paramtypes'.
365 const metadataCalls = elements.filter((el) => {
366 if (!isTslibHelper(el, '__metadata', tslibImports, checker)) {
367 return false;
368 }
369 if (el.arguments.length < 2 || !ts.isStringLiteral(el.arguments[0])) {
370 return false;
371 }
372 return true;
373 });
374 // Remove all __param calls.
375 const paramCalls = elements.filter((el) => {
376 if (!isTslibHelper(el, '__param', tslibImports, checker)) {
377 return false;
378 }
379 if (el.arguments.length !== 2 || !ts.isNumericLiteral(el.arguments[0])) {
380 return false;
381 }
382 return true;
383 });
384 if (ngDecoratorCalls.length === 0) {
385 return [];
386 }
387 const callCount = ngDecoratorCalls.length + metadataCalls.length + paramCalls.length;
388 // If all decorators are metadata decorators then return the whole `Class = __decorate([...])'`
389 // statement so that it is removed in entirety.
390 // If not then only remove the Angular decorators.
391 // The metadata and param calls may be used by the non-Angular decorators.
392 return elements.length === callCount ? [exprStmt] : ngDecoratorCalls;
393}
394// Remove Angular decorators from`Clazz.propDecorators = [...];`, or expression itself if all
395// are removed.
396function pickPropDecorationNodesToRemove(exprStmt, ngMetadata, checker) {
397 const expr = expect(exprStmt.expression, ts.SyntaxKind.BinaryExpression);
398 const literal = expect(expr.right, ts.SyntaxKind.ObjectLiteralExpression);
399 if (!literal.properties.every((elem) => ts.isPropertyAssignment(elem) && ts.isArrayLiteralExpression(elem.initializer))) {
400 return [];
401 }
402 const assignments = literal.properties;
403 // Consider each assignment individually. Either the whole assignment will be removed or
404 // a particular decorator within will.
405 const toRemove = assignments
406 .map((assign) => {
407 const decorators = expect(assign.initializer, ts.SyntaxKind.ArrayLiteralExpression).elements;
408 if (!decorators.every((el) => ts.isObjectLiteralExpression(el))) {
409 return [];
410 }
411 const decsToRemove = decorators.filter((expression) => {
412 const lit = expect(expression, ts.SyntaxKind.ObjectLiteralExpression);
413 return isAngularDecorator(lit, ngMetadata, checker);
414 });
415 if (decsToRemove.length === decorators.length) {
416 return [assign];
417 }
418 return decsToRemove;
419 })
420 .reduce((accum, toRm) => accum.concat(toRm), []);
421 // If every node to be removed is a property assignment (full property's decorators) and
422 // all properties are accounted for, remove the whole assignment. Otherwise, remove the
423 // nodes which were marked as safe.
424 if (toRemove.length === assignments.length &&
425 toRemove.every((node) => ts.isPropertyAssignment(node))) {
426 return [exprStmt];
427 }
428 return toRemove;
429}
430function isAngularDecorator(literal, ngMetadata, checker) {
431 const types = literal.properties.filter(isTypeProperty);
432 if (types.length !== 1) {
433 return false;
434 }
435 const assign = expect(types[0], ts.SyntaxKind.PropertyAssignment);
436 if (!ts.isIdentifier(assign.initializer)) {
437 return false;
438 }
439 const id = assign.initializer;
440 const res = identifierIsMetadata(id, ngMetadata, checker);
441 return res;
442}
443function isTypeProperty(prop) {
444 if (!ts.isPropertyAssignment(prop)) {
445 return false;
446 }
447 if (!ts.isIdentifier(prop.name)) {
448 return false;
449 }
450 return prop.name.text === 'type';
451}
452// Check if an identifier is part of the known Angular Metadata.
453function identifierIsMetadata(id, metadata, checker) {
454 const symbol = checker.getSymbolAtLocation(id);
455 if (!symbol || !symbol.declarations || !symbol.declarations.length) {
456 return false;
457 }
458 return symbol.declarations.some((spec) => metadata.includes(spec));
459}
460// Find all named imports for `tslib`.
461function findTslibImports(node) {
462 const imports = [];
463 ts.forEachChild(node, (child) => {
464 var _a, _b;
465 if (ts.isImportDeclaration(child) &&
466 child.moduleSpecifier &&
467 ts.isStringLiteral(child.moduleSpecifier) &&
468 child.moduleSpecifier.text === 'tslib' &&
469 ((_a = child.importClause) === null || _a === void 0 ? void 0 : _a.namedBindings) &&
470 ts.isNamedImports((_b = child.importClause) === null || _b === void 0 ? void 0 : _b.namedBindings)) {
471 imports.push(child.importClause.namedBindings);
472 }
473 });
474 return imports;
475}
476// Check if a function call is a tslib helper.
477function isTslibHelper(callExpr, helper, tslibImports, checker) {
478 var _a;
479 if (!ts.isIdentifier(callExpr.expression) || callExpr.expression.text !== helper) {
480 return false;
481 }
482 const symbol = checker.getSymbolAtLocation(callExpr.expression);
483 if (!((_a = symbol === null || symbol === void 0 ? void 0 : symbol.declarations) === null || _a === void 0 ? void 0 : _a.length)) {
484 return false;
485 }
486 for (const dec of symbol.declarations) {
487 if (ts.isImportSpecifier(dec) && tslibImports.some((name) => name.elements.includes(dec))) {
488 return true;
489 }
490 // Handle inline helpers `var __decorate = (this...`
491 if (ts.isVariableDeclaration(dec)) {
492 return true;
493 }
494 }
495 return false;
496}