mirror of https://github.com/lukechilds/node.git
Browse Source
We have been stalled on ESLint 3.8.0 for some time. Current ESLint is 3.13.0. We have been unable to upgrade because of more aggressive reporting on some rules, including indentation. ESLint configuration options and bugfixes are now such that we can reasonably upgrade. PR-URL: https://github.com/nodejs/node/pull/10561 Reviewed-By: Teddy Katz <teddy.katz@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Sam Roberts <vieuxtech@gmail.com>v6
Rich Trott
8 years ago
722 changed files with 20993 additions and 11049 deletions
@ -0,0 +1,121 @@ |
|||
/** |
|||
* @fileoverview Codeframe reporter |
|||
* @author Vitor Balocco |
|||
*/ |
|||
"use strict"; |
|||
|
|||
const chalk = require("chalk"); |
|||
const codeFrame = require("babel-code-frame"); |
|||
const path = require("path"); |
|||
|
|||
//------------------------------------------------------------------------------
|
|||
// Helpers
|
|||
//------------------------------------------------------------------------------
|
|||
|
|||
/** |
|||
* Given a word and a count, append an s if count is not one. |
|||
* @param {string} word A word in its singular form. |
|||
* @param {number} count A number controlling whether word should be pluralized. |
|||
* @returns {string} The original word with an s on the end if count is not one. |
|||
*/ |
|||
function pluralize(word, count) { |
|||
return (count === 1 ? word : `${word}s`); |
|||
} |
|||
|
|||
/** |
|||
* Gets a formatted relative file path from an absolute path and a line/column in the file. |
|||
* @param {string} filePath The absolute file path to format. |
|||
* @param {number} line The line from the file to use for formatting. |
|||
* @param {number} column The column from the file to use for formatting. |
|||
* @returns {string} The formatted file path. |
|||
*/ |
|||
function formatFilePath(filePath, line, column) { |
|||
let relPath = path.relative(process.cwd(), filePath); |
|||
|
|||
if (line && column) { |
|||
relPath += `:${line}:${column}`; |
|||
} |
|||
|
|||
return chalk.green(relPath); |
|||
} |
|||
|
|||
/** |
|||
* Gets the formatted output for a given message. |
|||
* @param {Object} message The object that represents this message. |
|||
* @param {Object} parentResult The result object that this message belongs to. |
|||
* @returns {string} The formatted output. |
|||
*/ |
|||
function formatMessage(message, parentResult) { |
|||
const type = (message.fatal || message.severity === 2) ? chalk.red("error") : chalk.yellow("warning"); |
|||
const msg = `${chalk.bold(message.message.replace(/\.$/, ""))}`; |
|||
const ruleId = message.fatal ? "" : chalk.dim(`(${message.ruleId})`); |
|||
const filePath = formatFilePath(parentResult.filePath, message.line, message.column); |
|||
const sourceCode = parentResult.output ? parentResult.output : parentResult.source; |
|||
|
|||
const firstLine = [ |
|||
`${type}:`, |
|||
`${msg}`, |
|||
ruleId ? `${ruleId}` : "", |
|||
sourceCode ? `at ${filePath}:` : `at ${filePath}`, |
|||
].filter(String).join(" "); |
|||
|
|||
const result = [firstLine]; |
|||
|
|||
if (sourceCode) { |
|||
result.push( |
|||
codeFrame(sourceCode, message.line, message.column, { highlightCode: false }) |
|||
); |
|||
} |
|||
|
|||
return result.join("\n"); |
|||
} |
|||
|
|||
/** |
|||
* Gets the formatted output summary for a given number of errors and warnings. |
|||
* @param {number} errors The number of errors. |
|||
* @param {number} warnings The number of warnings. |
|||
* @returns {string} The formatted output summary. |
|||
*/ |
|||
function formatSummary(errors, warnings) { |
|||
const summaryColor = errors > 0 ? "red" : "yellow"; |
|||
const summary = []; |
|||
|
|||
if (errors > 0) { |
|||
summary.push(`${errors} ${pluralize("error", errors)}`); |
|||
} |
|||
|
|||
if (warnings > 0) { |
|||
summary.push(`${warnings} ${pluralize("warning", warnings)}`); |
|||
} |
|||
|
|||
return chalk[summaryColor].bold(`${summary.join(" and ")} found.`); |
|||
} |
|||
|
|||
//------------------------------------------------------------------------------
|
|||
// Public Interface
|
|||
//------------------------------------------------------------------------------
|
|||
|
|||
module.exports = function(results) { |
|||
let errors = 0; |
|||
let warnings = 0; |
|||
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++; |
|||
} |
|||
|
|||
return `${formatMessage(message, result)}\n\n`; |
|||
}); |
|||
|
|||
return resultsOutput.concat(messages); |
|||
}, []).join("\n"); |
|||
|
|||
output += "\n"; |
|||
output += formatSummary(errors, warnings); |
|||
|
|||
return (errors + warnings) > 0 ? output : ""; |
|||
}; |
@ -0,0 +1,301 @@ |
|||
/** |
|||
* @fileoverview enforce or disallow capitalization of the first letter of a comment |
|||
* @author Kevin Partington |
|||
*/ |
|||
"use strict"; |
|||
|
|||
//------------------------------------------------------------------------------
|
|||
// Requirements
|
|||
//------------------------------------------------------------------------------
|
|||
|
|||
const LETTER_PATTERN = require("../util/patterns/letters"); |
|||
|
|||
//------------------------------------------------------------------------------
|
|||
// Helpers
|
|||
//------------------------------------------------------------------------------
|
|||
|
|||
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/, |
|||
WHITESPACE = /\s/g, |
|||
MAYBE_URL = /^\s*[^:/?#\s]+:\/\/[^?#]/, // TODO: Combine w/ max-len pattern?
|
|||
DEFAULTS = { |
|||
ignorePattern: null, |
|||
ignoreInlineComments: false, |
|||
ignoreConsecutiveComments: false |
|||
}; |
|||
|
|||
/* |
|||
* Base schema body for defining the basic capitalization rule, ignorePattern, |
|||
* and ignoreInlineComments values. |
|||
* This can be used in a few different ways in the actual schema. |
|||
*/ |
|||
const SCHEMA_BODY = { |
|||
type: "object", |
|||
properties: { |
|||
ignorePattern: { |
|||
type: "string" |
|||
}, |
|||
ignoreInlineComments: { |
|||
type: "boolean" |
|||
}, |
|||
ignoreConsecutiveComments: { |
|||
type: "boolean" |
|||
} |
|||
}, |
|||
additionalProperties: false |
|||
}; |
|||
|
|||
/** |
|||
* Get normalized options for either block or line comments from the given |
|||
* user-provided options. |
|||
* - If the user-provided options is just a string, returns a normalized |
|||
* set of options using default values for all other options. |
|||
* - If the user-provided options is an object, then a normalized option |
|||
* set is returned. Options specified in overrides will take priority |
|||
* over options specified in the main options object, which will in |
|||
* turn take priority over the rule's defaults. |
|||
* |
|||
* @param {Object|string} rawOptions The user-provided options. |
|||
* @param {string} which Either "line" or "block". |
|||
* @returns {Object} The normalized options. |
|||
*/ |
|||
function getNormalizedOptions(rawOptions, which) { |
|||
if (!rawOptions) { |
|||
return Object.assign({}, DEFAULTS); |
|||
} |
|||
|
|||
return Object.assign({}, DEFAULTS, rawOptions[which] || rawOptions); |
|||
} |
|||
|
|||
/** |
|||
* Get normalized options for block and line comments. |
|||
* |
|||
* @param {Object|string} rawOptions The user-provided options. |
|||
* @returns {Object} An object with "Line" and "Block" keys and corresponding |
|||
* normalized options objects. |
|||
*/ |
|||
function getAllNormalizedOptions(rawOptions) { |
|||
return { |
|||
Line: getNormalizedOptions(rawOptions, "line"), |
|||
Block: getNormalizedOptions(rawOptions, "block") |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* Creates a regular expression for each ignorePattern defined in the rule |
|||
* options. |
|||
* |
|||
* This is done in order to avoid invoking the RegExp constructor repeatedly. |
|||
* |
|||
* @param {Object} normalizedOptions The normalized rule options. |
|||
* @returns {void} |
|||
*/ |
|||
function createRegExpForIgnorePatterns(normalizedOptions) { |
|||
Object.keys(normalizedOptions).forEach(key => { |
|||
const ignorePatternStr = normalizedOptions[key].ignorePattern; |
|||
|
|||
if (ignorePatternStr) { |
|||
const regExp = RegExp(`^\\s*(?:${ignorePatternStr})`); |
|||
|
|||
normalizedOptions[key].ignorePatternRegExp = regExp; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
//------------------------------------------------------------------------------
|
|||
// Rule Definition
|
|||
//------------------------------------------------------------------------------
|
|||
|
|||
module.exports = { |
|||
meta: { |
|||
docs: { |
|||
description: "enforce or disallow capitalization of the first letter of a comment", |
|||
category: "Stylistic Issues", |
|||
recommended: false |
|||
}, |
|||
fixable: "code", |
|||
schema: [ |
|||
{ enum: ["always", "never"] }, |
|||
{ |
|||
oneOf: [ |
|||
SCHEMA_BODY, |
|||
{ |
|||
type: "object", |
|||
properties: { |
|||
line: SCHEMA_BODY, |
|||
block: SCHEMA_BODY |
|||
}, |
|||
additionalProperties: false |
|||
} |
|||
] |
|||
} |
|||
] |
|||
}, |
|||
|
|||
create(context) { |
|||
|
|||
const capitalize = context.options[0] || "always", |
|||
normalizedOptions = getAllNormalizedOptions(context.options[1]), |
|||
sourceCode = context.getSourceCode(); |
|||
|
|||
createRegExpForIgnorePatterns(normalizedOptions); |
|||
|
|||
//----------------------------------------------------------------------
|
|||
// Helpers
|
|||
//----------------------------------------------------------------------
|
|||
|
|||
/** |
|||
* Checks whether a comment is an inline comment. |
|||
* |
|||
* For the purpose of this rule, a comment is inline if: |
|||
* 1. The comment is preceded by a token on the same line; and |
|||
* 2. The command is followed by a token on the same line. |
|||
* |
|||
* Note that the comment itself need not be single-line! |
|||
* |
|||
* Also, it follows from this definition that only block comments can |
|||
* be considered as possibly inline. This is because line comments |
|||
* would consume any following tokens on the same line as the comment. |
|||
* |
|||
* @param {ASTNode} comment The comment node to check. |
|||
* @returns {boolean} True if the comment is an inline comment, false |
|||
* otherwise. |
|||
*/ |
|||
function isInlineComment(comment) { |
|||
const previousToken = sourceCode.getTokenOrCommentBefore(comment), |
|||
nextToken = sourceCode.getTokenOrCommentAfter(comment); |
|||
|
|||
return Boolean( |
|||
previousToken && |
|||
nextToken && |
|||
comment.loc.start.line === previousToken.loc.end.line && |
|||
comment.loc.end.line === nextToken.loc.start.line |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Determine if a comment follows another comment. |
|||
* |
|||
* @param {ASTNode} comment The comment to check. |
|||
* @returns {boolean} True if the comment follows a valid comment. |
|||
*/ |
|||
function isConsecutiveComment(comment) { |
|||
const previousTokenOrComment = sourceCode.getTokenOrCommentBefore(comment); |
|||
|
|||
return Boolean( |
|||
previousTokenOrComment && |
|||
["Block", "Line"].indexOf(previousTokenOrComment.type) !== -1 |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Check a comment to determine if it is valid for this rule. |
|||
* |
|||
* @param {ASTNode} comment The comment node to process. |
|||
* @param {Object} options The options for checking this comment. |
|||
* @returns {boolean} True if the comment is valid, false otherwise. |
|||
*/ |
|||
function isCommentValid(comment, options) { |
|||
|
|||
// 1. Check for default ignore pattern.
|
|||
if (DEFAULT_IGNORE_PATTERN.test(comment.value)) { |
|||
return true; |
|||
} |
|||
|
|||
// 2. Check for custom ignore pattern.
|
|||
const commentWithoutAsterisks = comment.value |
|||
.replace(/\*/g, ""); |
|||
|
|||
if (options.ignorePatternRegExp && options.ignorePatternRegExp.test(commentWithoutAsterisks)) { |
|||
return true; |
|||
} |
|||
|
|||
// 3. Check for inline comments.
|
|||
if (options.ignoreInlineComments && isInlineComment(comment)) { |
|||
return true; |
|||
} |
|||
|
|||
// 4. Is this a consecutive comment (and are we tolerating those)?
|
|||
if (options.ignoreConsecutiveComments && isConsecutiveComment(comment)) { |
|||
return true; |
|||
} |
|||
|
|||
// 5. Does the comment start with a possible URL?
|
|||
if (MAYBE_URL.test(commentWithoutAsterisks)) { |
|||
return true; |
|||
} |
|||
|
|||
// 6. Is the initial word character a letter?
|
|||
const commentWordCharsOnly = commentWithoutAsterisks |
|||
.replace(WHITESPACE, ""); |
|||
|
|||
if (commentWordCharsOnly.length === 0) { |
|||
return true; |
|||
} |
|||
|
|||
const firstWordChar = commentWordCharsOnly[0]; |
|||
|
|||
if (!LETTER_PATTERN.test(firstWordChar)) { |
|||
return true; |
|||
} |
|||
|
|||
// 7. Check the case of the initial word character.
|
|||
const isUppercase = firstWordChar !== firstWordChar.toLocaleLowerCase(), |
|||
isLowercase = firstWordChar !== firstWordChar.toLocaleUpperCase(); |
|||
|
|||
if (capitalize === "always" && isLowercase) { |
|||
return false; |
|||
} else if (capitalize === "never" && isUppercase) { |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* Process a comment to determine if it needs to be reported. |
|||
* |
|||
* @param {ASTNode} comment The comment node to process. |
|||
* @returns {void} |
|||
*/ |
|||
function processComment(comment) { |
|||
const options = normalizedOptions[comment.type], |
|||
commentValid = isCommentValid(comment, options); |
|||
|
|||
if (!commentValid) { |
|||
const message = capitalize === "always" ? |
|||
ALWAYS_MESSAGE : |
|||
NEVER_MESSAGE; |
|||
|
|||
context.report({ |
|||
node: null, // Intentionally using loc instead
|
|||
loc: comment.loc, |
|||
message, |
|||
fix(fixer) { |
|||
const match = comment.value.match(LETTER_PATTERN); |
|||
|
|||
return fixer.replaceTextRange( |
|||
|
|||
// Offset match.index by 2 to account for the first 2 characters that start the comment (// or /*)
|
|||
[comment.range[0] + match.index + 2, comment.range[0] + match.index + 3], |
|||
capitalize === "always" ? match[0].toLocaleUpperCase() : match[0].toLocaleLowerCase() |
|||
); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
|
|||
//----------------------------------------------------------------------
|
|||
// Public
|
|||
//----------------------------------------------------------------------
|
|||
|
|||
return { |
|||
Program() { |
|||
const comments = sourceCode.getAllComments(); |
|||
|
|||
comments.forEach(processComment); |
|||
} |
|||
}; |
|||
} |
|||
}; |
@ -0,0 +1,75 @@ |
|||
/** |
|||
* @fileoverview Rule to disallow uses of await inside of loops. |
|||
* @author Nat Mote (nmote) |
|||
*/ |
|||
"use strict"; |
|||
|
|||
// Node types which are considered loops.
|
|||
const loopTypes = new Set([ |
|||
"ForStatement", |
|||
"ForOfStatement", |
|||
"ForInStatement", |
|||
"WhileStatement", |
|||
"DoWhileStatement", |
|||
]); |
|||
|
|||
// Node types at which we should stop looking for loops. For example, it is fine to declare an async
|
|||
// function within a loop, and use await inside of that.
|
|||
const boundaryTypes = new Set([ |
|||
"FunctionDeclaration", |
|||
"FunctionExpression", |
|||
"ArrowFunctionExpression", |
|||
]); |
|||
|
|||
module.exports = { |
|||
meta: { |
|||
docs: { |
|||
description: "disallow `await` inside of loops", |
|||
category: "Possible Errors", |
|||
recommended: false, |
|||
}, |
|||
schema: [], |
|||
}, |
|||
create(context) { |
|||
return { |
|||
AwaitExpression(node) { |
|||
const ancestors = context.getAncestors(); |
|||
|
|||
// Reverse so that we can traverse from the deepest node upwards.
|
|||
ancestors.reverse(); |
|||
|
|||
// Create a set of all the ancestors plus this node so that we can check
|
|||
// if this use of await appears in the body of the loop as opposed to
|
|||
// the right-hand side of a for...of, for example.
|
|||
const ancestorSet = new Set(ancestors).add(node); |
|||
|
|||
for (let i = 0; i < ancestors.length; i++) { |
|||
const ancestor = ancestors[i]; |
|||
|
|||
if (boundaryTypes.has(ancestor.type)) { |
|||
|
|||
// Short-circuit out if we encounter a boundary type. Loops above
|
|||
// this do not matter.
|
|||
return; |
|||
} |
|||
if (loopTypes.has(ancestor.type)) { |
|||
|
|||
// Only report if we are actually in the body or another part that gets executed on
|
|||
// every iteration.
|
|||
if ( |
|||
ancestorSet.has(ancestor.body) || |
|||
ancestorSet.has(ancestor.test) || |
|||
ancestorSet.has(ancestor.update) |
|||
) { |
|||
context.report({ |
|||
node, |
|||
message: "Unexpected `await` inside a loop." |
|||
}); |
|||
return; |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
}; |
|||
} |
|||
}; |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue