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