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 { getIdentifierName, getClassPropertyAnnotationNames, PresetDecorators } from '../utils'; 18import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; 19 20// Define a function to add property data to the property map 21function addProperty(propertyMap: Map<string, Map<string, string>>, structName: string, 22 propertyName: string, annotationName: string): void { 23 if (!propertyMap.has(structName)) { 24 propertyMap.set(structName, new Map()); 25 } 26 const structProperties = propertyMap.get(structName); 27 if (structProperties) { 28 structProperties.set(propertyName, annotationName); 29 } 30} 31// categorizePropertyBasedOnAnnotations 32function checkPropertyByAnnotations( 33 item: arkts.AstNode, 34 structName: string, 35 mustInitMap: Map<string, Map<string, string>>, 36 cannotInitMap: Map<string, Map<string, string>> = new Map(), 37 mustInitArray: string[][], 38 cannotInitArray: string[][] 39): void { 40 if (!arkts.isClassProperty(item)) { 41 return; 42 } 43 const propertyName: string = item.key?.dumpSrc() ?? ''; 44 if (item.annotations.length === 0 || propertyName === '') { 45 return; 46 } 47 const annotationArray: string[] = getClassPropertyAnnotationNames(item); 48 // If the member variable is decorated, it is added to the corresponding map 49 mustInitArray.forEach(arr => { 50 if (arr.every(annotation => annotationArray.includes(annotation))) { 51 const annotationName: string = arr[0]; 52 addProperty(mustInitMap, structName, propertyName, annotationName); 53 } 54 }); 55 cannotInitArray.forEach(arr => { 56 if (arr.every(annotation => annotationArray.includes(annotation))) { 57 const annotationName: string = arr[0]; 58 addProperty(cannotInitMap, structName, propertyName, annotationName); 59 } 60 }); 61} 62 63function initMap( 64 node: arkts.AstNode, 65 mustInitMap: Map<string, Map<string, string>>, 66 cannotInitMap: Map<string, Map<string, string>> = new Map(), 67 mustInitArray: string[][], 68 cannotInitArray: string[][] 69): void { 70 if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { 71 return; 72 } 73 node.getChildren().forEach((member) => { 74 if (!arkts.isStructDeclaration(member)) { 75 return; 76 } 77 const structName: string = member.definition.ident?.name ?? ''; 78 if (structName === '') { 79 return; 80 } 81 member.definition?.body.forEach((item) => { 82 checkPropertyByAnnotations(item, structName, mustInitMap, cannotInitMap, mustInitArray, cannotInitArray); 83 }); 84 }); 85} 86 87function getChildKeyNameArray(member: arkts.AstNode): string[] { 88 const childkeyNameArray: string[] = []; 89 member.getChildren().forEach((property) => { 90 if (arkts.isProperty(property)) { 91 const childkeyName = property.key?.dumpSrc() ?? ''; 92 if (childkeyName !== '') { 93 childkeyNameArray.push(childkeyName); 94 } 95 } 96 }); 97 return childkeyNameArray; 98} 99 100function checkMustInitialize( 101 node: arkts.AstNode, 102 context: UISyntaxRuleContext, 103 mustInitMap: Map<string, Map<string, string>> 104): void { 105 if (!arkts.isIdentifier(node)) { 106 return; 107 } 108 const structName: string = getIdentifierName(node); 109 if (!mustInitMap.has(structName)) { 110 return; 111 } 112 const parentNode: arkts.AstNode = node.parent; 113 if (!arkts.isCallExpression(parentNode)) { 114 return; 115 } 116 // Get all the properties of a record via StructName 117 const mustInitName: Map<string, string> = mustInitMap.get(structName)!; 118 parentNode.arguments?.forEach((member) => { 119 const childkeyNameArray: string[] = getChildKeyNameArray(member); 120 mustInitName.forEach((value, key) => { 121 // If an attribute that must be initialized is not initialized, an error is reported 122 if (!childkeyNameArray.includes(key)) { 123 context.report({ 124 node: parentNode, 125 message: rule.messages.mustInitializeRule, 126 data: { 127 annotationName: value, 128 propertyName: key, 129 }, 130 }); 131 } 132 }); 133 }); 134} 135 136function checkCannotInitialize( 137 node: arkts.AstNode, 138 context: UISyntaxRuleContext, 139 cannotInitMap: Map<string, Map<string, string>> 140): void { 141 if (!arkts.isIdentifier(node)) { 142 return; 143 } 144 const structName: string = getIdentifierName(node); 145 if (!cannotInitMap.has(structName)) { 146 return; 147 } 148 const parentNode: arkts.AstNode = node.parent; 149 if (!arkts.isCallExpression(parentNode)) { 150 return; 151 } 152 // Get all the properties of a record via StructName 153 const cannotInitName: Map<string, string> = cannotInitMap.get(structName)!; 154 parentNode.arguments.forEach((member) => { 155 member.getChildren().forEach((property) => { 156 if (!arkts.isProperty(property)) { 157 return; 158 } 159 if (!property.key) { 160 return; 161 } 162 const propertyName = property.key.dumpSrc(); 163 // If a property that cannot be initialized is initialized, an error is reported 164 if (cannotInitName.has(propertyName)) { 165 context.report({ 166 node: property.key, 167 message: rule.messages.cannotInitializeRule, 168 data: { 169 annotationName: cannotInitName.get(propertyName)!, 170 propertyName: propertyName, 171 }, 172 }); 173 } 174 }); 175 }); 176} 177 178const rule: UISyntaxRule = { 179 name: 'variable-initialization-via-component-cons', 180 messages: { 181 mustInitializeRule: `'@{{annotationName}}' decorated '{{propertyName}}' must be initialized through the component constructor.`, 182 cannotInitializeRule: `'@{{annotationName}}' decorated '{{propertyName}}' cannot be initialized through the component constructor.`, 183 }, 184 setup(context) { 185 let mustInitMap: Map<string, Map<string, string>> = new Map(); 186 let cannotInitMap: Map<string, Map<string, string>> = new Map(); 187 const mustInitArray: string[][] = [ 188 [PresetDecorators.REQUIRE, PresetDecorators.PROP], 189 [PresetDecorators.REQUIRE, PresetDecorators.BUILDER_PARAM], 190 [PresetDecorators.LINK], 191 [PresetDecorators.OBJECT_LINK] 192 ]; 193 const cannotInitArray: string[][] = [ 194 [PresetDecorators.STORAGE_LINK], 195 [PresetDecorators.STORAGE_PROP], 196 [PresetDecorators.CONSUME], 197 [PresetDecorators.LOCAL_STORAGE_LINK], 198 [PresetDecorators.LOCAL_STORAGE_PROP] 199 ]; 200 return { 201 parsed: (node): void => { 202 initMap(node, mustInitMap, cannotInitMap, mustInitArray, cannotInitArray); 203 checkMustInitialize(node, context, mustInitMap); 204 checkCannotInitialize(node, context, cannotInitMap); 205 }, 206 }; 207 }, 208}; 209 210export default rule;