summaryrefslogtreecommitdiff
path: root/tools/eslint/lib
diff options
context:
space:
mode:
Diffstat (limited to 'tools/eslint/lib')
-rw-r--r--tools/eslint/lib/ast-utils.js3
-rw-r--r--tools/eslint/lib/cli-engine.js4
-rw-r--r--tools/eslint/lib/cli.js23
-rw-r--r--tools/eslint/lib/formatters/html.js3
-rw-r--r--tools/eslint/lib/ignored-paths.js2
-rw-r--r--tools/eslint/lib/internal-rules/.eslintrc.yml3
-rw-r--r--tools/eslint/lib/internal-rules/internal-consistent-docs-description.js130
-rw-r--r--tools/eslint/lib/internal-rules/internal-no-invalid-meta.js188
-rwxr-xr-xtools/eslint/lib/linter.js14
-rw-r--r--tools/eslint/lib/options.js6
-rw-r--r--tools/eslint/lib/rules/array-bracket-newline.js24
-rw-r--r--tools/eslint/lib/rules/block-spacing.js2
-rw-r--r--tools/eslint/lib/rules/callback-return.js3
-rw-r--r--tools/eslint/lib/rules/capitalized-comments.js3
-rw-r--r--tools/eslint/lib/rules/comma-style.js4
-rw-r--r--tools/eslint/lib/rules/indent-legacy.js7
-rw-r--r--tools/eslint/lib/rules/indent.js44
-rw-r--r--tools/eslint/lib/rules/lines-around-comment.js37
-rw-r--r--tools/eslint/lib/rules/lines-between-class-members.js91
-rw-r--r--tools/eslint/lib/rules/multiline-comment-style.js294
-rw-r--r--tools/eslint/lib/rules/new-cap.js3
-rw-r--r--tools/eslint/lib/rules/newline-before-return.js6
-rw-r--r--tools/eslint/lib/rules/no-alert.js3
-rw-r--r--tools/eslint/lib/rules/no-catch-shadow.js2
-rw-r--r--tools/eslint/lib/rules/no-constant-condition.js4
-rw-r--r--tools/eslint/lib/rules/no-control-regex.js3
-rw-r--r--tools/eslint/lib/rules/no-else-return.js51
-rw-r--r--tools/eslint/lib/rules/no-extra-parens.js9
-rw-r--r--tools/eslint/lib/rules/no-lonely-if.js3
-rw-r--r--tools/eslint/lib/rules/no-mixed-requires.js12
-rw-r--r--tools/eslint/lib/rules/no-restricted-imports.js103
-rw-r--r--tools/eslint/lib/rules/no-restricted-modules.js99
-rw-r--r--tools/eslint/lib/rules/no-trailing-spaces.js2
-rw-r--r--tools/eslint/lib/rules/no-unneeded-ternary.js4
-rw-r--r--tools/eslint/lib/rules/no-unused-labels.js3
-rw-r--r--tools/eslint/lib/rules/no-useless-computed-key.js3
-rw-r--r--tools/eslint/lib/rules/no-useless-escape.js9
-rw-r--r--tools/eslint/lib/rules/no-var.js11
-rw-r--r--tools/eslint/lib/rules/object-shorthand.js8
-rw-r--r--tools/eslint/lib/rules/operator-linebreak.js4
-rw-r--r--tools/eslint/lib/rules/require-jsdoc.js29
-rw-r--r--tools/eslint/lib/rules/sort-imports.js9
-rw-r--r--tools/eslint/lib/rules/valid-jsdoc.js72
-rw-r--r--tools/eslint/lib/testers/rule-tester.js26
-rw-r--r--tools/eslint/lib/util/node-event-generator.js2
-rw-r--r--tools/eslint/lib/util/source-code.js132
46 files changed, 927 insertions, 570 deletions
diff --git a/tools/eslint/lib/ast-utils.js b/tools/eslint/lib/ast-utils.js
index 47cc71990f..fc0471e9a4 100644
--- a/tools/eslint/lib/ast-utils.js
+++ b/tools/eslint/lib/ast-utils.js
@@ -1041,7 +1041,8 @@ module.exports = {
} else if (parent.type === "Property" || parent.type === "MethodDefinition") {
if (parent.kind === "constructor") {
return "constructor";
- } else if (parent.kind === "get") {
+ }
+ if (parent.kind === "get") {
tokens.push("getter");
} else if (parent.kind === "set") {
tokens.push("setter");
diff --git a/tools/eslint/lib/cli-engine.js b/tools/eslint/lib/cli-engine.js
index 38c49cb31d..5fc35ae0ac 100644
--- a/tools/eslint/lib/cli-engine.js
+++ b/tools/eslint/lib/cli-engine.js
@@ -240,8 +240,8 @@ function processFile(filename, configHelper, options, linter) {
function createIgnoreResult(filePath, baseDir) {
let message;
const isHidden = /^\./.test(path.basename(filePath));
- const isInNodeModules = baseDir && /^node_modules/.test(path.relative(baseDir, filePath));
- const isInBowerComponents = baseDir && /^bower_components/.test(path.relative(baseDir, filePath));
+ const isInNodeModules = baseDir && path.relative(baseDir, filePath).startsWith("node_modules");
+ const isInBowerComponents = baseDir && path.relative(baseDir, filePath).startsWith("bower_components");
if (isHidden) {
message = "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!<relative/path/to/filename>'\") to override.";
diff --git a/tools/eslint/lib/cli.js b/tools/eslint/lib/cli.js
index 0c8a7d62fb..6a5482bf9a 100644
--- a/tools/eslint/lib/cli.js
+++ b/tools/eslint/lib/cli.js
@@ -63,7 +63,7 @@ function translateOptions(cliOptions) {
cache: cliOptions.cache,
cacheFile: cliOptions.cacheFile,
cacheLocation: cliOptions.cacheLocation,
- fix: cliOptions.fix && (cliOptions.quiet ? quietFixPredicate : true),
+ fix: (cliOptions.fix || cliOptions.fixDryRun) && (cliOptions.quiet ? quietFixPredicate : true),
allowInlineConfig: cliOptions.inlineConfig,
reportUnusedDisableDirectives: cliOptions.reportUnusedDisableDirectives
};
@@ -144,6 +144,8 @@ const cli = {
const files = currentOptions._;
+ const useStdin = typeof text === "string";
+
if (currentOptions.version) { // version from package.json
log.info(`v${require("../package.json").version}`);
@@ -152,7 +154,8 @@ const cli = {
if (files.length) {
log.error("The --print-config option must be used with exactly one file name.");
return 1;
- } else if (text) {
+ }
+ if (useStdin) {
log.error("The --print-config option is not available for piped-in code.");
return 1;
}
@@ -163,23 +166,27 @@ const cli = {
log.info(JSON.stringify(fileConfig, null, " "));
return 0;
- } else if (currentOptions.help || (!files.length && !text)) {
+ } else if (currentOptions.help || (!files.length && !useStdin)) {
log.info(options.generateHelp());
} else {
- debug(`Running on ${text ? "text" : "files"}`);
+ debug(`Running on ${useStdin ? "text" : "files"}`);
+
+ if (currentOptions.fix && currentOptions.fixDryRun) {
+ log.error("The --fix option and the --fix-dry-run option cannot be used together.");
+ return 1;
+ }
- // disable --fix for piped-in code until we know how to do it correctly
- if (text && currentOptions.fix) {
- log.error("The --fix option is not available for piped-in code.");
+ if (useStdin && currentOptions.fix) {
+ log.error("The --fix option is not available for piped-in code; use --fix-dry-run instead.");
return 1;
}
const engine = new CLIEngine(translateOptions(currentOptions));
- const report = text ? engine.executeOnText(text, currentOptions.stdinFilename, true) : engine.executeOnFiles(files);
+ const report = useStdin ? engine.executeOnText(text, currentOptions.stdinFilename, true) : engine.executeOnFiles(files);
if (currentOptions.fix) {
debug("Fix mode enabled - applying fixes");
diff --git a/tools/eslint/lib/formatters/html.js b/tools/eslint/lib/formatters/html.js
index e61fdea6a9..d450f9dee2 100644
--- a/tools/eslint/lib/formatters/html.js
+++ b/tools/eslint/lib/formatters/html.js
@@ -51,7 +51,8 @@ function renderSummary(totalErrors, totalWarnings) {
function renderColor(totalErrors, totalWarnings) {
if (totalErrors !== 0) {
return 2;
- } else if (totalWarnings !== 0) {
+ }
+ if (totalWarnings !== 0) {
return 1;
}
return 0;
diff --git a/tools/eslint/lib/ignored-paths.js b/tools/eslint/lib/ignored-paths.js
index 3b7a00e9cd..f43ce46422 100644
--- a/tools/eslint/lib/ignored-paths.js
+++ b/tools/eslint/lib/ignored-paths.js
@@ -184,7 +184,7 @@ class IgnoredPaths {
addPattern(this.ig.default, pattern);
});
} else {
- throw new Error("Package.json eslintIgnore property requires an array of paths");
+ throw new TypeError("Package.json eslintIgnore property requires an array of paths");
}
}
}
diff --git a/tools/eslint/lib/internal-rules/.eslintrc.yml b/tools/eslint/lib/internal-rules/.eslintrc.yml
deleted file mode 100644
index 22d3a30ce3..0000000000
--- a/tools/eslint/lib/internal-rules/.eslintrc.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-rules:
- internal-no-invalid-meta: "error"
- internal-consistent-docs-description: "error"
diff --git a/tools/eslint/lib/internal-rules/internal-consistent-docs-description.js b/tools/eslint/lib/internal-rules/internal-consistent-docs-description.js
deleted file mode 100644
index 55e2a6c764..0000000000
--- a/tools/eslint/lib/internal-rules/internal-consistent-docs-description.js
+++ /dev/null
@@ -1,130 +0,0 @@
-/**
- * @fileoverview Internal rule to enforce meta.docs.description conventions.
- * @author Vitor Balocco
- */
-
-"use strict";
-
-const ALLOWED_FIRST_WORDS = [
- "enforce",
- "require",
- "disallow"
-];
-
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-/**
- * Gets the property of the Object node passed in that has the name specified.
- *
- * @param {string} property Name of the property to return.
- * @param {ASTNode} node The ObjectExpression node.
- * @returns {ASTNode} The Property node or null if not found.
- */
-function getPropertyFromObject(property, node) {
- const properties = node.properties;
-
- for (let i = 0; i < properties.length; i++) {
- if (properties[i].key.name === property) {
- return properties[i];
- }
- }
-
- return null;
-}
-
-/**
- * Verifies that the meta.docs.description property follows our internal conventions.
- *
- * @param {RuleContext} context The ESLint rule context.
- * @param {ASTNode} exportsNode ObjectExpression node that the rule exports.
- * @returns {void}
- */
-function checkMetaDocsDescription(context, exportsNode) {
- if (exportsNode.type !== "ObjectExpression") {
-
- // if the exported node is not the correct format, "internal-no-invalid-meta" will already report this.
- return;
- }
-
- const metaProperty = getPropertyFromObject("meta", exportsNode);
- const metaDocs = metaProperty && getPropertyFromObject("docs", metaProperty.value);
- const metaDocsDescription = metaDocs && getPropertyFromObject("description", metaDocs.value);
-
- if (!metaDocsDescription) {
-
- // if there is no `meta.docs.description` property, "internal-no-invalid-meta" will already report this.
- return;
- }
-
- const description = metaDocsDescription.value.value;
-
- if (typeof description !== "string") {
- context.report({
- node: metaDocsDescription.value,
- message: "`meta.docs.description` should be a string."
- });
- return;
- }
-
- if (description === "") {
- context.report({
- node: metaDocsDescription.value,
- message: "`meta.docs.description` should not be empty."
- });
- return;
- }
-
- if (description.indexOf(" ") === 0) {
- context.report({
- node: metaDocsDescription.value,
- message: "`meta.docs.description` should not start with whitespace."
- });
- return;
- }
-
- const firstWord = description.split(" ")[0];
-
- if (ALLOWED_FIRST_WORDS.indexOf(firstWord) === -1) {
- context.report({
- node: metaDocsDescription.value,
- message: "`meta.docs.description` should start with one of the following words: {{ allowedWords }}. Started with \"{{ firstWord }}\" instead.",
- data: {
- allowedWords: ALLOWED_FIRST_WORDS.join(", "),
- firstWord
- }
- });
- }
-}
-
-//------------------------------------------------------------------------------
-// Rule Definition
-//------------------------------------------------------------------------------
-
-module.exports = {
- meta: {
- docs: {
- description: "enforce correct conventions of `meta.docs.description` property in core rules",
- category: "Internal",
- recommended: false
- },
-
- schema: []
- },
-
- create(context) {
- return {
- AssignmentExpression(node) {
- if (node.left &&
- node.right &&
- node.left.type === "MemberExpression" &&
- node.left.object.name === "module" &&
- node.left.property.name === "exports") {
-
- checkMetaDocsDescription(context, node.right);
- }
- }
- };
- }
-};
diff --git a/tools/eslint/lib/internal-rules/internal-no-invalid-meta.js b/tools/eslint/lib/internal-rules/internal-no-invalid-meta.js
deleted file mode 100644
index d13df358bf..0000000000
--- a/tools/eslint/lib/internal-rules/internal-no-invalid-meta.js
+++ /dev/null
@@ -1,188 +0,0 @@
-/**
- * @fileoverview Internal rule to prevent missing or invalid meta property in core rules.
- * @author Vitor Balocco
- */
-
-"use strict";
-
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-/**
- * Gets the property of the Object node passed in that has the name specified.
- *
- * @param {string} property Name of the property to return.
- * @param {ASTNode} node The ObjectExpression node.
- * @returns {ASTNode} The Property node or null if not found.
- */
-function getPropertyFromObject(property, node) {
- const properties = node.properties;
-
- for (let i = 0; i < properties.length; i++) {
- if (properties[i].key.name === property) {
- return properties[i];
- }
- }
-
- return null;
-}
-
-/**
- * Extracts the `meta` property from the ObjectExpression that all rules export.
- *
- * @param {ASTNode} exportsNode ObjectExpression node that the rule exports.
- * @returns {ASTNode} The `meta` Property node or null if not found.
- */
-function getMetaPropertyFromExportsNode(exportsNode) {
- return getPropertyFromObject("meta", exportsNode);
-}
-
-/**
- * Whether this `meta` ObjectExpression has a `docs` property defined or not.
- *
- * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
- * @returns {boolean} `true` if a `docs` property exists.
- */
-function hasMetaDocs(metaPropertyNode) {
- return Boolean(getPropertyFromObject("docs", metaPropertyNode.value));
-}
-
-/**
- * Whether this `meta` ObjectExpression has a `docs.description` property defined or not.
- *
- * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
- * @returns {boolean} `true` if a `docs.description` property exists.
- */
-function hasMetaDocsDescription(metaPropertyNode) {
- const metaDocs = getPropertyFromObject("docs", metaPropertyNode.value);
-
- return metaDocs && getPropertyFromObject("description", metaDocs.value);
-}
-
-/**
- * Whether this `meta` ObjectExpression has a `docs.category` property defined or not.
- *
- * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
- * @returns {boolean} `true` if a `docs.category` property exists.
- */
-function hasMetaDocsCategory(metaPropertyNode) {
- const metaDocs = getPropertyFromObject("docs", metaPropertyNode.value);
-
- return metaDocs && getPropertyFromObject("category", metaDocs.value);
-}
-
-/**
- * Whether this `meta` ObjectExpression has a `docs.recommended` property defined or not.
- *
- * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
- * @returns {boolean} `true` if a `docs.recommended` property exists.
- */
-function hasMetaDocsRecommended(metaPropertyNode) {
- const metaDocs = getPropertyFromObject("docs", metaPropertyNode.value);
-
- return metaDocs && getPropertyFromObject("recommended", metaDocs.value);
-}
-
-/**
- * Whether this `meta` ObjectExpression has a `schema` property defined or not.
- *
- * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
- * @returns {boolean} `true` if a `schema` property exists.
- */
-function hasMetaSchema(metaPropertyNode) {
- return getPropertyFromObject("schema", metaPropertyNode.value);
-}
-
-/**
- * Checks the validity of the meta definition of this rule and reports any errors found.
- *
- * @param {RuleContext} context The ESLint rule context.
- * @param {ASTNode} exportsNode ObjectExpression node that the rule exports.
- * @param {boolean} ruleIsFixable whether the rule is fixable or not.
- * @returns {void}
- */
-function checkMetaValidity(context, exportsNode) {
- const metaProperty = getMetaPropertyFromExportsNode(exportsNode);
-
- if (!metaProperty) {
- context.report(exportsNode, "Rule is missing a meta property.");
- return;
- }
-
- if (!hasMetaDocs(metaProperty)) {
- context.report(metaProperty, "Rule is missing a meta.docs property.");
- return;
- }
-
- if (!hasMetaDocsDescription(metaProperty)) {
- context.report(metaProperty, "Rule is missing a meta.docs.description property.");
- return;
- }
-
- if (!hasMetaDocsCategory(metaProperty)) {
- context.report(metaProperty, "Rule is missing a meta.docs.category property.");
- return;
- }
-
- if (!hasMetaDocsRecommended(metaProperty)) {
- context.report(metaProperty, "Rule is missing a meta.docs.recommended property.");
- return;
- }
-
- if (!hasMetaSchema(metaProperty)) {
- context.report(metaProperty, "Rule is missing a meta.schema property.");
- }
-}
-
-/**
- * Whether this node is the correct format for a rule definition or not.
- *
- * @param {ASTNode} node node that the rule exports.
- * @returns {boolean} `true` if the exported node is the correct format for a rule definition
- */
-function isCorrectExportsFormat(node) {
- return node.type === "ObjectExpression";
-}
-
-//------------------------------------------------------------------------------
-// Rule Definition
-//------------------------------------------------------------------------------
-
-module.exports = {
- meta: {
- docs: {
- description: "enforce correct use of `meta` property in core rules",
- category: "Internal",
- recommended: false
- },
-
- schema: []
- },
-
- create(context) {
- let exportsNode;
-
- return {
- AssignmentExpression(node) {
- if (node.left &&
- node.right &&
- node.left.type === "MemberExpression" &&
- node.left.object.name === "module" &&
- node.left.property.name === "exports") {
-
- exportsNode = node.right;
- }
- },
-
- "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);
- }
- };
- }
-};
diff --git a/tools/eslint/lib/linter.js b/tools/eslint/lib/linter.js
index e96e713ffd..089f0bb250 100755
--- a/tools/eslint/lib/linter.js
+++ b/tools/eslint/lib/linter.js
@@ -291,7 +291,7 @@ function createDisableDirectives(type, loc, value) {
*/
function modifyConfigsFromComments(filename, ast, config, linterContext) {
- let commentConfig = {
+ const commentConfig = {
exported: {},
astGlobals: {},
rules: {},
@@ -320,10 +320,6 @@ function modifyConfigsFromComments(filename, ast, config, linterContext) {
Object.assign(commentConfig.astGlobals, parseBooleanConfig(value, comment));
break;
- case "eslint-env":
- Object.assign(commentConfig.env, parseListConfig(value));
- break;
-
case "eslint-disable":
[].push.apply(disableDirectives, createDisableDirectives("disable", comment.loc.start, value));
break;
@@ -361,14 +357,6 @@ function modifyConfigsFromComments(filename, ast, config, linterContext) {
}
});
- // apply environment configs
- Object.keys(commentConfig.env).forEach(name => {
- const env = linterContext.environments.get(name);
-
- if (env) {
- commentConfig = ConfigOps.merge(commentConfig, env);
- }
- });
Object.assign(commentConfig.rules, commentRules);
return {
diff --git a/tools/eslint/lib/options.js b/tools/eslint/lib/options.js
index 3bc45b3ac7..ee1d3369ce 100644
--- a/tools/eslint/lib/options.js
+++ b/tools/eslint/lib/options.js
@@ -191,6 +191,12 @@ module.exports = optionator({
description: "Automatically fix problems"
},
{
+ option: "fix-dry-run",
+ type: "Boolean",
+ default: false,
+ description: "Automatically fix problems without saving the changes to the file system"
+ },
+ {
option: "debug",
type: "Boolean",
default: false,
diff --git a/tools/eslint/lib/rules/array-bracket-newline.js b/tools/eslint/lib/rules/array-bracket-newline.js
index 319ac60f2c..5115ef4b56 100644
--- a/tools/eslint/lib/rules/array-bracket-newline.js
+++ b/tools/eslint/lib/rules/array-bracket-newline.js
@@ -23,7 +23,7 @@ module.exports = {
{
oneOf: [
{
- enum: ["always", "never"]
+ enum: ["always", "never", "consistent"]
},
{
type: "object",
@@ -58,11 +58,15 @@ module.exports = {
* @returns {{multiline: boolean, minItems: number}} Normalized option object.
*/
function normalizeOptionValue(option) {
+ let consistent = false;
let multiline = false;
let minItems = 0;
if (option) {
- if (option === "always" || option.minItems === 0) {
+ if (option === "consistent") {
+ consistent = true;
+ minItems = Number.POSITIVE_INFINITY;
+ } else if (option === "always" || option.minItems === 0) {
minItems = 0;
} else if (option === "never") {
minItems = Number.POSITIVE_INFINITY;
@@ -71,11 +75,12 @@ module.exports = {
minItems = option.minItems || Number.POSITIVE_INFINITY;
}
} else {
+ consistent = false;
multiline = true;
minItems = Number.POSITIVE_INFINITY;
}
- return { multiline, minItems };
+ return { consistent, multiline, minItems };
}
/**
@@ -173,8 +178,7 @@ module.exports = {
/**
* 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.
+ * @param {ASTNode} node - A node to check. This is an ArrayExpression node or an ArrayPattern node.
* @returns {void}
*/
function check(node) {
@@ -194,6 +198,16 @@ module.exports = {
options.multiline &&
elements.length > 0 &&
firstIncComment.loc.start.line !== lastIncComment.loc.end.line
+ ) ||
+ (
+ elements.length === 0 &&
+ firstIncComment.type === "Block" &&
+ firstIncComment.loc.start.line !== lastIncComment.loc.end.line &&
+ firstIncComment === lastIncComment
+ ) ||
+ (
+ options.consistent &&
+ firstIncComment.loc.start.line !== openBracket.loc.end.line
)
);
diff --git a/tools/eslint/lib/rules/block-spacing.js b/tools/eslint/lib/rules/block-spacing.js
index f18381a310..b3ea8405e2 100644
--- a/tools/eslint/lib/rules/block-spacing.js
+++ b/tools/eslint/lib/rules/block-spacing.js
@@ -14,7 +14,7 @@ const util = require("../ast-utils");
module.exports = {
meta: {
docs: {
- description: "enforce consistent spacing inside single-line blocks",
+ description: "disallow or enforce spaces inside of blocks after opening block and before closing block",
category: "Stylistic Issues",
recommended: false
},
diff --git a/tools/eslint/lib/rules/callback-return.js b/tools/eslint/lib/rules/callback-return.js
index 08600c01e5..0fa7f278a1 100644
--- a/tools/eslint/lib/rules/callback-return.js
+++ b/tools/eslint/lib/rules/callback-return.js
@@ -60,7 +60,8 @@ module.exports = {
if (node.type === "MemberExpression") {
if (node.object.type === "Identifier") {
return true;
- } else if (node.object.type === "MemberExpression") {
+ }
+ if (node.object.type === "MemberExpression") {
return containsOnlyIdentifiers(node.object);
}
}
diff --git a/tools/eslint/lib/rules/capitalized-comments.js b/tools/eslint/lib/rules/capitalized-comments.js
index bb5e5825a3..1a27608067 100644
--- a/tools/eslint/lib/rules/capitalized-comments.js
+++ b/tools/eslint/lib/rules/capitalized-comments.js
@@ -247,7 +247,8 @@ module.exports = {
if (capitalize === "always" && isLowercase) {
return false;
- } else if (capitalize === "never" && isUppercase) {
+ }
+ if (capitalize === "never" && isUppercase) {
return false;
}
diff --git a/tools/eslint/lib/rules/comma-style.js b/tools/eslint/lib/rules/comma-style.js
index 1a9382bea3..4c65338a44 100644
--- a/tools/eslint/lib/rules/comma-style.js
+++ b/tools/eslint/lib/rules/comma-style.js
@@ -208,7 +208,9 @@ module.exports = {
if (item) {
const tokenAfterItem = sourceCode.getTokenAfter(item, astUtils.isNotClosingParenToken);
- previousItemToken = tokenAfterItem ? sourceCode.getTokenBefore(tokenAfterItem) : sourceCode.ast.tokens[sourceCode.ast.tokens.length - 1];
+ previousItemToken = tokenAfterItem
+ ? sourceCode.getTokenBefore(tokenAfterItem)
+ : sourceCode.ast.tokens[sourceCode.ast.tokens.length - 1];
}
});
diff --git a/tools/eslint/lib/rules/indent-legacy.js b/tools/eslint/lib/rules/indent-legacy.js
index 5534944e62..a5db2bb5d7 100644
--- a/tools/eslint/lib/rules/indent-legacy.js
+++ b/tools/eslint/lib/rules/indent-legacy.js
@@ -733,7 +733,9 @@ 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] && 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.
@@ -797,7 +799,8 @@ module.exports = {
}
}
- checkLastNodeLineIndent(node, nodeIndent + (isNodeInVarOnTop(node, parentVarNode) ? options.VariableDeclarator[parentVarNode.parent.kind] * indentSize : 0));
+ checkLastNodeLineIndent(node, nodeIndent +
+ (isNodeInVarOnTop(node, parentVarNode) ? options.VariableDeclarator[parentVarNode.parent.kind] * indentSize : 0));
}
/**
diff --git a/tools/eslint/lib/rules/indent.js b/tools/eslint/lib/rules/indent.js
index 0f6468a7e1..a804f544ab 100644
--- a/tools/eslint/lib/rules/indent.js
+++ b/tools/eslint/lib/rules/indent.js
@@ -235,10 +235,12 @@ class OffsetStorage {
/**
* @param {TokenInfo} tokenInfo a TokenInfo instance
* @param {number} indentSize The desired size of each indentation level
+ * @param {string} indentType The indentation character
*/
- constructor(tokenInfo, indentSize) {
+ constructor(tokenInfo, indentSize, indentType) {
this._tokenInfo = tokenInfo;
this._indentSize = indentSize;
+ this._indentType = indentType;
this._tree = new BinarySearchTree();
this._tree.insert(0, { offset: 0, from: null, force: false });
@@ -408,7 +410,7 @@ class OffsetStorage {
/**
* Gets the desired indent of a token
* @param {Token} token The token
- * @returns {number} The desired indent of the token
+ * @returns {string} The desired indent of the token
*/
getDesiredIndent(token) {
if (!this._desiredIndentCache.has(token)) {
@@ -417,7 +419,10 @@ class OffsetStorage {
// If the token is ignored, use the actual indent of the token as the desired indent.
// This ensures that no errors are reported for this token.
- this._desiredIndentCache.set(token, this._tokenInfo.getTokenIndent(token).length / this._indentSize);
+ this._desiredIndentCache.set(
+ token,
+ this._tokenInfo.getTokenIndent(token)
+ );
} else if (this._lockedFirstTokens.has(token)) {
const firstToken = this._lockedFirstTokens.get(token);
@@ -428,7 +433,7 @@ class OffsetStorage {
this.getDesiredIndent(this._tokenInfo.getFirstTokenOfLine(firstToken)) +
// (space between the start of the first element's line and the first element)
- (firstToken.loc.start.column - this._tokenInfo.getFirstTokenOfLine(firstToken).loc.start.column) / this._indentSize
+ this._indentType.repeat(firstToken.loc.start.column - this._tokenInfo.getFirstTokenOfLine(firstToken).loc.start.column)
);
} else {
const offsetInfo = this._getOffsetDescriptor(token);
@@ -436,9 +441,12 @@ class OffsetStorage {
offsetInfo.from &&
offsetInfo.from.loc.start.line === token.loc.start.line &&
!offsetInfo.force
- ) ? 0 : offsetInfo.offset;
+ ) ? 0 : offsetInfo.offset * this._indentSize;
- this._desiredIndentCache.set(token, offset + (offsetInfo.from ? this.getDesiredIndent(offsetInfo.from) : 0));
+ this._desiredIndentCache.set(
+ token,
+ (offsetInfo.from ? this.getDesiredIndent(offsetInfo.from) : "") + this._indentType.repeat(offset)
+ );
}
}
return this._desiredIndentCache.get(token);
@@ -655,7 +663,7 @@ module.exports = {
const sourceCode = context.getSourceCode();
const tokenInfo = new TokenInfo(sourceCode);
- const offsets = new OffsetStorage(tokenInfo, indentSize);
+ const offsets = new OffsetStorage(tokenInfo, indentSize, indentType === "space" ? " " : "\t");
const parameterParens = new WeakSet();
/**
@@ -688,27 +696,24 @@ module.exports = {
/**
* Reports a given indent violation
* @param {Token} token Token violating the indent rule
- * @param {int} neededIndentLevel Expected indentation level
- * @param {int} gottenSpaces Actual number of indentation spaces for the token
- * @param {int} gottenTabs Actual number of indentation tabs for the token
+ * @param {string} neededIndent Expected indentation string
* @returns {void}
*/
- function report(token, neededIndentLevel) {
+ function report(token, neededIndent) {
const actualIndent = Array.from(tokenInfo.getTokenIndent(token));
const numSpaces = actualIndent.filter(char => char === " ").length;
const numTabs = actualIndent.filter(char => char === "\t").length;
- const neededChars = neededIndentLevel * indentSize;
context.report({
node: token,
- message: createErrorMessage(neededChars, numSpaces, numTabs),
+ message: createErrorMessage(neededIndent.length, numSpaces, numTabs),
loc: {
start: { line: token.loc.start.line, column: 0 },
end: { line: token.loc.start.line, column: token.loc.start.column }
},
fix(fixer) {
const range = [token.range[0] - token.loc.start.column, token.range[0]];
- const newText = (indentType === "space" ? " " : "\t").repeat(neededChars);
+ const newText = neededIndent;
return fixer.replaceTextRange(range, newText);
}
@@ -718,14 +723,13 @@ module.exports = {
/**
* Checks if a token's indentation is correct
* @param {Token} token Token to examine
- * @param {int} desiredIndentLevel needed indent level
+ * @param {string} desiredIndent Desired indentation of the string
* @returns {boolean} `true` if the token's indentation is correct
*/
- function validateTokenIndent(token, desiredIndentLevel) {
+ function validateTokenIndent(token, desiredIndent) {
const indentation = tokenInfo.getTokenIndent(token);
- const expectedChar = indentType === "space" ? " " : "\t";
- return indentation === expectedChar.repeat(desiredIndentLevel * indentSize) ||
+ return indentation === desiredIndent ||
// To avoid conflicts with no-mixed-spaces-and-tabs, don't report mixed spaces and tabs.
indentation.includes(" ") && indentation.includes("\t");
@@ -1241,7 +1245,9 @@ module.exports = {
NewExpression(node) {
// Only indent the arguments if the NewExpression has parens (e.g. `new Foo(bar)` or `new Foo()`, but not `new Foo`
- if (node.arguments.length > 0 || astUtils.isClosingParenToken(sourceCode.getLastToken(node)) && astUtils.isOpeningParenToken(sourceCode.getLastToken(node, 1))) {
+ if (node.arguments.length > 0 ||
+ astUtils.isClosingParenToken(sourceCode.getLastToken(node)) &&
+ astUtils.isOpeningParenToken(sourceCode.getLastToken(node, 1))) {
addFunctionCallIndent(node);
}
},
diff --git a/tools/eslint/lib/rules/lines-around-comment.js b/tools/eslint/lib/rules/lines-around-comment.js
index 5b4cd8ebe9..3df9cb86f4 100644
--- a/tools/eslint/lib/rules/lines-around-comment.js
+++ b/tools/eslint/lib/rules/lines-around-comment.js
@@ -82,6 +82,12 @@ module.exports = {
allowBlockEnd: {
type: "boolean"
},
+ allowClassStart: {
+ type: "boolean"
+ },
+ allowClassEnd: {
+ type: "boolean"
+ },
allowObjectStart: {
type: "boolean"
},
@@ -225,6 +231,24 @@ module.exports = {
}
/**
+ * Returns whether or not comments are at the class start or not.
+ * @param {token} token The Comment token.
+ * @returns {boolean} True if the comment is at class start.
+ */
+ function isCommentAtClassStart(token) {
+ return isCommentAtParentStart(token, "ClassBody");
+ }
+
+ /**
+ * Returns whether or not comments are at the class end or not.
+ * @param {token} token The Comment token.
+ * @returns {boolean} True if the comment is at class end.
+ */
+ function isCommentAtClassEnd(token) {
+ return isCommentAtParentEnd(token, "ClassBody");
+ }
+
+ /**
* Returns whether or not comments are at the object start or not.
* @param {token} token The Comment token.
* @returns {boolean} True if the comment is at object start.
@@ -284,15 +308,20 @@ module.exports = {
nextLineNum = token.loc.end.line + 1,
commentIsNotAlone = codeAroundComment(token);
- const blockStartAllowed = options.allowBlockStart && isCommentAtBlockStart(token),
- blockEndAllowed = options.allowBlockEnd && isCommentAtBlockEnd(token),
+ const blockStartAllowed = options.allowBlockStart &&
+ isCommentAtBlockStart(token) &&
+ !(options.allowClassStart === false &&
+ isCommentAtClassStart(token)),
+ blockEndAllowed = options.allowBlockEnd && isCommentAtBlockEnd(token) && !(options.allowClassEnd === false && isCommentAtClassEnd(token)),
+ classStartAllowed = options.allowClassStart && isCommentAtClassStart(token),
+ classEndAllowed = options.allowClassEnd && isCommentAtClassEnd(token),
objectStartAllowed = options.allowObjectStart && isCommentAtObjectStart(token),
objectEndAllowed = options.allowObjectEnd && isCommentAtObjectEnd(token),
arrayStartAllowed = options.allowArrayStart && isCommentAtArrayStart(token),
arrayEndAllowed = options.allowArrayEnd && isCommentAtArrayEnd(token);
- const exceptionStartAllowed = blockStartAllowed || objectStartAllowed || arrayStartAllowed;
- const exceptionEndAllowed = blockEndAllowed || objectEndAllowed || arrayEndAllowed;
+ const exceptionStartAllowed = blockStartAllowed || classStartAllowed || objectStartAllowed || arrayStartAllowed;
+ const exceptionEndAllowed = blockEndAllowed || classEndAllowed || objectEndAllowed || arrayEndAllowed;
// ignore top of the file and bottom of the file
if (prevLineNum < 1) {
diff --git a/tools/eslint/lib/rules/lines-between-class-members.js b/tools/eslint/lib/rules/lines-between-class-members.js
new file mode 100644
index 0000000000..85e8c69358
--- /dev/null
+++ b/tools/eslint/lib/rules/lines-between-class-members.js
@@ -0,0 +1,91 @@
+/**
+ * @fileoverview Rule to check empty newline between class members
+ * @author 薛定谔的猫<hh_2013@foxmail.com>
+ */
+"use strict";
+
+const astUtils = require("../ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ docs: {
+ description: "require or disallow an empty line between class members",
+ category: "Stylistic Issues",
+ recommended: false
+ },
+
+ fixable: "whitespace",
+
+ schema: [
+ {
+ enum: ["always", "never"]
+ },
+ {
+ type: "object",
+ properties: {
+ exceptAfterSingleLine: {
+ type: "boolean"
+ }
+ },
+ additionalProperties: false
+ }
+ ]
+ },
+
+ create(context) {
+
+ const options = [];
+
+ options[0] = context.options[0] || "always";
+ options[1] = context.options[1] || { exceptAfterSingleLine: false };
+
+ const ALWAYS_MESSAGE = "Expected blank line between class members.";
+ const NEVER_MESSAGE = "Unexpected blank line between class members.";
+
+ const sourceCode = context.getSourceCode();
+
+ /**
+ * Checks if there is padding between two tokens
+ * @param {Token} first The first token
+ * @param {Token} second The second token
+ * @returns {boolean} True if there is at least a line between the tokens
+ */
+ function isPaddingBetweenTokens(first, second) {
+ return second.loc.start.line - first.loc.end.line >= 2;
+ }
+
+ return {
+ ClassBody(node) {
+ const body = node.body;
+
+ for (let i = 0; i < body.length - 1; i++) {
+ const curFirst = sourceCode.getFirstToken(body[i]);
+ const curLast = sourceCode.getLastToken(body[i]);
+ const comments = sourceCode.getCommentsBefore(body[i + 1]);
+ const nextFirst = comments.length ? comments[0] : sourceCode.getFirstToken(body[i + 1]);
+ const isPadded = isPaddingBetweenTokens(curLast, nextFirst);
+ const isMulti = !astUtils.isTokenOnSameLine(curFirst, curLast);
+ const skip = !isMulti && options[1].exceptAfterSingleLine;
+
+
+ if ((options[0] === "always" && !skip && !isPadded) ||
+ (options[0] === "never" && isPadded)) {
+ context.report({
+ node: body[i + 1],
+ message: isPadded ? NEVER_MESSAGE : ALWAYS_MESSAGE,
+ fix(fixer) {
+ return isPadded
+ ? fixer.replaceTextRange([curLast.range[1], nextFirst.range[0]], "\n")
+ : fixer.insertTextAfter(curLast, "\n");
+ }
+ });
+ }
+ }
+ }
+ };
+ }
+};
diff --git a/tools/eslint/lib/rules/multiline-comment-style.js b/tools/eslint/lib/rules/multiline-comment-style.js
new file mode 100644
index 0000000000..db4f768443
--- /dev/null
+++ b/tools/eslint/lib/rules/multiline-comment-style.js
@@ -0,0 +1,294 @@
+/**
+ * @fileoverview enforce a particular style for multiline comments
+ * @author Teddy Katz
+ */
+"use strict";
+
+const astUtils = require("../ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ docs: {
+ description: "enforce a particular style for multiline comments",
+ category: "Stylistic Issues",
+ recommended: false
+ },
+ fixable: "whitespace",
+ schema: [{ enum: ["starred-block", "separate-lines", "bare-block"] }]
+ },
+
+ create(context) {
+ const sourceCode = context.getSourceCode();
+ const option = context.options[0] || "starred-block";
+
+ const EXPECTED_BLOCK_ERROR = "Expected a block comment instead of consecutive line comments.";
+ const START_NEWLINE_ERROR = "Expected a linebreak after '/*'.";
+ const END_NEWLINE_ERROR = "Expected a linebreak before '*/'.";
+ const MISSING_STAR_ERROR = "Expected a '*' at the start of this line.";
+ const ALIGNMENT_ERROR = "Expected this line to be aligned with the start of the comment.";
+ const EXPECTED_LINES_ERROR = "Expected multiple line comments instead of a block comment.";
+
+ //----------------------------------------------------------------------
+ // Helpers
+ //----------------------------------------------------------------------
+
+ /**
+ * Gets a list of comment lines in a group
+ * @param {Token[]} commentGroup A group of comments, containing either multiple line comments or a single block comment
+ * @returns {string[]} A list of comment lines
+ */
+ function getCommentLines(commentGroup) {
+ if (commentGroup[0].type === "Line") {
+ return commentGroup.map(comment => comment.value);
+ }
+ return commentGroup[0].value
+ .split(astUtils.LINEBREAK_MATCHER)
+ .map(line => line.replace(/^\s*\*?/, ""));
+ }
+
+ /**
+ * Converts a comment into starred-block form
+ * @param {Token} firstComment The first comment of the group being converted
+ * @param {string[]} commentLinesList A list of lines to appear in the new starred-block comment
+ * @returns {string} A representation of the comment value in starred-block form, excluding start and end markers
+ */
+ function convertToStarredBlock(firstComment, commentLinesList) {
+ const initialOffset = sourceCode.text.slice(firstComment.range[0] - firstComment.loc.start.column, firstComment.range[0]);
+ const starredLines = commentLinesList.map(line => `${initialOffset} *${line}`);
+
+ return `\n${starredLines.join("\n")}\n${initialOffset} `;
+ }
+
+ /**
+ * Converts a comment into separate-line form
+ * @param {Token} firstComment The first comment of the group being converted
+ * @param {string[]} commentLinesList A list of lines to appear in the new starred-block comment
+ * @returns {string} A representation of the comment value in separate-line form
+ */
+ function convertToSeparateLines(firstComment, commentLinesList) {
+ const initialOffset = sourceCode.text.slice(firstComment.range[0] - firstComment.loc.start.column, firstComment.range[0]);
+ const separateLines = commentLinesList.map(line => `// ${line.trim()}`);
+
+ return separateLines.join(`\n${initialOffset}`);
+ }
+
+ /**
+ * Converts a comment into bare-block form
+ * @param {Token} firstComment The first comment of the group being converted
+ * @param {string[]} commentLinesList A list of lines to appear in the new starred-block comment
+ * @returns {string} A representation of the comment value in bare-block form
+ */
+ function convertToBlock(firstComment, commentLinesList) {
+ const initialOffset = sourceCode.text.slice(firstComment.range[0] - firstComment.loc.start.column, firstComment.range[0]);
+ const blockLines = commentLinesList.map(line => line.trim());
+
+ return `/* ${blockLines.join(`\n${initialOffset} `)} */`;
+ }
+
+ /**
+ * Check a comment is JSDoc form
+ * @param {Token[]} commentGroup A group of comments, containing either multiple line comments or a single block comment
+ * @returns {boolean} if commentGroup is JSDoc form, return true
+ */
+ function isJSDoc(commentGroup) {
+ const lines = commentGroup[0].value.split(astUtils.LINEBREAK_MATCHER);
+
+ return commentGroup[0].type === "Block" &&
+ /^\*\s*$/.test(lines[0]) &&
+ lines.slice(1, -1).every(line => /^\s* /.test(line)) &&
+ /^\s*$/.test(lines[lines.length - 1]);
+ }
+
+ /**
+ * Each method checks a group of comments to see if it's valid according to the given option.
+ * @param {Token[]} commentGroup A list of comments that appear together. This will either contain a single
+ * block comment or multiple line comments.
+ * @returns {void}
+ */
+ const commentGroupCheckers = {
+ "starred-block"(commentGroup) {
+ const commentLines = getCommentLines(commentGroup);
+
+ if (commentLines.some(value => value.includes("*/"))) {
+ return;
+ }
+
+ if (commentGroup.length > 1) {
+ context.report({
+ loc: {
+ start: commentGroup[0].loc.start,
+ end: commentGroup[commentGroup.length - 1].loc.end
+ },
+ message: EXPECTED_BLOCK_ERROR,
+ fix(fixer) {
+ const range = [commentGroup[0].range[0], commentGroup[commentGroup.length - 1].range[1]];
+ const starredBlock = `/*${convertToStarredBlock(commentGroup[0], commentLines)}*/`;
+
+ return commentLines.some(value => value.startsWith("/"))
+ ? null
+ : fixer.replaceTextRange(range, starredBlock);
+ }
+ });
+ } else {
+ const block = commentGroup[0];
+ const lines = block.value.split(astUtils.LINEBREAK_MATCHER);
+ const expectedLinePrefix = `${sourceCode.text.slice(block.range[0] - block.loc.start.column, block.range[0])} *`;
+
+ if (!/^\*?\s*$/.test(lines[0])) {
+ const start = block.value.startsWith("*") ? block.range[0] + 1 : block.range[0];
+
+ context.report({
+ loc: {
+ start: block.loc.start,
+ end: { line: block.loc.start.line, column: block.loc.start.column + 2 }
+ },
+ message: START_NEWLINE_ERROR,
+ fix: fixer => fixer.insertTextAfterRange([start, start + 2], `\n${expectedLinePrefix}`)
+ });
+ }
+
+ if (!/^\s*$/.test(lines[lines.length - 1])) {
+ context.report({
+ loc: {
+ start: { line: block.loc.end.line, column: block.loc.end.column - 2 },
+ end: block.loc.end
+ },
+ message: END_NEWLINE_ERROR,
+ fix: fixer => fixer.replaceTextRange([block.range[1] - 2, block.range[1]], `\n${expectedLinePrefix}/`)
+ });
+ }
+
+ for (let lineNumber = block.loc.start.line + 1; lineNumber <= block.loc.end.line; lineNumber++) {
+ const lineText = sourceCode.lines[lineNumber - 1];
+
+ if (!lineText.startsWith(expectedLinePrefix)) {
+ context.report({
+ loc: {
+ start: { line: lineNumber, column: 0 },
+ end: { line: lineNumber, column: sourceCode.lines[lineNumber - 1].length }
+ },
+ message: /^\s*\*/.test(lineText)
+ ? ALIGNMENT_ERROR
+ : MISSING_STAR_ERROR,
+ fix(fixer) {
+ const lineStartIndex = sourceCode.getIndexFromLoc({ line: lineNumber, column: 0 });
+ const linePrefixLength = lineText.match(/^\s*\*? ?/)[0].length;
+ const commentStartIndex = lineStartIndex + linePrefixLength;
+
+ const replacementText = lineNumber === block.loc.end.line || lineText.length === linePrefixLength
+ ? expectedLinePrefix
+ : `${expectedLinePrefix} `;
+
+ return fixer.replaceTextRange([lineStartIndex, commentStartIndex], replacementText);
+ }
+ });
+ }
+ }
+ }
+ },
+ "separate-lines"(commentGroup) {
+ if (!isJSDoc(commentGroup) && commentGroup[0].type === "Block") {
+ const commentLines = getCommentLines(commentGroup);
+ const block = commentGroup[0];
+ const tokenAfter = sourceCode.getTokenAfter(block, { includeComments: true });
+
+ if (tokenAfter && block.loc.end.line === tokenAfter.loc.start.line) {
+ return;
+ }
+
+ context.report({
+ loc: {
+ start: block.loc.start,
+ end: { line: block.loc.start.line, column: block.loc.start.column + 2 }
+ },
+ message: EXPECTED_LINES_ERROR,
+ fix(fixer) {
+ return fixer.replaceText(block, convertToSeparateLines(block, commentLines.filter(line => line)));
+ }
+ });
+ }
+ },
+ "bare-block"(commentGroup) {
+ if (!isJSDoc(commentGroup)) {
+ const commentLines = getCommentLines(commentGroup);
+
+ // disallows consecutive line comments in favor of using a block comment.
+ if (commentGroup[0].type === "Line" && commentLines.length > 1 &&
+ !commentLines.some(value => value.includes("*/"))) {
+ context.report({
+ loc: {
+ start: commentGroup[0].loc.start,
+ end: commentGroup[commentGroup.length - 1].loc.end
+ },
+ message: EXPECTED_BLOCK_ERROR,
+ fix(fixer) {
+ const range = [commentGroup[0].range[0], commentGroup[commentGroup.length - 1].range[1]];
+ const block = convertToBlock(commentGroup[0], commentLines.filter(line => line));
+
+ return fixer.replaceTextRange(range, block);
+ }
+ });
+ }
+
+ // prohibits block comments from having a * at the beginning of each line.
+ if (commentGroup[0].type === "Block") {
+ const block = commentGroup[0];
+ const lines = block.value.split(astUtils.LINEBREAK_MATCHER).filter(line => line.trim());
+
+ if (lines.length > 0 && lines.every(line => /^\s*\*/.test(line))) {
+ context.report({
+ loc: {
+ start: block.loc.start,
+ end: { line: block.loc.start.line, column: block.loc.start.column + 2 }
+ },
+ message: EXPECTED_BLOCK_ERROR,
+ fix(fixer) {
+ return fixer.replaceText(block, convertToBlock(block, commentLines.filter(line => line)));
+ }
+ });
+ }
+ }
+ }
+ }
+ };
+
+ //----------------------------------------------------------------------
+ // Public
+ //----------------------------------------------------------------------
+
+ return {
+ Program() {
+ return sourceCode.getAllComments()
+ .filter(comment => comment.type !== "Shebang")
+ .filter(comment => !astUtils.COMMENTS_IGNORE_PATTERN.test(comment.value))
+ .filter(comment => {
+ const tokenBefore = sourceCode.getTokenBefore(comment, { includeComments: true });
+
+ return !tokenBefore || tokenBefore.loc.end.line < comment.loc.start.line;
+ })
+ .reduce((commentGroups, comment, index, commentList) => {
+ const tokenBefore = sourceCode.getTokenBefore(comment, { includeComments: true });
+
+ if (
+ comment.type === "Line" &&
+ index && commentList[index - 1].type === "Line" &&
+ tokenBefore && tokenBefore.loc.end.line === comment.loc.start.line - 1 &&
+ tokenBefore === commentList[index - 1]
+ ) {
+ commentGroups[commentGroups.length - 1].push(comment);
+ } else {
+ commentGroups.push([comment]);
+ }
+
+ return commentGroups;
+ }, [])
+ .filter(commentGroup => !(commentGroup.length === 1 && commentGroup[0].loc.start.line === commentGroup[0].loc.end.line))
+ .forEach(commentGroupCheckers[option]);
+ }
+ };
+ }
+};
diff --git a/tools/eslint/lib/rules/new-cap.js b/tools/eslint/lib/rules/new-cap.js
index 2f02c0924b..f01e8f90ac 100644
--- a/tools/eslint/lib/rules/new-cap.js
+++ b/tools/eslint/lib/rules/new-cap.js
@@ -178,7 +178,8 @@ module.exports = {
// char has no uppercase variant, so it's non-alphabetic
return "non-alpha";
- } else if (firstChar === firstCharLower) {
+ }
+ if (firstChar === firstCharLower) {
return "lower";
}
return "upper";
diff --git a/tools/eslint/lib/rules/newline-before-return.js b/tools/eslint/lib/rules/newline-before-return.js
index 1b9b0c7939..ef6c4a9264 100644
--- a/tools/eslint/lib/rules/newline-before-return.js
+++ b/tools/eslint/lib/rules/newline-before-return.js
@@ -59,9 +59,11 @@ module.exports = {
if (parentType === "IfStatement") {
return isPrecededByTokens(node, ["else", ")"]);
- } else if (parentType === "DoWhileStatement") {
+ }
+ if (parentType === "DoWhileStatement") {
return isPrecededByTokens(node, ["do"]);
- } else if (parentType === "SwitchCase") {
+ }
+ if (parentType === "SwitchCase") {
return isPrecededByTokens(node, [":"]);
}
return isPrecededByTokens(node, [")"]);
diff --git a/tools/eslint/lib/rules/no-alert.js b/tools/eslint/lib/rules/no-alert.js
index ecb52cedd6..bc1087253b 100644
--- a/tools/eslint/lib/rules/no-alert.js
+++ b/tools/eslint/lib/rules/no-alert.js
@@ -71,7 +71,8 @@ function isShadowed(scope, node) {
function isGlobalThisReferenceOrGlobalWindow(scope, node) {
if (scope.type === "global" && node.type === "ThisExpression") {
return true;
- } else if (node.name === "window") {
+ }
+ if (node.name === "window") {
return !isShadowed(scope, node);
}
diff --git a/tools/eslint/lib/rules/no-catch-shadow.js b/tools/eslint/lib/rules/no-catch-shadow.js
index 7cffae3b99..beb16aa2ba 100644
--- a/tools/eslint/lib/rules/no-catch-shadow.js
+++ b/tools/eslint/lib/rules/no-catch-shadow.js
@@ -51,7 +51,7 @@ module.exports = {
CatchClause(node) {
let scope = context.getScope();
- // When blockBindings is enabled, CatchClause creates its own scope
+ // When ecmaVersion >= 6, CatchClause creates its own scope
// so start from one upper scope to exclude the current node
if (scope.block === node) {
scope = scope.upper;
diff --git a/tools/eslint/lib/rules/no-constant-condition.js b/tools/eslint/lib/rules/no-constant-condition.js
index 31e5b372c4..0cd445dfdb 100644
--- a/tools/eslint/lib/rules/no-constant-condition.js
+++ b/tools/eslint/lib/rules/no-constant-condition.js
@@ -138,7 +138,7 @@ module.exports = {
function checkConstantConditionLoopInSet(node) {
if (loopsInCurrentScope.has(node)) {
loopsInCurrentScope.delete(node);
- context.report({ node, message: "Unexpected constant condition." });
+ context.report({ node: node.test, message: "Unexpected constant condition." });
}
}
@@ -150,7 +150,7 @@ module.exports = {
*/
function reportIfConstant(node) {
if (node.test && isConstant(node.test, true)) {
- context.report({ node, message: "Unexpected constant condition." });
+ context.report({ node: node.test, message: "Unexpected constant condition." });
}
}
diff --git a/tools/eslint/lib/rules/no-control-regex.js b/tools/eslint/lib/rules/no-control-regex.js
index 1ebf980000..14981f4ab1 100644
--- a/tools/eslint/lib/rules/no-control-regex.js
+++ b/tools/eslint/lib/rules/no-control-regex.js
@@ -31,7 +31,8 @@ module.exports = {
function getRegExp(node) {
if (node.value instanceof RegExp) {
return node.value;
- } else if (typeof node.value === "string") {
+ }
+ if (typeof node.value === "string") {
const parent = context.getAncestors().pop();
diff --git a/tools/eslint/lib/rules/no-else-return.js b/tools/eslint/lib/rules/no-else-return.js
index 36bfc26b3d..6eb6717495 100644
--- a/tools/eslint/lib/rules/no-else-return.js
+++ b/tools/eslint/lib/rules/no-else-return.js
@@ -24,8 +24,15 @@ module.exports = {
recommended: false
},
- schema: [],
-
+ schema: [{
+ type: "object",
+ properties: {
+ allowElseIf: {
+ type: "boolean"
+ }
+ },
+ additionalProperties: false
+ }],
fixable: "code"
},
@@ -134,13 +141,13 @@ module.exports = {
/**
* Check to see if the node is valid for evaluation,
- * meaning it has an else and not an else-if
+ * meaning it has an else.
*
* @param {Node} node The node being evaluated
* @returns {boolean} True if the node is valid
*/
function hasElse(node) {
- return node.alternate && node.consequent && node.alternate.type !== "IfStatement";
+ return node.alternate && node.consequent;
}
/**
@@ -189,14 +196,15 @@ module.exports = {
return checkForReturnOrIf(node);
}
+
/**
- * Check the if statement
+ * Check the if statement, but don't catch else-if blocks.
* @returns {void}
* @param {Node} node The node for the if statement to check
* @private
*/
- function IfStatement(node) {
- const parent = context.getAncestors().pop();
+ function checkIfWithoutElse(node) {
+ const parent = node.parent;
let consequents,
alternate;
@@ -221,13 +229,40 @@ module.exports = {
}
}
+ /**
+ * Check the if statement
+ * @returns {void}
+ * @param {Node} node The node for the if statement to check
+ * @private
+ */
+ function checkIfWithElse(node) {
+ const parent = node.parent;
+
+
+ /*
+ * 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;
+ }
+
+ const alternate = node.alternate;
+
+ if (alternate && alwaysReturns(node.consequent)) {
+ displayReport(alternate);
+ }
+ }
+
+ const allowElseIf = !(context.options[0] && context.options[0].allowElseIf === false);
+
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
return {
- "IfStatement:exit": IfStatement
+ "IfStatement:exit": allowElseIf ? checkIfWithoutElse : checkIfWithElse
};
diff --git a/tools/eslint/lib/rules/no-extra-parens.js b/tools/eslint/lib/rules/no-extra-parens.js
index 9c8fe70f03..020d2aeb10 100644
--- a/tools/eslint/lib/rules/no-extra-parens.js
+++ b/tools/eslint/lib/rules/no-extra-parens.js
@@ -195,10 +195,12 @@ module.exports = {
function containsAssignment(node) {
if (node.type === "AssignmentExpression") {
return true;
- } else if (node.type === "ConditionalExpression" &&
+ }
+ if (node.type === "ConditionalExpression" &&
(node.consequent.type === "AssignmentExpression" || node.alternate.type === "AssignmentExpression")) {
return true;
- } else if ((node.left && node.left.type === "AssignmentExpression") ||
+ }
+ if ((node.left && node.left.type === "AssignmentExpression") ||
(node.right && node.right.type === "AssignmentExpression")) {
return true;
}
@@ -219,7 +221,8 @@ module.exports = {
if (node.type === "ReturnStatement") {
return node.argument && containsAssignment(node.argument);
- } else if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
+ }
+ if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
return containsAssignment(node.body);
}
return containsAssignment(node);
diff --git a/tools/eslint/lib/rules/no-lonely-if.js b/tools/eslint/lib/rules/no-lonely-if.js
index 31f47b90e0..db127d1945 100644
--- a/tools/eslint/lib/rules/no-lonely-if.js
+++ b/tools/eslint/lib/rules/no-lonely-if.js
@@ -45,7 +45,8 @@ module.exports = {
const lastIfToken = sourceCode.getLastToken(node.consequent);
const sourceText = sourceCode.getText();
- if (sourceText.slice(openingElseCurly.range[1], node.range[0]).trim() || sourceText.slice(node.range[1], closingElseCurly.range[0]).trim()) {
+ if (sourceText.slice(openingElseCurly.range[1],
+ node.range[0]).trim() || sourceText.slice(node.range[1], closingElseCurly.range[0]).trim()) {
// Don't fix if there are any non-whitespace characters interfering (e.g. comments)
return null;
diff --git a/tools/eslint/lib/rules/no-mixed-requires.js b/tools/eslint/lib/rules/no-mixed-requires.js
index 55ad1e73e0..171052a52a 100644
--- a/tools/eslint/lib/rules/no-mixed-requires.js
+++ b/tools/eslint/lib/rules/no-mixed-requires.js
@@ -104,14 +104,16 @@ module.exports = {
// "var x = require('util');"
return DECL_REQUIRE;
- } else if (allowCall &&
+ }
+ if (allowCall &&
initExpression.type === "CallExpression" &&
initExpression.callee.type === "CallExpression"
) {
// "var x = require('diagnose')('sub-module');"
return getDeclarationType(initExpression.callee);
- } else if (initExpression.type === "MemberExpression") {
+ }
+ if (initExpression.type === "MemberExpression") {
// "var x = require('glob').Glob;"
return getDeclarationType(initExpression.object);
@@ -131,7 +133,8 @@ module.exports = {
// "var x = require('glob').Glob;"
return inferModuleType(initExpression.object);
- } else if (initExpression.arguments.length === 0) {
+ }
+ if (initExpression.arguments.length === 0) {
// "var x = require();"
return REQ_COMPUTED;
@@ -149,7 +152,8 @@ module.exports = {
// "var fs = require('fs');"
return REQ_CORE;
- } else if (/^\.{0,2}\//.test(arg.value)) {
+ }
+ if (/^\.{0,2}\//.test(arg.value)) {
// "var utils = require('./utils');"
return REQ_FILE;
diff --git a/tools/eslint/lib/rules/no-restricted-imports.js b/tools/eslint/lib/rules/no-restricted-imports.js
index c245f22a0a..d46b098ace 100644
--- a/tools/eslint/lib/rules/no-restricted-imports.js
+++ b/tools/eslint/lib/rules/no-restricted-imports.js
@@ -5,6 +5,13 @@
"use strict";
//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const DEFAULT_MESSAGE_TEMPLATE = "'{{importName}}' import is restricted from being used.";
+const CUSTOM_MESSAGE_TEMPLATE = "'{{importName}}' import is restricted from being used. {{customMessage}}";
+
+//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -12,8 +19,28 @@ const ignore = require("ignore");
const arrayOfStrings = {
type: "array",
+ items: { type: "string" },
+ uniqueItems: true
+};
+
+const arrayOfStringsOrObjects = {
+ type: "array",
items: {
- type: "string"
+ anyOf: [
+ { type: "string" },
+ {
+ type: "object",
+ properties: {
+ name: { type: "string" },
+ message: {
+ type: "string",
+ minLength: 1
+ }
+ },
+ additionalProperties: false,
+ required: ["name"]
+ }
+ ]
},
uniqueItems: true
};
@@ -28,17 +55,17 @@ module.exports = {
schema: {
anyOf: [
- arrayOfStrings,
+ arrayOfStringsOrObjects,
{
type: "array",
- items: [{
+ items: {
type: "object",
properties: {
- paths: arrayOfStrings,
+ paths: arrayOfStringsOrObjects,
patterns: arrayOfStrings
},
additionalProperties: false
- }],
+ },
additionalItems: false
}
]
@@ -47,35 +74,77 @@ module.exports = {
create(context) {
const options = Array.isArray(context.options) ? context.options : [];
- const isStringArray = typeof options[0] !== "object";
- const restrictedPaths = new Set(isStringArray ? context.options : options[0].paths || []);
- const restrictedPatterns = isStringArray ? [] : options[0].patterns || [];
+ const isPathAndPatternsObject =
+ typeof options[0] === "object" &&
+ (options[0].hasOwnProperty("paths") || options[0].hasOwnProperty("patterns"));
+
+ const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || [];
+ const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
+
+ const restrictedPathMessages = restrictedPaths.reduce((memo, importName) => {
+ if (typeof importName === "string") {
+ memo[importName] = null;
+ } else {
+ memo[importName.name] = importName.message;
+ }
+ return memo;
+ }, {});
// if no imports are restricted we don"t need to check
- if (restrictedPaths.size === 0 && restrictedPatterns.length === 0) {
+ if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) {
return {};
}
const ig = ignore().add(restrictedPatterns);
+ /**
+ * Report a restricted path.
+ * @param {node} node representing the restricted path reference
+ * @returns {void}
+ * @private
+ */
+ function reportPath(node) {
+ const importName = node.source.value.trim();
+ const customMessage = restrictedPathMessages[importName];
+ const message = customMessage
+ ? CUSTOM_MESSAGE_TEMPLATE
+ : DEFAULT_MESSAGE_TEMPLATE;
+
+ context.report({
+ node,
+ message,
+ data: {
+ importName,
+ customMessage
+ }
+ });
+ }
+
+ /**
+ * Check if the given name is a restricted path name.
+ * @param {string} name name of a variable
+ * @returns {boolean} whether the variable is a restricted path or not
+ * @private
+ */
+ function isRestrictedPath(name) {
+ return Object.prototype.hasOwnProperty.call(restrictedPathMessages, name);
+ }
+
return {
ImportDeclaration(node) {
if (node && node.source && node.source.value) {
-
const importName = node.source.value.trim();
- if (restrictedPaths.has(importName)) {
- context.report({
- node,
- message: "'{{importName}}' import is restricted from being used.",
- data: { importName }
- });
+ if (isRestrictedPath(importName)) {
+ reportPath(node);
}
if (restrictedPatterns.length > 0 && ig.ignores(importName)) {
context.report({
node,
message: "'{{importName}}' import is restricted from being used by a pattern.",
- data: { importName }
+ data: {
+ importName
+ }
});
}
}
diff --git a/tools/eslint/lib/rules/no-restricted-modules.js b/tools/eslint/lib/rules/no-restricted-modules.js
index 3a9634de9e..cd47975733 100644
--- a/tools/eslint/lib/rules/no-restricted-modules.js
+++ b/tools/eslint/lib/rules/no-restricted-modules.js
@@ -5,6 +5,13 @@
"use strict";
//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const DEFAULT_MESSAGE_TEMPLATE = "'{{moduleName}}' module is restricted from being used.";
+const CUSTOM_MESSAGE_TEMPLATE = "'{{moduleName}}' module is restricted from being used. {{customMessage}}";
+
+//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -12,8 +19,28 @@ const ignore = require("ignore");
const arrayOfStrings = {
type: "array",
+ items: { type: "string" },
+ uniqueItems: true
+};
+
+const arrayOfStringsOrObjects = {
+ type: "array",
items: {
- type: "string"
+ anyOf: [
+ { type: "string" },
+ {
+ type: "object",
+ properties: {
+ name: { type: "string" },
+ message: {
+ type: "string",
+ minLength: 1
+ }
+ },
+ additionalProperties: false,
+ required: ["name"]
+ }
+ ]
},
uniqueItems: true
};
@@ -28,17 +55,17 @@ module.exports = {
schema: {
anyOf: [
- arrayOfStrings,
+ arrayOfStringsOrObjects,
{
type: "array",
- items: [{
+ items: {
type: "object",
properties: {
- paths: arrayOfStrings,
+ paths: arrayOfStringsOrObjects,
patterns: arrayOfStrings
},
additionalProperties: false
- }],
+ },
additionalItems: false
}
]
@@ -47,17 +74,30 @@ module.exports = {
create(context) {
const options = Array.isArray(context.options) ? context.options : [];
- const isStringArray = typeof options[0] !== "object";
- const restrictedPaths = new Set(isStringArray ? context.options : options[0].paths || []);
- const restrictedPatterns = isStringArray ? [] : options[0].patterns || [];
+ const isPathAndPatternsObject =
+ typeof options[0] === "object" &&
+ (options[0].hasOwnProperty("paths") || options[0].hasOwnProperty("patterns"));
+
+ const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || [];
+ const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
+
+ const restrictedPathMessages = restrictedPaths.reduce((memo, importName) => {
+ if (typeof importName === "string") {
+ memo[importName] = null;
+ } else {
+ memo[importName.name] = importName.message;
+ }
+ return memo;
+ }, {});
// if no imports are restricted we don"t need to check
- if (restrictedPaths.size === 0 && restrictedPatterns.length === 0) {
+ if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) {
return {};
}
const ig = ignore().add(restrictedPatterns);
+
/**
* Function to check if a node is a string literal.
* @param {ASTNode} node The node to check.
@@ -76,6 +116,39 @@ module.exports = {
return node.callee.type === "Identifier" && node.callee.name === "require";
}
+ /**
+ * Report a restricted path.
+ * @param {node} node representing the restricted path reference
+ * @returns {void}
+ * @private
+ */
+ function reportPath(node) {
+ const moduleName = node.arguments[0].value.trim();
+ const customMessage = restrictedPathMessages[moduleName];
+ const message = customMessage
+ ? CUSTOM_MESSAGE_TEMPLATE
+ : DEFAULT_MESSAGE_TEMPLATE;
+
+ context.report({
+ node,
+ message,
+ data: {
+ moduleName,
+ customMessage
+ }
+ });
+ }
+
+ /**
+ * Check if the given name is a restricted path name
+ * @param {string} name name of a variable
+ * @returns {boolean} whether the variable is a restricted path or not
+ * @private
+ */
+ function isRestrictedPath(name) {
+ return Object.prototype.hasOwnProperty.call(restrictedPathMessages, name);
+ }
+
return {
CallExpression(node) {
if (isRequireCall(node)) {
@@ -85,12 +158,8 @@ module.exports = {
const moduleName = node.arguments[0].value.trim();
// check if argument value is in restricted modules array
- if (restrictedPaths.has(moduleName)) {
- context.report({
- node,
- message: "'{{moduleName}}' module is restricted from being used.",
- data: { moduleName }
- });
+ if (isRestrictedPath(moduleName)) {
+ reportPath(node);
}
if (restrictedPatterns.length > 0 && ig.ignores(moduleName)) {
diff --git a/tools/eslint/lib/rules/no-trailing-spaces.js b/tools/eslint/lib/rules/no-trailing-spaces.js
index b5d2f8d1b5..598bbea4f9 100644
--- a/tools/eslint/lib/rules/no-trailing-spaces.js
+++ b/tools/eslint/lib/rules/no-trailing-spaces.js
@@ -49,7 +49,7 @@ module.exports = {
const options = context.options[0] || {},
skipBlankLines = options.skipBlankLines || false,
- ignoreComments = typeof options.ignoreComments === "undefined" || options.ignoreComments;
+ ignoreComments = typeof options.ignoreComments === "boolean" && options.ignoreComments;
/**
* Report the error message
diff --git a/tools/eslint/lib/rules/no-unneeded-ternary.js b/tools/eslint/lib/rules/no-unneeded-ternary.js
index 929991f86b..5745537805 100644
--- a/tools/eslint/lib/rules/no-unneeded-ternary.js
+++ b/tools/eslint/lib/rules/no-unneeded-ternary.js
@@ -72,8 +72,10 @@ module.exports = {
node.right,
token => token.value === node.operator
);
+ const text = sourceCode.getText();
- return sourceCode.getText().slice(node.range[0], operatorToken.range[0]) + OPERATOR_INVERSES[node.operator] + sourceCode.getText().slice(operatorToken.range[1], node.range[1]);
+ return text.slice(node.range[0],
+ operatorToken.range[0]) + OPERATOR_INVERSES[node.operator] + text.slice(operatorToken.range[1], node.range[1]);
}
if (astUtils.getPrecedence(node) < astUtils.getPrecedence({ type: "UnaryExpression" })) {
diff --git a/tools/eslint/lib/rules/no-unused-labels.js b/tools/eslint/lib/rules/no-unused-labels.js
index 12f60ca184..bcd3cfdc47 100644
--- a/tools/eslint/lib/rules/no-unused-labels.js
+++ b/tools/eslint/lib/rules/no-unused-labels.js
@@ -59,7 +59,8 @@ module.exports = {
* Only perform a fix if there are no comments between the label and the body. This will be the case
* when there is exactly one token/comment (the ":") between the label and the body.
*/
- if (sourceCode.getTokenAfter(node.label, { includeComments: true }) === sourceCode.getTokenBefore(node.body, { includeComments: true })) {
+ if (sourceCode.getTokenAfter(node.label, { includeComments: true }) ===
+ sourceCode.getTokenBefore(node.body, { includeComments: true })) {
return fixer.removeRange([node.range[0], node.body.range[0]]);
}
diff --git a/tools/eslint/lib/rules/no-useless-computed-key.js b/tools/eslint/lib/rules/no-useless-computed-key.js
index 23de2f3734..f8114ab754 100644
--- a/tools/eslint/lib/rules/no-useless-computed-key.js
+++ b/tools/eslint/lib/rules/no-useless-computed-key.js
@@ -50,7 +50,8 @@ module.exports = {
const rightSquareBracket = sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isClosingBracketToken);
const tokensBetween = sourceCode.getTokensBetween(leftSquareBracket, rightSquareBracket, 1);
- if (tokensBetween.slice(0, -1).some((token, index) => sourceCode.getText().slice(token.range[1], tokensBetween[index + 1].range[0]).trim())) {
+ if (tokensBetween.slice(0, -1).some((token, index) =>
+ sourceCode.getText().slice(token.range[1], tokensBetween[index + 1].range[0]).trim())) {
// If there are comments between the brackets and the property name, don't do a fix.
return null;
diff --git a/tools/eslint/lib/rules/no-useless-escape.js b/tools/eslint/lib/rules/no-useless-escape.js
index 0212bd60e3..9e39eb6f43 100644
--- a/tools/eslint/lib/rules/no-useless-escape.js
+++ b/tools/eslint/lib/rules/no-useless-escape.js
@@ -63,7 +63,14 @@ function parseRegExp(regExpText) {
return Object.assign(state, { inCharClass: false, startingCharClass: false });
}
}
- charList.push({ text: char, index, escaped: state.escapeNextChar, inCharClass: state.inCharClass, startsCharClass: state.startingCharClass, endsCharClass: false });
+ charList.push({
+ text: char,
+ index,
+ escaped: state.escapeNextChar,
+ inCharClass: state.inCharClass,
+ startsCharClass: state.startingCharClass,
+ endsCharClass: false
+ });
return Object.assign(state, { escapeNextChar: false, startingCharClass: false });
}, { escapeNextChar: false, inCharClass: false, startingCharClass: false });
diff --git a/tools/eslint/lib/rules/no-var.js b/tools/eslint/lib/rules/no-var.js
index c74e0b9ad9..d3c163e557 100644
--- a/tools/eslint/lib/rules/no-var.js
+++ b/tools/eslint/lib/rules/no-var.js
@@ -16,6 +16,15 @@ const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
/**
+ * Check whether a given variable is a global variable or not.
+ * @param {eslint-scope.Variable} variable The variable to check.
+ * @returns {boolean} `true` if the variable is a global variable.
+ */
+function isGlobal(variable) {
+ return Boolean(variable.scope) && variable.scope.type === "global";
+}
+
+/**
* Finds the nearest function scope or global scope walking up the scope
* hierarchy.
*
@@ -203,6 +212,7 @@ module.exports = {
* Checks whether it can fix a given variable declaration or not.
* It cannot fix if the following cases:
*
+ * - A variable is a global variable.
* - A variable is declared on a SwitchCase node.
* - A variable is redeclared.
* - A variable is used from outside the scope.
@@ -256,6 +266,7 @@ module.exports = {
if (node.parent.type === "SwitchCase" ||
node.declarations.some(hasSelfReferenceInTDZ) ||
+ variables.some(isGlobal) ||
variables.some(isRedeclared) ||
variables.some(isUsedFromOutsideOf(scopeNode))
) {
diff --git a/tools/eslint/lib/rules/object-shorthand.js b/tools/eslint/lib/rules/object-shorthand.js
index 2f7b0ccf90..dfd8d1a64e 100644
--- a/tools/eslint/lib/rules/object-shorthand.js
+++ b/tools/eslint/lib/rules/object-shorthand.js
@@ -215,8 +215,12 @@ module.exports = {
* @returns {Object} A fix for this node
*/
function makeFunctionShorthand(fixer, node) {
- const firstKeyToken = node.computed ? sourceCode.getFirstToken(node, astUtils.isOpeningBracketToken) : sourceCode.getFirstToken(node.key);
- const lastKeyToken = node.computed ? sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isClosingBracketToken) : sourceCode.getLastToken(node.key);
+ const firstKeyToken = node.computed
+ ? sourceCode.getFirstToken(node, astUtils.isOpeningBracketToken)
+ : sourceCode.getFirstToken(node.key);
+ const lastKeyToken = node.computed
+ ? sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isClosingBracketToken)
+ : sourceCode.getLastToken(node.key);
const keyText = sourceCode.text.slice(firstKeyToken.range[0], lastKeyToken.range[1]);
let keyPrefix = "";
diff --git a/tools/eslint/lib/rules/operator-linebreak.js b/tools/eslint/lib/rules/operator-linebreak.js
index 8253094c52..809da1fc8c 100644
--- a/tools/eslint/lib/rules/operator-linebreak.js
+++ b/tools/eslint/lib/rules/operator-linebreak.js
@@ -87,7 +87,9 @@ module.exports = {
if (hasLinebreakBefore !== hasLinebreakAfter && desiredStyle !== "none") {
// If there is a comment before and after the operator, don't do a fix.
- if (sourceCode.getTokenBefore(operatorToken, { includeComments: true }) !== tokenBefore && sourceCode.getTokenAfter(operatorToken, { includeComments: true }) !== tokenAfter) {
+ if (sourceCode.getTokenBefore(operatorToken, { includeComments: true }) !== tokenBefore &&
+ sourceCode.getTokenAfter(operatorToken, { includeComments: true }) !== tokenAfter) {
+
return null;
}
diff --git a/tools/eslint/lib/rules/require-jsdoc.js b/tools/eslint/lib/rules/require-jsdoc.js
index f1ecde81f9..a02ee3659c 100644
--- a/tools/eslint/lib/rules/require-jsdoc.js
+++ b/tools/eslint/lib/rules/require-jsdoc.js
@@ -30,6 +30,9 @@ module.exports = {
},
ArrowFunctionExpression: {
type: "boolean"
+ },
+ FunctionExpression: {
+ type: "boolean"
}
},
additionalProperties: false
@@ -45,7 +48,9 @@ module.exports = {
const DEFAULT_OPTIONS = {
FunctionDeclaration: true,
MethodDefinition: false,
- ClassDeclaration: false
+ ClassDeclaration: false,
+ ArrowFunctionExpression: false,
+ FunctionExpression: false
};
const options = Object.assign(DEFAULT_OPTIONS, context.options[0] && context.options[0].require || {});
@@ -59,21 +64,6 @@ module.exports = {
}
/**
- * Check if the jsdoc comment is present for class methods
- * @param {ASTNode} node node to examine
- * @returns {void}
- */
- function checkClassMethodJsDoc(node) {
- if (node.parent.type === "MethodDefinition") {
- const jsdocComment = source.getJSDocComment(node);
-
- if (!jsdocComment) {
- report(node);
- }
- }
- }
-
- /**
* Check if the jsdoc comment is present or not.
* @param {ASTNode} node node to examine
* @returns {void}
@@ -93,8 +83,11 @@ module.exports = {
}
},
FunctionExpression(node) {
- if (options.MethodDefinition) {
- checkClassMethodJsDoc(node);
+ if (
+ (options.MethodDefinition && node.parent.type === "MethodDefinition") ||
+ (options.FunctionExpression && (node.parent.type === "VariableDeclarator" || (node.parent.type === "Property" && node === node.parent.value)))
+ ) {
+ checkJsDoc(node);
}
},
ClassDeclaration(node) {
diff --git a/tools/eslint/lib/rules/sort-imports.js b/tools/eslint/lib/rules/sort-imports.js
index 74db02ad3d..be1605dc47 100644
--- a/tools/eslint/lib/rules/sort-imports.js
+++ b/tools/eslint/lib/rules/sort-imports.js
@@ -67,9 +67,11 @@ module.exports = {
function usedMemberSyntax(node) {
if (node.specifiers.length === 0) {
return "none";
- } else if (node.specifiers[0].type === "ImportNamespaceSpecifier") {
+ }
+ if (node.specifiers[0].type === "ImportNamespaceSpecifier") {
return "all";
- } else if (node.specifiers.length === 1) {
+ }
+ if (node.specifiers.length === 1) {
return "single";
}
return "multiple";
@@ -149,7 +151,8 @@ module.exports = {
message: "Member '{{memberName}}' of the import declaration should be sorted alphabetically.",
data: { memberName: importSpecifiers[firstUnsortedIndex].local.name },
fix(fixer) {
- if (importSpecifiers.some(specifier => sourceCode.getCommentsBefore(specifier).length || sourceCode.getCommentsAfter(specifier).length)) {
+ if (importSpecifiers.some(specifier =>
+ sourceCode.getCommentsBefore(specifier).length || sourceCode.getCommentsAfter(specifier).length)) {
// If there are comments in the ImportSpecifier list, don't rearrange the specifiers.
return null;
diff --git a/tools/eslint/lib/rules/valid-jsdoc.js b/tools/eslint/lib/rules/valid-jsdoc.js
index d4ee4e0738..ea60b115ce 100644
--- a/tools/eslint/lib/rules/valid-jsdoc.js
+++ b/tools/eslint/lib/rules/valid-jsdoc.js
@@ -226,8 +226,10 @@ module.exports = {
function checkJSDoc(node) {
const jsdocNode = sourceCode.getJSDocComment(node),
functionData = fns.pop(),
- params = Object.create(null);
+ params = Object.create(null),
+ paramsTags = [];
let hasReturns = false,
+ returnsTag,
hasConstructor = false,
isInterface = false,
isOverride = false,
@@ -261,43 +263,13 @@ module.exports = {
case "param":
case "arg":
case "argument":
- if (!tag.type) {
- context.report({ node: jsdocNode, message: "Missing JSDoc parameter type for '{{name}}'.", data: { name: tag.name } });
- }
-
- if (!tag.description && requireParamDescription) {
- context.report({ node: jsdocNode, message: "Missing JSDoc parameter description for '{{name}}'.", data: { name: tag.name } });
- }
-
- if (params[tag.name]) {
- context.report({ node: jsdocNode, message: "Duplicate JSDoc parameter '{{name}}'.", data: { name: tag.name } });
- } else if (tag.name.indexOf(".") === -1) {
- params[tag.name] = 1;
- }
+ paramsTags.push(tag);
break;
case "return":
case "returns":
hasReturns = true;
-
- if (!requireReturn && !functionData.returnPresent && (tag.type === null || !isValidReturnType(tag)) && !isAbstract) {
- context.report({
- node: jsdocNode,
- message: "Unexpected @{{title}} tag; function has no return statement.",
- data: {
- title: tag.title
- }
- });
- } else {
- if (requireReturnType && !tag.type) {
- context.report({ node: jsdocNode, message: "Missing JSDoc return type." });
- }
-
- if (!isValidReturnType(tag) && !tag.description && requireReturnDescription) {
- context.report({ node: jsdocNode, message: "Missing JSDoc return description." });
- }
- }
-
+ returnsTag = tag;
break;
case "constructor":
@@ -333,6 +305,40 @@ module.exports = {
}
});
+ paramsTags.forEach(param => {
+ if (!param.type) {
+ context.report({ node: jsdocNode, message: "Missing JSDoc parameter type for '{{name}}'.", data: { name: param.name } });
+ }
+ if (!param.description && requireParamDescription) {
+ context.report({ node: jsdocNode, message: "Missing JSDoc parameter description for '{{name}}'.", data: { name: param.name } });
+ }
+ if (params[param.name]) {
+ context.report({ node: jsdocNode, message: "Duplicate JSDoc parameter '{{name}}'.", data: { name: param.name } });
+ } else if (param.name.indexOf(".") === -1) {
+ params[param.name] = 1;
+ }
+ });
+
+ if (hasReturns) {
+ if (!requireReturn && !functionData.returnPresent && (returnsTag.type === null || !isValidReturnType(returnsTag)) && !isAbstract) {
+ context.report({
+ node: jsdocNode,
+ message: "Unexpected @{{title}} tag; function has no return statement.",
+ data: {
+ title: returnsTag.title
+ }
+ });
+ } else {
+ if (requireReturnType && !returnsTag.type) {
+ context.report({ node: jsdocNode, message: "Missing JSDoc return type." });
+ }
+
+ if (!isValidReturnType(returnsTag) && !returnsTag.description && requireReturnDescription) {
+ context.report({ node: jsdocNode, message: "Missing JSDoc return description." });
+ }
+ }
+ }
+
// check for functions missing @returns
if (!isOverride && !hasReturns && !hasConstructor && !isInterface &&
node.parent.kind !== "get" && node.parent.kind !== "constructor" &&
diff --git a/tools/eslint/lib/testers/rule-tester.js b/tools/eslint/lib/testers/rule-tester.js
index d7e14878cf..a76a38c655 100644
--- a/tools/eslint/lib/testers/rule-tester.js
+++ b/tools/eslint/lib/testers/rule-tester.js
@@ -178,7 +178,7 @@ class RuleTester {
*/
static setDefaultConfig(config) {
if (typeof config !== "object") {
- throw new Error("RuleTester.setDefaultConfig: config must be an object");
+ throw new TypeError("RuleTester.setDefaultConfig: config must be an object");
}
defaultConfig = config;
@@ -254,7 +254,7 @@ class RuleTester {
linter = this.linter;
if (lodash.isNil(test) || typeof test !== "object") {
- throw new Error(`Test Scenarios for rule ${ruleName} : Could not find test scenario object`);
+ throw new TypeError(`Test Scenarios for rule ${ruleName} : Could not find test scenario object`);
}
requiredScenarios.forEach(scenarioType => {
@@ -369,6 +369,7 @@ class RuleTester {
if (!lodash.isEqual(beforeAST, afterAST)) {
// Not using directly to avoid performance problem in node 6.1.0. See #6111
+ // eslint-disable-next-line no-restricted-properties
assert.deepEqual(beforeAST, afterAST, "Rule should not modify AST.");
}
}
@@ -384,7 +385,7 @@ class RuleTester {
const result = runRuleForItem(item);
const messages = result.messages;
- assert.equal(messages.length, 0, util.format("Should have no errors but had %d: %s",
+ assert.strictEqual(messages.length, 0, util.format("Should have no errors but had %d: %s",
messages.length, util.inspect(messages)));
assertASTDidntChange(result.beforeAST, result.afterAST);
@@ -408,7 +409,7 @@ class RuleTester {
`Expected '${actual}' to match ${expected}`
);
} else {
- assert.equal(actual, expected);
+ assert.strictEqual(actual, expected);
}
}
@@ -428,10 +429,10 @@ class RuleTester {
if (typeof item.errors === "number") {
- assert.equal(messages.length, item.errors, util.format("Should have %d error%s but had %d: %s",
+ assert.strictEqual(messages.length, item.errors, util.format("Should have %d error%s but had %d: %s",
item.errors, item.errors === 1 ? "" : "s", messages.length, util.inspect(messages)));
} else {
- assert.equal(
+ assert.strictEqual(
messages.length, item.errors.length,
util.format(
"Should have %d error%s but had %d: %s",
@@ -460,23 +461,35 @@ class RuleTester {
assertMessageMatches(messages[i].message, item.errors[i].message);
}
+ // The following checks use loose equality assertions for backwards compatibility.
+
if (item.errors[i].type) {
+
+ // eslint-disable-next-line no-restricted-properties
assert.equal(messages[i].nodeType, item.errors[i].type, `Error type should be ${item.errors[i].type}, found ${messages[i].nodeType}`);
}
if (item.errors[i].hasOwnProperty("line")) {
+
+ // eslint-disable-next-line no-restricted-properties
assert.equal(messages[i].line, item.errors[i].line, `Error line should be ${item.errors[i].line}`);
}
if (item.errors[i].hasOwnProperty("column")) {
+
+ // eslint-disable-next-line no-restricted-properties
assert.equal(messages[i].column, item.errors[i].column, `Error column should be ${item.errors[i].column}`);
}
if (item.errors[i].hasOwnProperty("endLine")) {
+
+ // eslint-disable-next-line no-restricted-properties
assert.equal(messages[i].endLine, item.errors[i].endLine, `Error endLine should be ${item.errors[i].endLine}`);
}
if (item.errors[i].hasOwnProperty("endColumn")) {
+
+ // eslint-disable-next-line no-restricted-properties
assert.equal(messages[i].endColumn, item.errors[i].endColumn, `Error endColumn should be ${item.errors[i].endColumn}`);
}
} else {
@@ -497,6 +510,7 @@ class RuleTester {
} else {
const fixResult = SourceCodeFixer.applyFixes(item.code, messages);
+ // eslint-disable-next-line no-restricted-properties
assert.equal(fixResult.output, item.output, "Output is incorrect.");
}
}
diff --git a/tools/eslint/lib/util/node-event-generator.js b/tools/eslint/lib/util/node-event-generator.js
index 05b343ae9f..34ee78b494 100644
--- a/tools/eslint/lib/util/node-event-generator.js
+++ b/tools/eslint/lib/util/node-event-generator.js
@@ -160,7 +160,7 @@ function tryParseSelector(rawSelector) {
return esquery.parse(rawSelector.replace(/:exit$/, ""));
} catch (err) {
if (typeof err.offset === "number") {
- throw new Error(`Syntax error in selector "${rawSelector}" at position ${err.offset}: ${err.message}`);
+ throw new SyntaxError(`Syntax error in selector "${rawSelector}" at position ${err.offset}: ${err.message}`);
}
throw err;
}
diff --git a/tools/eslint/lib/util/source-code.js b/tools/eslint/lib/util/source-code.js
index af01ff3faf..d3fed5989c 100644
--- a/tools/eslint/lib/util/source-code.js
+++ b/tools/eslint/lib/util/source-code.js
@@ -25,7 +25,6 @@ const TokenStore = require("../token-store"),
* @private
*/
function validate(ast) {
-
if (!ast.tokens) {
throw new Error("AST is missing the tokens array.");
}
@@ -44,34 +43,9 @@ function validate(ast) {
}
/**
- * Finds a JSDoc comment node in an array of comment nodes.
- * @param {ASTNode[]} comments The array of comment nodes to search.
- * @param {int} line Line number to look around
- * @returns {ASTNode} The node if found, null if not.
- * @private
- */
-function findJSDocComment(comments, line) {
-
- if (comments) {
- for (let i = comments.length - 1; i >= 0; i--) {
- if (comments[i].type === "Block" && comments[i].value.charAt(0) === "*") {
-
- if (line - comments[i].loc.end.line <= 1) {
- return comments[i];
- }
- break;
-
- }
- }
- }
-
- return null;
-}
-
-/**
- * Check to see if its a ES6 export declaration
- * @param {ASTNode} astNode - any node
- * @returns {boolean} whether the given node represents a export declaration
+ * Check to see if its a ES6 export declaration.
+ * @param {ASTNode} astNode An AST node.
+ * @returns {boolean} whether the given node represents an export declaration.
* @private
*/
function looksLikeExport(astNode) {
@@ -80,10 +54,11 @@ function looksLikeExport(astNode) {
}
/**
- * Merges two sorted lists into a larger sorted list in O(n) time
- * @param {Token[]} tokens The list of tokens
- * @param {Token[]} comments The list of comments
- * @returns {Token[]} A sorted list of tokens and comments
+ * Merges two sorted lists into a larger sorted list in O(n) time.
+ * @param {Token[]} tokens The list of tokens.
+ * @param {Token[]} comments The list of comments.
+ * @returns {Token[]} A sorted list of tokens and comments.
+ * @private
*/
function sortedMerge(tokens, comments) {
const result = [];
@@ -182,9 +157,9 @@ class SourceCode extends TokenStore {
}
/**
- * Split the source code into multiple lines based on the line delimiters
- * @param {string} text Source code as a string
- * @returns {string[]} Array of source code lines
+ * Split the source code into multiple lines based on the line delimiters.
+ * @param {string} text Source code as a string.
+ * @returns {string[]} Array of source code lines.
* @public
*/
static splitLines(text) {
@@ -197,6 +172,7 @@ class SourceCode extends TokenStore {
* @param {int=} beforeCount The number of characters before the node to retrieve.
* @param {int=} afterCount The number of characters after the node to retrieve.
* @returns {string} The text representing the AST node.
+ * @public
*/
getText(node, beforeCount, afterCount) {
if (node) {
@@ -209,6 +185,7 @@ class SourceCode extends TokenStore {
/**
* Gets the entire source text split into an array of lines.
* @returns {Array} The source text as an array of lines.
+ * @public
*/
getLines() {
return this.lines;
@@ -217,6 +194,7 @@ class SourceCode extends TokenStore {
/**
* Retrieves an array containing all comments in the source code.
* @returns {ASTNode[]} An array of comment nodes.
+ * @public
*/
getAllComments() {
return this.ast.comments;
@@ -225,7 +203,8 @@ class SourceCode extends TokenStore {
/**
* Gets all comments for the given node.
* @param {ASTNode} node The AST node to get the comments for.
- * @returns {Object} The list of comments indexed by their position.
+ * @returns {Object} An object containing a leading and trailing array
+ * of comments indexed by their position.
* @public
*/
getComments(node) {
@@ -297,47 +276,68 @@ class SourceCode extends TokenStore {
/**
* Retrieves the JSDoc comment for a given node.
* @param {ASTNode} node The AST node to get the comment for.
- * @returns {ASTNode} The Block comment node containing the JSDoc for the
- * given node or null if not found.
+ * @returns {Token|null} The Block comment token containing the JSDoc comment
+ * for the given node or null if not found.
* @public
*/
getJSDocComment(node) {
+
+ /**
+ * Checks for the presence of a JSDoc comment for the given node and returns it.
+ * @param {ASTNode} astNode The AST node to get the comment for.
+ * @returns {Token|null} The Block comment token containing the JSDoc comment
+ * for the given node or null if not found.
+ * @private
+ */
+ const findJSDocComment = astNode => {
+ const tokenBefore = this.getTokenBefore(astNode, { includeComments: true });
+
+ if (
+ tokenBefore &&
+ astUtils.isCommentToken(tokenBefore) &&
+ tokenBefore.type === "Block" &&
+ tokenBefore.value.charAt(0) === "*" &&
+ astNode.loc.start.line - tokenBefore.loc.end.line <= 1
+ ) {
+ return tokenBefore;
+ }
+
+ return null;
+ };
let parent = node.parent;
- const leadingComments = this.getCommentsBefore(node);
switch (node.type) {
case "ClassDeclaration":
case "FunctionDeclaration":
- if (looksLikeExport(parent)) {
- return findJSDocComment(this.getCommentsBefore(parent), parent.loc.start.line);
- }
- return findJSDocComment(leadingComments, node.loc.start.line);
+ return findJSDocComment(looksLikeExport(parent) ? parent : node);
case "ClassExpression":
- return findJSDocComment(this.getCommentsBefore(parent.parent), parent.parent.loc.start.line);
+ return findJSDocComment(parent.parent);
case "ArrowFunctionExpression":
case "FunctionExpression":
if (parent.type !== "CallExpression" && parent.type !== "NewExpression") {
- let parentLeadingComments = this.getCommentsBefore(parent);
-
- while (!parentLeadingComments.length && !/Function/.test(parent.type) && parent.type !== "MethodDefinition" && parent.type !== "Property") {
+ while (
+ !this.getCommentsBefore(parent).length &&
+ !/Function/.test(parent.type) &&
+ parent.type !== "MethodDefinition" &&
+ parent.type !== "Property"
+ ) {
parent = parent.parent;
if (!parent) {
break;
}
-
- parentLeadingComments = this.getCommentsBefore(parent);
}
- return parent && parent.type !== "FunctionDeclaration" && parent.type !== "Program" ? findJSDocComment(parentLeadingComments, parent.loc.start.line) : null;
- } else if (leadingComments.length) {
- return findJSDocComment(leadingComments, node.loc.start.line);
+ if (parent && parent.type !== "FunctionDeclaration" && parent.type !== "Program") {
+ return findJSDocComment(parent);
+ }
}
- // falls through
+ return findJSDocComment(node);
+ // falls through
default:
return null;
}
@@ -347,6 +347,7 @@ class SourceCode extends TokenStore {
* Gets the deepest node containing a range index.
* @param {int} index Range index of the desired node.
* @returns {ASTNode} The node if found or null if not found.
+ * @public
*/
getNodeByRangeIndex(index) {
let result = null,
@@ -380,6 +381,7 @@ class SourceCode extends TokenStore {
* @param {Token} second The token to check before.
* @returns {boolean} True if there is only space between tokens, false
* if there is anything other than whitespace between tokens.
+ * @public
*/
isSpaceBetweenTokens(first, second) {
const text = this.text.slice(first.range[1], second.range[0]);
@@ -388,10 +390,11 @@ class SourceCode extends TokenStore {
}
/**
- * Converts a source text index into a (line, column) pair.
- * @param {number} index The index of a character in a file
- * @returns {Object} A {line, column} location object with a 0-indexed column
- */
+ * Converts a source text index into a (line, column) pair.
+ * @param {number} index The index of a character in a file
+ * @returns {Object} A {line, column} location object with a 0-indexed column
+ * @public
+ */
getLocFromIndex(index) {
if (typeof index !== "number") {
throw new TypeError("Expected `index` to be a number.");
@@ -422,12 +425,13 @@ class SourceCode extends TokenStore {
}
/**
- * Converts a (line, column) pair into a range index.
- * @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.
- */
+ * Converts a (line, column) pair into a range index.
+ * @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.
+ * @public
+ */
getIndexFromLoc(loc) {
if (typeof loc !== "object" || typeof loc.line !== "number" || typeof loc.column !== "number") {
throw new TypeError("Expected `loc` to be an object with numeric `line` and `column` properties.");