You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
544 lines
14 KiB
544 lines
14 KiB
'use strict'; |
|
|
|
const chalk = require('chalk'); |
|
const checkInvalidCLIOptions = require('./utils/checkInvalidCLIOptions'); |
|
const EOL = require('os').EOL; |
|
const getFormatterOptionsText = require('./utils/getFormatterOptionsText'); |
|
const getModulePath = require('./utils/getModulePath'); |
|
const getStdin = require('get-stdin'); |
|
const meow = require('meow'); |
|
const path = require('path'); |
|
const printConfig = require('./printConfig'); |
|
const resolveFrom = require('resolve-from'); |
|
const standalone = require('./standalone'); |
|
const writeOutputFile = require('./writeOutputFile'); |
|
|
|
const EXIT_CODE_ERROR = 2; |
|
|
|
/** |
|
* @typedef {object} CLIFlags |
|
* @property {boolean} [cache] |
|
* @property {string} [cacheLocation] |
|
* @property {string | false} config |
|
* @property {string} [configBasedir] |
|
* @property {string} [customSyntax] |
|
* @property {string} [printConfig] |
|
* @property {string} [color] |
|
* @property {string} [customFormatter] |
|
* @property {boolean} [disableDefaultIgnores] |
|
* @property {boolean} [fix] |
|
* @property {string} [formatter="string"] |
|
* @property {string} [help] |
|
* @property {boolean} [ignoreDisables] |
|
* @property {string} [ignorePath] |
|
* @property {string[]} [ignorePattern] |
|
* @property {string} [noColor] |
|
* @property {string} [outputFile] |
|
* @property {boolean} [stdin] |
|
* @property {string} [stdinFilename] |
|
* @property {boolean} [reportNeedlessDisables] |
|
* @property {boolean} [reportInvalidScopeDisables] |
|
* @property {boolean} [reportDescriptionlessDisables] |
|
* @property {number} [maxWarnings] |
|
* @property {string | boolean} quiet |
|
* @property {string} [syntax] |
|
* @property {string} [version] |
|
* @property {boolean} [allowEmptyInput] |
|
*/ |
|
|
|
/** |
|
* @typedef {object} CLIOptions |
|
* @property {any} input |
|
* @property {any} help |
|
* @property {any} pkg |
|
* @property {Function} showHelp |
|
* @property {Function} showVersion |
|
* @property {CLIFlags} flags |
|
*/ |
|
|
|
/** |
|
* @typedef {object} OptionBaseType |
|
* @property {any} formatter |
|
* @property {boolean} [cache] |
|
* @property {string} [configFile] |
|
* @property {string} [cacheLocation] |
|
* @property {string} [customSyntax] |
|
* @property {string} [codeFilename] |
|
* @property {string} [configBasedir] |
|
* @property {{ quiet?: any }} configOverrides |
|
* @property {any} [printConfig] |
|
* @property {boolean} [fix] |
|
* @property {boolean} [ignoreDisables] |
|
* @property {any} [ignorePath] |
|
* @property {string} [outputFile] |
|
* @property {boolean} [reportNeedlessDisables] |
|
* @property {boolean} [reportInvalidScopeDisables] |
|
* @property {boolean} [reportDescriptionlessDisables] |
|
* @property {boolean} [disableDefaultIgnores] |
|
* @property {number} [maxWarnings] |
|
* @property {string} [syntax] |
|
* @property {string[]} [ignorePattern] |
|
* @property {boolean} [allowEmptyInput] |
|
* @property {string} [files] |
|
* @property {string} [code] |
|
*/ |
|
|
|
const meowOptions = { |
|
autoHelp: false, |
|
autoVersion: false, |
|
help: ` |
|
Usage: stylelint [input] [options] |
|
|
|
Input: Files(s), glob(s), or nothing to use stdin. |
|
|
|
If an input argument is wrapped in quotation marks, it will be passed to |
|
globby for cross-platform glob support. node_modules are always ignored. |
|
You can also pass no input and use stdin, instead. |
|
|
|
Options: |
|
|
|
--config |
|
|
|
Path to a specific configuration file (JSON, YAML, or CommonJS), or the |
|
name of a module in node_modules that points to one. If no --config |
|
argument is provided, stylelint will search for configuration files in |
|
the following places, in this order: |
|
- a stylelint property in package.json |
|
- a .stylelintrc file (with or without filename extension: |
|
.json, .yaml, .yml, and .js are available) |
|
- a stylelint.config.js file exporting a JS object |
|
The search will begin in the working directory and move up the directory |
|
tree until a configuration file is found. |
|
|
|
--config-basedir |
|
|
|
An absolute path to the directory that relative paths defining "extends" |
|
and "plugins" are *relative to*. Only necessary if these values are |
|
relative paths. |
|
|
|
--print-config |
|
|
|
Print the configuration for the given path. |
|
|
|
--ignore-path, -i |
|
|
|
Path to a file containing patterns that describe files to ignore. The |
|
path can be absolute or relative to process.cwd(). By default, stylelint |
|
looks for .stylelintignore in process.cwd(). |
|
|
|
--ignore-pattern, --ip |
|
|
|
Pattern of files to ignore (in addition to those in .stylelintignore) |
|
|
|
--syntax, -s |
|
|
|
Specify a syntax. Options: "css", "css-in-js", "html", "less", |
|
"markdown", "sass", "scss", "sugarss". If you do not specify a syntax, |
|
syntaxes will be automatically inferred by the file extensions |
|
and file content. |
|
|
|
--fix |
|
|
|
Automatically fix violations of certain rules. |
|
|
|
--custom-syntax |
|
|
|
Module name or path to a JS file exporting a PostCSS-compatible syntax. |
|
|
|
--stdin |
|
|
|
Accept stdin input even if it is empty. |
|
|
|
--stdin-filename |
|
|
|
A filename to assign stdin input. |
|
|
|
--ignore-disables, --id |
|
|
|
Ignore styleline-disable comments. |
|
|
|
--disable-default-ignores, --di |
|
|
|
Allow linting of node_modules. |
|
|
|
--cache [default: false] |
|
|
|
Store the info about processed files in order to only operate on the |
|
changed ones the next time you run stylelint. By default, the cache |
|
is stored in "./.stylelintcache". To adjust this, use --cache-location. |
|
|
|
--cache-location [default: '.stylelintcache'] |
|
|
|
Path to a file or directory to be used for the cache location. |
|
Default is "./.stylelintcache". If a directory is specified, a cache |
|
file will be created inside the specified folder, with a name derived |
|
from a hash of the current working directory. |
|
|
|
If the directory for the cache does not exist, make sure you add a trailing "/" |
|
on *nix systems or "\\" on Windows. Otherwise the path will be assumed to be a file. |
|
|
|
--formatter, -f [default: "string"] |
|
|
|
The output formatter: ${getFormatterOptionsText({ useOr: true })}. |
|
|
|
--custom-formatter |
|
|
|
Path to a JS file exporting a custom formatting function. |
|
|
|
--quiet, -q |
|
|
|
Only register violations for rules with an "error"-level severity (ignore |
|
"warning"-level). |
|
|
|
--color |
|
--no-color |
|
|
|
Force enabling/disabling of color. |
|
|
|
--report-needless-disables, --rd |
|
|
|
Also report errors for stylelint-disable comments that are not blocking a lint warning. |
|
The process will exit with code ${EXIT_CODE_ERROR} if needless disables are found. |
|
|
|
--report-invalid-scope-disables, --risd |
|
|
|
Report stylelint-disable comments that used for rules that don't exist within the configuration object. |
|
The process will exit with code ${EXIT_CODE_ERROR} if invalid scope disables are found. |
|
|
|
--report-descriptionless-disables, --rdd |
|
|
|
Report stylelint-disable comments without a description. |
|
The process will exit with code ${EXIT_CODE_ERROR} if descriptionless disables are found. |
|
|
|
--max-warnings, --mw |
|
|
|
Number of warnings above which the process will exit with code ${EXIT_CODE_ERROR}. |
|
Useful when setting "defaultSeverity" to "warning" and expecting the |
|
process to fail on warnings (e.g. CI build). |
|
|
|
--output-file, -o |
|
|
|
Path of file to write report. |
|
|
|
--version, -v |
|
|
|
Show the currently installed version of stylelint. |
|
|
|
--allow-empty-input, --aei |
|
|
|
When glob pattern matches no files, the process will exit without throwing an error. |
|
`, |
|
flags: { |
|
allowEmptyInput: { |
|
alias: 'aei', |
|
type: 'boolean', |
|
}, |
|
cache: { |
|
type: 'boolean', |
|
}, |
|
cacheLocation: { |
|
type: 'string', |
|
}, |
|
color: { |
|
type: 'boolean', |
|
}, |
|
config: { |
|
type: 'string', |
|
}, |
|
configBasedir: { |
|
type: 'string', |
|
}, |
|
customFormatter: { |
|
type: 'string', |
|
}, |
|
customSyntax: { |
|
type: 'string', |
|
}, |
|
disableDefaultIgnores: { |
|
alias: 'di', |
|
type: 'boolean', |
|
}, |
|
fix: { |
|
type: 'boolean', |
|
}, |
|
formatter: { |
|
alias: 'f', |
|
default: 'string', |
|
type: 'string', |
|
}, |
|
help: { |
|
alias: 'h', |
|
type: 'boolean', |
|
}, |
|
ignoreDisables: { |
|
alias: 'id', |
|
type: 'boolean', |
|
}, |
|
ignorePath: { |
|
alias: 'i', |
|
type: 'string', |
|
}, |
|
ignorePattern: { |
|
alias: 'ip', |
|
type: 'string', |
|
isMultiple: true, |
|
}, |
|
maxWarnings: { |
|
alias: 'mw', |
|
type: 'number', |
|
}, |
|
outputFile: { |
|
alias: 'o', |
|
type: 'string', |
|
}, |
|
printConfig: { |
|
type: 'boolean', |
|
}, |
|
quiet: { |
|
alias: 'q', |
|
type: 'boolean', |
|
}, |
|
reportDescriptionlessDisables: { |
|
alias: 'rdd', |
|
type: 'boolean', |
|
}, |
|
reportInvalidScopeDisables: { |
|
alias: 'risd', |
|
type: 'boolean', |
|
}, |
|
reportNeedlessDisables: { |
|
alias: 'rd', |
|
type: 'boolean', |
|
}, |
|
stdin: { |
|
type: 'boolean', |
|
}, |
|
stdinFilename: { |
|
type: 'string', |
|
}, |
|
syntax: { |
|
alias: 's', |
|
type: 'string', |
|
}, |
|
version: { |
|
alias: 'v', |
|
type: 'boolean', |
|
}, |
|
}, |
|
pkg: require('../package.json'), |
|
argv: /** @type {string[]} */ ([]), |
|
}; |
|
|
|
/** |
|
* @param {string[]} argv |
|
* @returns {Promise<any>} |
|
*/ |
|
module.exports = (argv) => { |
|
const cli = buildCLI(argv); |
|
|
|
const invalidOptionsMessage = checkInvalidCLIOptions(meowOptions.flags, cli.flags); |
|
|
|
if (invalidOptionsMessage) { |
|
process.stderr.write(invalidOptionsMessage); |
|
process.exit(EXIT_CODE_ERROR); // eslint-disable-line no-process-exit |
|
} |
|
|
|
let formatter = cli.flags.formatter; |
|
|
|
if (cli.flags.customFormatter) { |
|
const customFormatter = path.isAbsolute(cli.flags.customFormatter) |
|
? cli.flags.customFormatter |
|
: path.join(process.cwd(), cli.flags.customFormatter); |
|
|
|
formatter = require(customFormatter); |
|
} |
|
|
|
/** @type {OptionBaseType} */ |
|
const optionsBase = { |
|
formatter, |
|
configOverrides: {}, |
|
}; |
|
|
|
if (cli.flags.quiet) { |
|
optionsBase.configOverrides.quiet = cli.flags.quiet; |
|
} |
|
|
|
if (cli.flags.syntax) { |
|
optionsBase.syntax = cli.flags.syntax; |
|
} |
|
|
|
if (cli.flags.customSyntax) { |
|
optionsBase.customSyntax = getModulePath(process.cwd(), cli.flags.customSyntax); |
|
} |
|
|
|
if (cli.flags.config) { |
|
// Should check these possibilities: |
|
// a. name of a node_module |
|
// b. absolute path |
|
// c. relative path relative to `process.cwd()`. |
|
// If none of the above work, we'll try a relative path starting |
|
// in `process.cwd()`. |
|
optionsBase.configFile = |
|
resolveFrom.silent(process.cwd(), cli.flags.config) || |
|
path.join(process.cwd(), cli.flags.config); |
|
} |
|
|
|
if (cli.flags.configBasedir) { |
|
optionsBase.configBasedir = path.isAbsolute(cli.flags.configBasedir) |
|
? cli.flags.configBasedir |
|
: path.resolve(process.cwd(), cli.flags.configBasedir); |
|
} |
|
|
|
if (cli.flags.stdinFilename) { |
|
optionsBase.codeFilename = cli.flags.stdinFilename; |
|
} |
|
|
|
if (cli.flags.ignorePath) { |
|
optionsBase.ignorePath = cli.flags.ignorePath; |
|
} |
|
|
|
if (cli.flags.ignorePattern) { |
|
optionsBase.ignorePattern = cli.flags.ignorePattern; |
|
} |
|
|
|
if (cli.flags.ignoreDisables) { |
|
optionsBase.ignoreDisables = cli.flags.ignoreDisables; |
|
} |
|
|
|
if (cli.flags.disableDefaultIgnores) { |
|
optionsBase.disableDefaultIgnores = cli.flags.disableDefaultIgnores; |
|
} |
|
|
|
if (cli.flags.cache) { |
|
optionsBase.cache = true; |
|
} |
|
|
|
if (cli.flags.cacheLocation) { |
|
optionsBase.cacheLocation = cli.flags.cacheLocation; |
|
} |
|
|
|
if (cli.flags.fix) { |
|
optionsBase.fix = cli.flags.fix; |
|
} |
|
|
|
if (cli.flags.outputFile) { |
|
optionsBase.outputFile = cli.flags.outputFile; |
|
} |
|
|
|
const reportNeedlessDisables = cli.flags.reportNeedlessDisables; |
|
const reportInvalidScopeDisables = cli.flags.reportInvalidScopeDisables; |
|
const reportDescriptionlessDisables = cli.flags.reportDescriptionlessDisables; |
|
|
|
if (reportNeedlessDisables) { |
|
optionsBase.reportNeedlessDisables = reportNeedlessDisables; |
|
} |
|
|
|
if (reportInvalidScopeDisables) { |
|
optionsBase.reportInvalidScopeDisables = reportInvalidScopeDisables; |
|
} |
|
|
|
if (reportDescriptionlessDisables) { |
|
optionsBase.reportDescriptionlessDisables = reportDescriptionlessDisables; |
|
} |
|
|
|
const maxWarnings = cli.flags.maxWarnings; |
|
|
|
if (maxWarnings !== undefined) { |
|
optionsBase.maxWarnings = maxWarnings; |
|
} |
|
|
|
if (cli.flags.help) { |
|
cli.showHelp(0); |
|
|
|
return Promise.resolve(); |
|
} |
|
|
|
if (cli.flags.version) { |
|
cli.showVersion(); |
|
|
|
return Promise.resolve(); |
|
} |
|
|
|
if (cli.flags.allowEmptyInput) { |
|
optionsBase.allowEmptyInput = cli.flags.allowEmptyInput; |
|
} |
|
|
|
return Promise.resolve() |
|
.then( |
|
/** |
|
* @returns {Promise<OptionBaseType>} |
|
*/ |
|
() => { |
|
// 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); |
|
} |
|
|
|
if (!options.files && !options.code && !cli.flags.stdin) { |
|
cli.showHelp(); |
|
|
|
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); |
|
}); |
|
}; |
|
|
|
/** |
|
* @param {{ stack: any, code: any }} err |
|
* @returns {void} |
|
*/ |
|
function handleError(err) { |
|
process.stderr.write(err.stack + EOL); |
|
const exitCode = typeof err.code === 'number' ? err.code : 1; |
|
|
|
process.exitCode = exitCode; |
|
} |
|
|
|
/** |
|
* @param {string[]} argv |
|
* @returns {CLIOptions} |
|
*/ |
|
function buildCLI(argv) { |
|
// @ts-ignore TODO TYPES |
|
return meow({ ...meowOptions, argv }); |
|
} |
|
|
|
module.exports.buildCLI = buildCLI;
|
|
|