diff --git a/lib/options-manager.js b/lib/options-manager.js index 7d7b1573..424a2d21 100644 --- a/lib/options-manager.js +++ b/lib/options-manager.js @@ -348,58 +348,58 @@ const buildXOConfig = options => config => { for (const [rule, ruleConfig] of Object.entries(ENGINE_RULES)) { for (const minVersion of Object.keys(ruleConfig).sort(semver.rcompare)) { if (!options.nodeVersion || semver.intersects(options.nodeVersion, `<${minVersion}`)) { - config.baseConfig.rules[rule] = ruleConfig[minVersion]; + config.baseConfig.rules[rule] ??= ruleConfig[minVersion]; } } } if (options.nodeVersion) { - config.baseConfig.rules['n/no-unsupported-features/es-builtins'] = ['error', {version: options.nodeVersion}]; - config.baseConfig.rules['n/no-unsupported-features/es-syntax'] = ['error', {version: options.nodeVersion, ignores: ['modules']}]; - config.baseConfig.rules['n/no-unsupported-features/node-builtins'] = ['error', {version: options.nodeVersion}]; + config.baseConfig.rules['n/no-unsupported-features/es-builtins'] ??= ['error', {version: options.nodeVersion}]; + config.baseConfig.rules['n/no-unsupported-features/es-syntax'] ??= ['error', {version: options.nodeVersion, ignores: ['modules']}]; + config.baseConfig.rules['n/no-unsupported-features/node-builtins'] ??= ['error', {version: options.nodeVersion}]; } if (options.space && !options.prettier) { if (options.ts) { - config.baseConfig.rules['@typescript-eslint/indent'] = ['error', spaces, {SwitchCase: 1}]; + config.baseConfig.rules['@typescript-eslint/indent'] ??= ['error', spaces, {SwitchCase: 1}]; } else { - config.baseConfig.rules.indent = ['error', spaces, {SwitchCase: 1}]; + config.baseConfig.rules.indent ??= ['error', spaces, {SwitchCase: 1}]; } // Only apply if the user has the React plugin if (options.cwd && resolveFrom.silent('eslint-plugin-react', options.cwd)) { config.baseConfig.plugins.push('react'); - config.baseConfig.rules['react/jsx-indent-props'] = ['error', spaces]; - config.baseConfig.rules['react/jsx-indent'] = ['error', spaces]; + config.baseConfig.rules['react/jsx-indent-props'] ??= ['error', spaces]; + config.baseConfig.rules['react/jsx-indent'] ??= ['error', spaces]; } } if (options.semicolon === false && !options.prettier) { if (options.ts) { - config.baseConfig.rules['@typescript-eslint/semi'] = ['error', 'never']; + config.baseConfig.rules['@typescript-eslint/semi'] ??= ['error', 'never']; } else { - config.baseConfig.rules.semi = ['error', 'never']; + config.baseConfig.rules.semi ??= ['error', 'never']; } - config.baseConfig.rules['semi-spacing'] = ['error', { + config.baseConfig.rules['semi-spacing'] ??= ['error', { before: false, after: true, }]; } if (options.ts) { - config.baseConfig.rules['unicorn/import-style'] = 'off'; - config.baseConfig.rules['node/file-extension-in-import'] = 'off'; + config.baseConfig.rules['unicorn/import-style'] ??= 'off'; + config.baseConfig.rules['node/file-extension-in-import'] ??= 'off'; // Disabled because of https://github.com/benmosher/eslint-plugin-import/issues/1590 - config.baseConfig.rules['import/export'] = 'off'; + config.baseConfig.rules['import/export'] ??= 'off'; // Does not work when the TS definition exports a default const. - config.baseConfig.rules['import/default'] = 'off'; + config.baseConfig.rules['import/default'] ??= 'off'; // Disabled as it doesn't work with TypeScript. // This issue and some others: https://github.com/benmosher/eslint-plugin-import/issues/1341 - config.baseConfig.rules['import/named'] = 'off'; + config.baseConfig.rules['import/named'] ??= 'off'; } config.baseConfig.settings['import/resolver'] = gatherImportResolvers(options); @@ -444,7 +444,7 @@ const buildPrettierConfig = (options, prettierConfig) => config => { config.baseConfig.extends.push('plugin:prettier/recommended'); // The `prettier/prettier` rule reports errors if the code is not formatted in accordance to Prettier - config.baseConfig.rules['prettier/prettier'] = ['error', mergeWithPrettierConfig(options, prettierConfig)]; + config.baseConfig.rules['prettier/prettier'] ??= ['error', mergeWithPrettierConfig(options, prettierConfig)]; } return config; diff --git a/test/cli.js b/test/cli.js index 43db20d4..0cbbedf1 100644 --- a/test/cli.js +++ b/test/cli.js @@ -197,3 +197,29 @@ test('print-config flag without filename', async t => { ); t.is(error.stderr.trim(), 'The `--print-config` flag must be used with exactly one filename'); }); + +test('Do not override user-config', async t => { + const cwd = path.join(__dirname, 'fixtures/no-override-user-config'); + const {stdout} = await main(['--print-config', 'index.js'], {cwd}); + const config = JSON.parse(stdout); + t.like(config, { + rules: { + 'n/no-unsupported-features/es-builtins': ['off'], + 'n/no-unsupported-features/es-syntax': ['off'], + 'n/no-unsupported-features/node-builtins': ['off'], + }, + }); + + const {stdout: stdoutOverrides} = await main(['--print-config', 'overrides.js'], {cwd}); + const configOverrides = JSON.parse(stdoutOverrides); + t.like(configOverrides, { + rules: { + 'n/no-unsupported-features/es-builtins': ['off'], + 'n/no-unsupported-features/es-syntax': ['off'], + 'n/no-unsupported-features/node-builtins': ['error'], + }, + }); + + await t.notThrowsAsync(() => main(['index.js'], {cwd})); + await t.throwsAsync(() => main(['overrides.js'], {cwd})); +}); diff --git a/test/fixtures/no-override-user-config/index.js b/test/fixtures/no-override-user-config/index.js new file mode 100644 index 00000000..9ff9f503 --- /dev/null +++ b/test/fixtures/no-override-user-config/index.js @@ -0,0 +1 @@ +const _ = new FormData(); diff --git a/test/fixtures/no-override-user-config/overrides.js b/test/fixtures/no-override-user-config/overrides.js new file mode 100644 index 00000000..9ff9f503 --- /dev/null +++ b/test/fixtures/no-override-user-config/overrides.js @@ -0,0 +1 @@ +const _ = new FormData(); diff --git a/test/fixtures/no-override-user-config/package.json b/test/fixtures/no-override-user-config/package.json new file mode 100644 index 00000000..264624c8 --- /dev/null +++ b/test/fixtures/no-override-user-config/package.json @@ -0,0 +1,20 @@ +{ + "xo": { + "rules": { + "n/no-unsupported-features/es-builtins": "off", + "n/no-unsupported-features/es-syntax": "off", + "n/no-unsupported-features/node-builtins": "off" + }, + "overrides": [ + { + "files": "overrides.js", + "rules": { + "n/no-unsupported-features/node-builtins": "error" + } + } + ] + }, + "engines": { + "node": ">=18" + } +}