Skip to content

Commit

Permalink
Update: Add Fixer method to Linter API
Browse files Browse the repository at this point in the history
  • Loading branch information
gyandeeps committed Jun 1, 2017
1 parent 4df33e7 commit 5dbacb2
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 80 deletions.
32 changes: 32 additions & 0 deletions docs/developer-guide/nodejs-api.md
Expand Up @@ -147,6 +147,38 @@ 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` method when it comes to verifying the code against the rules but it also runs the fixer logic from all the fix compatible rules and return the object which has the fixed source code in it.
The lifecycle is that it verifies the code and then runs the fix on it and this is done at most 10 times right now.

```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`.
Expand Down
80 changes: 2 additions & 78 deletions lib/cli-engine.js
Expand Up @@ -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");
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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, {
Expand Down
74 changes: 72 additions & 2 deletions lib/linter.js
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 5dbacb2

Please sign in to comment.