1/* 2 * Copyright (c) 2025 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 */ 15 16const fs = require('fs'); 17const path = require('path'); 18const ts = require('typescript'); 19const commander = require('commander'); 20// 处理的目录类型 21let dirType = ''; 22const deleteApiSet = new Set(); 23const importNameSet = new Set(); 24 25// 处理的目录类型,ets代表处理的是1.1目录,ets2代表处理的是1.2目录里有@arkts 1.1&1.2标签的文件, 26// noTagInEts2代表处理的是1.2目录里无标签的文件 27const DirType = { 28 'typeOne': 'ets', 29 'typeTwo': 'ets2', 30 'typeThree': 'noTagInEts2', 31}; 32 33const NotNullFilePath = [ 34 'api', 35 'api/@internal/ets', 36 'api/@internal/component/ets', 37 'api/arkui/component', 38 'arkts', 39 'kits', 40]; 41 42const NOT_COPY_DIR = ['build-tools', '.git', '.gitee']; 43 44function isEtsFile(path) { 45 return path.endsWith('d.ets'); 46} 47 48function isTsFile(path) { 49 return path.endsWith('d.ts'); 50} 51 52function isStaticFile(path) { 53 return path.endsWith('static.d.ets'); 54} 55 56function hasEtsFile(path) { 57 // 为StateManagement.d.ts设定白名单,在1.2打包的时候在Linux上有大小写不同的重名,碰到直接返回true 58 if (path.includes('StateManagement.d.ts')) { 59 console.log('StateManagement.d.ts is in white list, return true. path = ', path); 60 return true; 61 } else { 62 return fs.existsSync(path.replace(/\.d\.[e]?ts$/g, '.d.ets')); 63 } 64} 65 66function hasTsFile(path) { 67 return fs.existsSync(path.replace(/\.d\.[e]?ts$/g, '.d.ts')); 68} 69 70function hasStaticFile(path) { 71 // 为StateManagement.d.ts设定白名单,在1.2打包的时候在Linux上有大小写不同的重名,碰到直接返回true 72 if (path.includes('StateManagement.d.ts')) { 73 console.log('StateManagement.d.ts is in white list, return true. path = ', path); 74 return true; 75 } else { 76 return fs.existsSync(path.replace(/\.d\.[e]?ts$/g, '.static.d.ets')); 77 } 78} 79 80/** 81 * 配置参数 82 */ 83function start() { 84 const program = new commander.Command(); 85 program 86 .name('handleApiFile') 87 .version('0.0.1'); 88 program 89 .option('--path <string>', 'path name') 90 .option('--type <string>', 'handle type') 91 .option('--output <string>', 'output path') 92 .option('--isPublic <string>', 'is Public') 93 .option('--create-keep-file <string>', 'create keep file', 'false') 94 .action((opts) => { 95 dirType = opts.type; 96 handleApiFiles(opts.path, opts.type, opts.output, opts.isPublic, opts.createKeepFile); 97 }); 98 program.parse(process.argv); 99} 100/** 101 * 处理API文件的入口函数 102 * 103 * @param {*} rootPath 104 * @param {*} type 105 */ 106function handleApiFiles(rootPath, type, output, isPublic, createKeepFile) { 107 const allApiFilePathSet = new Set(); 108 const fileNames = fs.readdirSync(rootPath); 109 const apiRootPath = rootPath.replace(/\\/g, '/'); 110 fileNames.forEach(fileName => { 111 const apiPath = path.join(apiRootPath, fileName); 112 const stat = fs.statSync(apiPath); 113 if (NOT_COPY_DIR.includes(fileName)) { 114 return; 115 } 116 if (stat.isDirectory()) { 117 getApiFileName(apiPath, apiRootPath, allApiFilePathSet); 118 } else { 119 allApiFilePathSet.add(fileName); 120 } 121 122 }); 123 124 for (const apiRelativePath of allApiFilePathSet) { 125 try { 126 handleApiFileByType(apiRelativePath, rootPath, type, output, isPublic); 127 } catch (error) { 128 console.log('error===>', error); 129 } 130 } 131 const needCreateKeepFile = createKeepFile.toString() === 'true'; 132 if (type === DirType.typeTwo && needCreateKeepFile) { 133 NotNullFilePath.forEach((dir) => { 134 const outDir = path.join(output, dir); 135 if (!fs.existsSync(outDir)) { 136 fs.mkdirSync(outDir, { recursive: true }); 137 writeFile(path.join(outDir, '.keep'), ' '); 138 return; 139 } 140 const fileNames = fs.readdirSync(outDir); 141 if (fileNames.length === 0) { 142 writeFile(path.join(outDir, '.keep'), ' '); 143 } 144 }); 145 } 146} 147 148 149/** 150 * 根据传入的type值去处理文件 151 * 152 * @param {*} apiRelativePath 153 * @param {*} allApiFilePathSet 154 * @param {*} rootPath 155 * @param {*} type 156 * @returns 157 */ 158function handleApiFileByType(apiRelativePath, rootPath, type, output, isPublic) { 159 const fullPath = path.join(rootPath, apiRelativePath); 160 const isEndWithEts = isEtsFile(apiRelativePath); 161 const isEndWithTs = isTsFile(apiRelativePath); 162 const isEndWithStatic = isStaticFile(apiRelativePath); 163 const outputPath = output ? path.join(output, apiRelativePath) : fullPath; 164 const fileContent = fs.readFileSync(fullPath, 'utf-8'); 165 166 if (isEndWithStatic) { 167 if (type === 'ets2') { 168 if (isPublic === 'true') { 169 writeFile(outputPath, deleteArktsTag(fileContent)); 170 return; 171 } else { 172 writeFile(outputPath.replace(/\.static\.d\.ets$/, '.d.ets'), deleteArktsTag(fileContent)); 173 return; 174 } 175 } else { 176 return; 177 } 178 } 179 180 if (!isEndWithEts && !isEndWithTs) { 181 writeFile(outputPath, fileContent); 182 return; 183 } 184 const isCorrectHandleFullpath = isHandleFullPath(fullPath, apiRelativePath, type); 185 if (type === 'ets2' && isCorrectHandleFullpath) { 186 handleFileInSecondType(apiRelativePath, fullPath, type, output); 187 } else if (type === 'ets' && isCorrectHandleFullpath) { 188 handleFileInFirstType(apiRelativePath, fullPath, type, output); 189 } 190} 191 192/** 193 * 判断当前的文件路径是否符合当前打包场景,过滤同名文件 194 * @param {*} fullPath 195 * @param {*} apiRelativePath 196 * @param {*} type 197 * @returns 198 */ 199function isHandleFullPath(fullPath, apiRelativePath, type) { 200 // 当前文件为.ts结尾文件 201 if (isTsFile(apiRelativePath)) { 202 if (type === 'ets') { 203 return true; 204 } 205 if (!(hasEtsFile(fullPath)) && !(hasStaticFile(fullPath))) { 206 return true; 207 } 208 } 209 // 当前文件为.ets结尾文件 210 if (isEtsFile(apiRelativePath)) { 211 if (!(hasTsFile(fullPath)) && !(hasStaticFile(fullPath))) { 212 return true; 213 } 214 if (hasTsFile(fullPath) && !(hasStaticFile(fullPath)) && type === 'ets2') { 215 return true; 216 } 217 if (hasStaticFile(fullPath) && !(hasTsFile(fullPath)) && type === 'ets') { 218 return true; 219 } 220 } 221 // 当前文件为.static结尾文件 222 if (isStaticFile(apiRelativePath) && type === 'ets2') { 223 return true; 224 } 225 return false; 226} 227 228/** 229 * 处理文件过滤 if arkts 1.1|1.2|1.1&1.2 定义 230 * 231 * @param {*} type 232 * @param {*} fileContent 233 * @returns 234 */ 235function handleArktsDefinition(type, fileContent) { 236 let regx = /\/\*\*\* if arkts 1\.1 \*\/\s*([\s\S]*?)\s*\/\*\*\* endif \*\//g; 237 let regx2 = /\/\*\*\* if arkts 1\.2 \*\/\s*([\s\S]*?)\s*\/\*\*\* endif \*\//g; 238 let regx3 = /\/\*\*\* if arkts 1\.1\&1\.2 \*\/\s*([\s\S]*?)\s*\/\*\*\* endif \*\//g; 239 fileContent = fileContent.replace(regx, (substring, p1) => { 240 return type === 'ets' ? p1 : ''; 241 }); 242 fileContent = fileContent.replace(regx2, (substring, p1) => { 243 if (type === 'ets2') { 244 return p1.replace(/(\s*)(\*\s\@since)/g, '$1* @arkts 1.2$1$2'); 245 } else { 246 return ''; 247 } 248 }); 249 fileContent = fileContent.replace(regx3, (substring, p1) => { 250 if (type === 'ets') { 251 return p1; 252 } else { 253 return p1.replace(/(\s*)(\*\s\@since)/g, '$1* @arkts 1.2$1$2'); 254 } 255 }); 256 return fileContent; 257} 258 259/** 260 * 保留每个api最新一段jsdoc 261 * 262 * @param {*} fileContent 263 * @returns 264 */ 265function saveLatestJsDoc(fileContent) { 266 let regx = /(\/\*[\s\S]*?\*\*\/)/g; 267 268 fileContent = fileContent.split('').reverse().join(''); 269 let preset = 0; 270 fileContent = fileContent.replace(regx, (substring, p1, offset, str) => { 271 if (!/ecnis@\s*\*/g.test(substring)) { 272 return substring; 273 } 274 const preStr = str.substring(preset, offset); 275 preset = offset + substring.length; 276 if (/\S/g.test(preStr)) { 277 return substring; 278 } 279 return ''; 280 }); 281 fileContent = fileContent.split('').reverse().join(''); 282 return fileContent; 283} 284 285/** 286 * 处理ets目录 287 * 288 * @param {string} apiRelativePath 289 * @param {string} fullPath 290 * @returns 291 */ 292function handleFileInFirstType(apiRelativePath, fullPath, type, output) { 293 const outputPath = output ? path.join(output, apiRelativePath) : fullPath; 294 let fileContent = fs.readFileSync(fullPath, 'utf-8'); 295 //删除使用/*** if arkts 1.2 */ 296 fileContent = handleArktsDefinition(type, fileContent); 297 298 const sourceFile = ts.createSourceFile(path.basename(apiRelativePath), fileContent, ts.ScriptTarget.ES2017, true); 299 const secondRegx = /(?:@arkts1.2only|@arkts\s+>=\s*1.2|@arkts\s*1.2)/; 300 const thirdRegx = /(?:\*\s*@arkts\s+1.1&1.2\s*(\r|\n)\s*)/; 301 if (sourceFile.statements.length === 0) { 302 // reference文件识别不到首段jsdoc,全文匹配1.2标签,有的话直接删除 303 if (secondRegx.test(sourceFile.getFullText())) { 304 return; 305 } 306 // 标有@arkts 1.1&1.2的声明文件,处理since版本号,删除@arkts 1.1&1.2标签 307 if (thirdRegx.test(sourceFile.getFullText())) { 308 fileContent = handleSinceInFirstType(deleteArktsTag(fileContent)); 309 writeFile(outputPath, fileContent); 310 return; 311 } 312 313 handleNoTagFileInFirstType(sourceFile, outputPath, fileContent); 314 return; 315 } 316 const firstNode = sourceFile.statements.find(statement => { 317 return !ts.isExpressionStatement(statement); 318 }); 319 320 if (firstNode) { 321 const firstJsdocText = getFileJsdoc(firstNode); 322 // 标有1.2标签的声明文件,不拷贝 323 if (secondRegx.test(firstJsdocText)) { 324 return; 325 } 326 // 标有@arkts 1.1&1.2的声明文件,处理since版本号,删除@arkts 1.1&1.2标签 327 if (thirdRegx.test(firstJsdocText)) { 328 fileContent = handleSinceInFirstType(deleteArktsTag(fileContent)); 329 writeFile(outputPath, fileContent); 330 return; 331 } 332 } 333 334 handleNoTagFileInFirstType(sourceFile, outputPath, fileContent); 335} 336 337/** 338 * 处理1.1目录中无arkts标签的文件 339 * @param {*} sourceFile 340 * @param {*} outputPath 341 * @returns 342 */ 343function handleNoTagFileInFirstType(sourceFile, outputPath, fileContent) { 344 if (path.basename(outputPath) === 'index-full.d.ts') { 345 writeFile(outputPath, fileContent); 346 return; 347 } 348 fileContent = deleteApi(sourceFile); 349 350 if (fileContent === '') { 351 return; 352 } 353 fileContent = deleteArktsTag(fileContent); 354 fileContent = joinFileJsdoc(fileContent, sourceFile); 355 356 fileContent = handleSinceInFirstType(fileContent); 357 writeFile(outputPath, fileContent); 358} 359 360/** 361 * 删除指定的arkts标签 362 * 363 * @param {*} fileContent 文件内容 364 * @param {*} regx 删除的正则表达式 365 * @returns 366 */ 367function deleteArktsTag(fileContent) { 368 const arktsTagRegx = /\*\s*@arkts\s+1.1&1.2\s*(\r|\n)\s*|\*\s*@arkts\s*1.2s*(\r|\n)\s*|\*\s*@arkts\s*1.1s*(\r|\n)\s*/g; 369 fileContent = fileContent.replace(arktsTagRegx, (substring, p1) => { 370 return ''; 371 }); 372 return fileContent; 373} 374 375/** 376 * 删除1.2未支持标签 377 * 378 * @param {*} fileContent 文件内容 379 * @param {*} regx 删除的正则表达式 380 * @returns 381 */ 382function deleteUnsportedTag(fileContent) { 383 const arktsTagRegx = /\*\s*@crossplatform\s*(\r|\n)\s*|\*\s*@form\s*(\r|\n)\s*|\*\s*@atomicservice\s*(\r|\n)\s*/g; 384 fileContent = fileContent.replace(arktsTagRegx, (substring, p1) => { 385 return ''; 386 }); 387 return fileContent; 388} 389 390/** 391 * 生成1.1目录里文件时,需要去掉since标签里的1.2版本号 392 * 393 * @param {*} sourceFile 394 * @param {*} fullPath 395 */ 396function handleSinceInFirstType(fileContent) { 397 const regx = /@since\s+arkts\s*(\{.*\})/g; 398 fileContent = fileContent.replace(regx, (substring, p1) => { 399 return '@since ' + JSON.parse(p1.replace(/'/g, '"'))['1.1']; 400 }); 401 return fileContent; 402} 403 404/** 405 * 处理ets2目录 406 * 407 * @param {string} fullPath 文件完整路径 408 * @returns 409 */ 410function handleFileInSecondType(apiRelativePath, fullPath, type, output) { 411 const secondRegx = /(?:@arkts1.2only|@arkts\s+>=\s*1.2|@arkts\s*1.2)/; 412 const thirdRegx = /(?:\*\s*@arkts\s+1.1&1.2\s*(\r|\n)\s*)/; 413 const arktsRegx = /\/\*\*\* if arkts (1.1&)?1.2 \*\/\s*([\s\S]*?)\s*\/\*\*\* endif \*\//g; 414 let fileContent = fs.readFileSync(fullPath, 'utf-8'); 415 let sourceFile = ts.createSourceFile(path.basename(fullPath), fileContent, ts.ScriptTarget.ES2017, true); 416 const outputPath = output ? path.join(output, apiRelativePath) : fullPath; 417 if (!secondRegx.test(fileContent) && !thirdRegx.test(fileContent) && arktsRegx.test(fileContent)) { 418 saveApiByArktsDefinition(sourceFile, fileContent, outputPath); 419 return; 420 } 421 //删除使用/*** if arkts 1.2 */ 422 fileContent = handleArktsDefinition(type, fileContent); 423 sourceFile = ts.createSourceFile(path.basename(fullPath), fileContent, ts.ScriptTarget.ES2017, true); 424 const regx = /(?:@arkts1.1only|@arkts\s+<=\s+1.1)/; 425 426 if (sourceFile.statements.length === 0) { 427 // 有1.2标签的文件,删除标记 428 if (secondRegx.test(sourceFile.getFullText())) { 429 let newFileContent = deleteUnsportedTag(fileContent); 430 newFileContent = getFileContent(newFileContent, fullPath); 431 writeFile(outputPath, deleteArktsTag(newFileContent)); 432 return; 433 } 434 // 处理标有@arkts 1.1&1.2的声明文件 435 if (thirdRegx.test(sourceFile.getFullText())) { 436 handlehasTagFile(sourceFile, outputPath); 437 return; 438 } 439 // 处理既没有@arkts 1.2,也没有@arkts 1.1&1.2的声明文件 440 handleNoTagFileInSecondType(sourceFile, outputPath, fullPath); 441 return; 442 } 443 444 const firstNode = sourceFile.statements.find(statement => { 445 return !ts.isExpressionStatement(statement); 446 }); 447 448 if (firstNode) { 449 const firstJsdocText = getFileJsdoc(firstNode); 450 if (regx.test(firstJsdocText)) { 451 return; 452 } 453 // 有1.2标签的文件,删除标记 454 if (secondRegx.test(firstJsdocText)) { 455 let newFileContent = deleteUnsportedTag(fileContent); 456 newFileContent = getFileContent(newFileContent, fullPath); 457 writeFile(outputPath, deleteArktsTag(newFileContent)); 458 return; 459 } 460 // 处理标有@arkts 1.1&1.2的声明文件 461 if (thirdRegx.test(firstJsdocText)) { 462 handlehasTagFile(sourceFile, outputPath); 463 return; 464 } 465 } 466 467 // 处理既没有@arkts 1.2,也没有@arkts 1.1&1.2的声明文件 468 handleNoTagFileInSecondType(sourceFile, outputPath, fullPath); 469} 470 471/** 472 * 获取文件jsdoc 473 * @param {*} firstNode 474 * @returns 475 */ 476function getFileJsdoc(firstNode) { 477 const firstNodeJSDoc = firstNode.getFullText().replace(firstNode.getText(), ''); 478 const jsdocs = firstNodeJSDoc.split('*/'); 479 let fileJSDoc = ''; 480 for (let i = 0; i < jsdocs.length; i++) { 481 const jsdoc = jsdocs[i]; 482 if (/\@file/.test(jsdoc)) { 483 fileJSDoc = jsdoc; 484 break; 485 } 486 } 487 return fileJSDoc; 488} 489 490/** 491 * 处理有@arkts 1.1&1.2标签的文件 492 * @param {*} outputPath 493 */ 494function handlehasTagFile(sourceFile, outputPath) { 495 dirType = DirType.typeTwo; 496 let newContent = getDeletionContent(sourceFile); 497 if (newContent === '') { 498 return; 499 } 500 // 保留最后一段注释 501 newContent = saveLatestJsDoc(newContent); 502 newContent = getFileContent(newContent, outputPath); 503 newContent = deleteUnsportedTag(newContent); 504 writeFile(outputPath, deleteArktsTag(newContent)); 505} 506 507/** 508 * 处理1.2目录中无arkts标签的文件 509 * @param {*} sourceFile 510 * @param {*} outputPath 511 * @returns 512 */ 513function handleNoTagFileInSecondType(sourceFile, outputPath, fullPath) { 514 dirType = DirType.typeThree; 515 const arktsTagRegx = /\*\s*@arkts\s+1.1&1.2\s*(\r|\n)\s*|@arkts\s*1.2/g; 516 const fileContent = sourceFile.getFullText(); 517 let newContent = ''; 518 // API未标@arkts 1.2或@arkts 1.1&1.2标签,删除文件 519 if (!arktsTagRegx.test(fileContent)) { 520 if (fullPath.endsWith('.d.ts') && hasEtsFile(fullPath) || fullPath.endsWith('.d.ets') && hasTsFile(fullPath)) { 521 newContent = saveLatestJsDoc(fileContent); 522 newContent = deleteArktsTag(newContent); 523 writeFile(outputPath, newContent); 524 } 525 return; 526 } 527 newContent = getDeletionContent(sourceFile); 528 if (newContent === '') { 529 return; 530 } 531 // 保留最后一段注释 532 newContent = saveLatestJsDoc(newContent); 533 newContent = getFileContent(newContent, outputPath); 534 newContent = deleteArktsTag(newContent); 535 newContent = deleteUnsportedTag(newContent); 536 writeFile(outputPath, newContent); 537} 538 539/** 540 * 获取删除overload节点后的文件内容 541 * @param {*} newContent 文件内容 542 * @param {*} filePath 文件路径 543 * @returns 544 */ 545function getFileContent(newContent, filePath) { 546 const regex = /^overload\s+.+$/; 547 if (!regex.test(newContent)) { 548 return newContent; 549 } 550 const sourceFile = ts.createSourceFile(path.basename(filePath), newContent, ts.ScriptTarget.ES2017, true); 551 const printer = ts.createPrinter(); 552 const result = ts.transform(sourceFile, [deleteOverLoadJsDoc], { etsAnnotationsEnable: true }); 553 const output = printer.printFile(result.transformed[0]); 554 return output; 555} 556 557/** 558 * 递归去除overload节点jsDoc 559 * @param {*} context 560 * @returns 561 */ 562function deleteOverLoadJsDoc(context) { 563 return (sourceFile) => { 564 const visitNode = (node) => { 565 if (ts.isStructDeclaration(node)) { 566 return processStructDeclaration(node); 567 } 568 if (ts.isOverloadDeclaration(node)) { 569 return ts.factory.createOverloadDeclaration(node.modifiers, node.name, node.members); 570 } 571 return ts.visitEachChild(node, visitNode, context); 572 }; 573 const newSourceFile = ts.visitNode(sourceFile, visitNode); 574 return newSourceFile; 575 }; 576} 577 578/** 579 * 处理struct子节点,防止tsc自动增加constructor方法 580 */ 581function processStructDeclaration(node) { 582 const newMembers = []; 583 node.members.forEach((member, index) => { 584 if (index >= 1) { 585 newMembers.push(member); 586 } 587 }); 588 node = ts.factory.updateStructDeclaration( 589 node, 590 node.modifiers, 591 node.name, 592 node.typeParameters, 593 node.heritageClauses, 594 newMembers 595 ); 596 return node; 597} 598 599/** 600 * 没有arkts标签,但有if arkts 1.2和1.1&1.2的情况 601 * @param {*} sourceFile 602 * @param {*} fileContent 603 * @param {*} outputPath 604 */ 605function saveApiByArktsDefinition(sourceFile, fileContent, outputPath) { 606 const regx = /\/\*\*\* if arkts (1.1&)?1.2 \*\/\s*([\s\S]*?)\s*\/\*\*\* endif \*\//g; 607 const regex = /\/\*\r?\n\s*\*\s*Copyright[\s\S]*?\*\//g; 608 const copyrightMessage = fileContent.match(regex)[0]; 609 const firstNode = sourceFile.statements.find(statement => { 610 return !ts.isExpressionStatement(statement); 611 }); 612 let fileJsdoc = firstNode ? getFileJsdoc(firstNode) + '*/\n' : ''; 613 let newContent = copyrightMessage + fileJsdoc + Array.from(fileContent.matchAll(regx), match => match[2]).join('\n'); 614 615 writeFile(outputPath, saveLatestJsDoc(newContent)); 616} 617 618/** 619 * 拼接上被删除的文件注释 620 * 621 * @param {*} deletionContent 622 * @param {*} sourceFile 623 * @returns 624 */ 625function joinFileJsdoc(deletionContent, sourceFile) { 626 const fileJsdoc = sourceFile.getFullText().replace(sourceFile.getText(), ''); 627 const copyrightMessage = hasCopyright(fileJsdoc.split('*/')[0]) ? fileJsdoc.split('*/')[0] + '*/\r\n' : ''; 628 const regx = /@kit | @file/g; 629 let kitMessage = ''; 630 631 if (regx.test(fileJsdoc)) { 632 kitMessage = fileJsdoc.split('*/')[1] + '*/\r\n'; 633 } 634 let newContent = deletionContent; 635 const isHasCopyright = hasCopyright(deletionContent); 636 637 if (!isHasCopyright && !regx.test(deletionContent)) { 638 newContent = copyrightMessage + kitMessage + deletionContent; 639 } else if (!isHasCopyright) { 640 newContent = copyrightMessage + deletionContent; 641 } else if (isHasCopyright && !/@kit | @file/g.test(deletionContent)) { 642 const joinFileJsdoc = copyrightMessage + kitMessage; 643 newContent = deletionContent.replace(copyrightMessage, joinFileJsdoc); 644 } 645 646 if (dirType !== DirType.typeOne) { 647 // TODO:添加use static字符串 648 } 649 return newContent; 650} 651 652function getDeletionContent(sourceFile) { 653 const deletionContent = deleteApi(sourceFile); 654 if (deletionContent === '') { 655 return ''; 656 } 657 let newContent = joinFileJsdoc(deletionContent, sourceFile); 658 659 // 处理since版本 660 newContent = handleSinceInSecondType(newContent); 661 return newContent; 662} 663 664/** 665 * 重写文件内容 666 * @param {*} outputPath 667 * @param {*} fileContent 668 */ 669function writeFile(outputPath, fileContent) { 670 const outputDir = path.dirname(outputPath); 671 let newPath = outputPath; 672 if (!fs.existsSync(outputDir)) { 673 fs.mkdirSync(outputDir, { recursive: true }); 674 } 675 676 if (dirType !== DirType.typeOne && isTsFile(outputPath)) { 677 newPath = outputPath.replace('.d.ts', '.d.ets'); 678 } 679 fs.writeFileSync(newPath, fileContent); 680} 681 682/** 683 * 添加use static字符串 684 * 685 * @param {*} fileContent 文件内容 686 * @param {*} copyrightMessage 版权头内容 687 * @returns 688 */ 689function addStaticString(fileContent, copyrightMessage) { 690 const hasStaticMessage = /use\s+static/g.test(fileContent); 691 const regex = /\/\*\r?\n\s*\*\s*Copyright[\s\S]*?limitations under the License\.\r?\n\s*\*\//g; 692 const staticMessage = 'use static'; 693 let newContent = fileContent; 694 if (!hasStaticMessage) { 695 const newfileJsdoc = `${copyrightMessage}'${staticMessage}'\r\n`; 696 newContent = newContent.replace(regex, newfileJsdoc); 697 } 698 return newContent; 699} 700 701/** 702 * 判断新生成的文件内容有没有版权头 703 * 704 * @param {*} fileText 新生成的文件内容 705 * @returns 706 */ 707function hasCopyright(fileText) { 708 return /http(\:|\?:)\/\/www(\.|\/)apache\.org\/licenses\/LICENSE\-2\.0 | Copyright\s*\(c\)/gi.test(fileText); 709} 710 711// 创建 Transformer 712const transformer = (context) => { 713 return (rootNode) => { 714 const visit = (node) => { 715 //struct节点下面会自动生成constructor节点, 置为undefined 716 if (node.kind === ts.SyntaxKind.Constructor && node.parent.kind === ts.SyntaxKind.StructDeclaration) { 717 return undefined; 718 } 719 720 // 判断是否为要删除的变量声明 721 if ((apiNodeTypeArr.includes(node.kind) || validateExportDeclaration(node)) && judgeIsDeleteApi(node)) { 722 collectDeletionApiName(node); 723 // 删除该节点 724 return undefined; 725 } 726 727 // 非目标节点:继续遍历子节点 728 return ts.visitEachChild(node, visit, context); 729 }; 730 return ts.visitNode(rootNode, visit); 731 }; 732}; 733 734function validateExportDeclaration(node) { 735 return ts.isExportDeclaration(node) && node.jsDoc && node.jsDoc.length !== 0; 736} 737 738/** 739 * 删除API 740 * @param {*} sourceFile 741 * @returns 742 */ 743function deleteApi(sourceFile) { 744 let result = ts.transform(sourceFile, [transformer], { etsAnnotationsEnable: true }); 745 const newSourceFile = result.transformed[0]; 746 if (isEmptyFile(newSourceFile)) { 747 return ''; 748 } 749 750 // 打印结果 751 const printer = ts.createPrinter(); 752 let fileContent = printer.printFile(newSourceFile); 753 result = ts.transform(newSourceFile, [transformExportApi], { etsAnnotationsEnable: true }); 754 fileContent = printer.printFile(result.transformed[0]); 755 deleteApiSet.clear(); 756 return fileContent.replace(/export\s*(?:type\s*)?\{\s*\}\s*(;)?/g, ''); 757} 758 759/** 760 * api被删除后,对应的export api也需要被删除 761 * @param {*} context 762 * @returns 763 */ 764const transformExportApi = (context) => { 765 return (rootNode) => { 766 const importOrExportNodeVisitor = (node) => { 767 if (ts.isImportClause(node) && node.name && ts.isIdentifier(node.name) || 768 ts.isImportSpecifier(node) && node.name && ts.isIdentifier(node.name)) { 769 importNameSet.add(node.name?.getText()); 770 } 771 // 剩下未被删除的API中,如果还有与被删除API名字一样的API,就将其从set集合中删掉 772 if (apiNodeTypeArr.includes(node.kind) && deleteApiSet.has(node.name?.getText())) { 773 deleteApiSet.delete(node.name?.getText()); 774 } 775 // 非目标节点:继续遍历子节点 776 return ts.visitEachChild(node, importOrExportNodeVisitor, context); 777 }; 778 ts.visitNode(rootNode, importOrExportNodeVisitor); 779 780 const allNodeVisitor = (node) => { 781 // 判断是否为要删除的变量声明 782 if (ts.isExportAssignment(node) && deleteApiSet.has(node.expression.escapedText.toString()) && 783 !importNameSet.has(node.expression.escapedText.toString())) { 784 return undefined; 785 } 786 787 if (ts.isExportSpecifier(node) && deleteApiSet.has(node.name.escapedText.toString()) && 788 !importNameSet.has(node.name.escapedText.toString())) { 789 return undefined; 790 } 791 792 // 非目标节点:继续遍历子节点 793 return ts.visitEachChild(node, allNodeVisitor, context); 794 }; 795 return ts.visitNode(rootNode, allNodeVisitor); 796 }; 797}; 798 799function isEmptyFile(node) { 800 let isEmpty = true; 801 if (ts.isSourceFile(node) && node.statements) { 802 const needExportName = new Set(); 803 for (let i = 0; i < node.statements.length; i++) { 804 const statement = node.statements[i]; 805 if (ts.isExportDeclaration(statement) && statement.moduleSpecifier) { 806 isEmpty = false; 807 break; 808 } 809 if (judgeExportHasImport(statement, needExportName)) { 810 continue; 811 } 812 isEmpty = false; 813 break; 814 } 815 } 816 return isEmpty; 817} 818 819function collectDeletionApiName(node) { 820 if (!ts.isImportClause(node)) { 821 deleteApiSet.add(node.name?.getText()); 822 return; 823 } 824 825 if (ts.isImportDeclaration(node) && node.importClause?.name) { 826 deleteApiSet.add(node.importClause.name.escapedText.toString()); 827 return; 828 } 829 const namedBindings = node.namedBindings; 830 if (namedBindings !== undefined && ts.isNamedImports(namedBindings)) { 831 const elements = namedBindings.elements; 832 elements.forEach((element) => { 833 const exportName = element.propertyName ? 834 element.propertyName.escapedText.toString() : 835 element.name.escapedText.toString(); 836 deleteApiSet.add(exportName); 837 }); 838 } 839} 840 841/** 842 * 判断import节点和export节点。 843 * 当前文本如果还有其他节点则不能删除, 844 * 如果只有import和export则判断是否export导出import节点 845 * 846 * @param {*} statement 847 * @param {*} needExportName 848 * @returns 849 */ 850function judgeExportHasImport(statement, needExportName) { 851 if (ts.isImportDeclaration(statement)) { 852 processImportDeclaration(statement, needExportName); 853 return true; 854 } else if (ts.isExportAssignment(statement) && 855 !needExportName.has(statement.expression.escapedText.toString())) { 856 return true; 857 } else if (ts.isExportDeclaration(statement)) { 858 return !statement.exportClause.elements.some((element) => { 859 const exportName = element.propertyName ? 860 element.propertyName.escapedText.toString() : 861 element.name.escapedText.toString(); 862 return needExportName.has(exportName); 863 }); 864 } 865 return false; 866} 867 868function processImportDeclaration(statement, needExportName) { 869 const importClause = statement.importClause; 870 if (!ts.isImportClause(importClause)) { 871 return; 872 } 873 if (importClause.name) { 874 needExportName.add(importClause.name.escapedText.toString()); 875 } 876 const namedBindings = importClause.namedBindings; 877 if (namedBindings !== undefined && ts.isNamedImports(namedBindings)) { 878 const elements = namedBindings.elements; 879 elements.forEach((element) => { 880 const exportName = element.propertyName ? 881 element.propertyName.escapedText.toString() : 882 element.name.escapedText.toString(); 883 needExportName.add(exportName); 884 }); 885 } 886} 887 888/** 889 * 判断node节点中是否有famodelonly/deprecated/arkts <=1.1标签 890 * 891 * @param {*} node 892 * @returns 893 */ 894function judgeIsDeleteApi(node) { 895 const notesContent = node.getFullText().replace(node.getText(), '').replace(/[\s]/g, ''); 896 const notesArr = notesContent.split(/\/\*\*/); 897 const notesStr = notesArr[notesArr.length - 1]; 898 const sinceArr = notesStr.match(/@since\d+/); 899 let sinceVersion = 20; 900 901 if (dirType === DirType.typeOne) { 902 return /@arkts1\.2(?!\d)/g.test(notesStr); 903 } 904 905 if (sinceArr) { 906 sinceVersion = sinceArr[0].replace('@since', ''); 907 } 908 909 if (dirType === DirType.typeTwo) { 910 return (/@deprecated/g.test(notesStr) && sinceVersion < 20) || /@arkts<=1.1/g.test(notesStr); 911 } 912 913 if (dirType === DirType.typeThree) { 914 return !/@arkts1\.2\*|@arkts1\.1&1\.2\*/g.test(notesStr); 915 } 916 917 return false; 918} 919 920/** 921 * 生成1.2目录里文件时,需要去掉since标签里的dynamic版本号 922 * 923 * @param {*} fileContent 924 * @returns 925 */ 926function handleSinceInSecondType(fileContent) { 927 const regx = /@since\s+arkts\s*(\{.*\})/g; 928 fileContent = fileContent.replace(regx, (substring, p1) => { 929 return '@since ' + JSON.parse(p1.replace(/'/g, '"'))['1.2']; 930 }); 931 return fileContent; 932} 933 934 935function deleteSameNameFile(fullPath) { 936 try { 937 fs.unlinkSync(fullPath); 938 } catch (error) { 939 console.error('delete file failed: ', error); 940 } 941} 942 943/** 944 * 945 * @param { string } apiPath 需要处理的api文件所在路径 946 * @param { string } rootPath ets文件夹路径 947 * @returns { Set<string> } 需要处理的api文件的相对于ets目录的路径 948 */ 949function getApiFileName(apiPath, rootPath, allApiFilePathSet) { 950 const apiFilePathSet = new Set(); 951 const fileNames = fs.readdirSync(apiPath); 952 953 fileNames.forEach(fileName => { 954 const apiFilePath = path.join(apiPath, fileName).replace(/\\/g, '/'); 955 const stat = fs.statSync(apiFilePath); 956 957 if (stat.isDirectory()) { 958 getApiFileName(apiFilePath, rootPath, allApiFilePathSet); 959 } else { 960 const apiRelativePath = apiFilePath.replace(rootPath, ''); 961 allApiFilePathSet.add(apiRelativePath); 962 } 963 }); 964 965 return apiFilePathSet; 966} 967 968// 所有API的节点类型 969const apiNodeTypeArr = [ 970 ts.SyntaxKind.VariableStatement, 971 ts.SyntaxKind.MethodDeclaration, 972 ts.SyntaxKind.MethodSignature, 973 ts.SyntaxKind.FunctionDeclaration, 974 ts.SyntaxKind.Constructor, 975 ts.SyntaxKind.ConstructSignature, 976 ts.SyntaxKind.CallSignature, 977 ts.SyntaxKind.PropertyDeclaration, 978 ts.SyntaxKind.PropertySignature, 979 ts.SyntaxKind.EnumMember, 980 ts.SyntaxKind.EnumDeclaration, 981 ts.SyntaxKind.TypeAliasDeclaration, 982 ts.SyntaxKind.ClassDeclaration, 983 ts.SyntaxKind.InterfaceDeclaration, 984 ts.SyntaxKind.ModuleDeclaration, 985 ts.SyntaxKind.StructDeclaration, 986 ts.SyntaxKind.GetAccessor, 987 ts.SyntaxKind.SetAccessor, 988 ts.SyntaxKind.IndexSignature, 989 ts.SyntaxKind.OverloadDeclaration 990]; 991 992start(); 993