From b9a8c92baa4258fb1a11723cc4c22e3a48b45dc9 Mon Sep 17 00:00:00 2001 From: xiaonan_0 Date: Mon, 8 Sep 2025 14:23:32 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9Eapicheck=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: xiaonan_0 Change-Id: I2a95fd4c922e9e404b800962a7dff8911ec0e2e8 --- BUILD.gn | 25 + .../custom-comment-parser/index.ts | 16 + .../src/custom_comment_parser.ts | 113 ++ .../api-check-wrapper/index.ts | 18 + .../src/api_check_wrapper.ts | 324 ++++ .../utils/api_check_wrapper_enums.ts | 19 + .../utils/api_check_wrapper_typedef.ts | 117 ++ .../utils/ts_wrapper_node_util.ts | 1641 +++++++++++++++++ .../api-check-plugin-static/babel.config.js | 41 + .../custom-import-plugin.js | 51 + .../api-check-plugin-static/index.ts | 88 + .../api-check-plugin-static/package.json | 22 + .../src/api_check_config.ts | 173 ++ .../utils/api_check_plugin_define.ts | 61 + .../utils/api_check_plugin_enums.ts | 23 + .../utils/api_check_plugin_typedef.ts | 145 ++ .../utils/api_check_plugin_utils.ts | 1012 ++++++++++ build-tools/package.json | 2 +- build_api_check_plugin.py | 74 + 19 files changed, 3964 insertions(+), 1 deletion(-) create mode 100755 build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/custom-plugins/custom-comment-parser/index.ts create mode 100755 build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/custom-plugins/custom-comment-parser/src/custom_comment_parser.ts create mode 100755 build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/index.ts create mode 100755 build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/src/api_check_wrapper.ts create mode 100755 build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/utils/api_check_wrapper_enums.ts create mode 100755 build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/utils/api_check_wrapper_typedef.ts create mode 100755 build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/utils/ts_wrapper_node_util.ts create mode 100755 build-tools/compile-plugins/api-check-plugin-static/babel.config.js create mode 100755 build-tools/compile-plugins/api-check-plugin-static/custom-import-plugin.js create mode 100755 build-tools/compile-plugins/api-check-plugin-static/index.ts create mode 100755 build-tools/compile-plugins/api-check-plugin-static/package.json create mode 100755 build-tools/compile-plugins/api-check-plugin-static/src/api_check_config.ts create mode 100755 build-tools/compile-plugins/api-check-plugin-static/utils/api_check_plugin_define.ts create mode 100755 build-tools/compile-plugins/api-check-plugin-static/utils/api_check_plugin_enums.ts create mode 100755 build-tools/compile-plugins/api-check-plugin-static/utils/api_check_plugin_typedef.ts create mode 100755 build-tools/compile-plugins/api-check-plugin-static/utils/api_check_plugin_utils.ts create mode 100755 build_api_check_plugin.py diff --git a/BUILD.gn b/BUILD.gn index 083a5a35ac..68a0262ee6 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -401,3 +401,28 @@ ohos_copy("ohos_ets_api") { part_name = "sdk" subsystem_name = "sdk" } + +action("gen_api_check_plugin") { + deps = [ "//developtools/ace_ets2bundle/arkui-plugins:ui_plugin" ] + npm_path = "//prebuilts/build-tools/common/nodejs/current/bin/npm" + script = "build_api_check_plugin.py" + args = [ + "--source_path", + rebase_path( + get_path_info("./build-tools/compile-plugins/api-check-plugin-static", + "abspath")), + "--output_path", + rebase_path("$target_gen_dir"), + "--npm", + rebase_path(npm_path), + ] + outputs = [ "$target_gen_dir" ] +} + +ohos_copy("api_check_plugin") { + deps = [ ":gen_api_check_plugin" ] + sources = [ rebase_path("$target_gen_dir") ] + outputs = [ target_out_dir + "/$target_name" ] + module_source_dir = target_out_dir + "/$target_name" + module_install_name = "" +} diff --git a/build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/custom-plugins/custom-comment-parser/index.ts b/build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/custom-plugins/custom-comment-parser/index.ts new file mode 100755 index 0000000000..9c2f286b54 --- /dev/null +++ b/build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/custom-plugins/custom-comment-parser/index.ts @@ -0,0 +1,16 @@ +/* + * 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. + */ + +export { parseJSDoc } from './src/custom_comment_parser'; \ No newline at end of file diff --git a/build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/custom-plugins/custom-comment-parser/src/custom_comment_parser.ts b/build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/custom-plugins/custom-comment-parser/src/custom_comment_parser.ts new file mode 100755 index 0000000000..99f3932628 --- /dev/null +++ b/build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/custom-plugins/custom-comment-parser/src/custom_comment_parser.ts @@ -0,0 +1,113 @@ +/* + * 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. + */ + +import { JSDoc, JSDocTag } from "../../../utils/api_check_wrapper_typedef"; + +/** + * 解析JSDoc字符串 + * + * @param { string } jsDocContent JSDoc字符串 + * @returns { JSDoc[] } JSDoc数组 + */ +export function parseJSDoc(jsDocContent: string): JSDoc[] { + const jsdocReg: RegExp = /(\/\*\*\s)(.|\n)*?(\s\*\/)/g; + const jsDocs: JSDoc[] = []; + const matches: RegExpStringIterator = jsDocContent.matchAll(jsdocReg); + for (const match of matches) { + if (match && match[0]) { + const jsDocContent: string = match[0]; + const description: string = collectDescription(jsDocContent); + const jsDocTags: JSDocTag[] = collectJSDocTags(jsDocContent); + jsDocs.push({ + description: description, + tags: jsDocTags + }); + } + } + return jsDocs; +} + +/** + * 匹配JSDoc整体内容 + * + * @param { string } jsDocContent + * @returns { string } + */ +function collectDescription(jsDocContent: string): string { + const jsDocDescriptionReg: RegExp = /(?<=\/\*\*\s)(.|\n)*?(?=(\s|\*)(\@|\s\*\/))/g; + let description: string = ''; + const descriptionMatche: RegExpMatchArray | null = jsDocContent.match(jsDocDescriptionReg); + if (descriptionMatche && descriptionMatche[0]) { + description = descriptionMatche[0].replace(/ *\* */g, '').replace(/\n|\r\n/g, ' '); + } + return description; +} + +/** + * 匹配"@"符号后内容 + * + * @param { string } jsDocContent + * @returns { JSDocTag[] } + */ +function collectJSDocTags(jsDocContent: string): JSDocTag[] { + const jsdocTagReg: RegExp = /(?<=(\s|\*)\@)(.|\n)*?(?=(\@|\s\*\/))/g; + const jsDocTags: JSDocTag[] = []; + const matches: RegExpStringIterator = jsDocContent.matchAll(jsdocTagReg); + for (const match of matches) { + if (match && match[0]) { + const jsDocTagContent: string = match[0].replace(/ *\* */g, '').replace(/\n|\r\n/g, ' '); + const jsDocTag: JSDocTag | undefined = collectJSDocTag(jsDocTagContent); + if (jsDocTag) { + jsDocTags.push(jsDocTag); + } + } + } + return jsDocTags; +} + +/** + * 将解析后的JSDoc内容组装成JSDocTag对象 + * + * @param { string } jsDocTagContent + * @returns { JSDocTag | undefined } + */ +function collectJSDocTag(jsDocTagContent: string): JSDocTag | undefined { + const jsDocTagTypeReg: RegExp = /(?<=\{).*?(?=\})/; + let content: string = jsDocTagContent; + const tagMatch: RegExpMatchArray | null = content.match(/\S+/); + const jsDocTag: JSDocTag = { + tag: '', + name: '', + description: '', + comment: '' + } + if (tagMatch && tagMatch[0]) { + jsDocTag.tag = tagMatch[0]; + content = content.replace(tagMatch[0], '').trim(); + if (/\S+/g.test(content)) { + jsDocTag.comment = content; + const tagNameMatch: RegExpMatchArray | null = content.match(/\S+/); + if (tagNameMatch && tagNameMatch[0]) { + jsDocTag.name = tagNameMatch[0]; + content = content.replace(tagNameMatch[0], '').trim(); + if (/\S+/g.test(content)) { + jsDocTag.description = content; + } + } + } + return jsDocTag; + } + return undefined; +} \ No newline at end of file diff --git a/build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/index.ts b/build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/index.ts new file mode 100755 index 0000000000..2b9456ff52 --- /dev/null +++ b/build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/index.ts @@ -0,0 +1,18 @@ +/* + * 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. + */ + +export * from './utils/api_check_wrapper_typedef'; +export * from './utils/api_check_wrapper_enums'; +export * from './custom-plugins/custom-comment-parser'; \ No newline at end of file diff --git a/build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/src/api_check_wrapper.ts b/build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/src/api_check_wrapper.ts new file mode 100755 index 0000000000..fd43e76142 --- /dev/null +++ b/build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/src/api_check_wrapper.ts @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2022-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. + */ + +import * as arkts from '@koalaui/libarkts'; +import { traverseProgram, getJSDocInformation } from '../utils/ts_wrapper_node_util'; +import { + ApiCheckWrapperServiceHost, + JsDocNodeCheckConfig, + FileCheckModuleInfo, + JSDoc, + JSDocTag, + JsDocNodeCheckConfigItem, + CurrentAddress, + LegacyStructMap +} from '../utils/api_check_wrapper_typedef'; +import { SINCE_TAG_NAME } from "../../utils/api_check_plugin_define"; +import { parseJSDoc } from '../custom-plugins/custom-comment-parser'; +import { globalObject } from '../../index'; +export let curApiCheckWrapper: ApiCheckWrapper; +export let curFileCheckModuleInfo: FileCheckModuleInfo; + +/** + * 导出arkts命名空间 + */ +export const WrapperApi = { + ...arkts +} + +let checkedNode = new Map(); + +export class ApiCheckWrapper { + constructor(apiCheckHost: ApiCheckWrapperServiceHost) { + this.apiCheckHost = apiCheckHost; + } + setFileName(fileName: string) { + this.fileName = fileName; + } + + fileName: string // ets源文件位置 + + sourcefile: string // 声明节点位置 + + apiCheckHost: ApiCheckWrapperServiceHost +} + +/** + * API检查的入口逻辑,通过获取当前ets文件路径,设置参数,循环遍历节点。 + * + * @param { ApiCheckWrapperServiceHost } apiCheckHost host对象,提供检查配置和工具方法 + * @param { number | undefined } peer 上下文标识 + */ +export function checkApiExpression(apiCheckHost: ApiCheckWrapperServiceHost, peer: number | undefined):void { + const contextPtr = arkts.arktsGlobal.compilerContext?.peer ?? peer; + if (contextPtr == null || contextPtr == undefined) { + return; + } + + curApiCheckWrapper = new ApiCheckWrapper(apiCheckHost); + curFileCheckModuleInfo = {} as FileCheckModuleInfo; + let fileNames: Map = new Map(); + let legacyModuleList: string[] = []; + let legacyStructMap: Map = new Map(); + + // 获取当前ets文件路径,设置参数 + let program = arkts.getOrUpdateGlobalContext(contextPtr).program; + const visited = new Set(); + const queue: arkts.Program[] = [program]; + getLegacyModule(legacyStructMap, legacyModuleList); + while (queue.length > 0) { + const currProgram = queue.shift()!; + if (visited.has(currProgram.peer)) { + continue; + } + if (currProgram.peer !== program.peer) { + const name: string = fileNames.get(currProgram.peer)!; + if (legacyModuleList && matchPrefix(legacyModuleList, name)) { + curApiCheckWrapper.fileName = currProgram.sourceFilePath; + curFileCheckModuleInfo.currentFileName = currProgram.fileName; + // 清空map对象 + checkedNode = new Map(); + // 获取根节点,开始遍历 + traverseProgram(currProgram.astNode); + } + } + visited.add(currProgram.peer); + for (const externalSource of currProgram.externalSources) { + visitNextProgramInQueue(queue, visited, externalSource, fileNames); + } + } +} + +/** + * 从项目配置的依赖模块中收集需要验证的节点。 + * + * @param { Map } legacyStructMap 用于存储需要验证的结构映射的Map(键为模块名) + * @param { string[] } legacyModuleList 用于存储需要验证的节点数组 + */ +function getLegacyModule(legacyStructMap: Map, legacyModuleList: string[]): void { + const moduleList = globalObject.projectConfig?.dependentModuleList; + if (moduleList === undefined) { + return; + } + for (const module of moduleList) { + const moduleName = module.moduleName; + if (!legacyStructMap.has(moduleName)) { + legacyStructMap.set(moduleName, {}); + legacyModuleList.push(moduleName); + } + } +} + +/** + * 匹配当前文件名是否需要验证。 + * + * @param { (string | RegExp)[] } prefixCollection 前缀集合,元素可为字符串或正则表达式 + * @param { string } name 需要检查的文件名 + * @returns { boolean } 若文件名匹配任一前缀,返回true;否则返回false + */ +function matchPrefix(prefixCollection: (string | RegExp)[], name: string): boolean { + for (const prefix of prefixCollection) { + let regex: RegExp; + + if (typeof prefix === 'string') { + regex = new RegExp('^' + prefix); + } else { + regex = new RegExp('^' + prefix.source); + } + + if (regex.test(name)) { + return true; + } + } + return false; +} + +/** + * 将program节点加入遍历队列并记录文件名。 + * + * @param { arkts.Program[] } queue 程序遍历队列,用于存储待处理的程序对象 + * @param { Set } visited 已访问程序的标识集合,用于去重 + * @param { arkts.ExternalSource } externalSource 外部源对象,包含关联的程序 + * @param { Map } fileNames 用于存储程序标识与文件名映射的Map + */ +function visitNextProgramInQueue( + queue: arkts.Program[], + visited: Set, + externalSource: arkts.ExternalSource, + fileNames: Map +): void { + const nextProgramArr: arkts.Program[] = externalSource.programs ?? []; + for (const nextProgram of nextProgramArr) { + fileNames.set(nextProgram.peer, externalSource.getName()); + if (!visited.has(nextProgram.peer)) { + queue.push(nextProgram); + } + } +} + +/** + * 检查Identifier节点对应的声明是否符合API规范(基于JSDoc配置)。 + * + * @param { arkts.AstNode } node 需要检查的标识符AST节点 + */ +export function checkIdentifier(node: arkts.AstNode) { + // 获取校验节点的声明节点 + const decl = arkts.getDecl(node); + + if (decl === undefined || decl === null) { + return; + } + // 获取声明节点系统路径 + let sysPath = getSysPath(decl); + + if (sysPath === undefined || sysPath === null) { + return + } + let checkPram: JsDocNodeCheckConfig = curApiCheckWrapper.apiCheckHost.getJsDocNodeCheckedConfig( + curFileCheckModuleInfo.currentFileName, sysPath); + if (!checkPram.nodeNeedCheck) { + return; + } + // 获取校验节点的行列信息 + const address = getCurrentAddressByNode(node); + if (confirmNodeChecked(node.name, address.line, address.column)) { + return; + } + //进入检查jsdoc流程 + expressionCheckByJsDoc(decl, node, address, checkPram.checkConfig); +} + +/** + * 获取校验节点的行列信息,实现打印报错去重。 + * + * @param { string } nodeName 标识符节点的名称 + * @param { number } line 节点所在的行号 + * @param { number } col 节点所在的列号 + */ +function confirmNodeChecked(nodeName: string, line: number, col: number) { + let nodeKey = curApiCheckWrapper.fileName + "_" + nodeName + "_" + line + "_" + col; + if (checkedNode.has(nodeKey) && checkedNode.get(nodeKey) !== undefined) { + return true; + } else { + checkedNode.set(nodeKey, nodeName); + } +} + +/** + * 获取声明节点的Api文件路径 + * + * @param { arkts.AstNode } decl 声明节点 + * @returns { string } Api文件路径 + */ +function getSysPath(decl: arkts.AstNode): string { + // 获取节点的声明节点, + let program = arkts.getProgramFromAstNode(decl); + return program.sourceFilePath; +} + +/** + * 通过声明节点获取Jsdoc注释内容 + * + * @param { arkts.AstNde } decl 声明节点 + * @returns { string } 注释信息 + */ +export function getPeerJsDocs(decl: arkts.AstNde): string { + return getJSDocInformation(decl); +} + +/** + * 遍历jsdoc信息,对AST节点进行规则校验并打印报错信息。 + * + * @param { arkts.AstNode } declaration 声明节点 + * @param { arkts.AstNode } identifier AST节点 + * @param { CurrentAddress } address 当前文件地址 + * @param { JsDocNodeCheckConfigItem[] } checkConfig 校验配置 + */ +function expressionCheckByJsDoc( + declaration: arkts.AstNode, identifier: arkts.AstNode, + address: CurrentAddress, checkConfig: JsDocNodeCheckConfigItem[]): void { + const jsDocsString = getPeerJsDocs(declaration); + const jsDocs: JSDoc[] = parseJSDoc(jsDocsString); + const jsDocTags = getCurrentJSDoc(jsDocs); + for (let i = 0; i < checkConfig.length; i++) { + const config = checkConfig[i]; + let tagNameExisted = false; + jsDocTags.forEach((item) => { + tagNameExisted = false; + if (config.tagName.includes(item.tag)) { + if (config.checkValidCallback) { + tagNameExisted = config.checkValidCallback(jsDocs, config); + } else { + tagNameExisted = true; + } + } + if (tagNameExisted && !config.tagNameShouldExisted) { + curApiCheckWrapper.apiCheckHost.pushLogInfo( + identifier.name, curApiCheckWrapper.fileName, + address, config.type, config.message); + } + }) + if (config.tagNameShouldExisted && !tagNameExisted) { + curApiCheckWrapper.apiCheckHost.pushLogInfo( + identifier.name, curApiCheckWrapper.fileName, + address, config.type, config.message); + } + } +} + +/** + * 获取AST节点在源文件中的行列位置信息 + * + * @param { arkts.AstNode } node 需要获取行列信息的节点 + * @returns { CurrentAddress } 节点行列信息 + */ +function getCurrentAddressByNode(node: arkts.AstNode): CurrentAddress { + let address = {} as CurrentAddress; + let startPosition = node.startPosition; + address.column = startPosition.col(); + address.line = startPosition.line() + 1; + return address; +} + +/** + * 通过比较JSDoc注释中@since标签的版本号,筛选出版本号最大的注释对象, + * 返回其包含的所有标签(JSDocTag),用于获取最新版本的API文档注释信息。 + * + * @param { JSDoc[] } jsDocs JSDoc注释对象数组 + * @returns { JSDocTag[] } 最新版本JSDoc注释中的标签数组;若无有效注释,返回空数组 + */ +function getCurrentJSDoc(jsDocs: JSDoc[]): JSDocTag[] { + let jsDocTags: JSDocTag[] = []; + let maxVersion: number = 0; + if (jsDocs && jsDocs.length > 0) { + for (let i = 0; i < jsDocs.length; i++) { + const jsdoc: JSDoc = jsDocs[i]; + if (jsdoc.tags && jsdoc.tags.length > 0) { + for (let j = 0; j < jsdoc.tags.length; j++) { + const tag: JSDocTag = jsdoc.tags[j]; + if (tag.tag === SINCE_TAG_NAME) { + const currentVersion: number = Number.parseInt(tag.name ?? "0"); + if (!Number.isNaN(currentVersion) && currentVersion > maxVersion) { + maxVersion = currentVersion; + jsDocTags = jsdoc.tags; + } + break; + } + } + } + } + } + return jsDocTags; +} \ No newline at end of file diff --git a/build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/utils/api_check_wrapper_enums.ts b/build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/utils/api_check_wrapper_enums.ts new file mode 100755 index 0000000000..b5b3ee904f --- /dev/null +++ b/build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/utils/api_check_wrapper_enums.ts @@ -0,0 +1,19 @@ +/* + * 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. + */ + +export enum DiagnosticCategory { + WARNING = 0, + ERROR = 1 +} \ No newline at end of file diff --git a/build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/utils/api_check_wrapper_typedef.ts b/build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/utils/api_check_wrapper_typedef.ts new file mode 100755 index 0000000000..3a5d40359f --- /dev/null +++ b/build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/utils/api_check_wrapper_typedef.ts @@ -0,0 +1,117 @@ +/* + * 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. + */ + +import { DiagnosticCategory } from "./api_check_wrapper_enums"; +import * as arkts from '@koalaui/libarkts'; + +/** + * ApiCheckWrapper服务,绑定校验规则 + */ +export interface ApiCheckWrapperServiceHost { + getJsDocNodeCheckedConfig: (currentFileName: string, symbolSourceFilePath: string) => JsDocNodeCheckConfig; + getFileCheckedModuleInfo: (containFilePath: string) => FileCheckModuleInfo; + pushLogInfo: ( + apiName: string, currentFilePath: string, + currentAddress: CurrentAddress, logLevel: DiagnosticCategory, logMessage: string) => void; + collectImportInfo: (moduleName: string[], modulePath: string, currentFilePath: string) => void; +} + +export interface JsDocNodeCheckConfig { + nodeNeedCheck: boolean; + checkConfig: JsDocNodeCheckConfigItem[]; +} + +export interface JsDocNodeCheckConfigItem { + tagName: string[]; + message: string; + type: DiagnosticCategory; + tagNameShouldExisted: boolean; + checkValidCallback?: (jsDocs: JSDoc[], config: JsDocNodeCheckConfigItem) => boolean; +} + +/** + * JSDoc类型 + */ +export interface JSDoc { + description: string; + tags: JSDocTag[]; +} + +/** + * JSDoc标签类型 + */ +export interface JSDocTag { + /** + * 标签名 + */ + tag: string; + /** + * 参数名称 + */ + name: string; + /** + * 默认值 + */ + default?: string; + /** + * 描述 + */ + description: string; + /** + * 完整内容 + */ + comment: string; +} + +export interface CurrentAddress { + line: number; + column: number; +} + +export interface FileCheckModuleInfo { + currentFileName: string; + fileNeedCheck: boolean; +} + +export interface ConditionCheckResult { + valid: boolean; + type?: DiagnosticCategory; + message?: string; +} + +export interface ASTDeclaration extends arkts.AstNode { + kind: number; + pos: number; + end: number; + parent?: ASTDeclaration; + jsDoc?: JSDoc[]; +} + +export interface ASTIdentifier extends arkts.AstNode { + kind: number; + text: string; +} + +export interface ASTSourceFile extends arkts.AstNode { + fileName: string; + text: string; +} + +/** + * 存储需要验证的节点键值对 + */ +export interface LegacyStructMap { + [key: string]: string; +} \ No newline at end of file diff --git a/build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/utils/ts_wrapper_node_util.ts b/build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/utils/ts_wrapper_node_util.ts new file mode 100755 index 0000000000..92898858d9 --- /dev/null +++ b/build-tools/compile-plugins/api-check-plugin-static/api-check-wrapper/utils/ts_wrapper_node_util.ts @@ -0,0 +1,1641 @@ +/* + * 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. + */ + +import * as arkts from '@koalaui/libarkts'; +import { checkIdentifier } from "../src/api_check_wrapper"; + +export const nodeHandleFunctionMap = new Map void>([ + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ASSIGNMENT_EXPRESSION, handleAssignmentExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ANNOTATION_USAGE, handleAnnotatedAstNode], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_LABELLED_STATEMENT, handleLabelledStatement], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_THROW_STATEMENT, handleThrowStatement], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_CLASS_PROPERTY, handleClassProperty], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_VOID_KEYWORD, handleTSVoidKeyword], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_FUNCTION_TYPE, handleETSFunctionType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_TYPE_OPERATOR, handleTSTypeOperator], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_IF_STATEMENT, handleIfStatement], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_CONSTRUCTOR_TYPE, handleTSConstructorType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_DECORATOR, handleDecorator], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_ENUM_DECLARATION, handleTSEnumDeclaration], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_NEVER_KEYWORD, handleTSNeverKeyword], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_IMPORT_DEFAULT_SPECIFIER, handleImportDefaultSpecifier], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_IMPORT_SPECIFIER, handleImportSpecifier], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_CONDITIONAL_EXPRESSION, handleConditionalExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_CALL_EXPRESSION, handleCallExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_BIGINT_LITERAL, handleBigIntLiteral], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_IMPORT_TYPE, handleTSImportType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TAGGED_TEMPLATE_EXPRESSION, handleTaggedTemplateExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_FUNCTION_DECLARATION, handleFunctionDeclaration], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_TYPE_REFERENCE, handleETSTypeReference], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_TYPE_REFERENCE, handleTSTypeReference], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_NAMED_TYPE, handleNamedType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_NUMBER_LITERAL, handleNumberLiteral], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_FUNCTION_TYPE, handleTSFunctionType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TEMPLATE_ELEMENT, handleTemplateElement], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_INTERFACE_DECLARATION, handleTSInterfaceDeclaration], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_VARIABLE_DECLARATION, handleVariableDeclaration], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_UNDEFINED_LITERAL, handleUndefinedLiteral], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_MEMBER_EXPRESSION, handleMemberExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_CLASS_IMPLEMENTS, handleTSClassImplements], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_OBJECT_KEYWORD, handleTSObjectKeyword], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_UNION_TYPE, handleETSUnionType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_PROPERTY_SIGNATURE, handleTSPropertySignature], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_CONDITIONAL_TYPE, handleTSConditionalType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_LITERAL_TYPE, handleTSLiteralType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_TYPE_ALIAS_DECLARATION, handleTSTypeAliasDeclaration], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_DEBUGGER_STATEMENT, handleDebuggerStatement], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_RETURN_STATEMENT, handleReturnStatement], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_EXPORT_DEFAULT_DECLARATION, handleExportDefaultDeclaration], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_SCRIPT_FUNCTION, handleScriptFunction], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_CLASS_DEFINITION, handleClassDefinition], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_INTERFACE_BODY, handleTSInterfaceBody], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_TYPE_QUERY, handleTSTypeQuery], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_BIGINT_KEYWORD, handleTSBigintKeyword], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_PROPERTY, handleProperty], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_VARIABLE_DECLARATOR, handleVariableDeclarator], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_STRING_LITERAL, handleStringLiteral], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_TYPE_ASSERTION, handleTSTypeAssertion], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_EXTERNAL_MODULE_REFERENCE, handleTSExternalModuleReference], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_UNDEFINED_KEYWORD, handleTSUndefinedKeyword], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_TUPLE, handleETSTuple], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TRY_STATEMENT, handleTryStatement], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_UNARY_EXPRESSION, handleUnaryExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_FOR_IN_STATEMENT, handleForInStatement], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_THIS_EXPRESSION, handleThisExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_METHOD_SIGNATURE, handleTSMethodSignature], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_BINARY_EXPRESSION, handleBinaryExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_SUPER_EXPRESSION, handleSuperExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ASSERT_STATEMENT, handleAssertStatement], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_STRING_KEYWORD, handleTSStringKeyword], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_EXPRESSION_STATEMENT, handleExpressionStatement], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE, handleETSModule], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_META_PROPERTY_EXPRESSION, handleMetaProperty], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_ARRAY_TYPE, handleTSArrayType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_SIGNATURE_DECLARATION, handleTSSignatureDeclaration], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_EXPORT_ALL_DECLARATION, handleExportAllDeclaration], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_EXPORT_SPECIFIER, handleExportSpecifier], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_TUPLE_TYPE, handleTSTupleType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_FUNCTION_EXPRESSION, handleFunctionExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_INDEX_SIGNATURE, handleTSIndexSignature], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_MODULE_DECLARATION, handleTSModuleDeclaration], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_IMPORT_DECLARATION, handleImportDeclaration], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_PARENT_TYPE, handleTSParenthesizedType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_CHAR_LITERAL, handleCharLiteral], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_PACKAGE_DECLARATION, handleETSPackageDeclaration], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_IMPORT_DECLARATION, handleETSImportDeclaration], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_STRUCT_DECLARATION, handleETSStructDeclaration], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_MODULE_BLOCK, handleTSModuleBlock], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_NEW_ARRAY_INSTANCE_EXPRESSION, handleETSNewArrayInstanceExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ANNOTATION_DECLARATION, handleAnnotationDeclaration], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ANNOTATION_USAGE, handleAnnotationUsage], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_EMPTY_STATEMENT, handleEmptyStatement], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_WHILE_STATEMENT, handleWhileStatement], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_CHAIN_EXPRESSION, handleChainExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_INTERSECTION_TYPE, handleTSIntersectionType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_UPDATE_EXPRESSION, handleUpdateExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_BLOCK_EXPRESSION, handleBlockExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_TYPE_LITERAL, handleTSTypeLiteral], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_TYPE_PARAMETER, handleTSTypeParameter], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_BOOLEAN_LITERAL, handleTSBooleanKeyword], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_TYPE_PREDICATE, handleTSTypePredicate], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_IMPORT_NAMESPACE_SPECIFIER, handleImportNamespaceSpecifier], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_EXPORT_NAMED_DECLARATION, handleExportNamedDeclaration], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_PARAMETER_EXPRESSION, handleETSParameterExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_TYPE_PARAMETER_INSTANTIATION, handleTSTypeParameterInstantiation], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_NULL_LITERAL, handleNullLiteral], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_INFER_TYPE, handleTSInferType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_SWITCH_CASE_STATEMENT, handleSwitchCaseStatement], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_YIELD_EXPRESSION, handleYieldExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_IMPORT_EQUALS_DECLARATION, handleTSImportEqualsDeclaration], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_BOOLEAN_LITERAL, handleBooleanLiteral], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_NUMBER_KEYWORD, handleTSNumberKeyword], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_CLASS_STATIC_BLOCK, handleClassStaticBlock], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_NON_NULL_EXPRESSION, handleTSNonNullExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_PREFIX_ASSERTION_EXPRESSION, handlePrefixAssertionExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_CLASS_EXPRESSION, handleClassExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_FOR_OF_STATEMENT, handleForOfStatement], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TEMPLATE_LITERAL, handleTemplateLiteral], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_UNION_TYPE, handleTSUnionType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_UNKNOWN_KEYWORD, handleTSUnknownKeyword], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_IDENTIFIER, handleIdentifier], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_OPAQUE_TYPE_NODE, handleOpaqueTypeNode], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_BLOCK_STATEMENT, handleBlockStatement], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_DIRECT_EVAL, handleDirectEvalExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_TYPE_PARAMETER_DECLARATION, handleTSTypeParameterDeclaration], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_METHOD_DEFINITION, handleMethodDefinition], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_NULL_KEYWORD, handleTSNullKeyword], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_INTERFACE_HERITAGE, handleTSInterfaceHeritage], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_CLASS_LITERAL, handleETSClassLiteral], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_BREAK_STATEMENT, handleBreakStatement], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_REGEXP_LITERAL, handleRegExpLiteral], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_MAPPED_TYPE, handleTSMappedType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_ANY_KEYWORD, handleTSAnyKeyword], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_CLASS_DECLARATION, handleClassDeclaration], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_INDEXED_ACCESS_TYPE, handleTSIndexedAccessType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_QUALIFIED_NAME, handleTSQualifiedName], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_AWAIT_EXPRESSION, handleAwaitExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_CONTINUE_STATEMENT, handleContinueStatement], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_NEW_MULTI_DIM_ARRAY_INSTANCE_EXPRESSION, + handleETSNewMultiDimArrayInstanceExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_NAMED_TUPLE_MEMBER, handleTSNamedTupleMember], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_IMPORT_EXPRESSION, handleImportExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_NULL_TYPE, handleETSNullType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_UNDEFINED_TYPE, handleETSUndefinedType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TYPEOF_EXPRESSION, handleTypeofExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_ENUM_MEMBER, handleTSEnumMember], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_SWITCH_STATEMENT, handleSwitchStatement], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_DO_WHILE_STATEMENT, handleDoWhileStatement], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_CATCH_CLAUSE, handleCatchClause], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_SEQUENCE_EXPRESSION, handleSequenceExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ARROW_FUNCTION_EXPRESSION, handleArrowFunctionExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_OMITTED_EXPRESSION, handleOmittedExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_NEW_CLASS_INSTANCE_EXPRESSION, handleETSNewClassInstanceExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_AS_EXPRESSION, handleTSAsExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_FOR_UPDATE_STATEMENT, handleForUpdateStatement], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_TYPE_REFERENCE_PART, handleETSTypeReferencePart], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_REEXPORT_STATEMENT, handleETSReExportDeclaration], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_PRIMITIVE_TYPE, handleETSPrimitiveType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_NEW_EXPRESSION, handleNewExpression], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_PARAMETER_PROPERTY, handleTSParameterProperty], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_WILDCARD_TYPE, handleETSWildcardType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_THIS_TYPE, handleTSThisType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ARRAY_EXPRESSION, handleArrayExpression] +]) + +type NodeTuple = [string, number]; +const items: NodeTuple[] = []; +const isDebug = false; +const nodeMap = new Map(); +let exporssionCount = 0; + +/** + * 根节点处理 + * @param { arkts.AstNode } node + */ +export function traverseProgram(node: arkts.AstNode):void { + // 处理Identifier + if (!!node.statements) { + node.statements.forEach((item: arkts.AstNode) => handleAstNode(item)); + } +} + +/** + * 获取Jsdoc信息 + * @param { arkts.AstNode } node 节点 + * @returns { string } jsdoc 字符串 + */ +export function getJSDocInformation(node: arkts.AstNode): string { + return arkts.getJsdocStringFromDeclaration(node); +} + +/** + * 解析AST + * + * @param { arkts.AstNode } node 节点 + * @param { (node: any, ...args: any[]) => void } func 实际方法 + */ +function handleFunction(node: arkts.AstNode, func: (node: any, ...args: any[]) => void):void { + if (isDebug) { + items.push([func.name, node.peer.toString()]); + } + // Expression 下的identifier才会提示告警 + if (node instanceof arkts.Expression) { + exporssionCount++; + } + if (node == null || node == undefined) { + return; + } + + func(node); + + if (isDebug) { + if (nodeMap.has(node.peer.toString())) { + if (nodeMap.get(node.peer.toString()) > 2) { + console.error("[API_CHECK_PLUGIN] repeat traverse"); + } + nodeMap.set(node.peer.toString(), nodeMap.get(node.peer.toString()) + 1); + } else { + nodeMap.set(node.peer.toString(), 1); + } + items.pop(); + } + if (node instanceof arkts.Expression) { + exporssionCount--; + } +} + +export function handleAstNode(node: arkts.AstNode) { + if (isDebug) { + console.info('[API_CHECK_PLUGIN] handleAstNode'); + } + let kind: number = arkts.arktsGlobal.generatedEs2panda._AstNodeTypeConst(arkts.arktsGlobal.context, node.peer) + if (nodeHandleFunctionMap.has(kind) && nodeHandleFunctionMap.get(kind) !== undefined) { + handleFunction(node, nodeHandleFunctionMap.get(kind)!) + } +} + +export function handleAnnotatedAstNode(node: arkts.AstNode) { } + +export function handleAnnotatedExpression(node: arkts.AstNode) { + if (!!node.typeAnnotation) { + handleAstNode(node.typeAnnotation); + } +} + +export function handleAnnotatedStatement(node: arkts.AstNode) { } + +export function handleAnnotationDeclaration(node: arkts.AstNode) { + if (!!node.expr) { + handleAstNode(node.expr); + } + if (!!node.properties) { + node.properties.forEach((item: arkts.AstNode) => handleAstNode(item)); + } + if (!!node.annotations) { + node.annotations.forEach((item: arkts.AstNode) => handleFunction(item, handleAnnotationUsage)); + } +} + +export function handleAnnotationUsage(node: arkts.AstNode) { + if (!!node.expr) { + handleAstNode(node.expr); + } + if (!!node.properties) { + node.properties.forEach((item: arkts.AstNode) => handleAstNode(item)); + } +} + +export function handleArrayExpression(node: arkts.AstNode) { + if (!!node.elements) { + node.elements.forEach((item: arkts.AstNode) => handleAstNode(item)); + } + if (!!node.declarators) { + node.declarators.forEach((item: arkts.AstNode) => handleFunction(item, handleDecorator)); + } + if (!!node.typeAnnotation) { + handleAstNode(node.typeAnnotation); + } +} + +export function handleArrowFunctionExpression(node: arkts.AstNode) { + if (!!node.annotations) { + node.annotations.forEach((item: arkts.AstNode) => handleFunction(item, handleAnnotationUsage)); + } + if (!!node.scriptFunction) { + handleAstNode(node.scriptFunction); + } +} + +export function handleAssertStatement(node: arkts.AstNode) { + if (!!node.test) { + handleAstNode(node.test); + } + if (!!node.second) { + handleAstNode(node.second); + } +} + +export function handleAssignmentExpression(node: arkts.AstNode) { + if (!!node.left) { + handleAstNode(node.left); + } + if (!!node.right) { + handleAstNode(node.right); + } +} + +export function handleAstDumper(node: arkts.AstNode) { + return; +} + +export function handleAwaitExpression(node: arkts.AstNode) { + if (!!node.argument) { + handleAstNode(node.argument); + } +} + +export function handleBigIntLiteral(node: arkts.AstNode) { } + +export function handleBinaryExpression(node: arkts.AstNode) { + if (!!node.left) { + handleAstNode(node.left); + } + if (!!node.right) { + handleAstNode(node.right); + } + if (!!node.result) { + handleAstNode(node.result); + } +} + +export function handleBlockExpression(node: arkts.AstNode) { + if (!!node.statements) { + node.statements.forEach((item: arkts.AstNode) => handleAstNode(item)); + } +} + +export function handleBlockStatement(node: arkts.AstNode) { + if (!!node.statements) { + node.statements.forEach((item: arkts.AstNode) => handleAstNode(item)); + } +} + +export function handleBooleanLiteral(node: arkts.AstNode) { } + +export function handleBreakStatement(node: arkts.AstNode) { + if (!!node.ident) { + handleFunction(node.ident, handleIdentifier); + } + if (!!node.target) { + handleAstNode(node.target); + } +} + +export function handleCallExpression(node: arkts.AstNode) { + if (!!node.trailingBlock) { + handleAstNode(node.trailingBlock); + } + if (!!node.expression) { + handleAstNode(node.expression); + } + if (!!node.typeArguments) { + node.typeArguments.forEach((item: arkts.TypeNode) => handleAstNode(item)); + } + if (!!node.arguments) { + node.arguments.forEach((item: arkts.Expression) => handleAstNode(item)); + } + if (!!node.params) { + node.params.forEach((item: arkts.TypeNode) => handleAstNode(item)); + } +} + +export function handleCatchClause(node: arkts.AstNode) { + if (!!node.param) { + handleAstNode(node.param); + } + if (!!node.body) { + handleAstNode(node.body); + } +} + +export function handleChainExpression(node: arkts.AstNode) { + if (!!node.getExpression) { + handleAstNode(node.getExpression); + } +} + +export function handleCharLiteral(node: arkts.AstNode) { } + +export function handleClassDeclaration(node: arkts.AstNode) { + if (!!node.definition) { + handleFunction(node.definition, handleClassDefinition); + } + if (!!node.decorators) { + node.decorators.forEach((item: arkts.AstNode) => handleFunction(item, handleDecorator)); + } +} + +export function handleClassDefinition(node: arkts.AstNode) { + if (!!node.ident) { + handleFunction(node.ident, handleIdentifier); + } + if (!!node.super) { + handleAstNode(node.super); + } + if (!!node.body) { + node.body.forEach((item: arkts.AstNode) => handleAstNode(item)); + } + if (!!node.implments) { + node.implments.forEach((item: arkts.AstNode) => handleFunction(item, handleTSClassImplements)); + } + if (!!node.typeParams) { + handleFunction(node.typeParams, handleTSTypeParameterDeclaration); + } + if (!!node.superTypeParams) { + handleFunction(node.superTypeParams, handleTSTypeParameterInstantiation); + } + if (!!node.origEnumDecl) { + handleFunction(node.origEnumDecl, handleTSEnumDeclaration); + } + if (!!node.getAnonClass) { + handleAstNode(node.getAnonClass); + } +} + +export function handleClassElement(node: arkts.AstNode) { + if (!!node.key) { + handleAstNode(node.key); + } + if (!!node.value) { + handleAstNode(node.value); + } + if (!!node.decorators) { + node.decorators.forEach((item: arkts.AstNode) => handleFunction(item, handleDecorator)); + } +} + +export function handleClassExpression(node: arkts.AstNode) { + if (!!node.definition) { + handleFunction(node.definition, handleClassDefinition); + } +} + +export function handleClassProperty(node: arkts.AstNode) { + if (!!node.typeAnnotation) { + handleAstNode(node.typeAnnotation); + } + if (!!node.annotations) { + node.annotations.forEach((item: arkts.AstNode) => handleFunction(item, handleAnnotationUsage)); + } + if (!!node.key) { + handleAstNode(node.key); + } + if (!!node.value) { + handleAstNode(node.value); + } + if (!!node.decorators) { + node.decorators.forEach((item: arkts.AstNode) => handleFunction(item, handleDecorator)); + } +} + +export function handleClassStaticBlock(node: arkts.AstNode) { + if (!!node.key) { + handleAstNode(node.key); + } + if (!!node.value) { + handleAstNode(node.value); + } + if (!!node.decorators) { + node.decorators.forEach((item: arkts.AstNode) => handleFunction(item, handleDecorator)); + } +} + +export function handleConditionalExpression(node: arkts.AstNode) { + if (!!node.test) { + handleAstNode(node.test); + } + if (!!node.consequent) { + handleAstNode(node.consequent); + } + if (!!node.alternate) { + handleAstNode(node.alternate); + } +} + +export function handleContext(node: arkts.AstNode) { } + +/** + * target重复节点,不遍历 + * @param { arkts.AstNode } node + */ +export function handleContinueStatement(node: arkts.AstNode) { + if (!!node.ident) { + handleFunction(node.ident, handleIdentifier); + } +} + +export function handleDebuggerStatement(node: arkts.AstNode) { } + +export function handleDecorator(node: arkts.AstNode) { + if (!!node.expr) { + handleAstNode(node.expr); + } +} + +export function handleDirectEvalExpression(node: arkts.AstNode) { + if (!!node.trailingBlock) { + handleAstNode(node.trailingBlock); + } + if (!!node.expression) { + handleAstNode(node.expression); + } + if (!!node.typeArguments) { + node.typeArguments.forEach((item: arkts.TypeNode) => handleAstNode(item)); + } + if (!!node.arguments) { + node.arguments.forEach((item: arkts.Expression) => handleAstNode(item)); + } + if (!!node.params) { + node.params.forEach((item: arkts.TypeNode) => handleAstNode(item)); + } +} + +export function handleDoWhileStatement(node: arkts.AstNode) { + if (!!node.test) { + handleAstNode(node.test); + } + if (!!node.body) { + handleAstNode(node.body); + } +} + +export function handleETSClassLiteral(node: arkts.AstNode) { + if (!!node.expr) { + handleAstNode(node.expr); + } +} + +export function handleETSDynamicFunctionType(node: arkts.AstNode) { + if (!!node.typeParams) { + handleFunction(node.typeParams, handleTSTypeParameterDeclaration); + } + if (!!node.params) { + node.params.forEach((item: arkts.AstNode) => handleAstNode(item)); + } + if (!!node.returnType) { + handleAstNode(node.returnType); + } + if (!!node.functionInterface) { + handleAstNode(node.functionInterface); + } +} + +export function handleETSFunctionType(node: arkts.AstNode) { + if (!!node.typeParams) { + handleFunction(node.typeParams, handleTSTypeParameterDeclaration); + } + if (!!node.params) { + node.params.forEach((item: arkts.AstNode) => handleAstNode(item)); + } + if (!!node.returnType) { + handleAstNode(node.returnType); + } + if (!!node.functionInterface) { + handleAstNode(node.functionInterface); + } +} + +export function handleETSImportDeclaration(node: arkts.AstNode) { } + +export function handleETSModule(node: arkts.AstNode) { + if (!!node.ident) { + handleFunction(node.ident, handleIdentifier); + } + if (!!node.annotations) { + node.annotations.forEach((item: arkts.AstNode) => handleFunction(item, handleAnnotationUsage)); + } + if (!!node.statements) { + node.statements.forEach((item: arkts.AstNode) => handleAstNode(item)); + } +} + +export function handleETSNewArrayInstanceExpression(node: arkts.AstNode) { + if (!!node.typeReference) { + handleAstNode(node.typeReference); + } + if (!!node.dimension) { + handleAstNode(node.dimension); + } +} + +export function handleETSNewClassInstanceExpression(node: arkts.AstNode) { + if (!!node.getTypeRef) { + handleAstNode(node.getTypeRef); + } + if (!!node.getArgments) { + node.getArgments.forEach((item: arkts.AstNode) => handleAstNode(item)); + } +} + +export function handleETSNewMultiDimArrayInstanceExpression(node: arkts.AstNode) { + if (!!node.dimensions) { + node.dimensions.forEach((item: arkts.AstNode) => handleAstNode(item)); + } + if (!!node.typeReference) { + handleAstNode(node.typeReference); + } +} + +export function handleETSNullType(node: arkts.AstNode) { } + +export function handleETSPackageDeclaration(node: arkts.AstNode) { } + +export function handleETSParameterExpression(node: arkts.AstNode) { + if (!!node.annotations) { + node.annotations.forEach((item: arkts.AstNode) => handleFunction(item, handleAnnotationUsage)); + } + if (!!node.type) { + handleAstNode(node.type); + } + if (!!node.initializer) { + handleAstNode(node.initializer); + } + if (!!node.identifier) { + handleFunction(node.identifier, handleIdentifier); + } +} + +export function handleETSPrimitiveType(node: arkts.AstNode) { +} + +export function handleETSReExportDeclaration(node: arkts.AstNode) { + if (!!node.getETSImportDeclarations) { + handleFunction(node.getETSImportDeclarations, handleETSImportDeclaration); + } +} + +export function handleETSStructDeclaration(node: arkts.AstNode) { + if (!!node.definition) { + handleFunction(node.definition, handleClassDefinition); + } + if (!!node.decorators) { + node.decorators.forEach((item: arkts.AstNode) => handleFunction(item, handleDecorator)); + } +} + +export function handleETSTuple(node: arkts.AstNode) { + if (!!node.getTupleTypeAnnotationsList) { + node.getTupleTypeAnnotationsList.forEach((item: arkts.AstNde) => handleAstNode(item)); + } +} + +export function handleETSTypeReference(node: arkts.AstNode) { + if (!!node.part) { + handleFunction(node.part, handleETSTypeReferencePart); + } +} + +export function handleETSTypeReferencePart(node: arkts.AstNode) { + if (!!node.previous) { + handleFunction(node.previous, handleETSTypeReferencePart); + } + if (!!node.name) { + handleAstNode(node.name); + } + if (!!node.typeParams) { + handleFunction(node.typeParams, handleTSTypeParameterInstantiation); + } +} + +export function handleETSUndefinedType(node: arkts.AstNode) { } + +export function handleETSUnionType(node: arkts.AstNode) { + if (!!node.types) { + node.types.forEach((item: arkts.AstNde) => handleAstNode(item)); + } +} + +export function handleETSWildcardType(node: arkts.AstNode) { + if (!!node.typeReference) { + handleFunction(node.typeReference, handleETSTypeReference); + } +} + +export function handleEmptyStatement(node: arkts.AstNode) { } + +export function handleExportAllDeclaration(node: arkts.AstNode) { + if (!!node.source) { + handleFunction(node.source, handleStringLiteral); + } + if (!!node.exported) { + handleFunction(node.exported, handleIdentifier); + } +} + +export function handleExportDefaultDeclaration(node: arkts.AstNode) { + if (!!node.decl) { + handleAstNode(node.decl); + } +} + +export function handleExportNamedDeclaration(node: arkts.AstNode) { + if (!!node.decl) { + handleAstNode(node.decl); + } + if (!!node.source) { + handleFunction(node.source, handleStringLiteral); + } + if (!!node.specifiers) { + node.specifiers.forEach((item: arkts.AstNode) => handleFunction(item, handleExportSpecifier)); + } +} + +export function handleExportSpecifier(node: arkts.AstNode) { + if (!!node.local) { + handleFunction(node.local, handleIdentifier); + } + if (!!node.exported) { + handleFunction(node.exported, handleIdentifier); + } +} + +export function handleExpression(node: arkts.AstNode) { } + +export function handleExpressionStatement(node: arkts.AstNode) { + if (!!node.expression) { + handleAstNode(node.expression); + } +} + +export function handleForInStatement(node: arkts.AstNode) { + if (!!node.left) { + handleAstNode(node.left); + } + if (!!node.right) { + handleAstNode(node.right); + } + if (!!node.body) { + handleAstNode(node.body); + } +} + +export function handleForOfStatement(node: arkts.AstNode) { + if (!!node.left) { + handleAstNode(node.left); + } + if (!!node.right) { + handleAstNode(node.right); + } + if (!!node.body) { + handleAstNode(node.body); + } +} + +export function handleForUpdateStatement(node: arkts.AstNode) { + if (!!node.init) { + handleAstNode(node.init); + } + if (!!node.test) { + handleAstNode(node.test); + } + if (!!node.update) { + handleAstNode(node.update); + } + if (!!node.body) { + handleAstNode(node.body); + } +} + +export function handleFunctionDecl(node: arkts.AstNode) { + if (!!node.id) { + handleFunction(node.id, handleIdentifier); + } + if (!!node.params) { + node.params.forEach((item: arkts.AstNode) => handleAstNode(item)); + } + if (!!node.returnStatements) { + node.returnStatements.forEach((item: arkts.AstNode) => handleFunction(item, handleReturnStatement)); + } + if (!!node.typeParams) { + handleFunction(node.typeParams, handleTSTypeParameterDeclaration); + } + if (!!node.body) { + handleAstNode(node.body); + } + if (!!node.returnTypeAnnotation) { + handleAstNode(node.returnTypeAnnotation); + } + if (!!node.annotations) { + node.annotations.forEach((item: arkts.AstNde) => handleFunction(item, handleAnnotationUsage)); + } +} + +export function handleFunctionDeclaration(node: arkts.AstNode) { + if (!!node.annotations) { + node.annotations.forEach((item: arkts.AstNode) => handleFunction(item, handleAnnotationUsage)); + } + if (!!node.scriptFunction) { + handleAstNode(node.scriptFunction); + } + if (!!node.parameters) { + node.parameters.forEach((item: arkts.AstNode) => handleAstNode(item)); + } + if (!!node.name) { + handleFunction(node.name, handleIdentifier); + } + if (!!node.body) { + handleAstNode(node.body); + } + if (!!node.typeParamsDecl) { + handleFunction(node.typeParamsDecl, handleTSTypeParameterDeclaration); + } + if (!!node.returnType) { + handleAstNode(node.returnType); + } +} + +export function handleFunctionExpression(node: arkts.AstNode) { + if (!!node.scriptFunction) { + handleAstNode(node.scriptFunction); + } + +} + +export function handleFunctionSignature(node: arkts.AstNode) { + if (!!node.params) { + node.params.forEach((item: arkts.AstNode) => handleAstNode(item)); + } + if (!!node.typeParams) { + handleFunction(node.typeParams, handleTSTypeParameterDeclaration); + } + if (!!node.returnType) { + handleAstNode(node.returnType); + } +} + +export function handleIdentifier(node: arkts.AstNode) { + if (exporssionCount > 1) { + checkIdentifier(node); + } + + if (!!node.decorators) { + node.decorators.forEach((item: arkts.AstNode) => handleFunction(item, handleDecorator)); + } + if (!!node.typeAnnotation) { + handleAstNode(node.typeAnnotation); + } +} + +export function handleIfStatement(node: arkts.AstNode) { + if (!!node.test) { + handleAstNode(node.test); + } + if (!!node.consequent) { + handleAstNode(node.consequent); + } + if (!!node.alternate) { + handleAstNode(node.alternate); + } +} + +export function handleImportDeclaration(node: arkts.AstNode) { +} + +export function handleImportDefaultSpecifier(node: arkts.AstNode) { + if (!!node.local) { + handleFunction(node.local, handleIdentifier); + } +} + +export function handleImportExpression(node: arkts.AstNode) { + if (!!node.source) { + handleFunction(node.source, handleStringLiteral); + } +} + +export function handleImportNamespaceSpecifier(node: arkts.AstNode) { + if (!!node.local) { + handleFunction(node.local, handleIdentifier); + } + +} + +export function handleImportSource(node: arkts.AstNode) { + if (!!node.source) { + handleFunction(node.source, handleStringLiteral); + } + if (!!node.resolvedSource) { + handleFunction(node.resolvedSource, handleStringLiteral); + } +} + +export function handleImportSpecifier(node: arkts.AstNode) { + if (!!node.imported) { + handleFunction(node.imported, handleIdentifier); + } + if (!!node.local) { + handleFunction(node.local, handleIdentifier); + } + +} + +export function handleInterfaceDecl(node: arkts.AstNode) { + if (!!node.id) { + handleFunction(node.id, handleIdentifier); + } + if (!!node.body) { + handleFunction(node.body, handleTSInterfaceBody); + } + if (!!node.typeParams) { + handleFunction(node.typeParam, handleTSTypeParameterDeclaration); + } + if (!!node.extends) { + node.extends.forEach((item: arkts.TypeNode) => handleFunction(item, handleTSInterfaceHeritage)); + } + if (!!node.decorators) { + node.decorators.forEach((item: arkts.AstNode) => handleFunction(item, handleDecorator)); + } + if (!!node.getAnonClass) { + handleAstNode(node.getAnonClass); + } + if (!!node.annotations) { + node.annotations.forEach((item: arkts.AstNode) => handleFunction(item, handleAnnotationUsage)); + } +} + +export function handleLabelPair(node: arkts.AstNode) { + if (!!node.ident) { + handleFunction(node.ident, handleIdentifier); + } + if (!!node.body) { + handleAstNode(node.body); + } +} + +export function handleLabelledStatement(node: arkts.AstNode) { } + +export function handleLiteral(node: arkts.AstNode) { } + +export function handleLoopStatement(node: arkts.AstNode) { } + +export function handleMaybeOptionalExpression(node: arkts.AstNode) { } + +export function handleMemberExpression(node: arkts.AstNode) { + if (!!node.object) { + handleAstNode(node.object); + } + if (!!node.property) { + handleAstNode(node.property); + } +} + +export function handleMetaProperty(node: arkts.AstNode) { } + +export function handleMethodDefinition(node: arkts.AstNode) { + if (!!node.overloads) { + node.overloads.forEach((item: arkts.AstNode) => handleFunction(item, handleMethodDefinition)); + } + if (!!node.scriptFunction) { + handleAstNode(node.scriptFunction); + } + if (!!node.name) { + handleFunction(node.name, handleIdentifier); + } + if (!!node.key) { + handleAstNode(node.key); + } + if (!!node.value) { + handleAstNode(node.value); + } + if (!!node.decorators) { + node.decorators.forEach((item: arkts.AstNode) => handleFunction(item, handleDecorator)); + } +} + +export function handleNamedType(node: arkts.AstNode) { + if (!!node.name) { + handleFunction(node.name, handleIdentifier); + } + if (!!node.typeParams) { + handleFunction(node.typeParams, handleTSTypeParameterInstantiation); + } +} + +export function handleNewExpression(node: arkts.AstNode) { + if (!!node.callee) { + handleAstNode(node.callee); + } + if (!!node.arguments) { + node.arguments.forEach((item: arkts.AstNode) => handleAstNode(item)); + } +} + +export function handleNullLiteral(node: arkts.AstNode) { } + +export function handleNumberLiteral(node: arkts.AstNode) { } + +export function handleObjectExpression(node: arkts.AstNode) { + if (!!node.properties) { + node.properties.forEach((item: arkts.AstNode) => handleAstNode(item)); + } + if (!!node.decorators) { + node.decorators.forEach((item: arkts.AstNode) => handleFunction(item, handleDecorator)); + } + if (!!node.typeAnnotation) { + handleAstNode(node.typeAnnotation); + } +} + +export function handleOmittedExpression(node: arkts.AstNode) { } + +export function handleOpaqueTypeNode(node: arkts.AstNode) { } + +export function handlePrefixAssertionExpression(node: arkts.AstNode) { + if (!!node.expr) { + handleAstNode(node.expr); + } + if (!!node.type) { + handleAstNode(node.type); + } +} + +export function handleProperty(node: arkts.AstNode) { + if (!!node.key) { + handleAstNode(node.key); + } + if (!!node.value) { + handleAstNode(node.value); + } +} + +export function handleRegExpLiteral(node: arkts.AstNode) { } + +export function handleReturnStatement(node: arkts.AstNode) { + if (!!node.argument) { + handleAstNode(node.argument); + } +} + +export function handleScriptFunction(node: arkts.AstNode) { + if (!!node.id) { + handleFunction(node.id, handleIdentifier); + } + if (!!node.params) { + node.params.forEach((item: arkts.AstNode) => handleAstNode(item)); + } + if (!!node.returnStatements) { + node.returnStatements.forEach((item: arkts.AstNode) => handleFunction(item, handleReturnStatement)); + } + if (!!node.typeParams) { + handleFunction(node.typeParams, handleTSTypeParameterDeclaration); + } + if (!!node.body) { + handleAstNode(node.body); + } + if (!!node.returnTypeAnnotation) { + handleAstNode(node.returnTypeAnnotation); + } + if (!!node.annotations) { + node.annotations.forEach((item: arkts.AstNde) => handleFunction(item, handleAnnotationUsage)); + } +} + +export function handleSequenceExpression(node: arkts.AstNode) { + if (!!node.sequence) { + node.sequence.forEach((item: arkts.AstNode) => handleAstNode(item)); + } +} + +export function handleSpreadElement(node: arkts.AstNode) { + if (!!node.argument) { + handleAstNode(node.argument); + } + if (!!node.decorators) { + node.decorators.forEach((item: arkts.AstNode) => handleFunction(item, handleDecorator)); + } + if (!!node.typeAnnotation) { + handleAstNode(node.typeAnnotation); + } +} + +export function handleSrcDumper(node: arkts.AstNode) { + return; +} + +export function handleStatement(node: arkts.AstNode) { +} + +export function handleStringLiteral(node: arkts.AstNode) { } + +export function handleSuperExpression(node: arkts.AstNode) { + if (!!node.id) { + handleFunction(node.id, handleIdentifier); + } +} + +export function handleSwitchCaseStatement(node: arkts.AstNode) { + if (!!node.test) { + handleAstNode(node.test); + } + if (!!node.consequent) { + node.consequent.forEach((item: arkts.AstNode) => handleAstNode(item)); + } +} + +export function handleSwitchStatement(node: arkts.AstNode) { + if (!!node.discriminant) { + handleAstNode(node.discriminant); + } + if (!!node.cases) { + node.cases.forEach((item: arkts.AstNode) => handleFunction(item, handleSwitchCaseStatement)); + } +} + +export function handleTSAnyKeyword(node: arkts.AstNode) { +} + +export function handleTSArrayType(node: arkts.AstNode) { + if (!!node.elementType) { + handleAstNode(node.elementType); + } +} + +export function handleTSAsExpression(node: arkts.AstNode) { + if (!!node.expr) { + handleAstNode(node.expr); + } + if (!!node.typeAnnotation) { + handleAstNode(node.typeAnnotation); + } +} + +export function handleTSBigintKeyword(node: arkts.AstNode) { } + +export function handleTSBooleanKeyword(node: arkts.AstNode) { } + +export function handleTSClassImplements(node: arkts.AstNode) { + if (!!node.expr) { + handleAstNode(node.expr); + } + if (!!node.typeParameters) { + handleFunction(node.typeParameters, handleTSTypeParameterInstantiation); + } +} + +export function handleTSConditionalType(node: arkts.AstNode) { + if (!!node.checkType) { + handleAstNode(node.checkType); + } + if (!!node.extendsType) { + handleAstNode(node.extendsType); + } + if (!!node.trueType) { + handleAstNode(node.trueType); + } + if (!!node.falseType) { + handleAstNode(node.falseType); + } +} + +export function handleTSConstructorType(node: arkts.AstNode) { + if (!!node.typeParams) { + handleFunction(node.typeParameters, handleTSTypeParameterInstantiation); + } + if (!!node.params) { + node.params.forEach((item: arkts.AstNode) => handleAstNode(item)); + } + if (!!node.returnType) { + handleAstNode(node.returnType); + } +} + +export function handleTSEnumDeclaration(node: arkts.AstNode) { + if (!!node.key) { + handleFunction(node.key, handleIdentifier); + } + if (!!node.members) { + node.members.forEach((item: arkts.AstNde) => handleAstNode(item)); + } + if (!!node.boxedClass) { + handleFunction(node.boxedClass, handleClassDefinition); + } + if (!!node.decorators) { + node.decorators.forEach((item: arkts.AstNode) => handleFunction(item, handleDecorator)); + } +} + +export function handleTSEnumMember(node: arkts.AstNode) { + if (!!node.key) { + handleAstNode(node.key); + } + if (!!node.init) { + handleAstNode(node.init); + } +} + +export function handleTSExternalModuleReference(node: arkts.AstNode) { + if (!!node.expr) { + handleAstNode(node.expr); + } +} + +export function handleTSFunctionType(node: arkts.AstNode) { + if (!!node.typeParams) { + handleFunction(node.typeParams, handleTSTypeParameterInstantiation); + } + if (!!node.params) { + node.params.forEach((item: arkts.TypeNode) => handleAstNode(item)); + } + if (!!node.returnType) { + handleAstNode(node.returnType); + } +} + +export function handleTSImportEqualsDeclaration(node: arkts.AstNode) { + if (!!node.id) { + handleFunction(node.id, handleIdentifier); + } + if (!!node.moduleReference) { + handleAstNode(node.moduleReference); + } +} + +export function handleTSImportType(node: arkts.AstNode) { + if (!!node.typeParams) { + handleFunction(node.typeParams, handleTSTypeParameterInstantiation); + } + if (!!node.params) { + handleAstNode(node.params); + } + if (!!node.qualifier) { + handleAstNode(node.qualifier); + } +} + +export function handleTSIndexSignature(node: arkts.AstNode) { + if (!!node.objectType) { + handleAstNode(node.objectType); + } + if (!!node.indexType) { + handleAstNode(node.indexType); + } +} + +export function handleTSIndexedAccessType(node: arkts.AstNode) { + if (!!node.param) { + handleAstNode(node.param); + } + if (!!node.typeAnnotation) { + handleAstNode(node.typeAnnotation); + } +} + +export function handleTSInferType(node: arkts.AstNode) { + if (!!node.typeParam) { + handleFunction(node.typeParam, handleTSTypeParameter); + } +} + +export function handleTSInterfaceBody(node: arkts.AstNode) { + if (!!node.body) { + node.body.forEach((item: arkts.TypeNode) => handleAstNode(item)); + } +} + +export function handleTSInterfaceDeclaration(node: arkts.AstNode) { + if (!!node.id) { + handleFunction(node.id, handleIdentifier); + } + if (!!node.body) { + handleFunction(node.body, handleTSInterfaceBody); + } + if (!!node.typeParams) { + handleFunction(node.typeParam, handleTSTypeParameterDeclaration); + } + if (!!node.extends) { + node.extends.forEach((item: arkts.TypeNode) => handleFunction(item, handleTSInterfaceHeritage)); + } + if (!!node.decorators) { + node.decorators.forEach((item: arkts.AstNode) => handleFunction(item, handleDecorator)); + } + if (!!node.getAnonClass) { + handleAstNode(node.getAnonClass); + } + if (!!node.annotations) { + node.annotations.forEach((item: arkts.AstNode) => handleFunction(item, handleAnnotationUsage)); + } +} + +export function handleTSInterfaceHeritage(node: arkts.AstNode) { + if (!!node.expr) { + handleAstNode(node.expr); + } +} + +export function handleTSIntersectionType(node: arkts.AstNode) { + if (!!node.types) { + node.types.forEach((item: arkts.AstNode) => handleAstNode(item)); + } +} + +export function handleTSLiteralType(node: arkts.AstNode) { + if (!!node.literal) { + handleAstNode(node.literal); + } +} + +export function handleTSMappedType(node: arkts.AstNode) { + if (!!node.typeParameter) { + handleFunction(node.typeParameter, handleTSTypeParameter); + } + if (!!node.typeAnnotation) { + handleAstNode(node.typeAnnotation); + } +} + +export function handleTSMethodSignature(node: arkts.AstNode) { + if (!!node.key) { + handleAstNode(node.key); + } + if (!!node.typeParams) { + handleFunction(node.typeParams, handleTSTypeParameterDeclaration); + } + if (!!node.params) { + node.params.forEach((item: arkts.AstNode) => handleAstNode(item)); + } + if (!!node.returnTypeAnnotation) { + handleAstNode(node.returnTypeAnnotation); + } +} + +export function handleTSModuleBlock(node: arkts.AstNode) { + if (!!node.statements) { + node.statements.forEach((item: arkts.AstNode) => handleAstNode(item)); + } +} + +export function handleTSModuleDeclaration(node: arkts.AstNode) { + if (!!node.name) { + handleAstNode(node.name); + } + if (!!node.body) { + handleAstNode(node.body); + } +} + +export function handleTSNamedTupleMember(node: arkts.AstNode) { + if (!!node.label) { + handleAstNode(node.label); + } + if (!!node.elementType) { + handleAstNode(node.elementType); + } +} + +export function handleTSNeverKeyword(node: arkts.AstNode) { } + +export function handleTSNonNullExpression(node: arkts.AstNode) { + if (!!node.expr) { + handleAstNode(node.expr); + } +} + +export function handleTSNullKeyword(node: arkts.AstNode) { } + +export function handleTSNumberKeyword(node: arkts.AstNode) { } + +export function handleTSObjectKeyword(node: arkts.AstNode) { } + +export function handleTSParameterProperty(node: arkts.AstNode) { + if (!!node.parameter) { + handleAstNode(node.parameter); + } +} + +export function handleTSParenthesizedType(node: arkts.AstNode) { + if (!!node.type) { + handleAstNode(node.type); + } +} + +export function handleTSPropertySignature(node: arkts.AstNode) { + if (!!node.key) { + handleAstNode(node.key); + } + if (!!node.typeAnnotation) { + handleAstNode(node.typeAnnotation); + } +} + +export function handleTSQualifiedName(node: arkts.AstNode) { + if (!!node.left) { + handleAstNode(node.left); + } + if (!!node.right) { + handleFunction(node.right, handleIdentifier); + } +} + +export function handleTSSignatureDeclaration(node: arkts.AstNode) { + if (!!node.typeParams) { + handleFunction(node.typeParams, handleTSTypeParameterDeclaration); + } + if (!!node.params) { + node.params.forEach((item: arkts.AstNode) => handleAstNode(item)); + } + if (!!node.returnTypeAnnotation) { + handleAstNode(node.returnTypeAnnotation); + } +} + +export function handleTSStringKeyword(node: arkts.AstNode) { } + +export function handleTSThisType(node: arkts.AstNode) { } + +export function handleTSTupleType(node: arkts.AstNode) { + if (!!node.elementType) { + node.elementType.forEach((item: arkts.AstNode) => handleAstNode(item)); + } +} + +export function handleTSTypeAliasDeclaration(node: arkts.AstNode) { + if (!!node.id) { + handleFunction(node.id, handleIdentifier); + } + if (!!node.typeParams) { + handleFunction(node.typeParams, handleTSTypeParameterDeclaration); + } + if (!!node.decorators) { + node.decorators.forEach((item: arkts.AstNode) => handleFunction(item, handleDecorator)); + } + if (!!node.annotations) { + node.annotations.forEach((item: arkts.AstNode) => handleFunction(item, handleAnnotationUsage)); + } + if (!!node.typeAnnotation) { + handleAstNode(node.typeAnnotation); + } +} + +export function handleTSTypeAssertion(node: arkts.AstNode) { + if (!!node.getExpression) { + handleAstNode(node.getExpression); + } + if (!!node.typeAnnotation) { + handleAstNode(node.typeAnnotation); + } + +} + +export function handleTSTypeLiteral(node: arkts.AstNode) { + if (!!node.members) { + node.members.forEach((item: arkts.AstNode) => handleAstNode(item)); + } + if (!!node.type) { + handleAstNode(node.type); + } +} + +export function handleTSTypeOperator(node: arkts.AstNode) { + if (!!node.type) { + handleAstNode(node.type); + } +} + +export function handleTSTypeParameter(node: arkts.AstNode) { + if (!!node.name) { + handleFunction(node.name, handleIdentifier); + } + if (!!node.constraint) { + handleAstNode(node.constraint); + } + if (!!node.defaultType) { + handleAstNode(node.defaultType); + } + if (!!node.annotations) { + node.annotations.forEach((item: arkts.AstNode) => handleFunction(item, handleAnnotationUsage)); + } +} + +export function handleTSTypeParameterDeclaration(node: arkts.AstNode) { + if (!!node.params) { + node.params.forEach((item: arkts.AstNode) => handleFunction(item, handleTSTypeParameter)); + } +} + +export function handleTSTypeParameterInstantiation(node: arkts.AstNode) { + if (!!node.params) { + node.params.forEach((item: arkts.AstNode) => handleAstNode(item)); + } +} + +export function handleTSTypePredicate(node: arkts.AstNode) { + if (!!node.parameterName) { + handleAstNode(node.parameterName); + } + if (!!node.typeAnnotation) { + handleAstNode(node.typeAnnotation); + } +} + +export function handleTSTypeQuery(node: arkts.AstNode) { + if (!!node.exprName) { + handleAstNode(node.exprName); + } +} + +export function handleTSTypeReference(node: arkts.AstNode) { + if (!!node.typeName) { + handleAstNode(node.typeName); + } + if (!!node.typeParams) { + handleFunction(node.typeParams, handleTSTypeParameterInstantiation); + } +} + +export function handleTSUndefinedKeyword(node: arkts.AstNode) { } + +export function handleTSUnionType(node: arkts.AstNode) { + if (!!node.types) { + node.types.forEach((item: arkts.AstNode) => traverseProgram(item)); + } +} + +export function handleTSUnknownKeyword(node: arkts.AstNode) { } + +export function handleTSVoidKeyword(node: arkts.AstNode) { } + +export function handleTaggedTemplateExpression(node: arkts.AstNode) { + if (!!node.tag) { + handleAstNode(node.tag); + } + if (!!node.quasi) { + handleFunction(node.quasi, handleTemplateLiteral); + } + if (!!node.typeParams) { + handleFunction(node.typeParams, handleTSTypeParameterInstantiation); + } +} + +export function handleTemplateElement(node: arkts.AstNode) { } + +export function handleTemplateLiteral(node: arkts.AstNode) { + if (!!node.quasis) { + node.quasis.forEach((item: arkts.AstNode) => handleFunction(item, handleTemplateLiteral)); + } + if (!!node.expressions) { + node.expressions.forEach((item: arkts.AstNode) => handleAstNode(item)); + } +} + +export function handleThisExpression(node: arkts.AstNode) { } + +export function handleThrowStatement(node: arkts.AstNode) { + if (!!node.argument) { + handleAstNode(node.argument); + } +} + +export function handleTryStatement(node: arkts.AstNode) { + if (!!node.finallyBlock) { + handleAstNode(node.finallyBlock); + } + if (!!node.block) { + handleAstNode(node.block); + } + if (!!node.catchClauses) { + node.catchClauses.forEach((item: arkts.AstNode) => handleFunction(item, handleCatchClause)); + } +} + +export function handleTypeNode(node: arkts.AstNode) { } + +export function handleTypedAstNode(node: arkts.AstNode) { } + +export function handleTypedStatement(node: arkts.AstNode) { } + +export function handleTypeofExpression(node: arkts.AstNode) { + if (!!node.argument) { + handleAstNode(node.argument); + } +} + +export function handleUnaryExpression(node: arkts.AstNode) { + if (!!node.argument) { + handleAstNode(node.argument); + } +} + +export function handleUndefinedLiteral(node: arkts.AstNode) { } + +export function handleUpdateExpression(node: arkts.AstNode) { + if (!!node.argument) { + handleAstNode(node.argument); + } +} + +export function handleValidationInfo(node: arkts.AstNode) { } + +export function handleVariableDeclaration(node: arkts.AstNode) { + if (!!node.annotations) { + node.annotations.forEach((item: arkts.AstNode) => handleFunction(item, handleAnnotationUsage)); + } + if (!!node.declarators) { + node.declarators.forEach((item: arkts.AstNode) => handleFunction(item, handleVariableDeclarator)); + } +} + +export function handleVariableDeclarator(node: arkts.AstNode) { + if (!!node.initializer) { + handleAstNode(node.initializer); + } + if (!!node.name) { + handleFunction(node.name, handleIdentifier); + } +} + +export function handleWhileStatement(node: arkts.AstNode) { + if (!!node.test) { + handleAstNode(node.test); + } + if (!!node.body) { + handleAstNode(node.body); + } +} + +export function handleYieldExpression(node: arkts.AstNode) { + if (!!node.argument) { + handleAstNode(node.argument); + } +} diff --git a/build-tools/compile-plugins/api-check-plugin-static/babel.config.js b/build-tools/compile-plugins/api-check-plugin-static/babel.config.js new file mode 100755 index 0000000000..9749768be6 --- /dev/null +++ b/build-tools/compile-plugins/api-check-plugin-static/babel.config.js @@ -0,0 +1,41 @@ +/* + * 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. + */ + +module.exports = function(api) { + api.cache(true); + + const presets = ['@babel/typescript']; + const plugins = [ + '@babel/plugin-transform-modules-commonjs', + '@babel/plugin-proposal-class-properties', + [ + '@babel/plugin-transform-arrow-functions', + { + spec: true + } + ], + './custom-import-plugin' + ]; + const ignore = [ + '**/test/**', + '**/node_modules/**' + ]; + + return { + presets, + plugins, + ignore + }; +}; diff --git a/build-tools/compile-plugins/api-check-plugin-static/custom-import-plugin.js b/build-tools/compile-plugins/api-check-plugin-static/custom-import-plugin.js new file mode 100755 index 0000000000..a5ec31a241 --- /dev/null +++ b/build-tools/compile-plugins/api-check-plugin-static/custom-import-plugin.js @@ -0,0 +1,51 @@ +/* + * 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 path = require('path'); + +module.exports = function (babel) { + const { types: t } = babel; + + return { + name: 'custom-import-plugin', + visitor: { + ImportDeclaration(pathNode) { + const sourceValue = pathNode.node.source.value; + if ( + sourceValue === '@koalaui/libarkts' && + pathNode.node.specifiers.length === 1 && + t.isImportNamespaceSpecifier(pathNode.node.specifiers[0])) { + const currentFileDir = path.dirname(pathNode.hub.file.opts.filename); + const configDir = process.cwd(); + const relativePath = path.relative(currentFileDir, configDir); + const importPath = relativePath ? path.join(relativePath, '../path') : './path'; + + const newImport = t.importDeclaration( + [t.importSpecifier(t.identifier('getArktsPath'), t.identifier('getArktsPath'))], + t.stringLiteral(importPath)); + const requireCall = t.callExpression( + t.identifier('require'), + [t.callExpression(t.identifier('getArktsPath'), [])]); + const arkts = t.variableDeclaration( + 'const', + [t.variableDeclarator(t.identifier('arkts'), requireCall)]); + + pathNode.replaceWithMultiple([newImport, arkts]); + } + } + } + }; +}; \ No newline at end of file diff --git a/build-tools/compile-plugins/api-check-plugin-static/index.ts b/build-tools/compile-plugins/api-check-plugin-static/index.ts new file mode 100755 index 0000000000..13bfb04b97 --- /dev/null +++ b/build-tools/compile-plugins/api-check-plugin-static/index.ts @@ -0,0 +1,88 @@ +/* + * 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. + */ + +import { Plugins, PluginContext } from '../common/plugin-context'; +import { GlobalObject, ProjectConfig } from './utils/api_check_plugin_typedef'; +import { createOrCleanProjectConfig, readCardPageSet, readPermissions, readSyscapInfo, readSystemModules, creatApiCheckConfig } from './utils/api_check_plugin_utils'; +import { ApiCheckWrapperServiceHost } from './api-check-wrapper'; +import { getApiCheckWrapperServiceHost } from './src/api_check_config'; +import { checkApiExpression, WrapperApi } from './api-check-wrapper/src/api_check_wrapper'; + +/** + * 导出projectConfig作为全局变量 + */ +export const globalObject: GlobalObject = { + projectConfig: createOrCleanProjectConfig() +} + +/** + * 入口方法 + * + * @returns { Plugins } + */ +export function apiCheckPlugin(): Plugins { + return { + name: 'api-check-plugins', + checked: apiCheckCallback, + clean() { + console.info("[API_CHECK_PLUGIN] CLEAN"); + WrapperApi.arktsGlobal.clearContext(); + } + }; +} + +/** + * 入口回调方法,调用ApiCheckWrapper启动插件校验功能。 + * + * @param { PluginContext } this PluginContext对象 + */ +function apiCheckCallback(this: PluginContext): void { + console.info('[API_CHECK_PLUGIN] AFTER CHECKED ENTER'); + try { + const currentProjectConfig: ProjectConfig | undefined = this.getProjectConfig() as ProjectConfig | undefined; + if (currentProjectConfig) { + initApiCheckConfig(currentProjectConfig); + Object.assign(globalObject.projectConfig, currentProjectConfig); + const ContextPtr = this.getContextPtr(); + const apiCheckHost: ApiCheckWrapperServiceHost = getApiCheckWrapperServiceHost(); + checkApiExpression(apiCheckHost, ContextPtr); + } + else { + throw new Error('[API_CHECK_PLUGIN] Get ProjectConfig Fail'); + } + } + catch (error) { + if (error) { + console.error(`[API_CHECK_PLUGIN] ${error}`); + } + } + console.info('[API_CHECK_PLUGIN] AFTER CHECKED EXIT'); +} + +/** + * 初始化projectConfig,通过调用各方法改装该对象,新增插件需要的各属性。 + * + * @param { ProjectConfig } projectConfig 获取的系统配置信息 + */ +export function initApiCheckConfig(projectConfig: ProjectConfig): void { + if (projectConfig.initApiCheckTag) { + return; + } + Object.assign(projectConfig, creatApiCheckConfig()); + readPermissions(projectConfig); + readCardPageSet(projectConfig); + readSystemModules(projectConfig); + readSyscapInfo(projectConfig); +} \ No newline at end of file diff --git a/build-tools/compile-plugins/api-check-plugin-static/package.json b/build-tools/compile-plugins/api-check-plugin-static/package.json new file mode 100755 index 0000000000..2902bc4588 --- /dev/null +++ b/build-tools/compile-plugins/api-check-plugin-static/package.json @@ -0,0 +1,22 @@ +{ + "name": "api-check-plugin-static", + "version": "1.0.0", + "description": "", + "main": "compile-plugins/api-check-plugin-static/index.ts", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "compile:plugins": "./node_modules/.bin/babel ./ --out-dir ./lib --extensions .ts" + }, + "author": "", + "license": "ISC", + "dependencies": { + "fs": "^0.0.1-security", + "path": "^0.12.7", + "@babel/cli": "7.20.7", + "@babel/core": "7.20.12", + "@babel/plugin-proposal-class-properties": "7.18.6", + "@babel/preset-env": "7.20.2", + "@babel/preset-typescript": "7.18.6", + "@babel/runtime": "7.20.13" + } +} diff --git a/build-tools/compile-plugins/api-check-plugin-static/src/api_check_config.ts b/build-tools/compile-plugins/api-check-plugin-static/src/api_check_config.ts new file mode 100755 index 0000000000..9cf21cd2c5 --- /dev/null +++ b/build-tools/compile-plugins/api-check-plugin-static/src/api_check_config.ts @@ -0,0 +1,173 @@ +/* + * 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. + */ + +import path from 'path'; +import { + PERMISSION_TAG_CHECK_NAME, + PERMISSION_TAG_CHECK_ERROR, + SYSTEM_API_TAG_CHECK_NAME, + SYSTEM_API_TAG_CHECK_WARNING, + TEST_TAG_CHECK_NAME, + TEST_TAG_CHECK_ERROR, + SYSCAP_TAG_CHECK_NAME, + SYSCAP_TAG_CHECK_WARNING, + FORM_TAG_CHECK_NAME, + FORM_TAG_CHECK_ERROR, + FIND_MODULE_WARNING, + CROSSPLATFORM_TAG_CHECK_NAME, + CROSSPLATFORM_TAG_CHECK_ERROR, + DEPRECATED_TAG_CHECK_NAME, + DEPRECATED_TAG_CHECK_WARNING, + FA_TAG_CHECK_NAME, + FA_TAG_HUMP_CHECK_NAME, + FA_TAG_CHECK_ERROR, + ATOMICSERVICE_BUNDLE_TYPE, + ATOMICSERVICE_TAG_CHECK_NAME, + ATOMICSERVICE_TAG_CHECK_ERROR, + ATOMICSERVICE_TAG_CHECK_VERSION, + SINCE_TAG_NAME, + SINCE_TAG_CHECK_ERROR, + STAGE_TAG_CHECK_NAME, + STAGE_TAG_HUMP_CHECK_NAME, + STAGE_TAG_CHECK_ERROR, + STAGE_COMPILE_MODE +} from '../utils/api_check_plugin_define'; +import { globalObject } from '../index'; +import { + checkPermissionTag, + checkSinceTag, + checkSyscapTag, + getJsDocNodeCheckConfigItem, + isCardFile, + pushLog, + collectInfo +} from '../utils/api_check_plugin_utils'; +import { + ApiCheckWrapperServiceHost, + CurrentAddress, + DiagnosticCategory, + JsDocNodeCheckConfig, + JsDocNodeCheckConfigItem +} from '../api-check-wrapper'; + +const jsDocNodeCheckConfigCache: Map> = + new Map>(); + +/** + * 根据ProjectConfig对象存入JsDoc校验规则 + * + * @param { string } fileName - 原始node文件路径 + * @param { string } sourceFileName - 目标api文件路径 + * @returns { JsDocNodeCheckConfig } - 返回校验规则对象 + */ +function getJsDocNodeCheckConfig(fileName: string, sourceFileName: string): JsDocNodeCheckConfig { + let byFileName: Map | undefined = jsDocNodeCheckConfigCache.get(fileName); + if (byFileName === undefined) { + byFileName = new Map(); + jsDocNodeCheckConfigCache.set(fileName, byFileName); + } + let result: JsDocNodeCheckConfig | undefined = byFileName.get(sourceFileName); + if (result !== undefined) { + return result; + } + // 预留check条件 + let needCheckResult: boolean = true; + const checkConfigArray: JsDocNodeCheckConfigItem[] = []; + const apiName: string = path.basename(fileName); + const sourceBaseName: string = path.basename(sourceFileName); + if (/(?= ATOMICSERVICE_TAG_CHECK_VERSION) { + checkConfigArray.push(getJsDocNodeCheckConfigItem([ATOMICSERVICE_TAG_CHECK_NAME], + ATOMICSERVICE_TAG_CHECK_ERROR, DiagnosticCategory.ERROR, true)); + } + } else { + needCheckResult = false; + } + result = { + nodeNeedCheck: needCheckResult, + checkConfig: checkConfigArray + }; + byFileName.set(sourceFileName, result); + return result; +} + + +/** + * 返回apiHost + * + * @returns { ApiCheckWrapperServiceHost } - apiHost + */ +export function getApiCheckWrapperServiceHost(): ApiCheckWrapperServiceHost { + return { + getJsDocNodeCheckedConfig: (currentFileName: string, symbolSourceFilePath: string) => { + return getJsDocNodeCheckConfig(currentFileName, symbolSourceFilePath); + }, + getFileCheckedModuleInfo: (containFilePath: string) => { + return { + fileNeedCheck: true, + currentFileName: containFilePath + }; + }, + pushLogInfo: (apiName: string, currentFilePath: string, currentAddress: CurrentAddress, + logLevel: DiagnosticCategory, logMessage: string) => { + return pushLog(apiName, currentFilePath, currentAddress, logLevel, logMessage); + }, + collectImportInfo: (moduleName: string[], modulePath: string, currentFilePath: string) => { + collectInfo(moduleName, modulePath, currentFilePath); + } + }; +} \ No newline at end of file diff --git a/build-tools/compile-plugins/api-check-plugin-static/utils/api_check_plugin_define.ts b/build-tools/compile-plugins/api-check-plugin-static/utils/api_check_plugin_define.ts new file mode 100755 index 0000000000..e02607bf6a --- /dev/null +++ b/build-tools/compile-plugins/api-check-plugin-static/utils/api_check_plugin_define.ts @@ -0,0 +1,61 @@ +/* + * 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. + */ + +export const PERMISSION_TAG_CHECK_NAME: string = 'permission'; +export const PERMISSION_TAG_CHECK_ERROR: string = "To use this API, you need to apply for the permissions: $DT"; +export const SYSTEM_API_TAG_CHECK_NAME: string = 'systemapi'; +export const SYSTEM_API_TAG_CHECK_WARNING: string = "'{0}' is system api"; +export const TEST_TAG_CHECK_NAME: string = 'test'; +export const TEST_TAG_CHECK_ERROR: string = "'{0}' can only be used for testing directories "; +export const SYSCAP_TAG_CHECK_NAME: string = 'syscap'; +export const SYSCAP_TAG_CHECK_WARNING: string = "The system capacity of this api '{0}' is not supported on all devices"; +export const SYSCAP_TAG_CONDITION_CHECK_WARNING: string = + 'The API is not supported on all devices. Use the canIUse condition to determine whether the API is supported.'; +export const CANIUSE_FUNCTION_NAME: string = 'canIUse'; +export const VERSION_CHECK_FUNCTION_NAME: string = 'isApiVersionGreaterOrEqual'; +export const RUNTIME_OS_OH: string = 'OpenHarmony'; +export const FORM_TAG_CHECK_NAME: string = 'form'; +export const FORM_TAG_CHECK_ERROR: string = "'{0}' can't support form application."; +export const CROSSPLATFORM_TAG_CHECK_NAME: string = 'crossplatform'; +export const CROSSPLATFORM_TAG_CHECK_ERROR: string = "'{0}' can't support crossplatform application."; +export const DEPRECATED_TAG_CHECK_NAME: string = 'deprecated'; +export const DEPRECATED_TAG_CHECK_WARNING: string = "'{0}' has been deprecated."; +export const FA_TAG_CHECK_NAME: string = 'famodelonly'; +export const FA_TAG_HUMP_CHECK_NAME: string = 'FAModelOnly'; +export const FA_TAG_CHECK_ERROR: string = 'This API is used only in FA Mode, but the current Mode is Stage.'; +export const STAGE_TAG_CHECK_NAME: string = 'stagemodelonly'; +export const STAGE_TAG_HUMP_CHECK_NAME: string = 'StageModelOnly'; +export const STAGE_TAG_CHECK_ERROR: string = 'This API is used only in Stage Mode, but the current Mode is FA.'; +export const STAGE_COMPILE_MODE: string = 'moduleJson'; +export const ATOMICSERVICE_BUNDLE_TYPE: string = 'atomicService'; +export const ATOMICSERVICE_TAG_CHECK_NAME: string = 'atomicservice'; +export const ATOMICSERVICE_TAG_CHECK_ERROR: string = "'{0}' can't support atomicservice application."; +export const SINCE_TAG_NAME: string = 'since'; +export const SINCE_TAG_CHECK_ERROR: string = + "The '{0}' API is supported since SDK version $SINCE1. However, the current compatible SDK version is $SINCE2."; +export const ATOMICSERVICE_TAG_CHECK_VERSION: number = 11; +export const FIND_MODULE_WARNING: string = "Cannot find name '{0}'."; +export const CONSTANT_STEP_0: number = 0; +export const CONSTANT_STEP_1: number = 1; +export const CONSTANT_STEP_2: number = 2; +export const CONSTANT_STEP_3: number = 3; +export const GLOBAL_DECLARE_WHITE_LIST: Set = new Set(['Context', 'PointerStyle', 'PixelMap', 'UnifiedData', + 'Summary', 'UniformDataType', 'IntentionCode', 'NavDestinationInfo', 'UIContext', 'Resource', 'WebviewController']); +export const MESSAGE_CONFIG_COLOR_RED: string = '\u001b[31m'; +export const MESSAGE_CONFIG_COLOR_RESET: string = '\u001b[39m'; +export const MESSAGE_CONFIG_COLOR_ERROR: string = 'color:#f5a623'; +export const MESSAGE_CONFIG_COLOR_WARNING: string = 'color:#d0021b'; +export const MESSAGE_CONFIG_HEADER_ERROR: string = 'ERROR: ArkTS:ERROR File: '; +export const MESSAGE_CONFIG_HEADER_WARNING: string = 'WARN: ArkTS:WARN File: '; \ No newline at end of file diff --git a/build-tools/compile-plugins/api-check-plugin-static/utils/api_check_plugin_enums.ts b/build-tools/compile-plugins/api-check-plugin-static/utils/api_check_plugin_enums.ts new file mode 100755 index 0000000000..1208ff3c65 --- /dev/null +++ b/build-tools/compile-plugins/api-check-plugin-static/utils/api_check_plugin_enums.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +export enum PermissionValidTokenState { + Init, + LeftParenthesis, + RightParenthesis, + PermissionChar, + And, + Or, +} \ No newline at end of file diff --git a/build-tools/compile-plugins/api-check-plugin-static/utils/api_check_plugin_typedef.ts b/build-tools/compile-plugins/api-check-plugin-static/utils/api_check_plugin_typedef.ts new file mode 100755 index 0000000000..77f1f9e272 --- /dev/null +++ b/build-tools/compile-plugins/api-check-plugin-static/utils/api_check_plugin_typedef.ts @@ -0,0 +1,145 @@ +/* + * 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. + */ + +import { JSDoc, JsDocNodeCheckConfigItem } from "../api-check-wrapper"; +import { PermissionValidTokenState } from "./api_check_plugin_enums"; + +/** + * 定义与显示窗口相关的配置 + */ +export interface WindowConfig { + designWidth: number; + autoDesignWidth: boolean; +} + +/** + * 卡片form_config.json配置 + */ +export interface FormConfig { + name: string; + displayName: string; + description: string; + src: string; // 目标字段 + uiSyntax: string; + window: WindowConfig; + colorMode: string; + isDynamic: boolean; + isDefault: boolean; + updateEnabled: boolean; + scheduledUpdateTime: string; + updateDuration: number; + defaultDimension: string; + supportDimensions: string[]; +} + + +export interface ConfigSchema { + forms: FormConfig[]; +} + +/** + * 工程编译配置 + */ +export interface ProjectConfig extends ApiCheckConfig { + bundleName: string; + moduleName: string; + cachePath: string; + projectRootPath: string; + isCrossplatform: boolean; + ignoreCrossplatformCheck: boolean; + bundleType: string; + compileSdkVersion: number; + compatibleSdkVersion: number; + compileSdkPath: string; + externalApiPaths: string; + buildSdkPath: string; + nativeDependencies: string[]; + aceSoPath: string; + permissions: ConfigPermission; + sdkConfigPaths: string; + projectPath: string; + aceModuleJsonPath: string; + compileMode: string; + aceProfilePath: string; + deviceTypes: string[]; + runtimeOS: string; + dependentModuleList: DependentModuleConfig[]; +} + +export interface ApiCheckConfig { + permissionsArray: string[]; + cardPageSet: string[]; + sdkConfigs: SdkConfig[]; + systemModules: string[]; + allModulesPaths: string[]; + externalSdkPaths: string[]; + sdkConfigPrefix: string; + syscapIntersectionSet: Set; + syscapUnionSet: Set; + deviceTypesMessage: string; + initApiCheckTag: boolean; +} + +export interface DependentModuleConfig { + packageName: string; + moduleName: string; + moduleType: string; + modulePath: string; + sourceRoots: string[]; + entryFile: string; + language: string; + declFilesPath?: string; + dependencies?: string[]; +} + +export interface CheckValidCallbackInterface { + (jsDocTag: JSDoc[], config: JsDocNodeCheckConfigItem): boolean; +} + +export interface SyscapConfig { + SysCaps: string[] +} + +export interface SdkConfig { + prefix: string; + apiPath: string[]; +} + +export interface GlobalObject { + projectConfig: ProjectConfig +} + +export interface PermissionVaildCalcInfo { + valid: boolean; + currentToken: PermissionValidTokenState; + finish: boolean; + currentPermissionMatch: boolean; +} + +export interface PermissionValidCalcGroup { + subQueue: string[]; + includeParenthesis: boolean; +} + +export interface PermissionModule { + modulePath: string; + testPermissions: string[]; + permissions: string[]; +} + +export interface ConfigPermission { + requestPermissions: Array<{ name: string }>; + definePermissions: Array<{ name: string }>; +} \ No newline at end of file diff --git a/build-tools/compile-plugins/api-check-plugin-static/utils/api_check_plugin_utils.ts b/build-tools/compile-plugins/api-check-plugin-static/utils/api_check_plugin_utils.ts new file mode 100755 index 0000000000..6ec3670fb1 --- /dev/null +++ b/build-tools/compile-plugins/api-check-plugin-static/utils/api_check_plugin_utils.ts @@ -0,0 +1,1012 @@ +/* + * 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. + */ + +import fs from 'fs'; +import path from 'path'; +import { globalObject, initApiCheckConfig } from '../index'; +import { + CheckValidCallbackInterface, + ConfigPermission, + ConfigSchema, + PermissionValidCalcGroup, + PermissionVaildCalcInfo, + ProjectConfig, + SdkConfig, + SyscapConfig, + ApiCheckConfig +} from './api_check_plugin_typedef'; +import { + MESSAGE_CONFIG_COLOR_ERROR, + MESSAGE_CONFIG_COLOR_RED, + MESSAGE_CONFIG_COLOR_RESET, + MESSAGE_CONFIG_COLOR_WARNING, + MESSAGE_CONFIG_HEADER_ERROR, + MESSAGE_CONFIG_HEADER_WARNING, + PERMISSION_TAG_CHECK_ERROR, + PERMISSION_TAG_CHECK_NAME, + RUNTIME_OS_OH, + SINCE_TAG_CHECK_ERROR, + SINCE_TAG_NAME, + STAGE_COMPILE_MODE, + SYSCAP_TAG_CHECK_NAME +} from './api_check_plugin_define'; +import { + CurrentAddress, + DiagnosticCategory, + JSDoc, + JsDocNodeCheckConfigItem, + JSDocTag +} from '../api-check-wrapper'; +import { PermissionValidTokenState } from './api_check_plugin_enums'; + +/** + * 从 JSON 文件中提取所有 src 字段到数组 + * + * @param { string } filePath JSON 文件的绝对路径 + * @returns { string[] } 包含所有 src 字段的字符串数组 + * @throws 文件不存在、JSON 解析错误或数据结构不符时抛出异常 + */ +export function extractSrcPaths(filePath: string): string[] { + // 1. 验证路径格式和存在性 + if (!path.isAbsolute(filePath)) { + throw new Error(`路径必须是绝对路径: ${filePath}`); + } + + if (!fs.existsSync(filePath)) { + throw new Error(`文件不存在: ${filePath}`); + } + + try { + // 2. 读取并解析 JSON 文件 + const rawData = fs.readFileSync(filePath, 'utf-8'); + const config: ConfigSchema = JSON.parse(rawData); + + // 3. 验证数据结构 + if (!config.forms || !Array.isArray(config.forms)) { + throw new Error('JSON 缺少 forms 数组'); + } + + // 4. 提取所有 src 字段 + const srcPaths: string[] = []; + for (const form of config.forms) { + if (form.src && typeof form.src === 'string') { + let src = form.src.replace(/^\.\/ets/, ''); + srcPaths.push(globalObject.projectConfig?.projectPath + src); + } else { + console.warn(`跳过无效 src 字段的表单项: ${form.name}`); + } + } + + // 5. 返回结果数组 + return srcPaths; + } catch (error) { + // 6. 增强错误信息 + if (error instanceof SyntaxError) { + throw new SyntaxError(`JSON 解析错误: ${error.message}`); + } + throw new Error(`处理失败: ${error instanceof Error ? error.message : String(error)}`); + } +} + +/** + * 判断当前ets文件是否是卡片文件。 + * + * @param { string } file 当前ets文件路径 + * @returns { boolean } 是否为卡片文件 + */ +export function isCardFile(file: string): boolean { + if (globalObject.projectConfig.cardPageSet.includes(file)) { + return true; + } + return false; +} + +/** + * 校验since标签,当前api版本是否小于等于compatibleSdkVersion。 + * + * @param { JSDoc[] } jsDocs 当前api的JSDoc + * @param { JsDocNodeCheckConfigItem } config 当前的since标签校验规则 + * @returns { boolean } 是否报错该since标签 + */ +export function checkSinceTag(jsDocs: JSDoc[], config: JsDocNodeCheckConfigItem): boolean { + if (jsDocs && jsDocs.length > 0) { + const minorJSDocVersion: number = getJSDocMinorVersion(jsDocs); + const compatibleSdkVersion: number = globalObject.projectConfig.compatibleSdkVersion; + if (minorJSDocVersion > compatibleSdkVersion) { + config.message = SINCE_TAG_CHECK_ERROR.replace('$SINCE1', minorJSDocVersion.toString()) + .replace('$SINCE2', compatibleSdkVersion.toString()); + return true; + } + } + return false; +} + +/** + * 获取最小的Since版本号 + * + * @param { JSDoc[] } jsDocs JSDoc注释对象数组,用于提取版本信息 + * @returns { number } 从@since标签中提取的最大版本号(数值),若未找到则返回0 + */ +function getJSDocMinorVersion(jsDocs: JSDoc[]): number { + let minorVersion: number = 0; + if (jsDocs && jsDocs.length > 0) { + const jsdoc: JSDoc = jsDocs[0]; + if (jsdoc.tags && jsdoc.tags.length > 0) { + for (let i = 0; i < jsdoc.tags.length; i++) { + const tag: JSDocTag = jsdoc.tags[i]; + if (tag.tag === SINCE_TAG_NAME) { + const currentVersion: number = Number.parseInt(tag.name ?? ''); + if (minorVersion === 0 || + !Number.isNaN(currentVersion) && currentVersion > minorVersion) { + minorVersion = currentVersion; + } + break; + } + } + } + } + return minorVersion; +} + +/** + * 获取JSDoc数组中最新版本的JSDoc注释对象。 + * + * @param { JSDoc[] } jsDocs JSDoc注释对象数组,包含多个版本的注释信息 + * @returns { JSDoc } 数组中最后一个JSDoc对象,即最新版本的注释 + */ +function getCurrentJSDoc(jsDocs: JSDoc[]): JSDoc { + let currentJsDoc: JSDoc = jsDocs[jsDocs.length - 1]; + return currentJsDoc; +} + +/** + * 从JSDoc注释对象中获取指定名称的标签,提取如param、permission等特定标签。 + * + * @param { JSDoc } jsDoc + * @param { string } tagName + * @returns { JSDocTag | undefined } + */ +function getJSDocTag(jsDoc: JSDoc, tagName: string): JSDocTag | undefined { + const jsDocTag: JSDocTag | undefined = jsDoc.tags.find((item: JSDocTag) => { + return item.tag === tagName; + }); + return jsDocTag; +} + +/** + * 验证API文档注释中配置的permission是否被应用permission集合支持。 + * STEP1. 解析permission信息; + * STEP2. 递归验证permission; + * + * @param { string } comment jsdoc中comment信息 + * @param { string[] } permissionsArray 应用permission集合 + * @returns { boolean } 若应用权限集合满足注释中的权限表达式,返回 true;否则返回 false + */ +function validPermission(comment: string, permissionsArray: string[]): boolean { + const permissionsItem: string[] = getSplitsArrayWithDesignatedCharAndStr(comment ?? '', ' ') + .filter((item) => { + return item !== ''; + }); + const permissionsQueue: string[] = []; + permissionsItem.forEach((item: string) => { + // STEP1.1 Parse'(' + const leftParenthesisItem: string[] = getSplitsArrayWithDesignatedCharAndArrayStr([item], '('); + // STEP1.2 Parse')' + const rightParenthesisItem: string[] = getSplitsArrayWithDesignatedCharAndArrayStr(leftParenthesisItem, ')'); + permissionsQueue.push(...rightParenthesisItem); + }); + // STEP2 + const calcValidResult: PermissionVaildCalcInfo = { + valid: false, + currentToken: PermissionValidTokenState.Init, + finish: false, + currentPermissionMatch: true, + }; + validPermissionRecursion(permissionsQueue, permissionsArray, calcValidResult); + return calcValidResult.valid; +} + +/** + * 该函数是权限验证的核心递归入口,若权限队列中包含括号,则先按括号分组处理,再验证分组结果; + * 若不包含括号,则直接调用getPermissionVaildAtoms处理。 + * + * @param { string[] } permissionsQueue 权限表达式队列,包含权限项、逻辑运算符(and/or)及括号(()) + * @param { string[] } permissions 项目配置的权限列表,用于验证权限项是否有效 + * @param { PermissionVaildCalcInfo } calcValidResult 权限验证计算信息对象,存储中间状态和最终结果 + */ +function validPermissionRecursion(permissionsQueue: string[], permissions: string[], + calcValidResult: PermissionVaildCalcInfo): void { + if (permissionsQueue.some(item => ['(', ')'].includes(item))) { + const groups: PermissionValidCalcGroup[] = groupWithParenthesis(permissionsQueue); + const groupJoin: string[] = getGroupItemPermission(groups, calcValidResult, permissions); + getPermissionVaildAtoms(groupJoin, calcValidResult, permissions ?? []); + } else { + getPermissionVaildAtoms(permissionsQueue, calcValidResult, permissions ?? []); + } +} + +/** + * 按指定字符分割字符串,并去除每个分割项的首尾空格。 + * + * @param { string } permission 需要分割的权限表达式字符串(如"(perm1 or perm2) and perm3") + * @param { string } designatedChar 用于分割的指定字符(如'and'、'or'、'('等) + * @returns { string[] } 分割后的字符串数组,每个元素已去除首尾空格 + */ +function getSplitsArrayWithDesignatedCharAndStr(permission: string, designatedChar: string): string[] { + return permission.split(designatedChar).map(item => item.trim()); +} + +/** + * 处理groupWithParenthesis返回的分组,对包含括号的子分组递归验证, + * 并将验证结果转换为特定标识(空字符串表示有效,'NA'表示无效),用于上层表达式验证。 + * + * @param { PermissionValidCalcGroup[] } groups 由groupWithParenthesis返回的分组数组 + * @param { PermissionVaildCalcInfo } calcValidResult 权限验证计算信息对象(用于传递上下文) + * @param { string[] } permissions 配置的权限列表,用于验证子分组中的权限项 + * @returns { string[] } 处理后的中间结果数组,包含非括号分组的原始项和括号分组的验证标识(''或'NA') + */ +function getGroupItemPermission( + groups: PermissionValidCalcGroup[], + calcValidResult: PermissionVaildCalcInfo, + permissions: string[]): string[] { + const groupJoin: string[] = []; + groups.forEach((groupItem: PermissionValidCalcGroup) => { + if (groupItem.includeParenthesis) { + const calcValidResultItem: PermissionVaildCalcInfo = { + ...calcValidResult, + }; + const subStack: string[] = groupItem.subQueue.slice(1, groupItem.subQueue.length - 1); + validPermissionRecursion(subStack, permissions, calcValidResultItem); + if (calcValidResultItem.valid) { + groupJoin.push(''); + } else { + groupJoin.push('NA'); + } + } else { + groupJoin.push(...groupItem.subQueue); + } + }); + return groupJoin; +} + +/** + * 根据括号对权限队列进行分组,支持嵌套括号,生成包含分组信息的数组。 + * 该函数通过计数器跟踪括号的嵌套层级,将连续的括号及其中间内容划分为一个分组, + * 非括号内容划分为普通分组,用于处理带优先级的权限表达式。 + * + * @param { string[] } stack 权限表达式队列,包含权限项、逻辑运算符(and/or)及括号('('或')') + * @returns { PermissionValidCalcGroup[] } 分组数组,每个元素包含分组的子队列和是否含括号的标识 + */ +function groupWithParenthesis(stack: string[]): PermissionValidCalcGroup[] { + let currentLeftParenthesisCount: number = 0; + const groups: PermissionValidCalcGroup[] = []; + let currentGroupItem: PermissionValidCalcGroup = { + subQueue: [], + includeParenthesis: false, + }; + stack.forEach((item: string, index: number) => { + if (item === '(') { + if (currentLeftParenthesisCount === 0) { + groups.push(currentGroupItem); + currentGroupItem = { + subQueue: [item], + includeParenthesis: true + }; + } else { + currentGroupItem.subQueue.push(item); + } + currentLeftParenthesisCount++; + } else if (item === ')') { + currentLeftParenthesisCount--; + currentGroupItem.subQueue.push(item); + if (currentLeftParenthesisCount === 0) { + groups.push(currentGroupItem); + currentGroupItem = { + subQueue: [], + includeParenthesis: false, + }; + } + } else { + currentGroupItem.subQueue.push(item); + if (index === stack.length - 1) { + groups.push(currentGroupItem); + } + } + }); + return groups; +} + +/** + * 深度优先递归处理权限原子栈,根据逻辑运算符(and/or)验证权限匹配结果,并将结果存入calcValidResult对象中。 + * + * @param { string[] } atomStacks 权限atomStacks,包含权限项(如"perm1")和逻辑运算符("and"/"or"),按表达式顺序排列 + * @param { PermissionVaildCalcInfo } calcValidResult 权限验证计算信息对象,用于存储中间状态和最终结果 + * @param { string[] } configPermissions 项目配置的权限列表,用于验证单个权限项是否有效 + */ +function getPermissionVaildAtoms(atomStacks: string[], calcValidResult: PermissionVaildCalcInfo, + configPermissions: string[]): void { + if (calcValidResult.finish) { + return; + } + if (atomStacks[0] === 'and') { + calcValidResult.currentToken = PermissionValidTokenState.And; + } else if (atomStacks[0] === 'or') { + calcValidResult.currentToken = PermissionValidTokenState.Or; + } else { + if (calcValidResult.currentToken === PermissionValidTokenState.Or) { + if (inValidOrExpression( + atomStacks, + calcValidResult, + configPermissions + )) { + calcValidResult.currentPermissionMatch = false; + } + } else if (calcValidResult.currentToken === PermissionValidTokenState.And) { + if (inValidAndExpression( + atomStacks, + calcValidResult, + configPermissions + )) { + calcValidResult.currentPermissionMatch = false; + } + } else { + calcValidResult.currentPermissionMatch = + validPermissionItem(atomStacks[0], configPermissions); + } + } + if (atomStacks.length > 1) { + getPermissionVaildAtoms( + atomStacks.slice(1), + calcValidResult, + configPermissions + ); + } else { + calcValidResult.valid = calcValidResult.currentPermissionMatch; + calcValidResult.finish = true; + } +} + +/** + * 验证Or逻辑表达式的有效性 + * + * @param { string[] } atomStacks 权限atomStacks,当前处理的元素为需要验证的权限项(atomStacks[0]) + * @param { PermissionVaildCalcInfo } calcValidResult 权限限验证计算信息对象,存储中间状态 + * @param { string[] } configPermissions 项目配置的权限列表,用于验证权限项是否存在 + * @returns { boolean } 若Or表达式无效(所有条件均不满足),返回true;否则返回false + */ +function inValidOrExpression( + atomStacks: string[], + calcValidResult: PermissionVaildCalcInfo, + configPermissions: string[]): boolean { + if ( + !calcValidResult.currentPermissionMatch && + !validPermissionItem(atomStacks[0], configPermissions) + ) { + calcValidResult.valid = false; + return true; + } + calcValidResult.currentPermissionMatch = true; + return false; +} + +/** + * 验证And逻辑表达式的有效性 + * + * @param { string[] } atomStacks 权限atomStacks,当前处理的元素为需要验证的权限项(atomStacks[0]) + * @param { PermissionVaildCalcInfo } calcValidResult 权限验证计算信息对象,存储中间状态 + * @param { string[] } configPermissions 项目配置的权限列表,用于验证权限项是否存在 + * @returns { boolean } 若And表达式无效(存在不满足的条件),返回true;否则返回false + */ +function inValidAndExpression( + atomStacks: string[], + calcValidResult: PermissionVaildCalcInfo, + configPermissions: string[]): boolean { + if ( + !calcValidResult.currentPermissionMatch || + !validPermissionItem(atomStacks[0], configPermissions) + ) { + calcValidResult.valid = false; + return true; + } + calcValidResult.currentPermissionMatch = + validPermissionItem(atomStacks[0], configPermissions); + return false; +} + +/** + * 基础校验PermissionItem + * + * @param { string } atomStackItem 待校验的atomStackItem(如具体的权限名称) + * @param { string[] } configPermissions 项目配置的权限列表,用于校验atomStackItem是否存在 + * @returns { boolean } 若atomStackItem为空字符串或存在于配置列表中,返回true;否则返回false + */ +function validPermissionItem(atomStackItem: string, configPermissions: string[]): boolean { + return atomStackItem === '' || configPermissions.includes(atomStackItem); +} + +/** + * 将字符串数组中的每个元素按指定字符(如括号、逻辑运算符)拆分, + * 对拆分后产生的空值用指定字符替换,最终返回拆分后的扁平数组,支持处理嵌套拆分场景。 + * + * @param { string[] } leftParenthesisItems 需要拆分的字符串数组 + * @param { string } designatedChar 用于拆分的指定字符(如'('、')'、'and'等) + * @returns { string[] } 拆分后的字符串数组,空值被替换为指定字符 + */ +function getSplitsArrayWithDesignatedCharAndArrayStr( + leftParenthesisItems: string[], + designatedChar: string +): string[] { + const rightParenthesisItems: string[] = []; + leftParenthesisItems.forEach((leftParenthesisItem: string) => { + if (leftParenthesisItem.includes(designatedChar)) { + const rightParenthesis: string[] = + getSplitsArrayWithDesignatedCharAndStr( + leftParenthesisItem, + designatedChar + ); + rightParenthesis.forEach((item: string) => { + if (item === '') { + rightParenthesisItems.push(designatedChar); + } else { + rightParenthesisItems.push(item); + } + }); + } else { + rightParenthesisItems.push(leftParenthesisItem); + } + }); + return rightParenthesisItems; +} + +/** +* 获取jsDocNodeCheckConfigItem配置项 +* +* @param { string[] } tagName tag名 +* @param { string } message 报错信息 +* @param { DiagnosticCategory } type 报错类型 +* @param { boolean } tagNameShouldExisted 该tag是否应该存在 +* @param { CheckValidCallbackInterface } checkValidCallback 报错验证回调方法 +* @returns { JsDocNodeCheckConfigItem } JsDocNodeCheckConfigItem对象 +*/ +export function getJsDocNodeCheckConfigItem(tagName: string[], message: string, type: DiagnosticCategory, + tagNameShouldExisted: boolean, checkValidCallback?: CheckValidCallbackInterface): JsDocNodeCheckConfigItem { + return { + tagName: tagName, + message: message, + type: type, + tagNameShouldExisted: tagNameShouldExisted, + checkValidCallback: checkValidCallback + }; +} + +/** + * 创建/清空工程配置 + * + * @returns { ProjectConfig } + */ +export function createOrCleanProjectConfig(): ProjectConfig { + return { + bundleName: '', + moduleName: '', + cachePath: '', + aceModuleJsonPath: '', + compileMode: '', + permissions: { + requestPermissions: [], + definePermissions: [] + }, + projectRootPath: '', + isCrossplatform: false, + ignoreCrossplatformCheck: false, + bundleType: '', + compileSdkVersion: 0, + compatibleSdkVersion: 0, + projectPath: '', + aceProfilePath: '', + cardPageSet: [], + compileSdkPath: '', + systemModules: [], + allModulesPaths: [], + sdkConfigs: [], + externalApiPaths: '', + externalSdkPaths: [], + sdkConfigPrefix: '', + deviceTypes: [], + deviceTypesMessage: '', + runtimeOS: '', + syscapIntersectionSet: new Set([]), + syscapUnionSet: new Set([]), + permissionsArray: [], + buildSdkPath: '', + nativeDependencies: [], + aceSoPath: '', + sdkConfigPaths: '', + initApiCheckTag: true, + dependentModuleList: [] + }; +} + +/** + * 初始化组装projectConfig所需对象 + * + * @returns { ApiCheckConfig } + */ +export function creatApiCheckConfig(): ApiCheckConfig { + return { + cardPageSet: [], + systemModules: [], + allModulesPaths: [], + sdkConfigs: [], + externalSdkPaths: [], + sdkConfigPrefix: '', + deviceTypesMessage: '', + syscapIntersectionSet: new Set([]), + syscapUnionSet: new Set([]), + permissionsArray: [], + initApiCheckTag: true + } +} + +/** + * 重新拆解组装permissions对象成新的permissionsArray数组 + * + * @param { ProjectConfig } projectConfig 配置信息 + */ +export function readPermissions(projectConfig: ProjectConfig): void { + if (!projectConfig) { + return; + } + const permissions: ConfigPermission = projectConfig.permissions; + const requestPermissions: string[] = permissions.requestPermissions + ? getPermissionFromConfig(permissions.requestPermissions) + : []; + + const definePermissions: string[] = permissions.definePermissions + ? getPermissionFromConfig(permissions.definePermissions) + : []; + + const permissionsArray: string[] = [ + ...requestPermissions, + ...definePermissions + ]; + projectConfig.permissionsArray = permissionsArray; +} + +/** + * 遍历permissions数组,提取每个permissions对象的name属性值。 + * + * @param { Array<{ name: string }> } array 包含权限对象的数组,每个对象必须有name属性 + * @returns { string[] } 提取出的权限名称字符串数组 + */ +function getPermissionFromConfig(array: Array<{ name: string }>): string[] { + return array.map((item: { name: string }) => { + return String(item.name); + }); +} + +/** + * 从aceModuleJsonPath中提取模块的extensionAbilities对象。 + * + * @param { ProjectConfig } projectConfig 配置信息 + */ +export function readCardPageSet(projectConfig: ProjectConfig): void { + if (projectConfig.aceModuleJsonPath && fs.existsSync(projectConfig.aceModuleJsonPath)) { + projectConfig.compileMode = STAGE_COMPILE_MODE; + const moduleJson: any = JSON.parse(fs.readFileSync(projectConfig.aceModuleJsonPath).toString()); + const extensionAbilities: any = moduleJson?.module?.extensionAbilities; + if (extensionAbilities && extensionAbilities.length > 0) { + setCardPages(extensionAbilities, projectConfig); + } + } +} + +/** + * 遍历筛选出类型为'form'的extensionAbilities对象,遍历该对象的metadata数组。 + * + * @param { any } extensionAbilities 扩展能力数组,包含应用中定义的各类扩展能力配置对象 + * @param { ProjectConfig } projectConfig 配置信息 + */ +function setCardPages(extensionAbilities: any, projectConfig: ProjectConfig): void { + if (extensionAbilities && extensionAbilities.length > 0) { + extensionAbilities.forEach((extensionAbility: any) => { + if (extensionAbility.type === 'form' && extensionAbility.metadata) { + extensionAbility.metadata.forEach((metadata: any) => { + if (metadata.resource) { + readCardResource(metadata.resource, projectConfig); + } + }); + } + }); + } +} + +/** + * 读取卡片资源,筛选出类型为 eTS 或 uiSyntax 为 arkts 的卡片配置,更新到cardPageSet中。 + * + * @param { string } resource 卡片资源标识字符串,格式可能包含`$profile:`前缀(如`$profile:card_config`) + * @param { ProjectConfig } projectConfig 配置信息 + */ +function readCardResource(resource: string, projectConfig: ProjectConfig): void { + const cardJsonFileName: string = `${resource.replace(/\$profile\:/, '')}.json`; + const modulePagePath: string = path.resolve(projectConfig.aceProfilePath, cardJsonFileName); + if (fs.existsSync(modulePagePath)) { + const cardConfig: any = JSON.parse(fs.readFileSync(modulePagePath, 'utf-8')); + if (cardConfig.forms) { + cardConfig.forms.forEach((form: any) => { + if ((form.type && form.type === 'eTS') || (form.uiSyntax && form.uiSyntax === 'arkts')) { + const cardPath = path.resolve(projectConfig.projectPath, '..', form.src); + if (cardPath && fs.existsSync(cardPath) && !projectConfig.cardPageSet.includes(cardPath)) { + projectConfig.cardPageSet.push(cardPath); + } + } + }); + } + } +} + +/** + * 扫描系统模块目录(api、arkts、kits),收集模块路径信息 + * 将直接子模块名称添加到 systemModules,处理文件路径(过滤、替换、格式化)后添加到 allModulesPaths。 + * + * @param { ProjectConfig } projectConfig 配置信息 + */ +export function readSystemModules(projectConfig: ProjectConfig): void { + const apiDirPath = path.resolve(projectConfig.buildSdkPath, './api'); + const arktsDirPath = path.resolve(projectConfig.buildSdkPath, './arkts'); + const kitsDirPath = path.resolve(projectConfig.buildSdkPath, './kits'); + const systemModulePathArray = [apiDirPath, arktsDirPath, kitsDirPath]; + + systemModulePathArray.forEach(systemModulesPath => { + if (fs.existsSync(systemModulesPath) && projectConfig) { + const modulePaths: string[] = []; + readFile(systemModulesPath, modulePaths); + projectConfig.systemModules.push(...fs.readdirSync(systemModulesPath)); + modulePaths.filter(filePath => { + const dirName = path.dirname(filePath); + return !(dirName === apiDirPath || dirName === arktsDirPath || dirName === kitsDirPath); + }).map((filePath: string) => { + return filePath + .replace(apiDirPath, '') + .replace(arktsDirPath, '') + .replace(kitsDirPath, '') + .replace(/(^\\)|(.d.e?ts$)/g, '') + .replace(/\\/g, '/'); + }); + projectConfig.allModulesPaths.push(...modulePaths); + } + }); + const defaultSdkConfigs: SdkConfig[] = [ + { + 'apiPath': systemModulePathArray, + 'prefix': '@ohos' + }, { + 'apiPath': systemModulePathArray, + 'prefix': '@system' + }, { + 'apiPath': systemModulePathArray, + 'prefix': '@arkts' + } + ]; + const externalApiPathStr = projectConfig.sdkConfigPaths || ''; + const externalApiPaths = externalApiPathStr.split(path.delimiter); + projectConfig.externalSdkPaths = [...externalApiPaths]; + const extendSdkConfigs: SdkConfig[] = []; + collectExternalModules(externalApiPaths, extendSdkConfigs, projectConfig); + projectConfig.sdkConfigs = [...defaultSdkConfigs, ...extendSdkConfigs]; +} + +/** + * 遍历SDK路径,读取每个路径下的sdkConfig.json配置文件,解析并处理其中的API路径和前缀信息, + * 同时收集路径信息,最终将这些配置添加sdkConfigPrefix中。 + * + * @param { string[] } sdkPaths 外部SDK路径数组,每个路径指向一个外部模块的根目录 + * @param { SdkConfig[] } extendSdkConfigs 用于存储外部SDK配置的数组,将与默认配置合并 + * @param { ProjectConfig } projectConfig 配置信息 + */ +function collectExternalModules(sdkPaths: string[], extendSdkConfigs: SdkConfig[], projectConfig: ProjectConfig): void { + for (let i = 0; i < sdkPaths.length; i++) { + const sdkPath = sdkPaths[i]; + const sdkConfigPath = path.resolve(sdkPath, 'sdkConfig.json'); + if (!fs.existsSync(sdkConfigPath)) { + continue; + } + const sdkConfig: SdkConfig = JSON.parse(fs.readFileSync(sdkConfigPath, 'utf-8')); + if (!sdkConfig.apiPath) { + continue; + } + let externalApiPathArray: string[] = []; + if (Array.isArray(sdkConfig.apiPath)) { + externalApiPathArray = sdkConfig.apiPath; + } else { + externalApiPathArray.push(sdkConfig.apiPath); + } + const resolveApiPathArray: string[] = []; + externalApiPathArray.forEach((element: string) => { + const resolvePath: string = path.resolve(sdkPath, element); + resolveApiPathArray.push(resolvePath); + if (fs.existsSync(resolvePath) && projectConfig) { + const extrenalModulePaths: string[] = []; + projectConfig.systemModules.push(...fs.readdirSync(resolvePath)); + readFile(resolvePath, extrenalModulePaths); + projectConfig.allModulesPaths.push(...extrenalModulePaths); + } + }); + projectConfig.sdkConfigPrefix += `|${sdkConfig.prefix.replace(/^@/, '')}`; + sdkConfig.apiPath = resolveApiPathArray; + extendSdkConfigs.push(sdkConfig); + } +} + +/** + * 收集指定设备类型的系统能力信息(包括OH和其他的Syscap), + * 计算所有设备类型共有的系统能力(交集)和所有设备类型的系统能力总和(并集) + * + * @param { ProjectConfig } projectConfig 配置对象 + */ +export function readSyscapInfo(projectConfig: ProjectConfig): void { + projectConfig.deviceTypesMessage = projectConfig.deviceTypes.join(','); + const deviceDir: string = path.resolve(__dirname, '../../../../../api/device-define/'); + const deviceInfoMap: Map = new Map(); + const syscaps: Array = []; + let allSyscaps: string[] = []; + projectConfig.deviceTypes.forEach((deviceType: string) => { + collectOhSyscapInfos(deviceType, deviceDir, deviceInfoMap); + }); + if (projectConfig.runtimeOS !== RUNTIME_OS_OH) { + collectExternalSyscapInfos(projectConfig.externalSdkPaths, projectConfig.deviceTypes, + deviceInfoMap); + } + deviceInfoMap.forEach((value: string[]) => { + syscaps.push(value); + allSyscaps = allSyscaps.concat(value); + }); + const intersectNoRepeatTwice = (arrs: Array) => { + return arrs.reduce(function (prev: string[], cur: string[]) { + return Array.from(new Set(cur.filter((item: string) => { + return prev.includes(item); + }))); + }); + }; + let syscapIntersection: string[] = []; + if (projectConfig.deviceTypes.length === 1 || syscaps.length === 1) { + syscapIntersection = syscaps[0]; + } else if (syscaps.length > 1) { + syscapIntersection = intersectNoRepeatTwice(syscaps); + } + projectConfig.syscapIntersectionSet = new Set(syscapIntersection); + projectConfig.syscapUnionSet = new Set(allSyscaps); +} + +/** + * 指定的设备定义目录中读取对应设备类型的Syscap配置文件,解析并存储系统能力信息到映射表中。 + * + * @param { string } deviceType 设备类型 + * @param { string } deviceDir 设备定义配置文件所在的目录路径 + * @param { Map } deviceInfoMap 用于存储设备类型与对应Syscap数组的映射表 + */ +function collectOhSyscapInfos(deviceType: string, deviceDir: string, deviceInfoMap: Map) { + let syscapFilePath: string = ''; + if (deviceType === 'phone') { + syscapFilePath = path.resolve(deviceDir, 'default.json'); + } else { + syscapFilePath = path.resolve(deviceDir, deviceType + '.json'); + } + if (fs.existsSync(syscapFilePath)) { + const content: SyscapConfig = JSON.parse(fs.readFileSync(syscapFilePath, 'utf-8')); + if (deviceInfoMap.get(deviceType)) { + deviceInfoMap.set(deviceType, (deviceInfoMap.get(deviceType) as string[]).concat(content.SysCaps)); + } else { + deviceInfoMap.set(deviceType, content.SysCaps); + } + } +} + +/** + * 从外部SDK路径中查找设备定义目录,读取对应设备类型的Syscap配置文件, + * 解析并合并系统能力信息到映射表中,用于扩展系统默认的Syscap集合。 + * + * @param { string[] } externalApiPaths 外部SDK路径数组 + * @param { string[] } deviceTypes 项目支持的设备类型数组 + * @param { Map } deviceInfoMap 用于存储设备类型与对应Syscap数组的映射表 + */ +function collectExternalSyscapInfos( + externalApiPaths: string[], + deviceTypes: string[], + deviceInfoMap: Map +) { + const externalDeviceDirs: string[] = []; + externalApiPaths.forEach((externalApiPath: string) => { + const externalDeviceDir: string = path.resolve(externalApiPath, './api/device-define'); + if (fs.existsSync(externalDeviceDir)) { + externalDeviceDirs.push(externalDeviceDir); + } + }); + externalDeviceDirs.forEach((externalDeviceDir: string) => { + deviceTypes.forEach((deviceType: string) => { + let syscapFilePath: string = ''; + const files: string[] = fs.readdirSync(externalDeviceDir); + files.forEach((fileName: string) => { + if (fileName.startsWith(deviceType)) { + syscapFilePath = path.resolve(externalDeviceDir, fileName); + if (fs.existsSync(syscapFilePath)) { + const content: SyscapConfig = JSON.parse(fs.readFileSync(syscapFilePath, 'utf-8')); + if (deviceInfoMap.get(deviceType)) { + deviceInfoMap.set(deviceType, (deviceInfoMap.get(deviceType) as string[]).concat(content.SysCaps)); + } else { + deviceInfoMap.set(deviceType, content.SysCaps); + } + } + } + }); + }); + }); +} + +/** + * 递归读取目录下所有文件路径 + * + * @param { string } dir 要读取的目录路径(绝对路径或相对路径) + * @param { string[] } utFiles 用于存储收集到的文件路径的数组,函数会直接修改该数组 + */ +export function readFile(dir: string, utFiles: string[]): void { + try { + const files: string[] = fs.readdirSync(dir); + files.forEach((element) => { + const filePath: string = path.join(dir, element); + const status: fs.Stats = fs.statSync(filePath); + if (status.isDirectory()) { + readFile(filePath, utFiles); + } else { + utFiles.push(filePath); + } + }); + } catch (e) { + console.error(MESSAGE_CONFIG_COLOR_RED, 'ArkTS ERROR: ' + e, MESSAGE_CONFIG_COLOR_RESET); + } +} + +/** +* 解析最新版本JSDoc中的@permission标签内容,验证表达式是否与项目配置的权限集合匹配, +* 若不匹配则返回true(表示需要检查提示),并更新错误信息。 +* +* @param { JSDoc[] } jsDocs JSDoc注释对象数组,用于获取最新版本的注释 +* @param { JsDocNodeCheckConfigItem } config 检查配置对象,用于存储错误提示信息 +* @returns { boolean } 若@permission标签不存在、表达式为空或不满足权限要求,返回true;否则返回false +*/ +export function checkPermissionTag(jsDocs: JSDoc[], config: JsDocNodeCheckConfigItem): boolean { + const currentJSDoc: JSDoc = getCurrentJSDoc(jsDocs); + const jsDocTag: JSDocTag | undefined = getJSDocTag(currentJSDoc, PERMISSION_TAG_CHECK_NAME); + if (!jsDocTag) { + return false; + } + const permissionExpression: string = jsDocTag.comment ?? ''; + config.message = PERMISSION_TAG_CHECK_ERROR.replace('$DT', permissionExpression); + return permissionExpression !== '' && + !validPermission(permissionExpression, globalObject.projectConfig.permissionsArray); +} + +/** + * 从最新版本的JSDoc注释中提取@syscap标签,验证其是否存在于所有设备类型共有的系统能力集合中, + * 若不存在则返回true(表示需要检查提示)。 + * + * @param { JSDoc[] } jsDocs JSDoc注释对象数组,用于获取最新版本的注释 + * @param { JsDocNodeCheckConfigItem } config 检查配置对象(当前函数未使用,预留扩展) + * @returns { boolean } 若@syscap标签不存在、系统能力值为空或不在交集范围内,返回true;否则返回false + */ +export function checkSyscapTag(jsDocs: JSDoc[], config: JsDocNodeCheckConfigItem): boolean { + let currentSyscapValue: string = ''; + if (jsDocs && jsDocs.length > 0) { + const jsDoc: JSDoc = getCurrentJSDoc(jsDocs); + for (let i = 0; i < jsDoc.tags.length; i++) { + const jsDocTag: JSDocTag = jsDoc.tags[i]; + if (jsDocTag && jsDocTag.tag === SYSCAP_TAG_CHECK_NAME) { + currentSyscapValue = jsDocTag.name ?? ''; + break; + } + } + } + return globalObject.projectConfig.syscapIntersectionSet && + !globalObject.projectConfig.syscapIntersectionSet.has(currentSyscapValue); +} + +/** + * 格式化日志信息,整合API名称、文件路径、代码位置等上下文, + * 并调用打印函数输出带有指定级别和消息内容的日志。 + * + * @param { string } apiName 涉及的API名称,用于日志中的信息替换 + * @param { string } currentFilePath 当前文件的路径 + * @param { CurrentAddress } currentAddress 行列坐标 + * @param { DiagnosticCategory } logLevel 日志级别(ERROR、WARNING) + * @param { string } logMessage 日志消息模板,其中{0}会被替换为apiName + */ +export function pushLog(apiName: string, currentFilePath: string, currentAddress: CurrentAddress, + logLevel: DiagnosticCategory, logMessage: string) { + // 组装文件全路径 + const fileFullPath: string = currentFilePath + `(${currentAddress.line}:${currentAddress.column}).`; + // 替换api名称 + logMessage = logMessage.replace('{0}', apiName); + // 打印日志信息 + printMessage(fileFullPath, logMessage, logLevel); +} + +/** + * 根据日志级别打印格式化的日志信息,支持不同级别日志的样式区分。 + * + * @param { string } fileInfo 包含文件路径和行列信息的字符串(如“path/to/file.ts(5:10).”) + * @param { string } message 日志的具体内容消息 + * @param { DiagnosticCategory } level 日志级别 + */ +function printMessage(fileInfo: string, message: string, level: DiagnosticCategory) { + let messageHead: string = MESSAGE_CONFIG_HEADER_WARNING; + let messageColor: string = MESSAGE_CONFIG_COLOR_WARNING; + if (level === DiagnosticCategory.ERROR) { + messageHead = MESSAGE_CONFIG_HEADER_ERROR; + messageColor = MESSAGE_CONFIG_COLOR_ERROR; + } + // TODO: 待工具链日志输出方式确认后同步适配 + console.info(`%c${messageHead}${fileInfo}\n ${message}`, messageColor); +} + +/** + * 检测模块路径是否包含SO文件(.so后缀),并将对应的文件路径添加到项目配置的原生依赖列表中,避免重复添加。 + * + * @param { string[] } moduleName 模块名称数组 + * @param { string } modulePath 模块的路径字符串,用于检测是否为SO文件 + * @param { string } currentFilePath 当前模块文件的完整路径,将被添加到原生依赖列表 + */ +export function collectInfo(moduleName: string[], modulePath: string, currentFilePath: string) { + // 收集so模块依赖 + if (/lib(\S+)\.so/g.test(modulePath) && !globalObject.projectConfig.nativeDependencies.includes(currentFilePath)) { + globalObject.projectConfig.nativeDependencies.push(currentFilePath); + } +} + +/** + * 管理项目中使用的OS相关文件列表,若文件已存在则增量添加新文件路径(去重),若不存在则创建文件并写入内容, + * 同时确保文件所在目录存在。 + * + * @param { string[] } useOSFiles 需要写入的OS文件路径数组 + */ +export function writeUseOSFiles(useOSFiles: string[]): void { + let info: string = useOSFiles.join('\n'); + if (!fs.existsSync(globalObject.projectConfig.aceSoPath)) { + const parent: string = path.resolve(globalObject.projectConfig.aceSoPath, '..'); + if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) { + mkDir(parent); + } + } else { + const currentUseOSFiles: string[] = fs.readFileSync(globalObject.projectConfig.aceSoPath, 'utf-8').split('\n'); + useOSFiles.forEach((filePath: string) => { + if (!currentUseOSFiles.includes(filePath)) { + currentUseOSFiles.push(filePath); + } + }); + info = currentUseOSFiles.join('\n'); + } + fs.writeFileSync(globalObject.projectConfig.aceSoPath, info); +} + +/** + * 通过递归方式创建指定路径的目录,若父目录不存在则先创建父目录,确保最终目录可成功创建。 + * + * @param { string } path_ 需要创建的目录路径 + */ +function mkDir(path_: string): void { + const parent: string = path.join(path_, '..'); + if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) { + mkDir(parent); + } + fs.mkdirSync(path_); +} \ No newline at end of file diff --git a/build-tools/package.json b/build-tools/package.json index 7bbb31eaa8..70aad2fe86 100644 --- a/build-tools/package.json +++ b/build-tools/package.json @@ -5,7 +5,7 @@ "main": "delete_systemapi_plugin.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "postinstall": "cd arkui_transformer && npm install" + "postinstall": "cd arkui_transformer && npm install && cd ../compile-plugins/api-check-plugin-static && npm install" }, "author": "", "license": "ISC", diff --git a/build_api_check_plugin.py b/build_api_check_plugin.py new file mode 100755 index 0000000000..4f833e5984 --- /dev/null +++ b/build_api_check_plugin.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# 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. + +import argparse +import os +import shutil +import subprocess +import sys +import tarfile + + +def copy_files(source_path, dest_path, is_file=False): + try: + if is_file: + os.makedirs(os.path.dirname(dest_path), exist_ok=True) + shutil.copy(source_path, dest_path) + else: + shutil.copytree(source_path, dest_path, dirs_exist_ok=True, + symlinks=True) + except Exception as err: + raise Exception("Copy files failed. Error: " + str(err)) from err + + +def run_cmd(cmd, execution_path=None): + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=execution_path) + stdout, stderr = proc.communicate(timeout=1000) + if proc.returncode != 0: + raise Exception(stderr.decode()) + + +def build(options): + build_cmd = [options.npm, 'run', 'compile:plugins'] + run_cmd(build_cmd, options.source_path) + + +def copy_output(options): + copy_files(os.path.join(options.source_path, './lib'), + os.path.join(options.output_path, 'api-check-plugin')) + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument('--npm', help='path to a npm exetuable') + parser.add_argument('--source_path', help='path to api_check_plugin source') + parser.add_argument('--output_path', help='path to output') + + options = parser.parse_args() + return options + + +def main(): + options = parse_args() + + build(options) + copy_output(options) + + +if __name__ == '__main__': + sys.exit(main()) \ No newline at end of file -- Gitee