1/* 2 * Copyright (c) 2023 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 { 17 factory, 18 isBlock, 19 isExpressionStatement, 20 isReturnStatement, 21 isSourceFile, 22 isVariableStatement, 23 setParentRecursive, 24 SyntaxKind, 25 visitEachChild, 26 isStringLiteral, 27} from 'typescript'; 28 29import type { 30 ExpressionStatement, 31 Node, 32 NodeFlags, 33 Statement, 34 TransformationContext, 35 Transformer, 36 TransformerFactory, 37 VariableDeclaration, 38 VariableStatement, 39 ModifiersArray, 40 SourceFile, 41 Block, 42 Expression 43} from 'typescript'; 44 45import type {IOptions} from '../../configs/IOptions'; 46import type {TransformPlugin} from '../TransformPlugin'; 47import {isCommentedNode, isSuperCallStatement} from '../../utils/TransformUtil'; 48 49namespace secharmony { 50 const TRANSFORMER_ORDER: number = 5; 51 export let transformerPlugin: TransformPlugin = { 52 'name': 'simplifyPlugin', 53 'order': (1 << TRANSFORMER_ORDER), 54 'createTransformerFactory': createSimplifyFactory 55 }; 56 57 export function createSimplifyFactory(option: IOptions): TransformerFactory<Node> { 58 if (!option.mSimplify) { 59 return null; 60 } 61 62 return simplifyFactory; 63 64 function simplifyFactory(context: TransformationContext): Transformer<Node> { 65 const MIN_STATEMENTS_LEN = 2; 66 let sourceFile: SourceFile; 67 return transformer; 68 69 function transformer(node: Node): Node { 70 if (!isSourceFile(node) || node.fileName.endsWith('.d.ts')) { 71 return node; 72 } 73 74 sourceFile = node; 75 return setParentRecursive(visitStatements(node), true); 76 } 77 78 function visitStatements(node: Node): Node { 79 if (!isSourceFile(node) && !isBlock(node)) { 80 return visitEachChild(node, visitStatements, context); 81 } 82 83 const simplified: SourceFile | Block = visitEachChild(node, visitStatements, context); 84 if (node.statements.length < MIN_STATEMENTS_LEN) { 85 return node; 86 } 87 88 return simplifyStatements(simplified); 89 } 90 91 /** 92 * use variable statement merge and expression merge to simplify code 93 * @param node 94 */ 95 function simplifyStatements(node: Node): Node { 96 if (!isSourceFile(node) && !isBlock(node)) { 97 return node; 98 } 99 100 let simplifiedStatements: Statement[] = []; 101 const continuousStatements: Statement[] = []; 102 let nodeFlag: NodeFlags = undefined; 103 let modifiers: ModifiersArray = undefined; 104 let preType: SyntaxKind | undefined = undefined; 105 106 function mergeArray(): void { 107 if (continuousStatements.length < MIN_STATEMENTS_LEN) { 108 simplifiedStatements = [...simplifiedStatements, ...continuousStatements]; 109 return; 110 } 111 112 if (preType === SyntaxKind.VariableStatement) { 113 simplifiedStatements.push(mergeVariableStatements(continuousStatements as VariableStatement[])); 114 return; 115 } 116 117 if (preType === SyntaxKind.ExpressionStatement) { 118 simplifiedStatements.push(mergeExpression(continuousStatements as ExpressionStatement[])); 119 } 120 } 121 122 function doMerge(currentType: SyntaxKind | undefined, child: Statement | undefined): void { 123 if (preType === currentType) { 124 if (preType !== SyntaxKind.VariableStatement) { 125 return; 126 } 127 128 if (nodeFlag === (child as VariableStatement).declarationList.flags) { 129 if (!modifiers && !child.modifiers) { 130 return; 131 } 132 133 let isSame: boolean = true; 134 if (modifiers && child.modifiers && modifiers.length === child.modifiers.length) { 135 modifiers.forEach((modifier, index) => { 136 if (modifier.kind !== child.modifiers[index].kind) { 137 isSame = false; 138 } 139 }); 140 } else { 141 isSame = false; 142 } 143 144 if (isSame) { 145 return; 146 } 147 } 148 149 mergeArray(); 150 nodeFlag = (child as VariableStatement).declarationList.flags; 151 modifiers = child.modifiers; 152 continuousStatements.length = 0; 153 return; 154 } 155 156 if (preType === SyntaxKind.VariableStatement) { 157 nodeFlag = undefined; 158 modifiers = undefined; 159 mergeArray(); 160 } else if (preType === SyntaxKind.ExpressionStatement) { 161 mergeArray(); 162 } 163 164 continuousStatements.length = 0; 165 preType = currentType; 166 } 167 168 node.statements.forEach((child) => { 169 if (isCommentedNode(child, sourceFile) || 170 (isExpressionStatement(child) && isStringLiteral(child.expression)) || 171 isSuperCallStatement(child) 172 ) { 173 doMerge(undefined, undefined); 174 simplifiedStatements.push(child); 175 return; 176 } 177 178 if (isVariableStatement(child)) { 179 doMerge(SyntaxKind.VariableStatement, child); 180 continuousStatements.push(child); 181 nodeFlag = child.declarationList.flags; 182 modifiers = child.modifiers; 183 return; 184 } 185 186 if (isExpressionStatement(child)) { 187 doMerge(SyntaxKind.ExpressionStatement, child); 188 continuousStatements.push(child); 189 return; 190 } 191 192 if (isReturnStatement(child) && child.expression !== undefined) { 193 doMerge(SyntaxKind.ExpressionStatement, child); 194 continuousStatements.push(child); 195 doMerge(undefined, undefined); 196 return; 197 } 198 199 // do merge on continuous stopped 200 doMerge(undefined, child); 201 simplifiedStatements.push(child); 202 }); 203 204 doMerge(undefined, undefined); 205 206 if (isSourceFile(node)) { 207 return factory.updateSourceFile(node, simplifiedStatements); 208 } 209 210 return factory.createBlock(simplifiedStatements, true); 211 } 212 213 /** 214 * merge variable statement, need same type and same modifier variable and continuous 215 * @param variableStatements 216 */ 217 function mergeVariableStatements(variableStatements: VariableStatement[]): VariableStatement { 218 let variableDeclarations: VariableDeclaration[] = []; 219 variableStatements.forEach((statement) => { 220 variableDeclarations = [...variableDeclarations, ...statement.declarationList.declarations]; 221 }); 222 223 return factory.createVariableStatement( 224 variableStatements[0].modifiers, 225 factory.createVariableDeclarationList( 226 variableDeclarations, 227 variableStatements[0].declarationList.flags 228 ) 229 ); 230 } 231 232 /** 233 * merge expression, include: 234 * - continuous expression like a=a+1; b=b+1; 235 */ 236 function mergeExpression(expressionStatements: ExpressionStatement[]): Statement { 237 let pos: number = 1; 238 let expression: Expression = expressionStatements[0].expression; 239 const statementsLength: number = expressionStatements.length; 240 while (pos < statementsLength) { 241 expression = factory.createBinaryExpression( 242 expression, 243 SyntaxKind.CommaToken, 244 expressionStatements[pos].expression 245 ); 246 pos += 1; 247 } 248 249 if (isReturnStatement(expressionStatements[statementsLength - 1])) { 250 return factory.createReturnStatement(expression); 251 } 252 253 return factory.createExpressionStatement(expression); 254 } 255 } 256 } 257} 258 259export = secharmony; 260