Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update: Add Fixer method to Linter API #8631

Merged
merged 3 commits into from Jun 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 31 additions & 0 deletions docs/developer-guide/nodejs-api.md
Expand Up @@ -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`.
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
14 changes: 14 additions & 0 deletions tests/lib/linter.js
Expand Up @@ -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);
});
});
});