From b42ca80a074b401ad952cccccd860a6d164c53f5 Mon Sep 17 00:00:00 2001 From: Adaline Valentina Simonian Date: Sun, 21 Nov 2021 19:43:10 -0800 Subject: [PATCH 1/7] feat: add resolveConfig to API Allows resolving the effective configuration for a given file, similar to --print-config on the CLI. --- lib/__tests__/resolveConfig.test.js | 44 +++++++++++++++++++++++++++ lib/index.js | 2 ++ lib/printConfig.js | 24 +++------------ lib/resolveConfig.js | 47 +++++++++++++++++++++++++++++ types/stylelint/index.d.ts | 10 ++++++ 5 files changed, 107 insertions(+), 20 deletions(-) create mode 100644 lib/__tests__/resolveConfig.test.js create mode 100644 lib/resolveConfig.js diff --git a/lib/__tests__/resolveConfig.test.js b/lib/__tests__/resolveConfig.test.js new file mode 100644 index 0000000000..01236d521a --- /dev/null +++ b/lib/__tests__/resolveConfig.test.js @@ -0,0 +1,44 @@ +'use strict'; + +const path = require('path'); +const pluginWarnAboutFoo = require('./fixtures/plugin-warn-about-foo'); +const replaceBackslashes = require('../testUtils/replaceBackslashes'); +const stylelint = require('..'); + +it('resolveConfig uses getConfigForFile to retrieve the config', () => { + const filepath = replaceBackslashes( + path.join(__dirname, 'fixtures/getConfigForFile/a/b/foo.css'), + ); + + return stylelint.resolveConfig(filepath).then((result) => { + expect(result).toEqual({ + plugins: [path.join(__dirname, '/fixtures/plugin-warn-about-foo.js')], + rules: { + 'block-no-empty': [true], + 'plugin/warn-about-foo': ['always'], + }, + pluginFunctions: { + 'plugin/warn-about-foo': pluginWarnAboutFoo.rule, + }, + }); + }); +}); + +it('config overrides should apply', () => { + const filepath = replaceBackslashes( + path.join(__dirname, 'fixtures/config-overrides/testPrintConfig/style.css'), + ); + + return stylelint.resolveConfig(filepath).then((result) => { + expect(result).toEqual({ + rules: { + 'block-no-empty': [true], + 'color-named': ['never'], + }, + }); + }); +}); + +it('resolveConfig with no path should resolve to null', () => { + return expect(stylelint.resolveConfig()).resolves.toBeNull(); +}); diff --git a/lib/index.js b/lib/index.js index f8bca2f451..5e6c736837 100644 --- a/lib/index.js +++ b/lib/index.js @@ -10,6 +10,7 @@ const ruleMessages = require('./utils/ruleMessages'); const rules = require('./rules'); const standalone = require('./standalone'); const validateOptions = require('./utils/validateOptions'); +const resolveConfig = require('./resolveConfig'); /** @type {import('stylelint').PublicApi} */ const stylelint = Object.assign(postcssPlugin, { @@ -17,6 +18,7 @@ const stylelint = Object.assign(postcssPlugin, { rules, formatters, createPlugin, + resolveConfig, createLinter: createStylelint, utils: { report, diff --git a/lib/printConfig.js b/lib/printConfig.js index 286cf7f686..7b1445c154 100644 --- a/lib/printConfig.js +++ b/lib/printConfig.js @@ -1,8 +1,7 @@ 'use strict'; -const createStylelint = require('./createStylelint'); +const resolveConfig = require('./resolveConfig'); const globby = require('globby'); -const path = require('path'); /** @typedef {import('stylelint').Config} StylelintConfig */ @@ -33,25 +32,10 @@ module.exports = function printConfig({ return Promise.reject(new Error('The --print-config option does not support globs.')); } - const stylelint = createStylelint({ + return resolveConfig(filePath, { + cwd: (globbyOptions && globbyOptions.cwd) || cwd, config, - configFile, configBasedir, - cwd, - }); - - const globCWD = (globbyOptions && globbyOptions.cwd) || cwd; - const absoluteFilePath = !path.isAbsolute(filePath) - ? path.join(globCWD, filePath) - : path.normalize(filePath); - - const configSearchPath = stylelint._options.configFile || absoluteFilePath; - - return stylelint.getConfigForFile(configSearchPath, absoluteFilePath).then((result) => { - if (result === null) { - return result; - } - - return result.config; + configFile, }); }; diff --git a/lib/resolveConfig.js b/lib/resolveConfig.js new file mode 100644 index 0000000000..877e5457e9 --- /dev/null +++ b/lib/resolveConfig.js @@ -0,0 +1,47 @@ +'use strict'; + +const createStylelint = require('./createStylelint'); +const path = require('path'); + +/** + * Resolves the effective configuation for a given file. Resolves to `null` if + * no config is found. + * @param {string} filePath The path to the file to get the config for. + * @param {Pick< + * import('stylelint').LinterOptions, + * | 'cwd' + * | 'config' + * | 'configBasedir' + * | 'configFile' + * >} options The options to use when creating the Stylelint instance. + * @returns {Promise} + */ +module.exports = function resolveConfig( + filePath, + { cwd = process.cwd(), config, configBasedir, configFile } = {}, +) { + if (!filePath) { + return Promise.resolve(null); + } + + const stylelint = createStylelint({ + config, + configFile, + configBasedir, + cwd, + }); + + const absoluteFilePath = !path.isAbsolute(filePath) + ? path.join(cwd, filePath) + : path.normalize(filePath); + + const configSearchPath = stylelint._options.configFile || absoluteFilePath; + + return stylelint.getConfigForFile(configSearchPath, absoluteFilePath).then((result) => { + if (result === null) { + return result; + } + + return result.config; + }); +}; diff --git a/types/stylelint/index.d.ts b/types/stylelint/index.d.ts index ffbc8eac36..094b81fc51 100644 --- a/types/stylelint/index.d.ts +++ b/types/stylelint/index.d.ts @@ -324,6 +324,16 @@ declare module 'stylelint' { * @internal */ createLinter: (options: LinterOptions) => InternalApi; + /** + * Resolves the effective configuation for a given file. Resolves to + * `null` if no config is found. + * @param filePath The path to the file to get the config for. + * @param options The options to use when creating the Stylelint instance. + */ + resolveConfig: ( + filePath: string, + options: Pick, + ) => Promise; utils: { /** * Report a problem. From 360f73668e6a74fae223cb1477b9c72fef870b52 Mon Sep 17 00:00:00 2001 From: Adaline Valentina Simonian Date: Sun, 21 Nov 2021 19:51:50 -0800 Subject: [PATCH 2/7] docs: document stylelint.resolveConfig --- docs/user-guide/usage/node-api.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/user-guide/usage/node-api.md b/docs/user-guide/usage/node-api.md index d992885f13..390dff4713 100644 --- a/docs/user-guide/usage/node-api.md +++ b/docs/user-guide/usage/node-api.md @@ -171,3 +171,34 @@ stylelint ``` Note that the customSyntax option also accepts a string. [Refer to the options documentation for details](./options.md#customsyntax). + +## Resolving the effective config for a file + +If you want to find out what exact configuration will be used for a file without actually linting it, you can use the `resolveConfig()` function. Given a file path, it will return a Promise that resolves with the effective configuration object: + +```js +const config = await stylelint.resolveConfig(filePath); + +// config => { +// rules: { +// 'color-no-invalid-hex': true +// }, +// extends: [ +// 'stylelint-config-standard', +// 'stylelint-config-css-modules' +// ], +// plugins: [ +// 'stylelint-scss' +// ], +// … +// } +``` + +If a configuration cannot be found for a file, `resolveConfig()` will return a Promise that resolves to `null`. + +You can also pass the following subset of the [options that you would normally pass to `lint()`](#options): + +- `cwd` +- `config` +- `configBasedir` +- `customSyntax` From dfca5899c1792f234d3a5fe81999990ae85fc820 Mon Sep 17 00:00:00 2001 From: Adaline Valentina Simonian Date: Tue, 23 Nov 2021 10:13:35 -0800 Subject: [PATCH 3/7] refactor: use undefined for lint() compatibility --- lib/__tests__/resolveConfig.test.js | 4 ++-- lib/printConfig.js | 16 +++++++++------- lib/resolveConfig.js | 26 +++++++++++++------------- types/stylelint/index.d.ts | 8 ++++---- types/stylelint/type-test.ts | 23 +++++++++++++++++++++++ 5 files changed, 51 insertions(+), 26 deletions(-) diff --git a/lib/__tests__/resolveConfig.test.js b/lib/__tests__/resolveConfig.test.js index 01236d521a..f966ef8c20 100644 --- a/lib/__tests__/resolveConfig.test.js +++ b/lib/__tests__/resolveConfig.test.js @@ -39,6 +39,6 @@ it('config overrides should apply', () => { }); }); -it('resolveConfig with no path should resolve to null', () => { - return expect(stylelint.resolveConfig()).resolves.toBeNull(); +it('resolveConfig with no path should resolve to undefined', () => { + return expect(stylelint.resolveConfig()).resolves.toBeUndefined(); }); diff --git a/lib/printConfig.js b/lib/printConfig.js index 7b1445c154..dcd05e1367 100644 --- a/lib/printConfig.js +++ b/lib/printConfig.js @@ -9,7 +9,7 @@ const globby = require('globby'); * @param {import('stylelint').LinterOptions} options * @returns {Promise} */ -module.exports = function printConfig({ +module.exports = async function printConfig({ cwd = process.cwd(), code, config, @@ -32,10 +32,12 @@ module.exports = function printConfig({ return Promise.reject(new Error('The --print-config option does not support globs.')); } - return resolveConfig(filePath, { - cwd: (globbyOptions && globbyOptions.cwd) || cwd, - config, - configBasedir, - configFile, - }); + return ( + (await resolveConfig(filePath, { + cwd: (globbyOptions && globbyOptions.cwd) || cwd, + config, + configBasedir, + configFile, + })) || null + ); }; diff --git a/lib/resolveConfig.js b/lib/resolveConfig.js index 877e5457e9..3ff990ad57 100644 --- a/lib/resolveConfig.js +++ b/lib/resolveConfig.js @@ -4,24 +4,24 @@ const createStylelint = require('./createStylelint'); const path = require('path'); /** - * Resolves the effective configuation for a given file. Resolves to `null` if - * no config is found. - * @param {string} filePath The path to the file to get the config for. + * Resolves the effective configuation for a given file. Resolves to `undefined` + * if no config is found. + * @param {string} filePath - The path to the file to get the config for. * @param {Pick< * import('stylelint').LinterOptions, * | 'cwd' * | 'config' * | 'configBasedir' * | 'configFile' - * >} options The options to use when creating the Stylelint instance. - * @returns {Promise} + * >} [options] - The options to use when creating the Stylelint instance. + * @returns {Promise} */ -module.exports = function resolveConfig( +module.exports = async function resolveConfig( filePath, { cwd = process.cwd(), config, configBasedir, configFile } = {}, ) { if (!filePath) { - return Promise.resolve(null); + return undefined; } const stylelint = createStylelint({ @@ -37,11 +37,11 @@ module.exports = function resolveConfig( const configSearchPath = stylelint._options.configFile || absoluteFilePath; - return stylelint.getConfigForFile(configSearchPath, absoluteFilePath).then((result) => { - if (result === null) { - return result; - } + const resolved = await stylelint.getConfigForFile(configSearchPath, absoluteFilePath); - return result.config; - }); + if (!resolved) { + return undefined; + } + + return resolved.config; }; diff --git a/types/stylelint/index.d.ts b/types/stylelint/index.d.ts index 094b81fc51..67ad4b1811 100644 --- a/types/stylelint/index.d.ts +++ b/types/stylelint/index.d.ts @@ -327,13 +327,13 @@ declare module 'stylelint' { /** * Resolves the effective configuation for a given file. Resolves to * `null` if no config is found. - * @param filePath The path to the file to get the config for. - * @param options The options to use when creating the Stylelint instance. + * @param filePath - The path to the file to get the config for. + * @param options - The options to use when creating the Stylelint instance. */ resolveConfig: ( filePath: string, - options: Pick, - ) => Promise; + options?: Pick, + ) => Promise; utils: { /** * Report a problem. diff --git a/types/stylelint/type-test.ts b/types/stylelint/type-test.ts index f15b027c17..2a675c37fb 100644 --- a/types/stylelint/type-test.ts +++ b/types/stylelint/type-test.ts @@ -52,6 +52,29 @@ stylelint.lint(options).then((x: LinterResult) => { } }); +stylelint.resolveConfig('path').then((config) => stylelint.lint({ config })); + +stylelint.resolveConfig('path', { config: options }).then((config) => stylelint.lint({ config })); + +stylelint + .resolveConfig('path', { configBasedir: 'path' }) + .then((config) => stylelint.lint({ config })); + +stylelint + .resolveConfig('path', { configFile: 'path' }) + .then((config) => stylelint.lint({ config })); + +stylelint.resolveConfig('path', { cwd: 'path' }).then((config) => stylelint.lint({ config })); + +stylelint + .resolveConfig('path', { + config: options, + configBasedir: 'path', + configFile: 'path', + cwd: 'path', + }) + .then((config) => stylelint.lint({ config })); + const formatter: FormatterType = 'json'; const ruleName = 'sample-rule'; From af612338138596687c2dd7fa6cdc18783cefc768 Mon Sep 17 00:00:00 2001 From: Adaline Valentina Simonian Date: Tue, 23 Nov 2021 10:15:42 -0800 Subject: [PATCH 4/7] test: use async functions in resolveConfig test --- lib/__tests__/resolveConfig.test.js | 40 ++++++++++++++--------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/__tests__/resolveConfig.test.js b/lib/__tests__/resolveConfig.test.js index f966ef8c20..6f0bd250b3 100644 --- a/lib/__tests__/resolveConfig.test.js +++ b/lib/__tests__/resolveConfig.test.js @@ -5,37 +5,37 @@ const pluginWarnAboutFoo = require('./fixtures/plugin-warn-about-foo'); const replaceBackslashes = require('../testUtils/replaceBackslashes'); const stylelint = require('..'); -it('resolveConfig uses getConfigForFile to retrieve the config', () => { +it('resolveConfig uses getConfigForFile to retrieve the config', async () => { const filepath = replaceBackslashes( path.join(__dirname, 'fixtures/getConfigForFile/a/b/foo.css'), ); - return stylelint.resolveConfig(filepath).then((result) => { - expect(result).toEqual({ - plugins: [path.join(__dirname, '/fixtures/plugin-warn-about-foo.js')], - rules: { - 'block-no-empty': [true], - 'plugin/warn-about-foo': ['always'], - }, - pluginFunctions: { - 'plugin/warn-about-foo': pluginWarnAboutFoo.rule, - }, - }); + const config = await stylelint.resolveConfig(filepath); + + expect(config).toEqual({ + plugins: [path.join(__dirname, '/fixtures/plugin-warn-about-foo.js')], + rules: { + 'block-no-empty': [true], + 'plugin/warn-about-foo': ['always'], + }, + pluginFunctions: { + 'plugin/warn-about-foo': pluginWarnAboutFoo.rule, + }, }); }); -it('config overrides should apply', () => { +it('config overrides should apply', async () => { const filepath = replaceBackslashes( path.join(__dirname, 'fixtures/config-overrides/testPrintConfig/style.css'), ); - return stylelint.resolveConfig(filepath).then((result) => { - expect(result).toEqual({ - rules: { - 'block-no-empty': [true], - 'color-named': ['never'], - }, - }); + const config = await stylelint.resolveConfig(filepath); + + expect(config).toEqual({ + rules: { + 'block-no-empty': [true], + 'color-named': ['never'], + }, }); }); From 8d664efb03005f78922d7cce7809867b75015b43 Mon Sep 17 00:00:00 2001 From: Adaline Valentina Simonian Date: Tue, 23 Nov 2021 10:32:21 -0800 Subject: [PATCH 5/7] test: add options cases to resolveConfig test --- lib/__tests__/resolveConfig.test.js | 145 ++++++++++++++++++++++------ 1 file changed, 115 insertions(+), 30 deletions(-) diff --git a/lib/__tests__/resolveConfig.test.js b/lib/__tests__/resolveConfig.test.js index 6f0bd250b3..b6f761408f 100644 --- a/lib/__tests__/resolveConfig.test.js +++ b/lib/__tests__/resolveConfig.test.js @@ -5,40 +5,125 @@ const pluginWarnAboutFoo = require('./fixtures/plugin-warn-about-foo'); const replaceBackslashes = require('../testUtils/replaceBackslashes'); const stylelint = require('..'); -it('resolveConfig uses getConfigForFile to retrieve the config', async () => { - const filepath = replaceBackslashes( - path.join(__dirname, 'fixtures/getConfigForFile/a/b/foo.css'), - ); - - const config = await stylelint.resolveConfig(filepath); - - expect(config).toEqual({ - plugins: [path.join(__dirname, '/fixtures/plugin-warn-about-foo.js')], - rules: { - 'block-no-empty': [true], - 'plugin/warn-about-foo': ['always'], - }, - pluginFunctions: { - 'plugin/warn-about-foo': pluginWarnAboutFoo.rule, - }, +describe('resolveConfig', () => { + it('resolve to undefined without a path', () => { + return expect(stylelint.resolveConfig()).resolves.toBeUndefined(); }); -}); -it('config overrides should apply', async () => { - const filepath = replaceBackslashes( - path.join(__dirname, 'fixtures/config-overrides/testPrintConfig/style.css'), - ); + it('uses getConfigForFile to retrieve the config', async () => { + const filepath = replaceBackslashes( + path.join(__dirname, 'fixtures/getConfigForFile/a/b/foo.css'), + ); - const config = await stylelint.resolveConfig(filepath); + const config = await stylelint.resolveConfig(filepath); - expect(config).toEqual({ - rules: { - 'block-no-empty': [true], - 'color-named': ['never'], - }, + expect(config).toStrictEqual({ + plugins: [path.join(__dirname, '/fixtures/plugin-warn-about-foo.js')], + rules: { + 'block-no-empty': [true], + 'plugin/warn-about-foo': ['always'], + }, + pluginFunctions: { + 'plugin/warn-about-foo': pluginWarnAboutFoo.rule, + }, + }); }); -}); -it('resolveConfig with no path should resolve to undefined', () => { - return expect(stylelint.resolveConfig()).resolves.toBeUndefined(); + it('should apply config overrides', async () => { + const filepath = replaceBackslashes( + path.join(__dirname, 'fixtures/config-overrides/testPrintConfig/style.css'), + ); + + const config = await stylelint.resolveConfig(filepath); + + expect(config).toStrictEqual({ + rules: { + 'block-no-empty': [true], + 'color-named': ['never'], + }, + }); + }); + + it('should respect the passed config', async () => { + const filepath = replaceBackslashes( + path.join(__dirname, 'fixtures/config-overrides/testPrintConfig/style.css'), + ); + + const config = await stylelint.resolveConfig(filepath, { + config: { + rules: { + 'color-no-invalid-hex': true, + 'color-no-named': 'always', + }, + }, + }); + + expect(config).toStrictEqual({ + rules: { + 'color-no-invalid-hex': [true], + 'color-no-named': [], + }, + }); + }); + + it('should use the passed config file path', async () => { + const filepath = replaceBackslashes( + path.join(__dirname, 'fixtures/config-overrides/testPrintConfig/style.css'), + ); + + const config = await stylelint.resolveConfig(filepath, { + configFile: path.join(__dirname, 'fixtures/getConfigForFile/a/.stylelintrc'), + }); + + expect(config).toStrictEqual({ + pluginFunctions: { + 'plugin/warn-about-foo': expect.any(Function), + }, + plugins: [expect.stringMatching(/plugin-warn-about-foo/)], + rules: { + 'block-no-empty': [true], + 'plugin/warn-about-foo': ['always'], + }, + }); + }); + + it('should use the passed config base directory', async () => { + const filepath = replaceBackslashes( + path.join(__dirname, 'fixtures/config-overrides/testPrintConfig/style.css'), + ); + + const config = await stylelint.resolveConfig(filepath, { + configBasedir: path.join(__dirname, 'fixtures'), + config: { + extends: './config-extending-two', + }, + }); + + expect(config).toStrictEqual({ + rules: { + 'block-no-empty': [true], + 'color-no-invalid-hex': [true], + }, + }); + }); + + it('should use the passed cwd', async () => { + const filepath = replaceBackslashes( + path.join(__dirname, 'fixtures/config-overrides/testPrintConfig/style.css'), + ); + + const config = await stylelint.resolveConfig(filepath, { + cwd: path.join(__dirname, 'fixtures'), + config: { + extends: './config-extending-two', + }, + }); + + expect(config).toStrictEqual({ + rules: { + 'block-no-empty': [true], + 'color-no-invalid-hex': [true], + }, + }); + }); }); From 3d2ec8b8435c38d4548140efcb0b30decf8c0ccf Mon Sep 17 00:00:00 2001 From: Adaline Valentina Simonian Date: Tue, 23 Nov 2021 16:50:57 -0800 Subject: [PATCH 6/7] test: fix resolveConfig test case names --- lib/__tests__/resolveConfig.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/__tests__/resolveConfig.test.js b/lib/__tests__/resolveConfig.test.js index b6f761408f..f907b73ca2 100644 --- a/lib/__tests__/resolveConfig.test.js +++ b/lib/__tests__/resolveConfig.test.js @@ -6,11 +6,11 @@ const replaceBackslashes = require('../testUtils/replaceBackslashes'); const stylelint = require('..'); describe('resolveConfig', () => { - it('resolve to undefined without a path', () => { + it('should resolve to undefined without a path', () => { return expect(stylelint.resolveConfig()).resolves.toBeUndefined(); }); - it('uses getConfigForFile to retrieve the config', async () => { + it('should use getConfigForFile to retrieve the config', async () => { const filepath = replaceBackslashes( path.join(__dirname, 'fixtures/getConfigForFile/a/b/foo.css'), ); From dfbd98ef4ab4fc0ec142402b4878e30eb46fe316 Mon Sep 17 00:00:00 2001 From: Adaline Valentina Simonian Date: Tue, 23 Nov 2021 16:52:45 -0800 Subject: [PATCH 7/7] docs: fix return type in resolveConfig docs --- docs/user-guide/usage/node-api.md | 2 +- types/stylelint/index.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/user-guide/usage/node-api.md b/docs/user-guide/usage/node-api.md index 390dff4713..331ba08a6c 100644 --- a/docs/user-guide/usage/node-api.md +++ b/docs/user-guide/usage/node-api.md @@ -194,7 +194,7 @@ const config = await stylelint.resolveConfig(filePath); // } ``` -If a configuration cannot be found for a file, `resolveConfig()` will return a Promise that resolves to `null`. +If a configuration cannot be found for a file, `resolveConfig()` will return a Promise that resolves to `undefined`. You can also pass the following subset of the [options that you would normally pass to `lint()`](#options): diff --git a/types/stylelint/index.d.ts b/types/stylelint/index.d.ts index 67ad4b1811..b7621476ad 100644 --- a/types/stylelint/index.d.ts +++ b/types/stylelint/index.d.ts @@ -326,7 +326,7 @@ declare module 'stylelint' { createLinter: (options: LinterOptions) => InternalApi; /** * Resolves the effective configuation for a given file. Resolves to - * `null` if no config is found. + * `undefined` if no config is found. * @param filePath - The path to the file to get the config for. * @param options - The options to use when creating the Stylelint instance. */