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 * as ts from 'typescript'; 17import fs from 'fs'; 18import path from 'path'; 19 20import { 21 FileLog, 22 LogType 23} from './utils'; 24import { projectConfig } from '../main'; 25import { ModuleSourceFile } from './fast_build/ark_compiler/module/module_source_file'; 26import { collectKitModules } from './fast_build/system_api/rollup-plugin-system-api'; 27 28/* 29* basic implementation logic: 30* tsc -> transformer 31* | -> iterate top-level static import/export declaration 32* | -> for each declaration 33* | -> collect KitInfo 34* | -> generate corresponding ohosImports for each ohos-source 35* | -> replace each origin declaration with corresponding ohosImports 36*/ 37 38export const kitTransformLog: FileLog = new FileLog(); 39export let hasTsNoCheckOrTsIgnoreFiles: string[] = []; 40export let compilingEtsOrTsFiles: string[] = []; 41 42const KIT_PREFIX = '@kit.'; 43 44/* 45* This API is the TSC Transformer for transforming `KitImport` into `OhosImport` 46* e.g. 47* ``` 48* import { ability, ErrorCode } from '@kit.AbilityKit' 49* ---> 50* import ability from '@ohos.ability.ability' 51* import ErrorCode from '@ohos.ability.errorCode' 52* ``` 53*/ 54export function processKitImport(): Function { 55 return (context: ts.TransformationContext) => { 56 const visitor: ts.Visitor = node => { 57 // only transform static import/export declaration 58 if (ts.isImportDeclaration(node) || (ts.isExportDeclaration(node) && node.moduleSpecifier)) { 59 const moduleRequest: string = (node.moduleSpecifier as ts.StringLiteral).text.replace(/'|"/g, ''); 60 if (moduleRequest.startsWith(KIT_PREFIX)) { 61 const kitDefs = getKitDefs(moduleRequest); 62 if (kitDefs && kitDefs.symbols) { 63 KitInfo.processKitInfo(moduleRequest, kitDefs.symbols as KitSymbols, node); 64 return [...KitInfo.getCurrentKitInfo().getOhosImportNodes()]; 65 } else { 66 kitTransformLog.errors.push({ 67 type: LogType.ERROR, 68 message: `Kit '${moduleRequest}' has no corresponding config file in ArkTS SDK, `+ 69 `please check the consistency of SDK.`, 70 pos: node.getStart() 71 }); 72 } 73 } 74 } 75 return node; 76 } 77 78 return (node: ts.SourceFile) => { 79 compilingEtsOrTsFiles.push(path.normalize(node.fileName)); 80 81 KitInfo.init(node, context); 82 83 if (projectConfig.processTs === true) { 84 if (ts.hasTsNoCheckOrTsIgnoreFlag(node)) { 85 hasTsNoCheckOrTsIgnoreFiles.push(path.normalize(node.fileName)); 86 // process KitImport transforming 87 return ts.visitEachChild(node, visitor, context); // this node used for [writeFile] 88 } 89 // process [ConstEnum] + [TypeExportImport] + [KitImport] transforming 90 const processedNode: ts.SourceFile = 91 ts.visitEachChild(ts.getTypeExportImportAndConstEnumTransformer(context)(node), visitor, context); 92 ModuleSourceFile.newSourceFile(path.normalize(processedNode.fileName), processedNode); 93 return node; // this node not used for [writeFile] 94 } 95 // process KitImport transforming 96 return ts.visitEachChild(node, visitor, context); 97 }; 98 } 99} 100 101/* 102* Main implementation of Transforming 103*/ 104const DEFAULT_BINDINGS = 'default'; 105 106enum FileType { 107 ETS, 108 TS 109} 110 111interface KitSymbol { 112 source: string 113 bindings: string 114} 115 116declare type KitSymbols = Record<string, KitSymbol>; 117declare type TSspecifier = ts.ImportSpecifier | ts.ExportSpecifier; 118declare type TSModuleDeclaration = ts.ImportDeclaration | ts.ExportDeclaration; 119 120/* 121* class SpecificerInfo represents the corresponding info of each imported identifier which coming from Kit 122*/ 123class SpecificerInfo { 124 private localName: string; 125 private importName: string; 126 private symbol: KitSymbol; 127 private renamed: boolean; 128 129 private originElement: TSspecifier | undefined; 130 131 constructor(localName: string, importName: string, symbol: KitSymbol, originElement: TSspecifier | undefined) { 132 this.localName = localName; 133 this.importName = importName; 134 this.symbol = symbol; 135 this.originElement = originElement; 136 this.renamed = (this.localName !== this.symbol.bindings); 137 138 this.validateImportingETSDeclarationSymbol(); 139 } 140 141 getSource(): string { 142 return this.symbol.source; 143 } 144 145 getLocalName(): string { 146 return this.localName; 147 } 148 149 isRenamed(): boolean { 150 return this.renamed; 151 } 152 153 getBindings(): string { 154 return this.symbol.bindings; 155 } 156 157 isDefaultBinding(): boolean { 158 return this.symbol.bindings === DEFAULT_BINDINGS; 159 } 160 161 validateImportingETSDeclarationSymbol() { 162 if (KitInfo.isTSFile() && /.d.ets$/.test(this.symbol.source)) { 163 kitTransformLog.errors.push({ 164 type: LogType.ERROR, 165 message: `Identifier '${this.importName}' comes from '${this.symbol.source}' ` + 166 `which can not be imported in .ts file.`, 167 pos: this.getOriginElementNode().getStart() 168 }); 169 } 170 } 171 172 setOriginElementNode(originElement: TSspecifier) { 173 this.originElement = originElement; 174 } 175 176 getOriginElementNode(): TSspecifier { 177 return this.originElement; 178 } 179} 180 181class KitInfo { 182 private static currentKitInfo: KitInfo = undefined; 183 private static currentFileType: FileType = FileType.ETS; 184 private static currentKitName: string = ''; 185 private static currentSourcefile: string = ''; 186 private static needSkipType: boolean = true; 187 private static tsEmitResolver: any; 188 189 private symbols: KitSymbols; 190 private kitNode: TSModuleDeclaration; 191 private kitNodeModifier: readonly ts.Modifier[] | undefined; 192 private specifiers: Map<string, SpecificerInfo[]> = new Map<string, SpecificerInfo[]>(); 193 194 private ohosImportNodes: TSModuleDeclaration[] = []; 195 196 constructor(kitNode: TSModuleDeclaration, symbols: Record<string, KitSymbol>) { 197 this.kitNode = kitNode; 198 this.symbols = symbols; 199 200 this.kitNodeModifier = ts.canHaveDecorators(this.kitNode) ? ts.getModifiers(this.kitNode) : undefined; 201 } 202 203 static init(node: ts.SourceFile, context: ts.TransformationContext): void { 204 // @ts-ignore 205 this.tsEmitResolver = context.getEmitResolver(); 206 this.currentSourcefile = node.fileName; 207 if (/\.ts$/.test(node.fileName)) { 208 this.setFileType(FileType.TS); 209 } else { 210 this.setFileType(FileType.ETS); 211 } 212 213 if (projectConfig.processTs === true && !ts.hasTsNoCheckOrTsIgnoreFlag(node)) { 214 this.needSkipType = false; 215 } else { 216 this.needSkipType = true; 217 } 218 219 kitTransformLog.sourceFile = node; 220 } 221 222 static getCurrentKitName(): string { 223 return this.currentKitName; 224 } 225 226 static getCurrentKitInfo(): KitInfo { 227 return this.currentKitInfo; 228 } 229 230 static setFileType(fileType: FileType): void { 231 this.currentFileType = fileType; 232 } 233 234 static isTSFile(): boolean { 235 return this.currentFileType === FileType.TS; 236 } 237 238 static getCurrentSourcefile(): string { 239 return this.currentSourcefile; 240 } 241 242 static needSkipTypeSymbolOfNode(node: ts.Node): boolean { 243 if (!this.needSkipType) { 244 return false; 245 } 246 247 // need to skip type symbol 248 const resolver = this.tsEmitResolver; 249 let isTypeSymbol: boolean = false; 250 switch(node.kind) { 251 case ts.SyntaxKind.ImportDeclaration: 252 case ts.SyntaxKind.ImportClause: { 253 const importClause = ts.isImportClause(node) ? node : (node as ts.ImportDeclaration).importClause; 254 if (importClause) { 255 isTypeSymbol = importClause.isTypeOnly; 256 if (importClause.name && 257 !resolver.isReferencedAliasDeclaration(importClause) && resolver.isReferenced(node)) { 258 isTypeSymbol = true; 259 } 260 } 261 break; 262 } 263 case ts.SyntaxKind.ImportSpecifier: { 264 isTypeSymbol = (node as ts.ImportSpecifier).isTypeOnly; 265 if (!resolver.isReferencedAliasDeclaration(node) && resolver.isReferenced(node)) { 266 isTypeSymbol = true; 267 } 268 break; 269 } 270 case ts.SyntaxKind.ExportDeclaration: { 271 isTypeSymbol = (node as ts.ExportDeclaration).isTypeOnly; 272 break; 273 } 274 case ts.SyntaxKind.ExportSpecifier: { 275 isTypeSymbol = (node as ts.ExportSpecifier).isTypeOnly; 276 if (!resolver.isValueAliasDeclaration(node)) { 277 isTypeSymbol = true; 278 } 279 break; 280 } 281 } 282 return isTypeSymbol; 283 } 284 285 static processImportDecl(kitNode: ts.ImportDeclaration, symbols: Record<string, KitSymbol>) { 286 if (kitNode.importClause!.namedBindings) { 287 const namedBindings: ts.NamedImportBindings = kitNode.importClause.namedBindings; 288 if (ts.isNamespaceImport(namedBindings)) { 289 // e.g. import * as ns from "@kit.xxx" 290 this.currentKitInfo = new NameSpaceKitInfo(kitNode, symbols); 291 } 292 if (ts.isNamedImports(namedBindings) && namedBindings.elements.length !== 0) { 293 // e.g. import { ... } from "@kit.xxx" 294 this.currentKitInfo = new ImportSpecifierKitInfo(kitNode, symbols); 295 namedBindings.elements.forEach(element => { this.currentKitInfo.collectSpecifier(element) }); 296 } 297 } 298 299 if (kitNode.importClause!.name && !this.needSkipTypeSymbolOfNode(kitNode.importClause)) { 300 // e.g. import default from "@kit.xxx" 301 const defaultName: string = kitNode.importClause.name.text; 302 if (!this.currentKitInfo) { 303 this.currentKitInfo = new ImportSpecifierKitInfo(kitNode, symbols); 304 } 305 this.currentKitInfo.newSpecificerInfo(defaultName, DEFAULT_BINDINGS, undefined); 306 } 307 } 308 309 static processExportDecl(kitNode: ts.ExportDeclaration, symbols: Record<string, KitSymbol>) { 310 if (kitNode.exportClause) { 311 const namedExportBindings: ts.NamedExportBindings = kitNode.exportClause; 312 if (ts.isNamespaceExport(namedExportBindings)) { 313 // e.g. export * as ns from "@kit.xxx" 314 this.currentKitInfo = new NameSpaceKitInfo(kitNode, symbols); 315 } else if (ts.isNamedExports(namedExportBindings) && namedExportBindings.elements.length !== 0) { 316 // e.g. export { ... } from "@kit.xxx" 317 this.currentKitInfo = new ExportSpecifierKitInfo(kitNode, symbols); 318 namedExportBindings.elements.forEach(element => { this.currentKitInfo.collectSpecifier(element) }); 319 } 320 } else { 321 this.currentKitInfo = new ExportStarKitInfo(kitNode, symbols); 322 } 323 } 324 325 static processKitInfo(kitName: string, symbols: Record<string, KitSymbol>, kitNode: TSModuleDeclaration): void { 326 this.currentKitName = kitName; 327 328 // do not handle an empty import 329 if (ts.isImportDeclaration(kitNode) && kitNode.importClause) { 330 // case 1: import { ... } from '@kit.xxx' 331 // case 2: import * as ns from '@kit.xxx' 332 // case 3: import defalutValue from '@kit.xxx' 333 this.processImportDecl(kitNode, symbols); 334 } 335 336 if (ts.isExportDeclaration(kitNode) && kitNode.moduleSpecifier) { 337 // case 1: export { ... } from '@kit.xxx' 338 // case 2: export * from '@kit.xxx' 339 // case 3: export * as ns from '@kit.xxx' - considering forbidden 340 this.processExportDecl(kitNode, symbols); 341 } 342 // transform into ohos imports or exports 343 this.currentKitInfo && this.currentKitInfo.transform(); 344 } 345 346 getSymbols(): KitSymbols { 347 return this.symbols; 348 } 349 350 getKitNode(): TSModuleDeclaration { 351 return this.kitNode; 352 } 353 354 getKitNodeModifier(): readonly ts.Modifier[] | undefined { 355 return this.kitNodeModifier; 356 } 357 358 getSpecifiers(): Map<string, SpecificerInfo[]> { 359 return this.specifiers; 360 } 361 362 getOhosImportNodes(): TSModuleDeclaration[] { 363 return this.ohosImportNodes; 364 } 365 366 newSpecificerInfo(localName: string, importName: string, originElement: TSspecifier | undefined): void { 367 const symbol: KitSymbol | undefined = this.symbols[importName]; 368 if (symbol) { 369 const specifier: SpecificerInfo = new SpecificerInfo(localName, importName, symbol, originElement); 370 if (this.specifiers.has(symbol.source)) { 371 this.specifiers.get(symbol.source).push(specifier); 372 } else { 373 this.specifiers.set(symbol.source, [specifier]); 374 } 375 } else { 376 kitTransformLog.errors.push({ 377 type: LogType.ERROR, 378 message: `Kit '${KitInfo.getCurrentKitName()} has not exported the Identifier '${importName}'.`, 379 pos: originElement ? originElement.getStart() : this.getKitNode().getStart() 380 }); 381 } 382 } 383 384 collectSpecifier(element: TSspecifier) { 385 if (KitInfo.needSkipTypeSymbolOfNode(this.getKitNode()) || KitInfo.needSkipTypeSymbolOfNode(element)) { 386 // skip type symbol 387 return; 388 } 389 390 const localName: string = element.name.text; 391 const importName: string = element.propertyName ? element.propertyName.text : localName; 392 this.newSpecificerInfo(localName, importName, element); 393 } 394 395 // @ts-ignore 396 transform(): void {} //override api 397} 398 399class NameSpaceKitInfo extends KitInfo { 400 private namespaceName: string; 401 private localNameTable: string[] = []; 402 403 constructor(kitNode: ts.ImportDeclaration | ts.ExportDeclaration, symbols: Record<string, KitSymbol>) { 404 super(kitNode, symbols); 405 406 kitTransformLog.errors.push({ 407 type: LogType.ERROR, 408 message: `Namespace import or export of Kit is not supported currently.`, 409 pos: kitNode.getStart() 410 }); 411 } 412 413 transform(): void { 414 } 415} 416 417class ImportSpecifierKitInfo extends KitInfo { 418 private namedBindings: ts.ImportSpecifier[] = []; 419 private specifierDefaultName: ts.Identifier | undefined = undefined; 420 421 constructor(kitNode: ts.ImportDeclaration, symbols: Record<string, KitSymbol>) { 422 super(kitNode, symbols); 423 } 424 425 private hasNamedBindings(): boolean { 426 return this.namedBindings.length !== 0; 427 } 428 429 private clearSpecifierKitInfo(): void { 430 this.namedBindings = []; 431 this.specifierDefaultName = undefined; 432 } 433 434 transform() { 435 const node: ts.ImportDeclaration = this.getKitNode() as ts.ImportDeclaration; 436 437 this.getSpecifiers().forEach((specifiers: SpecificerInfo[], source: string) => { 438 collectKitModules(KitInfo.getCurrentSourcefile(), KitInfo.getCurrentKitName(), source); 439 specifiers.forEach((specifier: SpecificerInfo) => { 440 if (specifier.isDefaultBinding()) { 441 this.specifierDefaultName = ts.factory.createIdentifier(specifier.getLocalName()); 442 } else { 443 this.namedBindings.push( 444 ts.factory.createImportSpecifier( 445 specifier.getOriginElementNode() ? 446 (specifier.getOriginElementNode() as ts.ImportSpecifier).isTypeOnly : node.importClause.isTypeOnly, 447 specifier.isRenamed() ? ts.factory.createIdentifier(specifier.getBindings()) : undefined, 448 ts.factory.createIdentifier(specifier.getLocalName()) 449 ) 450 ); 451 } 452 }); 453 454 this.getOhosImportNodes().push(ts.factory.createImportDeclaration( 455 this.getKitNodeModifier(), 456 ts.factory.createImportClause( 457 node.importClause!.isTypeOnly, 458 this.specifierDefaultName, 459 this.hasNamedBindings() ? ts.factory.createNamedImports(this.namedBindings) : undefined 460 ), 461 ts.factory.createStringLiteral(trimSourceSuffix(source)) 462 )); 463 464 this.clearSpecifierKitInfo(); 465 }); 466 } 467} 468 469class ExportSpecifierKitInfo extends KitInfo { 470 private namedBindings: ts.ExportSpecifier[] = []; 471 472 constructor(kitNode: ts.ExportDeclaration, symbols: Record<string, KitSymbol>) { 473 super(kitNode, symbols); 474 } 475 476 private hasNamedBindings(): boolean { 477 return this.namedBindings.length !== 0; 478 } 479 480 private clearSpecifierKitInfo(): void { 481 this.namedBindings = []; 482 } 483 484 transform(): void { 485 const node: ts.ExportDeclaration = this.getKitNode() as ts.ExportDeclaration; 486 487 this.getSpecifiers().forEach((specifiers: SpecificerInfo[], source: string) => { 488 specifiers.forEach((specifier: SpecificerInfo) => { 489 this.namedBindings.push( 490 ts.factory.createExportSpecifier( 491 (specifier.getOriginElementNode() as ts.ExportSpecifier).isTypeOnly, 492 specifier.isRenamed() ? ts.factory.createIdentifier(specifier.getBindings()) : undefined, 493 ts.factory.createIdentifier(specifier.getLocalName()) 494 ) 495 ); 496 }); 497 498 this.getOhosImportNodes().push(ts.factory.createExportDeclaration( 499 this.getKitNodeModifier(), 500 node.isTypeOnly, 501 this.hasNamedBindings() ? ts.factory.createNamedExports(this.namedBindings) : undefined, 502 ts.factory.createStringLiteral(trimSourceSuffix(source)), 503 node.assertClause 504 )); 505 506 this.clearSpecifierKitInfo(); 507 }); 508 } 509} 510 511class ExportStarKitInfo extends KitInfo { 512 private sourceSet: Set<string> = new Set<string>(); 513 514 constructor(kitNode: ts.ExportDeclaration, symbols: Record<string, KitSymbol>) { 515 super(kitNode, symbols); 516 517 for (const symbol in symbols) { 518 this.sourceSet.add(symbols[symbol].source); 519 } 520 521 kitTransformLog.errors.push({ 522 type: LogType.WARN, 523 message: `Using 'export *' will load all the sub-module of Kit in runtime.`, 524 pos: this.getKitNode().getStart() 525 }); 526 } 527 528 transform(): void { 529 const node: ts.ExportDeclaration = this.getKitNode() as ts.ExportDeclaration; 530 531 this.sourceSet.forEach((source: string) => { 532 this.getOhosImportNodes().push(ts.factory.createExportDeclaration( 533 this.getKitNodeModifier(), 534 node.isTypeOnly, 535 undefined, 536 ts.factory.createStringLiteral(trimSourceSuffix(source)), 537 node.assertClause 538 )); 539 }); 540 } 541} 542 543/* 544* utils part 545*/ 546const JSON_SUFFIX = '.json'; 547const KIT_CONFIGS = 'kit_configs'; 548const KIT_CONFIG_PATH = './build-tools/ets-loader/kit_configs'; 549 550function getKitDefs(kitModuleRequest: string) { 551 const kitConfigs: string[] = [path.resolve(__dirname, `../${KIT_CONFIGS}`)]; 552 if (process.env.externalApiPaths) { 553 const externalApiPaths = process.env.externalApiPaths.split(path.delimiter); 554 externalApiPaths.forEach(sdkPath => { 555 kitConfigs.push(path.resolve(sdkPath, KIT_CONFIG_PATH)); 556 }); 557 } 558 559 for (const kitConfig of kitConfigs) { 560 const kitModuleConfigJson = path.resolve(kitConfig, `./${kitModuleRequest}${JSON_SUFFIX}`); 561 if (fs.existsSync(kitModuleConfigJson)) { 562 return JSON.parse(fs.readFileSync(kitModuleConfigJson, 'utf-8')); 563 } 564 } 565 return undefined; 566} 567 568function trimSourceSuffix(source: string): string { 569 return source.replace(/\.d.[e]?ts$/, ''); 570} 571 572export function cleanUpKitImportObjects(): void { 573 kitTransformLog.cleanUp(); 574}