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 ts = require('typescript'); 17const fs = require('fs'); 18const path = require('path'); 19const commander = require('commander'); 20 21const typeArray = ['long', 'double', 'int']; 22// 并发处理时单次执行文件数量 23const SPLIT_LENGTH = 100; 24 25let inputDir = ''; 26let outputDir = ''; 27let index = ''; 28let jsDocContent = ''; 29let tagDataList = []; 30const apiTransformPath = '\\APITransformOutPath'; 31const apiRemovalPath = '\\APIduplicateRemovalOutPath'; 32const jsDocTransformPath = '\\jsDocTransformOutPath'; 33 34function main() { 35 const program = new commander.Command(); 36 program 37 .name('intToNumber') 38 .version('0.0.1'); 39 program 40 .option('--path <string>', 'input path') 41 .option('--output <string>', 'output path') 42 .option('--index <string>', 'split index') 43 .action((opts) => { 44 if (!opts.path || !opts.output) { 45 console.error('Error: Must provide --path and --output parameters'); 46 process.exit(1); 47 } 48 49 // 将相对路径解析为绝对路径 50 inputDir = path.resolve(opts.path); 51 outputDir = path.resolve(opts.output); 52 index = opts.index; 53 54 // 检查输入目录是否存在 55 if (!fs.existsSync(inputDir)) { 56 console.error(`Error: Input directory does not exist: ${inputDir}`); 57 process.exit(1); 58 } 59 60 let apiUtFiles1 = []; 61 readFile(inputDir + '/api', apiUtFiles1); 62 readFile(inputDir + '/arkts', apiUtFiles1); 63 apiUtFiles1 = splitEntryArray(apiUtFiles1); 64 tsTransform(inputDir, apiTransformPath, apiUtFiles1, recursionAstCallback); 65 66 tsTransform(inputDir + apiTransformPath, apiRemovalPath, apiUtFiles1, recursionAstCallback2); 67 68 tsTransform(inputDir + apiRemovalPath, jsDocTransformPath, apiUtFiles1, parseJSDocCallback); 69 70 tsTransform(inputDir + jsDocTransformPath, outputDir, apiUtFiles1, parseJSDocCallback2); 71 }); 72 program.parse(process.argv); 73} 74 75function splitEntryArray(files) { 76 // 需要并发执行 77 if (index && index !== '') { 78 const splitNumber = Number.parseInt(index); 79 if (splitNumber < 9) { 80 files = files.splice(splitNumber * SPLIT_LENGTH, SPLIT_LENGTH); 81 } else { 82 files = files.splice(splitNumber * SPLIT_LENGTH, files.length - splitNumber * SPLIT_LENGTH); 83 } 84 } 85 return files; 86} 87 88/** 89 * 读取目录下所有文件 90 * @param {string} dir 文件目录 91 * @param {Array} utFiles 所有文件 92 */ 93function readFile(dir, utFiles) { 94 if (!fs.existsSync(dir)) { 95 return; 96 } 97 try { 98 const files = fs.readdirSync(dir); 99 files.forEach((element) => { 100 if (element === 'build-tools') { 101 return; 102 } 103 const filePath = path.join(dir, element); 104 const status = fs.statSync(filePath); 105 if (status.isDirectory()) { 106 readFile(filePath, utFiles); 107 } else { 108 utFiles.push(filePath.replace(inputDir, '')); 109 } 110 }); 111 } catch (e) { 112 console.error('Error reading files: ' + e.message); 113 } 114} 115 116 117/** 118 * 遍历所有文件进行处理 119 * @param {Array} utFiles 所有文件 120 * @param {recursionAstCallback} callback 回调函数 121 */ 122function tsTransform(inputurl, outputPath, utFiles, callback) { 123 utFiles.forEach((url) => { 124 const apiBaseName = path.basename(url); 125 tagDataList = []; 126 if (/\.d\.ts/.test(apiBaseName) || /\.d\.ets/.test(apiBaseName)) { 127 let content = fs.readFileSync(inputurl + url, 'utf-8'); 128 jsDocContent = content; 129 const fileName = processFileName(inputurl + url); 130 ts.transpileModule(content, { 131 compilerOptions: { 132 target: ts.ScriptTarget.ES2017, 133 etsAnnotationsEnable: true 134 }, 135 fileName: fileName, 136 transformers: { before: [callback(inputurl + url, outputPath)] } 137 }); 138 } else { 139 let content = fs.readFileSync(inputDir + url, 'utf-8'); 140 const outputPath = replaceInputDirWithOutputDir(inputDir + url, inputDir, outputDir); 141 ensureDirectoryExists(outputPath); 142 fs.writeFileSync(outputPath, content); 143 } 144 }); 145} 146 147/** 148 * 统一处理文件名称,修改后缀等 149 * @param {string} filePath 文件路径 150 * @returns {string} filename 文件名称 151 */ 152function processFileName(filePath) { 153 return path 154 .basename(filePath) 155 .replace(/\.d\.ts$/g, '.ts') 156 .replace(/\.d\.ets$/g, '.ets'); 157} 158 159/** 160 * 每个文件处理签回调函数第一个 161 * @callback recursionAstCallback 162 * @param {string} url 文件路径 163 * @returns {Function} 164 */ 165function recursionAstCallback(url, outputDir) { 166 return (context) => { 167 return (sourceFile) => { 168 node = processVisitEachChild1(context, sourceFile); 169 const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); 170 const result = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile); 171 const outputPath = url.replace(inputDir, inputDir + outputDir); 172 ensureDirectoryExists(outputPath); 173 fs.writeFileSync(outputPath, result); 174 return ts.factory.createSourceFile([], ts.SyntaxKind.EndOfFileToken, ts.NodeFlags.None); 175 }; 176 }; 177} 178 179/** 180 * 每个文件处理签回调函数第一个 181 * @callback recursionAstCallback 182 * @param {string} url 文件路径 183 * @returns {Function} 184 */ 185function recursionAstCallback2(url, outputDir) { 186 return (context) => { 187 return (sourceFile) => { 188 node = processVisitEachChild2(context, sourceFile); 189 const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); 190 const result = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile); 191 const outputPath = url.replace(inputDir + apiTransformPath, inputDir + outputDir); 192 ensureDirectoryExists(outputPath); 193 fs.writeFileSync(outputPath, result); 194 return ts.factory.createSourceFile([], ts.SyntaxKind.EndOfFileToken, ts.NodeFlags.None); 195 }; 196 }; 197} 198 199/** 200 * 每个文件处理签回调函数第一个 201 * @callback parseJSDocCallback 202 * @param {string} url 文件路径 203 * @returns {Function} 204 */ 205function parseJSDocCallback(url, outputDir) { 206 return (context) => { 207 return (sourceFile) => { 208 node = parseJSDocVisitEachChild1(context, sourceFile); 209 changeContent(tagDataList); 210 const outputPath = url.replace(inputDir + apiRemovalPath, inputDir + outputDir); 211 ensureDirectoryExists(outputPath); 212 fs.writeFileSync(outputPath, jsDocContent); 213 return ts.factory.createSourceFile([], ts.SyntaxKind.EndOfFileToken, ts.NodeFlags.None); 214 }; 215 }; 216} 217 218/** 219 * 每个文件处理签回调函数第一个 220 * @callback parseJSDocCallback 221 * @param {string} url 文件路径 222 * @returns {Function} 223 */ 224function parseJSDocCallback2(url) { 225 return (context) => { 226 return (sourceFile) => { 227 const contents = fs.readFileSync(url, 'utf-8'); 228 node = parseJSDocVisitEachChild2(context, sourceFile, contents); 229 changeContent(tagDataList); 230 const outputPath = replaceInputDirWithOutputDir(url, inputDir + jsDocTransformPath, outputDir); 231 ensureDirectoryExists(outputPath); 232 fs.writeFileSync(outputPath, jsDocContent); 233 return ts.factory.createSourceFile([], ts.SyntaxKind.EndOfFileToken, ts.NodeFlags.None); 234 }; 235 }; 236} 237 238/** 239 * 遍历处理tsnode节点 240 * @param {context} 解下过后的内容 241 * @param {node} 解下过后的节点 242 * @returns ts.node 243 */ 244function parseJSDocVisitEachChild1(context, node) { 245 return ts.visitEachChild(node, processAllNodes, context); 246 function processAllNodes(node) { 247 if (node.jsDoc) { 248 processAllNodesJSDoc(node.jsDoc); 249 } 250 return ts.visitEachChild(node, processAllNodes, context); 251 } 252 function jsDocNodeForeach(tags) { 253 tags.forEach(tag => { 254 if (!tag.typeExpression) { 255 return; 256 } 257 const typeExpr = tag.typeExpression; 258 const newTypeExpr = parseTypeExpr(typeExpr); 259 applJSDocTransformations(typeExpr.type, newTypeExpr, tagDataList, true); 260 }); 261 } 262 function processAllNodesJSDoc(jsDocNode) { 263 jsDocNode.forEach(doc => { 264 if (!doc.tags) { 265 return; 266 } 267 if (/(long|double|int)/g.test(doc.getText())) { 268 jsDocNodeForeach(doc.tags); 269 } 270 }); 271 } 272 function parseTypeExpr(typeExpr) { 273 let newTypeExpr = typeExpr; 274 if (typeExpr.type.kind === ts.SyntaxKind.JSDocNullableType) { 275 newTypeExpr = judgeKind(typeExpr.type); 276 } else { 277 newTypeExpr = parseTypeExpression(typeExpr.type); 278 } 279 return newTypeExpr; 280 } 281 function judgeKind(typeExprType) { 282 if (typeExprType.type.kind && typeExprType.type.kind === ts.SyntaxKind.ParenthesizedType) { 283 return parseTypeExpression(typeExprType.type.type); 284 } else { 285 return parseTypeExpression(typeExprType.type); 286 } 287 } 288 function parseTypeExpression(node) { 289 if (ts.isTypeReferenceNode(node) && ts.isIdentifier(node.typeName) && typeArray.includes(node.typeName.getText())) { 290 node = ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword); 291 } 292 return ts.visitEachChild(node, parseTypeExpression, context); 293 } 294} 295 296/** 297 * 298 * @param {typeExpr} 原始typeExpr 299 * @param {newTypeExpr} 新typeExpr 300 * @param {content} 文本内容 301 */ 302function applJSDocTransformations(typeExpr, newTypeExpr, tagDataList, isChange) { 303 if (!typeExpr && !newTypeExpr) { 304 return; 305 } 306 const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); 307 const finalContent = printer.printNode(ts.EmitHint.Unspecified, newTypeExpr); 308 if (finalContent.includes('number') && typeExpr.kind === ts.SyntaxKind.JSDocNullableType && !finalContent.includes('?number') && isChange) { 309 if (typeExpr.type.type && typeExpr.type.type.kind === ts.SyntaxKind.UnionType) { 310 const data = { 311 pos: typeExpr.pos, 312 end: typeExpr.end, 313 convertedText: '?' + `(${finalContent})` 314 }; 315 tagDataList.push(data); 316 } else { 317 const data = { 318 pos: typeExpr.pos, 319 end: typeExpr.end, 320 convertedText: '?' + finalContent 321 }; 322 tagDataList.push(data); 323 } 324 } else if (finalContent.includes('number')) { 325 const data = { 326 pos: typeExpr.pos, 327 end: typeExpr.end, 328 convertedText: finalContent 329 }; 330 tagDataList.push(data); 331 } 332} 333 334/** 335 * 336 * @param {typeExpr} 原始typeExpr 337 * @param {newTypeExpr} 新typeExpr 338 * @param {content} 文本内容 339 */ 340function changeContent(tagDataList) { 341 tagDataList.sort((a, b) => b.pos - a.pos); 342 for (const data of tagDataList) { 343 const before = jsDocContent.substring(0, data.pos); 344 const after = jsDocContent.substring(data.end); 345 // 保证转换后注释空格和源码文件空格一致 346 if (jsDocContent.substring(data.pos, data.pos + 1) === ' ') { 347 jsDocContent = before + ` ${data.convertedText}` + after; 348 } else { 349 jsDocContent = before + `${data.convertedText}` + after; 350 } 351 } 352} 353 354/** 355 * 遍历处理tsnode节点 356 * @param {context} 解下过后的内容 357 * @param {node} 解下过后的节点 358 * @returns ts.node 359 */ 360function parseJSDocVisitEachChild2(context, node) { 361 return ts.visitEachChild(node, processAllNodes, context); 362 function processAllNodes(node) { 363 if (node.jsDoc) { 364 removeChangeData(node.jsDoc); 365 } 366 return ts.visitEachChild(node, processAllNodes, context); 367 } 368 function jsDocNodeForeach(tags) { 369 tags.forEach(tag => { 370 if (!tag.typeExpression) { 371 return; 372 } 373 writeDataToFile(tag); 374 }); 375 } 376 function removeChangeData(node) { 377 node.forEach(doc => { 378 if (!doc.tags) { 379 return; 380 } 381 jsDocNodeForeach(doc.tags); 382 }); 383 } 384 function writeDataToFile(tag) { 385 const typeExpr = tag.typeExpression; 386 if (ts.isJSDocNullableType(typeExpr.type) && typeExpr.type.type) { 387 const newTypeExpr = parseTypeExpression(typeExpr.type.type); 388 applJSDocTransformations(typeExpr.type.type, newTypeExpr, tagDataList, false); 389 } else { 390 const newTypeExpr = parseTypeExpression(typeExpr.type); 391 applJSDocTransformations(typeExpr.type, newTypeExpr, tagDataList, false); 392 } 393 } 394 function parseTypeExpression(node) { 395 if (ts.isUnionTypeNode(node)) { 396 const types = node.types; 397 const newTypes1 = []; 398 let newTypes2 = []; 399 types.forEach(type => { 400 newTypes1.push(collectNewTypes(type)); 401 }); 402 newTypes2 = duplicateRemoval(newTypes1); 403 node = ts.factory.updateUnionTypeNode(node, newTypes2); 404 } 405 return ts.visitEachChild(node, parseTypeExpression, context); 406 } 407} 408 409 410/** 411 * @param {type} 需要去重的节点 412 */ 413function collectNewTypes(type) { 414 if (ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName) && typeArray.includes(type.typeName.getText())) { 415 return ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword); 416 } else { 417 return type; 418 } 419} 420 421 422/** 423 * 424 * @param {type} 解析过后的节点 425 * @returns 426 */ 427function duplicateRemoval(newTypesArr) { 428 let newTypes2 = []; 429 let hasNumberKeyWorld = false; 430 newTypesArr.forEach(type => { 431 if (type.kind === ts.SyntaxKind.NumberKeyword) { 432 if (!hasNumberKeyWorld) { 433 newTypes2.push(type); 434 hasNumberKeyWorld = true; 435 } 436 } else { 437 newTypes2.push(type); 438 } 439 }); 440 441 return newTypes2; 442} 443 444/** 445 * 遍历处理tsnode节点 446 * @param {context} 解下过后的内容 447 * @param {node} 解下过后的节点 448 * @returns ts.node 449 */ 450function processVisitEachChild1(context, node) { 451 return ts.visitEachChild(node, processAllNodes, context); 452 function processAllNodes(node) { 453 if (ts.isTypeReferenceNode(node) && ts.isIdentifier(node.typeName) && typeArray.includes(node.typeName.getText())) { 454 node = ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword); 455 } 456 if (ts.isStructDeclaration(node)) { 457 node = processStructDeclaration(node); 458 } 459 return ts.visitEachChild(node, processAllNodes, context); 460 }; 461} 462 463/** 464 * 处理struct子节点 465 */ 466function processStructDeclaration(node) { 467 const newMembers = []; 468 node.members.forEach((member, index) => { 469 if (index >= 1) { 470 newMembers.push(member); 471 } 472 }); 473 node = ts.factory.updateStructDeclaration( 474 node, 475 node.modifiers, 476 node.name, 477 node.typeParameters, 478 node.heritageClauses, 479 newMembers 480 ); 481 return node; 482} 483 484 485/** 486 * 遍历处理tsnode节点去重 487 * @param {context} 解下过后的内容 488 * @param {node} 解下过后的节点 489 * @returns ts.node 490 */ 491function processVisitEachChild2(context, node) { 492 const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); 493 return ts.visitEachChild(node, processAllNodes, context); 494 function processAllNodes(node) { 495 if (ts.isUnionTypeNode(node)) { 496 node = apiDuplicateRemoval(node); 497 } 498 if (ts.isStructDeclaration(node)) { 499 node = processStructDeclaration(node); 500 } 501 return ts.visitEachChild(node, processAllNodes, context); 502 }; 503 504 function apiDuplicateRemoval(node) { 505 const types = node.types; 506 const newTypes = []; 507 const newTypesText = new Set([]); 508 types.forEach(type => { 509 const text = printer.printNode(ts.EmitHint.Unspecified, type); 510 if (!newTypesText.has(text)) { 511 newTypes.push(type); 512 newTypesText.add(text); 513 } 514 }); 515 node = ts.factory.updateUnionTypeNode(node, newTypes); 516 return node; 517 } 518} 519 520/** 521 * 将输入路径替换为输出路径 522 * @param {string} inputFilePath 输入文件的绝对路径 523 * @param {string} inputDir 输入目录的绝对路径 524 * @param {string} outputDir 输出目录的绝对路径 525 * @returns {string} 输出文件的绝对路径 526 */ 527function replaceInputDirWithOutputDir(inputFilePath, inputDir, outputDir) { 528 return inputFilePath.replace(inputDir, outputDir); 529} 530 531/** 532 * 确保目录结构存在,如果不存在则创建 533 * @param {string} filePath 文件路径 534 */ 535function ensureDirectoryExists(filePath) { 536 try { 537 const dir = path.dirname(filePath); 538 if (!fs.existsSync(dir)) { 539 fs.mkdirSync(dir, { recursive: true }); 540 } 541 } catch (error) { 542 console.error(`Error creating directory: ${error.message}`); 543 } 544} 545 546main();