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