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