• 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  ScriptTarget,
18  SymbolFlags,
19  createCompilerHost,
20  createProgram,
21  createSourceFile
22} from 'typescript';
23
24import type {
25  CompilerHost,
26  CompilerOptions,
27  Program,
28  SourceFile,
29  Symbol,
30  TypeChecker
31} from 'typescript';
32import { Extension, PathAndExtension } from '../common/type';
33import { FileUtils } from './FileUtils';
34import { EventList, performancePrinter } from '../ArkObfuscator';
35import { endSingleFileEvent, startSingleFileEvent } from './PrinterUtils';
36import { exportSymbolAliasMap } from './ScopeAnalyzer';
37
38export class TypeUtils {
39  /**
40   * Create .d.ets, .d.ts, .ts ast from .d.ets, .d.ts, .ts content.
41   * Create .ts ast from .ets, .js content
42   * @param {string} sourceFilePath
43   * @param {string} content - The content in sourceFilePath
44   */
45  public static createObfSourceFile(sourceFilePath: string, content: string, compilerOptions?: CompilerOptions): SourceFile {
46    const pathOrExtension: PathAndExtension = FileUtils.getFileSuffix(sourceFilePath);
47    const fileSuffix = pathOrExtension.ext;
48
49    if (fileSuffix === Extension.JS) {
50      sourceFilePath = pathOrExtension.path + Extension.TS;
51    }
52
53    if (compilerOptions && compilerOptions.etsAnnotationsEnable === true) {
54      return createSourceFile(sourceFilePath, content, ScriptTarget.ES2015, true, undefined, compilerOptions, true);
55    }
56
57    return createSourceFile(sourceFilePath, content, ScriptTarget.ES2015, true);
58  }
59
60  public static tsToJs(ast: SourceFile): void {
61    const pathOrExtension: PathAndExtension = FileUtils.getFileSuffix(ast.fileName);
62    const fileSuffix = Extension.JS;
63    const targetName: string = pathOrExtension.path + fileSuffix;
64    ast.fileName = targetName;
65  }
66
67  public static createChecker(ast: SourceFile): TypeChecker {
68    const host: CompilerHost = createCompilerHost({});
69
70    const customHost: CompilerHost = {
71      getSourceFile(name, languageVersion): SourceFile | undefined {
72        if (name === ast.fileName) {
73          return ast;
74        } else {
75          return undefined;
76        }
77      },
78      // optional
79      getDefaultLibLocation: () => '',
80      getDefaultLibFileName: () => '',
81      writeFile: (filename, data) => {
82      },
83      getCurrentDirectory: () => '',
84      useCaseSensitiveFileNames: host.useCaseSensitiveFileNames,
85      getCanonicalFileName: host.getCanonicalFileName,
86      getNewLine: host.getNewLine,
87      fileExists: () => true,
88      readFile: (name): string => {
89        return name === ast.fileName ? ast.text : undefined;
90      },
91      // must, read program.ts => createCompilerHost
92      directoryExists: undefined,
93      getEnvironmentVariable: undefined,
94      getDirectories: undefined,
95    };
96
97    let option: CompilerOptions = {
98      'alwaysStrict': true
99    };
100    if (ast.fileName.endsWith('.js')) {
101      option.allowJs = true;
102    }
103
104    startSingleFileEvent(EventList.CREATE_PROGRAM, performancePrinter.timeSumPrinter);
105    let program: Program = createProgram([ast.fileName], option, customHost);
106    endSingleFileEvent(EventList.CREATE_PROGRAM, performancePrinter.timeSumPrinter);
107
108    startSingleFileEvent(EventList.GET_CHECKER, performancePrinter.timeSumPrinter);
109    let typeChecker: TypeChecker = program.getTypeChecker();
110    endSingleFileEvent(EventList.GET_CHECKER, performancePrinter.timeSumPrinter);
111    return typeChecker;
112  }
113
114  /**
115   * Retrieves the symbol associated with the declaration site of the given symbol.
116   *
117   * This method resolves the symbol to its declaration site to ensure consistency
118   * in obfuscated naming. Obfuscation names are bound to the symbol at the declaration
119   * site. If the symbol at the declaration site differs from the symbol at the usage
120   * site, discrepancies in obfuscated names may occur. By using this method, the
121   * obfuscation name can be consistently retrieved from the declaration site symbol,
122   * ensuring uniformity between the declaration and usage sites.
123   */
124  public static getOriginalSymbol(symbol: Symbol, checker: TypeChecker): Symbol {
125    if (!(symbol.getFlags() & SymbolFlags.Alias)) {
126      return symbol;
127    }
128
129    if (exportSymbolAliasMap.has(symbol)) {
130      return exportSymbolAliasMap.get(symbol);
131    }
132
133    // This method helps determine if the original symbol obtained for a given symbol is valid and usable.
134    // Sometimes the original symbol's name may differ from the current symbol's name due to aliasing.
135    // For example:
136    // `let A = 1; export { A as B };`
137    // The `originalSymbol` for `B` refers to the symbol for `A`, but the names are different.
138    // However, for obfuscation purposes, we want to ensure that the symbol used in the declaration (i.e., `A` in this case)
139    // aligns with the current symbol (i.e., `B`), because this allows us to apply the declaration's obfuscation name to `B`.
140    // If the names don't match, we should avoid applying the declaration's obfuscation name to the current symbol.
141    const isValidOriginalSymbol = (originalSymbol: Symbol | undefined): boolean =>
142      originalSymbol !== undefined && originalSymbol.name === symbol.name;
143
144    const originalSymbol: Symbol | undefined = checker.getAliasedSymbol(symbol);
145    if (isValidOriginalSymbol(originalSymbol)) {
146      return originalSymbol!;
147    }
148
149    const immediateSymbol: Symbol | undefined = checker.getImmediateAliasedSymbol(symbol);
150    if (isValidOriginalSymbol(immediateSymbol)) {
151      return immediateSymbol!;
152    }
153
154    return symbol;
155  }
156}
157