From 1513decc857580e6bd51794272874dbe01f4c01e Mon Sep 17 00:00:00 2001 From: Aleks Hudochenkov Date: Thu, 16 Sep 2021 21:35:11 +0200 Subject: [PATCH 1/4] Use async-await --- lib/__tests__/plugins.test.js | 2 +- lib/__tests__/standalone-quiet.test.js | 18 +- lib/__tests__/standalone.test.js | 2 +- lib/augmentConfig.js | 154 ++++++------ lib/cli.js | 106 +++++---- lib/createStylelintResult.js | 42 ++-- lib/getConfigForFile.js | 27 +-- lib/getPostcssResult.js | 94 ++++---- lib/isPathIgnored.js | 40 ++-- lib/lintSource.js | 96 ++++---- lib/prepareReturnValue.js | 8 +- lib/standalone.js | 311 +++++++++++-------------- types/stylelint/index.d.ts | 2 +- 13 files changed, 422 insertions(+), 480 deletions(-) diff --git a/lib/__tests__/plugins.test.js b/lib/__tests__/plugins.test.js index b16a3f95b4..81ecec5c28 100644 --- a/lib/__tests__/plugins.test.js +++ b/lib/__tests__/plugins.test.js @@ -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 () => { diff --git a/lib/__tests__/standalone-quiet.test.js b/lib/__tests__/standalone-quiet.test.js index 026839a706..1199fd55f9 100644 --- a/lib/__tests__/standalone-quiet.test.js +++ b/lib/__tests__/standalone-quiet.test.js @@ -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: { @@ -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([]); + }); +}); diff --git a/lib/__tests__/standalone.test.js b/lib/__tests__/standalone.test.js index d12ab9d46f..4d1932a655 100644 --- a/lib/__tests__/standalone.test.js +++ b/lib/__tests__/standalone.test.js @@ -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 () => { diff --git a/lib/augmentConfig.js b/lib/augmentConfig.js index bf958eeee1..25336a4650 100644 --- a/lib/augmentConfig.js +++ b/lib/augmentConfig.js @@ -28,26 +28,20 @@ const slash = require('slash'); * @param {string} [filePath] * @returns {Promise} */ -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); } /** @@ -58,18 +52,20 @@ function augmentConfigBasic(stylelint, config, configDir, allowOverrides, filePa * @param {CosmiconfigResult} [cosmiconfigResult] * @returns {Promise} */ -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, + }; } /** @@ -78,36 +74,33 @@ function augmentConfigExtended(stylelint, cosmiconfigResult) { * @param {CosmiconfigResult} [cosmiconfigResult] * @returns {Promise} */ -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, + }; } /** @@ -167,37 +160,34 @@ function absolutizeProcessors(processors, configDir) { * @param {string} configDir * @return {Promise} */ -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} */ -function loadExtendedConfig(stylelint, config, configDir, extendLookup) { +function loadExtendedConfig(stylelint, configDir, extendLookup) { const extendPath = getModulePath(configDir, extendLookup); return stylelint._extendExplorer.load(extendPath); @@ -283,11 +273,16 @@ 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 @@ -295,31 +290,24 @@ function addPluginFunctions(config) { // 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; diff --git a/lib/cli.js b/lib/cli.js index d2aae4cefb..d58887ab2e 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -326,7 +326,7 @@ const meowOptions = { * @param {string[]} argv * @returns {Promise} */ -module.exports = (argv) => { +module.exports = async (argv) => { const cli = buildCLI(argv); const invalidOptionsMessage = checkInvalidCLIOptions(meowOptions.flags, cli.flags); @@ -442,75 +442,73 @@ module.exports = (argv) => { if (cli.flags.help) { cli.showHelp(0); - return Promise.resolve(); + return; } if (cli.flags.version) { cli.showVersion(); - return Promise.resolve(); + return; } if (cli.flags.allowEmptyInput) { optionsBase.allowEmptyInput = cli.flags.allowEmptyInput; } - return Promise.resolve() - .then( - /** - * @returns {Promise} - */ - () => { - // Add input/code into options - if (cli.input.length) { - return Promise.resolve({ ...optionsBase, files: /** @type {string} */ (cli.input) }); - } - - return getStdin().then((stdin) => ({ ...optionsBase, code: stdin })); - }, - ) - .then((options) => { - if (cli.flags.printConfig) { - return printConfig(options) - .then((config) => { - process.stdout.write(JSON.stringify(config, null, ' ')); - }) - .catch(handleError); - } + // Add input/code into options + /** @type {OptionBaseType} */ + const options = cli.input.length + ? { + ...optionsBase, + files: /** @type {string} */ (cli.input), + } + : await getStdin().then((stdin) => { + return { + ...optionsBase, + code: stdin, + }; + }); + + if (cli.flags.printConfig) { + return printConfig(options) + .then((config) => { + process.stdout.write(JSON.stringify(config, null, ' ')); + }) + .catch(handleError); + } - if (!options.files && !options.code && !cli.flags.stdin) { - cli.showHelp(); + if (!options.files && !options.code && !cli.flags.stdin) { + cli.showHelp(); + + return; + } + return standalone(options) + .then((linted) => { + if (!linted.output) { return; } - return standalone(options) - .then((linted) => { - if (!linted.output) { - return; - } - - process.stdout.write(linted.output); - - if (options.outputFile) { - writeOutputFile(linted.output, options.outputFile).catch(handleError); - } - - if (linted.errored) { - process.exitCode = EXIT_CODE_ERROR; - } else if (maxWarnings !== undefined && linted.maxWarningsExceeded) { - const foundWarnings = linted.maxWarningsExceeded.foundWarnings; - - process.stderr.write( - `${EOL}${chalk.red(`Max warnings exceeded: `)}${foundWarnings} found. ${chalk.dim( - `${maxWarnings} allowed${EOL}${EOL}`, - )}`, - ); - process.exitCode = EXIT_CODE_ERROR; - } - }) - .catch(handleError); - }); + process.stdout.write(linted.output); + + if (options.outputFile) { + writeOutputFile(linted.output, options.outputFile).catch(handleError); + } + + if (linted.errored) { + process.exitCode = EXIT_CODE_ERROR; + } else if (maxWarnings !== undefined && linted.maxWarningsExceeded) { + const foundWarnings = linted.maxWarningsExceeded.foundWarnings; + + process.stderr.write( + `${EOL}${chalk.red(`Max warnings exceeded: `)}${foundWarnings} found. ${chalk.dim( + `${maxWarnings} allowed${EOL}${EOL}`, + )}`, + ); + process.exitCode = EXIT_CODE_ERROR; + } + }) + .catch(handleError); }; /** diff --git a/lib/createStylelintResult.js b/lib/createStylelintResult.js index fb899282ce..6990ae1202 100644 --- a/lib/createStylelintResult.js +++ b/lib/createStylelintResult.js @@ -12,7 +12,7 @@ const createPartialStylelintResult = require('./createPartialStylelintResult'); * @param {import('stylelint').StylelintCssSyntaxError} [cssSyntaxError] * @return {Promise} */ -module.exports = function createStylelintResult( +module.exports = async function createStylelintResult( stylelint, postcssResult, filePath, @@ -20,26 +20,22 @@ module.exports = function createStylelintResult( ) { let stylelintResult = createPartialStylelintResult(postcssResult, cssSyntaxError); - return stylelint.getConfigForFile(filePath, filePath).then((configForFile) => { - // TODO TYPES handle possible null here - const config = - /** @type {{ config: import('stylelint').StylelintConfig, filepath: string }} */ ( - configForFile - ).config; - const file = stylelintResult.source || (cssSyntaxError && cssSyntaxError.file); - - if (config.resultProcessors) { - config.resultProcessors.forEach((resultProcessor) => { - // Result processors might just mutate the result object, - // or might return a new one - const returned = resultProcessor(stylelintResult, file); - - if (returned) { - stylelintResult = returned; - } - }); - } - - return stylelintResult; - }); + const configForFile = await stylelint.getConfigForFile(filePath, filePath); + + const config = configForFile === null ? {} : configForFile.config; + const file = stylelintResult.source || (cssSyntaxError && cssSyntaxError.file); + + if (config.resultProcessors) { + config.resultProcessors.forEach((resultProcessor) => { + // Result processors might just mutate the result object, + // or might return a new one + const returned = resultProcessor(stylelintResult, file); + + if (returned) { + stylelintResult = returned; + } + }); + } + + return stylelintResult; }; diff --git a/lib/getConfigForFile.js b/lib/getConfigForFile.js index 3827c95610..e41b529481 100644 --- a/lib/getConfigForFile.js +++ b/lib/getConfigForFile.js @@ -19,7 +19,7 @@ const STOP_DIR = IS_TEST ? path.resolve(__dirname, '..') : undefined; * @param {string} [filePath] * @returns {ConfigPromise} */ -module.exports = function getConfigForFile(stylelint, searchPath = process.cwd(), filePath) { +module.exports = async function getConfigForFile(stylelint, searchPath = process.cwd(), filePath) { const optionsConfig = stylelint._options.config; if (optionsConfig !== undefined) { @@ -56,22 +56,17 @@ module.exports = function getConfigForFile(stylelint, searchPath = process.cwd() ? configExplorer.load(stylelint._options.configFile) : configExplorer.search(searchPath); - return /** @type {ConfigPromise} */ ( - searchForConfig - .then((config) => { - // If no config was found, try looking from process.cwd - if (!config) return configExplorer.search(process.cwd()); + let config = await searchForConfig; - return config; - }) - .then((config) => { - if (!config) { - const ending = searchPath ? ` for ${searchPath}` : ''; + if (!config) { + config = await configExplorer.search(process.cwd()); + } - throw configurationError(`No configuration provided${ending}`); - } + if (!config) { + return Promise.reject( + configurationError(`No configuration provided${searchPath ? ` for ${searchPath}` : ''}`), + ); + } - return config; - }) - ); + return config; }; diff --git a/lib/getPostcssResult.js b/lib/getPostcssResult.js index 0bc5dfcd86..5a2e4b1342 100644 --- a/lib/getPostcssResult.js +++ b/lib/getPostcssResult.js @@ -19,73 +19,65 @@ const postcssProcessor = postcss(); * * @returns {Promise} */ -module.exports = function getPostcssResult(stylelint, options = {}) { +module.exports = async function getPostcssResult(stylelint, options = {}) { const cached = options.filePath ? stylelint._postcssResultCache.get(options.filePath) : undefined; - if (cached) return Promise.resolve(cached); + if (cached) { + return cached; + } + + if (stylelint._options.syntax) { + return Promise.reject( + new Error( + 'The "syntax" (--syntax) option has been removed. Install the appropriate syntax and use the "customSyntax" (--custom-syntax) option instead', + ), + ); + } + + let syntax = options.customSyntax + ? getCustomSyntax(options.customSyntax) + : cssSyntax(stylelint, options.filePath); - /** @type {Promise | undefined} */ + const postcssOptions = { + from: options.filePath, + syntax, + }; + + /** @type {string | undefined} */ let getCode; if (options.code !== undefined) { - getCode = Promise.resolve(options.code); + getCode = options.code; } else if (options.filePath) { - getCode = fs.readFile(options.filePath, 'utf8'); + getCode = await fs.readFile(options.filePath, 'utf8'); } - if (!getCode) { - throw new Error('code or filePath required'); + if (getCode === undefined) { + return Promise.reject(new Error('code or filePath required')); } - return getCode - .then((code) => { - if (stylelint._options.syntax) { - throw new Error( - 'The "syntax" (--syntax) option has been removed. Install the appropriate syntax and use the "customSyntax" (--custom-syntax) option instead', - ); - } - - /** @type {Syntax | null} */ - let syntax = null; - - if (options.customSyntax) { - syntax = getCustomSyntax(options.customSyntax); - } else { - syntax = cssSyntax(stylelint, options.filePath); - } - - const postcssOptions = { - from: options.filePath, - syntax, - }; - - const source = options.code ? options.codeFilename : options.filePath; - let preProcessedCode = code; + if (options.codeProcessors && options.codeProcessors.length) { + if (stylelint._options.fix) { + console.warn( + 'Autofix is incompatible with processors and will be disabled. Are you sure you need a processor?', + ); + stylelint._options.fix = false; + } - if (options.codeProcessors && options.codeProcessors.length) { - if (stylelint._options.fix) { - console.warn( - 'Autofix is incompatible with processors and will be disabled. Are you sure you need a processor?', - ); - stylelint._options.fix = false; - } + const sourceName = options.code ? options.codeFilename : options.filePath; - options.codeProcessors.forEach((codeProcessor) => { - preProcessedCode = codeProcessor(preProcessedCode, source); - }); - } + options.codeProcessors.forEach((codeProcessor) => { + getCode = codeProcessor(getCode, sourceName); + }); + } - const result = new LazyResult(postcssProcessor, preProcessedCode, postcssOptions); + const postcssResult = await new LazyResult(postcssProcessor, getCode, postcssOptions); - return result; - }) - .then((postcssResult) => { - if (options.filePath) { - stylelint._postcssResultCache.set(options.filePath, postcssResult); - } + if (options.filePath) { + stylelint._postcssResultCache.set(options.filePath, postcssResult); + } - return postcssResult; - }); + return postcssResult; }; /** diff --git a/lib/isPathIgnored.js b/lib/isPathIgnored.js index 4a2b6ed693..20e44d4857 100644 --- a/lib/isPathIgnored.js +++ b/lib/isPathIgnored.js @@ -14,35 +14,35 @@ const slash = require('slash'); * @param {string} [filePath] * @return {Promise} */ -module.exports = function isPathIgnored(stylelint, filePath) { +module.exports = async function isPathIgnored(stylelint, filePath) { if (!filePath) { - return Promise.resolve(false); + return false; } const cwd = process.cwd(); const ignorer = getFileIgnorer(stylelint._options); - return stylelint.getConfigForFile(filePath, filePath).then((result) => { - if (!result) { - return true; - } + const result = await stylelint.getConfigForFile(filePath, filePath); + + if (!result) { + return true; + } - // Glob patterns for micromatch should be in POSIX-style - const ignoreFiles = /** @type {Array} */ (result.config.ignoreFiles || []).map(slash); + // Glob patterns for micromatch should be in POSIX-style + const ignoreFiles = /** @type {Array} */ (result.config.ignoreFiles || []).map(slash); - const absoluteFilePath = path.isAbsolute(filePath) - ? filePath - : path.resolve(process.cwd(), filePath); + const absoluteFilePath = path.isAbsolute(filePath) + ? filePath + : path.resolve(process.cwd(), filePath); - if (micromatch([absoluteFilePath], ignoreFiles).length) { - return true; - } + if (micromatch([absoluteFilePath], ignoreFiles).length) { + return true; + } - // Check filePath with .stylelintignore file - if (filterFilePaths(ignorer, [path.relative(cwd, absoluteFilePath)]).length === 0) { - return true; - } + // Check filePath with .stylelintignore file + if (filterFilePaths(ignorer, [path.relative(cwd, absoluteFilePath)]).length === 0) { + return true; + } - return false; - }); + return false; }; diff --git a/lib/lintSource.js b/lib/lintSource.js index deb93a832a..3d8d6301a0 100644 --- a/lib/lintSource.js +++ b/lib/lintSource.js @@ -17,7 +17,7 @@ const path = require('path'); * @param {Options} options * @returns {Promise} */ -module.exports = function lintSource(stylelint, options = {}) { +module.exports = async function lintSource(stylelint, options = {}) { if (!options.filePath && options.code === undefined && !options.existingPostcssResult) { return Promise.reject(new Error('You must provide filePath, code, or existingPostcssResult')); } @@ -34,74 +34,60 @@ module.exports = function lintSource(stylelint, options = {}) { return Promise.reject(new Error('filePath must be an absolute path')); } - const getIsIgnored = stylelint.isPathIgnored(inputFilePath).catch((err) => { + const isIgnored = await stylelint.isPathIgnored(inputFilePath).catch((err) => { if (isCodeNotFile && isPathNotFoundError(err)) return false; throw err; }); - return getIsIgnored.then((isIgnored) => { - if (isIgnored) { - /** @type {PostcssResult} */ - - return options.existingPostcssResult - ? Object.assign(options.existingPostcssResult, { - stylelint: createEmptyStylelintPostcssResult(), - }) - : createEmptyPostcssResult(inputFilePath); - } + if (isIgnored) { + return options.existingPostcssResult + ? Object.assign(options.existingPostcssResult, { + stylelint: createEmptyStylelintPostcssResult(), + }) + : createEmptyPostcssResult(inputFilePath); + } - const configSearchPath = stylelint._options.configFile || inputFilePath; + const configSearchPath = stylelint._options.configFile || inputFilePath; - const getConfig = stylelint.getConfigForFile(configSearchPath, inputFilePath).catch((err) => { + const configForFile = await stylelint + .getConfigForFile(configSearchPath, inputFilePath) + .catch((err) => { if (isCodeNotFile && isPathNotFoundError(err)) return stylelint.getConfigForFile(process.cwd()); throw err; }); - return getConfig.then((result) => { - if (!result) { - throw new Error('Config file not found'); - } - - const config = result.config; - const existingPostcssResult = options.existingPostcssResult; - const stylelintResult = { - ruleSeverities: {}, - customMessages: {}, - disabledRanges: {}, - }; - - if (existingPostcssResult) { - const stylelintPostcssResult = Object.assign(existingPostcssResult, { - stylelint: stylelintResult, - }); - - return lintPostcssResult(stylelint._options, stylelintPostcssResult, config).then( - () => stylelintPostcssResult, - ); - } - - return stylelint - ._getPostcssResult({ - code: options.code, - codeFilename: options.codeFilename, - filePath: inputFilePath, - codeProcessors: config.codeProcessors, - customSyntax: config.customSyntax, - }) - .then((postcssResult) => { - const stylelintPostcssResult = Object.assign(postcssResult, { - stylelint: stylelintResult, - }); - - return lintPostcssResult(stylelint._options, stylelintPostcssResult, config).then( - () => stylelintPostcssResult, - ); - }); - }); + if (!configForFile) { + return Promise.reject(new Error('Config file not found')); + } + + const config = configForFile.config; + const existingPostcssResult = options.existingPostcssResult; + const stylelintResult = { + ruleSeverities: {}, + customMessages: {}, + disabledRanges: {}, + }; + + const postcssResult = + existingPostcssResult || + (await stylelint._getPostcssResult({ + code: options.code, + codeFilename: options.codeFilename, + filePath: inputFilePath, + codeProcessors: config.codeProcessors, + customSyntax: config.customSyntax, + })); + + const stylelintPostcssResult = Object.assign(postcssResult, { + stylelint: stylelintResult, }); + + await lintPostcssResult(stylelint._options, stylelintPostcssResult, config); + + return stylelintPostcssResult; }; /** diff --git a/lib/prepareReturnValue.js b/lib/prepareReturnValue.js index 65273aec1a..4bb66f5247 100644 --- a/lib/prepareReturnValue.js +++ b/lib/prepareReturnValue.js @@ -7,19 +7,17 @@ const reportDisables = require('./reportDisables'); /** @typedef {import('stylelint').Formatter} Formatter */ /** @typedef {import('stylelint').StylelintResult} StylelintResult */ -/** @typedef {import('stylelint').StylelintStandaloneOptions} StylelintStandaloneOptions */ +/** @typedef {import('stylelint').StylelintStandaloneOptions["maxWarnings"]} maxWarnings */ /** @typedef {import('stylelint').StylelintStandaloneReturnValue} StylelintStandaloneReturnValue */ /** * @param {StylelintResult[]} stylelintResults - * @param {StylelintStandaloneOptions} options + * @param {maxWarnings} maxWarnings * @param {Formatter} formatter * * @returns {StylelintStandaloneReturnValue} */ -function prepareReturnValue(stylelintResults, options, formatter) { - const { maxWarnings } = options; - +function prepareReturnValue(stylelintResults, maxWarnings, formatter) { reportDisables(stylelintResults); needlessDisables(stylelintResults); invalidScopeDisables(stylelintResults); diff --git a/lib/standalone.js b/lib/standalone.js index 0e9e01950c..8eef88ac93 100644 --- a/lib/standalone.js +++ b/lib/standalone.js @@ -32,52 +32,60 @@ const writeFileAtomic = require('write-file-atomic'); * @param {StylelintStandaloneOptions} options * @returns {Promise} */ -module.exports = function (options) { - const cacheLocation = options.cacheLocation; - const code = options.code; - const codeFilename = options.codeFilename; - const config = options.config; - const configBasedir = options.configBasedir; - const configFile = options.configFile; - const customSyntax = options.customSyntax; - const globbyOptions = options.globbyOptions; - const files = options.files; - const fix = options.fix; - const formatter = options.formatter; - const ignoreDisables = options.ignoreDisables; - const reportNeedlessDisables = options.reportNeedlessDisables; - const reportInvalidScopeDisables = options.reportInvalidScopeDisables; - const reportDescriptionlessDisables = options.reportDescriptionlessDisables; - const syntax = options.syntax; - const allowEmptyInput = options.allowEmptyInput || false; - const useCache = options.cache || false; +module.exports = async function standalone({ + allowEmptyInput = false, + cache: useCache = false, + cacheLocation, + code, + codeFilename, + config, + configBasedir, + configFile, + customSyntax, + disableDefaultIgnores, + files, + fix, + formatter, + globbyOptions, + ignoreDisables, + ignorePath = DEFAULT_IGNORE_FILENAME, + ignorePattern = [], + maxWarnings, + quiet, + reportDescriptionlessDisables, + reportInvalidScopeDisables, + reportNeedlessDisables, + syntax, +}) { /** @type {FileCache} */ let fileCache; const startTime = Date.now(); + const isValidCode = typeof code === 'string'; + + if ((!files && !isValidCode) || (files && (code || isValidCode))) { + return Promise.reject( + new Error('You must pass stylelint a `files` glob or a `code` string, though not both'), + ); + } + // The ignorer will be used to filter file paths after the glob is checked, // before any files are actually read - const ignoreFilePath = options.ignorePath || DEFAULT_IGNORE_FILENAME; - const absoluteIgnoreFilePath = path.isAbsolute(ignoreFilePath) - ? ignoreFilePath - : path.resolve(process.cwd(), ignoreFilePath); + const absoluteIgnoreFilePath = path.isAbsolute(ignorePath) + ? ignorePath + : path.resolve(process.cwd(), ignorePath); let ignoreText = ''; try { ignoreText = fs.readFileSync(absoluteIgnoreFilePath, 'utf8'); } catch (readError) { - if (!isPathNotFoundError(readError)) throw readError; + if (!isPathNotFoundError(readError)) { + return Promise.reject(readError); + } } - const ignorePattern = options.ignorePattern || []; const ignorer = ignore().add(ignoreText).add(ignorePattern); - const isValidCode = typeof code === 'string'; - - if ((!files && !isValidCode) || (files && (code || isValidCode))) { - throw new Error('You must pass stylelint a `files` glob or a `code` string, though not both'); - } - /** @type {Formatter} */ let formatterFunction; @@ -92,13 +100,14 @@ module.exports = function (options) { configFile, configBasedir, ignoreDisables, - ignorePath: ignoreFilePath, + ignorePath, reportNeedlessDisables, reportInvalidScopeDisables, reportDescriptionlessDisables, syntax, customSyntax, fix, + quiet, }); if (!files) { @@ -112,71 +121,44 @@ module.exports = function (options) { absoluteCodeFilename && !filterFilePaths(ignorer, [path.relative(process.cwd(), absoluteCodeFilename)]).length ) { - return Promise.resolve(prepareReturnValue([], options, formatterFunction)); + return prepareReturnValue([], maxWarnings, formatterFunction); } - return stylelint - ._lintSource({ + let stylelintResult; + + try { + const postcssResult = await stylelint._lintSource({ code, codeFilename: absoluteCodeFilename, - }) - .then((postcssResult) => { - // Check for file existence - return /** @type {Promise} */ ( - new Promise((resolve, reject) => { - if (!absoluteCodeFilename) { - reject(); - - return; - } - - fs.stat(absoluteCodeFilename, (err) => { - if (err) { - reject(); - } else { - resolve(); - } - }); - }) - ) - .then(() => { - return stylelint._createStylelintResult(postcssResult, absoluteCodeFilename); - }) - .catch(() => { - return stylelint._createStylelintResult(postcssResult); - }); - }) - .catch((error) => handleError(stylelint, error)) - .then((stylelintResult) => { - const postcssResult = stylelintResult._postcssResult; - const returnValue = prepareReturnValue([stylelintResult], options, formatterFunction); + }); - if ( - options.fix && - postcssResult && - !postcssResult.stylelint.ignored && - !postcssResult.stylelint.ruleDisableFix - ) { - if (!postcssResult.stylelint.disableWritingFix) { - // If we're fixing, the output should be the fixed code - returnValue.output = postcssResult.root.toString(postcssResult.opts.syntax); - } else { - // If the writing of the fix is disabled, the input code is returned as-is - returnValue.output = code; - } - } + stylelintResult = await stylelint._createStylelintResult(postcssResult, absoluteCodeFilename); + } catch (error) { + stylelintResult = await handleError(stylelint, error); + } - return returnValue; - }); - } + const postcssResult = stylelintResult._postcssResult; + const returnValue = prepareReturnValue([stylelintResult], maxWarnings, formatterFunction); - let fileList = files; + if ( + fix && + postcssResult && + !postcssResult.stylelint.ignored && + !postcssResult.stylelint.ruleDisableFix + ) { + if (!postcssResult.stylelint.disableWritingFix) { + // If we're fixing, the output should be the fixed code + returnValue.output = postcssResult.root.toString(postcssResult.opts.syntax); + } else { + // If the writing of the fix is disabled, the input code is returned as-is + returnValue.output = code; + } + } - if (typeof fileList === 'string') { - fileList = [fileList]; + return returnValue; } - fileList = fileList.map((entry) => { + let fileList = [files].flat().map((entry) => { const cwd = (globbyOptions && globbyOptions.cwd) || process.cwd(); const absolutePath = !path.isAbsolute(entry) ? path.join(cwd, entry) : path.normalize(entry); @@ -188,7 +170,7 @@ module.exports = function (options) { return entry; }); - if (!options.disableDefaultIgnores) { + if (!disableDefaultIgnores) { fileList = fileList.concat(ALWAYS_IGNORED_GLOBS.map((glob) => `!${glob}`)); } @@ -204,98 +186,89 @@ module.exports = function (options) { fileCache.destroy(); } - return globby(fileList, globbyOptions) - .then((filePaths) => { - // The ignorer filter needs to check paths relative to cwd - filePaths = filterFilePaths( - ignorer, - filePaths.map((p) => path.relative(process.cwd(), p)), - ); + let filePaths = await globby(fileList, globbyOptions); - if (!filePaths.length) { - if (!allowEmptyInput) { - throw new NoFilesFoundError(fileList); - } + // The ignorer filter needs to check paths relative to cwd + filePaths = filterFilePaths( + ignorer, + filePaths.map((p) => path.relative(process.cwd(), p)), + ); - return Promise.all([]); - } + let stylelintResults; - const cwd = (globbyOptions && globbyOptions.cwd) || process.cwd(); - let absoluteFilePaths = filePaths.map((filePath) => { - const absoluteFilepath = !path.isAbsolute(filePath) - ? path.join(cwd, filePath) - : path.normalize(filePath); + if (filePaths.length) { + const cwd = (globbyOptions && globbyOptions.cwd) || process.cwd(); + let absoluteFilePaths = filePaths.map((filePath) => { + const absoluteFilepath = !path.isAbsolute(filePath) + ? path.join(cwd, filePath) + : path.normalize(filePath); - return absoluteFilepath; - }); + return absoluteFilepath; + }); - if (useCache) { - absoluteFilePaths = absoluteFilePaths.filter(fileCache.hasFileChanged.bind(fileCache)); - } + if (useCache) { + absoluteFilePaths = absoluteFilePaths.filter(fileCache.hasFileChanged.bind(fileCache)); + } - const getStylelintResults = absoluteFilePaths.map((absoluteFilepath) => { - debug(`Processing ${absoluteFilepath}`); - - return stylelint - ._lintSource({ - filePath: absoluteFilepath, - }) - .then((postcssResult) => { - if (postcssResult.stylelint.stylelintError && useCache) { - debug(`${absoluteFilepath} contains linting errors and will not be cached.`); - fileCache.removeEntry(absoluteFilepath); - } - - /** - * If we're fixing, save the file with changed code - * @type {Promise} - */ - let fixFile = Promise.resolve(); - - if ( - postcssResult.root && - postcssResult.opts && - !postcssResult.stylelint.ignored && - !postcssResult.stylelint.ruleDisableFix && - options.fix && - !postcssResult.stylelint.disableWritingFix - ) { - const fixedCss = postcssResult.root.toString(postcssResult.opts.syntax); - - if ( - postcssResult.root && - postcssResult.root.source && - postcssResult.root.source.input.css !== fixedCss - ) { - fixFile = writeFileAtomic(absoluteFilepath, fixedCss); - } - } - - return fixFile.then(() => - stylelint._createStylelintResult(postcssResult, absoluteFilepath), - ); - }) - .catch((error) => { - // On any error, we should not cache the lint result - fileCache.removeEntry(absoluteFilepath); - - return handleError(stylelint, error, absoluteFilepath); - }); - }); + const getStylelintResults = absoluteFilePaths.map(async (absoluteFilepath) => { + debug(`Processing ${absoluteFilepath}`); - return Promise.all(getStylelintResults); - }) - .then((stylelintResults) => { - if (useCache) { - fileCache.reconcile(); - } + try { + const postcssResult = await stylelint._lintSource({ + filePath: absoluteFilepath, + }); + + if (postcssResult.stylelint.stylelintError && useCache) { + debug(`${absoluteFilepath} contains linting errors and will not be cached.`); + fileCache.removeEntry(absoluteFilepath); + } - const rtn = prepareReturnValue(stylelintResults, options, formatterFunction); + /** + * If we're fixing, save the file with changed code + */ + if ( + postcssResult.root && + postcssResult.opts && + !postcssResult.stylelint.ignored && + fix && + !postcssResult.stylelint.disableWritingFix + ) { + const fixedCss = postcssResult.root.toString(postcssResult.opts.syntax); + + if ( + postcssResult.root && + postcssResult.root.source && + postcssResult.root.source.input.css !== fixedCss + ) { + await writeFileAtomic(absoluteFilepath, fixedCss); + } + } - debug(`Linting complete in ${Date.now() - startTime}ms`); + return stylelint._createStylelintResult(postcssResult, absoluteFilepath); + } catch (error) { + // On any error, we should not cache the lint result + fileCache.removeEntry(absoluteFilepath); - return rtn; + return handleError(stylelint, error, absoluteFilepath); + } }); + + stylelintResults = await Promise.all(getStylelintResults); + } else if (allowEmptyInput) { + stylelintResults = await Promise.all([]); + } else { + stylelintResults = await Promise.reject(new NoFilesFoundError(fileList)); + } + + if (useCache) { + fileCache.reconcile(); + } + + const result = prepareReturnValue(stylelintResults, maxWarnings, formatterFunction); + + debug(`Linting complete in ${Date.now() - startTime}ms`); + + return result; }; /** diff --git a/types/stylelint/index.d.ts b/types/stylelint/index.d.ts index b910272a3a..53e7a4b5dd 100644 --- a/types/stylelint/index.d.ts +++ b/types/stylelint/index.d.ts @@ -213,7 +213,6 @@ declare module 'stylelint' { config?: StylelintConfig; configFile?: string; configBasedir?: string; - printConfig?: string; ignoreDisables?: boolean; ignorePath?: string; ignorePattern?: string[]; @@ -221,6 +220,7 @@ declare module 'stylelint' { reportNeedlessDisables?: boolean; reportInvalidScopeDisables?: boolean; maxWarnings?: number; + /** @deprecated Use `customSyntax` instead. */ syntax?: string; customSyntax?: CustomSyntax; formatter?: FormatterIdentifier; From 25dd99e18fb83c5e50564c3a0de49d17e7ebeb71 Mon Sep 17 00:00:00 2001 From: Aleks Hudochenkov Date: Thu, 16 Sep 2021 21:35:57 +0200 Subject: [PATCH 2/4] Add name to unnamed functions --- lib/assignDisabledRanges.js | 4 ++-- lib/descriptionlessDisables.js | 2 +- lib/invalidScopeDisables.js | 2 +- lib/needlessDisables.js | 2 +- lib/normalizeRuleSettings.js | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/assignDisabledRanges.js b/lib/assignDisabledRanges.js index 8927787023..9d94c89807 100644 --- a/lib/assignDisabledRanges.js +++ b/lib/assignDisabledRanges.js @@ -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: {}, diff --git a/lib/descriptionlessDisables.js b/lib/descriptionlessDisables.js index 24275c5333..8da3559676 100644 --- a/lib/descriptionlessDisables.js +++ b/lib/descriptionlessDisables.js @@ -11,7 +11,7 @@ const validateDisableSettings = require('./validateDisableSettings'); /** * @param {import('stylelint').StylelintResult[]} results */ -module.exports = function (results) { +module.exports = function descriptionlessDisables(results) { results.forEach((result) => { const settings = validateDisableSettings( result._postcssResult, diff --git a/lib/invalidScopeDisables.js b/lib/invalidScopeDisables.js index e96be0bb7a..099ccdcc94 100644 --- a/lib/invalidScopeDisables.js +++ b/lib/invalidScopeDisables.js @@ -8,7 +8,7 @@ const validateDisableSettings = require('./validateDisableSettings'); /** * @param {import('stylelint').StylelintResult[]} results */ -module.exports = function (results) { +module.exports = function invalidScopeDisables(results) { results.forEach((result) => { const settings = validateDisableSettings(result._postcssResult, 'reportInvalidScopeDisables'); diff --git a/lib/needlessDisables.js b/lib/needlessDisables.js index bd28da7647..295bc5cd69 100644 --- a/lib/needlessDisables.js +++ b/lib/needlessDisables.js @@ -12,7 +12,7 @@ const validateDisableSettings = require('./validateDisableSettings'); /** * @param {import('stylelint').StylelintResult[]} results */ -module.exports = function (results) { +module.exports = function needlessDisables(results) { results.forEach((result) => { const settings = validateDisableSettings(result._postcssResult, 'reportNeedlessDisables'); diff --git a/lib/normalizeRuleSettings.js b/lib/normalizeRuleSettings.js index 3db45fa973..cbcaaceadc 100644 --- a/lib/normalizeRuleSettings.js +++ b/lib/normalizeRuleSettings.js @@ -23,7 +23,7 @@ const { isPlainObject } = require('is-plain-object'); * @param {boolean} [primaryOptionArray] If primaryOptionArray is not provided, we try to get it from the rules themselves, which will not work for plugins * @return {[T] | [T, O] | null} */ -module.exports = function ( +module.exports = function normalizeRuleSettings( rawSettings, ruleName, // If primaryOptionArray is not provided, we try to get it from the From 3d821e269dc06c9ac278544ff6ec9b602906c130 Mon Sep 17 00:00:00 2001 From: Aleks Hudochenkov Date: Thu, 16 Sep 2021 21:36:27 +0200 Subject: [PATCH 3/4] Tiny improvements --- lib/createStylelint.js | 14 +++++++------- lib/normalizeAllRuleSettings.js | 10 +++++----- lib/printConfig.js | 16 ++++++++-------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/createStylelint.js b/lib/createStylelint.js index 6b3e91d66c..f6c9212979 100644 --- a/lib/createStylelint.js +++ b/lib/createStylelint.js @@ -29,7 +29,7 @@ module.exports = function createStylelint(options = {}) { stylelint._extendExplorer = cosmiconfig(null, { transform: augmentConfig.augmentConfigExtended.bind( null, - /** @type{StylelintInternalApi} */ (stylelint), + /** @type {StylelintInternalApi} */ (stylelint), ), stopDir: STOP_DIR, }); @@ -38,22 +38,22 @@ module.exports = function createStylelint(options = {}) { stylelint._postcssResultCache = new Map(); stylelint._createStylelintResult = createStylelintResult.bind( null, - /** @type{StylelintInternalApi} */ (stylelint), + /** @type {StylelintInternalApi} */ (stylelint), ); stylelint._getPostcssResult = getPostcssResult.bind( null, - /** @type{StylelintInternalApi} */ (stylelint), + /** @type {StylelintInternalApi} */ (stylelint), ); - stylelint._lintSource = lintSource.bind(null, /** @type{StylelintInternalApi} */ (stylelint)); + stylelint._lintSource = lintSource.bind(null, /** @type {StylelintInternalApi} */ (stylelint)); stylelint.getConfigForFile = getConfigForFile.bind( null, - /** @type{StylelintInternalApi} */ (stylelint), + /** @type {StylelintInternalApi} */ (stylelint), ); stylelint.isPathIgnored = isPathIgnored.bind( null, - /** @type{StylelintInternalApi} */ (stylelint), + /** @type {StylelintInternalApi} */ (stylelint), ); - return /** @type{StylelintInternalApi} */ (stylelint); + return /** @type {StylelintInternalApi} */ (stylelint); }; diff --git a/lib/normalizeAllRuleSettings.js b/lib/normalizeAllRuleSettings.js index ee1dbcb111..4740d7c948 100644 --- a/lib/normalizeAllRuleSettings.js +++ b/lib/normalizeAllRuleSettings.js @@ -11,22 +11,22 @@ const rules = require('./rules'); * @return {StylelintConfig} */ function normalizeAllRuleSettings(config) { + if (!config.rules) return config; + /** @type {StylelintConfigRules} */ const normalizedRules = {}; - if (!config.rules) return config; - for (const [ruleName, rawRuleSettings] of Object.entries(config.rules)) { const rule = rules[ruleName] || (config.pluginFunctions && config.pluginFunctions[ruleName]); - if (!rule) { - normalizedRules[ruleName] = []; - } else { + if (rule) { normalizedRules[ruleName] = normalizeRuleSettings( rawRuleSettings, ruleName, rule.primaryOptionArray, ); + } else { + normalizedRules[ruleName] = []; } } diff --git a/lib/printConfig.js b/lib/printConfig.js index 8e73be8e78..c19776bbc1 100644 --- a/lib/printConfig.js +++ b/lib/printConfig.js @@ -10,14 +10,14 @@ const path = require('path'); * @param {import('stylelint').StylelintStandaloneOptions} options * @returns {Promise} */ -module.exports = function printConfig(options) { - const code = options.code; - const config = options.config; - const configBasedir = options.configBasedir; - const configFile = options.configFile; - const globbyOptions = options.globbyOptions; - const files = options.files; - +module.exports = function printConfig({ + code, + config, + configBasedir, + configFile, + globbyOptions, + files, +}) { const isCodeNotFile = code !== undefined; if (!files || files.length !== 1 || isCodeNotFile) { From 0b2ab7d4d0a144b6dc0ca271e1a6e8ebcc0f9dfe Mon Sep 17 00:00:00 2001 From: Aleks Hudochenkov Date: Thu, 16 Sep 2021 22:00:36 +0200 Subject: [PATCH 4/4] Further improvement for `quite` option in API #4476 --- docs/user-guide/usage/options.md | 6 ++++++ lib/__tests__/cli.test.js | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/docs/user-guide/usage/options.md b/docs/user-guide/usage/options.md index 348bf39699..cc19c9e67b 100644 --- a/docs/user-guide/usage/options.md +++ b/docs/user-guide/usage/options.md @@ -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). diff --git a/lib/__tests__/cli.test.js b/lib/__tests__/cli.test.js index 7ba07ac993..263cc1b015 100644 --- a/lib/__tests__/cli.test.js +++ b/lib/__tests__/cli.test.js @@ -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); + }); });