Skip to content

Commit

Permalink
Add resolveConfig to Node.js API (#5734)
Browse files Browse the repository at this point in the history
  • Loading branch information
adalinesimonian committed Nov 26, 2021
1 parent 315d3b6 commit 981ac5c
Show file tree
Hide file tree
Showing 7 changed files with 252 additions and 24 deletions.
31 changes: 31 additions & 0 deletions docs/user-guide/usage/node-api.md
Expand Up @@ -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 `undefined`.

You can also pass the following subset of the [options that you would normally pass to `lint()`](#options):

- `cwd`
- `config`
- `configBasedir`
- `customSyntax`
129 changes: 129 additions & 0 deletions lib/__tests__/resolveConfig.test.js
@@ -0,0 +1,129 @@
'use strict';

const path = require('path');
const pluginWarnAboutFoo = require('./fixtures/plugin-warn-about-foo');
const replaceBackslashes = require('../testUtils/replaceBackslashes');
const stylelint = require('..');

describe('resolveConfig', () => {
it('should resolve to undefined without a path', () => {
return expect(stylelint.resolveConfig()).resolves.toBeUndefined();
});

it('should use 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).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('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],
},
});
});
});
2 changes: 2 additions & 0 deletions lib/index.js
Expand Up @@ -10,13 +10,15 @@ 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, {
lint: standalone,
rules,
formatters,
createPlugin,
resolveConfig,
createLinter: createStylelint,
utils: {
report,
Expand Down
34 changes: 10 additions & 24 deletions lib/printConfig.js
@@ -1,16 +1,15 @@
'use strict';

const createStylelint = require('./createStylelint');
const resolveConfig = require('./resolveConfig');
const globby = require('globby');
const path = require('path');

/** @typedef {import('stylelint').Config} StylelintConfig */

/**
* @param {import('stylelint').LinterOptions} options
* @returns {Promise<StylelintConfig | null>}
*/
module.exports = function printConfig({
module.exports = async function printConfig({
cwd = process.cwd(),
code,
config,
Expand All @@ -33,25 +32,12 @@ module.exports = function printConfig({
return Promise.reject(new Error('The --print-config option does not support globs.'));
}

const stylelint = createStylelint({
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;
});
return (
(await resolveConfig(filePath, {
cwd: (globbyOptions && globbyOptions.cwd) || cwd,
config,
configBasedir,
configFile,
})) || null
);
};
47 changes: 47 additions & 0 deletions 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 `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<import('stylelint').Config | undefined>}
*/
module.exports = async function resolveConfig(
filePath,
{ cwd = process.cwd(), config, configBasedir, configFile } = {},
) {
if (!filePath) {
return undefined;
}

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;

const resolved = await stylelint.getConfigForFile(configSearchPath, absoluteFilePath);

if (!resolved) {
return undefined;
}

return resolved.config;
};
10 changes: 10 additions & 0 deletions types/stylelint/index.d.ts
Expand Up @@ -324,6 +324,16 @@ declare module 'stylelint' {
* @internal
*/
createLinter: (options: LinterOptions) => InternalApi;
/**
* Resolves the effective configuation for a given file. Resolves to
* `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.
*/
resolveConfig: (
filePath: string,
options?: Pick<LinterOptions, 'cwd' | 'config' | 'configBasedir' | 'configFile'>,
) => Promise<stylelint.Config | undefined>;
utils: {
/**
* Report a problem.
Expand Down
23 changes: 23 additions & 0 deletions types/stylelint/type-test.ts
Expand Up @@ -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';
Expand Down

0 comments on commit 981ac5c

Please sign in to comment.