mirror of
https://github.com/nodejs/node.git
synced 2026-10-14 10:26:26 -04:00
tools: update ESLint to 3.19.0
PR-URL: https://github.com/nodejs/node/pull/12162 Reviewed-By: Teddy Katz <teddy.katz@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Yuta Hiroto <hello@about-hiroppy.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
This commit is contained in:
1 parent
5ccaba49f0
commit
316665235c
374 files changed
+23611
-10895
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -112,6 +112,7 @@ These folks keep the project moving and are resources for help.
|
||||
* Toru Nagashima ([@mysticatea](https://github.com/mysticatea))
|
||||
* Alberto Rodríguez ([@alberto](https://github.com/alberto))
|
||||
* Kai Cataldo ([@kaicataldo](https://github.com/kaicataldo))
|
||||
* Teddy Katz ([@not-an-aardvark](https://github.com/not-an-aardvark))
|
||||
|
||||
### Development Team
|
||||
|
||||
@@ -129,7 +130,7 @@ These folks keep the project moving and are resources for help.
|
||||
* Kevin Partington ([@platinumazure](https://github.com/platinumazure))
|
||||
* Vitor Balocco ([@vitorbal](https://github.com/vitorbal))
|
||||
* James Henry ([@JamesHenry](https://github.com/JamesHenry))
|
||||
* Teddy Katz ([@not-an-aardvark](https://github.com/not-an-aardvark))
|
||||
* Reyad Attiyat ([@soda0289](https://github.com/soda0289))
|
||||
|
||||
## Releases
|
||||
|
||||
|
||||
@@ -1,7 +1,81 @@
|
||||
{
|
||||
"parser": "espree",
|
||||
"ecmaFeatures": {},
|
||||
"rules": {
|
||||
/**
|
||||
* @fileoverview Configuration applied when a user configuration extends from
|
||||
* eslint:recommended.
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/* eslint sort-keys: ["error", "asc"], quote-props: ["error", "consistent"] */
|
||||
/* eslint-disable sort-keys */
|
||||
|
||||
module.exports = {
|
||||
parser: "espree",
|
||||
ecmaFeatures: {},
|
||||
|
||||
rules: {
|
||||
|
||||
/* eslint-enable sort-keys */
|
||||
"accessor-pairs": "off",
|
||||
"array-bracket-spacing": "off",
|
||||
"array-callback-return": "off",
|
||||
"arrow-body-style": "off",
|
||||
"arrow-parens": "off",
|
||||
"arrow-spacing": "off",
|
||||
"block-scoped-var": "off",
|
||||
"block-spacing": "off",
|
||||
"brace-style": "off",
|
||||
"callback-return": "off",
|
||||
"camelcase": "off",
|
||||
"capitalized-comments": "off",
|
||||
"class-methods-use-this": "off",
|
||||
"comma-dangle": "off",
|
||||
"comma-spacing": "off",
|
||||
"comma-style": "off",
|
||||
"complexity": "off",
|
||||
"computed-property-spacing": "off",
|
||||
"consistent-return": "off",
|
||||
"consistent-this": "off",
|
||||
"constructor-super": "error",
|
||||
"curly": "off",
|
||||
"default-case": "off",
|
||||
"dot-location": "off",
|
||||
"dot-notation": "off",
|
||||
"eol-last": "off",
|
||||
"eqeqeq": "off",
|
||||
"func-call-spacing": "off",
|
||||
"func-name-matching": "off",
|
||||
"func-names": "off",
|
||||
"func-style": "off",
|
||||
"generator-star-spacing": "off",
|
||||
"global-require": "off",
|
||||
"guard-for-in": "off",
|
||||
"handle-callback-err": "off",
|
||||
"id-blacklist": "off",
|
||||
"id-length": "off",
|
||||
"id-match": "off",
|
||||
"indent": "off",
|
||||
"init-declarations": "off",
|
||||
"jsx-quotes": "off",
|
||||
"key-spacing": "off",
|
||||
"keyword-spacing": "off",
|
||||
"line-comment-position": "off",
|
||||
"linebreak-style": "off",
|
||||
"lines-around-comment": "off",
|
||||
"lines-around-directive": "off",
|
||||
"max-depth": "off",
|
||||
"max-len": "off",
|
||||
"max-lines": "off",
|
||||
"max-nested-callbacks": "off",
|
||||
"max-params": "off",
|
||||
"max-statements": "off",
|
||||
"max-statements-per-line": "off",
|
||||
"multiline-ternary": "off",
|
||||
"new-cap": "off",
|
||||
"new-parens": "off",
|
||||
"newline-after-var": "off",
|
||||
"newline-before-return": "off",
|
||||
"newline-per-chained-call": "off",
|
||||
"no-alert": "off",
|
||||
"no-array-constructor": "off",
|
||||
"no-await-in-loop": "off",
|
||||
@@ -10,6 +84,7 @@
|
||||
"no-case-declarations": "error",
|
||||
"no-catch-shadow": "off",
|
||||
"no-class-assign": "error",
|
||||
"no-compare-neg-zero": "off",
|
||||
"no-cond-assign": "error",
|
||||
"no-confusing-arrow": "off",
|
||||
"no-console": "error",
|
||||
@@ -61,6 +136,7 @@
|
||||
"no-mixed-operators": "off",
|
||||
"no-mixed-requires": "off",
|
||||
"no-mixed-spaces-and-tabs": "error",
|
||||
"no-multi-assign": "off",
|
||||
"no-multi-spaces": "off",
|
||||
"no-multi-str": "off",
|
||||
"no-multiple-empty-lines": "off",
|
||||
@@ -99,20 +175,20 @@
|
||||
"no-sequences": "off",
|
||||
"no-shadow": "off",
|
||||
"no-shadow-restricted-names": "off",
|
||||
"no-whitespace-before-property": "off",
|
||||
"no-spaced-func": "off",
|
||||
"no-sparse-arrays": "error",
|
||||
"no-sync": "off",
|
||||
"no-tabs": "off",
|
||||
"no-template-curly-in-string": "off",
|
||||
"no-ternary": "off",
|
||||
"no-trailing-spaces": "off",
|
||||
"no-this-before-super": "error",
|
||||
"no-throw-literal": "off",
|
||||
"no-trailing-spaces": "off",
|
||||
"no-undef": "error",
|
||||
"no-undef-init": "off",
|
||||
"no-undefined": "off",
|
||||
"no-unexpected-multiline": "error",
|
||||
"no-underscore-dangle": "off",
|
||||
"no-unexpected-multiline": "error",
|
||||
"no-unmodified-loop-condition": "off",
|
||||
"no-unneeded-ternary": "off",
|
||||
"no-unreachable": "error",
|
||||
@@ -129,70 +205,12 @@
|
||||
"no-useless-escape": "off",
|
||||
"no-useless-rename": "off",
|
||||
"no-useless-return": "off",
|
||||
"no-void": "off",
|
||||
"no-var": "off",
|
||||
"no-void": "off",
|
||||
"no-warning-comments": "off",
|
||||
"no-whitespace-before-property": "off",
|
||||
"no-with": "off",
|
||||
"array-bracket-spacing": "off",
|
||||
"array-callback-return": "off",
|
||||
"arrow-body-style": "off",
|
||||
"arrow-parens": "off",
|
||||
"arrow-spacing": "off",
|
||||
"accessor-pairs": "off",
|
||||
"block-scoped-var": "off",
|
||||
"block-spacing": "off",
|
||||
"brace-style": "off",
|
||||
"callback-return": "off",
|
||||
"camelcase": "off",
|
||||
"capitalized-comments": "off",
|
||||
"class-methods-use-this": "off",
|
||||
"comma-dangle": "off",
|
||||
"comma-spacing": "off",
|
||||
"comma-style": "off",
|
||||
"complexity": "off",
|
||||
"computed-property-spacing": "off",
|
||||
"consistent-return": "off",
|
||||
"consistent-this": "off",
|
||||
"constructor-super": "error",
|
||||
"curly": "off",
|
||||
"default-case": "off",
|
||||
"dot-location": "off",
|
||||
"dot-notation": "off",
|
||||
"eol-last": "off",
|
||||
"eqeqeq": "off",
|
||||
"func-call-spacing": "off",
|
||||
"func-names": "off",
|
||||
"func-name-matching": "off",
|
||||
"func-style": "off",
|
||||
"generator-star-spacing": "off",
|
||||
"global-require": "off",
|
||||
"guard-for-in": "off",
|
||||
"handle-callback-err": "off",
|
||||
"id-blacklist": "off",
|
||||
"id-length": "off",
|
||||
"id-match": "off",
|
||||
"indent": "off",
|
||||
"init-declarations": "off",
|
||||
"jsx-quotes": "off",
|
||||
"key-spacing": "off",
|
||||
"keyword-spacing": "off",
|
||||
"linebreak-style": "off",
|
||||
"line-comment-position": "off",
|
||||
"lines-around-comment": "off",
|
||||
"lines-around-directive": "off",
|
||||
"max-depth": "off",
|
||||
"max-len": "off",
|
||||
"max-lines": "off",
|
||||
"max-nested-callbacks": "off",
|
||||
"max-params": "off",
|
||||
"max-statements": "off",
|
||||
"max-statements-per-line": "off",
|
||||
"multiline-ternary": "off",
|
||||
"new-cap": "off",
|
||||
"new-parens": "off",
|
||||
"newline-after-var": "off",
|
||||
"newline-before-return": "off",
|
||||
"newline-per-chained-call": "off",
|
||||
"nonblock-statement-body-position": "off",
|
||||
"object-curly-newline": "off",
|
||||
"object-curly-spacing": ["off", "never"],
|
||||
"object-property-newline": "off",
|
||||
@@ -206,6 +224,7 @@
|
||||
"prefer-const": "off",
|
||||
"prefer-destructuring": "off",
|
||||
"prefer-numeric-literals": "off",
|
||||
"prefer-promise-reject-errors": "off",
|
||||
"prefer-reflect": "off",
|
||||
"prefer-rest-params": "off",
|
||||
"prefer-spread": "off",
|
||||
@@ -219,8 +238,8 @@
|
||||
"rest-spread-spacing": "off",
|
||||
"semi": "off",
|
||||
"semi-spacing": "off",
|
||||
"sort-keys": "off",
|
||||
"sort-imports": "off",
|
||||
"sort-keys": "off",
|
||||
"sort-vars": "off",
|
||||
"space-before-blocks": "off",
|
||||
"space-before-function-paren": "off",
|
||||
@@ -231,6 +250,7 @@
|
||||
"strict": "off",
|
||||
"symbol-description": "off",
|
||||
"template-curly-spacing": "off",
|
||||
"template-tag-spacing": "off",
|
||||
"unicode-bom": "off",
|
||||
"use-isnan": "error",
|
||||
"valid-jsdoc": "off",
|
||||
@@ -238,8 +258,7 @@
|
||||
"vars-on-top": "off",
|
||||
"wrap-iife": "off",
|
||||
"wrap-regex": "off",
|
||||
"no-template-curly-in-string": "off",
|
||||
"yield-star-spacing": "off",
|
||||
"yoda": "off"
|
||||
}
|
||||
}
|
||||
};
|
||||
+230
-75
@@ -10,7 +10,6 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const esutils = require("esutils");
|
||||
const lodash = require("lodash");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
@@ -24,6 +23,14 @@ const bindOrCallOrApplyPattern = /^(?:bind|call|apply)$/;
|
||||
const breakableTypePattern = /^(?:(?:Do)?While|For(?:In|Of)?|Switch)Statement$/;
|
||||
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]/;
|
||||
|
||||
// A set of node types that can contain a list of statements
|
||||
const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "SwitchCase"]);
|
||||
|
||||
/**
|
||||
* Checks reference if is non initializer and writable.
|
||||
* @param {Reference} reference - A reference to check.
|
||||
@@ -142,7 +149,7 @@ function isInLoop(node) {
|
||||
*/
|
||||
function isNullOrUndefined(node) {
|
||||
return (
|
||||
(node.type === "Literal" && node.value === null) ||
|
||||
module.exports.isNullLiteral(node) ||
|
||||
(node.type === "Identifier" && node.name === "undefined") ||
|
||||
(node.type === "UnaryExpression" && node.operator === "void")
|
||||
);
|
||||
@@ -158,9 +165,9 @@ function isCallee(node) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a node is `Reclect.apply`.
|
||||
* Checks whether or not a node is `Reflect.apply`.
|
||||
* @param {ASTNode} node - A node to check.
|
||||
* @returns {boolean} Whether or not the node is a `Reclect.apply`.
|
||||
* @returns {boolean} Whether or not the node is a `Reflect.apply`.
|
||||
*/
|
||||
function isReflectApply(node) {
|
||||
return (
|
||||
@@ -210,6 +217,15 @@ function isMethodWhichHasThisArg(node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the negate function of the given function.
|
||||
* @param {Function} f - The function to negate.
|
||||
* @returns {Function} Negated function.
|
||||
*/
|
||||
function negate(f) {
|
||||
return token => !f(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a node has a `@this` tag in its comments.
|
||||
* @param {ASTNode} node - A node to check.
|
||||
@@ -247,20 +263,123 @@ function isParenthesised(sourceCode, node) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `=>` token of the given arrow function node.
|
||||
* Checks if the given token is an arrow token or not.
|
||||
*
|
||||
* @param {ASTNode} node - The arrow function node to get.
|
||||
* @param {SourceCode} sourceCode - The source code object to get tokens.
|
||||
* @returns {Token} `=>` token.
|
||||
* @param {Token} token - The token to check.
|
||||
* @returns {boolean} `true` if the token is an arrow token.
|
||||
*/
|
||||
function getArrowToken(node, sourceCode) {
|
||||
let token = sourceCode.getTokenBefore(node.body);
|
||||
function isArrowToken(token) {
|
||||
return token.value === "=>" && token.type === "Punctuator";
|
||||
}
|
||||
|
||||
while (token.value !== "=>") {
|
||||
token = sourceCode.getTokenBefore(token);
|
||||
}
|
||||
/**
|
||||
* Checks if the given token is a comma token or not.
|
||||
*
|
||||
* @param {Token} token - The token to check.
|
||||
* @returns {boolean} `true` if the token is a comma token.
|
||||
*/
|
||||
function isCommaToken(token) {
|
||||
return token.value === "," && token.type === "Punctuator";
|
||||
}
|
||||
|
||||
return token;
|
||||
/**
|
||||
* Checks if the given token is a semicolon token or not.
|
||||
*
|
||||
* @param {Token} token - The token to check.
|
||||
* @returns {boolean} `true` if the token is a semicolon token.
|
||||
*/
|
||||
function isSemicolonToken(token) {
|
||||
return token.value === ";" && token.type === "Punctuator";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given token is a colon token or not.
|
||||
*
|
||||
* @param {Token} token - The token to check.
|
||||
* @returns {boolean} `true` if the token is a colon token.
|
||||
*/
|
||||
function isColonToken(token) {
|
||||
return token.value === ":" && token.type === "Punctuator";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given token is an opening parenthesis token or not.
|
||||
*
|
||||
* @param {Token} token - The token to check.
|
||||
* @returns {boolean} `true` if the token is an opening parenthesis token.
|
||||
*/
|
||||
function isOpeningParenToken(token) {
|
||||
return token.value === "(" && token.type === "Punctuator";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given token is a closing parenthesis token or not.
|
||||
*
|
||||
* @param {Token} token - The token to check.
|
||||
* @returns {boolean} `true` if the token is a closing parenthesis token.
|
||||
*/
|
||||
function isClosingParenToken(token) {
|
||||
return token.value === ")" && token.type === "Punctuator";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given token is an opening square bracket token or not.
|
||||
*
|
||||
* @param {Token} token - The token to check.
|
||||
* @returns {boolean} `true` if the token is an opening square bracket token.
|
||||
*/
|
||||
function isOpeningBracketToken(token) {
|
||||
return token.value === "[" && token.type === "Punctuator";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given token is a closing square bracket token or not.
|
||||
*
|
||||
* @param {Token} token - The token to check.
|
||||
* @returns {boolean} `true` if the token is a closing square bracket token.
|
||||
*/
|
||||
function isClosingBracketToken(token) {
|
||||
return token.value === "]" && token.type === "Punctuator";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given token is an opening brace token or not.
|
||||
*
|
||||
* @param {Token} token - The token to check.
|
||||
* @returns {boolean} `true` if the token is an opening brace token.
|
||||
*/
|
||||
function isOpeningBraceToken(token) {
|
||||
return token.value === "{" && token.type === "Punctuator";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given token is a closing brace token or not.
|
||||
*
|
||||
* @param {Token} token - The token to check.
|
||||
* @returns {boolean} `true` if the token is a closing brace token.
|
||||
*/
|
||||
function isClosingBraceToken(token) {
|
||||
return token.value === "}" && token.type === "Punctuator";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given token is a comment token or not.
|
||||
*
|
||||
* @param {Token} token - The token to check.
|
||||
* @returns {boolean} `true` if the token is a comment token.
|
||||
*/
|
||||
function isCommentToken(token) {
|
||||
return token.type === "Line" || token.type === "Block" || token.type === "Shebang";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given token is a keyword token or not.
|
||||
*
|
||||
* @param {Token} token - The token to check.
|
||||
* @returns {boolean} `true` if the token is a keyword token.
|
||||
*/
|
||||
function isKeywordToken(token) {
|
||||
return token.type === "Keyword";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,32 +390,18 @@ function getArrowToken(node, sourceCode) {
|
||||
* @returns {Token} `(` token.
|
||||
*/
|
||||
function getOpeningParenOfParams(node, sourceCode) {
|
||||
let token = node.id ? sourceCode.getTokenAfter(node.id) : sourceCode.getFirstToken(node);
|
||||
|
||||
while (token.value !== "(") {
|
||||
token = sourceCode.getTokenAfter(token);
|
||||
}
|
||||
|
||||
return token;
|
||||
return node.id
|
||||
? sourceCode.getTokenAfter(node.id, isOpeningParenToken)
|
||||
: sourceCode.getFirstToken(node, isOpeningParenToken);
|
||||
}
|
||||
|
||||
const lineIndexCache = new WeakMap();
|
||||
|
||||
/**
|
||||
* Gets the range index for the first character in each of the lines of `sourceCode`.
|
||||
* @param {SourceCode} sourceCode A sourceCode object
|
||||
* @returns {number[]} The indices of the first characters in the each of the lines of the code
|
||||
* Creates a version of the LINEBREAK_MATCHER regex with the global flag.
|
||||
* Global regexes are mutable, so this needs to be a function instead of a constant.
|
||||
* @returns {RegExp} A global regular expression that matches line terminators
|
||||
*/
|
||||
function getLineIndices(sourceCode) {
|
||||
|
||||
if (!lineIndexCache.has(sourceCode)) {
|
||||
const lineIndices = (sourceCode.text.match(/[^\r\n\u2028\u2029]*(\r\n|\r|\n|\u2028|\u2029)/g) || [])
|
||||
.reduce((indices, line) => indices.concat(indices[indices.length - 1] + line.length), [0]);
|
||||
|
||||
// Store the sourceCode object in a WeakMap to avoid iterating over all of the lines every time a sourceCode object is passed in.
|
||||
lineIndexCache.set(sourceCode, lineIndices);
|
||||
}
|
||||
return lineIndexCache.get(sourceCode);
|
||||
function createGlobalLinebreakMatcher() {
|
||||
return new RegExp(LINEBREAK_MATCHER.source, "g");
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -304,6 +409,10 @@ function getLineIndices(sourceCode) {
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
COMMENTS_IGNORE_PATTERN,
|
||||
LINEBREAKS,
|
||||
LINEBREAK_MATCHER,
|
||||
STATEMENT_LIST_PARENTS,
|
||||
|
||||
/**
|
||||
* Determines whether two adjacent tokens are on the same line.
|
||||
@@ -325,6 +434,29 @@ module.exports = {
|
||||
isInLoop,
|
||||
isArrayFromMethod,
|
||||
isParenthesised,
|
||||
createGlobalLinebreakMatcher,
|
||||
|
||||
isArrowToken,
|
||||
isClosingBraceToken,
|
||||
isClosingBracketToken,
|
||||
isClosingParenToken,
|
||||
isColonToken,
|
||||
isCommaToken,
|
||||
isCommentToken,
|
||||
isKeywordToken,
|
||||
isNotClosingBraceToken: negate(isClosingBraceToken),
|
||||
isNotClosingBracketToken: negate(isClosingBracketToken),
|
||||
isNotClosingParenToken: negate(isClosingParenToken),
|
||||
isNotColonToken: negate(isColonToken),
|
||||
isNotCommaToken: negate(isCommaToken),
|
||||
isNotOpeningBraceToken: negate(isOpeningBraceToken),
|
||||
isNotOpeningBracketToken: negate(isOpeningBracketToken),
|
||||
isNotOpeningParenToken: negate(isOpeningParenToken),
|
||||
isNotSemicolonToken: negate(isSemicolonToken),
|
||||
isOpeningBraceToken,
|
||||
isOpeningBracketToken,
|
||||
isOpeningParenToken,
|
||||
isSemicolonToken,
|
||||
|
||||
/**
|
||||
* Checks whether or not a given node is a string literal.
|
||||
@@ -658,6 +790,8 @@ module.exports = {
|
||||
case "/":
|
||||
case "%":
|
||||
return 13;
|
||||
case "**":
|
||||
return 15;
|
||||
|
||||
// no default
|
||||
}
|
||||
@@ -666,10 +800,10 @@ module.exports = {
|
||||
|
||||
case "UnaryExpression":
|
||||
case "AwaitExpression":
|
||||
return 14;
|
||||
return 16;
|
||||
|
||||
case "UpdateExpression":
|
||||
return 15;
|
||||
return 17;
|
||||
|
||||
case "CallExpression":
|
||||
|
||||
@@ -677,14 +811,14 @@ module.exports = {
|
||||
if (node.callee.type === "FunctionExpression") {
|
||||
return -1;
|
||||
}
|
||||
return 16;
|
||||
return 18;
|
||||
|
||||
case "NewExpression":
|
||||
return 17;
|
||||
return 19;
|
||||
|
||||
// no default
|
||||
}
|
||||
return 18;
|
||||
return 20;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1023,7 +1157,7 @@ module.exports = {
|
||||
let end = null;
|
||||
|
||||
if (node.type === "ArrowFunctionExpression") {
|
||||
const arrowToken = getArrowToken(node, sourceCode);
|
||||
const arrowToken = sourceCode.getTokenBefore(node.body, isArrowToken);
|
||||
|
||||
start = arrowToken.loc.start;
|
||||
end = arrowToken.loc.end;
|
||||
@@ -1037,42 +1171,10 @@ module.exports = {
|
||||
|
||||
return {
|
||||
start: Object.assign({}, start),
|
||||
end: Object.assign({}, end),
|
||||
end: Object.assign({}, end)
|
||||
};
|
||||
},
|
||||
|
||||
/*
|
||||
* Converts a range index into a (line, column) pair.
|
||||
* @param {SourceCode} sourceCode A SourceCode object
|
||||
* @param {number} rangeIndex The range index of a character in a file
|
||||
* @returns {Object} A {line, column} location object with a 0-indexed column
|
||||
*/
|
||||
getLocationFromRangeIndex(sourceCode, rangeIndex) {
|
||||
const lineIndices = getLineIndices(sourceCode);
|
||||
|
||||
/*
|
||||
* lineIndices is a sorted list of indices of the first character of each line.
|
||||
* To figure out which line rangeIndex is on, determine the last index at which rangeIndex could
|
||||
* be inserted into lineIndices to keep the list sorted.
|
||||
*/
|
||||
const lineNumber = lodash.sortedLastIndex(lineIndices, rangeIndex);
|
||||
|
||||
return { line: lineNumber, column: rangeIndex - lineIndices[lineNumber - 1] };
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts a (line, column) pair into a range index.
|
||||
* @param {SourceCode} sourceCode A SourceCode object
|
||||
* @param {Object} loc A line/column location
|
||||
* @param {number} loc.line The line number of the location (1-indexed)
|
||||
* @param {number} loc.column The column number of the location (0-indexed)
|
||||
* @returns {number} The range index of the location in the file.
|
||||
*/
|
||||
getRangeIndexFromLocation(sourceCode, loc) {
|
||||
return getLineIndices(sourceCode)[loc.line - 1] + loc.column;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the parenthesized text of a node. This is similar to sourceCode.getText(node), but it also includes any parentheses
|
||||
* surrounding the node.
|
||||
@@ -1097,5 +1199,58 @@ module.exports = {
|
||||
}
|
||||
|
||||
return sourceCode.getText().slice(leftToken.range[0], rightToken.range[1]);
|
||||
},
|
||||
|
||||
/*
|
||||
* Determine if a node has a possiblity to be an Error object
|
||||
* @param {ASTNode} node ASTNode to check
|
||||
* @returns {boolean} True if there is a chance it contains an Error obj
|
||||
*/
|
||||
couldBeError(node) {
|
||||
switch (node.type) {
|
||||
case "Identifier":
|
||||
case "CallExpression":
|
||||
case "NewExpression":
|
||||
case "MemberExpression":
|
||||
case "TaggedTemplateExpression":
|
||||
case "YieldExpression":
|
||||
case "AwaitExpression":
|
||||
return true; // possibly an error object.
|
||||
|
||||
case "AssignmentExpression":
|
||||
return module.exports.couldBeError(node.right);
|
||||
|
||||
case "SequenceExpression": {
|
||||
const exprs = node.expressions;
|
||||
|
||||
return exprs.length !== 0 && module.exports.couldBeError(exprs[exprs.length - 1]);
|
||||
}
|
||||
|
||||
case "LogicalExpression":
|
||||
return module.exports.couldBeError(node.left) || module.exports.couldBeError(node.right);
|
||||
|
||||
case "ConditionalExpression":
|
||||
return module.exports.couldBeError(node.consequent) || module.exports.couldBeError(node.alternate);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines whether the given node is a `null` literal.
|
||||
* @param {ASTNode} node The node to check
|
||||
* @returns {boolean} `true` if the node is a `null` literal
|
||||
*/
|
||||
isNullLiteral(node) {
|
||||
|
||||
/*
|
||||
* Checking `node.value === null` does not guarantee that a literal is a null literal.
|
||||
* When parsing values that cannot be represented in the current environment (e.g. unicode
|
||||
* regexes in Node 4), `node.value` is set to `null` because it wouldn't be possible to
|
||||
* set `node.value` to a unicode regex. To make sure a literal is actually `null`, check
|
||||
* `node.regex` instead. Also see: https://github.com/eslint/eslint/issues/8020
|
||||
*/
|
||||
return node.type === "Literal" && node.value === null && !node.regex;
|
||||
}
|
||||
};
|
||||
@@ -188,9 +188,9 @@ const cli = {
|
||||
}
|
||||
|
||||
return (report.errorCount || tooManyWarnings) ? 1 : 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -512,13 +512,8 @@ function processCodePathToExit(analyzer, node) {
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Skip updating the current segment to avoid creating useless segments if
|
||||
* the node type is the same as the parent node type.
|
||||
*/
|
||||
if (!dontForward && (!node.parent || node.type !== node.parent.type)) {
|
||||
|
||||
// Emits onCodePathSegmentStart events if updated.
|
||||
// Emits onCodePathSegmentStart events if updated.
|
||||
if (!dontForward) {
|
||||
forwardCurrentToHead(analyzer, node);
|
||||
}
|
||||
debug.dumpState(node, state, true);
|
||||
|
||||
@@ -467,8 +467,8 @@ class CodePathState {
|
||||
* Creates the next path from own true/false fork context.
|
||||
*/
|
||||
const prevForkContext =
|
||||
context.kind === "&&" ? context.trueForkContext :
|
||||
/* kind === "||" */ context.falseForkContext;
|
||||
context.kind === "&&" ? context.trueForkContext
|
||||
/* kind === "||" */ : context.falseForkContext;
|
||||
|
||||
forkContext.replaceHead(prevForkContext.makeNext(0, -1));
|
||||
prevForkContext.clear();
|
||||
|
||||
@@ -107,22 +107,23 @@ module.exports = {
|
||||
text += "style=\"rounded,dashed,filled\",fillcolor=\"#FF9800\",label=\"<<unreachable>>\\n";
|
||||
}
|
||||
|
||||
if (segment.internal.nodes.length > 0) {
|
||||
text += segment.internal.nodes.map(node => {
|
||||
switch (node.type) {
|
||||
case "Identifier": return `${node.type} (${node.name})`;
|
||||
case "Literal": return `${node.type} (${node.value})`;
|
||||
default: return node.type;
|
||||
}
|
||||
}).join("\\n");
|
||||
} else if (segment.internal.exitNodes.length > 0) {
|
||||
text += segment.internal.exitNodes.map(node => {
|
||||
switch (node.type) {
|
||||
case "Identifier": return `${node.type}:exit (${node.name})`;
|
||||
case "Literal": return `${node.type}:exit (${node.value})`;
|
||||
default: return `${node.type}:exit`;
|
||||
}
|
||||
}).join("\\n");
|
||||
if (segment.internal.nodes.length > 0 || segment.internal.exitNodes.length > 0) {
|
||||
text += [].concat(
|
||||
segment.internal.nodes.map(node => {
|
||||
switch (node.type) {
|
||||
case "Identifier": return `${node.type} (${node.name})`;
|
||||
case "Literal": return `${node.type} (${node.value})`;
|
||||
default: return node.type;
|
||||
}
|
||||
}),
|
||||
segment.internal.exitNodes.map(node => {
|
||||
switch (node.type) {
|
||||
case "Identifier": return `${node.type}:exit (${node.name})`;
|
||||
case "Literal": return `${node.type}:exit (${node.value})`;
|
||||
default: return `${node.type}:exit`;
|
||||
}
|
||||
})
|
||||
).join("\\n");
|
||||
} else {
|
||||
text += "????";
|
||||
}
|
||||
|
||||
@@ -234,8 +234,9 @@ class Config {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a config object merging the base config (conf/eslint.json), the
|
||||
* environments config (conf/environments.js) and eventually the user config.
|
||||
* Build a config object merging the base config (conf/eslint-recommended),
|
||||
* the environments config (conf/environments.js) and eventually the user
|
||||
* config.
|
||||
* @param {string} filePath a file in whose directory we start looking for a local config
|
||||
* @returns {Object} config object
|
||||
*/
|
||||
|
||||
@@ -13,7 +13,7 @@ const lodash = require("lodash"),
|
||||
eslint = require("../eslint"),
|
||||
configRule = require("./config-rule"),
|
||||
ConfigOps = require("./config-ops"),
|
||||
recConfig = require("../../conf/eslint.json");
|
||||
recConfig = require("../../conf/eslint-recommended");
|
||||
|
||||
const debug = require("debug")("eslint:autoconfig");
|
||||
|
||||
@@ -65,17 +65,16 @@ function makeRegistryItems(rulesConfig) {
|
||||
* Unless a rulesConfig is provided at construction, the registry will not contain
|
||||
* any rules, only methods. This will be useful for building up registries manually.
|
||||
*
|
||||
* @constructor
|
||||
* @class Registry
|
||||
* @param {rulesConfig} [rulesConfig] Hash of rule names and arrays of possible configurations
|
||||
* Registry class
|
||||
*/
|
||||
function Registry(rulesConfig) {
|
||||
this.rules = (rulesConfig) ? makeRegistryItems(rulesConfig) : {};
|
||||
}
|
||||
class Registry {
|
||||
|
||||
Registry.prototype = {
|
||||
|
||||
constructor: Registry,
|
||||
/**
|
||||
* @param {rulesConfig} [rulesConfig] Hash of rule names and arrays of possible configurations
|
||||
*/
|
||||
constructor(rulesConfig) {
|
||||
this.rules = (rulesConfig) ? makeRegistryItems(rulesConfig) : {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the registry with core rule configs.
|
||||
@@ -89,7 +88,7 @@ Registry.prototype = {
|
||||
const rulesConfig = configRule.createCoreRuleConfigs();
|
||||
|
||||
this.rules = makeRegistryItems(rulesConfig);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates sets of rule configurations which can be used for linting
|
||||
@@ -156,7 +155,7 @@ Registry.prototype = {
|
||||
}
|
||||
|
||||
return ruleSets;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all items from the registry with a non-zero number of errors
|
||||
@@ -182,7 +181,7 @@ Registry.prototype = {
|
||||
});
|
||||
|
||||
return newRegistry;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes rule configurations which were not included in a ruleSet
|
||||
@@ -199,7 +198,7 @@ Registry.prototype = {
|
||||
});
|
||||
|
||||
return newRegistry;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a registry of rules which had no error-free configs.
|
||||
@@ -221,7 +220,7 @@ Registry.prototype = {
|
||||
});
|
||||
|
||||
return failingRegistry;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an eslint config for any rules which only have one configuration
|
||||
@@ -240,7 +239,7 @@ Registry.prototype = {
|
||||
});
|
||||
|
||||
return config;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a cloned registry containing only configs with a desired specificity
|
||||
@@ -258,7 +257,7 @@ Registry.prototype = {
|
||||
});
|
||||
|
||||
return newRegistry;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Lint SourceCodes against all configurations in the registry, and record results
|
||||
@@ -297,8 +296,13 @@ Registry.prototype = {
|
||||
|
||||
// It is possible that the error is from a configuration comment
|
||||
// in a linted file, in which case there may not be a config
|
||||
// set in this ruleSetIdx. (https://github.com/eslint/eslint/issues/5992)
|
||||
if (lintedRegistry.rules[result.ruleId][ruleSetIdx]) {
|
||||
// set in this ruleSetIdx.
|
||||
// (https://github.com/eslint/eslint/issues/5992)
|
||||
// (https://github.com/eslint/eslint/issues/7860)
|
||||
if (
|
||||
lintedRegistry.rules[result.ruleId] &&
|
||||
lintedRegistry.rules[result.ruleId][ruleSetIdx]
|
||||
) {
|
||||
lintedRegistry.rules[result.ruleId][ruleSetIdx].errorCount += 1;
|
||||
}
|
||||
});
|
||||
@@ -316,7 +320,7 @@ Registry.prototype = {
|
||||
|
||||
return lintedRegistry;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract rule configuration into eslint:recommended where possible.
|
||||
|
||||
@@ -23,7 +23,7 @@ const fs = require("fs"),
|
||||
stripBom = require("strip-bom"),
|
||||
stripComments = require("strip-json-comments"),
|
||||
stringify = require("json-stable-stringify"),
|
||||
defaultOptions = require("../../conf/eslint.json"),
|
||||
defaultOptions = require("../../conf/eslint-recommended"),
|
||||
requireUncached = require("require-uncached");
|
||||
|
||||
const debug = require("debug")("eslint:config-file");
|
||||
@@ -183,6 +183,22 @@ function loadPackageJSONConfigFile(filePath) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an error to notify about a missing config to extend from.
|
||||
* @param {string} configName The name of the missing config.
|
||||
* @returns {Error} The error object to throw
|
||||
* @private
|
||||
*/
|
||||
function configMissingError(configName) {
|
||||
const error = new Error(`Failed to load config "${configName}" to extend from.`);
|
||||
|
||||
error.messageTemplate = "extend-config-missing";
|
||||
error.messageData = {
|
||||
configName
|
||||
};
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a configuration file regardless of the source. Inspects the file path
|
||||
* to determine the correctly way to load the config file.
|
||||
@@ -199,6 +215,9 @@ function loadConfigFile(file) {
|
||||
config = loadJSConfigFile(filePath);
|
||||
if (file.configName) {
|
||||
config = config.configs[file.configName];
|
||||
if (!config) {
|
||||
throw configMissingError(file.configFullName);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -340,6 +359,33 @@ function getLookupPath(configFilePath) {
|
||||
return path.join(basedir, "node_modules");
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a eslint core config path
|
||||
* @param {string} name The eslint config name.
|
||||
* @returns {string} The resolved path of the config.
|
||||
* @private
|
||||
*/
|
||||
function getEslintCoreConfigPath(name) {
|
||||
if (name === "eslint:recommended") {
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
return path.resolve(__dirname, "../../conf/eslint-all.js");
|
||||
}
|
||||
|
||||
throw configMissingError(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies values from the "extends" field in a configuration file.
|
||||
* @param {Object} config The configuration information.
|
||||
@@ -360,33 +406,20 @@ function applyExtends(config, filePath, relativeTo) {
|
||||
|
||||
// Make the last element in an array take the highest precedence
|
||||
config = configExtends.reduceRight((previousValue, parentPath) => {
|
||||
|
||||
if (parentPath === "eslint:recommended") {
|
||||
|
||||
/*
|
||||
* Add an explicit substitution for eslint:recommended to conf/eslint.json
|
||||
* this lets us use the eslint.json file as the recommended rules
|
||||
*/
|
||||
parentPath = path.resolve(__dirname, "../../conf/eslint.json");
|
||||
} else if (parentPath === "eslint:all") {
|
||||
|
||||
/*
|
||||
* Add an explicit substitution for eslint:all to conf/eslint-all.js
|
||||
*/
|
||||
parentPath = path.resolve(__dirname, "../../conf/eslint-all.js");
|
||||
} else if (isFilePath(parentPath)) {
|
||||
|
||||
/*
|
||||
* If the `extends` path is relative, use the directory of the current configuration
|
||||
* file as the reference point. Otherwise, use as-is.
|
||||
*/
|
||||
parentPath = (!path.isAbsolute(parentPath) ?
|
||||
path.join(relativeTo || path.dirname(filePath), parentPath) :
|
||||
parentPath
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
if (parentPath.startsWith("eslint:")) {
|
||||
parentPath = getEslintCoreConfigPath(parentPath);
|
||||
} else if (isFilePath(parentPath)) {
|
||||
|
||||
/*
|
||||
* If the `extends` path is relative, use the directory of the current configuration
|
||||
* file as the reference point. Otherwise, use as-is.
|
||||
*/
|
||||
parentPath = (path.isAbsolute(parentPath)
|
||||
? parentPath
|
||||
: path.join(relativeTo || path.dirname(filePath), parentPath)
|
||||
);
|
||||
}
|
||||
debug(`Loading ${parentPath}`);
|
||||
return ConfigOps.merge(load(parentPath, false, relativeTo), previousValue);
|
||||
} catch (e) {
|
||||
@@ -455,30 +488,34 @@ function normalizePackageName(name, prefix) {
|
||||
* or package name.
|
||||
* @param {string} filePath The filepath to resolve.
|
||||
* @param {string} [relativeTo] The path to resolve relative to.
|
||||
* @returns {Object} A path that can be used directly to load the configuration.
|
||||
* @returns {Object} An object containing 3 properties:
|
||||
* - 'filePath' (required) the resolved path that can be used directly to load the configuration.
|
||||
* - 'configName' the name of the configuration inside the plugin.
|
||||
* - 'configFullName' the name of the configuration as used in the eslint config (e.g. 'plugin:node/recommended').
|
||||
* @private
|
||||
*/
|
||||
function resolve(filePath, relativeTo) {
|
||||
if (isFilePath(filePath)) {
|
||||
return { filePath: path.resolve(relativeTo || "", filePath) };
|
||||
} else {
|
||||
let normalizedPackageName;
|
||||
|
||||
if (filePath.indexOf("plugin:") === 0) {
|
||||
const packagePath = filePath.substr(7, filePath.lastIndexOf("/") - 7);
|
||||
const configName = filePath.substr(filePath.lastIndexOf("/") + 1, filePath.length - filePath.lastIndexOf("/") - 1);
|
||||
|
||||
normalizedPackageName = normalizePackageName(packagePath, "eslint-plugin");
|
||||
debug(`Attempting to resolve ${normalizedPackageName}`);
|
||||
filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo));
|
||||
return { filePath, configName };
|
||||
} else {
|
||||
normalizedPackageName = normalizePackageName(filePath, "eslint-config");
|
||||
debug(`Attempting to resolve ${normalizedPackageName}`);
|
||||
filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo));
|
||||
return { filePath };
|
||||
}
|
||||
}
|
||||
let normalizedPackageName;
|
||||
|
||||
if (filePath.startsWith("plugin:")) {
|
||||
const configFullName = filePath;
|
||||
const pluginName = filePath.substr(7, filePath.lastIndexOf("/") - 7);
|
||||
const configName = filePath.substr(filePath.lastIndexOf("/") + 1, filePath.length - filePath.lastIndexOf("/") - 1);
|
||||
|
||||
normalizedPackageName = normalizePackageName(pluginName, "eslint-plugin");
|
||||
debug(`Attempting to resolve ${normalizedPackageName}`);
|
||||
filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo));
|
||||
return { filePath, configName, configFullName };
|
||||
}
|
||||
normalizedPackageName = normalizePackageName(filePath, "eslint-config");
|
||||
debug(`Attempting to resolve ${normalizedPackageName}`);
|
||||
filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo));
|
||||
return { filePath };
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ const util = require("util"),
|
||||
ConfigOps = require("./config-ops"),
|
||||
getSourceCodeOfFiles = require("../util/source-code-util").getSourceCodeOfFiles,
|
||||
npmUtil = require("../util/npm-util"),
|
||||
recConfig = require("../../conf/eslint.json"),
|
||||
recConfig = require("../../conf/eslint-recommended"),
|
||||
log = require("../logging");
|
||||
|
||||
const debug = require("debug")("eslint:config-initializer");
|
||||
@@ -317,7 +317,7 @@ function promptUser(callback) {
|
||||
default: false,
|
||||
when(answers) {
|
||||
return answers.styleguide === "airbnb";
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
|
||||
@@ -176,22 +176,21 @@ function combinePropertyObjects(objArr1, objArr2) {
|
||||
*
|
||||
* ruleConfigSet.ruleConfigs // -> [[2], [2, "always"], [2, "never"]]
|
||||
*
|
||||
* @param {ruleConfig[]} configs Valid rule configurations
|
||||
* @constructor
|
||||
* Rule configuration set class
|
||||
*/
|
||||
function RuleConfigSet(configs) {
|
||||
class RuleConfigSet {
|
||||
|
||||
/**
|
||||
* Stored valid rule configurations for this instance
|
||||
* @type {array}
|
||||
*/
|
||||
this.ruleConfigs = configs || [];
|
||||
* @param {ruleConfig[]} configs Valid rule configurations
|
||||
*/
|
||||
constructor(configs) {
|
||||
|
||||
}
|
||||
|
||||
RuleConfigSet.prototype = {
|
||||
|
||||
constructor: RuleConfigSet,
|
||||
/**
|
||||
* Stored valid rule configurations for this instance
|
||||
* @type {array}
|
||||
*/
|
||||
this.ruleConfigs = configs || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a severity level to the front of all configs in the instance.
|
||||
@@ -210,7 +209,7 @@ RuleConfigSet.prototype = {
|
||||
|
||||
// Add a single config at the beginning consisting of only the severity
|
||||
this.ruleConfigs.unshift(severity);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Add rule configs from an array of strings (schema enums)
|
||||
@@ -219,12 +218,12 @@ RuleConfigSet.prototype = {
|
||||
*/
|
||||
addEnums(enums) {
|
||||
this.ruleConfigs = this.ruleConfigs.concat(combineArrays(this.ruleConfigs, enums));
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Add rule configurations from a schema object
|
||||
* @param {Object} obj Schema item with type === "object"
|
||||
* @returns {void}
|
||||
* @returns {boolean} true if at least one schema for the object could be generated, false otherwise
|
||||
*/
|
||||
addObject(obj) {
|
||||
const objectConfigSet = {
|
||||
@@ -259,9 +258,12 @@ RuleConfigSet.prototype = {
|
||||
|
||||
if (objectConfigSet.objectConfigs.length > 0) {
|
||||
this.ruleConfigs = this.ruleConfigs.concat(combineArrays(this.ruleConfigs, objectConfigSet.objectConfigs));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate valid rule configurations based on a schema object
|
||||
@@ -272,20 +274,21 @@ function generateConfigsFromSchema(schema) {
|
||||
const configSet = new RuleConfigSet();
|
||||
|
||||
if (Array.isArray(schema)) {
|
||||
schema.forEach(opt => {
|
||||
for (const opt of schema) {
|
||||
if (opt.enum) {
|
||||
configSet.addEnums(opt.enum);
|
||||
}
|
||||
} else if (opt.type && opt.type === "object") {
|
||||
if (!configSet.addObject(opt)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (opt.type && opt.type === "object") {
|
||||
configSet.addObject(opt);
|
||||
}
|
||||
// TODO (IanVS): support oneOf
|
||||
} else {
|
||||
|
||||
if (opt.oneOf) {
|
||||
|
||||
// TODO (IanVS): not yet implemented
|
||||
// If we don't know how to fill in this option, don't fill in any of the following options.
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
configSet.addErrorSeverity();
|
||||
return configSet.ruleConfigs;
|
||||
|
||||
@@ -40,13 +40,13 @@ function getRuleOptionsSchema(id) {
|
||||
minItems: 0,
|
||||
maxItems: schema.length
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type: "array",
|
||||
minItems: 0,
|
||||
maxItems: 0
|
||||
};
|
||||
}
|
||||
return {
|
||||
type: "array",
|
||||
minItems: 0,
|
||||
maxItems: 0
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// Given a full schema, leave it alone
|
||||
|
||||
@@ -127,14 +127,25 @@ module.exports = {
|
||||
if (!plugins[shortName]) {
|
||||
try {
|
||||
plugin = require(longName);
|
||||
} catch (err) {
|
||||
debug(`Failed to load plugin ${longName}.`);
|
||||
err.message = `Failed to load plugin ${pluginName}: ${err.message}`;
|
||||
err.messageTemplate = "plugin-missing";
|
||||
err.messageData = {
|
||||
pluginName: longName
|
||||
};
|
||||
throw err;
|
||||
} catch (pluginLoadErr) {
|
||||
try {
|
||||
|
||||
// Check whether the plugin exists
|
||||
require.resolve(longName);
|
||||
} catch (missingPluginErr) {
|
||||
|
||||
// If the plugin can't be resolved, display the missing plugin error (usually a config or install error)
|
||||
debug(`Failed to load plugin ${longName}.`);
|
||||
missingPluginErr.message = `Failed to load plugin ${pluginName}: ${missingPluginErr.message}`;
|
||||
missingPluginErr.messageTemplate = "plugin-missing";
|
||||
missingPluginErr.messageData = {
|
||||
pluginName: longName
|
||||
};
|
||||
throw missingPluginErr;
|
||||
}
|
||||
|
||||
// Otherwise, the plugin exists and is throwing on module load for some reason, so print the stack trace.
|
||||
throw pluginLoadErr;
|
||||
}
|
||||
|
||||
this.define(pluginName, plugin);
|
||||
|
||||
+29
-28
@@ -14,7 +14,7 @@ const assert = require("assert"),
|
||||
escope = require("escope"),
|
||||
levn = require("levn"),
|
||||
blankScriptAST = require("../conf/blank-script.json"),
|
||||
DEFAULT_PARSER = require("../conf/eslint.json").parser,
|
||||
DEFAULT_PARSER = require("../conf/eslint-recommended").parser,
|
||||
replacements = require("../conf/replacements.json"),
|
||||
CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
|
||||
ConfigOps = require("./config/config-ops"),
|
||||
@@ -528,9 +528,9 @@ function createStubRule(message) {
|
||||
|
||||
if (message) {
|
||||
return createRuleModule;
|
||||
} else {
|
||||
throw new Error("No message passed to stub rule");
|
||||
}
|
||||
throw new Error("No message passed to stub rule");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -660,9 +660,9 @@ module.exports = (function() {
|
||||
try {
|
||||
if (typeof parser.parseForESLint === "function") {
|
||||
return parser.parseForESLint(text, parserOptions);
|
||||
} else {
|
||||
return parser.parse(text, parserOptions);
|
||||
}
|
||||
return parser.parse(text, parserOptions);
|
||||
|
||||
} catch (ex) {
|
||||
|
||||
// If the message includes a leading line number, strip it:
|
||||
@@ -695,9 +695,9 @@ module.exports = (function() {
|
||||
return ruleConfig;
|
||||
} else if (Array.isArray(ruleConfig)) {
|
||||
return ruleConfig[0];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -708,9 +708,9 @@ module.exports = (function() {
|
||||
function getRuleOptions(ruleConfig) {
|
||||
if (Array.isArray(ruleConfig)) {
|
||||
return ruleConfig.slice(1);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
return [];
|
||||
|
||||
}
|
||||
|
||||
// set unlimited listeners (see https://github.com/eslint/eslint/issues/524)
|
||||
@@ -778,17 +778,18 @@ module.exports = (function() {
|
||||
// search and apply "eslint-env *".
|
||||
const envInFile = findEslintEnv(text || textOrSourceCode.text);
|
||||
|
||||
config = Object.assign({}, config);
|
||||
|
||||
if (envInFile) {
|
||||
if (!config || !config.env) {
|
||||
config = Object.assign({}, config || {}, { env: envInFile });
|
||||
} else {
|
||||
config = Object.assign({}, config);
|
||||
if (config.env) {
|
||||
config.env = Object.assign({}, config.env, envInFile);
|
||||
} else {
|
||||
config.env = envInFile;
|
||||
}
|
||||
}
|
||||
|
||||
// process initial config to make it safe to extend
|
||||
config = prepareConfig(config || {});
|
||||
config = prepareConfig(config);
|
||||
|
||||
// only do this for text
|
||||
if (text !== null) {
|
||||
@@ -864,14 +865,14 @@ module.exports = (function() {
|
||||
(parseResult && parseResult.services ? parseResult.services : {})
|
||||
);
|
||||
|
||||
const rule = ruleCreator.create ? ruleCreator.create(ruleContext) :
|
||||
ruleCreator(ruleContext);
|
||||
const rule = ruleCreator.create ? ruleCreator.create(ruleContext)
|
||||
: ruleCreator(ruleContext);
|
||||
|
||||
// add all the node types as listeners
|
||||
Object.keys(rule).forEach(nodeType => {
|
||||
api.on(nodeType, timing.enabled
|
||||
? timing.time(key, rule[nodeType])
|
||||
: rule[nodeType]
|
||||
// add all the selectors from the rule as listeners
|
||||
Object.keys(rule).forEach(selector => {
|
||||
api.on(selector, timing.enabled
|
||||
? timing.time(key, rule[selector])
|
||||
: rule[selector]
|
||||
);
|
||||
});
|
||||
} catch (ex) {
|
||||
@@ -939,9 +940,9 @@ module.exports = (function() {
|
||||
|
||||
if (lineDiff === 0) {
|
||||
return a.column - b.column;
|
||||
} else {
|
||||
return lineDiff;
|
||||
}
|
||||
return lineDiff;
|
||||
|
||||
});
|
||||
|
||||
return messages;
|
||||
@@ -1109,9 +1110,9 @@ module.exports = (function() {
|
||||
if (scope) {
|
||||
if (scope.type === "function-expression-name") {
|
||||
return scope.childScopes[0];
|
||||
} else {
|
||||
return scope;
|
||||
}
|
||||
return scope;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1161,9 +1162,9 @@ module.exports = (function() {
|
||||
api.getFilename = function() {
|
||||
if (typeof currentFilename === "string") {
|
||||
return currentFilename;
|
||||
} else {
|
||||
return "<input>";
|
||||
}
|
||||
return "<input>";
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1192,7 +1193,7 @@ module.exports = (function() {
|
||||
* @returns {Object} Object mapping rule IDs to their default configurations
|
||||
*/
|
||||
api.defaults = function() {
|
||||
return require("../conf/eslint.json");
|
||||
return require("../conf/eslint-recommended");
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,9 +19,9 @@ const xmlEscape = require("../util/xml-escape");
|
||||
function getMessageType(message) {
|
||||
if (message.fatal || message.severity === 2) {
|
||||
return "error";
|
||||
} else {
|
||||
return "warning";
|
||||
}
|
||||
return "warning";
|
||||
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -56,7 +56,7 @@ function formatMessage(message, parentResult) {
|
||||
`${type}:`,
|
||||
`${msg}`,
|
||||
ruleId ? `${ruleId}` : "",
|
||||
sourceCode ? `at ${filePath}:` : `at ${filePath}`,
|
||||
sourceCode ? `at ${filePath}:` : `at ${filePath}`
|
||||
].filter(String).join(" ");
|
||||
|
||||
const result = [firstLine];
|
||||
@@ -101,15 +101,10 @@ module.exports = function(results) {
|
||||
const resultsWithMessages = results.filter(result => result.messages.length > 0);
|
||||
|
||||
let output = resultsWithMessages.reduce((resultsOutput, result) => {
|
||||
const messages = result.messages.map(message => {
|
||||
if (message.fatal || message.severity === 2) {
|
||||
errors++;
|
||||
} else {
|
||||
warnings++;
|
||||
}
|
||||
const messages = result.messages.map(message => `${formatMessage(message, result)}\n\n`);
|
||||
|
||||
return `${formatMessage(message, result)}\n\n`;
|
||||
});
|
||||
errors += result.errorCount;
|
||||
warnings += result.warningCount;
|
||||
|
||||
return resultsOutput.concat(messages);
|
||||
}, []).join("\n");
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
function getMessageType(message) {
|
||||
if (message.fatal || message.severity === 2) {
|
||||
return "Error";
|
||||
} else {
|
||||
return "Warning";
|
||||
}
|
||||
return "Warning";
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@ const xmlEscape = require("../util/xml-escape");
|
||||
function getMessageType(message) {
|
||||
if (message.fatal || message.severity === 2) {
|
||||
return "Error";
|
||||
} else {
|
||||
return "Warning";
|
||||
}
|
||||
return "Warning";
|
||||
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -28,7 +28,6 @@ function pluralize(word, count) {
|
||||
module.exports = function(results) {
|
||||
|
||||
let output = "\n",
|
||||
total = 0,
|
||||
errors = 0,
|
||||
warnings = 0,
|
||||
summaryColor = "yellow";
|
||||
@@ -40,7 +39,9 @@ module.exports = function(results) {
|
||||
return;
|
||||
}
|
||||
|
||||
total += messages.length;
|
||||
errors += result.errorCount;
|
||||
warnings += result.warningCount;
|
||||
|
||||
output += `${chalk.underline(result.filePath)}\n`;
|
||||
|
||||
output += `${table(
|
||||
@@ -50,10 +51,8 @@ module.exports = function(results) {
|
||||
if (message.fatal || message.severity === 2) {
|
||||
messageType = chalk.red("error");
|
||||
summaryColor = "red";
|
||||
errors++;
|
||||
} else {
|
||||
messageType = chalk.yellow("warning");
|
||||
warnings++;
|
||||
}
|
||||
|
||||
return [
|
||||
@@ -74,6 +73,8 @@ 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;
|
||||
|
||||
if (total > 0) {
|
||||
output += chalk[summaryColor].bold([
|
||||
"\u2716 ", total, pluralize(" problem", total),
|
||||
|
||||
@@ -18,9 +18,9 @@ const yaml = require("js-yaml");
|
||||
function getMessageType(message) {
|
||||
if (message.fatal || message.severity === 2) {
|
||||
return "error";
|
||||
} else {
|
||||
return "warning";
|
||||
}
|
||||
return "warning";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
function getMessageType(message) {
|
||||
if (message.fatal || message.severity === 2) {
|
||||
return "Error";
|
||||
} else {
|
||||
return "Warning";
|
||||
}
|
||||
return "Warning";
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
function getMessageType(message) {
|
||||
if (message.fatal || message.severity === 2) {
|
||||
return "error";
|
||||
} else {
|
||||
return "warning";
|
||||
}
|
||||
return "warning";
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -201,6 +201,12 @@ class IgnoredPaths {
|
||||
|
||||
const ig = ignore().add(DEFAULT_IGNORE_DIRS);
|
||||
|
||||
if (this.options.dotfiles !== true) {
|
||||
|
||||
// Ignore hidden folders. (This cannot be ".*", or else it's not possible to unignore hidden files)
|
||||
ig.add([".*/*", "!../"]);
|
||||
}
|
||||
|
||||
if (this.options.ignore) {
|
||||
ig.add(this.ig.custom);
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ function checkMetaDocsDescription(context, exportsNode) {
|
||||
if (description === "") {
|
||||
context.report({
|
||||
node: metaDocsDescription.value,
|
||||
message: "`meta.docs.description` should not be empty.",
|
||||
message: "`meta.docs.description` should not be empty."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -94,16 +94,6 @@ function hasMetaSchema(metaPropertyNode) {
|
||||
return getPropertyFromObject("schema", metaPropertyNode.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this `meta` ObjectExpression has a `fixable` property defined or not.
|
||||
*
|
||||
* @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
|
||||
* @returns {boolean} `true` if a `fixable` property exists.
|
||||
*/
|
||||
function hasMetaFixable(metaPropertyNode) {
|
||||
return getPropertyFromObject("fixable", metaPropertyNode.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the validity of the meta definition of this rule and reports any errors found.
|
||||
*
|
||||
@@ -112,7 +102,7 @@ function hasMetaFixable(metaPropertyNode) {
|
||||
* @param {boolean} ruleIsFixable whether the rule is fixable or not.
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkMetaValidity(context, exportsNode, ruleIsFixable) {
|
||||
function checkMetaValidity(context, exportsNode) {
|
||||
const metaProperty = getMetaPropertyFromExportsNode(exportsNode);
|
||||
|
||||
if (!metaProperty) {
|
||||
@@ -142,11 +132,6 @@ function checkMetaValidity(context, exportsNode, ruleIsFixable) {
|
||||
|
||||
if (!hasMetaSchema(metaProperty)) {
|
||||
context.report(metaProperty, "Rule is missing a meta.schema property.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ruleIsFixable && !hasMetaFixable(metaProperty)) {
|
||||
context.report(metaProperty, "Rule is fixable, but is missing a meta.fixable property.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +162,6 @@ module.exports = {
|
||||
|
||||
create(context) {
|
||||
let exportsNode;
|
||||
let ruleIsFixable = false;
|
||||
|
||||
return {
|
||||
AssignmentExpression(node) {
|
||||
@@ -191,35 +175,13 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
CallExpression(node) {
|
||||
|
||||
// If the rule has a call for `context.report` and a property `fix`
|
||||
// is being passed in, then we consider that the rule is fixable.
|
||||
//
|
||||
// Note that we only look for context.report() calls in the new
|
||||
// style (with single MessageDescriptor argument), because only
|
||||
// calls in the new style can specify a fix.
|
||||
if (node.callee.type === "MemberExpression" &&
|
||||
node.callee.object.type === "Identifier" &&
|
||||
node.callee.object.name === "context" &&
|
||||
node.callee.property.type === "Identifier" &&
|
||||
node.callee.property.name === "report" &&
|
||||
node.arguments.length === 1 &&
|
||||
node.arguments[0].type === "ObjectExpression") {
|
||||
|
||||
if (getPropertyFromObject("fix", node.arguments[0])) {
|
||||
ruleIsFixable = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"Program:exit"() {
|
||||
if (!isCorrectExportsFormat(exportsNode)) {
|
||||
context.report({ node: exportsNode, message: "Rule does not export an Object. Make sure the rule follows the new rule format." });
|
||||
return;
|
||||
}
|
||||
|
||||
checkMetaValidity(context, exportsNode, ruleIsFixable);
|
||||
checkMetaValidity(context, exportsNode);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const RuleFixer = require("./util/rule-fixer");
|
||||
const ruleFixer = require("./util/rule-fixer");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Constants
|
||||
@@ -124,7 +124,7 @@ class RuleContext {
|
||||
|
||||
// if there's a fix specified, get it
|
||||
if (typeof descriptor.fix === "function") {
|
||||
fix = descriptor.fix(new RuleFixer());
|
||||
fix = descriptor.fix(ruleFixer);
|
||||
}
|
||||
|
||||
this.eslint.report(
|
||||
|
||||
@@ -70,9 +70,9 @@ function importPlugin(plugin, pluginName) {
|
||||
function getHandler(ruleId) {
|
||||
if (typeof rules[ruleId] === "string") {
|
||||
return require(rules[ruleId]);
|
||||
} else {
|
||||
return rules[ruleId];
|
||||
}
|
||||
return rules[ruleId];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const lodash = require("lodash");
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -147,7 +149,8 @@ module.exports = {
|
||||
upper: null,
|
||||
codePath: null,
|
||||
hasReturn: false,
|
||||
shouldCheck: false
|
||||
shouldCheck: false,
|
||||
node: null
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -168,8 +171,11 @@ module.exports = {
|
||||
node,
|
||||
loc: getLocation(node, context.getSourceCode()).loc.start,
|
||||
message: funcInfo.hasReturn
|
||||
? "Expected to return a value at the end of this function."
|
||||
: "Expected to return a value in this function."
|
||||
? "Expected to return a value at the end of {{name}}."
|
||||
: "Expected to return a value in {{name}}.",
|
||||
data: {
|
||||
name: astUtils.getFunctionNameWithKind(funcInfo.node)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -187,7 +193,8 @@ module.exports = {
|
||||
node.body.type === "BlockStatement" &&
|
||||
isCallbackOfArrayMethod(node) &&
|
||||
!node.async &&
|
||||
!node.generator
|
||||
!node.generator,
|
||||
node
|
||||
};
|
||||
},
|
||||
|
||||
@@ -204,7 +211,10 @@ module.exports = {
|
||||
if (!node.argument) {
|
||||
context.report({
|
||||
node,
|
||||
message: "Expected a return value."
|
||||
message: "{{name}} expected a return value.",
|
||||
data: {
|
||||
name: lodash.upperFirst(astUtils.getFunctionNameWithKind(funcInfo.node))
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -94,7 +100,7 @@ module.exports = {
|
||||
const firstValueToken = sourceCode.getTokenAfter(returnKeyword);
|
||||
let lastValueToken = sourceCode.getLastToken(blockBody[0]);
|
||||
|
||||
if (lastValueToken.type === "Punctuator" && lastValueToken.value === ";") {
|
||||
if (astUtils.isSemicolonToken(lastValueToken)) {
|
||||
|
||||
/* The last token of the returned value is the last token of the ReturnExpression (if
|
||||
* the ReturnExpression has no semicolon), or the second-to-last token (if the ReturnExpression
|
||||
@@ -114,7 +120,7 @@ module.exports = {
|
||||
const textBeforeReturn = sourceText.slice(arrowBody.range[0] + 1, returnKeyword.range[0]);
|
||||
const textBetweenReturnAndValue = sourceText.slice(returnKeyword.range[1], firstValueToken.range[0]);
|
||||
const rawReturnValueText = sourceText.slice(firstValueToken.range[0], lastValueToken.range[1]);
|
||||
const returnValueText = firstValueToken.value === "{" ? `(${rawReturnValueText})` : rawReturnValueText;
|
||||
const returnValueText = astUtils.isOpeningBraceToken(firstValueToken) ? `(${rawReturnValueText})` : rawReturnValueText;
|
||||
const textAfterValue = sourceText.slice(lastValueToken.range[1], blockBody[0].range[1] - 1);
|
||||
const textAfterReturnStatement = sourceText.slice(blockBody[0].range[1], arrowBody.range[1] - 1);
|
||||
|
||||
@@ -136,10 +142,7 @@ module.exports = {
|
||||
loc: arrowBody.loc.start,
|
||||
message: "Expected block statement surrounding arrow body.",
|
||||
fix(fixer) {
|
||||
const lastTokenBeforeBody = sourceCode.getTokensBetween(sourceCode.getFirstToken(node), arrowBody)
|
||||
.reverse()
|
||||
.find(token => token.value !== "(");
|
||||
|
||||
const lastTokenBeforeBody = sourceCode.getLastTokenBetween(sourceCode.getFirstToken(node), arrowBody, astUtils.isNotOpeningParenToken);
|
||||
const firstBodyToken = sourceCode.getTokenAfter(lastTokenBeforeBody);
|
||||
|
||||
return fixer.replaceTextRange(
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -62,7 +68,7 @@ module.exports = {
|
||||
node.body.type !== "BlockStatement" &&
|
||||
!node.returnType
|
||||
) {
|
||||
if (token.type === "Punctuator" && token.value === "(") {
|
||||
if (astUtils.isOpeningParenToken(token)) {
|
||||
context.report({
|
||||
node,
|
||||
message: requireForBlockBodyMessage,
|
||||
@@ -84,7 +90,7 @@ module.exports = {
|
||||
requireForBlockBody &&
|
||||
node.body.type === "BlockStatement"
|
||||
) {
|
||||
if (token.type !== "Punctuator" || token.value !== "(") {
|
||||
if (!astUtils.isOpeningParenToken(token)) {
|
||||
context.report({
|
||||
node,
|
||||
message: requireForBlockBodyNoParensMessage,
|
||||
@@ -103,7 +109,7 @@ module.exports = {
|
||||
!node.params[0].typeAnnotation &&
|
||||
!node.returnType
|
||||
) {
|
||||
if (token.type === "Punctuator" && token.value === "(") {
|
||||
if (astUtils.isOpeningParenToken(token)) {
|
||||
context.report({
|
||||
node,
|
||||
message: asNeededMessage,
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -51,12 +57,7 @@ module.exports = {
|
||||
* @returns {Object} Tokens of arrow and before/after arrow.
|
||||
*/
|
||||
function getTokens(node) {
|
||||
let arrow = sourceCode.getTokenBefore(node.body);
|
||||
|
||||
// skip '(' tokens.
|
||||
while (arrow.value !== "=>") {
|
||||
arrow = sourceCode.getTokenBefore(arrow);
|
||||
}
|
||||
const arrow = sourceCode.getTokenBefore(node.body, astUtils.isArrowToken);
|
||||
|
||||
return {
|
||||
before: sourceCode.getTokenBefore(arrow),
|
||||
|
||||
@@ -74,8 +74,8 @@ module.exports = {
|
||||
// Gets braces and the first/last token of content.
|
||||
const openBrace = getOpenBrace(node);
|
||||
const closeBrace = sourceCode.getLastToken(node);
|
||||
const firstToken = sourceCode.getTokenOrCommentAfter(openBrace);
|
||||
const lastToken = sourceCode.getTokenOrCommentBefore(closeBrace);
|
||||
const firstToken = sourceCode.getTokenAfter(openBrace, { includeComments: true });
|
||||
const lastToken = sourceCode.getTokenBefore(closeBrace, { includeComments: true });
|
||||
|
||||
// Skip if the node is invalid or empty.
|
||||
if (openBrace.type !== "Punctuator" ||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -52,215 +54,86 @@ module.exports = {
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Determines if a given node is a block statement.
|
||||
* @param {ASTNode} node The node to check.
|
||||
* @returns {boolean} True if the node is a block statement, false if not.
|
||||
* @private
|
||||
*/
|
||||
function isBlock(node) {
|
||||
return node && node.type === "BlockStatement";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the token is an punctuator with a value of curly brace
|
||||
* @param {Object} token - Token to check
|
||||
* @returns {boolean} true if its a curly punctuator
|
||||
* @private
|
||||
*/
|
||||
function isCurlyPunctuator(token) {
|
||||
return token.value === "{" || token.value === "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports a place where a newline unexpectedly appears
|
||||
* @param {ASTNode} node The node to report
|
||||
* @param {string} message The message to report
|
||||
* Fixes a place where a newline unexpectedly appears
|
||||
* @param {Token} firstToken The token before the unexpected newline
|
||||
* @param {Token} secondToken The token after the unexpected newline
|
||||
* @returns {Function} A fixer function to remove the newlines between the tokens
|
||||
*/
|
||||
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, ""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a pair of curly brackets based on the user's config
|
||||
* @param {Token} openingCurly The opening curly bracket
|
||||
* @param {Token} closingCurly The closing curly bracket
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportExtraNewline(node, message, firstToken) {
|
||||
context.report({
|
||||
node,
|
||||
message,
|
||||
fix(fixer) {
|
||||
const secondToken = sourceCode.getTokenAfter(firstToken);
|
||||
const textBetween = sourceCode.getText().slice(firstToken.range[1], secondToken.range[0]);
|
||||
const NEWLINE_REGEX = /\r\n|\r|\n|\u2028|\u2029/g;
|
||||
function validateCurlyPair(openingCurly, closingCurly) {
|
||||
const tokenBeforeOpeningCurly = sourceCode.getTokenBefore(openingCurly);
|
||||
const tokenAfterOpeningCurly = sourceCode.getTokenAfter(openingCurly);
|
||||
const tokenBeforeClosingCurly = sourceCode.getTokenBefore(closingCurly);
|
||||
const singleLineException = params.allowSingleLine && astUtils.isTokenOnSameLine(openingCurly, closingCurly);
|
||||
|
||||
// Don't do a fix if there is a comment between the tokens.
|
||||
return textBetween.trim() ? null : fixer.replaceTextRange([firstToken.range[1], secondToken.range[0]], textBetween.replace(NEWLINE_REGEX, ""));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a list of properties to a function that verifies that the opening
|
||||
* curly brace is on the same line as its controlling statement of a given
|
||||
* node.
|
||||
* @param {...string} The properties to check on the node.
|
||||
* @returns {Function} A function that will perform the check on a node
|
||||
* @private
|
||||
*/
|
||||
function checkBlock() {
|
||||
const blockProperties = arguments;
|
||||
|
||||
return function(node) {
|
||||
Array.prototype.forEach.call(blockProperties, blockProp => {
|
||||
const block = node[blockProp];
|
||||
|
||||
if (!isBlock(block)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousToken = sourceCode.getTokenBefore(block);
|
||||
const curlyToken = sourceCode.getFirstToken(block);
|
||||
const curlyTokenEnd = sourceCode.getLastToken(block);
|
||||
const allOnSameLine = previousToken.loc.start.line === curlyTokenEnd.loc.start.line;
|
||||
|
||||
if (allOnSameLine && params.allowSingleLine) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (style !== "allman" && previousToken.loc.start.line !== curlyToken.loc.start.line) {
|
||||
reportExtraNewline(node, OPEN_MESSAGE, previousToken);
|
||||
} else if (style === "allman" && previousToken.loc.start.line === curlyToken.loc.start.line) {
|
||||
context.report({
|
||||
node,
|
||||
message: OPEN_MESSAGE_ALLMAN,
|
||||
fix: fixer => fixer.insertTextBefore(curlyToken, "\n")
|
||||
});
|
||||
}
|
||||
|
||||
if (!block.body.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (curlyToken.loc.start.line === block.body[0].loc.start.line) {
|
||||
context.report({
|
||||
node: block.body[0],
|
||||
message: BODY_MESSAGE,
|
||||
fix: fixer => fixer.insertTextAfter(curlyToken, "\n")
|
||||
});
|
||||
}
|
||||
|
||||
if (curlyTokenEnd.loc.start.line === block.body[block.body.length - 1].loc.end.line) {
|
||||
context.report({
|
||||
node: block.body[block.body.length - 1],
|
||||
message: CLOSE_MESSAGE_SINGLE,
|
||||
fix: fixer => fixer.insertTextBefore(curlyTokenEnd, "\n")
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforces the configured brace style on IfStatements
|
||||
* @param {ASTNode} node An IfStatement node.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function checkIfStatement(node) {
|
||||
checkBlock("consequent", "alternate")(node);
|
||||
|
||||
if (node.alternate) {
|
||||
|
||||
const tokens = sourceCode.getTokensBefore(node.alternate, 2);
|
||||
|
||||
if (style === "1tbs") {
|
||||
if (tokens[0].loc.start.line !== tokens[1].loc.start.line &&
|
||||
node.consequent.type === "BlockStatement" &&
|
||||
isCurlyPunctuator(tokens[0])) {
|
||||
reportExtraNewline(node.alternate, CLOSE_MESSAGE, tokens[0]);
|
||||
}
|
||||
} else if (tokens[0].loc.start.line === tokens[1].loc.start.line) {
|
||||
context.report({
|
||||
node: node.alternate,
|
||||
message: CLOSE_MESSAGE_STROUSTRUP_ALLMAN,
|
||||
fix: fixer => fixer.insertTextAfter(tokens[0], "\n")
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforces the configured brace style on TryStatements
|
||||
* @param {ASTNode} node A TryStatement node.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function checkTryStatement(node) {
|
||||
checkBlock("block", "finalizer")(node);
|
||||
|
||||
if (isBlock(node.finalizer)) {
|
||||
const tokens = sourceCode.getTokensBefore(node.finalizer, 2);
|
||||
|
||||
if (style === "1tbs") {
|
||||
if (tokens[0].loc.start.line !== tokens[1].loc.start.line) {
|
||||
reportExtraNewline(node.finalizer, CLOSE_MESSAGE, tokens[0]);
|
||||
}
|
||||
} else if (tokens[0].loc.start.line === tokens[1].loc.start.line) {
|
||||
context.report({
|
||||
node: node.finalizer,
|
||||
message: CLOSE_MESSAGE_STROUSTRUP_ALLMAN,
|
||||
fix: fixer => fixer.insertTextAfter(tokens[0], "\n")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforces the configured brace style on CatchClauses
|
||||
* @param {ASTNode} node A CatchClause node.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function checkCatchClause(node) {
|
||||
const previousToken = sourceCode.getTokenBefore(node),
|
||||
firstToken = sourceCode.getFirstToken(node);
|
||||
|
||||
checkBlock("body")(node);
|
||||
|
||||
if (isBlock(node.body)) {
|
||||
if (style === "1tbs") {
|
||||
if (previousToken.loc.start.line !== firstToken.loc.start.line) {
|
||||
reportExtraNewline(node, CLOSE_MESSAGE, previousToken);
|
||||
}
|
||||
} else {
|
||||
if (previousToken.loc.start.line === firstToken.loc.start.line) {
|
||||
context.report({
|
||||
node,
|
||||
message: CLOSE_MESSAGE_STROUSTRUP_ALLMAN,
|
||||
fix: fixer => fixer.insertTextAfter(previousToken, "\n")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforces the configured brace style on SwitchStatements
|
||||
* @param {ASTNode} node A SwitchStatement node.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function checkSwitchStatement(node) {
|
||||
let tokens;
|
||||
|
||||
if (node.cases && node.cases.length) {
|
||||
tokens = sourceCode.getTokensBefore(node.cases[0], 2);
|
||||
} else {
|
||||
tokens = sourceCode.getLastTokens(node, 3);
|
||||
}
|
||||
|
||||
if (style !== "allman" && tokens[0].loc.start.line !== tokens[1].loc.start.line) {
|
||||
reportExtraNewline(node, OPEN_MESSAGE, tokens[0]);
|
||||
} else if (style === "allman" && tokens[0].loc.start.line === tokens[1].loc.start.line) {
|
||||
if (style !== "allman" && !astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly)) {
|
||||
context.report({
|
||||
node,
|
||||
node: openingCurly,
|
||||
message: OPEN_MESSAGE,
|
||||
fix: removeNewlineBetween(tokenBeforeOpeningCurly, openingCurly)
|
||||
});
|
||||
}
|
||||
|
||||
if (style === "allman" && astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly) && !singleLineException) {
|
||||
context.report({
|
||||
node: openingCurly,
|
||||
message: OPEN_MESSAGE_ALLMAN,
|
||||
fix: fixer => fixer.insertTextBefore(tokens[1], "\n")
|
||||
fix: fixer => fixer.insertTextBefore(openingCurly, "\n")
|
||||
});
|
||||
}
|
||||
|
||||
if (astUtils.isTokenOnSameLine(openingCurly, tokenAfterOpeningCurly) && tokenAfterOpeningCurly !== closingCurly && !singleLineException) {
|
||||
context.report({
|
||||
node: openingCurly,
|
||||
message: BODY_MESSAGE,
|
||||
fix: fixer => fixer.insertTextAfter(openingCurly, "\n")
|
||||
});
|
||||
}
|
||||
|
||||
if (tokenBeforeClosingCurly !== openingCurly && !singleLineException && astUtils.isTokenOnSameLine(tokenBeforeClosingCurly, closingCurly)) {
|
||||
context.report({
|
||||
node: closingCurly,
|
||||
message: CLOSE_MESSAGE_SINGLE,
|
||||
fix: fixer => fixer.insertTextBefore(closingCurly, "\n")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the location of a token that appears before a keyword (e.g. a newline before `else`)
|
||||
* @param {Token} curlyToken The closing curly token. This is assumed to precede a keyword token (such as `else` or `finally`).
|
||||
* @returns {void}
|
||||
*/
|
||||
function validateCurlyBeforeKeyword(curlyToken) {
|
||||
const keywordToken = sourceCode.getTokenAfter(curlyToken);
|
||||
|
||||
if (style === "1tbs" && !astUtils.isTokenOnSameLine(curlyToken, keywordToken)) {
|
||||
context.report({
|
||||
node: curlyToken,
|
||||
message: CLOSE_MESSAGE,
|
||||
fix: removeNewlineBetween(curlyToken, keywordToken)
|
||||
});
|
||||
}
|
||||
|
||||
if (style !== "1tbs" && astUtils.isTokenOnSameLine(curlyToken, keywordToken)) {
|
||||
context.report({
|
||||
node: curlyToken,
|
||||
message: CLOSE_MESSAGE_STROUSTRUP_ALLMAN,
|
||||
fix: fixer => fixer.insertTextAfter(curlyToken, "\n")
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -270,20 +143,38 @@ module.exports = {
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
FunctionDeclaration: checkBlock("body"),
|
||||
FunctionExpression: checkBlock("body"),
|
||||
ArrowFunctionExpression: checkBlock("body"),
|
||||
IfStatement: checkIfStatement,
|
||||
TryStatement: checkTryStatement,
|
||||
CatchClause: checkCatchClause,
|
||||
DoWhileStatement: checkBlock("body"),
|
||||
WhileStatement: checkBlock("body"),
|
||||
WithStatement: checkBlock("body"),
|
||||
ForStatement: checkBlock("body"),
|
||||
ForInStatement: checkBlock("body"),
|
||||
ForOfStatement: checkBlock("body"),
|
||||
SwitchStatement: checkSwitchStatement
|
||||
};
|
||||
BlockStatement(node) {
|
||||
if (!astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) {
|
||||
validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));
|
||||
}
|
||||
},
|
||||
ClassBody(node) {
|
||||
validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));
|
||||
},
|
||||
SwitchStatement(node) {
|
||||
const closingCurly = sourceCode.getLastToken(node);
|
||||
const openingCurly = sourceCode.getTokenBefore(node.cases.length ? node.cases[0] : closingCurly);
|
||||
|
||||
validateCurlyPair(openingCurly, closingCurly);
|
||||
},
|
||||
IfStatement(node) {
|
||||
if (node.consequent.type === "BlockStatement" && node.alternate) {
|
||||
|
||||
// Handle the keyword after the `if` block (before `else`)
|
||||
validateCurlyBeforeKeyword(sourceCode.getLastToken(node.consequent));
|
||||
}
|
||||
},
|
||||
TryStatement(node) {
|
||||
|
||||
// Handle the keyword after the `try` block (before `catch` or `finally`)
|
||||
validateCurlyBeforeKeyword(sourceCode.getLastToken(node.block));
|
||||
|
||||
if (node.handler && node.finalizer) {
|
||||
|
||||
// Handle the keyword after the `catch` block (before `finally`)
|
||||
validateCurlyBeforeKeyword(sourceCode.getLastToken(node.handler.body));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -9,6 +9,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const LETTER_PATTERN = require("../util/patterns/letters");
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
@@ -16,7 +17,7 @@ const LETTER_PATTERN = require("../util/patterns/letters");
|
||||
|
||||
const ALWAYS_MESSAGE = "Comments should not begin with a lowercase character",
|
||||
NEVER_MESSAGE = "Comments should not begin with an uppercase character",
|
||||
DEFAULT_IGNORE_PATTERN = /^\s*(?:eslint|istanbul|jscs|jshint|globals?|exported)\b/,
|
||||
DEFAULT_IGNORE_PATTERN = astUtils.COMMENTS_IGNORE_PATTERN,
|
||||
WHITESPACE = /\s/g,
|
||||
MAYBE_URL = /^\s*[^:/?#\s]+:\/\/[^?#]/, // TODO: Combine w/ max-len pattern?
|
||||
DEFAULTS = {
|
||||
@@ -163,8 +164,8 @@ module.exports = {
|
||||
* otherwise.
|
||||
*/
|
||||
function isInlineComment(comment) {
|
||||
const previousToken = sourceCode.getTokenOrCommentBefore(comment),
|
||||
nextToken = sourceCode.getTokenOrCommentAfter(comment);
|
||||
const previousToken = sourceCode.getTokenBefore(comment, { includeComments: true }),
|
||||
nextToken = sourceCode.getTokenAfter(comment, { includeComments: true });
|
||||
|
||||
return Boolean(
|
||||
previousToken &&
|
||||
@@ -181,7 +182,7 @@ module.exports = {
|
||||
* @returns {boolean} True if the comment follows a valid comment.
|
||||
*/
|
||||
function isConsecutiveComment(comment) {
|
||||
const previousTokenOrComment = sourceCode.getTokenOrCommentBefore(comment);
|
||||
const previousTokenOrComment = sourceCode.getTokenBefore(comment, { includeComments: true });
|
||||
|
||||
return Boolean(
|
||||
previousTokenOrComment &&
|
||||
@@ -264,9 +265,9 @@ module.exports = {
|
||||
commentValid = isCommentValid(comment, options);
|
||||
|
||||
if (!commentValid) {
|
||||
const message = capitalize === "always" ?
|
||||
ALWAYS_MESSAGE :
|
||||
NEVER_MESSAGE;
|
||||
const message = capitalize === "always"
|
||||
? ALWAYS_MESSAGE
|
||||
: NEVER_MESSAGE;
|
||||
|
||||
context.report({
|
||||
node: null, // Intentionally using loc instead
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const lodash = require("lodash");
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
@@ -20,7 +21,7 @@ const DEFAULT_OPTIONS = Object.freeze({
|
||||
objects: "never",
|
||||
imports: "never",
|
||||
exports: "never",
|
||||
functions: "ignore",
|
||||
functions: "ignore"
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -53,7 +54,7 @@ function normalizeOptions(optionValue) {
|
||||
exports: optionValue,
|
||||
|
||||
// For backward compatibility, always ignore functions.
|
||||
functions: "ignore",
|
||||
functions: "ignore"
|
||||
};
|
||||
}
|
||||
if (typeof optionValue === "object" && optionValue !== null) {
|
||||
@@ -62,7 +63,7 @@ function normalizeOptions(optionValue) {
|
||||
objects: optionValue.objects || DEFAULT_OPTIONS.objects,
|
||||
imports: optionValue.imports || DEFAULT_OPTIONS.imports,
|
||||
exports: optionValue.exports || DEFAULT_OPTIONS.exports,
|
||||
functions: optionValue.functions || DEFAULT_OPTIONS.functions,
|
||||
functions: optionValue.functions || DEFAULT_OPTIONS.functions
|
||||
};
|
||||
}
|
||||
|
||||
@@ -121,7 +122,7 @@ module.exports = {
|
||||
additionalProperties: false
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -178,7 +179,7 @@ module.exports = {
|
||||
default: {
|
||||
const nextToken = sourceCode.getTokenAfter(lastItem);
|
||||
|
||||
if (nextToken.value === ",") {
|
||||
if (astUtils.isCommaToken(nextToken)) {
|
||||
return nextToken;
|
||||
}
|
||||
return sourceCode.getLastToken(lastItem);
|
||||
@@ -224,7 +225,7 @@ module.exports = {
|
||||
|
||||
const trailingToken = getTrailingToken(node, lastItem);
|
||||
|
||||
if (trailingToken.value === ",") {
|
||||
if (astUtils.isCommaToken(trailingToken)) {
|
||||
context.report({
|
||||
node: lastItem,
|
||||
loc: trailingToken.loc.start,
|
||||
@@ -312,7 +313,7 @@ module.exports = {
|
||||
"always-multiline": forceTrailingCommaIfMultiline,
|
||||
"only-multiline": allowTrailingCommaIfMultiline,
|
||||
never: forbidTrailingComma,
|
||||
ignore: lodash.noop,
|
||||
ignore: lodash.noop
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -330,7 +331,7 @@ module.exports = {
|
||||
FunctionExpression: predicate[options.functions],
|
||||
ArrowFunctionExpression: predicate[options.functions],
|
||||
CallExpression: predicate[options.functions],
|
||||
NewExpression: predicate[options.functions],
|
||||
NewExpression: predicate[options.functions]
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -53,16 +53,6 @@ module.exports = {
|
||||
// list of comma tokens to ignore for the check of leading whitespace
|
||||
const commaTokensToIgnore = [];
|
||||
|
||||
/**
|
||||
* Determines if a given token is a comma operator.
|
||||
* @param {ASTNode} token The token to check.
|
||||
* @returns {boolean} True if the token is a comma, false if not.
|
||||
* @private
|
||||
*/
|
||||
function isComma(token) {
|
||||
return !!token && (token.type === "Punctuator") && (token.value === ",");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports a spacing error with an appropriate message.
|
||||
* @param {ASTNode} node The binary expression node to report.
|
||||
@@ -78,27 +68,27 @@ module.exports = {
|
||||
if (options[dir]) {
|
||||
if (dir === "before") {
|
||||
return fixer.insertTextBefore(node, " ");
|
||||
} else {
|
||||
return fixer.insertTextAfter(node, " ");
|
||||
}
|
||||
} else {
|
||||
let start, end;
|
||||
const newText = "";
|
||||
return fixer.insertTextAfter(node, " ");
|
||||
|
||||
if (dir === "before") {
|
||||
start = otherNode.range[1];
|
||||
end = node.range[0];
|
||||
} else {
|
||||
start = node.range[1];
|
||||
end = otherNode.range[0];
|
||||
}
|
||||
|
||||
return fixer.replaceTextRange([start, end], newText);
|
||||
}
|
||||
let start, end;
|
||||
const newText = "";
|
||||
|
||||
if (dir === "before") {
|
||||
start = otherNode.range[1];
|
||||
end = node.range[0];
|
||||
} else {
|
||||
start = node.range[1];
|
||||
end = otherNode.range[0];
|
||||
}
|
||||
|
||||
return fixer.replaceTextRange([start, end], newText);
|
||||
|
||||
},
|
||||
message: options[dir] ?
|
||||
"A space is required {{dir}} ','." :
|
||||
"There should be no space {{dir}} ','.",
|
||||
message: options[dir]
|
||||
? "A space is required {{dir}} ','."
|
||||
: "There should be no space {{dir}} ','.",
|
||||
data: {
|
||||
dir
|
||||
}
|
||||
@@ -147,7 +137,7 @@ module.exports = {
|
||||
if (element === null) {
|
||||
token = sourceCode.getTokenAfter(previousToken);
|
||||
|
||||
if (isComma(token)) {
|
||||
if (astUtils.isCommaToken(token)) {
|
||||
commaTokensToIgnore.push(token);
|
||||
}
|
||||
} else {
|
||||
@@ -166,7 +156,7 @@ module.exports = {
|
||||
"Program:exit"() {
|
||||
tokensAndComments.forEach((token, i) => {
|
||||
|
||||
if (!isComma(token)) {
|
||||
if (!astUtils.isCommaToken(token)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -179,8 +169,8 @@ module.exports = {
|
||||
|
||||
validateCommaItemSpacing({
|
||||
comma: token,
|
||||
left: isComma(previousToken) || commaTokensToIgnore.indexOf(token) > -1 ? null : previousToken,
|
||||
right: isComma(nextToken) ? null : nextToken
|
||||
left: astUtils.isCommaToken(previousToken) || commaTokensToIgnore.indexOf(token) > -1 ? null : previousToken,
|
||||
right: astUtils.isCommaToken(nextToken) ? null : nextToken
|
||||
}, token);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -48,7 +48,7 @@ module.exports = {
|
||||
FunctionDeclaration: true,
|
||||
FunctionExpression: true,
|
||||
ImportDeclaration: true,
|
||||
ObjectPattern: true,
|
||||
ObjectPattern: true
|
||||
};
|
||||
|
||||
if (context.options.length === 2 && context.options[1].hasOwnProperty("exceptions")) {
|
||||
@@ -63,16 +63,6 @@ module.exports = {
|
||||
// Helpers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Determines if a given token is a comma operator.
|
||||
* @param {ASTNode} token The token to check.
|
||||
* @returns {boolean} True if the token is a comma, false if not.
|
||||
* @private
|
||||
*/
|
||||
function isComma(token) {
|
||||
return !!token && (token.type === "Punctuator") && (token.value === ",");
|
||||
}
|
||||
|
||||
/**
|
||||
* Modified text based on the style
|
||||
* @param {string} styleType Style type
|
||||
@@ -192,7 +182,7 @@ module.exports = {
|
||||
tokenBeforeComma = sourceCode.getTokenBefore(commaToken);
|
||||
|
||||
// Check if previous token is wrapped in parentheses
|
||||
if (tokenBeforeComma && tokenBeforeComma.value === ")") {
|
||||
if (tokenBeforeComma && astUtils.isClosingParenToken(tokenBeforeComma)) {
|
||||
previousItemToken = tokenBeforeComma;
|
||||
}
|
||||
|
||||
@@ -210,12 +200,16 @@ module.exports = {
|
||||
* All comparisons are done based on these tokens directly, so
|
||||
* they are always valid regardless of an undefined item.
|
||||
*/
|
||||
if (isComma(commaToken)) {
|
||||
if (astUtils.isCommaToken(commaToken)) {
|
||||
validateCommaItemSpacing(previousItemToken, commaToken,
|
||||
currentItemToken, reportItem);
|
||||
}
|
||||
|
||||
previousItemToken = item ? sourceCode.getLastToken(item) : previousItemToken;
|
||||
if (item) {
|
||||
const tokenAfterItem = sourceCode.getTokenAfter(item, astUtils.isNotClosingParenToken);
|
||||
|
||||
previousItemToken = tokenAfterItem ? sourceCode.getTokenBefore(tokenAfterItem) : sourceCode.ast.tokens[sourceCode.ast.tokens.length - 1];
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
@@ -229,7 +223,7 @@ module.exports = {
|
||||
const lastToken = sourceCode.getLastToken(node),
|
||||
nextToLastToken = sourceCode.getTokenBefore(lastToken);
|
||||
|
||||
if (isComma(nextToLastToken)) {
|
||||
if (astUtils.isCommaToken(nextToLastToken)) {
|
||||
validateCommaItemSpacing(
|
||||
sourceCode.getTokenBefore(nextToLastToken),
|
||||
nextToLastToken,
|
||||
|
||||
@@ -6,6 +6,14 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const lodash = require("lodash");
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -81,17 +89,15 @@ module.exports = {
|
||||
* @private
|
||||
*/
|
||||
function endFunction(node) {
|
||||
const name = lodash.upperFirst(astUtils.getFunctionNameWithKind(node));
|
||||
const complexity = fns.pop();
|
||||
let name = "anonymous";
|
||||
|
||||
if (node.id) {
|
||||
name = node.id.name;
|
||||
} else if (node.parent.type === "MethodDefinition" || node.parent.type === "Property") {
|
||||
name = node.parent.key.name;
|
||||
}
|
||||
|
||||
if (complexity > THRESHOLD) {
|
||||
context.report({ node, message: "Function '{{name}}' has a complexity of {{complexity}}.", data: { name, complexity } });
|
||||
context.report({
|
||||
node,
|
||||
message: "{{name}} has a complexity of {{complexity}}.",
|
||||
data: { name, complexity }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const lodash = require("lodash");
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -81,7 +83,7 @@ module.exports = {
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkLastSegment(node) {
|
||||
let loc, type;
|
||||
let loc, name;
|
||||
|
||||
/*
|
||||
* Skip if it expected no return value or unreachable.
|
||||
@@ -100,12 +102,11 @@ module.exports = {
|
||||
|
||||
// The head of program.
|
||||
loc = { line: 1, column: 0 };
|
||||
type = "program";
|
||||
name = "program";
|
||||
} else if (node.type === "ArrowFunctionExpression") {
|
||||
|
||||
// `=>` token
|
||||
loc = context.getSourceCode().getTokenBefore(node.body).loc.start;
|
||||
type = "function";
|
||||
loc = context.getSourceCode().getTokenBefore(node.body, astUtils.isArrowToken).loc.start;
|
||||
} else if (
|
||||
node.parent.type === "MethodDefinition" ||
|
||||
(node.parent.type === "Property" && node.parent.method)
|
||||
@@ -113,33 +114,36 @@ module.exports = {
|
||||
|
||||
// Method name.
|
||||
loc = node.parent.key.loc.start;
|
||||
type = "method";
|
||||
} else {
|
||||
|
||||
// Function name or `function` keyword.
|
||||
loc = (node.id || node).loc.start;
|
||||
type = "function";
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
name = astUtils.getFunctionNameWithKind(node);
|
||||
}
|
||||
|
||||
// Reports.
|
||||
context.report({
|
||||
node,
|
||||
loc,
|
||||
message: "Expected to return a value at the end of this {{type}}.",
|
||||
data: { type }
|
||||
message: "Expected to return a value at the end of {{name}}.",
|
||||
data: { name }
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
// Initializes/Disposes state of each code path.
|
||||
onCodePathStart(codePath) {
|
||||
onCodePathStart(codePath, node) {
|
||||
funcInfo = {
|
||||
upper: funcInfo,
|
||||
codePath,
|
||||
hasReturn: false,
|
||||
hasReturnValue: false,
|
||||
message: ""
|
||||
message: "",
|
||||
node
|
||||
};
|
||||
},
|
||||
onCodePathEnd() {
|
||||
@@ -158,8 +162,11 @@ module.exports = {
|
||||
if (!funcInfo.hasReturn) {
|
||||
funcInfo.hasReturn = true;
|
||||
funcInfo.hasReturnValue = hasReturnValue;
|
||||
funcInfo.message = "Expected {{which}} return value.";
|
||||
funcInfo.message = "{{name}} expected {{which}} return value.";
|
||||
funcInfo.data = {
|
||||
name: funcInfo.node.type === "Program"
|
||||
? "Program"
|
||||
: lodash.upperFirst(astUtils.getFunctionNameWithKind(funcInfo.node)),
|
||||
which: hasReturnValue ? "a" : "no"
|
||||
};
|
||||
} else if (funcInfo.hasReturnValue !== hasReturnValue) {
|
||||
|
||||
@@ -209,9 +209,9 @@ module.exports = {
|
||||
|
||||
if (!calledInEveryPaths) {
|
||||
context.report({
|
||||
message: calledInSomePaths ?
|
||||
"Lacked a call of 'super()' in some code paths." :
|
||||
"Expected to call 'super()'.",
|
||||
message: calledInSomePaths
|
||||
? "Lacked a call of 'super()' in some code paths."
|
||||
: "Expected to call 'super()'.",
|
||||
node: node.parent
|
||||
});
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ module.exports = {
|
||||
function isCollapsedOneLiner(node) {
|
||||
const before = sourceCode.getTokenBefore(node);
|
||||
const last = sourceCode.getLastToken(node);
|
||||
const lastExcludingSemicolon = last.type === "Punctuator" && last.value === ";" ? sourceCode.getTokenBefore(last) : last;
|
||||
const lastExcludingSemicolon = astUtils.isSemicolonToken(last) ? sourceCode.getTokenBefore(last) : last;
|
||||
|
||||
return before.loc.start.line === lastExcludingSemicolon.loc.end.line;
|
||||
}
|
||||
@@ -94,19 +94,23 @@ module.exports = {
|
||||
return first.loc.start.line === last.loc.end.line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given token is an `else` token or not.
|
||||
*
|
||||
* @param {Token} token - The token to check.
|
||||
* @returns {boolean} `true` if the token is an `else` token.
|
||||
*/
|
||||
function isElseKeywordToken(token) {
|
||||
return token.value === "else" && token.type === "Keyword";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `else` keyword token of a given `IfStatement` node.
|
||||
* @param {ASTNode} node - A `IfStatement` node to get.
|
||||
* @returns {Token} The `else` keyword token.
|
||||
*/
|
||||
function getElseKeyword(node) {
|
||||
let token = sourceCode.getTokenAfter(node.consequent);
|
||||
|
||||
while (token.type !== "Keyword" || token.value !== "else") {
|
||||
token = sourceCode.getTokenAfter(token);
|
||||
}
|
||||
|
||||
return token;
|
||||
return node.alternate && sourceCode.getFirstTokenBetween(node.consequent, node.alternate, isElseKeywordToken);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,7 +174,7 @@ module.exports = {
|
||||
const tokenAfter = sourceCode.getTokenAfter(closingBracket);
|
||||
const lastBlockNode = sourceCode.getNodeByRangeIndex(tokenBefore.range[0]);
|
||||
|
||||
if (tokenBefore.value === ";") {
|
||||
if (astUtils.isSemicolonToken(tokenBefore)) {
|
||||
|
||||
// If the last statement already has a semicolon, don't add another one.
|
||||
return false;
|
||||
|
||||
@@ -31,9 +31,9 @@ module.exports = {
|
||||
|
||||
create(context) {
|
||||
const options = context.options[0] || {};
|
||||
const commentPattern = options.commentPattern ?
|
||||
new RegExp(options.commentPattern) :
|
||||
DEFAULT_COMMENT_PATTERN;
|
||||
const commentPattern = options.commentPattern
|
||||
? new RegExp(options.commentPattern)
|
||||
: DEFAULT_COMMENT_PATTERN;
|
||||
|
||||
const sourceCode = context.getSourceCode();
|
||||
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -64,20 +70,20 @@ module.exports = {
|
||||
propertyValue: JSON.stringify(node.property.value)
|
||||
},
|
||||
fix(fixer) {
|
||||
const leftBracket = sourceCode.getTokenBefore(node.property);
|
||||
const rightBracket = sourceCode.getTokenAfter(node.property);
|
||||
const textBeforeProperty = sourceCode.text.slice(leftBracket.range[1], node.property.range[0]);
|
||||
const textAfterProperty = sourceCode.text.slice(node.property.range[1], rightBracket.range[0]);
|
||||
const leftBracket = sourceCode.getTokenAfter(node.object, astUtils.isOpeningBracketToken);
|
||||
const rightBracket = sourceCode.getLastToken(node);
|
||||
|
||||
if (textBeforeProperty.trim() || textAfterProperty.trim()) {
|
||||
if (sourceCode.getFirstTokenBetween(leftBracket, rightBracket, { includeComments: true, filter: astUtils.isCommentToken })) {
|
||||
|
||||
// Don't perform any fixes if there are comments inside the brackets.
|
||||
return null;
|
||||
}
|
||||
|
||||
const textBeforeDot = astUtils.isDecimalInteger(node.object) ? " " : "";
|
||||
|
||||
return fixer.replaceTextRange(
|
||||
[leftBracket.range[0], rightBracket.range[1]],
|
||||
`.${node.property.value}`
|
||||
`${textBeforeDot}.${node.property.value}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -57,9 +63,9 @@ module.exports = {
|
||||
const options = context.options[1] || {};
|
||||
const sourceCode = context.getSourceCode();
|
||||
|
||||
const nullOption = (config === "always") ?
|
||||
options.null || "always" :
|
||||
"ignore";
|
||||
const nullOption = (config === "always")
|
||||
? options.null || "always"
|
||||
: "ignore";
|
||||
const enforceRuleForNull = (nullOption === "always");
|
||||
const enforceInverseRuleForNull = (nullOption === "never");
|
||||
|
||||
@@ -100,8 +106,7 @@ module.exports = {
|
||||
* @private
|
||||
*/
|
||||
function isNullCheck(node) {
|
||||
return (node.right.type === "Literal" && node.right.value === null) ||
|
||||
(node.left.type === "Literal" && node.left.value === null);
|
||||
return astUtils.isNullLiteral(node.right) || astUtils.isNullLiteral(node.left);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,7 +139,11 @@ module.exports = {
|
||||
|
||||
// If the comparison is a `typeof` comparison or both sides are literals with the same type, then it's safe to fix.
|
||||
if (isTypeOfBinary(node) || areLiteralsAndSameType(node)) {
|
||||
const operatorToken = sourceCode.getTokensBetween(node.left, node.right).find(token => token.value === node.operator);
|
||||
const operatorToken = sourceCode.getFirstTokenBetween(
|
||||
node.left,
|
||||
node.right,
|
||||
token => token.value === node.operator
|
||||
);
|
||||
|
||||
return fixer.replaceText(operatorToken, expectedOperator);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -67,28 +73,19 @@ module.exports = {
|
||||
* @private
|
||||
*/
|
||||
function checkSpacing(node) {
|
||||
const lastToken = sourceCode.getLastToken(node);
|
||||
const lastCalleeToken = sourceCode.getLastToken(node.callee);
|
||||
let prevToken = lastCalleeToken;
|
||||
let parenToken = sourceCode.getTokenAfter(lastCalleeToken);
|
||||
|
||||
// advances to an open parenthesis.
|
||||
while (
|
||||
parenToken &&
|
||||
parenToken.range[1] < node.range[1] &&
|
||||
parenToken.value !== "("
|
||||
) {
|
||||
prevToken = parenToken;
|
||||
parenToken = sourceCode.getTokenAfter(parenToken);
|
||||
}
|
||||
const parenToken = sourceCode.getFirstTokenBetween(lastCalleeToken, lastToken, astUtils.isOpeningParenToken);
|
||||
const prevToken = parenToken && sourceCode.getTokenBefore(parenToken);
|
||||
|
||||
// Parens in NewExpression are optional
|
||||
if (!(parenToken && parenToken.range[1] < node.range[1])) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasWhitespace = sourceCode.isSpaceBetweenTokens(prevToken, parenToken);
|
||||
const hasNewline = hasWhitespace &&
|
||||
/\n/.test(text.slice(prevToken.range[1], parenToken.range[0]).replace(/\/\*.*?\*\//g, ""));
|
||||
const textBetweenTokens = text.slice(prevToken.range[1], parenToken.range[0]).replace(/\/\*.*?\*\//g, "");
|
||||
const hasWhitespace = /\s/.test(textBetweenTokens);
|
||||
const hasNewline = hasWhitespace && astUtils.LINEBREAK_MATCHER.test(textBetweenTokens);
|
||||
|
||||
/*
|
||||
* never allowNewlines hasWhitespace hasNewline message
|
||||
|
||||
@@ -132,6 +132,15 @@ module.exports = {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a given node is a string literal
|
||||
* @param {ASTNode} node The node to check
|
||||
* @returns {boolean} `true` if the node is a string literal
|
||||
*/
|
||||
function isStringLiteral(node) {
|
||||
return node.type === "Literal" && typeof node.value === "string";
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -139,7 +148,7 @@ module.exports = {
|
||||
return {
|
||||
|
||||
VariableDeclarator(node) {
|
||||
if (!node.init || node.init.type !== "FunctionExpression") {
|
||||
if (!node.init || node.init.type !== "FunctionExpression" || node.id.type !== "Identifier") {
|
||||
return;
|
||||
}
|
||||
if (node.init.id && shouldWarn(node.id.name, node.init.id.name)) {
|
||||
@@ -148,14 +157,16 @@ module.exports = {
|
||||
},
|
||||
|
||||
AssignmentExpression(node) {
|
||||
if (node.right.type !== "FunctionExpression" ||
|
||||
(node.left.computed && node.left.property.type !== "Literal") ||
|
||||
(!includeModuleExports && isModuleExports(node.left))
|
||||
) {
|
||||
if (
|
||||
node.right.type !== "FunctionExpression" ||
|
||||
(node.left.computed && node.left.property.type !== "Literal") ||
|
||||
(!includeModuleExports && isModuleExports(node.left)) ||
|
||||
(node.left.type !== "Identifier" && node.left.type !== "MemberExpression")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isProp = node.left.type === "MemberExpression" ? true : false;
|
||||
const isProp = node.left.type === "MemberExpression";
|
||||
const name = isProp ? astUtils.getStaticPropertyName(node.left) : node.left.name;
|
||||
|
||||
if (node.right.id && isIdentifier(name) && shouldWarn(name, node.right.id.name)) {
|
||||
@@ -164,13 +175,13 @@ module.exports = {
|
||||
},
|
||||
|
||||
Property(node) {
|
||||
if (node.value.type !== "FunctionExpression" || !node.value.id || node.computed && node.key.type !== "Literal") {
|
||||
if (node.value.type !== "FunctionExpression" || !node.value.id || node.computed && !isStringLiteral(node.key)) {
|
||||
return;
|
||||
}
|
||||
if (node.key.type === "Identifier" && shouldWarn(node.key.name, node.value.id.name)) {
|
||||
report(node, node.key.name, node.value.id.name, true);
|
||||
} else if (
|
||||
node.key.type === "Literal" &&
|
||||
isStringLiteral(node.key) &&
|
||||
isIdentifier(node.key.value, ecmaVersion) &&
|
||||
shouldWarn(node.key.value, node.value.id.name)
|
||||
) {
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
/**
|
||||
* Checks whether or not a given variable is a function name.
|
||||
* @param {escope.Variable} variable - A variable to check.
|
||||
@@ -82,15 +88,24 @@ module.exports = {
|
||||
return;
|
||||
}
|
||||
|
||||
const name = node.id && node.id.name;
|
||||
const hasName = Boolean(node.id && node.id.name);
|
||||
const name = astUtils.getFunctionNameWithKind(node);
|
||||
|
||||
if (never) {
|
||||
if (name) {
|
||||
context.report({ node, message: "Unexpected function expression name." });
|
||||
if (hasName) {
|
||||
context.report({
|
||||
node,
|
||||
message: "Unexpected named {{name}}.",
|
||||
data: { name }
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (!name && (asNeeded ? !hasInferredName(node) : !isObjectOrClassMethod(node))) {
|
||||
context.report({ node, message: "Missing function expression name." });
|
||||
if (!hasName && (asNeeded ? !hasInferredName(node) : !isObjectOrClassMethod(node))) {
|
||||
context.report({
|
||||
node,
|
||||
message: "Unexpected unnamed {{name}}.",
|
||||
data: { name }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,21 +55,26 @@ module.exports = {
|
||||
const sourceCode = context.getSourceCode();
|
||||
|
||||
/**
|
||||
* Gets `*` token from a given node.
|
||||
* Checks if the given token is a star token or not.
|
||||
*
|
||||
* @param {ASTNode} node - A node to get `*` token. This is one of
|
||||
* FunctionDeclaration, FunctionExpression, Property, and
|
||||
* MethodDefinition.
|
||||
* @returns {Token} `*` token.
|
||||
* @param {Token} token - The token to check.
|
||||
* @returns {boolean} `true` if the token is a star token.
|
||||
*/
|
||||
function isStarToken(token) {
|
||||
return token.value === "*" && token.type === "Punctuator";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the generator star token of the given function node.
|
||||
*
|
||||
* @param {ASTNode} node - The function node to get.
|
||||
* @returns {Token} Found star token.
|
||||
*/
|
||||
function getStarToken(node) {
|
||||
let token = sourceCode.getFirstToken(node);
|
||||
|
||||
while (token.value !== "*") {
|
||||
token = sourceCode.getTokenAfter(token);
|
||||
}
|
||||
|
||||
return token;
|
||||
return sourceCode.getFirstToken(
|
||||
(node.parent.method || node.parent.type === "MethodDefinition") ? node.parent : node,
|
||||
isStarToken
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,17 +121,11 @@ module.exports = {
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkFunction(node) {
|
||||
let starToken;
|
||||
|
||||
if (!node.generator) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.parent.method || node.parent.type === "MethodDefinition") {
|
||||
starToken = getStarToken(node.parent);
|
||||
} else {
|
||||
starToken = getStarToken(node);
|
||||
}
|
||||
const starToken = getStarToken(node);
|
||||
|
||||
// Only check before when preceded by `function`|`static` keyword
|
||||
const prevToken = sourceCode.getTokenBefore(starToken);
|
||||
|
||||
@@ -29,9 +29,9 @@ function findReference(scope, node) {
|
||||
/* istanbul ignore else: correctly returns null */
|
||||
if (references.length === 1) {
|
||||
return references[0];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -55,8 +55,8 @@ module.exports = {
|
||||
* @returns {boolean} whether an error should be reported or not
|
||||
*/
|
||||
function shouldReport(effectiveParent, name) {
|
||||
return effectiveParent.type !== "CallExpression"
|
||||
&& effectiveParent.type !== "NewExpression" &&
|
||||
return effectiveParent.type !== "CallExpression" &&
|
||||
effectiveParent.type !== "NewExpression" &&
|
||||
isInvalid(name);
|
||||
}
|
||||
|
||||
|
||||
@@ -104,9 +104,9 @@ module.exports = {
|
||||
if (isValidExpression && (isValidExpression === true || isValidExpression(parent, node))) {
|
||||
context.report({
|
||||
node,
|
||||
message: isShort ?
|
||||
"Identifier name '{{name}}' is too short (< {{min}})." :
|
||||
"Identifier name '{{name}}' is too long (> {{max}}).",
|
||||
message: isShort
|
||||
? "Identifier name '{{name}}' is too short (< {{min}})."
|
||||
: "Identifier name '{{name}}' is too long (> {{max}}).",
|
||||
data: { name, min: minLength, max: maxLength }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -63,8 +63,8 @@ module.exports = {
|
||||
* @returns {boolean} whether an error should be reported or not
|
||||
*/
|
||||
function shouldReport(effectiveParent, name) {
|
||||
return effectiveParent.type !== "CallExpression"
|
||||
&& effectiveParent.type !== "NewExpression" &&
|
||||
return effectiveParent.type !== "CallExpression" &&
|
||||
effectiveParent.type !== "NewExpression" &&
|
||||
isInvalid(name);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -435,15 +441,10 @@ module.exports = {
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkLastReturnStatementLineIndent(node, firstLineIndent) {
|
||||
const nodeLastToken = sourceCode.getLastToken(node);
|
||||
let lastToken = nodeLastToken;
|
||||
|
||||
// in case if return statement ends with ');' we have traverse back to ')'
|
||||
// otherwise we'll measure indent for ';' and replace ')'
|
||||
while (lastToken.value !== ")") {
|
||||
lastToken = sourceCode.getTokenBefore(lastToken);
|
||||
}
|
||||
|
||||
const lastToken = sourceCode.getLastToken(node, astUtils.isClosingParenToken);
|
||||
const textBeforeClosingParenthesis = sourceCode.getText(lastToken, lastToken.loc.start.column).slice(0, -1);
|
||||
|
||||
if (textBeforeClosingParenthesis.trim()) {
|
||||
@@ -691,9 +692,9 @@ module.exports = {
|
||||
function isFirstArrayElementOnSameLine(node) {
|
||||
if (node.type === "ArrayExpression" && node.elements[0]) {
|
||||
return node.elements[0].loc.start.line === node.loc.start.line && node.elements[0].type === "ObjectExpression";
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -729,7 +730,7 @@ module.exports = {
|
||||
} else if (parent.type === "ObjectExpression" || parent.type === "ArrayExpression") {
|
||||
const parentElements = node.parent.type === "ObjectExpression" ? node.parent.properties : node.parent.elements;
|
||||
|
||||
if (parentElements[0].loc.start.line === parent.loc.start.line && parentElements[0].loc.end.line !== parent.loc.start.line) {
|
||||
if (parentElements[0] && parentElements[0].loc.start.line === parent.loc.start.line && parentElements[0].loc.end.line !== parent.loc.start.line) {
|
||||
|
||||
/*
|
||||
* If the first element of the array spans multiple lines, don't increase the expected indentation of the rest.
|
||||
@@ -936,20 +937,20 @@ module.exports = {
|
||||
|
||||
if (caseIndentStore[switchNode.loc.start.line]) {
|
||||
return caseIndentStore[switchNode.loc.start.line];
|
||||
} else {
|
||||
if (typeof switchIndent === "undefined") {
|
||||
switchIndent = getNodeIndent(switchNode).goodChar;
|
||||
}
|
||||
|
||||
if (switchNode.cases.length > 0 && options.SwitchCase === 0) {
|
||||
caseIndent = switchIndent;
|
||||
} else {
|
||||
caseIndent = switchIndent + (indentSize * options.SwitchCase);
|
||||
}
|
||||
|
||||
caseIndentStore[switchNode.loc.start.line] = caseIndent;
|
||||
return caseIndent;
|
||||
}
|
||||
if (typeof switchIndent === "undefined") {
|
||||
switchIndent = getNodeIndent(switchNode).goodChar;
|
||||
}
|
||||
|
||||
if (switchNode.cases.length > 0 && options.SwitchCase === 0) {
|
||||
caseIndent = switchIndent;
|
||||
} else {
|
||||
caseIndent = switchIndent + (indentSize * options.SwitchCase);
|
||||
}
|
||||
|
||||
caseIndentStore[switchNode.loc.start.line] = caseIndent;
|
||||
return caseIndent;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -15,7 +21,7 @@
|
||||
* @returns {boolean} True if str contains a line terminator.
|
||||
*/
|
||||
function containsLineTerminator(str) {
|
||||
return /[\n\r\u2028\u2029]/.test(str);
|
||||
return astUtils.LINEBREAK_MATCHER.test(str);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -364,14 +370,9 @@ module.exports = {
|
||||
* @returns {ASTNode} The last token before a colon punctuator.
|
||||
*/
|
||||
function getLastTokenBeforeColon(node) {
|
||||
let prevNode;
|
||||
const colonToken = sourceCode.getTokenAfter(node, astUtils.isColonToken);
|
||||
|
||||
while (node && (node.type !== "Punctuator" || node.value !== ":")) {
|
||||
prevNode = node;
|
||||
node = sourceCode.getTokenAfter(node);
|
||||
}
|
||||
|
||||
return prevNode;
|
||||
return sourceCode.getTokenBefore(colonToken);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -381,12 +382,7 @@ module.exports = {
|
||||
* @returns {ASTNode} The colon punctuator.
|
||||
*/
|
||||
function getNextColon(node) {
|
||||
|
||||
while (node && (node.type !== "Punctuator" || node.value !== ":")) {
|
||||
node = sourceCode.getTokenAfter(node);
|
||||
}
|
||||
|
||||
return node;
|
||||
return sourceCode.getTokenAfter(node, astUtils.isColonToken);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -417,8 +413,8 @@ module.exports = {
|
||||
function report(property, side, whitespace, expected, mode) {
|
||||
const diff = whitespace.length - expected,
|
||||
nextColon = getNextColon(property.key),
|
||||
tokenBeforeColon = sourceCode.getTokenOrCommentBefore(nextColon),
|
||||
tokenAfterColon = sourceCode.getTokenOrCommentAfter(nextColon),
|
||||
tokenBeforeColon = sourceCode.getTokenBefore(nextColon, { includeComments: true }),
|
||||
tokenAfterColon = sourceCode.getTokenAfter(nextColon, { includeComments: true }),
|
||||
isKeySide = side === "key",
|
||||
locStart = isKeySide ? tokenBeforeColon.loc.start : tokenAfterColon.loc.start,
|
||||
isExtra = diff > 0,
|
||||
@@ -628,15 +624,16 @@ module.exports = {
|
||||
}
|
||||
};
|
||||
|
||||
} else { // Obey beforeColon and afterColon in each property as configured
|
||||
|
||||
return {
|
||||
Property(node) {
|
||||
verifySpacing(node, isSingleLine(node.parent) ? singleLineOptions : multiLineOptions);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// Obey beforeColon and afterColon in each property as configured
|
||||
return {
|
||||
Property(node) {
|
||||
verifySpacing(node, isSingleLine(node.parent) ? singleLineOptions : multiLineOptions);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
@@ -342,11 +342,7 @@ module.exports = {
|
||||
*/
|
||||
function checkSpacingAroundTokenBefore(node) {
|
||||
if (node) {
|
||||
let token = sourceCode.getTokenBefore(node);
|
||||
|
||||
while (token.type !== "Keyword") {
|
||||
token = sourceCode.getTokenBefore(token);
|
||||
}
|
||||
const token = sourceCode.getTokenBefore(node, astUtils.isKeywordToken);
|
||||
|
||||
checkSpacingAround(token);
|
||||
}
|
||||
@@ -363,7 +359,8 @@ module.exports = {
|
||||
const firstToken = node && sourceCode.getFirstToken(node);
|
||||
|
||||
if (firstToken &&
|
||||
(firstToken.type === "Keyword" || firstToken.value === "async")
|
||||
((firstToken.type === "Keyword" && firstToken.value === "function") ||
|
||||
firstToken.value === "async")
|
||||
) {
|
||||
checkSpacingBefore(firstToken);
|
||||
}
|
||||
@@ -439,14 +436,7 @@ module.exports = {
|
||||
*/
|
||||
function checkSpacingForForOfStatement(node) {
|
||||
checkSpacingAroundFirstToken(node);
|
||||
|
||||
// `of` is not a keyword token.
|
||||
let token = sourceCode.getTokenBefore(node.right);
|
||||
|
||||
while (token.value !== "of") {
|
||||
token = sourceCode.getTokenBefore(token);
|
||||
}
|
||||
checkSpacingAround(token);
|
||||
checkSpacingAround(sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -506,11 +496,25 @@ module.exports = {
|
||||
node.value.async
|
||||
)
|
||||
) {
|
||||
const token = sourceCode.getFirstToken(
|
||||
node,
|
||||
node.static ? 1 : 0
|
||||
const token = sourceCode.getTokenBefore(
|
||||
node.key,
|
||||
tok => {
|
||||
switch (tok.value) {
|
||||
case "get":
|
||||
case "set":
|
||||
case "async":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!token) {
|
||||
throw new Error("Failed to find token get, set, or async beside method name");
|
||||
}
|
||||
|
||||
|
||||
checkSpacingAround(token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -33,6 +35,9 @@ module.exports = {
|
||||
},
|
||||
applyDefaultPatterns: {
|
||||
type: "boolean"
|
||||
},
|
||||
applyDefaultIgnorePatterns: {
|
||||
type: "boolean"
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
@@ -43,12 +48,11 @@ module.exports = {
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const DEFAULT_IGNORE_PATTERN = "^\\s*(?:eslint|jshint\\s+|jslint\\s+|istanbul\\s+|globals?\\s+|exported\\s+|jscs|falls?\\s?through)";
|
||||
const options = context.options[0];
|
||||
|
||||
let above,
|
||||
ignorePattern,
|
||||
applyDefaultPatterns = true;
|
||||
applyDefaultIgnorePatterns = true;
|
||||
|
||||
if (!options || typeof options === "string") {
|
||||
above = !options || options === "above";
|
||||
@@ -56,10 +60,16 @@ module.exports = {
|
||||
} else {
|
||||
above = options.position === "above";
|
||||
ignorePattern = options.ignorePattern;
|
||||
applyDefaultPatterns = options.applyDefaultPatterns !== false;
|
||||
|
||||
if (options.hasOwnProperty("applyDefaultIgnorePatterns")) {
|
||||
applyDefaultIgnorePatterns = options.applyDefaultIgnorePatterns !== false;
|
||||
} else {
|
||||
applyDefaultIgnorePatterns = options.applyDefaultPatterns !== false;
|
||||
}
|
||||
}
|
||||
|
||||
const defaultIgnoreRegExp = new RegExp(DEFAULT_IGNORE_PATTERN);
|
||||
const defaultIgnoreRegExp = astUtils.COMMENTS_IGNORE_PATTERN;
|
||||
const fallThroughRegExp = /^\s*falls?\s?through/;
|
||||
const customIgnoreRegExp = new RegExp(ignorePattern);
|
||||
const sourceCode = context.getSourceCode();
|
||||
|
||||
@@ -69,7 +79,7 @@ module.exports = {
|
||||
|
||||
return {
|
||||
LineComment(node) {
|
||||
if (applyDefaultPatterns && defaultIgnoreRegExp.test(node.value)) {
|
||||
if (applyDefaultIgnorePatterns && (defaultIgnoreRegExp.test(node.value) || fallThroughRegExp.test(node.value))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -77,7 +87,7 @@ module.exports = {
|
||||
return;
|
||||
}
|
||||
|
||||
const previous = sourceCode.getTokenOrCommentBefore(node);
|
||||
const previous = sourceCode.getTokenBefore(node, { includeComments: true });
|
||||
const isOnSameLine = previous && previous.loc.end.line === node.loc.start.line;
|
||||
|
||||
if (above) {
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -60,7 +66,7 @@ module.exports = {
|
||||
expectedLF = linebreakStyle === "unix",
|
||||
expectedLFChars = expectedLF ? "\n" : "\r\n",
|
||||
source = sourceCode.getText(),
|
||||
pattern = /\r\n|\r|\n|\u2028|\u2029/g;
|
||||
pattern = astUtils.createGlobalLinebreakMatcher();
|
||||
let match;
|
||||
|
||||
let i = 0;
|
||||
|
||||
@@ -93,6 +93,12 @@ module.exports = {
|
||||
},
|
||||
allowArrayEnd: {
|
||||
type: "boolean"
|
||||
},
|
||||
ignorePattern: {
|
||||
type: "string"
|
||||
},
|
||||
applyDefaultIgnorePatterns: {
|
||||
type: "boolean"
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
@@ -103,6 +109,11 @@ module.exports = {
|
||||
create(context) {
|
||||
|
||||
const options = context.options[0] ? Object.assign({}, context.options[0]) : {};
|
||||
const ignorePattern = options.ignorePattern;
|
||||
const defaultIgnoreRegExp = astUtils.COMMENTS_IGNORE_PATTERN;
|
||||
const customIgnoreRegExp = new RegExp(ignorePattern);
|
||||
const applyDefaultIgnorePatterns = options.applyDefaultIgnorePatterns !== false;
|
||||
|
||||
|
||||
options.beforeLineComment = options.beforeLineComment || false;
|
||||
options.afterLineComment = options.afterLineComment || false;
|
||||
@@ -139,7 +150,7 @@ module.exports = {
|
||||
|
||||
token = node;
|
||||
do {
|
||||
token = sourceCode.getTokenOrCommentBefore(token);
|
||||
token = sourceCode.getTokenBefore(token, { includeComments: true });
|
||||
} while (isCommentNodeType(token));
|
||||
|
||||
if (token && astUtils.isTokenOnSameLine(token, node)) {
|
||||
@@ -148,7 +159,7 @@ module.exports = {
|
||||
|
||||
token = node;
|
||||
do {
|
||||
token = sourceCode.getTokenOrCommentAfter(token);
|
||||
token = sourceCode.getTokenAfter(token, { includeComments: true });
|
||||
} while (isCommentNodeType(token));
|
||||
|
||||
if (token && astUtils.isTokenOnSameLine(node, token)) {
|
||||
@@ -270,6 +281,14 @@ module.exports = {
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkForEmptyLine(node, opts) {
|
||||
if (applyDefaultIgnorePatterns && defaultIgnoreRegExp.test(node.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ignorePattern && customIgnoreRegExp.test(node.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let after = opts.after,
|
||||
before = opts.before;
|
||||
|
||||
@@ -300,8 +319,8 @@ module.exports = {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousTokenOrComment = sourceCode.getTokenOrCommentBefore(node);
|
||||
const nextTokenOrComment = sourceCode.getTokenOrCommentAfter(node);
|
||||
const previousTokenOrComment = sourceCode.getTokenBefore(node, { includeComments: true });
|
||||
const nextTokenOrComment = sourceCode.getTokenAfter(node, { includeComments: true });
|
||||
|
||||
// check for newline before
|
||||
if (!exceptionStartAllowed && before && !lodash.includes(commentAndEmptyLines, prevLineNum) &&
|
||||
|
||||
@@ -31,7 +31,7 @@ module.exports = {
|
||||
},
|
||||
after: {
|
||||
enum: ["always", "never"]
|
||||
},
|
||||
}
|
||||
},
|
||||
additionalProperties: false,
|
||||
minProperties: 2
|
||||
@@ -57,7 +57,7 @@ module.exports = {
|
||||
* @returns {boolean} Whether or not the passed in node is preceded by a blank newline.
|
||||
*/
|
||||
function hasNewlineBefore(node) {
|
||||
const tokenBefore = sourceCode.getTokenOrCommentBefore(node);
|
||||
const tokenBefore = sourceCode.getTokenBefore(node, { includeComments: true });
|
||||
const tokenLineBefore = tokenBefore ? tokenBefore.loc.end.line : 0;
|
||||
|
||||
return node.loc.start.line - tokenLineBefore >= 2;
|
||||
@@ -74,7 +74,7 @@ module.exports = {
|
||||
const lastToken = sourceCode.getLastToken(node);
|
||||
const secondToLastToken = sourceCode.getTokenBefore(lastToken);
|
||||
|
||||
return lastToken.type === "Punctuator" && lastToken.value === ";" && lastToken.loc.start.line > secondToLastToken.loc.end.line
|
||||
return astUtils.isSemicolonToken(lastToken) && lastToken.loc.start.line > secondToLastToken.loc.end.line
|
||||
? secondToLastToken
|
||||
: lastToken;
|
||||
}
|
||||
@@ -86,7 +86,7 @@ module.exports = {
|
||||
*/
|
||||
function hasNewlineAfter(node) {
|
||||
const lastToken = getLastTokenOnLine(node);
|
||||
const tokenAfter = sourceCode.getTokenOrCommentAfter(lastToken);
|
||||
const tokenAfter = sourceCode.getTokenAfter(lastToken, { includeComments: true });
|
||||
|
||||
return tokenAfter.loc.start.line - lastToken.loc.end.line >= 2;
|
||||
}
|
||||
@@ -131,7 +131,7 @@ module.exports = {
|
||||
}
|
||||
|
||||
const firstDirective = directives[0];
|
||||
const hasTokenOrCommentBefore = !!sourceCode.getTokenOrCommentBefore(firstDirective);
|
||||
const hasTokenOrCommentBefore = !!sourceCode.getTokenBefore(firstDirective, { includeComments: true });
|
||||
|
||||
// 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
|
||||
|
||||
@@ -90,7 +90,7 @@ module.exports = {
|
||||
|
||||
token = comment;
|
||||
do {
|
||||
token = sourceCode.getTokenOrCommentBefore(token);
|
||||
token = sourceCode.getTokenBefore(token, { includeComments: true });
|
||||
} while (isCommentNodeType(token));
|
||||
|
||||
if (token && astUtils.isTokenOnSameLine(token, comment)) {
|
||||
@@ -99,7 +99,7 @@ module.exports = {
|
||||
|
||||
token = comment;
|
||||
do {
|
||||
token = sourceCode.getTokenOrCommentAfter(token);
|
||||
token = sourceCode.getTokenAfter(token, { includeComments: true });
|
||||
} while (isCommentNodeType(token));
|
||||
|
||||
if (token && astUtils.isTokenOnSameLine(comment, token)) {
|
||||
@@ -134,7 +134,7 @@ module.exports = {
|
||||
message: "File must be at most {{max}} lines long. It's {{actual}} lines long.",
|
||||
data: {
|
||||
max,
|
||||
actual: lines.length,
|
||||
actual: lines.length
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,6 +5,14 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const lodash = require("lodash");
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -66,10 +74,15 @@ module.exports = {
|
||||
*/
|
||||
function checkFunction(node) {
|
||||
if (node.params.length > numParams) {
|
||||
context.report({ node, message: "This function has too many parameters ({{count}}). Maximum allowed is {{max}}.", data: {
|
||||
count: node.params.length,
|
||||
max: numParams
|
||||
} });
|
||||
context.report({
|
||||
node,
|
||||
message: "{{name}} has too many parameters ({{count}}). Maximum allowed is {{max}}.",
|
||||
data: {
|
||||
name: lodash.upperFirst(astUtils.getFunctionNameWithKind(node)),
|
||||
count: node.params.length,
|
||||
max: numParams
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -60,7 +66,7 @@ module.exports = {
|
||||
data: {
|
||||
numberOfStatementsOnThisLine,
|
||||
maxStatementsPerLine,
|
||||
statements: numberOfStatementsOnThisLine === 1 ? "statement" : "statements",
|
||||
statements: numberOfStatementsOnThisLine === 1 ? "statement" : "statements"
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -74,12 +80,7 @@ module.exports = {
|
||||
* @returns {Token} The actual last token.
|
||||
*/
|
||||
function getActualLastToken(node) {
|
||||
let lastToken = sourceCode.getLastToken(node);
|
||||
|
||||
if (lastToken.value === ";") {
|
||||
lastToken = sourceCode.getTokenBefore(lastToken);
|
||||
}
|
||||
return lastToken;
|
||||
return sourceCode.getLastToken(node, astUtils.isNotSemicolonToken);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,6 +5,14 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const lodash = require("lodash");
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -84,19 +92,12 @@ module.exports = {
|
||||
*/
|
||||
function reportIfTooManyStatements(node, count, max) {
|
||||
if (count > max) {
|
||||
const messageEnd = " has too many statements ({{count}}). Maximum allowed is {{max}}.";
|
||||
let name = "This function";
|
||||
|
||||
if (node.id) {
|
||||
name = `Function '${node.id.name}'`;
|
||||
} else if (node.parent.type === "MethodDefinition" || node.parent.type === "Property") {
|
||||
name = `Function '${context.getSource(node.parent.key)}'`;
|
||||
}
|
||||
const name = lodash.upperFirst(astUtils.getFunctionNameWithKind(node));
|
||||
|
||||
context.report({
|
||||
node,
|
||||
message: name + messageEnd,
|
||||
data: { count, max }
|
||||
message: "{{name}} has too many statements ({{count}}). Maximum allowed is {{max}}.",
|
||||
data: { name, count, max }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,9 +180,9 @@ module.exports = {
|
||||
return "non-alpha";
|
||||
} else if (firstChar === firstCharLower) {
|
||||
return "lower";
|
||||
} else {
|
||||
return "upper";
|
||||
}
|
||||
return "upper";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,28 +6,14 @@
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Checks whether the given token is an opening parenthesis or not.
|
||||
*
|
||||
* @param {Token} token - The token to check.
|
||||
* @returns {boolean} `true` if the token is an opening parenthesis.
|
||||
*/
|
||||
function isOpeningParen(token) {
|
||||
return token.type === "Punctuator" && token.value === "(";
|
||||
}
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
/**
|
||||
* Checks whether the given token is an closing parenthesis or not.
|
||||
*
|
||||
* @param {Token} token - The token to check.
|
||||
* @returns {boolean} `true` if the token is an closing parenthesis.
|
||||
*/
|
||||
function isClosingParen(token) {
|
||||
return token.type === "Punctuator" && token.value === ")";
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
@@ -56,8 +42,8 @@ module.exports = {
|
||||
}
|
||||
|
||||
const lastToken = sourceCode.getLastToken(node);
|
||||
const hasLastParen = lastToken && isClosingParen(lastToken);
|
||||
const hasParens = hasLastParen && isOpeningParen(sourceCode.getTokenBefore(lastToken));
|
||||
const hasLastParen = lastToken && astUtils.isClosingParenToken(lastToken);
|
||||
const hasParens = hasLastParen && astUtils.isOpeningParenToken(sourceCode.getTokenBefore(lastToken));
|
||||
|
||||
if (!hasParens) {
|
||||
context.report({
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -201,8 +207,7 @@ module.exports = {
|
||||
message: NEVER_MESSAGE,
|
||||
data: { identifier: node.name },
|
||||
fix(fixer) {
|
||||
const NEWLINE_REGEX = /\r\n|\r|\n|\u2028|\u2029/;
|
||||
const linesBetween = sourceCode.getText().slice(lastToken.range[1], nextToken.range[0]).split(NEWLINE_REGEX);
|
||||
const linesBetween = sourceCode.getText().slice(lastToken.range[1], nextToken.range[0]).split(astUtils.LINEBREAK_MATCHER);
|
||||
|
||||
return fixer.replaceTextRange([lastToken.range[1], nextToken.range[0]], `${linesBetween.slice(0, -1).join("")}\n${linesBetween[linesBetween.length - 1]}`);
|
||||
}
|
||||
|
||||
@@ -60,9 +60,9 @@ module.exports = {
|
||||
return isPrecededByTokens(node, ["do"]);
|
||||
} else if (parentType === "SwitchCase") {
|
||||
return isPrecededByTokens(node, [":"]);
|
||||
} else {
|
||||
return isPrecededByTokens(node, [")"]);
|
||||
}
|
||||
return isPrecededByTokens(node, [")"]);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -47,7 +49,7 @@ module.exports = {
|
||||
*/
|
||||
function getPropertyText(node) {
|
||||
const prefix = node.computed ? "[" : ".";
|
||||
const lines = sourceCode.getText(node.property).split(/\r\n|\r|\n/g);
|
||||
const lines = sourceCode.getText(node.property).split(astUtils.LINEBREAK_MATCHER);
|
||||
const suffix = node.computed && lines.length === 1 ? "]" : "";
|
||||
|
||||
return prefix + lines[0] + suffix;
|
||||
|
||||
@@ -10,7 +10,7 @@ const loopTypes = new Set([
|
||||
"ForOfStatement",
|
||||
"ForInStatement",
|
||||
"WhileStatement",
|
||||
"DoWhileStatement",
|
||||
"DoWhileStatement"
|
||||
]);
|
||||
|
||||
// Node types at which we should stop looking for loops. For example, it is fine to declare an async
|
||||
@@ -18,7 +18,7 @@ const loopTypes = new Set([
|
||||
const boundaryTypes = new Set([
|
||||
"FunctionDeclaration",
|
||||
"FunctionExpression",
|
||||
"ArrowFunctionExpression",
|
||||
"ArrowFunctionExpression"
|
||||
]);
|
||||
|
||||
module.exports = {
|
||||
@@ -26,9 +26,9 @@ module.exports = {
|
||||
docs: {
|
||||
description: "disallow `await` inside of loops",
|
||||
category: "Possible Errors",
|
||||
recommended: false,
|
||||
recommended: false
|
||||
},
|
||||
schema: [],
|
||||
schema: []
|
||||
},
|
||||
create(context) {
|
||||
return {
|
||||
@@ -69,7 +69,7 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* @fileoverview The rule should warn against code that tries to compare against -0.
|
||||
* @author Aladdin-ADD <hh_2013@foxmail.com>
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
docs: {
|
||||
description: "disallow comparing against -0",
|
||||
category: "Possible Errors",
|
||||
recommended: false
|
||||
},
|
||||
fixable: null,
|
||||
schema: []
|
||||
},
|
||||
|
||||
create(context) {
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Checks a given node is -0
|
||||
*
|
||||
* @param {ASTNode} node - A node to check.
|
||||
* @returns {boolean} `true` if the node is -0.
|
||||
*/
|
||||
function isNegZero(node) {
|
||||
return node.type === "UnaryExpression" && node.operator === "-" && node.argument.type === "Literal" && node.argument.value === 0;
|
||||
}
|
||||
const OPERATORS_TO_CHECK = new Set([">", ">=", "<", "<=", "==", "===", "!=", "!=="]);
|
||||
|
||||
return {
|
||||
BinaryExpression(node) {
|
||||
if (OPERATORS_TO_CHECK.has(node.operator)) {
|
||||
if (isNegZero(node.left) || isNegZero(node.right)) {
|
||||
context.report({
|
||||
node,
|
||||
message: "Do not use the '{{operator}}' operator to compare against -0.",
|
||||
data: { operator: node.operator }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -66,19 +66,6 @@ module.exports = {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the code represented by an AST node is enclosed in parentheses.
|
||||
* @param {!Object} node The node to test.
|
||||
* @returns {boolean} `true` if the code is enclosed in parentheses; otherwise, `false`.
|
||||
*/
|
||||
function isParenthesised(node) {
|
||||
const previousToken = sourceCode.getTokenBefore(node),
|
||||
nextToken = sourceCode.getTokenAfter(node);
|
||||
|
||||
return previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
|
||||
nextToken.value === ")" && nextToken.range[0] >= node.range[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the code represented by an AST node is enclosed in two sets of parentheses.
|
||||
* @param {!Object} node The node to test.
|
||||
@@ -88,9 +75,9 @@ module.exports = {
|
||||
const previousToken = sourceCode.getTokenBefore(node, 1),
|
||||
nextToken = sourceCode.getTokenAfter(node, 1);
|
||||
|
||||
return isParenthesised(node) &&
|
||||
previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
|
||||
nextToken.value === ")" && nextToken.range[0] >= node.range[1];
|
||||
return astUtils.isParenthesised(sourceCode, node) &&
|
||||
astUtils.isOpeningParenToken(previousToken) && previousToken.range[1] <= node.range[0] &&
|
||||
astUtils.isClosingParenToken(nextToken) && nextToken.range[0] >= node.range[1];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,9 +88,9 @@ module.exports = {
|
||||
function testForAssign(node) {
|
||||
if (node.test &&
|
||||
(node.test.type === "AssignmentExpression") &&
|
||||
(node.type === "ForStatement" ?
|
||||
!isParenthesised(node.test) :
|
||||
!isParenthesisedTwice(node.test)
|
||||
(node.type === "ForStatement"
|
||||
? !astUtils.isParenthesised(sourceCode, node.test)
|
||||
: !isParenthesisedTwice(node.test)
|
||||
)
|
||||
) {
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ module.exports = {
|
||||
node: info.node,
|
||||
loc: node.key.loc,
|
||||
message: "Duplicate key '{{name}}'.",
|
||||
data: { name },
|
||||
data: { name }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
const FixTracker = require("../util/fix-tracker");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -17,7 +24,9 @@ module.exports = {
|
||||
recommended: false
|
||||
},
|
||||
|
||||
schema: []
|
||||
schema: [],
|
||||
|
||||
fixable: "code"
|
||||
},
|
||||
|
||||
create(context) {
|
||||
@@ -33,7 +42,66 @@ module.exports = {
|
||||
* @returns {void}
|
||||
*/
|
||||
function displayReport(node) {
|
||||
context.report({ node, message: "Unnecessary 'else' after 'return'." });
|
||||
context.report({
|
||||
node,
|
||||
message: "Unnecessary 'else' after 'return'.",
|
||||
fix: fixer => {
|
||||
const sourceCode = context.getSourceCode();
|
||||
const startToken = sourceCode.getFirstToken(node);
|
||||
const elseToken = sourceCode.getTokenBefore(startToken);
|
||||
const source = sourceCode.getText(node);
|
||||
const lastIfToken = sourceCode.getTokenBefore(elseToken);
|
||||
let fixedSource, firstTokenOfElseBlock;
|
||||
|
||||
if (startToken.type === "Punctuator" && startToken.value === "{") {
|
||||
firstTokenOfElseBlock = sourceCode.getTokenAfter(startToken);
|
||||
} else {
|
||||
firstTokenOfElseBlock = startToken;
|
||||
}
|
||||
|
||||
// If the if block does not have curly braces and does not end in a semicolon
|
||||
// and the else block starts with (, [, /, +, ` or -, then it is not
|
||||
// safe to remove the else keyword, because ASI will not add a semicolon
|
||||
// after the if block
|
||||
const ifBlockMaybeUnsafe = node.parent.consequent.type !== "BlockStatement" && lastIfToken.value !== ";";
|
||||
const elseBlockUnsafe = /^[([/+`-]/.test(firstTokenOfElseBlock.value);
|
||||
|
||||
if (ifBlockMaybeUnsafe && elseBlockUnsafe) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const endToken = sourceCode.getLastToken(node);
|
||||
const lastTokenOfElseBlock = sourceCode.getTokenBefore(endToken);
|
||||
|
||||
if (lastTokenOfElseBlock.value !== ";") {
|
||||
const nextToken = sourceCode.getTokenAfter(endToken);
|
||||
|
||||
const nextTokenUnsafe = nextToken && /^[([/+`-]/.test(nextToken.value);
|
||||
const nextTokenOnSameLine = nextToken && nextToken.loc.start.line === lastTokenOfElseBlock.loc.start.line;
|
||||
|
||||
// If the else block contents does not end in a semicolon,
|
||||
// and the else block starts with (, [, /, +, ` or -, then it is not
|
||||
// safe to remove the else block, because ASI will not add a semicolon
|
||||
// after the remaining else block contents
|
||||
if (nextTokenUnsafe || (nextTokenOnSameLine && nextToken.value !== "}")) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (startToken.type === "Punctuator" && startToken.value === "{") {
|
||||
fixedSource = source.slice(1, -1);
|
||||
} else {
|
||||
fixedSource = source;
|
||||
}
|
||||
|
||||
// Extend the replacement range to include the entire
|
||||
// function to avoid conflicting with no-useless-return.
|
||||
// https://github.com/eslint/eslint/issues/8026
|
||||
return new FixTracker(fixer, sourceCode)
|
||||
.retainEnclosingFunction(node)
|
||||
.replaceTextRange([elseToken.start, node.end], fixedSource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,35 +189,45 @@ module.exports = {
|
||||
return checkForReturnOrIf(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the if statement
|
||||
* @returns {void}
|
||||
* @param {Node} node The node for the if statement to check
|
||||
* @private
|
||||
*/
|
||||
function IfStatement(node) {
|
||||
const parent = context.getAncestors().pop();
|
||||
let consequents,
|
||||
alternate;
|
||||
|
||||
/*
|
||||
* Fixing this would require splitting one statement into two, so no error should
|
||||
* be reported if this node is in a position where only one statement is allowed.
|
||||
*/
|
||||
if (!astUtils.STATEMENT_LIST_PARENTS.has(parent.type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (consequents = []; node.type === "IfStatement"; node = node.alternate) {
|
||||
if (!node.alternate) {
|
||||
return;
|
||||
}
|
||||
consequents.push(node.consequent);
|
||||
alternate = node.alternate;
|
||||
}
|
||||
|
||||
if (consequents.every(alwaysReturns)) {
|
||||
displayReport(alternate);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public API
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
|
||||
IfStatement(node) {
|
||||
const parent = context.getAncestors().pop();
|
||||
let consequents,
|
||||
alternate;
|
||||
|
||||
// Only "top-level" if statements are checked, meaning the first `if`
|
||||
// in a `if-else-if-...` chain.
|
||||
if (parent.type === "IfStatement" && parent.alternate === node) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (consequents = []; node.type === "IfStatement"; node = node.alternate) {
|
||||
if (!node.alternate) {
|
||||
return;
|
||||
}
|
||||
consequents.push(node.consequent);
|
||||
alternate = node.alternate;
|
||||
}
|
||||
|
||||
if (consequents.every(alwaysReturns)) {
|
||||
displayReport(alternate);
|
||||
}
|
||||
}
|
||||
"IfStatement:exit": IfStatement
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -19,18 +25,6 @@ const ALLOW_OPTIONS = Object.freeze([
|
||||
"setters",
|
||||
"constructors"
|
||||
]);
|
||||
const SHOW_KIND = Object.freeze({
|
||||
functions: "function",
|
||||
arrowFunctions: "arrow function",
|
||||
generatorFunctions: "generator function",
|
||||
asyncFunctions: "async function",
|
||||
methods: "method",
|
||||
generatorMethods: "generator method",
|
||||
asyncMethods: "async method",
|
||||
getters: "getter",
|
||||
setters: "setter",
|
||||
constructors: "constructor"
|
||||
});
|
||||
|
||||
/**
|
||||
* Gets the kind of a given function node.
|
||||
@@ -137,6 +131,7 @@ module.exports = {
|
||||
*/
|
||||
function reportIfEmpty(node) {
|
||||
const kind = getKind(node);
|
||||
const name = astUtils.getFunctionNameWithKind(node);
|
||||
|
||||
if (allowed.indexOf(kind) === -1 &&
|
||||
node.body.type === "BlockStatement" &&
|
||||
@@ -146,10 +141,8 @@ module.exports = {
|
||||
context.report({
|
||||
node,
|
||||
loc: node.body.loc.start,
|
||||
message: "Unexpected empty {{kind}}.",
|
||||
data: {
|
||||
kind: SHOW_KIND[kind]
|
||||
}
|
||||
message: "Unexpected empty {{name}}.",
|
||||
data: { name }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,9 +60,9 @@ module.exports = {
|
||||
return;
|
||||
}
|
||||
|
||||
const affectsProto = lhs.object.computed ?
|
||||
lhs.object.property.type === "Literal" && lhs.object.property.value === "prototype" :
|
||||
lhs.object.property.name === "prototype";
|
||||
const affectsProto = lhs.object.computed
|
||||
? lhs.object.property.type === "Literal" && lhs.object.property.value === "prototype"
|
||||
: lhs.object.property.name === "prototype";
|
||||
|
||||
if (!affectsProto) {
|
||||
return;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const getPropertyName = require("../ast-utils").getStaticPropertyName;
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
@@ -44,8 +44,7 @@ module.exports = {
|
||||
loc: node.parent.property.loc.start,
|
||||
fix(fixer) {
|
||||
const firstTokenToRemove = context.getSourceCode()
|
||||
.getTokensBetween(node.parent.object, node.parent.property)
|
||||
.find(token => token.value !== ")");
|
||||
.getFirstTokenBetween(node.parent.object, node.parent.property, astUtils.isNotClosingParenToken);
|
||||
|
||||
return fixer.removeRange([firstTokenToRemove.range[0], node.parent.parent.range[1]]);
|
||||
}
|
||||
@@ -73,7 +72,7 @@ module.exports = {
|
||||
grandparent.arguments.length === 1 &&
|
||||
parent.type === "MemberExpression" &&
|
||||
parent.object === node &&
|
||||
getPropertyName(parent) === "bind"
|
||||
astUtils.getStaticPropertyName(parent) === "bind"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -91,7 +97,22 @@ module.exports = {
|
||||
context.report({
|
||||
node,
|
||||
message: "Redundant Boolean call.",
|
||||
fix: fixer => fixer.replaceText(node, sourceCode.getText(node.arguments[0]))
|
||||
fix: fixer => {
|
||||
if (!node.arguments.length) {
|
||||
return fixer.replaceText(parent, "true");
|
||||
}
|
||||
|
||||
if (node.arguments.length > 1 || node.arguments[0].type === "SpreadElement") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const argument = node.arguments[0];
|
||||
|
||||
if (astUtils.getPrecedence(argument) < astUtils.getPrecedence(node.parent)) {
|
||||
return fixer.replaceText(node, `(${sourceCode.getText(argument)})`);
|
||||
}
|
||||
return fixer.replaceText(node, sourceCode.getText(argument));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,12 +110,7 @@ module.exports = {
|
||||
node: labelNode,
|
||||
message: "This label '{{name}}' is unnecessary.",
|
||||
data: labelNode,
|
||||
fix(fixer) {
|
||||
return fixer.replaceTextRange(
|
||||
[info.label.range[0], labelNode.range[1]],
|
||||
sourceCode.text.slice(info.label.parent.body.range[0], sourceCode.getFirstToken(node).range[1])
|
||||
);
|
||||
}
|
||||
fix: fixer => fixer.removeRange([sourceCode.getFirstToken(node).range[1], labelNode.range[1]])
|
||||
});
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -44,7 +44,8 @@ module.exports = {
|
||||
properties: {
|
||||
conditionalAssign: { type: "boolean" },
|
||||
nestedBinaryExpressions: { type: "boolean" },
|
||||
returnAssign: { type: "boolean" }
|
||||
returnAssign: { type: "boolean" },
|
||||
ignoreJSX: { enum: ["none", "all", "single-line", "multi-line"] }
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
@@ -59,12 +60,16 @@ module.exports = {
|
||||
create(context) {
|
||||
const sourceCode = context.getSourceCode();
|
||||
|
||||
const tokensToIgnore = new WeakSet();
|
||||
const isParenthesised = astUtils.isParenthesised.bind(astUtils, sourceCode);
|
||||
const precedence = astUtils.getPrecedence;
|
||||
const ALL_NODES = context.options[0] !== "functions";
|
||||
const EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false;
|
||||
const NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false;
|
||||
const EXCEPT_RETURN_ASSIGN = ALL_NODES && context.options[1] && context.options[1].returnAssign === false;
|
||||
const IGNORE_JSX = ALL_NODES && context.options[1] && context.options[1].ignoreJSX;
|
||||
const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" });
|
||||
const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" });
|
||||
|
||||
/**
|
||||
* Determines if this rule should be enforced for a node given the current configuration.
|
||||
@@ -73,6 +78,31 @@ module.exports = {
|
||||
* @private
|
||||
*/
|
||||
function ruleApplies(node) {
|
||||
if (node.type === "JSXElement") {
|
||||
const isSingleLine = node.loc.start.line === node.loc.end.line;
|
||||
|
||||
switch (IGNORE_JSX) {
|
||||
|
||||
// Exclude this JSX element from linting
|
||||
case "all":
|
||||
return false;
|
||||
|
||||
// Exclude this JSX element if it is multi-line element
|
||||
case "multi-line":
|
||||
return isSingleLine;
|
||||
|
||||
// Exclude this JSX element if it is single-line element
|
||||
case "single-line":
|
||||
return !isSingleLine;
|
||||
|
||||
// Nothing special to be done for JSX elements
|
||||
case "none":
|
||||
break;
|
||||
|
||||
// no default
|
||||
}
|
||||
}
|
||||
|
||||
return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
|
||||
}
|
||||
|
||||
@@ -87,8 +117,8 @@ module.exports = {
|
||||
nextToken = sourceCode.getTokenAfter(node, 1);
|
||||
|
||||
return isParenthesised(node) && previousToken && nextToken &&
|
||||
previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
|
||||
nextToken.value === ")" && nextToken.range[0] >= node.range[1];
|
||||
astUtils.isOpeningParenToken(previousToken) && previousToken.range[1] <= node.range[0] &&
|
||||
astUtils.isClosingParenToken(nextToken) && nextToken.range[0] >= node.range[1];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,6 +170,19 @@ module.exports = {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a constructor function is newed-up with parens
|
||||
* @param {ASTNode} newExpression - The NewExpression node to be checked.
|
||||
* @returns {boolean} True if the constructor is called with parens.
|
||||
* @private
|
||||
*/
|
||||
function isNewExpressionWithParens(newExpression) {
|
||||
const lastToken = sourceCode.getLastToken(newExpression);
|
||||
const penultimateToken = sourceCode.getTokenBefore(lastToken);
|
||||
|
||||
return newExpression.arguments.length > 0 || astUtils.isOpeningParenToken(penultimateToken) && astUtils.isClosingParenToken(lastToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a node is or contains an assignment expression
|
||||
* @param {ASTNode} node - The node to be checked.
|
||||
@@ -175,9 +218,9 @@ module.exports = {
|
||||
return node.argument && containsAssignment(node.argument);
|
||||
} else if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
|
||||
return containsAssignment(node.body);
|
||||
} else {
|
||||
return containsAssignment(node);
|
||||
}
|
||||
return containsAssignment(node);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -196,69 +239,6 @@ module.exports = {
|
||||
return hasDoubleExcessParens(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a given node is located at the head of ExpressionStatement.
|
||||
* @param {ASTNode} node - A node to check.
|
||||
* @returns {boolean} `true` if the node is located at the head of ExpressionStatement.
|
||||
*/
|
||||
function isHeadOfExpressionStatement(node) {
|
||||
let parent = node.parent;
|
||||
|
||||
while (parent) {
|
||||
switch (parent.type) {
|
||||
case "SequenceExpression":
|
||||
if (parent.expressions[0] !== node || isParenthesised(node)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case "UnaryExpression":
|
||||
case "UpdateExpression":
|
||||
if (parent.prefix || isParenthesised(node)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case "BinaryExpression":
|
||||
case "LogicalExpression":
|
||||
if (parent.left !== node || isParenthesised(node)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case "ConditionalExpression":
|
||||
if (parent.test !== node || isParenthesised(node)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case "CallExpression":
|
||||
if (parent.callee !== node || isParenthesised(node)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case "MemberExpression":
|
||||
if (parent.object !== node || isParenthesised(node)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case "ExpressionStatement":
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
node = parent;
|
||||
parent = parent.parent;
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
throw new Error("unreachable");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a node should be preceded by an additional space when removing parens
|
||||
* @param {ASTNode} node node to evaluate; must be surrounded by parentheses
|
||||
@@ -276,7 +256,7 @@ module.exports = {
|
||||
}
|
||||
|
||||
// If the parens are preceded by a keyword (e.g. `typeof(0)`), a space should be inserted (`typeof 0`)
|
||||
const precededByKeyword = tokenBeforeLeftParen.type === "Keyword";
|
||||
const precededByIdentiferPart = esUtils.code.isIdentifierPartES6(tokenBeforeLeftParen.value.slice(-1).charCodeAt(0));
|
||||
|
||||
// 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[]`
|
||||
@@ -289,7 +269,7 @@ module.exports = {
|
||||
const startsWithUnaryPlus = firstToken.type === "Punctuator" && firstToken.value === "+";
|
||||
const startsWithUnaryMinus = firstToken.type === "Punctuator" && firstToken.value === "-";
|
||||
|
||||
return (precededByKeyword && startsWithIdentifierPart) ||
|
||||
return (precededByIdentiferPart && startsWithIdentifierPart) ||
|
||||
(precededByUnaryPlus && startsWithUnaryPlus) ||
|
||||
(precededByUnaryMinus && startsWithUnaryMinus);
|
||||
}
|
||||
@@ -304,6 +284,10 @@ module.exports = {
|
||||
const leftParenToken = sourceCode.getTokenBefore(node);
|
||||
const rightParenToken = sourceCode.getTokenAfter(node);
|
||||
|
||||
if (tokensToIgnore.has(sourceCode.getFirstToken(node)) && !isParenthesisedTwice(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node,
|
||||
loc: leftParenToken.loc.start,
|
||||
@@ -325,7 +309,11 @@ module.exports = {
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function dryUnaryUpdate(node) {
|
||||
function checkUnaryUpdate(node) {
|
||||
if (node.type === "UnaryExpression" && node.argument.type === "BinaryExpression" && node.argument.operator === "**") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasExcessParens(node.argument) && precedence(node.argument) >= precedence(node)) {
|
||||
report(node.argument);
|
||||
}
|
||||
@@ -337,10 +325,11 @@ module.exports = {
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function dryCallNew(node) {
|
||||
function checkCallNew(node) {
|
||||
if (hasExcessParens(node.callee) && precedence(node.callee) >= precedence(node) && !(
|
||||
node.type === "CallExpression" &&
|
||||
node.callee.type === "FunctionExpression" &&
|
||||
(node.callee.type === "FunctionExpression" ||
|
||||
node.callee.type === "NewExpression" && !isNewExpressionWithParens(node.callee)) &&
|
||||
|
||||
// One set of parentheses are allowed for a function expression
|
||||
!hasDoubleExcessParens(node.callee)
|
||||
@@ -348,12 +337,12 @@ module.exports = {
|
||||
report(node.callee);
|
||||
}
|
||||
if (node.arguments.length === 1) {
|
||||
if (hasDoubleExcessParens(node.arguments[0]) && precedence(node.arguments[0]) >= precedence({ type: "AssignmentExpression" })) {
|
||||
if (hasDoubleExcessParens(node.arguments[0]) && precedence(node.arguments[0]) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
|
||||
report(node.arguments[0]);
|
||||
}
|
||||
} else {
|
||||
[].forEach.call(node.arguments, arg => {
|
||||
if (hasExcessParens(arg) && precedence(arg) >= precedence({ type: "AssignmentExpression" })) {
|
||||
if (hasExcessParens(arg) && precedence(arg) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
|
||||
report(arg);
|
||||
}
|
||||
});
|
||||
@@ -366,23 +355,91 @@ module.exports = {
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function dryBinaryLogical(node) {
|
||||
function checkBinaryLogical(node) {
|
||||
const prec = precedence(node);
|
||||
const shouldSkipLeft = NESTED_BINARY && (node.left.type === "BinaryExpression" || node.left.type === "LogicalExpression");
|
||||
const leftPrecedence = precedence(node.left);
|
||||
const rightPrecedence = precedence(node.right);
|
||||
const isExponentiation = node.operator === "**";
|
||||
const shouldSkipLeft = (NESTED_BINARY && (node.left.type === "BinaryExpression" || node.left.type === "LogicalExpression")) ||
|
||||
node.left.type === "UnaryExpression" && isExponentiation;
|
||||
const shouldSkipRight = NESTED_BINARY && (node.right.type === "BinaryExpression" || node.right.type === "LogicalExpression");
|
||||
|
||||
if (!shouldSkipLeft && hasExcessParens(node.left) && precedence(node.left) >= prec) {
|
||||
if (!shouldSkipLeft && hasExcessParens(node.left) && (leftPrecedence > prec || (leftPrecedence === prec && !isExponentiation))) {
|
||||
report(node.left);
|
||||
}
|
||||
if (!shouldSkipRight && hasExcessParens(node.right) && precedence(node.right) > prec) {
|
||||
if (!shouldSkipRight && hasExcessParens(node.right) && (rightPrecedence > prec || (rightPrecedence === prec && isExponentiation))) {
|
||||
report(node.right);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the parentheses around the super class of the given class definition.
|
||||
* @param {ASTNode} node The node of class declarations to check.
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkClass(node) {
|
||||
if (!node.superClass) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If `node.superClass` is a LeftHandSideExpression, parentheses are extra.
|
||||
// Otherwise, parentheses are needed.
|
||||
const hasExtraParens = precedence(node.superClass) > PRECEDENCE_OF_UPDATE_EXPR
|
||||
? hasExcessParens(node.superClass)
|
||||
: hasDoubleExcessParens(node.superClass);
|
||||
|
||||
if (hasExtraParens) {
|
||||
report(node.superClass);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the parentheses around the argument of the given spread operator.
|
||||
* @param {ASTNode} node The node of spread elements/properties to check.
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkSpreadOperator(node) {
|
||||
const hasExtraParens = precedence(node.argument) >= PRECEDENCE_OF_ASSIGNMENT_EXPR
|
||||
? hasExcessParens(node.argument)
|
||||
: hasDoubleExcessParens(node.argument);
|
||||
|
||||
if (hasExtraParens) {
|
||||
report(node.argument);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the parentheses for an ExpressionStatement or ExportDefaultDeclaration
|
||||
* @param {ASTNode} node The ExpressionStatement.expression or ExportDefaultDeclaration.declaration node
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkExpressionOrExportStatement(node) {
|
||||
const firstToken = isParenthesised(node) ? sourceCode.getTokenBefore(node) : sourceCode.getFirstToken(node);
|
||||
const secondToken = sourceCode.getTokenAfter(firstToken, astUtils.isNotOpeningParenToken);
|
||||
|
||||
if (
|
||||
astUtils.isOpeningParenToken(firstToken) &&
|
||||
(
|
||||
astUtils.isOpeningBraceToken(secondToken) ||
|
||||
secondToken.type === "Keyword" && (
|
||||
secondToken.value === "function" ||
|
||||
secondToken.value === "class" ||
|
||||
secondToken.value === "let" && astUtils.isOpeningBracketToken(sourceCode.getTokenAfter(secondToken))
|
||||
)
|
||||
)
|
||||
) {
|
||||
tokensToIgnore.add(secondToken);
|
||||
}
|
||||
|
||||
if (hasExcessParens(node)) {
|
||||
report(node);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ArrayExpression(node) {
|
||||
[].forEach.call(node.elements, e => {
|
||||
if (e && hasExcessParens(e) && precedence(e) >= precedence({ type: "AssignmentExpression" })) {
|
||||
if (e && hasExcessParens(e) && precedence(e) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
|
||||
report(e);
|
||||
}
|
||||
});
|
||||
@@ -394,13 +451,13 @@ module.exports = {
|
||||
}
|
||||
|
||||
if (node.body.type !== "BlockStatement") {
|
||||
if (sourceCode.getFirstToken(node.body).value !== "{" && hasExcessParens(node.body) && precedence(node.body) >= precedence({ type: "AssignmentExpression" })) {
|
||||
report(node.body);
|
||||
return;
|
||||
}
|
||||
const firstBodyToken = sourceCode.getFirstToken(node.body, astUtils.isNotOpeningParenToken);
|
||||
const tokenBeforeFirst = sourceCode.getTokenBefore(firstBodyToken);
|
||||
|
||||
// Object literals *must* be parenthesised
|
||||
if (node.body.type === "ObjectExpression" && hasDoubleExcessParens(node.body)) {
|
||||
if (astUtils.isOpeningParenToken(tokenBeforeFirst) && astUtils.isOpeningBraceToken(firstBodyToken)) {
|
||||
tokensToIgnore.add(firstBodyToken);
|
||||
}
|
||||
if (hasExcessParens(node.body) && precedence(node.body) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
|
||||
report(node.body);
|
||||
}
|
||||
}
|
||||
@@ -416,8 +473,8 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
BinaryExpression: dryBinaryLogical,
|
||||
CallExpression: dryCallNew,
|
||||
BinaryExpression: checkBinaryLogical,
|
||||
CallExpression: checkCallNew,
|
||||
|
||||
ConditionalExpression(node) {
|
||||
if (isReturnAssignException(node)) {
|
||||
@@ -428,11 +485,11 @@ module.exports = {
|
||||
report(node.test);
|
||||
}
|
||||
|
||||
if (hasExcessParens(node.consequent) && precedence(node.consequent) >= precedence({ type: "AssignmentExpression" })) {
|
||||
if (hasExcessParens(node.consequent) && precedence(node.consequent) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
|
||||
report(node.consequent);
|
||||
}
|
||||
|
||||
if (hasExcessParens(node.alternate) && precedence(node.alternate) >= precedence({ type: "AssignmentExpression" })) {
|
||||
if (hasExcessParens(node.alternate) && precedence(node.alternate) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
|
||||
report(node.alternate);
|
||||
}
|
||||
},
|
||||
@@ -443,27 +500,8 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
ExpressionStatement(node) {
|
||||
if (hasExcessParens(node.expression)) {
|
||||
const firstTokens = sourceCode.getFirstTokens(node.expression, 2);
|
||||
const firstToken = firstTokens[0];
|
||||
const secondToken = firstTokens[1];
|
||||
|
||||
if (
|
||||
!firstToken ||
|
||||
firstToken.value !== "{" &&
|
||||
firstToken.value !== "function" &&
|
||||
firstToken.value !== "class" &&
|
||||
(
|
||||
firstToken.value !== "let" ||
|
||||
!secondToken ||
|
||||
secondToken.value !== "["
|
||||
)
|
||||
) {
|
||||
report(node.expression);
|
||||
}
|
||||
}
|
||||
},
|
||||
ExportDefaultDeclaration: node => checkExpressionOrExportStatement(node.declaration),
|
||||
ExpressionStatement: node => checkExpressionOrExportStatement(node.expression),
|
||||
|
||||
ForInStatement(node) {
|
||||
if (hasExcessParens(node.right)) {
|
||||
@@ -497,7 +535,7 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
LogicalExpression: dryBinaryLogical,
|
||||
LogicalExpression: checkBinaryLogical,
|
||||
|
||||
MemberExpression(node) {
|
||||
if (
|
||||
@@ -506,19 +544,11 @@ module.exports = {
|
||||
(
|
||||
node.computed ||
|
||||
!(
|
||||
(node.object.type === "Literal" &&
|
||||
typeof node.object.value === "number" &&
|
||||
astUtils.isDecimalInteger(node.object))
|
||||
||
|
||||
astUtils.isDecimalInteger(node.object) ||
|
||||
|
||||
// RegExp literal is allowed to have parens (#1589)
|
||||
(node.object.type === "Literal" && node.object.regex)
|
||||
)
|
||||
) &&
|
||||
!(
|
||||
(node.object.type === "FunctionExpression" || node.object.type === "ClassExpression") &&
|
||||
isHeadOfExpressionStatement(node) &&
|
||||
!hasDoubleExcessParens(node.object)
|
||||
)
|
||||
) {
|
||||
report(node.object);
|
||||
@@ -528,13 +558,13 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
NewExpression: dryCallNew,
|
||||
NewExpression: checkCallNew,
|
||||
|
||||
ObjectExpression(node) {
|
||||
[].forEach.call(node.properties, e => {
|
||||
const v = e.value;
|
||||
|
||||
if (v && hasExcessParens(v) && precedence(v) >= precedence({ type: "AssignmentExpression" })) {
|
||||
if (v && hasExcessParens(v) && precedence(v) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
|
||||
report(v);
|
||||
}
|
||||
});
|
||||
@@ -584,13 +614,13 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
UnaryExpression: dryUnaryUpdate,
|
||||
UpdateExpression: dryUnaryUpdate,
|
||||
AwaitExpression: dryUnaryUpdate,
|
||||
UnaryExpression: checkUnaryUpdate,
|
||||
UpdateExpression: checkUnaryUpdate,
|
||||
AwaitExpression: checkUnaryUpdate,
|
||||
|
||||
VariableDeclarator(node) {
|
||||
if (node.init && hasExcessParens(node.init) &&
|
||||
precedence(node.init) >= precedence({ type: "AssignmentExpression" }) &&
|
||||
precedence(node.init) >= PRECEDENCE_OF_ASSIGNMENT_EXPR &&
|
||||
|
||||
// RegExp literal is allowed to have parens (#1589)
|
||||
!(node.init.type === "Literal" && node.init.regex)) {
|
||||
@@ -620,7 +650,14 @@ module.exports = {
|
||||
report(node.argument);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
ClassDeclaration: checkClass,
|
||||
ClassExpression: checkClass,
|
||||
|
||||
SpreadElement: checkSpreadOperator,
|
||||
SpreadProperty: checkSpreadOperator,
|
||||
ExperimentalSpreadProperty: checkSpreadOperator
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const FixTracker = require("../util/fix-tracker");
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -34,7 +41,13 @@ module.exports = {
|
||||
node: nodeOrToken,
|
||||
message: "Unnecessary semicolon.",
|
||||
fix(fixer) {
|
||||
return fixer.remove(nodeOrToken);
|
||||
|
||||
// Expand the replacement range to include the surrounding
|
||||
// tokens to avoid conflicting with semi.
|
||||
// https://github.com/eslint/eslint/issues/7928
|
||||
return new FixTracker(fixer, context.getSourceCode())
|
||||
.retainSurroundingTokens(nodeOrToken)
|
||||
.remove(nodeOrToken);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -48,10 +61,10 @@ module.exports = {
|
||||
*/
|
||||
function checkForPartOfClassBody(firstToken) {
|
||||
for (let token = firstToken;
|
||||
token.type === "Punctuator" && token.value !== "}";
|
||||
token.type === "Punctuator" && !astUtils.isClosingBraceToken(token);
|
||||
token = sourceCode.getTokenAfter(token)
|
||||
) {
|
||||
if (token.value === ";") {
|
||||
if (astUtils.isSemicolonToken(token)) {
|
||||
report(token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ module.exports = {
|
||||
docs: {
|
||||
description: "disallow assignments to native objects or read-only global variables",
|
||||
category: "Best Practices",
|
||||
recommended: false
|
||||
recommended: true
|
||||
},
|
||||
|
||||
schema: [
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"use strict";
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
const esUtils = require("esutils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
@@ -197,19 +198,31 @@ module.exports = {
|
||||
*/
|
||||
function report(node, recommendation, shouldFix) {
|
||||
shouldFix = typeof shouldFix === "undefined" ? true : shouldFix;
|
||||
const reportObj = {
|
||||
|
||||
context.report({
|
||||
node,
|
||||
message: "use `{{recommendation}}` instead.",
|
||||
data: {
|
||||
recommendation
|
||||
},
|
||||
fix(fixer) {
|
||||
if (!shouldFix) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tokenBefore = sourceCode.getTokenBefore(node);
|
||||
|
||||
if (
|
||||
tokenBefore &&
|
||||
tokenBefore.range[1] === node.range[0] &&
|
||||
esUtils.code.isIdentifierPartES6(tokenBefore.value.slice(-1).charCodeAt(0)) &&
|
||||
esUtils.code.isIdentifierPartES6(recommendation.charCodeAt(0))
|
||||
) {
|
||||
return fixer.replaceText(node, ` ${recommendation}`);
|
||||
}
|
||||
return fixer.replaceText(node, recommendation);
|
||||
}
|
||||
};
|
||||
|
||||
if (shouldFix) {
|
||||
reportObj.fix = fixer => fixer.replaceText(node, recommendation);
|
||||
}
|
||||
|
||||
context.report(reportObj);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -64,10 +64,10 @@ module.exports = {
|
||||
|
||||
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")
|
||||
} });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,8 @@ module.exports = {
|
||||
} catch (e) {
|
||||
context.report({
|
||||
node,
|
||||
message: `${e.message}.`
|
||||
message: "{{message}}.",
|
||||
data: e
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Constants
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -13,7 +19,7 @@
|
||||
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 IRREGULAR_LINE_TERMINATORS = /[\u2028\u2029]/mg;
|
||||
const LINE_BREAK = /\r\n|\r|\n|\u2028|\u2029/g;
|
||||
const LINE_BREAK = astUtils.createGlobalLinebreakMatcher();
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
|
||||
@@ -32,22 +32,22 @@ module.exports = {
|
||||
* @returns {void}
|
||||
*/
|
||||
function report(node) {
|
||||
const parent = context.getAncestors().pop();
|
||||
const message = node.parent.type === "BlockStatement" ? "Nested block is redundant." : "Block is redundant.";
|
||||
|
||||
context.report({ node, message: parent.type === "Program" ?
|
||||
"Block is redundant." :
|
||||
"Nested block is redundant."
|
||||
});
|
||||
context.report({ node, message });
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for any ocurrence of BlockStatement > BlockStatement or Program > BlockStatement
|
||||
* @returns {boolean} True if the current node is a lone block.
|
||||
* Checks for any ocurrence of a BlockStatement in a place where lists of statements can appear
|
||||
* @param {ASTNode} node The node to check
|
||||
* @returns {boolean} True if the node is a lone block.
|
||||
*/
|
||||
function isLoneBlock() {
|
||||
const parent = context.getAncestors().pop();
|
||||
function isLoneBlock(node) {
|
||||
return node.parent.type === "BlockStatement" ||
|
||||
node.parent.type === "Program" ||
|
||||
|
||||
return parent.type === "BlockStatement" || parent.type === "Program";
|
||||
// Don't report blocks in switch cases if the block is the only statement of the case.
|
||||
node.parent.type === "SwitchCase" && !(node.parent.consequent[0] === node && node.parent.consequent.length === 1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -148,13 +148,7 @@ module.exports = {
|
||||
* @returns {Token} The operator token of the node.
|
||||
*/
|
||||
function getOperatorToken(node) {
|
||||
let token = sourceCode.getTokenAfter(node.left);
|
||||
|
||||
while (token.value === ")") {
|
||||
token = sourceCode.getTokenAfter(token);
|
||||
}
|
||||
|
||||
return token;
|
||||
return sourceCode.getTokenAfter(node.left, astUtils.isNotClosingParenToken);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -153,11 +153,11 @@ module.exports = {
|
||||
|
||||
// "var utils = require('./utils');"
|
||||
return REQ_FILE;
|
||||
} else {
|
||||
|
||||
// "var async = require('async');"
|
||||
return REQ_MODULE;
|
||||
}
|
||||
|
||||
// "var async = require('async');"
|
||||
return REQ_MODULE;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @fileoverview Rule to check use of chained assignment expressions
|
||||
* @author Stewart Rand
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
docs: {
|
||||
description: "disallow use of chained assignment expressions",
|
||||
category: "Stylistic Issues",
|
||||
recommended: false
|
||||
},
|
||||
schema: []
|
||||
},
|
||||
|
||||
create(context) {
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
AssignmentExpression(node) {
|
||||
if (["AssignmentExpression", "VariableDeclarator"].indexOf(node.parent.type) !== -1) {
|
||||
context.report({
|
||||
node,
|
||||
message: "Unexpected chained assignment."
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -93,7 +95,8 @@ module.exports = {
|
||||
const sourceCode = context.getSourceCode(),
|
||||
source = sourceCode.getText(),
|
||||
allComments = sourceCode.getAllComments(),
|
||||
pattern = /[^\n\r\u2028\u2029\t ].? {2,}/g; // note: repeating space
|
||||
JOINED_LINEBEAKS = Array.from(astUtils.LINEBREAKS).join(""),
|
||||
pattern = new RegExp(String.raw`[^ \t${JOINED_LINEBEAKS}].? {2,}`, "g"); // note: repeating space
|
||||
let parent;
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -39,9 +45,7 @@ module.exports = {
|
||||
return {
|
||||
|
||||
Literal(node) {
|
||||
const lineBreak = /\n/;
|
||||
|
||||
if (lineBreak.test(node.raw) && !isJSXElement(node.parent)) {
|
||||
if (astUtils.LINEBREAK_MATCHER.test(node.raw) && !isJSXElement(node.parent)) {
|
||||
context.report({ node, message: "Multiline support is limited to browsers supporting ES5 only." });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -114,8 +112,8 @@ module.exports = {
|
||||
data: { max: maxAllowed, pluralizedLines: maxAllowed === 1 ? "line" : "lines" },
|
||||
fix(fixer) {
|
||||
return fixer.removeRange([
|
||||
astUtils.getRangeIndexFromLocation(sourceCode, { line: lastLineNumber + 1, column: 0 }),
|
||||
astUtils.getRangeIndexFromLocation(sourceCode, { line: lineNumber - maxAllowed, column: 0 })
|
||||
sourceCode.getIndexFromLoc({ line: lastLineNumber + 1, column: 0 }),
|
||||
sourceCode.getIndexFromLoc({ line: lineNumber - maxAllowed, column: 0 })
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ module.exports = {
|
||||
docs: {
|
||||
description: "disallow assignments to native objects or read-only global variables",
|
||||
category: "Best Practices",
|
||||
recommended: true,
|
||||
recommended: false,
|
||||
replacedBy: ["no-global-assign"]
|
||||
},
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ module.exports = {
|
||||
docs: {
|
||||
description: "disallow negating the left operand in `in` expressions",
|
||||
category: "Possible Errors",
|
||||
recommended: true,
|
||||
recommended: false,
|
||||
replacedBy: ["no-unsafe-negation"]
|
||||
},
|
||||
deprecated: true,
|
||||
|
||||
@@ -27,20 +27,18 @@ module.exports = {
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Checks if the callee is the Function constructor, and if so, reports an issue.
|
||||
* @param {ASTNode} node The node to check and report on
|
||||
* Reports a node.
|
||||
* @param {ASTNode} node The node to report
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function validateCallee(node) {
|
||||
if (node.callee.name === "Function") {
|
||||
context.report({ node, message: "The Function constructor is eval." });
|
||||
}
|
||||
function report(node) {
|
||||
context.report({ node, message: "The Function constructor is eval." });
|
||||
}
|
||||
|
||||
return {
|
||||
NewExpression: validateCallee,
|
||||
CallExpression: validateCallee
|
||||
"NewExpression[callee.name = 'Function']": report,
|
||||
"CallExpression[callee.name = 'Function']": report
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -24,12 +24,8 @@ module.exports = {
|
||||
create(context) {
|
||||
|
||||
return {
|
||||
|
||||
ExpressionStatement(node) {
|
||||
|
||||
if (node.expression.type === "NewExpression") {
|
||||
context.report({ node, message: "Do not use 'new' for side effects." });
|
||||
}
|
||||
"ExpressionStatement > NewExpression"(node) {
|
||||
context.report({ node: node.parent, message: "Do not use 'new' for side effects." });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -20,17 +20,40 @@ module.exports = {
|
||||
|
||||
schema: [
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
props: { type: "boolean" }
|
||||
},
|
||||
additionalProperties: false
|
||||
oneOf: [
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
props: {
|
||||
enum: [false]
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
props: {
|
||||
enum: [true]
|
||||
},
|
||||
ignorePropertyModificationsFor: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string"
|
||||
},
|
||||
uniqueItems: true
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const props = context.options[0] && Boolean(context.options[0].props);
|
||||
const ignoredPropertyAssignmentsFor = context.options[0] && context.options[0].ignorePropertyModificationsFor || [];
|
||||
|
||||
/**
|
||||
* Checks whether or not the reference modifies properties of its variable.
|
||||
@@ -73,8 +96,15 @@ module.exports = {
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// EXCLUDES: e.g. ({ [foo]: a }) = bar;
|
||||
case "Property":
|
||||
if (parent.key === node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// no default
|
||||
}
|
||||
|
||||
node = parent;
|
||||
@@ -103,7 +133,7 @@ module.exports = {
|
||||
) {
|
||||
if (reference.isWrite()) {
|
||||
context.report({ node: identifier, message: "Assignment to function parameter '{{name}}'.", data: { name: identifier.name } });
|
||||
} else if (props && isModifyingProp(reference)) {
|
||||
} else if (props && isModifyingProp(reference) && ignoredPropertyAssignmentsFor.indexOf(identifier.name) === -1) {
|
||||
context.report({ node: identifier, message: "Assignment to property of function parameter '{{name}}'.", data: { name: identifier.name } });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,17 +26,9 @@ module.exports = {
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
|
||||
CallExpression(node) {
|
||||
const callee = node.callee;
|
||||
|
||||
if (callee.type === "MemberExpression" && callee.object.name === "process" &&
|
||||
callee.property.name === "exit"
|
||||
) {
|
||||
context.report({ node, message: "Don't use process.exit(); throw an error instead." });
|
||||
}
|
||||
"CallExpression > MemberExpression.callee[object.name = 'process'][property.name = 'exit']"(node) {
|
||||
context.report({ node: node.parent, message: "Don't use process.exit(); throw an error instead." });
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Loaded 100 of 374 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user