* 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 commander = require('commander');
let collectAllIdentifierNode = undefined;
let filetContent = '';
let nodePositionArr = [];
let inputDir = '';
let outputDir = '';
const configPath = path.resolve(__dirname, '../dependence/arktsconfig.json');
const CONFIG = {
ES2PANDA_LIB_PATH: '/build-tools/koala-wrapper/build/lib/es2panda',
ETS_PANDA_LIB_PATH: '/build-tools/ets2panda/lib',
ARKTS_CONFIG_PATH: configPath,
ES_FILE_EXTENSION: 'ets',
DEBUG_INFO_FLAG: '--debug-info',
};
function isStaticFile(path) {
return path.endsWith('static.d.ets');
}
* 主函数:初始化命令行参数解析并执行文件处理任务
* @returns {void}
*/
function main() {
const program = new commander.Command();
program.name('delete').version('0.0.1').description('使用es2panda SDK解析文件并提取源码内容');
program
.option('--input <string>', '输入文件目录路径')
.option('--output <string>', '输出文件目录路径')
.option('--build_sdk_path <string>', 'SDK构建工具路径')
.action(opts => {
outputDir = opts.output;
let inputPath = opts.input.split('/');
inputPath.pop();
inputPath = path.resolve(inputPath.join('/'));
inputDir = inputPath;
processFilesInDirectory(inputDir, opts.build_sdk_path)
});
program.parse(process.argv);
}
function configGlobal(sdkPath) {
const req = require(sdkPath + CONFIG.ES2PANDA_LIB_PATH);
arkts = req.arkts;
arktsGlobal = req.arktsGlobal;
const tsWrapper = require('./ts_wrapper_node_util');
collectAllIdentifierNode = tsWrapper.collectAllIdentifierNode;
}
* 主入口函数:遍历指定目录下的所有文件并使用SDK工具进行解析处理
*
* @param {string} inputDir 输入文件夹路径
* @param {string} sdkPath SDK依赖路径
* @throws {Error} 当输入参数无效时抛出异常
*/
function processFilesInDirectory(inputDir, sdkPath) {
if (!inputDir || typeof inputDir !== 'string') {
throw new Error('输入目录路径不能为空');
}
if (!sdkPath || typeof sdkPath !== 'string') {
throw new Error('SDK路径不能为空');
}
try {
configGlobal(sdkPath);
const allFiles = [];
collectAllFiles(inputDir, allFiles);
if (allFiles.length === 0) {
console.log(`未在目录 ${inputDir} 中找到任何文件`);
return;
}
let fileNames = new Map();
const currentPath = process.env.PATH || '';
const etspandaPath = sdkPath + CONFIG.ETS_PANDA_LIB_PATH;
process.env.PATH = `${currentPath}${path.delimiter}${etspandaPath}`;
process.env.LD_LIBRARY_PATH = etspandaPath;
const ets2pandaCmd = [
'_',
'--extension',
CONFIG.ES_FILE_EXTENSION,
'--arktsconfig',
CONFIG.ARKTS_CONFIG_PATH,
'--simultaneous',
];
ets2pandaCmd.push(CONFIG.DEBUG_INFO_FLAG);
ets2pandaCmd.push(inputDir);
arktsGlobal.filePath = inputDir;
arktsGlobal.config = arkts.Config.create(ets2pandaCmd).peer;
arktsGlobal.compilerContext = arkts.Context.createContextGenerateAbcForExternalSourceFiles(allFiles);
arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_PARSED, arktsGlobal.compilerContext.peer);
const contextPtr = arkts.arktsGlobal.compilerContext?.peer ?? peer;
let program = arkts.getOrUpdateGlobalContext(contextPtr).program;
const visited = new Set();
const queue = [program];
let index = 0;
while (queue.length > 0) {
const currProgram = queue.shift();
index++
if (visited.has(currProgram.peer)) {
continue;
}
if (index !== 1) {
traverseProgram(currProgram.astNode, currProgram.absName);
}
visited.add(currProgram.peer);
for (const externalSource of currProgram.externalSources) {
visitNextProgramInQueue(queue, visited, externalSource, fileNames);
}
}
} catch (error) {
console.error('DELETE_SYSTEM_PLUGIN ERROR: ', error);
throw error;
}
}
* 递归收集目录下所有文件路径
* @param {string} dir 要扫描的目录路径
* @param {Array<string>} fileArray 用于存储所有文件路径的数组
* @throws {Error} 当目录不存在或无法访问时抛出异常
* @returns {void}
*/
function collectAllFiles(dir, fileArray) {
if (!dir || typeof dir !== 'string') {
throw new Error('目录路径不能为空');
}
if (!Array.isArray(fileArray)) {
throw new Error('文件数组必须为数组类型');
}
try {
const files = fs.readdirSync(dir);
files.forEach(element => {
try {
const filePath = path.join(dir, element);
const status = fs.statSync(filePath);
if (status.isDirectory()) {
collectAllFiles(filePath, fileArray);
} else {
fileArray.push(filePath);
}
} catch (e) {
console.warn(`无法访问文件/目录: ${element}`, e.message);
}
});
} catch (e) {
console.error(`读取目录失败: ${dir}`, e.message);
throw e;
}
}
function visitNextProgramInQueue(queue, visited, externalSource, fileNames) {
const nextProgramArr = externalSource.programs ?? [];
for (const nextProgram of nextProgramArr) {
fileNames.set(nextProgram.peer, externalSource.getName());
if (!visited.has(nextProgram.peer)) {
queue.push(nextProgram);
}
}
}
function traverseProgram(node, filePath) {
if (!isStaticFile(filePath)) {
return;
}
filetContent = fs.readFileSync(filePath, 'utf-8');
if (!/\@systemapi/.test(filetContent)) {
writeFile(filePath, filetContent)
return
}
if (node.statements) {
node.statements.forEach(statement => {
collectNodePosition(statement, true)
})
}
processVisitEachChild(node, filePath);
formatImportDeclaration(node)
formatExportDeclaration(node)
nodePositionArr = deduplicateAndSort(nodePositionArr)
console.log(nodePositionArr)
nodePositionArr.forEach(item => {
filetContent = replaceTextByPosition(filetContent, item.startLine, item.startCol, item.endLine, item.endCol)
})
writeFile(filePath, filetContent)
nodePositionArr = []
}
* 对收集到的位置信息进行去重并排序
* @param {Array} arr 需要删除的API位置信息
*/
function deduplicateAndSort(arr) {
const uniqueArr = arr.filter((obj, index, self) =>
index === self.findIndex((o) => (
o.startLine === obj.startLine &&
o.startCol === obj.startCol &&
o.endLine === obj.endLine &&
o.endCol === obj.endCol
))
);
uniqueArr.sort((a, b) => {
if (a.startLine !== b.startLine) {
return b.startLine - a.startLine;
}
if (a.startCol !== b.startCol) {
return b.startCol - a.startCol;
}
return b.endLine - a.endLine;
});
return uniqueArr;
}
* 递归访问AST节点的每个子节点,根据节点类型进行处理
* @param {arkts.AstNode} node 当前AST节点
* @returns {arkts.AstNode} 处理后的AST节点
*/
function processVisitEachChild(node, filePath) {
return arkts.visitEachChild(node, (child) => processAllNodes(child));
function processAllNodes(node) {
let kind = arkts.arktsGlobal.generatedEs2panda._AstNodeTypeConst(arkts.arktsGlobal.context, node.peer);
const statementJSDoc = arkts.getJsdocStringFromDeclaration(node);
if (arkts.isTSInterfaceDeclaration(node)) {
node.body.body.forEach(item => {
collectNodePosition(item, true);
});
} else if (arkts.isClassDeclaration(node)) {
if (node.definition.body) {
node.definition.body.forEach(item => {
collectNodePosition(item, true);
})
}
} else if (arkts.isEtsScript(node)) {
if (node.statements) {
node.statements.forEach(item => {
collectNodePosition(item, true);
})
}
} else if (arkts.isTSEnumDeclaration(node)) {
if (node.members) {
node.members.forEach(item => {
collectNodePosition(item, true);
})
}
} else if (arkts.isStructDeclaration(node)) {
if (node.definition.body) {
node.definition.body.forEach(item => {
collectNodePosition(item, true);
})
}
} else if (arkts.isAnnotationDeclaration(node)) {
if (node.properties) {
node.properties.forEach(item => {
collectNodePosition(item, true);
})
}
} else if (arkts.isTSTypeAliasDeclaration(node)) {
if (node.typeAnnotation && node.typeAnnotation.types) {
node.typeAnnotation.types.forEach(item => {
collectNodePosition(item, true);
})
}
} else if (arkts.isFunctionDeclaration(node)) {
collectNodePosition(node, true);
} else {
if (statementJSDoc !== 'Empty Jsdoc' && statementJSDoc !== '') {
processVisitEachChild(node);
}
}
return node;
}
}
function collectNodePosition(node, isHasJSDoc) {
if (!isSystemapi(node)) {
return;
}
let startLinePosition = ''
if (isHasJSDoc) {
startLinePosition = findCommentBoundariesAsync(node.startPosition.line() + 1)
if (nodePositionArr.length !== 0) {
if (node.parent.startPosition.line() > 1) {
const result = nodePositionArr.some(item =>
item.startLine === findCommentBoundariesAsync(node.parent.startPosition.line() + 1) &&
item.startCol === 1 &&
item.endLine === node.parent.endPosition.line() + 1 &&
item.endCol === node.parent.endPosition.col() + 1
);
if (result) {
return;
}
}
}
nodePositionArr.push({ startLine: startLinePosition, startCol: node.startPosition.col(), endLine: node.endPosition.line() + 1, endCol: node.endPosition.col() + 1 })
} else {
nodePositionArr.push({ startLine: node.startPosition.line() + 1, startCol: node.startPosition.col(), endLine: node.endPosition.line() + 1, endCol: node.endPosition.col() + 1 })
}
}
function findCommentBoundariesAsync(startLine) {
const lines = filetContent.split(/\r?\n/);
if (startLine < 1 || startLine > lines.length) {
throw new Error('Invalid start line number');
}
let endLine = -1;
let startComment = -1;
for (let i = startLine - 1; i >= 0; i--) {
const line = lines[i].trim();
if (endLine === -1 && line === '*/') {
endLine = i + 1;
continue;
}
if (endLine !== -1 && line === '/**') {
startComment = i + 1;
break;
}
}
if (startComment === -1 || endLine === -1) {
throw new Error('Comment boundaries not found');
}
return startComment
}
* 替换文本中指定位置的内容为 ""
* @param {string} originalText - 原始文本
* @param {number} startLine - 起始行号(从1开始)
* @param {number} startCol - 起始列号(从1开始)
* @param {number} endLine - 结束行号(从1开始)
* @param {number} endCol - 结束列号(从1开始)
* @returns {string} 替换后的文本
*/
function replaceTextByPosition(originalText, startLine, startCol, endLine, endCol) {
const lines = originalText.split('\n');
if (startLine < 1 || endLine < 1) {
throw new Error('Invalid line or column numbers');
}
if (startLine === endLine) {
const line = lines[startLine - 1];
const newLine = line.substring(0, startCol - 1) + '' + line.substring(endCol - 1);
lines[startLine - 1] = newLine;
} else {
lines.splice(startLine - 1, (endLine - startLine) + 1);
}
return lines.join('\n');
}
* 判断节点是否包含@systemapi标签
* @param {arkts.AstNode} node 要检查的AST节点
* @returns {boolean} 如果节点包含@systemapi标签则返回true,否则返回false
*/
function isSystemapi(node) {
const statementJSDoc = arkts.getJsdocStringFromDeclaration(node);
const notesArr = statementJSDoc.split(/\/\*\*/);
const notesStr = notesArr[notesArr.length - 1];
if (notesStr.length !== 0) {
return /@systemapi/g.test(notesStr);
}
return false;
}
function formatImportDeclaration(node) {
const allIdentifierSet = new Set([]);
let identifierArr = []
identifierArr = collectAllIdentifierNode(node);
processVisitEachImportChild(node, identifierArr)
if (identifierArr.length !== 0) {
identifierArr.forEach(item => {
allIdentifierSet.add(item)
})
}
let importItemArr = []
node.statements.forEach(item => {
if (arkts.isETSImportDeclaration(item)) {
item.specifiers.forEach(specifier => {
if (!allIdentifierSet.has(specifier.local.name)) {
importItemArr.push(specifier)
}
})
if (importItemArr.length === item.specifiers.length) {
collectNodePosition(item, false)
} else if (importItemArr.length !== 0) {
importItemArr.forEach(importItem => {
collectNodePosition(importItem, false)
})
}
}
importItemArr = [];
})
}
function processVisitEachImportChild(node, arr) {
return arkts.visitEachChild(node, (child) => processAllNodes(child));
function processAllNodes(node) {
const statementJSDoc = arkts.getJsdocStringFromDeclaration(node);
if (arkts.isTSInterfaceDeclaration(node)) {
node.body.body.forEach(item => {
collectIdentifierNode(node);
});
} else if (arkts.isClassDeclaration(node)) {
if (node.definition.body) {
node.definition.body.forEach(item => {
collectIdentifierNode(node);
})
}
} else if (arkts.isEtsScript(node)) {
if (node.statements) {
node.statements.forEach(item => {
collectIdentifierNode(node);
})
}
} else if (arkts.isTSEnumDeclaration(node)) {
if (node.members) {
node.members.forEach(item => {
collectIdentifierNode(node);
})
}
} else if (arkts.isStructDeclaration(node)) {
if (node.definition.body) {
node.definition.body.forEach(item => {
collectIdentifierNode(node);
})
}
} else if (arkts.isAnnotationDeclaration(node)) {
if (node.properties) {
node.properties.forEach(item => {
collectIdentifierNode(node);
})
}
} else if (arkts.isTSTypeAliasDeclaration(node)) {
if (node.typeAnnotation && node.typeAnnotation.types) {
node.typeAnnotation.types.forEach(item => {
collectIdentifierNode(node);
})
}
} else if (arkts.isFunctionDeclaration(node)) {
collectIdentifierNode(node);
} else {
if (statementJSDoc !== 'Empty Jsdoc' && statementJSDoc !== '') {
processVisitEachChild(node);
}
}
return node;
}
}
function collectIdentifierNode(node) {
if (!isSystemapi(node)) {
let newArr = collectAllIdentifierNode(node)
let arr = [];
newArr.forEach(item => {
if (!arr.includes(item)) {
arr.push(item)
}
})
}
}
* 获取当前节点类型
* @param {arkts.AstNode} node 当前AST节点
* @returns {void}
*/
function getNodeType(node) {
let kind = arkts.arktsGlobal.generatedEs2panda._AstNodeTypeConst(arkts.arktsGlobal.context, node.peer);
return kind
}
function formatExportDeclaration(node) {
const allIdentifierSet = new Set([]);
let identifierArr = collectAllIdentifierNode(node)
if (identifierArr.length !== 0) {
identifierArr.forEach(item => {
allIdentifierSet.add(item)
})
}
node.statements.forEach(statement => {
let kind = getNodeType(statement)
if (kind === arkts.Es2pandaAstNodeType.AST_NODE_TYPE_EXPORT_NAMED_DECLARATION) {
statement.specifiers.forEach(specifier => {
if (!allIdentifierSet.has(specifier.local.name)) {
collectNodePosition(specifier, false)
}
})
}
})
}
function writeFile(url, data) {
if (!outputDir) {
return
}
const relativePath = path.relative(inputDir, url);
const newFilePath = path.resolve(outputDir, relativePath).replace(/\.static\.d\.ets$/, '.d.ets');
fs.mkdir(path.dirname(newFilePath), { recursive: true }, (err) => {
if (err) {
console.log(`ERROR FOR CREATE PATH ${err.message}`);
} else {
if (data === '') {
fs.rmSync(newFilePath, { force: true });
return;
}
fs.writeFileSync(newFilePath, data, (err) => {
if (err) {
console.log(`ERROR FOR CREATE FILE ${err}`);
}
});
}
});
}
main()