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