* Copyright (c) 2026 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 path = require('path');
const fs = require('fs');
const ts = require('typescript');
const commander = require('commander');
const { execSync } = require('child_process');
let sourceFile = null;
let componentEtsFiles = [];
const componentEtsDeleteFiles = [];
const etsDeleteFiles = [];
const kitFileNeedDeleteMap = new Map();
const ARKTS_FLAG = 'use static';
const COMPILER_OPTIONS = {
target: ts.ScriptTarget.ES2017,
etsAnnotationsEnable: true
};
let needParseWithStatic = false;
let currentApiFileContent = '';
let buildSdkPath = '';
let isClosedSource = '';
function start() {
const program = new commander.Command();
program
.name('deleteSystemApi')
.version('0.0.1');
program
.option('--input <string>', 'path name')
.option('--output <string>', 'output path')
.option('--build-sdk-path <string>', 'build sdk path')
.option('--isClosedSource <string>', 'is closed source', 'false')
.action((opts) => {
outputPath = opts.output;
inputDir = opts.input;
buildSdkPath = opts.build_sdk_path;
isClosedSource = opts.isClosedSource;
collectDeclaration(opts.input);
});
program.parse(process.argv);
}
function collectDeclaration(inputDir) {
try {
const arktsPath = path.resolve(inputDir, '../arkts');
const kitPath = path.resolve(inputDir, '../kits');
const utFiles = [];
collectComponentEtsFiles();
readFile(inputDir, utFiles);
readFile(arktsPath, utFiles);
tsTransform(sortApiList(utFiles), deleteSystemApi);
tsTransformKitFile(kitPath);
} catch (error) {
console.error('DELETE_SYSTEM_PLUGIN ERROR: ', error);
}
}
function collectComponentEtsFiles() {
const ComponentDir = path.resolve(inputDir, '@internal', 'component', 'ets');
readFile(ComponentDir, componentEtsFiles);
const arkuiComponentDir = path.resolve(inputDir, 'arkui', 'component');
readFile(arkuiComponentDir, componentEtsFiles);
componentEtsFiles = componentEtsFiles.map(item => {
return getPureName(item);
});
}
function getPureName(name) {
return path
.basename(name)
.replace('.static.ets', '')
.replace('.static.d.ets', '')
.replace('.d.ts', '')
.replace('.d.ets', '')
.replace(/_/g, '')
.toLowerCase();
}
* 解析url目录下方的kit文件,删除对应systemapi
* @param { string } kitPath kit文件路径
*/
function tsTransformKitFile(kitPath) {
kitFileNeedDeleteMap.delete('');
if (kitFileNeedDeleteMap.size === 0) {
return;
}
const kitFiles = [];
readFile(kitPath, kitFiles);
kitFiles.forEach((kitFile) => {
const kitName = processFileNameWithoutExt(kitFile).replace('@kit.', '');
const content = fs.readFileSync(kitFile, 'utf-8');
const fileAndKitComment = getFileAndKitComment(content);
const copyrightMessage = getCopyrightComment(content);
const fileName = processFileName(kitFile);
let sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.ES2017, true);
const newSourceFile = getKitNewSourceFile(sourceFile, kitName);
if (isEmptyFile(newSourceFile)) {
return;
}
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
let result = printer.printNode(ts.EmitHint.Unspecified, newSourceFile, sourceFile);
result = collectCopyrightMessage(result, copyrightMessage, fileAndKitComment);
writeFile(kitFile, result);
});
}
* 处理kit中需要删除的节点,在其他文件被systemapi修饰的api
* @param { ts.SourceFile } sourceFile
* @param { string } kitName
* @returns 删除完的节点,全部删除为空字符串
*/
function getKitNewSourceFile(sourceFile, kitName) {
const newStatements = [];
const needDeleteExportName = new Set();
const factory = ts.factory;
sourceFile.statements.forEach((statement, index) => {
if (ts.isImportDeclaration(statement)) {
const newStatement = processKitImportDeclaration(statement, needDeleteExportName);
if (newStatement) {
newStatements.push(newStatement);
}
} else if (ts.isExportDeclaration(statement)) {
const newStatement = processKitExportDeclaration(statement, needDeleteExportName);
if (newStatement) {
newStatements.push(newStatement);
}
} else {
newStatements.push(statement);
}
});
sourceFile = factory.updateSourceFile(sourceFile, newStatements);
return sourceFile;
}
function addImportToNeedDeleteExportName(importClause, needDeleteExportName) {
if (importClause.name) {
needDeleteExportName.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();
needDeleteExportName.add(element.name.escapedText.toString());
});
}
}
* 根据节点和需要删除的节点数据生成新节点
* @param { ts.ImportDeclaration } statement 需要处理的import节点
* @param { Map} needDeleteExportName 需要删除的导出节点
* @returns { ts.ImportDeclaration | undefined } 返回新的import节点,全部删除为undefined
*/
function processKitImportDeclaration(statement, needDeleteExportName) {
const factory = ts.factory;
const importClause = statement.importClause;
if (!ts.isImportClause(importClause)) {
return statement;
}
const importPath = statement.moduleSpecifier.text.toString();
if (kitFileNeedDeleteMap === undefined || !kitFileNeedDeleteMap.has(importPath)) {
const hasFilePath = hasFileByImportPath(inputDir, importPath);
if (hasFilePath && !etsDeleteFiles.includes(importPath)) {
return statement;
}
addImportToNeedDeleteExportName(importClause, needDeleteExportName);
return undefined;
}
const currImportInfo = kitFileNeedDeleteMap.get(importPath);
let defaultName = '';
let importNodeNamedBindings = [];
if (importClause.name) {
if (currImportInfo.default === importClause.name.escapedText.toString()) {
needDeleteExportName.add(currImportInfo.default);
} else {
defaultName = 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();
if (!currImportInfo.exportName.has(exportName)) {
importNodeNamedBindings.push(factory.createImportSpecifier(false, element.propertyName, element.name));
} else {
needDeleteExportName.add(element.name.escapedText.toString());
}
});
}
if (defaultName !== '' || importNodeNamedBindings.length !== 0) {
const newImportNode = factory.createImportDeclaration(
undefined,
factory.createImportClause(
false,
defaultName === '' ? undefined : factory.createIdentifier(defaultName),
importNodeNamedBindings.length === 0 ? undefined : factory.createNamedImports(importNodeNamedBindings)
),
statement.moduleSpecifier
);
return newImportNode;
}
return undefined;
}
* 处理Kit的export节点
* @param { ts.ExportDeclaration } statement 需要处理的export节点
* @param { Map} needDeleteExportName 需要删除的导出节点
* @returns { ts.ExportDeclaration | undefined } 返回新的import节点,全部删除为undefined
*/
function processKitExportDeclaration(statement, needDeleteExportName) {
const moduleSpecifier = statement.moduleSpecifier;
const exportClause = statement.exportClause;
const factory = ts.factory;
if (moduleSpecifier) {
const importPath = moduleSpecifier.text.toString();
if (kitFileNeedDeleteMap === undefined || !kitFileNeedDeleteMap.has(importPath)) {
const hasFilePath = hasFileByImportPath(inputDir, importPath);
if (hasFilePath && !etsDeleteFiles.includes(importPath)) {
return statement;
}
return undefined;
}
const currNeedDeleteInfo = kitFileNeedDeleteMap.get(importPath);
currNeedDeleteInfo.exportName.forEach(item => {
needDeleteExportName.add(item);
});
}
if (ts.isNamedExports(exportClause)) {
const exportSpecifiers = exportClause.elements.filter((element) => {
const exportName = element.propertyName ?
element.propertyName.escapedText.toString() :
element.name.escapedText.toString();
return !needDeleteExportName.has(exportName);
});
if (exportSpecifiers && exportSpecifiers.length !== 0) {
statement.exportClause = factory.updateNamedExports(statement.exportClause, exportSpecifiers);
return statement;
}
} else if (ts.isNamespaceExport(exportClause)) {
return statement;
}
return undefined;
}
* 判断文件路径对应的文件是否存在
* @param {string} apiDir 引用接口所在目录
* @param {string} importPath kit文件import
* @returns {boolean} importPath是否存在
*/
function hasFileByImportPath(apiDir, importPath) {
let fileDir = path.resolve(apiDir);
if (importPath.startsWith('@arkts')) {
fileDir = path.resolve(inputDir, '../arkts');
}
return isExistImportFile(fileDir, importPath) ||
isExistArkUIFile(path.resolve(inputDir, 'arkui', 'component'), importPath) ||
isDotPathExist(fileDir, importPath);
}
* Dot路径特殊处理
* @param {string} resolvedPath 引用接口所在目录
* @param {string} importPath kit文件import
* @returns {boolean} importPath是否存在
*/
function isDotPathExist(fileDir, importPath) {
if (['./', '@ohos', '@system'].every(str => {
return importPath.includes(str);
})) {
return false;
}
const replaced = importPath.replace(/\./g, '/');
return isExistImportFile(fileDir, replaced);
}
* Arkui import路径特殊处理
* @param {string} resolvedPath 引用接口所在目录
* @param {string} importPath kit文件import
* @returns {boolean} importPath是否存在
*/
function isExistArkUIFile(resolvedPath, importPath) {
if (importPath.startsWith('@ohos.arkui.')) {
resolvedPath = path.resolve(inputDir);
}
if (importPath.startsWith('arkui.component.') || importPath.startsWith('arkui.stateManagement.')) {
resolvedPath = path.resolve(inputDir);
importPath = importPath.replace(/\./g, '/');
}
const filePath = path.resolve(resolvedPath, importPath);
if (
filePath.includes(path.resolve(inputDir, '@internal', 'component', 'ets')) ||
filePath.includes(path.resolve(inputDir, 'arkui', 'component'))
) {
const fileName = getPureName(filePath);
return componentEtsFiles.includes(fileName);
}
return isExistImportFile(resolvedPath, importPath);
}
function isExistImportFile(fileDir, importPath) {
return ['.d.ts', '.d.ets', '.static.d.ets'].some(ext => {
return fs.existsSync(path.resolve(fileDir, `${importPath}${ext}`));
});
}
* 统一处理文件名称,修改后缀等
* @param {string} filePath 文件路径
* @returns {string} filename 文件名称
*/
function processFileName(filePath) {
return path
.basename(filePath)
.replace(/\.d\.ts$/g, '.ts')
.replace(/\.d\.ets$/g, '.ets');
}
function processFileNameWithoutExt(filePath) {
return path
.basename(filePath)
.replace(/\.static\.ets$/g, '')
.replace(/\.d\.ts$/g, '')
.replace(/\.d\.ets$/g, '')
.replace(/\.ts$/g, '')
.replace(/\.ets$/g, '');
}
* 遍历所有文件进行处理
* @param {Array} utFiles 所有文件
* @param {deleteSystemApi} callback 回调函数
*/
function tsTransform(utFiles, callback) {
utFiles.forEach((url) => {
if (!fs.existsSync(url)) {
return;
}
const apiBaseName = path.basename(url);
let content = fs.readFileSync(url, 'utf-8');
let isTransformer = /\.d\.ts/.test(apiBaseName) || /\.d\.ets/.test(apiBaseName);
if (/\.json/.test(url) || apiBaseName === 'index-full.d.ts') {
isTransformer = false;
}
if (!/\@systemapi/.test(content) && apiBaseName !== '@ohos.arkui.component.static.d.ets') {
isTransformer = false;
}
if (!isTransformer) {
writeFile(url, content);
return;
}
const uiFileDir = path.resolve(inputDir, 'arkui', 'component');
if (/\@memo/.test(content) && !url.includes(uiFileDir)) {
const nodePath = process.argv[0];
currentApiFileContent =
execSync(`cd ./package_tools/src/deleteTool && ${nodePath} ./entry.js --input ${url} --build_sdk_path ${buildSdkPath}`).toString('utf-8');
needParseWithStatic = true;
} else {
currentApiFileContent = '';
needParseWithStatic = false;
}
const fileName = processFileName(url);
ts.transpileModule(content, {
compilerOptions: COMPILER_OPTIONS,
fileName: fileName,
transformers: { before: [callback(url)] },
});
});
}
* 读取目录下所有文件
* @param {string} dir 文件目录
* @param {Array} utFiles 所有文件
*/
function readFile(dir, utFiles) {
try {
const files = fs.readdirSync(dir);
files.forEach((element) => {
const filePath = path.join(dir, element);
const status = fs.statSync(filePath);
if (status.isDirectory()) {
readFile(filePath, utFiles);
} else {
utFiles.push(filePath);
}
});
} catch (e) {
console.log('ETS ERROR: ' + e);
}
}
* 修改特殊文件的执行时序
* @param {string[]} params
* @returns {string[]}
*/
function sortApiList(apiFiles) {
const newApiFiles = [];
const specFileName = '@ohos.arkui.component.static.d.ets';
let specApiFilePath = '';
apiFiles.forEach(filePath => {
if (filePath.endsWith(specFileName)) {
specApiFilePath = filePath;
} else {
newApiFiles.push(filePath);
}
});
if (specApiFilePath !== '') {
newApiFiles.push(specApiFilePath);
}
return newApiFiles;
}
function writeFile(url, data, option) {
const urlPath = url.replace(/\.static\.d\.ets$/, '.d.ets');
const urlDirName = path.dirname(inputDir);
const relativePath = path.relative(urlDirName, urlPath);
const newFilePath = path.resolve(outputPath, relativePath);
fs.mkdir(path.dirname(newFilePath), { recursive: true }, (err) => {
if (err) {
console.log(`ERROR FOR CREATE PATH ${err}`);
} else {
if (data === '') {
fs.rmSync(newFilePath, { force: true });
return;
}
fs.writeFileSync(newFilePath, data, option, (err) => {
if (err) {
console.log(`ERROR FOR CREATE FILE ${err}`);
}
});
}
});
}
* 每个文件处理前回调函数第二个
* @param {string} url 文件路径
* @param {string} copyrightMessage 版权头注释
* @param {string} fileAndKitComment 文件kit注释
* @param {boolean} firstNodeIsStatic 第一个节点是否为use static
* @returns {Function}
*/
function formatImportDeclaration(url, copyrightMessage = '', fileAndKitComment = '') {
const allIdentifierSet = new Set([]);
return (context) => {
return (node) => {
sourceFile = node;
collectAllIdentifier(context, node, allIdentifierSet);
node = formatAllNodes(url, node, allIdentifierSet);
if (isEmptyFile(node)) {
return ts.factory.createSourceFile([], ts.SyntaxKind.EndOfFileToken, ts.NodeFlags.None);
}
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
let result = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
result = collectCopyrightMessage(result, copyrightMessage, fileAndKitComment);
copyrightMessage = node.getFullText().replace(node.getText(), '');
result = removeSystemapiDoc(result);
result = needParseWithStatic && currentApiFileContent === result ? currentApiFileContent : result;
writeFile(url, result);
return ts.factory.createSourceFile([], ts.SyntaxKind.EndOfFileToken, ts.NodeFlags.None);
};
};
}
function collectAllIdentifier(context, node, allIdentifierSet) {
if (!ts.isSourceFile(node) || !node.statements) {
return;
}
node.statements.forEach((stat) => {
if (stat.illegalDecorators) {
collectillegalNodes(ts.getAllDecorators(stat), allIdentifierSet);
}
if (!ts.isImportDeclaration(stat)) {
ts.visitEachChild(stat, collectAllNodes(context, allIdentifierSet), context);
}
});
}
function collectAllNodes(context, allIdentifierSet) {
return (node) => {
if (ts.isIdentifier(node)) {
allIdentifierSet.add(node.escapedText.toString());
}
return ts.visitEachChild(node, collectAllNodes(context, allIdentifierSet), context);
};
}
function collectillegalNodes(decorator, allIdentifierSet) {
decorator.forEach(decorator => {
allIdentifierSet.add(decorator.expression.escapedText.toString());
});
}
* 给result添加被systemapi裁剪掉的版权头信息和kit标签
* @param {string} result 文件内容
* @param {string} copyrightMessage 版权头注释
* @param {string} fileAndKitComment 文件kit注释
* @returns
*/
function collectCopyrightMessage(result, copyrightMessage, fileAndKitComment) {
const newFileAndKitComment = getFileAndKitComment(result);
const newCopyrightMessage = getCopyrightComment(result);
let commentIndex = 0;
const indexStatic = result.match(/use static.*\n/);
if (indexStatic) {
commentIndex = indexStatic.index + indexStatic[0].length;
}
if (newFileAndKitComment === '') {
result =
result.substring(0, commentIndex) +
fileAndKitComment +
'\n\n' +
result.substring(commentIndex);
}
if (newCopyrightMessage === '') {
result =
result.substring(0, commentIndex) +
copyrightMessage +
'\n\n' +
result.substring(commentIndex);
}
return result;
}
function formatAllNodes(url, node, allIdentifierSet) {
if (!ts.isSourceFile(node) || !node.statements) {
return node;
}
const newStatements = [];
node.statements.forEach((statement) => {
if (ts.isImportDeclaration(statement)) {
const importInfo = formatAllNodesImportDeclaration(
node,
statement,
url,
allIdentifierSet
);
if (importInfo) {
newStatements.push(statement);
}
} else if (ts.isStructDeclaration(statement)) {
statement = ts.factory.updateStructDeclaration(
statement,
statement.modifiers,
statement.name,
statement.typeParameters,
statement.heritageClauses,
statement.members.slice(1)
);
newStatements.push(statement);
} else {
newStatements.push(statement);
}
});
return ts.factory.updateSourceFile(node, newStatements);
}
function hasCopyright(node) {
return /http\:\/\/www\.apache\.org\/licenses\/LICENSE\-2\.0/g.test(node.getFullText().replace(node.getText(), ''));
}
* 处理Import节点 去除未使用、不存在的导入
* @param {ts.node} node 当前节点
* @param {ts.ImportDeclaration} statement 导入节点
* @param {string} url 文件路径
* @param {Set<string>} allIdentifierSet 该文件的所有Identifier关键字
* @returns {ts.ImportDeclaration|undefined} statement 处理完成的导入节点、copyrightMessage
*/
function formatAllNodesImportDeclaration(node, statement, url, allIdentifierSet) {
const clauseSet = collectClauseSet(statement);
const importSpecifier = statement.moduleSpecifier.getText().replace(/[\'\"]/g, '');
const fileDir = path.dirname(url);
let hasImportSpecifierFile = isClosedSource === 'true' || hasFileByImportPath(fileDir, importSpecifier);
if (!hasImportSpecifierFile) {
return undefined;
}
let { exsitClauseSet, hasExsitStatus, hasNonExsitStatus } =
collectNeedDeleteFlag(clauseSet, allIdentifierSet);
if (!hasExsitStatus) {
return undefined;
}
if (!hasNonExsitStatus) {
return statement;
}
return updataImportNode(statement, exsitClauseSet);
}
* 保留import节点中exsitClauseSet使用到的模块
*
* @param {ts.ImportDeclaration} statement
* @param {Set<string>} exsitClauseSet
* @returns
*/
function updataImportNode(statement, exsitClauseSet) {
const newSpecifiers = [];
statement.importClause.namedBindings.elements.forEach((element) => {
if (exsitClauseSet.has(element.name.escapedText.toString())) {
newSpecifiers.push(element);
}
});
statement.importClause.namedBindings = ts.factory.updateNamedImports(
statement.importClause.namedBindings,
newSpecifiers
);
return statement;
}
* 判断当前import的模块是否需要删除
*
* @param {Set<string>} clauseSet 当前模块集合
* @param {Set<string>} allIdentifierSet 当前文件使用到的Identifer名称
* @returns
*/
function collectNeedDeleteFlag(clauseSet, allIdentifierSet) {
let exsitClauseSet = new Set([]);
let hasExsitStatus = false;
let hasNonExsitStatus = false;
for (const clause of clauseSet) {
let flag = allIdentifierSet.has(clause);
if (flag) {
exsitClauseSet.add(clause);
hasExsitStatus = true;
} else {
hasNonExsitStatus = true;
}
}
return {
exsitClauseSet,
hasExsitStatus,
hasNonExsitStatus
};
}
* 收集import节点的导入模块
*
* @param {ts.ImportDeclaration} statement 导入节点
* @returns {Set<string>} clauseSet
*/
function collectClauseSet(statement) {
const clauseSet = new Set([]);
if (!statement.importClause || !ts.isImportClause(statement.importClause)) {
return clauseSet;
}
const clauseNode = statement.importClause;
if (!clauseNode.namedBindings && clauseNode.name && ts.isIdentifier(clauseNode.name)) {
clauseSet.add(clauseNode.name.escapedText.toString());
} else if (
clauseNode.namedBindings &&
clauseNode.namedBindings.name &&
ts.isIdentifier(clauseNode.namedBindings.name)
) {
clauseSet.add(clauseNode.namedBindings.name.escapedText.toString());
} else if (clauseNode.namedBindings && clauseNode.namedBindings.elements) {
clauseNode.namedBindings.elements.forEach((ele) => {
if (ele.name && ts.isIdentifier(ele.name)) {
clauseSet.add(ele.name.escapedText.toString());
}
});
}
return clauseSet;
}
*
* 防止@file和@kit段注释丢失
* @param {string} fileFullText
* @returns {string}
*
*/
function getFileAndKitComment(fileFullText) {
let fileAndKitComment = '';
let pattern = /\/\*\*\s*\*\s*@file[\s\S]*?@kit[\s\S]*?\*\//;
let comment = fileFullText.match(pattern);
if (comment) {
fileAndKitComment = comment[0];
}
return fileAndKitComment;
}
*
* 防止版权头段注释丢失
* @param {string} fileFullText
* @returns {string}
*
*/
function getCopyrightComment(fileFullText) {
let copyrightComment = '';
let pattern = /\/\*\s*\r?\n\s*\*\s*Copyright[\s\S]*?\*\//g;
let comment = fileFullText.match(pattern);
if (comment) {
copyrightComment = comment[0];
}
return copyrightComment;
}
* 处理最终结果中的systemapi
* @param {string} result
*/
function removeSystemapiDoc(result) {
return result.replace(/\/\*\*[\s\S]*?\*\//g, (substring, p1) => {
return /@systemapi/g.test(substring) ? '' : substring;
});
}
* 每个文件处理前回调函数第一个
* @callback deleteSystemApi
* @param {string} url 文件路径
* @returns {Function}
*/
function deleteSystemApi(url) {
return (context) => {
return (node) => {
const fullText = String(node.getFullText());
const fileAndKitComment = getFileAndKitComment(fullText);
const copyrightMessage = getCopyrightComment(fullText);
let kitName = '';
if (fullText.match(/\@kit (.*)\r?\n/g)) {
kitName = RegExp.$1.replace(/\s/g, '');
}
sourceFile = node;
const deleteNode = processSourceFile(node, url);
node = processVisitEachChild(context, deleteNode.node);
if (!isEmptyFile(node)) {
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
const result = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
const fileName = processFileName(url);
ts.transpileModule(result, {
compilerOptions: COMPILER_OPTIONS,
fileName: fileName,
transformers: { before: [formatImportDeclaration(url, copyrightMessage, fileAndKitComment)] },
});
}
return ts.factory.createSourceFile([], ts.SyntaxKind.EndOfFileToken, ts.NodeFlags.None);
};
};
}
exports.deleteSystemApi = deleteSystemApi;
* 遍历每个文件下的所有节点,然后删除节点
* @param node
* @returns
*/
* 处理最外层的节点看是否删除
* @param {ts.Node} node 解析过后的节点
* @param {string} url 当前文件kitName
* @returns
*/
function processSourceFile(node, url) {
let firstNodeIsStatic = false;
const newStatements = [];
const newStatementsWithoutExport = [];
const deleteSystemApiSet = new Set();
let needDeleteExport = {
fileName: '',
default: '',
exportName: new Set(),
};
firstNodeIsStatic = addNewStatements(node, newStatements, deleteSystemApiSet, needDeleteExport);
newStatements.forEach((statement) => {
const names = getExportIdentifierName(statement);
if (ts.isExportDeclaration(statement) && statement.moduleSpecifier && statement.moduleSpecifier.text.startsWith('./arkui/component/')) {
const importPath = statement.moduleSpecifier.text.replace('./arkui/component/', '');
const isDeleteSystemFile = componentEtsDeleteFiles.includes(getPureName(importPath));
const hasEtsFile = componentEtsFiles.includes(getPureName(importPath));
const existFile = isExistImportFile(path.dirname(url), statement.moduleSpecifier.text.toString());
if (isDeleteSystemFile || !hasEtsFile && !existFile) {
return;
}
}
if (names.length === 0) {
newStatementsWithoutExport.push(statement);
return;
}
if (names.length === 1 && !deleteSystemApiSet.has(names[0])) {
newStatementsWithoutExport.push(statement);
return;
}
processExportNode(statement, node, needDeleteExport, names, deleteSystemApiSet, newStatementsWithoutExport);
});
if (needDeleteExport.fileName !== '') {
kitFileNeedDeleteMap.set(needDeleteExport.fileName, needDeleteExport);
}
return {
node: ts.factory.updateSourceFile(node, newStatementsWithoutExport, node.isDeclarationFile, node.referencedFiles),
firstNodeIsStatic,
};
}
function processExportNode(statement, node, needDeleteExport, names, deleteSystemApiSet, newStatementsWithoutExport) {
if (ts.isExportAssignment(statement)) {
needDeleteExport.fileName = processFileNameWithoutExt(node.fileName);
needDeleteExport.default = statement.expression.escapedText.toString();
} else if (ts.isExportDeclaration(statement)) {
let needExport = false;
const newSpecifiers = [];
names.forEach((name, index) => {
const exportSpecifier = statement.exportClause.elements[index];
if (!deleteSystemApiSet.has(name)) {
newSpecifiers.push(exportSpecifier);
needExport = true;
} else {
needDeleteExport.fileName = processFileNameWithoutExt(node.fileName);
needDeleteExport.exportName.add(exportSpecifier.name.escapedText.toString());
}
});
if (needExport) {
statement.exportClause = ts.factory.updateNamedExports(statement.exportClause, newSpecifiers);
newStatementsWithoutExport.push(statement);
}
}
}
function addNewStatements(node, newStatements, deleteSystemApiSet, needDeleteExport) {
let firstNodeIsStatic = false;
node.statements.forEach((statement, index) => {
if (index === 0 && isStaticFlag(statement)) {
firstNodeIsStatic = true;
}
if (!isSystemapi(statement)) {
newStatements.push(statement);
return;
}
if (ts.isVariableStatement(statement)) {
const variableName = variableStatementGetEscapedText(statement);
deleteSystemApiSet.add(variableName);
needDeleteExport.fileName = processFileNameWithoutExt(node.fileName);
needDeleteExport.exportName.add(variableName);
} else if (
ts.isModuleDeclaration(statement) ||
ts.isInterfaceDeclaration(statement) ||
ts.isClassDeclaration(statement) ||
ts.isEnumDeclaration(statement) ||
ts.isStructDeclaration(statement) ||
ts.isTypeAliasDeclaration(statement) ||
ts.isAnnotationDeclaration(statement) ||
ts.isFunctionDeclaration(statement)
) {
if (statement && statement.name && statement.name.escapedText) {
deleteSystemApiSet.add(statement.name.escapedText.toString());
}
setDeleteExport(statement, node, needDeleteExport, deleteSystemApiSet);
} else if (ts.isExportAssignment(statement) || ts.isExportDeclaration(statement)) {
setDeleteExport(statement, node, needDeleteExport, deleteSystemApiSet);
}
});
return firstNodeIsStatic;
}
function setDeleteExport(statement, node, needDeleteExport, deleteSystemApiSet) {
if (ts.isExportAssignment(statement) && deleteSystemApiSet.has(statement.expression.escapedText.toString())) {
needDeleteExport.fileName = processFileNameWithoutExt(node.fileName);
needDeleteExport.default = statement.expression.escapedText.toString();
} else if (ts.isExportDeclaration(statement)) {
needDeleteExport.fileName = processFileNameWithoutExt(node.fileName);
statement.exportClause.elements.forEach((element) => {
const exportName = element.propertyName ?
element.propertyName.escapedText.toString() :
element.name.escapedText.toString();
if (deleteSystemApiSet.has(exportName)) {
needDeleteExport.exportName.add(element.name.escapedText.toString());
}
});
}
const modifiers = statement.modifiers;
if (modifiers === undefined) {
return;
}
const exportFlag = modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword);
const defaultFlag = modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword);
if (exportFlag && defaultFlag) {
needDeleteExport.fileName = processFileNameWithoutExt(node.fileName);
needDeleteExport.default = statement.name.escapedText.toString();
} else if (exportFlag) {
needDeleteExport.fileName = processFileNameWithoutExt(node.fileName);
needDeleteExport.exportName.add(statement.name.escapedText.toString());
}
}
* 获取export节点的名字,只获取第一个关键词
* @param {ts.node} statement
* @returns {Array<string>}
*/
function getExportIdentifierName(statement) {
const names = [];
if (ts.isExpressionStatement(statement)) {
if (ts.isBinaryExpression(statement.expression)) {
names.push(statement.expression.right.escapedText.toString());
}
} else if (ts.isExportAssignment(statement)) {
names.push(statement.expression.escapedText.toString());
} else if (ts.isExportDeclaration(statement) && statement.exportClause) {
const specifiers = exportClause.elements;
specifiers.forEach((specifier) => {
if (ts.isExportSpecifier(specifier)) {
const name = specifier.propertyName ? specifier.propertyName : specifier.name;
names.push(name.escapedText.toString());
}
});
}
return names;
}
* 遍历处理tsnode节点
* @param context 解析过后的内容
* @param node 解析过后的节点
* @returns ts.node
*/
function processVisitEachChild(context, node) {
return ts.visitEachChild(node, processAllNodes, context);
function processAllNodes(node) {
if (ts.isInterfaceDeclaration(node)) {
node = processInterfaceDeclaration(node);
} else if (ts.isClassDeclaration(node)) {
node = processClassDeclaration(node);
} else if (ts.isModuleDeclaration(node) && node.body && ts.isModuleBlock(node.body)) {
node = processModuleDeclaration(node);
} else if (ts.isEnumDeclaration(node)) {
node = processEnumDeclaration(node);
} else if (ts.isStructDeclaration(node)) {
node = processStructDeclaration(node);
} else if (ts.isAnnotationDeclaration(node)) {
node = processAnnotationDeclaration(node);
}
return ts.visitEachChild(node, processAllNodes, context);
}
}
* 处理ModuleDeclaration子节点
*/
function processModuleDeclaration(node) {
const newStatements = [];
node.body.statements.forEach((statement) => {
if (!isSystemapi(statement)) {
newStatements.push(statement);
}
});
const newModuleBody = ts.factory.updateModuleBlock(node.body, newStatements);
node = ts.factory.updateModuleDeclaration(
node,
node.modifiers,
node.name,
newModuleBody
);
return node;
}
* 处理@interface子节点
*/
function processAnnotationDeclaration(node) {
const newMembers = [];
node.members.forEach((member) => {
if (!isSystemapi(member)) {
newMembers.push(member);
}
});
node = ts.factory.updateAnnotationDeclaration(
node,
node.modifiers,
node.name,
newMembers
);
return node;
}
* 处理interface子节点
*/
function processInterfaceDeclaration(node) {
const newMembers = [];
node.members.forEach((member) => {
if (!isSystemapi(member)) {
newMembers.push(member);
}
});
node = ts.factory.updateInterfaceDeclaration(
node,
node.modifiers,
node.name,
node.typeParameters,
node.heritageClauses,
newMembers
);
return node;
}
* 处理class子节点
*/
function processClassDeclaration(node) {
const newMembers = [];
node.members.forEach((member) => {
if (!isSystemapi(member)) {
newMembers.push(member);
}
});
node = ts.factory.updateClassDeclaration(
node,
node.modifiers,
node.name,
node.typeParameters,
node.heritageClauses,
newMembers
);
return node;
}
* 处理enum子节点
*/
function processEnumDeclaration(node) {
const newMembers = [];
node.members.forEach((member) => {
if (!isSystemapi(member)) {
newMembers.push(member);
}
});
node = ts.factory.updateEnumDeclaration(
node,
node.modifiers,
node.name,
newMembers
);
return node;
}
* 处理struct子节点
*/
function processStructDeclaration(node) {
const newMembers = [];
node.members.forEach((member, index) => {
if (index >= 1 && !isSystemapi(member)) {
newMembers.push(member);
}
});
node = ts.factory.updateStructDeclaration(
node,
node.modifiers,
node.name,
node.typeParameters,
node.heritageClauses,
newMembers
);
return node;
}
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;
}
function isSystemapi(node) {
const notesContent = node.getFullText().replace(node.getText(), '').replace(/[\s]/g, '');
const notesArr = notesContent.split(/\/\*\*/);
const notesStr = notesArr[notesArr.length - 1];
if (notesStr.length !== 0) {
return /@systemapi/g.test(notesStr);
}
return false;
}
* 判断是否为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) {
for (let i = 0; i < node.statements.length; i++) {
const statement = node.statements[i];
if (ts.isImportDeclaration(statement) || isStaticFlag(statement)) {
continue;
}
isEmpty = false;
break;
}
}
const fileName = node.fileName.replace('.ts', '').replace('.static.ets', '').replace('.ets', '');
const componentFileName = getPureName(fileName);
if (isEmpty && componentEtsFiles.includes(componentFileName)) {
componentEtsDeleteFiles.push(componentFileName);
}
if (isEmpty) {
etsDeleteFiles.push(fileName);
}
return isEmpty;
}
let outputPath = '';
let inputDir = '';
start();