prettier/prettier-eslint

Only Prettier (but not ESLint) formatting is applied to `.cjs` files

mxxk opened this issue · 7 comments

mxxk commented

Versions:

  • prettier-eslint version: 7.1.0
  • node version: v18.12.1
  • npm (or yarn) version: 8.19.2

Have you followed the debugging tips?

Yes

Relevant code or config

example.cjs:

module.exports = '\'none\'';`

example.js:

export default '\'none\'';

What I did:

  • Configured Prettier and ESLint to format strings using single quotes.
  • Put the same literal string into example.js and example.cjs'\'none\''.
  • Ran prettier-eslint --write to format both files.
  • Ran eslint . to assert that the formatted files still comply with ESLint rules.

What happened:

  • prettier-eslint left the string '\'none\'' in example.js as-is (expected).
  • However, prettier-eslint reformatted the same string in example.cjs to "'none'" (unexpected).
  • Subsequently, eslint failed on example.cjs due to the double-quoted string.
$ npx prettier-eslint example.cjs example.js --write
success formatting 1 file with prettier-eslint
1 file was unchanged
$ npx eslint example.cjs example.js

/repo/example.cjs
  1:16  error  Strings must use singlequote  quotes

✖ 1 problem (1 error, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.

Reproduction repository:

https://github.com/mxxk-examples/prettier-eslint-issue-830

Problem description:

Though prettier-eslint is expected to not make any formatting changes to either file, it reformats example.cjs with a double-quoted string. This is inconsistent with ESLint configuration, which makes me suspect that only Prettier formatting is being applied to that file.

Aside 1: When Prettier and ESLint are run separately, the end result is correct

Since prettier-eslint describes itself as

Formats your JavaScript using prettier followed by eslint --fix

I ran Prettier and ESLint separately to see what would happen. Interestingly, this does produce the expected result:

  1. Run Prettier:

    $ npx prettier --write example.cjs example.js
    example.cjs 22ms
    example.js 2ms
    

    At this point, both files are reformatted to contain "'none'", which is expected according to https://prettier.io/docs/en/options.html#quotes:

    • If the number of quotes outweighs the other quote, the quote which is less used will be used to format the string - Example: "I'm double quoted" results in "I'm double quoted" and "This \"example\" is single quoted" results in 'This "example" is single quoted'.
  2. Run ESLint:

    $ npx eslint --fix example.cjs example.js
    

    ESLint reformats both files back to '\'none\'' to comply with the ESLint rule quotes: ['error', 'single'].

Aside 2: ESLint appears to ignore .cjs files when run with .

When ESLint is run with . as the target directory, it appears to completely ignore example.cjs (due to the extension, I'm guessing):

$ npx prettier --write example.cjs example.js
example.cjs 22ms
example.js 2ms
$ npx eslint .

/repo/example.js
  1:16  error  Strings must use singlequote  quotes

✖ 1 problem (1 error, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.

The above output only flagged one problem with example.js, but not example.cjs:

$ npx eslint example.cjs

/repo/example.cjs
  1:18  error  Strings must use singlequote  quotes

✖ 1 problem (1 error, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.

Perhaps it is this discrepancy in ESLint which is causing this formatting problem to surface in prettier-eslint, despite the explicit files being mentioned in the command-line (example.cjs, example.js).

mxxk commented

Posting some more findings with regards to "Aside 2" above:

Aside 2: ESLint appears to ignore .cjs files when run with .

After debugging via npx eslint . --debug, this appears to be the effect of the --ext CLI flag. Since . is a directory target, the implicit --ext .js takes effect and .cjs files are ignored:

$ npx eslint . --debug
eslint:file-enumerator Didn't match: example.cjs
eslint:file-enumerator Yield: example.js

On the contrary, ESLint file targets are included unconditionally:

$ npx eslint example.cjs example.js --debug
eslint:file-enumerator File: /repo/example.cjs
eslint:file-enumerator File: /repo/example.js

Since --ext has no direct configuration file equivalent (see eslint/eslint#2274 and eslint/eslint#11223), and the CLI docs state

  • Default Value: .js and the files that match the overrides entries of your configuration.

a no-op overrides item can be used to make ESLint consider .cjs files:

module.exports = {
  overrides: [
    { files: '*.cjs' },
  ],
};

This can be confirmed by rerunning eslint --debug:

$ npx eslint . --debug
eslint:file-enumerator Yield: example.cjs
eslint:file-enumerator Yield: example.js

The prettier-eslint issue persists, though...

I was hoping that the above change to ESLint configuration could serve as a workaround for the prettier-eslint formatting inconsistency, but unfortunately the formatting inconsistency is still reproducible.

Stale issue

mxxk commented

No issue activity due to lack of response from maintainers

mxxk commented

This is still an issue, so mentioning @idahogurl @JounQin for visibility

@mxxk Try putting this in your ESLint config

extensions: '[.js',
    '.jsx',
    '.ts',
    '.tsx',
    '.cjs',
    '.mjs',
    '.vue']
mxxk commented

Thanks @idahogurl, but if I understood your suggestion correctly, extensions is not a valid top-level property in ESLint config (.eslintrc.*). Using the repro example above,

$ npx eslint example.cjs example.js

Oops! Something went wrong! :(

ESLint: 8.31.0

Error: ESLint configuration in .eslintrc.cjs is invalid:
	- Unexpected top-level property "extensions".

    at ConfigValidator.validateConfigSchema (/repo/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2156:19)
    at ConfigArrayFactory._normalizeConfigData (/repo/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2998:19)
    at ConfigArrayFactory.loadInDirectory (/repo/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2866:33)
    at CascadingConfigArrayFactory._loadConfigInAncestors (/repo/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3848:46)
    at CascadingConfigArrayFactory.getConfigArrayForFile (/repo/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3769:18)
    at FileEnumerator._iterateFilesWithFile (/repo/node_modules/eslint/lib/cli-engine/file-enumerator.js:368:43)
    at FileEnumerator._iterateFiles (/repo/node_modules/eslint/lib/cli-engine/file-enumerator.js:349:25)
    at FileEnumerator.iterateFiles (/repo/node_modules/eslint/lib/cli-engine/file-enumerator.js:299:59)
    at iterateFiles.next (<anonymous>)
    at CLIEngine.executeOnFiles (/repo/node_modules/eslint/lib/cli-engine/cli-engine.js:786:48)

Digging a little more, according to eslint/eslint#1674, eslint/eslint#2274, and eslint/eslint#10828, ESLint does not support a config file equivalent to passing --ext to the ESLint CLI.

@mxxk You're right. It will have to be fixed in the code. The extension needs to be added to this array.

const eslintExtensions = eslintConfig.extensions || [
'.js',
'.jsx',
'.ts',
'.tsx',
'.mjs',
'.vue'
];