From 7829275459ce5128f259d6c5f3a46ab8f8268e5b Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Tue, 9 Nov 2021 02:35:20 +0900 Subject: [PATCH] Fix extending rules within overrides (#5683) --- .../config-overrides/at-rule-no-unknown.json | 5 + .../fixtures/config-overrides/indent4.json | 5 + .../fixtures/config-overrides/indent8.json | 5 + .../fixtures/config-overrides/scss.json | 5 + .../fixtures/config-overrides/style.scss | 3 + lib/__tests__/overrides.test.js | 117 ++++++++++++++++++ lib/augmentConfig.js | 65 +++++++--- lib/createStylelint.js | 2 +- 8 files changed, 189 insertions(+), 18 deletions(-) create mode 100644 lib/__tests__/fixtures/config-overrides/at-rule-no-unknown.json create mode 100644 lib/__tests__/fixtures/config-overrides/indent4.json create mode 100644 lib/__tests__/fixtures/config-overrides/indent8.json create mode 100644 lib/__tests__/fixtures/config-overrides/scss.json create mode 100644 lib/__tests__/fixtures/config-overrides/style.scss diff --git a/lib/__tests__/fixtures/config-overrides/at-rule-no-unknown.json b/lib/__tests__/fixtures/config-overrides/at-rule-no-unknown.json new file mode 100644 index 0000000000..e01d31fa86 --- /dev/null +++ b/lib/__tests__/fixtures/config-overrides/at-rule-no-unknown.json @@ -0,0 +1,5 @@ +{ + "rules": { + "at-rule-no-unknown": true + } +} diff --git a/lib/__tests__/fixtures/config-overrides/indent4.json b/lib/__tests__/fixtures/config-overrides/indent4.json new file mode 100644 index 0000000000..9c99262695 --- /dev/null +++ b/lib/__tests__/fixtures/config-overrides/indent4.json @@ -0,0 +1,5 @@ +{ + "rules": { + "indentation": 4 + } +} diff --git a/lib/__tests__/fixtures/config-overrides/indent8.json b/lib/__tests__/fixtures/config-overrides/indent8.json new file mode 100644 index 0000000000..9f42107a1f --- /dev/null +++ b/lib/__tests__/fixtures/config-overrides/indent8.json @@ -0,0 +1,5 @@ +{ + "rules": { + "indentation": 8 + } +} diff --git a/lib/__tests__/fixtures/config-overrides/scss.json b/lib/__tests__/fixtures/config-overrides/scss.json new file mode 100644 index 0000000000..2992dc500d --- /dev/null +++ b/lib/__tests__/fixtures/config-overrides/scss.json @@ -0,0 +1,5 @@ +{ + "rules": { + "at-rule-no-unknown": null + } +} diff --git a/lib/__tests__/fixtures/config-overrides/style.scss b/lib/__tests__/fixtures/config-overrides/style.scss new file mode 100644 index 0000000000..af37de614e --- /dev/null +++ b/lib/__tests__/fixtures/config-overrides/style.scss @@ -0,0 +1,3 @@ +a { + @extend .foo; +} diff --git a/lib/__tests__/overrides.test.js b/lib/__tests__/overrides.test.js index b1df5dce27..d787a44bb7 100644 --- a/lib/__tests__/overrides.test.js +++ b/lib/__tests__/overrides.test.js @@ -116,6 +116,123 @@ describe('single input file. all overrides are matching', () => { expect(linted.results[0].warnings[0].rule).toBe('block-no-empty'); expect(linted.results[0].warnings[1].rule).toBe('color-named'); }); + + it('priority to apply overrides: apply overrides extends', async () => { + const linted = await standalone({ + files: [path.join(fixturesPath, 'style.css')], + config: { + extends: [path.join(fixturesPath, 'indent4.json')], + overrides: [ + { + files: ['*.css'], + extends: [path.join(fixturesPath, 'indent8.json')], + }, + ], + }, + configBasedir: fixturesPath, + }); + + expect(linted.results).toHaveLength(1); + expect(linted.results[0].warnings).toHaveLength(1); + expect(linted.results[0].warnings[0].rule).toBe('indentation'); + expect(linted.results[0].warnings[0].text).toBe( + 'Expected indentation of 8 spaces (indentation)', + ); + }); + + it('priority to apply overrides: apply overrides rules', async () => { + const linted = await standalone({ + files: [path.join(fixturesPath, 'style.css')], + config: { + extends: [path.join(fixturesPath, 'indent4.json')], + rules: { + indentation: 3, + }, + overrides: [ + { + files: ['*.css'], + extends: [path.join(fixturesPath, 'indent8.json')], + rules: { + indentation: 'tab', + }, + }, + ], + }, + configBasedir: fixturesPath, + }); + + expect(linted.results).toHaveLength(1); + expect(linted.results[0].warnings).toHaveLength(1); + expect(linted.results[0].warnings[0].rule).toBe('indentation'); + expect(linted.results[0].warnings[0].text).toBe('Expected indentation of 1 tab (indentation)'); + }); + + it('priority to apply overrides: apply rules', async () => { + const linted = await standalone({ + files: [path.join(fixturesPath, 'style.css')], + config: { + extends: [path.join(fixturesPath, 'indent4.json')], + rules: { + indentation: 'tab', + }, + overrides: [ + { + files: ['*.css'], + extends: [path.join(fixturesPath, 'indent8.json')], + }, + ], + }, + configBasedir: fixturesPath, + }); + + expect(linted.results).toHaveLength(1); + expect(linted.results[0].warnings).toHaveLength(1); + expect(linted.results[0].warnings[0].rule).toBe('indentation'); + expect(linted.results[0].warnings[0].text).toBe('Expected indentation of 1 tab (indentation)'); + }); + + // https://github.com/stylelint/stylelint/issues/5656 + it('priority to apply overrides (scss): scss extends', async () => { + const linted = await standalone({ + files: [path.join(fixturesPath, 'style.scss')], + config: { + extends: ['./at-rule-no-unknown.json'], + overrides: [ + { + files: '**/*.scss', + extends: ['./scss.json'], + }, + ], + }, + configBasedir: fixturesPath, + }); + + expect(linted.results).toHaveLength(1); + expect(linted.results[0].warnings).toHaveLength(0); + }); + + // https://github.com/stylelint/stylelint/issues/5656 + it('priority to apply overrides (scss): apply rules', async () => { + const linted = await standalone({ + files: [path.join(fixturesPath, 'style.scss')], + config: { + rules: { + 'at-rule-no-unknown': true, + }, + overrides: [ + { + files: '**/*.scss', + extends: ['./scss.json'], + }, + ], + }, + configBasedir: fixturesPath, + }); + + expect(linted.results).toHaveLength(1); + expect(linted.results[0].warnings).toHaveLength(1); + expect(linted.results[0].warnings[0].rule).toBe('at-rule-no-unknown'); + }); }); it('override is not matching', async () => { diff --git a/lib/augmentConfig.js b/lib/augmentConfig.js index e952a71f0d..294dc1944b 100644 --- a/lib/augmentConfig.js +++ b/lib/augmentConfig.js @@ -26,26 +26,37 @@ const path = require('path'); * @param {StylelintInternalApi} stylelint * @param {StylelintConfig} config * @param {string} configDir - * @param {boolean} [allowOverrides] + * @param {boolean} allowOverrides + * @param {string} rootConfigDir * @param {string} [filePath] * @returns {Promise} */ -async function augmentConfigBasic(stylelint, config, configDir, allowOverrides, filePath) { +async function augmentConfigBasic( + stylelint, + config, + configDir, + allowOverrides, + rootConfigDir, + filePath, +) { let augmentedConfig = config; if (allowOverrides) { augmentedConfig = addOptions(stylelint, augmentedConfig); } - augmentedConfig = await extendConfig(stylelint, augmentedConfig, configDir); - if (filePath) { - while (augmentedConfig.overrides) { - augmentedConfig = applyOverrides(augmentedConfig, configDir, filePath); - augmentedConfig = await extendConfig(stylelint, augmentedConfig, configDir); - } + augmentedConfig = applyOverrides(augmentedConfig, rootConfigDir, filePath); } + augmentedConfig = await extendConfig( + stylelint, + augmentedConfig, + configDir, + rootConfigDir, + filePath, + ); + return absolutizePaths(augmentedConfig, configDir); } @@ -53,11 +64,10 @@ async function augmentConfigBasic(stylelint, config, configDir, allowOverrides, * Extended configs need to be run through augmentConfigBasic * but do not need the full treatment. Things like pluginFunctions * will be resolved and added by the parent config. - * @param {StylelintInternalApi} stylelint * @param {StylelintCosmiconfigResult} [cosmiconfigResult] * @returns {Promise} */ -async function augmentConfigExtended(stylelint, cosmiconfigResult) { +async function augmentConfigExtended(cosmiconfigResult) { if (!cosmiconfigResult) { return null; } @@ -65,7 +75,7 @@ async function augmentConfigExtended(stylelint, cosmiconfigResult) { const configDir = path.dirname(cosmiconfigResult.filepath || ''); const { config } = cosmiconfigResult; - const augmentedConfig = await augmentConfigBasic(stylelint, config, configDir); + const augmentedConfig = absolutizePaths(config, configDir); return { config: augmentedConfig, @@ -89,7 +99,14 @@ async function augmentConfigFull(stylelint, filePath, cosmiconfigResult) { const configDir = stylelint._options.configBasedir || path.dirname(filepath || ''); - let augmentedConfig = await augmentConfigBasic(stylelint, config, configDir, true, filePath); + let augmentedConfig = await augmentConfigBasic( + stylelint, + config, + configDir, + true, + configDir, + filePath, + ); augmentedConfig = addPluginFunctions(augmentedConfig); augmentedConfig = addProcessorFunctions(augmentedConfig); @@ -161,9 +178,11 @@ function absolutizeProcessors(processors, configDir) { * @param {StylelintInternalApi} stylelint * @param {StylelintConfig} config * @param {string} configDir + * @param {string} rootConfigDir + * @param {string} [filePath] * @return {Promise} */ -async function extendConfig(stylelint, config, configDir) { +async function extendConfig(stylelint, config, configDir, rootConfigDir, filePath) { if (config.extends === undefined) { return config; } @@ -177,7 +196,19 @@ async function extendConfig(stylelint, config, configDir) { const extendResult = await loadExtendedConfig(stylelint, configDir, extendLookup); if (extendResult) { - resultConfig = mergeConfigs(resultConfig, extendResult.config); + let extendResultConfig = extendResult.config; + const extendConfigDir = path.dirname(extendResult.filepath || ''); + + extendResultConfig = await augmentConfigBasic( + stylelint, + extendResultConfig, + extendConfigDir, + false, + rootConfigDir, + filePath, + ); + + resultConfig = mergeConfigs(resultConfig, extendResultConfig); } } @@ -379,11 +410,11 @@ function addProcessorFunctions(config) { /** * @param {StylelintConfig} fullConfig - * @param {string} configDir + * @param {string} rootConfigDir * @param {string} filePath * @return {StylelintConfig} */ -function applyOverrides(fullConfig, configDir, filePath) { +function applyOverrides(fullConfig, rootConfigDir, filePath) { let { overrides, ...config } = fullConfig; if (!overrides) { @@ -412,7 +443,7 @@ function applyOverrides(fullConfig, configDir, filePath) { return glob; } - return globjoin(configDir, glob); + return globjoin(rootConfigDir, glob); }) // Glob patterns for micromatch should be in POSIX-style .map((s) => normalizePath(s)); diff --git a/lib/createStylelint.js b/lib/createStylelint.js index 4a0b48639d..8cef189234 100644 --- a/lib/createStylelint.js +++ b/lib/createStylelint.js @@ -27,7 +27,7 @@ function createStylelint(options = {}) { const stylelint = { _options: options }; stylelint._extendExplorer = cosmiconfig('', { - transform: augmentConfig.augmentConfigExtended.bind(null, stylelint), + transform: augmentConfig.augmentConfigExtended, stopDir: STOP_DIR, });