From 08ef531bde76dc77c25dc0cbece2b3bfaa299153 Mon Sep 17 00:00:00 2001 From: Attacktive <66462458+Attacktive@users.noreply.github.com> Date: Wed, 9 Jul 2025 18:03:32 +0900 Subject: [PATCH 01/17] docs: rephrase rule details (#2782) --- docs/rules/padding-line-between-blocks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/padding-line-between-blocks.md b/docs/rules/padding-line-between-blocks.md index 4f3ee0290..645efeac7 100644 --- a/docs/rules/padding-line-between-blocks.md +++ b/docs/rules/padding-line-between-blocks.md @@ -14,7 +14,7 @@ since: v6.2.0 ## :book: Rule Details -This rule requires or disallows blank lines between the given 2 blocks. Properly blank lines help developers to understand the code. +This rule requires or disallows blank lines between blocks. Properly placed blank lines help developers understand the code. From a2e49a7a1a35d54651b3cc02745cb3ca606e1bee Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Wed, 9 Jul 2025 15:28:49 +0200 Subject: [PATCH 02/17] refactor: use regexp-groups to simplify ignores (#2776) Co-authored-by: Flo Edelmann --- lib/rules/attribute-hyphenation.js | 13 +- .../component-name-in-template-casing.js | 7 +- lib/rules/custom-event-name-casing.js | 7 +- lib/rules/no-restricted-block.js | 18 +-- lib/rules/no-restricted-class.js | 38 +----- lib/rules/no-restricted-component-names.js | 2 +- lib/rules/no-restricted-component-options.js | 18 +-- lib/rules/no-restricted-custom-event.js | 18 +-- lib/rules/no-restricted-props.js | 18 +-- lib/rules/no-restricted-static-attribute.js | 26 +--- lib/rules/no-restricted-v-bind.js | 22 +--- lib/rules/no-restricted-v-on.js | 23 +--- lib/rules/no-undef-properties.js | 9 +- lib/rules/prefer-true-attribute-shorthand.js | 7 +- lib/rules/prop-name-casing.js | 7 +- lib/rules/restricted-component-names.js | 7 +- lib/rules/syntaxes/slot-attribute.js | 14 +- lib/rules/v-on-event-hyphenation.js | 15 +-- lib/utils/regexp.js | 43 +++++- tests/lib/utils/regexp.js | 122 +++++++++++++++++- 20 files changed, 219 insertions(+), 215 deletions(-) diff --git a/lib/rules/attribute-hyphenation.js b/lib/rules/attribute-hyphenation.js index 65d096cd4..45e7ca687 100644 --- a/lib/rules/attribute-hyphenation.js +++ b/lib/rules/attribute-hyphenation.js @@ -6,7 +6,7 @@ const utils = require('../utils') const casing = require('../utils/casing') -const { toRegExp } = require('../utils/regexp') +const { toRegExpGroupMatcher } = require('../utils/regexp') const svgAttributes = require('../utils/svg-attributes-weird-case.json') /** @@ -79,11 +79,7 @@ module.exports = { const option = context.options[0] const optionsPayload = context.options[1] const useHyphenated = option !== 'never' - /** @type {RegExp[]} */ - const ignoredTagsRegexps = ( - (optionsPayload && optionsPayload.ignoreTags) || - [] - ).map(toRegExp) + const isIgnoredTagName = toRegExpGroupMatcher(optionsPayload?.ignoreTags) const ignoredAttributes = ['data-', 'aria-', 'slot-scope', ...svgAttributes] if (optionsPayload && optionsPayload.ignore) { @@ -142,11 +138,6 @@ module.exports = { return useHyphenated ? value.toLowerCase() === value : !/-/.test(value) } - /** @param {string} name */ - function isIgnoredTagName(name) { - return ignoredTagsRegexps.some((re) => re.test(name)) - } - return utils.defineTemplateBodyVisitor(context, { VAttribute(node) { const element = node.parent.parent diff --git a/lib/rules/component-name-in-template-casing.js b/lib/rules/component-name-in-template-casing.js index d330f60da..c7267cd49 100644 --- a/lib/rules/component-name-in-template-casing.js +++ b/lib/rules/component-name-in-template-casing.js @@ -6,7 +6,7 @@ const utils = require('../utils') const casing = require('../utils/casing') -const { toRegExp } = require('../utils/regexp') +const { toRegExpGroupMatcher } = require('../utils/regexp') const allowedCaseOptions = ['PascalCase', 'kebab-case'] const defaultCase = 'PascalCase' @@ -81,8 +81,7 @@ module.exports = { const caseType = allowedCaseOptions.includes(caseOption) ? caseOption : defaultCase - /** @type {RegExp[]} */ - const ignores = (options.ignores || []).map(toRegExp) + const isIgnored = toRegExpGroupMatcher(options.ignores) /** @type {string[]} */ const globals = (options.globals || []).map(casing.pascalCase) const registeredComponentsOnly = options.registeredComponentsOnly !== false @@ -116,7 +115,7 @@ module.exports = { * @returns {boolean} `true` if the given node is the verification target node. */ function isVerifyTarget(node) { - if (ignores.some((re) => re.test(node.rawName))) { + if (isIgnored(node.rawName)) { // ignore return false } diff --git a/lib/rules/custom-event-name-casing.js b/lib/rules/custom-event-name-casing.js index aff4609b5..c63b4e9d9 100644 --- a/lib/rules/custom-event-name-casing.js +++ b/lib/rules/custom-event-name-casing.js @@ -7,7 +7,7 @@ const { findVariable } = require('@eslint-community/eslint-utils') const utils = require('../utils') const casing = require('../utils/casing') -const { toRegExp } = require('../utils/regexp') +const { toRegExpGroupMatcher } = require('../utils/regexp') /** * @typedef {import('../utils').VueObjectData} VueObjectData @@ -92,8 +92,7 @@ module.exports = { const caseType = context.options[0] || DEFAULT_CASE const objectOption = context.options[1] || {} const caseChecker = casing.getChecker(caseType) - /** @type {RegExp[]} */ - const ignores = (objectOption.ignores || []).map(toRegExp) + const isIgnored = toRegExpGroupMatcher(objectOption.ignores) /** * Check whether the given event name is valid. @@ -109,7 +108,7 @@ module.exports = { */ function verify(nameWithLoc) { const name = nameWithLoc.name - if (isValidEventName(name) || ignores.some((re) => re.test(name))) { + if (isValidEventName(name) || isIgnored(name)) { return } context.report({ diff --git a/lib/rules/no-restricted-block.js b/lib/rules/no-restricted-block.js index 87b4bda3e..000e2c7b8 100644 --- a/lib/rules/no-restricted-block.js +++ b/lib/rules/no-restricted-block.js @@ -12,30 +12,16 @@ const regexp = require('../utils/regexp') * @property {string} [message] */ -/** - * @param {string} str - * @returns {(str: string) => boolean} - */ -function buildMatcher(str) { - if (regexp.isRegExp(str)) { - const re = regexp.toRegExp(str) - return (s) => { - re.lastIndex = 0 - return re.test(s) - } - } - return (s) => s === str -} /** * @param {any} option * @returns {ParsedOption} */ function parseOption(option) { if (typeof option === 'string') { - const matcher = buildMatcher(option) + const matcher = regexp.toRegExp(option, { remove: 'g' }) return { test(block) { - return matcher(block.rawName) + return matcher.test(block.rawName) } } } diff --git a/lib/rules/no-restricted-class.js b/lib/rules/no-restricted-class.js index 41d30df2d..1a74fd249 100644 --- a/lib/rules/no-restricted-class.js +++ b/lib/rules/no-restricted-class.js @@ -12,20 +12,10 @@ const regexp = require('../utils/regexp') * @param {string} className * @param {*} node * @param {RuleContext} context - * @param {Set} forbiddenClasses - * @param {Array} forbiddenClassesRegexps + * @param {(name: string) => boolean} isForbiddenClass */ -const reportForbiddenClass = ( - className, - node, - context, - forbiddenClasses, - forbiddenClassesRegexps -) => { - if ( - forbiddenClasses.has(className) || - forbiddenClassesRegexps.some((re) => re.test(className)) - ) { +const reportForbiddenClass = (className, node, context, isForbiddenClass) => { + if (isForbiddenClass(className)) { const loc = node.value ? node.value.loc : node.loc context.report({ node, @@ -123,10 +113,8 @@ module.exports = { /** @param {RuleContext} context */ create(context) { - const forbiddenClasses = new Set(context.options || []) - const forbiddenClassesRegexps = (context.options || []) - .filter((cl) => regexp.isRegExp(cl)) - .map((cl) => regexp.toRegExp(cl)) + const { options = [] } = context + const isForbiddenClass = regexp.toRegExpGroupMatcher(options) return utils.defineTemplateBodyVisitor(context, { /** @@ -134,13 +122,7 @@ module.exports = { */ 'VAttribute[directive=false][key.name="class"][value!=null]'(node) { for (const className of node.value.value.split(/\s+/)) { - reportForbiddenClass( - className, - node, - context, - forbiddenClasses, - forbiddenClassesRegexps - ) + reportForbiddenClass(className, node, context, isForbiddenClass) } }, @@ -155,13 +137,7 @@ module.exports = { for (const { className, reportNode } of extractClassNames( /** @type {Expression} */ (node.expression) )) { - reportForbiddenClass( - className, - reportNode, - context, - forbiddenClasses, - forbiddenClassesRegexps - ) + reportForbiddenClass(className, reportNode, context, isForbiddenClass) } } }) diff --git a/lib/rules/no-restricted-component-names.js b/lib/rules/no-restricted-component-names.js index e5111a748..df5ad4a23 100644 --- a/lib/rules/no-restricted-component-names.js +++ b/lib/rules/no-restricted-component-names.js @@ -22,7 +22,7 @@ const { isRegExp, toRegExp } = require('../utils/regexp') */ function buildMatcher(str) { if (isRegExp(str)) { - const regex = toRegExp(str) + const regex = toRegExp(str, { remove: 'g' }) return (s) => regex.test(s) } return (s) => s === casing.pascalCase(str) || s === casing.kebabCase(str) diff --git a/lib/rules/no-restricted-component-options.js b/lib/rules/no-restricted-component-options.js index b8563a92b..227a89382 100644 --- a/lib/rules/no-restricted-component-options.js +++ b/lib/rules/no-restricted-component-options.js @@ -23,21 +23,6 @@ const regexp = require('../utils/regexp') * @typedef { (node: Property | SpreadElement) => (MatchResult | null) } Tester */ -/** - * @param {string} str - * @returns {Matcher} - */ -function buildMatcher(str) { - if (regexp.isRegExp(str)) { - const re = regexp.toRegExp(str) - return (s) => { - re.lastIndex = 0 - return re.test(s) - } - } - return (s) => s === str -} - /** * @param {string | string[] | { name: string | string[], message?: string } } option * @returns {ParsedOption} @@ -65,7 +50,8 @@ function parseOption(option) { if (name === '*') { steps.push({ wildcard: true }) } else { - steps.push({ test: buildMatcher(name) }) + const matcher = regexp.toRegExp(name, { remove: 'g' }) + steps.push({ test: (value) => matcher.test(value) }) } } const message = option.message diff --git a/lib/rules/no-restricted-custom-event.js b/lib/rules/no-restricted-custom-event.js index 5ddda037f..93c1aa764 100644 --- a/lib/rules/no-restricted-custom-event.js +++ b/lib/rules/no-restricted-custom-event.js @@ -15,30 +15,16 @@ const regexp = require('../utils/regexp') * @property {string|undefined} [suggest] */ -/** - * @param {string} str - * @returns {(str: string) => boolean} - */ -function buildMatcher(str) { - if (regexp.isRegExp(str)) { - const re = regexp.toRegExp(str) - return (s) => { - re.lastIndex = 0 - return re.test(s) - } - } - return (s) => s === str -} /** * @param {string|{event: string, message?: string, suggest?: string}} option * @returns {ParsedOption} */ function parseOption(option) { if (typeof option === 'string') { - const matcher = buildMatcher(option) + const matcher = regexp.toRegExp(option, { remove: 'g' }) return { test(name) { - return matcher(name) + return matcher.test(name) } } } diff --git a/lib/rules/no-restricted-props.js b/lib/rules/no-restricted-props.js index 2d2f74bb0..e0684393d 100644 --- a/lib/rules/no-restricted-props.js +++ b/lib/rules/no-restricted-props.js @@ -18,30 +18,16 @@ const regexp = require('../utils/regexp') * @property {string|undefined} [suggest] */ -/** - * @param {string} str - * @returns {(str: string) => boolean} - */ -function buildMatcher(str) { - if (regexp.isRegExp(str)) { - const re = regexp.toRegExp(str) - return (s) => { - re.lastIndex = 0 - return re.test(s) - } - } - return (s) => s === str -} /** * @param {string|{name:string, message?: string, suggest?:string}} option * @returns {ParsedOption} */ function parseOption(option) { if (typeof option === 'string') { - const matcher = buildMatcher(option) + const matcher = regexp.toRegExp(option, { remove: 'g' }) return { test(name) { - return matcher(name) + return matcher.test(name) } } } diff --git a/lib/rules/no-restricted-static-attribute.js b/lib/rules/no-restricted-static-attribute.js index d9241620b..d7223044a 100644 --- a/lib/rules/no-restricted-static-attribute.js +++ b/lib/rules/no-restricted-static-attribute.js @@ -15,30 +15,16 @@ const regexp = require('../utils/regexp') * @property {string} [message] */ -/** - * @param {string} str - * @returns {(str: string) => boolean} - */ -function buildMatcher(str) { - if (regexp.isRegExp(str)) { - const re = regexp.toRegExp(str) - return (s) => { - re.lastIndex = 0 - return re.test(s) - } - } - return (s) => s === str -} /** * @param {any} option * @returns {ParsedOption} */ function parseOption(option) { if (typeof option === 'string') { - const matcher = buildMatcher(option) + const matcher = regexp.toRegExp(option, { remove: 'g' }) return { test({ key }) { - return matcher(key.rawName) + return matcher.test(key.rawName) } } } @@ -53,25 +39,25 @@ function parseOption(option) { return node.value == null || node.value.value === node.key.rawName } } else { - const valueMatcher = buildMatcher(option.value) + const valueMatcher = regexp.toRegExp(option.value, { remove: 'g' }) parsed.test = (node) => { if (!keyTest(node)) { return false } - return node.value != null && valueMatcher(node.value.value) + return node.value != null && valueMatcher.test(node.value.value) } } parsed.useValue = true } if (option.element) { const argTest = parsed.test - const tagMatcher = buildMatcher(option.element) + const tagMatcher = regexp.toRegExp(option.element, { remove: 'g' }) parsed.test = (node) => { if (!argTest(node)) { return false } const element = node.parent.parent - return tagMatcher(element.rawName) + return tagMatcher.test(element.rawName) } parsed.useElement = true } diff --git a/lib/rules/no-restricted-v-bind.js b/lib/rules/no-restricted-v-bind.js index f9bf5462b..e16622174 100644 --- a/lib/rules/no-restricted-v-bind.js +++ b/lib/rules/no-restricted-v-bind.js @@ -22,33 +22,19 @@ const DEFAULT_OPTIONS = [ } ] -/** - * @param {string} str - * @returns {(str: string) => boolean} - */ -function buildMatcher(str) { - if (regexp.isRegExp(str)) { - const re = regexp.toRegExp(str) - return (s) => { - re.lastIndex = 0 - return re.test(s) - } - } - return (s) => s === str -} /** * @param {any} option * @returns {ParsedOption} */ function parseOption(option) { if (typeof option === 'string') { - const matcher = buildMatcher(option) + const matcher = regexp.toRegExp(option, { remove: 'g' }) return { test(key) { return Boolean( key.argument && key.argument.type === 'VIdentifier' && - matcher(key.argument.rawName) + matcher.test(key.argument.rawName) ) }, modifiers: [] @@ -77,13 +63,13 @@ function parseOption(option) { } if (option.element) { const argTest = parsed.test - const tagMatcher = buildMatcher(option.element) + const tagMatcher = regexp.toRegExp(option.element, { remove: 'g' }) parsed.test = (key) => { if (!argTest(key)) { return false } const element = key.parent.parent.parent - return tagMatcher(element.rawName) + return tagMatcher.test(element.rawName) } parsed.useElement = true } diff --git a/lib/rules/no-restricted-v-on.js b/lib/rules/no-restricted-v-on.js index 2379df349..893d511a6 100644 --- a/lib/rules/no-restricted-v-on.js +++ b/lib/rules/no-restricted-v-on.js @@ -15,34 +15,19 @@ const regexp = require('../utils/regexp') * @property {string} [message] */ -/** - * @param {string} str - * @returns {(str: string) => boolean} - */ -function buildMatcher(str) { - if (regexp.isRegExp(str)) { - const re = regexp.toRegExp(str) - return (s) => { - re.lastIndex = 0 - return re.test(s) - } - } - return (s) => s === str -} - /** * @param {any} option * @returns {ParsedOption} */ function parseOption(option) { if (typeof option === 'string') { - const matcher = buildMatcher(option) + const matcher = regexp.toRegExp(option, { remove: 'g' }) return { test(key) { return Boolean( key.argument && key.argument.type === 'VIdentifier' && - matcher(key.argument.rawName) + matcher.test(key.argument.rawName) ) } } @@ -70,12 +55,12 @@ function parseOption(option) { } if (option.element) { const argTest = parsed.test - const tagMatcher = buildMatcher(option.element) + const tagMatcher = regexp.toRegExp(option.element, { remove: 'g' }) parsed.test = (key) => { if (!argTest(key)) { return false } - return tagMatcher(key.parent.parent.parent.rawName) + return tagMatcher.test(key.parent.parent.parent.rawName) } parsed.useElement = true } diff --git a/lib/rules/no-undef-properties.js b/lib/rules/no-undef-properties.js index 711c2ed22..3ff49bd83 100644 --- a/lib/rules/no-undef-properties.js +++ b/lib/rules/no-undef-properties.js @@ -6,7 +6,7 @@ const utils = require('../utils') const reserved = require('../utils/vue-reserved.json') -const { toRegExp } = require('../utils/regexp') +const { toRegExpGroupMatcher } = require('../utils/regexp') const { getStyleVariablesContext } = require('../utils/style-variables') const { definePropertyReferenceExtractor @@ -106,9 +106,8 @@ module.exports = { /** @param {RuleContext} context */ create(context) { const options = context.options[0] || {} - const ignores = /** @type {string[]} */ ( - options.ignores || [String.raw`/^\$/`] - ).map(toRegExp) + const { ignores = [String.raw`/^\$/`] } = options + const isIgnored = toRegExpGroupMatcher(ignores) const propertyReferenceExtractor = definePropertyReferenceExtractor(context) const programNode = context.getSourceCode().ast /** @@ -190,7 +189,7 @@ module.exports = { report(node, name, messageId = 'undef') { if ( reserved.includes(name) || - ignores.some((ignore) => ignore.test(name)) || + isIgnored(name) || propertiesDefinedByStoreHelpers.has(name) ) { return diff --git a/lib/rules/prefer-true-attribute-shorthand.js b/lib/rules/prefer-true-attribute-shorthand.js index 817525d1d..3f5446bda 100644 --- a/lib/rules/prefer-true-attribute-shorthand.js +++ b/lib/rules/prefer-true-attribute-shorthand.js @@ -4,7 +4,7 @@ */ 'use strict' -const { toRegExp } = require('../utils/regexp') +const { toRegExpGroupMatcher } = require('../utils/regexp') const utils = require('../utils') /** @@ -99,8 +99,7 @@ module.exports = { create(context) { /** @type {'always' | 'never'} */ const option = context.options[0] || 'always' - /** @type {RegExp[]} */ - const exceptReg = (context.options[1]?.except || []).map(toRegExp) + const exceptMatcher = toRegExpGroupMatcher(context.options[1]?.except) /** * @param {VAttribute | VDirective} node @@ -155,7 +154,7 @@ module.exports = { const name = getAttributeName(node) if (name === null) return - const isExcepted = exceptReg.some((re) => re.test(name)) + const isExcepted = exceptMatcher(name) if (shouldConvertToLongForm(node, isExcepted, option)) { const key = /** @type {VIdentifier} */ (node.key) diff --git a/lib/rules/prop-name-casing.js b/lib/rules/prop-name-casing.js index fd4f0dc31..7121c66c6 100644 --- a/lib/rules/prop-name-casing.js +++ b/lib/rules/prop-name-casing.js @@ -6,7 +6,7 @@ const utils = require('../utils') const casing = require('../utils/casing') -const { toRegExp } = require('../utils/regexp') +const { toRegExpGroupMatcher } = require('../utils/regexp') const allowedCaseOptions = ['camelCase', 'snake_case'] /** @@ -16,8 +16,7 @@ const allowedCaseOptions = ['camelCase', 'snake_case'] /** @param {RuleContext} context */ function create(context) { const options = context.options[0] - /** @type {RegExp[]} */ - const ignoreProps = (context.options[1]?.ignoreProps || []).map(toRegExp) + const isIgnoredProp = toRegExpGroupMatcher(context.options[1]?.ignoreProps) const caseType = allowedCaseOptions.includes(options) ? options : 'camelCase' const checker = casing.getChecker(caseType) @@ -30,7 +29,7 @@ function create(context) { if (propName == null) { continue } - if (!checker(propName) && !ignoreProps.some((re) => re.test(propName))) { + if (!checker(propName) && !isIgnoredProp(propName)) { context.report({ node: item.node, messageId: 'invalidCase', diff --git a/lib/rules/restricted-component-names.js b/lib/rules/restricted-component-names.js index 636224db6..89d04bfad 100644 --- a/lib/rules/restricted-component-names.js +++ b/lib/rules/restricted-component-names.js @@ -5,7 +5,7 @@ 'use strict' const utils = require('../utils') -const { toRegExp } = require('../utils/regexp') +const { toRegExpGroupMatcher } = require('../utils/regexp') const htmlElements = require('../utils/html-elements.json') const deprecatedHtmlElements = require('../utils/deprecated-html-elements.json') @@ -51,12 +51,11 @@ module.exports = { /** @param {RuleContext} context */ create(context) { const options = context.options[0] || {} - /** @type {RegExp[]} */ - const allow = (options.allow || []).map(toRegExp) + const isAllowed = toRegExpGroupMatcher(options.allow) /** @param {string} name */ function isAllowedTarget(name) { - return reservedNames.has(name) || allow.some((re) => re.test(name)) + return reservedNames.has(name) || isAllowed(name) } return utils.defineTemplateBodyVisitor(context, { diff --git a/lib/rules/syntaxes/slot-attribute.js b/lib/rules/syntaxes/slot-attribute.js index b77fc6c20..8d854230c 100644 --- a/lib/rules/syntaxes/slot-attribute.js +++ b/lib/rules/syntaxes/slot-attribute.js @@ -16,8 +16,7 @@ module.exports = { /** @type {{ ignore: string[] }} */ const options = context.options[0] || {} const { ignore = [] } = options - /** @type {RegExp[]} */ - const ignorePatterns = ignore.map(regexp.toRegExp) + const isAnyIgnored = regexp.toRegExpGroupMatcher(ignore) const sourceCode = context.getSourceCode() const tokenStore = @@ -125,15 +124,12 @@ module.exports = { */ function reportSlot(slotAttr) { const componentName = slotAttr.parent.parent.rawName - const componentNamePascalCase = casing.pascalCase(componentName) - const componentNameKebabCase = casing.kebabCase(componentName) if ( - ignorePatterns.some( - (pattern) => - pattern.test(componentName) || - pattern.test(componentNamePascalCase) || - pattern.test(componentNameKebabCase) + isAnyIgnored( + componentName, + casing.pascalCase(componentName), + casing.kebabCase(componentName) ) ) { return diff --git a/lib/rules/v-on-event-hyphenation.js b/lib/rules/v-on-event-hyphenation.js index c9fac76e8..056890fa0 100644 --- a/lib/rules/v-on-event-hyphenation.js +++ b/lib/rules/v-on-event-hyphenation.js @@ -2,7 +2,7 @@ const utils = require('../utils') const casing = require('../utils/casing') -const { toRegExp } = require('../utils/regexp') +const { toRegExpGroupMatcher } = require('../utils/regexp') module.exports = { meta: { @@ -63,11 +63,7 @@ module.exports = { const useHyphenated = option !== 'never' /** @type {string[]} */ const ignoredAttributes = (optionsPayload && optionsPayload.ignore) || [] - /** @type {RegExp[]} */ - const ignoredTagsRegexps = ( - (optionsPayload && optionsPayload.ignoreTags) || - [] - ).map(toRegExp) + const isIgnoredTag = toRegExpGroupMatcher(optionsPayload?.ignoreTags) const autofix = Boolean(optionsPayload && optionsPayload.autofix) const caseConverter = casing.getConverter( @@ -111,17 +107,12 @@ module.exports = { return useHyphenated ? value.toLowerCase() === value : !/-/.test(value) } - /** @param {string} name */ - function isIgnoredTagName(name) { - return ignoredTagsRegexps.some((re) => re.test(name)) - } - return utils.defineTemplateBodyVisitor(context, { "VAttribute[directive=true][key.name.name='on']"(node) { const element = node.parent.parent if ( !utils.isCustomComponent(element) || - isIgnoredTagName(element.rawName) + isIgnoredTag(element.rawName) ) { return } diff --git a/lib/utils/regexp.js b/lib/utils/regexp.js index 3ee40ae41..006568067 100644 --- a/lib/utils/regexp.js +++ b/lib/utils/regexp.js @@ -22,14 +22,25 @@ function escape(string) { * Strings like `"/^foo/i"` are converted to `/^foo/i` of `RegExp`. * * @param {string} string The string to convert. + * @param {{add?: string, remove?: string}} [flags] The flags to add or remove. + * - `add`: Flags to add to the `RegExp` (e.g. `'i'` for case-insensitive). + * - `remove`: Flags to remove from the `RegExp` (e.g. `'g'` to remove global matching). * @returns {RegExp} Returns the `RegExp`. */ -function toRegExp(string) { +function toRegExp(string, flags = {}) { const parts = RE_REGEXP_STR.exec(string) + const { add: forceAddFlags = '', remove: forceRemoveFlags = '' } = + typeof flags === 'object' ? flags : {} // Avoid issues when this is called directly from array.map if (parts) { - return new RegExp(parts[1], parts[2]) + return new RegExp( + parts[1], + parts[2].replace( + new RegExp(`[${forceAddFlags}${forceRemoveFlags}]`, 'g'), + '' + ) + forceAddFlags + ) } - return new RegExp(`^${escape(string)}$`) + return new RegExp(`^${escape(string)}$`, forceAddFlags) } /** @@ -41,8 +52,32 @@ function isRegExp(string) { return RE_REGEXP_STR.test(string) } +/** + * Converts an array of strings to a singular function to match any of them. + * This function converts each string to a `RegExp` and returns a function that checks all of them. + * + * @param {string[]} [patterns] The strings or regular expression strings to match. + * @returns {(...toCheck: string[]) => boolean} Returns a function that checks if any string matches any of the given patterns. + */ +function toRegExpGroupMatcher(patterns = []) { + if (patterns.length === 0) { + return () => false + } + + // In the future, we could optimize this by joining expressions with identical flags. + const regexps = patterns.map((pattern) => toRegExp(pattern, { remove: 'g' })) + + if (regexps.length === 1) { + return (...toCheck) => toCheck.some((str) => regexps[0].test(str)) + } + + return (...toCheck) => + regexps.some((regexp) => toCheck.some((str) => regexp.test(str))) +} + module.exports = { escape, toRegExp, - isRegExp + isRegExp, + toRegExpGroupMatcher } diff --git a/tests/lib/utils/regexp.js b/tests/lib/utils/regexp.js index 830fa2a11..e27a0596f 100644 --- a/tests/lib/utils/regexp.js +++ b/tests/lib/utils/regexp.js @@ -1,6 +1,10 @@ 'use strict' -const { escape, toRegExp } = require('../../../lib/utils/regexp') +const { + escape, + toRegExp, + toRegExpGroupMatcher +} = require('../../../lib/utils/regexp') const assert = require('assert') const ESCAPED = '\\^\\$\\.\\*\\+\\?\\(\\)\\[\\]\\{\\}\\|\\\\' @@ -35,4 +39,120 @@ describe('toRegExp()', () => { assert.deepEqual(toRegExp(`${/^bar/i}`), /^bar/i) assert.deepEqual(toRegExp(`${/[\sA-Z]+/u}`), /[\sA-Z]+/u) }) + + it('should handle simple patterns', () => { + const regex = toRegExp('foo') + assert.strictEqual(regex.test('foo'), true) + assert.strictEqual(regex.test('bar'), false) + assert.strictEqual(regex.test('foobar'), false) + assert.strictEqual(regex.test('afoo'), false) + assert.strictEqual(regex.test('afoobar'), false) + assert.strictEqual(regex.test('Foo'), false) + }) + + it('should handle simple patterns with added flags', () => { + const regex = toRegExp('foo', { add: 'i' }) + assert.strictEqual(regex.test('foo'), true) + assert.strictEqual(regex.test('bar'), false) + assert.strictEqual(regex.test('foobar'), false) + assert.strictEqual(regex.test('afoo'), false) + assert.strictEqual(regex.test('afoobar'), false) + assert.strictEqual(regex.test('Foo'), true) + }) + + it('should handle regexp patterns', () => { + const regex = toRegExp('/^foo/') + assert.strictEqual(regex.test('foo'), true) + assert.strictEqual(regex.test('bar'), false) + assert.strictEqual(regex.test('foobar'), true) + assert.strictEqual(regex.test('afoo'), false) + assert.strictEqual(regex.test('afoobar'), false) + assert.strictEqual(regex.test('Foo'), false) + }) + + it('should handle regexp patterns with attached flags', () => { + const regex = toRegExp('/^foo/i') + assert.strictEqual(regex.test('foo'), true) + assert.strictEqual(regex.test('bar'), false) + assert.strictEqual(regex.test('foobar'), true) + assert.strictEqual(regex.test('afoo'), false) + assert.strictEqual(regex.test('afoobar'), false) + assert.strictEqual(regex.test('Foo'), true) + }) + + it('should handle regexp patterns with added flags', () => { + const regex = toRegExp('/^foo/', { add: 'i' }) + assert.deepEqual(regex, /^foo/i) + assert.strictEqual(regex.test('foo'), true) + assert.strictEqual(regex.test('bar'), false) + assert.strictEqual(regex.test('foobar'), true) + assert.strictEqual(regex.test('afoo'), false) + assert.strictEqual(regex.test('afoobar'), false) + assert.strictEqual(regex.test('Foo'), true) + }) + + it('should handle regexp patterns with removed flags', () => { + const regex = toRegExp('/^foo/i', { remove: 'i' }) + assert.deepEqual(regex, /^foo/) + assert.strictEqual(regex.test('foo'), true) + assert.strictEqual(regex.test('bar'), false) + assert.strictEqual(regex.test('foobar'), true) + assert.strictEqual(regex.test('afoo'), false) + assert.strictEqual(regex.test('afoobar'), false) + assert.strictEqual(regex.test('Foo'), false) + }) +}) + +describe('toRegExpGroupMatcher()', () => { + it('should return a function missing input', () => { + const groupMatcher = toRegExpGroupMatcher() + assert.strictEqual(groupMatcher(''), false) + assert.strictEqual(groupMatcher('foo'), false) + assert.strictEqual(groupMatcher('bar'), false) + }) + + it('should return a function for empty array', () => { + const groupMatcher = toRegExpGroupMatcher([]) + assert.strictEqual(groupMatcher(''), false) + assert.strictEqual(groupMatcher('foo'), false) + assert.strictEqual(groupMatcher('bar'), false) + }) + + it('should return a function for single simple pattern', () => { + const groupMatcher = toRegExpGroupMatcher(['foo']) + assert.strictEqual(groupMatcher(''), false) + assert.strictEqual(groupMatcher('foo'), true) + assert.strictEqual(groupMatcher('foo', 'early'), true) + assert.strictEqual(groupMatcher('late', 'matches', 'foo'), true) + assert.strictEqual(groupMatcher('foobar'), false) + assert.strictEqual(groupMatcher('afoo', 'fooa', 'afooa', 'bar'), false) + }) + + it('should return a function for multiple simple patterns', () => { + const groupMatcher = toRegExpGroupMatcher(['foo', 'bar']) + assert.strictEqual(groupMatcher('foo'), true) + assert.strictEqual(groupMatcher('bar', 'early'), true) + assert.strictEqual(groupMatcher('late', 'matches', 'foo'), true) + assert.strictEqual(groupMatcher('foobar'), false) + assert.strictEqual(groupMatcher('afoo', 'fooa', 'afooa'), false) + }) + + it('should return a function for single regexp pattern', () => { + const groupMatcher = toRegExpGroupMatcher(['/^foo/g']) + assert.strictEqual(groupMatcher(''), false) + assert.strictEqual(groupMatcher('foo'), true) + assert.strictEqual(groupMatcher('fooa', 'early'), true) + assert.strictEqual(groupMatcher('late', 'matches', 'fooa'), true) + assert.strictEqual(groupMatcher('barfoo'), false) + assert.strictEqual(groupMatcher('afoo', 'afooa', 'bar'), false) + }) + + it('should return a function for multiple regexp patterns', () => { + const groupMatcher = toRegExpGroupMatcher(['/^foo/', '/bar$/gi']) + assert.strictEqual(groupMatcher('foo'), true) + assert.strictEqual(groupMatcher('Bar', 'early'), true) + assert.strictEqual(groupMatcher('late', 'matches', 'foo'), true) + assert.strictEqual(groupMatcher('barfoo'), false) + assert.strictEqual(groupMatcher('afoo', 'afooa', 'bara'), false) + }) }) From 3d9e15ed867342f746a6139dd8d1e45abb0516bd Mon Sep 17 00:00:00 2001 From: 2nofa11 <47783146+2nofa11@users.noreply.github.com> Date: Wed, 9 Jul 2025 23:37:10 +0900 Subject: [PATCH 03/17] docs: add Project Setup section to Developer Guide (#2780) Co-authored-by: Flo Edelmann --- docs/developer-guide/index.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/developer-guide/index.md b/docs/developer-guide/index.md index 2e1bb7da9..9bfc68007 100644 --- a/docs/developer-guide/index.md +++ b/docs/developer-guide/index.md @@ -8,6 +8,15 @@ If you think you’ve found a bug in ESLint, please [create a new issue](https:/ Please include as much detail as possible to help us properly address your issue. If we need to triage issues and constantly ask people for more detail, that’s time taken away from actually fixing issues. Help us be as efficient as possible by including a lot of detail in your issues. +## :seedling: Project Setup + +To develop locally, fork the eslint-plugin-vue repository and clone it in your local machine. Use the [npm](https://www.npmjs.com/) package manager to install and link dependencies and the LTS version of [Node.js](https://nodejs.org/). + +To develop and test the `eslint-plugin-vue` package: + +1. Run `npm install` in the project's root folder. +2. Run `npm test` to make sure everything is set up correctly. + ## :sparkles: Proposing a new rule or a rule change In order to add a new rule or a rule change, you should: From 26d99fda70e81b8d328f46122b670582366783ff Mon Sep 17 00:00:00 2001 From: Wayne Zhang Date: Fri, 11 Jul 2025 18:09:07 +0800 Subject: [PATCH 04/17] test(require-explicit-slots): import slot type (#2786) --- tests/fixtures/typescript/src/test01.ts | 5 +++ tests/lib/rules/require-explicit-slots.js | 40 +++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/tests/fixtures/typescript/src/test01.ts b/tests/fixtures/typescript/src/test01.ts index d14550843..917643c67 100644 --- a/tests/fixtures/typescript/src/test01.ts +++ b/tests/fixtures/typescript/src/test01.ts @@ -18,3 +18,8 @@ export type Props2 = { h?: string[] i?: readonly string[] } + +export type Slots1 = { + default(props: { msg: string }): any + foo(props: { msg: string }): any +} diff --git a/tests/lib/rules/require-explicit-slots.js b/tests/lib/rules/require-explicit-slots.js index f99614119..afee739a6 100644 --- a/tests/lib/rules/require-explicit-slots.js +++ b/tests/lib/rules/require-explicit-slots.js @@ -6,6 +6,9 @@ const RuleTester = require('../../eslint-compat').RuleTester const rule = require('../../../lib/rules/require-explicit-slots') +const { + getTypeScriptFixtureTestOptions +} = require('../../test-utils/typescript') const tester = new RuleTester({ languageOptions: { @@ -276,6 +279,21 @@ tester.run('require-explicit-slots', rule, { }) ` }, + { + filename: 'test.vue', + code: ` + + `, + ...getTypeScriptFixtureTestOptions() + }, { filename: 'test.vue', code: ` @@ -656,6 +674,28 @@ tester.run('require-explicit-slots', rule, { } ] }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Slots must be explicitly defined.', + line: 5, + column: 11 + } + ], + ...getTypeScriptFixtureTestOptions() + }, { // ignore attribute binding except string literal filename: 'test.vue', From 9140c63532991b1cb6261a837871f1b5e80b04a3 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Sun, 13 Jul 2025 09:54:58 +0200 Subject: [PATCH 05/17] feat(no-deprecated-slot-attribute): add ignoreParents option (#2784) Co-authored-by: Flo Edelmann --- .changeset/beige-teams-camp.md | 5 + docs/rules/no-deprecated-slot-attribute.md | 38 +++++- lib/rules/no-deprecated-slot-attribute.js | 5 + lib/rules/syntaxes/slot-attribute.js | 15 ++- lib/utils/index.js | 2 +- .../lib/rules/no-deprecated-slot-attribute.js | 108 ++++++++++++++++++ 6 files changed, 167 insertions(+), 6 deletions(-) create mode 100644 .changeset/beige-teams-camp.md diff --git a/.changeset/beige-teams-camp.md b/.changeset/beige-teams-camp.md new file mode 100644 index 000000000..49c2a317b --- /dev/null +++ b/.changeset/beige-teams-camp.md @@ -0,0 +1,5 @@ +--- +'eslint-plugin-vue': minor +--- + +Added `ignoreParents` option to [`vue/no-deprecated-slot-attribute`](https://eslint.vuejs.org/rules/no-deprecated-slot-attribute.html) diff --git a/docs/rules/no-deprecated-slot-attribute.md b/docs/rules/no-deprecated-slot-attribute.md index df4575cc4..34f941ec4 100644 --- a/docs/rules/no-deprecated-slot-attribute.md +++ b/docs/rules/no-deprecated-slot-attribute.md @@ -43,16 +43,18 @@ This rule reports deprecated `slot` attribute in Vue.js v2.6.0+. ```json { "vue/no-deprecated-slot-attribute": ["error", { - "ignore": ["my-component"] + "ignore": ["my-component"], + "ignoreParents": ["my-web-component"], }] } ``` - `"ignore"` (`string[]`) An array of tags or regular expression patterns (e.g. `/^custom-/`) that ignore these rules. This option will check both kebab-case and PascalCase versions of the given tag names. Default is empty. +- `"ignoreParents"` (`string[]`) An array of tags or regular expression patterns (e.g. `/^custom-/`) for parents that ignore these rules. This option is especially useful for [Web-Components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components). Default is empty. ### `"ignore": ["my-component"]` - + ```vue `, options: [{ ignore: ['/one/', '/^Two$/i', '/^my-.*/i'] }] + }, + { + code: ``, + options: [{ ignoreParents: ['LinkList'] }] + }, + { + code: ``, + options: [{ ignoreParents: ['/^Link/'] }] } ], invalid: [ @@ -732,6 +756,90 @@ tester.run('no-deprecated-slot-attribute', rule, { } ] }, + { + code: ` + `, + output: ` + `, + options: [ + { + ignoreParents: ['my-component'] + } + ], + errors: [ + { + message: '`slot` attributes are deprecated.', + line: 9, + column: 16, + endLine: 9, + endColumn: 20 + } + ] + }, + { + code: ` + `, + output: ` + `, + options: [ + { + ignoreParents: ['/component$/'] + } + ], + errors: [ + { + message: '`slot` attributes are deprecated.', + line: 9, + column: 16, + endLine: 9, + endColumn: 20 + } + ] + }, { code: ` `, errors: [ - '`slot` attributes are deprecated.', - '`slot` attributes are deprecated.' + { + message: '`slot` attributes are deprecated.', + line: 4, + column: 37, + endLine: 4, + endColumn: 42 + }, + { + message: '`slot` attributes are deprecated.', + line: 7, + column: 37, + endLine: 7, + endColumn: 42 + } ] }, { @@ -646,8 +784,20 @@ tester.run('no-deprecated-slot-attribute', rule, { `, errors: [ - '`slot` attributes are deprecated.', - '`slot` attributes are deprecated.' + { + message: '`slot` attributes are deprecated.', + line: 4, + column: 41, + endLine: 4, + endColumn: 46 + }, + { + message: '`slot` attributes are deprecated.', + line: 7, + column: 37, + endLine: 7, + endColumn: 42 + } ] }, { @@ -678,7 +828,15 @@ tester.run('no-deprecated-slot-attribute', rule, { ignore: ['one'] } ], - errors: ['`slot` attributes are deprecated.'] + errors: [ + { + message: '`slot` attributes are deprecated.', + line: 7, + column: 16, + endLine: 7, + endColumn: 20 + } + ] }, { code: ` @@ -863,7 +1021,15 @@ tester.run('no-deprecated-slot-attribute', rule, { `, - errors: ['`slot` attributes are deprecated.'] + errors: [ + { + message: '`slot` attributes are deprecated.', + line: 6, + column: 13, + endLine: 6, + endColumn: 18 + } + ] }, { code: ` @@ -876,7 +1042,15 @@ tester.run('no-deprecated-slot-attribute', rule, { `, output: null, - errors: ['`slot` attributes are deprecated.'] + errors: [ + { + message: '`slot` attributes are deprecated.', + line: 4, + column: 16, + endLine: 4, + endColumn: 20 + } + ] } ] }) From cf3c4eb97e83d7190a035275b3e2f339e2ade8af Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Fri, 18 Jul 2025 09:04:24 +0200 Subject: [PATCH 09/17] test(array-bracket-newline): make tests more strict (#2799) --- tests/lib/rules/array-bracket-newline.js | 124 ++++++++++++++++++++--- 1 file changed, 110 insertions(+), 14 deletions(-) diff --git a/tests/lib/rules/array-bracket-newline.js b/tests/lib/rules/array-bracket-newline.js index 380f61e4f..0334d2942 100644 --- a/tests/lib/rules/array-bracket-newline.js +++ b/tests/lib/rules/array-bracket-newline.js @@ -40,61 +40,145 @@ tester.run('array-bracket-newline', rule, { { code: '', output: '', - errors: ["There should be no linebreak after '['."] + errors: [ + { + message: "There should be no linebreak after '['.", + line: 1, + column: 23, + endLine: 1, + endColumn: 24 + } + ] }, { code: '', output: '', - errors: ["There should be no linebreak before ']'."] + errors: [ + { + message: "There should be no linebreak before ']'.", + line: 2, + column: 1, + endLine: 2, + endColumn: 2 + } + ] }, { code: '', output: '', errors: [ - "There should be no linebreak after '['.", - "There should be no linebreak before ']'." + { + message: "There should be no linebreak after '['.", + line: 1, + column: 23, + endLine: 1, + endColumn: 24 + }, + { + message: "There should be no linebreak before ']'.", + line: 3, + column: 1, + endLine: 3, + endColumn: 2 + } ] }, { code: '', output: '', options: ['never'], - errors: ["There should be no linebreak after '['."] + errors: [ + { + message: "There should be no linebreak after '['.", + line: 1, + column: 23, + endLine: 1, + endColumn: 24 + } + ] }, { code: '', output: '', options: ['never'], - errors: ["There should be no linebreak before ']'."] + errors: [ + { + message: "There should be no linebreak before ']'.", + line: 2, + column: 1, + endLine: 2, + endColumn: 2 + } + ] }, { code: '', output: '', options: ['never'], errors: [ - "There should be no linebreak after '['.", - "There should be no linebreak before ']'." + { + message: "There should be no linebreak after '['.", + line: 1, + column: 23, + endLine: 1, + endColumn: 24 + }, + { + message: "There should be no linebreak before ']'.", + line: 3, + column: 1, + endLine: 3, + endColumn: 2 + } ] }, { code: '', output: '', options: ['always'], - errors: ["A linebreak is required before ']'."] + errors: [ + { + message: "A linebreak is required before ']'.", + line: 2, + column: 2, + endLine: 2, + endColumn: 3 + } + ] }, { code: '', output: '', options: ['always'], - errors: ["A linebreak is required after '['."] + errors: [ + { + message: "A linebreak is required after '['.", + line: 1, + column: 23, + endLine: 1, + endColumn: 24 + } + ] }, { code: '', output: '', options: ['always'], errors: [ - "A linebreak is required after '['.", - "A linebreak is required before ']'." + { + message: "A linebreak is required after '['.", + line: 1, + column: 23, + endLine: 1, + endColumn: 24 + }, + { + message: "A linebreak is required before ']'.", + line: 1, + column: 25, + endLine: 1, + endColumn: 26 + } ] }, { @@ -102,8 +186,20 @@ tester.run('array-bracket-newline', rule, { output: '', options: ['always'], errors: [ - "A linebreak is required after '['.", - "A linebreak is required before ']'." + { + message: "A linebreak is required after '['.", + line: 1, + column: 27, + endLine: 1, + endColumn: 28 + }, + { + message: "A linebreak is required before ']'.", + line: 1, + column: 29, + endLine: 1, + endColumn: 30 + } ] } ] From 5bde25ae9995461bd75e29457573d63ea21b63ee Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Fri, 18 Jul 2025 11:23:11 +0200 Subject: [PATCH 10/17] test(array-bracket-spacing): make tests more strict (#2800) --- tests/lib/rules/array-bracket-spacing.js | 124 ++++++++++++++++++++--- 1 file changed, 110 insertions(+), 14 deletions(-) diff --git a/tests/lib/rules/array-bracket-spacing.js b/tests/lib/rules/array-bracket-spacing.js index 1eea108e6..6218d232e 100644 --- a/tests/lib/rules/array-bracket-spacing.js +++ b/tests/lib/rules/array-bracket-spacing.js @@ -39,61 +39,145 @@ tester.run('array-bracket-spacing', rule, { { code: '', output: '', - errors: ["There should be no space after '['."] + errors: [ + { + message: "There should be no space after '['.", + line: 1, + column: 24, + endLine: 1, + endColumn: 25 + } + ] }, { code: '', output: '', - errors: ["There should be no space before ']'."] + errors: [ + { + message: "There should be no space before ']'.", + line: 1, + column: 25, + endLine: 1, + endColumn: 26 + } + ] }, { code: '', output: '', errors: [ - "There should be no space after '['.", - "There should be no space before ']'." + { + message: "There should be no space after '['.", + line: 1, + column: 24, + endLine: 1, + endColumn: 25 + }, + { + message: "There should be no space before ']'.", + line: 1, + column: 26, + endLine: 1, + endColumn: 27 + } ] }, { code: '', output: '', options: ['never'], - errors: ["There should be no space after '['."] + errors: [ + { + message: "There should be no space after '['.", + line: 1, + column: 24, + endLine: 1, + endColumn: 25 + } + ] }, { code: '', output: '', options: ['never'], - errors: ["There should be no space before ']'."] + errors: [ + { + message: "There should be no space before ']'.", + line: 1, + column: 25, + endLine: 1, + endColumn: 26 + } + ] }, { code: '', output: '', options: ['never'], errors: [ - "There should be no space after '['.", - "There should be no space before ']'." + { + message: "There should be no space after '['.", + line: 1, + column: 24, + endLine: 1, + endColumn: 25 + }, + { + message: "There should be no space before ']'.", + line: 1, + column: 26, + endLine: 1, + endColumn: 27 + } ] }, { code: '', output: '', options: ['always'], - errors: ["A space is required before ']'."] + errors: [ + { + message: "A space is required before ']'.", + line: 1, + column: 26, + endLine: 1, + endColumn: 27 + } + ] }, { code: '', output: '', options: ['always'], - errors: ["A space is required after '['."] + errors: [ + { + message: "A space is required after '['.", + line: 1, + column: 23, + endLine: 1, + endColumn: 24 + } + ] }, { code: '', output: '', options: ['always'], errors: [ - "A space is required after '['.", - "A space is required before ']'." + { + message: "A space is required after '['.", + line: 1, + column: 23, + endLine: 1, + endColumn: 24 + }, + { + message: "A space is required before ']'.", + line: 1, + column: 25, + endLine: 1, + endColumn: 26 + } ] }, { @@ -101,8 +185,20 @@ tester.run('array-bracket-spacing', rule, { output: '', options: ['always'], errors: [ - "A space is required after '['.", - "A space is required before ']'." + { + message: "A space is required after '['.", + line: 1, + column: 27, + endLine: 1, + endColumn: 28 + }, + { + message: "A space is required before ']'.", + line: 1, + column: 29, + endLine: 1, + endColumn: 30 + } ] } ] From 468789bfbf67691d4163f5808ce960d7b16f3f83 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Fri, 18 Jul 2025 15:04:17 +0200 Subject: [PATCH 11/17] test(arrow-spacing): make tests more strict (#2801) --- tests/lib/rules/arrow-spacing.js | 40 +++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/tests/lib/rules/arrow-spacing.js b/tests/lib/rules/arrow-spacing.js index d80c255e8..d05c9f269 100644 --- a/tests/lib/rules/arrow-spacing.js +++ b/tests/lib/rules/arrow-spacing.js @@ -48,11 +48,17 @@ tester.run('arrow-spacing', rule, { errors: [ { message: 'Missing space before =>.', - line: 3 + line: 3, + column: 24, + endLine: 3, + endColumn: 25 }, { message: 'Missing space after =>.', - line: 3 + line: 3, + column: 27, + endLine: 3, + endColumn: 28 } ] }, @@ -68,11 +74,17 @@ tester.run('arrow-spacing', rule, { errors: [ { message: 'Missing space before =>.', - line: 3 + line: 3, + column: 25, + endLine: 3, + endColumn: 26 }, { message: 'Missing space after =>.', - line: 3 + line: 3, + column: 28, + endLine: 3, + endColumn: 29 } ] }, @@ -94,11 +106,17 @@ tester.run('arrow-spacing', rule, { errors: [ { message: 'Missing space before =>.', - line: 4 + line: 4, + column: 25, + endLine: 4, + endColumn: 26 }, { message: 'Missing space after =>.', - line: 4 + line: 4, + column: 28, + endLine: 4, + endColumn: 29 } ] }, @@ -115,11 +133,17 @@ tester.run('arrow-spacing', rule, { errors: [ { message: 'Unexpected space before =>.', - line: 3 + line: 3, + column: 24, + endLine: 3, + endColumn: 25 }, { message: 'Unexpected space after =>.', - line: 3 + line: 3, + column: 29, + endLine: 3, + endColumn: 30 } ] } From f75a0030657c49fda257c720fcf9bf43852291a8 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Fri, 18 Jul 2025 15:06:54 +0200 Subject: [PATCH 12/17] test(attribute-hyphenation): make tests more strict (#2802) --- tests/lib/rules/attribute-hyphenation.js | 138 ++++++++++++++++++----- 1 file changed, 110 insertions(+), 28 deletions(-) diff --git a/tests/lib/rules/attribute-hyphenation.js b/tests/lib/rules/attribute-hyphenation.js index 738d59ae9..eac974e4e 100644 --- a/tests/lib/rules/attribute-hyphenation.js +++ b/tests/lib/rules/attribute-hyphenation.js @@ -118,7 +118,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute 'my-prop' can't be hyphenated.", type: 'VIdentifier', - line: 1 + line: 1, + column: 24, + endLine: 1, + endColumn: 37 } ] }, @@ -131,7 +134,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute 'MyProp' must be hyphenated.", type: 'VIdentifier', - line: 1 + line: 1, + column: 24, + endLine: 1, + endColumn: 36 } ] }, @@ -145,7 +151,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute ':my-prop' can't be hyphenated.", type: 'VDirectiveKey', - line: 1 + line: 1, + column: 24, + endLine: 1, + endColumn: 39 } ] }, @@ -158,7 +167,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute ':MyProp' must be hyphenated.", type: 'VDirectiveKey', - line: 1 + line: 1, + column: 24, + endLine: 1, + endColumn: 38 } ] }, @@ -172,7 +184,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute 'v-bind:my-prop' can't be hyphenated.", type: 'VDirectiveKey', - line: 1 + line: 1, + column: 24, + endLine: 1, + endColumn: 45 } ] }, @@ -185,7 +200,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute 'v-bind:MyProp' must be hyphenated.", type: 'VDirectiveKey', - line: 1 + line: 1, + column: 24, + endLine: 1, + endColumn: 44 } ] }, @@ -198,7 +216,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute 'v-bind:MyProp' must be hyphenated.", type: 'VDirectiveKey', - line: 1 + line: 1, + column: 24, + endLine: 1, + endColumn: 44 } ] }, @@ -212,7 +233,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute ':second-prop' can't be hyphenated.", type: 'VDirectiveKey', - line: 1 + line: 1, + column: 46, + endLine: 1, + endColumn: 65 } ] }, @@ -226,7 +250,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute 'v-bind:myProp' must be hyphenated.", type: 'VDirectiveKey', - line: 1 + line: 1, + column: 24, + endLine: 1, + endColumn: 44 } ] }, @@ -240,7 +267,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute 'v-bind:propID' must be hyphenated.", type: 'VDirectiveKey', - line: 1 + line: 1, + column: 24, + endLine: 1, + endColumn: 44 } ] }, @@ -255,7 +285,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute 'v-model:my-prop' can't be hyphenated.", type: 'VDirectiveKey', - line: 1 + line: 1, + column: 24, + endLine: 1, + endColumn: 46 } ] }, @@ -269,7 +302,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute 'v-model:myProp' must be hyphenated.", type: 'VDirectiveKey', - line: 1 + line: 1, + column: 24, + endLine: 1, + endColumn: 45 } ] }, @@ -282,7 +318,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute 'v-model:MyProp' must be hyphenated.", type: 'VDirectiveKey', - line: 1 + line: 1, + column: 24, + endLine: 1, + endColumn: 45 } ] }, @@ -307,7 +346,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute 'third-custom' can't be hyphenated.", type: 'VIdentifier', - line: 3 + line: 3, + column: 111, + endLine: 3, + endColumn: 129 } ] }, @@ -332,12 +374,18 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute 'custom-hyphen' can't be hyphenated.", type: 'VIdentifier', - line: 3 + line: 3, + column: 71, + endLine: 3, + endColumn: 90 }, { message: "Attribute 'second-custom' can't be hyphenated.", type: 'VIdentifier', - line: 3 + line: 3, + column: 91, + endLine: 3, + endColumn: 110 } ] }, @@ -350,7 +398,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute 'my-prop' can't be hyphenated.", type: 'VIdentifier', - line: 1 + line: 1, + column: 22, + endLine: 1, + endColumn: 35 } ] }, @@ -363,7 +414,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute 'MyProp' must be hyphenated.", type: 'VIdentifier', - line: 1 + line: 1, + column: 22, + endLine: 1, + endColumn: 34 } ] }, @@ -376,7 +430,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute ':attr_Gg' must be hyphenated.", type: 'VDirectiveKey', - line: 1 + line: 1, + column: 24, + endLine: 1, + endColumn: 39 } ] }, @@ -389,7 +446,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute ':Attr_Hh' must be hyphenated.", type: 'VDirectiveKey', - line: 1 + line: 1, + column: 24, + endLine: 1, + endColumn: 39 } ] }, @@ -402,7 +462,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute ':_attr_Jj' must be hyphenated.", type: 'VDirectiveKey', - line: 1 + line: 1, + column: 24, + endLine: 1, + endColumn: 40 } ] }, @@ -415,7 +478,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute ':_attrKk' must be hyphenated.", type: 'VDirectiveKey', - line: 1 + line: 1, + column: 24, + endLine: 1, + endColumn: 39 } ] }, @@ -428,7 +494,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute ':_AttrLl' must be hyphenated.", type: 'VDirectiveKey', - line: 1 + line: 1, + column: 24, + endLine: 1, + endColumn: 39 } ] }, @@ -441,7 +510,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute ':my-custom_prop' can't be hyphenated.", type: 'VDirectiveKey', - line: 1 + line: 1, + column: 24, + endLine: 1, + endColumn: 46 } ] }, @@ -454,7 +526,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute ':myAge.sync' must be hyphenated.", type: 'VDirectiveKey', - line: 1 + line: 1, + column: 24, + endLine: 1, + endColumn: 42 } ] }, @@ -467,7 +542,10 @@ ruleTester.run('attribute-hyphenation', rule, { { message: "Attribute ':my-age.sync' can't be hyphenated.", type: 'VDirectiveKey', - line: 1 + line: 1, + column: 24, + endLine: 1, + endColumn: 43 } ] }, @@ -490,7 +568,9 @@ ruleTester.run('attribute-hyphenation', rule, { message: "Attribute 'my-prop' can't be hyphenated.", type: 'VIdentifier', line: 3, - column: 17 + column: 17, + endLine: 3, + endColumn: 24 } ] }, @@ -513,7 +593,9 @@ ruleTester.run('attribute-hyphenation', rule, { message: "Attribute 'myProp' must be hyphenated.", type: 'VIdentifier', line: 3, - column: 17 + column: 17, + endLine: 3, + endColumn: 23 } ] } From c66d6b641d8dd79596d9b31d9223640827c0f870 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Fri, 18 Jul 2025 16:35:14 +0200 Subject: [PATCH 13/17] test(block-lang): make tests more strict (#2804) --- tests/lib/rules/block-lang.js | 52 ++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/tests/lib/rules/block-lang.js b/tests/lib/rules/block-lang.js index 5f7851b54..ad1b9842c 100644 --- a/tests/lib/rules/block-lang.js +++ b/tests/lib/rules/block-lang.js @@ -44,7 +44,9 @@ tester.run('block-lang', rule, { { message: `Only "ts" can be used for the 'lang' attribute of '