diff --git a/arkui-plugins/ui-syntax-plugins/rules/check-construct-private-parameter.ts b/arkui-plugins/ui-syntax-plugins/rules/check-construct-private-parameter.ts index 7dfa570b04da98eeea09a1f315c47adcc1e2b67c..6bbdbc59c666cd96be0be42ebc8fa4475e04d895 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/check-construct-private-parameter.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/check-construct-private-parameter.ts @@ -17,52 +17,52 @@ import * as arkts from '@koalaui/libarkts'; import { getClassPropertyName, isPrivateClassProperty } from '../utils'; import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; -function addProperty(item: arkts.AstNode, structName: string, privateMap: Map): void { +function addProperty(item: arkts.AstNode, structName: string, privatePropertyMap: Map): void { if (!arkts.isClassProperty(item) || !isPrivateClassProperty(item)) { return; } // Check if structName already exists in privateMap - if (privateMap.has(structName)) { + if (privatePropertyMap.has(structName)) { // If it exists, retrieve the current string[] and append the new content - privateMap.get(structName)?.push(getClassPropertyName(item)); + privatePropertyMap.get(structName)?.push(getClassPropertyName(item)); } else { // If it doesn't exist, create a new string[] and add the content - privateMap.set(structName, [getClassPropertyName(item)]); + privatePropertyMap.set(structName, [getClassPropertyName(item)]); } } function checkPrivateVariables( node: arkts.AstNode, context: UISyntaxRuleContext, - privateMap: Map + privatePropertyMap: Map ): 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)) { + if (!arkts.isStructDeclaration(member) || !member.definition.ident || !member.definition.ident.name) { return; } - const structName: string = member.definition.ident?.name ?? ''; - member.definition?.body?.forEach((item) => { - addProperty(item, structName, privateMap); + const structName: string = member.definition.ident.name; + member.definition.body.forEach((item) => { + addProperty(item, structName, privatePropertyMap); }); }); } - if (!arkts.isCallExpression(node)) { + if (!arkts.isCallExpression(node) || !arkts.isIdentifier(node.expression)) { return; } - const componentName = node.expression.dumpSrc(); + const componentName = node.expression.name; // If the initialization is for a component with private properties - if (!privateMap.has(componentName)) { + if (!privatePropertyMap.has(componentName)) { return; } - node.arguments?.forEach((member) => { + node.arguments.forEach((member) => { member.getChildren().forEach((property) => { - if (!arkts.isProperty(property)) { + if (!arkts.isProperty(property) || !property.key || !arkts.isIdentifier(property.key)) { return; } - const propertyName: string = property.key?.dumpSrc() ?? ''; - if (privateMap.get(componentName)!.includes(propertyName)) { + const propertyName: string = property.key.name; + if (privatePropertyMap.get(componentName)!.includes(propertyName)) { context.report({ node: property, message: rule.messages.cannotInitializePrivateVariables, @@ -81,10 +81,10 @@ const rule: UISyntaxRule = { cannotInitializePrivateVariables: `Property '{{propertyName}}' is private and can not be initialized through the component constructor.`, }, setup(context) { - let privateMap: Map = new Map(); + let privatePropertyMap: Map = new Map(); return { parsed: (node): void => { - checkPrivateVariables(node, context, privateMap); + checkPrivateVariables(node, context, privatePropertyMap); }, }; }, diff --git a/arkui-plugins/ui-syntax-plugins/rules/entry-localstorage-check.ts b/arkui-plugins/ui-syntax-plugins/rules/entry-localstorage-check.ts index aa82f31044b738f69246d70bc95354f54a737801..65d894146ade6e3b6638fdc0af14a5f7318c2a4e 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/entry-localstorage-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/entry-localstorage-check.ts @@ -20,7 +20,7 @@ import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; function checkLocalStorageLink(node: arkts.StructDeclaration, context: UISyntaxRuleContext): void { // Check if @Entry decorator exists with parameter const entryDecorator = getAnnotationUsage(node, PresetDecorators.ENTRY); - const isStorageUsed = entryDecorator && node.definition?.annotations[0].properties[0]; + const isStorageUsed = entryDecorator && node.definition.annotations[0].properties[0]; // Check if @LocalStorageLink exists let localStorageLinkUsed = false; node.definition.body.forEach(body => { @@ -29,14 +29,15 @@ function checkLocalStorageLink(node: arkts.StructDeclaration, context: UISyntaxR } const propertyDecorators = getClassPropertyAnnotationNames(body); localStorageLinkUsed = propertyDecorators.some( - decorator => decorator === PresetDecorators.LOCAL_STORAGE_LINK); + decorator => decorator === PresetDecorators.LOCAL_STORAGE_LINK || + decorator === PresetDecorators.LOCAL_STORAGE_PROP); }); // If @LocalStorageLink is used but @Entry(storage) is missing, report error if (entryDecorator && localStorageLinkUsed && !isStorageUsed) { context.report({ node: entryDecorator, - message: rule.messages.invalidUsage + message: rule.messages.entryLocalStorageCheck }); } } @@ -44,7 +45,7 @@ function checkLocalStorageLink(node: arkts.StructDeclaration, context: UISyntaxR const rule: UISyntaxRule = { name: 'entry-localstorage-check', messages: { - invalidUsage: `@LocalStorageLink requires @Entry(storage) on the struct.`, + entryLocalStorageCheck: `'@Entry' should have a parameter, like '@Entry ({ storage: "__get_local_storage__" })'.`, }, setup(context) { return { diff --git a/arkui-plugins/ui-syntax-plugins/rules/observedV2-trace-usage-validation.ts b/arkui-plugins/ui-syntax-plugins/rules/observedV2-trace-usage-validation.ts index d6285abd1be1ad49e2bbb4fed6606bab9c809ab3..bb21f5dd3482c2636b5fa5099c78ea04f5a22475 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/observedV2-trace-usage-validation.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/observedV2-trace-usage-validation.ts @@ -22,8 +22,12 @@ const rule: UISyntaxRule = { messages: { observedV2DecoratorError: `The '@ObservedV2' decorator can only be used in 'class'.`, traceDecoratorError: `The '@Trace' decorator can only be used in 'class'.`, - traceInObservedV2Error: `The '@Trace' decorator can only be used in a 'class' decorated with '@ObservedV2'.`, traceMemberVariableError: `The '@Trace' decorator can only decorate member variables within a 'class' decorated with '@ObservedV2'.`, + //The repair logic is different, if there is v1, update to v2 + traceMustUsedWithObservedV2: `The '@Trace' decorator can only be used within a 'class' decorated with 'ObservedV2'.`, + traceMustUsedWithObservedV2Update: `The '@Trace' decorator can only be used within a 'class' decorated with 'ObservedV2'.`, + + }, setup(context) { return { @@ -34,14 +38,19 @@ const rule: UISyntaxRule = { }, }; +function getObservedDecorator(node: arkts.ClassDeclaration): arkts.AnnotationUsage | undefined { + return node.definition?.annotations.find(annotation => + annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name === PresetDecorators.OBSERVED_V1); +} + function reportObservedV2DecoratorError(context: UISyntaxRuleContext, hasObservedV2Decorator: arkts.AnnotationUsage) : void { context.report({ node: hasObservedV2Decorator, message: rule.messages.observedV2DecoratorError, fix: (hasObservedV2Decorator) => { - const startPosition = arkts.getStartPosition(hasObservedV2Decorator); - const endPosition = arkts.getEndPosition(hasObservedV2Decorator); + const startPosition = hasObservedV2Decorator.startPosition; + const endPosition = hasObservedV2Decorator.endPosition; return { range: [startPosition, endPosition], code: '', @@ -56,8 +65,8 @@ function reportTraceMemberVariableError(context: UISyntaxRuleContext, hasTraceDe node: hasTraceDecorator, message: rule.messages.traceMemberVariableError, fix: (hasTraceDecorator) => { - const startPosition = arkts.getStartPosition(hasTraceDecorator); - const endPosition = arkts.getEndPosition(hasTraceDecorator); + const startPosition = hasTraceDecorator.startPosition; + const endPosition = hasTraceDecorator.endPosition; return { range: [startPosition, endPosition], code: '', @@ -73,10 +82,14 @@ function tracePerportyRule( if (arkts.isStructDeclaration(currentNode)) { reportTraceDecoratorError(context, hasTraceDecorator); } else if (arkts.isClassDeclaration(currentNode)) { + const observedDecorator = getObservedDecorator(currentNode); // The '@Trace' decorator can only be used in a 'class' decorated with '@ObservedV2' if (!currentNode.definition?.annotations?.some((annotation: any) => annotation.expr.name === - PresetDecorators.OBSERVED_V2)) { - reportTraceInObservedV2Error(context, hasTraceDecorator, currentNode); + PresetDecorators.OBSERVED_V2) && !observedDecorator) { + reportTraceMustUsedWithObservedV2(context, hasTraceDecorator, currentNode); + } else if (!currentNode.definition?.annotations?.some((annotation: any) => annotation.expr.name === + PresetDecorators.OBSERVED_V2) && observedDecorator) { + reportTraceMustUsedWithObservedV2Update(context, hasTraceDecorator, observedDecorator); } } } @@ -87,8 +100,8 @@ function reportTraceDecoratorError(context: UISyntaxRuleContext, hasTraceDecorat node: hasTraceDecorator, message: rule.messages.traceDecoratorError, fix: (hasTraceDecorator) => { - const startPosition = arkts.getStartPosition(hasTraceDecorator); - const endPosition = arkts.getEndPosition(hasTraceDecorator); + const startPosition = hasTraceDecorator.startPosition; + const endPosition = hasTraceDecorator.endPosition; return { range: [startPosition, endPosition], code: '', @@ -97,13 +110,13 @@ function reportTraceDecoratorError(context: UISyntaxRuleContext, hasTraceDecorat }); } -function reportTraceInObservedV2Error(context: UISyntaxRuleContext, hasTraceDecorator: arkts.AnnotationUsage, +function reportTraceMustUsedWithObservedV2(context: UISyntaxRuleContext, hasTraceDecorator: arkts.AnnotationUsage, currentNode: arkts.ClassDeclaration): void { context.report({ node: hasTraceDecorator, - message: rule.messages.traceInObservedV2Error, + message: rule.messages.traceMustUsedWithObservedV2, fix: () => { - const startPosition = arkts.getStartPosition(currentNode); + const startPosition = currentNode.startPosition; return { range: [startPosition, startPosition], code: `@${PresetDecorators.OBSERVED_V2}\n`, @@ -112,6 +125,22 @@ function reportTraceInObservedV2Error(context: UISyntaxRuleContext, hasTraceDeco }); } +function reportTraceMustUsedWithObservedV2Update(context: UISyntaxRuleContext, hasTraceDecorator: arkts.AnnotationUsage, + observedDecorator: arkts.AnnotationUsage): void { + context.report({ + node: hasTraceDecorator, + message: rule.messages.traceMustUsedWithObservedV2Update, + fix: () => { + const startPosition = observedDecorator.startPosition; + const endPosition = observedDecorator.endPosition; + return { + range: [startPosition, endPosition], + code: `@${PresetDecorators.OBSERVED_V2}`, + }; + }, + }); +} + function validateTraceDecoratorUsage(node: arkts.AstNode, context: UISyntaxRuleContext): void { let currentNode = node; if (arkts.isStructDeclaration(node)) { @@ -123,7 +152,7 @@ function validateTraceDecoratorUsage(node: arkts.AstNode, context: UISyntaxRuleC } if (arkts.isClassProperty(node)) { const hasTraceDecorator = node.annotations?.find(annotation => - annotation.expr && annotation.expr.dumpSrc() === PresetDecorators.TRACE); + annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name === PresetDecorators.TRACE); if (hasTraceDecorator) { // Iterate up the parent node to check whether it is a class or a custom component while (!arkts.isStructDeclaration(currentNode) && !arkts.isClassDeclaration(currentNode)) { @@ -136,7 +165,7 @@ function validateTraceDecoratorUsage(node: arkts.AstNode, context: UISyntaxRuleC if (arkts.isMethodDefinition(node)) { // Check that @Trace is in the correct location const hasTraceDecorator = node.scriptFunction.annotations?.find(annotation => - annotation.expr && annotation.expr.dumpSrc() === PresetDecorators.TRACE); + annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name === PresetDecorators.TRACE); if (hasTraceDecorator) { reportTraceMemberVariableError(context, hasTraceDecorator); } diff --git a/arkui-plugins/ui-syntax-plugins/rules/old-new-decorator-mix-use-check.ts b/arkui-plugins/ui-syntax-plugins/rules/old-new-decorator-mix-use-check.ts index 356a28098854e9031f63161d2cf9c69aa0b39f3d..92d5258e3457dac8400a7d7f50ea93e2b9ac7229 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/old-new-decorator-mix-use-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/old-new-decorator-mix-use-check.ts @@ -14,53 +14,62 @@ */ import * as arkts from '@koalaui/libarkts'; -import { PresetDecorators, getAnnotationUsage } from '../utils'; +import { PresetDecorators, getAnnotationName, getAnnotationUsage, getIdentifierName } from '../utils'; import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; +const oldV1Decorators: string[] = [ + PresetDecorators.STATE, + PresetDecorators.PROP, + PresetDecorators.LINK, + PresetDecorators.PROVIDE, + PresetDecorators.CONSUME, + PresetDecorators.WATCH, + PresetDecorators.STORAGE_LINK, + PresetDecorators.STORAGE_PROP, + PresetDecorators.LOCAL_STORAGE_LINK, + PresetDecorators.LOCAL_STORAGE_PROP, + PresetDecorators.OBJECT_LINK, +]; +const newV2decorators: string[] = [ + PresetDecorators.LOCAL, + PresetDecorators.PARAM, + PresetDecorators.ONCE, + PresetDecorators.EVENT, + PresetDecorators.MONITOR, + PresetDecorators.PROVIDER, + PresetDecorators.CONSUMER, + PresetDecorators.COMPUTED, +]; + function findPropertyDecorator( node: arkts.ClassProperty, - decoratorName: string + decoratorList: string[] ): arkts.AnnotationUsage | undefined { - const annotation = node.annotations?.find(annotation => + return node.annotations?.find(annotation => annotation.expr && - annotation.expr.dumpSrc() === decoratorName + arkts.isIdentifier(annotation.expr) && + decoratorList.includes(getIdentifierName(annotation.expr)) ); - return annotation; -} - -function paramDecoratorError( - context: UISyntaxRuleContext, - hasParamDecorator: arkts.AnnotationUsage, - hasComponentDecorator: arkts.AnnotationUsage -): void { - context.report({ - node: hasParamDecorator, - message: rule.messages.paramDecoratorError, - fix: () => { - const startPosition = arkts.getStartPosition(hasComponentDecorator); - const endPosition = arkts.getEndPosition(hasComponentDecorator); - return { - range: [startPosition, endPosition], - code: `@${PresetDecorators.COMPONENT_V2}`, - }; - }, - }); } -function stateDecoratorError( +function reportDecoratorError( context: UISyntaxRuleContext, hasStateDecorator: arkts.AnnotationUsage, - hasComponentV2Decorator: arkts.AnnotationUsage + hasComponentV2Decorator: arkts.AnnotationUsage, + structDecoratorName: string ): void { + let propertyDecoratorName = getAnnotationName(hasStateDecorator); context.report({ node: hasStateDecorator, - message: rule.messages.stateDecoratorError, + message: rule.messages.oldAndNewDecoratorsMixUse, + data: { + decoratorName: propertyDecoratorName, + component: structDecoratorName, + }, fix: () => { - const startPosition = arkts.getStartPosition(hasComponentV2Decorator); - const endPosition = arkts.getEndPosition(hasComponentV2Decorator); return { - range: [startPosition, endPosition], - code: `@${PresetDecorators.COMPONENT_V1}`, + range: [hasComponentV2Decorator.startPosition, hasComponentV2Decorator.endPosition], + code: `@${structDecoratorName}`, }; }, }); @@ -70,21 +79,22 @@ function findDecoratorError( node: arkts.StructDeclaration, context: UISyntaxRuleContext ): void { + // Gets the decorator version of a custom component const hasComponentV2Decorator = getAnnotationUsage(node, PresetDecorators.COMPONENT_V2); const hasComponentDecorator = getAnnotationUsage(node, PresetDecorators.COMPONENT_V1); - // Check where @Param and @State are used node.definition.body.forEach((property) => { - if (arkts.isClassProperty(property)) { - const hasParamDecorator = findPropertyDecorator(property, PresetDecorators.PARAM); - const hasStateDecorator = findPropertyDecorator(property, PresetDecorators.STATE); - // Check that @Param is in the correct location - if (hasParamDecorator && !hasComponentV2Decorator && hasComponentDecorator) { - paramDecoratorError(context, hasParamDecorator, hasComponentDecorator); - } - // Check that @State is in the correct location - if (hasStateDecorator && !hasComponentDecorator && hasComponentV2Decorator) { - stateDecoratorError(context, hasStateDecorator, hasComponentV2Decorator); - } + if (!arkts.isClassProperty(property)) { + return; + } + const hasNewDecorator = findPropertyDecorator(property, newV2decorators); + const hasOldDecorator = findPropertyDecorator(property, oldV1Decorators); + // Check that the new decorator is used for componennt v2 + if (hasNewDecorator && !hasComponentV2Decorator && hasComponentDecorator) { + reportDecoratorError(context, hasNewDecorator, hasComponentDecorator, PresetDecorators.COMPONENT_V2); + } + // Check that the old decorator is used for componennt v1 + if (hasOldDecorator && !hasComponentDecorator && hasComponentV2Decorator) { + reportDecoratorError(context, hasOldDecorator, hasComponentV2Decorator, PresetDecorators.COMPONENT_V1); } }); } @@ -93,8 +103,7 @@ function findDecoratorError( const rule: UISyntaxRule = { name: 'old-new-decorator-mix-use-check', messages: { - paramDecoratorError: `The '@Param' decorator can only be used in a 'struct' decorated with '@ComponentV2'.`, - stateDecoratorError: `The '@State' decorator can only be used in a 'struct' decorated with '@Component'.`, + oldAndNewDecoratorsMixUse: `The '@{{decoratorName}}' decorator can only be used in a 'struct' decorated with '@{{component}}'.`, }, setup(context) { return { @@ -108,4 +117,4 @@ const rule: UISyntaxRule = { }, }; -export default rule; +export default rule; \ No newline at end of file diff --git a/arkui-plugins/ui-syntax-plugins/rules/once-decorator-check.ts b/arkui-plugins/ui-syntax-plugins/rules/once-decorator-check.ts index fabe04af00c11adc8fd5e43f1a1e279e1c307bf6..6d5970ecbcf3fd2c4735e6b7d95a43d036559d54 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/once-decorator-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/once-decorator-check.ts @@ -19,8 +19,8 @@ import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; function findDecorator(member: arkts.ClassProperty, decorator: string): arkts.AnnotationUsage | undefined { return member.annotations?.find(annotation => - annotation.expr && - annotation.expr.dumpSrc() === decorator + annotation.expr && arkts.isIdentifier(annotation.expr) && + annotation.expr.name === decorator ); } @@ -28,22 +28,22 @@ function findDecorator(member: arkts.ClassProperty, decorator: string): arkts.An function validatePropertyAnnotations( body: arkts.ClassProperty, context: UISyntaxRuleContext, - hasOnceDecorator: arkts.AnnotationUsage | undefined + onceDecorator: arkts.AnnotationUsage | undefined ): void { const propertyAnnotations = getClassPropertyAnnotationNames(body); - hasOnceDecorator = findDecorator(body, PresetDecorators.ONCE); - if (hasOnceDecorator) { + onceDecorator = findDecorator(body, PresetDecorators.ONCE); + if (onceDecorator) { const isParamUsed = propertyAnnotations.includes(PresetDecorators.PARAM); // If @Once is found, check if @Param is also used if (!isParamUsed) { - reportMissingParamWithOnce(hasOnceDecorator, context); + reportMissingParamWithOnce(onceDecorator, context); } else { // If both @Once and @Param are used, check for other // incompatible decorators const otherDecorators = body.annotations?.find(annotation => - annotation.expr && - annotation.expr.dumpSrc() !== PresetDecorators.ONCE && - annotation.expr.dumpSrc() !== PresetDecorators.PARAM + annotation.expr && arkts.isIdentifier(annotation.expr) && + annotation.expr.name !== PresetDecorators.ONCE && + annotation.expr.name !== PresetDecorators.PARAM ); reportInvalidDecoratorsWithOnceAndParam(otherDecorators, context); } @@ -51,18 +51,18 @@ function validatePropertyAnnotations( } function reportMissingParamWithOnce( - hasOnceDecorator: arkts.AnnotationUsage | undefined, + onceDecorator: arkts.AnnotationUsage | undefined, context: UISyntaxRuleContext ): void { - if (!hasOnceDecorator) { + if (!onceDecorator) { return; } context.report({ - node: hasOnceDecorator, + node: onceDecorator, message: rule.messages.invalidDecorator, - fix: (hasOnceDecorator) => { - const startPosition = arkts.getEndPosition(hasOnceDecorator); - const endPosition = arkts.getEndPosition(hasOnceDecorator); + fix: (onceDecorator) => { + const startPosition = onceDecorator.endPosition; + const endPosition = onceDecorator.endPosition; return { range: [startPosition, endPosition], code: `@${PresetDecorators.PARAM}` @@ -82,8 +82,8 @@ function reportInvalidDecoratorsWithOnceAndParam( node: otherDecorators, message: rule.messages.invalidDecorator, fix: (otherDecorators) => { - const startPosition = arkts.getStartPosition(otherDecorators); - const endPosition = arkts.getEndPosition(otherDecorators); + const startPosition = otherDecorators.startPosition; + const endPosition = otherDecorators.endPosition; return { range: [startPosition, endPosition], code: '' @@ -95,16 +95,16 @@ function reportInvalidDecoratorsWithOnceAndParam( // Check if the method is @Once decorated (not allowed) function validateMethodAnnotations(body: arkts.MethodDefinition, context: UISyntaxRuleContext): void { const methodAnnotations = body.scriptFunction.annotations?.find(annotation => - annotation.expr && - annotation.expr.dumpSrc() === PresetDecorators.ONCE + annotation.expr && arkts.isIdentifier(annotation.expr) && + annotation.expr.name === PresetDecorators.ONCE ); if (methodAnnotations) { context.report({ node: methodAnnotations, message: rule.messages.invalidMemberDecorate, fix: (methodAnnotations) => { - const startPosition = arkts.getStartPosition(methodAnnotations); - const endPosition = arkts.getEndPosition(methodAnnotations); + const startPosition = methodAnnotations.startPosition; + const endPosition = methodAnnotations.endPosition; return { range: [startPosition, endPosition], code: '' @@ -114,33 +114,56 @@ function validateMethodAnnotations(body: arkts.MethodDefinition, context: UISynt } } -function invalidComponentUsage( - body: arkts.ClassProperty, - hasOnceDecorator: arkts.AnnotationUsage | undefined, - componentV2DocoratorUsage: arkts.AnnotationUsage | undefined, + +function reportInvalidOnceUsage( + onceDecorator: arkts.AnnotationUsage | undefined, + node: arkts.StructDeclaration, componentDocoratorUsage: arkts.AnnotationUsage | undefined, - context: UISyntaxRuleContext + context: UISyntaxRuleContext, ): void { - hasOnceDecorator = findDecorator(body, PresetDecorators.ONCE); - if (hasOnceDecorator && !componentV2DocoratorUsage && componentDocoratorUsage) { - context.report({ - node: hasOnceDecorator, - message: rule.messages.invalidUsage, - fix: (hasOnceDecorator) => { - const startPosition = arkts.getStartPosition(componentDocoratorUsage); - const endPosition = arkts.getEndPosition(componentDocoratorUsage); + if (!onceDecorator) { + return; + } + context.report({ + node: onceDecorator, + message: rule.messages.invalidUsage, + fix: () => { + if (componentDocoratorUsage) { + const startPosition = componentDocoratorUsage.startPosition; + const endPosition = componentDocoratorUsage.endPosition; return { range: [startPosition, endPosition], code: `@${PresetDecorators.COMPONENT_V2}` }; + } else { + const startPosition = node.startPosition; + const endPosition = node.startPosition; + return { + range: [startPosition, endPosition], + code: `@${PresetDecorators.COMPONENT_V2}\n` + }; } - }); + } + }); +} + +function invalidComponentUsage( + node: arkts.StructDeclaration, + body: arkts.ClassProperty, + onceDecorator: arkts.AnnotationUsage | undefined, + componentV2DocoratorUsage: arkts.AnnotationUsage | undefined, + componentDocoratorUsage: arkts.AnnotationUsage | undefined, + context: UISyntaxRuleContext +): void { + onceDecorator = findDecorator(body, PresetDecorators.ONCE); + if (onceDecorator && !componentV2DocoratorUsage) { + reportInvalidOnceUsage(onceDecorator, node, componentDocoratorUsage, context); } } function validateDecorater( node: arkts.StructDeclaration, - hasOnceDecorator: arkts.AnnotationUsage | undefined, + onceDecorator: arkts.AnnotationUsage | undefined, componentV2DocoratorUsage: arkts.AnnotationUsage | undefined, componentDocoratorUsage: arkts.AnnotationUsage | undefined, context: UISyntaxRuleContext, @@ -148,9 +171,9 @@ function validateDecorater( node.definition?.body.forEach(body => { // Check if @Once is used on a property and if @Param is used with if (arkts.isClassProperty(body)) { - validatePropertyAnnotations(body, context, hasOnceDecorator); + validatePropertyAnnotations(body, context, onceDecorator); // If @Once is used but not in a @ComponentV2 struct, report an error - invalidComponentUsage(body, hasOnceDecorator, componentV2DocoratorUsage, componentDocoratorUsage, context); + invalidComponentUsage(node, body, onceDecorator, componentV2DocoratorUsage, componentDocoratorUsage, context); } if (!arkts.isMethodDefinition(body)) { return; @@ -160,25 +183,56 @@ function validateDecorater( }); } +function checkClassForInvalidDecorator( + node: arkts.ClassDeclaration, + onceDecorator: arkts.AnnotationUsage | undefined, + context: UISyntaxRuleContext, +): void { + node.definition?.body.forEach(member => { + if (!arkts.isClassProperty(member)) { + return; + } + onceDecorator = findDecorator(member, PresetDecorators.ONCE); + if (!onceDecorator) { + return; + } + context.report({ + node: onceDecorator, + message: rule.messages.invalidNOtInStruct, + fix: (onceDecorator) => { + const startPosition = onceDecorator.startPosition; + const endPosition = onceDecorator.endPosition; + return { + range: [startPosition, endPosition], + code: `` + }; + } + }); + }); +} + const rule: UISyntaxRule = { name: 'once-decorator-check', messages: { - invalidUsage: `@Once can only decorate member properties in a @ComponentV2 struct.`, - invalidMemberDecorate: `@Once can only decorate member properties.`, - invalidDecorator: `@Once must be only used with @Param. ` + invalidUsage: `The '@Once' decorator can only be used in a 'struct' decorated with '@ComponentV2'.`, + invalidMemberDecorate: `@Once can only decorate member property.`, + invalidDecorator: `When a variable decorated with '@Once', it must also be decorated with '@Param'.`, + invalidNOtInStruct: `'@Once' decorator can only be used with 'struct'.` }, setup(context) { return { parsed: (node): void => { + let onceDecorator: arkts.AnnotationUsage | undefined; // Check if the node is a struct declaration - if (!arkts.isStructDeclaration(node)) { - return; + if (arkts.isClassDeclaration(node)) { + checkClassForInvalidDecorator(node, onceDecorator, context); + } + if (arkts.isStructDeclaration(node)) { + // Check if the struct is decorated with @ComponentV2 + const componentV2DocoratorUsage = getAnnotationUsage(node, PresetDecorators.COMPONENT_V2); + const componentDocoratorUsage = getAnnotationUsage(node, PresetDecorators.COMPONENT_V1); + validateDecorater(node, onceDecorator, componentV2DocoratorUsage, componentDocoratorUsage, context); } - let hasOnceDecorator: arkts.AnnotationUsage | undefined; - // Check if the struct is decorated with @ComponentV2 - const componentV2DocoratorUsage = getAnnotationUsage(node, PresetDecorators.COMPONENT_V2); - const componentDocoratorUsage = getAnnotationUsage(node, PresetDecorators.COMPONENT_V1); - validateDecorater(node, hasOnceDecorator, componentV2DocoratorUsage, componentDocoratorUsage, context); }, }; }, diff --git a/arkui-plugins/ui-syntax-plugins/rules/reusable-component-in-V2-check.ts b/arkui-plugins/ui-syntax-plugins/rules/reusable-component-in-V2-check.ts new file mode 100644 index 0000000000000000000000000000000000000000..8ca843c94a9fcd9a70e5b2f96fa082c7ff09cca2 --- /dev/null +++ b/arkui-plugins/ui-syntax-plugins/rules/reusable-component-in-V2-check.ts @@ -0,0 +1,92 @@ +/* + * 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'; +import { PresetDecorators } from '../utils'; + +function initStructName(node: arkts.AstNode, reusableStructName: string[]): void { + if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { + return; + } + //Go through all the children of Program + for (const childNode of node.getChildren()) { + // Check whether the type is struct + if (!arkts.isStructDeclaration(childNode)) { + return; + } + // Get a list of annotations + const annotationsList = childNode.definition.annotations; + // Check that the current component has @Reusable decorators + if (annotationsList?.some((annotation: any) => annotation.expr.name === PresetDecorators.REUSABLE_V1)) { + const struceName = childNode.definition?.ident?.name || ''; + reusableStructName.push(struceName); + } + } +} + +function reportNoReusableV1InComponentV2(node: arkts.AstNode, context: UISyntaxRuleContext): void { + context.report({ + node: node, + message: rule.messages.noReusableV1InComponentV2, + fix: (node) => { + return { + range: [node.startPosition, node.endPosition], + code: '', + }; + } + }); +} + +function checkNoReusableV1InComponentV2( + node: arkts.AstNode, + context: UISyntaxRuleContext, + reusableStructName: string[] +): void { + if (!arkts.isCallExpression(node) || !arkts.isIdentifier(node.expression)) { + return; + } + if (reusableStructName.includes(node.expression.name)) { + // Traverse upwards to find the custom component. + let struceNode: arkts.AstNode = node; + while (!arkts.isStructDeclaration(struceNode)) { + struceNode = struceNode.parent; + } + const annotationsList = struceNode.definition.annotations; + // Check that the current component is decorated by the @ComponentV2 decorator + if (annotationsList?.some((annotation: any) => annotation.expr.name === PresetDecorators.COMPONENT_V2)) { + reportNoReusableV1InComponentV2(node, context); + } + } +} + +const rule: UISyntaxRule = { + name: 'reusable-component-in-V2-check', + messages: { + noReusableV1InComponentV2: `When a custom component is decorated with @ComponentV2 and contains a child component decorated with @Reusable, the child component will not create.`, + }, + setup(context) { + // Create an array to store custom components that are modified using the @Reusable decorator + const reusableStructName: string[] = []; + return { + parsed: (node): void => { + initStructName(node, reusableStructName); + checkNoReusableV1InComponentV2(node, context, reusableStructName); + }, + }; + }, +}; + +export default rule; diff --git a/arkui-plugins/ui-syntax-plugins/rules/reuse-attribute-check.ts b/arkui-plugins/ui-syntax-plugins/rules/reuse-attribute-check.ts index 773b133ac3fad557e76e0c22fc4ec61f9201487f..9cc5dc5ab4fdca6af1163f275cc997108421b6d5 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/reuse-attribute-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/reuse-attribute-check.ts @@ -15,7 +15,7 @@ import * as arkts from '@koalaui/libarkts'; import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; -import { PresetDecorators, getAnnotationUsage } from '../utils'; +import { PresetDecorators, getAnnotationUsage, ReuseConstants } from '../utils'; function findStructsWithReusableAndComponentV2(node: arkts.AstNode, reusableV2ComponentV2Struct: string[]): void { //Go through all the children of Program @@ -41,11 +41,13 @@ function validateReuseOrReuseIdUsage(context: UISyntaxRuleContext, node: arkts.M const decoratedNode = node.property; if (arkts.isCallExpression(structNode)) { const Node = structNode.expression; - if (decoratedNode.dumpSrc() === 'reuse' && !reusableV2ComponentV2Struct.includes(Node.dumpSrc())) { - reportInvalidReuseUsage(context, node, structNode, rule); - } - else if (decoratedNode.dumpSrc() === 'reuseId' && reusableV2ComponentV2Struct.includes(Node.dumpSrc())) { - reportInvalidReuseIdUsage(context, node, structNode, rule); + if (arkts.isIdentifier(Node) && arkts.isIdentifier(decoratedNode)) { + if (decoratedNode.name === ReuseConstants.REUSE && !reusableV2ComponentV2Struct.includes(Node.name)) { + reportInvalidReuseUsage(context, node, decoratedNode, rule); + } + else if (decoratedNode.name === ReuseConstants.REUSE_ID && reusableV2ComponentV2Struct.includes(Node.name)) { + reportInvalidReuseIdUsage(context, node, decoratedNode, rule); + } } } } @@ -55,12 +57,12 @@ function reportInvalidReuseUsage(context: UISyntaxRuleContext, node: arkts.AstNo context.report({ node: node, message: rule.messages.invalidReuseUsage, - fix: (node) => { - const startPosition = arkts.getStartPosition(node); - const endPosition = arkts.getEndPosition(node); + fix: () => { + const startPosition = structNode.startPosition; + const endPosition = structNode.endPosition; return { range: [startPosition, endPosition], - code: `${structNode.dumpSrc()}.reuseId`, + code: ReuseConstants.REUSE_ID, }; }, }); @@ -71,12 +73,12 @@ function reportInvalidReuseIdUsage(context: UISyntaxRuleContext, node: arkts.Ast context.report({ node: node, message: rule.messages.invalidReuseIdUsage, - fix: (node) => { - const startPosition = arkts.getStartPosition(node); - const endPosition = arkts.getEndPosition(node); + fix: () => { + const startPosition = structNode.startPosition; + const endPosition = structNode.endPosition; return { range: [startPosition, endPosition], - code: `${structNode.dumpSrc()}.reuse`, + code: ReuseConstants.REUSE, }; }, }); 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 9aaa53de01b43f3b6dcf7be32c17dd39156f6a7e..8e18dc7e624a64c478e82d7999945968357e8b97 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/struct-property-decorator.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/struct-property-decorator.ts @@ -14,15 +14,38 @@ */ import * as arkts from '@koalaui/libarkts'; -import { getClassPropertyAnnotationNames } from '../utils'; +import { getClassPropertyAnnotationNames, PresetDecorators } from '../utils'; import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; -function checkInvalidStaticPropertyDecorations(context: UISyntaxRuleContext, node: arkts.StructDeclaration): void { +const decorators: string[] = [ + PresetDecorators.BUILDER_PARAM, + PresetDecorators.STATE, + PresetDecorators.PROP, + PresetDecorators.LINK, + PresetDecorators.OBJECT_LINK, + PresetDecorators.STORAGE_PROP, + PresetDecorators.STORAGE_LINK, + PresetDecorators.WATCH, + PresetDecorators.LOCAL_STORAGE_LINK, + PresetDecorators.LOCAL_STORAGE_PROP, + PresetDecorators.REQUIRE, +]; + +function hasPropertyDecorator( + member: arkts.ClassProperty, +): boolean { + const annotationName = getClassPropertyAnnotationNames(member); + return decorators.some(decorator => + annotationName.includes(decorator) + ); +} + +function checkInvalidStaticPropertyDecorations(context: UISyntaxRuleContext, node: arkts.StructDeclaration,): 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 && getClassPropertyAnnotationNames(member).length > 0) && propertyNameNode) { + if ((member.isStatic && hasPropertyDecorator(member)) && propertyNameNode) { context.report({ node: propertyNameNode, message: rule.messages.invalidStaticUsage @@ -35,7 +58,7 @@ function checkInvalidStaticPropertyDecorations(context: UISyntaxRuleContext, nod const rule: UISyntaxRule = { name: 'struct-property-decorator', messages: { - invalidStaticUsage: `Static variables in custom components cannot be decorated by built-in variable decorators.` + invalidStaticUsage: `The static variable of struct cannot be used together with built-in decorators.` }, setup(context) { return { diff --git a/arkui-plugins/ui-syntax-plugins/rules/track-decorator-check.ts b/arkui-plugins/ui-syntax-plugins/rules/track-decorator-check.ts index db909687b0635ef60fb91ca85e451d374e386733..b2a2b1ce0ae7117ca30934d439c9192559da7f08 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/track-decorator-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/track-decorator-check.ts @@ -20,8 +20,8 @@ import { PresetDecorators } from '../utils/index'; const rule: UISyntaxRule = { name: 'track-decorator-check', messages: { - invalidTarget: `The '@Track' decorator can only be used on class member variables.`, - invalidClass: `The '@Track' decorator can only be used within a 'class' decorated with '@Observed'.` + trackOnClassMemberOnly: `The '@Track' decorator can decorate only member variables of a class.`, + trackMustUsedWithObserved: `'@Track' cannot be used with classes decorated by '@ObservedV2'. Use the '@Trace' decorator instead.`, }, setup(context) { return { @@ -31,7 +31,7 @@ const rule: UISyntaxRule = { } // Check if the current node is a class declaration if (arkts.isClassDeclaration(node)) { - checkTrackOnlyUsedWithObserved(context, node); + checkTrackUsedWithObservedV2(context, node); } } }; @@ -60,13 +60,13 @@ function checkInvalidTrackAnnotations(context: UISyntaxRuleContext, node: arkts. },); } -function checkTrackOnlyUsedWithObserved(context: UISyntaxRuleContext, node: arkts.ClassDeclaration): void { +function checkTrackUsedWithObservedV2(context: UISyntaxRuleContext, node: arkts.ClassDeclaration): void { // Check if the class is decorated with @Observed - const hasObservedDecorator = node.definition?.annotations?.find( + const hasObservedV2Decorator = node.definition?.annotations?.find( annotations => annotations.expr && arkts.isIdentifier(annotations.expr) && - annotations.expr.name === PresetDecorators.OBSERVED_V1 + annotations.expr.name === PresetDecorators.OBSERVED_V2 ); // Traverse all members of the body class node.definition?.body.forEach((member) => { @@ -74,7 +74,7 @@ function checkTrackOnlyUsedWithObserved(context: UISyntaxRuleContext, node: arkt if (arkts.isClassProperty(member)) { const hasTrackDecorator = findClassPropertyAnnotation(member, PresetDecorators.TRACK); // If the class is not decorated with @Observed and has decorators, an error is reported - if (!hasObservedDecorator && hasTrackDecorator) { + if (hasObservedV2Decorator && !(node.definition?.annotations.length === 0) && hasTrackDecorator) { reportInvalidClass(context, hasTrackDecorator); } } @@ -92,10 +92,10 @@ function checkTrackOnlyUsedWithObserved(context: UISyntaxRuleContext, node: arkt function reportInvalidClass(context: UISyntaxRuleContext, hasTrackDecorator: arkts.AnnotationUsage): void { context.report({ node: hasTrackDecorator, - message: rule.messages.invalidClass, + message: rule.messages.trackMustUsedWithObserved, fix: (hasTrackDecorator) => { - const startPosition = arkts.getStartPosition(hasTrackDecorator); - const endPosition = arkts.getEndPosition(hasTrackDecorator); + const startPosition = hasTrackDecorator.startPosition; + const endPosition = hasTrackDecorator.endPosition; return { range: [startPosition, endPosition], code: '', @@ -111,7 +111,8 @@ function getMethodAnnotation( return node.scriptFunction.annotations?.find( annotation => annotation.expr && - annotation.expr.dumpSrc() === annotationName + arkts.isIdentifier(annotation.expr) && + annotation.expr.name === annotationName ); } @@ -121,7 +122,8 @@ function findClassPropertyAnnotation( : arkts.AnnotationUsage | undefined { return node.annotations?.find(annotation => annotation.expr && - annotation.expr.dumpSrc() === annotationName + arkts.isIdentifier(annotation.expr) && + annotation.expr.name === annotationName ); } @@ -131,10 +133,10 @@ function reportInvalidTarget( : void { context.report({ node: node, - message: rule.messages.invalidTarget, + message: rule.messages.trackOnClassMemberOnly, fix: (node) => { - const startPosition = arkts.getStartPosition(node); - const endPosition = arkts.getEndPosition(node); + const startPosition = node.startPosition; + const endPosition = node.endPosition; return { range: [startPosition, endPosition], code: '', diff --git a/arkui-plugins/ui-syntax-plugins/rules/variable-initialization-via-component-constructor.ts b/arkui-plugins/ui-syntax-plugins/rules/variable-initialization-via-component-constructor.ts new file mode 100644 index 0000000000000000000000000000000000000000..b33b1ce8eb85cd534566e3b1e0f2822980ca2282 --- /dev/null +++ b/arkui-plugins/ui-syntax-plugins/rules/variable-initialization-via-component-constructor.ts @@ -0,0 +1,268 @@ +/* + * 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 { getIdentifierName, getClassPropertyAnnotationNames, PresetDecorators } from '../utils'; +import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; + +const mustInitInConstructorDecorators: string[][] = [ + [PresetDecorators.REQUIRE], + [PresetDecorators.REQUIRE, PresetDecorators.STATE], + [PresetDecorators.REQUIRE, PresetDecorators.PROVIDE], + [PresetDecorators.REQUIRE, PresetDecorators.PROP], + [PresetDecorators.REQUIRE, PresetDecorators.BUILDER_PARAM] +]; +const shouldInitInConstructorDecorators: string[][] = [ + [PresetDecorators.PROP], + [PresetDecorators.BUILDER_PARAM], + [PresetDecorators.LINK], + [PresetDecorators.OBJECT_LINK] +]; +const notAllowInitInConstructorDecorators: string[][] = [ + [PresetDecorators.STORAGE_LINK], + [PresetDecorators.STORAGE_PROP], + [PresetDecorators.CONSUME], + [PresetDecorators.LOCAL_STORAGE_LINK], + [PresetDecorators.LOCAL_STORAGE_PROP] +]; + +// Define a function to add property data to the property map +function addProperty(propertyMap: Map>, structName: string, + propertyName: string, annotationName: string): void { + if (!propertyMap.has(structName)) { + propertyMap.set(structName, new Map()); + } + const structProperties = propertyMap.get(structName); + if (structProperties) { + structProperties.set(propertyName, annotationName); + } +} +// categorizePropertyBasedOnAnnotations +function checkPropertyByAnnotations( + item: arkts.AstNode, + structName: string, + mustInitMap: Map>, + shouldInitMap: Map>, + cannotInitMap: Map> +): void { + if (!arkts.isClassProperty(item) || !item.key || !arkts.isIdentifier(item.key)) { + return; + } + const propertyName: string = item.key.name; + if (item.annotations.length === 0 || propertyName === '') { + return; + } + const annotationArray: string[] = getClassPropertyAnnotationNames(item); + // If the member variable is decorated, it is added to the corresponding map + mustInitInConstructorDecorators.forEach(arr => { + if (arr.every(annotation => annotationArray.includes(annotation))) { + const annotationName: string = arr[0]; + addProperty(mustInitMap, structName, propertyName, annotationName); + } + }); + notAllowInitInConstructorDecorators.forEach(arr => { + if (arr.every(annotation => annotationArray.includes(annotation))) { + const annotationName: string = arr[0]; + addProperty(cannotInitMap, structName, propertyName, annotationName); + } + }); + shouldInitInConstructorDecorators.forEach(arr => { + if (arr.every(annotation => annotationArray.includes(annotation)) && + !annotationArray.includes(PresetDecorators.REQUIRE)) { + const annotationName: string = arr[0]; + addProperty(shouldInitMap, structName, propertyName, annotationName); + } + }); +} + +function initMap( + node: arkts.AstNode, + mustInitMap: Map>, + shouldInitMap: Map>, + cannotInitMap: Map> +): void { + if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { + return; + } + node.getChildren().forEach((member) => { + if (!arkts.isStructDeclaration(member)) { + return; + } + if (!member.definition || !member.definition.ident || !arkts.isIdentifier(member.definition.ident)) { + return; + } + const structName: string = member.definition.ident.name; + if (structName === '') { + return; + } + member.definition?.body.forEach((item) => { + checkPropertyByAnnotations(item, structName, mustInitMap, shouldInitMap, cannotInitMap); + }); + }); +} + +function getChildKeyNameArray(node: arkts.CallExpression): string[] { + const childkeyNameArray: string[] = []; + node.arguments.forEach((member) => { + member.getChildren().forEach((property) => { + if (!arkts.isProperty(property)) { + return; + } + if (!property.key || !arkts.isIdentifier(property.key)) { + return; + } + const childkeyName = property.key.name; + if (childkeyName !== '') { + childkeyNameArray.push(childkeyName); + } + }); + }); + return childkeyNameArray; +} + +function checkMustInitialize( + node: arkts.AstNode, + context: UISyntaxRuleContext, + mustInitMap: Map> +): void { + if (!arkts.isIdentifier(node)) { + return; + } + const structName: string = getIdentifierName(node); + if (!mustInitMap.has(structName)) { + return; + } + const parentNode: arkts.AstNode = node.parent; + if (!arkts.isCallExpression(parentNode)) { + return; + } + // Get all the properties of a record via StructName + const mustInitProperty: Map = mustInitMap.get(structName)!; + const childkeyNameArray: string[] = getChildKeyNameArray(parentNode); + // If an attribute that must be initialized is not initialized, an error is reported + mustInitProperty.forEach((value, key) => { + if (!childkeyNameArray.includes(key)) { + context.report({ + node: parentNode, + message: rule.messages.requireVariableInitializationViaComponentConstructor, + data: { + varName: key, + }, + }); + } + }); +} + +function checkShouldInitialize( + node: arkts.AstNode, + context: UISyntaxRuleContext, + shouldInitMap: Map>, +): void { + if (!arkts.isIdentifier(node)) { + return; + } + const structName: string = getIdentifierName(node); + if (!shouldInitMap.has(structName)) { + return; + } + const parentNode: arkts.AstNode = node.parent; + if (!arkts.isCallExpression(parentNode)) { + return; + } + // Get all the properties of a record via StructName + const shouldInitProperty: Map = shouldInitMap.get(structName)!; + const childkeyNameArray: string[] = getChildKeyNameArray(parentNode); + // If the attribute that should be initialized is not initialized, an error is reported + shouldInitProperty.forEach((value, key) => { + if (!childkeyNameArray.includes(key)) { + context.report({ + node: parentNode, + message: rule.messages.shouldInitializeViaComponentConstructor, + data: { + varName: `@${key}`, + customComponentName: structName, + }, + }); + } + }); +} + +function checkCannotInitialize( + node: arkts.AstNode, + context: UISyntaxRuleContext, + cannotInitMap: Map> +): void { + if (!arkts.isIdentifier(node)) { + return; + } + const structName: string = getIdentifierName(node); + if (!cannotInitMap.has(structName)) { + return; + } + const parentNode: arkts.AstNode = node.parent; + if (!arkts.isCallExpression(parentNode)) { + return; + } + // Get all the properties of a record via StructName + const cannotInitName: Map = cannotInitMap.get(structName)!; + parentNode.arguments.forEach((member) => { + member.getChildren().forEach((property) => { + if (!arkts.isProperty(property)) { + return; + } + if (!property.key || !arkts.isIdentifier(property.key)) { + return; + } + const propertyName = property.key.name; + // If a property that cannot be initialized is initialized, an error is reported + if (cannotInitName.has(propertyName)) { + const propertyType: string = cannotInitName.get(propertyName)!; + context.report({ + node: property.key, + message: rule.messages.disallowVariableInitializationViaComponentConstructor, + data: { + decoratorName: `@${propertyType}`, + varName: propertyName, + customComponentName: structName + }, + }); + } + }); + }); +} + +const rule: UISyntaxRule = { + name: 'variable-initialization-via-component-constructor', + messages: { + requireVariableInitializationViaComponentConstructor: `'@Require' decorated '{{varName}}' must be initialized through the component constructor.`, + shouldInitializeViaComponentConstructor: `The property '{{varName}}' in the custom component '{{customComponentName}}' is missing (mandatory to specify).`, + disallowVariableInitializationViaComponentConstructor: `The '{{decoratorName}}' property '{{varName}}' in the custom component '{{customComponentName}}' cannot be initialized here (forbidden to specify).`, + }, + setup(context) { + let mustInitMap: Map> = new Map(); + let shouldInitMap: Map> = new Map(); + let cannotInitMap: Map> = new Map(); + return { + parsed: (node): void => { + initMap(node, mustInitMap, shouldInitMap, cannotInitMap); + checkMustInitialize(node, context, mustInitMap); + checkShouldInitialize(node, context, shouldInitMap); + checkCannotInitialize(node, context, cannotInitMap); + }, + }; + }, +}; + +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 87ba336a7ce55c0fcc12bce3a392b2b1d2dc0898..0aa64047b950ae39f2955257baf9cb4befe6fa50 100644 --- a/arkui-plugins/ui-syntax-plugins/utils/index.ts +++ b/arkui-plugins/ui-syntax-plugins/utils/index.ts @@ -58,6 +58,12 @@ export const PresetDecorators = { const PUBLIC_PROPERTY_MODIFIERS: Number = 4; const PROTECTED_PROPERTY_MODIFIERS: Number = 8; const PRIVATE_PROPERTY_MODIFIERS: Number = 16; + +export const ReuseConstants = { + REUSE: 'reuse', + REUSE_ID: 'reuseId', +}; + export function getIdentifierName(node: arkts.AstNode): string { if (!arkts.isIdentifier(node)) { throw new Error(`Except a Identifier type!`);