diff --git a/docs/developer-guide/nodejs-api.md b/docs/developer-guide/nodejs-api.md index 03a1dd794c3..f48670896be 100644 --- a/docs/developer-guide/nodejs-api.md +++ b/docs/developer-guide/nodejs-api.md @@ -147,6 +147,37 @@ console.log(code.text); // "var foo = bar;" In this way, you can retrieve the text and AST used for the last run of `linter.verify()`. +### verifyAndFix() + +This method is similar to verify except that it also runs autofixing logic, similar to the `--fix` flag on the command line. The result object will contain the autofixed code, along with any remaining linting messages for the code that were not autofixed. + +```js +var Linter = require("eslint").Linter; +var linter = new Linter(); + +var messages = linter.verifyAndFix("var foo", { + rules: { + semi: 2 + } +}, { filename: "foo.js" }); +``` + +Output object from this method: + +```js +{ + fixed: true, + text: "var foo;", + messages: [] +} +``` + +The information available is: + +* `fixed` - True, if the code was fixed. +* `text` - Fixed code text (might be the same as input if no fixes were applied). +* `messages` - Collection of all messages for the given code (It has the same information as explained above under `verify` block). + ## linter The `eslint.linter` object (deprecated) is an instance of the `Linter` class as defined [above](#Linter). `eslint.linter` exists for backwards compatibility, but we do not recommend using it because any mutations to it are shared among every module that uses `eslint`. Instead, please create your own instance of `eslint.Linter`. diff --git a/lib/cli-engine.js b/lib/cli-engine.js index 46628c1e4a2..48f484a79b4 100644 --- a/lib/cli-engine.js +++ b/lib/cli-engine.js @@ -23,11 +23,9 @@ const fs = require("fs"), Config = require("./config"), fileEntryCache = require("file-entry-cache"), globUtil = require("./util/glob-util"), - SourceCodeFixer = require("./util/source-code-fixer"), validator = require("./config/config-validator"), stringify = require("json-stable-stringify"), hash = require("./util/hash"), - pkg = require("../package.json"); const debug = require("debug")("eslint:cli-engine"); @@ -132,80 +130,6 @@ function calculateStatsPerRun(results) { }); } -/** - * Performs multiple autofix passes over the text until as many fixes as possible - * have been applied. - * @param {string} text The source text to apply fixes to. - * @param {Object} config The ESLint config object to use. - * @param {Object} options The ESLint options object to use. - * @param {string} options.filename The filename from which the text was read. - * @param {boolean} options.allowInlineConfig Flag indicating if inline comments - * should be allowed. - * @param {Linter} linter Linter context - * @returns {Object} The result of the fix operation as returned from the - * SourceCodeFixer. - * @private - */ -function multipassFix(text, config, options, linter) { - const MAX_PASSES = 10; - let messages = [], - fixedResult, - fixed = false, - passNumber = 0; - - /** - * This loop continues until one of the following is true: - * - * 1. No more fixes have been applied. - * 2. Ten passes have been made. - * - * That means anytime a fix is successfully applied, there will be another pass. - * Essentially, guaranteeing a minimum of two passes. - */ - do { - passNumber++; - - debug(`Linting code for ${options.filename} (pass ${passNumber})`); - messages = linter.verify(text, config, options); - - debug(`Generating fixed text for ${options.filename} (pass ${passNumber})`); - fixedResult = SourceCodeFixer.applyFixes(linter.getSourceCode(), messages); - - // stop if there are any syntax errors. - // 'fixedResult.output' is a empty string. - if (messages.length === 1 && messages[0].fatal) { - break; - } - - // keep track if any fixes were ever applied - important for return value - fixed = fixed || fixedResult.fixed; - - // update to use the fixed output instead of the original text - text = fixedResult.output; - - } while ( - fixedResult.fixed && - passNumber < MAX_PASSES - ); - - - /* - * If the last result had fixes, we need to lint again to be sure we have - * the most up-to-date information. - */ - if (fixedResult.fixed) { - fixedResult.messages = linter.verify(text, config, options); - } - - - // ensure the last result properly reflects if fixes were done - fixedResult.fixed = fixed; - fixedResult.output = text; - - return fixedResult; - -} - /** * Processes an source code using ESLint. * @param {string} text The source code to check. @@ -269,10 +193,10 @@ function processText(text, configHelper, filename, fix, allowInlineConfig, linte } else { if (fix) { - fixedResult = multipassFix(text, config, { + fixedResult = linter.verifyAndFix(text, config, { filename, allowInlineConfig - }, linter); + }); messages = fixedResult.messages; } else { messages = linter.verify(text, config, { diff --git a/lib/linter.js b/lib/linter.js index 25af05223d0..0b0d06a8663 100755 --- a/lib/linter.js +++ b/lib/linter.js @@ -27,9 +27,11 @@ const assert = require("assert"), Rules = require("./rules"), timing = require("./timing"), astUtils = require("./ast-utils"), + pkg = require("../package.json"), + SourceCodeFixer = require("./util/source-code-fixer"); - pkg = require("../package.json"); - +const debug = require("debug")("eslint:linter"); +const MAX_AUTOFIX_PASSES = 10; //------------------------------------------------------------------------------ // Typedefs @@ -1185,6 +1187,74 @@ class Linter extends EventEmitter { getDeclaredVariables(node) { return (this.scopeManager && this.scopeManager.getDeclaredVariables(node)) || []; } + + /** + * Performs multiple autofix passes over the text until as many fixes as possible + * have been applied. + * @param {string} text The source text to apply fixes to. + * @param {Object} config The ESLint config object to use. + * @param {Object} options The ESLint options object to use. + * @param {string} options.filename The filename from which the text was read. + * @param {boolean} options.allowInlineConfig Flag indicating if inline comments + * should be allowed. + * @returns {Object} The result of the fix operation as returned from the + * SourceCodeFixer. + */ + verifyAndFix(text, config, options) { + let messages = [], + fixedResult, + fixed = false, + passNumber = 0; + + /** + * This loop continues until one of the following is true: + * + * 1. No more fixes have been applied. + * 2. Ten passes have been made. + * + * That means anytime a fix is successfully applied, there will be another pass. + * Essentially, guaranteeing a minimum of two passes. + */ + do { + passNumber++; + + debug(`Linting code for ${options.filename} (pass ${passNumber})`); + messages = this.verify(text, config, options); + + debug(`Generating fixed text for ${options.filename} (pass ${passNumber})`); + fixedResult = SourceCodeFixer.applyFixes(this.getSourceCode(), messages); + + // stop if there are any syntax errors. + // 'fixedResult.output' is a empty string. + if (messages.length === 1 && messages[0].fatal) { + break; + } + + // keep track if any fixes were ever applied - important for return value + fixed = fixed || fixedResult.fixed; + + // update to use the fixed output instead of the original text + text = fixedResult.output; + + } while ( + fixedResult.fixed && + passNumber < MAX_AUTOFIX_PASSES + ); + + /* + * If the last result had fixes, we need to lint again to be sure we have + * the most up-to-date information. + */ + if (fixedResult.fixed) { + fixedResult.messages = this.verify(text, config, options); + } + + // ensure the last result properly reflects if fixes were done + fixedResult.fixed = fixed; + fixedResult.output = text; + + return fixedResult; + } } // methods that exist on SourceCode object diff --git a/tests/lib/linter.js b/tests/lib/linter.js index ed5fb2238cc..9c47fafe521 100644 --- a/tests/lib/linter.js +++ b/tests/lib/linter.js @@ -70,4 +70,18 @@ describe("Linter", () => { }); }); }); + + describe("verifyAndFix", () => { + it("Fixes the code", () => { + const linter = new Linter(); + const messages = linter.verifyAndFix("var a", { + rules: { + semi: 2 + } + }, { filename: "test.js" }); + + assert.equal(messages.output, "var a;", "Fixes were applied correctly"); + assert.isTrue(messages.fixed); + }); + }); });