diff --git a/arkui-plugins/ui-syntax-plugins/rules/index.ts b/arkui-plugins/ui-syntax-plugins/rules/index.ts index 0757b96b19f97453611b811e6640958fbbff0366..6fdab381261947aa33f879e12306f57cf8f2acfb 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/index.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/index.ts @@ -41,6 +41,7 @@ import NoDuplicatePreviewRule from './no-duplicate-preview'; import NoPropLinkObjectLinkInEntryRule from './no-prop-link-objectlink-in-entry'; import NoSameAsBuiltInAttributeRule from './no-same-as-built-in-attribute'; import ReuseAttributeCheckRule from './reuse-attribute-check'; +import StaticParamRequireRule from './static-param-require'; import StructMissingDecoratorRule from './struct-missing-decorator'; import StructPropertyDecoratorRule from './struct-property-decorator'; import TrackDecoratorCheckRule from './track-decorator-check'; @@ -94,6 +95,7 @@ const rules: Array = [ [NoPropLinkObjectLinkInEntryRule, 'warn'], [NoSameAsBuiltInAttributeRule, 'error'], [ReuseAttributeCheckRule, 'error'], + [StaticParamRequireRule, 'warn'], [StructMissingDecoratorRule, 'error'], [StructPropertyDecoratorRule, 'error'], [TrackDecoratorCheckRule, 'error'], @@ -117,7 +119,7 @@ const rules: Array = [ [WatchDecoratorFunctionRule, 'error'], [WatchDecoratorRegularRule, 'error'], [OldNewDecoratorMixUseCheckRule, 'error'], - [RequireDecoratorRegularRule, 'error'], + [RequireDecoratorRegularRule, 'warn'], [ReusableComponentInV2CheckRule, 'warn'], [SpecificComponentChildrenRule, 'error'], ]; diff --git a/arkui-plugins/ui-syntax-plugins/rules/static-param-require.ts b/arkui-plugins/ui-syntax-plugins/rules/static-param-require.ts new file mode 100644 index 0000000000000000000000000000000000000000..8f2fdb8e2653c2e9e6376394afd5b1e5d863dba7 --- /dev/null +++ b/arkui-plugins/ui-syntax-plugins/rules/static-param-require.ts @@ -0,0 +1,110 @@ +/* + * 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 { getClassPropertyName, hasAnnotation, PresetDecorators } from '../utils'; +import { AbstractUISyntaxRule } from './ui-syntax-rule'; + +class StaticParamRequireRule extends AbstractUISyntaxRule { + private staticPropertyMap: Map = new Map(); + + public setup(): Record { + return { + cannotInitializePrivateVariables: `Static property '{{propertyName}}' can not be initialized through the component constructor.`, + }; + } + + public beforeTransform(): void { + this.staticPropertyMap = new Map(); + } + + public parsed(node: arkts.StructDeclaration): void { + // Check if the current node is the root node + if (arkts.nodeType(node) === arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { + node.getChildren().forEach((member) => { + if (!arkts.isStructDeclaration(member) || !member.definition.ident || !member.definition.ident.name) { + return; + } + const hasComponentV1 = hasAnnotation(member.definition.annotations, PresetDecorators.COMPONENT_V1); + const hasComponentV2 = hasAnnotation(member.definition.annotations, PresetDecorators.COMPONENT_V2); + const structName: string = member.definition.ident.name; + member.definition.body.forEach((item) => { + this.addStaticProperty(item, structName, hasComponentV1, hasComponentV2); + }); + }); + } + if (!arkts.isCallExpression(node) || !arkts.isIdentifier(node.expression)) { + return; + } + const componentName = node.expression.name; + // If the initialization is for a component with private properties + if (!this.staticPropertyMap.has(componentName)) { + return; + } + node.arguments.forEach((member) => { + member.getChildren().forEach((property) => { + if (!arkts.isProperty(property) || !property.key || !arkts.isIdentifier(property.key)) { + return; + } + const propertyName: string = property.key.name; + if (this.staticPropertyMap.get(componentName)!.includes(propertyName)) { + this.report({ + node: property, + message: this.messages.cannotInitializePrivateVariables, + data: { + propertyName: propertyName, + }, + }); + } + }); + }); + } + + private addStaticProperty( + item: arkts.AstNode, + structName: string, + hasComponentV1: boolean, + hasComponentV2: boolean + ): void { + if (!arkts.isClassProperty(item) || !item.isStatic) { + return; + } + const propertyName = getClassPropertyName(item); + if (!propertyName) { + return; + } + // Static properties with decorators in componentV2 need to be checked + if (hasComponentV2 && item.annotations.length > 0) { + this.addElement(structName, propertyName); + } + // Static properties in componentV1 need to be verified + if (hasComponentV1 && !hasComponentV2) { + this.addElement(structName, propertyName); + } + } + + private addElement(structName: string, propertyName: string): void { + // Check if structName already exists in privateMap + if (this.staticPropertyMap.has(structName)) { + // If it exists, retrieve the current string[] and append the new content + this.staticPropertyMap.get(structName)!.push(propertyName); + } else { + // If it doesn't exist, create a new string[] and add the content + this.staticPropertyMap.set(structName, [propertyName]); + } + } +} + +export default StaticParamRequireRule; \ No newline at end of file diff --git a/arkui-plugins/ui-syntax-plugins/rules/struct-property-decorator.ts b/arkui-plugins/ui-syntax-plugins/rules/struct-property-decorator.ts index caac4a335fb730ccf980dfb3f0f1651ab226834c..fd6f98af874cc05d974dafbd22dc0acab2a9b985 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/struct-property-decorator.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/struct-property-decorator.ts @@ -14,7 +14,7 @@ */ import * as arkts from '@koalaui/libarkts'; -import { getClassPropertyAnnotationNames, PresetDecorators } from '../utils'; +import { getClassPropertyAnnotationNames, hasAnnotation, PresetDecorators } from '../utils'; import { AbstractUISyntaxRule } from './ui-syntax-rule'; const decorators: string[] = [ @@ -41,7 +41,8 @@ class StructPropertyDecoratorRule extends AbstractUISyntaxRule { if (!arkts.isStructDeclaration(node)) { return; } - this.checkInvalidStaticPropertyDecorations(node); + const hasComponentV1 = hasAnnotation(node.definition.annotations, PresetDecorators.COMPONENT_V1); + this.checkInvalidStaticPropertyDecorations(node, hasComponentV1); } private hasPropertyDecorator( @@ -53,18 +54,20 @@ class StructPropertyDecoratorRule extends AbstractUISyntaxRule { ); } - private checkInvalidStaticPropertyDecorations(node: arkts.StructDeclaration,): void { + private checkInvalidStaticPropertyDecorations(node: arkts.StructDeclaration, hasComponentV1: boolean): void { node.definition.body.forEach((member) => { - // Errors are reported when the node type is ClassProperty, - if (arkts.isClassProperty(member)) { - const propertyNameNode = member.key; - if ((member.isStatic && this.hasPropertyDecorator(member)) && propertyNameNode) { - this.report({ - node: propertyNameNode, - message: this.messages.invalidStaticUsage - }); - } + // Errors are reported when the node type is static ClassProperty, + if (!arkts.isClassProperty(member) || !member.key) { + return; } + if (!hasComponentV1 || !member.isStatic || !this.hasPropertyDecorator(member)) { + return; + } + const propertyNameNode = member.key; + this.report({ + node: propertyNameNode, + message: this.messages.invalidStaticUsage + }); }); } }