Skip to content

Commit

Permalink
feat: add sort-astro-attributes rule
Browse files Browse the repository at this point in the history
  • Loading branch information
azat-io committed Jul 21, 2023
1 parent ec8e78b commit 1929461
Show file tree
Hide file tree
Showing 8 changed files with 674 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/rules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ description: ESLint Plugin Perfectionist list of rules
| Name | Description | 🔧 |
| :------------------------------------------------------ | :------------------------------------------ | :- |
| [sort-array-includes](/rules/sort-array-includes) | enforce sorted arrays before include method | 🔧 |
| [sort-astro-attributes](/rules/sort-astro-attributes) | enforce sorted union types | 🔧 |
| [sort-classes](/rules/sort-classes) | enforce sorted classes | 🔧 |
| [sort-enums](/rules/sort-enums) | enforce sorted TypeScript enums | 🔧 |
| [sort-exports](/rules/sort-exports) | enforce sorted exports | 🔧 |
Expand Down
108 changes: 108 additions & 0 deletions docs/rules/sort-astro-attributes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
---
title: sort-astro-attributes
description: ESLint Plugin Perfectionist rule which enforce sorted ES class members
---

# sort-astro-attributes

💼 This rule is enabled in the following [configs](/configs/): `recommended-alphabetical`, `recommended-line-length`, `recommended-natural`.

🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).

<!-- end auto-generated rule header -->

## 📖 Rule Details

Enforce sorted attributes in Astro elements.

It's **safe**. The rule considers spread elements in an attributes list and does not break component functionality.

## 🔧 Options

This rule accepts an options object with the following properties:

```ts
interface Options {
type?: 'alphabetical' | 'natural' | 'line-length'
order?: 'asc' | 'desc'
'ignore-case'?: boolean
}
```

### type

<sub>(default: `'alphabetical'`)</sub>

- `alphabetical` - sort alphabetically.
- `natural` - sort in natural order.
- `line-length` - sort by code line length.

### order

<sub>(default: `'asc'`)</sub>

- `asc` - enforce properties to be in ascending order.
- `desc` - enforce properties to be in descending order.

### ignore-case

<sub>(default: `false`)</sub>

Only affects alphabetical and natural sorting. When `true` the rule ignores the case-sensitivity of the order.

## ⚙️ Usage

In order to start using this rule, you need to install additional dependency:

- `astro-eslint-parser`

::: code-group

```json [Legacy Config]
// .eslintrc
{
"plugins": ["perfectionist"],
"rules": {
"perfectionist/sort-astro-attributes": [
"error",
{
"type": "natural",
"order": "asc"
}
]
}
}
```

```js [Flat Config]
// eslint.config.js
import perfectionist from 'eslint-plugin-perfectionist'

export default [
{
plugins: {
perfectionist,
},
rules: {
'perfectionist/sort-astro-attributes': [
'error',
{
type: 'natural',
order: 'asc',
},
],
},
},
]
```

:::

## 🚀 Version

Coming soon.

## 📚 Resources

- [Rule source](https://github.com/azat-io/eslint-plugin-perfectionist/blob/main/rules/sort-astro-attributes.ts)
- [Test source](https://github.com/azat-io/eslint-plugin-perfectionist/blob/main/test/sort-astro-attributes.test.ts)
3 changes: 3 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import sortSvelteAttributes, { RULE_NAME as sortSvelteAttributesName } from './rules/sort-svelte-attributes'
import sortAstroAttributes, { RULE_NAME as sortAstroAttributesName } from './rules/sort-astro-attributes'
import sortArrayIncludes, { RULE_NAME as sortArrayIncludesName } from './rules/sort-array-includes'
import sortNamedImports, { RULE_NAME as sortNamedImportsName } from './rules/sort-named-imports'
import sortNamedExports, { RULE_NAME as sortNamedExportsName } from './rules/sort-named-exports'
Expand Down Expand Up @@ -92,6 +93,7 @@ let createConfigWithOptions = (options: {
},
],
[sortSvelteAttributesName]: ['error'],
[sortAstroAttributesName]: ['error'],
[sortNamedExportsName]: ['error'],
[sortNamedImportsName]: ['error'],
[sortObjectTypesName]: ['error'],
Expand All @@ -116,6 +118,7 @@ let createConfigWithOptions = (options: {
export default {
rules: {
[sortArrayIncludesName]: sortArrayIncludes,
[sortAstroAttributesName]: sortAstroAttributes,
[sortClassesName]: sortClasses,
[sortEnumsName]: sortEnums,
[sortExportsName]: sortExports,
Expand Down
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,19 @@
"./package.json": "./package.json"
},
"peerDependenciesMeta": {
"astro-eslint-parser": {
"optional": true
},
"svelte": {
"optional": true
},
"svelte-eslint-parser": {
"optional": true
}
},

"peerDependencies": {
"astro-eslint-parser": "^0.14.0",
"eslint": ">=8.0.0",
"svelte": ">=3.0.0",
"svelte-eslint-parser": "^0.32.0"
Expand All @@ -81,6 +86,7 @@
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@vitest/coverage-v8": "^0.33.0",
"astro-eslint-parser": "^0.14.0",
"changelogen": "^0.5.4",
"clean-publish": "^4.2.0",
"eslint": "^8.45.0",
Expand Down
33 changes: 33 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export default [
| Name | Description | 🔧 |
| :------------------------------------------------------------------------------------------------- | :------------------------------------------ | :- |
| [sort-array-includes](https://eslint-plugin-perfectionist.azat.io/rules/sort-array-includes) | enforce sorted arrays before include method | 🔧 |
| [sort-astro-attributes](https://eslint-plugin-perfectionist.azat.io/rules/sort-astro-attributes) | enforce sorted union types | 🔧 |
| [sort-classes](https://eslint-plugin-perfectionist.azat.io/rules/sort-classes) | enforce sorted classes | 🔧 |
| [sort-enums](https://eslint-plugin-perfectionist.azat.io/rules/sort-enums) | enforce sorted TypeScript enums | 🔧 |
| [sort-exports](https://eslint-plugin-perfectionist.azat.io/rules/sort-exports) | enforce sorted exports | 🔧 |
Expand Down
133 changes: 133 additions & 0 deletions rules/sort-astro-attributes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import type { TSESTree } from '@typescript-eslint/types'
import type { AST } from 'astro-eslint-parser'

import path from 'path'

import type { SortingNode } from '../typings'

import { createEslintRule } from '../utils/create-eslint-rule'
import { rangeToDiff } from '../utils/range-to-diff'
import { SortOrder, SortType } from '../typings'
import { makeFixes } from '../utils/make-fixes'
import { sortNodes } from '../utils/sort-nodes'
import { pairwise } from '../utils/pairwise'
import { complete } from '../utils/complete'
import { compare } from '../utils/compare'

export const RULE_NAME = 'sort-astro-attributes'

type MESSAGE_ID = 'unexpectedAstroAttributesOrder'

type Options = [
Partial<{
'ignore-case': boolean
order: SortOrder
type: SortType
}>,
]

export default createEslintRule<Options, MESSAGE_ID>({
name: RULE_NAME,
meta: {
type: 'suggestion',
docs: {
description: 'enforce sorted union types',
recommended: false,
},
fixable: 'code',
schema: [
{
type: 'object',
properties: {
type: {
enum: [
SortType.alphabetical,
SortType.natural,
SortType['line-length'],
],
default: SortType.natural,
},
order: {
enum: [SortOrder.asc, SortOrder.desc],
default: SortOrder.asc,
},
'ignore-case': {
type: 'boolean',
default: false,
},
},
additionalProperties: false,
},
],
messages: {
unexpectedAstroAttributesOrder:
'Expected "{{right}}" to come before "{{left}}"',
},
},
defaultOptions: [
{
type: SortType.alphabetical,
order: SortOrder.asc,
},
],
// @ts-ignore
create: context => {
if (path.extname(context.getFilename()) !== '.astro') {
return {}
}

return {
JSXElement: (node: AST.JSXElement) => {
let { attributes } = node.openingElement

if (attributes.length > 1) {
let options = complete(context.options.at(0), {
type: SortType.alphabetical,
order: SortOrder.asc,
'ignore-case': false,
})

let source = context.getSourceCode()

let parts: SortingNode[][] = attributes.reduce(
(accumulator: SortingNode[][], attribute) => {
if (attribute.type === 'JSXSpreadAttribute') {
accumulator.push([])
return accumulator
}

accumulator.at(-1)!.push({
size: rangeToDiff(attribute.range),
node: attribute as unknown as TSESTree.Node,
name:
typeof attribute.name.name === 'string'
? attribute.name.name
: source.text.slice(...attribute.name.range),
})

return accumulator
},
[[]],
)

for (let nodes of parts) {
pairwise(nodes, (left, right) => {
if (compare(left, right, options)) {
context.report({
messageId: 'unexpectedAstroAttributesOrder',
data: {
left: left.name,
right: right.name,
},
node: right.node,
fix: fixer =>
makeFixes(fixer, nodes, sortNodes(nodes, options), source),
})
}
})
}
}
},
}
},
})
Loading

0 comments on commit 1929461

Please sign in to comment.