prettier / eslint-plugin-prettier

ESLint plugin for Prettier formatting

Home Page:https://npm.im/eslint-plugin-prettier

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

eslint-plugin-prettier is very slow

juzi214032 opened this issue · comments

What version of eslint are you using?
v7.2.0
What version of prettier are you using?
v2.0.5
What version of eslint-plugin-prettier are you using?
v3.1.3
Please paste any applicable config files that you're using (e.g. .prettierrc or .eslintrc files)

{
  "extends": [
    "eslint:recommended",
    "plugin:import/errors",
    "plugin:import/warnings",
    "plugin:promise/recommended",
    "plugin:prettier/recommended"
  ],
  "plugins": [
    "import",
    "promise"
  ],
  "env": {
    "node": true,
    "es2017": true,
    "browser": true,
    "commonjs": true
  },
  "parserOptions": {
    "ecmaVersion": 2018,
    "sourceType": "module"
  },
  "globals": {
    "Component": true,
    "Page": true,
    "wx": true,
    "App": true
  },
  "ignorePatterns": [
    "dist/*",
    "examples/dist/*",
    "src/common/async-validator/*",
    "examples/pages/filter/components/wemark"
  ],
  "rules": {
    "space-before-function-paren": "off",
    "indent": ["error", 2,{
      "SwitchCase":1
    }],
    "quotes": [
      "error", "single", {
        "allowTemplateLiterals": false
      }
    ],
    "semi": ["error"],
    "no-console": [
      "warn", {
        "allow": ["info", "warn", "error"]
      }
    ],
    "no-undef": "error",
    "no-useless-escape": "off",
    "eqeqeq": ["error", "always"],
    "promise/always-return": "off"
  }
}
{
  "singleQuote": true,
  "trailingComma": "none",
  "arrowParens": "avoid",
  "quoteProps": "preserve",
  "printWidth": 120
}

What source code are you linting?
JavaScript、JSON、LESS
What did you expect to happen?
less time
What actually happened?
It takes a long time

image

That is indeed a long time compared to your other files.

  • Does it take that long if you run prettier over those files instead of going through eslint? I'm wondering if this is an issue with prettier itself rather than the eslint plugin.
  • What happens if you run eslint with the --debug flag? Are there any particular files that take longer than expected time to process? I wonder if there are any files that prettier finds tough to parse - I've saw that before if you accidentally try and run large files through prettier.

I was reading a tweet regarding this today and thought I should give it a try, I was surprised to see the results (this is a React Native, Typescript codebase).

TIMING=1 npm run lint:

Rule                              | Time (ms) | Relative
:---------------------------------|----------:|--------:
prettier/prettier                 |  1333.671 |    91.6%
react/no-string-refs              |    46.890 |     3.2%
@typescript-eslint/no-unused-vars |     7.696 |     0.5%
semi                              |     4.267 |     0.3%
react-native/no-inline-styles     |     4.207 |     0.3%
react-hooks/exhaustive-deps       |     3.590 |     0.2%
react-hooks/rules-of-hooks        |     3.248 |     0.2%
no-obj-calls                      |     2.784 |     0.2%
no-div-regex                      |     2.560 |     0.2%
no-undef-init                     |     2.220 |     0.2%

cat package.json | grep -E "eslint|prettier":

    "@react-native-community/eslint-config": "^2.0.0",
    "@typescript-eslint/eslint-plugin": "^3.2.0",
    "@typescript-eslint/parser": "^3.2.0",
    "eslint": "^7.1.0",
    "eslint-config-prettier": "^6.11.0",
    "eslint-plugin-prettier": "^3.1.3",
    "eslint-plugin-react-hooks": "^4.0.4",
    "prettier": "^2.0.5",

cat .prettierrc.js:

module.exports = {
  bracketSpacing: true,
  jsxBracketSameLine: true,
  singleQuote: true,
  trailingComma: 'all',
  semi: false,
};

cat .eslintrc.js:

module.exports = {
  root: true,
  extends: [
    '@react-native-community',
    'prettier/@typescript-eslint',
    'plugin:prettier/recommended',
    'plugin:react-hooks/recommended',
  ],
  parser: '@typescript-eslint/parser',
  plugins: ['@typescript-eslint'],
  rules: {
    semi: [2, 'never'],
  },
  overrides: [
    {
      files: ['**/*.test.js', '**/*.test.jsx', '**/*.test.ts', '**/*.test.tsx'],
      env: {
        jest: true,
      },
    },
  ],
}

I'm not accustomed with prettier cli's API so I don't know how to trigger the Timing=1 to compare the results, neither could I capture the output of eslint --debug's output in order to do anything meaningful with the results (they show up on the screen too fast and then are not piped to stdout npm run lint -- --debug > res.txt).

neither could I capture the output of eslint --debug's output in order to do anything meaningful with the results

You'll want to redirect stderr in addition to stdout to capture the debugging information:
https://stackoverflow.com/questions/637827/redirect-stderr-and-stdout-in-bash

Seeing the same thing:

Rule                              | Time (ms) | Relative
:---------------------------------|----------:|--------:
prettier/prettier                 | 11580.842 |    57.7%
import/namespace                  |  4941.682 |    24.6%
react/no-deprecated               |   465.174 |     2.3%
import/no-extraneous-dependencies |   450.004 |     2.2%
react/no-direct-mutation-state    |   312.252 |     1.6%
import/no-unresolved              |   237.725 |     1.2%
simple-import-sort/sort           |   185.009 |     0.9%
react/no-string-refs              |   181.877 |     0.9%
react/require-render-return       |   160.600 |     0.8%
no-unused-vars                    |   159.425 |     0.8%

package.json

{
   "eslint": "6.8.0",
    "eslint-config-prettier": "6.9.0",
    "eslint-import-resolver-babel-module": "5.1.1",
    "eslint-plugin-import": "2.19.1",
    "eslint-plugin-jsx-a11y": "6.2.3",
    "eslint-plugin-prettier": "3.1.2",
    "eslint-plugin-react": "7.17.0",
    "eslint-plugin-react-hooks": "2.3.0",
    "eslint-plugin-simple-import-sort": "5.0.0",
    "eslint-plugin-testcafe": "0.2.1",
    "prettier": "1.19.1",
}
.eslintrc.js
const typescriptEslintRecommendedRules = require('@typescript-eslint/eslint-plugin').configs.recommended.rules;

const config = {
"env": {
  "browser": true,
  "commonjs": true,
  "es6": true,
  "node": true
},
"extends": [
  "eslint:recommended",
  "plugin:react/recommended",
  "plugin:import/errors",
  "plugin:import/warnings",
  "plugin:jsx-a11y/recommended",
  "plugin:prettier/recommended"
],
"parser": "babel-eslint",
"parserOptions": {
  "ecmaFeatures": {
    "jsx": true
  },
  "ecmaVersion": 2019,
  "sourceType": "module"
},
"plugins": ["react-hooks", "simple-import-sort"],
"rules": {
  "import/order": "off",
  "jsx-a11y/label-has-associated-control": "off",
  "jsx-a11y/no-onchange": "off",
  "jsx-a11y/anchor-is-valid": [
    "error",
    {
      "components": ["Link"],
      "specialLink": ["hrefLeft", "hrefRight"],
      "aspects": ["invalidHref", "preferButton"]
    }
  ],
  "react/react-in-jsx-scope": "off",
  "react/display-name": "off",
  "react-hooks/rules-of-hooks": "error",
  "react-hooks/exhaustive-deps": "warn",
  "simple-import-sort/sort": "error",
  "import/no-extraneous-dependencies": ["error", { "devDependencies": false }]
},
"settings": {
  "react": {
    "version": "detect"
  },
  "import/resolver": {
    "babel-module": {},
    "node": {
      "extensions": [".js", ".jsx", ".ts", ".tsx"]
    }
  }
},
"overrides": []
};

config.overrides.push({
files: ["**/*.ts", "**/*.tsx"],
parser: "@typescript-eslint/parser",
parserOptions: {
  ecmaFeatures: {
    jsx: true
  },
  ecmaVersion: 2019,
  sourceType: 'module',
  project: './tsconfig.json',
},
extends: config.extends.concat(["plugin:@typescript-eslint/recommended"]),
plugins: config.plugins.concat(['@typescript-eslint']),
rules: Object.assign({}, config.rules, typescriptEslintRecommendedRules, {
  "@typescript-eslint/no-this-alias": "warn",
  "@typescript-eslint/camelcase": "warn",
  "@typescript-eslint/no-explicit-any": "off",
  "@typescript-eslint/explicit-function-return-type": "off",
  "@typescript-eslint/no-unused-vars": "off",
  "@typescript-eslint/no-use-before-define": "off",
  "@typescript-eslint/camelcase": "off",
  "@typescript-eslint/no-empty-function": "off",
  "for-direction": "off",
  "no-useless-escape": "off",
  "react/prop-types": "off",
})
});

// Allow test files to import devDependencies
config.overrides.push({
files: ["**/*.test.js", "**/*.test.ts", "**/*.test.tsx", "functional-tests/**/*.js"],
rules: {
  "import/no-extraneous-dependencies": ["error", { "devDependencies": true }]
}
});

config.overrides.push({
"files": ["functional-tests/**/*.js"],
"plugins": [
  "testcafe"
],
"extends": "plugin:testcafe/recommended"
});

module.exports = config;

Same issue here. It's fast when we run Prettier via the VSCode plugin esbenp.prettier-vscode, but it's extremely slow when we run it with ESLint.

Rule                              | Time (ms) | Relative
:---------------------------------|----------:|--------:
prettier/prettier                 |  8142.741 |    45.4%
import/no-named-as-default        |  2217.457 |    12.4%
import/no-extraneous-dependencies |  1190.158 |     6.6%
import/named                      |   712.467 |     4.0%
import/no-unresolved              |   405.091 |     2.3%
react/sort-comp                   |   303.724 |     1.7%
react/no-deprecated               |   278.299 |     1.6%
react/jsx-no-bind                 |   240.232 |     1.3%
import/no-duplicates              |   240.053 |     1.3%
import/order                      |   227.723 |     1.3%

Dependencies:

"devDependencies": {
    "@calm/eslint-plugin-react-intl": "^1.4.1",
    "@umijs/fabric": "^2.1.1",
    "babel-eslint": "^10.1.0",
    "eslint": "^7.3.1",
    "eslint-plugin-prettier": "^3.1.3",
    "eslint-plugin-sort-keys-fix": "^1.1.1",
    "fs-readdir-recursive": "^1.1.0",
    "husky": "^4.2.5",
    "jest": "^26.1.0",
    "jest-expect-message": "^1.0.2",
    "lerna": "^3.22.1",
    "lint-staged": "^10.2.11",
    "prettier": "^2.0.4",
    "pretty-quick": "^2.0.1",
    "stylelint": "^13.6.1",
    "stylelint-prettier": "^1.1.2"
  },

ESLint config:

const OFF = 0;
const WARNING = 1;
const ERROR = 2;

module.exports = {
  extends: [require.resolve('@umijs/fabric/dist/eslint')],
  plugins: ['prettier', 'import', 'jest'],

  globals: {
    REACT_APP_ENV: true,
  },

  env: {
    node: true,
    browser: true,
  },

  rules: {
    '@typescript-eslint/no-unused-vars': [
      'error',
      { varsIgnorePattern: '^_', argsIgnorePattern: '^_' },
    ],
    '@typescript-eslint/naming-convention': OFF,
    'import/no-extraneous-dependencies': WARNING,
    'jsx-a11y/label-has-associated-control': OFF,
    'jsx-a11y/label-has-for': OFF,
    'jsx-a11y/media-has-caption': OFF,
    'no-loop-func': OFF,
    'no-nested-ternary': OFF,
    'no-param-reassign': OFF,
    'no-plusplus': OFF,
    'no-return-assign': OFF,
    'no-underscore-dangle': OFF,
    'no-unused-expressions': OFF,
    'no-unused-vars': ['error', { varsIgnorePattern: '^_', argsIgnorePattern: '^_' }],
    'max-classes-per-file': OFF,
    'global-require': OFF,
    'prettier/prettier': ERROR,
    'react/jsx-filename-extension': OFF,
    'react/prop-types': OFF,
    'react/require-default-props': OFF,
  },

  root: true,
};
commented

Seeing the same thing here
Screenshot 2020-07-18 at 09 45 55

Eslint config:

module.exports = {
  parser: '@typescript-eslint/parser', // Specifies the ESLint parser
  env: {
    browser: true,
    jest: false,
  },
  parserOptions: {
    ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
    sourceType: 'module', // Allows for the use of imports
    ecmaFeatures: {
      jsx: true, // Allows for the parsing of JSX
    },
  },
  settings: {
    react: {
      version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use
    },
  },
  extends: [
    'plugin:react-hooks/recommended',
    'plugin:react/recommended', // Uses the recommended rules from @eslint-plugin-react
    'plugin:@typescript-eslint/recommended', // Uses the recommended rules from @typescript-eslint/eslint-plugin
    'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
    'plugin:prettier/recommended', // Enables eslint-plugin-prettier and eslin
  ],
  rules: {
    // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
    'react/prop-types': 0,
    'react/display-name': 0,
    'react/no-unescaped-entities': 0,
    'arrow-parens': ['error', 'as-needed'],
    '@typescript-eslint/ban-ts-comment': [0, 'allow-with-description'],
  },
  ignorePatterns: ['scripts', 'config', 'node_modules'],
}

@BPScott Can this problem be solved?

commented

This looks related to the issue opened in prettier-eslint-cli repo

image
Seeing similar

Same here, prettier takes a lot of time, which results in really bad developer experience

Rule Time (ms) Relative
prettier/prettier 4426.921 84.0%
import/order 412.818 7.8%
react/no-direct-mutation-state 91.420 1.7%
@typescript-eslint/no-unused-vars 71.658 1.4%
react/no-typos 39.917 0.8%
react/require-render-return 33.776 0.6%
no-empty-character-class 11.034 0.2%
react-hooks/exhaustive-deps 10.065 0.2%
no-restricted-globals 9.641 0.2%
react-hooks/rules-of-hooks 8.002 0.2%
lint success

Same here

Rule                                  | Time (ms) | Relative
:-------------------------------------|----------:|--------:
prettier/prettier                     | 29282.228 |    82.8%
compat/compat                         |   849.040 |     2.4%
import/no-unresolved                  |   704.407 |     2.0%
...

Same here

Rule                              | Time (ms) | Relative
:---------------------------------|----------:|--------:
prettier/prettier                 | 25445.485 |    44.7%
import/no-cycle                   | 12162.685 |    21.4%
import/extensions                 |  2531.717 |     4.4%
import/no-unresolved              |  2133.016 |     3.7%
import/no-extraneous-dependencies |  1431.038 |     2.5%
...

same here

Rule Time (ms) Relative
prettier/prettier 91360.401 70.1%
import/no-cycle 10669.895 8.2%
import/no-duplicates 3134.220 2.4%
import/default 2746.444 2.1%
import/named 2690.323 2.1%
import/no-unresolved 1206.509 0.9%
react/no-deprecated 1000.266 0.8%
react/jsx-no-bind 890.601 0.7%
no-unused-vars 878.121 0.7%
no-redeclare 737.186 0.6%
Rule Time (ms) Relative

any updates on this?

@yekver no, I give up

Rule Time (ms) Relative
import/namespace 20665.908 53.5%
prettier/prettier 10506.962 27.2%
node/no-extraneous-import 1789.480 4.6%
@typescript-eslint/no-implied-eval 1662.286 4.3%
@typescript-eslint/no-misused-promises 1268.487 3.3%
@typescript-eslint/no-unused-vars 280.963 0.7%
@typescript-eslint/no-unnecessary-type-arguments 209.879 0.5%
import/no-unresolved 194.264 0.5%
@typescript-eslint/no-unnecessary-qualifier 191.079 0.5%
sonarjs/cognitive-complexity 138.541 0.4%

Bad to see import/namespace rule is the slowest one :(

Same problem here. I'm removing this rule from my eslint config and running prettier as a separate step (it is drastically faster)

Could this be one cause?

const prettierRcOptions = usePrettierrc
? prettier.resolveConfig.sync(filepath, {
editorconfig: true
})
: null;
const prettierFileInfo = prettier.getFileInfo.sync(
filepath,
Object.assign(
{},
{ resolveConfig: true, ignorePath: '.prettierignore' },
eslintFileInfoOptions
)
);

This means it is stuck until reading/parsing the files finished and then it still has to run prettier.format. import/namespace also accesses files (e.g. fs.statSync).

Correct me if I am wrong, but it doesn't look like eslint plugins can use any async code?
Maybe reading the prettier config files could be optimized? Instead of reading it for every file, just read it once and then watch it? I don't know. 🤷

This should be fixed as of v3.3.0.

I've known for a while that not clearing the prettier config cache is the fix for this but wanted to see if there was a way to get the performance speedup without impacting usage in editors - as with this change eslint plugins in editors keep using old cached prettier config instead of spotting when you make changes to prettier config.

The solution is easy but not ideal: "When you make a config change you may need to restart your editor for it to notice". I couldn't find fix that avoided that, but the size of the speedup is worth needing that extra step as 99% of the time you're not fiddling with settings.

@BPScott
How much of a savings would you expect for v3.3.0?

In my trials running it from the CLI, comparing my old version v3.1.4 to v3.3.0, it represents a savings of about 20%. The relative cost of prettier/prettier in my configuration drops from about 78% to about 74%. Is that about consistent with your expectations?


Further to this, the version of prettier plays a role in execution time: prettier 2.2.1 takes about 15% longer than prettier 2.0.5.

starting with 3.1.3 and prettier on 2.1.2

Rule                                 |  Time (ms) | Relative
:------------------------------------|-----------:|--------:
prettier/prettier                    | 150623.446 |    92.2%
react/no-deprecated                  |   3007.632 |     1.8%
react/no-direct-mutation-state       |   1944.462 |     1.2%
@typescript-eslint/no-unused-vars    |   1637.788 |     1.0%
react/no-string-refs                 |   1558.513 |     1.0%
react/require-render-return          |   1457.691 |     0.9%
no-unused-vars                       |    313.724 |     0.2%
react/no-unknown-property            |    275.788 |     0.2%
react/react-in-jsx-scope             |    190.502 |     0.1%
@typescript-eslint/no-empty-function |    173.773 |     0.1%
✨  Done in 257.45s.

and now with 3.3.1 and prettier still on 2.1.2

Rule                                 | Time (ms) | Relative
:------------------------------------|----------:|--------:
prettier/prettier                    | 43108.276 |    76.6%
react/no-deprecated                  |  3019.793 |     5.4%
react/no-direct-mutation-state       |  2020.851 |     3.6%
@typescript-eslint/no-unused-vars    |  1884.639 |     3.3%
react/no-string-refs                 |  1613.230 |     2.9%
react/require-render-return          |  1471.966 |     2.6%
no-unused-vars                       |   315.334 |     0.6%
react/no-unknown-property            |   291.186 |     0.5%
react/react-in-jsx-scope             |   266.839 |     0.5%
@typescript-eslint/no-empty-function |   167.369 |     0.3%
✨  Done in 146.07s.

thx a lot @BPScott !

eslint: 8.11.0
prettier: 2.6.0
eslint-plugin-prettier: 4.0.0

Rule Time (ms) Relative
prettier/prettier 153133.363 98.3%
vue/attribute-hyphenation 648.661 0.4%
@typescript-eslint/no-unused-vars 595.310 0.4%
no-redeclare 176.444 0.1%
padding-line-between-statements 144.199 0.1%
max-len 120.984 0.1%
vue/valid-next-tick 42.244 0.0%
no-restricted-imports 40.963 0.0%
@typescript-eslint/no-empty-function 39.269 0.0%
@typescript-eslint/no-loss-of-precision 39.268 0.0%

This may help someone: I was able to reduce the time from 4 seconds to 370ms on my small project by creating a .prettierignore file:

build
coverage
.gitlab
.vscode
node_modules

Indeed upgrading to the latest version 4.2.1 helped a bit. Still feels slow to me, but I'm not that used the the JS-ecosystem so it might the at the fault of the project complexity. Adding a .prettierignore mentioned above did not have any impact for me.

Before:

Rule                                 | Time (ms) | Relative
:------------------------------------|----------:|--------:
prettier/prettier                    |   793.872 |    89.5%
react/display-name                   |    14.987 |     1.7%
react/no-deprecated                  |     8.424 |     0.9%
react/no-direct-mutation-state       |     7.278 |     0.8%
react/no-string-refs                 |     5.602 |     0.6%
react/no-unknown-property            |     5.157 |     0.6%
react/require-render-return          |     5.021 |     0.6%
@typescript-eslint/no-empty-function |     4.726 |     0.5%
@typescript-eslint/naming-convention |     4.311 |     0.5%
react-hooks/exhaustive-deps          |     3.885 |     0.4%

After:

Rule                                 | Time (ms) | Relative
:------------------------------------|----------:|--------:
prettier/prettier                    |   549.801 |    89.4%
react/display-name                   |    11.733 |     1.9%
@typescript-eslint/naming-convention |     8.575 |     1.4%
react/no-deprecated                  |     4.636 |     0.8%
react/no-direct-mutation-state       |     3.880 |     0.6%
react/require-render-return          |     3.067 |     0.5%
react/no-string-refs                 |     2.937 |     0.5%
@typescript-eslint/no-empty-function |     2.641 |     0.4%
react/react-in-jsx-scope             |     2.516 |     0.4%
react/no-unknown-property            |     2.388 |     0.4%

Interesting :D

Rule                                            | Time (ms) | Relative
:-----------------------------------------------|----------:|--------:
prettier/prettier                               | 28315.193 |    99.3%
lit/no-legacy-template-syntax                   |    67.812 |     0.2%
simple-import-sort/imports                      |    40.667 |     0.1%
@typescript-eslint/no-loss-of-precision         |    24.350 |     0.1%
@typescript-eslint/no-empty-function            |    13.465 |     0.0%
lit/attribute-value-entities                    |     7.950 |     0.0%
@typescript-eslint/no-extra-non-null-assertion  |     5.667 |     0.0%
@typescript-eslint/adjacent-overload-signatures |     4.350 |     0.0%
@typescript-eslint/ban-types                    |     3.496 |     0.0%
@typescript-eslint/triple-slash-reference       |     3.063 |     0.0%

This is with latest prettier

    "@typescript-eslint/eslint-plugin": "^5.52.0",
    "@typescript-eslint/parser": "^5.52.0",
    "eslint": "^8.34.0",
    "eslint-config-prettier": "^8.6.0",
    "eslint-plugin-lit": "^1.8.2",
    "eslint-plugin-prettier": "^4.2.1",
    "eslint-plugin-simple-import-sort": "^10.0.0",
    "prettier": "^2.8.4",

eslint.rc

module.exports = {
  // Specifies the ESLint parser
  parser: '@typescript-eslint/parser',
  extends: [
    // Uses the recommended rules from the @typescript-eslint/eslint-plugin
    'plugin:@typescript-eslint/recommended',
    // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
    'prettier',
    'plugin:lit/recommended',
  ],
  parserOptions: {
    // Allows for the parsing of modern ECMAScript features
    ecmaVersion: 2018,
    // Allows for the use of imports
    sourceType: 'module',
  },
  rules: {
    '@typescript-eslint/explicit-function-return-type': 0,
    'simple-import-sort/imports': 'error',
    'sort-imports': 'off',
    'import/order': 'off',
    'prettier/prettier': [
      'error',
      {
        trailingComma: 'es5',
        semi: true,
        singleQuote: true,
        printWidth: 120,
        endOfLine: 'auto',
      },
    ],
    indent: ['off', 2],
    '@typescript-eslint/interface-name-prefix': 'off',
    '@typescript-eslint/camelcase': 0,
    'no-unused-vars': 'off',
    '@typescript-eslint/no-unused-vars': ['off'],
    'no-use-before-define': 'off',
    '@typescript-eslint/no-use-before-define': 'off',
    'lit/no-legacy-template-syntax': 'error',
    'lit/no-template-arrow': 'off',
  },
  plugins: ['simple-import-sort', '@typescript-eslint', 'prettier', 'lit'],
};
commented

I had a large js file with 30K line of code in my project's root directory. adding it to the .prettierignore file solved the issue for me. so, I think that the issue is not with the prettier plugin it's related to projects, just look for the large files and prettierignore it.