• 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();
24const kitFileNeedDeleteMap = new Map();
25/**
26 * @enum {string} references地址的切换类型
27 */
28const REFERENCE_TYPE = {
29  TOLOCAL: 'toLocal',
30  TORIGHTSDK: 'toRightSDK',
31  TOSDK: 'toSDK',
32};
33const PATT = {
34  GET_REFERENCE: /\/\/\/\s*<reference.*>/g,
35  GET_REFERENCEURL: /\/\/\/\s*<reference\s*path=("|')(.*)("|')\s*\/>/g,
36  REFERENCEURL_LOCAL: /(.\/)?(\S*)@internal\/component\/ets\/(\S*)/g,
37  REFERENCEURL_RIGHTSDK: /(..\/)(\S*)build-tools\/ets-loader\/declarations\/(\S*)/g,
38  REFERENCEURL_SDK: /(..\/)(\S*)component\/(\S*)/g,
39};
40function collectDeclaration(url) {
41  // 入口
42  try {
43    const utPath = path.resolve(__dirname, url);
44    const arktsPath = path.resolve(utPath, '../arkts');
45    const kitPath = path.resolve(utPath, '../kits');
46    const utFiles = [];
47    readFile(utPath, utFiles); // 读取文件
48    readFile(arktsPath, utFiles); // 读取文件
49    tsTransform(utFiles, deleteSystemApi);
50    tsTransformKitFile(kitPath);
51  } catch (error) {
52    console.error('DELETE_SYSTEM_PLUGIN ERROR: ', error);
53  }
54}
55
56/**
57 * 解析url目录下方的kit文件,删除对应systemapi
58 * @param { string } kitPath kit文件路径
59 */
60function tsTransformKitFile(kitPath) {
61  kitFileNeedDeleteMap.delete('');
62  if (kitFileNeedDeleteMap.length === 0) {
63    return;
64  }
65  const kitFiles = [];
66  readFile(kitPath, kitFiles); // 读取文件
67  kitFiles.forEach((kitFile) => {
68    const kitName = processFileNameWithoutExt(kitFile).replace('@kit.', '');
69    const content = fs.readFileSync(kitFile, 'utf-8');
70    const fileName = processFileName(kitFile);
71    let sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.ES2017, true);
72    const sourceInfo = getKitNewSourceFile(sourceFile, kitName);
73    if (isEmptyFile(sourceInfo.sourceFile)) {
74      return;
75    }
76    const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
77    let result = printer.printNode(ts.EmitHint.Unspecified, sourceInfo.sourceFile, sourceFile);
78    if (sourceInfo.copyrightMessage !== '') {
79      result = sourceInfo.copyrightMessage + result;
80    }
81    writeFile(kitFile, result);
82  });
83}
84
85/**
86 * 处理kit中需要删除的节点,在其他文件被systemapi修饰的api
87 * @param { ts.SourceFile } sourceFile
88 * @param { string } kitName
89 * @returns 删除完的节点,全部删除为空字符串
90 */
91function getKitNewSourceFile(sourceFile, kitName) {
92  const newStatements = [];
93  const needDeleteExportName = new Set();
94  let copyrightMessage = '';
95  // 初始化ts工厂
96  const factory = ts.factory;
97  sourceFile.statements.forEach((statement, index) => {
98    if (ts.isImportDeclaration(statement)) {
99      const newStatement = processKitImportDeclaration(statement, needDeleteExportName);
100      if (newStatement) {
101        newStatements.push(newStatement);
102      } else if (index === 0) {
103        copyrightMessage = sourceFile.getFullText().replace(sourceFile.getText(), '');
104      }
105    } else if (ts.isExportDeclaration(statement)) {
106      const exportSpecifiers = statement.exportClause.elements.filter((item) => {
107        return !needDeleteExportName.has(item.name.escapedText.toString());
108      });
109      if (exportSpecifiers.length !== 0) {
110        statement.exportClause = factory.updateNamedExports(statement.exportClause, exportSpecifiers);
111        newStatements.push(statement);
112      }
113    }
114  });
115  sourceFile = factory.updateSourceFile(sourceFile, newStatements);
116  return { sourceFile, copyrightMessage };
117}
118
119/**
120 * 根据节点和需要删除的节点数据生成新节点
121 * @param { ts.ImportDeclaration } statement 需要处理的import节点
122 * @param { Map} needDeleteMap 需要删除的节点数据
123 * @param { Map} needDeleteExportName 需要删除的导出节点
124 * @returns { ts.ImportDeclaration | undefined } 返回新的import节点,全部删除为undefined
125 */
126function processKitImportDeclaration(statement, needDeleteExportName) {
127  // 初始化ts工厂
128  const factory = ts.factory;
129  const importClause = statement.importClause;
130  if (!ts.isImportClause(importClause)) {
131    return statement;
132  }
133  const importPath = statement.moduleSpecifier.text.replace('../', '');
134  if (kitFileNeedDeleteMap === undefined || !kitFileNeedDeleteMap.has(importPath)) {
135    const hasFilePath = hasFileByImportPath(importPath);
136    return hasFilePath ? statement : undefined;
137  }
138  const currImportInfo = kitFileNeedDeleteMap.get(importPath);
139  let defaultName = '';
140  let importNodeNamedBindings = [];
141  if (importClause.name) {
142    if (currImportInfo.default === importClause.name.escapedText.toString()) {
143      //import buffer from "../@ohos.buffer";
144      needDeleteExportName.add(currImportInfo.default);
145    } else {
146      defaultName = importClause.name.escapedText.toString();
147    }
148  }
149  const namedBindings = importClause.namedBindings;
150  if (namedBindings !== undefined && ts.isNamedImports(namedBindings)) {
151    const elements = namedBindings.elements;
152    elements.forEach((element) => {
153      const exportName = element.propertyName ?
154        element.propertyName.escapedText.toString() :
155        element.name.escapedText.toString();
156      if (!currImportInfo.exportName.has(exportName)) {
157        importNodeNamedBindings.push(factory.createImportSpecifier(false, element.propertyName, element.name));
158      } else {
159        needDeleteExportName.add(element.name.escapedText.toString());
160      }
161    });
162  }
163  if (defaultName !== '' || importNodeNamedBindings.length !== 0) {
164    const newImportNode = factory.createImportDeclaration(
165      undefined,
166      factory.createImportClause(
167        false,
168        defaultName === '' ? undefined : factory.createIdentifier(defaultName),
169        importNodeNamedBindings.length === 0 ? undefined : factory.createNamedImports(importNodeNamedBindings)
170      ),
171      statement.moduleSpecifier
172    );
173    return newImportNode;
174  }
175  return undefined;
176}
177
178/**
179 * 判断文件路径对应的文件是否存在
180 * @param {string} importPath kit文件import
181 * @returns {boolean} importPath是否存在
182 */
183function hasFileByImportPath(importPath) {
184  let fileDir = path.resolve(apiSourcePath);
185  if (importPath.startsWith('@arkts')) {
186    fileDir = path.resolve(apiSourcePath, '../arkts');
187  }
188  const flag = ['.d.ts', '.d.ets'].some(ext => {
189    const filePath = path.resolve(fileDir, `${importPath}${ext}`);
190    return fs.existsSync(filePath);
191  });
192  return flag;
193}
194
195/**
196 * 统一处理文件名称,修改后缀等
197 * @param {string} filePath 文件路径
198 * @returns {string} filename 文件名称
199 */
200function processFileName(filePath) {
201  return path
202    .basename(filePath)
203    .replace(/\.d\.ts$/g, '.ts')
204    .replace(/\.d\.ets$/g, '.ets');
205}
206
207function processFileNameWithoutExt(filePath) {
208  return path
209    .basename(filePath)
210    .replace(/\.d\.ts$/g, '')
211    .replace(/\.d\.ets$/g, '')
212    .replace(/\.ts$/g, '')
213    .replace(/\.ets$/g, '');
214}
215
216/**
217 * 遍历所有文件进行处理
218 * @param {Array} utFiles 所有文件
219 * @param {deleteSystemApi} callback 回调函数
220 */
221function tsTransform(utFiles, callback) {
222  utFiles.forEach((url) => {
223    const apiBaseName = path.basename(url);
224    if (/\.json/.test(url) || apiBaseName === 'index-full.d.ts') {
225      // 特殊类型文件处理
226      const content = fs.readFileSync(url, 'utf-8');
227      writeFile(url, content);
228    } else if (/\.d\.ts/.test(apiBaseName) || /\.d\.ets/.test(apiBaseName)) {
229      // dts文件处理
230      let content = fs.readFileSync(url, 'utf-8'); // 文件内容
231      const fileName = processFileName(url);
232      let references = content.match(PATT.GET_REFERENCE);
233      if (references) {
234        referencesMap.set(url, { references: references });
235        for (let index = 0; index < references.length; index++) {
236          const item = references[index];
237          content = content.replace(item, '');
238        }
239      }
240      ts.transpileModule(content, {
241        compilerOptions: {
242          target: ts.ScriptTarget.ES2017,
243        },
244        fileName: fileName,
245        transformers: { before: [callback(url)] },
246      });
247    }
248  });
249}
250/**
251 * 切换references或者references中path的格式
252 * @param {string} references references或者references中的path
253 * @param {REFERENCE_TYPE} reverse 切换类型
254 * @returns {string}
255 */
256function referencesToOthers(references, type) {
257  let referencesurl;
258  let hasFullpatt = references.match(PATT.GET_REFERENCEURL);
259  let isFullReferenceurl = hasFullpatt && hasFullpatt.length > 0;
260  if (isFullReferenceurl) {
261    referencesurl = RegExp.$2;
262  } else {
263    referencesurl = references;
264  }
265  let currentType = '';
266  if (referencesurl.match(PATT.REFERENCEURL_LOCAL)) {
267    currentType = REFERENCE_TYPE.TOLOCAL;
268  } else if (referencesurl.match(PATT.REFERENCEURL_RIGHTSDK)) {
269    currentType = REFERENCE_TYPE.TORIGHTSDK;
270  } else if (referencesurl.match(PATT.REFERENCEURL_SDK)) {
271    currentType = REFERENCE_TYPE.TOSDK;
272  }
273  if (currentType === '' || currentType === type) {
274    return references;
275  }
276  let starturl = '';
277  let fileName = '';
278  switch (currentType) {
279    case REFERENCE_TYPE.TOLOCAL:
280      starturl = RegExp.$2;
281      fileName = RegExp.$3;
282      break;
283    case REFERENCE_TYPE.TORIGHTSDK:
284      starturl = RegExp.$2;
285      fileName = RegExp.$3;
286      break;
287    case REFERENCE_TYPE.TOSDK:
288      starturl = RegExp.$2;
289      fileName = RegExp.$3;
290      break;
291    default:
292      break;
293  }
294  let finallyurl;
295  switch (type) {
296    case REFERENCE_TYPE.TOLOCAL:
297      finallyurl = `${starturl === '' ? './' : ''}${starturl}@internal/component/ets/${fileName}`;
298      break;
299    case REFERENCE_TYPE.TORIGHTSDK:
300      finallyurl = `../${starturl}build-tools/ets-loader/declarations/${fileName}`;
301      break;
302    case REFERENCE_TYPE.TOSDK:
303      finallyurl = `../${starturl}component/${fileName}`;
304      break;
305    default:
306      break;
307  }
308  if (isFullReferenceurl) {
309    finallyurl = `/// <reference path="${finallyurl}"/>`;
310  }
311  return finallyurl;
312}
313
314/**
315 * 读取目录下所有文件
316 * @param {string} dir 文件目录
317 * @param {Array} utFiles 所有文件
318 */
319function readFile(dir, utFiles) {
320  try {
321    const files = fs.readdirSync(dir);
322    files.forEach((element) => {
323      const filePath = path.join(dir, element);
324      const status = fs.statSync(filePath);
325      if (status.isDirectory()) {
326        readFile(filePath, utFiles);
327      } else {
328        utFiles.push(filePath);
329      }
330    });
331  } catch (e) {
332    console.log('ETS ERROR: ' + e);
333  }
334}
335
336function writeFile(url, data, option) {
337  if (fs.existsSync(outputPath)) {
338    fs.rmdirSync(outputPath, { recursive: true });
339  }
340  const newFilePath = path.resolve(outputPath, path.relative(__dirname, url));
341  fs.mkdir(path.dirname(newFilePath), { recursive: true }, (err) => {
342    if (err) {
343      console.log(`ERROR FOR CREATE PATH ${err}`);
344    } else {
345      if (data === '') {
346        fs.rmSync(newFilePath);
347        return;
348      }
349      fs.writeFileSync(newFilePath, data, option, (err) => {
350        if (err) {
351          console.log(`ERROR FOR CREATE FILE ${err}`);
352        }
353      });
354    }
355  });
356}
357
358const globalModules = new Map();
359
360/**
361 * 每个文件处理前回调函数第二个
362 * @param {string} url 文件路径
363 * @returns {Function}
364 */
365function formatImportDeclaration(url, copyrightMessage = '', isCopyrightDeleted = false) {
366  return (context) => {
367    const allIdentifierSet = new Set([]);
368    return (node) => {
369      sourceFile = node;
370      collectAllIdentifier(node); // 获取所有标识符
371      formatValue = formatAllNodes(url, node, allIdentifierSet); // 获取所有节点
372      node = formatValue.node;
373      const referencesMessage = formatValue.referencesMessage;
374      if (formatValue.isCopyrightDeleted) {
375        copyrightMessage = formatValue.copyrightMessage;
376        isCopyrightDeleted = formatValue.isCopyrightDeleted;
377      }
378      if (!isEmptyFile(node)) {
379        const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
380        let result = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
381        if (isCopyrightDeleted) {
382          // 当第一个节点被删除时会同步删除整个文件jsdoc
383          result = copyrightMessage + '\n' + result;
384        }
385        copyrightMessage = node.getFullText().replace(node.getText(), '');
386        if (referencesMessage) {
387          // 将references写入文件
388          result =
389            result.substring(0, copyrightMessage.length) +
390            '\n' +
391            referencesMessage +
392            result.substring(copyrightMessage.length);
393        }
394        result = removeSystemapiDoc(result);
395        writeFile(url, result);
396      }
397      return ts.factory.createSourceFile([], ts.SyntaxKind.EndOfFileToken, ts.NodeFlags.None);
398    };
399    function collectAllIdentifier(node) {
400      if (ts.isSourceFile(node) && node.statements) {
401        node.statements.forEach((stat) => {
402          if (!ts.isImportDeclaration(stat)) {
403            ts.visitEachChild(stat, collectAllNodes, context);
404          }
405        });
406      }
407    }
408    function collectAllNodes(node) {
409      if (ts.isIdentifier(node)) {
410        allIdentifierSet.add(node.escapedText.toString());
411      }
412      return ts.visitEachChild(node, collectAllNodes, context);
413    }
414  };
415}
416
417function formatAllNodes(url, node, allIdentifierSet, copyrightMessage = '', isCopyrightDeleted = false) {
418  let referencesMessage = '';
419  let currReferencesModule = formatAllNodesReferences(url);
420  if (ts.isSourceFile(node) && node.statements) {
421    const newStatements = [];
422    node.statements.forEach((statement) => {
423      if (ts.isImportDeclaration(statement)) {
424        const importInfo = formatAllNodesImportDeclaration(
425          node,
426          statement,
427          url,
428          currReferencesModule,
429          allIdentifierSet
430        );
431        if (importInfo.statement) {
432          newStatements.push(statement);
433        } else if (importInfo.isCopyrightDeleted) {
434          copyrightMessage = importInfo.copyrightMessage;
435          isCopyrightDeleted = importInfo.isCopyrightDeleted;
436        }
437      } else if (ts.isStructDeclaration(statement)) {
438        statement = ts.factory.updateStructDeclaration(
439          statement,
440          statement.modifiers,
441          statement.name,
442          statement.typeParameters,
443          statement.heritageClauses,
444          statement.members.slice(1)
445        );
446        newStatements.push(statement);
447      } else {
448        newStatements.push(statement);
449      }
450    });
451    currReferencesModule.forEach((item) => {
452      if (item.isUsed) {
453        referencesMessage += item.reference + '\n';
454      }
455    });
456    node = ts.factory.updateSourceFile(node, newStatements);
457  }
458  return { node, referencesMessage, copyrightMessage, isCopyrightDeleted };
459}
460
461function hasCopyright(node) {
462  return /http\:\/\/www\.apache\.org\/licenses\/LICENSE\-2\.0/g.test(node.getFullText().replace(node.getText(), ''));
463}
464
465/**
466 * 处理References节点
467 * @param {ts.node} node 当前节点
468 * @param {string} url 文件路径
469 * @returns {Array} currReferencesModule 返回该文件的references数据
470 */
471function formatAllNodesReferences(url) {
472  globalModules.clear();
473  let currReferences = [];
474  let currReferencesModule = [];
475  if (referencesMap.has(url)) {
476    currReferences = referencesMap.get(url);
477    currReferencesModule = currReferences.references.map((element, index) => {
478      element.match(PATT.GET_REFERENCEURL);
479      let referencePath = RegExp.$2;
480      referencePath = referencesToOthers(referencePath, REFERENCE_TYPE.TOLOCAL);
481      let fullReferencePath = path.resolve(path.dirname(url), referencePath);
482      let module = referencesModuleMap.get(fullReferencePath);
483      for (const key in module) {
484        if (Object.hasOwnProperty.call(module, key)) {
485          globalModules.set(key, index);
486        }
487      }
488      return { modules: module, fullReferencePath: fullReferencePath, reference: element, isUsed: false };
489    });
490  }
491  return currReferencesModule;
492}
493
494/**
495 * 处理Import节点 去除未使用、不存在、References中没有对应模块的导入
496 * @param {ts.node} node 当前节点
497 * @param {ts.ImportDeclaration} statement 导入节点
498 * @param {string} url 文件路径
499 * @param {string} url 文件路径
500 * @param {Set} allIdentifierSet 该文件的所有Identifier关键字
501 * @returns {{statement:ts.ImportDeclaration,copyrightMessage:string,isCopyrightDeleted:boolean}} statement 处理完成的导入节点、copyrightMessage
502 */
503function formatAllNodesImportDeclaration(node, statement, url, currReferencesModule, allIdentifierSet) {
504  // 是import节点 import { AsyncCallback } from './@ohos.base';
505  const clauseSet = new Set([]);
506  if (statement.importClause && ts.isImportClause(statement.importClause)) {
507    // 标识符
508    const clauseNode = statement.importClause;
509    if (!clauseNode.namedBindings && clauseNode.name && ts.isIdentifier(clauseNode.name)) {
510      // 没有大括号的标识符
511      clauseSet.add(clauseNode.name.escapedText.toString());
512    } else if (
513      clauseNode.namedBindings &&
514      clauseNode.namedBindings.name &&
515      ts.isIdentifier(clauseNode.namedBindings.name)
516    ) {
517      // 没有标识符 *号
518      clauseSet.add(clauseNode.namedBindings.name.escapedText.toString());
519    } else if (clauseNode.namedBindings && clauseNode.namedBindings.elements) {
520      // 有花括号的标识符
521      clauseNode.namedBindings.elements.forEach((ele) => {
522        if (ele.name && ts.isIdentifier(ele.name)) {
523          clauseSet.add(ele.name.escapedText.toString());
524        }
525      });
526    }
527  }
528  const importSpecifier = statement.moduleSpecifier.getText().replace(/[\'\"]/g, '');
529  const dtsImportSpecifierPath = path.resolve(url, `../${importSpecifier}.d.ts`); // import 文件路径判断
530  const detsImportSpecifierPath = path.resolve(url, `../${importSpecifier}.d.ets`); // import 文件路径判断
531  let hasImportSpecifierFile = fs.existsSync(dtsImportSpecifierPath) || fs.existsSync(detsImportSpecifierPath);
532  let hasImportSpecifierInModules = globalModules.has(importSpecifier);
533  if ((hasImportSpecifierFile || hasImportSpecifierInModules) && clauseSet.size > 0) {
534    let currModule = [];
535    if (hasImportSpecifierInModules) {
536      let index = globalModules.get(importSpecifier);
537      currModule = currReferencesModule[index].modules[importSpecifier];
538    }
539    const clasueCheckList = [];
540    let exsitClauseSet = new Set([]);
541    for (const clause of clauseSet) {
542      let flag = allIdentifierSet.has(clause);
543      if (hasImportSpecifierInModules) {
544        flag = allIdentifierSet.has(clause) && currModule.includes(clause);
545      }
546      if (flag) {
547        // 标识符使用到了当前import中的引用
548        exsitClauseSet.add(clause);
549        clasueCheckList.push('exist');
550      } else {
551        clasueCheckList.push('non-exist');
552      }
553    }
554    let hasExsitStatus = false;
555    let hasNonExsitStatus = false;
556    clasueCheckList.forEach((ele) => {
557      if (ele === 'exist') {
558        hasExsitStatus = true;
559      } else {
560        hasNonExsitStatus = true;
561      }
562    });
563    if (hasExsitStatus) {
564      // 有使用到的标识符
565      if (hasNonExsitStatus) {
566        // 有没有使用到的标识符
567        const newSpecifiers = [];
568        statement.importClause.namedBindings.elements.forEach((element) => {
569          if (exsitClauseSet.has(element.name.escapedText.toString())) {
570            newSpecifiers.push(element);
571          }
572        });
573        statement.importClause.namedBindings = ts.factory.updateNamedImports(
574          statement.importClause.namedBindings,
575          newSpecifiers
576        );
577      }
578      if (hasImportSpecifierInModules) {
579        let index = globalModules.get(importSpecifier);
580        currReferencesModule[index].isUsed = true;
581      }
582      return { statement };
583    } else if (hasCopyright(statement)) {
584      return { copyrightMessage: node.getFullText().replace(node.getText(), ''), isCopyrightDeleted: true };
585    }
586  } else if (hasCopyright(statement)) {
587    return { copyrightMessage: node.getFullText().replace(node.getText(), ''), isCopyrightDeleted: true };
588  }
589  return { statement: undefined, copyrightMessage: '', isCopyrightDeleted: false };
590}
591
592/**
593 *
594 * 防止@file和@kit段注释丢失
595 * @param {string} fileFullText
596 * @returns {string}
597 *
598 */
599function getFileAndKitComment(fileFullText) {
600  let fileAndKitComment = '';
601  let pattern = /\/\*\*\s*\*\s*@file[\s\S]*?@kit[\s\S]*?\*\//;
602  let comment = fileFullText.match(pattern);
603  if (comment) {
604    fileAndKitComment = comment[0];
605  }
606  return fileAndKitComment;
607}
608
609/**
610 * 处理最终结果中的systemapi
611 * @param {string} result
612 */
613function removeSystemapiDoc(result) {
614  result.split;
615  return result.replace(/\/\*\*[\s\S]*?\*\//g, (substring, p1) => {
616    return /@systemapi/g.test(substring) ? '' : substring;
617  });
618}
619
620/**
621 * 每个文件处理前回调函数第一个
622 * @callback deleteSystemApi
623 * @param {string} url 文件路径
624 * @returns {Function}
625 */
626function deleteSystemApi(url) {
627  return (context) => {
628    return (node) => {
629      const fullText = String(node.getFullText());
630      //获取文件头部的注释信息--这里可能会涉及到@file和@kit段注释丢失
631      let fileAndKitComment = getFileAndKitComment(fullText);
632      const copyrightMessage = fullText.replace(node.getText(), '').split(/\/\*\*/)[0] + fileAndKitComment + '\n';
633      let kitName = '';
634      if (fullText.match(/\@kit (.*)\r?\n/g)) {
635        kitName = RegExp.$1.replace(/\s/g, '');
636      }
637      sourceFile = node;
638      const deleteNode = processSourceFile(node, kitName); // 处理最外层节点
639      node = processVisitEachChild(context, deleteNode.node);
640      if (!isEmptyFile(node)) {
641        const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
642        const result = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
643        if (referencesMap.has(url)) {
644          resolveReferences(url);
645        }
646        const fileName = processFileName(url);
647        ts.transpileModule(result, {
648          compilerOptions: {
649            target: ts.ScriptTarget.ES2017,
650          },
651          fileName: fileName,
652          transformers: { before: [formatImportDeclaration(url, copyrightMessage, deleteNode.isCopyrightDeleted)] },
653        });
654      }
655      return ts.factory.createSourceFile([], ts.SyntaxKind.EndOfFileToken, ts.NodeFlags.None);
656    };
657  };
658}
659
660exports.deleteSystemApi = deleteSystemApi;
661
662/**
663 * 遍历每个文件下的所有节点,然后删除节点
664 * @param node
665 * @returns
666 */
667
668/**
669 * 处理最外层的节点看是否删除
670 * @param node 解析过后的节点
671 * @param kitName 当前文件kitName
672 * @returns
673 */
674function processSourceFile(node, kitName) {
675  let isCopyrightDeleted = false;
676  const newStatements = [];
677  const newStatementsWithoutExport = [];
678  const deleteSystemApiSet = new Set();
679  let needDeleteExport = {
680    fileName: '',
681    default: '',
682    exportName: new Set(),
683  };
684  isCopyrightDeleted = addNewStatements(node, newStatements, deleteSystemApiSet, needDeleteExport);
685  newStatements.forEach((statement) => {
686    const names = getExportIdentifierName(statement);
687    if (names.length === 0) {
688      newStatementsWithoutExport.push(statement);
689      return;
690    }
691    if (names.length === 1 && !deleteSystemApiSet.has(names[0])) {
692      //exports.name = test;
693      //export default test1
694      //export {test1}
695      newStatementsWithoutExport.push(statement);
696      return;
697    }
698    processExportNode(statement, node, needDeleteExport, names, deleteSystemApiSet, newStatementsWithoutExport);
699  });
700  if (needDeleteExport.fileName !== '') {
701    kitFileNeedDeleteMap.set(needDeleteExport.fileName, needDeleteExport);
702  }
703  return {
704    node: ts.factory.updateSourceFile(node, newStatementsWithoutExport, node.isDeclarationFile, node.referencedFiles),
705    isCopyrightDeleted,
706  };
707}
708
709function processExportNode(statement, node, needDeleteExport, names, deleteSystemApiSet, newStatementsWithoutExport) {
710  //删除export节点信息
711  if (ts.isExportAssignment(statement)) {
712    //export default abilityAccessCtrl;
713    needDeleteExport.fileName = processFileNameWithoutExt(node.fileName);
714    needDeleteExport.default = statement.expression.escapedText.toString();
715  } else if (ts.isExportDeclaration(statement)) {
716    //export {test1 as test,testa as test2}
717    let needExport = false;
718    const newSpecifiers = [];
719    names.forEach((name, index) => {
720      const exportSpecifier = statement.exportClause.elements[index];
721      if (!deleteSystemApiSet.has(name)) {
722        //未被删除的节点
723        newSpecifiers.push(exportSpecifier);
724        needExport = true;
725      } else {
726        //被删除的节点
727        needDeleteExport.fileName = processFileNameWithoutExt(node.fileName);
728        needDeleteExport.exportName.add(exportSpecifier.name.escapedText.toString());
729      }
730    });
731    if (needExport) {
732      statement.exportClause = ts.factory.updateNamedExports(statement.exportClause, newSpecifiers);
733      newStatementsWithoutExport.push(statement);
734    }
735  }
736}
737
738function addNewStatements(node, newStatements, deleteSystemApiSet, needDeleteExport) {
739  let isCopyrightDeleted = false;
740  node.statements.forEach((statement, index) => {
741    if (!isSystemapi(statement)) {
742      newStatements.push(statement);
743      return;
744    }
745    if (index === 0) {
746      isCopyrightDeleted = true;
747    }
748    if (ts.isVariableStatement(statement)) {
749      deleteSystemApiSet.add(variableStatementGetEscapedText(statement));
750    } else if (
751      ts.isModuleDeclaration(statement) ||
752      ts.isInterfaceDeclaration(statement) ||
753      ts.isClassDeclaration(statement) ||
754      ts.isEnumDeclaration(statement) ||
755      ts.isStructDeclaration(statement) ||
756      ts.isTypeAliasDeclaration(statement)
757    ) {
758      if (statement && statement.name && statement.name.escapedText) {
759        deleteSystemApiSet.add(statement.name.escapedText.toString());
760      }
761      setDeleteExport(statement, node, needDeleteExport, deleteSystemApiSet);
762    } else if (ts.isExportAssignment(statement) || ts.isExportDeclaration(statement)) {
763      setDeleteExport(statement, node, needDeleteExport, deleteSystemApiSet);
764    }
765  });
766
767  return isCopyrightDeleted;
768}
769
770function setDeleteExport(statement, node, needDeleteExport, deleteSystemApiSet) {
771  if (ts.isExportAssignment(statement) && deleteSystemApiSet.has(statement.expression.escapedText.toString())) {
772    needDeleteExport.fileName = processFileNameWithoutExt(node.fileName);
773    needDeleteExport.default = statement.expression.escapedText.toString();
774  } else if (ts.isExportDeclaration(statement)) {
775    needDeleteExport.fileName = processFileNameWithoutExt(node.fileName);
776    statement.exportClause.elements.forEach((element) => {
777      const exportName = element.propertyName ?
778        element.propertyName.escapedText.toString() :
779        element.name.escapedText.toString();
780      if (deleteSystemApiSet.has(exportName)) {
781        needDeleteExport.exportName.add(element.name.escapedText.toString());
782      }
783    });
784  }
785  //export namespace test {}
786  const modifiers = statement.modifiers;
787  if (modifiers === undefined) {
788    return;
789  }
790  const exportFlag = modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword);
791  const defaultFlag = modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword);
792  if (exportFlag && defaultFlag) {
793    needDeleteExport.fileName = processFileNameWithoutExt(node.fileName);
794    needDeleteExport.default = statement.name.escapedText.toString();
795  } else if (exportFlag) {
796    needDeleteExport.fileName = processFileNameWithoutExt(node.fileName);
797    needDeleteExport.exportName.add(statement.name.escapedText.toString());
798  }
799}
800
801/**
802 * 获取export节点的名字,只获取第一个关键词
803 * @param {ts.node} statement
804 * @returns {Array<string>}
805 */
806function getExportIdentifierName(statement) {
807  const names = [];
808  if (ts.isExpressionStatement(statement)) {
809    //exports.name = test;
810    if (ts.isBinaryExpression(statement.expression)) {
811      names.push(statement.expression.right.escapedText.toString());
812    }
813  } else if (ts.isExportAssignment(statement)) {
814    //export default test1
815    names.push(statement.expression.escapedText.toString());
816  } else if (ts.isExportDeclaration(statement)) {
817    //export {test1} 、export {test1 as test} 、export * from './featureability'
818    const exportClause = statement.exportClause;
819    if (exportClause) {
820      const specifiers = exportClause.elements;
821      specifiers.forEach((specifier) => {
822        if (ts.isExportSpecifier(specifier)) {
823          const name = specifier.propertyName ? specifier.propertyName : specifier.name;
824          names.push(name.escapedText.toString());
825        }
826      });
827    }
828  }
829  return names;
830}
831
832/**
833 * 遍历处理tsnode节点
834 * @param  context 解析过后的内容
835 * @param  node 解析过后的节点
836 * @returns ts.node
837 */
838function processVisitEachChild(context, node) {
839  return ts.visitEachChild(node, processAllNodes, context); // 遍历所有子节点
840  function processAllNodes(node) {
841    if (ts.isInterfaceDeclaration(node)) {
842      node = processInterfaceDeclaration(node);
843    } else if (ts.isClassDeclaration(node)) {
844      node = processClassDeclaration(node);
845    } else if (ts.isModuleDeclaration(node) && node.body && ts.isModuleBlock(node.body)) {
846      const newStatements = [];
847      node.body.statements.forEach((statement) => {
848        if (!isSystemapi(statement)) {
849          newStatements.push(statement);
850        }
851      });
852      const newModuleBody = ts.factory.updateModuleBlock(node.body, newStatements);
853      node = ts.factory.updateModuleDeclaration(
854        node,
855        node.modifiers,
856        node.name,
857        newModuleBody
858      );
859    } else if (ts.isEnumDeclaration(node)) {
860      node = processEnumDeclaration(node);
861    } else if (ts.isStructDeclaration(node)) {
862      node = processStructDeclaration(node);
863    }
864    return ts.visitEachChild(node, processAllNodes, context);
865  }
866}
867
868/**
869 * 处理interface子节点
870 */
871function processInterfaceDeclaration(node) {
872  const newMembers = [];
873  node.members.forEach((member) => {
874    if (!isSystemapi(member)) {
875      newMembers.push(member);
876    }
877  });
878  node = ts.factory.updateInterfaceDeclaration(
879    node,
880    node.modifiers,
881    node.name,
882    node.typeParameters,
883    node.heritageClauses,
884    newMembers
885  );
886  return node;
887}
888
889/**
890 * 处理class子节点
891 */
892function processClassDeclaration(node) {
893  const newMembers = [];
894  node.members.forEach((member) => {
895    if (!isSystemapi(member)) {
896      newMembers.push(member);
897    }
898  });
899  node = ts.factory.updateClassDeclaration(
900    node,
901    node.modifiers,
902    node.name,
903    node.typeParameters,
904    node.heritageClauses,
905    newMembers
906  );
907  return node;
908}
909
910/**
911 * 处理enum子节点
912 */
913function processEnumDeclaration(node) {
914  const newMembers = [];
915  node.members.forEach((member) => {
916    if (!isSystemapi(member)) {
917      newMembers.push(member);
918    }
919  });
920  node = ts.factory.updateEnumDeclaration(
921    node,
922    node.modifiers,
923    node.name,
924    newMembers
925  );
926  return node;
927}
928
929/**
930 * 处理struct子节点
931 */
932function processStructDeclaration(node) {
933  const newMembers = [];
934  node.members.forEach((member, index) => {
935    if (index >= 1 && !isSystemapi(member)) {
936      newMembers.push(member);
937    }
938  });
939  node = ts.factory.updateStructDeclaration(
940    node,
941    node.modifiers,
942    node.name,
943    node.typeParameters,
944    node.heritageClauses,
945    newMembers
946  );
947  return node;
948}
949
950/**
951 * 解析reference
952 * @param {string} url reference文件地址
953 */
954function resolveReferences(url) {
955  const obj = referencesMap.get(url);
956  let references = obj.references;
957  if (!references || references.length === 0) {
958    return;
959  }
960  for (let index = 0; index < references.length; index++) {
961    const element = references[index];
962    element.match(PATT.GET_REFERENCEURL);
963    let referencePath = RegExp.$2;
964    referencePath = referencesToOthers(referencePath, REFERENCE_TYPE.TOLOCAL);
965    let fullReferencePath = path.resolve(path.dirname(url), referencePath);
966    if (fs.existsSync(fullReferencePath) && !referencesModuleMap.has(fullReferencePath)) {
967      const content = fs.readFileSync(fullReferencePath, 'utf-8'); //文件内容
968      const fileName = processFileName(fullReferencePath);
969      ts.transpileModule(content, {
970        compilerOptions: {
971          target: ts.ScriptTarget.ES2017,
972        },
973        fileName: fileName,
974        transformers: { before: [resolveCallback(fullReferencePath)] },
975      });
976    }
977  }
978}
979function resolveCallback(url) {
980  return (context) => {
981    const allReferencesIdentifierSet = new Set([]);
982    let allModule = {};
983    return (node) => {
984      const referenceSourceFile = node;
985      node.statements.forEach((statement) => {
986        if (
987          ts.isModuleDeclaration(statement) &&
988          statement.name &&
989          ts.isStringLiteral(statement.name) &&
990          statement.body &&
991          ts.isModuleBlock(statement.body) &&
992          !isSystemapi(statement)
993        ) {
994          ts.visitEachChild(statement, collectAllIdentifier, context);
995          dealExternalStatements(referenceSourceFile);
996          allModule[statement.name.text.toString()] = [...allReferencesIdentifierSet];
997          allReferencesIdentifierSet.clear();
998        }
999      });
1000      referencesModuleMap.set(url, allModule);
1001      allModule = {};
1002      return node;
1003    };
1004    function dealExternalStatements(node) {
1005      node.statements.forEach((statement) => {
1006        let name = '';
1007        if (isSystemapi(statement)) {
1008          if (ts.isVariableStatement(statement)) {
1009            name = variableStatementGetEscapedText(statement);
1010          } else if (
1011            ts.isInterfaceDeclaration(statement) ||
1012            ts.isClassDeclaration(statement) ||
1013            ts.isEnumDeclaration(statement)
1014          ) {
1015            if (statement && statement.name && statement.name.escapedText) {
1016              name = statement.name.escapedText.toString();
1017            }
1018          }
1019          allReferencesIdentifierSet.delete(name);
1020        }
1021      });
1022    }
1023    function collectAllIdentifier(node) {
1024      if (isSystemapi(node)) {
1025        return node;
1026      }
1027      if (ts.isIdentifier(node)) {
1028        allReferencesIdentifierSet.add(node.escapedText.toString());
1029      }
1030      return ts.visitEachChild(node, collectAllIdentifier, context);
1031    }
1032  };
1033}
1034
1035function variableStatementGetEscapedText(statement) {
1036  let name = '';
1037  if (
1038    statement &&
1039    statement.declarationList &&
1040    statement.declarationList.declarations &&
1041    statement.declarationList.declarations.length > 0 &&
1042    statement.declarationList.declarations[0].name &&
1043    statement.declarationList.declarations[0].name.escapedText
1044  ) {
1045    name = statement.declarationList.declarations[0].name.escapedText.toString();
1046  }
1047  return name;
1048}
1049
1050function isSystemapi(node) {
1051  const notesContent = node.getFullText().replace(node.getText(), '').replace(/[\s]/g, '');
1052  const notesArr = notesContent.split(/\/\*\*/);
1053  const notesStr = notesArr[notesArr.length - 1];
1054  if (notesStr.length !== 0) {
1055    return /@systemapi/g.test(notesStr);
1056  }
1057  return false;
1058}
1059
1060function isEmptyFile(node) {
1061  let isEmpty = true;
1062  if (ts.isSourceFile(node) && node.statements) {
1063    for (let i = 0; i < node.statements.length; i++) {
1064      const statement = node.statements[i];
1065      if (ts.isImportDeclaration(statement)) {
1066        continue;
1067      }
1068      isEmpty = false;
1069      break;
1070    }
1071  }
1072  return isEmpty;
1073}
1074
1075const apiSourcePath = '../api';
1076const outputPath = path.resolve(__dirname, 'output');
1077collectDeclaration(apiSourcePath); //入口
1078