* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const fs = require('fs');
const path = require('path');
const ts = require('typescript');
const commander = require('commander');
let dirType = '';
const deleteApiSet = new Set();
const importNameSet = new Set();
const ARKTS_FLAG = 'use static';
const DirType = {
'dynamicApi': 'ets',
'staticApi': 'ets2',
'staticFile': 'noTagInEts2',
};
const NotNullFilePath = [
'api',
'api/@internal/ets',
'api/arkui/component',
'arkts',
'kits',
];
const COMPILER_OPTIONS = {
target: ts.ScriptTarget.ES2017,
etsAnnotationsEnable: true
};
const NOT_COPY_DIR = ['build-tools', '.git', '.gitee'];
* 默认不带标签的since为dynamic,在特殊场景需要将不带标签的处理为static,例如
* 1、文件名称使用.static.d.ets,接口未添加@since xx static
* 2、文件名称重复,同时存在.d.ets和.d.ts,.d.ets中接口未添加@since xx static
*/
let defaultIsDynamic = true;
function isEtsFile(path) {
return path.endsWith('d.ets');
}
function isTsFile(path) {
return path.endsWith('d.ts');
}
function isStaticFile(path) {
return path.endsWith('static.d.ets');
}
function hasEtsFile(path) {
if (path.includes('StateManagement.d.ts')) {
console.log('StateManagement.d.ts is in white list, return true. path = ', path);
return true;
} else {
return fs.existsSync(path.replace(/\.d\.[e]?ts$/g, '.d.ets'));
}
}
function hasTsFile(path) {
return fs.existsSync(path.replace(/\.d\.[e]?ts$/g, '.d.ts'));
}
function hasStaticFile(path) {
if (path.includes('StateManagement.d.ts')) {
console.log('StateManagement.d.ts is in white list, return true. path = ', path);
return true;
} else {
return fs.existsSync(path.replace(/\.d\.[e]?ts$/g, '.static.d.ets'));
}
}
* 配置参数
*/
function start() {
const program = new commander.Command();
program
.name('handleApiFile')
.version('0.0.1');
program
.option('--path <string>', 'path name')
.option('--type <string>', 'handle type')
.option('--output <string>', 'output path')
.option('--isPublic <string>', 'is Public')
.option('--create-keep-file <string>', 'create keep file', 'false')
.action((opts) => {
dirType = opts.type;
handleApiFiles(opts.path, opts.type, opts.output, opts.isPublic, opts.createKeepFile);
});
program.parse(process.argv);
}
* 处理API文件的入口函数
*
* @param {*} rootPath
* @param {*} type
*/
function handleApiFiles(rootPath, type, output, isPublic, createKeepFile) {
const allApiFilePathSet = new Set();
const fileNames = fs.readdirSync(rootPath);
const apiRootPath = rootPath.replace(/\\/g, '/');
fileNames.forEach(fileName => {
const apiPath = path.join(apiRootPath, fileName);
const stat = fs.statSync(apiPath);
if (NOT_COPY_DIR.includes(fileName)) {
return;
}
if (stat.isDirectory()) {
getApiFileName(apiPath, apiRootPath, allApiFilePathSet);
} else {
allApiFilePathSet.add(fileName);
}
});
for (const apiRelativePath of allApiFilePathSet) {
try {
handleApiFileByType(apiRelativePath, rootPath, type, output, isPublic);
} catch (error) {
console.log('error===>', error);
}
}
const needCreateKeepFile = createKeepFile.toString() === 'true';
if (type === DirType.staticApi && needCreateKeepFile) {
NotNullFilePath.forEach((dir) => {
const outDir = path.join(output, dir);
if (!fs.existsSync(outDir)) {
fs.mkdirSync(outDir, { recursive: true });
writeFile(path.join(outDir, '.keep'), ' ');
return;
}
const fileNames = fs.readdirSync(outDir);
if (fileNames.length === 0) {
writeFile(path.join(outDir, '.keep'), ' ');
}
});
}
}
* 根据传入的type值去处理文件
*
* @param {*} apiRelativePath
* @param {*} allApiFilePathSet
* @param {*} rootPath
* @param {*} type
* @returns
*/
function handleApiFileByType(apiRelativePath, rootPath, type, output, isPublic) {
const fullPath = path.join(rootPath, apiRelativePath);
const isEndWithEts = isEtsFile(apiRelativePath);
const isEndWithTs = isTsFile(apiRelativePath);
const isEndWithStatic = isStaticFile(apiRelativePath);
const outputPath = output ? path.join(output, apiRelativePath) : fullPath;
const fileContent = fs.readFileSync(fullPath, 'utf-8');
defaultIsDynamic = true;
if (isEndWithStatic) {
defaultIsDynamic = false;
if (type === DirType.staticApi) {
let staticFileCotent = deleteArktsTag(fileContent);
if (isPublic === 'true') {
writeFile(outputPath, deleteUnsportedTag(staticFileCotent));
return;
} else {
writeFile(outputPath.replace(/\.static\.d\.ets$/, '.d.ets'), deleteUnsportedTag(staticFileCotent));
return;
}
} else {
return;
}
}
if (!isEndWithEts && !isEndWithTs) {
writeFile(outputPath, fileContent);
return;
}
const isCorrectHandleFullpath = isHandleFullPath(fullPath, apiRelativePath, type);
if (type === DirType.staticApi && isCorrectHandleFullpath) {
handleFileInStaticApi(apiRelativePath, fullPath, type, output);
}
}
* 判断当前的文件路径是否符合当前打包场景,过滤同名文件
* @param {*} fullPath
* @param {*} apiRelativePath
* @param {*} type
* @returns
*/
function isHandleFullPath(fullPath, apiRelativePath, type) {
if (isTsFile(apiRelativePath)) {
if (type === DirType.dynamicApi) {
return true;
}
if (!(hasEtsFile(fullPath)) && !(hasStaticFile(fullPath))) {
return true;
}
}
if (isEtsFile(apiRelativePath)) {
if (!(hasTsFile(fullPath)) && !(hasStaticFile(fullPath))) {
return true;
}
if (hasTsFile(fullPath) && !(hasStaticFile(fullPath)) && type === DirType.staticApi) {
return true;
}
if (hasStaticFile(fullPath) && !(hasTsFile(fullPath)) && type === DirType.dynamicApi) {
return true;
}
}
if (isStaticFile(apiRelativePath) && type === DirType.staticApi) {
return true;
}
return false;
}
* 处理文件过滤 if arkts 1.1|1.2|1.1&1.2 定义
*
* @param {*} type
* @param {*} fileContent
* @returns
*/
function handleArktsDefinition(type, fileContent) {
const REGX_DYNAMIC = /\/\*\*\* if arkts (1\.1|dynamic) \*\/\s*([\s\S]*?)\s*\/\*\*\* endif \*\//g;
const REGX_STATIC = /\/\*\*\* if arkts (1\.2|static) \*\/\s*([\s\S]*?)\s*\/\*\*\* endif \*\//g;
const REGX_DYNAMIC_STATIC = /\/\*\*\* if arkts (1.1&1.2|dynamic&static) \*\/\s*([\s\S]*?)\s*\/\*\*\* endif \*\//g;
fileContent = fileContent.replace(REGX_DYNAMIC, (substring, p1, p2) => {
return type === DirType.dynamicApi ? p2 : '';
});
fileContent = fileContent.replace(REGX_STATIC, (substring, p1, p2) => {
if (type === DirType.staticApi) {
return p2.replace(/(\s*)(\*\s\@since\s*\S*)\s*(?=\r?\n)/g, '$1* @arkts 1.2$1$2 static');
} else {
return '';
}
});
fileContent = fileContent.replace(REGX_DYNAMIC_STATIC, (substring, p1, p2) => {
if (type === DirType.dynamicApi) {
return p2;
} else {
return p2.replace(/(\s*)(\*\s\@since\s*\S*)\s*(?=\r?\n)/g, '$1* @arkts 1.2$1$2 dynamic&static');
}
});
return fileContent;
}
* 保留static接口中static相关的jsdoc
*
* @param {*} fileContent
* @returns
*/
function saveStaticJsDoc(fileContent) {
return fileContent.replace(/\s*\/\*\*(?:(?!\/\*\*)[\s\S])*?@since[\s\S]*?\*\/\s*(?=\r?\n)/g, (substring) => {
let arktsSinceTagRegx = /\s*\*\s*@since\s\S*\s(dynamic&static|staticonly|static)\s*(?=\r?\n)/g;
let arktsTagRegx = /\s*\*\s*@arkts\s*((1\.1&)?1\.2|dynamic&static|staticonly|static)\s*(?=\r?\n)/g;
if (!defaultIsDynamic) {
arktsSinceTagRegx = /\s*\*\s*@since\s\S*(\s(dynamic&static|staticonly|static))?\s*(?=\r?\n)/g;
}
if (arktsSinceTagRegx.test(substring) || arktsTagRegx.test(substring)) {
return substring;
} else {
return '';
}
});
}
* 删除指定的arkts标签
*
* @param {*} fileContent 文件内容
* @param {*} regx 删除的正则表达式
* @returns
*/
function deleteArktsTag(fileContent) {
const arktsTagRegx = /\s*\*\s*@arkts\s(1\.1|1\.2|1\.1&1\.2|dynamic|static|dynamic&static)\s*(?=\r|\n)/g;
fileContent = fileContent.replace(arktsTagRegx, (substring, p1) => {
return '';
});
fileContent = replaceSinceDynamicStatic(fileContent);
fileContent = replaceJsDocDynamicStatic(fileContent);
return fileContent;
}
* 通过正则去除since标签之后的dynamic和static修饰符,根据dirType判断场景替换
* @param {string} fileContent
* @returns
*/
function replaceSinceDynamicStatic(fileContent) {
const arktsSinceTagRegx = /\s*\*\s*@since\s\S*(\s(dynamic&static|dynamiconly|dynamic|staticonly|static))?\s*(?=\r?\n)/g;
return fileContent.replace(arktsSinceTagRegx, (substring, tag) => {
if (!tag && defaultIsDynamic && dirType === DirType.dynamicApi) {
return substring;
}
if (!tag && !defaultIsDynamic && (dirType === DirType.staticApi || dirType === DirType.staticFile)) {
return substring;
}
const hasStatic = substring.includes('static');
if ((dirType === DirType.staticApi || dirType === DirType.staticFile) && hasStatic) {
substring = substring.replace(/\s(staticonly|(dynamic&)?static)/g, '');
} else {
substring = '';
}
return substring;
});
}
* 通过正则去除jsdoc所有标签之后的dynamiconly和staticonly修饰符,根据dirType判断场景替换
* @param {string} fileContent
* @returns
*/
function replaceJsDocDynamicStatic(fileContent) {
const getJsDocWithDynamicStaticRegx = /\s*\/\*\*(?:(?!\*\/)[\s\S])*?\[(dynamiconly|staticonly)\][\s\S]*?\*\/\s*?/g;
return fileContent.replace(getJsDocWithDynamicStaticRegx, (jsDoc) => {
return replaceJsDocTagDynamicStatic(jsDoc);
});
}
function replaceJsDocTagDynamicStatic(jsDoc) {
const jsdocTagRegx = /\s*\*\s*@\S*(?:(?!\s*\*\s*@\S*)[\s\S])*\s*\[(dynamiconly|staticonly)\]\s*(?=\r|\n)/g;
return jsDoc.replace(jsdocTagRegx, (substring, p1) => {
if (dirType === DirType.dynamicApi && p1 === 'staticonly') {
substring = '';
} else if ((dirType === DirType.staticApi || dirType === DirType.staticFile) && p1 === 'dynamiconly') {
substring = '';
} else {
substring = substring.replace(/\s*\[(dynamiconly|staticonly)\]\s*$/g, '');
substring = substring.replace(/\s*\*\s*$/g, '');
}
return substring;
});
}
* 删除1.2未支持标签
*
* @param {*} fileContent 文件内容
* @param {*} regx 删除的正则表达式
* @returns
*/
function deleteUnsportedTag(fileContent) {
const arktsTagRegx = /\*\s*@crossplatform\s*(\r|\n)\s*|\*\s*@form\s*(\r|\n)\s*|\*\s*@atomicservice\s*(\r|\n)\s*/g;
fileContent = fileContent.replace(arktsTagRegx, (substring, p1) => {
return '';
});
return fileContent;
}
* 处理ets2目录
*
* @param {string} fullPath 文件完整路径
* @returns
*/
function handleFileInStaticApi(apiRelativePath, fullPath, type, output) {
const secondRegx = /(?:\*\s(@arkts\s1.2|@arkts\sstatic|@since\s\S*\sstatic)\s*(\r|\n)\s*)/;
const thirdRegx = /(?:\*\s(@arkts\s1\.1&1\.2|@arkts\sdynamic&static|@since\s\S*\sdynamic&static)\s*(\r|\n)\s*)/;
const arktsRegx = /\/\*\*\* if arkts ((1.1|dynamic)&)?(1.2|static) \*\/\s*([\s\S]*?)\s*\/\*\*\* endif \*\//g;
let fileContent = fs.readFileSync(fullPath, 'utf-8');
let sourceFile = ts.createSourceFile(path.basename(fullPath), fileContent, ts.ScriptTarget.ES2017, true, undefined, COMPILER_OPTIONS);
const outputPath = output ? path.join(output, apiRelativePath) : fullPath;
if (!secondRegx.test(fileContent) && !thirdRegx.test(fileContent) && arktsRegx.test(fileContent)) {
saveApiByArktsDefinition(sourceFile, fileContent, outputPath);
return;
}
fileContent = handleArktsDefinition(type, fileContent);
sourceFile = ts.createSourceFile(path.basename(fullPath), fileContent, ts.ScriptTarget.ES2017, true, undefined, COMPILER_OPTIONS);
const regx = /(?:\*\s(@arkts\s1\.1|@since\s\S\sdynamic)\s*(\r|\n)\s*)/;
if (sourceFile.statements.length === 0) {
if (secondRegx.test(sourceFile.getFullText())) {
let newFileContent = deleteUnsportedTag(fileContent);
newFileContent = addStaticString(newFileContent);
writeFile(outputPath, deleteArktsTag(newFileContent));
return;
}
if (thirdRegx.test(sourceFile.getFullText())) {
handlehasTagFile(sourceFile, outputPath);
return;
}
handleNoTagFileInSecondType(sourceFile, outputPath, fullPath);
return;
}
const firstNode = sourceFile.statements.find(statement => {
return !ts.isExpressionStatement(statement);
});
if (firstNode) {
const firstJsdocText = getFileJsdoc(firstNode);
if (regx.test(firstJsdocText)) {
return;
}
}
handleNoTagFileInSecondType(sourceFile, outputPath, fullPath);
}
* 获取文件jsdoc
* @param {*} firstNode
* @returns
*/
function getFileJsdoc(firstNode) {
const firstNodeJSDoc = firstNode.getFullText().replace(firstNode.getText(), '');
const jsdocs = firstNodeJSDoc.split('*/');
let fileJSDoc = '';
for (let i = 0; i < jsdocs.length; i++) {
const jsdoc = jsdocs[i];
if (/\@file/.test(jsdoc)) {
fileJSDoc = jsdoc;
break;
}
}
return fileJSDoc;
}
* 处理有@arkts 1.1&1.2标签的文件
* @param {*} outputPath
*/
function handlehasTagFile(sourceFile, outputPath) {
dirType = DirType.staticApi;
let newContent = getDeletionContent(sourceFile);
if (newContent === '') {
return;
}
newContent = saveStaticJsDoc(newContent);
newContent = deleteUnsportedTag(newContent);
writeFile(outputPath, deleteArktsTag(newContent));
}
* 处理1.2目录中无arkts标签的文件
* @param {*} sourceFile
* @param {*} outputPath
* @returns
*/
function handleNoTagFileInSecondType(sourceFile, outputPath, fullPath) {
dirType = DirType.staticFile;
const arktsTagRegx = /\*\s*(@arkts\s(1.1&)?1.2|@since\s\S*\s(staticonly|(dynamic&)?static))\s*(\r|\n)\s*/g;
const fileContent = sourceFile.getFullText();
let newContent = '';
if (!arktsTagRegx.test(fileContent)) {
return;
}
newContent = getDeletionContent(sourceFile);
if (newContent === '') {
return;
}
newContent = saveStaticJsDoc(newContent);
newContent = deleteArktsTag(newContent);
newContent = deleteUnsportedTag(newContent);
writeFile(outputPath, newContent);
}
* 没有arkts标签,但有if arkts 1.2和1.1&1.2的情况
* @param {*} sourceFile
* @param {*} fileContent
* @param {*} outputPath
*/
function saveApiByArktsDefinition(sourceFile, fileContent, outputPath) {
const regx = /\/\*\*\* if arkts ((1\.1|dynamic)&)?(1\.2|static) \*\/\s*([\s\S]*?)\s*\/\*\*\* endif \*\//g;
const regex = /\/\*\r?\n\s*\*\s*Copyright[\s\S]*?\*\//g;
const copyrightMessage = fileContent.match(regex)[0];
const firstNode = sourceFile.statements.find(statement => {
return !ts.isExpressionStatement(statement);
});
let fileJsdoc = firstNode ? getFileJsdoc(firstNode) + '*/\n' : '';
let newContent = copyrightMessage + fileJsdoc + Array.from(fileContent.matchAll(regx), match => match[4]).join('\n');
newContent = addStaticString(newContent);
writeFile(outputPath, saveStaticJsDoc(newContent));
}
* 拼接上被删除的文件注释
*
* @param {*} deletionContent
* @param {*} sourceFile
* @returns
*/
function joinFileJsdoc(deletionContent, sourceFile) {
const fileJsdoc = sourceFile.getFullText().replace(sourceFile.getText(), '');
const copyrightMessage = hasCopyright(fileJsdoc.split('*/')[0]) ? fileJsdoc.split('*/')[0] + '*/' : '';
const regx = /@kit | @file/g;
let kitMessage = '';
if (regx.test(fileJsdoc)) {
kitMessage = fileJsdoc.split('*/')[1] + '*/';
}
let newContent = deletionContent;
const isHasCopyright = hasCopyright(deletionContent);
if (!isHasCopyright && !regx.test(deletionContent)) {
newContent = copyrightMessage + '\r\n' + kitMessage + deletionContent;
} else if (!isHasCopyright) {
newContent = copyrightMessage + '\r\n' + deletionContent;
} else if (isHasCopyright && !/@kit | @file/g.test(deletionContent)) {
const joinFileJsdoc = copyrightMessage + '\r\n' + kitMessage;
newContent = deletionContent.replace(copyrightMessage, joinFileJsdoc);
}
if (dirType !== DirType.dynamicApi) {
newContent = addStaticString(newContent);
}
return newContent;
}
function getDeletionContent(sourceFile) {
const deletionContent = deleteApi(sourceFile);
if (deletionContent === '') {
return '';
}
let newContent = joinFileJsdoc(deletionContent, sourceFile);
newContent = handleSinceInSecondType(newContent);
return newContent;
}
* 重写文件内容
* @param {*} outputPath
* @param {*} fileContent
*/
function writeFile(outputPath, fileContent) {
const outputDir = path.dirname(outputPath);
let newPath = outputPath;
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
if (dirType !== DirType.dynamicApi && isTsFile(outputPath)) {
newPath = outputPath.replace('.d.ts', '.d.ets');
}
fs.writeFileSync(newPath, fileContent);
}
* 添加use static字符串
*
* @param {*} fileContent 文件内容
* @param {*} copyrightMessage 版权头内容
* @returns
*/
function addStaticString(fileContent) {
let newContent = fileContent;
const fileContentRegex = /^\s*(['"])use static\1\s*;?$/gm;
if (fileContentRegex.test(fileContent) && fileContentRegex.exec(fileContent) &&
fileContentRegex.exec(fileContent).index > 0) {
newContent = newContent.replace(/^\s*(['"])use static\1\s*;?$/gm, '');
}
const hasStaticMessage = fileContentRegex.test(newContent);
const staticMessage = 'use static';
if (!hasStaticMessage) {
newContent = `'${staticMessage}'\r\n${newContent}`;
}
return newContent;
}
* 判断新生成的文件内容有没有版权头
*
* @param {*} fileText 新生成的文件内容
* @returns
*/
function hasCopyright(fileText) {
return /http(\:|\?:)\/\/www(\.|\/)apache\.org\/licenses\/LICENSE\-2\.0 | Copyright\s*\(c\)/gi.test(fileText);
}
const transformer = (context) => {
return (rootNode) => {
const visit = (node) => {
if (node.kind === ts.SyntaxKind.Constructor && node.parent.kind === ts.SyntaxKind.StructDeclaration) {
return undefined;
}
if ((apiNodeTypeArr.includes(node.kind) || validateExportDeclaration(node)) && judgeIsDeleteApi(node)) {
collectDeletionApiName(node);
return undefined;
}
return ts.visitEachChild(node, visit, context);
};
return ts.visitNode(rootNode, visit);
};
};
function validateExportDeclaration(node) {
return ts.isExportDeclaration(node) && node.jsDoc && node.jsDoc.length !== 0;
}
* 删除API
* @param {*} sourceFile
* @returns
*/
function deleteApi(sourceFile) {
let result = ts.transform(sourceFile, [transformer], { etsAnnotationsEnable: true });
const newSourceFile = result.transformed[0];
if (isEmptyFile(newSourceFile)) {
return '';
}
const printer = ts.createPrinter();
let fileContent = printer.printFile(newSourceFile);
result = ts.transform(newSourceFile, [transformExportApi], { etsAnnotationsEnable: true });
fileContent = printer.printFile(result.transformed[0]);
deleteApiSet.clear();
return fileContent.replace(/export\s*(?:type\s*)?\{\s*\}\s*(;)?/g, '');
}
* api被删除后,对应的export api也需要被删除
* @param {*} context
* @returns
*/
const transformExportApi = (context) => {
return (rootNode) => {
const importOrExportNodeVisitor = (node) => {
if (ts.isImportClause(node) && node.name && ts.isIdentifier(node.name) ||
ts.isImportSpecifier(node) && node.name && ts.isIdentifier(node.name)) {
importNameSet.add(node.name?.getText());
}
const apiNodeName = getApiNodeName(node);
if (apiNodeTypeArr.includes(node.kind) && deleteApiSet.has(apiNodeName)) {
deleteApiSet.delete(apiNodeName);
}
return ts.visitEachChild(node, importOrExportNodeVisitor, context);
};
ts.visitNode(rootNode, importOrExportNodeVisitor);
const allNodeVisitor = (node) => {
if (ts.isExportAssignment(node) && deleteApiSet.has(node.expression.escapedText.toString()) &&
!importNameSet.has(node.expression.escapedText.toString())) {
return undefined;
}
if (ts.isExportSpecifier(node) && deleteApiSet.has(node.name.escapedText.toString()) &&
!importNameSet.has(node.name.escapedText.toString())) {
return undefined;
}
return ts.visitEachChild(node, allNodeVisitor, context);
};
return ts.visitNode(rootNode, allNodeVisitor);
};
};
* 获取api节点名称,VariableStatement需要特殊处理
*
* @param { ts.Node } node
* @returns { string }
*/
function getApiNodeName(node) {
let apiName = '';
if (ts.isVariableStatement(node)) {
apiName = variableStatementGetEscapedText(node);
} else {
apiName = node.name?.getText();
}
return apiName;
}
* 获取 variableStatement节点名称
* @param {ts.Node} statement
* @returns
*/
function variableStatementGetEscapedText(statement) {
let name = '';
if (
statement &&
statement.declarationList &&
statement.declarationList.declarations &&
statement.declarationList.declarations.length > 0 &&
statement.declarationList.declarations[0].name &&
statement.declarationList.declarations[0].name.escapedText
) {
name = statement.declarationList.declarations[0].name.escapedText.toString();
}
return name;
}
* 判断是否为use static标记
* @param { ts.Node } node
* @returns { boolean }
*/
function isStaticFlag(node) {
return ts.isExpressionStatement(node) &&
node.expression &&
ts.isStringLiteral(node.expression) &&
node.expression.text &&
node.expression.text === ARKTS_FLAG;
}
function isEmptyFile(node) {
let isEmpty = true;
if (ts.isSourceFile(node) && node.statements) {
const needExportName = new Set();
for (let i = 0; i < node.statements.length; i++) {
const statement = node.statements[i];
if (isStaticFlag(statement)) {
continue;
}
if (ts.isExportDeclaration(statement) && statement.moduleSpecifier) {
isEmpty = false;
break;
}
if (judgeExportHasImport(statement, needExportName)) {
continue;
}
isEmpty = false;
break;
}
}
return isEmpty;
}
function collectDeletionApiName(node) {
if (!ts.isImportClause(node)) {
deleteApiSet.add(node.name?.getText());
return;
}
if (ts.isImportDeclaration(node) && node.importClause?.name) {
deleteApiSet.add(node.importClause.name.escapedText.toString());
return;
}
const namedBindings = node.namedBindings;
if (namedBindings !== undefined && ts.isNamedImports(namedBindings)) {
const elements = namedBindings.elements;
elements.forEach((element) => {
const exportName = element.propertyName ?
element.propertyName.escapedText.toString() :
element.name.escapedText.toString();
deleteApiSet.add(exportName);
});
}
}
* 判断import节点和export节点。
* 当前文本如果还有其他节点则不能删除,
* 如果只有import和export则判断是否export导出import节点
*
* @param {*} statement
* @param {*} needExportName
* @returns
*/
function judgeExportHasImport(statement, needExportName) {
if (ts.isImportDeclaration(statement)) {
processImportDeclaration(statement, needExportName);
return true;
} else if (ts.isExportAssignment(statement) &&
!needExportName.has(statement.expression.escapedText.toString())) {
return true;
} else if (ts.isExportDeclaration(statement)) {
return !statement.exportClause.elements.some((element) => {
const exportName = element.propertyName ?
element.propertyName.escapedText.toString() :
element.name.escapedText.toString();
return needExportName.has(exportName);
});
}
return false;
}
function processImportDeclaration(statement, needExportName) {
const importClause = statement.importClause;
if (!ts.isImportClause(importClause)) {
return;
}
if (importClause.name) {
needExportName.add(importClause.name.escapedText.toString());
}
const namedBindings = importClause.namedBindings;
if (namedBindings !== undefined && ts.isNamedImports(namedBindings)) {
const elements = namedBindings.elements;
elements.forEach((element) => {
const exportName = element.propertyName ?
element.propertyName.escapedText.toString() :
element.name.escapedText.toString();
needExportName.add(exportName);
});
}
}
* 判断node节点中是否有famodelonly/deprecated/arkts <=1.1标签
*
* @param {*} node
* @returns
*/
function judgeIsDeleteApi(node) {
const notesContent = node.getFullText().replace(node.getText(), '');
if (notesContent.replace(/\s/g, '') === '') {
return false;
}
const notesArr = notesContent.split(/\/\*\*/);
const notesStr = notesArr[notesArr.length - 1];
const sinceArr = notesStr.match(/@since\s*\d+/);
let sinceVersion = 20;
const hasDynamicTag = /@arkts\s*1\.1(&1\.2)?/g.test(notesStr);
const hasStaticTag = /@arkts\s*(1\.1&)?1\.2/g.test(notesStr);
const hasDynamicSince = /@since\s\S*(\s(dynamiconly|dynamic(&static)?))?\s*(?=\r?\n)/g.test(notesStr);
const hasStaticSince = /@since\s\S*\s(staticonly|(dynamic&)?static)/g.test(notesStr);
const hasDeprecatedTag = /@deprecated/g.test(notesStr);
if (dirType === DirType.dynamicApi) {
return (!hasDynamicTag && hasStaticTag) || (hasStaticSince && !hasDynamicSince);
}
if (sinceArr) {
sinceVersion = sinceArr[0].replace(/@since\s*/, '');
}
if (dirType === DirType.staticApi) {
return (hasDeprecatedTag && sinceVersion < 20) ||
(hasDynamicTag && !hasStaticTag) ||
(hasDynamicSince && !hasStaticSince);
}
if (dirType === DirType.staticFile) {
return !hasStaticTag && !hasStaticSince;
}
return false;
}
* 生成1.2目录里文件时,需要去掉since标签里的dynamic版本号
*
* @param {*} fileContent
* @returns
*/
function handleSinceInSecondType(fileContent) {
const regx = /@since\s+arkts\s*(\{.*\})/g;
fileContent = fileContent.replace(regx, (substring, p1) => {
const versionObj = JSON.parse(p1.replace(/'/g, '"'));
const staticVersion = versionObj['1.2'] || versionObj.static;
return '@since ' + staticVersion + ' static';
});
return fileContent;
}
*
* @param { string } apiPath 需要处理的api文件所在路径
* @param { string } rootPath ets文件夹路径
* @returns { Set<string> } 需要处理的api文件的相对于ets目录的路径
*/
function getApiFileName(apiPath, rootPath, allApiFilePathSet) {
const apiFilePathSet = new Set();
const fileNames = fs.readdirSync(apiPath);
fileNames.forEach(fileName => {
const apiFilePath = path.join(apiPath, fileName).replace(/\\/g, '/');
const stat = fs.statSync(apiFilePath);
if (stat.isDirectory()) {
getApiFileName(apiFilePath, rootPath, allApiFilePathSet);
} else {
const apiRelativePath = apiFilePath.replace(rootPath, '');
allApiFilePathSet.add(apiRelativePath);
}
});
return apiFilePathSet;
}
const apiNodeTypeArr = [
ts.SyntaxKind.VariableStatement,
ts.SyntaxKind.MethodDeclaration,
ts.SyntaxKind.MethodSignature,
ts.SyntaxKind.FunctionDeclaration,
ts.SyntaxKind.Constructor,
ts.SyntaxKind.ConstructSignature,
ts.SyntaxKind.CallSignature,
ts.SyntaxKind.PropertyDeclaration,
ts.SyntaxKind.PropertySignature,
ts.SyntaxKind.EnumMember,
ts.SyntaxKind.EnumDeclaration,
ts.SyntaxKind.TypeAliasDeclaration,
ts.SyntaxKind.ClassDeclaration,
ts.SyntaxKind.InterfaceDeclaration,
ts.SyntaxKind.ModuleDeclaration,
ts.SyntaxKind.StructDeclaration,
ts.SyntaxKind.GetAccessor,
ts.SyntaxKind.SetAccessor,
ts.SyntaxKind.IndexSignature,
ts.SyntaxKind.OverloadDeclaration
];
start();