mirror of https://github.com/lukechilds/node.git
Browse Source
Update ESLint and configuration to version 4.0.0. Backport-PR-URL: https://github.com/nodejs/node/pull/14340 PR-URL: https://github.com/nodejs/node/pull/13645 Reviewed-By: Teddy Katz <teddy.katz@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: Sam Roberts <vieuxtech@gmail.com> Reviewed-By: Alexey Orlenko <eaglexrlnk@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>v6.x
Rich Trott
8 years ago
committed by
Myles Borins
1031 changed files with 36736 additions and 42098 deletions
File diff suppressed because it is too large
@ -0,0 +1,15 @@ |
|||
{ |
|||
"type": "object", |
|||
"properties": { |
|||
"root": { "type": "boolean" }, |
|||
"globals": { "type": ["object"] }, |
|||
"parser": { "type": ["string", "null"] }, |
|||
"env": { "type": "object" }, |
|||
"plugins": { "type": ["array"] }, |
|||
"settings": { "type": "object" }, |
|||
"extends": { "type": ["string", "array"] }, |
|||
"rules": { "type": "object" }, |
|||
"parserOptions": { "type": "object" } |
|||
}, |
|||
"additionalProperties": false |
|||
} |
@ -0,0 +1,33 @@ |
|||
/** |
|||
* @fileoverview Default config options |
|||
* @author Teddy Katz |
|||
*/ |
|||
|
|||
"use strict"; |
|||
|
|||
/** |
|||
* Freezes an object and all its nested properties |
|||
* @param {Object} obj The object to deeply freeze |
|||
* @returns {Object} `obj` after freezing it |
|||
*/ |
|||
function deepFreeze(obj) { |
|||
if (obj === null || typeof obj !== "object") { |
|||
return obj; |
|||
} |
|||
|
|||
Object.keys(obj).map(key => obj[key]).forEach(deepFreeze); |
|||
return Object.freeze(obj); |
|||
} |
|||
|
|||
module.exports = deepFreeze({ |
|||
env: {}, |
|||
globals: {}, |
|||
rules: {}, |
|||
settings: {}, |
|||
parser: "espree", |
|||
parserOptions: { |
|||
ecmaVersion: 5, |
|||
sourceType: "script", |
|||
ecmaFeatures: {} |
|||
} |
|||
}); |
@ -0,0 +1,235 @@ |
|||
/** |
|||
* @fileoverview Rule to enforce linebreaks after open and before close array brackets |
|||
* @author Jan Peer Stöcklmair <https://github.com/JPeer264>
|
|||
*/ |
|||
|
|||
"use strict"; |
|||
|
|||
const astUtils = require("../ast-utils"); |
|||
|
|||
//------------------------------------------------------------------------------
|
|||
// Rule Definition
|
|||
//------------------------------------------------------------------------------
|
|||
|
|||
module.exports = { |
|||
meta: { |
|||
docs: { |
|||
description: "enforce linebreaks after opening and before closing array brackets", |
|||
category: "Stylistic Issues", |
|||
recommended: false |
|||
}, |
|||
fixable: "whitespace", |
|||
schema: [ |
|||
{ |
|||
oneOf: [ |
|||
{ |
|||
enum: ["always", "never"] |
|||
}, |
|||
{ |
|||
type: "object", |
|||
properties: { |
|||
multiline: { |
|||
type: "boolean" |
|||
}, |
|||
minItems: { |
|||
type: ["integer", "null"], |
|||
minimum: 0 |
|||
} |
|||
}, |
|||
additionalProperties: false |
|||
} |
|||
] |
|||
} |
|||
] |
|||
}, |
|||
|
|||
create(context) { |
|||
const sourceCode = context.getSourceCode(); |
|||
|
|||
|
|||
//----------------------------------------------------------------------
|
|||
// Helpers
|
|||
//----------------------------------------------------------------------
|
|||
|
|||
/** |
|||
* Normalizes a given option value. |
|||
* |
|||
* @param {string|Object|undefined} option - An option value to parse. |
|||
* @returns {{multiline: boolean, minItems: number}} Normalized option object. |
|||
*/ |
|||
function normalizeOptionValue(option) { |
|||
let multiline = false; |
|||
let minItems = 0; |
|||
|
|||
if (option) { |
|||
if (option === "always" || option.minItems === 0) { |
|||
minItems = 0; |
|||
} else if (option === "never") { |
|||
minItems = Number.POSITIVE_INFINITY; |
|||
} else { |
|||
multiline = Boolean(option.multiline); |
|||
minItems = option.minItems || Number.POSITIVE_INFINITY; |
|||
} |
|||
} else { |
|||
multiline = true; |
|||
minItems = Number.POSITIVE_INFINITY; |
|||
} |
|||
|
|||
return { multiline, minItems }; |
|||
} |
|||
|
|||
/** |
|||
* Normalizes a given option value. |
|||
* |
|||
* @param {string|Object|undefined} options - An option value to parse. |
|||
* @returns {{ArrayExpression: {multiline: boolean, minItems: number}, ArrayPattern: {multiline: boolean, minItems: number}}} Normalized option object. |
|||
*/ |
|||
function normalizeOptions(options) { |
|||
const value = normalizeOptionValue(options); |
|||
|
|||
return { ArrayExpression: value, ArrayPattern: value }; |
|||
} |
|||
|
|||
/** |
|||
* Reports that there shouldn't be a linebreak after the first token |
|||
* @param {ASTNode} node - The node to report in the event of an error. |
|||
* @param {Token} token - The token to use for the report. |
|||
* @returns {void} |
|||
*/ |
|||
function reportNoBeginningLinebreak(node, token) { |
|||
context.report({ |
|||
node, |
|||
loc: token.loc, |
|||
message: "There should be no linebreak after '['.", |
|||
fix(fixer) { |
|||
const nextToken = sourceCode.getTokenAfter(token, { includeComments: true }); |
|||
|
|||
if (astUtils.isCommentToken(nextToken)) { |
|||
return null; |
|||
} |
|||
|
|||
return fixer.removeRange([token.range[1], nextToken.range[0]]); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Reports that there shouldn't be a linebreak before the last token |
|||
* @param {ASTNode} node - The node to report in the event of an error. |
|||
* @param {Token} token - The token to use for the report. |
|||
* @returns {void} |
|||
*/ |
|||
function reportNoEndingLinebreak(node, token) { |
|||
context.report({ |
|||
node, |
|||
loc: token.loc, |
|||
message: "There should be no linebreak before ']'.", |
|||
fix(fixer) { |
|||
const previousToken = sourceCode.getTokenBefore(token, { includeComments: true }); |
|||
|
|||
if (astUtils.isCommentToken(previousToken)) { |
|||
return null; |
|||
} |
|||
|
|||
return fixer.removeRange([previousToken.range[1], token.range[0]]); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Reports that there should be a linebreak after the first token |
|||
* @param {ASTNode} node - The node to report in the event of an error. |
|||
* @param {Token} token - The token to use for the report. |
|||
* @returns {void} |
|||
*/ |
|||
function reportRequiredBeginningLinebreak(node, token) { |
|||
context.report({ |
|||
node, |
|||
loc: token.loc, |
|||
message: "A linebreak is required after '['.", |
|||
fix(fixer) { |
|||
return fixer.insertTextAfter(token, "\n"); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Reports that there should be a linebreak before the last token |
|||
* @param {ASTNode} node - The node to report in the event of an error. |
|||
* @param {Token} token - The token to use for the report. |
|||
* @returns {void} |
|||
*/ |
|||
function reportRequiredEndingLinebreak(node, token) { |
|||
context.report({ |
|||
node, |
|||
loc: token.loc, |
|||
message: "A linebreak is required before ']'.", |
|||
fix(fixer) { |
|||
return fixer.insertTextBefore(token, "\n"); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Reports a given node if it violated this rule. |
|||
* |
|||
* @param {ASTNode} node - A node to check. This is an ObjectExpression node or an ObjectPattern node. |
|||
* @param {{multiline: boolean, minItems: number}} options - An option object. |
|||
* @returns {void} |
|||
*/ |
|||
function check(node) { |
|||
const elements = node.elements; |
|||
const normalizedOptions = normalizeOptions(context.options[0]); |
|||
const options = normalizedOptions[node.type]; |
|||
const openBracket = sourceCode.getFirstToken(node); |
|||
const closeBracket = sourceCode.getLastToken(node); |
|||
const firstIncComment = sourceCode.getTokenAfter(openBracket, { includeComments: true }); |
|||
const lastIncComment = sourceCode.getTokenBefore(closeBracket, { includeComments: true }); |
|||
const first = sourceCode.getTokenAfter(openBracket); |
|||
const last = sourceCode.getTokenBefore(closeBracket); |
|||
|
|||
const needsLinebreaks = ( |
|||
elements.length >= options.minItems || |
|||
( |
|||
options.multiline && |
|||
elements.length > 0 && |
|||
firstIncComment.loc.start.line !== lastIncComment.loc.end.line |
|||
) |
|||
); |
|||
|
|||
/* |
|||
* Use tokens or comments to check multiline or not. |
|||
* But use only tokens to check whether linebreaks are needed. |
|||
* This allows: |
|||
* var arr = [ // eslint-disable-line foo
|
|||
* 'a' |
|||
* ] |
|||
*/ |
|||
|
|||
if (needsLinebreaks) { |
|||
if (astUtils.isTokenOnSameLine(openBracket, first)) { |
|||
reportRequiredBeginningLinebreak(node, openBracket); |
|||
} |
|||
if (astUtils.isTokenOnSameLine(last, closeBracket)) { |
|||
reportRequiredEndingLinebreak(node, closeBracket); |
|||
} |
|||
} else { |
|||
if (!astUtils.isTokenOnSameLine(openBracket, first)) { |
|||
reportNoBeginningLinebreak(node, openBracket); |
|||
} |
|||
if (!astUtils.isTokenOnSameLine(last, closeBracket)) { |
|||
reportNoEndingLinebreak(node, closeBracket); |
|||
} |
|||
} |
|||
} |
|||
|
|||
//----------------------------------------------------------------------
|
|||
// Public
|
|||
//----------------------------------------------------------------------
|
|||
|
|||
return { |
|||
ArrayPattern: check, |
|||
ArrayExpression: check |
|||
}; |
|||
} |
|||
}; |
@ -0,0 +1,230 @@ |
|||
/** |
|||
* @fileoverview Rule to enforce line breaks after each array element |
|||
* @author Jan Peer Stöcklmair <https://github.com/JPeer264>
|
|||
*/ |
|||
|
|||
"use strict"; |
|||
|
|||
const astUtils = require("../ast-utils"); |
|||
|
|||
//------------------------------------------------------------------------------
|
|||
// Rule Definition
|
|||
//------------------------------------------------------------------------------
|
|||
|
|||
module.exports = { |
|||
meta: { |
|||
docs: { |
|||
description: "enforce line breaks after each array element", |
|||
category: "Stylistic Issues", |
|||
recommended: false |
|||
}, |
|||
fixable: "whitespace", |
|||
schema: [ |
|||
{ |
|||
oneOf: [ |
|||
{ |
|||
enum: ["always", "never"] |
|||
}, |
|||
{ |
|||
type: "object", |
|||
properties: { |
|||
multiline: { |
|||
type: "boolean" |
|||
}, |
|||
minItems: { |
|||
type: ["integer", "null"], |
|||
minimum: 0 |
|||
} |
|||
}, |
|||
additionalProperties: false |
|||
} |
|||
] |
|||
} |
|||
] |
|||
}, |
|||
|
|||
create(context) { |
|||
const sourceCode = context.getSourceCode(); |
|||
|
|||
//----------------------------------------------------------------------
|
|||
// Helpers
|
|||
//----------------------------------------------------------------------
|
|||
|
|||
/** |
|||
* Normalizes a given option value. |
|||
* |
|||
* @param {string|Object|undefined} option - An option value to parse. |
|||
* @returns {{multiline: boolean, minItems: number}} Normalized option object. |
|||
*/ |
|||
function normalizeOptionValue(option) { |
|||
let multiline = false; |
|||
let minItems; |
|||
|
|||
option = option || "always"; |
|||
|
|||
if (option === "always" || option.minItems === 0) { |
|||
minItems = 0; |
|||
} else if (option === "never") { |
|||
minItems = Number.POSITIVE_INFINITY; |
|||
} else { |
|||
multiline = Boolean(option.multiline); |
|||
minItems = option.minItems || Number.POSITIVE_INFINITY; |
|||
} |
|||
|
|||
return { multiline, minItems }; |
|||
} |
|||
|
|||
/** |
|||
* Normalizes a given option value. |
|||
* |
|||
* @param {string|Object|undefined} options - An option value to parse. |
|||
* @returns {{ArrayExpression: {multiline: boolean, minItems: number}, ArrayPattern: {multiline: boolean, minItems: number}}} Normalized option object. |
|||
*/ |
|||
function normalizeOptions(options) { |
|||
const value = normalizeOptionValue(options); |
|||
|
|||
return { ArrayExpression: value, ArrayPattern: value }; |
|||
} |
|||
|
|||
/** |
|||
* Reports that there shouldn't be a line break after the first token |
|||
* @param {Token} token - The token to use for the report. |
|||
* @returns {void} |
|||
*/ |
|||
function reportNoLineBreak(token) { |
|||
const tokenBefore = sourceCode.getTokenBefore(token, { includeComments: true }); |
|||
|
|||
context.report({ |
|||
loc: { |
|||
start: tokenBefore.loc.end, |
|||
end: token.loc.start |
|||
}, |
|||
message: "There should be no linebreak here.", |
|||
fix(fixer) { |
|||
if (astUtils.isCommentToken(tokenBefore)) { |
|||
return null; |
|||
} |
|||
|
|||
if (!astUtils.isTokenOnSameLine(tokenBefore, token)) { |
|||
return fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], " "); |
|||
} |
|||
|
|||
/* |
|||
* This will check if the comma is on the same line as the next element |
|||
* Following array: |
|||
* [ |
|||
* 1 |
|||
* , 2 |
|||
* , 3 |
|||
* ] |
|||
* |
|||
* will be fixed to: |
|||
* [ |
|||
* 1, 2, 3 |
|||
* ] |
|||
*/ |
|||
const twoTokensBefore = sourceCode.getTokenBefore(tokenBefore, { includeComments: true }); |
|||
|
|||
if (astUtils.isCommentToken(twoTokensBefore)) { |
|||
return null; |
|||
} |
|||
|
|||
return fixer.replaceTextRange([twoTokensBefore.range[1], tokenBefore.range[0]], ""); |
|||
|
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Reports that there should be a line break after the first token |
|||
* @param {Token} token - The token to use for the report. |
|||
* @returns {void} |
|||
*/ |
|||
function reportRequiredLineBreak(token) { |
|||
const tokenBefore = sourceCode.getTokenBefore(token, { includeComments: true }); |
|||
|
|||
context.report({ |
|||
loc: { |
|||
start: tokenBefore.loc.end, |
|||
end: token.loc.start |
|||
}, |
|||
message: "There should be a linebreak after this element.", |
|||
fix(fixer) { |
|||
return fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], "\n"); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Reports a given node if it violated this rule. |
|||
* |
|||
* @param {ASTNode} node - A node to check. This is an ObjectExpression node or an ObjectPattern node. |
|||
* @param {{multiline: boolean, minItems: number}} options - An option object. |
|||
* @returns {void} |
|||
*/ |
|||
function check(node) { |
|||
const elements = node.elements; |
|||
const normalizedOptions = normalizeOptions(context.options[0]); |
|||
const options = normalizedOptions[node.type]; |
|||
|
|||
let elementBreak = false; |
|||
|
|||
/* |
|||
* MULTILINE: true |
|||
* loop through every element and check |
|||
* if at least one element has linebreaks inside |
|||
* this ensures that following is not valid (due to elements are on the same line): |
|||
* |
|||
* [ |
|||
* 1, |
|||
* 2, |
|||
* 3 |
|||
* ] |
|||
*/ |
|||
if (options.multiline) { |
|||
elementBreak = elements |
|||
.filter(element => element !== null) |
|||
.some(element => element.loc.start.line !== element.loc.end.line); |
|||
} |
|||
|
|||
const needsLinebreaks = ( |
|||
elements.length >= options.minItems || |
|||
( |
|||
options.multiline && |
|||
elementBreak |
|||
) |
|||
); |
|||
|
|||
elements.forEach((element, i) => { |
|||
const previousElement = elements[i - 1]; |
|||
|
|||
if (i === 0 || element === null || previousElement === null) { |
|||
return; |
|||
} |
|||
|
|||
const commaToken = sourceCode.getFirstTokenBetween(previousElement, element, astUtils.isCommaToken); |
|||
const lastTokenOfPreviousElement = sourceCode.getTokenBefore(commaToken); |
|||
const firstTokenOfCurrentElement = sourceCode.getTokenAfter(commaToken); |
|||
|
|||
if (needsLinebreaks) { |
|||
if (astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) { |
|||
reportRequiredLineBreak(firstTokenOfCurrentElement); |
|||
} |
|||
} else { |
|||
if (!astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) { |
|||
reportNoLineBreak(firstTokenOfCurrentElement); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
//----------------------------------------------------------------------
|
|||
// Public
|
|||
//----------------------------------------------------------------------
|
|||
|
|||
return { |
|||
ArrayPattern: check, |
|||
ArrayExpression: check |
|||
}; |
|||
} |
|||
}; |
@ -0,0 +1,105 @@ |
|||
/** |
|||
* @fileoverview enforce "for" loop update clause moving the counter in the right direction.(for-direction) |
|||
* @author Aladdin-ADD<hh_2013@foxmail.com> |
|||
*/ |
|||
|
|||
"use strict"; |
|||
|
|||
//------------------------------------------------------------------------------
|
|||
// Rule Definition
|
|||
//------------------------------------------------------------------------------
|
|||
|
|||
module.exports = { |
|||
meta: { |
|||
docs: { |
|||
description: "enforce \"for\" loop update clause moving the counter in the right direction.", |
|||
category: "Possible Errors", |
|||
recommended: false |
|||
}, |
|||
fixable: null, |
|||
schema: [] |
|||
}, |
|||
|
|||
create(context) { |
|||
|
|||
/** |
|||
* report an error. |
|||
* @param {ASTNode} node the node to report. |
|||
* @returns {void} |
|||
*/ |
|||
function report(node) { |
|||
context.report({ |
|||
node, |
|||
message: "The update clause in this loop moves the variable in the wrong direction." |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* check UpdateExpression add/sub the counter |
|||
* @param {ASTNode} update UpdateExpression to check |
|||
* @param {string} counter variable name to check |
|||
* @returns {int} if add return 1, if sub return -1, if nochange, return 0 |
|||
*/ |
|||
function getUpdateDirection(update, counter) { |
|||
if (update.argument.type === "Identifier" && update.argument.name === counter) { |
|||
if (update.operator === "++") { |
|||
return 1; |
|||
} |
|||
if (update.operator === "--") { |
|||
return -1; |
|||
} |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
/** |
|||
* check AssignmentExpression add/sub the counter |
|||
* @param {ASTNode} update AssignmentExpression to check |
|||
* @param {string} counter variable name to check |
|||
* @returns {int} if add return 1, if sub return -1, if nochange, return 0 |
|||
*/ |
|||
function getAssignmentDirection(update, counter) { |
|||
if (update.left.name === counter) { |
|||
if (update.operator === "+=") { |
|||
return 1; |
|||
} |
|||
if (update.operator === "-=") { |
|||
return -1; |
|||
} |
|||
} |
|||
return 0; |
|||
} |
|||
return { |
|||
ForStatement(node) { |
|||
|
|||
if (node.test && node.test.type === "BinaryExpression" && node.test.left.type === "Identifier" && node.update) { |
|||
const counter = node.test.left.name; |
|||
const operator = node.test.operator; |
|||
const update = node.update; |
|||
|
|||
if (operator === "<" || operator === "<=") { |
|||
|
|||
// report error if update sub the counter (--, -=)
|
|||
if (update.type === "UpdateExpression" && getUpdateDirection(update, counter) < 0) { |
|||
report(node); |
|||
} |
|||
|
|||
if (update.type === "AssignmentExpression" && getAssignmentDirection(update, counter) < 0) { |
|||
report(node); |
|||
} |
|||
} else if (operator === ">" || operator === ">=") { |
|||
|
|||
// report error if update add the counter (++, +=)
|
|||
if (update.type === "UpdateExpression" && getUpdateDirection(update, counter) > 0) { |
|||
report(node); |
|||
} |
|||
|
|||
if (update.type === "AssignmentExpression" && getAssignmentDirection(update, counter) > 0) { |
|||
report(node); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
} |
|||
}; |
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -0,0 +1,37 @@ |
|||
/** |
|||
* @fileoverview disallow use of the Buffer() constructor |
|||
* @author Teddy Katz |
|||
*/ |
|||
"use strict"; |
|||
|
|||
//------------------------------------------------------------------------------
|
|||
// Rule Definition
|
|||
//------------------------------------------------------------------------------
|
|||
|
|||
module.exports = { |
|||
meta: { |
|||
docs: { |
|||
description: "disallow use of the Buffer() constructor", |
|||
category: "Node.js and CommonJS", |
|||
recommended: false |
|||
}, |
|||
schema: [] |
|||
}, |
|||
|
|||
create(context) { |
|||
|
|||
//----------------------------------------------------------------------
|
|||
// Public
|
|||
//----------------------------------------------------------------------
|
|||
|
|||
return { |
|||
"CallExpression[callee.name='Buffer'], NewExpression[callee.name='Buffer']"(node) { |
|||
context.report({ |
|||
node, |
|||
message: "{{example}} is deprecated. Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead.", |
|||
data: { example: node.type === "CallExpression" ? "Buffer()" : "new Buffer()" } |
|||
}); |
|||
} |
|||
}; |
|||
} |
|||
}; |
@ -0,0 +1,587 @@ |
|||
/** |
|||
* @fileoverview Rule to require or disallow newlines between statements |
|||
* @author Toru Nagashima |
|||
*/ |
|||
|
|||
"use strict"; |
|||
|
|||
//------------------------------------------------------------------------------
|
|||
// Requirements
|
|||
//------------------------------------------------------------------------------
|
|||
|
|||
const astUtils = require("../ast-utils"); |
|||
|
|||
//------------------------------------------------------------------------------
|
|||
// Helpers
|
|||
//------------------------------------------------------------------------------
|
|||
|
|||
const LT = `[${Array.from(astUtils.LINEBREAKS).join("")}]`; |
|||
const PADDING_LINE_SEQUENCE = new RegExp( |
|||
String.raw`^(\s*?${LT})\s*${LT}(\s*;?)$` |
|||
); |
|||
const CJS_EXPORT = /^(?:module\s*\.\s*)?exports(?:\s*\.|\s*\[|$)/; |
|||
const CJS_IMPORT = /^require\(/; |
|||
|
|||
/** |
|||
* Creates tester which check if a node starts with specific keyword. |
|||
* |
|||
* @param {string} keyword The keyword to test. |
|||
* @returns {Object} the created tester. |
|||
* @private |
|||
*/ |
|||
function newKeywordTester(keyword) { |
|||
return { |
|||
test: (node, sourceCode) => |
|||
sourceCode.getFirstToken(node).value === keyword |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* Creates tester which check if a node is specific type. |
|||
* |
|||
* @param {string} type The node type to test. |
|||
* @returns {Object} the created tester. |
|||
* @private |
|||
*/ |
|||
function newNodeTypeTester(type) { |
|||
return { |
|||
test: node => |
|||
node.type === type |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* Checks the given node is an expression statement of IIFE. |
|||
* |
|||
* @param {ASTNode} node The node to check. |
|||
* @returns {boolean} `true` if the node is an expression statement of IIFE. |
|||
* @private |
|||
*/ |
|||
function isIIFEStatement(node) { |
|||
if (node.type === "ExpressionStatement") { |
|||
let call = node.expression; |
|||
|
|||
if (call.type === "UnaryExpression") { |
|||
call = call.argument; |
|||
} |
|||
return call.type === "CallExpression" && astUtils.isFunction(call.callee); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* Checks whether the given node is a block-like statement. |
|||
* This checks the last token of the node is the closing brace of a block. |
|||
* |
|||
* @param {SourceCode} sourceCode The source code to get tokens. |
|||
* @param {ASTNode} node The node to check. |
|||
* @returns {boolean} `true` if the node is a block-like statement. |
|||
* @private |
|||
*/ |
|||
function isBlockLikeStatement(sourceCode, node) { |
|||
|
|||
// do-while with a block is a block-like statement.
|
|||
if (node.type === "DoWhileStatement" && node.body.type === "BlockStatement") { |
|||
return true; |
|||
} |
|||
|
|||
// IIFE is a block-like statement specially from
|
|||
// JSCS#disallowPaddingNewLinesAfterBlocks.
|
|||
if (isIIFEStatement(node)) { |
|||
return true; |
|||
} |
|||
|
|||
// Checks the last token is a closing brace of blocks.
|
|||
const lastToken = sourceCode.getLastToken(node, astUtils.isNotSemicolonToken); |
|||
const belongingNode = astUtils.isClosingBraceToken(lastToken) |
|||
? sourceCode.getNodeByRangeIndex(lastToken.range[0]) |
|||
: null; |
|||
|
|||
return Boolean(belongingNode) && ( |
|||
belongingNode.type === "BlockStatement" || |
|||
belongingNode.type === "SwitchStatement" |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Check whether the given node is a directive or not. |
|||
* @param {ASTNode} node The node to check. |
|||
* @param {SourceCode} sourceCode The source code object to get tokens. |
|||
* @returns {boolean} `true` if the node is a directive. |
|||
*/ |
|||
function isDirective(node, sourceCode) { |
|||
return ( |
|||
node.type === "ExpressionStatement" && |
|||
( |
|||
node.parent.type === "Program" || |
|||
( |
|||
node.parent.type === "BlockStatement" && |
|||
astUtils.isFunction(node.parent.parent) |
|||
) |
|||
) && |
|||
node.expression.type === "Literal" && |
|||
typeof node.expression.value === "string" && |
|||
!astUtils.isParenthesised(sourceCode, node.expression) |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Check whether the given node is a part of directive prologue or not. |
|||
* @param {ASTNode} node The node to check. |
|||
* @param {SourceCode} sourceCode The source code object to get tokens. |
|||
* @returns {boolean} `true` if the node is a part of directive prologue. |
|||
*/ |
|||
function isDirectivePrologue(node, sourceCode) { |
|||
if (isDirective(node, sourceCode)) { |
|||
for (const sibling of node.parent.body) { |
|||
if (sibling === node) { |
|||
break; |
|||
} |
|||
if (!isDirective(sibling, sourceCode)) { |
|||
return false; |
|||
} |
|||
} |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* Gets the actual last token. |
|||
* |
|||
* If a semicolon is semicolon-less style's semicolon, this ignores it. |
|||
* For example: |
|||
* |
|||
* foo() |
|||
* ;[1, 2, 3].forEach(bar) |
|||
* |
|||
* @param {SourceCode} sourceCode The source code to get tokens. |
|||
* @param {ASTNode} node The node to get. |
|||
* @returns {Token} The actual last token. |
|||
* @private |
|||
*/ |
|||
function getActualLastToken(sourceCode, node) { |
|||
const semiToken = sourceCode.getLastToken(node); |
|||
const prevToken = sourceCode.getTokenBefore(semiToken); |
|||
const nextToken = sourceCode.getTokenAfter(semiToken); |
|||
const isSemicolonLessStyle = Boolean( |
|||
prevToken && |
|||
nextToken && |
|||
prevToken.range[0] >= node.range[0] && |
|||
astUtils.isSemicolonToken(semiToken) && |
|||
semiToken.loc.start.line !== prevToken.loc.end.line && |
|||
semiToken.loc.end.line === nextToken.loc.start.line |
|||
); |
|||
|
|||
return isSemicolonLessStyle ? prevToken : semiToken; |
|||
} |
|||
|
|||
/** |
|||
* This returns the concatenation of the first 2 captured strings. |
|||
* @param {string} _ Unused. Whole matched string. |
|||
* @param {string} trailingSpaces The trailing spaces of the first line. |
|||
* @param {string} indentSpaces The indentation spaces of the last line. |
|||
* @returns {string} The concatenation of trailingSpaces and indentSpaces. |
|||
* @private |
|||
*/ |
|||
function replacerToRemovePaddingLines(_, trailingSpaces, indentSpaces) { |
|||
return trailingSpaces + indentSpaces; |
|||
} |
|||
|
|||
/** |
|||
* Check and report statements for `any` configuration. |
|||
* It does nothing. |
|||
* |
|||
* @returns {void} |
|||
* @private |
|||
*/ |
|||
function verifyForAny() { |
|||
} |
|||
|
|||
/** |
|||
* Check and report statements for `never` configuration. |
|||
* This autofix removes blank lines between the given 2 statements. |
|||
* However, if comments exist between 2 blank lines, it does not remove those |
|||
* blank lines automatically. |
|||
* |
|||
* @param {RuleContext} context The rule context to report. |
|||
* @param {ASTNode} prevNode The previous node to check. |
|||
* @param {ASTNode} nextNode The next node to check. |
|||
* @param {Array<Token[]>} paddingLines The array of token pairs that blank |
|||
* lines exist between the pair. |
|||
* @returns {void} |
|||
* @private |
|||
*/ |
|||
function verifyForNever(context, prevNode, nextNode, paddingLines) { |
|||
if (paddingLines.length === 0) { |
|||
return; |
|||
} |
|||
|
|||
context.report({ |
|||
node: nextNode, |
|||
message: "Unexpected blank line before this statement.", |
|||
fix(fixer) { |
|||
if (paddingLines.length >= 2) { |
|||
return null; |
|||
} |
|||
|
|||
const prevToken = paddingLines[0][0]; |
|||
const nextToken = paddingLines[0][1]; |
|||
const start = prevToken.range[1]; |
|||
const end = nextToken.range[0]; |
|||
const text = context.getSourceCode().text |
|||
.slice(start, end) |
|||
.replace(PADDING_LINE_SEQUENCE, replacerToRemovePaddingLines); |
|||
|
|||
return fixer.replaceTextRange([start, end], text); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Check and report statements for `always` configuration. |
|||
* This autofix inserts a blank line between the given 2 statements. |
|||
* If the `prevNode` has trailing comments, it inserts a blank line after the |
|||
* trailing comments. |
|||
* |
|||
* @param {RuleContext} context The rule context to report. |
|||
* @param {ASTNode} prevNode The previous node to check. |
|||
* @param {ASTNode} nextNode The next node to check. |
|||
* @param {Array<Token[]>} paddingLines The array of token pairs that blank |
|||
* lines exist between the pair. |
|||
* @returns {void} |
|||
* @private |
|||
*/ |
|||
function verifyForAlways(context, prevNode, nextNode, paddingLines) { |
|||
if (paddingLines.length > 0) { |
|||
return; |
|||
} |
|||
|
|||
context.report({ |
|||
node: nextNode, |
|||
message: "Expected blank line before this statement.", |
|||
fix(fixer) { |
|||
const sourceCode = context.getSourceCode(); |
|||
let prevToken = getActualLastToken(sourceCode, prevNode); |
|||
const nextToken = sourceCode.getFirstTokenBetween( |
|||
prevToken, |
|||
nextNode, |
|||
{ |
|||
includeComments: true, |
|||
|
|||
/** |
|||
* Skip the trailing comments of the previous node. |
|||
* This inserts a blank line after the last trailing comment. |
|||
* |
|||
* For example: |
|||
* |
|||
* foo(); // trailing comment.
|
|||
* // comment.
|
|||
* bar(); |
|||
* |
|||
* Get fixed to: |
|||
* |
|||
* foo(); // trailing comment.
|
|||
* |
|||
* // comment.
|
|||
* bar(); |
|||
* |
|||
* @param {Token} token The token to check. |
|||
* @returns {boolean} `true` if the token is not a trailing comment. |
|||
* @private |
|||
*/ |
|||
filter(token) { |
|||
if (astUtils.isTokenOnSameLine(prevToken, token)) { |
|||
prevToken = token; |
|||
return false; |
|||
} |
|||
return true; |
|||
} |
|||
} |
|||
) || nextNode; |
|||
const insertText = astUtils.isTokenOnSameLine(prevToken, nextToken) |
|||
? "\n\n" |
|||
: "\n"; |
|||
|
|||
return fixer.insertTextAfter(prevToken, insertText); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Types of blank lines. |
|||
* `any`, `never`, and `always` are defined. |
|||
* Those have `verify` method to check and report statements. |
|||
* @private |
|||
*/ |
|||
const PaddingTypes = { |
|||
any: { verify: verifyForAny }, |
|||
never: { verify: verifyForNever }, |
|||
always: { verify: verifyForAlways } |
|||
}; |
|||
|
|||
/** |
|||
* Types of statements. |
|||
* Those have `test` method to check it matches to the given statement. |
|||
* @private |
|||
*/ |
|||
const StatementTypes = { |
|||
"*": { test: () => true }, |
|||
"block-like": { |
|||
test: (node, sourceCode) => isBlockLikeStatement(sourceCode, node) |
|||
}, |
|||
"cjs-export": { |
|||
test: (node, sourceCode) => |
|||
node.type === "ExpressionStatement" && |
|||
node.expression.type === "AssignmentExpression" && |
|||
CJS_EXPORT.test(sourceCode.getText(node.expression.left)) |
|||
}, |
|||
"cjs-import": { |
|||
test: (node, sourceCode) => |
|||
node.type === "VariableDeclaration" && |
|||
node.declarations.length > 0 && |
|||
Boolean(node.declarations[0].init) && |
|||
CJS_IMPORT.test(sourceCode.getText(node.declarations[0].init)) |
|||
}, |
|||
directive: { |
|||
test: isDirectivePrologue |
|||
}, |
|||
expression: { |
|||
test: (node, sourceCode) => |
|||
node.type === "ExpressionStatement" && |
|||
!isDirectivePrologue(node, sourceCode) |
|||
}, |
|||
"multiline-block-like": { |
|||
test: (node, sourceCode) => |
|||
node.loc.start.line !== node.loc.end.line && |
|||
isBlockLikeStatement(sourceCode, node) |
|||
}, |
|||
|
|||
block: newNodeTypeTester("BlockStatement"), |
|||
empty: newNodeTypeTester("EmptyStatement"), |
|||
|
|||
break: newKeywordTester("break"), |
|||
case: newKeywordTester("case"), |
|||
class: newKeywordTester("class"), |
|||
const: newKeywordTester("const"), |
|||
continue: newKeywordTester("continue"), |
|||
debugger: newKeywordTester("debugger"), |
|||
default: newKeywordTester("default"), |
|||
do: newKeywordTester("do"), |
|||
export: newKeywordTester("export"), |
|||
for: newKeywordTester("for"), |
|||
function: newKeywordTester("function"), |
|||
if: newKeywordTester("if"), |
|||
import: newKeywordTester("import"), |
|||
let: newKeywordTester("let"), |
|||
return: newKeywordTester("return"), |
|||
switch: newKeywordTester("switch"), |
|||
throw: newKeywordTester("throw"), |
|||
try: newKeywordTester("try"), |
|||
var: newKeywordTester("var"), |
|||
while: newKeywordTester("while"), |
|||
with: newKeywordTester("with") |
|||
}; |
|||
|
|||
//------------------------------------------------------------------------------
|
|||
// Rule Definition
|
|||
//------------------------------------------------------------------------------
|
|||
|
|||
module.exports = { |
|||
meta: { |
|||
docs: { |
|||
description: "require or disallow padding lines between statements", |
|||
category: "Stylistic Issues", |
|||
recommended: false |
|||
}, |
|||
fixable: "whitespace", |
|||
schema: { |
|||
definitions: { |
|||
paddingType: { |
|||
enum: Object.keys(PaddingTypes) |
|||
}, |
|||
statementType: { |
|||
anyOf: [ |
|||
{ enum: Object.keys(StatementTypes) }, |
|||
{ |
|||
type: "array", |
|||
items: { enum: Object.keys(StatementTypes) }, |
|||
minItems: 1, |
|||
uniqueItems: true, |
|||
additionalItems: false |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
type: "array", |
|||
items: { |
|||
type: "object", |
|||
properties: { |
|||
blankLine: { $ref: "#/definitions/paddingType" }, |
|||
prev: { $ref: "#/definitions/statementType" }, |
|||
next: { $ref: "#/definitions/statementType" } |
|||
}, |
|||
additionalProperties: false, |
|||
required: ["blankLine", "prev", "next"] |
|||
}, |
|||
additionalItems: false |
|||
} |
|||
}, |
|||
|
|||
create(context) { |
|||
const sourceCode = context.getSourceCode(); |
|||
const configureList = context.options || []; |
|||
let scopeInfo = null; |
|||
|
|||
/** |
|||
* Processes to enter to new scope. |
|||
* This manages the current previous statement. |
|||
* @returns {void} |
|||
* @private |
|||
*/ |
|||
function enterScope() { |
|||
scopeInfo = { |
|||
upper: scopeInfo, |
|||
prevNode: null |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* Processes to exit from the current scope. |
|||
* @returns {void} |
|||
* @private |
|||
*/ |
|||
function exitScope() { |
|||
scopeInfo = scopeInfo.upper; |
|||
} |
|||
|
|||
/** |
|||
* Checks whether the given node matches the given type. |
|||
* |
|||
* @param {ASTNode} node The statement node to check. |
|||
* @param {string|string[]} type The statement type to check. |
|||
* @returns {boolean} `true` if the statement node matched the type. |
|||
* @private |
|||
*/ |
|||
function match(node, type) { |
|||
while (node.type === "LabeledStatement") { |
|||
node = node.body; |
|||
} |
|||
if (Array.isArray(type)) { |
|||
return type.some(match.bind(null, node)); |
|||
} |
|||
return StatementTypes[type].test(node, sourceCode); |
|||
} |
|||
|
|||
/** |
|||
* Finds the last matched configure from configureList. |
|||
* |
|||
* @param {ASTNode} prevNode The previous statement to match. |
|||
* @param {ASTNode} nextNode The current statement to match. |
|||
* @returns {Object} The tester of the last matched configure. |
|||
* @private |
|||
*/ |
|||
function getPaddingType(prevNode, nextNode) { |
|||
for (let i = configureList.length - 1; i >= 0; --i) { |
|||
const configure = configureList[i]; |
|||
const matched = |
|||
match(prevNode, configure.prev) && |
|||
match(nextNode, configure.next); |
|||
|
|||
if (matched) { |
|||
return PaddingTypes[configure.blankLine]; |
|||
} |
|||
} |
|||
return PaddingTypes.any; |
|||
} |
|||
|
|||
/** |
|||
* Gets padding line sequences between the given 2 statements. |
|||
* Comments are separators of the padding line sequences. |
|||
* |
|||
* @param {ASTNode} prevNode The previous statement to count. |
|||
* @param {ASTNode} nextNode The current statement to count. |
|||
* @returns {Array<Token[]>} The array of token pairs. |
|||
* @private |
|||
*/ |
|||
function getPaddingLineSequences(prevNode, nextNode) { |
|||
const pairs = []; |
|||
let prevToken = getActualLastToken(sourceCode, prevNode); |
|||
|
|||
if (nextNode.loc.start.line - prevToken.loc.end.line >= 2) { |
|||
do { |
|||
const token = sourceCode.getTokenAfter( |
|||
prevToken, |
|||
{ includeComments: true } |
|||
); |
|||
|
|||
if (token.loc.start.line - prevToken.loc.end.line >= 2) { |
|||
pairs.push([prevToken, token]); |
|||
} |
|||
prevToken = token; |
|||
|
|||
} while (prevToken.range[0] < nextNode.range[0]); |
|||
} |
|||
|
|||
return pairs; |
|||
} |
|||
|
|||
/** |
|||
* Verify padding lines between the given node and the previous node. |
|||
* |
|||
* @param {ASTNode} node The node to verify. |
|||
* @returns {void} |
|||
* @private |
|||
*/ |
|||
function verify(node) { |
|||
const parentType = node.parent.type; |
|||
const validParent = |
|||
astUtils.STATEMENT_LIST_PARENTS.has(parentType) || |
|||
parentType === "SwitchStatement"; |
|||
|
|||
if (!validParent) { |
|||
return; |
|||
} |
|||
|
|||
// Save this node as the current previous statement.
|
|||
const prevNode = scopeInfo.prevNode; |
|||
|
|||
// Verify.
|
|||
if (prevNode) { |
|||
const type = getPaddingType(prevNode, node); |
|||
const paddingLines = getPaddingLineSequences(prevNode, node); |
|||
|
|||
type.verify(context, prevNode, node, paddingLines); |
|||
} |
|||
|
|||
scopeInfo.prevNode = node; |
|||
} |
|||
|
|||
/** |
|||
* Verify padding lines between the given node and the previous node. |
|||
* Then process to enter to new scope. |
|||
* |
|||
* @param {ASTNode} node The node to verify. |
|||
* @returns {void} |
|||
* @private |
|||
*/ |
|||
function verifyThenEnterScope(node) { |
|||
verify(node); |
|||
enterScope(); |
|||
} |
|||
|
|||
return { |
|||
Program: enterScope, |
|||
BlockStatement: enterScope, |
|||
SwitchStatement: enterScope, |
|||
"Program:exit": exitScope, |
|||
"BlockStatement:exit": exitScope, |
|||
"SwitchStatement:exit": exitScope, |
|||
|
|||
":statement": verify, |
|||
|
|||
SwitchCase: verifyThenEnterScope, |
|||
"SwitchCase:exit": exitScope |
|||
}; |
|||
} |
|||
}; |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue