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