diff --git a/.cspell.json b/.cspell.json index 9c0cf4a5..692148eb 100644 --- a/.cspell.json +++ b/.cspell.json @@ -62,6 +62,7 @@ "cout", "nodownload", "nofullscreen", - "controlslist" + "controlslist", + "describedby" ] } diff --git a/docs/rules/sort-attrs.md b/docs/rules/sort-attrs.md index 29ff4418..f2fb7ced 100644 --- a/docs/rules/sort-attrs.md +++ b/docs/rules/sort-attrs.md @@ -31,15 +31,20 @@ Examples of **correct** code for this rule: ```ts //... "@html-eslint/sort-attrs": ["error", { - "priority": Array + "priority": Array }] ``` #### priority -This option allows you to set an array of attributes keyć„“. +This option allows you to set an array of attribute names or pattern objects. When `priority` is defined, the specified attributes are sorted to the front with the highest priority. +Priority items can be: + +- Strings: exact attribute name matches +- Objects with `pattern` property: regular expression patterns to match attribute names + The default value of `priority` is `["id", "type", "class", "style"]`. Examples of **incorrect** code for this rule with the default options (`{ "priority": ["id", "type", "class", "style"] }`). @@ -71,3 +76,26 @@ Examples of **correct** code for this rule with the `{ "priority": ["id", "style ```html,correct
``` + +##### Pattern Support + +You can use regular expressions in priority items using the pattern object format: + +Examples of **correct** code for this rule with the `{ "priority": ["id", { "pattern": "data-.*" }, "style"] }` option: + +```html,correct +
+``` + +In this example: + +- `id` has the highest priority (position 0) +- Any attribute matching `data-.*` pattern has the second priority (position 1) +- `style` has the third priority (position 2) +- All other attributes are sorted alphabetically after the priority items + +Examples of **incorrect** code for this rule with the `{ "priority": ["id", { "pattern": "data-.*" }, "style"] }` option: + +```html,incorrect +
+``` diff --git a/packages/eslint-plugin/lib/rules/sort-attrs.js b/packages/eslint-plugin/lib/rules/sort-attrs.js index dde86b4b..ca3edb9b 100644 --- a/packages/eslint-plugin/lib/rules/sort-attrs.js +++ b/packages/eslint-plugin/lib/rules/sort-attrs.js @@ -3,7 +3,7 @@ * @import {RuleFixer, RuleModule} from "../types"; * * @typedef {Object} Option - * @property {string[]} [Option.priority] + * @property {Array} [Option.priority] */ const { hasTemplate } = require("./utils/node"); @@ -37,9 +37,23 @@ module.exports = { priority: { type: "array", items: { - type: "string", - uniqueItems: true, + oneOf: [ + { + type: "string", + }, + { + type: "object", + properties: { + pattern: { + type: "string", + }, + }, + required: ["pattern"], + additionalProperties: false, + }, + ], }, + uniqueItems: true, }, }, additionalProperties: false, @@ -55,9 +69,49 @@ module.exports = { priority: ["id", "type", "class", "style"], }; /** - * @type {string[]} + * @type {Array} */ - const priority = option.priority || []; + const priority = (option.priority || []).map((item) => { + if (item && typeof item === "object" && "pattern" in item) { + return { + ...item, + regex: new RegExp(item.pattern, "u"), + }; + } + return item; + }); + + /** + * @param {string} attrName + * @param {string | {pattern: string, regex: RegExp}} priorityItem + * @returns {boolean} + */ + function matchesPriority(attrName, priorityItem) { + if (typeof priorityItem === "string") { + return attrName === priorityItem; + } + if ( + priorityItem && + typeof priorityItem === "object" && + priorityItem.regex + ) { + return priorityItem.regex.test(attrName); + } + return false; + } + + /** + * @param {string} attrName + * @returns {number} + */ + function getPriorityIndex(attrName) { + for (let i = 0; i < priority.length; i++) { + if (matchesPriority(attrName, priority[i])) { + return i; + } + } + return -1; + } /** * @param {Attribute} attrA @@ -68,8 +122,8 @@ module.exports = { const keyA = attrA.key.value; const keyB = attrB.key.value; - const keyAReservedValue = priority.indexOf(keyA); - const keyBReservedValue = priority.indexOf(keyB); + const keyAReservedValue = getPriorityIndex(keyA); + const keyBReservedValue = getPriorityIndex(keyB); if (keyAReservedValue >= 0 && keyBReservedValue >= 0) { return keyAReservedValue - keyBReservedValue; } else if (keyAReservedValue >= 0) { diff --git a/packages/eslint-plugin/tests/rules/sort-attrs.test.js b/packages/eslint-plugin/tests/rules/sort-attrs.test.js index 7e722109..f7f8c5e7 100644 --- a/packages/eslint-plugin/tests/rules/sort-attrs.test.js +++ b/packages/eslint-plugin/tests/rules/sort-attrs.test.js @@ -40,6 +40,44 @@ ruleTester.run("sort-attrs", rule, { }, }, }, + // Pattern tests + { + code: '
', + options: [ + { + priority: ["id", { pattern: "data-.*" }, "style"], + }, + ], + }, + { + code: '', + options: [ + { + priority: ["id", { pattern: "aria-.*" }, "type"], + }, + ], + }, + { + code: '', + options: [ + { + priority: ["id", { pattern: "ng-.*" }, "class", "type"], + }, + ], + }, + { + code: '
', + options: [ + { + priority: [ + "id", + { pattern: "data-.*" }, + { pattern: "aria-.*" }, + "class", + ], + }, + ], + }, { code: ` ', + output: + '', + options: [ + { + priority: ["id", { pattern: "ng-.*" }, "class", "type"], + }, + ], + errors: [{ messageId: "unsorted" }], + }, + { + code: '
', + output: + '
', + options: [ + { + priority: ["id", { pattern: "v-.*" }], + }, + ], + errors: [{ messageId: "unsorted" }], + }, + { + code: '
', + output: + '
', + options: [ + { + priority: ["id", { pattern: "data-.*" }], + }, + ], + errors: [{ messageId: "unsorted" }], + }, + // Multiple patterns invalid tests + { + code: '
', + output: + '
', + options: [ + { + priority: [ + "id", + { pattern: "data-.*" }, + { pattern: "aria-.*" }, + "class", + ], + }, + ], + errors: [{ messageId: "unsorted" }], + }, + { + code: '', + output: + '', + options: [ + { + priority: [ + "id", + { pattern: "data-.*" }, + { pattern: "aria-.*" }, + { pattern: "v-.*" }, + "type", + ], + }, + ], + errors: [{ messageId: "unsorted" }], + }, ], }); @@ -280,6 +419,27 @@ templateRuleTester.run("[template] sort-attrs", rule, { { code: 'html``', }, + { + code: 'html`
`', + options: [ + { + priority: ["id", { pattern: "data-.*" }, "style"], + }, + ], + }, + { + code: 'html`
`', + options: [ + { + priority: [ + "id", + { pattern: "data-.*" }, + { pattern: "aria-.*" }, + "class", + ], + }, + ], + }, ], invalid: [ { @@ -346,5 +506,16 @@ templateRuleTester.run("[template] sort-attrs", rule, { }, ], }, + { + code: 'html`
`', + output: + 'html`
`', + options: [ + { + priority: ["id", { pattern: "data-.*" }, "style"], + }, + ], + errors: [{ messageId: "unsorted" }], + }, ], });