azat-io / eslint-plugin-perfectionist

☂️ ESLint plugin for sorting various data such as objects, imports, types, enums, JSX props, etc.
https://perfectionist.dev
MIT License
1.86k stars 37 forks source link

Bug: Unexpected (non-rule-related) replacements #158

Closed OlivierZal closed 2 months ago

OlivierZal commented 3 months ago

Describe the bug

Hi @azat-io,

Autofix leads to unexpected code replacements, which does not seem directly related to configured rules.

3 video examples:

https://github.com/azat-io/eslint-plugin-perfectionist/assets/88216225/bbf85a6c-3b5b-4efd-9a7e-ccac39ee418c

https://github.com/azat-io/eslint-plugin-perfectionist/assets/88216225/9e190438-e6a8-4a0b-91b2-94b252863db4

https://github.com/azat-io/eslint-plugin-perfectionist/assets/88216225/df205387-b445-4acf-a391-5bcac83edc04

Code example

See attached videos + the branch I've created for debug: https://github.com/OlivierZal/melcloud-api/tree/perfectionist

Eslint config: I use the recommended-natural config with the custom following:

'perfectionist/sort-classes': 'off',
'perfectionist/sort-interfaces': 'off',
'perfectionist/sort-intersection-types': 'off',
'perfectionist/sort-object-types': 'off',
'perfectionist/sort-union-types': 'off',
'sort-imports': 'off',
'sort-keys': 'off',

Here's the full config:

import eslint from '@eslint/js'
// @ts-expect-error: untyped module
import stylistic from '@stylistic/eslint-plugin'
import prettier from 'eslint-config-prettier'
import importPlugin from 'eslint-plugin-import'
import jsdoc from 'eslint-plugin-jsdoc'
import perfectionist from 'eslint-plugin-perfectionist/configs/recommended-natural'
import tsEslint from 'typescript-eslint'

export default tsEslint.config(
  {
    ignores: ['dist/'],
  },
  {
    extends: [
      eslint.configs.all,
      ...tsEslint.configs.all,
      perfectionist,
      importPlugin.configs.typescript,
      jsdoc.configs['flat/recommended-typescript-error'],
      prettier,
    ],
    languageOptions: {
      parserOptions: {
        project: 'tsconfig.json',
      },
    },
    linterOptions: {
      reportUnusedDisableDirectives: true,
    },
    plugins: {
      // @ts-expect-error: incorrect type
      '@stylistic': stylistic,
      import: importPlugin,
    },
    rules: {
      // ...importPlugin.configs.recommended.rules,
      '@stylistic/lines-between-class-members': ['error', 'always'],
      '@stylistic/spaced-comment': [
        'error',
        'always',
        {
          block: {
            balanced: true,
            exceptions: ['*'],
            markers: ['!'],
          },
          line: {
            exceptions: ['/', '#'],
            markers: ['/'],
          },
        },
      ],
      '@typescript-eslint/member-ordering': [
        'error',
        {
          default: {
            memberTypes: [
              // Index signature
              'signature',
              'readonly-signature',

              // Fields
              'public-static-field',
              'public-static-readonly-field',
              'protected-static-field',
              'protected-static-readonly-field',
              'private-static-field',
              'private-static-readonly-field',
              '#private-static-field',
              '#private-static-readonly-field',

              'public-decorated-field',
              'public-decorated-readonly-field',
              'protected-decorated-field',
              'protected-decorated-readonly-field',
              'private-decorated-field',
              'private-decorated-readonly-field',

              'public-instance-field',
              'public-instance-readonly-field',
              'protected-instance-field',
              'protected-instance-readonly-field',
              'private-instance-field',
              'private-instance-readonly-field',
              '#private-instance-field',
              '#private-instance-readonly-field',

              'public-abstract-field',
              'public-abstract-readonly-field',
              'protected-abstract-field',
              'protected-abstract-readonly-field',

              'public-field',
              'public-readonly-field',
              'protected-field',
              'protected-readonly-field',
              'private-field',
              'private-readonly-field',
              '#private-field',
              '#private-readonly-field',

              'static-field',
              'static-readonly-field',
              'instance-field',
              'instance-readonly-field',
              'abstract-field',
              'abstract-readonly-field',

              'decorated-field',
              'decorated-readonly-field',

              'field',
              'readonly-field',

              // Static initialization
              'static-initialization',

              // Constructors
              'public-constructor',
              'protected-constructor',
              'private-constructor',

              // Getters and setters
              ['public-static-get', 'public-static-set'],
              ['protected-static-get', 'protected-static-set'],
              ['private-static-get', 'private-static-set'],
              ['#private-static-get', '#private-static-set'],

              ['public-decorated-get', 'public-decorated-set'],
              ['protected-decorated-get', 'protected-decorated-set'],
              ['private-decorated-get', 'private-decorated-set'],

              ['public-instance-get', 'public-instance-set'],
              ['protected-instance-get', 'protected-instance-set'],
              ['private-instance-get', 'private-instance-set'],
              ['#private-instance-get', '#private-instance-set'],

              ['public-abstract-get', 'public-abstract-set'],
              ['protected-abstract-get', 'protected-abstract-set'],

              ['public-get', 'public-set'],
              ['protected-get', 'protected-set'],
              ['private-get', 'private-set'],
              ['#private-get', '#private-set'],

              ['static-get', 'static-set'],
              ['instance-get', 'instance-set'],
              ['abstract-get', 'abstract-set'],

              ['decorated-get', 'decorated-set'],

              ['get', 'set'],

              // Methods
              'public-static-method',
              'protected-static-method',
              'private-static-method',
              '#private-static-method',
              'public-decorated-method',
              'protected-decorated-method',
              'private-decorated-method',
              'public-instance-method',
              'protected-instance-method',
              'private-instance-method',
              '#private-instance-method',
              'public-abstract-method',
              'protected-abstract-method',
            ],
            optionalityOrder: 'optional-first',
            order: 'natural',
          },
        },
      ],
      '@typescript-eslint/naming-convention': [
        'error',
        {
          filter: {
            match: true,
            regex: '^(Ata|Atw|Erv)$',
          },
          format: null,
          selector: 'enumMember',
        },
        {
          format: ['snake_case'],
          selector: 'enumMember',
        },
        {
          format: ['camelCase', 'PascalCase', 'snake_case'],
          selector: ['objectLiteralProperty', 'typeProperty'],
        },
        {
          format: ['camelCase', 'PascalCase'],
          selector: 'import',
        },
        {
          format: ['PascalCase'],
          prefix: ['can', 'did', 'has', 'is', 'should', 'will'],
          selector: 'variable',
          types: ['boolean'],
        },
        {
          format: ['camelCase'],
          modifiers: ['global'],
          selector: 'variable',
          types: ['function'],
        },
        {
          format: ['camelCase', 'UPPER_CASE'],
          modifiers: ['global'],
          selector: 'variable',
        },
        {
          format: ['PascalCase'],
          selector: 'typeLike',
        },
        {
          format: ['camelCase'],
          leadingUnderscore: 'allow',
          selector: 'default',
        },
      ],
      '@typescript-eslint/no-explicit-any': [
        'error',
        {
          ignoreRestArgs: true,
        },
      ],
      '@typescript-eslint/no-magic-numbers': [
        'error',
        {
          ignoreEnums: true,
        },
      ],
      '@typescript-eslint/no-unused-vars': [
        'error',
        {
          argsIgnorePattern: '^_',
          caughtErrorsIgnorePattern: '^_',
        },
      ],
      '@typescript-eslint/prefer-readonly-parameter-types': 'off',
      camelcase: 'off',
      'import/no-duplicates': [
        'error',
        {
          'prefer-inline': true,
        },
      ],
      'max-lines': 'off',
      'no-bitwise': 'off',
      'no-empty': [
        'error',
        {
          allowEmptyCatch: true,
        },
      ],
      'no-ternary': 'off',
      'one-var': ['error', 'never'],
      'perfectionist/sort-classes': 'off',
      'perfectionist/sort-interfaces': 'off',
      'perfectionist/sort-intersection-types': 'off',
      'perfectionist/sort-object-types': 'off',
      'perfectionist/sort-union-types': 'off',
      'sort-imports': 'off',
      'sort-keys': 'off',
    },
    settings: {
      'import/resolver': {
        ...importPlugin.configs.typescript.settings['import/resolver'],
        typescript: {
          alwaysTryTypes: true,
        },
      },
    },
  },
  {
    files: ['**/*.mjs'],
    ...tsEslint.configs.disableTypeChecked,
    languageOptions: {
      parserOptions: {
        project: 'tsconfig.checkjs.json',
      },
    },
  },
)

ESLint version

v9.5.0

ESLint Plugin Perfectionist version

v2.11.0

Additional comments

No response

Validations

azat-io commented 3 months ago

Hi. Thank you for your issue. Do you have the ability to create a small repository or example on Stackblitz to reproduce the problem?

OlivierZal commented 3 months ago

Isn't the dedicated branch I've created sufficient? https://github.com/OlivierZal/melcloud-api/tree/perfectionist

azat-io commented 3 months ago

Thanks, I'll try to fix the problem

azat-io commented 2 months ago

Thank you for your issue!

Looks like this bug was fixed in 8c35a7dfbe9b841f86f303c4e9fd4170b47c9c0b and released in v3.0.0.

If you'd like, you can support the release with a retweet: https://x.com/azat_io_en/status/1815367279191761054

OlivierZal commented 2 months ago

Hi @azat-io, thanks a lot, I confirm that now it works.

I don't have X but I'll try to promote your project in other ways 😀