]> git.proxmox.com Git - pve-eslint.git/blob - eslint/tests/lib/cli.js
import eslint 7.28.0
[pve-eslint.git] / eslint / tests / lib / cli.js
1 /**
2 * @fileoverview Tests for cli.
3 * @author Ian Christian Myers
4 */
5
6 "use strict";
7
8 /*
9 * NOTE: If you are adding new tests for cli.js, use verifyESLintOpts(). The
10 * test only needs to verify that ESLint receives the correct opts.
11 */
12
13 //------------------------------------------------------------------------------
14 // Requirements
15 //------------------------------------------------------------------------------
16
17 const assert = require("chai").assert,
18 stdAssert = require("assert"),
19 { ESLint } = require("../../lib/eslint"),
20 BuiltinRules = require("../../lib/rules"),
21 path = require("path"),
22 sinon = require("sinon"),
23 fs = require("fs"),
24 os = require("os"),
25 sh = require("shelljs");
26
27 const proxyquire = require("proxyquire").noCallThru().noPreserveCache();
28
29 //------------------------------------------------------------------------------
30 // Tests
31 //------------------------------------------------------------------------------
32
33 describe("cli", () => {
34 let fixtureDir;
35 const log = {
36 info: sinon.spy(),
37 error: sinon.spy()
38 };
39 const RuntimeInfo = {
40 environment: sinon.stub(),
41 version: sinon.stub()
42 };
43 const cli = proxyquire("../../lib/cli", {
44 "./shared/logging": log,
45 "./shared/runtime-info": RuntimeInfo
46 });
47
48 /**
49 * Verify that ESLint class receives correct opts via await cli.execute().
50 * @param {string} cmd CLI command.
51 * @param {Object} opts Options hash that should match that received by ESLint class.
52 * @returns {void}
53 */
54 async function verifyESLintOpts(cmd, opts) {
55
56 // create a fake ESLint class to test with
57 const fakeESLint = sinon.mock().withExactArgs(sinon.match(opts));
58
59 Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype));
60 sinon.stub(fakeESLint.prototype, "lintFiles").returns([]);
61 sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: sinon.spy() });
62
63 const localCLI = proxyquire("../../lib/cli", {
64 "./eslint": { ESLint: fakeESLint },
65 "./shared/logging": log
66 });
67
68 await localCLI.execute(cmd);
69 sinon.verifyAndRestore();
70 }
71
72 // verifyESLintOpts
73
74 /**
75 * Returns the path inside of the fixture directory.
76 * @param {...string} args file path segments.
77 * @returns {string} The path inside the fixture directory.
78 * @private
79 */
80 function getFixturePath(...args) {
81 return path.join(fixtureDir, ...args);
82 }
83
84 // copy into clean area so as not to get "infected" by this project's .eslintrc files
85 before(function() {
86
87 /*
88 * GitHub Actions Windows and macOS runners occasionally exhibit
89 * extremely slow filesystem operations, during which copying fixtures
90 * exceeds the default test timeout, so raise it just for this hook.
91 * Mocha uses `this` to set timeouts on an individual hook level.
92 */
93 this.timeout(60 * 1000); // eslint-disable-line no-invalid-this
94 fixtureDir = `${os.tmpdir()}/eslint/fixtures`;
95 sh.mkdir("-p", fixtureDir);
96 sh.cp("-r", "./tests/fixtures/.", fixtureDir);
97 });
98
99 afterEach(() => {
100 log.info.resetHistory();
101 log.error.resetHistory();
102 });
103
104 after(() => {
105 sh.rm("-r", fixtureDir);
106 });
107
108 describe("execute()", () => {
109 it("should return error when text with incorrect quotes is passed as argument", async () => {
110 const configFile = getFixturePath("configurations", "quotes-error.json");
111 const result = await cli.execute(`-c ${configFile}`, "var foo = 'bar';");
112
113 assert.strictEqual(result, 1);
114 });
115
116 it("should not print debug info when passed the empty string as text", async () => {
117 const result = await cli.execute(["--stdin", "--no-eslintrc"], "");
118
119 assert.strictEqual(result, 0);
120 assert.isTrue(log.info.notCalled);
121 });
122
123 it("should return no error when --ext .js2 is specified", async () => {
124 const filePath = getFixturePath("files");
125 const result = await cli.execute(`--ext .js2 ${filePath}`);
126
127 assert.strictEqual(result, 0);
128 });
129
130 it("should exit with console error when passed unsupported arguments", async () => {
131 const filePath = getFixturePath("files");
132 const result = await cli.execute(`--blah --another ${filePath}`);
133
134 assert.strictEqual(result, 2);
135 });
136
137 });
138
139 describe("when given a config file", () => {
140 it("should load the specified config file", async () => {
141 const configPath = getFixturePath(".eslintrc");
142 const filePath = getFixturePath("passing.js");
143
144 await cli.execute(`--config ${configPath} ${filePath}`);
145 });
146 });
147
148 describe("when there is a local config file", () => {
149 const code = "lib/cli.js";
150
151 it("should load the local config file", async () => {
152
153 // Mock CWD
154 process.eslintCwd = getFixturePath("configurations", "single-quotes");
155
156 await cli.execute(code);
157
158 process.eslintCwd = null;
159 });
160 });
161
162 describe("when given a config with rules with options and severity level set to error", () => {
163 it("should exit with an error status (1)", async () => {
164 const configPath = getFixturePath("configurations", "quotes-error.json");
165 const filePath = getFixturePath("single-quoted.js");
166 const code = `--no-ignore --config ${configPath} ${filePath}`;
167
168 const exitStatus = await cli.execute(code);
169
170 assert.strictEqual(exitStatus, 1);
171 });
172 });
173
174 describe("when given a config file and a directory of files", () => {
175 it("should load and execute without error", async () => {
176 const configPath = getFixturePath("configurations", "semi-error.json");
177 const filePath = getFixturePath("formatters");
178 const code = `--config ${configPath} ${filePath}`;
179
180 const exitStatus = await cli.execute(code);
181
182 assert.strictEqual(exitStatus, 0);
183 });
184 });
185
186 describe("when given a config with environment set to browser", () => {
187 it("should execute without any errors", async () => {
188 const configPath = getFixturePath("configurations", "env-browser.json");
189 const filePath = getFixturePath("globals-browser.js");
190 const code = `--config ${configPath} ${filePath}`;
191
192 const exit = await cli.execute(code);
193
194 assert.strictEqual(exit, 0);
195 });
196 });
197
198 describe("when given a config with environment set to Node.js", () => {
199 it("should execute without any errors", async () => {
200 const configPath = getFixturePath("configurations", "env-node.json");
201 const filePath = getFixturePath("globals-node.js");
202 const code = `--config ${configPath} ${filePath}`;
203
204 const exit = await cli.execute(code);
205
206 assert.strictEqual(exit, 0);
207 });
208 });
209
210 describe("when given a config with environment set to Nashorn", () => {
211 it("should execute without any errors", async () => {
212 const configPath = getFixturePath("configurations", "env-nashorn.json");
213 const filePath = getFixturePath("globals-nashorn.js");
214 const code = `--config ${configPath} ${filePath}`;
215
216 const exit = await cli.execute(code);
217
218 assert.strictEqual(exit, 0);
219 });
220 });
221
222 describe("when given a config with environment set to WebExtensions", () => {
223 it("should execute without any errors", async () => {
224 const configPath = getFixturePath("configurations", "env-webextensions.json");
225 const filePath = getFixturePath("globals-webextensions.js");
226 const code = `--config ${configPath} ${filePath}`;
227
228 const exit = await cli.execute(code);
229
230 assert.strictEqual(exit, 0);
231 });
232 });
233
234 describe("when given a valid built-in formatter name", () => {
235 it("should execute without any errors", async () => {
236 const filePath = getFixturePath("passing.js");
237 const exit = await cli.execute(`-f checkstyle ${filePath}`);
238
239 assert.strictEqual(exit, 0);
240 });
241 });
242
243 describe("when given a valid built-in formatter name that uses rules meta.", () => {
244 it("should execute without any errors", async () => {
245 const filePath = getFixturePath("passing.js");
246 const exit = await cli.execute(`-f json-with-metadata ${filePath} --no-eslintrc`);
247
248 assert.strictEqual(exit, 0);
249
250 // Check metadata.
251 const { metadata } = JSON.parse(log.info.args[0][0]);
252 const expectedMetadata = Array.from(BuiltinRules).reduce((obj, [ruleId, rule]) => {
253 obj.rulesMeta[ruleId] = rule.meta;
254 return obj;
255 }, { rulesMeta: {} });
256
257 assert.deepStrictEqual(metadata, expectedMetadata);
258 });
259 });
260
261 describe("when given an invalid built-in formatter name", () => {
262 it("should execute with error", async () => {
263 const filePath = getFixturePath("passing.js");
264 const exit = await cli.execute(`-f fakeformatter ${filePath}`);
265
266 assert.strictEqual(exit, 2);
267 });
268 });
269
270 describe("when given a valid formatter path", () => {
271 it("should execute without any errors", async () => {
272 const formatterPath = getFixturePath("formatters", "simple.js");
273 const filePath = getFixturePath("passing.js");
274 const exit = await cli.execute(`-f ${formatterPath} ${filePath}`);
275
276 assert.strictEqual(exit, 0);
277 });
278 });
279
280 describe("when given an invalid formatter path", () => {
281 it("should execute with error", async () => {
282 const formatterPath = getFixturePath("formatters", "file-does-not-exist.js");
283 const filePath = getFixturePath("passing.js");
284 const exit = await cli.execute(`-f ${formatterPath} ${filePath}`);
285
286 assert.strictEqual(exit, 2);
287 });
288 });
289
290 describe("when executing a file with a lint error", () => {
291 it("should exit with error", async () => {
292 const filePath = getFixturePath("undef.js");
293 const code = `--no-ignore --rule no-undef:2 ${filePath}`;
294
295 const exit = await cli.execute(code);
296
297 assert.strictEqual(exit, 1);
298 });
299 });
300
301 describe("when using --fix-type without --fix or --fix-dry-run", () => {
302 it("should exit with error", async () => {
303 const filePath = getFixturePath("passing.js");
304 const code = `--fix-type suggestion ${filePath}`;
305
306 const exit = await cli.execute(code);
307
308 assert.strictEqual(exit, 2);
309 });
310 });
311
312 describe("when executing a file with a syntax error", () => {
313 it("should exit with error", async () => {
314 const filePath = getFixturePath("syntax-error.js");
315 const exit = await cli.execute(`--no-ignore ${filePath}`);
316
317 assert.strictEqual(exit, 1);
318 });
319 });
320
321 describe("when calling execute more than once", () => {
322 it("should not print the results from previous execution", async () => {
323 const filePath = getFixturePath("missing-semicolon.js");
324 const passingPath = getFixturePath("passing.js");
325
326 await cli.execute(`--no-ignore --rule semi:2 ${filePath}`);
327
328 assert.isTrue(log.info.called, "Log should have been called.");
329
330 log.info.resetHistory();
331
332 await cli.execute(`--no-ignore --rule semi:2 ${passingPath}`);
333 assert.isTrue(log.info.notCalled);
334
335 });
336 });
337
338 describe("when executing with version flag", () => {
339 it("should print out current version", async () => {
340 assert.strictEqual(await cli.execute("-v"), 0);
341 assert.strictEqual(log.info.callCount, 1);
342 });
343 });
344
345 describe("when executing with env-info flag", () => {
346 it("should print out environment information", async () => {
347 assert.strictEqual(await cli.execute("--env-info"), 0);
348 assert.strictEqual(log.info.callCount, 1);
349 });
350
351 it("should print error message and return error code", async () => {
352 RuntimeInfo.environment.throws("There was an error!");
353
354 assert.strictEqual(await cli.execute("--env-info"), 2);
355 assert.strictEqual(log.error.callCount, 1);
356 });
357 });
358
359 describe("when executing without no-error-on-unmatched-pattern flag", () => {
360 it("should throw an error on unmatched glob pattern", async () => {
361 const filePath = getFixturePath("unmatched-patterns");
362 const globPattern = "*.js3";
363
364 await stdAssert.rejects(async () => {
365 await cli.execute(`"${filePath}/${globPattern}"`);
366 }, new Error(`No files matching '${filePath}/${globPattern}' were found.`));
367 });
368
369 it("should throw an error on unmatched --ext", async () => {
370 const filePath = getFixturePath("unmatched-patterns");
371 const extension = ".js3";
372
373 await stdAssert.rejects(async () => {
374 await cli.execute(`--ext ${extension} ${filePath}`);
375 }, `No files matching '${filePath}' were found`);
376 });
377 });
378
379 describe("when executing with no-error-on-unmatched-pattern flag", () => {
380 it("should not throw an error on unmatched node glob syntax patterns", async () => {
381 const filePath = getFixturePath("unmatched-patterns");
382 const exit = await cli.execute(`--no-error-on-unmatched-pattern "${filePath}/*.js3"`);
383
384 assert.strictEqual(exit, 0);
385 });
386
387 it("should not throw an error on unmatched --ext", async () => {
388 const filePath = getFixturePath("unmatched-patterns");
389 const exit = await cli.execute(`--no-error-on-unmatched-pattern --ext .js3 ${filePath}`);
390
391 assert.strictEqual(exit, 0);
392 });
393 });
394
395 describe("when executing with no-error-on-unmatched-pattern flag and multiple patterns", () => {
396 it("should not throw an error on multiple unmatched node glob syntax patterns", async () => {
397 const filePath = getFixturePath("unmatched-patterns");
398 const exit = await cli.execute(`--no-error-on-unmatched-pattern ${filePath}/*.js3 ${filePath}/*.js4`);
399
400 assert.strictEqual(exit, 0);
401 });
402
403 it("should still throw an error on when a matched pattern has lint errors", async () => {
404 const filePath = getFixturePath("unmatched-patterns");
405 const exit = await cli.execute(`--no-error-on-unmatched-pattern ${filePath}/*.js3 ${filePath}/*.js`);
406
407 assert.strictEqual(exit, 1);
408 });
409 });
410
411 describe("when executing with no-error-on-unmatched-pattern flag and multiple --ext arguments", () => {
412 it("should not throw an error on multiple unmatched --ext arguments", async () => {
413 const filePath = getFixturePath("unmatched-patterns");
414 const exit = await cli.execute(`--no-error-on-unmatched-pattern --ext .js3 --ext .js4 ${filePath}`);
415
416 assert.strictEqual(exit, 0);
417 });
418
419 it("should still throw an error on when a matched pattern has lint errors", async () => {
420 const filePath = getFixturePath("unmatched-patterns");
421 const exit = await cli.execute(`--no-error-on-unmatched-pattern --ext .js3 --ext .js ${filePath}`);
422
423 assert.strictEqual(exit, 1);
424 });
425 });
426
427 describe("when executing with help flag", () => {
428 it("should print out help", async () => {
429 assert.strictEqual(await cli.execute("-h"), 0);
430 assert.strictEqual(log.info.callCount, 1);
431 });
432 });
433
434 describe("when given a directory with eslint excluded files in the directory", () => {
435 it("should throw an error and not process any files", async () => {
436 const ignorePath = getFixturePath(".eslintignore");
437 const filePath = getFixturePath("cli");
438
439 await stdAssert.rejects(async () => {
440 await cli.execute(`--ignore-path ${ignorePath} ${filePath}`);
441 }, new Error(`All files matched by '${filePath}' are ignored.`));
442 });
443 });
444
445 describe("when given a file in excluded files list", () => {
446 it("should not process the file", async () => {
447 const ignorePath = getFixturePath(".eslintignore");
448 const filePath = getFixturePath("passing.js");
449 const exit = await cli.execute(`--ignore-path ${ignorePath} ${filePath}`);
450
451 // a warning about the ignored file
452 assert.isTrue(log.info.called);
453 assert.strictEqual(exit, 0);
454 });
455
456 it("should process the file when forced", async () => {
457 const ignorePath = getFixturePath(".eslintignore");
458 const filePath = getFixturePath("passing.js");
459 const exit = await cli.execute(`--ignore-path ${ignorePath} --no-ignore ${filePath}`);
460
461 // no warnings
462 assert.isFalse(log.info.called);
463 assert.strictEqual(exit, 0);
464 });
465 });
466
467 describe("when given a pattern to ignore", () => {
468 it("should not process any files", async () => {
469 const ignoredFile = getFixturePath("cli/syntax-error.js");
470 const filePath = getFixturePath("cli/passing.js");
471 const exit = await cli.execute(`--ignore-pattern cli/ ${ignoredFile} ${filePath}`);
472
473 // warnings about the ignored files
474 assert.isTrue(log.info.called);
475 assert.strictEqual(exit, 0);
476 });
477 });
478
479 describe("when given patterns to ignore", () => {
480 it("should not process any matching files", async () => {
481 const ignorePaths = ["a", "b"];
482
483 const cmd = ignorePaths.map(ignorePath => `--ignore-pattern ${ignorePath}`).concat(".").join(" ");
484
485 const opts = {
486 overrideConfig: {
487 ignorePatterns: ignorePaths
488 }
489 };
490
491 await verifyESLintOpts(cmd, opts);
492 });
493 });
494
495 describe("when executing a file with a shebang", () => {
496 it("should execute without error", async () => {
497 const filePath = getFixturePath("shebang.js");
498 const exit = await cli.execute(`--no-ignore ${filePath}`);
499
500 assert.strictEqual(exit, 0);
501 });
502 });
503
504 describe("when loading a custom rule", () => {
505 it("should return an error when rule isn't found", async () => {
506 const rulesPath = getFixturePath("rules", "wrong");
507 const configPath = getFixturePath("rules", "eslint.json");
508 const filePath = getFixturePath("rules", "test", "test-custom-rule.js");
509 const code = `--rulesdir ${rulesPath} --config ${configPath} --no-ignore ${filePath}`;
510
511 await stdAssert.rejects(async () => {
512 const exit = await cli.execute(code);
513
514 assert.strictEqual(exit, 2);
515 }, /Error while loading rule 'custom-rule': Cannot read property/u);
516 });
517
518 it("should return a warning when rule is matched", async () => {
519 const rulesPath = getFixturePath("rules");
520 const configPath = getFixturePath("rules", "eslint.json");
521 const filePath = getFixturePath("rules", "test", "test-custom-rule.js");
522 const code = `--rulesdir ${rulesPath} --config ${configPath} --no-ignore ${filePath}`;
523
524 await cli.execute(code);
525
526 assert.isTrue(log.info.calledOnce);
527 assert.isTrue(log.info.neverCalledWith(""));
528 });
529
530 it("should return warnings from multiple rules in different directories", async () => {
531 const rulesPath = getFixturePath("rules", "dir1");
532 const rulesPath2 = getFixturePath("rules", "dir2");
533 const configPath = getFixturePath("rules", "multi-rulesdirs.json");
534 const filePath = getFixturePath("rules", "test-multi-rulesdirs.js");
535 const code = `--rulesdir ${rulesPath} --rulesdir ${rulesPath2} --config ${configPath} --no-ignore ${filePath}`;
536 const exit = await cli.execute(code);
537
538 const call = log.info.getCall(0);
539
540 assert.isTrue(log.info.calledOnce);
541 assert.isTrue(call.args[0].indexOf("String!") > -1);
542 assert.isTrue(call.args[0].indexOf("Literal!") > -1);
543 assert.isTrue(call.args[0].indexOf("2 problems") > -1);
544 assert.isTrue(log.info.neverCalledWith(""));
545 assert.strictEqual(exit, 1);
546 });
547
548
549 });
550
551 describe("when executing with no-eslintrc flag", () => {
552 it("should ignore a local config file", async () => {
553 const filePath = getFixturePath("eslintrc", "quotes.js");
554 const exit = await cli.execute(`--no-eslintrc --no-ignore ${filePath}`);
555
556 assert.isTrue(log.info.notCalled);
557 assert.strictEqual(exit, 0);
558 });
559 });
560
561 describe("when executing without no-eslintrc flag", () => {
562 it("should load a local config file", async () => {
563 const filePath = getFixturePath("eslintrc", "quotes.js");
564 const exit = await cli.execute(`--no-ignore ${filePath}`);
565
566 assert.isTrue(log.info.calledOnce);
567 assert.strictEqual(exit, 1);
568 });
569 });
570
571 describe("when executing without env flag", () => {
572 it("should not define environment-specific globals", async () => {
573 const files = [
574 getFixturePath("globals-browser.js"),
575 getFixturePath("globals-node.js")
576 ];
577
578 await cli.execute(`--no-eslintrc --config ./conf/eslint-recommended.js --no-ignore ${files.join(" ")}`);
579
580 assert.strictEqual(log.info.args[0][0].split("\n").length, 10);
581 });
582 });
583
584 describe("when executing with global flag", () => {
585 it("should default defined variables to read-only", async () => {
586 const filePath = getFixturePath("undef.js");
587 const exit = await cli.execute(`--global baz,bat --no-ignore --rule no-global-assign:2 ${filePath}`);
588
589 assert.isTrue(log.info.calledOnce);
590 assert.strictEqual(exit, 1);
591 });
592
593 it("should allow defining writable global variables", async () => {
594 const filePath = getFixturePath("undef.js");
595 const exit = await cli.execute(`--global baz:false,bat:true --no-ignore ${filePath}`);
596
597 assert.isTrue(log.info.notCalled);
598 assert.strictEqual(exit, 0);
599 });
600
601 it("should allow defining variables with multiple flags", async () => {
602 const filePath = getFixturePath("undef.js");
603 const exit = await cli.execute(`--global baz --global bat:true --no-ignore ${filePath}`);
604
605 assert.isTrue(log.info.notCalled);
606 assert.strictEqual(exit, 0);
607 });
608 });
609
610 describe("when supplied with rule flag and severity level set to error", () => {
611 it("should exit with an error status (2)", async () => {
612 const filePath = getFixturePath("single-quoted.js");
613 const code = `--no-ignore --rule 'quotes: [2, double]' ${filePath}`;
614 const exitStatus = await cli.execute(code);
615
616 assert.strictEqual(exitStatus, 1);
617 });
618 });
619
620 describe("when the quiet option is enabled", () => {
621
622 it("should only print error", async () => {
623 const filePath = getFixturePath("single-quoted.js");
624 const cliArgs = `--no-ignore --quiet -f compact --rule 'quotes: [2, double]' --rule 'no-unused-vars: 1' ${filePath}`;
625
626 await cli.execute(cliArgs);
627
628 sinon.assert.calledOnce(log.info);
629
630 const formattedOutput = log.info.firstCall.args[0];
631
632 assert.include(formattedOutput, "Error");
633 assert.notInclude(formattedOutput, "Warning");
634 });
635
636 it("should print nothing if there are no errors", async () => {
637 const filePath = getFixturePath("single-quoted.js");
638 const cliArgs = `--quiet -f compact --rule 'quotes: [1, double]' --rule 'no-unused-vars: 1' ${filePath}`;
639
640 await cli.execute(cliArgs);
641
642 sinon.assert.notCalled(log.info);
643 });
644 });
645
646 describe("when supplied with report output file path", () => {
647 afterEach(() => {
648 sh.rm("-rf", "tests/output");
649 });
650
651 it("should write the file and create dirs if they don't exist", async () => {
652 const filePath = getFixturePath("single-quoted.js");
653 const code = `--no-ignore --rule 'quotes: [1, double]' --o tests/output/eslint-output.txt ${filePath}`;
654
655 await cli.execute(code);
656
657 assert.include(fs.readFileSync("tests/output/eslint-output.txt", "utf8"), filePath);
658 assert.isTrue(log.info.notCalled);
659 });
660
661 it("should return an error if the path is a directory", async () => {
662 const filePath = getFixturePath("single-quoted.js");
663 const code = `--no-ignore --rule 'quotes: [1, double]' --o tests/output ${filePath}`;
664
665 fs.mkdirSync("tests/output");
666
667 const exit = await cli.execute(code);
668
669 assert.strictEqual(exit, 2);
670 assert.isTrue(log.info.notCalled);
671 assert.isTrue(log.error.calledOnce);
672 });
673
674 it("should return an error if the path could not be written to", async () => {
675 const filePath = getFixturePath("single-quoted.js");
676 const code = `--no-ignore --rule 'quotes: [1, double]' --o tests/output/eslint-output.txt ${filePath}`;
677
678 fs.writeFileSync("tests/output", "foo");
679
680 const exit = await cli.execute(code);
681
682 assert.strictEqual(exit, 2);
683 assert.isTrue(log.info.notCalled);
684 assert.isTrue(log.error.calledOnce);
685 });
686 });
687
688 describe("when supplied with a plugin", () => {
689 it("should pass plugins to ESLint", async () => {
690 const examplePluginName = "eslint-plugin-example";
691
692 await verifyESLintOpts(`--no-ignore --plugin ${examplePluginName} foo.js`, {
693 overrideConfig: {
694 plugins: [examplePluginName]
695 }
696 });
697 });
698
699 });
700
701 describe("when supplied with a plugin-loading path", () => {
702 it("should pass the option to ESLint", async () => {
703 const examplePluginDirPath = "foo/bar";
704
705 await verifyESLintOpts(`--resolve-plugins-relative-to ${examplePluginDirPath} foo.js`, {
706 resolvePluginsRelativeTo: examplePluginDirPath
707 });
708 });
709 });
710
711 describe("when given an parser name", () => {
712 it("should exit with a fatal error if parser is invalid", async () => {
713 const filePath = getFixturePath("passing.js");
714
715 await stdAssert.rejects(async () => await cli.execute(`--no-ignore --parser test111 ${filePath}`), "Cannot find module 'test111'");
716 });
717
718 it("should exit with no error if parser is valid", async () => {
719 const filePath = getFixturePath("passing.js");
720 const exit = await cli.execute(`--no-ignore --parser espree ${filePath}`);
721
722 assert.strictEqual(exit, 0);
723 });
724 });
725
726 describe("when given parser options", () => {
727 it("should exit with error if parser options are invalid", async () => {
728 const filePath = getFixturePath("passing.js");
729 const exit = await cli.execute(`--no-ignore --parser-options test111 ${filePath}`);
730
731 assert.strictEqual(exit, 2);
732 });
733
734 it("should exit with no error if parser is valid", async () => {
735 const filePath = getFixturePath("passing.js");
736 const exit = await cli.execute(`--no-ignore --parser-options=ecmaVersion:6 ${filePath}`);
737
738 assert.strictEqual(exit, 0);
739 });
740
741 it("should exit with an error on ecmaVersion 7 feature in ecmaVersion 6", async () => {
742 const filePath = getFixturePath("passing-es7.js");
743 const exit = await cli.execute(`--no-ignore --parser-options=ecmaVersion:6 ${filePath}`);
744
745 assert.strictEqual(exit, 1);
746 });
747
748 it("should exit with no error on ecmaVersion 7 feature in ecmaVersion 7", async () => {
749 const filePath = getFixturePath("passing-es7.js");
750 const exit = await cli.execute(`--no-ignore --parser-options=ecmaVersion:7 ${filePath}`);
751
752 assert.strictEqual(exit, 0);
753 });
754
755 it("should exit with no error on ecmaVersion 7 feature with config ecmaVersion 6 and command line ecmaVersion 7", async () => {
756 const configPath = getFixturePath("configurations", "es6.json");
757 const filePath = getFixturePath("passing-es7.js");
758 const exit = await cli.execute(`--no-ignore --config ${configPath} --parser-options=ecmaVersion:7 ${filePath}`);
759
760 assert.strictEqual(exit, 0);
761 });
762 });
763
764 describe("when given the max-warnings flag", () => {
765 it("should not change exit code if warning count under threshold", async () => {
766 const filePath = getFixturePath("max-warnings");
767 const exitCode = await cli.execute(`--no-ignore --max-warnings 10 ${filePath}`);
768
769 assert.strictEqual(exitCode, 0);
770 });
771
772 it("should exit with exit code 1 if warning count exceeds threshold", async () => {
773 const filePath = getFixturePath("max-warnings");
774 const exitCode = await cli.execute(`--no-ignore --max-warnings 5 ${filePath}`);
775
776 assert.strictEqual(exitCode, 1);
777 assert.ok(log.error.calledOnce);
778 assert.include(log.error.getCall(0).args[0], "ESLint found too many warnings");
779 });
780
781 it("should exit with exit code 1 without printing warnings if the quiet option is enabled and warning count exceeds threshold", async () => {
782 const filePath = getFixturePath("max-warnings");
783 const exitCode = await cli.execute(`--no-ignore --quiet --max-warnings 5 ${filePath}`);
784
785 assert.strictEqual(exitCode, 1);
786 assert.ok(log.error.calledOnce);
787 assert.include(log.error.getCall(0).args[0], "ESLint found too many warnings");
788 assert.ok(log.info.notCalled); // didn't print warnings
789 });
790
791 it("should not change exit code if warning count equals threshold", async () => {
792 const filePath = getFixturePath("max-warnings");
793 const exitCode = await cli.execute(`--no-ignore --max-warnings 6 ${filePath}`);
794
795 assert.strictEqual(exitCode, 0);
796 });
797
798 it("should not change exit code if flag is not specified and there are warnings", async () => {
799 const filePath = getFixturePath("max-warnings");
800 const exitCode = await cli.execute(filePath);
801
802 assert.strictEqual(exitCode, 0);
803 });
804 });
805
806 describe("when passed --no-inline-config", () => {
807 let localCLI;
808
809 afterEach(() => {
810 sinon.verifyAndRestore();
811 });
812
813 it("should pass allowInlineConfig:false to ESLint when --no-inline-config is used", async () => {
814
815 // create a fake ESLint class to test with
816 const fakeESLint = sinon.mock().withExactArgs(sinon.match({ allowInlineConfig: false }));
817
818 Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype));
819 sinon.stub(fakeESLint.prototype, "lintFiles").returns([{
820 filePath: "./foo.js",
821 output: "bar",
822 messages: [
823 {
824 severity: 2,
825 message: "Fake message"
826 }
827 ],
828 errorCount: 1,
829 warningCount: 0
830 }]);
831 sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" });
832 fakeESLint.outputFixes = sinon.stub();
833
834 localCLI = proxyquire("../../lib/cli", {
835 "./eslint": { ESLint: fakeESLint },
836 "./shared/logging": log
837 });
838
839 await localCLI.execute("--no-inline-config .");
840 });
841
842 it("should not error and allowInlineConfig should be true by default", async () => {
843
844 // create a fake ESLint class to test with
845 const fakeESLint = sinon.mock().withExactArgs(sinon.match({ allowInlineConfig: true }));
846
847 Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype));
848 sinon.stub(fakeESLint.prototype, "lintFiles").returns([]);
849 sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" });
850 fakeESLint.outputFixes = sinon.stub();
851
852 localCLI = proxyquire("../../lib/cli", {
853 "./eslint": { ESLint: fakeESLint },
854 "./shared/logging": log
855 });
856
857 const exitCode = await localCLI.execute(".");
858
859 assert.strictEqual(exitCode, 0);
860
861 });
862
863 });
864
865 describe("when passed --fix", () => {
866 let localCLI;
867
868 afterEach(() => {
869 sinon.verifyAndRestore();
870 });
871
872 it("should pass fix:true to ESLint when executing on files", async () => {
873
874 // create a fake ESLint class to test with
875 const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true }));
876
877 Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype));
878 sinon.stub(fakeESLint.prototype, "lintFiles").returns([]);
879 sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" });
880 fakeESLint.outputFixes = sinon.mock().once();
881
882 localCLI = proxyquire("../../lib/cli", {
883 "./eslint": { ESLint: fakeESLint },
884 "./shared/logging": log
885 });
886
887 const exitCode = await localCLI.execute("--fix .");
888
889 assert.strictEqual(exitCode, 0);
890
891 });
892
893
894 it("should rewrite files when in fix mode", async () => {
895
896 const report = [{
897 filePath: "./foo.js",
898 output: "bar",
899 messages: [
900 {
901 severity: 2,
902 message: "Fake message"
903 }
904 ],
905 errorCount: 1,
906 warningCount: 0
907 }];
908
909 // create a fake ESLint class to test with
910 const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true }));
911
912 Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype));
913 sinon.stub(fakeESLint.prototype, "lintFiles").returns(report);
914 sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" });
915 fakeESLint.outputFixes = sinon.mock().withExactArgs(report);
916
917 localCLI = proxyquire("../../lib/cli", {
918 "./eslint": { ESLint: fakeESLint },
919 "./shared/logging": log
920 });
921
922 const exitCode = await localCLI.execute("--fix .");
923
924 assert.strictEqual(exitCode, 1);
925
926 });
927
928 it("should provide fix predicate and rewrite files when in fix mode and quiet mode", async () => {
929
930 const report = [{
931 filePath: "./foo.js",
932 output: "bar",
933 messages: [
934 {
935 severity: 1,
936 message: "Fake message"
937 }
938 ],
939 errorCount: 0,
940 warningCount: 1
941 }];
942
943 // create a fake ESLint class to test with
944 const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: sinon.match.func }));
945
946 Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype));
947 sinon.stub(fakeESLint.prototype, "lintFiles").returns(report);
948 sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" });
949 fakeESLint.getErrorResults = sinon.stub().returns([]);
950 fakeESLint.outputFixes = sinon.mock().withExactArgs(report);
951
952 localCLI = proxyquire("../../lib/cli", {
953 "./eslint": { ESLint: fakeESLint },
954 "./shared/logging": log
955 });
956
957 const exitCode = await localCLI.execute("--fix --quiet .");
958
959 assert.strictEqual(exitCode, 0);
960
961 });
962
963 it("should not call ESLint and return 2 when executing on text", async () => {
964
965 // create a fake ESLint class to test with
966 const fakeESLint = sinon.mock().never();
967
968 localCLI = proxyquire("../../lib/cli", {
969 "./eslint": { ESLint: fakeESLint },
970 "./shared/logging": log
971 });
972
973 const exitCode = await localCLI.execute("--fix .", "foo = bar;");
974
975 assert.strictEqual(exitCode, 2);
976 });
977
978 });
979
980 describe("when passed --fix-dry-run", () => {
981 let localCLI;
982
983 afterEach(() => {
984 sinon.verifyAndRestore();
985 });
986
987 it("should pass fix:true to ESLint when executing on files", async () => {
988
989 // create a fake ESLint class to test with
990 const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true }));
991
992 Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype));
993 sinon.stub(fakeESLint.prototype, "lintFiles").returns([]);
994 sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" });
995 fakeESLint.outputFixes = sinon.mock().never();
996
997 localCLI = proxyquire("../../lib/cli", {
998 "./eslint": { ESLint: fakeESLint },
999 "./shared/logging": log
1000 });
1001
1002 const exitCode = await localCLI.execute("--fix-dry-run .");
1003
1004 assert.strictEqual(exitCode, 0);
1005
1006 });
1007
1008 it("should pass fixTypes to ESLint when --fix-type is passed", async () => {
1009
1010 const expectedESLintOptions = {
1011 fix: true,
1012 fixTypes: ["suggestion"]
1013 };
1014
1015 // create a fake ESLint class to test with
1016 const fakeESLint = sinon.mock().withExactArgs(sinon.match(expectedESLintOptions));
1017
1018 Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype));
1019 sinon.stub(fakeESLint.prototype, "lintFiles").returns([]);
1020 sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" });
1021 fakeESLint.outputFixes = sinon.stub();
1022
1023 localCLI = proxyquire("../../lib/cli", {
1024 "./eslint": { ESLint: fakeESLint },
1025 "./shared/logging": log
1026 });
1027
1028 const exitCode = await localCLI.execute("--fix-dry-run --fix-type suggestion .");
1029
1030 assert.strictEqual(exitCode, 0);
1031 });
1032
1033 it("should not rewrite files when in fix-dry-run mode", async () => {
1034
1035 const report = [{
1036 filePath: "./foo.js",
1037 output: "bar",
1038 messages: [
1039 {
1040 severity: 2,
1041 message: "Fake message"
1042 }
1043 ],
1044 errorCount: 1,
1045 warningCount: 0
1046 }];
1047
1048 // create a fake ESLint class to test with
1049 const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true }));
1050
1051 Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype));
1052 sinon.stub(fakeESLint.prototype, "lintFiles").returns(report);
1053 sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" });
1054 fakeESLint.outputFixes = sinon.mock().never();
1055
1056 localCLI = proxyquire("../../lib/cli", {
1057 "./eslint": { ESLint: fakeESLint },
1058 "./shared/logging": log
1059 });
1060
1061 const exitCode = await localCLI.execute("--fix-dry-run .");
1062
1063 assert.strictEqual(exitCode, 1);
1064
1065 });
1066
1067 it("should provide fix predicate when in fix-dry-run mode and quiet mode", async () => {
1068
1069 const report = [{
1070 filePath: "./foo.js",
1071 output: "bar",
1072 messages: [
1073 {
1074 severity: 1,
1075 message: "Fake message"
1076 }
1077 ],
1078 errorCount: 0,
1079 warningCount: 1
1080 }];
1081
1082 // create a fake ESLint class to test with
1083 const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: sinon.match.func }));
1084
1085 Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype));
1086 sinon.stub(fakeESLint.prototype, "lintFiles").returns(report);
1087 sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" });
1088 fakeESLint.getErrorResults = sinon.stub().returns([]);
1089 fakeESLint.outputFixes = sinon.mock().never();
1090
1091 localCLI = proxyquire("../../lib/cli", {
1092 "./eslint": { ESLint: fakeESLint },
1093 "./shared/logging": log
1094 });
1095
1096 const exitCode = await localCLI.execute("--fix-dry-run --quiet .");
1097
1098 assert.strictEqual(exitCode, 0);
1099
1100 });
1101
1102 it("should allow executing on text", async () => {
1103
1104 const report = [{
1105 filePath: "./foo.js",
1106 output: "bar",
1107 messages: [
1108 {
1109 severity: 2,
1110 message: "Fake message"
1111 }
1112 ],
1113 errorCount: 1,
1114 warningCount: 0
1115 }];
1116
1117 // create a fake ESLint class to test with
1118 const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true }));
1119
1120 Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype));
1121 sinon.stub(fakeESLint.prototype, "lintText").returns(report);
1122 sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" });
1123 fakeESLint.outputFixes = sinon.mock().never();
1124
1125 localCLI = proxyquire("../../lib/cli", {
1126 "./eslint": { ESLint: fakeESLint },
1127 "./shared/logging": log
1128 });
1129
1130 const exitCode = await localCLI.execute("--fix-dry-run .", "foo = bar;");
1131
1132 assert.strictEqual(exitCode, 1);
1133 });
1134
1135 it("should not call ESLint and return 2 when used with --fix", async () => {
1136
1137 // create a fake ESLint class to test with
1138 const fakeESLint = sinon.mock().never();
1139
1140 localCLI = proxyquire("../../lib/cli", {
1141 "./eslint": { ESLint: fakeESLint },
1142 "./shared/logging": log
1143 });
1144
1145 const exitCode = await localCLI.execute("--fix --fix-dry-run .", "foo = bar;");
1146
1147 assert.strictEqual(exitCode, 2);
1148 });
1149 });
1150
1151 describe("when passing --print-config", () => {
1152 it("should print out the configuration", async () => {
1153 const filePath = getFixturePath("xxxx");
1154
1155 const exitCode = await cli.execute(`--print-config ${filePath}`);
1156
1157 assert.isTrue(log.info.calledOnce);
1158 assert.strictEqual(exitCode, 0);
1159 });
1160
1161 it("should error if any positional file arguments are passed", async () => {
1162 const filePath1 = getFixturePath("files", "bar.js");
1163 const filePath2 = getFixturePath("files", "foo.js");
1164
1165 const exitCode = await cli.execute(`--print-config ${filePath1} ${filePath2}`);
1166
1167 assert.isTrue(log.info.notCalled);
1168 assert.isTrue(log.error.calledOnce);
1169 assert.strictEqual(exitCode, 2);
1170 });
1171
1172 it("should error out when executing on text", async () => {
1173 const exitCode = await cli.execute("--print-config=myFile.js", "foo = bar;");
1174
1175 assert.isTrue(log.info.notCalled);
1176 assert.isTrue(log.error.calledOnce);
1177 assert.strictEqual(exitCode, 2);
1178 });
1179 });
1180
1181 });