diff --git a/arkui-plugins/ui-syntax-plugins/rules/component-componentV2-init-check.ts b/arkui-plugins/ui-syntax-plugins/rules/component-componentV2-init-check.ts new file mode 100644 index 0000000000000000000000000000000000000000..df746ab4c0d7fe7dda2fdc311dfa0dac6d555074 --- /dev/null +++ b/arkui-plugins/ui-syntax-plugins/rules/component-componentV2-init-check.ts @@ -0,0 +1,88 @@ +/* + * 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 { getAnnotationUsage, getIdentifierName, hasAnnotation, PresetDecorators } from '../utils'; +import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; + +function initComponentV1WithLinkList(node: arkts.AstNode, componentV1WithLinkList: string[]): void { + if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { + return; + } + node.getChildren().forEach((member) => { + if (!arkts.isStructDeclaration(member) || !member.definition.ident || + !hasAnnotation(member?.definition.annotations, PresetDecorators.COMPONENT_V1)) { + return; + } + let structName: string = member.definition.ident?.name ?? ''; + member.definition?.body?.forEach((item) => { + if (!arkts.isClassProperty(item) || !item.key) { + return; + } + if (item.annotations.some(annotation => annotation.expr && + getIdentifierName(annotation.expr) === PresetDecorators.LINK)) { + componentV1WithLinkList.push(structName); + } + }); + }); +} + +function checkComponentInitLink( + node: arkts.AstNode, + context: UISyntaxRuleContext, + componentV1WithLinkList: string[] +): void { + if (!arkts.isIdentifier(node) || !componentV1WithLinkList.includes(getIdentifierName(node))) { + return; + } + let parentNode = node.parent; + while (!arkts.isStructDeclaration(parentNode)) { + if (!parentNode.parent) { + return; + } + parentNode = parentNode.parent; + } + if (getAnnotationUsage(parentNode, PresetDecorators.COMPONENT_V2) !== undefined) { + context.report({ + node: node.parent, + message: rule.messages.componentInitLinkCheck, + fix: (node) => { + return { + range: [node.startPosition, node.endPosition], + code: '', + }; + } + }); + } +} + +const rule: UISyntaxRule = { + name: 'component-componentV2-init-check', + messages: { + componentInitLinkCheck: `A V2 component cannot be used with any member property decorated by '@Link' in a V1 component.`, + }, + setup(context) { + // Decorated by component v1 and uses the link attribute + let componentV1WithLinkList: string[] = []; + return { + parsed: (node): void => { + initComponentV1WithLinkList(node, componentV1WithLinkList); + checkComponentInitLink(node, context, componentV1WithLinkList); + }, + }; + }, +}; + +export default rule; \ No newline at end of file diff --git a/arkui-plugins/ui-syntax-plugins/rules/no-prop-link-objectlink-in-entry.ts b/arkui-plugins/ui-syntax-plugins/rules/no-prop-link-objectlink-in-entry.ts index 8815feff46576bfaec7dc8c0713ea58a62144993..01e12efda2c4053c6fe4589c882158f1d017bc7a 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/no-prop-link-objectlink-in-entry.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/no-prop-link-objectlink-in-entry.ts @@ -17,9 +17,15 @@ import * as arkts from '@koalaui/libarkts'; import { getAnnotationUsage, PresetDecorators } from '../utils'; import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; +const invalidDecorators = [PresetDecorators.PROP, PresetDecorators.LINK, PresetDecorators.OBJECT_LINK]; + function checkNoPropLinkOrObjectLinkInEntry(context: UISyntaxRuleContext, node: arkts.StructDeclaration): void { // Check if the struct has the @Entry decorator const isEntryComponent = !!getAnnotationUsage(node, PresetDecorators.ENTRY); + if (!node.definition.ident || !arkts.isIdentifier(node.definition.ident)) { + return; + } + const componentName = node.definition.ident.name; if (!isEntryComponent) { return; } @@ -27,25 +33,32 @@ function checkNoPropLinkOrObjectLinkInEntry(context: UISyntaxRuleContext, node: if (!arkts.isClassProperty(body)) { return; } - const invalidDecorators = [PresetDecorators.PROP, PresetDecorators.LINK, PresetDecorators.OBJECT_LINK]; + if (!body.key || !arkts.isIdentifier(body.key)) { + return; + } + const propertyName = body.key.name; // Check if any invalid decorators are applied to the class property body.annotations?.forEach(annotation => { - reportInvalidDecorator(context, annotation, invalidDecorators); + reportInvalidDecorator(context, annotation, invalidDecorators, componentName, propertyName); }); }); } function reportInvalidDecorator(context: UISyntaxRuleContext, annotation: arkts.AnnotationUsage, - invalidDecorators: string[],): void { - if (annotation.expr && invalidDecorators.includes(annotation.expr.dumpSrc())) { - const decorator = annotation.expr.dumpSrc(); + invalidDecorators: string[], componentName: string, propertyName: string): void { + if (annotation.expr && arkts.isIdentifier(annotation.expr) && invalidDecorators.includes(annotation.expr.name)) { + const decoratorName = annotation.expr.name; context.report({ node: annotation, - message: rule.messages.invalidUsage, - data: { decorator }, + message: rule.messages.disallowDecoratorInEntry, + data: { + componentName, + decoratorName, + propertyName, + }, fix: (annotation) => { - const startPosition = arkts.getStartPosition(annotation); - const endPosition = arkts.getEndPosition(annotation); + const startPosition = annotation.startPosition; + const endPosition = annotation.endPosition; return { range: [startPosition, endPosition], code: '', @@ -58,7 +71,7 @@ function reportInvalidDecorator(context: UISyntaxRuleContext, annotation: arkts. const rule: UISyntaxRule = { name: 'no-prop-link-objectlink-in-entry', messages: { - invalidUsage: `@{{decorator}} decorator cannot be used for '@Entry' decorated components.`, + disallowDecoratorInEntry: `The '@Entry' component '{{componentName}}' cannot have the '@{{decoratorName}}' property '{{propertyName}}'.`, }, setup(context) { return { diff --git a/arkui-plugins/ui-syntax-plugins/rules/one-decorator-on-function-method.ts b/arkui-plugins/ui-syntax-plugins/rules/one-decorator-on-function-method.ts index 66dd9cdbd86d4d9028486a94d949d67bfa80c53b..2e9f11fbf250cb483baee025d3128a49b6bc15e3 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/one-decorator-on-function-method.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/one-decorator-on-function-method.ts @@ -14,10 +14,11 @@ */ import * as arkts from '@koalaui/libarkts'; -import { getAnnotationName } from '../utils'; +import { getAnnotationName, PresetDecorators } from '../utils'; import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; -const allowedDecorators = new Set(['Extend', 'Builder', 'Styles']); +const allowedDecorators = PresetDecorators.BUILDER; +const DECORATOR_LIMIT = 1; function validateFunctionDecorator(node: arkts.EtsScript, context: UISyntaxRuleContext): void { node.statements.forEach((statement) => { @@ -30,27 +31,48 @@ function validateFunctionDecorator(node: arkts.EtsScript, context: UISyntaxRuleC if (!annotations) { return; } + const otherDecorator = annotations?.find(annotation => + annotation.expr && arkts.isIdentifier(annotation.expr) && + annotation.expr.name !== PresetDecorators.BUILDER + ); // Check that each annotation is in the list of allowed decorators annotations.forEach((annotation) => { const decoratorName = getAnnotationName(annotation); - // rule1: misuse of decorator, only '@Extend', '@Builder' , '@Styles' decorators allowed on global functions - if (!allowedDecorators.has(decoratorName)) { - context.report({ - node: annotation, - message: rule.messages.invalidDecorator, - data: { - decoratorName, - }, - }); + // rule1: misuse of decorator, only '@Builder' , '@Styles' decorators allowed on global functions + if (allowedDecorators !== decoratorName || + (allowedDecorators === decoratorName && decoratorName.length > DECORATOR_LIMIT)) { + reportInvalidDecorator(annotation, otherDecorator, context); } }); }); } +function reportInvalidDecorator( + annotation: arkts.AnnotationUsage, + otherDecorator: arkts.AnnotationUsage | undefined, + context: UISyntaxRuleContext +): void { + if (!otherDecorator) { + return; + } + context.report({ + node: annotation, + message: rule.messages.invalidDecorator, + fix: () => { + const startPosition = otherDecorator.startPosition; + const endPosition = otherDecorator.endPosition; + return { + range: [startPosition, endPosition], + code: `` + }; + } + }); +} + const rule: UISyntaxRule = { name: 'one-decorator-on-function-method', messages: { - invalidDecorator: `misuse of '@{{decoratorName}}' decorator, only '@Extend', '@Builder' and '@Styles' decorators allowed on global functions.`, + invalidDecorator: `A function can only be decorated by the 'Builder'.`, }, setup(context) { return { diff --git a/arkui-plugins/ui-syntax-plugins/rules/reusableV2-decorator-check.ts b/arkui-plugins/ui-syntax-plugins/rules/reusableV2-decorator-check.ts new file mode 100644 index 0000000000000000000000000000000000000000..41bee3a605cc350933a4b4212c5064143594b942 --- /dev/null +++ b/arkui-plugins/ui-syntax-plugins/rules/reusableV2-decorator-check.ts @@ -0,0 +1,84 @@ +/* + * 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 { PresetDecorators, getAnnotationUsage } from '../utils'; +import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; + +function reportInvalidDecoratorUsage( + reusableDocoratorUsage: arkts.AnnotationUsage | undefined, + structNode: arkts.Identifier | undefined, + context: UISyntaxRuleContext +): void { + if (!structNode || !reusableDocoratorUsage) { + return; + } + context.report({ + node: structNode, + message: rule.messages.conflictingDecorators, + }); +} + +function reportConflictingDecorators( + node: arkts.StructDeclaration, + structNode: arkts.Identifier | undefined, + context: UISyntaxRuleContext +): void { + if (!structNode || !node) { + return; + } + context.report({ + node: structNode, + message: rule.messages.invalidDecoratorUsage, + }); +} + +const rule: UISyntaxRule = { + name: 'reusableV2-decorator-check', + messages: { + conflictingDecorators: `The '@Reusable' and '@ReusableV2' decorators cannot be applied simultaneously.`, + invalidDecoratorUsage: `@ReusableV2 is only applicable to custom components decorated by @ComponentV2.`, + }, + setup(context) { + return { + parsed: (node): void => { + if (!arkts.isStructDeclaration(node)) { + return; + } + if (!node.definition) { + return; + } + if (!arkts.isClassDefinition(node.definition)) { + return; + } + const structNode = node.definition.ident; + // Check whether the decoration exists, and mark true if it does + const reusableDocoratorUsage = getAnnotationUsage(node, PresetDecorators.REUSABLE_V1); + const reusableV2DocoratorUsage = getAnnotationUsage(node, PresetDecorators.REUSABLE_V2); + const componnetV2DocoratorUsage = getAnnotationUsage(node, PresetDecorators.COMPONENT_V2); + // Check whether @Reusable and @ReusableV2 exist at the same time + if (reusableV2DocoratorUsage && reusableDocoratorUsage && structNode) { + reportInvalidDecoratorUsage(reusableDocoratorUsage, structNode, context); + } + // Check if @ReusableV2 is applied to a class decorated by @ComponentV2 + if (reusableV2DocoratorUsage && !componnetV2DocoratorUsage && structNode) { + reportConflictingDecorators(node, structNode, context); + } + }, + }; + }, +}; + +export default rule; diff --git a/arkui-plugins/ui-syntax-plugins/rules/specific-component-children.ts b/arkui-plugins/ui-syntax-plugins/rules/specific-component-children.ts new file mode 100644 index 0000000000000000000000000000000000000000..279ea271e1880ad290538e3df1b9de0b56f88fda --- /dev/null +++ b/arkui-plugins/ui-syntax-plugins/rules/specific-component-children.ts @@ -0,0 +1,113 @@ +/* + * 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, + PresetDecorators, + SINGLE_CHILD_COMPONENT, + TOGGLE_TYPE, + ToggleType, + TYPE +} from '../utils'; +import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; + +function getToggleType(node: arkts.CallExpression): string { + let toggleType = ''; + node.arguments.forEach((member) => { + if (!arkts.isObjectExpression(member) || !member.properties) { + return; + } + member.properties.forEach((property) => { + if (!arkts.isProperty(property) || !property.value) { + return; + } + // If the property name is not 'toggle type' + if (!arkts.isMemberExpression(property.value) || !property.value.object || + !arkts.isIdentifier(property.value.object) || property.value.object.name !== TOGGLE_TYPE) { + return; + } + if (!property.value.property || !arkts.isIdentifier(property.value.property)) { + return; + } + toggleType = property.value.property.name; + }); + }); + return toggleType; +} + +function checkSingleChildComponent(node: arkts.AstNode, context: UISyntaxRuleContext): void { + // Check whether the current node is an identifier and toggle component + if (!arkts.isIdentifier(node)) { + return; + } + const componentName: string = getIdentifierName(node); + if (componentName !== PresetDecorators.TOGGLE || !node.parent) { + return; + } + const parentNode = node.parent; + if (!arkts.isCallExpression(parentNode)) { + return; + } + const toggleTypeValue = getToggleType(parentNode); + // If there is more than one subcomponent in the BlockStatement, an error is reported + parentNode.getChildren().forEach(member => { + if (!arkts.isBlockStatement(member)) { + return; + } + // If the toggle component type is checkbox and has child components, an error is reported + if (toggleTypeValue === ToggleType.CHECKBOX && member.statements.length > 0) { + context.report({ + node: node, + message: rule.messages.toggleTypeCheckboxWithNoChild, + data: { + componentName: componentName, + toggleTypeKey: TYPE, + toggleTypeValue: toggleTypeValue + } + }); + } + // If the toggle component type is button and there is more than one child component, an error is reported + if (toggleTypeValue === ToggleType.BUTTON && member.statements.length > SINGLE_CHILD_COMPONENT) { + context.report({ + node: node, + message: rule.messages.toggleTypeButtonWithsingleChild, + data: { + componentName: componentName, + toggleTypeKey: TYPE, + toggleTypeValue: toggleTypeValue + } + }); + } + }); +} + + +const rule: UISyntaxRule = { + name: 'specific-component-children', + messages: { + toggleTypeCheckboxWithNoChild: `When the component '{{componentName}}' set '{{toggleTypeKey}}' as '{{toggleTypeValue}}', it can't have any child.`, + toggleTypeButtonWithsingleChild: `When the component '{{componentName}}' set '{{toggleTypeKey}}' as '{{toggleTypeValue}}', it can only have a single child component.`, + }, + setup(context) { + return { + parsed: (node): void => { + checkSingleChildComponent(node, context); + }, + }; + }, +}; + +export default rule; \ No newline at end of file diff --git a/arkui-plugins/ui-syntax-plugins/rules/struct-missing-decorator.ts b/arkui-plugins/ui-syntax-plugins/rules/struct-missing-decorator.ts index 7ca8e7b3a619c974d1eea7dae318313ff951ff47..6f3019c5a309b7b4ee3b2ca07b2ca664616adabd 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/struct-missing-decorator.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/struct-missing-decorator.ts @@ -20,9 +20,7 @@ import { UISyntaxRule } from './ui-syntax-rule'; const rule: UISyntaxRule = { name: 'struct-missing-decorator', messages: { - missingComponentDecorator: `struct '{{structName}}' is missing '@Component' or '@CustomDialog' decorators`, - misusedPreview: `struct '{{structName}}' misused '@Preview' decorator. it should be decorated by '@Component' or '@CustomDialog'`, - misusedObserved: `struct '{{structName}}' misused '@Observed' decorator. it should be decorated by '@Component' or '@CustomDialog'`, + missingComponentDecorator: `Decorator '@Component', '@ComponentV2', or '@CustomDialog' is missing for struct '{{structName}}'.` }, setup(context) { function hasDecorator(node: arkts.StructDeclaration, decorator: string): boolean { @@ -34,14 +32,18 @@ const rule: UISyntaxRule = { if (!arkts.isStructDeclaration(node)) { return; } + if (!node.definition) { + return; + } + if (!arkts.isClassDefinition(node.definition)) { + return; + } // Check for the presence of specific decorators on the struct const structName = node.definition.ident?.name ?? ''; const structNode = node.definition.ident; const hasComponent = hasDecorator(node, PresetDecorators.COMPONENT_V1); const hasComponentV2 = hasDecorator(node, PresetDecorators.COMPONENT_V2); const hasCustomDialog = hasDecorator(node, PresetDecorators.CUSTOM_DIALOG); - const hasPreview = getAnnotationUsage(node, PresetDecorators.PREVIEW); - const hasObserved = getAnnotationUsage(node, PresetDecorators.OBSERVED_V1); // If no valid component decorators (@Component or @CustomDialog) are found if (!hasComponent && !hasComponentV2 && !hasCustomDialog && structNode) { context.report({ @@ -50,22 +52,6 @@ const rule: UISyntaxRule = { data: { structName }, }); } - // If the @Preview decorator is used but the struct is not decorated with @Component or @CustomDialog - if (hasPreview && !hasComponent && !hasComponentV2 && !hasCustomDialog && structNode) { - context.report({ - node: structNode, - message: rule.messages.misusedPreview, - data: { structName }, - }); - } - // If the @Observed decorator is used but the struct is not decorated with @Component or @CustomDialog - if (hasObserved && !hasComponent && !hasComponentV2 && !hasCustomDialog && structNode) { - context.report({ - node: structNode, - message: rule.messages.misusedObserved, - data: { structName }, - }); - } }, }; }, diff --git a/arkui-plugins/ui-syntax-plugins/rules/struct-no-extends.ts b/arkui-plugins/ui-syntax-plugins/rules/struct-no-extends.ts new file mode 100644 index 0000000000000000000000000000000000000000..99018623dd3e202afddf1acc3350e9382b176284 --- /dev/null +++ b/arkui-plugins/ui-syntax-plugins/rules/struct-no-extends.ts @@ -0,0 +1,52 @@ +/* + * 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'; + +function checkExtendOrImplement( + node: arkts.AstNode, + context: UISyntaxRuleContext +): void { + if (!arkts.isStructDeclaration(node) || !node.definition.ident) { + return; + } + const hasSuperClass: boolean = node.definition.super !== undefined; + const hasImplements: boolean = node.definition.implements.length > 0; + // If there is an inheritance class or implementation interface, an error is reported + if (hasSuperClass || hasImplements) { + const errorNode = node.definition.ident; + context.report({ + node: errorNode, + message: rule.messages.structNoExtends, + }); + } +} + +const rule: UISyntaxRule = { + name: 'struct-no-extends', + messages: { + structNoExtends: `Structs are not allowed to inherit from classes or implement interfaces.`, + }, + setup(context) { + return { + parsed: (node): void => { + checkExtendOrImplement(node, context); + }, + }; + }, +}; + +export default rule; diff --git a/arkui-plugins/ui-syntax-plugins/rules/validate-build-in-struct.ts b/arkui-plugins/ui-syntax-plugins/rules/validate-build-in-struct.ts index 938c3a210931105d572ea62ab5011d5b68b281b3..f5d308652448e0e43395c43df45cf5ee36f1f0ac 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/validate-build-in-struct.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/validate-build-in-struct.ts @@ -44,8 +44,8 @@ function reportBuildParamNotAllowed( node: param, message: rule.messages.invalidComponet, fix: (param) => { - const startPosition = arkts.getStartPosition(param); - const endPosition = arkts.getEndPosition(param); + const startPosition = param.startPosition; + const endPosition = param.endPosition; return { range: [startPosition, endPosition], code: '' @@ -83,8 +83,11 @@ function reportMissingBuildInStruct( context.report({ node: structName, message: rule.messages.invalidBuild, + data: { + structName: getIdentifierName(structName), + }, fix: (structName) => { - const startPosition = arkts.getStartPosition(blockStatement); + const startPosition = blockStatement.startPosition; const endPosition = startPosition; return { range: [startPosition, endPosition], @@ -101,7 +104,7 @@ function validateBuild( ): void { node.definition.body.forEach((member) => { // Check if the member is defined for the method and the method name is 'build' - if (arkts.isMethodDefinition(member) && getIdentifierName(member.name) === BUILD_NAME) { + if (arkts.isMethodDefinition(member) && arkts.isIdentifier(member.name) && getIdentifierName(member.name) === BUILD_NAME) { buildFunctionCount++; validateBuildFunctionParameters(member, context); } @@ -116,8 +119,8 @@ function validateBuild( const rule: UISyntaxRule = { name: 'validate-build-in-struct', messages: { - invalidComponet: `A custom component can have only one 'build' function, which does not require parameters.`, - invalidBuild: `This rule validates the use of the 'build' function`, + invalidComponet: `The 'build' method can not have arguments.`, + invalidBuild: `The struct '{{structName}}' must have at least and at most one 'build' method.`, }, setup(context) { return { diff --git a/arkui-plugins/ui-syntax-plugins/utils/index.ts b/arkui-plugins/ui-syntax-plugins/utils/index.ts index 87ba336a7ce55c0fcc12bce3a392b2b1d2dc0898..16b42fc35f445f49b40e3743739da07207a12f27 100644 --- a/arkui-plugins/ui-syntax-plugins/utils/index.ts +++ b/arkui-plugins/ui-syntax-plugins/utils/index.ts @@ -170,6 +170,12 @@ export class MultiMap { } } +export function hasAnnotation(annoArray: readonly arkts.AnnotationUsage[], annotationName: string): boolean { + return (annoArray || []).some(anno => + anno.expr && getIdentifierName(anno.expr) === annotationName + ); +} + interface ComponentJson { name: string; atomic?: boolean;