From 09eced7f65814d9c4a4999018eedc39dcc45f744 Mon Sep 17 00:00:00 2001 From: hexu28huawei Date: Mon, 26 May 2025 09:48:20 +0800 Subject: [PATCH] rule validate-decorator-target, struct-no-custom-decorator, struct-no-extends Signed-off-by: hexu28huawei --- .../ui-syntax-plugins/rules/index.ts | 6 + .../rules/struct-no-custom-decorator.ts | 73 ++++++++++ .../rules/struct-no-extends.ts | 62 +++++++++ .../rules/validate-decorator-target.ts | 126 ++++++++++++++++++ 4 files changed, 267 insertions(+) create mode 100644 arkui-plugins/ui-syntax-plugins/rules/struct-no-custom-decorator.ts create mode 100644 arkui-plugins/ui-syntax-plugins/rules/struct-no-extends.ts create mode 100644 arkui-plugins/ui-syntax-plugins/rules/validate-decorator-target.ts diff --git a/arkui-plugins/ui-syntax-plugins/rules/index.ts b/arkui-plugins/ui-syntax-plugins/rules/index.ts index 13b008b62..030d0d180 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/index.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/index.ts @@ -53,6 +53,9 @@ import ObservedV2TraceUsageValidation from './observedV2-trace-usage-validation' import OnceDecoratorCheck from './once-decorator-check'; import OneDecoratorOnFunctionMethod from './one-decorator-on-function-method'; import OldNewDecoratorMixUseCheck from './old-new-decorator-mix-use-check'; +import ValidateDecoratorTarget from './validate-decorator-target'; +import StructNoCustomDecorator from './struct-no-custom-decorator'; +import StructNoExtends from './struct-no-extends'; const rules: UISyntaxRule[] = [ BuildRootNode, @@ -94,6 +97,9 @@ const rules: UISyntaxRule[] = [ OnceDecoratorCheck, OneDecoratorOnFunctionMethod, OldNewDecoratorMixUseCheck, + ValidateDecoratorTarget, + StructNoCustomDecorator, + StructNoExtends, ]; export default rules; diff --git a/arkui-plugins/ui-syntax-plugins/rules/struct-no-custom-decorator.ts b/arkui-plugins/ui-syntax-plugins/rules/struct-no-custom-decorator.ts new file mode 100644 index 000000000..e696a7dcd --- /dev/null +++ b/arkui-plugins/ui-syntax-plugins/rules/struct-no-custom-decorator.ts @@ -0,0 +1,73 @@ +/* + * 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 { PresetDecorators } from '../utils'; +import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; + +const validDecoratorsWithStruct = new Set([ + PresetDecorators.ENTRY, + PresetDecorators.PREVIEW, + PresetDecorators.COMPONENT_V1, + PresetDecorators.COMPONENT_V2, + PresetDecorators.CUSTOM_DIALOG, + PresetDecorators.REUSABLE_V1, +]); + +function checkInvalidDecorators(node: arkts.AstNode, context: UISyntaxRuleContext): void { + if (!arkts.isStructDeclaration(node)) { + return; + } + const invalidDecorator = node.definition.annotations.find( + (annotation) => + annotation.expr && + arkts.isIdentifier(annotation.expr) && + !validDecoratorsWithStruct.has(annotation.expr.name) + ); + if (invalidDecorator) { + reportDecoratorError(context, invalidDecorator, rule.messages.decoratorInvalidWithStruct); + } +} + +function reportDecoratorError(context: UISyntaxRuleContext, Decorator: arkts.AnnotationUsage, message: string): void { + context.report({ + node: Decorator, + message: message, + fix: () => { + const startPosition = arkts.getStartPosition(Decorator); + const endPosition = arkts.getEndPosition(Decorator); + return { + range: [startPosition, endPosition], + code: '', + }; + }, + }); +} + +const rule: UISyntaxRule = { + name: 'struct-no-custom-decorator', + messages: { + decoratorInvalidWithStruct: `Decorators other than '@Entry/@Preview/@Component/@ComponentV2/@CustomDialog/Reusable' not allowed on struct.`, + }, + setup(context) { + return { + parsed: (node): void => { + checkInvalidDecorators(node, context); + }, + }; + }, +}; + +export default rule; diff --git a/arkui-plugins/ui-syntax-plugins/rules/struct-no-extends.ts b/arkui-plugins/ui-syntax-plugins/rules/struct-no-extends.ts new file mode 100644 index 000000000..ab03170b4 --- /dev/null +++ b/arkui-plugins/ui-syntax-plugins/rules/struct-no-extends.ts @@ -0,0 +1,62 @@ +/* + * 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 { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; + +function checkInvalidDecorators(node: arkts.AstNode, context: UISyntaxRuleContext): void { + if (!arkts.isStructDeclaration(node)) { + return; + } + if (node.definition.implements && node.definition.implements.length > 0) { + reportError(context, node, rule.messages.structNoExtend); + } + // arkts.AstNode error when get superClass on struct, use json to get superClass + const originJson = JSON.parse(node.dumpJson()); + if (originJson.definition.superClass) { + reportError(context, node, rule.messages.structNoExtend); + } +} + +function reportError(context: UISyntaxRuleContext, node: arkts.AstNode, message: string): void { + context.report({ + node: node, + message: message, + fix: () => { + const startPosition = arkts.getStartPosition(node); + const endPosition = arkts.getEndPosition(node); + return { + range: [startPosition, endPosition], + code: '', + }; + }, + }); +} + +const rule: UISyntaxRule = { + name: 'struct-no-extends', + messages: { + structNoExtend: `The struct component is not allowed to extends other class or implements other interface.`, + }, + setup(context) { + return { + parsed: (node): void => { + checkInvalidDecorators(node, context); + }, + }; + }, +}; + +export default rule; diff --git a/arkui-plugins/ui-syntax-plugins/rules/validate-decorator-target.ts b/arkui-plugins/ui-syntax-plugins/rules/validate-decorator-target.ts new file mode 100644 index 000000000..8eb007ce4 --- /dev/null +++ b/arkui-plugins/ui-syntax-plugins/rules/validate-decorator-target.ts @@ -0,0 +1,126 @@ +/* + * 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 { PresetDecorators } from '../utils'; +import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; + +const invalidDecoratorsWithProp = new Set([ + PresetDecorators.REUSABLE_V1, + PresetDecorators.COMPONENT_V2, +]); + +const invalidDecoratorsWithClass = new Set([ + PresetDecorators.REUSABLE_V1, + PresetDecorators.COMPONENT_V2, +]); + +const invalidDecoratorsWithMethod = new Set([ + PresetDecorators.REUSABLE_V1, + PresetDecorators.COMPONENT_V2, + PresetDecorators.STATE, +]); + +// check point 1, check invalid decorators when target is class. +function checkInvalidDecoratorOnClass(context: UISyntaxRuleContext, node: arkts.ClassDeclaration): void { + const invalidDecorator = node.definition?.annotations?.find( + (annotation) => + annotation.expr && + arkts.isIdentifier(annotation.expr) && + invalidDecoratorsWithClass.has(annotation.expr.name) + ); + if (invalidDecorator) { + const decoratorName = invalidDecorator.expr ? invalidDecorator.expr.dumpSrc() : ''; + reportDecoratorError(context, invalidDecorator, rule.messages.decoratorInvalidWithTarget, decoratorName, 'class'); + } +} + +// Helper function to find invalid decorator in a MethodDefinition or ClassProperty. +const findInvalidDecorator = (node: arkts.MethodDefinition | arkts.ClassProperty, invalidDecorators: Set): arkts.AnnotationUsage | undefined => { + const annotations = 'scriptFunction' in node ? node.scriptFunction.annotations : node.annotations; + return annotations?.find( + (annotation) => + annotation.expr && invalidDecorators.has(annotation.expr.dumpSrc()) + ); +}; + +// check point 2, check invalid decorators when target is method. +function checkInvalidDecoratorOnMethod(context: UISyntaxRuleContext, node: arkts.MethodDefinition): void { + const propDecorator = findInvalidDecorator(node, invalidDecoratorsWithMethod); + if (propDecorator) { + const decoratorName = propDecorator.expr ? propDecorator.expr.dumpSrc() : ''; + reportDecoratorError(context, propDecorator, rule.messages.decoratorInvalidWithTarget, decoratorName, 'method'); + } +}; + +// check point 3, check invalid decorators when target is property. +function checkInvalidDecoratorOnProperty(context: UISyntaxRuleContext, node: arkts.ClassProperty, currentNode: arkts.AstNode) + : void { + const propDecorator = findInvalidDecorator(node, invalidDecoratorsWithProp); + if (propDecorator) { + const decoratorName = propDecorator.expr ? propDecorator.expr.dumpSrc() : ''; + reportDecoratorError(context, propDecorator, rule.messages.decoratorInvalidWithTarget, decoratorName, 'property'); + } +}; + +function checkInvalidDecorators(node: arkts.AstNode, context: UISyntaxRuleContext): void { + const loadedContainerComponents = context.containerComponents; + if (arkts.isStructDeclaration(node)) { + return; + } + if (arkts.isClassDeclaration(node)) { + checkInvalidDecoratorOnClass(context, node); + } + if (arkts.isMethodDefinition(node)) { + checkInvalidDecoratorOnMethod(context, node); + } + let currentNode = node; + if (arkts.isClassProperty(node)) { + checkInvalidDecoratorOnProperty(context, node, currentNode); + } +} + +function reportDecoratorError(context: UISyntaxRuleContext, Decorator: arkts.AnnotationUsage, message: string, +decoratorName: string, targetName: string): void { + context.report({ + node: Decorator, + message: message, + data: {decoratorName, targetName}, + fix: () => { + const startPosition = arkts.getStartPosition(Decorator); + const endPosition = arkts.getEndPosition(Decorator); + return { + range: [startPosition, endPosition], + code: '', + }; + }, + }); +} + +const rule: UISyntaxRule = { + name: 'validate-decorator-target', + messages: { + decoratorInvalidWithTarget: `The '@{{decoratorName}}' can not decorate the {{targetName}}.`, + }, + setup(context) { + return { + parsed: (node): void => { + checkInvalidDecorators(node, context); + }, + }; + }, +}; + +export default rule; -- Gitee