• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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