diff --git a/arkui-plugins/ui-syntax-plugins/rules/index.ts b/arkui-plugins/ui-syntax-plugins/rules/index.ts index 13b008b627bb821cbb05603a977ad1238898fc87..d3d603242c9c7851ba1993b010d39e6b89493264 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/index.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/index.ts @@ -53,6 +53,14 @@ 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 ComputedDecoratorCheck from './computed-decorator-check'; +import ReusableV2DecoratorCheck from './reusableV2-decorator-check'; +import RequireDecoratorRegular from './require-decorator-regular'; +import ReusableComponentInV2Check from './reusable-component-in-V2-check'; +import VariableInitializationViaComponentConstructor from './variable-initialization-via-component-constructor'; +import ComponentComponentV2InitCheck from './component-componentV2-init-check'; +import SpecificComponentChildren from './specific-component-children'; +import StructNoExtends from './struct-no-extends'; const rules: UISyntaxRule[] = [ BuildRootNode, @@ -94,6 +102,14 @@ const rules: UISyntaxRule[] = [ OnceDecoratorCheck, OneDecoratorOnFunctionMethod, OldNewDecoratorMixUseCheck, + ComputedDecoratorCheck, + ReusableV2DecoratorCheck, + RequireDecoratorRegular, + ReusableComponentInV2Check, + VariableInitializationViaComponentConstructor, + ComponentComponentV2InitCheck, + SpecificComponentChildren, + StructNoExtends, ]; export default rules; diff --git a/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-decorators.ts b/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-decorators.ts index ff42b2180727514624a4dfc03e5d2b05558c27b5..42ded49de87bc6ef623923b66e0fa724d2c1aa93 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-decorators.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-decorators.ts @@ -17,18 +17,16 @@ import * as arkts from '@koalaui/libarkts'; import { getAnnotationName, PresetDecorators } from '../utils'; import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; -// Decorators that cannot be repeated -const validDecorators = [ +//Unsupported decorators +const unsupportedDecorators = [ PresetDecorators.ENTRY, - PresetDecorators.COMPONENT_V1, - PresetDecorators.COMPONENT_V2, - PresetDecorators.REUSABLE_V1, PresetDecorators.PREVIEW, - PresetDecorators.REUSABLE_V2, - PresetDecorators.CUSTOM_DIALOG, ]; -function checkForDuplicateDecorators(context: UISyntaxRuleContext, node: arkts.StructDeclaration): void { +function checkForDuplicateStructDecorators( + context: UISyntaxRuleContext, + node: arkts.StructDeclaration +): void { // Initialize a map to record decorators and their occurrences const decoratorCounts: Map = new Map(); if (!node.definition || !node.definition.annotations) { @@ -37,10 +35,9 @@ function checkForDuplicateDecorators(context: UISyntaxRuleContext, node: arkts.S // Record all decorators and their counts node.definition.annotations.forEach((annotation) => { const decoratorName = getAnnotationName(annotation); - if (!validDecorators.includes(decoratorName)) { + if (unsupportedDecorators.includes(decoratorName)) { return; } - if (decoratorCounts.has(decoratorName)) { const decoratorInfo = decoratorCounts.get(decoratorName)!; decoratorInfo.count += 1; @@ -58,24 +55,142 @@ function checkForDuplicateDecorators(context: UISyntaxRuleContext, node: arkts.S // Report errors for all occurrences except the last one for (let i = 0; i < annotations.length - 1; i++) { const prevAnnotation = annotations[i]; - reportDuplicateDecorator(context, prevAnnotation); + reportStructDuplicateDecorator(context, prevAnnotation, decoratorName); } // For the last occurrence, report an error but do not provide a fix const lastAnnotation = annotations[annotations.length - 1]; context.report({ node: lastAnnotation, - message: rule.messages.duplicateDecorator, + message: rule.messages.duplicateStructDecorators, + data: { decoratorName }, + }); + }); +} + +function reportStructDuplicateDecorator(context: UISyntaxRuleContext, annotation: arkts.AnnotationUsage, + decoratorName: string): void { + context.report({ + node: annotation, + message: rule.messages.duplicateStructDecorators, + data: { decoratorName }, + fix: () => { + const startPosition = annotation.startPosition; + const endPosition = annotation.endPosition; + return { + range: [startPosition, endPosition], + code: '', + }; + }, + }); +} + +function checkForDuplicateFunctionDecorators( + context: UISyntaxRuleContext, + functionNode: arkts.FunctionDeclaration +): void { + const annotations = functionNode.annotations; + if (!annotations || annotations.length <= 1) { + return; + } + const decoratorCounts: Map = new Map(); + for (const annotation of annotations) { + const decoratorName = getAnnotationName(annotation); + if (unsupportedDecorators.includes(decoratorName)) { + continue; + } + if (decoratorCounts.has(decoratorName)) { + const info = decoratorCounts.get(decoratorName)!; + info.count += 1; + info.annotations.push(annotation); + } else { + decoratorCounts.set(decoratorName, { count: 1, annotations: [annotation] }); + } + } + decoratorCounts.forEach(({ count, annotations }, decoratorName) => { + if (count <= 1) { + return; + } + + // Keep the last one and delete the rest + for (let i = 0; i < annotations.length - 1; i++) { + reportFunctionDuplicateDecorator(context, annotations[i], decoratorName); + } + + // The last one doesn't provide a fix + const last = annotations[annotations.length - 1]; + context.report({ + node: last, + message: rule.messages.duplicateFunctionDecorators, + data: { decoratorName }, + }); + }); +} + +function reportFunctionDuplicateDecorator(context: UISyntaxRuleContext, annotation: arkts.AnnotationUsage, + decoratorName: string): void { + context.report({ + node: annotation, + message: rule.messages.duplicateFunctionDecorators, + data: { decoratorName }, + fix: () => { + const startPosition = annotation.startPosition; + const endPosition = annotation.endPosition; + return { + range: [startPosition, endPosition], + code: '', + }; + }, + }); +} + +function checkForDuplicateMethodDecorators( + context: UISyntaxRuleContext, + methodNode: arkts.MethodDefinition +): void { + const annotations = methodNode.scriptFunction.annotations; + if (!annotations || annotations.length <= 1) { + return; + } + const decoratorCounts: Map = new Map(); + for (const annotation of annotations) { + const decoratorName = getAnnotationName(annotation); + if (unsupportedDecorators.includes(decoratorName)) { + continue; + } + + if (decoratorCounts.has(decoratorName)) { + const info = decoratorCounts.get(decoratorName)!; + info.count += 1; + info.annotations.push(annotation); + } else { + decoratorCounts.set(decoratorName, { count: 1, annotations: [annotation] }); + } + } + decoratorCounts.forEach(({ count, annotations }, decoratorName) => { + if (count <= 1) { + return; + } + for (let i = 0; i < annotations.length - 1; i++) { + reportMethodDuplicateDecorator(context, annotations[i], decoratorName); + } + const last = annotations[annotations.length - 1]; + context.report({ + node: last, + message: rule.messages.duplicateMethodDecorators, + data: { decoratorName }, }); }); } -function reportDuplicateDecorator(context: UISyntaxRuleContext, annotation: arkts.AnnotationUsage): void { +function reportMethodDuplicateDecorator(context: UISyntaxRuleContext, annotation: arkts.AnnotationUsage, + decoratorName: string): void { context.report({ node: annotation, - message: rule.messages.duplicateDecorator, + message: rule.messages.duplicateMethodDecorators, + data: { decoratorName }, fix: () => { - const startPosition = arkts.getStartPosition(annotation); - const endPosition = arkts.getEndPosition(annotation); + const startPosition = annotation.startPosition; + const endPosition = annotation.endPosition; return { range: [startPosition, endPosition], code: '', @@ -87,15 +202,20 @@ function reportDuplicateDecorator(context: UISyntaxRuleContext, annotation: arkt const rule: UISyntaxRule = { name: 'no-duplicate-decorators', messages: { - duplicateDecorator: `Duplicate decorators for struct are not allowed.`, + duplicateFunctionDecorators: `Duplicate '{{decoratorName}}' decorators for function are not allowed.`, + duplicateMethodDecorators: `Duplicate '{{decoratorName}}' decorators for method are not allowed.`, + duplicateStructDecorators: `Duplicate '{{decoratorName}}' decorators for struct are not allowed.`, }, setup(context) { return { parsed: (node): void => { - if (!arkts.isStructDeclaration(node)) { - return; + if (arkts.isStructDeclaration(node)) { + checkForDuplicateStructDecorators(context, node); + } else if (arkts.isFunctionDeclaration(node)) { + checkForDuplicateFunctionDecorators(context, node); + } else if (arkts.isMethodDefinition(node)) { + checkForDuplicateMethodDecorators(context, node); } - checkForDuplicateDecorators(context, node); }, }; }, diff --git a/arkui-plugins/ui-syntax-plugins/rules/require-decorator-regular.ts b/arkui-plugins/ui-syntax-plugins/rules/require-decorator-regular.ts new file mode 100644 index 0000000000000000000000000000000000000000..55253c13bf5a8f372feb4c7d5dc42c1c444635e1 --- /dev/null +++ b/arkui-plugins/ui-syntax-plugins/rules/require-decorator-regular.ts @@ -0,0 +1,108 @@ +/* + * 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 { getClassPropertyAnnotationNames, PresetDecorators, isPrivateClassProperty, getClassPropertyName } from '../utils'; +import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; + +const allowedDecorators = [ + PresetDecorators.STATE, + PresetDecorators.PROVIDE, + PresetDecorators.PROP, + PresetDecorators.PARAM, + PresetDecorators.BUILDER_PARAM +]; + +function getRequireDecorator( + member: arkts.ClassProperty +): arkts.AnnotationUsage | undefined { + return member.annotations?.find(annotation => + annotation.expr && arkts.isIdentifier(annotation.expr) && + annotation.expr.name === PresetDecorators.REQUIRE + ); +} + +function findConflictingDecorator( + member: arkts.ClassProperty, + allowedDecorators: string[] +): arkts.AnnotationUsage | undefined { + return member.annotations?.find(annotation => + annotation.expr && arkts.isIdentifier(annotation.expr) && + annotation.expr.name !== PresetDecorators.REQUIRE && + !allowedDecorators.includes(annotation.expr.name) + ); +} + +function handlePrivateWithRequire( + member: arkts.ClassProperty, + context: UISyntaxRuleContext, +): void { + const requireDecorator = getRequireDecorator(member); + if (requireDecorator) { + context.report({ + node: requireDecorator, + message: rule.messages.invalidPrivateWithRequire, + data: { + propertyName: getClassPropertyName(member), + decoratorName: PresetDecorators.REQUIRE, + }, + }); + } +} + +function checkRequireDecorator(node: arkts.StructDeclaration, context: UISyntaxRuleContext): void { + node.definition.body.forEach(member => { + if (!arkts.isClassProperty(member)) { + return; + } + // Get the list of decorators applied to the class property + const propertyDecorators = getClassPropertyAnnotationNames(member); + if (!propertyDecorators.includes(PresetDecorators.REQUIRE)) { + return; + } + if (isPrivateClassProperty(member)) { + handlePrivateWithRequire(member, context); + } + // Filter the decorators to find any that are not allowed with @Require + const otherDecorator = findConflictingDecorator(member, allowedDecorators); + const requireDecorator = getRequireDecorator(member); + if (otherDecorator && requireDecorator) { + context.report({ + node: requireDecorator, + message: rule.messages.invalidUsage, + }); + } + }); +} + +const rule: UISyntaxRule = { + name: 'require-decorator-regular', + messages: { + invalidUsage: `The @Require decorator can only be used on a regular variable or a variable decorated by @State, @Provide, @Prop, @Param, or @BuilderParam.`, + invalidPrivateWithRequire: `Property '{{propertyName}}' can not be decorated with both {{decoratorName}} and private.`, + }, + setup(context) { + return { + parsed: (node): void => { + if (!arkts.isStructDeclaration(node)) { + return; + } + checkRequireDecorator(node, context); + }, + }; + }, +}; + +export default rule; diff --git a/arkui-plugins/ui-syntax-plugins/rules/struct-variable-initialization.ts b/arkui-plugins/ui-syntax-plugins/rules/struct-variable-initialization.ts index 63c06587f6aff660befe92e7c0ace646ec249363..67617caf5b163dec057ad3232c83bd196b15a86e 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/struct-variable-initialization.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/struct-variable-initialization.ts @@ -15,46 +15,88 @@ import * as arkts from '@koalaui/libarkts'; import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; +import { PresetDecorators } from '../utils'; // A list of decorators that needs to be initialized locally -const mustInitializeDecorators = ['State', 'StorageLink', 'StorageProp', 'LocalStorageLink', 'LocalStorageProp', - 'Provide']; +const mustInitializeDecorators = [ + PresetDecorators.STATE, + PresetDecorators.STORAGE_LINK, + PresetDecorators.STORAGE_PROP, + PresetDecorators.LOCAL_STORAGE_LINK, + PresetDecorators.LOCAL_STORAGE_PROP, + PresetDecorators.PROVIDE, +]; // Disables a list of decorators that are initialized locally -const prohibitInitializeDecorators = ['Link', 'Consume', 'ObjectLink']; +const prohibitInitializeDecorators = [ + PresetDecorators.LINK, + PresetDecorators.CONSUME, + PresetDecorators.OBJECT_LINK, +]; + +// When used with @require, non-initialization is allowed +const requireCanReleaseMandatoryDecorators = [ + PresetDecorators.STATE, + PresetDecorators.PROVIDE, +]; function reportVariablesInitializationError(context: UISyntaxRuleContext, node: arkts.ClassProperty): void { // Check whether the value field exists and whether it has been initialized const valueExists = !!node.value; - // Iterate through each decorator and verify the initialization rules - node.annotations.forEach(annotation => { - const decoratorName = annotation.expr?.dumpSrc() ?? ''; - if (mustInitializeDecorators.includes(decoratorName)) { - // If it is a decorator that needs to be initialized - if (!valueExists) { - // If there is no initialization expression and there is no @Require, an error is reported - context.report({ - node: annotation, - message: rule.messages.mustBeInitializedLocally, - }); - } - } else if (prohibitInitializeDecorators.includes(decoratorName)) { - // If it is a decorator that prohibits initialization - if (valueExists) { - // If an initialization expression exists, an error is reported - context.report({ - node: annotation, - message: rule.messages.prohibitLocalInitialization, - }); - } + // Check for the presence of require decorator + const hasRequire = node.annotations.some(annotation => { + annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name === PresetDecorators.REQUIRE; + }); + node.annotations.some(annotation => { + if (annotation.expr && arkts.isIdentifier(annotation.expr) && + mustInitializeDecorators.includes(annotation.expr.name)) { + const decoratorName = annotation.expr.name; + reportInitializeError(context, decoratorName, valueExists, annotation, hasRequire); + return true; + } else if (annotation.expr && arkts.isIdentifier(annotation.expr) && + prohibitInitializeDecorators.includes(annotation.expr.name)) { + const decoratorName = annotation.expr.name; + reportProHibitInitializeError(context, decoratorName, valueExists, annotation); + return true; } + return false; }); } +function reportInitializeError(context: UISyntaxRuleContext, decoratorName: string, valueExists: boolean, + annotation: arkts.AnnotationUsage, hasRequire: boolean): void { + // Used with @require allows non-initialization + if (hasRequire && requireCanReleaseMandatoryDecorators.includes(decoratorName)) { + return; + } + // If it is a decorator that needs to be initialized + if (!valueExists) { + // If there is no initialization expression and there is no @Require, an error is reported + context.report({ + node: annotation, + message: rule.messages.mustBeInitializedLocally, + data: { decoratorName }, + }); + } +} + +function reportProHibitInitializeError(context: UISyntaxRuleContext, decoratorName: string, valueExists: boolean, + annotation: arkts.AnnotationUsage): void { + // If it is a decorator that prohibits initialization + if (valueExists) { + // If an initialization expression exists, an error is reported + context.report({ + node: annotation, + message: rule.messages.prohibitLocalInitialization, + data: { decoratorName }, + }); + } +} + const rule: UISyntaxRule = { name: 'struct-variable-initialization', messages: { - mustBeInitializedLocally: `Variables decorated by '@State', '@StorageLink', '@StorageProp', '@LocalStorageLink', '@LocalStorageProp' and '@Provide' must be initialized locally.`, - prohibitLocalInitialization: `Variables decorated by '@Link', '@Consume', and '@ObjectLink' cannot be initialized locally.` + mustBeInitializedLocally: `The '{{decoratorName}}' property must be specified a default value.`, + prohibitLocalInitialization: `The '{{decoratorName}}' property cannot be specified a default value.` }, setup(context) { return { diff --git a/arkui-plugins/ui-syntax-plugins/rules/type-decorator-check.ts b/arkui-plugins/ui-syntax-plugins/rules/type-decorator-check.ts index 79c60be2e1557b43ad9d29e255dc5fb61f60d7fc..2f6ff790010244c55525a6edc5e8ec9c5f5b7001 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/type-decorator-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/type-decorator-check.ts @@ -20,23 +20,23 @@ import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; function findTypeDecorator( annotations: readonly arkts.AnnotationUsage[] ): arkts.AnnotationUsage | undefined { - let hasTypeDecorator = annotations?.find(annotation => - annotation.expr && - annotation.expr.dumpSrc() === PresetDecorators.TYPE + let typeDecorator = annotations?.find(annotation => + annotation.expr && arkts.isIdentifier(annotation.expr) && + annotation.expr.name === PresetDecorators.TYPE ); - return hasTypeDecorator; + return typeDecorator; } -function hasDecoratorType( +function getTypeDecorator( node: arkts.ClassDeclaration, ): arkts.AnnotationUsage | undefined { - let hasTypeDecorator: arkts.AnnotationUsage | undefined; + let typeDecorator: arkts.AnnotationUsage | undefined; node.definition?.body.forEach(member => { if (arkts.isClassProperty(member) && member.annotations) { - hasTypeDecorator = findTypeDecorator(member.annotations); + typeDecorator = findTypeDecorator(member.annotations); } }); - return hasTypeDecorator; + return typeDecorator; } // rule1: @Type can only be used for class @@ -44,28 +44,28 @@ function checkTypeInStruct( node: arkts.StructDeclaration, context: UISyntaxRuleContext, ): void { - let hasTypeDecorator: arkts.AnnotationUsage | undefined; - node.definition?.body.forEach(member => { + let typeDecorator: arkts.AnnotationUsage | undefined; + node.definition.body.forEach(member => { if (arkts.isClassProperty(member) && member.annotations) { - hasTypeDecorator = findTypeDecorator(member.annotations); - reportInvalidTypeUsageInStruct(hasTypeDecorator, context); + typeDecorator = findTypeDecorator(member.annotations); + reportInvalidTypeUsageInStruct(typeDecorator, context); } }); } function reportInvalidTypeUsageInStruct( - hasTypeDecorator: arkts.AnnotationUsage | undefined, + typeDecorator: arkts.AnnotationUsage | undefined, context: UISyntaxRuleContext ): void { - if (!hasTypeDecorator) { + if (!typeDecorator) { return; } context.report({ - node: hasTypeDecorator, + node: typeDecorator, message: rule.messages.invalidType, - fix: (hasTypeDecorator) => { - const startPosition = arkts.getStartPosition(hasTypeDecorator); - const endPosition = arkts.getEndPosition(hasTypeDecorator); + fix: (typeDecorator) => { + const startPosition = typeDecorator.startPosition; + const endPosition = typeDecorator.endPosition; return { range: [startPosition, endPosition], code: '' @@ -79,29 +79,29 @@ function checkObservedAndTypeConflict( node: arkts.ClassDeclaration, context: UISyntaxRuleContext ): void { - let hasTypeDecorator: arkts.AnnotationUsage | undefined; + let typeDecorator: arkts.AnnotationUsage | undefined; node.definition?.annotations.forEach(member => { const annotation = getAnnotationName(member); - hasTypeDecorator = hasDecoratorType(node); - if (annotation === PresetDecorators.OBSERVED_V1) { - reportObservedAndTypeDecoratorConflict(hasTypeDecorator, context); + typeDecorator = getTypeDecorator(node); + if (annotation !== PresetDecorators.OBSERVED_V2) { + reportObservedAndTypeDecoratorConflict(typeDecorator, context); } }); } function reportObservedAndTypeDecoratorConflict( - hasTypeDecorator: arkts.AnnotationUsage | undefined, + typeDecorator: arkts.AnnotationUsage | undefined, context: UISyntaxRuleContext ): void { - if (!hasTypeDecorator) { + if (!typeDecorator) { return; } context.report({ - node: hasTypeDecorator, + node: typeDecorator, message: rule.messages.invalidDecoratorWith, fix: () => { - const startPosition = arkts.getStartPosition(hasTypeDecorator); - const endPosition = arkts.getEndPosition(hasTypeDecorator); + const startPosition = typeDecorator.startPosition; + const endPosition = typeDecorator.endPosition; return { range: [startPosition, endPosition], code: '' @@ -115,23 +115,23 @@ function validateScriptFunctionForTypeDecorator( node: arkts.ScriptFunction, context: UISyntaxRuleContext ): void { - const hasTypeDecorator = findTypeDecorator(node.annotations); - reportInvalidTypeDecorator(hasTypeDecorator, context); + const typeDecorator = findTypeDecorator(node.annotations); + reportInvalidTypeDecorator(typeDecorator, context); } function reportInvalidTypeDecorator( - hasTypeDecorator: arkts.AnnotationUsage | undefined, + typeDecorator: arkts.AnnotationUsage | undefined, context: UISyntaxRuleContext ): void { - if (!hasTypeDecorator) { + if (!typeDecorator) { return; } context.report({ - node: hasTypeDecorator, + node: typeDecorator, message: rule.messages.invalidTypeMember, - fix: (hasTypeDecorator) => { - const startPosition = arkts.getStartPosition(hasTypeDecorator); - const endPosition = arkts.getEndPosition(hasTypeDecorator); + fix: (typeDecorator) => { + const startPosition = typeDecorator.startPosition; + const endPosition = typeDecorator.endPosition; return { range: [startPosition, endPosition], code: '' @@ -143,9 +143,9 @@ function reportInvalidTypeDecorator( const rule: UISyntaxRule = { name: 'type-decorator-check', messages: { - invalidType: `The @Type decorator can only be used in 'class'.`, + invalidType: `The @Type decorator is not allowed here. It must be used in a class.`, invalidDecoratorWith: `The @Type decorator can not be used within a 'class' decorated with @Observed.`, - invalidTypeMember: `The @Type can decorate only member variables in a 'class'.` + invalidTypeMember: `The @Type decorator is not allowed here. It can only decorate properties of a class.` }, setup(context) { return { diff --git a/arkui-plugins/ui-syntax-plugins/rules/watch-decorator-regular.ts b/arkui-plugins/ui-syntax-plugins/rules/watch-decorator-regular.ts index 22eb47c74686b3882d25c52357c8e1079e7685e1..fa88f5b997bfb34576f0d6f3cf68a2a93b921cf7 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/watch-decorator-regular.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/watch-decorator-regular.ts @@ -14,7 +14,7 @@ */ import * as arkts from '@koalaui/libarkts'; -import { getClassPropertyAnnotationNames, PresetDecorators } from '../utils'; +import { getClassPropertyAnnotationNames, getClassPropertyName, PresetDecorators } from '../utils'; import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; const PROPERTY_ANNOTATION_NUM: number = 2; @@ -25,16 +25,20 @@ function validateWatchDecorator(node: arkts.StructDeclaration, context: UISyntax return; } const hasWatchDecorator = member.annotations?.find(annotation => - annotation.expr && - annotation.expr.dumpSrc() === PresetDecorators.WATCH + annotation.expr && arkts.isIdentifier(annotation.expr) && + annotation.expr.name === PresetDecorators.WATCH ); const propertyAnnotationNames = getClassPropertyAnnotationNames(member); + const propertyName = getClassPropertyName(member); // Determine if there are any decorations other than @watch decorations // rule1: The @Watch decorator must be used with other decorators if (hasWatchDecorator && propertyAnnotationNames.length < PROPERTY_ANNOTATION_NUM) { context.report({ node: hasWatchDecorator, message: rule.messages.invalidWatch, + data: { + propertyName: propertyName + } }); } }); @@ -43,7 +47,7 @@ function validateWatchDecorator(node: arkts.StructDeclaration, context: UISyntax const rule: UISyntaxRule = { name: 'watch-decorator-regular', messages: { - invalidWatch: `The @Watch decorator must be used with other decorators.`, + invalidWatch: `Regular variable '{{propertyName}}' can not be decorated with '@Watch'.`, }, setup(context) { return { diff --git a/arkui-plugins/ui-syntax-plugins/rules/wrap-builder-check.ts b/arkui-plugins/ui-syntax-plugins/rules/wrap-builder-check.ts index 5f87ef3fc7ac23eeae4e586a5972049d2a04ce29..c956e903b2e67ae1bed7aced4d93224abe2f276d 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/wrap-builder-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/wrap-builder-check.ts @@ -14,10 +14,9 @@ */ import * as arkts from '@koalaui/libarkts'; -import { getIdentifierName, getAnnotationName, PresetDecorators } from '../utils'; +import { getIdentifierName, getAnnotationName, PresetDecorators, WRAP_BUILDER } from '../utils'; import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; -const WRAPBUILDER_NAME: string = 'wrapBuilder'; // Collect all the function names that are decorated with @Builder function collectBuilderFunctions(node: arkts.EtsScript, builderFunctionNames: string[]): void { node.statements.forEach((statement) => { @@ -35,7 +34,7 @@ function collectBuilderFunctions(node: arkts.EtsScript, builderFunctionNames: st return; } const functionName = statement.scriptFunction.id?.name; - if (!functionName || builderFunctionNames.includes(functionName)) { + if (!functionName || functionName === '' || builderFunctionNames.includes(functionName)) { return; } builderFunctionNames.push(functionName); @@ -43,52 +42,72 @@ function collectBuilderFunctions(node: arkts.EtsScript, builderFunctionNames: st }); } -// Verify that the wrapBuilder's arguments are decorated with @Builder -function validateWrapBuilderArguments( - member: arkts.ClassProperty, - context: UISyntaxRuleContext, - builderFunctionNames: string[] +function validateWrapBuilderInIdentifier( + node: arkts.AstNode, + builderFunctionNames: string[], + context: UISyntaxRuleContext ): void { - member.getChildren().forEach((child) => { - if (!arkts.isCallExpression(child) || child.expression.dumpSrc() !== WRAPBUILDER_NAME) { - return; + // If the current node is not a wrap builder, return + if (!arkts.isIdentifier(node) || getIdentifierName(node) !== WRAP_BUILDER) { + return; + } + const parentNode = node.parent; + if (!arkts.isCallExpression(parentNode)) { + return; + } + let functionName: string = ''; + // Get the parameters of the wrap builder + parentNode.arguments.forEach(argument => { + if (arkts.isIdentifier(argument)) { + functionName = argument.name; } - let functionName: string | undefined; - child.arguments.forEach(firstArgument => { - if (arkts.isMemberExpression(firstArgument)) { - functionName = getIdentifierName(firstArgument.property); - } else if (arkts.isIdentifier(firstArgument)) { - functionName = firstArgument.name; - } - // Verify that wrapBuilder's arguments are decorated with @Builder - // rule1: The wrapBuilder accepts only a function decorated by '@Builder' - if (functionName && !builderFunctionNames.includes(functionName)) { - context.report({ - node: firstArgument, - message: rule.messages.invalidBuilderCheck, - }); - } - }); }); + // If the function name is not empty and not decorated by the @builder, an error is reported + if (functionName === '' || !builderFunctionNames.includes(functionName)) { + const errorNode = parentNode.arguments[0]; + context.report({ + node: errorNode, + message: rule.messages.invalidWrapBuilderCheck, + }); + } } -function validateWrapBuilder( - node: arkts.StructDeclaration, +function validateWrapBuilderInReturnStatement( + node: arkts.AstNode, builderFunctionNames: string[], context: UISyntaxRuleContext ): void { - node.definition.body.forEach(member => { - if (!arkts.isClassProperty(member)) { - return; + if (!arkts.isReturnStatement(node)) { + return; + } + if (!node.argument || !arkts.isCallExpression(node.argument)) { + return; + } + if (!node.argument.expression || !arkts.isIdentifier(node.argument.expression) || + node.argument.expression.name !== WRAP_BUILDER) { + return; + } + let functionName: string = ''; + // Get the parameters of the wrap builder + node.argument.arguments.forEach(argument => { + if (arkts.isIdentifier(argument)) { + functionName = argument.name; } - validateWrapBuilderArguments(member, context, builderFunctionNames); }); + // If the function name is not empty and not decorated by the @builder, an error is reported + if (functionName === '' || !builderFunctionNames.includes(functionName)) { + const errorNode = node.argument.arguments[0]; + context.report({ + node: errorNode, + message: rule.messages.invalidWrapBuilderCheck, + }); + } } const rule: UISyntaxRule = { name: 'wrap-builder-check', messages: { - invalidBuilderCheck: 'The wrapBuilder accepts only a function decorated by @Builder.', + invalidWrapBuilderCheck: 'The wrapBuilder\'s parameter should be @Builder function.', }, setup(context) { let builderFunctionNames: string[] = []; @@ -97,13 +116,11 @@ const rule: UISyntaxRule = { if (arkts.isEtsScript(node)) { collectBuilderFunctions(node, builderFunctionNames); } - if (!arkts.isStructDeclaration(node)) { - return; - } - validateWrapBuilder(node, builderFunctionNames, context); + validateWrapBuilderInIdentifier(node, builderFunctionNames, context); + validateWrapBuilderInReturnStatement(node, builderFunctionNames, context); }, }; }, }; -export default rule; +export default rule; \ No newline at end of file diff --git a/arkui-plugins/ui-syntax-plugins/utils/index.ts b/arkui-plugins/ui-syntax-plugins/utils/index.ts index 6e025ba13602174754a09cf1f55611a15cffdea4..f867406cd25f51543f26c991ae7ebd2347b5f5cf 100644 --- a/arkui-plugins/ui-syntax-plugins/utils/index.ts +++ b/arkui-plugins/ui-syntax-plugins/utils/index.ts @@ -20,6 +20,7 @@ import { UISyntaxRuleComponents } from 'ui-syntax-plugins/processor'; import { UISyntaxRuleContext } from 'ui-syntax-plugins/rules/ui-syntax-rule'; export const PresetDecorators = { + TOGGLE: 'Toggle', BUILDER_PARAM: 'BuilderParam', COMPONENT_V1: 'Component', COMPONENT_V2: 'ComponentV2', @@ -57,6 +58,15 @@ export const PresetDecorators = { LOCAL_BUILDER: 'LocalBuilder', }; +export const TOGGLE_TYPE: string = 'ToggleType'; +export const TYPE: string = 'type'; +export const WRAP_BUILDER: string = 'wrapBuilder'; + +export const ToggleType = { + CHECKBOX: 'Checkbox', + BUTTON: 'Button' +}; + const PUBLIC_PROPERTY_MODIFIERS: Number = 4; const PROTECTED_PROPERTY_MODIFIERS: Number = 8; const PRIVATE_PROPERTY_MODIFIERS: Number = 16;