• 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 * as ts from 'typescript';
17import fs from 'fs';
18import path from 'path';
19
20import {
21  IFileLog,
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';
27import { hasTsNoCheckOrTsIgnoreFiles, compilingEtsOrTsFiles } from './fast_build/ark_compiler/utils';
28import { compilerOptions } from './ets_checker';
29import {
30  transformLazyImport,
31  lazyImportReExportCheck,
32  reExportNoCheckMode,
33  LazyImportOptions
34} from './process_lazy_import';
35import createAstNodeUtils from './create_ast_node_utils';
36import { MemoryMonitor } from './fast_build/meomry_monitor/rollup-plugin-memory-monitor';
37import { MemoryDefine } from './fast_build/meomry_monitor/memory_define';
38import {
39  CompileEvent,
40  createAndStartEvent,
41  stopEvent
42} from './performance';
43import {
44  ArkTSErrorDescription,
45  ErrorCode
46} from './fast_build/ark_compiler/error_code';
47import {
48  LogData,
49  LogDataFactory
50} from './fast_build/ark_compiler/logger';
51import { etsLoaderErrorReferences } from './fast_build/ark_compiler/url_config.json';
52
53/*
54* basic implementation logic:
55* tsc -> transformer
56*           | -> iterate top-level static import/export declaration
57*                  | -> for each declaration
58*                        | -> collect KitInfo
59*                        | -> generate corresponding ohosImports for each ohos-source
60*                  | -> replace each origin declaration with corresponding ohosImports
61*/
62
63export const kitTransformLog: IFileLog = new createAstNodeUtils.FileLog();
64
65const KIT_PREFIX = '@kit.';
66const KEEPTS = '// @keepTs';
67
68/*
69* This API is the TSC Transformer for transforming `KitImport` into `OhosImport`
70* e.g.
71*    ```
72*      import { ability, ErrorCode } from '@kit.AbilityKit'
73*      --->
74*      import ability from '@ohos.ability.ability'
75*      import ErrorCode from '@ohos.ability.errorCode'
76*    ```
77*/
78export function processKitImport(id: string, metaInfo: Object, parentEvent: CompileEvent,
79  shouldReturnOriginalNode: boolean = true,
80  lazyImportOptions: LazyImportOptions = { autoLazyImport: false, reExportCheckMode: reExportNoCheckMode }): Function {
81  const { autoLazyImport, reExportCheckMode } = lazyImportOptions;
82  return (context: ts.TransformationContext) => {
83    const visitor: ts.Visitor = node => {
84      // only transform static import/export declaration
85      if (ts.isImportDeclaration(node) || (ts.isExportDeclaration(node) && node.moduleSpecifier)) {
86        const moduleRequest: string = (node.moduleSpecifier as ts.StringLiteral).text.replace(/'|"/g, '');
87        if (moduleRequest.startsWith(KIT_PREFIX)) {
88          const kitDefs = getKitDefs(moduleRequest);
89          if (kitDefs && kitDefs.symbols) {
90            KitInfo.processKitInfo(moduleRequest, kitDefs.symbols as KitSymbols, node);
91            const currentKitInfo: KitInfo | undefined = KitInfo.getCurrentKitInfo();
92            return currentKitInfo ? [...currentKitInfo.getOhosImportNodes()] : [];
93          } else {
94            const errInfo: LogData = LogDataFactory.newInstance(
95              ErrorCode.ETS2BUNDLE_EXTERNAL_KIT_CONFIG_FILE_NOT_FOUND,
96              ArkTSErrorDescription,
97              `Kit '${moduleRequest}' has no corresponding config file in ArkTS SDK.`,
98              '',
99              [
100                "Please make sure the Kit apis are consistent with SDK and there's no local modification on Kit apis.",
101                `For more details on Kit apis, please refer to ${etsLoaderErrorReferences.harmonyOSReferencesAPI}.`
102              ]
103            );
104            kitTransformLog.errors.push({
105              type: LogType.ERROR,
106              message: errInfo.toString(),
107              pos: node.getStart()
108            });
109          }
110        }
111      }
112      return node;
113    };
114
115    return (node: ts.SourceFile) => {
116      const eventProcessKitImport = createAndStartEvent(parentEvent, 'processKitImport');
117      const newSourceFileRecordInfo = MemoryMonitor.recordStage(MemoryDefine.NEW_SOURCE_FILE);
118      compilingEtsOrTsFiles.push(path.normalize(node.fileName));
119
120      KitInfo.init(node, context, id);
121      // @ts-ignore
122      const resolver = context.getEmitResolver();
123
124      // When compile hap or hsp, it is used to determine whether there is a keepTsNode in the file.
125      let hasKeepTs: boolean = false;
126      if (!projectConfig.complieHar) {
127        hasKeepTs = checkHasKeepTs(node);
128      }
129
130      if (projectConfig.processTs === true) {
131        if (ts.hasTsNoCheckOrTsIgnoreFlag(node) && !hasKeepTs) {
132          hasTsNoCheckOrTsIgnoreFiles.push(path.normalize(node.fileName));
133          // process KitImport transforming
134          const processedNode: ts.SourceFile =
135            ts.visitEachChild(node, visitor, context); // this node used for [writeFile]
136          MemoryMonitor.stopRecordStage(newSourceFileRecordInfo);
137          stopEvent(eventProcessKitImport);
138          // this processNode is used to convert ets/ts to js intermediate products
139          return processedNode;
140        }
141        // process [ConstEnum] + [TypeExportImport] + [KitImport] transforming
142        // when autoLazyImport is true, some imports are converted to lazy-import
143        // eg. import { xxx } form "xxx" --> import lazy { xxx } form "xxx"
144        let processedNode: ts.SourceFile =
145          ts.visitEachChild(ts.getTypeExportImportAndConstEnumTransformer(context)(node), visitor, context);
146        processedNode = <ts.SourceFile> (autoLazyImport ? transformLazyImport(processedNode, resolver) : processedNode);
147        lazyImportReExportCheck(processedNode, reExportCheckMode);
148        ModuleSourceFile.newSourceFile(id, processedNode, metaInfo, projectConfig.singleFileEmit);
149        MemoryMonitor.stopRecordStage(newSourceFileRecordInfo);
150        stopEvent(eventProcessKitImport);
151        return shouldReturnOriginalNode ? node : processedNode; // this node not used for [writeFile]
152      }
153      // process KitImport transforming
154      const processedNode: ts.SourceFile = ts.visitEachChild(node, visitor, context);
155      MemoryMonitor.stopRecordStage(newSourceFileRecordInfo);
156      stopEvent(eventProcessKitImport);
157      return processedNode;
158    };
159  };
160}
161
162/*
163*  Main implementation of Transforming
164*/
165const DEFAULT_BINDINGS = 'default';
166
167enum FileType {
168  ETS,
169  TS
170}
171
172interface KitSymbol {
173  source: string
174  bindings: string
175}
176
177declare type KitSymbols = Record<string, KitSymbol>;
178declare type TSspecifier = ts.ImportSpecifier | ts.ExportSpecifier;
179declare type TSModuleDeclaration = ts.ImportDeclaration | ts.ExportDeclaration;
180
181/*
182* class SpecificerInfo represents the corresponding info of each imported identifier which coming from Kit
183*/
184class SpecificerInfo {
185  private localName: string;
186  private importName: string;
187  private symbol: KitSymbol;
188  private renamed: boolean;
189
190  private originElement: TSspecifier | undefined;
191  private tsImportSendableEnable : boolean = compilerOptions.tsImportSendableEnable;
192
193  constructor(localName: string, importName: string, symbol: KitSymbol, originElement: TSspecifier | undefined) {
194    this.localName = localName;
195    this.importName = importName;
196    this.symbol = symbol;
197    this.originElement = originElement;
198    this.renamed = (this.localName !== this.symbol.bindings);
199
200    this.validateImportingETSDeclarationSymbol();
201  }
202
203  getSource(): string {
204    return this.symbol.source;
205  }
206
207  getLocalName(): string {
208    return this.localName;
209  }
210
211  isRenamed(): boolean {
212    return this.renamed;
213  }
214
215  getBindings(): string {
216    return this.symbol.bindings;
217  }
218
219  isDefaultBinding(): boolean {
220    return this.symbol.bindings === DEFAULT_BINDINGS;
221  }
222
223  validateImportingETSDeclarationSymbol() {
224    if (!this.tsImportSendableEnable && KitInfo.isTSFile() && /.d.ets$/.test(this.symbol.source)) {
225      const errInfo: LogData = LogDataFactory.newInstance(
226        ErrorCode.ETS2BUNDLE_EXTERNAL_IDENTIFIER_IMPORT_NOT_ALLOWED_IN_TS_FILE,
227        ArkTSErrorDescription,
228        `Identifier '${this.importName}' comes from '${this.symbol.source}' ` +
229        'which can not be imported in .ts file.',
230        '',
231        ['Please remove the import statement or change the file extension to .ets.']
232      );
233      kitTransformLog.errors.push({
234        type: LogType.ERROR,
235        message: errInfo.toString(),
236        pos: this.getOriginElementNode().getStart()
237      });
238    }
239  }
240
241  setOriginElementNode(originElement: TSspecifier): void {
242    this.originElement = originElement;
243  }
244
245  getOriginElementNode(): TSspecifier {
246    return this.originElement;
247  }
248}
249
250export class KitInfo {
251  private static currentKitInfo: KitInfo = undefined;
252  private static currentFileType: FileType = FileType.ETS;
253  private static currentKitName: string = '';
254  private static currentSourcefile: string = '';
255  private static needSkipType: boolean = true;
256  private static tsEmitResolver: Object;
257
258  private symbols: KitSymbols;
259  private kitNode: TSModuleDeclaration;
260  private kitNodeModifier: readonly ts.Modifier[] | undefined;
261  private specifiers: Map<string, SpecificerInfo[]> = new Map<string, SpecificerInfo[]>();
262
263  private ohosImportNodes: TSModuleDeclaration[] = [];
264
265  constructor(kitNode: TSModuleDeclaration, symbols: Record<string, KitSymbol>) {
266    this.kitNode = kitNode;
267    this.symbols = symbols;
268
269    this.kitNodeModifier = ts.canHaveDecorators(this.kitNode) ? ts.getModifiers(this.kitNode) : undefined;
270  }
271
272  static init(node: ts.SourceFile, context: ts.TransformationContext, moduleId: string): void {
273    // @ts-ignore
274    this.tsEmitResolver = context.getEmitResolver();
275    this.currentSourcefile = moduleId;
276    if (/\.ts$/.test(node.fileName)) {
277      this.setFileType(FileType.TS);
278    } else {
279      this.setFileType(FileType.ETS);
280    }
281
282    if (projectConfig.processTs === true && !ts.hasTsNoCheckOrTsIgnoreFlag(node)) {
283      this.needSkipType = false;
284    } else {
285      this.needSkipType = true;
286    }
287
288    kitTransformLog.sourceFile = node;
289  }
290
291  static getCurrentKitName(): string {
292    return this.currentKitName;
293  }
294
295  static getCurrentKitInfo(): KitInfo {
296    return this.currentKitInfo;
297  }
298
299  static setFileType(fileType: FileType): void {
300    this.currentFileType = fileType;
301  }
302
303  static isTSFile(): boolean {
304    return this.currentFileType === FileType.TS;
305  }
306
307  static getCurrentSourcefile(): string {
308    return this.currentSourcefile;
309  }
310
311  static needSkipTypeSymbolOfNode(node: ts.Node): boolean {
312    if (!this.needSkipType) {
313      return false;
314    }
315
316    // need to skip type symbol
317    const resolver = this.tsEmitResolver;
318    let isTypeSymbol: boolean = false;
319    switch (node.kind) {
320      case ts.SyntaxKind.ImportDeclaration:
321      case ts.SyntaxKind.ImportClause: {
322        const importClause = ts.isImportClause(node) ? node : (node as ts.ImportDeclaration).importClause;
323        if (importClause) {
324          isTypeSymbol = importClause.isTypeOnly;
325          if (importClause.name && !resolver.isReferencedAliasDeclaration(importClause)) {
326              isTypeSymbol = true;
327          }
328        }
329        break;
330      }
331      case ts.SyntaxKind.ImportSpecifier: {
332        isTypeSymbol = (node as ts.ImportSpecifier).isTypeOnly;
333        if (!resolver.isReferencedAliasDeclaration(node)) {
334          isTypeSymbol = true;
335        }
336        break;
337      }
338      case ts.SyntaxKind.ExportDeclaration: {
339        isTypeSymbol = (node as ts.ExportDeclaration).isTypeOnly;
340        break;
341      }
342      case ts.SyntaxKind.ExportSpecifier: {
343        isTypeSymbol = (node as ts.ExportSpecifier).isTypeOnly;
344        if (!resolver.isValueAliasDeclaration(node)) {
345          isTypeSymbol = true;
346        }
347        break;
348      }
349    }
350    return isTypeSymbol;
351  }
352
353  static processImportDecl(kitNode: ts.ImportDeclaration, symbols: Record<string, KitSymbol>) {
354    if (!kitNode.importClause) {
355      // e.g. import "@kit.xxx"
356      this.currentKitInfo = new EmptyImportKitInfo(kitNode, symbols);
357      return;
358    }
359
360    if (kitNode.importClause!.namedBindings) {
361      const namedBindings: ts.NamedImportBindings = kitNode.importClause.namedBindings;
362      if (ts.isNamespaceImport(namedBindings)) {
363        // e.g. import * as ns from "@kit.xxx"
364        this.currentKitInfo = new NameSpaceKitInfo(kitNode, symbols);
365      }
366      if (ts.isNamedImports(namedBindings) && namedBindings.elements.length !== 0) {
367        // e.g. import { ... } from "@kit.xxx"
368        this.currentKitInfo = new ImportSpecifierKitInfo(kitNode, symbols);
369        namedBindings.elements.forEach(element => { this.currentKitInfo.collectSpecifier(element) });
370      }
371    }
372
373    if (kitNode.importClause!.name && !this.needSkipTypeSymbolOfNode(kitNode.importClause)) {
374      // e.g. import default from "@kit.xxx"
375      const defaultName: string = kitNode.importClause.name.text;
376      if (!this.currentKitInfo) {
377        this.currentKitInfo = new ImportSpecifierKitInfo(kitNode, symbols);
378      }
379      this.currentKitInfo.newSpecificerInfo(defaultName, DEFAULT_BINDINGS, undefined);
380    }
381  }
382
383  static processExportDecl(kitNode: ts.ExportDeclaration, symbols: Record<string, KitSymbol>): void {
384    if (kitNode.exportClause) {
385      const namedExportBindings: ts.NamedExportBindings = kitNode.exportClause;
386      if (ts.isNamespaceExport(namedExportBindings)) {
387        // e.g. export * as ns from "@kit.xxx"
388        this.currentKitInfo = new NameSpaceKitInfo(kitNode, symbols);
389      } else if (ts.isNamedExports(namedExportBindings) && namedExportBindings.elements.length !== 0) {
390        // e.g. export { ... } from "@kit.xxx"
391        this.currentKitInfo = new ExportSpecifierKitInfo(kitNode, symbols);
392        namedExportBindings.elements.forEach(element => { this.currentKitInfo.collectSpecifier(element) });
393      }
394    } else {
395      this.currentKitInfo = new ExportStarKitInfo(kitNode, symbols);
396    }
397  }
398
399  static processKitInfo(kitName: string, symbols: Record<string, KitSymbol>, kitNode: TSModuleDeclaration): void {
400    // clean up the currentKitInfo to prevent the following process getting
401    // the incorrect symbolTable with the KitInfo from last kit import.
402    this.currentKitInfo = undefined;
403    this.currentKitName = kitName;
404
405    // do not handle an empty import
406    if (ts.isImportDeclaration(kitNode)) {
407      // case 1: import { ... } from '@kit.xxx'
408      // case 2: import * as ns from '@kit.xxx'
409      // case 3: import defalutValue from '@kit.xxx'
410      // case 4: import '@kit.xxx'
411      this.processImportDecl(kitNode, symbols);
412    }
413
414    if (ts.isExportDeclaration(kitNode) && kitNode.moduleSpecifier) {
415      // case 1: export { ... } from '@kit.xxx'
416      // case 2: export * from '@kit.xxx'
417      // case 3: export * as ns from '@kit.xxx' - considering forbidden
418      this.processExportDecl(kitNode, symbols);
419    }
420    // transform into ohos imports or exports
421    this.currentKitInfo && this.currentKitInfo.transform();
422  }
423
424  static cleanUp(): void {
425    this.currentKitInfo = undefined;
426    this.tsEmitResolver = undefined;
427  }
428
429  getSymbols(): KitSymbols {
430    return this.symbols;
431  }
432
433  getKitNode(): TSModuleDeclaration {
434    return this.kitNode;
435  }
436
437  getKitNodeModifier(): readonly ts.Modifier[] | undefined {
438      return this.kitNodeModifier;
439  }
440
441  getSpecifiers(): Map<string, SpecificerInfo[]> {
442    return this.specifiers;
443  }
444
445  getOhosImportNodes(): TSModuleDeclaration[] {
446    return this.ohosImportNodes;
447  }
448
449  newSpecificerInfo(localName: string, importName: string, originElement: TSspecifier | undefined): void {
450    const symbol: KitSymbol | undefined = this.symbols[importName];
451    if (symbol) {
452      const specifier: SpecificerInfo = new SpecificerInfo(localName, importName, symbol, originElement);
453      if (this.specifiers.has(symbol.source)) {
454        this.specifiers.get(symbol.source).push(specifier);
455      } else {
456        this.specifiers.set(symbol.source, [specifier]);
457      }
458    } else {
459      const errInfo: LogData = LogDataFactory.newInstance(
460        ErrorCode.ETS2BUNDLE_EXTERNAL_IMPORT_NAME_NOT_EXPORTED_FROM_KIT,
461        ArkTSErrorDescription,
462        `'${importName}' is not exported from Kit '${KitInfo.getCurrentKitName()}'.`,
463        '',
464        [
465          "Please make sure the Kit apis are consistent with SDK and there's no local modification on Kit apis.",
466          `For more details on Kit apis, please refer to ${etsLoaderErrorReferences.harmonyOSReferencesAPI}.`
467        ]
468      );
469      kitTransformLog.errors.push({
470        type: LogType.ERROR,
471        message: errInfo.toString(),
472        pos: originElement ? originElement.getStart() : this.getKitNode().getStart()
473      });
474    }
475  }
476
477  collectSpecifier(element: TSspecifier): void {
478    if (KitInfo.needSkipTypeSymbolOfNode(this.getKitNode()) || KitInfo.needSkipTypeSymbolOfNode(element)) {
479      // skip type symbol
480      return;
481    }
482
483    const localName: string = element.name.text;
484    const importName: string = element.propertyName ? element.propertyName.text : localName;
485    this.newSpecificerInfo(localName, importName, element);
486  }
487
488  // @ts-ignore
489  transform(): void {} //override api
490}
491
492class NameSpaceKitInfo extends KitInfo {
493  private namespaceName: string;
494  private localNameTable: string[] = [];
495
496  constructor(kitNode: ts.ImportDeclaration | ts.ExportDeclaration, symbols: Record<string, KitSymbol>) {
497    super(kitNode, symbols);
498
499    const errInfo: LogData = LogDataFactory.newInstance(
500      ErrorCode.ETS2BUNDLE_EXTERNAL_KIT_NAMESPACE_IMPORT_EXPORT_NOT_SUPPORTED,
501      ArkTSErrorDescription,
502      'Namespace import or export of Kit is not supported currently.',
503      '',
504      ['Please namespace import or export of Kit replace it with named import or export instead. ' +
505       'For example, import * as ArkTS from "@kit.ArkUI"; -> import { AlertDialog } from "@kit.ArkUI";']
506    );
507    kitTransformLog.errors.push({
508      type: LogType.ERROR,
509      message: errInfo.toString(),
510      pos: kitNode.getStart()
511    });
512  }
513
514  transform(): void {
515  }
516}
517
518class ImportSpecifierKitInfo extends KitInfo {
519  private namedBindings: ts.ImportSpecifier[] = [];
520  private specifierDefaultName: ts.Identifier | undefined = undefined;
521
522  constructor(kitNode: ts.ImportDeclaration, symbols: Record<string, KitSymbol>) {
523    super(kitNode, symbols);
524  }
525
526  private hasNamedBindings(): boolean {
527    return this.namedBindings.length !== 0;
528  }
529
530  private clearSpecifierKitInfo(): void {
531    this.namedBindings = [];
532    this.specifierDefaultName = undefined;
533  }
534
535  transform(): void {
536    const node: ts.ImportDeclaration = this.getKitNode() as ts.ImportDeclaration;
537
538    this.getSpecifiers().forEach((specifiers: SpecificerInfo[], source: string) => {
539      collectKitModules(KitInfo.getCurrentSourcefile(), KitInfo.getCurrentKitName(), source);
540      specifiers.forEach((specifier: SpecificerInfo) => {
541        if (specifier.isDefaultBinding()) {
542          this.specifierDefaultName = ts.factory.createIdentifier(specifier.getLocalName());
543        } else {
544          this.namedBindings.push(
545            ts.factory.createImportSpecifier(
546              specifier.getOriginElementNode() ?
547                (specifier.getOriginElementNode() as ts.ImportSpecifier).isTypeOnly : node.importClause.isTypeOnly,
548              specifier.isRenamed() ? ts.factory.createIdentifier(specifier.getBindings()) : undefined,
549              ts.factory.createIdentifier(specifier.getLocalName())
550            )
551          );
552        }
553      });
554
555      let newImportClause: ts.ImportClause = ts.factory.createImportClause(
556        node.importClause!.isTypeOnly,
557        this.specifierDefaultName,
558        this.hasNamedBindings() ? ts.factory.createNamedImports(this.namedBindings) : undefined
559      );
560      // @ts-ignore
561      newImportClause.isLazy = node.importClause!.isLazy;
562      this.getOhosImportNodes().push(ts.factory.createImportDeclaration(
563        this.getKitNodeModifier(),
564        newImportClause,
565        ts.factory.createStringLiteral(trimSourceSuffix(source))
566      ));
567
568      this.clearSpecifierKitInfo();
569    });
570  }
571}
572
573class EmptyImportKitInfo extends KitInfo {
574  constructor(kitNode: ts.ImportDeclaration, symbols: Record<string, KitSymbol>) {
575    super(kitNode, symbols);
576
577    /*
578     * Side-effect import can not be used by Kit since Kit actually has no spcific implementation.
579     * In general, a Kit may be imported in a Side-effect import statement by mistake. So we
580     * illustrate explicitly that Kit can not in Side-effect import to avoid misunderstanding
581     * of runtime's behavior.
582     */
583    const errInfo: LogData = LogDataFactory.newInstance(
584      ErrorCode.ETS2BUNDLE_EXTERNAL_EMPTY_IMPORT_NOT_ALLOWED_WITH_KIT,
585      ArkTSErrorDescription,
586      `Can not use empty import(side-effect import) statement with Kit ` +
587      `'${(kitNode.moduleSpecifier as ts.StringLiteral).text.replace(/'|"/g, '')}'.`,
588      '',
589      ['Please specify imported symbols explicitly. ' +
590       'For example, import "@kit.ArkUI"; -> import { lang } from "@kit.ArkUI";']
591    );
592    kitTransformLog.errors.push({
593      type: LogType.ERROR,
594      message: errInfo.toString(),
595      pos: kitNode.getStart()
596    });
597  }
598
599  transform(): void {
600  }
601}
602
603class ExportSpecifierKitInfo extends KitInfo {
604  private namedBindings: ts.ExportSpecifier[] = [];
605
606  constructor(kitNode: ts.ExportDeclaration, symbols: Record<string, KitSymbol>) {
607    super(kitNode, symbols);
608  }
609
610  private hasNamedBindings(): boolean {
611    return this.namedBindings.length !== 0;
612  }
613
614  private clearSpecifierKitInfo(): void {
615    this.namedBindings = [];
616  }
617
618  transform(): void {
619    const node: ts.ExportDeclaration = this.getKitNode() as ts.ExportDeclaration;
620
621    this.getSpecifiers().forEach((specifiers: SpecificerInfo[], source: string) => {
622      specifiers.forEach((specifier: SpecificerInfo) => {
623        this.namedBindings.push(
624          ts.factory.createExportSpecifier(
625            (specifier.getOriginElementNode() as ts.ExportSpecifier).isTypeOnly,
626            specifier.isRenamed() ? ts.factory.createIdentifier(specifier.getBindings()) : undefined,
627            ts.factory.createIdentifier(specifier.getLocalName())
628          )
629        );
630      });
631
632      this.getOhosImportNodes().push(ts.factory.createExportDeclaration(
633        this.getKitNodeModifier(),
634        node.isTypeOnly,
635        this.hasNamedBindings() ? ts.factory.createNamedExports(this.namedBindings) : undefined,
636        ts.factory.createStringLiteral(trimSourceSuffix(source)),
637        node.assertClause
638      ));
639
640      this.clearSpecifierKitInfo();
641    });
642  }
643}
644
645class ExportStarKitInfo extends KitInfo {
646  private sourceSet: Set<string> = new Set<string>();
647
648  constructor(kitNode: ts.ExportDeclaration, symbols: Record<string, KitSymbol>) {
649    super(kitNode, symbols);
650
651    for (const symbol in symbols) {
652      this.sourceSet.add(symbols[symbol].source);
653    }
654
655    kitTransformLog.errors.push({
656      type: LogType.WARN,
657      message: `Using 'export *' will load all the sub-module of Kit in runtime.`,
658      pos: this.getKitNode().getStart()
659    });
660  }
661
662  transform(): void {
663    const node: ts.ExportDeclaration = this.getKitNode() as ts.ExportDeclaration;
664
665    this.sourceSet.forEach((source: string) => {
666      this.getOhosImportNodes().push(ts.factory.createExportDeclaration(
667        this.getKitNodeModifier(),
668        node.isTypeOnly,
669        undefined,
670        ts.factory.createStringLiteral(trimSourceSuffix(source)),
671        node.assertClause
672      ));
673    });
674  }
675}
676
677/*
678* utils part
679*/
680const JSON_SUFFIX = '.json';
681const KIT_CONFIGS = 'kit_configs';
682const KIT_CONFIG_PATH = './build-tools/ets-loader/kit_configs';
683
684function getKitDefs(kitModuleRequest: string): Object | undefined {
685  const kitConfigs: string[] = [path.resolve(__dirname, `../${KIT_CONFIGS}`)];
686  if (process.env.externalApiPaths) {
687    const externalApiPaths = process.env.externalApiPaths.split(path.delimiter);
688    externalApiPaths.forEach(sdkPath => {
689      kitConfigs.push(path.resolve(sdkPath, KIT_CONFIG_PATH));
690    });
691  }
692
693  for (const kitConfig of kitConfigs) {
694    const kitModuleConfigJson = path.resolve(kitConfig, `./${kitModuleRequest}${JSON_SUFFIX}`);
695    if (fs.existsSync(kitModuleConfigJson)) {
696      return JSON.parse(fs.readFileSync(kitModuleConfigJson, 'utf-8'));
697    }
698  }
699  return undefined;
700}
701
702function trimSourceSuffix(source: string): string {
703  return source.replace(/\.d.[e]?ts$/, '');
704}
705
706export function checkHasKeepTs(node: ts.SourceFile): boolean {
707  // Get the first comment in the file and determine whether it is "// @keepTs"
708  const comments = ts.getTrailingCommentRanges(node.getFullText(), 0) || [];
709  if (comments.length === 0) {
710    return false;
711  }
712  return node.getFullText().substring(comments[0].pos, comments[0].end).trim() === KEEPTS;
713}
714
715export function resetKitImportLog(): void {
716  kitTransformLog.cleanUp();
717}
718
719export function cleanUpKitImportObjects(): void {
720  KitInfo.cleanUp();
721  kitTransformLog.cleanUp();
722}