1/* 2 * Copyright (c) 2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import * as arkts from '@koalaui/libarkts'; 17import { PresetDecorators } from '../utils'; 18import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; 19 20// Report an Observed version violation error 21function reportObservedConflict( 22 node: arkts.ClassProperty, 23 context: UISyntaxRuleContext, 24 message: string 25): void { 26 node.annotations.forEach((anno) => { 27 if (anno.expr?.dumpSrc()) { 28 context.report({ 29 node: anno, 30 message: message, 31 data: { 32 annotation: anno.expr?.dumpSrc(), 33 } 34 }); 35 } 36 }); 37} 38 39function processNode( 40 node: arkts.ClassProperty, 41 annotationName: string, 42 observedV1Name: Set<string>, 43 observedV2Name: Set<string>, 44 context: UISyntaxRuleContext 45): void { 46 const queue: Array<arkts.AstNode> = [node]; 47 while (queue.length > 0) { 48 const currentNode: arkts.AstNode = queue.shift() as arkts.AstNode; 49 if (arkts.isIdentifier(currentNode)) { 50 if (observedV1Name.has(currentNode.dumpSrc()) && annotationName === PresetDecorators.COMPONENT_V2) { 51 reportObservedConflict(node, context, rule.messages.observedv1_v2); 52 break; 53 } 54 if (observedV2Name.has(currentNode.dumpSrc()) && annotationName === PresetDecorators.COMPONENT_V1) { 55 reportObservedConflict(node, context, rule.messages.observedv2_v1); 56 break; 57 } 58 } 59 const children = currentNode.getChildren(); 60 for (const child of children) { 61 queue.push(child); 62 } 63 } 64} 65 66function traverseTree( 67 node: arkts.AstNode, 68 annotationName: string, 69 observedV1Name: Set<string>, 70 observedV2Name: Set<string>, 71 context: UISyntaxRuleContext 72): void { 73 if (arkts.isClassProperty(node)) { 74 processNode(node, annotationName, observedV1Name, observedV2Name, context); 75 } 76 const children = node.getChildren(); 77 for (const child of children) { 78 traverseTree(child, annotationName, observedV1Name, observedV2Name, context); 79 } 80} 81 82function findAllObserved(node: arkts.AstNode, observedV1Name: Set<string>, observedV2Name: Set<string>): void { 83 if (arkts.isClassDeclaration(node)) { 84 node.definition?.annotations.forEach((anno) => { 85 if (anno.expr?.dumpSrc() === PresetDecorators.OBSERVED_V1) { 86 const componentV1Name = node?.definition?.ident?.name; 87 componentV1Name ? observedV1Name.add(componentV1Name) : null; 88 } 89 if (anno.expr?.dumpSrc() === PresetDecorators.OBSERVED_V2) { 90 const componentV2Name = node?.definition?.ident?.name; 91 componentV2Name ? observedV2Name.add(componentV2Name) : null; 92 } 93 }); 94 } 95 const children = node.getChildren(); 96 for (const child of children) { 97 findAllObserved(child, observedV1Name, observedV2Name); 98 } 99} 100 101function findAllTSTypeAliasDeclaration( 102 node: arkts.AstNode, 103 observedV1Name: Set<string>, 104 observedV2Name: Set<string> 105): void { 106 if (arkts.nodeType(node) === arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_TYPE_ALIAS_DECLARATION) { 107 node.getChildren().forEach((child) => { 108 if (arkts.isIdentifier(child)) { 109 const typeName = child.dumpSrc(); 110 findAllObservedType(node, typeName, observedV1Name, observedV2Name); 111 } 112 }); 113 } 114 const children = node.getChildren(); 115 for (const child of children) { 116 findAllTSTypeAliasDeclaration(child, observedV1Name, observedV2Name); 117 } 118} 119 120function findAllObservedType( 121 node: arkts.AstNode, 122 typeName: string, 123 observedV1Name: Set<string>, 124 observedV2Name: Set<string> 125): void { 126 if (arkts.isIdentifier(node) && observedV1Name.has(node.dumpSrc())) { 127 observedV1Name.add(typeName); 128 } 129 if (arkts.isIdentifier(node) && observedV2Name.has(node.dumpSrc())) { 130 observedV2Name.add(typeName); 131 } 132 const children = node.getChildren(); 133 for (const child of children) { 134 findAllObservedType(child, typeName, observedV1Name, observedV2Name); 135 } 136} 137 138function processComponentAnnotations( 139 node: arkts.StructDeclaration, 140 observedV1Name: Set<string>, 141 observedV2Name: Set<string>, 142 context: UISyntaxRuleContext 143): void { 144 node?.definition?.annotations.forEach((anno) => { 145 if (anno.expr?.dumpSrc() === PresetDecorators.COMPONENT_V2) { 146 traverseTree(node, PresetDecorators.COMPONENT_V2, observedV1Name, observedV2Name, context); 147 } 148 if (anno.expr?.dumpSrc() === PresetDecorators.COMPONENT_V1) { 149 traverseTree(node, PresetDecorators.COMPONENT_V1, observedV1Name, observedV2Name, context); 150 } 151 }); 152} 153 154const rule: UISyntaxRule = { 155 name: 'component-componentV2-mix-use-check', 156 messages: { 157 observedv1_v2: `The type of the @{{annotation}} Decorator property can not be a class decorated with @Observed.`, 158 observedv2_v1: `The type of the @{{annotation}} Decorator property can not be a class decorated with @ObservedV2.` 159 }, 160 setup(context) { 161 let observedV1Name: Set<string> = new Set(); 162 let observedV2Name: Set<string> = new Set(); 163 return { 164 parsed: (node): void => { 165 if (arkts.nodeType(node) === arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { 166 findAllObserved(node, observedV1Name, observedV2Name); 167 findAllTSTypeAliasDeclaration(node, observedV1Name, observedV2Name); 168 } 169 if (arkts.isStructDeclaration(node)) { 170 processComponentAnnotations(node, observedV1Name, observedV2Name, context); 171 } 172 }, 173 }; 174 }, 175}; 176 177export default rule;