1 | 'use strict';
|
2 |
|
3 | const chalk = require('chalk');
|
4 | const checkInvalidCLIOptions = require('./utils/checkInvalidCLIOptions');
|
5 | const disableOptionsReportStringFormatter = require('./formatters/disableOptionsReportStringFormatter');
|
6 | const EOL = require('os').EOL;
|
7 | const getFormatterOptionsText = require('./utils/getFormatterOptionsText');
|
8 | const getModulePath = require('./utils/getModulePath');
|
9 | const getStdin = require('get-stdin');
|
10 | const meow = require('meow');
|
11 | const path = require('path');
|
12 | const printConfig = require('./printConfig');
|
13 | const resolveFrom = require('resolve-from');
|
14 | const standalone = require('./standalone');
|
15 | const writeOutputFile = require('./writeOutputFile');
|
16 |
|
17 | const EXIT_CODE_ERROR = 2;
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 | const meowOptions = {
|
88 | autoHelp: false,
|
89 | autoVersion: false,
|
90 | help: `
|
91 | Usage: stylelint [input] [options]
|
92 |
|
93 | Input: Files(s), glob(s), or nothing to use stdin.
|
94 |
|
95 | If an input argument is wrapped in quotation marks, it will be passed to
|
96 | globby for cross-platform glob support. node_modules are always ignored.
|
97 | You can also pass no input and use stdin, instead.
|
98 |
|
99 | Options:
|
100 |
|
101 | --config
|
102 |
|
103 | Path to a specific configuration file (JSON, YAML, or CommonJS), or the
|
104 | name of a module in node_modules that points to one. If no --config
|
105 | argument is provided, stylelint will search for configuration files in
|
106 | the following places, in this order:
|
107 | - a stylelint property in package.json
|
108 | - a .stylelintrc file (with or without filename extension:
|
109 | .json, .yaml, .yml, and .js are available)
|
110 | - a stylelint.config.js file exporting a JS object
|
111 | The search will begin in the working directory and move up the directory
|
112 | tree until a configuration file is found.
|
113 |
|
114 | --config-basedir
|
115 |
|
116 | An absolute path to the directory that relative paths defining "extends"
|
117 | and "plugins" are *relative to*. Only necessary if these values are
|
118 | relative paths.
|
119 |
|
120 | --print-config
|
121 |
|
122 | Print the configuration for the given path.
|
123 |
|
124 | --ignore-path, -i
|
125 |
|
126 | Path to a file containing patterns that describe files to ignore. The
|
127 | path can be absolute or relative to process.cwd(). By default, stylelint
|
128 | looks for .stylelintignore in process.cwd().
|
129 |
|
130 | --ignore-pattern, --ip
|
131 |
|
132 | Pattern of files to ignore (in addition to those in .stylelintignore)
|
133 |
|
134 | --syntax, -s
|
135 |
|
136 | Specify a syntax. Options: "css", "css-in-js", "html", "less",
|
137 | "markdown", "sass", "scss", "sugarss". If you do not specify a syntax,
|
138 | syntaxes will be automatically inferred by the file extensions
|
139 | and file content.
|
140 |
|
141 | --fix
|
142 |
|
143 | Automatically fix violations of certain rules.
|
144 |
|
145 | --custom-syntax
|
146 |
|
147 | Module name or path to a JS file exporting a PostCSS-compatible syntax.
|
148 |
|
149 | --stdin
|
150 |
|
151 | Accept stdin input even if it is empty.
|
152 |
|
153 | --stdin-filename
|
154 |
|
155 | A filename to assign stdin input.
|
156 |
|
157 | --ignore-disables, --id
|
158 |
|
159 | Ignore styleline-disable comments.
|
160 |
|
161 | --disable-default-ignores, --di
|
162 |
|
163 | Allow linting of node_modules.
|
164 |
|
165 | --cache [default: false]
|
166 |
|
167 | Store the info about processed files in order to only operate on the
|
168 | changed ones the next time you run stylelint. By default, the cache
|
169 | is stored in "./.stylelintcache". To adjust this, use --cache-location.
|
170 |
|
171 | --cache-location [default: '.stylelintcache']
|
172 |
|
173 | Path to a file or directory to be used for the cache location.
|
174 | Default is "./.stylelintcache". If a directory is specified, a cache
|
175 | file will be created inside the specified folder, with a name derived
|
176 | from a hash of the current working directory.
|
177 |
|
178 | If the directory for the cache does not exist, make sure you add a trailing "/"
|
179 | on *nix systems or "\\" on Windows. Otherwise the path will be assumed to be a file.
|
180 |
|
181 | --formatter, -f [default: "string"]
|
182 |
|
183 | The output formatter: ${getFormatterOptionsText({ useOr: true })}.
|
184 |
|
185 | --custom-formatter
|
186 |
|
187 | Path to a JS file exporting a custom formatting function.
|
188 |
|
189 | --quiet, -q
|
190 |
|
191 | Only register violations for rules with an "error"-level severity (ignore
|
192 | "warning"-level).
|
193 |
|
194 | --color
|
195 | --no-color
|
196 |
|
197 | Force enabling/disabling of color.
|
198 |
|
199 | --report-needless-disables, --rd
|
200 |
|
201 | Also report errors for stylelint-disable comments that are not blocking a lint warning.
|
202 | The process will exit with code ${EXIT_CODE_ERROR} if needless disables are found.
|
203 |
|
204 | --report-invalid-scope-disables, --risd
|
205 |
|
206 | Report stylelint-disable comments that used for rules that don't exist within the configuration object.
|
207 | The process will exit with code ${EXIT_CODE_ERROR} if invalid scope disables are found.
|
208 |
|
209 | --report-descriptionless-disables, --rdd
|
210 |
|
211 | Report stylelint-disable comments without a description.
|
212 | The process will exit with code ${EXIT_CODE_ERROR} if descriptionless disables are found.
|
213 |
|
214 | --max-warnings, --mw
|
215 |
|
216 | Number of warnings above which the process will exit with code ${EXIT_CODE_ERROR}.
|
217 | Useful when setting "defaultSeverity" to "warning" and expecting the
|
218 | process to fail on warnings (e.g. CI build).
|
219 |
|
220 | --output-file, -o
|
221 |
|
222 | Path of file to write report.
|
223 |
|
224 | --version, -v
|
225 |
|
226 | Show the currently installed version of stylelint.
|
227 |
|
228 | --allow-empty-input, --aei
|
229 |
|
230 | When glob pattern matches no files, the process will exit without throwing an error.
|
231 | `,
|
232 | flags: {
|
233 | allowEmptyInput: {
|
234 | alias: 'aei',
|
235 | type: 'boolean',
|
236 | },
|
237 | cache: {
|
238 | type: 'boolean',
|
239 | },
|
240 | cacheLocation: {
|
241 | type: 'string',
|
242 | },
|
243 | color: {
|
244 | type: 'boolean',
|
245 | },
|
246 | config: {
|
247 | type: 'string',
|
248 | },
|
249 | configBasedir: {
|
250 | type: 'string',
|
251 | },
|
252 | customFormatter: {
|
253 | type: 'string',
|
254 | },
|
255 | customSyntax: {
|
256 | type: 'string',
|
257 | },
|
258 | disableDefaultIgnores: {
|
259 | alias: 'di',
|
260 | type: 'boolean',
|
261 | },
|
262 | fix: {
|
263 | type: 'boolean',
|
264 | },
|
265 | formatter: {
|
266 | alias: 'f',
|
267 | default: 'string',
|
268 | type: 'string',
|
269 | },
|
270 | help: {
|
271 | alias: 'h',
|
272 | type: 'boolean',
|
273 | },
|
274 | ignoreDisables: {
|
275 | alias: 'id',
|
276 | type: 'boolean',
|
277 | },
|
278 | ignorePath: {
|
279 | alias: 'i',
|
280 | type: 'string',
|
281 | },
|
282 | ignorePattern: {
|
283 | alias: 'ip',
|
284 | type: 'string',
|
285 | isMultiple: true,
|
286 | },
|
287 | maxWarnings: {
|
288 | alias: 'mw',
|
289 | type: 'number',
|
290 | },
|
291 | outputFile: {
|
292 | alias: 'o',
|
293 | type: 'string',
|
294 | },
|
295 | printConfig: {
|
296 | type: 'boolean',
|
297 | },
|
298 | quiet: {
|
299 | alias: 'q',
|
300 | type: 'boolean',
|
301 | },
|
302 | reportDescriptionlessDisables: {
|
303 | alias: 'rdd',
|
304 | type: 'boolean',
|
305 | },
|
306 | reportInvalidScopeDisables: {
|
307 | alias: 'risd',
|
308 | type: 'boolean',
|
309 | },
|
310 | reportNeedlessDisables: {
|
311 | alias: 'rd',
|
312 | type: 'boolean',
|
313 | },
|
314 | stdin: {
|
315 | type: 'boolean',
|
316 | },
|
317 | stdinFilename: {
|
318 | type: 'string',
|
319 | },
|
320 | syntax: {
|
321 | alias: 's',
|
322 | type: 'string',
|
323 | },
|
324 | version: {
|
325 | alias: 'v',
|
326 | type: 'boolean',
|
327 | },
|
328 | },
|
329 | pkg: require('../package.json'),
|
330 | argv: ([]),
|
331 | };
|
332 |
|
333 |
|
334 |
|
335 |
|
336 |
|
337 | module.exports = (argv) => {
|
338 | const cli = buildCLI(argv);
|
339 |
|
340 | const invalidOptionsMessage = checkInvalidCLIOptions(meowOptions.flags, cli.flags);
|
341 |
|
342 | if (invalidOptionsMessage) {
|
343 | process.stderr.write(invalidOptionsMessage);
|
344 | process.exit(EXIT_CODE_ERROR);
|
345 | }
|
346 |
|
347 | let formatter = cli.flags.formatter;
|
348 |
|
349 | if (cli.flags.customFormatter) {
|
350 | const customFormatter = path.isAbsolute(cli.flags.customFormatter)
|
351 | ? cli.flags.customFormatter
|
352 | : path.join(process.cwd(), cli.flags.customFormatter);
|
353 |
|
354 | formatter = require(customFormatter);
|
355 | }
|
356 |
|
357 |
|
358 | const optionsBase = {
|
359 | formatter,
|
360 | configOverrides: {},
|
361 | };
|
362 |
|
363 | if (cli.flags.quiet) {
|
364 | optionsBase.configOverrides.quiet = cli.flags.quiet;
|
365 | }
|
366 |
|
367 | if (cli.flags.syntax) {
|
368 | optionsBase.syntax = cli.flags.syntax;
|
369 | }
|
370 |
|
371 | if (cli.flags.customSyntax) {
|
372 | optionsBase.customSyntax = getModulePath(process.cwd(), cli.flags.customSyntax);
|
373 | }
|
374 |
|
375 | if (cli.flags.config) {
|
376 |
|
377 |
|
378 |
|
379 |
|
380 |
|
381 |
|
382 | optionsBase.configFile =
|
383 | resolveFrom.silent(process.cwd(), cli.flags.config) ||
|
384 | path.join(process.cwd(), cli.flags.config);
|
385 | }
|
386 |
|
387 | if (cli.flags.configBasedir) {
|
388 | optionsBase.configBasedir = path.isAbsolute(cli.flags.configBasedir)
|
389 | ? cli.flags.configBasedir
|
390 | : path.resolve(process.cwd(), cli.flags.configBasedir);
|
391 | }
|
392 |
|
393 | if (cli.flags.stdinFilename) {
|
394 | optionsBase.codeFilename = cli.flags.stdinFilename;
|
395 | }
|
396 |
|
397 | if (cli.flags.ignorePath) {
|
398 | optionsBase.ignorePath = cli.flags.ignorePath;
|
399 | }
|
400 |
|
401 | if (cli.flags.ignorePattern) {
|
402 | optionsBase.ignorePattern = cli.flags.ignorePattern;
|
403 | }
|
404 |
|
405 | if (cli.flags.ignoreDisables) {
|
406 | optionsBase.ignoreDisables = cli.flags.ignoreDisables;
|
407 | }
|
408 |
|
409 | if (cli.flags.disableDefaultIgnores) {
|
410 | optionsBase.disableDefaultIgnores = cli.flags.disableDefaultIgnores;
|
411 | }
|
412 |
|
413 | if (cli.flags.cache) {
|
414 | optionsBase.cache = true;
|
415 | }
|
416 |
|
417 | if (cli.flags.cacheLocation) {
|
418 | optionsBase.cacheLocation = cli.flags.cacheLocation;
|
419 | }
|
420 |
|
421 | if (cli.flags.fix) {
|
422 | optionsBase.fix = cli.flags.fix;
|
423 | }
|
424 |
|
425 | if (cli.flags.outputFile) {
|
426 | optionsBase.outputFile = cli.flags.outputFile;
|
427 | }
|
428 |
|
429 | const reportNeedlessDisables = cli.flags.reportNeedlessDisables;
|
430 | const reportInvalidScopeDisables = cli.flags.reportInvalidScopeDisables;
|
431 | const reportDescriptionlessDisables = cli.flags.reportDescriptionlessDisables;
|
432 |
|
433 | if (reportNeedlessDisables) {
|
434 | optionsBase.reportNeedlessDisables = reportNeedlessDisables;
|
435 | }
|
436 |
|
437 | if (reportInvalidScopeDisables) {
|
438 | optionsBase.reportInvalidScopeDisables = reportInvalidScopeDisables;
|
439 | }
|
440 |
|
441 | if (reportDescriptionlessDisables) {
|
442 | optionsBase.reportDescriptionlessDisables = reportDescriptionlessDisables;
|
443 | }
|
444 |
|
445 | const maxWarnings = cli.flags.maxWarnings;
|
446 |
|
447 | if (maxWarnings !== undefined) {
|
448 | optionsBase.maxWarnings = maxWarnings;
|
449 | }
|
450 |
|
451 | if (cli.flags.help) {
|
452 | cli.showHelp(0);
|
453 |
|
454 | return Promise.resolve();
|
455 | }
|
456 |
|
457 | if (cli.flags.version) {
|
458 | cli.showVersion();
|
459 |
|
460 | return Promise.resolve();
|
461 | }
|
462 |
|
463 | if (cli.flags.allowEmptyInput) {
|
464 | optionsBase.allowEmptyInput = cli.flags.allowEmptyInput;
|
465 | }
|
466 |
|
467 | return Promise.resolve()
|
468 | .then(
|
469 | |
470 |
|
471 |
|
472 | () => {
|
473 |
|
474 | if (cli.input.length) {
|
475 | return Promise.resolve({ ...optionsBase, files: (cli.input) });
|
476 | }
|
477 |
|
478 | return getStdin().then((stdin) => ({ ...optionsBase, code: stdin }));
|
479 | },
|
480 | )
|
481 | .then((options) => {
|
482 | if (cli.flags.printConfig) {
|
483 | return printConfig(options)
|
484 | .then((config) => {
|
485 | process.stdout.write(JSON.stringify(config, null, ' '));
|
486 | })
|
487 | .catch(handleError);
|
488 | }
|
489 |
|
490 | if (!options.files && !options.code && !cli.flags.stdin) {
|
491 | cli.showHelp();
|
492 |
|
493 | return;
|
494 | }
|
495 |
|
496 | return standalone(options)
|
497 | .then((linted) => {
|
498 | const reports = [];
|
499 |
|
500 | const report = disableOptionsReportStringFormatter(
|
501 | linted.reportedDisables || [],
|
502 | 'forbidden disable',
|
503 | );
|
504 |
|
505 | if (report) reports.push(report);
|
506 |
|
507 | if (reportNeedlessDisables) {
|
508 |
|
509 |
|
510 | const report = disableOptionsReportStringFormatter(
|
511 | linted.needlessDisables || [],
|
512 | 'needless disable',
|
513 | );
|
514 |
|
515 | if (report) {
|
516 | reports.push(report);
|
517 | }
|
518 | }
|
519 |
|
520 | if (reportInvalidScopeDisables) {
|
521 |
|
522 |
|
523 | const report = disableOptionsReportStringFormatter(
|
524 | linted.invalidScopeDisables || [],
|
525 | 'disable with invalid scope',
|
526 | );
|
527 |
|
528 | if (report) {
|
529 | reports.push(report);
|
530 | }
|
531 | }
|
532 |
|
533 | if (reportDescriptionlessDisables) {
|
534 |
|
535 |
|
536 | const report = disableOptionsReportStringFormatter(
|
537 | linted.descriptionlessDisables || [],
|
538 | 'descriptionless disable',
|
539 | );
|
540 |
|
541 | if (report) {
|
542 | reports.push(report);
|
543 | }
|
544 | }
|
545 |
|
546 | if (reports.length > 0) {
|
547 |
|
548 |
|
549 | reports.forEach((report) => process.stdout.write(report));
|
550 | process.exitCode = EXIT_CODE_ERROR;
|
551 | }
|
552 |
|
553 | if (!linted.output) {
|
554 | return;
|
555 | }
|
556 |
|
557 | process.stdout.write(linted.output);
|
558 |
|
559 | if (options.outputFile) {
|
560 | writeOutputFile(linted.output, options.outputFile).catch(handleError);
|
561 | }
|
562 |
|
563 | if (linted.errored) {
|
564 | process.exitCode = EXIT_CODE_ERROR;
|
565 | } else if (maxWarnings !== undefined && linted.maxWarningsExceeded) {
|
566 | const foundWarnings = linted.maxWarningsExceeded.foundWarnings;
|
567 |
|
568 | process.stdout.write(
|
569 | `${chalk.red(`Max warnings exceeded: `)}${foundWarnings} found. ${chalk.dim(
|
570 | `${maxWarnings} allowed${EOL}${EOL}`,
|
571 | )}`,
|
572 | );
|
573 | process.exitCode = EXIT_CODE_ERROR;
|
574 | }
|
575 | })
|
576 | .catch(handleError);
|
577 | });
|
578 | };
|
579 |
|
580 |
|
581 |
|
582 |
|
583 |
|
584 | function handleError(err) {
|
585 | process.stderr.write(err.stack + EOL);
|
586 | const exitCode = typeof err.code === 'number' ? err.code : 1;
|
587 |
|
588 | process.exitCode = exitCode;
|
589 | }
|
590 |
|
591 |
|
592 |
|
593 |
|
594 |
|
595 | function buildCLI(argv) {
|
596 |
|
597 | return meow({ ...meowOptions, argv });
|
598 | }
|
599 |
|
600 | module.exports.buildCLI = buildCLI;
|