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