• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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();