tools: update ESLint to v4.0.0
Update ESLint and configuration to version 4.0.0. PR-URL: https://github.com/nodejs/node/pull/13645 Reviewed-By: Teddy Katz <teddy.katz@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: Sam Roberts <vieuxtech@gmail.com> Reviewed-By: Alexey Orlenko <eaglexrlnk@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
22436b055d
commit
3ad90a4ac8
@ -40,7 +40,7 @@ rules:
|
||||
eqeqeq: [2, smart]
|
||||
no-fallthrough: 2
|
||||
no-global-assign: 2
|
||||
no-multi-spaces: 2
|
||||
no-multi-spaces: [2, {ignoreEOLComments: true}]
|
||||
no-octal: 2
|
||||
no-proto: 2
|
||||
no-redeclare: 2
|
||||
@ -98,11 +98,18 @@ rules:
|
||||
func-call-spacing: 2
|
||||
func-name-matching: 2
|
||||
func-style: [2, declaration, {allowArrowFunctions: true}]
|
||||
indent: [2, 2, {ArrayExpression: first,
|
||||
CallExpression: {arguments: first},
|
||||
MemberExpression: 1,
|
||||
ObjectExpression: first,
|
||||
SwitchCase: 1}]
|
||||
# indent: [2, 2, {ArrayExpression: first,
|
||||
# CallExpression: {arguments: first},
|
||||
# FunctionDeclaration: {parameters: first},
|
||||
# FunctionExpression: {parameters: first},
|
||||
# MemberExpression: off,
|
||||
# ObjectExpression: first,
|
||||
# SwitchCase: 1}]
|
||||
indent-legacy: [2, 2, {ArrayExpression: first,
|
||||
CallExpression: {arguments: first},
|
||||
MemberExpression: 1,
|
||||
ObjectExpression: first,
|
||||
SwitchCase: 1}]
|
||||
key-spacing: [2, {mode: minimum}]
|
||||
keyword-spacing: 2
|
||||
linebreak-style: [2, unix]
|
||||
@ -122,7 +129,7 @@ rules:
|
||||
}, {
|
||||
selector: "ThrowStatement > CallExpression[callee.name=/Error$/]",
|
||||
message: "Use new keyword when throwing an Error."
|
||||
}]
|
||||
}]
|
||||
no-tabs: 2
|
||||
no-trailing-spaces: 2
|
||||
one-var-declaration-per-line: 2
|
||||
@ -131,7 +138,11 @@ rules:
|
||||
semi: 2
|
||||
semi-spacing: 2
|
||||
space-before-blocks: [2, always]
|
||||
space-before-function-paren: [2, never]
|
||||
space-before-function-paren: [2, {
|
||||
"anonymous": "never",
|
||||
"named": "never",
|
||||
"asyncArrow": "always"
|
||||
}]
|
||||
space-in-parens: [2, never]
|
||||
space-infix-ops: 2
|
||||
space-unary-ops: 2
|
||||
|
@ -19,7 +19,7 @@
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
/* eslint-disable indent, no-tabs */
|
||||
/* eslint-disable indent-legacy, no-tabs */
|
||||
'use strict';
|
||||
require('../common');
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,3 @@
|
||||
ESLint
|
||||
Copyright JS Foundation and other contributors, https://js.foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
|
@ -5,6 +5,7 @@
|
||||
[![Downloads][downloads-image]][downloads-url]
|
||||
[](https://www.bountysource.com/trackers/282608-eslint?utm_source=282608&utm_medium=shield&utm_campaign=TRACKER_BADGE)
|
||||
[](https://gitter.im/eslint/eslint?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint?ref=badge_shield)
|
||||
|
||||
# ESLint
|
||||
|
||||
@ -13,7 +14,7 @@
|
||||
[Rules](http://eslint.org/docs/rules/) |
|
||||
[Contributing](http://eslint.org/docs/developer-guide/contributing) |
|
||||
[Reporting Bugs](http://eslint.org/docs/developer-guide/contributing/reporting-bugs) |
|
||||
[Code of Conduct](https://js.foundation/conduct/) |
|
||||
[Code of Conduct](https://js.foundation/community/code-of-conduct) |
|
||||
[Twitter](https://twitter.com/geteslint) |
|
||||
[Mailing List](https://groups.google.com/group/eslint) |
|
||||
[Chat Room](https://gitter.im/eslint/eslint)
|
||||
@ -123,7 +124,7 @@ These folks keep the project moving and are resources for help.
|
||||
* Michael Ficarra ([@michaelficarra](https://github.com/michaelficarra))
|
||||
* Mark Pedrotti ([@pedrottimark](https://github.com/pedrottimark))
|
||||
* Oleg Gaidarenko ([@markelog](https://github.com/markelog))
|
||||
* Mike Sherov [@mikesherov](https://github.com/mikesherov))
|
||||
* Mike Sherov ([@mikesherov](https://github.com/mikesherov))
|
||||
* Henry Zhu ([@hzoo](https://github.com/hzoo))
|
||||
* Marat Dulin ([@mdevils](https://github.com/mdevils))
|
||||
* Alexej Yaroshevich ([@zxqfox](https://github.com/zxqfox))
|
||||
@ -172,6 +173,10 @@ ESLint follows [semantic versioning](http://semver.org). However, due to the nat
|
||||
|
||||
According to our policy, any minor update may report more errors than the previous release (ex: from a bug fix). As such, we recommend using the tilde (`~`) in `package.json` e.g. `"eslint": "~3.1.0"` to guarantee the results of your builds.
|
||||
|
||||
## License
|
||||
|
||||
[](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint?ref=badge_large)
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
### How is ESLint different from JSHint?
|
||||
@ -205,7 +210,7 @@ Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [confi
|
||||
|
||||
### What about ECMAScript 6 support?
|
||||
|
||||
ESLint has full support for ECMAScript 6. By default, this support is off. You can enable ECMAScript 6 support through [configuration](http://eslint.org/docs/user-guide/configuring).
|
||||
ESLint has full support for ECMAScript 6. By default, this support is off. You can enable ECMAScript 6 syntax and global variables through [configuration](http://eslint.org/docs/user-guide/configuring).
|
||||
|
||||
### What about experimental features?
|
||||
|
||||
|
@ -61,14 +61,12 @@ if (useStdIn) {
|
||||
} else if (init) {
|
||||
const configInit = require("../lib/config/config-initializer");
|
||||
|
||||
configInit.initializeConfig(err => {
|
||||
if (err) {
|
||||
process.exitCode = 1;
|
||||
console.error(err.message);
|
||||
console.error(err.stack);
|
||||
} else {
|
||||
process.exitCode = 0;
|
||||
}
|
||||
configInit.initializeConfig().then(() => {
|
||||
process.exitCode = 0;
|
||||
}).catch(err => {
|
||||
process.exitCode = 1;
|
||||
console.error(err.message);
|
||||
console.error(err.stack);
|
||||
});
|
||||
} else {
|
||||
process.exitCode = cli.execute(process.argv);
|
||||
|
15
tools/eslint/conf/config-schema.json
Normal file
15
tools/eslint/conf/config-schema.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"root": { "type": "boolean" },
|
||||
"globals": { "type": ["object"] },
|
||||
"parser": { "type": ["string", "null"] },
|
||||
"env": { "type": "object" },
|
||||
"plugins": { "type": ["array"] },
|
||||
"settings": { "type": "object" },
|
||||
"extends": { "type": ["string", "array"] },
|
||||
"rules": { "type": "object" },
|
||||
"parserOptions": { "type": "object" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
@ -12,11 +12,9 @@ module.exports = {
|
||||
useEslintrc: true,
|
||||
envs: [],
|
||||
globals: [],
|
||||
rules: {},
|
||||
extensions: [".js"],
|
||||
ignore: true,
|
||||
ignorePath: null,
|
||||
parser: "", // must be empty
|
||||
cache: false,
|
||||
|
||||
// in order to honor the cacheFile option if specified
|
33
tools/eslint/conf/default-config-options.js
Normal file
33
tools/eslint/conf/default-config-options.js
Normal file
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* @fileoverview Default config options
|
||||
* @author Teddy Katz
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Freezes an object and all its nested properties
|
||||
* @param {Object} obj The object to deeply freeze
|
||||
* @returns {Object} `obj` after freezing it
|
||||
*/
|
||||
function deepFreeze(obj) {
|
||||
if (obj === null || typeof obj !== "object") {
|
||||
return obj;
|
||||
}
|
||||
|
||||
Object.keys(obj).map(key => obj[key]).forEach(deepFreeze);
|
||||
return Object.freeze(obj);
|
||||
}
|
||||
|
||||
module.exports = deepFreeze({
|
||||
env: {},
|
||||
globals: {},
|
||||
rules: {},
|
||||
settings: {},
|
||||
parser: "espree",
|
||||
parserOptions: {
|
||||
ecmaVersion: 5,
|
||||
sourceType: "script",
|
||||
ecmaFeatures: {}
|
||||
}
|
||||
});
|
@ -10,7 +10,8 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const load = require("../lib/load-rules"),
|
||||
rules = require("../lib/rules");
|
||||
Rules = require("../lib/rules");
|
||||
const rules = new Rules();
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
|
@ -10,15 +10,14 @@
|
||||
/* eslint-disable sort-keys */
|
||||
|
||||
module.exports = {
|
||||
parser: "espree",
|
||||
ecmaFeatures: {},
|
||||
|
||||
rules: {
|
||||
|
||||
/* eslint-enable sort-keys */
|
||||
"accessor-pairs": "off",
|
||||
"array-bracket-newline": "off",
|
||||
"array-bracket-spacing": "off",
|
||||
"array-callback-return": "off",
|
||||
"array-element-newline": "off",
|
||||
"arrow-body-style": "off",
|
||||
"arrow-parens": "off",
|
||||
"arrow-spacing": "off",
|
||||
@ -43,6 +42,7 @@ module.exports = {
|
||||
"dot-notation": "off",
|
||||
"eol-last": "off",
|
||||
"eqeqeq": "off",
|
||||
"for-direction": "off",
|
||||
"func-call-spacing": "off",
|
||||
"func-name-matching": "off",
|
||||
"func-names": "off",
|
||||
@ -55,6 +55,7 @@ module.exports = {
|
||||
"id-length": "off",
|
||||
"id-match": "off",
|
||||
"indent": "off",
|
||||
"indent-legacy": "off",
|
||||
"init-declarations": "off",
|
||||
"jsx-quotes": "off",
|
||||
"key-spacing": "off",
|
||||
@ -80,11 +81,12 @@ module.exports = {
|
||||
"no-array-constructor": "off",
|
||||
"no-await-in-loop": "off",
|
||||
"no-bitwise": "off",
|
||||
"no-buffer-constructor": "off",
|
||||
"no-caller": "off",
|
||||
"no-case-declarations": "error",
|
||||
"no-catch-shadow": "off",
|
||||
"no-class-assign": "error",
|
||||
"no-compare-neg-zero": "off",
|
||||
"no-compare-neg-zero": "error",
|
||||
"no-cond-assign": "error",
|
||||
"no-confusing-arrow": "off",
|
||||
"no-console": "error",
|
||||
@ -202,7 +204,7 @@ module.exports = {
|
||||
"no-useless-computed-key": "off",
|
||||
"no-useless-concat": "off",
|
||||
"no-useless-constructor": "off",
|
||||
"no-useless-escape": "off",
|
||||
"no-useless-escape": "error",
|
||||
"no-useless-rename": "off",
|
||||
"no-useless-return": "off",
|
||||
"no-var": "off",
|
||||
@ -212,7 +214,7 @@ module.exports = {
|
||||
"no-with": "off",
|
||||
"nonblock-statement-body-position": "off",
|
||||
"object-curly-newline": "off",
|
||||
"object-curly-spacing": ["off", "never"],
|
||||
"object-curly-spacing": "off",
|
||||
"object-property-newline": "off",
|
||||
"object-shorthand": "off",
|
||||
"one-var": "off",
|
||||
@ -220,6 +222,7 @@ module.exports = {
|
||||
"operator-assignment": "off",
|
||||
"operator-linebreak": "off",
|
||||
"padded-blocks": "off",
|
||||
"padding-line-between-statements": "off",
|
||||
"prefer-arrow-callback": "off",
|
||||
"prefer-const": "off",
|
||||
"prefer-destructuring": "off",
|
||||
@ -238,6 +241,7 @@ module.exports = {
|
||||
"rest-spread-spacing": "off",
|
||||
"semi": "off",
|
||||
"semi-spacing": "off",
|
||||
"semi-style": "off",
|
||||
"sort-imports": "off",
|
||||
"sort-keys": "off",
|
||||
"sort-vars": "off",
|
||||
@ -248,6 +252,7 @@ module.exports = {
|
||||
"space-unary-ops": "off",
|
||||
"spaced-comment": "off",
|
||||
"strict": "off",
|
||||
"switch-colon-spacing": "off",
|
||||
"symbol-description": "off",
|
||||
"template-curly-spacing": "off",
|
||||
"template-tag-spacing": "off",
|
||||
|
@ -5,8 +5,11 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const Linter = require("./linter");
|
||||
|
||||
module.exports = {
|
||||
linter: require("./eslint"),
|
||||
linter: new Linter(),
|
||||
Linter,
|
||||
CLIEngine: require("./cli-engine"),
|
||||
RuleTester: require("./testers/rule-tester"),
|
||||
SourceCode: require("./util/source-code")
|
||||
|
@ -10,6 +10,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const esutils = require("esutils");
|
||||
const espree = require("espree");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
@ -27,6 +28,7 @@ const thisTagPattern = /^[\s*]*@this/m;
|
||||
const COMMENTS_IGNORE_PATTERN = /^\s*(?:eslint|jshint\s+|jslint\s+|istanbul\s+|globals?\s+|exported\s+|jscs)/;
|
||||
const LINEBREAKS = new Set(["\r\n", "\r", "\n", "\u2028", "\u2029"]);
|
||||
const LINEBREAK_MATCHER = /\r\n|[\r\n\u2028\u2029]/;
|
||||
const SHEBANG_MATCHER = /^#!([^\r\n]+)/;
|
||||
|
||||
// A set of node types that can contain a list of statements
|
||||
const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "SwitchCase"]);
|
||||
@ -243,7 +245,7 @@ function hasJSDocThisTag(node, sourceCode) {
|
||||
// because callbacks don't have its JSDoc comment.
|
||||
// e.g.
|
||||
// sinon.test(/* @this sinon.Sandbox */function() { this.spy(); });
|
||||
return sourceCode.getComments(node).leading.some(comment => thisTagPattern.test(comment.value));
|
||||
return sourceCode.getCommentsBefore(node).some(comment => thisTagPattern.test(comment.value));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -412,6 +414,7 @@ module.exports = {
|
||||
COMMENTS_IGNORE_PATTERN,
|
||||
LINEBREAKS,
|
||||
LINEBREAK_MATCHER,
|
||||
SHEBANG_MATCHER,
|
||||
STATEMENT_LIST_PARENTS,
|
||||
|
||||
/**
|
||||
@ -524,7 +527,7 @@ module.exports = {
|
||||
|
||||
/**
|
||||
* Returns whether the provided node is an ESLint directive comment or not
|
||||
* @param {LineComment|BlockComment} node The node to be checked
|
||||
* @param {Line|Block} node The comment token to be checked
|
||||
* @returns {boolean} `true` if the node is an ESLint directive comment
|
||||
*/
|
||||
isDirectiveComment(node) {
|
||||
@ -556,9 +559,9 @@ module.exports = {
|
||||
/**
|
||||
* Finds the variable by a given name in a given scope and its upper scopes.
|
||||
*
|
||||
* @param {escope.Scope} initScope - A scope to start find.
|
||||
* @param {eslint-scope.Scope} initScope - A scope to start find.
|
||||
* @param {string} name - A variable name to find.
|
||||
* @returns {escope.Variable|null} A found variable or `null`.
|
||||
* @returns {eslint-scope.Variable|null} A found variable or `null`.
|
||||
*/
|
||||
getVariableByName(initScope, name) {
|
||||
let scope = initScope;
|
||||
@ -1252,5 +1255,52 @@ module.exports = {
|
||||
* `node.regex` instead. Also see: https://github.com/eslint/eslint/issues/8020
|
||||
*/
|
||||
return node.type === "Literal" && node.value === null && !node.regex;
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines whether two tokens can safely be placed next to each other without merging into a single token
|
||||
* @param {Token|string} leftValue The left token. If this is a string, it will be tokenized and the last token will be used.
|
||||
* @param {Token|string} rightValue The right token. If this is a string, it will be tokenized and the first token will be used.
|
||||
* @returns {boolean} If the tokens cannot be safely placed next to each other, returns `false`. If the tokens can be placed
|
||||
* next to each other, behavior is undefined (although it should return `true` in most cases).
|
||||
*/
|
||||
canTokensBeAdjacent(leftValue, rightValue) {
|
||||
let leftToken;
|
||||
|
||||
if (typeof leftValue === "string") {
|
||||
const leftTokens = espree.tokenize(leftValue, { ecmaVersion: 2015 });
|
||||
|
||||
leftToken = leftTokens[leftTokens.length - 1];
|
||||
} else {
|
||||
leftToken = leftValue;
|
||||
}
|
||||
|
||||
const rightToken = typeof rightValue === "string" ? espree.tokenize(rightValue, { ecmaVersion: 2015 })[0] : rightValue;
|
||||
|
||||
if (leftToken.type === "Punctuator" || rightToken.type === "Punctuator") {
|
||||
if (leftToken.type === "Punctuator" && rightToken.type === "Punctuator") {
|
||||
const PLUS_TOKENS = new Set(["+", "++"]);
|
||||
const MINUS_TOKENS = new Set(["-", "--"]);
|
||||
|
||||
return !(
|
||||
PLUS_TOKENS.has(leftToken.value) && PLUS_TOKENS.has(rightToken.value) ||
|
||||
MINUS_TOKENS.has(leftToken.value) && MINUS_TOKENS.has(rightToken.value)
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
leftToken.type === "String" || rightToken.type === "String" ||
|
||||
leftToken.type === "Template" || rightToken.type === "Template"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (leftToken.type !== "Numeric" && rightToken.type === "Numeric" && rightToken.value.startsWith(".")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
@ -17,12 +17,10 @@
|
||||
|
||||
const fs = require("fs"),
|
||||
path = require("path"),
|
||||
rules = require("./rules"),
|
||||
eslint = require("./eslint"),
|
||||
defaultOptions = require("../conf/cli-options"),
|
||||
defaultOptions = require("../conf/default-cli-options"),
|
||||
Linter = require("./linter"),
|
||||
IgnoredPaths = require("./ignored-paths"),
|
||||
Config = require("./config"),
|
||||
Plugins = require("./config/plugins"),
|
||||
fileEntryCache = require("file-entry-cache"),
|
||||
globUtil = require("./util/glob-util"),
|
||||
SourceCodeFixer = require("./util/source-code-fixer"),
|
||||
@ -73,8 +71,10 @@ const debug = require("debug")("eslint:cli-engine");
|
||||
* @typedef {Object} LintResult
|
||||
* @property {string} filePath The path to the file that was linted.
|
||||
* @property {LintMessage[]} messages All of the messages for the result.
|
||||
* @property {number} errorCount Number or errors for the result.
|
||||
* @property {number} warningCount Number or warnings for the result.
|
||||
* @property {number} errorCount Number of errors for the result.
|
||||
* @property {number} warningCount Number of warnings for the result.
|
||||
* @property {number} fixableErrorCount Number of fixable errors for the result.
|
||||
* @property {number} fixableWarningCount Number of fixable warnings for the result.
|
||||
* @property {string=} [source] The source code of the file that was linted.
|
||||
* @property {string=} [output] The source code of the file that was linted, with as many fixes applied as possible.
|
||||
*/
|
||||
@ -93,13 +93,21 @@ function calculateStatsPerFile(messages) {
|
||||
return messages.reduce((stat, message) => {
|
||||
if (message.fatal || message.severity === 2) {
|
||||
stat.errorCount++;
|
||||
if (message.fix) {
|
||||
stat.fixableErrorCount++;
|
||||
}
|
||||
} else {
|
||||
stat.warningCount++;
|
||||
if (message.fix) {
|
||||
stat.fixableWarningCount++;
|
||||
}
|
||||
}
|
||||
return stat;
|
||||
}, {
|
||||
errorCount: 0,
|
||||
warningCount: 0
|
||||
warningCount: 0,
|
||||
fixableErrorCount: 0,
|
||||
fixableWarningCount: 0
|
||||
});
|
||||
}
|
||||
|
||||
@ -113,10 +121,14 @@ function calculateStatsPerRun(results) {
|
||||
return results.reduce((stat, result) => {
|
||||
stat.errorCount += result.errorCount;
|
||||
stat.warningCount += result.warningCount;
|
||||
stat.fixableErrorCount += result.fixableErrorCount;
|
||||
stat.fixableWarningCount += result.fixableWarningCount;
|
||||
return stat;
|
||||
}, {
|
||||
errorCount: 0,
|
||||
warningCount: 0
|
||||
warningCount: 0,
|
||||
fixableErrorCount: 0,
|
||||
fixableWarningCount: 0
|
||||
});
|
||||
}
|
||||
|
||||
@ -129,11 +141,12 @@ function calculateStatsPerRun(results) {
|
||||
* @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) {
|
||||
function multipassFix(text, config, options, linter) {
|
||||
const MAX_PASSES = 10;
|
||||
let messages = [],
|
||||
fixedResult,
|
||||
@ -153,10 +166,10 @@ function multipassFix(text, config, options) {
|
||||
passNumber++;
|
||||
|
||||
debug(`Linting code for ${options.filename} (pass ${passNumber})`);
|
||||
messages = eslint.verify(text, config, options);
|
||||
messages = linter.verify(text, config, options);
|
||||
|
||||
debug(`Generating fixed text for ${options.filename} (pass ${passNumber})`);
|
||||
fixedResult = SourceCodeFixer.applyFixes(eslint.getSourceCode(), messages);
|
||||
fixedResult = SourceCodeFixer.applyFixes(linter.getSourceCode(), messages);
|
||||
|
||||
// stop if there are any syntax errors.
|
||||
// 'fixedResult.output' is a empty string.
|
||||
@ -181,7 +194,7 @@ function multipassFix(text, config, options) {
|
||||
* the most up-to-date information.
|
||||
*/
|
||||
if (fixedResult.fixed) {
|
||||
fixedResult.messages = eslint.verify(text, config, options);
|
||||
fixedResult.messages = linter.verify(text, config, options);
|
||||
}
|
||||
|
||||
|
||||
@ -200,13 +213,14 @@ function multipassFix(text, config, options) {
|
||||
* @param {string} filename An optional string representing the texts filename.
|
||||
* @param {boolean} fix Indicates if fixes should be processed.
|
||||
* @param {boolean} allowInlineConfig Allow/ignore comments that change config.
|
||||
* @param {Linter} linter Linter context
|
||||
* @returns {LintResult} The results for linting on this text.
|
||||
* @private
|
||||
*/
|
||||
function processText(text, configHelper, filename, fix, allowInlineConfig) {
|
||||
function processText(text, configHelper, filename, fix, allowInlineConfig, linter) {
|
||||
|
||||
// clear all existing settings for a new file
|
||||
eslint.reset();
|
||||
linter.reset();
|
||||
|
||||
let filePath,
|
||||
messages,
|
||||
@ -224,10 +238,10 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) {
|
||||
const config = configHelper.getConfig(filePath);
|
||||
|
||||
if (config.plugins) {
|
||||
Plugins.loadAll(config.plugins);
|
||||
configHelper.plugins.loadAll(config.plugins);
|
||||
}
|
||||
|
||||
const loadedPlugins = Plugins.getAll();
|
||||
const loadedPlugins = configHelper.plugins.getAll();
|
||||
|
||||
for (const plugin in loadedPlugins) {
|
||||
if (loadedPlugins[plugin].processors && Object.keys(loadedPlugins[plugin].processors).indexOf(fileExtension) >= 0) {
|
||||
@ -242,7 +256,7 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) {
|
||||
const unprocessedMessages = [];
|
||||
|
||||
parsedBlocks.forEach(block => {
|
||||
unprocessedMessages.push(eslint.verify(block, config, {
|
||||
unprocessedMessages.push(linter.verify(block, config, {
|
||||
filename,
|
||||
allowInlineConfig
|
||||
}));
|
||||
@ -258,10 +272,10 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) {
|
||||
fixedResult = multipassFix(text, config, {
|
||||
filename,
|
||||
allowInlineConfig
|
||||
});
|
||||
}, linter);
|
||||
messages = fixedResult.messages;
|
||||
} else {
|
||||
messages = eslint.verify(text, config, {
|
||||
messages = linter.verify(text, config, {
|
||||
filename,
|
||||
allowInlineConfig
|
||||
});
|
||||
@ -274,7 +288,9 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) {
|
||||
filePath: filename,
|
||||
messages,
|
||||
errorCount: stats.errorCount,
|
||||
warningCount: stats.warningCount
|
||||
warningCount: stats.warningCount,
|
||||
fixableErrorCount: stats.fixableErrorCount,
|
||||
fixableWarningCount: stats.fixableWarningCount
|
||||
};
|
||||
|
||||
if (fixedResult && fixedResult.fixed) {
|
||||
@ -294,13 +310,14 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) {
|
||||
* @param {string} filename The filename of the file being checked.
|
||||
* @param {Object} configHelper The configuration options for ESLint.
|
||||
* @param {Object} options The CLIEngine options object.
|
||||
* @param {Linter} linter Linter context
|
||||
* @returns {LintResult} The results for linting on this file.
|
||||
* @private
|
||||
*/
|
||||
function processFile(filename, configHelper, options) {
|
||||
function processFile(filename, configHelper, options, linter) {
|
||||
|
||||
const text = fs.readFileSync(path.resolve(filename), "utf8"),
|
||||
result = processText(text, configHelper, filename, options.fix, options.allowInlineConfig);
|
||||
result = processText(text, configHelper, filename, options.fix, options.allowInlineConfig, linter);
|
||||
|
||||
return result;
|
||||
|
||||
@ -339,7 +356,9 @@ function createIgnoreResult(filePath, baseDir) {
|
||||
}
|
||||
],
|
||||
errorCount: 0,
|
||||
warningCount: 1
|
||||
warningCount: 1,
|
||||
fixableErrorCount: 0,
|
||||
fixableWarningCount: 0
|
||||
};
|
||||
}
|
||||
|
||||
@ -432,141 +451,104 @@ function getCacheFile(cacheFile, cwd) {
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Creates a new instance of the core CLI engine.
|
||||
* @param {CLIEngineOptions} options The options for this instance.
|
||||
* @constructor
|
||||
*/
|
||||
function CLIEngine(options) {
|
||||
|
||||
options = Object.assign(
|
||||
Object.create(null),
|
||||
defaultOptions,
|
||||
{ cwd: process.cwd() },
|
||||
options
|
||||
);
|
||||
class CLIEngine {
|
||||
|
||||
/**
|
||||
* Stored options for this instance
|
||||
* @type {Object}
|
||||
* Creates a new instance of the core CLI engine.
|
||||
* @param {CLIEngineOptions} options The options for this instance.
|
||||
* @constructor
|
||||
*/
|
||||
this.options = options;
|
||||
constructor(options) {
|
||||
|
||||
const cacheFile = getCacheFile(this.options.cacheLocation || this.options.cacheFile, this.options.cwd);
|
||||
options = Object.assign(
|
||||
Object.create(null),
|
||||
defaultOptions,
|
||||
{ cwd: process.cwd() },
|
||||
options
|
||||
);
|
||||
|
||||
/**
|
||||
* Stored options for this instance
|
||||
* @type {Object}
|
||||
*/
|
||||
this.options = options;
|
||||
this.linter = new Linter();
|
||||
|
||||
const cacheFile = getCacheFile(this.options.cacheLocation || this.options.cacheFile, this.options.cwd);
|
||||
|
||||
/**
|
||||
* Cache used to avoid operating on files that haven't changed since the
|
||||
* last successful execution (e.g., file passed linting with no errors and
|
||||
* no warnings).
|
||||
* @type {Object}
|
||||
*/
|
||||
this._fileCache = fileEntryCache.create(cacheFile);
|
||||
|
||||
// load in additional rules
|
||||
if (this.options.rulePaths) {
|
||||
const cwd = this.options.cwd;
|
||||
|
||||
this.options.rulePaths.forEach(rulesdir => {
|
||||
debug(`Loading rules from ${rulesdir}`);
|
||||
this.linter.rules.load(rulesdir, cwd);
|
||||
});
|
||||
}
|
||||
|
||||
Object.keys(this.options.rules || {}).forEach(name => {
|
||||
validator.validateRuleOptions(name, this.options.rules[name], "CLI", this.linter.rules);
|
||||
});
|
||||
|
||||
this.config = new Config(this.options, this.linter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache used to avoid operating on files that haven't changed since the
|
||||
* last successful execution (e.g., file passed linting with no errors and
|
||||
* no warnings).
|
||||
* @type {Object}
|
||||
* Returns results that only contains errors.
|
||||
* @param {LintResult[]} results The results to filter.
|
||||
* @returns {LintResult[]} The filtered results.
|
||||
*/
|
||||
this._fileCache = fileEntryCache.create(cacheFile);
|
||||
static getErrorResults(results) {
|
||||
const filtered = [];
|
||||
|
||||
// load in additional rules
|
||||
if (this.options.rulePaths) {
|
||||
const cwd = this.options.cwd;
|
||||
results.forEach(result => {
|
||||
const filteredMessages = result.messages.filter(isErrorMessage);
|
||||
|
||||
this.options.rulePaths.forEach(rulesdir => {
|
||||
debug(`Loading rules from ${rulesdir}`);
|
||||
rules.load(rulesdir, cwd);
|
||||
if (filteredMessages.length > 0) {
|
||||
filtered.push(
|
||||
Object.assign(result, {
|
||||
messages: filteredMessages,
|
||||
errorCount: filteredMessages.length,
|
||||
warningCount: 0,
|
||||
fixableErrorCount: result.fixableErrorCount,
|
||||
fixableWarningCount: 0
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs fixes from the given results to files.
|
||||
* @param {Object} report The report object created by CLIEngine.
|
||||
* @returns {void}
|
||||
*/
|
||||
static outputFixes(report) {
|
||||
report.results.filter(result => result.hasOwnProperty("output")).forEach(result => {
|
||||
fs.writeFileSync(result.filePath, result.output);
|
||||
});
|
||||
}
|
||||
|
||||
Object.keys(this.options.rules || {}).forEach(name => {
|
||||
validator.validateRuleOptions(name, this.options.rules[name], "CLI");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatter representing the given format or null if no formatter
|
||||
* with the given name can be found.
|
||||
* @param {string} [format] The name of the format to load or the path to a
|
||||
* custom formatter.
|
||||
* @returns {Function} The formatter function or null if not found.
|
||||
*/
|
||||
CLIEngine.getFormatter = function(format) {
|
||||
|
||||
let formatterPath;
|
||||
|
||||
// default is stylish
|
||||
format = format || "stylish";
|
||||
|
||||
// only strings are valid formatters
|
||||
if (typeof format === "string") {
|
||||
|
||||
// replace \ with / for Windows compatibility
|
||||
format = format.replace(/\\/g, "/");
|
||||
|
||||
// if there's a slash, then it's a file
|
||||
if (format.indexOf("/") > -1) {
|
||||
const cwd = this.options ? this.options.cwd : process.cwd();
|
||||
|
||||
formatterPath = path.resolve(cwd, format);
|
||||
} else {
|
||||
formatterPath = `./formatters/${format}`;
|
||||
}
|
||||
|
||||
try {
|
||||
return require(formatterPath);
|
||||
} catch (ex) {
|
||||
ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`;
|
||||
throw ex;
|
||||
}
|
||||
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns results that only contains errors.
|
||||
* @param {LintResult[]} results The results to filter.
|
||||
* @returns {LintResult[]} The filtered results.
|
||||
*/
|
||||
CLIEngine.getErrorResults = function(results) {
|
||||
const filtered = [];
|
||||
|
||||
results.forEach(result => {
|
||||
const filteredMessages = result.messages.filter(isErrorMessage);
|
||||
|
||||
if (filteredMessages.length > 0) {
|
||||
filtered.push(
|
||||
Object.assign(result, {
|
||||
messages: filteredMessages,
|
||||
errorCount: filteredMessages.length,
|
||||
warningCount: 0
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return filtered;
|
||||
};
|
||||
|
||||
/**
|
||||
* Outputs fixes from the given results to files.
|
||||
* @param {Object} report The report object created by CLIEngine.
|
||||
* @returns {void}
|
||||
*/
|
||||
CLIEngine.outputFixes = function(report) {
|
||||
report.results.filter(result => result.hasOwnProperty("output")).forEach(result => {
|
||||
fs.writeFileSync(result.filePath, result.output);
|
||||
});
|
||||
};
|
||||
|
||||
CLIEngine.prototype = {
|
||||
|
||||
constructor: CLIEngine,
|
||||
|
||||
/**
|
||||
* Add a plugin by passing it's configuration
|
||||
* Add a plugin by passing its configuration
|
||||
* @param {string} name Name of the plugin.
|
||||
* @param {Object} pluginobject Plugin configuration object.
|
||||
* @returns {void}
|
||||
*/
|
||||
addPlugin(name, pluginobject) {
|
||||
Plugins.define(name, pluginobject);
|
||||
},
|
||||
this.config.plugins.define(name, pluginobject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the patterns passed into executeOnFiles() into glob-based patterns
|
||||
@ -576,7 +558,7 @@ CLIEngine.prototype = {
|
||||
*/
|
||||
resolveFileGlobPatterns(patterns) {
|
||||
return globUtil.resolveFileGlobPatterns(patterns, this.options);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the current configuration on an array of file and directory names.
|
||||
@ -587,7 +569,7 @@ CLIEngine.prototype = {
|
||||
const results = [],
|
||||
options = this.options,
|
||||
fileCache = this._fileCache,
|
||||
configHelper = new Config(options);
|
||||
configHelper = this.config;
|
||||
let prevConfig; // the previous configuration used
|
||||
|
||||
/**
|
||||
@ -624,9 +606,10 @@ CLIEngine.prototype = {
|
||||
* unsupported file extensions and any files that are already linted.
|
||||
* @param {string} filename The resolved filename of the file to be linted
|
||||
* @param {boolean} warnIgnored always warn when a file is ignored
|
||||
* @param {Linter} linter Linter context
|
||||
* @returns {void}
|
||||
*/
|
||||
function executeOnFile(filename, warnIgnored) {
|
||||
function executeOnFile(filename, warnIgnored, linter) {
|
||||
let hashOfConfig,
|
||||
descriptor;
|
||||
|
||||
@ -669,7 +652,7 @@ CLIEngine.prototype = {
|
||||
|
||||
debug(`Processing ${filename}`);
|
||||
|
||||
const res = processFile(filename, configHelper, options);
|
||||
const res = processFile(filename, configHelper, options, linter);
|
||||
|
||||
if (options.cache) {
|
||||
|
||||
@ -702,12 +685,11 @@ CLIEngine.prototype = {
|
||||
const startTime = Date.now();
|
||||
|
||||
|
||||
|
||||
patterns = this.resolveFileGlobPatterns(patterns);
|
||||
const fileList = globUtil.listFilesToProcess(patterns, options);
|
||||
|
||||
fileList.forEach(fileInfo => {
|
||||
executeOnFile(fileInfo.filename, fileInfo.ignored);
|
||||
executeOnFile(fileInfo.filename, fileInfo.ignored, this.linter);
|
||||
});
|
||||
|
||||
const stats = calculateStatsPerRun(results);
|
||||
@ -723,9 +705,11 @@ CLIEngine.prototype = {
|
||||
return {
|
||||
results,
|
||||
errorCount: stats.errorCount,
|
||||
warningCount: stats.warningCount
|
||||
warningCount: stats.warningCount,
|
||||
fixableErrorCount: stats.fixableErrorCount,
|
||||
fixableWarningCount: stats.fixableWarningCount
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the current configuration on text.
|
||||
@ -738,7 +722,7 @@ CLIEngine.prototype = {
|
||||
|
||||
const results = [],
|
||||
options = this.options,
|
||||
configHelper = new Config(options),
|
||||
configHelper = this.config,
|
||||
ignoredPaths = new IgnoredPaths(options);
|
||||
|
||||
// resolve filename based on options.cwd (for reporting, ignoredPaths also resolves)
|
||||
@ -751,7 +735,7 @@ CLIEngine.prototype = {
|
||||
results.push(createIgnoreResult(filename, options.cwd));
|
||||
}
|
||||
} else {
|
||||
results.push(processText(text, configHelper, filename, options.fix, options.allowInlineConfig));
|
||||
results.push(processText(text, configHelper, filename, options.fix, options.allowInlineConfig, this.linter));
|
||||
}
|
||||
|
||||
const stats = calculateStatsPerRun(results);
|
||||
@ -759,9 +743,11 @@ CLIEngine.prototype = {
|
||||
return {
|
||||
results,
|
||||
errorCount: stats.errorCount,
|
||||
warningCount: stats.warningCount
|
||||
warningCount: stats.warningCount,
|
||||
fixableErrorCount: stats.fixableErrorCount,
|
||||
fixableWarningCount: stats.fixableWarningCount
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a configuration object for the given file based on the CLI options.
|
||||
@ -771,10 +757,10 @@ CLIEngine.prototype = {
|
||||
* @returns {Object} A configuration object for the file.
|
||||
*/
|
||||
getConfigForFile(filePath) {
|
||||
const configHelper = new Config(this.options);
|
||||
const configHelper = this.config;
|
||||
|
||||
return configHelper.getConfig(filePath);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given path is ignored by ESLint.
|
||||
@ -786,12 +772,51 @@ CLIEngine.prototype = {
|
||||
const ignoredPaths = new IgnoredPaths(this.options);
|
||||
|
||||
return ignoredPaths.contains(resolvedPath);
|
||||
},
|
||||
}
|
||||
|
||||
getFormatter: CLIEngine.getFormatter
|
||||
/**
|
||||
* Returns the formatter representing the given format or null if no formatter
|
||||
* with the given name can be found.
|
||||
* @param {string} [format] The name of the format to load or the path to a
|
||||
* custom formatter.
|
||||
* @returns {Function} The formatter function or null if not found.
|
||||
*/
|
||||
getFormatter(format) {
|
||||
|
||||
};
|
||||
let formatterPath;
|
||||
|
||||
// default is stylish
|
||||
format = format || "stylish";
|
||||
|
||||
// only strings are valid formatters
|
||||
if (typeof format === "string") {
|
||||
|
||||
// replace \ with / for Windows compatibility
|
||||
format = format.replace(/\\/g, "/");
|
||||
|
||||
// if there's a slash, then it's a file
|
||||
if (format.indexOf("/") > -1) {
|
||||
const cwd = this.options ? this.options.cwd : process.cwd();
|
||||
|
||||
formatterPath = path.resolve(cwd, format);
|
||||
} else {
|
||||
formatterPath = `./formatters/${format}`;
|
||||
}
|
||||
|
||||
try {
|
||||
return require(formatterPath);
|
||||
} catch (ex) {
|
||||
ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`;
|
||||
throw ex;
|
||||
}
|
||||
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CLIEngine.version = pkg.version;
|
||||
CLIEngine.getFormatter = CLIEngine.prototype.getFormatter;
|
||||
|
||||
module.exports = CLIEngine;
|
||||
|
@ -17,7 +17,6 @@
|
||||
|
||||
const fs = require("fs"),
|
||||
path = require("path"),
|
||||
shell = require("shelljs"),
|
||||
options = require("./options"),
|
||||
CLIEngine = require("./cli-engine"),
|
||||
mkdirp = require("mkdirp"),
|
||||
@ -83,7 +82,7 @@ function printResults(engine, results, format, outputFile) {
|
||||
if (outputFile) {
|
||||
const filePath = path.resolve(process.cwd(), outputFile);
|
||||
|
||||
if (shell.test("-d", filePath)) {
|
||||
if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
|
||||
log.error("Cannot write to output file path, it is a directory: %s", outputFile);
|
||||
return false;
|
||||
}
|
||||
|
@ -988,7 +988,7 @@ class CodePathState {
|
||||
switch (context.type) {
|
||||
case "WhileStatement":
|
||||
case "ForStatement":
|
||||
choiceContext = this.popChoiceContext();
|
||||
this.popChoiceContext();
|
||||
makeLooped(
|
||||
this,
|
||||
forkContext.head,
|
||||
|
@ -10,13 +10,12 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const path = require("path"),
|
||||
os = require("os"),
|
||||
ConfigOps = require("./config/config-ops"),
|
||||
ConfigFile = require("./config/config-file"),
|
||||
Plugins = require("./config/plugins"),
|
||||
FileFinder = require("./file-finder"),
|
||||
userHome = require("user-home"),
|
||||
isResolvable = require("is-resolvable"),
|
||||
pathIsInside = require("path-is-inside");
|
||||
isResolvable = require("is-resolvable");
|
||||
|
||||
const debug = require("debug")("eslint:config");
|
||||
|
||||
@ -24,7 +23,7 @@ const debug = require("debug")("eslint:config");
|
||||
// Constants
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const PERSONAL_CONFIG_DIR = userHome || null;
|
||||
const PERSONAL_CONFIG_DIR = os.homedir() || null;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
@ -43,10 +42,11 @@ function isObject(item) {
|
||||
/**
|
||||
* Load and parse a JSON config object from a file.
|
||||
* @param {string|Object} configToLoad the path to the JSON config file or the config object itself.
|
||||
* @param {Config} configContext config instance object
|
||||
* @returns {Object} the parsed config object (empty object if there was a parse error)
|
||||
* @private
|
||||
*/
|
||||
function loadConfig(configToLoad) {
|
||||
function loadConfig(configToLoad, configContext) {
|
||||
let config = {},
|
||||
filePath = "";
|
||||
|
||||
@ -56,32 +56,33 @@ function loadConfig(configToLoad) {
|
||||
config = configToLoad;
|
||||
|
||||
if (config.extends) {
|
||||
config = ConfigFile.applyExtends(config, filePath);
|
||||
config = ConfigFile.applyExtends(config, configContext, filePath);
|
||||
}
|
||||
} else {
|
||||
filePath = configToLoad;
|
||||
config = ConfigFile.load(filePath);
|
||||
config = ConfigFile.load(filePath, configContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get personal config object from ~/.eslintrc.
|
||||
* @param {Config} configContext Plugin context for the config instance
|
||||
* @returns {Object} the personal config object (null if there is no personal config)
|
||||
* @private
|
||||
*/
|
||||
function getPersonalConfig() {
|
||||
function getPersonalConfig(configContext) {
|
||||
let config;
|
||||
|
||||
if (PERSONAL_CONFIG_DIR) {
|
||||
|
||||
const filename = ConfigFile.getFilenameForDirectory(PERSONAL_CONFIG_DIR);
|
||||
|
||||
if (filename) {
|
||||
debug("Using personal config");
|
||||
config = loadConfig(filename);
|
||||
config = loadConfig(filename, configContext);
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,21 +100,18 @@ function hasRules(options) {
|
||||
|
||||
/**
|
||||
* Get a local config object.
|
||||
* @param {Object} thisConfig A Config object.
|
||||
* @param {Config} thisConfig A Config object.
|
||||
* @param {string} directory The directory to start looking in for a local config file.
|
||||
* @returns {Object} The local config object, or an empty object if there is no local config.
|
||||
*/
|
||||
function getLocalConfig(thisConfig, directory) {
|
||||
const localConfigFiles = thisConfig.findLocalConfigFiles(directory),
|
||||
numFiles = localConfigFiles.length,
|
||||
projectConfigPath = ConfigFile.getFilenameForDirectory(thisConfig.options.cwd);
|
||||
|
||||
const projectConfigPath = ConfigFile.getFilenameForDirectory(thisConfig.options.cwd);
|
||||
const localConfigFiles = thisConfig.findLocalConfigFiles(directory);
|
||||
let found,
|
||||
config = {},
|
||||
rootPath;
|
||||
config = {};
|
||||
|
||||
for (let i = 0; i < numFiles; i++) {
|
||||
|
||||
const localConfigFile = localConfigFiles[i];
|
||||
for (const localConfigFile of localConfigFiles) {
|
||||
|
||||
// Don't consider the personal config file in the home directory,
|
||||
// except if the home directory is the same as the current working directory
|
||||
@ -121,27 +119,22 @@ function getLocalConfig(thisConfig, directory) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If root flag is set, don't consider file if it is above root
|
||||
if (rootPath && !pathIsInside(path.dirname(localConfigFile), rootPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
debug(`Loading ${localConfigFile}`);
|
||||
const localConfig = loadConfig(localConfigFile);
|
||||
const localConfig = loadConfig(localConfigFile, thisConfig);
|
||||
|
||||
// Don't consider a local config file found if the config is null
|
||||
if (!localConfig) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for root flag
|
||||
if (localConfig.root === true) {
|
||||
rootPath = path.dirname(localConfigFile);
|
||||
}
|
||||
|
||||
found = true;
|
||||
debug(`Using ${localConfigFile}`);
|
||||
config = ConfigOps.merge(localConfig, config);
|
||||
|
||||
// Check for root flag
|
||||
if (localConfig.root === true) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found && !thisConfig.useSpecificConfig) {
|
||||
@ -152,7 +145,7 @@ function getLocalConfig(thisConfig, directory) {
|
||||
* - Otherwise, if no rules were manually passed in, throw and error.
|
||||
* - Note: This function is not called if useEslintrc is false.
|
||||
*/
|
||||
const personalConfig = getPersonalConfig();
|
||||
const personalConfig = getPersonalConfig(thisConfig);
|
||||
|
||||
if (personalConfig) {
|
||||
config = ConfigOps.merge(config, personalConfig);
|
||||
@ -186,17 +179,21 @@ class Config {
|
||||
/**
|
||||
* Config options
|
||||
* @param {Object} options Options to be passed in
|
||||
* @param {Linter} linterContext Linter instance object
|
||||
*/
|
||||
constructor(options) {
|
||||
constructor(options, linterContext) {
|
||||
options = options || {};
|
||||
|
||||
this.linterContext = linterContext;
|
||||
this.plugins = new Plugins(linterContext.environments, linterContext.rules);
|
||||
|
||||
this.ignore = options.ignore;
|
||||
this.ignorePath = options.ignorePath;
|
||||
this.cache = {};
|
||||
this.parser = options.parser;
|
||||
this.parserOptions = options.parserOptions || {};
|
||||
|
||||
this.baseConfig = options.baseConfig ? loadConfig(options.baseConfig) : { rules: {} };
|
||||
this.baseConfig = options.baseConfig ? loadConfig(options.baseConfig, this) : { rules: {} };
|
||||
|
||||
this.useEslintrc = (options.useEslintrc !== false);
|
||||
|
||||
@ -219,16 +216,22 @@ class Config {
|
||||
return globals;
|
||||
}, {});
|
||||
|
||||
const useConfig = options.configFile;
|
||||
|
||||
this.options = options;
|
||||
this.loadConfigFile(options.configFile);
|
||||
}
|
||||
|
||||
if (useConfig) {
|
||||
debug(`Using command line config ${useConfig}`);
|
||||
if (isResolvable(useConfig) || isResolvable(`eslint-config-${useConfig}`) || useConfig.charAt(0) === "@") {
|
||||
this.useSpecificConfig = loadConfig(useConfig);
|
||||
/**
|
||||
* Loads the config from the configuration file
|
||||
* @param {string} configFile - patch to the config file
|
||||
* @returns {undefined}
|
||||
*/
|
||||
loadConfigFile(configFile) {
|
||||
if (configFile) {
|
||||
debug(`Using command line config ${configFile}`);
|
||||
if (isResolvable(configFile) || isResolvable(`eslint-config-${configFile}`) || configFile.charAt(0) === "@") {
|
||||
this.useSpecificConfig = loadConfig(configFile, this);
|
||||
} else {
|
||||
this.useSpecificConfig = loadConfig(path.resolve(this.options.cwd, useConfig));
|
||||
this.useSpecificConfig = loadConfig(path.resolve(this.options.cwd, configFile), this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -248,7 +251,6 @@ class Config {
|
||||
debug(`Constructing config for ${filePath ? filePath : "text"}`);
|
||||
|
||||
config = this.cache[directory];
|
||||
|
||||
if (config) {
|
||||
debug("Using config from cache");
|
||||
return config;
|
||||
@ -306,13 +308,13 @@ class Config {
|
||||
// Step 8: Merge in command line plugins
|
||||
if (this.options.plugins) {
|
||||
debug("Merging command line plugins");
|
||||
Plugins.loadAll(this.options.plugins);
|
||||
this.plugins.loadAll(this.options.plugins);
|
||||
config = ConfigOps.merge(config, { plugins: this.options.plugins });
|
||||
}
|
||||
|
||||
// Step 9: Apply environments to the config if present
|
||||
if (config.env) {
|
||||
config = ConfigOps.applyEnvironments(config);
|
||||
config = ConfigOps.applyEnvironments(config, this.linterContext.environments);
|
||||
}
|
||||
|
||||
this.cache[directory] = config;
|
||||
@ -323,7 +325,7 @@ class Config {
|
||||
/**
|
||||
* Find local config files from directory and parent directories.
|
||||
* @param {string} directory The directory to start searching from.
|
||||
* @returns {string[]} The paths of local config files found.
|
||||
* @returns {GeneratorFunction} The paths of local config files found.
|
||||
*/
|
||||
findLocalConfigFiles(directory) {
|
||||
|
||||
|
@ -10,12 +10,13 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const lodash = require("lodash"),
|
||||
eslint = require("../eslint"),
|
||||
Linter = require("../linter"),
|
||||
configRule = require("./config-rule"),
|
||||
ConfigOps = require("./config-ops"),
|
||||
recConfig = require("../../conf/eslint-recommended");
|
||||
|
||||
const debug = require("debug")("eslint:autoconfig");
|
||||
const linter = new Linter();
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Data
|
||||
@ -37,11 +38,11 @@ const MAX_CONFIG_COMBINATIONS = 17, // 16 combinations + 1 for severity only
|
||||
* @param {number} errorCount The number of errors encountered when linting with the config
|
||||
*/
|
||||
|
||||
/**
|
||||
* This callback is used to measure execution status in a progress bar
|
||||
* @callback progressCallback
|
||||
* @param {number} The total number of times the callback will be called.
|
||||
*/
|
||||
/**
|
||||
* This callback is used to measure execution status in a progress bar
|
||||
* @callback progressCallback
|
||||
* @param {number} The total number of times the callback will be called.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create registryItems for rules
|
||||
@ -290,7 +291,7 @@ class Registry {
|
||||
|
||||
ruleSets.forEach(ruleSet => {
|
||||
const lintConfig = Object.assign({}, config, { rules: ruleSet });
|
||||
const lintResults = eslint.verify(sourceCodes[filename], lintConfig);
|
||||
const lintResults = linter.verify(sourceCodes[filename], lintConfig);
|
||||
|
||||
lintResults.forEach(result => {
|
||||
|
||||
@ -310,7 +311,7 @@ class Registry {
|
||||
ruleSetIdx += 1;
|
||||
|
||||
if (cb) {
|
||||
cb(totalFilesLinting); // eslint-disable-line callback-return
|
||||
cb(totalFilesLinting); // eslint-disable-line callback-return
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -13,17 +13,13 @@
|
||||
|
||||
const fs = require("fs"),
|
||||
path = require("path"),
|
||||
shell = require("shelljs"),
|
||||
ConfigOps = require("./config-ops"),
|
||||
validator = require("./config-validator"),
|
||||
Plugins = require("./plugins"),
|
||||
pathUtil = require("../util/path-util"),
|
||||
ModuleResolver = require("../util/module-resolver"),
|
||||
pathIsInside = require("path-is-inside"),
|
||||
stripBom = require("strip-bom"),
|
||||
stripComments = require("strip-json-comments"),
|
||||
stringify = require("json-stable-stringify"),
|
||||
defaultOptions = require("../../conf/eslint-recommended"),
|
||||
requireUncached = require("require-uncached");
|
||||
|
||||
const debug = require("debug")("eslint:config-file");
|
||||
@ -63,11 +59,11 @@ const resolver = new ModuleResolver();
|
||||
/**
|
||||
* Convenience wrapper for synchronously reading file contents.
|
||||
* @param {string} filePath The filename to read.
|
||||
* @returns {string} The file contents.
|
||||
* @returns {string} The file contents, with the BOM removed.
|
||||
* @private
|
||||
*/
|
||||
function readFile(filePath) {
|
||||
return stripBom(fs.readFileSync(filePath, "utf8"));
|
||||
return fs.readFileSync(filePath, "utf8").replace(/^\ufeff/, "");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -368,18 +364,18 @@ function getLookupPath(configFilePath) {
|
||||
function getEslintCoreConfigPath(name) {
|
||||
if (name === "eslint:recommended") {
|
||||
|
||||
/*
|
||||
* Add an explicit substitution for eslint:recommended to
|
||||
* conf/eslint-recommended.js.
|
||||
*/
|
||||
/*
|
||||
* Add an explicit substitution for eslint:recommended to
|
||||
* conf/eslint-recommended.js.
|
||||
*/
|
||||
return path.resolve(__dirname, "../../conf/eslint-recommended.js");
|
||||
}
|
||||
|
||||
if (name === "eslint:all") {
|
||||
|
||||
/*
|
||||
* Add an explicit substitution for eslint:all to conf/eslint-all.js
|
||||
*/
|
||||
/*
|
||||
* Add an explicit substitution for eslint:all to conf/eslint-all.js
|
||||
*/
|
||||
return path.resolve(__dirname, "../../conf/eslint-all.js");
|
||||
}
|
||||
|
||||
@ -389,6 +385,7 @@ function getEslintCoreConfigPath(name) {
|
||||
/**
|
||||
* Applies values from the "extends" field in a configuration file.
|
||||
* @param {Object} config The configuration information.
|
||||
* @param {Config} configContext Plugin context for the config instance
|
||||
* @param {string} filePath The file path from which the configuration information
|
||||
* was loaded.
|
||||
* @param {string} [relativeTo] The path to resolve relative to.
|
||||
@ -396,7 +393,7 @@ function getEslintCoreConfigPath(name) {
|
||||
* loaded and merged.
|
||||
* @private
|
||||
*/
|
||||
function applyExtends(config, filePath, relativeTo) {
|
||||
function applyExtends(config, configContext, filePath, relativeTo) {
|
||||
let configExtends = config.extends;
|
||||
|
||||
// normalize into an array for easier handling
|
||||
@ -421,7 +418,7 @@ function applyExtends(config, filePath, relativeTo) {
|
||||
);
|
||||
}
|
||||
debug(`Loading ${parentPath}`);
|
||||
return ConfigOps.merge(load(parentPath, false, relativeTo), previousValue);
|
||||
return ConfigOps.merge(load(parentPath, configContext, false, relativeTo), previousValue);
|
||||
} catch (e) {
|
||||
|
||||
/*
|
||||
@ -516,19 +513,18 @@ function resolve(filePath, relativeTo) {
|
||||
return { filePath };
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a configuration file from the given file path.
|
||||
* @param {string} filePath The filename or package name to load the configuration
|
||||
* information from.
|
||||
* @param {Config} configContext Plugins context
|
||||
* @param {boolean} [applyEnvironments=false] Set to true to merge in environment settings.
|
||||
* @param {string} [relativeTo] The path to resolve relative to.
|
||||
* @returns {Object} The configuration information.
|
||||
* @private
|
||||
*/
|
||||
function load(filePath, applyEnvironments, relativeTo) {
|
||||
function load(filePath, configContext, applyEnvironments, relativeTo) {
|
||||
const resolvedPath = resolve(filePath, relativeTo),
|
||||
dirname = path.dirname(resolvedPath.filePath),
|
||||
lookupPath = getLookupPath(dirname);
|
||||
@ -538,12 +534,7 @@ function load(filePath, applyEnvironments, relativeTo) {
|
||||
|
||||
// ensure plugins are properly loaded first
|
||||
if (config.plugins) {
|
||||
Plugins.loadAll(config.plugins);
|
||||
}
|
||||
|
||||
// remove parser from config if it is the default parser
|
||||
if (config.parser === defaultOptions.parser) {
|
||||
config.parser = null;
|
||||
configContext.plugins.loadAll(config.plugins);
|
||||
}
|
||||
|
||||
// include full path of parser if present
|
||||
@ -556,20 +547,20 @@ function load(filePath, applyEnvironments, relativeTo) {
|
||||
}
|
||||
|
||||
// validate the configuration before continuing
|
||||
validator.validate(config, filePath);
|
||||
validator.validate(config, filePath, configContext.linterContext.rules, configContext.linterContext.environments);
|
||||
|
||||
/*
|
||||
* If an `extends` property is defined, it represents a configuration file to use as
|
||||
* a "parent". Load the referenced file and merge the configuration recursively.
|
||||
*/
|
||||
if (config.extends) {
|
||||
config = applyExtends(config, filePath, dirname);
|
||||
config = applyExtends(config, configContext, filePath, dirname);
|
||||
}
|
||||
|
||||
if (config.env && applyEnvironments) {
|
||||
|
||||
// Merge in environment-specific globals and parserOptions.
|
||||
config = ConfigOps.applyEnvironments(config);
|
||||
config = ConfigOps.applyEnvironments(config, configContext.linterContext.environments);
|
||||
}
|
||||
|
||||
}
|
||||
@ -603,7 +594,7 @@ module.exports = {
|
||||
for (let i = 0, len = CONFIG_FILES.length; i < len; i++) {
|
||||
const filename = path.join(directory, CONFIG_FILES[i]);
|
||||
|
||||
if (shell.test("-f", filename)) {
|
||||
if (fs.existsSync(filename) && fs.statSync(filename).isFile()) {
|
||||
return filename;
|
||||
}
|
||||
}
|
||||
|
@ -282,13 +282,12 @@ function getConfigForStyleGuide(guide) {
|
||||
/* istanbul ignore next: no need to test inquirer*/
|
||||
/**
|
||||
* Ask use a few questions on command prompt
|
||||
* @param {Function} callback callback function when file has been written
|
||||
* @returns {void}
|
||||
* @returns {Promise} The promise with the result of the prompt
|
||||
*/
|
||||
function promptUser(callback) {
|
||||
function promptUser() {
|
||||
let config;
|
||||
|
||||
inquirer.prompt([
|
||||
return inquirer.prompt([
|
||||
{
|
||||
type: "list",
|
||||
name: "source",
|
||||
@ -343,29 +342,26 @@ function promptUser(callback) {
|
||||
return ((answers.source === "guide" && answers.packageJsonExists) || answers.source === "auto");
|
||||
}
|
||||
}
|
||||
], earlyAnswers => {
|
||||
]).then(earlyAnswers => {
|
||||
|
||||
// early exit if you are using a style guide
|
||||
if (earlyAnswers.source === "guide") {
|
||||
if (!earlyAnswers.packageJsonExists) {
|
||||
log.info("A package.json is necessary to install plugins such as style guides. Run `npm init` to create a package.json file and try again.");
|
||||
return;
|
||||
return void 0;
|
||||
}
|
||||
if (earlyAnswers.styleguide === "airbnb" && !earlyAnswers.airbnbReact) {
|
||||
earlyAnswers.styleguide = "airbnb-base";
|
||||
}
|
||||
try {
|
||||
config = getConfigForStyleGuide(earlyAnswers.styleguide);
|
||||
writeFile(config, earlyAnswers.format);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
|
||||
config = getConfigForStyleGuide(earlyAnswers.styleguide);
|
||||
writeFile(config, earlyAnswers.format);
|
||||
|
||||
return void 0;
|
||||
}
|
||||
|
||||
// continue with the questions otherwise...
|
||||
inquirer.prompt([
|
||||
return inquirer.prompt([
|
||||
{
|
||||
type: "confirm",
|
||||
name: "es6",
|
||||
@ -412,25 +408,21 @@ function promptUser(callback) {
|
||||
return answers.jsx;
|
||||
}
|
||||
}
|
||||
], secondAnswers => {
|
||||
]).then(secondAnswers => {
|
||||
|
||||
// early exit if you are using automatic style generation
|
||||
if (earlyAnswers.source === "auto") {
|
||||
try {
|
||||
const combinedAnswers = Object.assign({}, earlyAnswers, secondAnswers);
|
||||
const combinedAnswers = Object.assign({}, earlyAnswers, secondAnswers);
|
||||
|
||||
config = processAnswers(combinedAnswers);
|
||||
installModules(config);
|
||||
writeFile(config, earlyAnswers.format);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
config = processAnswers(combinedAnswers);
|
||||
installModules(config);
|
||||
writeFile(config, earlyAnswers.format);
|
||||
|
||||
return void 0;
|
||||
}
|
||||
|
||||
// continue with the style questions otherwise...
|
||||
inquirer.prompt([
|
||||
return inquirer.prompt([
|
||||
{
|
||||
type: "list",
|
||||
name: "indent",
|
||||
@ -465,16 +457,12 @@ function promptUser(callback) {
|
||||
default: "JavaScript",
|
||||
choices: ["JavaScript", "YAML", "JSON"]
|
||||
}
|
||||
], answers => {
|
||||
try {
|
||||
const totalAnswers = Object.assign({}, earlyAnswers, secondAnswers, answers);
|
||||
]).then(answers => {
|
||||
const totalAnswers = Object.assign({}, earlyAnswers, secondAnswers, answers);
|
||||
|
||||
config = processAnswers(totalAnswers);
|
||||
installModules(config);
|
||||
writeFile(config, answers.format);
|
||||
} catch (err) {
|
||||
callback(err); // eslint-disable-line callback-return
|
||||
}
|
||||
config = processAnswers(totalAnswers);
|
||||
installModules(config);
|
||||
writeFile(config, answers.format);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -487,8 +475,8 @@ function promptUser(callback) {
|
||||
const init = {
|
||||
getConfigForStyleGuide,
|
||||
processAnswers,
|
||||
/* istanbul ignore next */initializeConfig(callback) {
|
||||
promptUser(callback);
|
||||
/* istanbul ignore next */initializeConfig() {
|
||||
return promptUser();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -9,8 +9,6 @@
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const Environments = require("./environments");
|
||||
|
||||
const debug = require("debug")("eslint:config-ops");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@ -46,10 +44,11 @@ module.exports = {
|
||||
/**
|
||||
* Creates an environment config based on the specified environments.
|
||||
* @param {Object<string,boolean>} env The environment settings.
|
||||
* @param {Environments} envContext The environment context.
|
||||
* @returns {Object} A configuration object with the appropriate rules and globals
|
||||
* set.
|
||||
*/
|
||||
createEnvironmentConfig(env) {
|
||||
createEnvironmentConfig(env, envContext) {
|
||||
|
||||
const envConfig = this.createEmptyConfig();
|
||||
|
||||
@ -58,7 +57,7 @@ module.exports = {
|
||||
envConfig.env = env;
|
||||
|
||||
Object.keys(env).filter(name => env[name]).forEach(name => {
|
||||
const environment = Environments.get(name);
|
||||
const environment = envContext.get(name);
|
||||
|
||||
if (environment) {
|
||||
debug(`Creating config for environment ${name}`);
|
||||
@ -80,12 +79,13 @@ module.exports = {
|
||||
* Given a config with environment settings, applies the globals and
|
||||
* ecmaFeatures to the configuration and returns the result.
|
||||
* @param {Object} config The configuration information.
|
||||
* @param {Environments} envContent env context.
|
||||
* @returns {Object} The updated configuration information.
|
||||
*/
|
||||
applyEnvironments(config) {
|
||||
applyEnvironments(config, envContent) {
|
||||
if (config.env && typeof config.env === "object") {
|
||||
debug("Apply environment settings to config");
|
||||
return this.merge(this.createEnvironmentConfig(config.env), config);
|
||||
return this.merge(this.createEnvironmentConfig(config.env, envContent), config);
|
||||
}
|
||||
|
||||
return config;
|
||||
@ -175,7 +175,7 @@ module.exports = {
|
||||
}
|
||||
Object.keys(src).forEach(key => {
|
||||
if (Array.isArray(src[key]) || Array.isArray(target[key])) {
|
||||
dst[key] = deepmerge(target[key], src[key], key === "plugins", isRule);
|
||||
dst[key] = deepmerge(target[key], src[key], key === "plugins" || key === "extends", isRule);
|
||||
} else if (typeof src[key] !== "object" || !src[key] || key === "exported" || key === "astGlobals") {
|
||||
dst[key] = src[key];
|
||||
} else {
|
||||
|
@ -9,9 +9,10 @@
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const rules = require("../rules"),
|
||||
const Rules = require("../rules"),
|
||||
loadRules = require("../load-rules");
|
||||
|
||||
const rules = new Rules();
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
@ -168,16 +169,16 @@ function combinePropertyObjects(objArr1, objArr2) {
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of a rule configuration set
|
||||
*
|
||||
* A rule configuration set is an array of configurations that are valid for a
|
||||
* given rule. For example, the configuration set for the "semi" rule could be:
|
||||
*
|
||||
* ruleConfigSet.ruleConfigs // -> [[2], [2, "always"], [2, "never"]]
|
||||
*
|
||||
* Rule configuration set class
|
||||
*/
|
||||
/**
|
||||
* Creates a new instance of a rule configuration set
|
||||
*
|
||||
* A rule configuration set is an array of configurations that are valid for a
|
||||
* given rule. For example, the configuration set for the "semi" rule could be:
|
||||
*
|
||||
* ruleConfigSet.ruleConfigs // -> [[2], [2, "always"], [2, "never"]]
|
||||
*
|
||||
* Rule configuration set class
|
||||
*/
|
||||
class RuleConfigSet {
|
||||
|
||||
/**
|
||||
|
@ -9,9 +9,8 @@
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const rules = require("../rules"),
|
||||
Environments = require("./environments"),
|
||||
schemaValidator = require("is-my-json-valid"),
|
||||
const schemaValidator = require("is-my-json-valid"),
|
||||
configSchema = require("../../conf/config-schema.json"),
|
||||
util = require("util");
|
||||
|
||||
const validators = {
|
||||
@ -25,10 +24,11 @@ const validators = {
|
||||
/**
|
||||
* Gets a complete options schema for a rule.
|
||||
* @param {string} id The rule's unique name.
|
||||
* @param {Rules} rulesContext Rule context
|
||||
* @returns {Object} JSON Schema for the rule's options.
|
||||
*/
|
||||
function getRuleOptionsSchema(id) {
|
||||
const rule = rules.get(id),
|
||||
function getRuleOptionsSchema(id, rulesContext) {
|
||||
const rule = rulesContext.get(id),
|
||||
schema = rule && rule.schema || rule && rule.meta && rule.meta.schema;
|
||||
|
||||
// Given a tuple of schemas, insert warning level at the beginning
|
||||
@ -72,10 +72,11 @@ function validateRuleSeverity(options) {
|
||||
* Validates the non-severity options passed to a rule, based on its schema.
|
||||
* @param {string} id The rule's unique name
|
||||
* @param {array} localOptions The options for the rule, excluding severity
|
||||
* @param {Rules} rulesContext Rule context
|
||||
* @returns {void}
|
||||
*/
|
||||
function validateRuleSchema(id, localOptions) {
|
||||
const schema = getRuleOptionsSchema(id);
|
||||
function validateRuleSchema(id, localOptions, rulesContext) {
|
||||
const schema = getRuleOptionsSchema(id, rulesContext);
|
||||
|
||||
if (!validators.rules[id] && schema) {
|
||||
validators.rules[id] = schemaValidator(schema, { verbose: true });
|
||||
@ -95,15 +96,16 @@ function validateRuleSchema(id, localOptions) {
|
||||
* Validates a rule's options against its schema.
|
||||
* @param {string} id The rule's unique name.
|
||||
* @param {array|number} options The given options for the rule.
|
||||
* @param {string} source The name of the configuration source.
|
||||
* @param {string} source The name of the configuration source to report in any errors.
|
||||
* @param {Rules} rulesContext Rule context
|
||||
* @returns {void}
|
||||
*/
|
||||
function validateRuleOptions(id, options, source) {
|
||||
function validateRuleOptions(id, options, source, rulesContext) {
|
||||
try {
|
||||
const severity = validateRuleSeverity(options);
|
||||
|
||||
if (severity !== 0 && !(typeof severity === "string" && severity.toLowerCase() === "off")) {
|
||||
validateRuleSchema(id, Array.isArray(options) ? options.slice(1) : []);
|
||||
validateRuleSchema(id, Array.isArray(options) ? options.slice(1) : [], rulesContext);
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(`${source}:\n\tConfiguration for rule "${id}" is invalid:\n${err.message}`);
|
||||
@ -113,51 +115,91 @@ function validateRuleOptions(id, options, source) {
|
||||
/**
|
||||
* Validates an environment object
|
||||
* @param {Object} environment The environment config object to validate.
|
||||
* @param {string} source The location to report with any errors.
|
||||
* @param {string} source The name of the configuration source to report in any errors.
|
||||
* @param {Environments} envContext Env context
|
||||
* @returns {void}
|
||||
*/
|
||||
function validateEnvironment(environment, source) {
|
||||
function validateEnvironment(environment, source, envContext) {
|
||||
|
||||
// not having an environment is ok
|
||||
if (!environment) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(environment)) {
|
||||
throw new Error("Environment must not be an array");
|
||||
Object.keys(environment).forEach(env => {
|
||||
if (!envContext.get(env)) {
|
||||
const message = `${source}:\n\tEnvironment key "${env}" is unknown\n`;
|
||||
|
||||
throw new Error(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a rules config object
|
||||
* @param {Object} rulesConfig The rules config object to validate.
|
||||
* @param {string} source The name of the configuration source to report in any errors.
|
||||
* @param {Rules} rulesContext Rule context
|
||||
* @returns {void}
|
||||
*/
|
||||
function validateRules(rulesConfig, source, rulesContext) {
|
||||
if (!rulesConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof environment === "object") {
|
||||
Object.keys(environment).forEach(env => {
|
||||
if (!Environments.get(env)) {
|
||||
const message = [
|
||||
source, ":\n",
|
||||
"\tEnvironment key \"", env, "\" is unknown\n"
|
||||
];
|
||||
Object.keys(rulesConfig).forEach(id => {
|
||||
validateRuleOptions(id, rulesConfig[id], source, rulesContext);
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error(message.join(""));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw new Error("Environment must be an object");
|
||||
/**
|
||||
* Formats an array of schema validation errors.
|
||||
* @param {Array} errors An array of error messages to format.
|
||||
* @returns {string} Formatted error message
|
||||
*/
|
||||
function formatErrors(errors) {
|
||||
|
||||
return errors.map(error => {
|
||||
if (error.message === "has additional properties") {
|
||||
return `Unexpected top-level property "${error.value.replace(/^data\./, "")}"`;
|
||||
}
|
||||
if (error.message === "is the wrong type") {
|
||||
const formattedField = error.field.replace(/^data\./, "");
|
||||
const formattedExpectedType = typeof error.type === "string" ? error.type : error.type.join("/");
|
||||
const formattedValue = JSON.stringify(error.value);
|
||||
|
||||
return `Property "${formattedField}" is the wrong type (expected ${formattedExpectedType} but got \`${formattedValue}\`)`;
|
||||
}
|
||||
return `"${error.field.replace(/^(data\.)/, "")}" ${error.message}. Value: ${error.value}`;
|
||||
}).map(message => `\t- ${message}.\n`).join("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the top level properties of the config object.
|
||||
* @param {Object} config The config object to validate.
|
||||
* @param {string} source The name of the configuration source to report in any errors.
|
||||
* @returns {void}
|
||||
*/
|
||||
function validateConfigSchema(config, source) {
|
||||
const validator = schemaValidator(configSchema, { verbose: true });
|
||||
|
||||
if (!validator(config)) {
|
||||
throw new Error(`${source}:\n\tESLint configuration is invalid:\n${formatErrors(validator.errors)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an entire config object.
|
||||
* @param {Object} config The config object to validate.
|
||||
* @param {string} source The location to report with any errors.
|
||||
* @param {string} source The name of the configuration source to report in any errors.
|
||||
* @param {Rules} rulesContext The rules context
|
||||
* @param {Environments} envContext The env context
|
||||
* @returns {void}
|
||||
*/
|
||||
function validate(config, source) {
|
||||
|
||||
if (typeof config.rules === "object") {
|
||||
Object.keys(config.rules).forEach(id => {
|
||||
validateRuleOptions(id, config.rules[id], source);
|
||||
});
|
||||
}
|
||||
|
||||
validateEnvironment(config.env, source);
|
||||
function validate(config, source, rulesContext, envContext) {
|
||||
validateConfigSchema(config, source);
|
||||
validateRules(config.rules, source, rulesContext);
|
||||
validateEnvironment(config.env, source, envContext);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
@ -10,33 +10,31 @@
|
||||
|
||||
const envs = require("../../conf/environments");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Private
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
let environments = new Map();
|
||||
|
||||
/**
|
||||
* Loads the default environments.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function load() {
|
||||
Object.keys(envs).forEach(envName => {
|
||||
environments.set(envName, envs[envName]);
|
||||
});
|
||||
}
|
||||
|
||||
// always load default environments upfront
|
||||
load();
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
class Environments {
|
||||
|
||||
load,
|
||||
/**
|
||||
* create env context
|
||||
*/
|
||||
constructor() {
|
||||
this._environments = new Map();
|
||||
|
||||
this.load();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the default environments.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
load() {
|
||||
Object.keys(envs).forEach(envName => {
|
||||
this._environments.set(envName, envs[envName]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the environment with the given name.
|
||||
@ -44,8 +42,19 @@ module.exports = {
|
||||
* @returns {Object?} The environment object or null if not found.
|
||||
*/
|
||||
get(name) {
|
||||
return environments.get(name) || null;
|
||||
},
|
||||
return this._environments.get(name) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the environment present
|
||||
* @returns {Object} The environment object for each env name
|
||||
*/
|
||||
getAll() {
|
||||
return Array.from(this._environments).reduce((coll, env) => {
|
||||
coll[env[0]] = env[1];
|
||||
return coll;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines an environment.
|
||||
@ -54,8 +63,8 @@ module.exports = {
|
||||
* @returns {void}
|
||||
*/
|
||||
define(name, env) {
|
||||
environments.set(name, env);
|
||||
},
|
||||
this._environments.set(name, env);
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports all environments from a plugin.
|
||||
@ -69,14 +78,7 @@ module.exports = {
|
||||
this.define(`${pluginName}/${envName}`, plugin.environments[envName]);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Resets all environments. Only use for tests!
|
||||
* @returns {void}
|
||||
*/
|
||||
testReset() {
|
||||
environments = new Map();
|
||||
load();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = Environments;
|
||||
|
@ -8,56 +8,61 @@
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const Environments = require("./environments"),
|
||||
Rules = require("../rules");
|
||||
|
||||
const debug = require("debug")("eslint:plugins");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Private
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
let plugins = Object.create(null);
|
||||
|
||||
const PLUGIN_NAME_PREFIX = "eslint-plugin-",
|
||||
NAMESPACE_REGEX = /^@.*\//i;
|
||||
|
||||
/**
|
||||
* Removes the prefix `eslint-plugin-` from a plugin name.
|
||||
* @param {string} pluginName The name of the plugin which may have the prefix.
|
||||
* @returns {string} The name of the plugin without prefix.
|
||||
*/
|
||||
function removePrefix(pluginName) {
|
||||
return pluginName.indexOf(PLUGIN_NAME_PREFIX) === 0 ? pluginName.substring(PLUGIN_NAME_PREFIX.length) : pluginName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the scope (namespace) of a plugin.
|
||||
* @param {string} pluginName The name of the plugin which may have the prefix.
|
||||
* @returns {string} The name of the plugins namepace if it has one.
|
||||
*/
|
||||
function getNamespace(pluginName) {
|
||||
return pluginName.match(NAMESPACE_REGEX) ? pluginName.match(NAMESPACE_REGEX)[0] : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the namespace from a plugin name.
|
||||
* @param {string} pluginName The name of the plugin which may have the prefix.
|
||||
* @returns {string} The name of the plugin without the namespace.
|
||||
*/
|
||||
function removeNamespace(pluginName) {
|
||||
return pluginName.replace(NAMESPACE_REGEX, "");
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Plugin class
|
||||
*/
|
||||
class Plugins {
|
||||
|
||||
removePrefix,
|
||||
getNamespace,
|
||||
removeNamespace,
|
||||
/**
|
||||
* Creates the plugins context
|
||||
* @param {Environments} envContext - env context
|
||||
* @param {Rules} rulesContext - rules context
|
||||
*/
|
||||
constructor(envContext, rulesContext) {
|
||||
this._plugins = Object.create(null);
|
||||
this._environments = envContext;
|
||||
this._rules = rulesContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the prefix `eslint-plugin-` from a plugin name.
|
||||
* @param {string} pluginName The name of the plugin which may have the prefix.
|
||||
* @returns {string} The name of the plugin without prefix.
|
||||
*/
|
||||
static removePrefix(pluginName) {
|
||||
return pluginName.startsWith(PLUGIN_NAME_PREFIX) ? pluginName.substring(PLUGIN_NAME_PREFIX.length) : pluginName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the scope (namespace) of a plugin.
|
||||
* @param {string} pluginName The name of the plugin which may have the prefix.
|
||||
* @returns {string} The name of the plugins namepace if it has one.
|
||||
*/
|
||||
static getNamespace(pluginName) {
|
||||
return pluginName.match(NAMESPACE_REGEX) ? pluginName.match(NAMESPACE_REGEX)[0] : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the namespace from a plugin name.
|
||||
* @param {string} pluginName The name of the plugin which may have the prefix.
|
||||
* @returns {string} The name of the plugin without the namespace.
|
||||
*/
|
||||
static removeNamespace(pluginName) {
|
||||
return pluginName.replace(NAMESPACE_REGEX, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a plugin with a given name rather than loading from disk.
|
||||
@ -66,22 +71,16 @@ module.exports = {
|
||||
* @returns {void}
|
||||
*/
|
||||
define(pluginName, plugin) {
|
||||
const pluginNamespace = getNamespace(pluginName),
|
||||
pluginNameWithoutNamespace = removeNamespace(pluginName),
|
||||
pluginNameWithoutPrefix = removePrefix(pluginNameWithoutNamespace),
|
||||
const pluginNamespace = Plugins.getNamespace(pluginName),
|
||||
pluginNameWithoutNamespace = Plugins.removeNamespace(pluginName),
|
||||
pluginNameWithoutPrefix = Plugins.removePrefix(pluginNameWithoutNamespace),
|
||||
shortName = pluginNamespace + pluginNameWithoutPrefix;
|
||||
|
||||
// load up environments and rules
|
||||
plugins[shortName] = plugin;
|
||||
Environments.importPlugin(plugin, shortName);
|
||||
Rules.importPlugin(plugin, shortName);
|
||||
|
||||
// load up environments and rules for the name that '@scope/' was omitted
|
||||
// 3 lines below will be removed by 4.0.0
|
||||
plugins[pluginNameWithoutPrefix] = plugin;
|
||||
Environments.importPlugin(plugin, pluginNameWithoutPrefix);
|
||||
Rules.importPlugin(plugin, pluginNameWithoutPrefix);
|
||||
},
|
||||
this._plugins[shortName] = plugin;
|
||||
this._environments.importPlugin(plugin, shortName);
|
||||
this._rules.importPlugin(plugin, shortName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a plugin with the given name.
|
||||
@ -89,16 +88,16 @@ module.exports = {
|
||||
* @returns {Object} The plugin or null if not loaded.
|
||||
*/
|
||||
get(pluginName) {
|
||||
return plugins[pluginName] || null;
|
||||
},
|
||||
return this._plugins[pluginName] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all plugins that are loaded.
|
||||
* @returns {Object} The plugins cache.
|
||||
*/
|
||||
getAll() {
|
||||
return plugins;
|
||||
},
|
||||
return this._plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a plugin with the given name.
|
||||
@ -107,9 +106,9 @@ module.exports = {
|
||||
* @throws {Error} If the plugin cannot be loaded.
|
||||
*/
|
||||
load(pluginName) {
|
||||
const pluginNamespace = getNamespace(pluginName),
|
||||
pluginNameWithoutNamespace = removeNamespace(pluginName),
|
||||
pluginNameWithoutPrefix = removePrefix(pluginNameWithoutNamespace),
|
||||
const pluginNamespace = Plugins.getNamespace(pluginName),
|
||||
pluginNameWithoutNamespace = Plugins.removeNamespace(pluginName),
|
||||
pluginNameWithoutPrefix = Plugins.removePrefix(pluginNameWithoutNamespace),
|
||||
shortName = pluginNamespace + pluginNameWithoutPrefix,
|
||||
longName = pluginNamespace + PLUGIN_NAME_PREFIX + pluginNameWithoutPrefix;
|
||||
let plugin = null;
|
||||
@ -124,7 +123,7 @@ module.exports = {
|
||||
throw whitespaceError;
|
||||
}
|
||||
|
||||
if (!plugins[shortName]) {
|
||||
if (!this._plugins[shortName]) {
|
||||
try {
|
||||
plugin = require(longName);
|
||||
} catch (pluginLoadErr) {
|
||||
@ -150,7 +149,7 @@ module.exports = {
|
||||
|
||||
this.define(pluginName, plugin);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all plugins from an array.
|
||||
@ -160,13 +159,7 @@ module.exports = {
|
||||
*/
|
||||
loadAll(pluginNames) {
|
||||
pluginNames.forEach(this.load, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Resets plugin information. Use for tests only.
|
||||
* @returns {void}
|
||||
*/
|
||||
testReset() {
|
||||
plugins = Object.create(null);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = Plugins;
|
||||
|
@ -25,6 +25,7 @@ const fs = require("fs"),
|
||||
*/
|
||||
function getDirectoryEntries(directory) {
|
||||
try {
|
||||
|
||||
return fs.readdirSync(directory);
|
||||
} catch (ex) {
|
||||
return [];
|
||||
@ -79,9 +80,9 @@ class FileFinder {
|
||||
* Searches for all the file names in this.fileNames.
|
||||
* Is currently used by lib/config.js to find .eslintrc and package.json files.
|
||||
* @param {string} directory The directory to start the search from.
|
||||
* @returns {string[]} The file paths found.
|
||||
* @returns {GeneratorFunction} to iterate the file paths found
|
||||
*/
|
||||
findAllInDirectoryAndParents(directory) {
|
||||
*findAllInDirectoryAndParents(directory) {
|
||||
const cache = this.cache;
|
||||
|
||||
if (directory) {
|
||||
@ -91,7 +92,8 @@ class FileFinder {
|
||||
}
|
||||
|
||||
if (cache.hasOwnProperty(directory)) {
|
||||
return cache[directory];
|
||||
yield* cache[directory];
|
||||
return; // to avoid doing the normal loop afterwards
|
||||
}
|
||||
|
||||
const dirs = [];
|
||||
@ -114,19 +116,21 @@ class FileFinder {
|
||||
for (let j = 0; j < searched; j++) {
|
||||
cache[dirs[j]].push(filePath);
|
||||
}
|
||||
|
||||
yield filePath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const child = directory;
|
||||
|
||||
// Assign parent directory to directory.
|
||||
directory = path.dirname(directory);
|
||||
|
||||
if (directory === child) {
|
||||
return cache[dirs[0]];
|
||||
return;
|
||||
}
|
||||
|
||||
} while (!cache.hasOwnProperty(directory));
|
||||
|
||||
// Add what has been cached previously to the cache of each directory searched.
|
||||
@ -134,7 +138,7 @@ class FileFinder {
|
||||
dirs.push.apply(cache[dirs[i]], cache[directory]);
|
||||
}
|
||||
|
||||
return cache[dirs[0]];
|
||||
yield* cache[dirs[0]];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,11 +74,14 @@ function formatMessage(message, parentResult) {
|
||||
* Gets the formatted output summary for a given number of errors and warnings.
|
||||
* @param {number} errors The number of errors.
|
||||
* @param {number} warnings The number of warnings.
|
||||
* @param {number} fixableErrors The number of fixable errors.
|
||||
* @param {number} fixableWarnings The number of fixable warnings.
|
||||
* @returns {string} The formatted output summary.
|
||||
*/
|
||||
function formatSummary(errors, warnings) {
|
||||
function formatSummary(errors, warnings, fixableErrors, fixableWarnings) {
|
||||
const summaryColor = errors > 0 ? "red" : "yellow";
|
||||
const summary = [];
|
||||
const fixablesSummary = [];
|
||||
|
||||
if (errors > 0) {
|
||||
summary.push(`${errors} ${pluralize("error", errors)}`);
|
||||
@ -88,7 +91,21 @@ function formatSummary(errors, warnings) {
|
||||
summary.push(`${warnings} ${pluralize("warning", warnings)}`);
|
||||
}
|
||||
|
||||
return chalk[summaryColor].bold(`${summary.join(" and ")} found.`);
|
||||
if (fixableErrors > 0) {
|
||||
fixablesSummary.push(`${fixableErrors} ${pluralize("error", fixableErrors)}`);
|
||||
}
|
||||
|
||||
if (fixableWarnings > 0) {
|
||||
fixablesSummary.push(`${fixableWarnings} ${pluralize("warning", fixableWarnings)}`);
|
||||
}
|
||||
|
||||
let output = chalk[summaryColor].bold(`${summary.join(" and ")} found.`);
|
||||
|
||||
if (fixableErrors || fixableWarnings) {
|
||||
output += chalk[summaryColor].bold(`\n${fixablesSummary.join(" and ")} potentially fixable with the \`--fix\` option.`);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@ -98,6 +115,9 @@ function formatSummary(errors, warnings) {
|
||||
module.exports = function(results) {
|
||||
let errors = 0;
|
||||
let warnings = 0;
|
||||
let fixableErrors = 0;
|
||||
let fixableWarnings = 0;
|
||||
|
||||
const resultsWithMessages = results.filter(result => result.messages.length > 0);
|
||||
|
||||
let output = resultsWithMessages.reduce((resultsOutput, result) => {
|
||||
@ -105,12 +125,14 @@ module.exports = function(results) {
|
||||
|
||||
errors += result.errorCount;
|
||||
warnings += result.warningCount;
|
||||
fixableErrors += result.fixableErrorCount;
|
||||
fixableWarnings += result.fixableWarningCount;
|
||||
|
||||
return resultsOutput.concat(messages);
|
||||
}, []).join("\n");
|
||||
|
||||
output += "\n";
|
||||
output += formatSummary(errors, warnings);
|
||||
output += formatSummary(errors, warnings, fixableErrors, fixableWarnings);
|
||||
|
||||
return (errors + warnings) > 0 ? output : "";
|
||||
};
|
||||
|
@ -28,8 +28,10 @@ function pluralize(word, count) {
|
||||
module.exports = function(results) {
|
||||
|
||||
let output = "\n",
|
||||
errors = 0,
|
||||
warnings = 0,
|
||||
errorCount = 0,
|
||||
warningCount = 0,
|
||||
fixableErrorCount = 0,
|
||||
fixableWarningCount = 0,
|
||||
summaryColor = "yellow";
|
||||
|
||||
results.forEach(result => {
|
||||
@ -39,8 +41,10 @@ module.exports = function(results) {
|
||||
return;
|
||||
}
|
||||
|
||||
errors += result.errorCount;
|
||||
warnings += result.warningCount;
|
||||
errorCount += result.errorCount;
|
||||
warningCount += result.warningCount;
|
||||
fixableErrorCount += result.fixableErrorCount;
|
||||
fixableWarningCount += result.fixableWarningCount;
|
||||
|
||||
output += `${chalk.underline(result.filePath)}\n`;
|
||||
|
||||
@ -73,14 +77,22 @@ module.exports = function(results) {
|
||||
).split("\n").map(el => el.replace(/(\d+)\s+(\d+)/, (m, p1, p2) => chalk.dim(`${p1}:${p2}`))).join("\n")}\n\n`;
|
||||
});
|
||||
|
||||
const total = errors + warnings;
|
||||
const total = errorCount + warningCount;
|
||||
|
||||
if (total > 0) {
|
||||
output += chalk[summaryColor].bold([
|
||||
"\u2716 ", total, pluralize(" problem", total),
|
||||
" (", errors, pluralize(" error", errors), ", ",
|
||||
warnings, pluralize(" warning", warnings), ")\n"
|
||||
" (", errorCount, pluralize(" error", errorCount), ", ",
|
||||
warningCount, pluralize(" warning", warningCount), ")\n"
|
||||
].join(""));
|
||||
|
||||
if (fixableErrorCount > 0 || fixableWarningCount > 0) {
|
||||
output += chalk[summaryColor].bold([
|
||||
" ", fixableErrorCount, pluralize(" error", fixableErrorCount), ", ",
|
||||
fixableWarningCount, pluralize(" warning", fixableWarningCount),
|
||||
" potentially fixable with the `--fix` option.\n"
|
||||
].join(""));
|
||||
}
|
||||
}
|
||||
|
||||
return total > 0 ? output : "";
|
||||
|
@ -9,7 +9,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const chalk = require("chalk"),
|
||||
table = require("table").default,
|
||||
table = require("table").table,
|
||||
pluralize = require("pluralize");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
@ -12,7 +12,6 @@
|
||||
const fs = require("fs"),
|
||||
path = require("path"),
|
||||
ignore = require("ignore"),
|
||||
shell = require("shelljs"),
|
||||
pathUtil = require("./util/path-util");
|
||||
|
||||
const debug = require("debug")("eslint:ignored-paths");
|
||||
@ -54,7 +53,7 @@ function findIgnoreFile(cwd) {
|
||||
|
||||
const ignoreFilePath = path.resolve(cwd, ESLINT_IGNORE_FILENAME);
|
||||
|
||||
return shell.test("-f", ignoreFilePath) ? ignoreFilePath : "";
|
||||
return fs.existsSync(ignoreFilePath) && fs.statSync(ignoreFilePath).isFile() ? ignoreFilePath : "";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -179,7 +178,7 @@ class IgnoredPaths {
|
||||
|
||||
let result = false;
|
||||
const absolutePath = path.resolve(this.options.cwd, filepath);
|
||||
const relativePath = pathUtil.getRelativePath(absolutePath, this.options.cwd);
|
||||
const relativePath = pathUtil.getRelativePath(absolutePath, this.baseDir);
|
||||
|
||||
if ((typeof category === "undefined") || (category === "default")) {
|
||||
result = result || (this.ig.default.filter([relativePath]).length === 0);
|
||||
@ -213,15 +212,7 @@ class IgnoredPaths {
|
||||
|
||||
const filter = ig.createFilter();
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* 1.
|
||||
* Actually, it should be `this.options.baseDir`, which is the base dir of `ignore-path`,
|
||||
* as well as Line 177.
|
||||
* But doing this leads to a breaking change and fails tests.
|
||||
* Related to #6759
|
||||
*/
|
||||
const base = this.options.cwd;
|
||||
const base = this.baseDir;
|
||||
|
||||
return function(absolutePath) {
|
||||
const relative = pathUtil.getRelativePath(absolutePath, base);
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @fileoverview Main ESLint object.
|
||||
* @author Nicholas C. Zakas
|
||||
* @fileoverview Main Linter Class
|
||||
* @author Gyandeep Singh
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
@ -11,22 +11,22 @@
|
||||
|
||||
const assert = require("assert"),
|
||||
EventEmitter = require("events").EventEmitter,
|
||||
escope = require("escope"),
|
||||
eslintScope = require("eslint-scope"),
|
||||
levn = require("levn"),
|
||||
blankScriptAST = require("../conf/blank-script.json"),
|
||||
DEFAULT_PARSER = require("../conf/eslint-recommended").parser,
|
||||
defaultConfig = require("../conf/default-config-options.js"),
|
||||
replacements = require("../conf/replacements.json"),
|
||||
CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
|
||||
ConfigOps = require("./config/config-ops"),
|
||||
validator = require("./config/config-validator"),
|
||||
Environments = require("./config/environments"),
|
||||
CommentEventGenerator = require("./util/comment-event-generator"),
|
||||
NodeEventGenerator = require("./util/node-event-generator"),
|
||||
SourceCode = require("./util/source-code"),
|
||||
Traverser = require("./util/traverser"),
|
||||
RuleContext = require("./rule-context"),
|
||||
rules = require("./rules"),
|
||||
Rules = require("./rules"),
|
||||
timing = require("./timing"),
|
||||
astUtils = require("./ast-utils"),
|
||||
|
||||
pkg = require("../package.json");
|
||||
|
||||
@ -158,19 +158,20 @@ function parseListConfig(string) {
|
||||
* @param {ASTNode} program The top node of the AST.
|
||||
* @param {Scope} globalScope The global scope.
|
||||
* @param {Object} config The existing configuration data.
|
||||
* @param {Environments} envContext Env context
|
||||
* @returns {void}
|
||||
*/
|
||||
function addDeclaredGlobals(program, globalScope, config) {
|
||||
function addDeclaredGlobals(program, globalScope, config, envContext) {
|
||||
const declaredGlobals = {},
|
||||
exportedGlobals = {},
|
||||
explicitGlobals = {},
|
||||
builtin = Environments.get("builtin");
|
||||
builtin = envContext.get("builtin");
|
||||
|
||||
Object.assign(declaredGlobals, builtin);
|
||||
|
||||
Object.keys(config.env).forEach(name => {
|
||||
if (config.env[name]) {
|
||||
const env = Environments.get(name),
|
||||
const env = envContext.get(name),
|
||||
environmentGlobals = env && env.globals;
|
||||
|
||||
if (environmentGlobals) {
|
||||
@ -187,7 +188,7 @@ function addDeclaredGlobals(program, globalScope, config) {
|
||||
let variable = globalScope.set.get(name);
|
||||
|
||||
if (!variable) {
|
||||
variable = new escope.Variable(name, globalScope);
|
||||
variable = new eslintScope.Variable(name, globalScope);
|
||||
variable.eslintExplicitGlobal = false;
|
||||
globalScope.variables.push(variable);
|
||||
globalScope.set.set(name, variable);
|
||||
@ -199,7 +200,7 @@ function addDeclaredGlobals(program, globalScope, config) {
|
||||
let variable = globalScope.set.get(name);
|
||||
|
||||
if (!variable) {
|
||||
variable = new escope.Variable(name, globalScope);
|
||||
variable = new eslintScope.Variable(name, globalScope);
|
||||
variable.eslintExplicitGlobal = true;
|
||||
variable.eslintExplicitGlobalComment = explicitGlobals[name].comment;
|
||||
globalScope.variables.push(variable);
|
||||
@ -314,11 +315,10 @@ function enableReporting(reportingConfig, start, rulesToEnable) {
|
||||
* @param {string} filename The file being checked.
|
||||
* @param {ASTNode} ast The top node of the AST.
|
||||
* @param {Object} config The existing configuration data.
|
||||
* @param {Object[]} reportingConfig The existing reporting configuration data.
|
||||
* @param {Object[]} messages The messages queue.
|
||||
* @param {Linter} linterContext Linter context object
|
||||
* @returns {Object} Modified config object
|
||||
*/
|
||||
function modifyConfigsFromComments(filename, ast, config, reportingConfig, messages) {
|
||||
function modifyConfigsFromComments(filename, ast, config, linterContext) {
|
||||
|
||||
let commentConfig = {
|
||||
exported: {},
|
||||
@ -327,6 +327,8 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
|
||||
env: {}
|
||||
};
|
||||
const commentRules = {};
|
||||
const messages = linterContext.messages;
|
||||
const reportingConfig = linterContext.reportingConfig;
|
||||
|
||||
ast.comments.forEach(comment => {
|
||||
|
||||
@ -365,7 +367,7 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
|
||||
Object.keys(items).forEach(name => {
|
||||
const ruleValue = items[name];
|
||||
|
||||
validator.validateRuleOptions(name, ruleValue, `${filename} line ${comment.loc.start.line}`);
|
||||
validator.validateRuleOptions(name, ruleValue, `${filename} line ${comment.loc.start.line}`, linterContext.rules);
|
||||
commentRules[name] = ruleValue;
|
||||
});
|
||||
break;
|
||||
@ -373,7 +375,7 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
|
||||
|
||||
// no default
|
||||
}
|
||||
} else { // comment.type === "Line"
|
||||
} else { // comment.type === "Line"
|
||||
if (match[1] === "eslint-disable-line") {
|
||||
disableReporting(reportingConfig, { line: comment.loc.start.line, column: 0 }, Object.keys(parseListConfig(value)));
|
||||
enableReporting(reportingConfig, comment.loc.end, Object.keys(parseListConfig(value)));
|
||||
@ -387,7 +389,7 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
|
||||
|
||||
// apply environment configs
|
||||
Object.keys(commentConfig.env).forEach(name => {
|
||||
const env = Environments.get(name);
|
||||
const env = linterContext.environments.get(name);
|
||||
|
||||
if (env) {
|
||||
commentConfig = ConfigOps.merge(commentConfig, env);
|
||||
@ -446,15 +448,13 @@ function normalizeEcmaVersion(ecmaVersion, isModule) {
|
||||
/**
|
||||
* Process initial config to make it safe to extend by file comment config
|
||||
* @param {Object} config Initial config
|
||||
* @param {Environments} envContext Env context
|
||||
* @returns {Object} Processed config
|
||||
*/
|
||||
function prepareConfig(config) {
|
||||
|
||||
config.globals = config.globals || config.global || {};
|
||||
delete config.global;
|
||||
|
||||
const copiedRules = {};
|
||||
let parserOptions = {};
|
||||
function prepareConfig(config, envContext) {
|
||||
config.globals = config.globals || {};
|
||||
const copiedRules = Object.assign({}, defaultConfig.rules);
|
||||
let parserOptions = Object.assign({}, defaultConfig.parserOptions);
|
||||
|
||||
if (typeof config.rules === "object") {
|
||||
Object.keys(config.rules).forEach(k => {
|
||||
@ -474,7 +474,7 @@ function prepareConfig(config) {
|
||||
// merge in environment parserOptions
|
||||
if (typeof config.env === "object") {
|
||||
Object.keys(config.env).forEach(envName => {
|
||||
const env = Environments.get(envName);
|
||||
const env = envContext.get(envName);
|
||||
|
||||
if (config.env[envName] && env && env.parserOptions) {
|
||||
parserOptions = ConfigOps.merge(parserOptions, env.parserOptions);
|
||||
@ -484,21 +484,18 @@ function prepareConfig(config) {
|
||||
|
||||
const preparedConfig = {
|
||||
rules: copiedRules,
|
||||
parser: config.parser || DEFAULT_PARSER,
|
||||
globals: ConfigOps.merge({}, config.globals),
|
||||
env: ConfigOps.merge({}, config.env || {}),
|
||||
settings: ConfigOps.merge({}, config.settings || {}),
|
||||
parser: config.parser || defaultConfig.parser,
|
||||
globals: ConfigOps.merge(defaultConfig.globals, config.globals),
|
||||
env: ConfigOps.merge(defaultConfig.env, config.env || {}),
|
||||
settings: ConfigOps.merge(defaultConfig.settings, config.settings || {}),
|
||||
parserOptions: ConfigOps.merge(parserOptions, config.parserOptions || {})
|
||||
};
|
||||
const isModule = preparedConfig.parserOptions.sourceType === "module";
|
||||
|
||||
if (isModule) {
|
||||
if (!preparedConfig.parserOptions.ecmaFeatures) {
|
||||
preparedConfig.parserOptions.ecmaFeatures = {};
|
||||
}
|
||||
|
||||
// can't have global return inside of modules
|
||||
preparedConfig.parserOptions.ecmaFeatures.globalReturn = false;
|
||||
preparedConfig.parserOptions.ecmaFeatures = Object.assign({}, preparedConfig.parserOptions.ecmaFeatures, { globalReturn: false });
|
||||
}
|
||||
|
||||
preparedConfig.parserOptions.ecmaVersion = normalizeEcmaVersion(preparedConfig.parserOptions.ecmaVersion, isModule);
|
||||
@ -586,6 +583,114 @@ function stripUnicodeBOM(text) {
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the severity level of a rule (0 - none, 1 - warning, 2 - error)
|
||||
* Returns 0 if the rule config is not valid (an Array or a number)
|
||||
* @param {Array|number} ruleConfig rule configuration
|
||||
* @returns {number} 0, 1, or 2, indicating rule severity
|
||||
*/
|
||||
function getRuleSeverity(ruleConfig) {
|
||||
if (typeof ruleConfig === "number") {
|
||||
return ruleConfig;
|
||||
} else if (Array.isArray(ruleConfig)) {
|
||||
return ruleConfig[0];
|
||||
}
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the options for a rule (not including severity), if any
|
||||
* @param {Array|number} ruleConfig rule configuration
|
||||
* @returns {Array} of rule options, empty Array if none
|
||||
*/
|
||||
function getRuleOptions(ruleConfig) {
|
||||
if (Array.isArray(ruleConfig)) {
|
||||
return ruleConfig.slice(1);
|
||||
}
|
||||
return [];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses text into an AST. Moved out here because the try-catch prevents
|
||||
* optimization of functions, so it's best to keep the try-catch as isolated
|
||||
* as possible
|
||||
* @param {string} text The text to parse.
|
||||
* @param {Object} config The ESLint configuration object.
|
||||
* @param {string} filePath The path to the file being parsed.
|
||||
* @returns {ASTNode|CustomParseResult} The AST or parse result if successful,
|
||||
* or null if not.
|
||||
* @param {Array<Object>} messages Messages array for the linter object
|
||||
* @returns {*} parsed text if successful otherwise null
|
||||
* @private
|
||||
*/
|
||||
function parse(text, config, filePath, messages) {
|
||||
|
||||
let parser,
|
||||
parserOptions = {
|
||||
loc: true,
|
||||
range: true,
|
||||
raw: true,
|
||||
tokens: true,
|
||||
comment: true,
|
||||
filePath
|
||||
};
|
||||
|
||||
try {
|
||||
parser = require(config.parser);
|
||||
} catch (ex) {
|
||||
messages.push({
|
||||
ruleId: null,
|
||||
fatal: true,
|
||||
severity: 2,
|
||||
source: null,
|
||||
message: ex.message,
|
||||
line: 0,
|
||||
column: 0
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// merge in any additional parser options
|
||||
if (config.parserOptions) {
|
||||
parserOptions = Object.assign({}, config.parserOptions, parserOptions);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for parsing errors first. If there's a parsing error, nothing
|
||||
* else can happen. However, a parsing error does not throw an error
|
||||
* from this method - it's just considered a fatal error message, a
|
||||
* problem that ESLint identified just like any other.
|
||||
*/
|
||||
try {
|
||||
if (typeof parser.parseForESLint === "function") {
|
||||
return parser.parseForESLint(text, parserOptions);
|
||||
}
|
||||
return parser.parse(text, parserOptions);
|
||||
|
||||
} catch (ex) {
|
||||
|
||||
// If the message includes a leading line number, strip it:
|
||||
const message = ex.message.replace(/^line \d+:/i, "").trim();
|
||||
const source = (ex.lineNumber) ? SourceCode.splitLines(text)[ex.lineNumber - 1] : null;
|
||||
|
||||
messages.push({
|
||||
ruleId: null,
|
||||
fatal: true,
|
||||
severity: 2,
|
||||
source,
|
||||
message: `Parsing error: ${message}`,
|
||||
|
||||
line: ex.lineNumber,
|
||||
column: ex.column
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
@ -594,142 +699,41 @@ function stripUnicodeBOM(text) {
|
||||
* Object that is responsible for verifying JavaScript text
|
||||
* @name eslint
|
||||
*/
|
||||
module.exports = (function() {
|
||||
class Linter extends EventEmitter {
|
||||
|
||||
const api = Object.create(new EventEmitter());
|
||||
let messages = [],
|
||||
currentConfig = null,
|
||||
currentScopes = null,
|
||||
scopeManager = null,
|
||||
currentFilename = null,
|
||||
traverser = null,
|
||||
reportingConfig = [],
|
||||
sourceCode = null;
|
||||
constructor() {
|
||||
super();
|
||||
this.messages = [];
|
||||
this.currentConfig = null;
|
||||
this.currentScopes = null;
|
||||
this.scopeManager = null;
|
||||
this.currentFilename = null;
|
||||
this.traverser = null;
|
||||
this.reportingConfig = [];
|
||||
this.sourceCode = null;
|
||||
this.version = pkg.version;
|
||||
|
||||
/**
|
||||
* Parses text into an AST. Moved out here because the try-catch prevents
|
||||
* optimization of functions, so it's best to keep the try-catch as isolated
|
||||
* as possible
|
||||
* @param {string} text The text to parse.
|
||||
* @param {Object} config The ESLint configuration object.
|
||||
* @param {string} filePath The path to the file being parsed.
|
||||
* @returns {ASTNode|CustomParseResult} The AST or parse result if successful,
|
||||
* or null if not.
|
||||
* @private
|
||||
*/
|
||||
function parse(text, config, filePath) {
|
||||
this.rules = new Rules();
|
||||
this.environments = new Environments();
|
||||
|
||||
let parser,
|
||||
parserOptions = {
|
||||
loc: true,
|
||||
range: true,
|
||||
raw: true,
|
||||
tokens: true,
|
||||
comment: true,
|
||||
attachComment: true,
|
||||
filePath
|
||||
};
|
||||
|
||||
try {
|
||||
parser = require(config.parser);
|
||||
} catch (ex) {
|
||||
messages.push({
|
||||
ruleId: null,
|
||||
fatal: true,
|
||||
severity: 2,
|
||||
source: null,
|
||||
message: ex.message,
|
||||
line: 0,
|
||||
column: 0
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// merge in any additional parser options
|
||||
if (config.parserOptions) {
|
||||
parserOptions = Object.assign({}, config.parserOptions, parserOptions);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for parsing errors first. If there's a parsing error, nothing
|
||||
* else can happen. However, a parsing error does not throw an error
|
||||
* from this method - it's just considered a fatal error message, a
|
||||
* problem that ESLint identified just like any other.
|
||||
*/
|
||||
try {
|
||||
if (typeof parser.parseForESLint === "function") {
|
||||
return parser.parseForESLint(text, parserOptions);
|
||||
}
|
||||
return parser.parse(text, parserOptions);
|
||||
|
||||
} catch (ex) {
|
||||
|
||||
// If the message includes a leading line number, strip it:
|
||||
const message = ex.message.replace(/^line \d+:/i, "").trim();
|
||||
const source = (ex.lineNumber) ? SourceCode.splitLines(text)[ex.lineNumber - 1] : null;
|
||||
|
||||
messages.push({
|
||||
ruleId: null,
|
||||
fatal: true,
|
||||
severity: 2,
|
||||
source,
|
||||
message: `Parsing error: ${message}`,
|
||||
|
||||
line: ex.lineNumber,
|
||||
column: ex.column
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
// set unlimited listeners (see https://github.com/eslint/eslint/issues/524)
|
||||
this.setMaxListeners(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the severity level of a rule (0 - none, 1 - warning, 2 - error)
|
||||
* Returns 0 if the rule config is not valid (an Array or a number)
|
||||
* @param {Array|number} ruleConfig rule configuration
|
||||
* @returns {number} 0, 1, or 2, indicating rule severity
|
||||
*/
|
||||
function getRuleSeverity(ruleConfig) {
|
||||
if (typeof ruleConfig === "number") {
|
||||
return ruleConfig;
|
||||
} else if (Array.isArray(ruleConfig)) {
|
||||
return ruleConfig[0];
|
||||
}
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the options for a rule (not including severity), if any
|
||||
* @param {Array|number} ruleConfig rule configuration
|
||||
* @returns {Array} of rule options, empty Array if none
|
||||
*/
|
||||
function getRuleOptions(ruleConfig) {
|
||||
if (Array.isArray(ruleConfig)) {
|
||||
return ruleConfig.slice(1);
|
||||
}
|
||||
return [];
|
||||
|
||||
}
|
||||
|
||||
// set unlimited listeners (see https://github.com/eslint/eslint/issues/524)
|
||||
api.setMaxListeners(0);
|
||||
|
||||
/**
|
||||
* Resets the internal state of the object.
|
||||
* @returns {void}
|
||||
*/
|
||||
api.reset = function() {
|
||||
reset() {
|
||||
this.removeAllListeners();
|
||||
messages = [];
|
||||
currentConfig = null;
|
||||
currentScopes = null;
|
||||
scopeManager = null;
|
||||
traverser = null;
|
||||
reportingConfig = [];
|
||||
sourceCode = null;
|
||||
};
|
||||
this.messages = [];
|
||||
this.currentConfig = null;
|
||||
this.currentScopes = null;
|
||||
this.scopeManager = null;
|
||||
this.traverser = null;
|
||||
this.reportingConfig = [];
|
||||
this.sourceCode = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration object for the `verify` API. A JS representation of the eslintrc files.
|
||||
@ -739,7 +743,7 @@ module.exports = (function() {
|
||||
* @property {Object} [parserOptions] Options for the parsed used.
|
||||
* @property {Object} [settings] Global settings passed to each rule.
|
||||
* @property {Object} [env] The environment to verify in.
|
||||
* @property {Object} [globals] Available globalsto the code.
|
||||
* @property {Object} [globals] Available globals to the code.
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -755,20 +759,19 @@ module.exports = (function() {
|
||||
* Useful if you want to validate JS without comments overriding rules.
|
||||
* @returns {Object[]} The results as an array of messages or null if no messages.
|
||||
*/
|
||||
api.verify = function(textOrSourceCode, config, filenameOrOptions, saveState) {
|
||||
verify(textOrSourceCode, config, filenameOrOptions, saveState) {
|
||||
const text = (typeof textOrSourceCode === "string") ? textOrSourceCode : null;
|
||||
let ast,
|
||||
parseResult,
|
||||
shebang,
|
||||
allowInlineConfig;
|
||||
|
||||
// evaluate arguments
|
||||
if (typeof filenameOrOptions === "object") {
|
||||
currentFilename = filenameOrOptions.filename;
|
||||
this.currentFilename = filenameOrOptions.filename;
|
||||
allowInlineConfig = filenameOrOptions.allowInlineConfig;
|
||||
saveState = filenameOrOptions.saveState;
|
||||
} else {
|
||||
currentFilename = filenameOrOptions;
|
||||
this.currentFilename = filenameOrOptions;
|
||||
}
|
||||
|
||||
if (!saveState) {
|
||||
@ -789,24 +792,22 @@ module.exports = (function() {
|
||||
}
|
||||
|
||||
// process initial config to make it safe to extend
|
||||
config = prepareConfig(config);
|
||||
config = prepareConfig(config, this.environments);
|
||||
|
||||
// only do this for text
|
||||
if (text !== null) {
|
||||
|
||||
// there's no input, just exit here
|
||||
if (text.trim().length === 0) {
|
||||
sourceCode = new SourceCode(text, blankScriptAST);
|
||||
return messages;
|
||||
this.sourceCode = new SourceCode(text, blankScriptAST);
|
||||
return this.messages;
|
||||
}
|
||||
|
||||
parseResult = parse(
|
||||
stripUnicodeBOM(text).replace(/^#!([^\r\n]+)/, (match, captured) => {
|
||||
shebang = captured;
|
||||
return `//${captured}`;
|
||||
}),
|
||||
stripUnicodeBOM(text).replace(astUtils.SHEBANG_MATCHER, (match, captured) => `//${captured}`),
|
||||
config,
|
||||
currentFilename
|
||||
this.currentFilename,
|
||||
this.messages
|
||||
);
|
||||
|
||||
// if this result is from a parseForESLint() method, normalize
|
||||
@ -818,12 +819,12 @@ module.exports = (function() {
|
||||
}
|
||||
|
||||
if (ast) {
|
||||
sourceCode = new SourceCode(text, ast);
|
||||
this.sourceCode = new SourceCode(text, ast);
|
||||
}
|
||||
|
||||
} else {
|
||||
sourceCode = textOrSourceCode;
|
||||
ast = sourceCode.ast;
|
||||
this.sourceCode = textOrSourceCode;
|
||||
ast = this.sourceCode.ast;
|
||||
}
|
||||
|
||||
// if espree failed to parse the file, there's no sense in setting up rules
|
||||
@ -831,7 +832,7 @@ module.exports = (function() {
|
||||
|
||||
// parse global comments and modify config
|
||||
if (allowInlineConfig !== false) {
|
||||
config = modifyConfigsFromComments(currentFilename, ast, config, reportingConfig, messages);
|
||||
config = modifyConfigsFromComments(this.currentFilename, ast, config, this);
|
||||
}
|
||||
|
||||
// ensure that severities are normalized in the config
|
||||
@ -841,7 +842,7 @@ module.exports = (function() {
|
||||
Object.keys(config.rules).filter(key => getRuleSeverity(config.rules[key]) > 0).forEach(key => {
|
||||
let ruleCreator;
|
||||
|
||||
ruleCreator = rules.get(key);
|
||||
ruleCreator = this.rules.get(key);
|
||||
|
||||
if (!ruleCreator) {
|
||||
const replacementMsg = getRuleReplacementMessage(key);
|
||||
@ -851,7 +852,7 @@ module.exports = (function() {
|
||||
} else {
|
||||
ruleCreator = createStubRule(`Definition for rule '${key}' was not found`);
|
||||
}
|
||||
rules.define(key, ruleCreator);
|
||||
this.rules.define(key, ruleCreator);
|
||||
}
|
||||
|
||||
const severity = getRuleSeverity(config.rules[key]);
|
||||
@ -859,7 +860,7 @@ module.exports = (function() {
|
||||
|
||||
try {
|
||||
const ruleContext = new RuleContext(
|
||||
key, api, severity, options,
|
||||
key, this, severity, options,
|
||||
config.settings, config.parserOptions, config.parser,
|
||||
ruleCreator.meta,
|
||||
(parseResult && parseResult.services ? parseResult.services : {})
|
||||
@ -870,7 +871,7 @@ module.exports = (function() {
|
||||
|
||||
// add all the selectors from the rule as listeners
|
||||
Object.keys(rule).forEach(selector => {
|
||||
api.on(selector, timing.enabled
|
||||
this.on(selector, timing.enabled
|
||||
? timing.time(key, rule[selector])
|
||||
: rule[selector]
|
||||
);
|
||||
@ -882,40 +883,30 @@ module.exports = (function() {
|
||||
});
|
||||
|
||||
// save config so rules can access as necessary
|
||||
currentConfig = config;
|
||||
traverser = new Traverser();
|
||||
this.currentConfig = config;
|
||||
this.traverser = new Traverser();
|
||||
|
||||
const ecmaFeatures = currentConfig.parserOptions.ecmaFeatures || {};
|
||||
const ecmaVersion = currentConfig.parserOptions.ecmaVersion || 5;
|
||||
const ecmaFeatures = this.currentConfig.parserOptions.ecmaFeatures || {};
|
||||
const ecmaVersion = this.currentConfig.parserOptions.ecmaVersion || 5;
|
||||
|
||||
// gather scope data that may be needed by the rules
|
||||
scopeManager = escope.analyze(ast, {
|
||||
this.scopeManager = eslintScope.analyze(ast, {
|
||||
ignoreEval: true,
|
||||
nodejsScope: ecmaFeatures.globalReturn,
|
||||
impliedStrict: ecmaFeatures.impliedStrict,
|
||||
ecmaVersion,
|
||||
sourceType: currentConfig.parserOptions.sourceType || "script",
|
||||
sourceType: this.currentConfig.parserOptions.sourceType || "script",
|
||||
fallback: Traverser.getKeys
|
||||
});
|
||||
|
||||
currentScopes = scopeManager.scopes;
|
||||
this.currentScopes = this.scopeManager.scopes;
|
||||
|
||||
// augment global scope with declared global variables
|
||||
addDeclaredGlobals(ast, currentScopes[0], currentConfig);
|
||||
addDeclaredGlobals(ast, this.currentScopes[0], this.currentConfig, this.environments);
|
||||
|
||||
// remove shebang comments
|
||||
if (shebang && ast.comments.length && ast.comments[0].value === shebang) {
|
||||
ast.comments.splice(0, 1);
|
||||
|
||||
if (ast.body.length && ast.body[0].leadingComments && ast.body[0].leadingComments[0].value === shebang) {
|
||||
ast.body[0].leadingComments.splice(0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
let eventGenerator = new NodeEventGenerator(api);
|
||||
let eventGenerator = new NodeEventGenerator(this);
|
||||
|
||||
eventGenerator = new CodePathAnalyzer(eventGenerator);
|
||||
eventGenerator = new CommentEventGenerator(eventGenerator, sourceCode);
|
||||
|
||||
/*
|
||||
* Each node has a type property. Whenever a particular type of
|
||||
@ -923,7 +914,7 @@ module.exports = (function() {
|
||||
* automatically be informed that this type of node has been found
|
||||
* and react accordingly.
|
||||
*/
|
||||
traverser.traverse(ast, {
|
||||
this.traverser.traverse(ast, {
|
||||
enter(node, parent) {
|
||||
node.parent = parent;
|
||||
eventGenerator.enterNode(node);
|
||||
@ -935,7 +926,7 @@ module.exports = (function() {
|
||||
}
|
||||
|
||||
// sort by line and column
|
||||
messages.sort((a, b) => {
|
||||
this.messages.sort((a, b) => {
|
||||
const lineDiff = a.line - b.line;
|
||||
|
||||
if (lineDiff === 0) {
|
||||
@ -945,8 +936,8 @@ module.exports = (function() {
|
||||
|
||||
});
|
||||
|
||||
return messages;
|
||||
};
|
||||
return this.messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports a message from one of the rules.
|
||||
@ -963,11 +954,13 @@ module.exports = (function() {
|
||||
* @param {Object} meta Metadata of the rule
|
||||
* @returns {void}
|
||||
*/
|
||||
api.report = function(ruleId, severity, node, location, message, opts, fix, meta) {
|
||||
report(ruleId, severity, node, location, message, opts, fix, meta) {
|
||||
if (node) {
|
||||
assert.strictEqual(typeof node, "object", "Node must be an object");
|
||||
}
|
||||
|
||||
let endLocation;
|
||||
|
||||
if (typeof location === "string") {
|
||||
assert.ok(node, "Node must be provided when reporting error if location is not provided");
|
||||
|
||||
@ -976,14 +969,14 @@ module.exports = (function() {
|
||||
opts = message;
|
||||
message = location;
|
||||
location = node.loc.start;
|
||||
endLocation = node.loc.end;
|
||||
} else {
|
||||
endLocation = location.end;
|
||||
}
|
||||
|
||||
// Store end location.
|
||||
const endLocation = location.end;
|
||||
|
||||
location = location.start || location;
|
||||
|
||||
if (isDisabledByReportingConfig(reportingConfig, ruleId, location)) {
|
||||
if (isDisabledByReportingConfig(this.reportingConfig, ruleId, location)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1003,15 +996,15 @@ module.exports = (function() {
|
||||
severity,
|
||||
message,
|
||||
line: location.line,
|
||||
column: location.column + 1, // switch to 1-base instead of 0-base
|
||||
column: location.column + 1, // switch to 1-base instead of 0-base
|
||||
nodeType: node && node.type,
|
||||
source: sourceCode.lines[location.line - 1] || ""
|
||||
source: this.sourceCode.lines[location.line - 1] || ""
|
||||
};
|
||||
|
||||
// Define endLine and endColumn if exists.
|
||||
if (endLocation) {
|
||||
problem.endLine = endLocation.line;
|
||||
problem.endColumn = endLocation.column + 1; // switch to 1-base instead of 0-base
|
||||
problem.endColumn = endLocation.column + 1; // switch to 1-base instead of 0-base
|
||||
}
|
||||
|
||||
// ensure there's range and text properties, otherwise it's not a valid fix
|
||||
@ -1025,73 +1018,39 @@ module.exports = (function() {
|
||||
problem.fix = fix;
|
||||
}
|
||||
|
||||
messages.push(problem);
|
||||
};
|
||||
this.messages.push(problem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SourceCode object representing the parsed source.
|
||||
* @returns {SourceCode} The SourceCode object.
|
||||
*/
|
||||
api.getSourceCode = function() {
|
||||
return sourceCode;
|
||||
};
|
||||
|
||||
// methods that exist on SourceCode object
|
||||
const externalMethods = {
|
||||
getSource: "getText",
|
||||
getSourceLines: "getLines",
|
||||
getAllComments: "getAllComments",
|
||||
getNodeByRangeIndex: "getNodeByRangeIndex",
|
||||
getComments: "getComments",
|
||||
getJSDocComment: "getJSDocComment",
|
||||
getFirstToken: "getFirstToken",
|
||||
getFirstTokens: "getFirstTokens",
|
||||
getLastToken: "getLastToken",
|
||||
getLastTokens: "getLastTokens",
|
||||
getTokenAfter: "getTokenAfter",
|
||||
getTokenBefore: "getTokenBefore",
|
||||
getTokenByRangeStart: "getTokenByRangeStart",
|
||||
getTokens: "getTokens",
|
||||
getTokensAfter: "getTokensAfter",
|
||||
getTokensBefore: "getTokensBefore",
|
||||
getTokensBetween: "getTokensBetween"
|
||||
};
|
||||
|
||||
// copy over methods
|
||||
Object.keys(externalMethods).forEach(methodName => {
|
||||
const exMethodName = externalMethods[methodName];
|
||||
|
||||
// All functions expected to have less arguments than 5.
|
||||
api[methodName] = function(a, b, c, d, e) {
|
||||
if (sourceCode) {
|
||||
return sourceCode[exMethodName](a, b, c, d, e);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
});
|
||||
getSourceCode() {
|
||||
return this.sourceCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets nodes that are ancestors of current node.
|
||||
* @returns {ASTNode[]} Array of objects representing ancestors.
|
||||
*/
|
||||
api.getAncestors = function() {
|
||||
return traverser.parents();
|
||||
};
|
||||
getAncestors() {
|
||||
return this.traverser.parents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the scope for the current node.
|
||||
* @returns {Object} An object representing the current node's scope.
|
||||
*/
|
||||
api.getScope = function() {
|
||||
const parents = traverser.parents();
|
||||
getScope() {
|
||||
const parents = this.traverser.parents();
|
||||
|
||||
// Don't do this for Program nodes - they have no parents
|
||||
if (parents.length) {
|
||||
|
||||
// if current node introduces a scope, add it to the list
|
||||
const current = traverser.current();
|
||||
const current = this.traverser.current();
|
||||
|
||||
if (currentConfig.parserOptions.ecmaVersion >= 6) {
|
||||
if (this.currentConfig.parserOptions.ecmaVersion >= 6) {
|
||||
if (["BlockStatement", "SwitchStatement", "CatchClause", "FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].indexOf(current.type) >= 0) {
|
||||
parents.push(current);
|
||||
}
|
||||
@ -1105,7 +1064,7 @@ module.exports = (function() {
|
||||
for (let i = parents.length - 1; i >= 0; --i) {
|
||||
|
||||
// Get the innermost scope
|
||||
const scope = scopeManager.acquire(parents[i], true);
|
||||
const scope = this.scopeManager.acquire(parents[i], true);
|
||||
|
||||
if (scope) {
|
||||
if (scope.type === "function-expression-name") {
|
||||
@ -1119,8 +1078,8 @@ module.exports = (function() {
|
||||
|
||||
}
|
||||
|
||||
return currentScopes[0];
|
||||
};
|
||||
return this.currentScopes[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Record that a particular variable has been used in code
|
||||
@ -1128,9 +1087,9 @@ module.exports = (function() {
|
||||
* @returns {boolean} True if the variable was found and marked as used,
|
||||
* false if not.
|
||||
*/
|
||||
api.markVariableAsUsed = function(name) {
|
||||
const hasGlobalReturn = currentConfig.parserOptions.ecmaFeatures && currentConfig.parserOptions.ecmaFeatures.globalReturn,
|
||||
specialScope = hasGlobalReturn || currentConfig.parserOptions.sourceType === "module";
|
||||
markVariableAsUsed(name) {
|
||||
const hasGlobalReturn = this.currentConfig.parserOptions.ecmaFeatures && this.currentConfig.parserOptions.ecmaFeatures.globalReturn,
|
||||
specialScope = hasGlobalReturn || this.currentConfig.parserOptions.sourceType === "module";
|
||||
let scope = this.getScope(),
|
||||
i,
|
||||
len;
|
||||
@ -1152,20 +1111,20 @@ module.exports = (function() {
|
||||
} while ((scope = scope.upper));
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the filename for the currently parsed source.
|
||||
* @returns {string} The filename associated with the source being parsed.
|
||||
* Defaults to "<input>" if no filename info is present.
|
||||
*/
|
||||
api.getFilename = function() {
|
||||
if (typeof currentFilename === "string") {
|
||||
return currentFilename;
|
||||
getFilename() {
|
||||
if (typeof this.currentFilename === "string") {
|
||||
return this.currentFilename;
|
||||
}
|
||||
return "<input>";
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a new linting rule.
|
||||
@ -1173,38 +1132,36 @@ module.exports = (function() {
|
||||
* @param {Function} ruleModule Function from context to object mapping AST node types to event handlers
|
||||
* @returns {void}
|
||||
*/
|
||||
const defineRule = api.defineRule = function(ruleId, ruleModule) {
|
||||
rules.define(ruleId, ruleModule);
|
||||
};
|
||||
defineRule(ruleId, ruleModule) {
|
||||
this.rules.define(ruleId, ruleModule);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines many new linting rules.
|
||||
* @param {Object} rulesToDefine map from unique rule identifier to rule
|
||||
* @returns {void}
|
||||
*/
|
||||
api.defineRules = function(rulesToDefine) {
|
||||
defineRules(rulesToDefine) {
|
||||
Object.getOwnPropertyNames(rulesToDefine).forEach(ruleId => {
|
||||
defineRule(ruleId, rulesToDefine[ruleId]);
|
||||
this.defineRule(ruleId, rulesToDefine[ruleId]);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default eslint configuration.
|
||||
* @returns {Object} Object mapping rule IDs to their default configurations
|
||||
*/
|
||||
api.defaults = function() {
|
||||
return require("../conf/eslint-recommended");
|
||||
};
|
||||
defaults() { // eslint-disable-line class-methods-use-this
|
||||
return defaultConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an object with all loaded rules.
|
||||
* @returns {Map} All loaded rules
|
||||
*/
|
||||
api.getRules = function() {
|
||||
return rules.getAllLoadedRules();
|
||||
};
|
||||
|
||||
api.version = pkg.version;
|
||||
getRules() {
|
||||
return this.rules.getAllLoadedRules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets variables that are declared by a specified node.
|
||||
@ -1223,12 +1180,48 @@ module.exports = (function() {
|
||||
* - others - always an empty array.
|
||||
*
|
||||
* @param {ASTNode} node A node to get.
|
||||
* @returns {escope.Variable[]} Variables that are declared by the node.
|
||||
* @returns {eslint-scope.Variable[]} Variables that are declared by the node.
|
||||
*/
|
||||
api.getDeclaredVariables = function(node) {
|
||||
return (scopeManager && scopeManager.getDeclaredVariables(node)) || [];
|
||||
getDeclaredVariables(node) {
|
||||
return (this.scopeManager && this.scopeManager.getDeclaredVariables(node)) || [];
|
||||
}
|
||||
}
|
||||
|
||||
// methods that exist on SourceCode object
|
||||
const externalMethods = {
|
||||
getSource: "getText",
|
||||
getSourceLines: "getLines",
|
||||
getAllComments: "getAllComments",
|
||||
getNodeByRangeIndex: "getNodeByRangeIndex",
|
||||
getComments: "getComments",
|
||||
getCommentsBefore: "getCommentsBefore",
|
||||
getCommentsAfter: "getCommentsAfter",
|
||||
getCommentsInside: "getCommentsInside",
|
||||
getJSDocComment: "getJSDocComment",
|
||||
getFirstToken: "getFirstToken",
|
||||
getFirstTokens: "getFirstTokens",
|
||||
getLastToken: "getLastToken",
|
||||
getLastTokens: "getLastTokens",
|
||||
getTokenAfter: "getTokenAfter",
|
||||
getTokenBefore: "getTokenBefore",
|
||||
getTokenByRangeStart: "getTokenByRangeStart",
|
||||
getTokens: "getTokens",
|
||||
getTokensAfter: "getTokensAfter",
|
||||
getTokensBefore: "getTokensBefore",
|
||||
getTokensBetween: "getTokensBetween"
|
||||
};
|
||||
|
||||
// copy over methods
|
||||
Object.keys(externalMethods).forEach(methodName => {
|
||||
const exMethodName = externalMethods[methodName];
|
||||
|
||||
// All functions expected to have less arguments than 5.
|
||||
Linter.prototype[methodName] = function(a, b, c, d, e) {
|
||||
if (this.sourceCode) {
|
||||
return this.sourceCode[exMethodName](a, b, c, d, e);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
});
|
||||
|
||||
return api;
|
||||
|
||||
}());
|
||||
module.exports = Linter;
|
@ -12,6 +12,8 @@
|
||||
const fs = require("fs"),
|
||||
path = require("path");
|
||||
|
||||
const rulesDirCache = {};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
@ -29,6 +31,11 @@ module.exports = function(rulesDir, cwd) {
|
||||
rulesDir = path.resolve(cwd, rulesDir);
|
||||
}
|
||||
|
||||
// cache will help performance as IO operation are expensive
|
||||
if (rulesDirCache[rulesDir]) {
|
||||
return rulesDirCache[rulesDir];
|
||||
}
|
||||
|
||||
const rules = Object.create(null);
|
||||
|
||||
fs.readdirSync(rulesDir).forEach(file => {
|
||||
@ -37,5 +44,7 @@ module.exports = function(rulesDir, cwd) {
|
||||
}
|
||||
rules[file.slice(0, -3)] = path.join(rulesDir, file);
|
||||
});
|
||||
rulesDirCache[rulesDir] = rules;
|
||||
|
||||
return rules;
|
||||
};
|
||||
|
@ -11,115 +11,85 @@
|
||||
|
||||
const loadRules = require("./load-rules");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Privates
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
let rules = Object.create(null);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Registers a rule module for rule id in storage.
|
||||
* @param {string} ruleId Rule id (file name).
|
||||
* @param {Function} ruleModule Rule handler.
|
||||
* @returns {void}
|
||||
*/
|
||||
function define(ruleId, ruleModule) {
|
||||
rules[ruleId] = ruleModule;
|
||||
}
|
||||
class Rules {
|
||||
constructor() {
|
||||
this._rules = Object.create(null);
|
||||
|
||||
/**
|
||||
* Loads and registers all rules from passed rules directory.
|
||||
* @param {string} [rulesDir] Path to rules directory, may be relative. Defaults to `lib/rules`.
|
||||
* @param {string} cwd Current working directory
|
||||
* @returns {void}
|
||||
*/
|
||||
function load(rulesDir, cwd) {
|
||||
const newRules = loadRules(rulesDir, cwd);
|
||||
|
||||
Object.keys(newRules).forEach(ruleId => {
|
||||
define(ruleId, newRules[ruleId]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all given rules of a plugin.
|
||||
* @param {Object} plugin The plugin object to import.
|
||||
* @param {string} pluginName The name of the plugin without prefix (`eslint-plugin-`).
|
||||
* @returns {void}
|
||||
*/
|
||||
function importPlugin(plugin, pluginName) {
|
||||
if (plugin.rules) {
|
||||
Object.keys(plugin.rules).forEach(ruleId => {
|
||||
const qualifiedRuleId = `${pluginName}/${ruleId}`,
|
||||
rule = plugin.rules[ruleId];
|
||||
|
||||
define(qualifiedRuleId, rule);
|
||||
});
|
||||
this.load();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Access rule handler by id (file name).
|
||||
* @param {string} ruleId Rule id (file name).
|
||||
* @returns {Function} Rule handler.
|
||||
*/
|
||||
function getHandler(ruleId) {
|
||||
if (typeof rules[ruleId] === "string") {
|
||||
return require(rules[ruleId]);
|
||||
}
|
||||
return rules[ruleId];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an object with all currently loaded rules
|
||||
* @returns {Map} All loaded rules
|
||||
*/
|
||||
function getAllLoadedRules() {
|
||||
const allRules = new Map();
|
||||
|
||||
Object.keys(rules).forEach(name => {
|
||||
const rule = getHandler(name);
|
||||
|
||||
allRules.set(name, rule);
|
||||
});
|
||||
return allRules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset rules storage.
|
||||
* Should be used only in tests.
|
||||
* @returns {void}
|
||||
*/
|
||||
function testClear() {
|
||||
rules = Object.create(null);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
define,
|
||||
load,
|
||||
importPlugin,
|
||||
get: getHandler,
|
||||
getAllLoadedRules,
|
||||
testClear,
|
||||
|
||||
/**
|
||||
* Resets rules to its starting state. Use for tests only.
|
||||
* Registers a rule module for rule id in storage.
|
||||
* @param {string} ruleId Rule id (file name).
|
||||
* @param {Function} ruleModule Rule handler.
|
||||
* @returns {void}
|
||||
*/
|
||||
testReset() {
|
||||
testClear();
|
||||
load();
|
||||
define(ruleId, ruleModule) {
|
||||
this._rules[ruleId] = ruleModule;
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Initialization
|
||||
//------------------------------------------------------------------------------
|
||||
/**
|
||||
* Loads and registers all rules from passed rules directory.
|
||||
* @param {string} [rulesDir] Path to rules directory, may be relative. Defaults to `lib/rules`.
|
||||
* @param {string} cwd Current working directory
|
||||
* @returns {void}
|
||||
*/
|
||||
load(rulesDir, cwd) {
|
||||
const newRules = loadRules(rulesDir, cwd);
|
||||
|
||||
// loads built-in rules
|
||||
load();
|
||||
Object.keys(newRules).forEach(ruleId => {
|
||||
this.define(ruleId, newRules[ruleId]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all given rules of a plugin.
|
||||
* @param {Object} plugin The plugin object to import.
|
||||
* @param {string} pluginName The name of the plugin without prefix (`eslint-plugin-`).
|
||||
* @returns {void}
|
||||
*/
|
||||
importPlugin(plugin, pluginName) {
|
||||
if (plugin.rules) {
|
||||
Object.keys(plugin.rules).forEach(ruleId => {
|
||||
const qualifiedRuleId = `${pluginName}/${ruleId}`,
|
||||
rule = plugin.rules[ruleId];
|
||||
|
||||
this.define(qualifiedRuleId, rule);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Access rule handler by id (file name).
|
||||
* @param {string} ruleId Rule id (file name).
|
||||
* @returns {Function} Rule handler.
|
||||
*/
|
||||
get(ruleId) {
|
||||
if (typeof this._rules[ruleId] === "string") {
|
||||
return require(this._rules[ruleId]);
|
||||
}
|
||||
return this._rules[ruleId];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an object with all currently loaded rules
|
||||
* @returns {Map} All loaded rules
|
||||
*/
|
||||
getAllLoadedRules() {
|
||||
const allRules = new Map();
|
||||
|
||||
Object.keys(this._rules).forEach(name => {
|
||||
const rule = this.get(name);
|
||||
|
||||
allRules.set(name, rule);
|
||||
});
|
||||
return allRules;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Rules;
|
||||
|
235
tools/eslint/lib/rules/array-bracket-newline.js
Normal file
235
tools/eslint/lib/rules/array-bracket-newline.js
Normal file
@ -0,0 +1,235 @@
|
||||
/**
|
||||
* @fileoverview Rule to enforce linebreaks after open and before close array brackets
|
||||
* @author Jan Peer Stöcklmair <https://github.com/JPeer264>
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
docs: {
|
||||
description: "enforce linebreaks after opening and before closing array brackets",
|
||||
category: "Stylistic Issues",
|
||||
recommended: false
|
||||
},
|
||||
fixable: "whitespace",
|
||||
schema: [
|
||||
{
|
||||
oneOf: [
|
||||
{
|
||||
enum: ["always", "never"]
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
multiline: {
|
||||
type: "boolean"
|
||||
},
|
||||
minItems: {
|
||||
type: ["integer", "null"],
|
||||
minimum: 0
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const sourceCode = context.getSourceCode();
|
||||
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Helpers
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Normalizes a given option value.
|
||||
*
|
||||
* @param {string|Object|undefined} option - An option value to parse.
|
||||
* @returns {{multiline: boolean, minItems: number}} Normalized option object.
|
||||
*/
|
||||
function normalizeOptionValue(option) {
|
||||
let multiline = false;
|
||||
let minItems = 0;
|
||||
|
||||
if (option) {
|
||||
if (option === "always" || option.minItems === 0) {
|
||||
minItems = 0;
|
||||
} else if (option === "never") {
|
||||
minItems = Number.POSITIVE_INFINITY;
|
||||
} else {
|
||||
multiline = Boolean(option.multiline);
|
||||
minItems = option.minItems || Number.POSITIVE_INFINITY;
|
||||
}
|
||||
} else {
|
||||
multiline = true;
|
||||
minItems = Number.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
return { multiline, minItems };
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a given option value.
|
||||
*
|
||||
* @param {string|Object|undefined} options - An option value to parse.
|
||||
* @returns {{ArrayExpression: {multiline: boolean, minItems: number}, ArrayPattern: {multiline: boolean, minItems: number}}} Normalized option object.
|
||||
*/
|
||||
function normalizeOptions(options) {
|
||||
const value = normalizeOptionValue(options);
|
||||
|
||||
return { ArrayExpression: value, ArrayPattern: value };
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there shouldn't be a linebreak after the first token
|
||||
* @param {ASTNode} node - The node to report in the event of an error.
|
||||
* @param {Token} token - The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportNoBeginningLinebreak(node, token) {
|
||||
context.report({
|
||||
node,
|
||||
loc: token.loc,
|
||||
message: "There should be no linebreak after '['.",
|
||||
fix(fixer) {
|
||||
const nextToken = sourceCode.getTokenAfter(token, { includeComments: true });
|
||||
|
||||
if (astUtils.isCommentToken(nextToken)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fixer.removeRange([token.range[1], nextToken.range[0]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there shouldn't be a linebreak before the last token
|
||||
* @param {ASTNode} node - The node to report in the event of an error.
|
||||
* @param {Token} token - The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportNoEndingLinebreak(node, token) {
|
||||
context.report({
|
||||
node,
|
||||
loc: token.loc,
|
||||
message: "There should be no linebreak before ']'.",
|
||||
fix(fixer) {
|
||||
const previousToken = sourceCode.getTokenBefore(token, { includeComments: true });
|
||||
|
||||
if (astUtils.isCommentToken(previousToken)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fixer.removeRange([previousToken.range[1], token.range[0]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there should be a linebreak after the first token
|
||||
* @param {ASTNode} node - The node to report in the event of an error.
|
||||
* @param {Token} token - The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportRequiredBeginningLinebreak(node, token) {
|
||||
context.report({
|
||||
node,
|
||||
loc: token.loc,
|
||||
message: "A linebreak is required after '['.",
|
||||
fix(fixer) {
|
||||
return fixer.insertTextAfter(token, "\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there should be a linebreak before the last token
|
||||
* @param {ASTNode} node - The node to report in the event of an error.
|
||||
* @param {Token} token - The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportRequiredEndingLinebreak(node, token) {
|
||||
context.report({
|
||||
node,
|
||||
loc: token.loc,
|
||||
message: "A linebreak is required before ']'.",
|
||||
fix(fixer) {
|
||||
return fixer.insertTextBefore(token, "\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports a given node if it violated this rule.
|
||||
*
|
||||
* @param {ASTNode} node - A node to check. This is an ObjectExpression node or an ObjectPattern node.
|
||||
* @param {{multiline: boolean, minItems: number}} options - An option object.
|
||||
* @returns {void}
|
||||
*/
|
||||
function check(node) {
|
||||
const elements = node.elements;
|
||||
const normalizedOptions = normalizeOptions(context.options[0]);
|
||||
const options = normalizedOptions[node.type];
|
||||
const openBracket = sourceCode.getFirstToken(node);
|
||||
const closeBracket = sourceCode.getLastToken(node);
|
||||
const firstIncComment = sourceCode.getTokenAfter(openBracket, { includeComments: true });
|
||||
const lastIncComment = sourceCode.getTokenBefore(closeBracket, { includeComments: true });
|
||||
const first = sourceCode.getTokenAfter(openBracket);
|
||||
const last = sourceCode.getTokenBefore(closeBracket);
|
||||
|
||||
const needsLinebreaks = (
|
||||
elements.length >= options.minItems ||
|
||||
(
|
||||
options.multiline &&
|
||||
elements.length > 0 &&
|
||||
firstIncComment.loc.start.line !== lastIncComment.loc.end.line
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
* Use tokens or comments to check multiline or not.
|
||||
* But use only tokens to check whether linebreaks are needed.
|
||||
* This allows:
|
||||
* var arr = [ // eslint-disable-line foo
|
||||
* 'a'
|
||||
* ]
|
||||
*/
|
||||
|
||||
if (needsLinebreaks) {
|
||||
if (astUtils.isTokenOnSameLine(openBracket, first)) {
|
||||
reportRequiredBeginningLinebreak(node, openBracket);
|
||||
}
|
||||
if (astUtils.isTokenOnSameLine(last, closeBracket)) {
|
||||
reportRequiredEndingLinebreak(node, closeBracket);
|
||||
}
|
||||
} else {
|
||||
if (!astUtils.isTokenOnSameLine(openBracket, first)) {
|
||||
reportNoBeginningLinebreak(node, openBracket);
|
||||
}
|
||||
if (!astUtils.isTokenOnSameLine(last, closeBracket)) {
|
||||
reportNoEndingLinebreak(node, closeBracket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Public
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
ArrayPattern: check,
|
||||
ArrayExpression: check
|
||||
};
|
||||
}
|
||||
};
|
230
tools/eslint/lib/rules/array-element-newline.js
Normal file
230
tools/eslint/lib/rules/array-element-newline.js
Normal file
@ -0,0 +1,230 @@
|
||||
/**
|
||||
* @fileoverview Rule to enforce line breaks after each array element
|
||||
* @author Jan Peer Stöcklmair <https://github.com/JPeer264>
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
docs: {
|
||||
description: "enforce line breaks after each array element",
|
||||
category: "Stylistic Issues",
|
||||
recommended: false
|
||||
},
|
||||
fixable: "whitespace",
|
||||
schema: [
|
||||
{
|
||||
oneOf: [
|
||||
{
|
||||
enum: ["always", "never"]
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
multiline: {
|
||||
type: "boolean"
|
||||
},
|
||||
minItems: {
|
||||
type: ["integer", "null"],
|
||||
minimum: 0
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const sourceCode = context.getSourceCode();
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Helpers
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Normalizes a given option value.
|
||||
*
|
||||
* @param {string|Object|undefined} option - An option value to parse.
|
||||
* @returns {{multiline: boolean, minItems: number}} Normalized option object.
|
||||
*/
|
||||
function normalizeOptionValue(option) {
|
||||
let multiline = false;
|
||||
let minItems;
|
||||
|
||||
option = option || "always";
|
||||
|
||||
if (option === "always" || option.minItems === 0) {
|
||||
minItems = 0;
|
||||
} else if (option === "never") {
|
||||
minItems = Number.POSITIVE_INFINITY;
|
||||
} else {
|
||||
multiline = Boolean(option.multiline);
|
||||
minItems = option.minItems || Number.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
return { multiline, minItems };
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a given option value.
|
||||
*
|
||||
* @param {string|Object|undefined} options - An option value to parse.
|
||||
* @returns {{ArrayExpression: {multiline: boolean, minItems: number}, ArrayPattern: {multiline: boolean, minItems: number}}} Normalized option object.
|
||||
*/
|
||||
function normalizeOptions(options) {
|
||||
const value = normalizeOptionValue(options);
|
||||
|
||||
return { ArrayExpression: value, ArrayPattern: value };
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there shouldn't be a line break after the first token
|
||||
* @param {Token} token - The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportNoLineBreak(token) {
|
||||
const tokenBefore = sourceCode.getTokenBefore(token, { includeComments: true });
|
||||
|
||||
context.report({
|
||||
loc: {
|
||||
start: tokenBefore.loc.end,
|
||||
end: token.loc.start
|
||||
},
|
||||
message: "There should be no linebreak here.",
|
||||
fix(fixer) {
|
||||
if (astUtils.isCommentToken(tokenBefore)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!astUtils.isTokenOnSameLine(tokenBefore, token)) {
|
||||
return fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], " ");
|
||||
}
|
||||
|
||||
/*
|
||||
* This will check if the comma is on the same line as the next element
|
||||
* Following array:
|
||||
* [
|
||||
* 1
|
||||
* , 2
|
||||
* , 3
|
||||
* ]
|
||||
*
|
||||
* will be fixed to:
|
||||
* [
|
||||
* 1, 2, 3
|
||||
* ]
|
||||
*/
|
||||
const twoTokensBefore = sourceCode.getTokenBefore(tokenBefore, { includeComments: true });
|
||||
|
||||
if (astUtils.isCommentToken(twoTokensBefore)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fixer.replaceTextRange([twoTokensBefore.range[1], tokenBefore.range[0]], "");
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there should be a line break after the first token
|
||||
* @param {Token} token - The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportRequiredLineBreak(token) {
|
||||
const tokenBefore = sourceCode.getTokenBefore(token, { includeComments: true });
|
||||
|
||||
context.report({
|
||||
loc: {
|
||||
start: tokenBefore.loc.end,
|
||||
end: token.loc.start
|
||||
},
|
||||
message: "There should be a linebreak after this element.",
|
||||
fix(fixer) {
|
||||
return fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], "\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports a given node if it violated this rule.
|
||||
*
|
||||
* @param {ASTNode} node - A node to check. This is an ObjectExpression node or an ObjectPattern node.
|
||||
* @param {{multiline: boolean, minItems: number}} options - An option object.
|
||||
* @returns {void}
|
||||
*/
|
||||
function check(node) {
|
||||
const elements = node.elements;
|
||||
const normalizedOptions = normalizeOptions(context.options[0]);
|
||||
const options = normalizedOptions[node.type];
|
||||
|
||||
let elementBreak = false;
|
||||
|
||||
/*
|
||||
* MULTILINE: true
|
||||
* loop through every element and check
|
||||
* if at least one element has linebreaks inside
|
||||
* this ensures that following is not valid (due to elements are on the same line):
|
||||
*
|
||||
* [
|
||||
* 1,
|
||||
* 2,
|
||||
* 3
|
||||
* ]
|
||||
*/
|
||||
if (options.multiline) {
|
||||
elementBreak = elements
|
||||
.filter(element => element !== null)
|
||||
.some(element => element.loc.start.line !== element.loc.end.line);
|
||||
}
|
||||
|
||||
const needsLinebreaks = (
|
||||
elements.length >= options.minItems ||
|
||||
(
|
||||
options.multiline &&
|
||||
elementBreak
|
||||
)
|
||||
);
|
||||
|
||||
elements.forEach((element, i) => {
|
||||
const previousElement = elements[i - 1];
|
||||
|
||||
if (i === 0 || element === null || previousElement === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const commaToken = sourceCode.getFirstTokenBetween(previousElement, element, astUtils.isCommaToken);
|
||||
const lastTokenOfPreviousElement = sourceCode.getTokenBefore(commaToken);
|
||||
const firstTokenOfCurrentElement = sourceCode.getTokenAfter(commaToken);
|
||||
|
||||
if (needsLinebreaks) {
|
||||
if (astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) {
|
||||
reportRequiredLineBreak(firstTokenOfCurrentElement);
|
||||
}
|
||||
} else {
|
||||
if (!astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) {
|
||||
reportNoLineBreak(firstTokenOfCurrentElement);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Public
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
ArrayPattern: check,
|
||||
ArrayExpression: check
|
||||
};
|
||||
}
|
||||
};
|
@ -50,14 +50,31 @@ module.exports = {
|
||||
|
||||
const sourceCode = context.getSourceCode();
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether a arrow function argument end with `)`
|
||||
* @param {ASTNode} node The arrow function node.
|
||||
* @returns {void}
|
||||
*/
|
||||
function parens(node) {
|
||||
const token = sourceCode.getFirstToken(node, node.async ? 1 : 0);
|
||||
const isAsync = node.async;
|
||||
const firstTokenOfParam = sourceCode.getFirstToken(node, isAsync ? 1 : 0);
|
||||
|
||||
/**
|
||||
* Remove the parenthesis around a parameter
|
||||
* @param {Fixer} fixer Fixer
|
||||
* @returns {string} fixed parameter
|
||||
*/
|
||||
function fixParamsWithParenthesis(fixer) {
|
||||
const paramToken = sourceCode.getTokenAfter(firstTokenOfParam);
|
||||
const closingParenToken = sourceCode.getTokenAfter(paramToken);
|
||||
const asyncToken = isAsync ? sourceCode.getTokenBefore(firstTokenOfParam) : null;
|
||||
const shouldAddSpaceForAsync = asyncToken && (asyncToken.end === firstTokenOfParam.start);
|
||||
|
||||
return fixer.replaceTextRange([
|
||||
firstTokenOfParam.range[0],
|
||||
closingParenToken.range[1]
|
||||
], `${shouldAddSpaceForAsync ? " " : ""}${paramToken.value}`);
|
||||
}
|
||||
|
||||
// "as-needed", { "requireForBlockBody": true }: x => x
|
||||
if (
|
||||
@ -68,19 +85,11 @@ module.exports = {
|
||||
node.body.type !== "BlockStatement" &&
|
||||
!node.returnType
|
||||
) {
|
||||
if (astUtils.isOpeningParenToken(token)) {
|
||||
if (astUtils.isOpeningParenToken(firstTokenOfParam)) {
|
||||
context.report({
|
||||
node,
|
||||
message: requireForBlockBodyMessage,
|
||||
fix(fixer) {
|
||||
const paramToken = context.getTokenAfter(token);
|
||||
const closingParenToken = context.getTokenAfter(paramToken);
|
||||
|
||||
return fixer.replaceTextRange([
|
||||
token.range[0],
|
||||
closingParenToken.range[1]
|
||||
], paramToken.value);
|
||||
}
|
||||
fix: fixParamsWithParenthesis
|
||||
});
|
||||
}
|
||||
return;
|
||||
@ -90,12 +99,12 @@ module.exports = {
|
||||
requireForBlockBody &&
|
||||
node.body.type === "BlockStatement"
|
||||
) {
|
||||
if (!astUtils.isOpeningParenToken(token)) {
|
||||
if (!astUtils.isOpeningParenToken(firstTokenOfParam)) {
|
||||
context.report({
|
||||
node,
|
||||
message: requireForBlockBodyNoParensMessage,
|
||||
fix(fixer) {
|
||||
return fixer.replaceText(token, `(${token.value})`);
|
||||
return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -109,26 +118,18 @@ module.exports = {
|
||||
!node.params[0].typeAnnotation &&
|
||||
!node.returnType
|
||||
) {
|
||||
if (astUtils.isOpeningParenToken(token)) {
|
||||
if (astUtils.isOpeningParenToken(firstTokenOfParam)) {
|
||||
context.report({
|
||||
node,
|
||||
message: asNeededMessage,
|
||||
fix(fixer) {
|
||||
const paramToken = context.getTokenAfter(token);
|
||||
const closingParenToken = context.getTokenAfter(paramToken);
|
||||
|
||||
return fixer.replaceTextRange([
|
||||
token.range[0],
|
||||
closingParenToken.range[1]
|
||||
], paramToken.value);
|
||||
}
|
||||
fix: fixParamsWithParenthesis
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.type === "Identifier") {
|
||||
const after = sourceCode.getTokenAfter(token);
|
||||
if (firstTokenOfParam.type === "Identifier") {
|
||||
const after = sourceCode.getTokenAfter(firstTokenOfParam);
|
||||
|
||||
// (x) => x
|
||||
if (after.value !== ")") {
|
||||
@ -136,7 +137,7 @@ module.exports = {
|
||||
node,
|
||||
message,
|
||||
fix(fixer) {
|
||||
return fixer.replaceText(token, `(${token.value})`);
|
||||
return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ module.exports = {
|
||||
|
||||
/**
|
||||
* Reports a given reference.
|
||||
* @param {escope.Reference} reference - A reference to report.
|
||||
* @param {eslint-scope.Reference} reference - A reference to report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function report(reference) {
|
||||
|
@ -62,10 +62,12 @@ module.exports = {
|
||||
function removeNewlineBetween(firstToken, secondToken) {
|
||||
const textRange = [firstToken.range[1], secondToken.range[0]];
|
||||
const textBetween = sourceCode.text.slice(textRange[0], textRange[1]);
|
||||
const NEWLINE_REGEX = astUtils.createGlobalLinebreakMatcher();
|
||||
|
||||
// Don't do a fix if there is a comment between the tokens
|
||||
return fixer => fixer.replaceTextRange(textRange, textBetween.trim() ? null : textBetween.replace(NEWLINE_REGEX, ""));
|
||||
if (textBetween.trim()) {
|
||||
return null;
|
||||
}
|
||||
return fixer => fixer.replaceTextRange(textRange, " ");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,7 +19,7 @@ const ALWAYS_MESSAGE = "Comments should not begin with a lowercase character",
|
||||
NEVER_MESSAGE = "Comments should not begin with an uppercase character",
|
||||
DEFAULT_IGNORE_PATTERN = astUtils.COMMENTS_IGNORE_PATTERN,
|
||||
WHITESPACE = /\s/g,
|
||||
MAYBE_URL = /^\s*[^:/?#\s]+:\/\/[^?#]/, // TODO: Combine w/ max-len pattern?
|
||||
MAYBE_URL = /^\s*[^:/?#\s]+:\/\/[^?#]/, // TODO: Combine w/ max-len pattern?
|
||||
DEFAULTS = {
|
||||
ignorePattern: null,
|
||||
ignoreInlineComments: false,
|
||||
@ -270,7 +270,7 @@ module.exports = {
|
||||
: NEVER_MESSAGE;
|
||||
|
||||
context.report({
|
||||
node: null, // Intentionally using loc instead
|
||||
node: null, // Intentionally using loc instead
|
||||
loc: comment.loc,
|
||||
message,
|
||||
fix(fixer) {
|
||||
@ -295,7 +295,7 @@ module.exports = {
|
||||
Program() {
|
||||
const comments = sourceCode.getAllComments();
|
||||
|
||||
comments.forEach(processComment);
|
||||
comments.filter(token => token.type !== "Shebang").forEach(processComment);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -87,8 +87,8 @@ module.exports = {
|
||||
|
||||
},
|
||||
message: options[dir]
|
||||
? "A space is required {{dir}} ','."
|
||||
: "There should be no space {{dir}} ','.",
|
||||
? "A space is required {{dir}} ','."
|
||||
: "There should be no space {{dir}} ','.",
|
||||
data: {
|
||||
dir
|
||||
}
|
||||
|
@ -202,7 +202,7 @@ module.exports = {
|
||||
*/
|
||||
if (astUtils.isCommaToken(commaToken)) {
|
||||
validateCommaItemSpacing(previousItemToken, commaToken,
|
||||
currentItemToken, reportItem);
|
||||
currentItemToken, reportItem);
|
||||
}
|
||||
|
||||
if (item) {
|
||||
|
@ -122,7 +122,7 @@ module.exports = {
|
||||
|
||||
// Avoiding `default`
|
||||
if (node.test) {
|
||||
increaseComplexity(node);
|
||||
increaseComplexity();
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,7 +136,7 @@ module.exports = {
|
||||
|
||||
// Avoiding &&
|
||||
if (node.operator === "||") {
|
||||
increaseComplexity(node);
|
||||
increaseComplexity();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,6 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
const esUtils = require("esutils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
@ -240,7 +239,7 @@ module.exports = {
|
||||
// e.g. `do{foo()} while (bar)` should be corrected to `do foo() while (bar)`
|
||||
const needsPrecedingSpace = node.type === "DoWhileStatement" &&
|
||||
sourceCode.getTokenBefore(bodyNode).end === bodyNode.start &&
|
||||
esUtils.code.isIdentifierPartES6(sourceCode.getText(bodyNode).charCodeAt(1));
|
||||
!astUtils.canTokensBeAdjacent("do", sourceCode.getFirstToken(bodyNode, { skip: 1 }));
|
||||
|
||||
const openingBracket = sourceCode.getFirstToken(bodyNode);
|
||||
const closingBracket = sourceCode.getLastToken(bodyNode);
|
||||
@ -294,7 +293,7 @@ module.exports = {
|
||||
}
|
||||
} else if (multiOrNest) {
|
||||
if (hasBlock && body.body.length === 1 && isOneLiner(body.body[0])) {
|
||||
const leadingComments = sourceCode.getComments(body.body[0]).leading;
|
||||
const leadingComments = sourceCode.getCommentsBefore(body.body[0]);
|
||||
|
||||
expected = leadingComments.length > 0;
|
||||
} else if (!isOneLiner(body)) {
|
||||
|
@ -74,7 +74,7 @@ module.exports = {
|
||||
let comment;
|
||||
|
||||
const lastCase = last(node.cases);
|
||||
const comments = sourceCode.getComments(lastCase).trailing;
|
||||
const comments = sourceCode.getCommentsAfter(lastCase);
|
||||
|
||||
if (comments.length) {
|
||||
comment = last(comments);
|
||||
|
@ -79,11 +79,17 @@ module.exports = {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tokenAfterProperty = sourceCode.getTokenAfter(rightBracket);
|
||||
const needsSpaceAfterProperty = tokenAfterProperty &&
|
||||
rightBracket.range[1] === tokenAfterProperty.range[0] &&
|
||||
!astUtils.canTokensBeAdjacent(String(node.property.value), tokenAfterProperty);
|
||||
|
||||
const textBeforeDot = astUtils.isDecimalInteger(node.object) ? " " : "";
|
||||
const textAfterProperty = needsSpaceAfterProperty ? " " : "";
|
||||
|
||||
return fixer.replaceTextRange(
|
||||
[leftBracket.range[0], rightBracket.range[1]],
|
||||
`${textBeforeDot}.${node.property.value}`
|
||||
`${textBeforeDot}.${node.property.value}${textAfterProperty}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
105
tools/eslint/lib/rules/for-direction.js
Normal file
105
tools/eslint/lib/rules/for-direction.js
Normal file
@ -0,0 +1,105 @@
|
||||
/**
|
||||
* @fileoverview enforce "for" loop update clause moving the counter in the right direction.(for-direction)
|
||||
* @author Aladdin-ADD<hh_2013@foxmail.com>
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
docs: {
|
||||
description: "enforce \"for\" loop update clause moving the counter in the right direction.",
|
||||
category: "Possible Errors",
|
||||
recommended: false
|
||||
},
|
||||
fixable: null,
|
||||
schema: []
|
||||
},
|
||||
|
||||
create(context) {
|
||||
|
||||
/**
|
||||
* report an error.
|
||||
* @param {ASTNode} node the node to report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function report(node) {
|
||||
context.report({
|
||||
node,
|
||||
message: "The update clause in this loop moves the variable in the wrong direction."
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* check UpdateExpression add/sub the counter
|
||||
* @param {ASTNode} update UpdateExpression to check
|
||||
* @param {string} counter variable name to check
|
||||
* @returns {int} if add return 1, if sub return -1, if nochange, return 0
|
||||
*/
|
||||
function getUpdateDirection(update, counter) {
|
||||
if (update.argument.type === "Identifier" && update.argument.name === counter) {
|
||||
if (update.operator === "++") {
|
||||
return 1;
|
||||
}
|
||||
if (update.operator === "--") {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* check AssignmentExpression add/sub the counter
|
||||
* @param {ASTNode} update AssignmentExpression to check
|
||||
* @param {string} counter variable name to check
|
||||
* @returns {int} if add return 1, if sub return -1, if nochange, return 0
|
||||
*/
|
||||
function getAssignmentDirection(update, counter) {
|
||||
if (update.left.name === counter) {
|
||||
if (update.operator === "+=") {
|
||||
return 1;
|
||||
}
|
||||
if (update.operator === "-=") {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return {
|
||||
ForStatement(node) {
|
||||
|
||||
if (node.test && node.test.type === "BinaryExpression" && node.test.left.type === "Identifier" && node.update) {
|
||||
const counter = node.test.left.name;
|
||||
const operator = node.test.operator;
|
||||
const update = node.update;
|
||||
|
||||
if (operator === "<" || operator === "<=") {
|
||||
|
||||
// report error if update sub the counter (--, -=)
|
||||
if (update.type === "UpdateExpression" && getUpdateDirection(update, counter) < 0) {
|
||||
report(node);
|
||||
}
|
||||
|
||||
if (update.type === "AssignmentExpression" && getAssignmentDirection(update, counter) < 0) {
|
||||
report(node);
|
||||
}
|
||||
} else if (operator === ">" || operator === ">=") {
|
||||
|
||||
// report error if update add the counter (++, +=)
|
||||
if (update.type === "UpdateExpression" && getUpdateDirection(update, counter) > 0) {
|
||||
report(node);
|
||||
}
|
||||
|
||||
if (update.type === "AssignmentExpression" && getAssignmentDirection(update, counter) > 0) {
|
||||
report(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
@ -13,7 +13,7 @@ const astUtils = require("../ast-utils");
|
||||
|
||||
/**
|
||||
* Checks whether or not a given variable is a function name.
|
||||
* @param {escope.Variable} variable - A variable to check.
|
||||
* @param {eslint-scope.Variable} variable - A variable to check.
|
||||
* @returns {boolean} `true` if the variable is a function name.
|
||||
*/
|
||||
function isFunctionName(variable) {
|
||||
|
@ -17,7 +17,7 @@ const ACCEPTABLE_PARENTS = [
|
||||
];
|
||||
|
||||
/**
|
||||
* Finds the escope reference in the given scope.
|
||||
* Finds the eslint-scope reference in the given scope.
|
||||
* @param {Object} scope The scope to search.
|
||||
* @param {ASTNode} node The identifier node.
|
||||
* @returns {Reference|null} Returns the found reference or null if none were found.
|
||||
|
@ -96,7 +96,7 @@ module.exports = {
|
||||
const isLong = name.length > maxLength;
|
||||
|
||||
if (!(isShort || isLong) || exceptions[name]) {
|
||||
return; // Nothing to report
|
||||
return; // Nothing to report
|
||||
}
|
||||
|
||||
const isValidExpression = SUPPORTED_EXPRESSIONS[parent.type];
|
||||
|
1125
tools/eslint/lib/rules/indent-legacy.js
Normal file
1125
tools/eslint/lib/rules/indent-legacy.js
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -33,41 +33,6 @@ function last(arr) {
|
||||
return arr[arr.length - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a property is a member of the property group it follows.
|
||||
* @param {ASTNode} lastMember The last Property known to be in the group.
|
||||
* @param {ASTNode} candidate The next Property that might be in the group.
|
||||
* @returns {boolean} True if the candidate property is part of the group.
|
||||
*/
|
||||
function continuesPropertyGroup(lastMember, candidate) {
|
||||
const groupEndLine = lastMember.loc.start.line,
|
||||
candidateStartLine = candidate.loc.start.line;
|
||||
|
||||
if (candidateStartLine - groupEndLine <= 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check that the first comment is adjacent to the end of the group, the
|
||||
// last comment is adjacent to the candidate property, and that successive
|
||||
// comments are adjacent to each other.
|
||||
const comments = candidate.leadingComments;
|
||||
|
||||
if (
|
||||
comments &&
|
||||
comments[0].loc.start.line - groupEndLine <= 1 &&
|
||||
candidateStartLine - last(comments).loc.end.line <= 1
|
||||
) {
|
||||
for (let i = 1; i < comments.length; i++) {
|
||||
if (comments[i].loc.start.line - comments[i - 1].loc.end.line > 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a node is contained on a single line.
|
||||
* @param {ASTNode} node AST Node being evaluated.
|
||||
@ -350,6 +315,41 @@ module.exports = {
|
||||
|
||||
const sourceCode = context.getSourceCode();
|
||||
|
||||
/**
|
||||
* Checks whether a property is a member of the property group it follows.
|
||||
* @param {ASTNode} lastMember The last Property known to be in the group.
|
||||
* @param {ASTNode} candidate The next Property that might be in the group.
|
||||
* @returns {boolean} True if the candidate property is part of the group.
|
||||
*/
|
||||
function continuesPropertyGroup(lastMember, candidate) {
|
||||
const groupEndLine = lastMember.loc.start.line,
|
||||
candidateStartLine = candidate.loc.start.line;
|
||||
|
||||
if (candidateStartLine - groupEndLine <= 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check that the first comment is adjacent to the end of the group, the
|
||||
// last comment is adjacent to the candidate property, and that successive
|
||||
// comments are adjacent to each other.
|
||||
const leadingComments = sourceCode.getCommentsBefore(candidate);
|
||||
|
||||
if (
|
||||
leadingComments.length &&
|
||||
leadingComments[0].loc.start.line - groupEndLine <= 1 &&
|
||||
candidateStartLine - last(leadingComments).loc.end.line <= 1
|
||||
) {
|
||||
for (let i = 1; i < leadingComments.length; i++) {
|
||||
if (leadingComments[i].loc.start.line - leadingComments[i - 1].loc.end.line > 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the given property is key-value property.
|
||||
* @param {ASTNode} property Property node to check.
|
||||
@ -634,6 +634,5 @@ module.exports = {
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
|
@ -78,33 +78,37 @@ module.exports = {
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
LineComment(node) {
|
||||
if (applyDefaultIgnorePatterns && (defaultIgnoreRegExp.test(node.value) || fallThroughRegExp.test(node.value))) {
|
||||
return;
|
||||
}
|
||||
Program() {
|
||||
const comments = sourceCode.getAllComments();
|
||||
|
||||
if (ignorePattern && customIgnoreRegExp.test(node.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previous = sourceCode.getTokenBefore(node, { includeComments: true });
|
||||
const isOnSameLine = previous && previous.loc.end.line === node.loc.start.line;
|
||||
|
||||
if (above) {
|
||||
if (isOnSameLine) {
|
||||
context.report({
|
||||
node,
|
||||
message: "Expected comment to be above code."
|
||||
});
|
||||
comments.filter(token => token.type === "Line").forEach(node => {
|
||||
if (applyDefaultIgnorePatterns && (defaultIgnoreRegExp.test(node.value) || fallThroughRegExp.test(node.value))) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!isOnSameLine) {
|
||||
context.report({
|
||||
node,
|
||||
message: "Expected comment to be beside code."
|
||||
});
|
||||
|
||||
if (ignorePattern && customIgnoreRegExp.test(node.value)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const previous = sourceCode.getTokenBefore(node, { includeComments: true });
|
||||
const isOnSameLine = previous && previous.loc.end.line === node.loc.start.line;
|
||||
|
||||
if (above) {
|
||||
if (isOnSameLine) {
|
||||
context.report({
|
||||
node,
|
||||
message: "Expected comment to be above code."
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (!isOnSameLine) {
|
||||
context.report({
|
||||
node,
|
||||
message: "Expected comment to be beside code."
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ function getEmptyLineNums(lines) {
|
||||
|
||||
/**
|
||||
* Return an array with with any line numbers that contain comments.
|
||||
* @param {Array} comments An array of comment nodes.
|
||||
* @param {Array} comments An array of comment tokens.
|
||||
* @returns {Array} An array of line numbers.
|
||||
*/
|
||||
function getCommentLineNums(comments) {
|
||||
@ -131,38 +131,28 @@ module.exports = {
|
||||
emptyLines = getEmptyLineNums(lines),
|
||||
commentAndEmptyLines = commentLines.concat(emptyLines);
|
||||
|
||||
/**
|
||||
* Returns whether or not a token is a comment node type
|
||||
* @param {Token} token The token to check
|
||||
* @returns {boolean} True if the token is a comment node
|
||||
*/
|
||||
function isCommentNodeType(token) {
|
||||
return token && (token.type === "Block" || token.type === "Line");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not comments are on lines starting with or ending with code
|
||||
* @param {ASTNode} node The comment node to check.
|
||||
* @param {token} token The comment token to check.
|
||||
* @returns {boolean} True if the comment is not alone.
|
||||
*/
|
||||
function codeAroundComment(node) {
|
||||
let token;
|
||||
function codeAroundComment(token) {
|
||||
let currentToken = token;
|
||||
|
||||
token = node;
|
||||
do {
|
||||
token = sourceCode.getTokenBefore(token, { includeComments: true });
|
||||
} while (isCommentNodeType(token));
|
||||
currentToken = sourceCode.getTokenBefore(currentToken, { includeComments: true });
|
||||
} while (currentToken && astUtils.isCommentToken(currentToken));
|
||||
|
||||
if (token && astUtils.isTokenOnSameLine(token, node)) {
|
||||
if (currentToken && astUtils.isTokenOnSameLine(currentToken, token)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
token = node;
|
||||
currentToken = token;
|
||||
do {
|
||||
token = sourceCode.getTokenAfter(token, { includeComments: true });
|
||||
} while (isCommentNodeType(token));
|
||||
currentToken = sourceCode.getTokenAfter(currentToken, { includeComments: true });
|
||||
} while (currentToken && astUtils.isCommentToken(currentToken));
|
||||
|
||||
if (token && astUtils.isTokenOnSameLine(node, token)) {
|
||||
if (currentToken && astUtils.isTokenOnSameLine(token, currentToken)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -171,137 +161,135 @@ module.exports = {
|
||||
|
||||
/**
|
||||
* Returns whether or not comments are inside a node type or not.
|
||||
* @param {ASTNode} node The Comment node.
|
||||
* @param {ASTNode} parent The Comment parent node.
|
||||
* @param {string} nodeType The parent type to check against.
|
||||
* @returns {boolean} True if the comment is inside nodeType.
|
||||
*/
|
||||
function isCommentInsideNodeType(node, parent, nodeType) {
|
||||
function isParentNodeType(parent, nodeType) {
|
||||
return parent.type === nodeType ||
|
||||
(parent.body && parent.body.type === nodeType) ||
|
||||
(parent.consequent && parent.consequent.type === nodeType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent node that contains the given token.
|
||||
* @param {token} token The token to check.
|
||||
* @returns {ASTNode} The parent node that contains the given token.
|
||||
*/
|
||||
function getParentNodeOfToken(token) {
|
||||
return sourceCode.getNodeByRangeIndex(token.range[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not comments are at the parent start or not.
|
||||
* @param {ASTNode} node The Comment node.
|
||||
* @param {token} token The Comment token.
|
||||
* @param {string} nodeType The parent type to check against.
|
||||
* @returns {boolean} True if the comment is at parent start.
|
||||
*/
|
||||
function isCommentAtParentStart(node, nodeType) {
|
||||
const ancestors = context.getAncestors();
|
||||
let parent;
|
||||
function isCommentAtParentStart(token, nodeType) {
|
||||
const parent = getParentNodeOfToken(token);
|
||||
|
||||
if (ancestors.length) {
|
||||
parent = ancestors.pop();
|
||||
}
|
||||
|
||||
return parent && isCommentInsideNodeType(node, parent, nodeType) &&
|
||||
node.loc.start.line - parent.loc.start.line === 1;
|
||||
return parent && isParentNodeType(parent, nodeType) &&
|
||||
token.loc.start.line - parent.loc.start.line === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not comments are at the parent end or not.
|
||||
* @param {ASTNode} node The Comment node.
|
||||
* @param {token} token The Comment token.
|
||||
* @param {string} nodeType The parent type to check against.
|
||||
* @returns {boolean} True if the comment is at parent end.
|
||||
*/
|
||||
function isCommentAtParentEnd(node, nodeType) {
|
||||
const ancestors = context.getAncestors();
|
||||
let parent;
|
||||
function isCommentAtParentEnd(token, nodeType) {
|
||||
const parent = getParentNodeOfToken(token);
|
||||
|
||||
if (ancestors.length) {
|
||||
parent = ancestors.pop();
|
||||
}
|
||||
|
||||
return parent && isCommentInsideNodeType(node, parent, nodeType) &&
|
||||
parent.loc.end.line - node.loc.end.line === 1;
|
||||
return parent && isParentNodeType(parent, nodeType) &&
|
||||
parent.loc.end.line - token.loc.end.line === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not comments are at the block start or not.
|
||||
* @param {ASTNode} node The Comment node.
|
||||
* @param {token} token The Comment token.
|
||||
* @returns {boolean} True if the comment is at block start.
|
||||
*/
|
||||
function isCommentAtBlockStart(node) {
|
||||
return isCommentAtParentStart(node, "ClassBody") || isCommentAtParentStart(node, "BlockStatement") || isCommentAtParentStart(node, "SwitchCase");
|
||||
function isCommentAtBlockStart(token) {
|
||||
return isCommentAtParentStart(token, "ClassBody") || isCommentAtParentStart(token, "BlockStatement") || isCommentAtParentStart(token, "SwitchCase");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not comments are at the block end or not.
|
||||
* @param {ASTNode} node The Comment node.
|
||||
* @param {token} token The Comment token.
|
||||
* @returns {boolean} True if the comment is at block end.
|
||||
*/
|
||||
function isCommentAtBlockEnd(node) {
|
||||
return isCommentAtParentEnd(node, "ClassBody") || isCommentAtParentEnd(node, "BlockStatement") || isCommentAtParentEnd(node, "SwitchCase") || isCommentAtParentEnd(node, "SwitchStatement");
|
||||
function isCommentAtBlockEnd(token) {
|
||||
return isCommentAtParentEnd(token, "ClassBody") || isCommentAtParentEnd(token, "BlockStatement") || isCommentAtParentEnd(token, "SwitchCase") || isCommentAtParentEnd(token, "SwitchStatement");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not comments are at the object start or not.
|
||||
* @param {ASTNode} node The Comment node.
|
||||
* @param {token} token The Comment token.
|
||||
* @returns {boolean} True if the comment is at object start.
|
||||
*/
|
||||
function isCommentAtObjectStart(node) {
|
||||
return isCommentAtParentStart(node, "ObjectExpression") || isCommentAtParentStart(node, "ObjectPattern");
|
||||
function isCommentAtObjectStart(token) {
|
||||
return isCommentAtParentStart(token, "ObjectExpression") || isCommentAtParentStart(token, "ObjectPattern");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not comments are at the object end or not.
|
||||
* @param {ASTNode} node The Comment node.
|
||||
* @param {token} token The Comment token.
|
||||
* @returns {boolean} True if the comment is at object end.
|
||||
*/
|
||||
function isCommentAtObjectEnd(node) {
|
||||
return isCommentAtParentEnd(node, "ObjectExpression") || isCommentAtParentEnd(node, "ObjectPattern");
|
||||
function isCommentAtObjectEnd(token) {
|
||||
return isCommentAtParentEnd(token, "ObjectExpression") || isCommentAtParentEnd(token, "ObjectPattern");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not comments are at the array start or not.
|
||||
* @param {ASTNode} node The Comment node.
|
||||
* @param {token} token The Comment token.
|
||||
* @returns {boolean} True if the comment is at array start.
|
||||
*/
|
||||
function isCommentAtArrayStart(node) {
|
||||
return isCommentAtParentStart(node, "ArrayExpression") || isCommentAtParentStart(node, "ArrayPattern");
|
||||
function isCommentAtArrayStart(token) {
|
||||
return isCommentAtParentStart(token, "ArrayExpression") || isCommentAtParentStart(token, "ArrayPattern");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not comments are at the array end or not.
|
||||
* @param {ASTNode} node The Comment node.
|
||||
* @param {token} token The Comment token.
|
||||
* @returns {boolean} True if the comment is at array end.
|
||||
*/
|
||||
function isCommentAtArrayEnd(node) {
|
||||
return isCommentAtParentEnd(node, "ArrayExpression") || isCommentAtParentEnd(node, "ArrayPattern");
|
||||
function isCommentAtArrayEnd(token) {
|
||||
return isCommentAtParentEnd(token, "ArrayExpression") || isCommentAtParentEnd(token, "ArrayPattern");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a comment node has lines around it (ignores inline comments)
|
||||
* @param {ASTNode} node The Comment node.
|
||||
* Checks if a comment token has lines around it (ignores inline comments)
|
||||
* @param {token} token The Comment token.
|
||||
* @param {Object} opts Options to determine the newline.
|
||||
* @param {boolean} opts.after Should have a newline after this line.
|
||||
* @param {boolean} opts.before Should have a newline before this line.
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkForEmptyLine(node, opts) {
|
||||
if (applyDefaultIgnorePatterns && defaultIgnoreRegExp.test(node.value)) {
|
||||
function checkForEmptyLine(token, opts) {
|
||||
if (applyDefaultIgnorePatterns && defaultIgnoreRegExp.test(token.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ignorePattern && customIgnoreRegExp.test(node.value)) {
|
||||
if (ignorePattern && customIgnoreRegExp.test(token.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let after = opts.after,
|
||||
before = opts.before;
|
||||
|
||||
const prevLineNum = node.loc.start.line - 1,
|
||||
nextLineNum = node.loc.end.line + 1,
|
||||
commentIsNotAlone = codeAroundComment(node);
|
||||
const prevLineNum = token.loc.start.line - 1,
|
||||
nextLineNum = token.loc.end.line + 1,
|
||||
commentIsNotAlone = codeAroundComment(token);
|
||||
|
||||
const blockStartAllowed = options.allowBlockStart && isCommentAtBlockStart(node),
|
||||
blockEndAllowed = options.allowBlockEnd && isCommentAtBlockEnd(node),
|
||||
objectStartAllowed = options.allowObjectStart && isCommentAtObjectStart(node),
|
||||
objectEndAllowed = options.allowObjectEnd && isCommentAtObjectEnd(node),
|
||||
arrayStartAllowed = options.allowArrayStart && isCommentAtArrayStart(node),
|
||||
arrayEndAllowed = options.allowArrayEnd && isCommentAtArrayEnd(node);
|
||||
const blockStartAllowed = options.allowBlockStart && isCommentAtBlockStart(token),
|
||||
blockEndAllowed = options.allowBlockEnd && isCommentAtBlockEnd(token),
|
||||
objectStartAllowed = options.allowObjectStart && isCommentAtObjectStart(token),
|
||||
objectEndAllowed = options.allowObjectEnd && isCommentAtObjectEnd(token),
|
||||
arrayStartAllowed = options.allowArrayStart && isCommentAtArrayStart(token),
|
||||
arrayEndAllowed = options.allowArrayEnd && isCommentAtArrayEnd(token);
|
||||
|
||||
const exceptionStartAllowed = blockStartAllowed || objectStartAllowed || arrayStartAllowed;
|
||||
const exceptionEndAllowed = blockEndAllowed || objectEndAllowed || arrayEndAllowed;
|
||||
@ -319,17 +307,17 @@ module.exports = {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousTokenOrComment = sourceCode.getTokenBefore(node, { includeComments: true });
|
||||
const nextTokenOrComment = sourceCode.getTokenAfter(node, { includeComments: true });
|
||||
const previousTokenOrComment = sourceCode.getTokenBefore(token, { includeComments: true });
|
||||
const nextTokenOrComment = sourceCode.getTokenAfter(token, { includeComments: true });
|
||||
|
||||
// check for newline before
|
||||
if (!exceptionStartAllowed && before && !lodash.includes(commentAndEmptyLines, prevLineNum) &&
|
||||
!(isCommentNodeType(previousTokenOrComment) && astUtils.isTokenOnSameLine(previousTokenOrComment, node))) {
|
||||
const lineStart = node.range[0] - node.loc.start.column;
|
||||
!(astUtils.isCommentToken(previousTokenOrComment) && astUtils.isTokenOnSameLine(previousTokenOrComment, token))) {
|
||||
const lineStart = token.range[0] - token.loc.start.column;
|
||||
const range = [lineStart, lineStart];
|
||||
|
||||
context.report({
|
||||
node,
|
||||
node: token,
|
||||
message: "Expected line before comment.",
|
||||
fix(fixer) {
|
||||
return fixer.insertTextBeforeRange(range, "\n");
|
||||
@ -339,12 +327,12 @@ module.exports = {
|
||||
|
||||
// check for newline after
|
||||
if (!exceptionEndAllowed && after && !lodash.includes(commentAndEmptyLines, nextLineNum) &&
|
||||
!(isCommentNodeType(nextTokenOrComment) && astUtils.isTokenOnSameLine(node, nextTokenOrComment))) {
|
||||
!(astUtils.isCommentToken(nextTokenOrComment) && astUtils.isTokenOnSameLine(token, nextTokenOrComment))) {
|
||||
context.report({
|
||||
node,
|
||||
node: token,
|
||||
message: "Expected line after comment.",
|
||||
fix(fixer) {
|
||||
return fixer.insertTextAfter(node, "\n");
|
||||
return fixer.insertTextAfter(token, "\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -356,25 +344,25 @@ module.exports = {
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
|
||||
LineComment(node) {
|
||||
if (options.beforeLineComment || options.afterLineComment) {
|
||||
checkForEmptyLine(node, {
|
||||
after: options.afterLineComment,
|
||||
before: options.beforeLineComment
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
BlockComment(node) {
|
||||
if (options.beforeBlockComment || options.afterBlockComment) {
|
||||
checkForEmptyLine(node, {
|
||||
after: options.afterBlockComment,
|
||||
before: options.beforeBlockComment
|
||||
});
|
||||
}
|
||||
Program() {
|
||||
comments.forEach(token => {
|
||||
if (token.type === "Line") {
|
||||
if (options.beforeLineComment || options.afterLineComment) {
|
||||
checkForEmptyLine(token, {
|
||||
after: options.afterLineComment,
|
||||
before: options.beforeLineComment
|
||||
});
|
||||
}
|
||||
} else if (token.type === "Block") {
|
||||
if (options.beforeBlockComment || options.afterBlockComment) {
|
||||
checkForEmptyLine(token, {
|
||||
after: options.afterBlockComment,
|
||||
before: options.beforeBlockComment
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
/**
|
||||
* @fileoverview Require or disallow newlines around directives.
|
||||
* @author Kai Cataldo
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
@ -16,7 +17,8 @@ module.exports = {
|
||||
docs: {
|
||||
description: "require or disallow newlines around directives",
|
||||
category: "Stylistic Issues",
|
||||
recommended: false
|
||||
recommended: false,
|
||||
replacedBy: ["padding-line-between-statements"]
|
||||
},
|
||||
schema: [{
|
||||
oneOf: [
|
||||
@ -38,7 +40,8 @@ module.exports = {
|
||||
}
|
||||
]
|
||||
}],
|
||||
fixable: "whitespace"
|
||||
fixable: "whitespace",
|
||||
deprecated: true
|
||||
},
|
||||
|
||||
create(context) {
|
||||
@ -131,17 +134,12 @@ module.exports = {
|
||||
}
|
||||
|
||||
const firstDirective = directives[0];
|
||||
const hasTokenOrCommentBefore = !!sourceCode.getTokenBefore(firstDirective, { includeComments: true });
|
||||
const leadingComments = sourceCode.getCommentsBefore(firstDirective);
|
||||
|
||||
// Only check before the first directive if it is preceded by a comment or if it is at the top of
|
||||
// the file and expectLineBefore is set to "never". This is to not force a newline at the top of
|
||||
// the file if there are no comments as well as for compatibility with padded-blocks.
|
||||
if (
|
||||
firstDirective.leadingComments && firstDirective.leadingComments.length ||
|
||||
|
||||
// Shebangs are not added to leading comments but are accounted for by the following.
|
||||
node.type === "Program" && hasTokenOrCommentBefore
|
||||
) {
|
||||
if (leadingComments.length) {
|
||||
if (expectLineBefore === "always" && !hasNewlineBefore(firstDirective)) {
|
||||
reportError(firstDirective, "before", true);
|
||||
}
|
||||
@ -152,7 +150,7 @@ module.exports = {
|
||||
} else if (
|
||||
node.type === "Program" &&
|
||||
expectLineBefore === "never" &&
|
||||
!hasTokenOrCommentBefore &&
|
||||
!leadingComments.length &&
|
||||
hasNewlineBefore(firstDirective)
|
||||
) {
|
||||
reportError(firstDirective, "before", false);
|
||||
|
@ -108,7 +108,7 @@ module.exports = {
|
||||
previousTabStopOffset = tabWidth ? totalOffset % tabWidth : 0,
|
||||
spaceCount = tabWidth - previousTabStopOffset;
|
||||
|
||||
extraCharacterCount += spaceCount - 1; // -1 for the replaced tab
|
||||
extraCharacterCount += spaceCount - 1; // -1 for the replaced tab
|
||||
});
|
||||
return Array.from(line).length + extraCharacterCount;
|
||||
}
|
||||
@ -268,13 +268,13 @@ module.exports = {
|
||||
// we iterate over comments in parallel with the lines
|
||||
let commentsIndex = 0;
|
||||
|
||||
const strings = getAllStrings(sourceCode);
|
||||
const strings = getAllStrings();
|
||||
const stringsByLine = strings.reduce(groupByLineNumber, {});
|
||||
|
||||
const templateLiterals = getAllTemplateLiterals(sourceCode);
|
||||
const templateLiterals = getAllTemplateLiterals();
|
||||
const templateLiteralsByLine = templateLiterals.reduce(groupByLineNumber, {});
|
||||
|
||||
const regExpLiterals = getAllRegExpLiterals(sourceCode);
|
||||
const regExpLiterals = getAllRegExpLiterals();
|
||||
const regExpLiteralsByLine = regExpLiterals.reduce(groupByLineNumber, {});
|
||||
|
||||
lines.forEach((line, i) => {
|
||||
@ -321,21 +321,24 @@ module.exports = {
|
||||
}
|
||||
|
||||
const lineLength = computeLineLength(line, tabWidth);
|
||||
const commentLengthApplies = lineIsComment && maxCommentLength;
|
||||
|
||||
if (lineIsComment && ignoreComments) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (lineIsComment && lineLength > maxCommentLength) {
|
||||
context.report({
|
||||
node,
|
||||
loc: { line: lineNumber, column: 0 },
|
||||
message: "Line {{lineNumber}} exceeds the maximum comment line length of {{maxCommentLength}}.",
|
||||
data: {
|
||||
lineNumber: i + 1,
|
||||
maxCommentLength
|
||||
}
|
||||
});
|
||||
if (commentLengthApplies) {
|
||||
if (lineLength > maxCommentLength) {
|
||||
context.report({
|
||||
node,
|
||||
loc: { line: lineNumber, column: 0 },
|
||||
message: "Line {{lineNumber}} exceeds the maximum comment line length of {{maxCommentLength}}.",
|
||||
data: {
|
||||
lineNumber: i + 1,
|
||||
maxCommentLength
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (lineLength > maxLength) {
|
||||
context.report({
|
||||
node,
|
||||
|
@ -38,7 +38,7 @@ module.exports = {
|
||||
return {
|
||||
NewExpression(node) {
|
||||
if (node.arguments.length !== 0) {
|
||||
return; // shortcut: if there are arguments, there have to be parens
|
||||
return; // shortcut: if there are arguments, there have to be parens
|
||||
}
|
||||
|
||||
const lastToken = sourceCode.getLastToken(node);
|
||||
|
@ -1,6 +1,7 @@
|
||||
/**
|
||||
* @fileoverview Rule to check empty newline after "var" statement
|
||||
* @author Gopal Venkatesan
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
@ -20,7 +21,8 @@ module.exports = {
|
||||
docs: {
|
||||
description: "require or disallow an empty line after variable declarations",
|
||||
category: "Stylistic Issues",
|
||||
recommended: false
|
||||
recommended: false,
|
||||
replacedBy: ["padding-line-between-statements"]
|
||||
},
|
||||
|
||||
schema: [
|
||||
@ -29,7 +31,9 @@ module.exports = {
|
||||
}
|
||||
],
|
||||
|
||||
fixable: "whitespace"
|
||||
fixable: "whitespace",
|
||||
|
||||
deprecated: true
|
||||
},
|
||||
|
||||
create(context) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
/**
|
||||
* @fileoverview Rule to require newlines before `return` statement
|
||||
* @author Kai Cataldo
|
||||
* @deprecated
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
@ -13,10 +14,12 @@ module.exports = {
|
||||
docs: {
|
||||
description: "require an empty line before `return` statements",
|
||||
category: "Stylistic Issues",
|
||||
recommended: false
|
||||
recommended: false,
|
||||
replacedBy: ["padding-line-between-statements"]
|
||||
},
|
||||
fixable: "whitespace",
|
||||
schema: []
|
||||
schema: [],
|
||||
deprecated: true
|
||||
},
|
||||
|
||||
create(context) {
|
||||
@ -50,8 +53,8 @@ module.exports = {
|
||||
|
||||
if (node.parent.body) {
|
||||
return Array.isArray(node.parent.body)
|
||||
? node.parent.body[0] === node
|
||||
: node.parent.body === node;
|
||||
? node.parent.body[0] === node
|
||||
: node.parent.body === node;
|
||||
}
|
||||
|
||||
if (parentType === "IfStatement") {
|
||||
@ -73,7 +76,7 @@ module.exports = {
|
||||
* @private
|
||||
*/
|
||||
function calcCommentLines(node, lineNumTokenBefore) {
|
||||
const comments = sourceCode.getComments(node).leading;
|
||||
const comments = sourceCode.getCommentsBefore(node);
|
||||
let numLinesComments = 0;
|
||||
|
||||
if (!comments.length) {
|
||||
@ -121,7 +124,7 @@ module.exports = {
|
||||
if (tokenBefore) {
|
||||
lineNumTokenBefore = tokenBefore.loc.end.line;
|
||||
} else {
|
||||
lineNumTokenBefore = 0; // global return at beginning of script
|
||||
lineNumTokenBefore = 0; // global return at beginning of script
|
||||
}
|
||||
|
||||
return lineNumTokenBefore;
|
||||
@ -153,7 +156,7 @@ module.exports = {
|
||||
* @private
|
||||
*/
|
||||
function canFix(node) {
|
||||
const leadingComments = sourceCode.getComments(node).leading;
|
||||
const leadingComments = sourceCode.getCommentsBefore(node);
|
||||
const lastLeadingComment = leadingComments[leadingComments.length - 1];
|
||||
const tokenBefore = sourceCode.getTokenBefore(node);
|
||||
|
||||
|
@ -35,7 +35,7 @@ function report(context, node, identifierName) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the escope reference in the given scope.
|
||||
* Finds the eslint-scope reference in the given scope.
|
||||
* @param {Object} scope The scope to search.
|
||||
* @param {ASTNode} node The identifier node.
|
||||
* @returns {Reference|null} Returns the found reference or null if none were found.
|
||||
|
37
tools/eslint/lib/rules/no-buffer-constructor.js
Normal file
37
tools/eslint/lib/rules/no-buffer-constructor.js
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @fileoverview disallow use of the Buffer() constructor
|
||||
* @author Teddy Katz
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
docs: {
|
||||
description: "disallow use of the Buffer() constructor",
|
||||
category: "Node.js and CommonJS",
|
||||
recommended: false
|
||||
},
|
||||
schema: []
|
||||
},
|
||||
|
||||
create(context) {
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Public
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
"CallExpression[callee.name='Buffer'], NewExpression[callee.name='Buffer']"(node) {
|
||||
context.report({
|
||||
node,
|
||||
message: "{{example}} is deprecated. Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead.",
|
||||
data: { example: node.type === "CallExpression" ? "Buffer()" : "new Buffer()" }
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
@ -13,7 +13,7 @@ module.exports = {
|
||||
docs: {
|
||||
description: "disallow comparing against -0",
|
||||
category: "Possible Errors",
|
||||
recommended: false
|
||||
recommended: true
|
||||
},
|
||||
fixable: null,
|
||||
schema: []
|
||||
|
@ -33,6 +33,8 @@ module.exports = {
|
||||
recommended: false
|
||||
},
|
||||
|
||||
fixable: "code",
|
||||
|
||||
schema: [{
|
||||
type: "object",
|
||||
properties: {
|
||||
@ -55,7 +57,15 @@ module.exports = {
|
||||
const body = node.body;
|
||||
|
||||
if (isConditional(body) && !(config.allowParens && astUtils.isParenthesised(sourceCode, body))) {
|
||||
context.report({ node, message: "Arrow function used ambiguously with a conditional expression." });
|
||||
context.report({
|
||||
node,
|
||||
message: "Arrow function used ambiguously with a conditional expression.",
|
||||
fix(fixer) {
|
||||
|
||||
// if `allowParens` is not set to true dont bother wrapping in parens
|
||||
return config.allowParens && fixer.replaceText(node.body, `(${sourceCode.getText(node.body)})`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ module.exports = {
|
||||
/**
|
||||
* Checks whether the given reference is 'console' or not.
|
||||
*
|
||||
* @param {escope.Reference} reference - The reference to check.
|
||||
* @param {eslint-scope.Reference} reference - The reference to check.
|
||||
* @returns {boolean} `true` if the reference is 'console'.
|
||||
*/
|
||||
function isConsole(reference) {
|
||||
@ -74,7 +74,7 @@ module.exports = {
|
||||
* Checks whether the given reference is a member access which is not
|
||||
* allowed by options or not.
|
||||
*
|
||||
* @param {escope.Reference} reference - The reference to check.
|
||||
* @param {eslint-scope.Reference} reference - The reference to check.
|
||||
* @returns {boolean} `true` if the reference is a member access which
|
||||
* is not allowed by options.
|
||||
*/
|
||||
@ -92,7 +92,7 @@ module.exports = {
|
||||
/**
|
||||
* Reports the given reference as a violation.
|
||||
*
|
||||
* @param {escope.Reference} reference - The reference to report.
|
||||
* @param {eslint-scope.Reference} reference - The reference to report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function report(reference) {
|
||||
|
@ -16,7 +16,7 @@ module.exports = {
|
||||
category: "Possible Errors",
|
||||
recommended: true
|
||||
},
|
||||
|
||||
fixable: "code",
|
||||
schema: []
|
||||
},
|
||||
|
||||
@ -24,7 +24,13 @@ module.exports = {
|
||||
|
||||
return {
|
||||
DebuggerStatement(node) {
|
||||
context.report({ node, message: "Unexpected 'debugger' statement." });
|
||||
context.report({
|
||||
node,
|
||||
message: "Unexpected 'debugger' statement.",
|
||||
fix(fixer) {
|
||||
return fixer.remove(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -28,7 +28,7 @@ module.exports = {
|
||||
|
||||
/**
|
||||
* Checks whether or not a given definition is a parameter's.
|
||||
* @param {escope.DefEntry} def - A definition to check.
|
||||
* @param {eslint-scope.DefEntry} def - A definition to check.
|
||||
* @returns {boolean} `true` if the definition is a parameter's.
|
||||
*/
|
||||
function isParameter(def) {
|
||||
|
@ -132,11 +132,15 @@ module.exports = {
|
||||
function reportIfEmpty(node) {
|
||||
const kind = getKind(node);
|
||||
const name = astUtils.getFunctionNameWithKind(node);
|
||||
const innerComments = sourceCode.getTokens(node.body, {
|
||||
includeComments: true,
|
||||
filter: astUtils.isCommentToken
|
||||
});
|
||||
|
||||
if (allowed.indexOf(kind) === -1 &&
|
||||
node.body.type === "BlockStatement" &&
|
||||
node.body.body.length === 0 &&
|
||||
sourceCode.getComments(node.body).trailing.length === 0
|
||||
innerComments.length === 0
|
||||
) {
|
||||
context.report({
|
||||
node,
|
||||
|
@ -59,7 +59,7 @@ module.exports = {
|
||||
}
|
||||
|
||||
// any other block is only allowed to be empty, if it contains a comment
|
||||
if (sourceCode.getComments(node).trailing.length > 0) {
|
||||
if (sourceCode.getCommentsInside(node).length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -166,7 +166,7 @@ module.exports = {
|
||||
/**
|
||||
* Reports accesses of `eval` via the global object.
|
||||
*
|
||||
* @param {escope.Scope} globalScope - The global scope.
|
||||
* @param {eslint-scope.Scope} globalScope - The global scope.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportAccessingEvalViaGlobalObject(globalScope) {
|
||||
@ -200,7 +200,7 @@ module.exports = {
|
||||
/**
|
||||
* Reports all accesses of `eval` (excludes direct calls to eval).
|
||||
*
|
||||
* @param {escope.Scope} globalScope - The global scope.
|
||||
* @param {eslint-scope.Scope} globalScope - The global scope.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportAccessingEval(globalScope) {
|
||||
|
@ -9,8 +9,15 @@
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
const globals = require("globals");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const propertyDefinitionMethods = new Set(["defineProperty", "defineProperties"]);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@ -43,73 +50,123 @@ module.exports = {
|
||||
create(context) {
|
||||
|
||||
const config = context.options[0] || {};
|
||||
const exceptions = config.exceptions || [];
|
||||
let modifiedBuiltins = Object.keys(globals.builtin).filter(builtin => builtin[0].toUpperCase() === builtin[0]);
|
||||
const exceptions = new Set(config.exceptions || []);
|
||||
const modifiedBuiltins = new Set(
|
||||
Object.keys(globals.builtin)
|
||||
.filter(builtin => builtin[0].toUpperCase() === builtin[0])
|
||||
.filter(builtin => !exceptions.has(builtin))
|
||||
);
|
||||
|
||||
if (exceptions.length) {
|
||||
modifiedBuiltins = modifiedBuiltins.filter(builtIn => exceptions.indexOf(builtIn) === -1);
|
||||
/**
|
||||
* Reports a lint error for the given node.
|
||||
* @param {ASTNode} node The node to report.
|
||||
* @param {string} builtin The name of the native builtin being extended.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportNode(node, builtin) {
|
||||
context.report({
|
||||
node,
|
||||
message: "{{builtin}} prototype is read only, properties should not be added.",
|
||||
data: {
|
||||
builtin
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if the `prototype` property of the given object
|
||||
* identifier node is being accessed.
|
||||
* @param {ASTNode} identifierNode The Identifier representing the object
|
||||
* to check.
|
||||
* @returns {boolean} True if the identifier is the object of a
|
||||
* MemberExpression and its `prototype` property is being accessed,
|
||||
* false otherwise.
|
||||
*/
|
||||
function isPrototypePropertyAccessed(identifierNode) {
|
||||
return Boolean(
|
||||
identifierNode &&
|
||||
identifierNode.parent &&
|
||||
identifierNode.parent.type === "MemberExpression" &&
|
||||
identifierNode.parent.object === identifierNode &&
|
||||
astUtils.getStaticPropertyName(identifierNode.parent) === "prototype"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that an identifier is an object of a prototype whose member
|
||||
* is being assigned in an AssignmentExpression.
|
||||
* Example: Object.prototype.foo = "bar"
|
||||
* @param {ASTNode} identifierNode The identifier to check.
|
||||
* @returns {boolean} True if the identifier's prototype is modified.
|
||||
*/
|
||||
function isInPrototypePropertyAssignment(identifierNode) {
|
||||
return Boolean(
|
||||
isPrototypePropertyAccessed(identifierNode) &&
|
||||
identifierNode.parent.parent.type === "MemberExpression" &&
|
||||
identifierNode.parent.parent.parent.type === "AssignmentExpression" &&
|
||||
identifierNode.parent.parent.parent.left === identifierNode.parent.parent
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that an identifier is an object of a prototype whose member
|
||||
* is being extended via the Object.defineProperty() or
|
||||
* Object.defineProperties() methods.
|
||||
* Example: Object.defineProperty(Array.prototype, "foo", ...)
|
||||
* Example: Object.defineProperties(Array.prototype, ...)
|
||||
* @param {ASTNode} identifierNode The identifier to check.
|
||||
* @returns {boolean} True if the identifier's prototype is modified.
|
||||
*/
|
||||
function isInDefinePropertyCall(identifierNode) {
|
||||
return Boolean(
|
||||
isPrototypePropertyAccessed(identifierNode) &&
|
||||
identifierNode.parent.parent.type === "CallExpression" &&
|
||||
identifierNode.parent.parent.arguments[0] === identifierNode.parent &&
|
||||
identifierNode.parent.parent.callee.type === "MemberExpression" &&
|
||||
identifierNode.parent.parent.callee.object.type === "Identifier" &&
|
||||
identifierNode.parent.parent.callee.object.name === "Object" &&
|
||||
identifierNode.parent.parent.callee.property.type === "Identifier" &&
|
||||
propertyDefinitionMethods.has(identifierNode.parent.parent.callee.property.name)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if object prototype access is part of a prototype
|
||||
* extension. There are three ways a prototype can be extended:
|
||||
* 1. Assignment to prototype property (Object.prototype.foo = 1)
|
||||
* 2. Object.defineProperty()/Object.defineProperties() on a prototype
|
||||
* If prototype extension is detected, report the AssignmentExpression
|
||||
* or CallExpression node.
|
||||
* @param {ASTNode} identifierNode The Identifier representing the object
|
||||
* which prototype is being accessed and possibly extended.
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkAndReportPrototypeExtension(identifierNode) {
|
||||
if (isInPrototypePropertyAssignment(identifierNode)) {
|
||||
|
||||
// Identifier --> MemberExpression --> MemberExpression --> AssignmentExpression
|
||||
reportNode(identifierNode.parent.parent.parent, identifierNode.name);
|
||||
} else if (isInDefinePropertyCall(identifierNode)) {
|
||||
|
||||
// Identifier --> MemberExpression --> CallExpression
|
||||
reportNode(identifierNode.parent.parent, identifierNode.name);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
// handle the Array.prototype.extra style case
|
||||
AssignmentExpression(node) {
|
||||
const lhs = node.left;
|
||||
|
||||
if (lhs.type !== "MemberExpression" || lhs.object.type !== "MemberExpression") {
|
||||
return;
|
||||
}
|
||||
|
||||
const affectsProto = lhs.object.computed
|
||||
? lhs.object.property.type === "Literal" && lhs.object.property.value === "prototype"
|
||||
: lhs.object.property.name === "prototype";
|
||||
|
||||
if (!affectsProto) {
|
||||
return;
|
||||
}
|
||||
"Program:exit"() {
|
||||
const globalScope = context.getScope();
|
||||
|
||||
modifiedBuiltins.forEach(builtin => {
|
||||
if (lhs.object.object.name === builtin) {
|
||||
context.report({
|
||||
node,
|
||||
message: "{{builtin}} prototype is read only, properties should not be added.",
|
||||
data: {
|
||||
builtin
|
||||
}
|
||||
});
|
||||
const builtinVar = globalScope.set.get(builtin);
|
||||
|
||||
if (builtinVar && builtinVar.references) {
|
||||
builtinVar.references
|
||||
.map(ref => ref.identifier)
|
||||
.forEach(checkAndReportPrototypeExtension);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// handle the Object.definePropert[y|ies](Array.prototype) case
|
||||
CallExpression(node) {
|
||||
|
||||
const callee = node.callee;
|
||||
|
||||
// only worry about Object.definePropert[y|ies]
|
||||
if (callee.type === "MemberExpression" &&
|
||||
callee.object.name === "Object" &&
|
||||
(callee.property.name === "defineProperty" || callee.property.name === "defineProperties")) {
|
||||
|
||||
// verify the object being added to is a native prototype
|
||||
const subject = node.arguments[0];
|
||||
const object = subject && subject.object;
|
||||
|
||||
if (object &&
|
||||
object.type === "Identifier" &&
|
||||
(modifiedBuiltins.indexOf(object.name) > -1) &&
|
||||
subject.property.name === "prototype") {
|
||||
|
||||
context.report({
|
||||
node,
|
||||
message: "{{objectName}} prototype is read only, properties should not be added.",
|
||||
data: {
|
||||
objectName: object.name
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -9,7 +9,6 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils.js");
|
||||
const esUtils = require("esutils");
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
@ -250,28 +249,27 @@ module.exports = {
|
||||
const tokenBeforeLeftParen = sourceCode.getTokenBefore(node, 1);
|
||||
const firstToken = sourceCode.getFirstToken(node);
|
||||
|
||||
// If there is already whitespace before the previous token, don't add more.
|
||||
if (!tokenBeforeLeftParen || tokenBeforeLeftParen.end !== leftParenToken.start) {
|
||||
return false;
|
||||
}
|
||||
return tokenBeforeLeftParen &&
|
||||
tokenBeforeLeftParen.range[1] === leftParenToken.range[0] &&
|
||||
leftParenToken.range[1] === firstToken.range[0] &&
|
||||
!astUtils.canTokensBeAdjacent(tokenBeforeLeftParen, firstToken);
|
||||
}
|
||||
|
||||
// If the parens are preceded by a keyword (e.g. `typeof(0)`), a space should be inserted (`typeof 0`)
|
||||
const precededByIdentiferPart = esUtils.code.isIdentifierPartES6(tokenBeforeLeftParen.value.slice(-1).charCodeAt(0));
|
||||
/**
|
||||
* Determines whether a node should be followed by an additional space when removing parens
|
||||
* @param {ASTNode} node node to evaluate; must be surrounded by parentheses
|
||||
* @returns {boolean} `true` if a space should be inserted after the node
|
||||
* @private
|
||||
*/
|
||||
function requiresTrailingSpace(node) {
|
||||
const nextTwoTokens = sourceCode.getTokensAfter(node, { count: 2 });
|
||||
const rightParenToken = nextTwoTokens[0];
|
||||
const tokenAfterRightParen = nextTwoTokens[1];
|
||||
const tokenBeforeRightParen = sourceCode.getLastToken(node);
|
||||
|
||||
// However, a space should not be inserted unless the first character of the token is an identifier part
|
||||
// e.g. `typeof([])` should be fixed to `typeof[]`
|
||||
const startsWithIdentifierPart = esUtils.code.isIdentifierPartES6(firstToken.value.charCodeAt(0));
|
||||
|
||||
// If the parens are preceded by and start with a unary plus/minus (e.g. `+(+foo)`), a space should be inserted (`+ +foo`)
|
||||
const precededByUnaryPlus = tokenBeforeLeftParen.type === "Punctuator" && tokenBeforeLeftParen.value === "+";
|
||||
const precededByUnaryMinus = tokenBeforeLeftParen.type === "Punctuator" && tokenBeforeLeftParen.value === "-";
|
||||
|
||||
const startsWithUnaryPlus = firstToken.type === "Punctuator" && firstToken.value === "+";
|
||||
const startsWithUnaryMinus = firstToken.type === "Punctuator" && firstToken.value === "-";
|
||||
|
||||
return (precededByIdentiferPart && startsWithIdentifierPart) ||
|
||||
(precededByUnaryPlus && startsWithUnaryPlus) ||
|
||||
(precededByUnaryMinus && startsWithUnaryMinus);
|
||||
return rightParenToken && tokenAfterRightParen &&
|
||||
!sourceCode.isSpaceBetweenTokens(rightParenToken, tokenAfterRightParen) &&
|
||||
!astUtils.canTokensBeAdjacent(tokenBeforeRightParen, tokenAfterRightParen);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -298,7 +296,7 @@ module.exports = {
|
||||
return fixer.replaceTextRange([
|
||||
leftParenToken.range[0],
|
||||
rightParenToken.range[1]
|
||||
], (requiresLeadingSpace(node) ? " " : "") + parenthesizedSource);
|
||||
], (requiresLeadingSpace(node) ? " " : "") + parenthesizedSource + (requiresTrailingSpace(node) ? " " : ""));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -507,12 +505,18 @@ module.exports = {
|
||||
if (hasExcessParens(node.right)) {
|
||||
report(node.right);
|
||||
}
|
||||
if (hasExcessParens(node.left)) {
|
||||
report(node.left);
|
||||
}
|
||||
},
|
||||
|
||||
ForOfStatement(node) {
|
||||
if (hasExcessParens(node.right)) {
|
||||
report(node.right);
|
||||
}
|
||||
if (hasExcessParens(node.left)) {
|
||||
report(node.left);
|
||||
}
|
||||
},
|
||||
|
||||
ForStatement(node) {
|
||||
|
@ -25,7 +25,7 @@ const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/i;
|
||||
*/
|
||||
function hasFallthroughComment(node, context, fallthroughCommentPattern) {
|
||||
const sourceCode = context.getSourceCode();
|
||||
const comment = lodash.last(sourceCode.getComments(node).leading);
|
||||
const comment = lodash.last(sourceCode.getCommentsBefore(node));
|
||||
|
||||
return Boolean(comment && fallthroughCommentPattern.test(comment.value));
|
||||
}
|
||||
|
@ -5,6 +5,12 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@ -23,16 +29,24 @@ module.exports = {
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const sourceCode = context.getSourceCode();
|
||||
|
||||
return {
|
||||
Literal(node) {
|
||||
|
||||
if (typeof node.value === "number") {
|
||||
if (node.raw.indexOf(".") === 0) {
|
||||
if (node.raw.startsWith(".")) {
|
||||
context.report({
|
||||
node,
|
||||
message: "A leading decimal point can be confused with a dot.",
|
||||
fix: fixer => fixer.insertTextBefore(node, "0")
|
||||
fix(fixer) {
|
||||
const tokenBefore = sourceCode.getTokenBefore(node);
|
||||
const needsSpaceBefore = tokenBefore &&
|
||||
tokenBefore.range[1] === node.range[0] &&
|
||||
!astUtils.canTokensBeAdjacent(tokenBefore, `0${node.raw}`);
|
||||
|
||||
return fixer.insertTextBefore(node, needsSpaceBefore ? " 0" : "0");
|
||||
}
|
||||
});
|
||||
}
|
||||
if (node.raw.indexOf(".") === node.raw.length - 1) {
|
||||
|
@ -6,7 +6,6 @@
|
||||
"use strict";
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
const esUtils = require("esutils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
@ -215,8 +214,7 @@ module.exports = {
|
||||
if (
|
||||
tokenBefore &&
|
||||
tokenBefore.range[1] === node.range[0] &&
|
||||
esUtils.code.isIdentifierPartES6(tokenBefore.value.slice(-1).charCodeAt(0)) &&
|
||||
esUtils.code.isIdentifierPartES6(recommendation.charCodeAt(0))
|
||||
!astUtils.canTokensBeAdjacent(tokenBefore, recommendation)
|
||||
) {
|
||||
return fixer.replaceText(node, ` ${recommendation}`);
|
||||
}
|
||||
|
@ -55,10 +55,11 @@ module.exports = {
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
Program() {
|
||||
const comments = sourceCode.getAllComments();
|
||||
|
||||
LineComment: testCodeAroundComment,
|
||||
BlockComment: testCodeAroundComment
|
||||
|
||||
comments.filter(token => token.type !== "Shebang").forEach(testCodeAroundComment);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@ -58,16 +58,14 @@ module.exports = {
|
||||
* @returns {void}
|
||||
*/
|
||||
function check(node) {
|
||||
const body = nearestBody(node),
|
||||
const body = nearestBody(),
|
||||
valid = ((body.type === "Program" && body.distance === 1) ||
|
||||
body.distance === 2);
|
||||
|
||||
if (!valid) {
|
||||
context.report({ node, message: "Move {{type}} declaration to {{body}} root.", data: {
|
||||
type: (node.type === "FunctionDeclaration"
|
||||
? "function" : "variable"),
|
||||
body: (body.type === "Program"
|
||||
? "program" : "function body")
|
||||
type: (node.type === "FunctionDeclaration" ? "function" : "variable"),
|
||||
body: (body.type === "Program" ? "program" : "function body")
|
||||
} });
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,8 @@ const astUtils = require("../ast-utils");
|
||||
// Constants
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const ALL_IRREGULARS = /[\f\v\u0085\u00A0\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000\u2028\u2029]/;
|
||||
const IRREGULAR_WHITESPACE = /[\f\v\u0085\u00A0\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000]+/mg;
|
||||
const ALL_IRREGULARS = /[\f\v\u0085\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000\u2028\u2029]/;
|
||||
const IRREGULAR_WHITESPACE = /[\f\v\u0085\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000]+/mg;
|
||||
const IRREGULAR_LINE_TERMINATORS = /[\u2028\u2029]/mg;
|
||||
const LINE_BREAK = astUtils.createGlobalLinebreakMatcher();
|
||||
|
||||
@ -60,9 +60,6 @@ module.exports = {
|
||||
// Module store of errors that we have found
|
||||
let errors = [];
|
||||
|
||||
// Comment nodes. We accumulate these as we go, so we can be sure to trigger them after the whole `Program` entity is parsed, even for top-of-file comments.
|
||||
const commentNodes = [];
|
||||
|
||||
// Lookup the `skipComments` option, which defaults to `false`.
|
||||
const options = context.options[0] || {};
|
||||
const skipComments = !!options.skipComments;
|
||||
@ -71,6 +68,7 @@ module.exports = {
|
||||
const skipTemplates = !!options.skipTemplates;
|
||||
|
||||
const sourceCode = context.getSourceCode();
|
||||
const commentNodes = sourceCode.getAllComments();
|
||||
|
||||
/**
|
||||
* Removes errors that occur inside a string node
|
||||
@ -188,16 +186,6 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a comment node (`LineComment` or `BlockComment`) for later stripping of errors within; a necessary deferring of processing to deal with top-of-file comments.
|
||||
* @param {ASTNode} node The comment node
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function rememberCommentNode(node) {
|
||||
commentNodes.push(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* A no-op function to act as placeholder for comment accumulation when the `skipComments` option is `false`.
|
||||
* @returns {void}
|
||||
@ -220,7 +208,6 @@ module.exports = {
|
||||
* We can later filter the errors when they are found to be not an
|
||||
* issue in nodes we don't care about.
|
||||
*/
|
||||
|
||||
checkForIrregularWhitespace(node);
|
||||
checkForIrregularLineTerminators(node);
|
||||
};
|
||||
@ -228,13 +215,10 @@ module.exports = {
|
||||
nodes.Identifier = removeInvalidNodeErrorsInIdentifierOrLiteral;
|
||||
nodes.Literal = removeInvalidNodeErrorsInIdentifierOrLiteral;
|
||||
nodes.TemplateElement = skipTemplates ? removeInvalidNodeErrorsInTemplateLiteral : noop;
|
||||
nodes.LineComment = skipComments ? rememberCommentNode : noop;
|
||||
nodes.BlockComment = skipComments ? rememberCommentNode : noop;
|
||||
nodes["Program:exit"] = function() {
|
||||
|
||||
if (skipComments) {
|
||||
|
||||
// First strip errors occurring in comment nodes. We have to do this post-`Program` to deal with top-of-file comments.
|
||||
// First strip errors occurring in comment nodes.
|
||||
commentNodes.forEach(removeInvalidNodeErrorsInComment);
|
||||
}
|
||||
|
||||
|
@ -94,13 +94,13 @@ module.exports = {
|
||||
|
||||
ruleDef.VariableDeclaration = function(node) {
|
||||
if (node.kind === "let" || node.kind === "const") {
|
||||
markLoneBlock(node);
|
||||
markLoneBlock();
|
||||
}
|
||||
};
|
||||
|
||||
ruleDef.FunctionDeclaration = function(node) {
|
||||
ruleDef.FunctionDeclaration = function() {
|
||||
if (context.getScope().isStrict) {
|
||||
markLoneBlock(node);
|
||||
markLoneBlock();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -90,7 +90,7 @@ function getTopLoopNode(node, excludedNode) {
|
||||
*
|
||||
* @param {ASTNode} funcNode - A target function node.
|
||||
* @param {ASTNode} loopNode - A containing loop node.
|
||||
* @param {escope.Reference} reference - A reference to check.
|
||||
* @param {eslint-scope.Reference} reference - A reference to check.
|
||||
* @returns {boolean} `true` if the reference is safe or not.
|
||||
*/
|
||||
function isSafe(funcNode, loopNode, reference) {
|
||||
@ -131,7 +131,7 @@ function isSafe(funcNode, loopNode, reference) {
|
||||
* - is readonly.
|
||||
* - doesn't exist inside a local function and after the border.
|
||||
*
|
||||
* @param {escope.Reference} upperRef - A reference to check.
|
||||
* @param {eslint-scope.Reference} upperRef - A reference to check.
|
||||
* @returns {boolean} `true` if the reference is safe.
|
||||
*/
|
||||
function isSafeReference(upperRef) {
|
||||
|
@ -33,6 +33,9 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
},
|
||||
ignoreEOLComments: {
|
||||
type: "boolean"
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
@ -43,8 +46,10 @@ module.exports = {
|
||||
create(context) {
|
||||
|
||||
// the index of the last comment that was checked
|
||||
const exceptions = { Property: true },
|
||||
options = context.options[0];
|
||||
const sourceCode = context.getSourceCode(),
|
||||
exceptions = { Property: true },
|
||||
options = context.options[0] || {},
|
||||
ignoreEOLComments = options.ignoreEOLComments;
|
||||
let hasExceptions = true,
|
||||
lastCommentIndex = 0;
|
||||
|
||||
@ -59,6 +64,23 @@ module.exports = {
|
||||
hasExceptions = Object.keys(exceptions).length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given token is the last token of the line or not.
|
||||
* @param {Token} token The token to check.
|
||||
* @returns {boolean} Whether or not a token is at the end of the line it occurs in.
|
||||
* @private
|
||||
*/
|
||||
function isLastTokenOfLine(token) {
|
||||
const nextToken = sourceCode.getTokenAfter(token, { includeComments: true });
|
||||
|
||||
// nextToken is null if the comment is the last token in the program.
|
||||
if (!nextToken) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !astUtils.isTokenOnSameLine(token, nextToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a given source index is in a comment or not by checking
|
||||
* the index against the comment range. Since the check goes straight
|
||||
@ -73,7 +95,7 @@ module.exports = {
|
||||
while (lastCommentIndex < comments.length) {
|
||||
const comment = comments[lastCommentIndex];
|
||||
|
||||
if (comment.range[0] <= index && index < comment.range[1]) {
|
||||
if (comment.range[0] < index && index < comment.range[1]) {
|
||||
return true;
|
||||
} else if (index > comment.range[1]) {
|
||||
lastCommentIndex++;
|
||||
@ -85,6 +107,33 @@ module.exports = {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats value of given comment token for error message by truncating its length.
|
||||
* @param {Token} token comment token
|
||||
* @returns {string} formatted value
|
||||
* @private
|
||||
*/
|
||||
function formatReportedCommentValue(token) {
|
||||
const valueLines = token.value.split("\n");
|
||||
const value = valueLines[0];
|
||||
const formattedValue = `${value.substring(0, 12)}...`;
|
||||
|
||||
return valueLines.length === 1 && value.length <= 12 ? value : formattedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a fix function that removes the multiple spaces between the two tokens
|
||||
* @param {Token} leftToken left token
|
||||
* @param {Token} rightToken right token
|
||||
* @returns {Function} fix function
|
||||
* @private
|
||||
*/
|
||||
function createFix(leftToken, rightToken) {
|
||||
return function(fixer) {
|
||||
return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ");
|
||||
};
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
@ -92,47 +141,43 @@ module.exports = {
|
||||
return {
|
||||
Program() {
|
||||
|
||||
const sourceCode = context.getSourceCode(),
|
||||
source = sourceCode.getText(),
|
||||
const source = sourceCode.getText(),
|
||||
allComments = sourceCode.getAllComments(),
|
||||
JOINED_LINEBEAKS = Array.from(astUtils.LINEBREAKS).join(""),
|
||||
pattern = new RegExp(String.raw`[^ \t${JOINED_LINEBEAKS}].? {2,}`, "g"); // note: repeating space
|
||||
pattern = /[^\s].*? {2,}/g;
|
||||
let parent;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a fix function that removes the multiple spaces between the two tokens
|
||||
* @param {RuleFixer} leftToken left token
|
||||
* @param {RuleFixer} rightToken right token
|
||||
* @returns {Function} fix function
|
||||
* @private
|
||||
*/
|
||||
function createFix(leftToken, rightToken) {
|
||||
return function(fixer) {
|
||||
return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ");
|
||||
};
|
||||
}
|
||||
|
||||
while (pattern.test(source)) {
|
||||
|
||||
// do not flag anything inside of comments
|
||||
if (!isIndexInComment(pattern.lastIndex, allComments)) {
|
||||
|
||||
const token = sourceCode.getTokenByRangeStart(pattern.lastIndex);
|
||||
const token = sourceCode.getTokenByRangeStart(pattern.lastIndex, { includeComments: true });
|
||||
|
||||
if (token) {
|
||||
const previousToken = sourceCode.getTokenBefore(token);
|
||||
if (ignoreEOLComments && astUtils.isCommentToken(token) && isLastTokenOfLine(token)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousToken = sourceCode.getTokenBefore(token, { includeComments: true });
|
||||
|
||||
if (hasExceptions) {
|
||||
parent = sourceCode.getNodeByRangeIndex(pattern.lastIndex - 1);
|
||||
}
|
||||
|
||||
if (!parent || !exceptions[parent.type]) {
|
||||
let value = token.value;
|
||||
|
||||
if (token.type === "Block") {
|
||||
value = `/*${formatReportedCommentValue(token)}*/`;
|
||||
} else if (token.type === "Line") {
|
||||
value = `//${formatReportedCommentValue(token)}`;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node: token,
|
||||
loc: token.loc.start,
|
||||
message: "Multiple spaces found before '{{value}}'.",
|
||||
data: { value: token.value },
|
||||
data: { value },
|
||||
fix: createFix(previousToken, token)
|
||||
});
|
||||
}
|
||||
|
@ -111,10 +111,19 @@ module.exports = {
|
||||
message,
|
||||
data: { max: maxAllowed, pluralizedLines: maxAllowed === 1 ? "line" : "lines" },
|
||||
fix(fixer) {
|
||||
return fixer.removeRange([
|
||||
sourceCode.getIndexFromLoc({ line: lastLineNumber + 1, column: 0 }),
|
||||
sourceCode.getIndexFromLoc({ line: lineNumber - maxAllowed, column: 0 })
|
||||
]);
|
||||
const rangeStart = sourceCode.getIndexFromLoc({ line: lastLineNumber + 1, column: 0 });
|
||||
|
||||
/*
|
||||
* The end of the removal range is usually the start index of the next line.
|
||||
* However, at the end of the file there is no next line, so the end of the
|
||||
* range is just the length of the text.
|
||||
*/
|
||||
const lineNumberAfterRemovedLines = lineNumber - maxAllowed;
|
||||
const rangeEnd = lineNumberAfterRemovedLines <= allLines.length
|
||||
? sourceCode.getIndexFromLoc({ line: lineNumberAfterRemovedLines, column: 0 })
|
||||
: sourceCode.text.length;
|
||||
|
||||
return fixer.removeRange([rangeStart, rangeEnd]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ module.exports = {
|
||||
|
||||
/**
|
||||
* Find variables in a given scope and flag redeclared ones.
|
||||
* @param {Scope} scope - An escope scope object.
|
||||
* @param {Scope} scope - An eslint-scope scope object.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
|
@ -22,15 +22,28 @@ module.exports = {
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const sourceCode = context.getSourceCode();
|
||||
|
||||
/**
|
||||
* Determines whether two nodes are composed of the same tokens.
|
||||
* @param {ASTNode} nodeA The first node
|
||||
* @param {ASTNode} nodeB The second node
|
||||
* @returns {boolean} true if the nodes have identical token representations
|
||||
*/
|
||||
function hasSameTokens(nodeA, nodeB) {
|
||||
const tokensA = sourceCode.getTokens(nodeA);
|
||||
const tokensB = sourceCode.getTokens(nodeB);
|
||||
|
||||
return tokensA.length === tokensB.length &&
|
||||
tokensA.every((token, index) => token.type === tokensB[index].type && token.value === tokensB[index].value);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
BinaryExpression(node) {
|
||||
const operators = ["===", "==", "!==", "!=", ">", "<", ">=", "<="];
|
||||
const operators = new Set(["===", "==", "!==", "!=", ">", "<", ">=", "<="]);
|
||||
|
||||
if (operators.indexOf(node.operator) > -1 &&
|
||||
(node.left.type === "Identifier" && node.right.type === "Identifier" && node.left.name === node.right.name ||
|
||||
node.left.type === "Literal" && node.right.type === "Literal" && node.left.value === node.right.value)) {
|
||||
if (operators.has(node.operator) && hasSameTokens(node.left, node.right)) {
|
||||
context.report({ node, message: "Comparing to itself is potentially pointless." });
|
||||
}
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ module.exports = {
|
||||
*/
|
||||
function isBeforeCallOfSuper() {
|
||||
return (
|
||||
isInConstructorOfDerivedClass(funcInfo) &&
|
||||
isInConstructorOfDerivedClass() &&
|
||||
!funcInfo.codePath.currentSegments.every(isCalled)
|
||||
);
|
||||
}
|
||||
@ -206,7 +206,7 @@ module.exports = {
|
||||
* @returns {void}
|
||||
*/
|
||||
onCodePathSegmentStart(segment) {
|
||||
if (!isInConstructorOfDerivedClass(funcInfo)) {
|
||||
if (!isInConstructorOfDerivedClass()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -230,7 +230,7 @@ module.exports = {
|
||||
* @returns {void}
|
||||
*/
|
||||
onCodePathSegmentLoop(fromSegment, toSegment) {
|
||||
if (!isInConstructorOfDerivedClass(funcInfo)) {
|
||||
if (!isInConstructorOfDerivedClass()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ module.exports = {
|
||||
/**
|
||||
* Checks the given scope for references to `undefined` and reports
|
||||
* all references found.
|
||||
* @param {escope.Scope} scope The scope to check.
|
||||
* @param {eslint-scope.Scope} scope The scope to check.
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkScope(scope) {
|
||||
|
@ -30,6 +30,9 @@ module.exports = {
|
||||
const FUNCTION_MESSAGE = "Unexpected newline between function and ( of function call.";
|
||||
const PROPERTY_MESSAGE = "Unexpected newline between object and [ of property access.";
|
||||
const TAGGED_TEMPLATE_MESSAGE = "Unexpected newline between template tag and template literal.";
|
||||
const DIVISION_MESSAGE = "Unexpected newline between numerator and division operator.";
|
||||
|
||||
const REGEX_FLAG_MATCHER = /^[gimuy]+$/;
|
||||
|
||||
const sourceCode = context.getSourceCode();
|
||||
|
||||
@ -75,6 +78,19 @@ module.exports = {
|
||||
return;
|
||||
}
|
||||
checkForBreakAfter(node.callee, FUNCTION_MESSAGE);
|
||||
},
|
||||
|
||||
"BinaryExpression[operator='/'] > BinaryExpression[operator='/'].left"(node) {
|
||||
const secondSlash = sourceCode.getTokenAfter(node, token => token.value === "/");
|
||||
const tokenAfterOperator = sourceCode.getTokenAfter(secondSlash);
|
||||
|
||||
if (
|
||||
tokenAfterOperator.type === "Identifier" &&
|
||||
REGEX_FLAG_MATCHER.test(tokenAfterOperator.value) &&
|
||||
secondSlash.range[1] === tokenAfterOperator.range[0]
|
||||
) {
|
||||
checkForBreakAfter(node.left, DIVISION_MESSAGE);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -18,14 +18,14 @@ const Traverser = require("../util/traverser"),
|
||||
|
||||
const pushAll = Function.apply.bind(Array.prototype.push);
|
||||
const SENTINEL_PATTERN = /(?:(?:Call|Class|Function|Member|New|Yield)Expression|Statement|Declaration)$/;
|
||||
const LOOP_PATTERN = /^(?:DoWhile|For|While)Statement$/; // for-in/of statements don't have `test` property.
|
||||
const LOOP_PATTERN = /^(?:DoWhile|For|While)Statement$/; // for-in/of statements don't have `test` property.
|
||||
const GROUP_PATTERN = /^(?:BinaryExpression|ConditionalExpression)$/;
|
||||
const SKIP_PATTERN = /^(?:ArrowFunction|Class|Function)Expression$/;
|
||||
const DYNAMIC_PATTERN = /^(?:Call|Member|New|TaggedTemplate|Yield)Expression$/;
|
||||
|
||||
/**
|
||||
* @typedef {Object} LoopConditionInfo
|
||||
* @property {escope.Reference} reference - The reference.
|
||||
* @property {eslint-scope.Reference} reference - The reference.
|
||||
* @property {ASTNode} group - BinaryExpression or ConditionalExpression nodes
|
||||
* that the reference is belonging to.
|
||||
* @property {Function} isInLoop - The predicate which checks a given reference
|
||||
@ -37,7 +37,7 @@ const DYNAMIC_PATTERN = /^(?:Call|Member|New|TaggedTemplate|Yield)Expression$/;
|
||||
/**
|
||||
* Checks whether or not a given reference is a write reference.
|
||||
*
|
||||
* @param {escope.Reference} reference - A reference to check.
|
||||
* @param {eslint-scope.Reference} reference - A reference to check.
|
||||
* @returns {boolean} `true` if the reference is a write reference.
|
||||
*/
|
||||
function isWriteReference(reference) {
|
||||
@ -77,7 +77,7 @@ function isUnmodifiedAndNotBelongToGroup(condition) {
|
||||
* Checks whether or not a given reference is inside of a given node.
|
||||
*
|
||||
* @param {ASTNode} node - A node to check.
|
||||
* @param {escope.Reference} reference - A reference to check.
|
||||
* @param {eslint-scope.Reference} reference - A reference to check.
|
||||
* @returns {boolean} `true` if the reference is inside of the node.
|
||||
*/
|
||||
function isInRange(node, reference) {
|
||||
@ -91,7 +91,7 @@ function isInRange(node, reference) {
|
||||
* Checks whether or not a given reference is inside of a loop node's condition.
|
||||
*
|
||||
* @param {ASTNode} node - A node to check.
|
||||
* @param {escope.Reference} reference - A reference to check.
|
||||
* @param {eslint-scope.Reference} reference - A reference to check.
|
||||
* @returns {boolean} `true` if the reference is inside of the loop node's
|
||||
* condition.
|
||||
*/
|
||||
@ -134,7 +134,7 @@ function hasDynamicExpressions(root) {
|
||||
/**
|
||||
* Creates the loop condition information from a given reference.
|
||||
*
|
||||
* @param {escope.Reference} reference - A reference to create.
|
||||
* @param {eslint-scope.Reference} reference - A reference to create.
|
||||
* @returns {LoopConditionInfo|null} Created loop condition info, or null.
|
||||
*/
|
||||
function toLoopCondition(reference) {
|
||||
@ -188,7 +188,7 @@ function toLoopCondition(reference) {
|
||||
* Gets the function which encloses a given reference.
|
||||
* This supports only FunctionDeclaration.
|
||||
*
|
||||
* @param {escope.Reference} reference - A reference to get.
|
||||
* @param {eslint-scope.Reference} reference - A reference to get.
|
||||
* @returns {ASTNode|null} The function node or null.
|
||||
*/
|
||||
function getEncloseFunctionDeclaration(reference) {
|
||||
@ -209,7 +209,7 @@ function getEncloseFunctionDeclaration(reference) {
|
||||
* Updates the "modified" flags of given loop conditions with given modifiers.
|
||||
*
|
||||
* @param {LoopConditionInfo[]} conditions - The loop conditions to be updated.
|
||||
* @param {escope.Reference[]} modifiers - The references to update.
|
||||
* @param {eslint-scope.Reference[]} modifiers - The references to update.
|
||||
* @returns {void}
|
||||
*/
|
||||
function updateModifiedFlag(conditions, modifiers) {
|
||||
@ -311,7 +311,7 @@ module.exports = {
|
||||
* Finds unmodified references which are inside of a loop condition.
|
||||
* Then reports the references which are outside of groups.
|
||||
*
|
||||
* @param {escope.Variable} variable - A variable to report.
|
||||
* @param {eslint-scope.Variable} variable - A variable to report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkReferences(variable) {
|
||||
|
@ -134,7 +134,17 @@ module.exports = {
|
||||
node,
|
||||
loc: node.consequent.loc.start,
|
||||
message: "Unnecessary use of conditional expression for default assignment.",
|
||||
fix: fixer => fixer.replaceText(node, `${astUtils.getParenthesisedText(sourceCode, node.test)} || ${astUtils.getParenthesisedText(sourceCode, node.alternate)}`)
|
||||
fix: fixer => {
|
||||
let nodeAlternate = astUtils.getParenthesisedText(sourceCode, node.alternate);
|
||||
|
||||
if (node.alternate.type === "ConditionalExpression") {
|
||||
const isAlternateParenthesised = astUtils.isParenthesised(sourceCode, node.alternate);
|
||||
|
||||
nodeAlternate = isAlternateParenthesised ? nodeAlternate : `(${nodeAlternate})`;
|
||||
}
|
||||
|
||||
return fixer.replaceText(node, `${astUtils.getParenthesisedText(sourceCode, node.test)} || ${nodeAlternate}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ module.exports = {
|
||||
|
||||
/**
|
||||
* Determines if a given variable is being exported from a module.
|
||||
* @param {Variable} variable - EScope variable object.
|
||||
* @param {Variable} variable - eslint-scope variable object.
|
||||
* @returns {boolean} True if the variable is exported, false if not.
|
||||
* @private
|
||||
*/
|
||||
@ -134,7 +134,7 @@ module.exports = {
|
||||
|
||||
/**
|
||||
* Determines if a variable has a sibling rest property
|
||||
* @param {Variable} variable - EScope variable object.
|
||||
* @param {Variable} variable - eslint-scope variable object.
|
||||
* @returns {boolean} True if the variable is exported, false if not.
|
||||
* @private
|
||||
*/
|
||||
@ -157,7 +157,7 @@ module.exports = {
|
||||
|
||||
/**
|
||||
* Determines if a reference is a read operation.
|
||||
* @param {Reference} ref - An escope Reference
|
||||
* @param {Reference} ref - An eslint-scope Reference
|
||||
* @returns {boolean} whether the given reference represents a read operation
|
||||
* @private
|
||||
*/
|
||||
@ -212,7 +212,7 @@ module.exports = {
|
||||
* - The reference is inside of a function scope which is different from
|
||||
* the declaration.
|
||||
*
|
||||
* @param {escope.Reference} ref - A reference to check.
|
||||
* @param {eslint-scope.Reference} ref - A reference to check.
|
||||
* @param {ASTNode} prevRhsNode - The previous RHS node.
|
||||
* @returns {ASTNode|null} The RHS node or null.
|
||||
* @private
|
||||
@ -322,7 +322,7 @@ module.exports = {
|
||||
/**
|
||||
* Checks whether a given reference is a read to update itself or not.
|
||||
*
|
||||
* @param {escope.Reference} ref - A reference to check.
|
||||
* @param {eslint-scope.Reference} ref - A reference to check.
|
||||
* @param {ASTNode} rhsNode - The RHS node of the previous assignment.
|
||||
* @returns {boolean} The reference is a read to update itself.
|
||||
* @private
|
||||
@ -422,7 +422,7 @@ module.exports = {
|
||||
/**
|
||||
* Checks whether the given variable is the last parameter in the non-ignored parameters.
|
||||
*
|
||||
* @param {escope.Variable} variable - The variable to check.
|
||||
* @param {eslint-scope.Variable} variable - The variable to check.
|
||||
* @returns {boolean} `true` if the variable is the last.
|
||||
*/
|
||||
function isLastInNonIgnoredParameters(variable) {
|
||||
@ -448,7 +448,7 @@ module.exports = {
|
||||
|
||||
/**
|
||||
* Gets an array of variables without read references.
|
||||
* @param {Scope} scope - an escope Scope object.
|
||||
* @param {Scope} scope - an eslint-scope Scope object.
|
||||
* @param {Variable[]} unusedVars - an array that saving result.
|
||||
* @returns {Variable[]} unused variables of the scope and descendant scopes.
|
||||
* @private
|
||||
@ -513,7 +513,7 @@ module.exports = {
|
||||
}
|
||||
|
||||
// if "args" option is "after-used", skip all but the last parameter
|
||||
if (config.args === "after-used" && !isLastInNonIgnoredParameters(variable)) {
|
||||
if (config.args === "after-used" && astUtils.isFunction(def.name.parent) && !isLastInNonIgnoredParameters(variable)) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
@ -540,7 +540,7 @@ module.exports = {
|
||||
|
||||
/**
|
||||
* Gets the index of a given variable name in a given comment.
|
||||
* @param {escope.Variable} variable - A variable to get.
|
||||
* @param {eslint-scope.Variable} variable - A variable to get.
|
||||
* @param {ASTNode} comment - A comment node which includes the variable name.
|
||||
* @returns {number} The index of the variable name's location.
|
||||
* @private
|
||||
@ -561,7 +561,7 @@ module.exports = {
|
||||
* Creates the correct location of a given variables.
|
||||
* The location is at its name string in a `/*global` comment.
|
||||
*
|
||||
* @param {escope.Variable} variable - A variable to get its location.
|
||||
* @param {eslint-scope.Variable} variable - A variable to get its location.
|
||||
* @returns {{line: number, column: number}} The location object for the variable.
|
||||
* @private
|
||||
*/
|
||||
|
@ -37,7 +37,7 @@ function parseOptions(options) {
|
||||
/**
|
||||
* Checks whether or not a given variable is a function declaration.
|
||||
*
|
||||
* @param {escope.Variable} variable - A variable to check.
|
||||
* @param {eslint-scope.Variable} variable - A variable to check.
|
||||
* @returns {boolean} `true` if the variable is a function declaration.
|
||||
*/
|
||||
function isFunction(variable) {
|
||||
@ -47,8 +47,8 @@ function isFunction(variable) {
|
||||
/**
|
||||
* Checks whether or not a given variable is a class declaration in an upper function scope.
|
||||
*
|
||||
* @param {escope.Variable} variable - A variable to check.
|
||||
* @param {escope.Reference} reference - A reference to check.
|
||||
* @param {eslint-scope.Variable} variable - A variable to check.
|
||||
* @param {eslint-scope.Reference} reference - A reference to check.
|
||||
* @returns {boolean} `true` if the variable is a class declaration.
|
||||
*/
|
||||
function isOuterClass(variable, reference) {
|
||||
@ -60,8 +60,8 @@ function isOuterClass(variable, reference) {
|
||||
|
||||
/**
|
||||
* Checks whether or not a given variable is a variable declaration in an upper function scope.
|
||||
* @param {escope.Variable} variable - A variable to check.
|
||||
* @param {escope.Reference} reference - A reference to check.
|
||||
* @param {eslint-scope.Variable} variable - A variable to check.
|
||||
* @param {eslint-scope.Reference} reference - A reference to check.
|
||||
* @returns {boolean} `true` if the variable is a variable declaration.
|
||||
*/
|
||||
function isOuterVariable(variable, reference) {
|
||||
@ -167,8 +167,8 @@ module.exports = {
|
||||
|
||||
/**
|
||||
* Determines whether a given use-before-define case should be reported according to the options.
|
||||
* @param {escope.Variable} variable The variable that gets used before being defined
|
||||
* @param {escope.Reference} reference The reference to the variable
|
||||
* @param {eslint-scope.Variable} variable The variable that gets used before being defined
|
||||
* @param {eslint-scope.Reference} reference The reference to the variable
|
||||
* @returns {boolean} `true` if the usage should be reported
|
||||
*/
|
||||
function isForbidden(variable, reference) {
|
||||
@ -250,7 +250,7 @@ module.exports = {
|
||||
|
||||
ruleDefinition["ArrowFunctionExpression:exit"] = function(node) {
|
||||
if (node.body.type !== "BlockStatement") {
|
||||
findVariables(node);
|
||||
findVariables();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
|
@ -9,7 +9,6 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
const esUtils = require("esutils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
@ -61,8 +60,7 @@ module.exports = {
|
||||
|
||||
// Insert a space before the key to avoid changing identifiers, e.g. ({ get[2]() {} }) to ({ get2() {} })
|
||||
const needsSpaceBeforeKey = tokenBeforeLeftBracket.range[1] === leftSquareBracket.range[0] &&
|
||||
esUtils.code.isIdentifierPartES6(tokenBeforeLeftBracket.value.slice(-1).charCodeAt(0)) &&
|
||||
esUtils.code.isIdentifierPartES6(key.raw.charCodeAt(0));
|
||||
!astUtils.canTokensBeAdjacent(tokenBeforeLeftBracket, sourceCode.getFirstToken(key));
|
||||
|
||||
const replacementKey = (needsSpaceBeforeKey ? " " : "") + key.raw;
|
||||
|
||||
|
@ -75,7 +75,7 @@ module.exports = {
|
||||
docs: {
|
||||
description: "disallow unnecessary escape characters",
|
||||
category: "Best Practices",
|
||||
recommended: false
|
||||
recommended: true
|
||||
},
|
||||
|
||||
schema: []
|
||||
|
@ -19,8 +19,8 @@ const astUtils = require("../ast-utils");
|
||||
* Finds the nearest function scope or global scope walking up the scope
|
||||
* hierarchy.
|
||||
*
|
||||
* @param {escope.Scope} scope - The scope to traverse.
|
||||
* @returns {escope.Scope} a function scope or global scope containing the given
|
||||
* @param {eslint-scope.Scope} scope - The scope to traverse.
|
||||
* @returns {eslint-scope.Scope} a function scope or global scope containing the given
|
||||
* scope.
|
||||
*/
|
||||
function getEnclosingFunctionScope(scope) {
|
||||
@ -34,7 +34,7 @@ function getEnclosingFunctionScope(scope) {
|
||||
* Checks whether the given variable has any references from a more specific
|
||||
* function expression (i.e. a closure).
|
||||
*
|
||||
* @param {escope.Variable} variable - A variable to check.
|
||||
* @param {eslint-scope.Variable} variable - A variable to check.
|
||||
* @returns {boolean} `true` if the variable is used from a closure.
|
||||
*/
|
||||
function isReferencedInClosure(variable) {
|
||||
@ -93,7 +93,7 @@ function getScopeNode(node) {
|
||||
/**
|
||||
* Checks whether a given variable is redeclared or not.
|
||||
*
|
||||
* @param {escope.Variable} variable - A variable to check.
|
||||
* @param {eslint-scope.Variable} variable - A variable to check.
|
||||
* @returns {boolean} `true` if the variable is redeclared.
|
||||
*/
|
||||
function isRedeclared(variable) {
|
||||
@ -112,7 +112,7 @@ function isUsedFromOutsideOf(scopeNode) {
|
||||
/**
|
||||
* Checks whether a given reference is inside of the specified scope or not.
|
||||
*
|
||||
* @param {escope.Reference} reference - A reference to check.
|
||||
* @param {eslint-scope.Reference} reference - A reference to check.
|
||||
* @returns {boolean} `true` if the reference is inside of the specified
|
||||
* scope.
|
||||
*/
|
||||
|
@ -40,7 +40,8 @@ module.exports = {
|
||||
|
||||
create(context) {
|
||||
|
||||
const configuration = context.options[0] || {},
|
||||
const sourceCode = context.getSourceCode(),
|
||||
configuration = context.options[0] || {},
|
||||
warningTerms = configuration.terms || ["todo", "fixme", "xxx"],
|
||||
location = configuration.location || "start",
|
||||
selfConfigRegEx = /\bno-warning-comments\b/;
|
||||
@ -128,8 +129,11 @@ module.exports = {
|
||||
}
|
||||
|
||||
return {
|
||||
BlockComment: checkComment,
|
||||
LineComment: checkComment
|
||||
Program() {
|
||||
const comments = sourceCode.getAllComments();
|
||||
|
||||
comments.filter(token => token.type !== "Shebang").forEach(checkComment);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@ -30,6 +30,9 @@ const OPTION_VALUE = {
|
||||
minProperties: {
|
||||
type: "integer",
|
||||
minimum: 0
|
||||
},
|
||||
consistent: {
|
||||
type: "boolean"
|
||||
}
|
||||
},
|
||||
additionalProperties: false,
|
||||
@ -42,11 +45,12 @@ const OPTION_VALUE = {
|
||||
* Normalizes a given option value.
|
||||
*
|
||||
* @param {string|Object|undefined} value - An option value to parse.
|
||||
* @returns {{multiline: boolean, minProperties: number}} Normalized option object.
|
||||
* @returns {{multiline: boolean, minProperties: number, consistent: boolean}} Normalized option object.
|
||||
*/
|
||||
function normalizeOptionValue(value) {
|
||||
let multiline = false;
|
||||
let minProperties = Number.POSITIVE_INFINITY;
|
||||
let consistent = false;
|
||||
|
||||
if (value) {
|
||||
if (value === "always") {
|
||||
@ -56,12 +60,13 @@ function normalizeOptionValue(value) {
|
||||
} else {
|
||||
multiline = Boolean(value.multiline);
|
||||
minProperties = value.minProperties || Number.POSITIVE_INFINITY;
|
||||
consistent = Boolean(value.consistent);
|
||||
}
|
||||
} else {
|
||||
multiline = true;
|
||||
}
|
||||
|
||||
return { multiline, minProperties };
|
||||
return { multiline, minProperties, consistent };
|
||||
}
|
||||
|
||||
/**
|
||||
@ -172,7 +177,14 @@ module.exports = {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (!astUtils.isTokenOnSameLine(openBrace, first)) {
|
||||
const consistent = options.consistent;
|
||||
const hasLineBreakBetweenOpenBraceAndFirst = !astUtils.isTokenOnSameLine(openBrace, first);
|
||||
const hasLineBreakBetweenCloseBraceAndLast = !astUtils.isTokenOnSameLine(last, closeBrace);
|
||||
|
||||
if (
|
||||
(!consistent && hasLineBreakBetweenOpenBraceAndFirst) ||
|
||||
(consistent && hasLineBreakBetweenOpenBraceAndFirst && !hasLineBreakBetweenCloseBraceAndLast)
|
||||
) {
|
||||
context.report({
|
||||
message: "Unexpected line break after this opening brace.",
|
||||
node,
|
||||
@ -185,7 +197,10 @@ module.exports = {
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!astUtils.isTokenOnSameLine(last, closeBrace)) {
|
||||
if (
|
||||
(!consistent && hasLineBreakBetweenCloseBraceAndLast) ||
|
||||
(consistent && !hasLineBreakBetweenOpenBraceAndFirst && hasLineBreakBetweenCloseBraceAndLast)
|
||||
) {
|
||||
context.report({
|
||||
message: "Unexpected line break before this closing brace.",
|
||||
node,
|
||||
|
@ -392,7 +392,7 @@ module.exports = {
|
||||
});
|
||||
}
|
||||
} else if (APPLY_TO_METHODS && !node.value.id && (node.value.type === "FunctionExpression" || node.value.type === "ArrowFunctionExpression")) {
|
||||
if (IGNORE_CONSTRUCTORS && isConstructor(node.key.name)) {
|
||||
if (IGNORE_CONSTRUCTORS && node.key.type === "Identifier" && isConstructor(node.key.name)) {
|
||||
return;
|
||||
}
|
||||
if (AVOID_QUOTES && isStringLiteral(node.key)) {
|
||||
|
@ -51,7 +51,11 @@ module.exports = {
|
||||
const config = context.options[0] || "always";
|
||||
|
||||
if (typeof config === "string") {
|
||||
options.blocks = config === "always";
|
||||
const shouldHavePadding = config === "always";
|
||||
|
||||
options.blocks = shouldHavePadding;
|
||||
options.switches = shouldHavePadding;
|
||||
options.classes = shouldHavePadding;
|
||||
} else {
|
||||
if (config.hasOwnProperty("blocks")) {
|
||||
options.blocks = config.blocks === "always";
|
||||
|
587
tools/eslint/lib/rules/padding-line-between-statements.js
Normal file
587
tools/eslint/lib/rules/padding-line-between-statements.js
Normal file
@ -0,0 +1,587 @@
|
||||
/**
|
||||
* @fileoverview Rule to require or disallow newlines between statements
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const LT = `[${Array.from(astUtils.LINEBREAKS).join("")}]`;
|
||||
const PADDING_LINE_SEQUENCE = new RegExp(
|
||||
String.raw`^(\s*?${LT})\s*${LT}(\s*;?)$`
|
||||
);
|
||||
const CJS_EXPORT = /^(?:module\s*\.\s*)?exports(?:\s*\.|\s*\[|$)/;
|
||||
const CJS_IMPORT = /^require\(/;
|
||||
|
||||
/**
|
||||
* Creates tester which check if a node starts with specific keyword.
|
||||
*
|
||||
* @param {string} keyword The keyword to test.
|
||||
* @returns {Object} the created tester.
|
||||
* @private
|
||||
*/
|
||||
function newKeywordTester(keyword) {
|
||||
return {
|
||||
test: (node, sourceCode) =>
|
||||
sourceCode.getFirstToken(node).value === keyword
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates tester which check if a node is specific type.
|
||||
*
|
||||
* @param {string} type The node type to test.
|
||||
* @returns {Object} the created tester.
|
||||
* @private
|
||||
*/
|
||||
function newNodeTypeTester(type) {
|
||||
return {
|
||||
test: node =>
|
||||
node.type === type
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the given node is an expression statement of IIFE.
|
||||
*
|
||||
* @param {ASTNode} node The node to check.
|
||||
* @returns {boolean} `true` if the node is an expression statement of IIFE.
|
||||
* @private
|
||||
*/
|
||||
function isIIFEStatement(node) {
|
||||
if (node.type === "ExpressionStatement") {
|
||||
let call = node.expression;
|
||||
|
||||
if (call.type === "UnaryExpression") {
|
||||
call = call.argument;
|
||||
}
|
||||
return call.type === "CallExpression" && astUtils.isFunction(call.callee);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given node is a block-like statement.
|
||||
* This checks the last token of the node is the closing brace of a block.
|
||||
*
|
||||
* @param {SourceCode} sourceCode The source code to get tokens.
|
||||
* @param {ASTNode} node The node to check.
|
||||
* @returns {boolean} `true` if the node is a block-like statement.
|
||||
* @private
|
||||
*/
|
||||
function isBlockLikeStatement(sourceCode, node) {
|
||||
|
||||
// do-while with a block is a block-like statement.
|
||||
if (node.type === "DoWhileStatement" && node.body.type === "BlockStatement") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// IIFE is a block-like statement specially from
|
||||
// JSCS#disallowPaddingNewLinesAfterBlocks.
|
||||
if (isIIFEStatement(node)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Checks the last token is a closing brace of blocks.
|
||||
const lastToken = sourceCode.getLastToken(node, astUtils.isNotSemicolonToken);
|
||||
const belongingNode = astUtils.isClosingBraceToken(lastToken)
|
||||
? sourceCode.getNodeByRangeIndex(lastToken.range[0])
|
||||
: null;
|
||||
|
||||
return Boolean(belongingNode) && (
|
||||
belongingNode.type === "BlockStatement" ||
|
||||
belongingNode.type === "SwitchStatement"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given node is a directive or not.
|
||||
* @param {ASTNode} node The node to check.
|
||||
* @param {SourceCode} sourceCode The source code object to get tokens.
|
||||
* @returns {boolean} `true` if the node is a directive.
|
||||
*/
|
||||
function isDirective(node, sourceCode) {
|
||||
return (
|
||||
node.type === "ExpressionStatement" &&
|
||||
(
|
||||
node.parent.type === "Program" ||
|
||||
(
|
||||
node.parent.type === "BlockStatement" &&
|
||||
astUtils.isFunction(node.parent.parent)
|
||||
)
|
||||
) &&
|
||||
node.expression.type === "Literal" &&
|
||||
typeof node.expression.value === "string" &&
|
||||
!astUtils.isParenthesised(sourceCode, node.expression)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given node is a part of directive prologue or not.
|
||||
* @param {ASTNode} node The node to check.
|
||||
* @param {SourceCode} sourceCode The source code object to get tokens.
|
||||
* @returns {boolean} `true` if the node is a part of directive prologue.
|
||||
*/
|
||||
function isDirectivePrologue(node, sourceCode) {
|
||||
if (isDirective(node, sourceCode)) {
|
||||
for (const sibling of node.parent.body) {
|
||||
if (sibling === node) {
|
||||
break;
|
||||
}
|
||||
if (!isDirective(sibling, sourceCode)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the actual last token.
|
||||
*
|
||||
* If a semicolon is semicolon-less style's semicolon, this ignores it.
|
||||
* For example:
|
||||
*
|
||||
* foo()
|
||||
* ;[1, 2, 3].forEach(bar)
|
||||
*
|
||||
* @param {SourceCode} sourceCode The source code to get tokens.
|
||||
* @param {ASTNode} node The node to get.
|
||||
* @returns {Token} The actual last token.
|
||||
* @private
|
||||
*/
|
||||
function getActualLastToken(sourceCode, node) {
|
||||
const semiToken = sourceCode.getLastToken(node);
|
||||
const prevToken = sourceCode.getTokenBefore(semiToken);
|
||||
const nextToken = sourceCode.getTokenAfter(semiToken);
|
||||
const isSemicolonLessStyle = Boolean(
|
||||
prevToken &&
|
||||
nextToken &&
|
||||
prevToken.range[0] >= node.range[0] &&
|
||||
astUtils.isSemicolonToken(semiToken) &&
|
||||
semiToken.loc.start.line !== prevToken.loc.end.line &&
|
||||
semiToken.loc.end.line === nextToken.loc.start.line
|
||||
);
|
||||
|
||||
return isSemicolonLessStyle ? prevToken : semiToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the concatenation of the first 2 captured strings.
|
||||
* @param {string} _ Unused. Whole matched string.
|
||||
* @param {string} trailingSpaces The trailing spaces of the first line.
|
||||
* @param {string} indentSpaces The indentation spaces of the last line.
|
||||
* @returns {string} The concatenation of trailingSpaces and indentSpaces.
|
||||
* @private
|
||||
*/
|
||||
function replacerToRemovePaddingLines(_, trailingSpaces, indentSpaces) {
|
||||
return trailingSpaces + indentSpaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and report statements for `any` configuration.
|
||||
* It does nothing.
|
||||
*
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function verifyForAny() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and report statements for `never` configuration.
|
||||
* This autofix removes blank lines between the given 2 statements.
|
||||
* However, if comments exist between 2 blank lines, it does not remove those
|
||||
* blank lines automatically.
|
||||
*
|
||||
* @param {RuleContext} context The rule context to report.
|
||||
* @param {ASTNode} prevNode The previous node to check.
|
||||
* @param {ASTNode} nextNode The next node to check.
|
||||
* @param {Array<Token[]>} paddingLines The array of token pairs that blank
|
||||
* lines exist between the pair.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function verifyForNever(context, prevNode, nextNode, paddingLines) {
|
||||
if (paddingLines.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node: nextNode,
|
||||
message: "Unexpected blank line before this statement.",
|
||||
fix(fixer) {
|
||||
if (paddingLines.length >= 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const prevToken = paddingLines[0][0];
|
||||
const nextToken = paddingLines[0][1];
|
||||
const start = prevToken.range[1];
|
||||
const end = nextToken.range[0];
|
||||
const text = context.getSourceCode().text
|
||||
.slice(start, end)
|
||||
.replace(PADDING_LINE_SEQUENCE, replacerToRemovePaddingLines);
|
||||
|
||||
return fixer.replaceTextRange([start, end], text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and report statements for `always` configuration.
|
||||
* This autofix inserts a blank line between the given 2 statements.
|
||||
* If the `prevNode` has trailing comments, it inserts a blank line after the
|
||||
* trailing comments.
|
||||
*
|
||||
* @param {RuleContext} context The rule context to report.
|
||||
* @param {ASTNode} prevNode The previous node to check.
|
||||
* @param {ASTNode} nextNode The next node to check.
|
||||
* @param {Array<Token[]>} paddingLines The array of token pairs that blank
|
||||
* lines exist between the pair.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function verifyForAlways(context, prevNode, nextNode, paddingLines) {
|
||||
if (paddingLines.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node: nextNode,
|
||||
message: "Expected blank line before this statement.",
|
||||
fix(fixer) {
|
||||
const sourceCode = context.getSourceCode();
|
||||
let prevToken = getActualLastToken(sourceCode, prevNode);
|
||||
const nextToken = sourceCode.getFirstTokenBetween(
|
||||
prevToken,
|
||||
nextNode,
|
||||
{
|
||||
includeComments: true,
|
||||
|
||||
/**
|
||||
* Skip the trailing comments of the previous node.
|
||||
* This inserts a blank line after the last trailing comment.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* foo(); // trailing comment.
|
||||
* // comment.
|
||||
* bar();
|
||||
*
|
||||
* Get fixed to:
|
||||
*
|
||||
* foo(); // trailing comment.
|
||||
*
|
||||
* // comment.
|
||||
* bar();
|
||||
*
|
||||
* @param {Token} token The token to check.
|
||||
* @returns {boolean} `true` if the token is not a trailing comment.
|
||||
* @private
|
||||
*/
|
||||
filter(token) {
|
||||
if (astUtils.isTokenOnSameLine(prevToken, token)) {
|
||||
prevToken = token;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
) || nextNode;
|
||||
const insertText = astUtils.isTokenOnSameLine(prevToken, nextToken)
|
||||
? "\n\n"
|
||||
: "\n";
|
||||
|
||||
return fixer.insertTextAfter(prevToken, insertText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Types of blank lines.
|
||||
* `any`, `never`, and `always` are defined.
|
||||
* Those have `verify` method to check and report statements.
|
||||
* @private
|
||||
*/
|
||||
const PaddingTypes = {
|
||||
any: { verify: verifyForAny },
|
||||
never: { verify: verifyForNever },
|
||||
always: { verify: verifyForAlways }
|
||||
};
|
||||
|
||||
/**
|
||||
* Types of statements.
|
||||
* Those have `test` method to check it matches to the given statement.
|
||||
* @private
|
||||
*/
|
||||
const StatementTypes = {
|
||||
"*": { test: () => true },
|
||||
"block-like": {
|
||||
test: (node, sourceCode) => isBlockLikeStatement(sourceCode, node)
|
||||
},
|
||||
"cjs-export": {
|
||||
test: (node, sourceCode) =>
|
||||
node.type === "ExpressionStatement" &&
|
||||
node.expression.type === "AssignmentExpression" &&
|
||||
CJS_EXPORT.test(sourceCode.getText(node.expression.left))
|
||||
},
|
||||
"cjs-import": {
|
||||
test: (node, sourceCode) =>
|
||||
node.type === "VariableDeclaration" &&
|
||||
node.declarations.length > 0 &&
|
||||
Boolean(node.declarations[0].init) &&
|
||||
CJS_IMPORT.test(sourceCode.getText(node.declarations[0].init))
|
||||
},
|
||||
directive: {
|
||||
test: isDirectivePrologue
|
||||
},
|
||||
expression: {
|
||||
test: (node, sourceCode) =>
|
||||
node.type === "ExpressionStatement" &&
|
||||
!isDirectivePrologue(node, sourceCode)
|
||||
},
|
||||
"multiline-block-like": {
|
||||
test: (node, sourceCode) =>
|
||||
node.loc.start.line !== node.loc.end.line &&
|
||||
isBlockLikeStatement(sourceCode, node)
|
||||
},
|
||||
|
||||
block: newNodeTypeTester("BlockStatement"),
|
||||
empty: newNodeTypeTester("EmptyStatement"),
|
||||
|
||||
break: newKeywordTester("break"),
|
||||
case: newKeywordTester("case"),
|
||||
class: newKeywordTester("class"),
|
||||
const: newKeywordTester("const"),
|
||||
continue: newKeywordTester("continue"),
|
||||
debugger: newKeywordTester("debugger"),
|
||||
default: newKeywordTester("default"),
|
||||
do: newKeywordTester("do"),
|
||||
export: newKeywordTester("export"),
|
||||
for: newKeywordTester("for"),
|
||||
function: newKeywordTester("function"),
|
||||
if: newKeywordTester("if"),
|
||||
import: newKeywordTester("import"),
|
||||
let: newKeywordTester("let"),
|
||||
return: newKeywordTester("return"),
|
||||
switch: newKeywordTester("switch"),
|
||||
throw: newKeywordTester("throw"),
|
||||
try: newKeywordTester("try"),
|
||||
var: newKeywordTester("var"),
|
||||
while: newKeywordTester("while"),
|
||||
with: newKeywordTester("with")
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
docs: {
|
||||
description: "require or disallow padding lines between statements",
|
||||
category: "Stylistic Issues",
|
||||
recommended: false
|
||||
},
|
||||
fixable: "whitespace",
|
||||
schema: {
|
||||
definitions: {
|
||||
paddingType: {
|
||||
enum: Object.keys(PaddingTypes)
|
||||
},
|
||||
statementType: {
|
||||
anyOf: [
|
||||
{ enum: Object.keys(StatementTypes) },
|
||||
{
|
||||
type: "array",
|
||||
items: { enum: Object.keys(StatementTypes) },
|
||||
minItems: 1,
|
||||
uniqueItems: true,
|
||||
additionalItems: false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
blankLine: { $ref: "#/definitions/paddingType" },
|
||||
prev: { $ref: "#/definitions/statementType" },
|
||||
next: { $ref: "#/definitions/statementType" }
|
||||
},
|
||||
additionalProperties: false,
|
||||
required: ["blankLine", "prev", "next"]
|
||||
},
|
||||
additionalItems: false
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const sourceCode = context.getSourceCode();
|
||||
const configureList = context.options || [];
|
||||
let scopeInfo = null;
|
||||
|
||||
/**
|
||||
* Processes to enter to new scope.
|
||||
* This manages the current previous statement.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function enterScope() {
|
||||
scopeInfo = {
|
||||
upper: scopeInfo,
|
||||
prevNode: null
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes to exit from the current scope.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function exitScope() {
|
||||
scopeInfo = scopeInfo.upper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given node matches the given type.
|
||||
*
|
||||
* @param {ASTNode} node The statement node to check.
|
||||
* @param {string|string[]} type The statement type to check.
|
||||
* @returns {boolean} `true` if the statement node matched the type.
|
||||
* @private
|
||||
*/
|
||||
function match(node, type) {
|
||||
while (node.type === "LabeledStatement") {
|
||||
node = node.body;
|
||||
}
|
||||
if (Array.isArray(type)) {
|
||||
return type.some(match.bind(null, node));
|
||||
}
|
||||
return StatementTypes[type].test(node, sourceCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the last matched configure from configureList.
|
||||
*
|
||||
* @param {ASTNode} prevNode The previous statement to match.
|
||||
* @param {ASTNode} nextNode The current statement to match.
|
||||
* @returns {Object} The tester of the last matched configure.
|
||||
* @private
|
||||
*/
|
||||
function getPaddingType(prevNode, nextNode) {
|
||||
for (let i = configureList.length - 1; i >= 0; --i) {
|
||||
const configure = configureList[i];
|
||||
const matched =
|
||||
match(prevNode, configure.prev) &&
|
||||
match(nextNode, configure.next);
|
||||
|
||||
if (matched) {
|
||||
return PaddingTypes[configure.blankLine];
|
||||
}
|
||||
}
|
||||
return PaddingTypes.any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets padding line sequences between the given 2 statements.
|
||||
* Comments are separators of the padding line sequences.
|
||||
*
|
||||
* @param {ASTNode} prevNode The previous statement to count.
|
||||
* @param {ASTNode} nextNode The current statement to count.
|
||||
* @returns {Array<Token[]>} The array of token pairs.
|
||||
* @private
|
||||
*/
|
||||
function getPaddingLineSequences(prevNode, nextNode) {
|
||||
const pairs = [];
|
||||
let prevToken = getActualLastToken(sourceCode, prevNode);
|
||||
|
||||
if (nextNode.loc.start.line - prevToken.loc.end.line >= 2) {
|
||||
do {
|
||||
const token = sourceCode.getTokenAfter(
|
||||
prevToken,
|
||||
{ includeComments: true }
|
||||
);
|
||||
|
||||
if (token.loc.start.line - prevToken.loc.end.line >= 2) {
|
||||
pairs.push([prevToken, token]);
|
||||
}
|
||||
prevToken = token;
|
||||
|
||||
} while (prevToken.range[0] < nextNode.range[0]);
|
||||
}
|
||||
|
||||
return pairs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify padding lines between the given node and the previous node.
|
||||
*
|
||||
* @param {ASTNode} node The node to verify.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function verify(node) {
|
||||
const parentType = node.parent.type;
|
||||
const validParent =
|
||||
astUtils.STATEMENT_LIST_PARENTS.has(parentType) ||
|
||||
parentType === "SwitchStatement";
|
||||
|
||||
if (!validParent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save this node as the current previous statement.
|
||||
const prevNode = scopeInfo.prevNode;
|
||||
|
||||
// Verify.
|
||||
if (prevNode) {
|
||||
const type = getPaddingType(prevNode, node);
|
||||
const paddingLines = getPaddingLineSequences(prevNode, node);
|
||||
|
||||
type.verify(context, prevNode, node, paddingLines);
|
||||
}
|
||||
|
||||
scopeInfo.prevNode = node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify padding lines between the given node and the previous node.
|
||||
* Then process to enter to new scope.
|
||||
*
|
||||
* @param {ASTNode} node The node to verify.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function verifyThenEnterScope(node) {
|
||||
verify(node);
|
||||
enterScope();
|
||||
}
|
||||
|
||||
return {
|
||||
Program: enterScope,
|
||||
BlockStatement: enterScope,
|
||||
SwitchStatement: enterScope,
|
||||
"Program:exit": exitScope,
|
||||
"BlockStatement:exit": exitScope,
|
||||
"SwitchStatement:exit": exitScope,
|
||||
|
||||
":statement": verify,
|
||||
|
||||
SwitchCase: verifyThenEnterScope,
|
||||
"SwitchCase:exit": exitScope
|
||||
};
|
||||
}
|
||||
};
|
@ -11,7 +11,7 @@
|
||||
|
||||
/**
|
||||
* Checks whether or not a given variable is a function name.
|
||||
* @param {escope.Variable} variable - A variable to check.
|
||||
* @param {eslint-scope.Variable} variable - A variable to check.
|
||||
* @returns {boolean} `true` if the variable is a function name.
|
||||
*/
|
||||
function isFunctionName(variable) {
|
||||
@ -31,8 +31,8 @@ function checkMetaProperty(node, metaName, propertyName) {
|
||||
|
||||
/**
|
||||
* Gets the variable object of `arguments` which is defined implicitly.
|
||||
* @param {escope.Scope} scope - A scope to get.
|
||||
* @returns {escope.Variable} The found variable object.
|
||||
* @param {eslint-scope.Scope} scope - A scope to get.
|
||||
* @returns {eslint-scope.Variable} The found variable object.
|
||||
*/
|
||||
function getVariableOfArguments(scope) {
|
||||
const variables = scope.variables;
|
||||
@ -159,7 +159,7 @@ module.exports = {
|
||||
create(context) {
|
||||
const options = context.options[0] || {};
|
||||
|
||||
const allowUnboundThis = options.allowUnboundThis !== false; // default to true
|
||||
const allowUnboundThis = options.allowUnboundThis !== false; // default to true
|
||||
const allowNamedFunctions = options.allowNamedFunctions;
|
||||
const sourceCode = context.getSourceCode();
|
||||
|
||||
@ -277,15 +277,23 @@ module.exports = {
|
||||
const paramsRightParen = sourceCode.getTokenBefore(node.body);
|
||||
const asyncKeyword = node.async ? "async " : "";
|
||||
const paramsFullText = sourceCode.text.slice(paramsLeftParen.range[0], paramsRightParen.range[1]);
|
||||
const arrowFunctionText = `${asyncKeyword}${paramsFullText} => ${sourceCode.getText(node.body)}`;
|
||||
|
||||
if (callbackInfo.isLexicalThis) {
|
||||
/*
|
||||
* If the callback function has `.bind(this)`, replace it with an arrow function and remove the binding.
|
||||
* Otherwise, just replace the arrow function itself.
|
||||
*/
|
||||
const replacedNode = callbackInfo.isLexicalThis ? node.parent.parent : node;
|
||||
|
||||
// If the callback function has `.bind(this)`, replace it with an arrow function and remove the binding.
|
||||
return fixer.replaceText(node.parent.parent, `${asyncKeyword}${paramsFullText} => ${sourceCode.getText(node.body)}`);
|
||||
}
|
||||
/*
|
||||
* If the replaced node is part of a BinaryExpression, LogicalExpression, or MemberExpression, then
|
||||
* the arrow function needs to be parenthesized, because `foo || () => {}` is invalid syntax even
|
||||
* though `foo || function() {}` is valid.
|
||||
*/
|
||||
const needsParens = replacedNode.parent.type !== "CallExpression" && replacedNode.parent.type !== "ConditionalExpression";
|
||||
const replacementText = needsParens ? `(${arrowFunctionText})` : arrowFunctionText;
|
||||
|
||||
// Otherwise, only replace the `function` keyword and parameters with the arrow function parameters.
|
||||
return fixer.replaceTextRange([node.start, node.body.start], `${asyncKeyword}${paramsFullText} => `);
|
||||
return fixer.replaceText(replacedNode, replacementText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ function canBecomeVariableDeclaration(identifier) {
|
||||
* warn such variables because this rule cannot distinguish whether the
|
||||
* exported variables are reassigned or not.
|
||||
*
|
||||
* @param {escope.Variable} variable - A variable to get.
|
||||
* @param {eslint-scope.Variable} variable - A variable to get.
|
||||
* @param {boolean} ignoreReadBeforeAssign -
|
||||
* The value of `ignoreReadBeforeAssign` option.
|
||||
* @returns {ASTNode|null}
|
||||
@ -85,17 +85,6 @@ function getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Due to a bug in acorn, code such as `let foo = 1; let foo = 2;` will not throw a syntax error. As a sanity
|
||||
* check, make sure that the variable only has one declaration. After the parsing bug is fixed, this check
|
||||
* will no longer be necessary, because variables declared with `let` or `const` should always have exactly one
|
||||
* declaration.
|
||||
* https://github.com/ternjs/acorn/issues/487
|
||||
*/
|
||||
if (variable.defs.length > 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Finds the unique WriteReference.
|
||||
let writer = null;
|
||||
let isReadBeforeInit = false;
|
||||
@ -146,7 +135,7 @@ function getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign) {
|
||||
* This is used to detect a mix of reassigned and never reassigned in a
|
||||
* destructuring.
|
||||
*
|
||||
* @param {escope.Reference} reference - A reference to get.
|
||||
* @param {eslint-scope.Reference} reference - A reference to get.
|
||||
* @returns {ASTNode|null} A VariableDeclarator/AssignmentExpression node or
|
||||
* null.
|
||||
*/
|
||||
@ -172,7 +161,7 @@ function getDestructuringHost(reference) {
|
||||
* This is used to detect a mix of reassigned and never reassigned in a
|
||||
* destructuring.
|
||||
*
|
||||
* @param {escope.Variable[]} variables - Variables to group by destructuring.
|
||||
* @param {eslint-scope.Variable[]} variables - Variables to group by destructuring.
|
||||
* @param {boolean} ignoreReadBeforeAssign -
|
||||
* The value of `ignoreReadBeforeAssign` option.
|
||||
* @returns {Map<ASTNode, ASTNode[]>} Grouped identifier nodes.
|
||||
@ -274,7 +263,7 @@ module.exports = {
|
||||
* the array is 1. In destructuring cases, the length of the array can
|
||||
* be 2 or more.
|
||||
*
|
||||
* @param {(escope.Reference|null)[]} nodes -
|
||||
* @param {(eslint-scope.Reference|null)[]} nodes -
|
||||
* References which are grouped by destructuring to report.
|
||||
* @returns {void}
|
||||
*/
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user