• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;