UNPKG

@angular-eslint/eslint-plugin

Version:

ESLint plugin for Angular applications, following https://angular.dev/style-guide

157 lines (156 loc) 7.57 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RULE_DOCS_EXTENSION = exports.RULE_NAME = void 0; const utils_1 = require("@angular-eslint/utils"); const create_eslint_rule_1 = require("../utils/create-eslint-rule"); exports.RULE_NAME = 'directive-selector'; exports.default = (0, create_eslint_rule_1.createESLintRule)({ name: exports.RULE_NAME, meta: { type: 'suggestion', docs: { description: 'Directive selectors should follow given naming rules. See more at https://angular.dev/style-guide#choosing-directive-selectors.', }, schema: [ { oneOf: [ // Single config object { type: 'object', properties: { type: { oneOf: [ { type: 'string' }, { type: 'array', items: { type: 'string', enum: [ utils_1.SelectorUtils.OPTION_TYPE_ELEMENT, utils_1.SelectorUtils.OPTION_TYPE_ATTRIBUTE, ], }, }, ], }, prefix: { oneOf: [{ type: 'string' }, { type: 'array' }], }, style: { type: 'string', enum: [ utils_1.ASTUtils.OPTION_STYLE_CAMEL_CASE, utils_1.ASTUtils.OPTION_STYLE_KEBAB_CASE, ], }, }, required: ['type', 'style'], additionalProperties: false, }, // Array of 1-2 config objects { type: 'array', items: { type: 'object', properties: { type: { type: 'string', enum: [ utils_1.SelectorUtils.OPTION_TYPE_ELEMENT, utils_1.SelectorUtils.OPTION_TYPE_ATTRIBUTE, ], }, prefix: { oneOf: [{ type: 'string' }, { type: 'array' }], }, style: { type: 'string', enum: [ utils_1.ASTUtils.OPTION_STYLE_CAMEL_CASE, utils_1.ASTUtils.OPTION_STYLE_KEBAB_CASE, ], }, }, additionalProperties: false, required: ['type', 'style'], }, minItems: 1, maxItems: 2, }, ], }, ], messages: { prefixFailure: 'The selector should start with one of these prefixes: {{prefix}}', styleFailure: 'The selector should be {{style}}', typeFailure: 'The selector should be used as an {{type}}', selectorAfterPrefixFailure: `There should be a selector after the {{prefix}} prefix`, }, }, defaultOptions: [ { type: undefined, prefix: 'app', // Match default Angular CLI prefix style: undefined, }, ], create(context, [options]) { // Options are required by schema, so if undefined, ESLint will throw an error if (!options) { return {}; } // Normalize options to a consistent format using shared utility const configByType = utils_1.SelectorUtils.normalizeOptionsToConfigs(options); return { [utils_1.Selectors.DIRECTIVE_CLASS_DECORATOR](node) { const rawSelectors = utils_1.ASTUtils.getDecoratorPropertyValue(node, 'selector'); if (!rawSelectors) { return; } // Parse selectors once for reuse const parsedSelectors = utils_1.SelectorUtils.parseSelectorNode(rawSelectors); if (!parsedSelectors || parsedSelectors.length === 0) { return; } const applicableConfig = utils_1.SelectorUtils.getApplicableConfig(rawSelectors, configByType); if (!applicableConfig) { return; } const { type, prefix, style } = applicableConfig; const isValidOptions = utils_1.SelectorUtils.checkValidOptions(type, prefix, style); if (!isValidOptions) { return; } const hasExpectedSelector = utils_1.SelectorUtils.checkSelector(rawSelectors, type, prefix, style, parsedSelectors); if (hasExpectedSelector === null) { return; } // Directive-specific validation logic (simpler than component) if (!hasExpectedSelector.hasExpectedType) { utils_1.SelectorUtils.reportTypeError(rawSelectors, type, context); } else if (!hasExpectedSelector.hasSelectorAfterPrefix) { // Only report selector after prefix error if prefix is actually required if (prefix !== undefined) { utils_1.SelectorUtils.reportSelectorAfterPrefixError(rawSelectors, prefix, context); } } else if (!hasExpectedSelector.hasExpectedStyle) { utils_1.SelectorUtils.reportStyleError(rawSelectors, style, context); } else if (!hasExpectedSelector.hasExpectedPrefix) { // Only report prefix error if prefix is actually required (not empty) if (prefix !== undefined) { const prefixArray = (0, utils_1.arrayify)(prefix); if (prefixArray.length > 0) { utils_1.SelectorUtils.reportPrefixError(rawSelectors, prefix, context); } } } }, }; }, }); exports.RULE_DOCS_EXTENSION = { rationale: "Consistent directive selector naming conventions help identify which directives belong to your application versus third-party libraries, prevent naming collisions with native HTML attributes and other directives, and make code reviews and debugging easier. For example, using a camelCase attribute selector with a prefix like 'appHighlight' makes it immediately clear that this is a custom directive from your application.", };