projecte_ionic/node_modules/@angular-devkit/build-angular/src/babel/plugins/adjust-static-class-members.js
2022-02-09 18:30:03 +01:00

272 lines
12 KiB
JavaScript
Executable file

"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getKeywords = void 0;
const core_1 = require("@babel/core");
const helper_annotate_as_pure_1 = __importDefault(require("@babel/helper-annotate-as-pure"));
/**
* The name of the Typescript decorator helper function created by the TypeScript compiler.
*/
const TSLIB_DECORATE_HELPER_NAME = '__decorate';
/**
* The set of Angular static fields that should always be wrapped.
* These fields may appear to have side effects but are safe to remove if the associated class
* is otherwise unused within the output.
*/
const angularStaticsToWrap = new Set([
'ɵcmp',
'ɵdir',
'ɵfac',
'ɵinj',
'ɵmod',
'ɵpipe',
'ɵprov',
'INJECTOR_KEY',
]);
/**
* An object map of static fields and related value checks for discovery of Angular generated
* JIT related static fields.
*/
const angularStaticsToElide = {
'ctorParameters'(path) {
return path.isFunctionExpression() || path.isArrowFunctionExpression();
},
'decorators'(path) {
return path.isArrayExpression();
},
'propDecorators'(path) {
return path.isObjectExpression();
},
};
/**
* Provides one or more keywords that if found within the content of a source file indicate
* that this plugin should be used with a source file.
*
* @returns An a string iterable containing one or more keywords.
*/
function getKeywords() {
return ['class'];
}
exports.getKeywords = getKeywords;
/**
* Determines whether a property and its initializer value can be safely wrapped in a pure
* annotated IIFE. Values that may cause side effects are not considered safe to wrap.
* Wrapping such values may cause runtime errors and/or incorrect runtime behavior.
*
* @param propertyName The name of the property to analyze.
* @param assignmentValue The initializer value that will be assigned to the property.
* @returns If the property can be safely wrapped, then true; otherwise, false.
*/
function canWrapProperty(propertyName, assignmentValue) {
if (angularStaticsToWrap.has(propertyName)) {
return true;
}
const { leadingComments } = assignmentValue.node;
if (leadingComments === null || leadingComments === void 0 ? void 0 : leadingComments.some(
// `@pureOrBreakMyCode` is used by closure and is present in Angular code
({ value }) => value.includes('#__PURE__') || value.includes('@pureOrBreakMyCode'))) {
return true;
}
return assignmentValue.isPure();
}
/**
* Analyze the sibling nodes of a class to determine if any downlevel elements should be
* wrapped in a pure annotated IIFE. Also determines if any elements have potential side
* effects.
*
* @param origin The starting NodePath location for analyzing siblings.
* @param classIdentifier The identifier node that represents the name of the class.
* @param allowWrappingDecorators Whether to allow decorators to be wrapped.
* @returns An object containing the results of the analysis.
*/
function analyzeClassSiblings(origin, classIdentifier, allowWrappingDecorators) {
var _a;
const wrapStatementPaths = [];
let hasPotentialSideEffects = false;
for (let i = 1;; ++i) {
const nextStatement = origin.getSibling(+origin.key + i);
if (!nextStatement.isExpressionStatement()) {
break;
}
// Valid sibling statements for class declarations are only assignment expressions
// and TypeScript decorator helper call expressions
const nextExpression = nextStatement.get('expression');
if (nextExpression.isCallExpression()) {
if (!core_1.types.isIdentifier(nextExpression.node.callee) ||
nextExpression.node.callee.name !== TSLIB_DECORATE_HELPER_NAME) {
break;
}
if (allowWrappingDecorators) {
wrapStatementPaths.push(nextStatement);
}
else {
// Statement cannot be safely wrapped which makes wrapping the class unneeded.
// The statement will prevent even a wrapped class from being optimized away.
hasPotentialSideEffects = true;
}
continue;
}
else if (!nextExpression.isAssignmentExpression()) {
break;
}
// Valid assignment expressions should be member access expressions using the class
// name as the object and an identifier as the property for static fields or only
// the class name for decorators.
const left = nextExpression.get('left');
if (left.isIdentifier()) {
if (!left.scope.bindingIdentifierEquals(left.node.name, classIdentifier) ||
!core_1.types.isCallExpression(nextExpression.node.right) ||
!core_1.types.isIdentifier(nextExpression.node.right.callee) ||
nextExpression.node.right.callee.name !== TSLIB_DECORATE_HELPER_NAME) {
break;
}
if (allowWrappingDecorators) {
wrapStatementPaths.push(nextStatement);
}
else {
// Statement cannot be safely wrapped which makes wrapping the class unneeded.
// The statement will prevent even a wrapped class from being optimized away.
hasPotentialSideEffects = true;
}
continue;
}
else if (!left.isMemberExpression() ||
!core_1.types.isIdentifier(left.node.object) ||
!left.scope.bindingIdentifierEquals(left.node.object.name, classIdentifier) ||
!core_1.types.isIdentifier(left.node.property)) {
break;
}
const propertyName = left.node.property.name;
const assignmentValue = nextExpression.get('right');
if ((_a = angularStaticsToElide[propertyName]) === null || _a === void 0 ? void 0 : _a.call(angularStaticsToElide, assignmentValue)) {
nextStatement.remove();
--i;
}
else if (canWrapProperty(propertyName, assignmentValue)) {
wrapStatementPaths.push(nextStatement);
}
else {
// Statement cannot be safely wrapped which makes wrapping the class unneeded.
// The statement will prevent even a wrapped class from being optimized away.
hasPotentialSideEffects = true;
}
}
return { hasPotentialSideEffects, wrapStatementPaths };
}
/**
* The set of classed already visited and analyzed during the plugin's execution.
* This is used to prevent adjusted classes from being repeatedly analyzed which can lead
* to an infinite loop.
*/
const visitedClasses = new WeakSet();
/**
* A babel plugin factory function for adjusting classes; primarily with Angular metadata.
* The adjustments include wrapping classes with known safe or no side effects with pure
* annotations to support dead code removal of unused classes. Angular compiler generated
* metadata static fields not required in AOT mode are also elided to better support bundler-
* level treeshaking.
*
* @returns A babel plugin object instance.
*/
function default_1() {
return {
visitor: {
ClassDeclaration(path, state) {
const { node: classNode, parentPath } = path;
const { wrapDecorators } = state.opts;
if (visitedClasses.has(classNode)) {
return;
}
// Analyze sibling statements for elements of the class that were downleveled
const hasExport = parentPath.isExportNamedDeclaration() || parentPath.isExportDefaultDeclaration();
const origin = hasExport ? parentPath : path;
const { wrapStatementPaths, hasPotentialSideEffects } = analyzeClassSiblings(origin, classNode.id, wrapDecorators);
visitedClasses.add(classNode);
if (hasPotentialSideEffects || wrapStatementPaths.length === 0) {
return;
}
const wrapStatementNodes = [];
for (const statementPath of wrapStatementPaths) {
wrapStatementNodes.push(statementPath.node);
statementPath.remove();
}
// Wrap class and safe static assignments in a pure annotated IIFE
const container = core_1.types.arrowFunctionExpression([], core_1.types.blockStatement([
classNode,
...wrapStatementNodes,
core_1.types.returnStatement(core_1.types.cloneNode(classNode.id)),
]));
const replacementInitializer = core_1.types.callExpression(core_1.types.parenthesizedExpression(container), []);
helper_annotate_as_pure_1.default(replacementInitializer);
// Replace class with IIFE wrapped class
const declaration = core_1.types.variableDeclaration('let', [
core_1.types.variableDeclarator(core_1.types.cloneNode(classNode.id), replacementInitializer),
]);
if (parentPath.isExportDefaultDeclaration()) {
// When converted to a variable declaration, the default export must be moved
// to a subsequent statement to prevent a JavaScript syntax error.
parentPath.replaceWithMultiple([
declaration,
core_1.types.exportNamedDeclaration(undefined, [
core_1.types.exportSpecifier(core_1.types.cloneNode(classNode.id), core_1.types.identifier('default')),
]),
]);
}
else {
path.replaceWith(declaration);
}
},
ClassExpression(path, state) {
const { node: classNode, parentPath } = path;
const { wrapDecorators } = state.opts;
// Class expressions are used by TypeScript to represent downlevel class/constructor decorators.
// If not wrapping decorators, they do not need to be processed.
if (!wrapDecorators || visitedClasses.has(classNode)) {
return;
}
if (!classNode.id ||
!parentPath.isVariableDeclarator() ||
!core_1.types.isIdentifier(parentPath.node.id) ||
parentPath.node.id.name !== classNode.id.name) {
return;
}
const origin = parentPath.parentPath;
if (!origin.isVariableDeclaration() || origin.node.declarations.length !== 1) {
return;
}
const { wrapStatementPaths, hasPotentialSideEffects } = analyzeClassSiblings(origin, parentPath.node.id, wrapDecorators);
visitedClasses.add(classNode);
if (hasPotentialSideEffects || wrapStatementPaths.length === 0) {
return;
}
const wrapStatementNodes = [];
for (const statementPath of wrapStatementPaths) {
wrapStatementNodes.push(statementPath.node);
statementPath.remove();
}
// Wrap class and safe static assignments in a pure annotated IIFE
const container = core_1.types.arrowFunctionExpression([], core_1.types.blockStatement([
core_1.types.variableDeclaration('let', [
core_1.types.variableDeclarator(core_1.types.cloneNode(classNode.id), classNode),
]),
...wrapStatementNodes,
core_1.types.returnStatement(core_1.types.cloneNode(classNode.id)),
]));
const replacementInitializer = core_1.types.callExpression(core_1.types.parenthesizedExpression(container), []);
helper_annotate_as_pure_1.default(replacementInitializer);
// Add the wrapped class directly to the variable declaration
parentPath.get('init').replaceWith(replacementInitializer);
},
},
};
}
exports.default = default_1;