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 quiet option to Node.js API #5542

Merged
merged 5 commits into from Sep 17, 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
6 changes: 6 additions & 0 deletions docs/user-guide/usage/options.md
Expand Up @@ -187,3 +187,9 @@ CLI flag: `--stdin-filename`
A filename to assign the input.

If using `code` or `stdin` to pass a source string directly, you can use `codeFilename` to associate that code with a particular filename.

## `quiet`

CLI flag: `--quiet`

Only register violations for rules with an "error"-level severity (ignore "warning"-level).
13 changes: 13 additions & 0 deletions lib/__tests__/cli.test.js
Expand Up @@ -320,4 +320,17 @@ describe('CLI', () => {
'Max warnings exceeded: 1 found. 0 allowed',
);
});

it('--quiet', async () => {
await cli([
'--quiet',
'--config',
fixturesPath('default-severity-warning.json'),
fixturesPath('empty-block.css'),
]);

expect(process.exitCode).toEqual(2);

expect(process.stdout.write).toHaveBeenCalledTimes(0);
});
});
2 changes: 1 addition & 1 deletion lib/__tests__/plugins.test.js
Expand Up @@ -195,7 +195,7 @@ it('slashless plugin causes configuration error', async () => {

await expect(
postcss().use(stylelint(config)).process('.foo {}', { from: undefined }),
).rejects.toThrow(/^stylelint v7\+ requires plugin rules to be namespaced/);
).rejects.toThrow(/^stylelint requires plugin rules to be namespaced/);
});

it('plugin with primary option array', async () => {
Expand Down
18 changes: 17 additions & 1 deletion lib/__tests__/standalone-quiet.test.js
Expand Up @@ -2,7 +2,7 @@

const standalone = require('../standalone');

it('standalone with input css and quiet mode', () => {
it('standalone with input css and quiet mode (in config)', () => {
const config = {
quiet: true,
rules: {
Expand All @@ -14,3 +14,19 @@ it('standalone with input css and quiet mode', () => {
expect(linted.results[0].warnings).toEqual([]);
});
});

it('standalone with input css and quiet mode (in option)', () => {
const config = {
rules: {
'block-no-empty': [true, { severity: 'warning' }],
},
};

return standalone({
code: 'a {}',
config,
quiet: true,
}).then((linted) => {
expect(linted.results[0].warnings).toEqual([]);
});
});
2 changes: 1 addition & 1 deletion lib/__tests__/standalone.test.js
Expand Up @@ -114,7 +114,7 @@ it('standalone without input css and file(s) should throw error', () => {
'You must pass stylelint a `files` glob or a `code` string, though not both',
);

expect(() => standalone({ config: configBlockNoEmpty })).toThrow(expectedError);
return expect(() => standalone({ config: configBlockNoEmpty })).rejects.toThrow(expectedError);
});

it('standalone with non-existent-file throws an error', async () => {
Expand Down
4 changes: 2 additions & 2 deletions lib/assignDisabledRanges.js
Expand Up @@ -36,12 +36,12 @@ function createDisableRange(comment, start, strictStart, description, end, stric
}

/**
* Run it like a plugin ...
* Run it like a PostCSS plugin
* @param {PostcssRoot} root
* @param {PostcssResult} result
* @returns {PostcssResult}
*/
module.exports = function (root, result) {
module.exports = function assignDisabledRanges(root, result) {
result.stylelint = result.stylelint || {
disabledRanges: {},
ruleSeverities: {},
Expand Down
154 changes: 71 additions & 83 deletions lib/augmentConfig.js
Expand Up @@ -28,26 +28,20 @@ const path = require('path');
* @param {string} [filePath]
* @returns {Promise<StylelintConfig>}
*/
function augmentConfigBasic(stylelint, config, configDir, allowOverrides, filePath) {
return Promise.resolve()
.then(() => {
if (!allowOverrides) return config;

return addOptions(stylelint, config);
})
.then((augmentedConfig) => {
return extendConfig(stylelint, augmentedConfig, configDir);
})
.then((augmentedConfig) => {
if (filePath) {
return applyOverrides(augmentedConfig, configDir, filePath);
}
async function augmentConfigBasic(stylelint, config, configDir, allowOverrides, filePath) {
let augmentedConfig = config;

return augmentedConfig;
})
.then((augmentedConfig) => {
return absolutizePaths(augmentedConfig, configDir);
});
if (allowOverrides) {
augmentedConfig = addOptions(stylelint, augmentedConfig);
}

augmentedConfig = await extendConfig(stylelint, augmentedConfig, configDir);

if (filePath) {
augmentedConfig = applyOverrides(augmentedConfig, configDir, filePath);
}

return absolutizePaths(augmentedConfig, configDir);
}

/**
Expand All @@ -58,18 +52,20 @@ function augmentConfigBasic(stylelint, config, configDir, allowOverrides, filePa
* @param {CosmiconfigResult} [cosmiconfigResult]
* @returns {Promise<CosmiconfigResult | null>}
*/
function augmentConfigExtended(stylelint, cosmiconfigResult) {
if (!cosmiconfigResult) return Promise.resolve(null);
async function augmentConfigExtended(stylelint, cosmiconfigResult) {
if (!cosmiconfigResult) {
return null;
}

const configDir = path.dirname(cosmiconfigResult.filepath || '');
const { ignoreFiles, ...cleanedConfig } = cosmiconfigResult.config;

return augmentConfigBasic(stylelint, cleanedConfig, configDir).then((augmentedConfig) => {
return {
config: augmentedConfig,
filepath: cosmiconfigResult.filepath,
};
});
const augmentedConfig = await augmentConfigBasic(stylelint, cleanedConfig, configDir);

return {
config: augmentedConfig,
filepath: cosmiconfigResult.filepath,
};
}

/**
Expand All @@ -78,36 +74,33 @@ function augmentConfigExtended(stylelint, cosmiconfigResult) {
* @param {CosmiconfigResult} [cosmiconfigResult]
* @returns {Promise<CosmiconfigResult | null>}
*/
function augmentConfigFull(stylelint, filePath, cosmiconfigResult) {
if (!cosmiconfigResult) return Promise.resolve(null);
async function augmentConfigFull(stylelint, filePath, cosmiconfigResult) {
if (!cosmiconfigResult) {
return null;
}

const config = cosmiconfigResult.config;
const filepath = cosmiconfigResult.filepath;

const configDir = stylelint._options.configBasedir || path.dirname(filepath || '');

return augmentConfigBasic(stylelint, config, configDir, true, filePath)
.then((augmentedConfig) => {
return addPluginFunctions(augmentedConfig);
})
.then((augmentedConfig) => {
return addProcessorFunctions(augmentedConfig);
})
.then((augmentedConfig) => {
if (!augmentedConfig.rules) {
throw configurationError(
'No rules found within configuration. Have you provided a "rules" property?',
);
}
let augmentedConfig = await augmentConfigBasic(stylelint, config, configDir, true, filePath);

return normalizeAllRuleSettings(augmentedConfig);
})
.then((augmentedConfig) => {
return {
config: augmentedConfig,
filepath: cosmiconfigResult.filepath,
};
});
augmentedConfig = addPluginFunctions(augmentedConfig);
augmentedConfig = addProcessorFunctions(augmentedConfig);

if (!augmentedConfig.rules) {
throw configurationError(
'No rules found within configuration. Have you provided a "rules" property?',
);
}

augmentedConfig = normalizeAllRuleSettings(augmentedConfig);

return {
config: augmentedConfig,
filepath: cosmiconfigResult.filepath,
};
}

/**
Expand Down Expand Up @@ -167,37 +160,34 @@ function absolutizeProcessors(processors, configDir) {
* @param {string} configDir
* @return {Promise<StylelintConfig>}
*/
function extendConfig(stylelint, config, configDir) {
if (config.extends === undefined) return Promise.resolve(config);
async function extendConfig(stylelint, config, configDir) {
if (config.extends === undefined) {
return config;
}

const normalizedExtends = Array.isArray(config.extends) ? config.extends : [config.extends];
const { extends: configExtends, ...originalWithoutExtends } = config;
const normalizedExtends = [configExtends].flat();

const loadExtends = normalizedExtends.reduce((resultPromise, extendLookup) => {
return resultPromise.then((resultConfig) => {
return loadExtendedConfig(stylelint, resultConfig, configDir, extendLookup).then(
(extendResult) => {
if (!extendResult) return resultConfig;
let resultConfig = originalWithoutExtends;

return mergeConfigs(resultConfig, extendResult.config);
},
);
});
}, Promise.resolve(originalWithoutExtends));
for (const extendLookup of normalizedExtends) {
const extendResult = await loadExtendedConfig(stylelint, configDir, extendLookup);

return loadExtends.then((resultConfig) => {
return mergeConfigs(resultConfig, originalWithoutExtends);
});
if (extendResult) {
resultConfig = mergeConfigs(resultConfig, extendResult.config);
}
}

return mergeConfigs(resultConfig, originalWithoutExtends);
}

/**
* @param {StylelintInternalApi} stylelint
* @param {StylelintConfig} config
* @param {string} configDir
* @param {string} extendLookup
* @return {Promise<CosmiconfigResult | null>}
*/
function loadExtendedConfig(stylelint, config, configDir, extendLookup) {
function loadExtendedConfig(stylelint, configDir, extendLookup) {
const extendPath = getModulePath(configDir, extendLookup);

return stylelint._extendExplorer.load(extendPath);
Expand Down Expand Up @@ -283,43 +273,41 @@ function mergeConfigs(a, b) {
* @returns {StylelintConfig}
*/
function addPluginFunctions(config) {
if (!config.plugins) return config;
if (!config.plugins) {
return config;
}

const normalizedPlugins = [config.plugins].flat();

const normalizedPlugins = Array.isArray(config.plugins) ? config.plugins : [config.plugins];
/** @type {{[k: string]: Function}} */
const pluginFunctions = {};

const pluginFunctions = normalizedPlugins.reduce((result, pluginLookup) => {
for (const pluginLookup of normalizedPlugins) {
let pluginImport = require(pluginLookup);

// Handle either ES6 or CommonJS modules
pluginImport = pluginImport.default || pluginImport;

// A plugin can export either a single rule definition
// or an array of them
const normalizedPluginImport = Array.isArray(pluginImport) ? pluginImport : [pluginImport];
const normalizedPluginImport = [pluginImport].flat();

normalizedPluginImport.forEach((pluginRuleDefinition) => {
if (!pluginRuleDefinition.ruleName) {
throw configurationError(
'stylelint v3+ requires plugins to expose a ruleName. ' +
`The plugin "${pluginLookup}" is not doing this, so will not work ` +
'with stylelint v3+. Please file an issue with the plugin.',
`stylelint requires plugins to expose a ruleName. The plugin "${pluginLookup}" is not doing this, so will not work with stylelint. Please file an issue with the plugin.`,
);
}

if (!pluginRuleDefinition.ruleName.includes('/')) {
throw configurationError(
'stylelint v7+ requires plugin rules to be namespaced, ' +
'i.e. only `plugin-namespace/plugin-rule-name` plugin rule names are supported. ' +
`The plugin rule "${pluginRuleDefinition.ruleName}" does not do this, so will not work. ` +
'Please file an issue with the plugin.',
`stylelint requires plugin rules to be namespaced, i.e. only \`plugin-namespace/plugin-rule-name\` plugin rule names are supported. The plugin rule "${pluginRuleDefinition.ruleName}" does not do this, so will not work. Please file an issue with the plugin.`,
);
}

result[pluginRuleDefinition.ruleName] = pluginRuleDefinition.rule;
pluginFunctions[pluginRuleDefinition.ruleName] = pluginRuleDefinition.rule;
});

return result;
}, /** @type {{[k: string]: Function}} */ ({}));
}

config.pluginFunctions = pluginFunctions;

Expand Down