• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2022-2025 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 * as arkts from '@koalaui/libarkts';
17import { AbstractVisitor, VisitorOptions } from '../common/abstract-visitor';
18import { CustomComponentNames } from './utils';
19import { factory } from './ui-factory';
20import {
21    ARKUI_COMPONENT_IMPORT_NAME,
22    IMPORT_SOURCE_MAP,
23    OUTPUT_DEPENDENCY_MAP,
24    ARKUI_STATEMANAGEMENT_IMPORT_NAME,
25    KIT_ARKUI_NAME,
26} from '../common/predefines';
27import { NameCollector } from './name-collector';
28
29interface MemoImportCollection {
30    memo: boolean;
31    memoContextType: boolean;
32    memoIdType: boolean;
33}
34
35export class PreprocessorTransformer extends AbstractVisitor {
36    private outNameArr: string[] = [];
37    private memoNameArr: string[] = [];
38    private structInterfaceImport: arkts.ETSImportDeclaration[] = [];
39    private memoImportCollection: Partial<MemoImportCollection> = {};
40    private localComponentNames: string[] = [];
41    private isMemoImportOnce: boolean = false;
42
43    private readonly nameCollector: NameCollector;
44
45    constructor(options?: VisitorOptions) {
46        super(options);
47        this.nameCollector = NameCollector.getInstance();
48    }
49
50    reset(): void {
51        super.reset();
52        this.outNameArr = [];
53        this.memoNameArr = [];
54        this.structInterfaceImport = [];
55        this.memoImportCollection = {};
56        this.localComponentNames = [];
57        this.isMemoImportOnce = false;
58        IMPORT_SOURCE_MAP.clear();
59        IMPORT_SOURCE_MAP.set('arkui.stateManagement.runtime', new Set(['memo', '__memo_context_type', '__memo_id_type']));
60    }
61
62    isCustomConponentDecl(node: arkts.CallExpression): boolean {
63        const structCollection: Set<string> = arkts.GlobalInfo.getInfoInstance().getStructCollection();
64        const nodeName: string = node.expression.dumpSrc();
65        if (structCollection.has(nodeName)) {
66            return true;
67        }
68        return false;
69    }
70
71    isComponentFunctionCall(node: arkts.CallExpression): boolean {
72        if (!node || !arkts.isIdentifier(node.expression)) return false;
73        return this.localComponentNames.includes(node.expression.name);
74    }
75
76    transformComponentCall(node: arkts.CallExpression): arkts.TSAsExpression | arkts.CallExpression {
77        if (node.arguments.length === 0 && node.trailingBlock) {
78            return arkts.factory.updateCallExpression(node, node.expression, node.typeArguments, [
79                arkts.factory.createUndefinedLiteral(),
80            ]);
81        } else if (arkts.isObjectExpression(node.arguments[0])) {
82            const componentName: string = `${
83                CustomComponentNames.COMPONENT_INTERFACE_PREFIX
84            }${node.expression.dumpSrc()}`;
85            const newArg = arkts.factory.createTSAsExpression(
86                node.arguments[0].clone(),
87                arkts.factory.createTypeReference(
88                    arkts.factory.createTypeReferencePart(arkts.factory.createIdentifier(componentName))
89                ),
90                true
91            );
92            return arkts.factory.updateCallExpression(node, node.expression, node.typeArguments, [
93                newArg,
94                ...node.arguments.slice(1),
95            ]);
96        } else {
97            return node;
98        }
99    }
100
101    transformComponentFunctionCall(node: arkts.CallExpression) {
102        if (!node || !arkts.isIdentifier(node.expression)) return node;
103
104        const componentInfo = this.nameCollector.getComponentInfo(node.expression.name);
105        if (!componentInfo) return node;
106        if (componentInfo.argsNum === 0) return node;
107        if (node.arguments.length >= componentInfo.argsNum - 1) return node;
108
109        const defaultArgs: arkts.UndefinedLiteral[] = [];
110        let count = 0;
111        while (count < componentInfo.argsNum - node.arguments.length - 1) {
112            defaultArgs.push(arkts.factory.createUndefinedLiteral());
113            count++;
114        }
115        return arkts.factory.updateCallExpression(node, node.expression, node.typeArguments, [
116            ...node.arguments,
117            ...defaultArgs,
118        ]);
119    }
120
121    addDependencesImport(node: arkts.ETSImportDeclaration): void {
122        if (!node.source) return;
123
124        const isFromCompImport: boolean = node.source.str === ARKUI_COMPONENT_IMPORT_NAME || node.source.str === KIT_ARKUI_NAME;
125        const structCollection: Set<string> = arkts.GlobalInfo.getInfoInstance().getStructCollection();
126        node.specifiers.forEach((item: arkts.AstNode) => {
127            if (!arkts.isImportSpecifier(item) || !item.imported?.name) return;
128
129            const importName: string = item.imported.name;
130            this.memoImportCollection.memo ||= importName === 'memo';
131            this.memoImportCollection.memoContextType ||= importName === '__memo_context_type';
132            this.memoImportCollection.memoIdType ||= importName === '__memo_id_type';
133            if (isFromCompImport && this.nameCollector.getComponents().includes(importName)) {
134                this.localComponentNames.push(item.local?.name ?? importName);
135            }
136
137            if (structCollection.has(importName)) {
138                const interfaceName: string = CustomComponentNames.COMPONENT_INTERFACE_PREFIX + importName;
139                const newImport: arkts.ETSImportDeclaration = arkts.factory.createImportDeclaration(
140                    node.source?.clone(),
141                    [factory.createAdditionalImportSpecifier(interfaceName, interfaceName)],
142                    arkts.Es2pandaImportKinds.IMPORT_KINDS_VALUE,
143                    this.program!,
144                    arkts.Es2pandaImportFlags.IMPORT_FLAGS_NONE
145                );
146                this.structInterfaceImport.push(newImport);
147            } else {
148                this.addImportWithSpecifier(item, node.source!);
149            }
150        });
151    }
152
153    getSourceDependency(sourceName: string): string {
154        let dependencyName: string = '';
155        IMPORT_SOURCE_MAP.forEach((value: Set<string>, key: string) => {
156            if (value.has(sourceName)) {
157                dependencyName = key;
158            }
159        });
160        return dependencyName;
161    }
162
163    updateSourceDependencyMap(key: string, value: string[]): void {
164        const newValue: Set<string> = IMPORT_SOURCE_MAP.get(key) ?? new Set();
165        for (const v of value) {
166            newValue.add(v);
167        }
168        IMPORT_SOURCE_MAP.set(key, newValue);
169    }
170
171    getOutDependencyName(inputName: string): string[] {
172        const sourceName: string[] = [];
173        if (OUTPUT_DEPENDENCY_MAP.has(inputName)) {
174            OUTPUT_DEPENDENCY_MAP.get(inputName)!.forEach((item: string) => {
175                sourceName.push(item);
176            });
177        }
178        return sourceName;
179    }
180
181    updateOutDependencyMap(key: string, value: string[]): void {
182        const oldValue: string[] = OUTPUT_DEPENDENCY_MAP.get(key) ?? [];
183        const newValue: string[] = [...value, ...oldValue];
184        OUTPUT_DEPENDENCY_MAP.set(key, newValue);
185    }
186
187    clearGenSymInOutDependencyMap(genSymKey: string): void {
188        if (OUTPUT_DEPENDENCY_MAP.has(genSymKey)) {
189            OUTPUT_DEPENDENCY_MAP.delete(genSymKey);
190        }
191    }
192
193    prepareDependencyMap(node: arkts.ImportSpecifier, source: arkts.StringLiteral): void {
194        if (!node.imported?.name) return;
195
196        // Handling component imports
197        const importName: string = node.imported.name;
198        const sourceName: string = source.str;
199        if (
200            this.nameCollector.getComponents().includes(importName) &&
201            (sourceName === ARKUI_COMPONENT_IMPORT_NAME || sourceName === KIT_ARKUI_NAME)
202        ) {
203            const newDependencies = [`${importName}Attribute`];
204            this.updateOutDependencyMap(importName, newDependencies);
205            this.updateSourceDependencyMap(sourceName, newDependencies);
206        } else if (
207            OUTPUT_DEPENDENCY_MAP.get(importName) &&
208            (sourceName === ARKUI_COMPONENT_IMPORT_NAME ||
209                sourceName === ARKUI_STATEMANAGEMENT_IMPORT_NAME ||
210                sourceName === KIT_ARKUI_NAME)
211        ) {
212            const newDependencies: string[] = OUTPUT_DEPENDENCY_MAP.get(importName) ?? [];
213            this.updateSourceDependencyMap(sourceName, newDependencies);
214        }
215    }
216
217    prepareMemoImports(): void {
218        const newDependencies = [];
219        if (!this.memoImportCollection.memo) {
220            newDependencies.push('memo');
221        }
222        if (!this.memoImportCollection.memoContextType) {
223            newDependencies.push('__memo_context_type');
224        }
225        if (!this.memoImportCollection.memoIdType) {
226            newDependencies.push('__memo_id_type');
227        }
228        if (newDependencies.length > 0) {
229            this.memoNameArr.push(...newDependencies);
230            this.isMemoImportOnce = true;
231        }
232    }
233
234    addImportWithSpecifier(node: arkts.ImportSpecifier, source: arkts.StringLiteral): void {
235        if (!node.imported?.name) return;
236
237        this.prepareDependencyMap(node, source);
238        const outName: string[] = this.getOutDependencyName(node.imported?.name);
239        this.outNameArr.push(...outName);
240    }
241
242    updateScriptWithImport(): void {
243        if (!this.program) {
244            throw Error('Failed to insert import: Transformer has no program');
245        }
246
247        const outNames = new Set([...this.outNameArr, ...this.memoNameArr]);
248        outNames.forEach((item: string) => {
249            const source: string = this.getSourceDependency(item);
250            const newImport: arkts.ETSImportDeclaration = arkts.factory.createImportDeclaration(
251                arkts.factory.create1StringLiteral(source),
252                [factory.createAdditionalImportSpecifier(item, item)],
253                arkts.Es2pandaImportKinds.IMPORT_KINDS_VALUE,
254                this.program!,
255                arkts.Es2pandaImportFlags.IMPORT_FLAGS_NONE
256            );
257            arkts.importDeclarationInsert(newImport, this.program!);
258        });
259        this.structInterfaceImport.forEach((element: arkts.ETSImportDeclaration) => {
260            arkts.importDeclarationInsert(element, this.program!);
261        });
262    }
263
264    enter(node: arkts.AstNode): void {
265        if (this.isExternal && arkts.isFunctionDeclaration(node)) {
266            const component = this.nameCollector.findComponentFunction(node);
267            if (!!component) this.nameCollector.collectInfoFromComponentFunction(component);
268        }
269    }
270
271    visitor(node: arkts.AstNode): arkts.AstNode {
272        this.enter(node);
273        const newNode = this.visitEachChild(node);
274        if (arkts.isCallExpression(newNode) && this.isCustomConponentDecl(newNode)) {
275            return this.transformComponentCall(newNode);
276        } else if (arkts.isCallExpression(newNode) && this.isComponentFunctionCall(newNode)) {
277            return this.transformComponentFunctionCall(newNode);
278        }
279        if (arkts.isETSImportDeclaration(node)) {
280            this.addDependencesImport(node);
281        } else if (arkts.isEtsScript(node)) {
282            if (!this.isMemoImportOnce) this.prepareMemoImports();
283            this.updateScriptWithImport();
284        }
285        return newNode;
286    }
287}
288