Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: azat-io/eslint-plugin-perfectionist
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v2.1.0
Choose a base ref
...
head repository: azat-io/eslint-plugin-perfectionist
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v2.2.0
Choose a head ref
  • 10 commits
  • 14 files changed
  • 2 contributors

Commits on Sep 13, 2023

  1. fix: side-effect import with an internal pattern are defined as inter…

    …nal module in sort-imports rule
    Wondermarin authored Sep 13, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    b6f4e91 View commit details

Commits on Sep 18, 2023

  1. chore: update dependencies

    azat-io committed Sep 18, 2023

    Verified

    This commit was signed with the committer’s verified signature.
    azat-io Azat S.
    Copy the full SHA
    4594706 View commit details

Commits on Sep 23, 2023

  1. chore: update dependencies

    azat-io committed Sep 23, 2023

    Verified

    This commit was signed with the committer’s verified signature.
    azat-io Azat S.
    Copy the full SHA
    06f591e View commit details

Commits on Sep 28, 2023

  1. chore: update dependencies

    azat-io committed Sep 28, 2023

    Verified

    This commit was signed with the committer’s verified signature.
    azat-io Azat S.
    Copy the full SHA
    9848d7b View commit details

Commits on Oct 3, 2023

  1. chore: update dependencies

    azat-io committed Oct 3, 2023

    Verified

    This commit was signed with the committer’s verified signature.
    azat-io Azat S.
    Copy the full SHA
    7cc3652 View commit details

Commits on Oct 12, 2023

  1. chore: update dependencies

    azat-io committed Oct 12, 2023

    Verified

    This commit was signed with the committer’s verified signature.
    azat-io Azat S.
    Copy the full SHA
    6205d70 View commit details
  2. Verified

    This commit was signed with the committer’s verified signature.
    azat-io Azat S.
    Copy the full SHA
    a22eaf6 View commit details
  3. Verified

    This commit was signed with the committer’s verified signature.
    azat-io Azat S.
    Copy the full SHA
    4ad560b View commit details

Commits on Oct 13, 2023

  1. Verified

    This commit was signed with the committer’s verified signature.
    azat-io Azat S.
    Copy the full SHA
    53b4d57 View commit details
  2. build: publish v2.2.0

    azat-io committed Oct 13, 2023

    Verified

    This commit was signed with the committer’s verified signature.
    azat-io Azat S.
    Copy the full SHA
    471f080 View commit details
18 changes: 18 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
# Changelog


## v2.2.0

[compare changes](https://github.com/azat-io/eslint-plugin-perfectionist/compare/v2.1.0...v2.2.0)

### 🚀 Features

- Add ignore-alias option to sort-named-imports rule ([4ad560b](https://github.com/azat-io/eslint-plugin-perfectionist/commit/4ad560b))

### 🐞 Bug Fixes

- Side-effect import with an internal pattern are defined as internal module in sort-imports rule ([b6f4e91](https://github.com/azat-io/eslint-plugin-perfectionist/commit/b6f4e91))
- Improve recognition of external modules ([a22eaf6](https://github.com/azat-io/eslint-plugin-perfectionist/commit/a22eaf6))

### ❤️ Contributors

- Azat S. ([@azat-io](http://github.com/azat-io))
- Wondermarin

## v2.1.0

[compare changes](https://github.com/azat-io/eslint-plugin-perfectionist/compare/v2.0.1...v2.1.0)
7 changes: 7 additions & 0 deletions docs/rules/sort-named-imports.md
Original file line number Diff line number Diff line change
@@ -86,6 +86,7 @@ interface Options {
type?: 'alphabetical' | 'natural' | 'line-length'
order?: 'asc' | 'desc'
'ignore-case'?: boolean
'ignore-alias'?: boolean
}
```

@@ -110,6 +111,12 @@ interface Options {

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

### ignore-alias

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

Use import alias as name instead of exported name.

## ⚙️ Usage

::: code-group
2 changes: 1 addition & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ module.exports = [
...config,
eslintPlugin,
{
ignores: ['**/.vitepress/cache/**/*'],
ignores: ['**/.vitepress/cache/**/*', 'coverage/**/*'],
},
{
rules: {
47 changes: 24 additions & 23 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "eslint-plugin-perfectionist",
"description": "ESLint plugin for sorting various data such as objects, imports, types, enums, JSX props, etc.",
"version": "2.1.0",
"version": "2.2.0",
"homepage": "https://eslint-plugin-perfectionist.azat.io",
"repository": "https://github.com/azat-io/eslint-plugin-perfectionist",
"author": "Azat S. <to@azat.io>",
@@ -68,44 +68,45 @@
}
},
"peerDependencies": {
"astro-eslint-parser": "^0.14.0",
"astro-eslint-parser": "^0.16.0",
"eslint": ">=8.0.0",
"svelte": ">=3.0.0",
"svelte-eslint-parser": "^0.32.0",
"svelte-eslint-parser": "^0.33.0",
"vue-eslint-parser": ">=9.0.0"
},
"dependencies": {
"@typescript-eslint/utils": "^6.6.0",
"@typescript-eslint/utils": "^6.7.5",
"minimatch": "^9.0.3",
"natural-compare-lite": "^1.4.0"
},
"devDependencies": {
"@azat-io/eslint-config-typescript": "^1.3.0",
"@azat-io/eslint-config-typescript": "^1.6.0",
"@azat-io/stylelint-config": "^0.1.0",
"@commitlint/cli": "^17.7.1",
"@commitlint/cli": "^17.7.2",
"@commitlint/config-conventional": "^17.7.0",
"@types/natural-compare-lite": "^1.4.0",
"@types/node": "^20.5.9",
"@typescript-eslint/eslint-plugin": "^6.6.0",
"@typescript-eslint/parser": "^6.6.0",
"@typescript-eslint/rule-tester": "^6.6.0",
"@typescript-eslint/types": "^6.6.0",
"@vitest/coverage-v8": "^0.34.3",
"astro-eslint-parser": "^0.15.0",
"@types/node": "^20.8.4",
"@typescript-eslint/eslint-plugin": "^6.7.5",
"@typescript-eslint/parser": "^6.7.5",
"@typescript-eslint/rule-tester": "^6.7.5",
"@typescript-eslint/types": "^6.7.5",
"@vitest/coverage-v8": "^0.34.6",
"astro-eslint-parser": "^0.16.0",
"changelogen": "^0.5.5",
"clean-publish": "^4.2.0",
"eslint": "^8.48.0",
"eslint-doc-generator": "^1.4.3",
"eslint": "^8.51.0",
"eslint-doc-generator": "^1.5.0",
"eslint-plugin-eslint-plugin": "^5.1.1",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-n": "^16.0.2",
"eslint-plugin-perfectionist": "^1.5.1",
"eslint-plugin-n": "^16.2.0",
"eslint-plugin-node-import": "^1.0.4",
"eslint-plugin-perfectionist": "^2.1.0",
"eslint-plugin-prefer-arrow": "^1.2.3",
"eslint-plugin-prefer-let": "^3.0.1",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-sonarjs": "^0.21.0",
"eslint-plugin-unicorn": "^48.0.1",
"eslint-plugin-vitest": "^0.3.1",
"eslint-plugin-vitest": "^0.3.2",
"postcss-html": "^1.5.0",
"prettier": "^3.0.3",
"simple-git-hooks": "^2.9.0",
@@ -114,14 +115,14 @@
"stylelint-gamut": "^1.3.3",
"stylelint-order": "^6.0.3",
"stylelint-plugin-logical-css": "^0.13.2",
"svelte": "^4.2.0",
"svelte-eslint-parser": "^0.33.0",
"svelte": "^4.2.1",
"svelte-eslint-parser": "^0.33.1",
"ts-dedent": "^2.2.0",
"typescript": "^5.2.2",
"vite": "^4.4.9",
"vite": "^4.4.11",
"vite-plugin-lightningcss": "^0.0.5",
"vitepress": "1.0.0-rc.10",
"vitest": "^0.34.3",
"vitepress": "1.0.0-rc.21",
"vitest": "^0.34.6",
"vue": "^3.3.4"
}
}
1,464 changes: 769 additions & 695 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion rules/sort-astro-attributes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { TSESTree } from '@typescript-eslint/types'
import type { AST } from 'astro-eslint-parser'

import path from 'path'
import path from 'node:path'

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

78 changes: 53 additions & 25 deletions rules/sort-imports.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { TSESTree } from '@typescript-eslint/types'
import type { TSESLint } from '@typescript-eslint/utils'

import { builtinModules } from 'module'
import { builtinModules } from 'node:module'
import { minimatch } from 'minimatch'

import type { SortingNode } from '../typings'
@@ -161,6 +161,26 @@ export default createEslintRule<Options<string[]>, MESSAGE_ID>({
groups: [],
})

let hasUnknownGroup = false

for (let group of options.groups) {
if (Array.isArray(group)) {
for (let subGroup of group) {
if (subGroup === 'unknown') {
hasUnknownGroup = true
}
}
} else {
if (group === 'unknown') {
hasUnknownGroup = true
}
}
}

if (!hasUnknownGroup) {
options.groups = [...options.groups, 'unknown']
}

let source = context.getSourceCode()

let nodes: SortingNode[] = []
@@ -218,67 +238,75 @@ export default createEslintRule<Options<string[]>, MESSAGE_ID>({
)
}

let isExternal = (value: string) =>
!(value.startsWith('.') || value.startsWith('/'))

if (node.importKind === 'type') {
if (node.type === 'ImportDeclaration') {
setCustomGroups(options['custom-groups'].type, node.source.value)

if (isCoreModule(node.source.value)) {
defineGroup('builtin-type')
}

if (isInternal(node)) {
defineGroup('internal-type')
}

if (isIndex(node.source.value)) {
defineGroup('index-type')
}

if (isSibling(node.source.value)) {
defineGroup('sibling-type')
}

if (isParent(node.source.value)) {
defineGroup('parent-type')
}

if (isSibling(node.source.value)) {
defineGroup('sibling-type')
if (isInternal(node)) {
defineGroup('internal-type')
}

if (isCoreModule(node.source.value)) {
defineGroup('builtin-type')
}

if (isExternal(node.source.value)) {
defineGroup('external-type')
}
}

defineGroup('external-type')
defineGroup('type')
}

if (node.type === 'ImportDeclaration') {
setCustomGroups(options['custom-groups'].value, node.source.value)

if (isCoreModule(node.source.value)) {
defineGroup('builtin')
}

if (isInternal(node)) {
defineGroup('internal')
if (isSideEffectImport(node)) {
defineGroup('side-effect')
}

if (isStyle(node.source.value)) {
defineGroup('style')
}

if (isSideEffectImport(node)) {
defineGroup('side-effect')
}

if (isIndex(node.source.value)) {
defineGroup('index')
}

if (isSibling(node.source.value)) {
defineGroup('sibling')
}

if (isParent(node.source.value)) {
defineGroup('parent')
}

if (isSibling(node.source.value)) {
defineGroup('sibling')
if (isInternal(node)) {
defineGroup('internal')
}

defineGroup('external')
if (isCoreModule(node.source.value)) {
defineGroup('builtin')
}

if (isExternal(node.source.value)) {
defineGroup('external')
}
}

return getGroup()
2 changes: 1 addition & 1 deletion rules/sort-jsx-props.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { TSESTree } from '@typescript-eslint/types'

import path from 'path'
import path from 'node:path'

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

24 changes: 19 additions & 5 deletions rules/sort-named-imports.ts
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ type MESSAGE_ID = 'unexpectedNamedImportsOrder'

type Options = [
Partial<{
'ignore-alias': boolean
'ignore-case': boolean
order: SortOrder
type: SortType
@@ -52,6 +53,10 @@ export default createEslintRule<Options, MESSAGE_ID>({
type: 'boolean',
default: false,
},
'ignore-alias': {
type: 'boolean',
default: false,
},
},
additionalProperties: false,
},
@@ -76,17 +81,26 @@ export default createEslintRule<Options, MESSAGE_ID>({
if (specifiers.length > 1) {
let options = complete(context.options.at(0), {
type: SortType.alphabetical,
'ignore-alias': true,
'ignore-case': false,
order: SortOrder.asc,
})

let source = context.getSourceCode()

let nodes: SortingNode[] = specifiers.map(specifier => ({
size: rangeToDiff(specifier.range),
name: specifier.local.name,
node: specifier,
}))
let nodes: SortingNode[] = specifiers.map(specifier => {
let { name } = specifier.local

if (options['ignore-alias'] && specifier.type === 'ImportSpecifier') {
;({ name } = specifier.imported)
}

return {
size: rangeToDiff(specifier.range),
node: specifier,
name,
}
})

pairwise(nodes, (left, right) => {
if (isPositive(compare(left, right, options))) {
2 changes: 1 addition & 1 deletion rules/sort-svelte-attributes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { TSESTree } from '@typescript-eslint/types'
import type { AST } from 'svelte-eslint-parser'

import path from 'path'
import path from 'node:path'

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

2 changes: 1 addition & 1 deletion rules/sort-vue-attributes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { TSESTree } from '@typescript-eslint/types'
import type { AST } from 'vue-eslint-parser'

import path from 'path'
import path from 'node:path'

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

57 changes: 51 additions & 6 deletions test/sort-imports.test.ts
Original file line number Diff line number Diff line change
@@ -851,8 +851,7 @@ describe(RULE_NAME, () => {
'internal',
['parent-type', 'sibling-type', 'index-type'],
['parent', 'sibling', 'index'],
'object',
'unknown',
['object', 'unknown'],
],
},
],
@@ -1989,8 +1988,7 @@ describe(RULE_NAME, () => {
'internal',
['parent-type', 'sibling-type', 'index-type'],
['parent', 'sibling', 'index'],
'object',
'unknown',
['object', 'unknown'],
],
},
],
@@ -3168,8 +3166,7 @@ describe(RULE_NAME, () => {
'internal',
['parent-type', 'sibling-type', 'index-type'],
['parent', 'sibling', 'index'],
'object',
'unknown',
['object', 'unknown'],
],
},
],
@@ -3608,5 +3605,53 @@ describe(RULE_NAME, () => {
],
},
)

ruleTester.run(
`${RULE_NAME}: define side-effect import with internal pattern as side-effect import`,
rule,
{
valid: [
{
code: dedent`
import { useClient } from '~/hooks/useClient'
import '~/css/globals.css'
`,
options: [
{
groups: ['internal', 'side-effect'],
},
],
},
],
invalid: [
{
code: dedent`
import { useClient } from '~/hooks/useClient'
import '~/css/globals.css'
`,
output: dedent`
import { useClient } from '~/hooks/useClient'
import '~/css/globals.css'
`,
options: [
{
groups: ['internal', 'side-effect'],
},
],
errors: [
{
messageId: 'missedSpacingBetweenImports',
data: {
left: '~/hooks/useClient',
right: '~/css/globals.css',
},
},
],
},
],
},
)
})
})
106 changes: 106 additions & 0 deletions test/sort-named-imports.test.ts
Original file line number Diff line number Diff line change
@@ -247,6 +247,59 @@ describe(RULE_NAME, () => {
},
],
})

ruleTester.run(`${RULE_NAME}: allows to ignore import aliases`, rule, {
valid: [
{
code: dedent`
import {
miri,
kazuki as papa1,
rei as papa2,
} from 'buddy-daddies'
`,
options: [
{
...options,
'ignore-alias': false,
},
],
},
],
invalid: [
{
code: dedent`
import {
kazuki as papa1,
miri,
rei as papa2,
} from 'buddy-daddies'
`,
output: dedent`
import {
miri,
kazuki as papa1,
rei as papa2,
} from 'buddy-daddies'
`,
options: [
{
...options,
'ignore-alias': false,
},
],
errors: [
{
messageId: 'unexpectedNamedImportsOrder',
data: {
left: 'papa1',
right: 'miri',
},
},
],
},
],
})
})

describe(`${RULE_NAME}: sorting by natural order`, () => {
@@ -479,6 +532,59 @@ describe(RULE_NAME, () => {
},
],
})

ruleTester.run(`${RULE_NAME}: allows to ignore import aliases`, rule, {
valid: [
{
code: dedent`
import {
miri,
kazuki as papa1,
rei as papa2,
} from 'buddy-daddies'
`,
options: [
{
...options,
'ignore-alias': false,
},
],
},
],
invalid: [
{
code: dedent`
import {
kazuki as papa1,
miri,
rei as papa2,
} from 'buddy-daddies'
`,
output: dedent`
import {
miri,
kazuki as papa1,
rei as papa2,
} from 'buddy-daddies'
`,
options: [
{
...options,
'ignore-alias': false,
},
],
errors: [
{
messageId: 'unexpectedNamedImportsOrder',
data: {
left: 'papa1',
right: 'miri',
},
},
],
},
],
})
})

describe(`${RULE_NAME}: sorting by line length`, () => {
2 changes: 1 addition & 1 deletion vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { defineConfig } from 'vite'
import path from 'path'
import path from 'node:path'

export default defineConfig({
build: {