Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add resolveConfig to Node.js API #5734

Merged
merged 7 commits into from Nov 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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],
},
});
});
});
adalinesimonian marked this conversation as resolved.
Show resolved Hide resolved
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