UNPKG

@ui5/linter

Version:

A static code analysis tool for UI5

269 lines 11.6 kB
import ts from "typescript"; import { getLogger } from "@ui5/logger"; import { toPosStr, UnsupportedModuleError } from "./util.js"; const { SyntaxKind } = ts; const log = getLogger("linter:ui5Types:amdTranspiler:pruneNode"); export class UnsafeNodeRemoval extends Error { constructor(message) { super(message); this.name = this.constructor.name; } } /** * Marks a given node for removal. The node will be removed during a later step in the the transformation process. * * If the node's parent would become completely empty after removing the node, the parent is also marked for removal. * If the node's parent can't exist without the to-be-removed node, the node won't be removed. * * This is checked recursively for all parents */ export default function (node) { let nodeToRemove = node; try { while (nodeToRemove) { // Attempt to prune the node, if the parent can exist without it if (pruneNode(nodeToRemove)) { nodeToRemove = nodeToRemove.parent; } else { nodeToRemove = undefined; } } } catch (err) { if ((err instanceof UnsafeNodeRemoval || err instanceof UnsupportedModuleError) && node !== nodeToRemove) { // Ignore UnsafeNodeRemoval and UnsupportedModuleError exceptions produced by parent nodes return; } throw err; } } /** * Attempt to prune a node, if the parent can exist without it (=> UnsafeNodeRemoval exception) * * Returns true if parent can be removed. This way the caller can continue to check the parent's parent, knowing * that the parent is already safe to be removed. Therefore no check is required at the beginning of this function. */ function pruneNode(node) { log.verbose(`Pruning node ${SyntaxKind[node.kind]}`); node._remove = true; try { const parent = node.parent; if (isNodeRemovable(parent)) { // Parent can be removed log.verbose(`Parent node ${SyntaxKind[parent.kind]} can be removed`); return true; } } catch (err) { if (err instanceof UnsafeNodeRemoval || err instanceof UnsupportedModuleError) { // If an UnsafeNodeRemoval or UnsupporedModuleError is detected, revert the removal of the node as // this would indicate that the parent can't exist without it node._remove = false; } // Re-throw error in any case throw err; } return false; } /** * Check whether a given node can be marked for removal by checking whether * relevant child nodes have already been marked for removal. * * If removing the given node is not possible, but it is also not possible to keep it with the given * child nodes marked for removal, an error is thrown. * * Note that some nodes can't exist without certain child nodes. Leaving those * nodes after removing their children will cause errors during later AST processing */ function isNodeRemovable(node) { switch (node.kind) { case SyntaxKind.SourceFile: // Never remove the source file return false; // Nodes with only one child case SyntaxKind.Identifier: case SyntaxKind.ExpressionStatement: case SyntaxKind.VariableDeclaration: case SyntaxKind.VariableStatement: case SyntaxKind.ParenthesizedExpression: case SyntaxKind.ReturnStatement: case SyntaxKind.AwaitExpression: case SyntaxKind.ComputedPropertyName: case SyntaxKind.Parameter: case SyntaxKind.PrefixUnaryExpression: case SyntaxKind.PostfixUnaryExpression: // These nodes can only have one child // So if the child is removed, the parent must be removed as well return true; // Nodes with multiple children case SyntaxKind.FunctionExpression: return isRemovableFunctionExpression(node); case SyntaxKind.MethodDeclaration: return isRemovableMethodDeclaration(node); case SyntaxKind.NewExpression: return isRemovableNewExpression(node); case SyntaxKind.ArrowFunction: return isRemovableArrowFunction(node); case SyntaxKind.IfStatement: return isRemovableIfStatement(node); case SyntaxKind.BinaryExpression: return isRemovableBinaryExpression(node); case SyntaxKind.ConditionalExpression: return isRemovableConditionalExpression(node); case SyntaxKind.ForStatement: return isRemovableForStatement(node); case SyntaxKind.WhileStatement: return isRemovableWhileStatement(node); case SyntaxKind.DoStatement: return isRemovableDoStatement(node); case SyntaxKind.PropertyAccessExpression: return isRemovablePropertyAccessExpression(node); case SyntaxKind.ElementAccessExpression: return isRemovableElementAccessExpression(node); case SyntaxKind.FunctionDeclaration: return isRemovableFunctionDeclaration(node); case SyntaxKind.PropertyAssignment: return isRemovablePropertyAssignment(node); case SyntaxKind.PropertyDeclaration: return isRemovablePropertyDeclaration(node); case SyntaxKind.YieldExpression: return isRemovableYieldExpression(node); case SyntaxKind.ArrayLiteralExpression: return isRemovableArrayLiteralExpression(node); case SyntaxKind.VariableDeclarationList: return isRemovableVariableDeclarationList(node); case SyntaxKind.Block: return isRemovableBlock(node); case SyntaxKind.ObjectLiteralExpression: return isRemovableObjectLiteralExpression(node); case SyntaxKind.CallExpression: return isRemovableCallExpression(node); case SyntaxKind.ClassDeclaration: return isRemovableClassDeclaration(node); default: throw new UnsupportedModuleError(`Unsupported parent node type for prune operation: ${SyntaxKind[node.kind]} at ` + `${toPosStr(node)}`); } } function isNotMarkedForRemoval(node) { return !node._remove; } function hasAllNodesMarkedForRemoval(nodes) { return !nodes.some(isNotMarkedForRemoval); } function isRemovableFunctionExpression(node) { // If the body is marked for removal, remove the whole function // Even if parameters are not marked for removal return !!node.body?._remove; } function isRemovableMethodDeclaration(node) { // If the body is marked for removal, remove the whole function // Even if parameters are not marked for removal return !!node.body?._remove; } function isRemovableNewExpression(node) { return !!node.expression._remove && (!node.arguments || hasAllNodesMarkedForRemoval(node.arguments)); } function isRemovableArrowFunction(node) { return !!node.body._remove; } function isRemovableIfStatement(node) { if (node.thenStatement._remove && (!node.elseStatement || node.elseStatement?._remove)) { return true; } throw new UnsafeNodeRemoval(`Cannot remove IfStatement with only one part marked for removal at ${toPosStr(node)}`); } function isRemovableBinaryExpression(node) { // Both sides of the binary expression are marked for removal => Remove the whole expression if (node.left._remove && node.right._remove) { return true; } throw new UnsafeNodeRemoval(`Cannot remove BinaryExpression with only one side marked for removal at ${toPosStr(node)}`); } function isRemovableConditionalExpression(node) { if (node.condition._remove && node.whenTrue._remove && node.whenFalse._remove) { // All parts of the conditional expression are marked for removal => Remove the whole expression return true; } throw new UnsafeNodeRemoval(`Cannot remove ConditionalExpression with only one part marked for removal at ${toPosStr(node)}`); } function isRemovableForStatement(node) { if (node.statement._remove && (!node.initializer || node.initializer._remove) && (!node.condition || node.condition._remove) && (!node.incrementor || node.incrementor._remove)) { return true; } throw new UnsafeNodeRemoval(`Cannot remove ForStatement with only one part marked for removal at ${toPosStr(node)}`); } function isRemovableWhileStatement(node) { if (node.statement._remove && node.expression._remove) { return true; } throw new UnsafeNodeRemoval(`Cannot remove WhileStatment with only one part marked for removal at ${toPosStr(node)}`); } function isRemovableDoStatement(node) { if (node.statement._remove && node.expression._remove) { return true; } throw new UnsafeNodeRemoval(`Cannot remove DoStatement with only one part marked for removal at ${toPosStr(node)}`); } function isRemovablePropertyAccessExpression(node) { if (node.name._remove && node.expression._remove) { return true; } throw new UnsafeNodeRemoval(`Cannot remove PropertyAccessExpression with only one part marked for removal at ${toPosStr(node)}`); } function isRemovableElementAccessExpression(node) { if (node.argumentExpression._remove && node.expression._remove) { return true; } throw new UnsafeNodeRemoval(`Cannot remove ElementAccessExpression with only one part marked for removal at ${toPosStr(node)}`); } function isRemovableFunctionDeclaration(node) { // If the body is marked for removal, remove the whole function return !!node.body?._remove; } function isRemovablePropertyAssignment(node) { // If the name is marked for removal, remove the whole property assignment if (node.name?._remove) { return true; } throw new UnsafeNodeRemoval(`Cannot remove PropertyAssignment with only the initializer marked for removal at ${toPosStr(node)}`); } function isRemovablePropertyDeclaration(node) { // If the name is marked for removal, remove the whole property declaration if (node.name?._remove) { return true; } throw new UnsafeNodeRemoval(`Cannot remove PropertyDeclaration with only the initializer marked for removal at ${toPosStr(node)}`); } function isRemovableYieldExpression(node) { // If the expression is marked for removal, remove the whole yield expression return !!node.expression?._remove; } function isRemovableArrayLiteralExpression(node) { // All elements are marked for removal => Remove the whole array literal return hasAllNodesMarkedForRemoval(node.elements); } function isRemovableVariableDeclarationList(node) { // All declarations are marked for removal => Remove the whole list return hasAllNodesMarkedForRemoval(node.declarations); } function isRemovableBlock(node) { // All statements in the block are marked for removal => Remove the whole block return hasAllNodesMarkedForRemoval(node.statements); } function isRemovableObjectLiteralExpression(node) { // All properties are marked for removal => Remove the whole object literal return hasAllNodesMarkedForRemoval(node.properties); } function isRemovableCallExpression(node) { // All arguments are marked for removal => Remove the whole call expression return !!node.expression._remove && hasAllNodesMarkedForRemoval(node.arguments); } function isRemovableClassDeclaration(node) { // If the body is marked for removal, remove the whole class return hasAllNodesMarkedForRemoval(node.members); } //# sourceMappingURL=pruneNode.js.map