• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021-2022 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 */
15const path = require('path');
16const fs = require('fs');
17const ts = require('typescript');
18
19let sourceFile = null;
20let lastNoteStr = '';
21let lastNodeName = '';
22const referencesMap = new Map();
23const referencesModuleMap = new Map();
24/**
25 * @enum {string} references地址的切换类型
26 */
27const REFERENCE_TYPE = {
28  TOLOCAL: 'toLocal',
29  TORIGHTSDK: 'toRightSDK',
30  TOSDK: 'toSDK',
31};
32const PATT = {
33  GET_REFERENCE: /\/\/\/\s*<reference.*>/g,
34  GET_REFERENCEURL: /\/\/\/\s*<reference\s*path=("|')(.*)("|')\s*\/>/g,
35  REFERENCEURL_LOCAL: /(.\/)?(\S*)@internal\/component\/ets\/(\S*)/g,
36  REFERENCEURL_RIGHTSDK: /(..\/)(\S*)build-tools\/ets-loader\/declarations\/(\S*)/g,
37  REFERENCEURL_SDK: /(..\/)(\S*)component\/(\S*)/g,
38};
39function collectDeclaration(url) {
40  // 入口
41  try {
42    const utPath = path.resolve(__dirname, url);
43    const utFiles = [];
44    readFile(utPath, utFiles); // 读取文件
45    tsTransform(utFiles, deleteSystemApi);
46  } catch (error) {
47    console.error('DELETE_SYSTEM_PLUGIN ERROR: ', error);
48  }
49}
50/**
51 * 遍历所有文件进行处理
52 * @param {Array} utFiles 所有文件
53 * @param {deleteSystemApi} callback 回调函数
54 */
55function tsTransform(utFiles, callback) {
56  utFiles.forEach((url) => {
57    if (/\.json/.test(url) || /index\-full\.d\.ts/.test(url) || /common\.d\.ts/.test(url) || /\.d\.ets/.test(url)) {
58      // 特殊类型文件处理
59      const content = fs.readFileSync(url, 'utf-8');
60      writeFile(url, content);
61    } else if (/\.d\.ts/.test(url)) {
62      // dts文件处理
63      let content = fs.readFileSync(url, 'utf-8'); // 文件内容
64      const fileName = path.basename(url).replace(/.d.ts/g, '.ts');
65      let references = content.match(PATT.GET_REFERENCE);
66      if (references) {
67        referencesMap.set(url, { references: references });
68        for (let index = 0; index < references.length; index++) {
69          const item = references[index];
70          content = content.replace(item, '');
71        }
72      }
73      ts.transpileModule(content, {
74        compilerOptions: {
75          target: ts.ScriptTarget.ES2017,
76        },
77        fileName: fileName,
78        transformers: { before: [callback(url)] },
79      });
80    }
81  });
82}
83/**
84 * 切换references或者references中path的格式
85 * @param {string} references references或者references中的path
86 * @param {REFERENCE_TYPE} reverse 切换类型
87 * @returns {string}
88 */
89function referencesToOthers(references, type) {
90  let referencesurl;
91  let hasFullpatt = references.match(PATT.GET_REFERENCEURL);
92  let isFullReferenceurl = hasFullpatt && hasFullpatt.length > 0;
93  if (isFullReferenceurl) {
94    referencesurl = RegExp.$2;
95  } else {
96    referencesurl = references;
97  }
98  let currentType = '';
99  if (referencesurl.match(PATT.REFERENCEURL_LOCAL)) {
100    currentType = REFERENCE_TYPE.TOLOCAL;
101  } else if (referencesurl.match(PATT.REFERENCEURL_RIGHTSDK)) {
102    currentType = REFERENCE_TYPE.TORIGHTSDK;
103  } else if (referencesurl.match(PATT.REFERENCEURL_SDK)) {
104    currentType = REFERENCE_TYPE.TOSDK;
105  }
106  if (currentType === '' || currentType === type) {
107    return references;
108  }
109  let starturl = '';
110  let fileName = '';
111  switch (currentType) {
112    case REFERENCE_TYPE.TOLOCAL:
113      starturl = RegExp.$2;
114      fileName = RegExp.$3;
115      break;
116    case REFERENCE_TYPE.TORIGHTSDK:
117      starturl = RegExp.$2;
118      fileName = RegExp.$3;
119      break;
120    case REFERENCE_TYPE.TOSDK:
121      starturl = RegExp.$2;
122      fileName = RegExp.$3;
123      break;
124    default:
125      break;
126  }
127  let finallyurl;
128  switch (type) {
129    case REFERENCE_TYPE.TOLOCAL:
130      finallyurl = `${starturl === '' ? './' : ''}${starturl}@internal/component/ets/${fileName}`;
131      break;
132    case REFERENCE_TYPE.TORIGHTSDK:
133      finallyurl = `../${starturl}build-tools/ets-loader/declarations/${fileName}`;
134      break;
135    case REFERENCE_TYPE.TOSDK:
136      finallyurl = `../${starturl}component/${fileName}`;
137      break;
138    default:
139      break;
140  }
141  if (isFullReferenceurl) {
142    finallyurl = `/// <reference path="${finallyurl}"/>`;
143  }
144  return finallyurl;
145}
146
147/**
148 * 读取目录下所有文件
149 * @param {string} dir 文件目录
150 * @param {Array} utFiles 所有文件
151 */
152function readFile(dir, utFiles) {
153  try {
154    const files = fs.readdirSync(dir);
155    files.forEach((element) => {
156      const filePath = path.join(dir, element);
157      const status = fs.statSync(filePath);
158      if (status.isDirectory()) {
159        readFile(filePath, utFiles);
160      } else {
161        utFiles.push(filePath);
162      }
163    });
164  } catch (e) {
165    console.log('ETS ERROR: ' + e);
166  }
167}
168
169function writeFile(url, data, option) {
170  if (fs.existsSync(outputPath)) {
171    fs.rmdirSync(outputPath, { recursive: true });
172  }
173  const newFilePath = path.resolve(outputPath, path.relative(__dirname, url));
174  fs.mkdir(path.relative(__dirname, path.dirname(newFilePath)), { recursive: true }, (err) => {
175    if (err) {
176      console.log(`ERROR FOR CREATE PATH ${err}`);
177    } else {
178      fs.writeFile(newFilePath, data, option, (err) => {
179        if (err) {
180          console.log(`ERROR FOR CREATE FILE ${err}`);
181        }
182      });
183    }
184  });
185}
186
187const globalModules = new Map();
188
189/**
190 * 每个文件处理前回调函数第二个
191 * @param {string} url 文件路径
192 * @returns {Function}
193 */
194function formatImportDeclaration(url) {
195  return (context) => {
196    const allIdentifierSet = new Set([]);
197    let referencesMessage = '';
198    let copyrightMessage = '';
199    let isCopyrightDeleted = false;
200    return (node) => {
201      sourceFile = node;
202      collectAllIdentifier(node); // 获取所有标识符
203      node = formatAllNodes(node); // 获取所有节点
204      if (!isEmptyFile(node)) {
205        const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
206        let result = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
207        if (isCopyrightDeleted) {
208          // 当第一个节点被删除时会同步删除整个文件jsdoc
209          result = copyrightMessage + '\n' + result;
210        }
211        copyrightMessage = node.getFullText().replace(node.getText(), '');
212        if (referencesMessage) {
213          // 将references写入文件
214          result =
215            result.substring(0, copyrightMessage.length) +
216            '\n' +
217            referencesMessage +
218            result.substring(copyrightMessage.length);
219        }
220        writeFile(url, result);
221      }
222      return node;
223    };
224    function collectAllIdentifier(node) {
225      if (ts.isSourceFile(node) && node.statements) {
226        node.statements.forEach((stat) => {
227          if (!ts.isImportDeclaration(stat)) {
228            ts.visitEachChild(stat, collectAllNodes, context);
229          }
230        });
231      }
232    }
233    function collectAllNodes(node) {
234      if (ts.isIdentifier(node)) {
235        allIdentifierSet.add(node.escapedText.toString());
236      }
237      return ts.visitEachChild(node, collectAllNodes, context);
238    }
239    function formatAllNodes(node) {
240      globalModules.clear();
241      let currReferences = [];
242      let currReferencesModule = [];
243      if (referencesMap.has(url)) {
244        currReferences = referencesMap.get(url);
245        currReferencesModule = currReferences.references.map((element, index) => {
246          element.match(PATT.GET_REFERENCEURL);
247          let referencePath = RegExp.$2;
248          referencePath = referencesToOthers(referencePath, REFERENCE_TYPE.TOLOCAL);
249          let fullReferencePath = path.resolve(path.dirname(url), referencePath);
250          let module = referencesModuleMap.get(fullReferencePath);
251          for (const key in module) {
252            if (Object.hasOwnProperty.call(module, key)) {
253              globalModules.set(key, index);
254            }
255          }
256          return { modules: module, fullReferencePath: fullReferencePath, reference: element, isUsed: false };
257        });
258      }
259      if (ts.isSourceFile(node) && node.statements) {
260        const newStatements = [];
261        node.statements.forEach((statement) => {
262          if (ts.isImportDeclaration(statement)) {
263            // 是import节点 import { AsyncCallback } from './@ohos.base';
264            const clauseSet = new Set([]);
265            if (statement.importClause && ts.isImportClause(statement.importClause)) {
266              // 标识符
267              const clauseNode = statement.importClause;
268              if (!clauseNode.namedBindings && clauseNode.name && ts.isIdentifier(clauseNode.name)) {
269                // 没有大括号的标识符
270                clauseSet.add(clauseNode.name.escapedText.toString());
271              } else if (
272                clauseNode.namedBindings &&
273                clauseNode.namedBindings.name &&
274                ts.isIdentifier(clauseNode.namedBindings.name)
275              ) {
276                // 没有标识符 *号
277                clauseSet.add(clauseNode.namedBindings.name.escapedText.toString());
278              } else if (clauseNode.namedBindings && clauseNode.namedBindings.elements) {
279                // 有花括号的标识符
280                clauseNode.namedBindings.elements.forEach((ele) => {
281                  if (ele.name && ts.isIdentifier(ele.name)) {
282                    clauseSet.add(ele.name.escapedText.toString());
283                  }
284                });
285              }
286            }
287            const importSpecifier = statement.moduleSpecifier.getText().replace(/[\'\"]/g, '');
288            const importSpecifierRealPath = path.resolve(url, `../${importSpecifier}.d.ts`); // import 文件路径判断
289            let hasImportSpecifierFile = fs.existsSync(importSpecifierRealPath);
290            let hasImportSpecifierInModules = globalModules.has(importSpecifier);
291            if ((hasImportSpecifierFile || hasImportSpecifierInModules) && clauseSet.size > 0) {
292              let currModule = [];
293              if (hasImportSpecifierInModules) {
294                let index = globalModules.get(importSpecifier);
295                currModule = currReferencesModule[index].modules[importSpecifier];
296              }
297              const clasueCheckList = [];
298              let exsitClauseSet = new Set([]);
299              for (const clause of clauseSet) {
300                let flag = allIdentifierSet.has(clause);
301                if (hasImportSpecifierInModules) {
302                  flag = allIdentifierSet.has(clause) && currModule.includes(clause);
303                }
304                if (flag) {
305                  // 标识符使用到了当前import中的引用
306                  exsitClauseSet.add(clause);
307                  clasueCheckList.push('exist');
308                } else {
309                  clasueCheckList.push('non-exist');
310                }
311              }
312              let hasExsitStatus = false;
313              let hasNonExsitStatus = false;
314              clasueCheckList.forEach((ele) => {
315                if (ele === 'exist') {
316                  hasExsitStatus = true;
317                } else {
318                  hasNonExsitStatus = true;
319                }
320              });
321              if (hasExsitStatus) {
322                // 有使用到的标识符
323                if (hasNonExsitStatus) {
324                  // 有没有使用到的标识符
325                  const newSpecifiers = [];
326                  statement.importClause.namedBindings.elements.forEach((element) => {
327                    if (exsitClauseSet.has(element.name.escapedText.toString())) {
328                      newSpecifiers.push(element);
329                    }
330                  });
331                  statement.importClause.namedBindings = ts.factory.updateNamedImports(
332                    statement.importClause.namedBindings,
333                    newSpecifiers
334                  );
335                }
336                if (hasImportSpecifierInModules) {
337                  let index = globalModules.get(importSpecifier);
338                  currReferencesModule[index].isUsed = true;
339                }
340                newStatements.push(statement);
341              } else if (hasCopyright(statement)) {
342                copyrightMessage = node.getFullText().replace(node.getText(), '');
343                isCopyrightDeleted = true;
344              }
345            } else if (hasCopyright(statement)) {
346              copyrightMessage = node.getFullText().replace(node.getText(), '');
347              isCopyrightDeleted = true;
348            }
349          } else {
350            newStatements.push(statement);
351          }
352        });
353        currReferencesModule.forEach((item) => {
354          if (item.isUsed) {
355            referencesMessage += item.reference + '\n';
356          }
357        });
358        node = ts.factory.updateSourceFile(node, newStatements);
359      }
360      return node;
361    }
362    function hasCopyright(node) {
363      return /http\:\/\/www\.apache\.org\/licenses\/LICENSE\-2\.0/g.test(
364        node.getFullText().replace(node.getText(), '')
365      );
366    }
367  };
368}
369
370/**
371 * 每个文件处理前回调函数第一个
372 * @callback deleteSystemApi
373 * @param {string} url 文件路径
374 * @returns {Function}
375 */
376function deleteSystemApi(url) {
377  return (context) => {
378    return (node) => {
379      sourceFile = node;
380      node = processSourceFile(node); // 处理最外层节点
381      node = processVisitEachChild(context, node);
382      if (!isEmptyFile(node)) {
383        const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
384        const result = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
385        if (referencesMap.has(url)) {
386          resolveReferences(url);
387        }
388        const fileName = path.basename(url).replace(/.d.ts/g, '.ts');
389        ts.transpileModule(result, {
390          compilerOptions: {
391            target: ts.ScriptTarget.ES2017,
392          },
393          fileName: fileName,
394          transformers: { before: [formatImportDeclaration(url)] },
395        });
396      }
397      return node;
398    };
399  };
400}
401
402exports.deleteSystemApi = deleteSystemApi;
403
404/**
405 * 遍历每个文件下的所有节点,然后删除节点
406 * @param node
407 * @returns
408 */
409
410/**
411 * 处理最外层的节点看是否删除
412 * @param  node 解析过后的节点
413 * @returns
414 */
415function processSourceFile(node) {
416  const newStatements = [];
417  const newStatementsWithoutExport = [];
418  const deleteSystemApiSet = new Set();
419  node.statements.forEach((statement) => {
420    if (isSystemapi(statement)) {
421      if (ts.isVariableStatement(statement)) {
422        deleteSystemApiSet.add(variableStatementGetEscapedText(statement));
423      } else if (
424        ts.isModuleDeclaration(statement) ||
425        ts.isInterfaceDeclaration(statement) ||
426        ts.isClassDeclaration(statement) ||
427        ts.isEnumDeclaration(statement)
428      ) {
429        if (statement && statement.name && statement.name.escapedText) {
430          deleteSystemApiSet.add(statement.name.escapedText.toString());
431        }
432      }
433    } else {
434      newStatements.push(statement);
435    }
436  });
437  newStatements.forEach((statement) => {
438    const names = getExportIdentifierName(statement);
439    if (names.length === 0) {
440      newStatementsWithoutExport.push(statement);
441      return;
442    }
443    if (names.length === 1 && !deleteSystemApiSet.has(names[0])) {
444      //exports.name = test;
445      //export default test1
446      //export {test1}
447      newStatementsWithoutExport.push(statement);
448      return;
449    }
450    //export {test1 as test,testa as test2}
451    if (ts.isExportDeclaration(statement)) {
452      let needExport = false;
453      const newSpecifiers = [];
454      names.forEach((name, index) => {
455        if (!deleteSystemApiSet.has(name)) {
456          newSpecifiers.push(statement.exportClause.elements[index]);
457          needExport = true;
458        }
459      });
460      if (needExport) {
461        statement.exportClause = ts.factory.updateNamedExports(statement.exportClause, newSpecifiers);
462        newStatementsWithoutExport.push(statement);
463      }
464    }
465  });
466  return ts.factory.updateSourceFile(node, newStatementsWithoutExport, node.isDeclarationFile, node.referencedFiles);
467}
468/**
469 * 获取export节点的名字,只获取第一个关键词
470 * @param {ts.node} statement
471 * @returns {Array<string>}
472 */
473function getExportIdentifierName(statement) {
474  const names = [];
475  if (ts.isExpressionStatement(statement)) {
476    //exports.name = test;
477    if (ts.isBinaryExpression(statement.expression)) {
478      names.push(statement.expression.right.escapedText.toString());
479    }
480  } else if (ts.isExportAssignment(statement)) {
481    //export default test1
482    names.push(statement.expression.escapedText.toString());
483  } else if (ts.isExportDeclaration(statement)) {
484    //export {test1} 、export {test1 as test} 、export * from './featureability'
485    const exportClause = statement.exportClause;
486    if (exportClause) {
487      const specifiers = exportClause.elements;
488      specifiers.forEach((specifier) => {
489        if (ts.isExportSpecifier(specifier)) {
490          const name = specifier.propertyName ? specifier.propertyName : specifier.name;
491          names.push(name.escapedText.toString());
492        }
493      });
494    }
495  }
496  return names;
497}
498
499/**
500 * 遍历处理tsnode节点
501 * @param  context 解析过后的内容
502 * @param  node 解析过后的节点
503 * @returns ts.node
504 */
505function processVisitEachChild(context, node) {
506  return ts.visitEachChild(node, processAllNodes, context); // 遍历所有子节点
507  function processAllNodes(node) {
508    if (ts.isInterfaceDeclaration(node)) {
509      const newMembers = [];
510      node.members.forEach((member) => {
511        if (!isSystemapi(member)) {
512          newMembers.push(member);
513        }
514      });
515      node = ts.factory.updateInterfaceDeclaration(
516        node,
517        node.modifiers,
518        node.name,
519        node.typeParameters,
520        node.heritageClauses,
521        newMembers
522      );
523    } else if (ts.isClassDeclaration(node)) {
524      const newMembers = [];
525      node.members.forEach((member) => {
526        if (!isSystemapi(member)) {
527          newMembers.push(member);
528        }
529      });
530      node = ts.factory.updateClassDeclaration(
531        node,
532        node.modifiers,
533        node.name,
534        node.typeParameters,
535        node.heritageClauses,
536        newMembers
537      );
538    } else if (ts.isModuleDeclaration(node) && node.body && ts.isModuleBlock(node.body)) {
539      const newStatements = [];
540      node.body.statements.forEach((statement) => {
541        if (!isSystemapi(statement)) {
542          newStatements.push(statement);
543        }
544      });
545      const newModuleBody = ts.factory.updateModuleBlock(node.body, newStatements);
546      node = ts.factory.updateModuleDeclaration(node, node.modifiers, node.name, newModuleBody);
547    } else if (ts.isEnumDeclaration(node)) {
548      const newMembers = [];
549      node.members.forEach((member) => {
550        if (!isSystemapi(member)) {
551          newMembers.push(member);
552        }
553      });
554      node = ts.factory.updateEnumDeclaration(node, node.modifiers, node.name, newMembers);
555    }
556    return ts.visitEachChild(node, processAllNodes, context);
557  }
558}
559
560/**
561 * 解析reference
562 * @param {string} url reference文件地址
563 */
564function resolveReferences(url) {
565  const obj = referencesMap.get(url);
566  let references = obj.references;
567  if (!references || references.length === 0) {
568    return;
569  }
570  for (let index = 0; index < references.length; index++) {
571    const element = references[index];
572    element.match(PATT.GET_REFERENCEURL);
573    let referencePath = RegExp.$2;
574    referencePath = referencesToOthers(referencePath, REFERENCE_TYPE.TOLOCAL);
575    let fullReferencePath = path.resolve(path.dirname(url), referencePath);
576    if (fs.existsSync(fullReferencePath) && !referencesModuleMap.has(fullReferencePath)) {
577      const content = fs.readFileSync(fullReferencePath, 'utf-8'); //文件内容
578      const fileName = path.basename(fullReferencePath).replace(/.d.ts/g, '.ts');
579      ts.transpileModule(content, {
580        compilerOptions: {
581          target: ts.ScriptTarget.ES2017,
582        },
583        fileName: fileName,
584        transformers: { before: [resolveCallback(fullReferencePath)] },
585      });
586    }
587  }
588}
589function resolveCallback(url) {
590  return (context) => {
591    const allReferencesIdentifierSet = new Set([]);
592    let allModule = {};
593    return (node) => {
594      const referenceSourceFile = node;
595      node.statements.forEach((statement) => {
596        if (
597          ts.isModuleDeclaration(statement) &&
598          statement.name &&
599          ts.isStringLiteral(statement.name) &&
600          statement.body &&
601          ts.isModuleBlock(statement.body) &&
602          !isSystemapi(statement)
603        ) {
604          ts.visitEachChild(statement, collectAllIdentifier, context);
605          dealExternalStatements(referenceSourceFile);
606          allModule[statement.name.text.toString()] = [...allReferencesIdentifierSet];
607          allReferencesIdentifierSet.clear();
608        }
609      });
610      referencesModuleMap.set(url, allModule);
611      allModule = {};
612      return node;
613    };
614    function dealExternalStatements(node) {
615      node.statements.forEach((statement) => {
616        let name = '';
617        if (isSystemapi(statement)) {
618          if (ts.isVariableStatement(statement)) {
619            name = variableStatementGetEscapedText(statement);
620          } else if (
621            ts.isInterfaceDeclaration(statement) ||
622            ts.isClassDeclaration(statement) ||
623            ts.isEnumDeclaration(statement)
624          ) {
625            if (statement && statement.name && statement.name.escapedText) {
626              name = statement.name.escapedText.toString();
627            }
628          }
629          allReferencesIdentifierSet.delete(name);
630        }
631      });
632    }
633    function collectAllIdentifier(node) {
634      if (isSystemapi(node)) {
635        return;
636      }
637      if (ts.isIdentifier(node)) {
638        allReferencesIdentifierSet.add(node.escapedText.toString());
639      }
640      return ts.visitEachChild(node, collectAllIdentifier, context);
641    }
642  };
643}
644
645function variableStatementGetEscapedText(statement) {
646  let name = '';
647  if (
648    statement &&
649    statement.declarationList &&
650    statement.declarationList.declarations &&
651    statement.declarationList.declarations.length > 0 &&
652    statement.declarationList.declarations[0].name &&
653    statement.declarationList.declarations[0].name.escapedText
654  ) {
655    name = statement.declarationList.declarations[0].name.escapedText.toString();
656  }
657  return name;
658}
659
660function isSystemapi(node) {
661  const notesContent = node.getFullText().replace(node.getText(), '').replace(/[\s]/g, '');
662  const notesArr = notesContent.split(/\/\*\*/);
663  const notesStr = notesArr[notesArr.length - 1];
664  if (notesStr.length !== 0) {
665    if (ts.isFunctionDeclaration(node) || ts.isMethodSignature(node) || ts.isMethodDeclaration(node)) {
666      lastNodeName = node.name && node.name.escapedText ? node.name.escapedText.toString() : '';
667      lastNoteStr = notesStr;
668    }
669    return /@systemapi/g.test(notesStr);
670  } else {
671    if (
672      (ts.isFunctionDeclaration(node) || ts.isMethodSignature(node) || ts.isMethodDeclaration(node)) &&
673      node.name &&
674      node.name.escapedText.toString() !== '' &&
675      node.name.escapedText.toString() === lastNodeName
676    ) {
677      return /@systemapi/g.test(lastNoteStr);
678    } else {
679      return false;
680    }
681  }
682}
683
684function isEmptyFile(node) {
685  let isEmpty = true;
686  if (ts.isSourceFile(node) && node.statements) {
687    for (let i = 0; i < node.statements.length; i++) {
688      const statement = node.statements[i];
689      if (ts.isImportDeclaration(statement)) {
690        continue;
691      }
692      isEmpty = false;
693      break;
694    }
695  }
696  return isEmpty;
697}
698
699const apiSourcePath = '../api';
700const outputPath = path.resolve(__dirname, 'output');
701collectDeclaration(apiSourcePath); //入口
702