• 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 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