]> git.proxmox.com Git - pve-eslint.git/blame - eslint/tests/bin/eslint.js
upgrade to v7.0.0
[pve-eslint.git] / eslint / tests / bin / eslint.js
CommitLineData
eb39fafa
DC
1/**
2 * @fileoverview Integration tests for the eslint.js executable.
3 * @author Teddy Katz
4 */
5
6"use strict";
7
8const childProcess = require("child_process");
9const fs = require("fs");
10const assert = require("chai").assert;
56c4a2cb
DC
11const path = require("path");
12
13const EXECUTABLE_PATH = path.resolve(path.join(__dirname, "../../bin/eslint.js"));
eb39fafa
DC
14
15/**
16 * Returns a Promise for when a child process exits
17 * @param {ChildProcess} exitingProcess The child process
18 * @returns {Promise<number>} A Promise that fulfills with the exit code when the child process exits
19 */
20function awaitExit(exitingProcess) {
21 return new Promise(resolve => exitingProcess.once("exit", resolve));
22}
23
24/**
25 * Asserts that the exit code of a given child process will equal the given value.
26 * @param {ChildProcess} exitingProcess The child process
27 * @param {number} expectedExitCode The expected exit code of the child process
28 * @returns {Promise} A Promise that fulfills if the exit code ends up matching, and rejects otherwise.
29 */
30function assertExitCode(exitingProcess, expectedExitCode) {
31 return awaitExit(exitingProcess).then(exitCode => {
32 assert.strictEqual(exitCode, expectedExitCode, `Expected an exit code of ${expectedExitCode} but got ${exitCode}.`);
33 });
34}
35
36/**
37 * Returns a Promise for the stdout of a process.
38 * @param {ChildProcess} runningProcess The child process
39 * @returns {Promise<{stdout: string, stderr: string}>} A Promise that fulfills with all of the
40 * stdout and stderr output produced by the process when it exits.
41 */
42function getOutput(runningProcess) {
43 let stdout = "";
44 let stderr = "";
45
46 runningProcess.stdout.on("data", data => (stdout += data));
47 runningProcess.stderr.on("data", data => (stderr += data));
48 return awaitExit(runningProcess).then(() => ({ stdout, stderr }));
49}
50
51describe("bin/eslint.js", () => {
52 const forkedProcesses = new Set();
53
54 /**
55 * Forks the process to run an instance of ESLint.
56 * @param {string[]} [args] An array of arguments
57 * @param {Object} [options] An object containing options for the resulting child process
58 * @returns {ChildProcess} The resulting child process
59 */
60 function runESLint(args, options) {
61 const newProcess = childProcess.fork(EXECUTABLE_PATH, args, Object.assign({ silent: true }, options));
62
63 forkedProcesses.add(newProcess);
64 return newProcess;
65 }
66
67 describe("reading from stdin", () => {
68 it("has exit code 0 if no linting errors are reported", () => {
69 const child = runESLint(["--stdin", "--no-eslintrc"]);
70
71 child.stdin.write("var foo = bar;\n");
72 child.stdin.end();
73 return assertExitCode(child, 0);
74 });
75
76 it("has exit code 0 if no linting errors are reported", () => {
77 const child = runESLint([
78 "--stdin",
79 "--no-eslintrc",
80 "--rule",
81 "{'no-extra-semi': 2}",
82 "--fix-dry-run",
83 "--format",
84 "json"
85 ]);
86
87 const expectedOutput = JSON.stringify([
88 {
89 filePath: "<text>",
90 messages: [],
91 errorCount: 0,
92 warningCount: 0,
93 fixableErrorCount: 0,
94 fixableWarningCount: 0,
56c4a2cb
DC
95 output: "var foo = bar;\n",
96 usedDeprecatedRules: []
eb39fafa
DC
97 }
98 ]);
99
100 const exitCodePromise = assertExitCode(child, 0);
101 const stdoutPromise = getOutput(child).then(output => {
102 assert.strictEqual(output.stdout.trim(), expectedOutput);
103 assert.strictEqual(output.stderr, "");
104 });
105
106 child.stdin.write("var foo = bar;;\n");
107 child.stdin.end();
108
109 return Promise.all([exitCodePromise, stdoutPromise]);
110 });
111
112 it("has exit code 1 if a syntax error is thrown", () => {
113 const child = runESLint(["--stdin", "--no-eslintrc"]);
114
115 child.stdin.write("This is not valid JS syntax.\n");
116 child.stdin.end();
117 return assertExitCode(child, 1);
118 });
119
120 it("has exit code 1 if a linting error occurs", () => {
121 const child = runESLint(["--stdin", "--no-eslintrc", "--rule", "semi:2"]);
122
123 child.stdin.write("var foo = bar // <-- no semicolon\n");
124 child.stdin.end();
125 return assertExitCode(child, 1);
126 });
127
128 it(
129 "gives a detailed error message if no config file is found in /",
130 () => {
131 if (
132 fs.readdirSync("/").some(
133 fileName =>
134 /^\.eslintrc(?:\.(?:js|yaml|yml|json))?$/u
135 .test(fileName)
136 )
137 ) {
138 return Promise.resolve(true);
139 }
140 const child = runESLint(
141 ["--stdin"], { cwd: "/", env: { HOME: "/" } }
142 );
143
144 const exitCodePromise = assertExitCode(child, 2);
145 const stderrPromise = getOutput(child).then(output => {
146 assert.match(
147 output.stderr,
148 /ESLint couldn't find a configuration file/u
149 );
150 });
151
152 child.stdin.write("1 < 3;\n");
153 child.stdin.end();
154 return Promise.all([exitCodePromise, stderrPromise]);
155 }
156 );
157
158 it("successfully reads from an asynchronous pipe", () => {
159 const child = runESLint(["--stdin", "--no-eslintrc"]);
160
161 child.stdin.write("var foo = bar;\n");
162 return new Promise(resolve => setTimeout(resolve, 300)).then(() => {
163 child.stdin.write("var baz = qux;\n");
164 child.stdin.end();
165
166 return assertExitCode(child, 0);
167 });
168 });
169
170 it("successfully handles more than 4k data via stdin", () => {
171 const child = runESLint(["--stdin", "--no-eslintrc"]);
56c4a2cb 172 const large = fs.createReadStream(path.join(__dirname, "../bench/large.js"), "utf8");
eb39fafa
DC
173
174 large.pipe(child.stdin);
175
176 return assertExitCode(child, 0);
177 });
178 });
179
180 describe("running on files", () => {
181 it("has exit code 0 if no linting errors occur", () => assertExitCode(runESLint(["bin/eslint.js"]), 0));
56c4a2cb
DC
182 it("has exit code 0 if a linting warning is reported", () => assertExitCode(runESLint(["bin/eslint.js", "--env", "es2020", "--no-eslintrc", "--rule", "semi: [1, never]"]), 0));
183 it("has exit code 1 if a linting error is reported", () => assertExitCode(runESLint(["bin/eslint.js", "--env", "es2020", "--no-eslintrc", "--rule", "semi: [2, never]"]), 1));
eb39fafa
DC
184 it("has exit code 1 if a syntax error is thrown", () => assertExitCode(runESLint(["README.md"]), 1));
185 });
186
187 describe("automatically fixing files", () => {
56c4a2cb 188 const fixturesPath = path.join(__dirname, "../fixtures/autofix-integration");
eb39fafa
DC
189 const tempFilePath = `${fixturesPath}/temp.js`;
190 const startingText = fs.readFileSync(`${fixturesPath}/left-pad.js`).toString();
191 const expectedFixedText = fs.readFileSync(`${fixturesPath}/left-pad-expected.js`).toString();
192 const expectedFixedTextQuiet = fs.readFileSync(`${fixturesPath}/left-pad-expected-quiet.js`).toString();
193
194 beforeEach(() => {
195 fs.writeFileSync(tempFilePath, startingText);
196 });
197
198 it("has exit code 0 and fixes a file if all rules can be fixed", () => {
199 const child = runESLint(["--fix", "--no-eslintrc", "--no-ignore", tempFilePath]);
200 const exitCodeAssertion = assertExitCode(child, 0);
201 const outputFileAssertion = awaitExit(child).then(() => {
202 assert.strictEqual(fs.readFileSync(tempFilePath).toString(), expectedFixedText);
203 });
204
205 return Promise.all([exitCodeAssertion, outputFileAssertion]);
206 });
207
208 it("has exit code 0, fixes errors in a file, and does not report or fix warnings if --quiet and --fix are used", () => {
209 const child = runESLint(["--fix", "--quiet", "--no-eslintrc", "--no-ignore", tempFilePath]);
210 const exitCodeAssertion = assertExitCode(child, 0);
211 const stdoutAssertion = getOutput(child).then(output => assert.strictEqual(output.stdout, ""));
212 const outputFileAssertion = awaitExit(child).then(() => {
213 assert.strictEqual(fs.readFileSync(tempFilePath).toString(), expectedFixedTextQuiet);
214 });
215
216 return Promise.all([exitCodeAssertion, stdoutAssertion, outputFileAssertion]);
217 });
218
219 it("has exit code 1 and fixes a file if not all rules can be fixed", () => {
220 const child = runESLint(["--fix", "--no-eslintrc", "--no-ignore", "--rule", "max-len: [2, 10]", tempFilePath]);
221 const exitCodeAssertion = assertExitCode(child, 1);
222 const outputFileAssertion = awaitExit(child).then(() => {
223 assert.strictEqual(fs.readFileSync(tempFilePath).toString(), expectedFixedText);
224 });
225
226 return Promise.all([exitCodeAssertion, outputFileAssertion]);
227 });
228
229 afterEach(() => {
230 fs.unlinkSync(tempFilePath);
231 });
232 });
233
234 describe("cache files", () => {
235 const CACHE_PATH = ".temp-eslintcache";
236 const SOURCE_PATH = "tests/fixtures/cache/src/test-file.js";
237 const ARGS_WITHOUT_CACHE = ["--no-eslintrc", "--no-ignore", SOURCE_PATH, "--cache-location", CACHE_PATH];
238 const ARGS_WITH_CACHE = ARGS_WITHOUT_CACHE.concat("--cache");
239
240 describe("when no cache file exists", () => {
241 it("creates a cache file when the --cache flag is used", () => {
242 const child = runESLint(ARGS_WITH_CACHE);
243
244 return assertExitCode(child, 0).then(() => {
245 assert.isTrue(fs.existsSync(CACHE_PATH), "Cache file should exist at the given location");
246
247 // Cache file should contain valid JSON
248 JSON.parse(fs.readFileSync(CACHE_PATH, "utf8"));
249 });
250 });
251 });
252
253 describe("when a valid cache file already exists", () => {
254 beforeEach(() => {
255 const child = runESLint(ARGS_WITH_CACHE);
256
257 return assertExitCode(child, 0).then(() => {
258 assert.isTrue(fs.existsSync(CACHE_PATH), "Cache file should exist at the given location");
259 });
260 });
261 it("can lint with an existing cache file and the --cache flag", () => {
262 const child = runESLint(ARGS_WITH_CACHE);
263
264 return assertExitCode(child, 0).then(() => {
265
266 // Note: This doesn't actually verify that the cache file is used for anything.
267 assert.isTrue(fs.existsSync(CACHE_PATH), "Cache file should still exist after linting with --cache");
268 });
269 });
270 it("updates the cache file when the source file is modified", () => {
271 const initialCacheContent = fs.readFileSync(CACHE_PATH, "utf8");
272
273 // Update the file to change its mtime
274 fs.writeFileSync(SOURCE_PATH, fs.readFileSync(SOURCE_PATH, "utf8"));
275
276 const child = runESLint(ARGS_WITH_CACHE);
277
278 return assertExitCode(child, 0).then(() => {
279 const newCacheContent = fs.readFileSync(CACHE_PATH, "utf8");
280
281 assert.notStrictEqual(initialCacheContent, newCacheContent, "Cache file should change after source is modified");
282 });
283 });
284 it("deletes the cache file when run without the --cache argument", () => {
285 const child = runESLint(ARGS_WITHOUT_CACHE);
286
287 return assertExitCode(child, 0).then(() => {
288 assert.isFalse(fs.existsSync(CACHE_PATH), "Cache file should be deleted after running ESLint without the --cache argument");
289 });
290 });
291 });
292
293 // https://github.com/eslint/eslint/issues/7748
294 describe("when an invalid cache file already exists", () => {
295 beforeEach(() => {
296 fs.writeFileSync(CACHE_PATH, "This is not valid JSON.");
297
298 // Sanity check
299 assert.throws(
300 () => JSON.parse(fs.readFileSync(CACHE_PATH, "utf8")),
301 SyntaxError,
302 /Unexpected token/u,
303 "Cache file should not contain valid JSON at the start"
304 );
305 });
306
307 it("overwrites the invalid cache file with a valid one when the --cache argument is used", () => {
308 const child = runESLint(ARGS_WITH_CACHE);
309
310 return assertExitCode(child, 0).then(() => {
311 assert.isTrue(fs.existsSync(CACHE_PATH), "Cache file should exist at the given location");
312
313 // Cache file should contain valid JSON
314 JSON.parse(fs.readFileSync(CACHE_PATH, "utf8"));
315 });
316 });
317
318 it("deletes the invalid cache file when the --cache argument is not used", () => {
319 const child = runESLint(ARGS_WITHOUT_CACHE);
320
321 return assertExitCode(child, 0).then(() => {
322 assert.isFalse(fs.existsSync(CACHE_PATH), "Cache file should be deleted after running ESLint without the --cache argument");
323 });
324 });
325 });
326
327 afterEach(() => {
328 if (fs.existsSync(CACHE_PATH)) {
329 fs.unlinkSync(CACHE_PATH);
330 }
331 });
332 });
333
334 describe("handling crashes", () => {
335 it("prints the error message to stderr in the event of a crash", () => {
336 const child = runESLint(["--rule=no-restricted-syntax:[error, 'Invalid Selector [[[']", "Makefile.js"]);
337 const exitCodeAssertion = assertExitCode(child, 2);
338 const outputAssertion = getOutput(child).then(output => {
339 const expectedSubstring = "Syntax error in selector";
340
341 assert.strictEqual(output.stdout, "");
342 assert.include(output.stderr, expectedSubstring);
343 });
344
345 return Promise.all([exitCodeAssertion, outputAssertion]);
346 });
347
348 it("prints the error message exactly once to stderr in the event of a crash", () => {
349 const child = runESLint(["--rule=no-restricted-syntax:[error, 'Invalid Selector [[[']", "Makefile.js"]);
350 const exitCodeAssertion = assertExitCode(child, 2);
351 const outputAssertion = getOutput(child).then(output => {
352 const expectedSubstring = "Syntax error in selector";
353
354 assert.strictEqual(output.stdout, "");
355 assert.include(output.stderr, expectedSubstring);
356
357 // The message should appear exactly once in stderr
358 assert.strictEqual(output.stderr.indexOf(expectedSubstring), output.stderr.lastIndexOf(expectedSubstring));
359 });
360
361 return Promise.all([exitCodeAssertion, outputAssertion]);
362 });
363
364 it("prints the error message pointing to line of code", () => {
56c4a2cb 365 const invalidConfig = path.join(__dirname, "../fixtures/bin/.eslintrc.yml");
eb39fafa
DC
366 const child = runESLint(["--no-ignore", invalidConfig]);
367 const exitCodeAssertion = assertExitCode(child, 2);
368 const outputAssertion = getOutput(child).then(output => {
369 const expectedSubstring = ": bad indentation of a mapping entry at line";
370
371 assert.strictEqual(output.stdout, "");
372 assert.include(output.stderr, expectedSubstring);
373 });
374
375 return Promise.all([exitCodeAssertion, outputAssertion]);
376 });
377 });
378
379
380 describe("emitting a warning for ecmaFeatures", () => {
381 it("does not emit a warning when it does not find an ecmaFeatures option", () => {
382 const child = runESLint(["Makefile.js"]);
383
384 const exitCodePromise = assertExitCode(child, 0);
385 const outputPromise = getOutput(child).then(output => assert.strictEqual(output.stderr, ""));
386
387 return Promise.all([exitCodePromise, outputPromise]);
388 });
389 it("emits a warning when it finds an ecmaFeatures option", () => {
390 const child = runESLint(["-c", "tests/fixtures/config-file/ecma-features/.eslintrc.yml", "Makefile.js"]);
391
392 const exitCodePromise = assertExitCode(child, 0);
393 const outputPromise = getOutput(child).then(output => {
394 assert.include(output.stderr, "The 'ecmaFeatures' config file property is deprecated and has no effect.");
395 });
396
397 return Promise.all([exitCodePromise, outputPromise]);
398 });
399 });
400
401 afterEach(() => {
402
403 // Clean up all the processes after every test.
404 forkedProcesses.forEach(child => child.kill());
405 forkedProcesses.clear();
406 });
407});