From f30fb69f536022714dba46e4b123bcadbea390c0 Mon Sep 17 00:00:00 2001 From: Colin Chang Date: Sun, 7 Oct 2018 11:11:02 -0400 Subject: [PATCH] Fix: Warn for deprecation in Node output (#7443) --- docs/developer-guide/nodejs-api.md | 7 +- lib/cli-engine.js | 29 +++++- tests/lib/cli-engine.js | 155 +++++++++++++++++------------ 3 files changed, 123 insertions(+), 68 deletions(-) diff --git a/docs/developer-guide/nodejs-api.md b/docs/developer-guide/nodejs-api.md index a2959d8e97a6..524f1dc406f8 100644 --- a/docs/developer-guide/nodejs-api.md +++ b/docs/developer-guide/nodejs-api.md @@ -416,7 +416,8 @@ The return value is an object containing the results of the linting operation. H errorCount: 1, warningCount: 0, fixableErrorCount: 1, - fixableWarningCount: 0 + fixableWarningCount: 0, + usedDeprecatedRules: [] } ``` @@ -474,6 +475,7 @@ var report = cli.executeOnFiles(["myfile.js", "lib/"]); warningCount: 0, fixableErrorCount: 1, fixableWarningCount: 0, + usedDeprecatedRules: [] } ``` @@ -505,6 +507,7 @@ If the operation ends with a parsing error, you will get a single message for th warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, + usedDeprecatedRules: [] } ``` @@ -516,7 +519,7 @@ The top-level report object has a `results` array containing all linting results * `source` - The source code for the given file. This property is omitted if this file has no errors/warnings or if the `output` property is present. * `output` - The source code for the given file with as many fixes applied as possible, so you can use that to rewrite the files if necessary. This property is omitted if no fix is available. -The top-level report object also has `errorCount` and `warningCount` which give the exact number of errors and warnings respectively on all the files. +The top-level report object also has `errorCount` and `warningCount` which give the exact number of errors and warnings respectively on all the files. Additionally, `usedDeprecatedRules` signals any deprecated rules used and their replacement (if available). Once you get a report object, it's up to you to determine how to output the results. Fixes will not be automatically applied to the files, even if you set `fix: true` when constructing the `CLIEngine` instance. To apply fixes to the files, call [`outputFixes`](#cliengineoutputfixes). diff --git a/lib/cli-engine.js b/lib/cli-engine.js index 5b52459ac830..c25ff4f7c732 100644 --- a/lib/cli-engine.js +++ b/lib/cli-engine.js @@ -272,6 +272,30 @@ function createIgnoreResult(filePath, baseDir) { }; } +/** + * Produces rule warnings (i.e. deprecation) from configured rules + * @param {Object} rules - Rules configured + * @param {Map} loadedRules - Map of loaded rules + * @returns {Object} Contains rule warnings + * @private + */ +function createRuleWarnings(rules, loadedRules) { + const ruleWarnings = { usedDeprecatedRules: [] }; + + if (!rules) { + return ruleWarnings; + } + + Object.keys(rules).forEach(name => { + const loadedRule = loadedRules.get(name); + + if (loadedRule.meta && loadedRule.meta.deprecated) { + ruleWarnings.usedDeprecatedRules.push({ ruleId: name, replacedBy: loadedRule.meta.docs.replacedBy }); + } + }); + + return ruleWarnings; +} /** * Checks if the given message is an error message. @@ -555,6 +579,8 @@ class CLIEngine { const stats = calculateStatsPerRun(results); + const ruleWarnings = createRuleWarnings(this.options.rules, this.getRules()); + debug(`Linting complete in: ${Date.now() - startTime}ms`); return { @@ -562,7 +588,8 @@ class CLIEngine { errorCount: stats.errorCount, warningCount: stats.warningCount, fixableErrorCount: stats.fixableErrorCount, - fixableWarningCount: stats.fixableWarningCount + fixableWarningCount: stats.fixableWarningCount, + usedDeprecatedRules: ruleWarnings.usedDeprecatedRules }; } diff --git a/tests/lib/cli-engine.js b/tests/lib/cli-engine.js index cac7671b197e..92458b59af70 100644 --- a/tests/lib/cli-engine.js +++ b/tests/lib/cli-engine.js @@ -1375,6 +1375,33 @@ describe("CLIEngine", () => { assert.strictEqual(report.results[0].messages.length, 0); }); + it("should warn when deprecated rules are configured", () => { + engine = new CLIEngine({ + cwd: originalDir, + configFile: ".eslintrc.js", + rules: { "indent-legacy": 1 } + }); + + const report = engine.executeOnFiles(["lib/cli*.js"]); + + assert.deepStrictEqual( + report.usedDeprecatedRules, + [{ ruleId: "indent-legacy", replacedBy: ["indent"] }] + ); + }); + + it("should not warn when deprecated rules are not configured", () => { + engine = new CLIEngine({ + cwd: originalDir, + configFile: ".eslintrc.js", + rules: { indent: 1 } + }); + + const report = engine.executeOnFiles(["lib/cli*.js"]); + + assert.deepStrictEqual(report.usedDeprecatedRules, []); + }); + describe("Fix Mode", () => { it("should return fixed text on multiple files when in fix mode", () => { @@ -1407,70 +1434,68 @@ describe("CLIEngine", () => { const report = engine.executeOnFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); report.results.forEach(convertCRLF); - assert.deepStrictEqual(report, { - results: [ - { - filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/multipass.js")), - messages: [], - errorCount: 0, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - output: "true ? \"yes\" : \"no\";\n" - }, - { - filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/ok.js")), - messages: [], - errorCount: 0, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0 - }, - { - filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/quotes-semi-eqeqeq.js")), - messages: [ - { - column: 9, - line: 2, - message: "Expected '===' and instead saw '=='.", - messageId: "unexpected", - nodeType: "BinaryExpression", - ruleId: "eqeqeq", - severity: 2 - } - ], - errorCount: 1, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - output: "var msg = \"hi\";\nif (msg == \"hi\") {\n\n}\n" - }, - { - filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/quotes.js")), - messages: [ - { - column: 18, - line: 1, - endColumn: 21, - endLine: 1, - message: "'foo' is not defined.", - nodeType: "Identifier", - ruleId: "no-undef", - severity: 2 - } - ], - errorCount: 1, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - output: "var msg = \"hi\" + foo;\n" - } - ], - errorCount: 2, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0 - }); + assert.deepStrictEqual(report.results, [ + { + filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/multipass.js")), + messages: [], + errorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: "true ? \"yes\" : \"no\";\n" + }, + { + filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/ok.js")), + messages: [], + errorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0 + }, + { + filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/quotes-semi-eqeqeq.js")), + messages: [ + { + column: 9, + line: 2, + message: "Expected '===' and instead saw '=='.", + messageId: "unexpected", + nodeType: "BinaryExpression", + ruleId: "eqeqeq", + severity: 2 + } + ], + errorCount: 1, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: "var msg = \"hi\";\nif (msg == \"hi\") {\n\n}\n" + }, + { + filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/quotes.js")), + messages: [ + { + column: 18, + line: 1, + endColumn: 21, + endLine: 1, + message: "'foo' is not defined.", + nodeType: "Identifier", + ruleId: "no-undef", + severity: 2 + } + ], + errorCount: 1, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: "var msg = \"hi\" + foo;\n" + } + ]); + assert.strictEqual(report.errorCount, 2); + assert.strictEqual(report.warningCount, 0); + assert.strictEqual(report.fixableErrorCount, 0); + assert.strictEqual(report.fixableWarningCount, 0); }); it("should run autofix even if files are cached without autofix results", () => { @@ -2117,7 +2142,7 @@ describe("CLIEngine", () => { assert.deepStrictEqual(result, cachedResult, "the result is the same regardless of using cache or not"); // assert the file was not processed because the cache was used - assert.isFalse(spy.called, "the file was not loaded because it used the cache"); + assert.isFalse(spy.calledWith(file), "the file was not loaded because it used the cache"); }); it("should remember the files from a previous run and do not operate on then if not changed", () => {