/* @flow */ "use strict"; const dynamicRequire = require("./dynamicRequire"); const getModulePath = require("./utils/getModulePath"); const getStdin = require("get-stdin"); const meow = require("meow"); const needlessDisablesStringFormatter = require("./formatters/needlessDisablesStringFormatter"); const path = require("path"); const resolveFrom = require("resolve-from"); const standalone = require("./standalone"); /*:: type meowOptionsType = { autoHelp: boolean, autoVersion: boolean, help: string, flags: { "allow-empty-input": { alias: string, type: string }, cache: { type: string }, "cache-location": { type: string }, config: { default: boolean, type: string }, "config-basedir": { type: string }, color: { type: string }, "custom-formatter": { type: string }, "custom-syntax": { type: string }, "disable-default-ignores": { alias: string, type: string }, fix: { type: string }, formatter: { alias: string, default: "string", type: string }, help: { alias: string, type: string }, "ignore-disables": { alias: string, type: string }, "ignore-path": { alias: string }, "ignore-pattern": { alias: string }, "no-color": { type: string }, "report-needless-disables": { alias: string }, "stdin-filename": { type: string }, quiet: { alias: string, type: string, default: boolean }, syntax: { alias: string }, version: { alias: string, type: string } }, pkg: string, } */ /*:: type cliType = { flags: { allowEmptyInput: any, cache: any, cacheLocation: any, config: any, configBasedir: any, customFormatter: any, customSyntax: any, fix: any, formatter: any, ignoreDisables: any, ignorePath: string, quiet: any, reportNeedlessDisables: any, stdinFilename: any, syntax: any, }, input: any, help: any, pkg: any, showHelp: Function, showVersion: Function }*/ /*:: type optionBaseType = { allowEmptyInput?: any, formater?: any, cache?: boolean, cacheLocation?: any, codeFilename?: any, configBasedir?: any, configFile?: any, configOverrides: { quiet?: any, }, customSyntax?: any, fix?: any, ignoreDisables?: any, ignorePath?: any, reportNeedlessDisables?: any, syntax?: any, disableDefaultIgnores?: any, ignorePattern?: any }*/ const meowOptions /*: meowOptionsType*/ = { 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 node-glob for cross-platform glob support. node_modules and bower_components 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. --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 non-standard syntax. Options: "scss", "less", "sugarss". If you do not specify a syntax, non-standard syntaxes will be automatically inferred by the file extensions .scss, .less, and .sss. --fix Automatically fix violations of certain rules. --custom-syntax Module name or path to a JS file exporting a PostCSS-compatible syntax. --stdin-filename A filename to assign stdin input. --ignore-disables, --id Ignore styleline-disable comments. --disable-default-ignores, --di Allow linting of node_modules and bower_components. --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: "json", "string" or "verbose". --custom-formatter Path to a JS file exporting a custom formatting function. --quiet, -q Only register warnings for rules with an "error"-level severity (ignore "warning"-level). --color --no-color Force enabling/disabling of color. --allow-empty-input, --aei If no files match glob pattern, exits without throwing an error. --report-needless-disables, --rd Report stylelint-disable comments that are not blocking a lint warning. If you provide the argument "error", the process will exit with code 2 if needless disables are found. --version, -v Show the currently installed version of stylelint. `, flags: { "allow-empty-input": { alias: "aei", type: "boolean" }, cache: { type: "boolean" }, "cache-location": { type: "string" }, config: { default: false, type: "string" }, "config-basedir": { type: "string" }, color: { type: "boolean" }, "custom-formatter": { type: "string" }, "custom-syntax": { type: "string" }, "disable-default-ignores": { alias: "di", type: "boolean" }, fix: { type: "boolean" }, formatter: { alias: "f", default: "string", type: "string" }, help: { alias: "h", type: "boolean" }, "ignore-disables": { alias: "id", type: "boolean" }, "ignore-path": { alias: "i" }, "ignore-pattern": { alias: "ip" }, "no-color": { type: "boolean" }, "report-needless-disables": { alias: "rd" }, "stdin-filename": { type: "string" }, quiet: { alias: "q", type: "boolean", default: false }, syntax: { alias: "s" }, version: { alias: "v", type: "boolean" } }, pkg: require("../package.json") }; const cli /*: cliType*/ = meow(meowOptions); 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 = dynamicRequire(customFormatter); } const optionsBase /*: optionBaseType*/ = { 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; } const reportNeedlessDisables = cli.flags.reportNeedlessDisables; if (reportNeedlessDisables) { optionsBase.reportNeedlessDisables = reportNeedlessDisables; } if (cli.flags.help || cli.flags.h) { cli.showHelp(); } if (cli.flags.version || cli.flags.v) { cli.showVersion(); } Promise.resolve() .then(() => { // Add input/code into options if (cli.input.length) { return Object.assign({}, optionsBase, { files: cli.input }); } return getStdin().then(stdin => Object.assign({}, optionsBase, { code: stdin }) ); }) .then(options => { if (!options.files && !options.code) { cli.showHelp(); } return standalone(options); }) .then(linted => { if (reportNeedlessDisables) { const hasReportNeedlessDisable = !!linted.needlessDisables && linted.needlessDisables.some(sourceReport => { if (!sourceReport.ranges || sourceReport.ranges.length === 0) { return false; } return true; }); if (hasReportNeedlessDisable) { process.stdout.write( needlessDisablesStringFormatter(linted.needlessDisables) ); process.exitCode = 2; } return; } if (!linted.output) { return; } process.stdout.write(linted.output); if (linted.errored) { process.exitCode = 2; } return; }) .catch((err /*: { stack: any, code: any }*/) => { console.log(err.stack); // eslint-disable-line no-console const exitCode = typeof err.code === "number" ? err.code : 1; process.exit(exitCode); // eslint-disable-line no-process-exit });