-
-
Notifications
You must be signed in to change notification settings - Fork 681
Add block-attributes-order rule #1973
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
7c9babb
to
31666df
Compare
@Kingwl thanks for the PR! is the default order going to be <script setup lang="ts"></script> OR <script lang="ts" setup></script> it's hard to tell from the PR markdown file alone. PS: I think the former is better for consistency with official Vue documentation |
Added the default orders into markdown. AFAIK, there's no official Vue recommended block attributes order. @sodatea Any suggestions? |
@Kingwl https://vuejs.org/guide/typescript/composition-api.html Official Vue docs use So I think having the same default as that seems best : ) |
Done. And make style block has |
|
||
This rule aims to enforce ordering of block attributes. | ||
|
||
### The default order |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to discuss option formats.
If we want to be more granular and configurable by the user, I suggest using an array of objects with elements and ordering, like this:
{'vue/block-attributes-order': ['error',
[
{ element: 'script', order: ['setup, 'lang', 'src'] },
{ element: 'template', order: ['functional', 'lang', 'src'] },
{ element: 'style', order: ['scoped', 'module', 'lang', 'src'] },
{ element: 'i18n', order: ['global', 'locale', 'lang', 'src'] },
]
]}
You can use CSS selectors for the element
property. This is similar to the vue/component-tags-order option.
The order option specifies the order of the attributes. It can use attribute names, regular expressions and their arrays, and negation. This is similar to the svelte/sort-attributes option.
If you want lang and src to always be at the back:
{'vue/block-attributes-order': ['error',
[
{ element: '*', order: [['/.*/', '!/^(?:src|lang)$/'], 'lang', 'src'] }
]
]}
Patterns can be used to correct some order even for unknown blocks and unknown attributes.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated as comments.
This comment was marked as off-topic.
This comment was marked as off-topic.
{ | ||
element: 'style', | ||
order: [ | ||
WELL_KNOWN_STYLE_ATTRS.module, | ||
WELL_KNOWN_STYLE_ATTRS.scoped, | ||
WELL_KNOWN_STYLE_ATTRS.lang, | ||
WELL_KNOWN_STYLE_ATTRS.src | ||
] | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can inline the constants since they are only used here. I.e.:
{
element: 'style',
order: ['module', 'scoped', 'lang', 'src']
},
sidebarDepth: 0 | ||
title: vue/block-attributes-order | ||
description: enforce order of block attributes | ||
since: v4.3.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This rule is not yet released. Please remove this line and re-run npm run update
.
const matchedOption = normalizedOrderOptions.find((option) => | ||
matchOrderOption(option, element) | ||
) | ||
if (matchedOption) { | ||
return matchedOption | ||
} | ||
return normalizedDefaultOptions.find((option) => | ||
matchOrderOption(option, element) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why there is an if
and the same function call again otherwise. Could it not be simplified to this?
const matchedOption = normalizedOrderOptions.find((option) => | |
matchOrderOption(option, element) | |
) | |
if (matchedOption) { | |
return matchedOption | |
} | |
return normalizedDefaultOptions.find((option) => | |
matchOrderOption(option, element) | |
) | |
return normalizedDefaultOptions.find((option) => | |
matchOrderOption(option, element) | |
) |
{ | ||
filename: 'test-attributes-options-with-unknown-attribute.vue', | ||
code: '<template functional foo lang="pug" src="./template.html"></template>', | ||
options: [ | ||
[ | ||
{ | ||
element: 'template', | ||
order: ['functional', 'foo', 'lang', 'src'] | ||
} | ||
] | ||
] | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test file name doesn't match the code/options here.
}, | ||
{ | ||
filename: 'test-attributes-options-not-specify-some-attribute-2.vue', | ||
code: '<style module scoped src="./style.css" lang="less"></style>', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that unknown attributes should only be allowed at the end. This would simplify the rule logic and make some test cases obsolete.
{ | ||
type: 'VAttribute', | ||
message: 'Attribute "lang" should go before "src".' | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add line
and column
of the reported error to all the expected errors in this file. However, you can also remove the type: 'VAttribute'
everywhere.
function ordersToRegexOrders(item) { | ||
return Array.isArray(item) ? item.map((x) => new RegExp(x)) : new RegExp(item) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you change it to accept more complex options by doing the following?
function ordersToRegexOrders(item) { | |
return Array.isArray(item) ? item.map((x) => new RegExp(x)) : new RegExp(item) | |
} | |
const { toRegExp } = require('../utils/regexp') | |
/** | |
* @param {string | string[]} item | |
* @return {(str: string) => boolean} | |
*/ | |
function ordersToMatcher(item) { | |
return Array.isArray(item) ? compileMatcher(item) : compileMatcher([item]) | |
} | |
/** | |
* Compile matcher | |
* @param {string[]} pattern | |
* @return {(str: string) => boolean} | |
*/ | |
function compileMatcher(pattern) { | |
const rules = [] | |
for (const p of pattern) { | |
let negative, patternStr | |
if (p.startsWith("!")) { | |
// If there is `!` at the beginning, it will be parsed with a negative pattern. | |
negative = true | |
patternStr = p.substring(1) | |
} else { | |
negative = false | |
patternStr = p | |
} | |
const regex = toRegExp(patternStr) | |
rules.push({ negative, match: (str) => regex.test(str) }) | |
} | |
return (str) => { | |
// If the first rule is a negative pattern, they are considered to match if they do not match that pattern. | |
let result = Boolean(rules[0]?.negative) | |
for (const { negative, match } of rules) { | |
if (result === !negative) { | |
// Even if it matches, the result does not change, so skip it. | |
continue | |
} | |
if (match(str)) { | |
result = !negative | |
} | |
} | |
return result | |
} | |
} |
This change allows users to use pattern formats such as:
"foo"
… Matches only thefoo
attribute name."/foo/"
… Matches attribute names that match the/foo/
regex. That is, it matches the attribute name includingfoo
."!foo"
… Excludefoo
attribute from the matched attribute names. When used first in the array or alone, matches other than thefoo
attribute name."!/foo/"
… Excludes attributes that match the/foo/
regex from the matched attribute names. When used first in the array or alone, matches an attribute name that does not match the/foo/
regex.["foo", "/^bar/u"]
… Matches thefoo
attribute or the attribute name that matches the/^bar/u
regex.["/^foo/u", "!foo-bar", "/^baz/u"]
… Matches an attribute name that matches/^foo/u
and other thanfoo-bar
, or an attribute name that matches/^baz/u
.
I think the rule of other plugin will also be helpful.
https://ota-meshi.github.io/eslint-plugin-svelte/rules/sort-attributes/
type: 'string' | ||
}, | ||
order: { | ||
type: 'array', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please define the schema so that at least one item is set?
{ | ||
type: 'array', | ||
items: { | ||
type: 'object', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you define the schema so that the two properties are required?
WELL_KNOWN_TEMPLATE_ATTRS.functional, | ||
WELL_KNOWN_TEMPLATE_ATTRS.lang, | ||
WELL_KNOWN_TEMPLATE_ATTRS.src |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After changing it to accept complex ordering, could you change it so that lang
and src
are always ordered last?
e.g.
WELL_KNOWN_TEMPLATE_ATTRS.functional, | |
WELL_KNOWN_TEMPLATE_ATTRS.lang, | |
WELL_KNOWN_TEMPLATE_ATTRS.src | |
"functional", | |
["!lang", "!src"], | |
"lang", | |
"src" |
Change other elements in the same way.
WELL_KNOWN_TEMPLATE_ATTRS.src | ||
] | ||
} | ||
]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add the following item to force the order of the custom elements as well?
{
element: '*',
order: [
["!lang", "!src"],
"lang",
"src"
]
},
@Kingwl friendly bump : ) |
Hey there, would be great to have this rule 👍 Happy to help! I can also come up with a new pull request if that is preferred. |
From our side, a new PR replacing this one would be fine. |
I'll close this for now as it seems abandoned and there are already some merge conflicts. Feel free to open another PR 🙂 @paescuj this is your chance! 😉 |
@FloEdelmann I was looking for this rule. Has it been implemented since? |
No |
Fixes #1970