-
-
Notifications
You must be signed in to change notification settings - Fork 43
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
Support sorting of attributes #22
Comments
In frameworks like Vue we have property bindings In example: //- Button will be enabled
v-btn(color="primary", disabled, :disabled="false") Test
//- Button will be disabled
v-btn(color="primary", :disabled="false", disabled) Test Currently, the formatter first places an attribute with a colon because the ASCII literal is In my opinion, property bindings should take precedence over non-property bindings Need to look into the behavior of |
Theoretically ready for a beta release Further thoughts:
|
I'd like to help with this feature, how could I start? |
Thanks :) and yes I could possibly need your (someone else help) So all in all, broken down: It's just a problem of how I consume a thing from were I create a And then I want to provide a robust default value for this option to support a common base for vue AND angular together |
What about this: interface pugSortingAttributes {
sortLeft?: string[]
sortRight?: string[]
} where you can specify which starting content should be placed where. You have 3 positions: left - middle - right left and rightAre positioned by the order that you specified in the options. middleSorted ASC Exampleoptions const pugSortingAttributes = {
sortLeft: ['#', ':', 'v-'],
sortRight: ['@'],
} input FooComponent(
@click="fooAction"
foo="123"
:bind="888"
#child="props"
v-if="true"
v-for="foo in bar"
bar="321"
) output FooComponent(
#child="props"
:bind="888"
v-for="foo in bar"
v-if="true"
bar="321"
foo="123"
@click="fooAction"
) Testingconst sort = (options) => {
const {
sortLeft = [],
sortRight = []
} = options || {}
const sortFirst = [...sortLeft].reverse()
const sortLast = [...sortRight].reverse()
return (items) => [...items].sort((A, B) => {
const [a] = A
const [b] = B
const aLeft = sortFirst.findIndex(item => a.startsWith(item)) + 1
const bLeft = sortFirst.findIndex(item => b.startsWith(item)) + 1
const left = aLeft - bLeft
if (left > 0) return -1
if (left < 0) return 1
const aRight = sortLast.findIndex(item => a.startsWith(item)) + 1
const bRight = sortLast.findIndex(item => b.startsWith(item)) + 1
const right = aRight - bRight
if (right > 0) return 1
if (right < 0) return -1
if (a > b) return 1
if (a < b) return -1
return 0
})
}
// Test Cases
const cases = [
// Case 0
{
input: {},
expect: {},
options: {},
},
// Case 1
{
input: {
foo: 123,
'v-for': 555,
bar: 321,
},
expect: {
bar: 321,
foo: 123,
'v-for': 555,
},
options: {},
},
// Case 2
{
input: {
foo: 123,
'v-for': 555,
bar: 321,
},
expect: {
bar: 321,
foo: 123,
'v-for': 555,
},
options: {
sortLeft: [],
sortRight: [],
},
},
// Case 3
{
input: {
foo: 123,
'v-for': 555,
bar: 321,
},
expect: {
'v-for': 555,
bar: 321,
foo: 123,
},
options: {
sortLeft: ['v-'],
sortRight: [],
},
},
// Case 4
{
input: {
foo: 123,
'v-if': 666,
'v-for': 555,
bar: 321,
},
expect: {
'v-for': 555,
'v-if': 666,
bar: 321,
foo: 123,
},
options: {
sortLeft: ['v-'],
sortRight: [],
},
},
// Case 5
{
input: {
'@click': 000,
foo: 123,
'v-if': 666,
'v-for': 555,
bar: 321,
},
expect: {
'v-for': 555,
'v-if': 666,
bar: 321,
foo: 123,
'@click': 000,
},
options: {
sortLeft: ['v-'],
sortRight: ['@'],
},
},
// Case 6
{
input: {
'@click': 000,
foo: 123,
':bind': 888,
'#slot': 777,
'v-if': 666,
'v-for': 555,
bar: 321,
},
expect: {
'#slot': 777,
':bind': 888,
'v-for': 555,
'v-if': 666,
bar: 321,
foo: 123,
'@click': 000,
},
options: {
sortLeft: ['#', ':', 'v-',],
sortRight: ['@'],
},
},
]
// "Test Framework"
const isEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b)
const fromEntries = (acc, [key, value]) => ({...acc, [key]: value})
const sortObject = (obj, sortHandler) =>
sortHandler(Object.entries(obj))
.reduce(fromEntries, {})
const test = (cases, sortHandler) => cases
.map((payload, index) => {
const {input, expect, options} = payload
const output = sortObject(input, sortHandler(options))
const equal = isEqual(output, expect)
return equal
? {testCase: index, equal}
: {testCase: index, equal, expect, output}
})
.forEach(item => console.log(item))
test(cases, sort) I don't know if you already though about this, honestly I should've seen what you already did before, but I forgot it and I didn't want to throw this away 😅 |
What about |
You could set const pugSortingAttributes = {
sortLeft: [
'#',
'v-slot',
'v-for',
':key',
'v-if',
':',
'v-',
],
sortRight: ['v-on', '@'],
} it would output FooComponent(
#child="props"
v-for="foo in bar"
:key="foo.id"
v-if="true"
:bind="888"
bar="321"
foo="123"
v-on="$listeners"
@click="fooAction"
) Not that this would be the best pattern, I'm thinking only about the options specs |
Mhhhh Could you also please look into angular support? https://angular.io/guide/binding-syntax |
However, if you had a prop named like Maybe should be interesting the mixed usage of RegEx, as you proposed, and maybe a object configuration for easing. Spectype SortingParameter = string | RegExp | {
value: string,
type: 'exactMatch' | 'contains' | 'startsWith' | 'endsWith'
}
interface SortingAttributes {
sortLeft?: SortingParameter[]
sortRight?: SortingParameter[]
} Vue usageconst pugSortingAttributes: SortingAttributes = {
sortLeft: [
{ value: '#', type: 'startsWith' },
{ value: 'v-slot', type: 'startsWith' },
{ value: 'v-for', type: 'exactMatch' },
{ value: ':key', type: 'exactMatch' },
{ value: 'v-bind:key', type: 'exactMatch' },
{ value: 'v-if', type: 'exactMatch' },
{ value: ':', type: 'startsWith' },
{ value: 'v-bind:', type: 'startsWith' },
{ value: 'v-', type: 'startsWith' },
],
sortRight: [
'v-on',
'@',
],
} Angular usage
WorkaroundsIf these propositions aren't compatible with Prettier's possibilities, we could create some kind of syntax to convert this into a string format, which, however, would make it a lot harder to customize. Default patternsThere should be an option to toggle between frameworks too, as they may contradict themselves. |
I've been thinking about a parameter for specifying the framework for a long time. |
TODO on this issueOk, so, just for organize this topic. Frameworks
Feature
|
It would be ignored in case of a custom sorting is defined, so this parameter may be actually pointless. |
@Shinigami92 can I split this issue in 3?
The reason is facilitate on organize information about syntaxes and use cases and avoid distraction from the main subject of this issue, which, in my opinion, should be the implementation itself. Sorry for spamming |
Don't be sorry for spamming and yes feel free 😁 You should also have a look into what I already had implemented in #23 You (or I when I have more time e.g. over the upcoming weekend) should mainly investigate deeper into whats possible via prettier options I'm currently working on new typedefs of these options: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/47626/files |
@SkyaTura https://v3.vuejs.org/guide/migration/v-if-v-for.html#overview 👀 |
So, two things:
Edit: |
About Angular, it would be nice if someone that really uses it took this task, because I really don't know much and I am no having enough time to learn it :/ |
I'm thinking about a Oh! and my formatter is not a linter 😉 Edit: the more I thought about provide a template, the more I think that's not a good idea. |
I agree.
Sometimes I forget this, because I use like as if it was 🙈 |
Taking your prev example and convert it to a regex array 🤔 const pugSortingAttributes: SortingAttributes = {
sortLeft: [
'^#',
'^v-slot',
'^v-for$',
'^:key$',
'^v-bind:key$',
'^v-if$',
'^:',
'^v-bind',
'^v-'
],
sortRight: [
'^v-on',
'^@'
]
} And cause of prettier limitations I thing it should be: const pugSortAttributesLeft: string[] = [
'^#',
'^v-slot',
'^v-for$',
'^:key$',
'^v-bind:key$',
'^v-if$',
'^:',
'^v-bind',
'^v-'
];
const pugSortAttributesRight: string[] = ['^v-on', '^@']; ... but what should be done with all the attributes in the middle?Sort literally?This could break code in the worst case //- ALWAYS disabled
v-text-field(:disabled="val", disabled)
//- depends on `val`
v-text-field(disabled, :disabled="val") Maybe this is also in angular, but I don't know angular+pug Don't sort at all?Why are some parts sorted and some parts not 🤔 🤷♂️ |
Maybe the const pugSortAttributes: ('none' | 'ascending' | 'descending') = 'none'
const pugSortAttributesLeft: string[] = []
const pugSortAttributesRight: string[] = []
I see the example you gave is a design flaw, that shouldn't be there at all. HOWEVER, if this is a valid use case for someone, it would be simply solved by moving bindings to the
We'll never know Maybe sorting in a way that's "visually pretty", like a stair of attributes: Component(
simple
bigAttributes
evenBiggerAttributes
makesABeautifulStaircase
ofAscendingLengthVariables
) What about |
@SkyaTura I started to work on the sorting feature! So there are some changes to the concept:
|
@SkyaTura I released a beta: https://www.npmjs.com/package/@prettier/plugin-pug/v/1.7.0-sorting-feature.1 I'm currently testing against a huge organizational repo (~80 pug files each around 300 lines long) My setup so far: {
"arrowParens": "always",
"bracketSpacing": true,
"printWidth": 120,
"semi": true,
"singleQuote": true,
"pugSingleQuote": false,
"trailingComma": "none",
"tabWidth": 2,
"useTabs": false,
"pugSortAttributesBeginning": [
"^cols$",
"^v-else$",
"^v-for$",
"^:key$",
"^v-if$",
"^v-on$",
"^v-bind$",
"^v-model$",
"^name$",
"^:*label$",
"^:items$",
"^:*item-text$",
"^:*item-value$",
"^:*item-disabled$",
"^:*placeholder$",
"^:*src$"
],
"pugSortAttributesEnd": [
"^:(?!(loading|disabled))",
"^target$",
"^@click",
"^@",
"^:loading$",
"^:disabled$",
"^data-"
]
} |
@SkyaTura One problem I encountered so far is: e.g. v-dialog(v-model="csvUploadStart", :persistent="loading", width="500")
// first v-mode
// then others by alphabet... So to fix this, I currently have to define 26 patterns to order alphabetically ^^' But I think more and more about to just change my own art of sort :D |
Wow, interesting case... v-text-field(v-model="email", name="email", label="Your email address", filled, required, :rules="...")
v-checkbox(v-model="", :label="$t('...')", color="primary")
v-chip.mr-2.px-1(v-text="$t('...')", color="...", label, outlined, small)
🤔 So this is something impossible to configure... |
Closer to perfection... (Vue 2) {
"arrowParens": "always",
"bracketSpacing": true,
"printWidth": 120,
"semi": true,
"singleQuote": true,
"pugSingleQuote": false,
"trailingComma": "none",
"tabWidth": 2,
"useTabs": false,
"pugSortAttributesBeginning": [
"^cols$",
"^v-else$",
"^v-for$",
"^:key$",
"^v-if$",
"^v-else-if$",
"^v-on$",
"^v-bind$",
"^ref$",
"^v-model",
"^name$",
"^:?type$",
"^:value$",
"^v-text$",
"^:?label$",
"^:headers$",
"^:items$",
"^:?item-text$",
"^:?item-value$",
"^:?item-disabled$",
"^:?placeholder$",
"^:?src$",
"^:?color$",
"^:?text-color$",
"^:?icon$",
"^:?small$"
],
"pugSortAttributesEnd": [
"^:?hint$",
"^:?persistent-hint$",
"^prepend-",
"^@click:prepend",
"^append-",
"^@click:append",
"^:to$",
"^exact$",
"^:(?!(width|height|loading|disabled|data-))",
"^target$",
"^:?width$",
"^:?height$",
"^@click",
"^@",
"^:loading$",
"^:disabled$",
"^:?data-"
]
}
|
Would be nice when you also share your sort config after testing the beta release 🙂 |
I think I may found a bug 😱 Every time I try to format this file it get a different output: <template lang="pug">
Foo(
B
a
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
V
W
X
Y
Z
A
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x
y
z
)
</template> My config to reproduce: const prettier = {
trailingComma: 'es5',
printWidth: 80,
tabWidth: 2,
semi: false,
singleQuote: true,
endOfLine: 'lf',
plugins: ['@prettier/plugin-pug'],
pugSingleQuote: false,
attributeSeparator: 'none',
closingBracketPosition: 'new-line',
commentPreserveSpaces: 'trim-all',
pugSortAttributesEnd: ['^:'],
} EditI've made a PR for this problem #120 |
Support sorting of attributes
It would be nice if attributes could optionally be sorted.
Acceptance criterion
The text was updated successfully, but these errors were encountered: