Skip to content

Commit

Permalink
fix(eslint-plugin): improve class ordering for Svelte components (#4205)
Browse files Browse the repository at this point in the history
Co-authored-by: Anthony Fu <[email protected]>
Co-authored-by: Chris <[email protected]>
  • Loading branch information
3 people authored Nov 29, 2024
1 parent d7cd925 commit c2df128
Show file tree
Hide file tree
Showing 6 changed files with 290 additions and 122 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
"simple-git-hooks": "catalog:",
"splitpanes": "catalog:",
"std-env": "catalog:",
"svelte-eslint-parser": "catalog:",
"taze": "catalog:",
"terser": "catalog:",
"tinyglobby": "catalog:",
Expand Down
1 change: 1 addition & 0 deletions packages/eslint-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
},
"devDependencies": {
"@unocss/eslint-plugin": "workspace:*",
"svelte-eslint-parser": "catalog:",
"vue-eslint-parser": "catalog:"
}
}
59 changes: 59 additions & 0 deletions packages/eslint-plugin/src/rules/order.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { fileURLToPath } from 'node:url'
import { $ as html, run } from 'eslint-vitest-rule-tester'
import svelteParser from 'svelte-eslint-parser'
import { expect } from 'vitest'
import * as vueParser from 'vue-eslint-parser'
import rule from './order'
Expand Down Expand Up @@ -43,3 +44,61 @@ run({
},
],
})

run({
name: 'order-svelte',
rule,
languageOptions: {
parser: svelteParser,
},
settings: {
unocss: {
configPath: fileURLToPath(new URL('./uno.config.ts', import.meta.url)),
},
},
valid: [
html`
<div class="ml-1 mr-1"></div>
`,
html`
<div class="pl1 pr1 {test ? 'ml-1 mr-1' : 'left-1 right-1'}"></div>
`,
],
invalid: [
{
code: html`
<div class="mr-1 ml-1"></div>
`,
output: output => expect(output).toMatchInlineSnapshot(`
"<div class="ml-1 mr-1"></div>"
`),
errors: [
{
messageId: 'invalid-order',
},
],
},
{
code: html`
<div class="pr1 pl1{test ? 'mr-1 ml-1' : 'right-1 left-1'}top-1 bottom-1"></div>
`,
output: output => expect(output).toMatchInlineSnapshot(`
"<div class="pl1 pr1 {test ? 'ml-1 mr-1' : 'left-1 right-1'} bottom-1 top-1"></div>"
`),
errors: [
{
messageId: 'invalid-order',
},
{
messageId: 'invalid-order',
},
{
messageId: 'invalid-order',
},
{
messageId: 'invalid-order',
},
],
},
],
})
53 changes: 46 additions & 7 deletions packages/eslint-plugin/src/rules/order.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { TSESTree } from '@typescript-eslint/types'
import type { ESLintUtils } from '@typescript-eslint/utils'
import type { RuleListener } from '@typescript-eslint/utils/ts-eslint'
import type { SvelteAttribute, SvelteLiteral, SvelteMustacheTag } from 'svelte-eslint-parser/lib/ast/html'
import { AST_TOKEN_TYPES } from '@typescript-eslint/types'
import { AST_NODES_WITH_QUOTES, CLASS_FIELDS } from '../constants'
import { createRule, syncAction } from './_'

Expand All @@ -19,24 +21,33 @@ export default createRule({
},
defaultOptions: [],
create(context) {
function checkLiteral(node: TSESTree.Literal) {
function checkLiteral(node: TSESTree.Literal | SvelteLiteral, addSpace?: 'before' | 'after' | undefined) {
if (typeof node.value !== 'string' || !node.value.trim())
return
const input = node.value
const sorted = syncAction(
let sorted = syncAction(
context.settings.unocss?.configPath,
'sort',
input,
).trim()

if (addSpace === 'before')
sorted = ` ${sorted}`
else if (addSpace === 'after')
sorted += ' '

if (sorted !== input) {
const nodeOrToken: TSESTree.Token | TSESTree.Node = node.type === 'SvelteLiteral' ? { type: AST_TOKEN_TYPES.String, value: node.value, loc: node.loc, range: node.range } : node

context.report({
node,
node: nodeOrToken,
loc: node.loc,
messageId: 'invalid-order',
fix(fixer) {
if (AST_NODES_WITH_QUOTES.includes(node.type))
return fixer.replaceTextRange([node.range[0] + 1, node.range[1] - 1], sorted)
else
return fixer.replaceText(node, sorted)
return fixer.replaceText(nodeOrToken, sorted)
},
})
}
Expand All @@ -49,10 +60,38 @@ export default createRule({
checkLiteral(node.value)
}
},
SvelteAttribute(node: any) {
SvelteAttribute(node: SvelteAttribute) {
if (node.key.name === 'class') {
if (node.value?.[0].type === 'SvelteLiteral')
checkLiteral(node.value[0])
if (!node.value.length)
return

function checkExpressionRecursively(expression: SvelteMustacheTag['expression']) {
if (expression.type !== 'ConditionalExpression')
return

if (expression.consequent.type === 'Literal') {
checkLiteral(expression.consequent as TSESTree.Literal)
}
if (expression.alternate) {
if (expression.alternate.type === 'ConditionalExpression') {
checkExpressionRecursively(expression.alternate)
}
else if (expression.alternate.type === 'Literal') {
checkLiteral(expression.alternate as TSESTree.Literal)
}
}
}

(node.value).forEach((obj, i) => {
if (obj.type === 'SvelteMustacheTag') {
checkExpressionRecursively(obj.expression)
}
else if (obj.type === 'SvelteLiteral') {
const addSpace: 'before' | 'after' | undefined = node.value?.[i - 1]?.type === 'SvelteMustacheTag' ? 'before' : node.value?.[i + 1]?.type === 'SvelteMustacheTag' ? 'after' : undefined

checkLiteral(obj, addSpace)
}
})
}
},
}
Expand Down
Loading

0 comments on commit c2df128

Please sign in to comment.