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