123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544 |
- '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;
|