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