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  isCallExpression,
20  isCaseClause,
21  isDefaultClause,
22  isElementAccessExpression,
23  isExpressionStatement,
24  isIdentifier,
25  isModuleBlock,
26  isPropertyAccessExpression,
27  isSourceFile,
28  setParentRecursive,
29  visitEachChild
30} from 'typescript';
31
32import type {
33  Block,
34  CaseClause,
35  DefaultClause,
36  LeftHandSideExpression,
37  ModuleBlock,
38  Node,
39  NodeArray,
40  SourceFile,
41  Statement,
42  TransformationContext,
43  Transformer,
44  TransformerFactory
45} from 'typescript';
46
47import type {IOptions} from '../../configs/IOptions';
48import type {TransformPlugin} from '../TransformPlugin';
49import {TransformerOrder} from '../TransformPlugin';
50import { NodeUtils } from '../../utils/NodeUtils';
51import { performancePrinter } from '../../ArkObfuscator';
52import { EventList } from '../../utils/PrinterUtils';
53
54namespace secharmony {
55  export let transformerPlugin: TransformPlugin = {
56    'name': 'disableConsolePlugin',
57    'order': TransformerOrder.DISABLE_CONSOLE_TRANSFORMER,
58    'createTransformerFactory': createDisableConsoleFactory
59  };
60
61  export function createDisableConsoleFactory(option: IOptions): TransformerFactory<Node> {
62    if (!option.mDisableConsole) {
63      return null;
64    }
65
66    return disableConsoleFactory;
67
68    function disableConsoleFactory(context: TransformationContext): Transformer<Node> {
69      return transformer;
70
71      function transformer(node: Node): Node {
72        if (!isSourceFile(node) || NodeUtils.isDeclarationFile(node)) {
73          return node;
74        }
75
76        performancePrinter?.singleFilePrinter?.startEvent(EventList.REMOVE_CONSOLE, performancePrinter.timeSumPrinter);
77        let resultAst: Node = visitAst(node);
78        let parentNodes = setParentRecursive(resultAst, true);
79        performancePrinter?.singleFilePrinter?.endEvent(EventList.REMOVE_CONSOLE, performancePrinter.timeSumPrinter);
80        return parentNodes;
81      }
82
83      /**
84       * delete console log print expression, only support simple format like:
85       *  - console.xxx();
86       *  - console['xxx']();
87       * @param node
88       */
89      function visitAst(node: Node): Node {
90        const visitedAst = visitEachChild(node, visitAst, context);
91
92        if (!(isSourceFile(node) || isBlock(node) || isModuleBlock(node) || isCaseClause(node) || isDefaultClause(node))) {
93          return visitedAst;
94        }
95
96        //@ts-ignore
97        const deletedStatements: Statement[] = deleteConsoleStatement(visitedAst.statements);
98
99        if (isSourceFile(node)) {
100          return factory.updateSourceFile(node, deletedStatements);
101        }
102
103        if (isBlock(node)) {
104          return factory.createBlock(deletedStatements, true);
105        }
106
107        if (isModuleBlock(node)) {
108          return factory.createModuleBlock(deletedStatements)
109        }
110
111        if (isCaseClause(node)) {
112          return factory.createCaseClause(node.expression, deletedStatements);
113        }
114
115        if (isDefaultClause(node)) {
116          return factory.createDefaultClause(deletedStatements);
117        }
118      }
119
120      function deleteConsoleStatement(statements: NodeArray<Statement>): Statement[] {
121        const reservedStatements: Statement[] = [];
122        statements.forEach((child) => {
123          if (!isSimpleConsoleStatement(child)) {
124            reservedStatements.push(child);
125          }
126        });
127
128        return reservedStatements;
129      }
130
131      function isSimpleConsoleStatement(node: Statement): boolean {
132        if (!isExpressionStatement(node)) {
133          return false;
134        }
135
136        if (!node.expression || !isCallExpression(node.expression)) {
137          return false;
138        }
139
140        const expressionCalled: LeftHandSideExpression = node.expression.expression;
141        if (!expressionCalled) {
142          return false;
143        }
144
145        if (isPropertyAccessExpression(expressionCalled) && expressionCalled.expression) {
146          if (isIdentifier(expressionCalled.expression) && expressionCalled.expression.text === 'console') {
147            return true;
148          }
149        }
150
151        if (isElementAccessExpression(expressionCalled) && expressionCalled.expression) {
152          if (isIdentifier(expressionCalled.expression) && expressionCalled.expression.text === 'console') {
153            return true;
154          }
155        }
156
157        return false;
158      }
159    }
160  }
161}
162
163export = secharmony;
164