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