-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(deps): bump eslint from 8.48.0 to 8.50.0 #1250
Merged
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
dependabot
bot
added
dependencies
Pull requests that update a dependency file
javascript
Pull requests that update Javascript code
labels
Oct 1, 2023
Diff between eslint 8.48.0 and 8.50.0diff --git a/lib/rules/array-callback-return.js b/lib/rules/array-callback-return.js
index v8.48.0..v8.50.0 100644
--- a/lib/rules/array-callback-return.js
+++ b/lib/rules/array-callback-return.js
@@ -20,13 +20,4 @@
/**
- * Checks a given code path segment is reachable.
- * @param {CodePathSegment} segment A segment to check.
- * @returns {boolean} `true` if the segment is reachable.
- */
-function isReachable(segment) {
- return segment.reachable;
-}
-
-/**
* Checks a given node is a member access which has the specified name's
* property.
@@ -40,4 +31,20 @@
/**
+ * Checks all segments in a set and returns true if any are reachable.
+ * @param {Set<CodePathSegment>} segments The segments to check.
+ * @returns {boolean} True if any segment is reachable; false otherwise.
+ */
+function isAnySegmentReachable(segments) {
+
+ for (const segment of segments) {
+ if (segment.reachable) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
* Returns a human-legible description of an array method
* @param {string} arrayMethodName A method name to fully qualify
@@ -130,4 +137,74 @@
}
+/**
+ * Checks if the given node is a void expression.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} - `true` if the node is a void expression
+ */
+function isExpressionVoid(node) {
+ return node.type === "UnaryExpression" && node.operator === "void";
+}
+
+/**
+ * Fixes the linting error by prepending "void " to the given node
+ * @param {Object} sourceCode context given by context.sourceCode
+ * @param {ASTNode} node The node to fix.
+ * @param {Object} fixer The fixer object provided by ESLint.
+ * @returns {Array<Object>} - An array of fix objects to apply to the node.
+ */
+function voidPrependFixer(sourceCode, node, fixer) {
+
+ const requiresParens =
+
+ // prepending `void ` will fail if the node has a lower precedence than void
+ astUtils.getPrecedence(node) < astUtils.getPrecedence({ type: "UnaryExpression", operator: "void" }) &&
+
+ // check if there are parentheses around the node to avoid redundant parentheses
+ !astUtils.isParenthesised(sourceCode, node);
+
+ // avoid parentheses issues
+ const returnOrArrowToken = sourceCode.getTokenBefore(
+ node,
+ node.parent.type === "ArrowFunctionExpression"
+ ? astUtils.isArrowToken
+
+ // isReturnToken
+ : token => token.type === "Keyword" && token.value === "return"
+ );
+
+ const firstToken = sourceCode.getTokenAfter(returnOrArrowToken);
+
+ const prependSpace =
+
+ // is return token, as => allows void to be adjacent
+ returnOrArrowToken.value === "return" &&
+
+ // If two tokens (return and "(") are adjacent
+ returnOrArrowToken.range[1] === firstToken.range[0];
+
+ return [
+ fixer.insertTextBefore(firstToken, `${prependSpace ? " " : ""}void ${requiresParens ? "(" : ""}`),
+ fixer.insertTextAfter(node, requiresParens ? ")" : "")
+ ];
+}
+
+/**
+ * Fixes the linting error by `wrapping {}` around the given node's body.
+ * @param {Object} sourceCode context given by context.sourceCode
+ * @param {ASTNode} node The node to fix.
+ * @param {Object} fixer The fixer object provided by ESLint.
+ * @returns {Array<Object>} - An array of fix objects to apply to the node.
+ */
+function curlyWrapFixer(sourceCode, node, fixer) {
+ const arrowToken = sourceCode.getTokenBefore(node.body, astUtils.isArrowToken);
+ const firstToken = sourceCode.getTokenAfter(arrowToken);
+ const lastToken = sourceCode.getLastToken(node);
+
+ return [
+ fixer.insertTextBefore(firstToken, "{"),
+ fixer.insertTextAfter(lastToken, "}")
+ ];
+}
+
//------------------------------------------------------------------------------
// Rule Definition
@@ -145,4 +222,7 @@
},
+ // eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- false positive
+ hasSuggestions: true,
+
schema: [
{
@@ -156,4 +236,8 @@
type: "boolean",
default: false
+ },
+ allowVoid: {
+ type: "boolean",
+ default: false
}
},
@@ -166,5 +250,7 @@
expectedInside: "{{arrayMethodName}}() expects a return value from {{name}}.",
expectedReturnValue: "{{arrayMethodName}}() expects a return value from {{name}}.",
- expectedNoReturnValue: "{{arrayMethodName}}() expects no useless return value from {{name}}."
+ expectedNoReturnValue: "{{arrayMethodName}}() expects no useless return value from {{name}}.",
+ wrapBraces: "Wrap the expression in `{}`.",
+ prependVoid: "Prepend `void` to the expression."
}
},
@@ -172,5 +258,5 @@
create(context) {
- const options = context.options[0] || { allowImplicit: false, checkForEach: false };
+ const options = context.options[0] || { allowImplicit: false, checkForEach: false, allowVoid: false };
const sourceCode = context.sourceCode;
@@ -199,17 +285,46 @@
}
- let messageId = null;
+ const messageAndSuggestions = { messageId: "", suggest: [] };
if (funcInfo.arrayMethodName === "forEach") {
if (options.checkForEach && node.type === "ArrowFunctionExpression" && node.expression) {
- messageId = "expectedNoReturnValue";
+
+ if (options.allowVoid) {
+ if (isExpressionVoid(node.body)) {
+ return;
+ }
+
+ messageAndSuggestions.messageId = "expectedNoReturnValue";
+ messageAndSuggestions.suggest = [
+ {
+ messageId: "wrapBraces",
+ fix(fixer) {
+ return curlyWrapFixer(sourceCode, node, fixer);
+ }
+ },
+ {
+ messageId: "prependVoid",
+ fix(fixer) {
+ return voidPrependFixer(sourceCode, node.body, fixer);
+ }
+ }
+ ];
+ } else {
+ messageAndSuggestions.messageId = "expectedNoReturnValue";
+ messageAndSuggestions.suggest = [{
+ messageId: "wrapBraces",
+ fix(fixer) {
+ return curlyWrapFixer(sourceCode, node, fixer);
+ }
+ }];
+ }
}
} else {
- if (node.body.type === "BlockStatement" && funcInfo.codePath.currentSegments.some(isReachable)) {
- messageId = funcInfo.hasReturn ? "expectedAtEnd" : "expectedInside";
+ if (node.body.type === "BlockStatement" && isAnySegmentReachable(funcInfo.currentSegments)) {
+ messageAndSuggestions.messageId = funcInfo.hasReturn ? "expectedAtEnd" : "expectedInside";
}
}
- if (messageId) {
+ if (messageAndSuggestions.messageId) {
const name = astUtils.getFunctionNameWithKind(node);
@@ -217,6 +332,7 @@
node,
loc: astUtils.getFunctionHeadLoc(node, sourceCode),
- messageId,
- data: { name, arrayMethodName: fullMethodName(funcInfo.arrayMethodName) }
+ messageId: messageAndSuggestions.messageId,
+ data: { name, arrayMethodName: fullMethodName(funcInfo.arrayMethodName) },
+ suggest: messageAndSuggestions.suggest.length !== 0 ? messageAndSuggestions.suggest : null
});
}
@@ -243,5 +359,6 @@
!node.async &&
!node.generator,
- node
+ node,
+ currentSegments: new Set()
};
},
@@ -252,4 +369,21 @@
},
+ onUnreachableCodePathSegmentStart(segment) {
+ funcInfo.currentSegments.add(segment);
+ },
+
+ onUnreachableCodePathSegmentEnd(segment) {
+ funcInfo.currentSegments.delete(segment);
+ },
+
+ onCodePathSegmentStart(segment) {
+ funcInfo.currentSegments.add(segment);
+ },
+
+ onCodePathSegmentEnd(segment) {
+ funcInfo.currentSegments.delete(segment);
+ },
+
+
// Checks the return statement is valid.
ReturnStatement(node) {
@@ -261,5 +395,5 @@
funcInfo.hasReturn = true;
- let messageId = null;
+ const messageAndSuggestions = { messageId: "", suggest: [] };
if (funcInfo.arrayMethodName === "forEach") {
@@ -267,5 +401,20 @@
// if checkForEach: true, returning a value at any path inside a forEach is not allowed
if (options.checkForEach && node.argument) {
- messageId = "expectedNoReturnValue";
+
+ if (options.allowVoid) {
+ if (isExpressionVoid(node.argument)) {
+ return;
+ }
+
+ messageAndSuggestions.messageId = "expectedNoReturnValue";
+ messageAndSuggestions.suggest = [{
+ messageId: "prependVoid",
+ fix(fixer) {
+ return voidPrependFixer(sourceCode, node.argument, fixer);
+ }
+ }];
+ } else {
+ messageAndSuggestions.messageId = "expectedNoReturnValue";
+ }
}
} else {
@@ -273,16 +422,17 @@
// if allowImplicit: false, should also check node.argument
if (!options.allowImplicit && !node.argument) {
- messageId = "expectedReturnValue";
+ messageAndSuggestions.messageId = "expectedReturnValue";
}
}
- if (messageId) {
+ if (messageAndSuggestions.messageId) {
context.report({
node,
- messageId,
+ messageId: messageAndSuggestions.messageId,
data: {
name: astUtils.getFunctionNameWithKind(funcInfo.node),
arrayMethodName: fullMethodName(funcInfo.arrayMethodName)
- }
+ },
+ suggest: messageAndSuggestions.suggest.length !== 0 ? messageAndSuggestions.suggest : null
});
}
diff --git a/lib/linter/code-path-analysis/code-path-analyzer.js b/lib/linter/code-path-analysis/code-path-analyzer.js
index v8.48.0..v8.50.0 100644
--- a/lib/linter/code-path-analysis/code-path-analyzer.js
+++ b/lib/linter/code-path-analysis/code-path-analyzer.js
@@ -193,13 +193,16 @@
if (currentSegment !== headSegment && currentSegment) {
- debug.dump(`onCodePathSegmentEnd ${currentSegment.id}`);
- if (currentSegment.reachable) {
- analyzer.emitter.emit(
- "onCodePathSegmentEnd",
- currentSegment,
- node
- );
- }
+ const eventName = currentSegment.reachable
+ ? "onCodePathSegmentEnd"
+ : "onUnreachableCodePathSegmentEnd";
+
+ debug.dump(`${eventName} ${currentSegment.id}`);
+
+ analyzer.emitter.emit(
+ eventName,
+ currentSegment,
+ node
+ );
}
}
@@ -214,14 +217,17 @@
if (currentSegment !== headSegment && headSegment) {
- debug.dump(`onCodePathSegmentStart ${headSegment.id}`);
+ const eventName = headSegment.reachable
+ ? "onCodePathSegmentStart"
+ : "onUnreachableCodePathSegmentStart";
+
+ debug.dump(`${eventName} ${headSegment.id}`);
+
CodePathSegment.markUsed(headSegment);
- if (headSegment.reachable) {
- analyzer.emitter.emit(
- "onCodePathSegmentStart",
- headSegment,
- node
- );
- }
+ analyzer.emitter.emit(
+ eventName,
+ headSegment,
+ node
+ );
}
}
@@ -242,13 +248,15 @@
for (let i = 0; i < currentSegments.length; ++i) {
const currentSegment = currentSegments[i];
+ const eventName = currentSegment.reachable
+ ? "onCodePathSegmentEnd"
+ : "onUnreachableCodePathSegmentEnd";
- debug.dump(`onCodePathSegmentEnd ${currentSegment.id}`);
- if (currentSegment.reachable) {
- analyzer.emitter.emit(
- "onCodePathSegmentEnd",
- currentSegment,
- node
- );
- }
+ debug.dump(`${eventName} ${currentSegment.id}`);
+
+ analyzer.emitter.emit(
+ eventName,
+ currentSegment,
+ node
+ );
}
diff --git a/lib/linter/code-path-analysis/code-path.js b/lib/linter/code-path-analysis/code-path.js
index v8.48.0..v8.50.0 100644
--- a/lib/linter/code-path-analysis/code-path.js
+++ b/lib/linter/code-path-analysis/code-path.js
@@ -118,4 +118,5 @@
* Current code path segments.
* @type {CodePathSegment[]}
+ * @deprecated
*/
get currentSegments() {
diff --git a/lib/rules/consistent-return.js b/lib/rules/consistent-return.js
index v8.48.0..v8.50.0 100644
--- a/lib/rules/consistent-return.js
+++ b/lib/rules/consistent-return.js
@@ -17,10 +17,17 @@
/**
- * Checks whether or not a given code path segment is unreachable.
- * @param {CodePathSegment} segment A CodePathSegment to check.
- * @returns {boolean} `true` if the segment is unreachable.
+ * Checks all segments in a set and returns true if all are unreachable.
+ * @param {Set<CodePathSegment>} segments The segments to check.
+ * @returns {boolean} True if all segments are unreachable; false otherwise.
*/
-function isUnreachable(segment) {
- return !segment.reachable;
+function areAllSegmentsUnreachable(segments) {
+
+ for (const segment of segments) {
+ if (segment.reachable) {
+ return false;
+ }
+ }
+
+ return true;
}
@@ -89,5 +96,5 @@
*/
if (!funcInfo.hasReturnValue ||
- funcInfo.codePath.currentSegments.every(isUnreachable) ||
+ areAllSegmentsUnreachable(funcInfo.currentSegments) ||
astUtils.isES5Constructor(node) ||
isClassConstructor(node)
@@ -142,5 +149,6 @@
hasReturnValue: false,
messageId: "",
- node
+ node,
+ currentSegments: new Set()
};
},
@@ -149,4 +157,21 @@
},
+ onUnreachableCodePathSegmentStart(segment) {
+ funcInfo.currentSegments.add(segment);
+ },
+
+ onUnreachableCodePathSegmentEnd(segment) {
+ funcInfo.currentSegments.delete(segment);
+ },
+
+ onCodePathSegmentStart(segment) {
+ funcInfo.currentSegments.add(segment);
+ },
+
+ onCodePathSegmentEnd(segment) {
+ funcInfo.currentSegments.delete(segment);
+ },
+
+
// Reports a given return statement if it's inconsistent.
ReturnStatement(node) {
diff --git a/lib/rules/constructor-super.js b/lib/rules/constructor-super.js
index v8.48.0..v8.50.0 100644
--- a/lib/rules/constructor-super.js
+++ b/lib/rules/constructor-super.js
@@ -11,10 +11,17 @@
/**
- * Checks whether a given code path segment is reachable or not.
- * @param {CodePathSegment} segment A code path segment to check.
- * @returns {boolean} `true` if the segment is reachable.
+ * Checks all segments in a set and returns true if any are reachable.
+ * @param {Set<CodePathSegment>} segments The segments to check.
+ * @returns {boolean} True if any segment is reachable; false otherwise.
*/
-function isReachable(segment) {
- return segment.reachable;
+function isAnySegmentReachable(segments) {
+
+ for (const segment of segments) {
+ if (segment.reachable) {
+ return true;
+ }
+ }
+
+ return false;
}
@@ -211,5 +218,6 @@
hasExtends: Boolean(superClass),
superIsConstructor: isPossibleConstructor(superClass),
- codePath
+ codePath,
+ currentSegments: new Set()
};
} else {
@@ -219,5 +227,6 @@
hasExtends: false,
superIsConstructor: false,
- codePath
+ codePath,
+ currentSegments: new Set()
};
}
@@ -262,4 +271,7 @@
*/
onCodePathSegmentStart(segment) {
+
+ funcInfo.currentSegments.add(segment);
+
if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
return;
@@ -282,4 +294,17 @@
},
+ onUnreachableCodePathSegmentStart(segment) {
+ funcInfo.currentSegments.add(segment);
+ },
+
+ onUnreachableCodePathSegmentEnd(segment) {
+ funcInfo.currentSegments.delete(segment);
+ },
+
+ onCodePathSegmentEnd(segment) {
+ funcInfo.currentSegments.delete(segment);
+ },
+
+
/**
* Update information of the code path segment when a code path was
@@ -345,10 +370,9 @@
// Reports if needed.
if (funcInfo.hasExtends) {
- const segments = funcInfo.codePath.currentSegments;
+ const segments = funcInfo.currentSegments;
let duplicate = false;
let info = null;
- for (let i = 0; i < segments.length; ++i) {
- const segment = segments[i];
+ for (const segment of segments) {
if (segment.reachable) {
@@ -375,5 +399,5 @@
}
}
- } else if (funcInfo.codePath.currentSegments.some(isReachable)) {
+ } else if (isAnySegmentReachable(funcInfo.currentSegments)) {
context.report({
messageId: "unexpected",
@@ -399,8 +423,7 @@
// Returning argument is a substitute of 'super()'.
- const segments = funcInfo.codePath.currentSegments;
+ const segments = funcInfo.currentSegments;
- for (let i = 0; i < segments.length; ++i) {
- const segment = segments[i];
+ for (const segment of segments) {
if (segment.reachable) {
diff --git a/lib/config/flat-config-schema.js b/lib/config/flat-config-schema.js
index v8.48.0..v8.50.0 100644
--- a/lib/config/flat-config-schema.js
+++ b/lib/config/flat-config-schema.js
@@ -508,5 +508,5 @@
//-----------------------------------------------------------------------------
-exports.flatConfigSchema = {
+const flatConfigSchema = {
// eslintrc-style keys that should always error
@@ -534,2 +534,12 @@
rules: rulesSchema
};
+
+//-----------------------------------------------------------------------------
+// Exports
+//-----------------------------------------------------------------------------
+
+module.exports = {
+ flatConfigSchema,
+ assertIsRuleSeverity,
+ assertIsRuleOptions
+};
diff --git a/lib/rule-tester/flat-rule-tester.js b/lib/rule-tester/flat-rule-tester.js
index v8.48.0..v8.50.0 100644
--- a/lib/rule-tester/flat-rule-tester.js
+++ b/lib/rule-tester/flat-rule-tester.js
@@ -17,5 +17,7 @@
Traverser = require("../shared/traverser"),
{ getRuleOptionsSchema } = require("../config/flat-config-helpers"),
- { Linter, SourceCodeFixer, interpolate } = require("../linter");
+ { Linter, SourceCodeFixer, interpolate } = require("../linter"),
+ CodePath = require("../linter/code-path-analysis/code-path");
+
const { FlatConfigArray } = require("../config/flat-config-array");
const { defaultConfig } = require("../config/default-config");
@@ -132,4 +134,13 @@
const friendlySuggestionObjectParameterList = `[${[...suggestionObjectParameters].map(key => `'${key}'`).join(", ")}]`;
+const forbiddenMethods = [
+ "applyInlineConfig",
+ "applyLanguageOptions",
+ "finalize"
+];
+
+/** @type {Map<string,WeakSet>} */
+const forbiddenMethodCalls = new Map(forbiddenMethods.map(methodName => ([methodName, new WeakSet()])));
+
const hasOwnProperty = Function.call.bind(Object.hasOwnProperty);
@@ -275,4 +286,47 @@
}
+/**
+ * Emit a deprecation warning if rule uses CodePath#currentSegments.
+ * @param {string} ruleName Name of the rule.
+ * @returns {void}
+ */
+function emitCodePathCurrentSegmentsWarning(ruleName) {
+ if (!emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`]) {
+ emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`] = true;
+ process.emitWarning(
+ `"${ruleName}" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples`,
+ "DeprecationWarning"
+ );
+ }
+}
+
+/**
+ * Function to replace forbidden `SourceCode` methods. Allows just one call per method.
+ * @param {string} methodName The name of the method to forbid.
+ * @param {Function} prototype The prototype with the original method to call.
+ * @returns {Function} The function that throws the error.
+ */
+function throwForbiddenMethodError(methodName, prototype) {
+
+ const original = prototype[methodName];
+
+ return function(...args) {
+
+ const called = forbiddenMethodCalls.get(methodName);
+
+ /* eslint-disable no-invalid-this -- needed to operate as a method. */
+ if (!called.has(this)) {
+ called.add(this);
+
+ return original.apply(this, args);
+ }
+ /* eslint-enable no-invalid-this -- not needed past this point */
+
+ throw new Error(
+ `\`SourceCode#${methodName}()\` cannot be called inside a rule.`
+ );
+ };
+}
+
//------------------------------------------------------------------------------
// Public Interface
@@ -482,4 +536,5 @@
const baseConfig = [
+ { files: ["**"] }, // Make sure the default config matches for all files
{
plugins: {
@@ -663,8 +718,4 @@
}
- // Verify the code.
- const { getComments } = SourceCode.prototype;
- let messages;
-
// check for validation errors
try {
@@ -676,11 +727,32 @@
}
+ // Verify the code.
+ const { getComments, applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype;
+ const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments");
+ let messages;
+
try {
SourceCode.prototype.getComments = getCommentsDeprecation;
+ Object.defineProperty(CodePath.prototype, "currentSegments", {
+ get() {
+ emitCodePathCurrentSegmentsWarning(ruleName);
+ return originalCurrentSegments.get.call(this);
+ }
+ });
+
+ forbiddenMethods.forEach(methodName => {
+ SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName, SourceCode.prototype);
+ });
+
messages = linter.verify(code, configs, filename);
} finally {
SourceCode.prototype.getComments = getComments;
+ Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments);
+ SourceCode.prototype.applyInlineConfig = applyInlineConfig;
+ SourceCode.prototype.applyLanguageOptions = applyLanguageOptions;
+ SourceCode.prototype.finalize = finalize;
}
+
const fatalErrorMessage = messages.find(m => m.fatal);
diff --git a/lib/rules/getter-return.js b/lib/rules/getter-return.js
index v8.48.0..v8.50.0 100644
--- a/lib/rules/getter-return.js
+++ b/lib/rules/getter-return.js
@@ -15,13 +15,21 @@
// Helpers
//------------------------------------------------------------------------------
+
const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u;
/**
- * Checks a given code path segment is reachable.
- * @param {CodePathSegment} segment A segment to check.
- * @returns {boolean} `true` if the segment is reachable.
+ * Checks all segments in a set and returns true if any are reachable.
+ * @param {Set<CodePathSegment>} segments The segments to check.
+ * @returns {boolean} True if any segment is reachable; false otherwise.
*/
-function isReachable(segment) {
- return segment.reachable;
+function isAnySegmentReachable(segments) {
+
+ for (const segment of segments) {
+ if (segment.reachable) {
+ return true;
+ }
+ }
+
+ return false;
}
@@ -72,5 +80,6 @@
hasReturn: false,
shouldCheck: false,
- node: null
+ node: null,
+ currentSegments: []
};
@@ -86,5 +95,5 @@
function checkLastSegment(node) {
if (funcInfo.shouldCheck &&
- funcInfo.codePath.currentSegments.some(isReachable)
+ isAnySegmentReachable(funcInfo.currentSegments)
) {
context.report({
@@ -145,5 +154,6 @@
hasReturn: false,
shouldCheck: isGetter(node),
- node
+ node,
+ currentSegments: new Set()
};
},
@@ -153,5 +163,20 @@
funcInfo = funcInfo.upper;
},
+ onUnreachableCodePathSegmentStart(segment) {
+ funcInfo.currentSegments.add(segment);
+ },
+ onUnreachableCodePathSegmentEnd(segment) {
+ funcInfo.currentSegments.delete(segment);
+ },
+
+ onCodePathSegmentStart(segment) {
+ funcInfo.currentSegments.add(segment);
+ },
+
+ onCodePathSegmentEnd(segment) {
+ funcInfo.currentSegments.delete(segment);
+ },
+
// Checks the return statement is valid.
ReturnStatement(node) {
diff --git a/lib/rules/index.js b/lib/rules/index.js
index v8.48.0..v8.50.0 100644
--- a/lib/rules/index.js
+++ b/lib/rules/index.js
@@ -176,4 +176,5 @@
"no-nonoctal-decimal-escape": () => require("./no-nonoctal-decimal-escape"),
"no-obj-calls": () => require("./no-obj-calls"),
+ "no-object-constructor": () => require("./no-object-constructor"),
"no-octal": () => require("./no-octal"),
"no-octal-escape": () => require("./no-octal-escape"),
diff --git a/lib/rules/lines-between-class-members.js b/lib/rules/lines-between-class-members.js
index v8.48.0..v8.50.0 100644
--- a/lib/rules/lines-between-class-members.js
+++ b/lib/rules/lines-between-class-members.js
@@ -12,4 +12,19 @@
//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Types of class members.
+ * Those have `test` method to check it matches to the given class member.
+ * @private
+ */
+const ClassMemberTypes = {
+ "*": { test: () => true },
+ field: { test: node => node.type === "PropertyDefinition" },
+ method: { test: node => node.type === "MethodDefinition" }
+};
+
+//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -30,5 +45,30 @@
schema: [
{
- enum: ["always", "never"]
+ anyOf: [
+ {
+ type: "object",
+ properties: {
+ enforce: {
+ type: "array",
+ items: {
+ type: "object",
+ properties: {
+ blankLine: { enum: ["always", "never"] },
+ prev: { enum: ["method", "field", "*"] },
+ next: { enum: ["method", "field", "*"] }
+ },
+ additionalProperties: false,
+ required: ["blankLine", "prev", "next"]
+ },
+ minItems: 1
+ }
+ },
+ additionalProperties: false,
+ required: ["enforce"]
+ },
+ {
+ enum: ["always", "never"]
+ }
+ ]
},
{
@@ -56,4 +96,5 @@
options[1] = context.options[1] || { exceptAfterSingleLine: false };
+ const configureList = typeof options[0] === "object" ? options[0].enforce : [{ blankLine: options[0], prev: "*", next: "*" }];
const sourceCode = context.sourceCode;
@@ -145,4 +186,36 @@
}
+ /**
+ * Checks whether the given node matches the given type.
+ * @param {ASTNode} node The class member node to check.
+ * @param {string} type The class member type to check.
+ * @returns {boolean} `true` if the class member node matched the type.
+ * @private
+ */
+ function match(node, type) {
+ return ClassMemberTypes[type].test(node);
+ }
+
+ /**
+ * Finds the last matched configuration from the configureList.
+ * @param {ASTNode} prevNode The previous node to match.
+ * @param {ASTNode} nextNode The current node to match.
+ * @returns {string|null} Padding type or `null` if no matches were found.
+ * @private
+ */
+ function getPaddingType(prevNode, nextNode) {
+ for (let i = configureList.length - 1; i >= 0; --i) {
+ const configure = configureList[i];
+ const matched =
+ match(prevNode, configure.prev) &&
+ match(nextNode, configure.next);
+
+ if (matched) {
+ return configure.blankLine;
+ }
+ }
+ return null;
+ }
+
return {
ClassBody(node) {
@@ -159,20 +232,32 @@
const hasTokenInPadding = hasTokenOrCommentBetween(beforePadding, afterPadding);
const curLineLastToken = findLastConsecutiveTokenAfter(curLast, nextFirst, 0);
+ const paddingType = getPaddingType(body[i], body[i + 1]);
- if ((options[0] === "always" && !skip && !isPadded) ||
- (options[0] === "never" && isPadded)) {
+ if (paddingType === "never" && isPadded) {
context.report({
node: body[i + 1],
- messageId: isPadded ? "never" : "always",
+ messageId: "never",
+
fix(fixer) {
if (hasTokenInPadding) {
return null;
}
- return isPadded
- ? fixer.replaceTextRange([beforePadding.range[1], afterPadding.range[0]], "\n")
- : fixer.insertTextAfter(curLineLastToken, "\n");
+ return fixer.replaceTextRange([beforePadding.range[1], afterPadding.range[0]], "\n");
}
});
+ } else if (paddingType === "always" && !skip && !isPadded) {
+ context.report({
+ node: body[i + 1],
+ messageId: "always",
+
+ fix(fixer) {
+ if (hasTokenInPadding) {
+ return null;
+ }
+ return fixer.insertTextAfter(curLineLastToken, "\n");
+ }
+ });
}
+
}
}
diff --git a/lib/linter/linter.js b/lib/linter/linter.js
index v8.48.0..v8.50.0 100644
--- a/lib/linter/linter.js
+++ b/lib/linter/linter.js
@@ -43,5 +43,6 @@
const { getRuleFromConfig } = require("../config/flat-config-helpers");
const { FlatConfigArray } = require("../config/flat-config-array");
-
+const { RuleValidator } = require("../config/rule-validator");
+const { assertIsRuleOptions, assertIsRuleSeverity } = require("../config/flat-config-schema");
const debug = require("debug")("eslint:linter");
const MAX_AUTOFIX_PASSES = 10;
@@ -51,5 +52,4 @@
const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } };
const parserSymbol = Symbol.for("eslint.RuleTester.parser");
-const globals = require("../../conf/globals");
//------------------------------------------------------------------------------
@@ -147,27 +147,4 @@
/**
- * Retrieves globals for the given ecmaVersion.
- * @param {number} ecmaVersion The version to retrieve globals for.
- * @returns {Object} The globals for the given ecmaVersion.
- */
-function getGlobalsForEcmaVersion(ecmaVersion) {
-
- switch (ecmaVersion) {
- case 3:
- return globals.es3;
-
- case 5:
- return globals.es5;
-
- default:
- if (ecmaVersion < 2015) {
- return globals[`es${ecmaVersion + 2009}`];
- }
-
- return globals[`es${ecmaVersion}`];
- }
-}
-
-/**
* Ensures that variables representing built-in properties of the Global Object,
* and any globals declared by special block comments, are present in the global
@@ -362,5 +339,5 @@
* and environments and merges them with global config; also code blocks
* where reporting is disabled or enabled and merges them with reporting config.
- * @param {ASTNode} ast The top node of the AST.
+ * @param {SourceCode} sourceCode The SourceCode object to get comments from.
* @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
* @param {string|null} warnInlineConfig If a string then it should warn directive comments as disabled. The string value is the config name what the setting came from.
@@ -368,5 +345,5 @@
* A collection of the directive comments that were found, along with any problems that occurred when parsing
*/
-function getDirectiveComments(ast, ruleMapper, warnInlineConfig) {
+function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig) {
const configuredRules = {};
const enabledGlobals = Object.create(null);
@@ -378,5 +355,5 @@
});
- ast.comments.filter(token => token.type !== "Shebang").forEach(comment => {
+ sourceCode.getInlineConfigNodes().filter(token => token.type !== "Shebang").forEach(comment => {
const { directivePart, justificationPart } = extractDirectiveComment(comment.value);
@@ -513,4 +490,67 @@
/**
+ * Parses comments in file to extract disable directives.
+ * @param {SourceCode} sourceCode The SourceCode object to get comments from.
+ * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
+ * @returns {{problems: LintMessage[], disableDirectives: DisableDirective[]}}
+ * A collection of the directive comments that were found, along with any problems that occurred when parsing
+ */
+function getDirectiveCommentsForFlatConfig(sourceCode, ruleMapper) {
+ const problems = [];
+ const disableDirectives = [];
+
+ sourceCode.getInlineConfigNodes().filter(token => token.type !== "Shebang").forEach(comment => {
+ const { directivePart, justificationPart } = extractDirectiveComment(comment.value);
+
+ const match = directivesPattern.exec(directivePart);
+
+ if (!match) {
+ return;
+ }
+ const directiveText = match[1];
+ const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(directiveText);
+
+ if (comment.type === "Line" && !lineCommentSupported) {
+ return;
+ }
+
+ if (directiveText === "eslint-disable-line" && comment.loc.start.line !== comment.loc.end.line) {
+ const message = `${directiveText} comment should not span multiple lines.`;
+
+ problems.push(createLintingProblem({
+ ruleId: null,
+ message,
+ loc: comment.loc
+ }));
+ return;
+ }
+
+ const directiveValue = directivePart.slice(match.index + directiveText.length);
+
+ switch (directiveText) {
+ case "eslint-disable":
+ case "eslint-enable":
+ case "eslint-disable-next-line":
+ case "eslint-disable-line": {
+ const directiveType = directiveText.slice("eslint-".length);
+ const options = { commentToken: comment, type: directiveType, value: directiveValue, justification: justificationPart, ruleMapper };
+ const { directives, directiveProblems } = createDisableDirectives(options);
+
+ disableDirectives.push(...directives);
+ problems.push(...directiveProblems);
+ break;
+ }
+
+ // no default
+ }
+ });
+
+ return {
+ problems,
+ disableDirectives
+ };
+}
+
+/**
* Normalize ECMAScript version from the initial config
* @param {Parser} parser The parser which uses this options.
@@ -899,4 +939,5 @@
};
+
const BASE_TRAVERSAL_CONTEXT = Object.freeze(
Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).reduce(
@@ -1313,5 +1354,5 @@
const sourceCode = slots.lastSourceCode;
const commentDirectives = options.allowInlineConfig
- ? getDirectiveComments(sourceCode.ast, ruleId => getRule(slots, ruleId), options.warnInlineConfig)
+ ? getDirectiveComments(sourceCode, ruleId => getRule(slots, ruleId), options.warnInlineConfig)
: { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] };
@@ -1324,5 +1365,4 @@
const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules);
-
let lintingProblems;
@@ -1540,17 +1580,4 @@
);
- /*
- * add configured globals and language globals
- *
- * using Object.assign instead of object spread for performance reasons
- * https://github.com/eslint/eslint/issues/16302
- */
- const configuredGlobals = Object.assign(
- {},
- getGlobalsForEcmaVersion(languageOptions.ecmaVersion),
- languageOptions.sourceType === "commonjs" ? globals.commonjs : void 0,
- languageOptions.globals
- );
-
// double check that there is a parser to avoid mysterious error messages
if (!languageOptions.parser) {
@@ -1608,23 +1635,111 @@
const sourceCode = slots.lastSourceCode;
- const commentDirectives = options.allowInlineConfig
- ? getDirectiveComments(
- sourceCode.ast,
- ruleId => getRuleFromConfig(ruleId, config),
- options.warnInlineConfig
- )
- : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] };
- // augment global scope with declared global variables
- addDeclaredGlobals(
- sourceCode.scopeManager.scopes[0],
- configuredGlobals,
- { exportedVariables: commentDirectives.exportedVariables, enabledGlobals: commentDirectives.enabledGlobals }
- );
+ /*
+ * Make adjustments based on the language options. For JavaScript,
+ * this is primarily about adding variables into the global scope
+ * to account for ecmaVersion and configured globals.
+ */
+ sourceCode.applyLanguageOptions(languageOptions);
- const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules);
+ const mergedInlineConfig = {
+ rules: {}
+ };
+ const inlineConfigProblems = [];
+ /*
+ * Inline config can be either enabled or disabled. If disabled, it's possible
+ * to detect the inline config and emit a warning (though this is not required).
+ * So we first check to see if inline config is allowed at all, and if so, we
+ * need to check if it's a warning or not.
+ */
+ if (options.allowInlineConfig) {
+
+ // if inline config should warn then add the warnings
+ if (options.warnInlineConfig) {
+ sourceCode.getInlineConfigNodes().forEach(node => {
+ inlineConfigProblems.push(createLintingProblem({
+ ruleId: null,
+ message: `'${sourceCode.text.slice(node.range[0], node.range[1])}' has no effect because you have 'noInlineConfig' setting in ${options.warnInlineConfig}.`,
+ loc: node.loc,
+ severity: 1
+ }));
+
+ });
+ } else {
+ const inlineConfigResult = sourceCode.applyInlineConfig();
+
+ inlineConfigProblems.push(
+ ...inlineConfigResult.problems
+ .map(createLintingProblem)
+ .map(problem => {
+ problem.fatal = true;
+ return problem;
+ })
+ );
+
+ // next we need to verify information about the specified rules
+ const ruleValidator = new RuleValidator();
+
+ for (const { config: inlineConfig, node } of inlineConfigResult.configs) {
+
+ Object.keys(inlineConfig.rules).forEach(ruleId => {
+ const rule = getRuleFromConfig(ruleId, config);
+ const ruleValue = inlineConfig.rules[ruleId];
+
+ if (!rule) {
+ inlineConfigProblems.push(createLintingProblem({ ruleId, loc: node.loc }));
+ return;
+ }
+
+ try {
+
+ const ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue];
+
+ assertIsRuleOptions(ruleId, ruleValue);
+ assertIsRuleSeverity(ruleId, ruleOptions[0]);
+
+ ruleValidator.validate({
+ plugins: config.plugins,
+ rules: {
+ [ruleId]: ruleOptions
+ }
+ });
+ mergedInlineConfig.rules[ruleId] = ruleValue;
+ } catch (err) {
+
+ let baseMessage = err.message.slice(
+ err.message.startsWith("Key \"rules\":")
+ ? err.message.indexOf(":", 12) + 1
+ : err.message.indexOf(":") + 1
+ ).trim();
+
+ if (err.messageTemplate) {
+ baseMessage += ` You passed "${ruleValue}".`;
+ }
+
+ inlineConfigProblems.push(createLintingProblem({
+ ruleId,
+ message: `Inline configuration for rule "${ruleId}" is invalid:\n\t${baseMessage}\n`,
+ loc: node.loc
+ }));
+ }
+ });
+ }
+ }
+ }
+
+ const commentDirectives = options.allowInlineConfig && !options.warnInlineConfig
+ ? getDirectiveCommentsForFlatConfig(
+ sourceCode,
+ ruleId => getRuleFromConfig(ruleId, config)
+ )
+ : { problems: [], disableDirectives: [] };
+
+ const configuredRules = Object.assign({}, config.rules, mergedInlineConfig.rules);
let lintingProblems;
+ sourceCode.finalize();
+
try {
lintingProblems = runRules(
@@ -1667,4 +1782,5 @@
problems: lintingProblems
.concat(commentDirectives.problems)
+ .concat(inlineConfigProblems)
.sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column),
reportUnusedDisableDirectives: options.reportUnusedDisableDirectives
diff --git a/lib/rules/no-fallthrough.js b/lib/rules/no-fallthrough.js
index v8.48.0..v8.50.0 100644
--- a/lib/rules/no-fallthrough.js
+++ b/lib/rules/no-fallthrough.js
@@ -18,4 +18,20 @@
/**
+ * Checks all segments in a set and returns true if any are reachable.
+ * @param {Set<CodePathSegment>} segments The segments to check.
+ * @returns {boolean} True if any segment is reachable; false otherwise.
+ */
+function isAnySegmentReachable(segments) {
+
+ for (const segment of segments) {
+ if (segment.reachable) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
* Checks whether or not a given comment string is really a fallthrough comment and not an ESLint directive.
* @param {string} comment The comment string to check.
@@ -53,13 +69,4 @@
/**
- * Checks whether or not a given code path segment is reachable.
- * @param {CodePathSegment} segment A CodePathSegment to check.
- * @returns {boolean} `true` if the segment is reachable.
- */
-function isReachable(segment) {
- return segment.reachable;
-}
-
-/**
* Checks whether a node and a token are separated by blank lines
* @param {ASTNode} node The node to check
@@ -110,5 +117,6 @@
create(context) {
const options = context.options[0] || {};
- let currentCodePath = null;
+ const codePathSegments = [];
+ let currentCodePathSegments = new Set();
const sourceCode = context.sourceCode;
const allowEmptyCase = options.allowEmptyCase || false;
@@ -127,11 +135,31 @@
}
return {
- onCodePathStart(codePath) {
- currentCodePath = codePath;
+
+ onCodePathStart() {
+ codePathSegments.push(currentCodePathSegments);
+ currentCodePathSegments = new Set();
},
+
onCodePathEnd() {
- currentCodePath = currentCodePath.upper;
+ currentCodePathSegments = codePathSegments.pop();
},
+ onUnreachableCodePathSegmentStart(segment) {
+ currentCodePathSegments.add(segment);
+ },
+
+ onUnreachableCodePathSegmentEnd(segment) {
+ currentCodePathSegments.delete(segment);
+ },
+
+ onCodePathSegmentStart(segment) {
+ currentCodePathSegments.add(segment);
+ },
+
+ onCodePathSegmentEnd(segment) {
+ currentCodePathSegments.delete(segment);
+ },
+
+
SwitchCase(node) {
@@ -158,5 +186,5 @@
* And allows empty cases and the last case.
*/
- if (currentCodePath.currentSegments.some(isReachable) &&
+ if (isAnySegmentReachable(currentCodePathSegments) &&
(node.consequent.length > 0 || (!allowEmptyCase && hasBlankLinesBetween(node, nextToken))) &&
node.parent.cases[node.parent.cases.length - 1] !== node) {
diff --git a/lib/rules/no-misleading-character-class.js b/lib/rules/no-misleading-character-class.js
index v8.48.0..v8.50.0 100644
--- a/lib/rules/no-misleading-character-class.js
+++ b/lib/rules/no-misleading-character-class.js
@@ -15,12 +15,19 @@
/**
+ * @typedef {import('@eslint-community/regexpp').AST.Character} Character
+ * @typedef {import('@eslint-community/regexpp').AST.CharacterClassElement} CharacterClassElement
+ */
+
+/**
* Iterate character sequences of a given nodes.
*
* CharacterClassRange syntax can steal a part of character sequence,
* so this function reverts CharacterClassRange syntax and restore the sequence.
- * @param {import('@eslint-community/regexpp').AST.CharacterClassElement[]} nodes The node list to iterate character sequences.
- * @returns {IterableIterator<number[]>} The list of character sequences.
+ * @param {CharacterClassElement[]} nodes The node list to iterate character sequences.
+ * @returns {IterableIterator<Character[]>} The list of character sequences.
*/
function *iterateCharacterSequence(nodes) {
+
+ /** @type {Character[]} */
let seq = [];
@@ -28,11 +35,11 @@
switch (node.type) {
case "Character":
- seq.push(node.value);
+ seq.push(node);
break;
case "CharacterClassRange":
- seq.push(node.min.value);
+ seq.push(node.min);
yield seq;
- seq = [node.max.value];
+ seq = [node.max];
break;
@@ -56,14 +63,56 @@
}
+
+/**
+ * Checks whether the given character node is a Unicode code point escape or not.
+ * @param {Character} char the character node to check.
+ * @returns {boolean} `true` if the character node is a Unicode code point escape.
+ */
+function isUnicodeCodePointEscape(char) {
+ return /^\\u\{[\da-f]+\}$/iu.test(char.raw);
+}
+
+/**
+ * Each function returns `true` if it detects that kind of problem.
+ * @type {Record<string, (chars: Character[]) => boolean>}
+ */
const hasCharacterSequence = {
surrogatePairWithoutUFlag(chars) {
- return chars.some((c, i) => i !== 0 && isSurrogatePair(chars[i - 1], c));
+ return chars.some((c, i) => {
+ if (i === 0) {
+ return false;
+ }
+ const c1 = chars[i - 1];
+
+ return (
+ isSurrogatePair(c1.value, c.value) &&
+ !isUnicodeCodePointEscape(c1) &&
+ !isUnicodeCodePointEscape(c)
+ );
+ });
},
+ surrogatePair(chars) {
+ return chars.some((c, i) => {
+ if (i === 0) {
+ return false;
+ }
+ const c1 = chars[i - 1];
+
+ return (
+ isSurrogatePair(c1.value, c.value) &&
+ (
+ isUnicodeCodePointEscape(c1) ||
+ isUnicodeCodePointEscape(c)
+ )
+ );
+ });
+ },
+
combiningClass(chars) {
return chars.some((c, i) => (
i !== 0 &&
- isCombiningCharacter(c) &&
- !isCombiningCharacter(chars[i - 1])
+ isCombiningCharacter(c.value) &&
+ !isCombiningCharacter(chars[i - 1].value)
));
},
@@ -72,6 +121,6 @@
return chars.some((c, i) => (
i !== 0 &&
- isEmojiModifier(c) &&
- !isEmojiModifier(chars[i - 1])
+ isEmojiModifier(c.value) &&
+ !isEmojiModifier(chars[i - 1].value)
));
},
@@ -80,6 +129,6 @@
return chars.some((c, i) => (
i !== 0 &&
- isRegionalIndicatorSymbol(c) &&
- isRegionalIndicatorSymbol(chars[i - 1])
+ isRegionalIndicatorSymbol(c.value) &&
+ isRegionalIndicatorSymbol(chars[i - 1].value)
));
},
@@ -91,7 +140,7 @@
i !== 0 &&
i !== lastIndex &&
- c === 0x200d &&
- chars[i - 1] !== 0x200d &&
- chars[i + 1] !== 0x200d
+ c.value === 0x200d &&
+ chars[i - 1].value !== 0x200d &&
+ chars[i + 1].value !== 0x200d
));
}
@@ -121,4 +170,5 @@
messages: {
surrogatePairWithoutUFlag: "Unexpected surrogate pair in character class. Use 'u' flag.",
+ surrogatePair: "Unexpected surrogate pair in character class.",
combiningClass: "Unexpected combined character in character class.",
emojiModifier: "Unexpected modified Emoji in character class.",
diff --git a/lib/rules/no-new-object.js b/lib/rules/no-new-object.js
index v8.48.0..v8.50.0 100644
--- a/lib/rules/no-new-object.js
+++ b/lib/rules/no-new-object.js
@@ -2,4 +2,5 @@
* @fileoverview A rule to disallow calls to the Object constructor
* @author Matt DuVall <http://www.mattduvall.com/>
+ * @deprecated in ESLint v8.50.0
*/
@@ -27,4 +28,10 @@
},
+ deprecated: true,
+
+ replacedBy: [
+ "no-object-constructor"
+ ],
+
schema: [],
diff --git a/lib/rules/no-this-before-super.js b/lib/rules/no-this-before-super.js
index v8.48.0..v8.50.0 100644
--- a/lib/rules/no-this-before-super.js
+++ b/lib/rules/no-this-before-super.js
@@ -92,4 +92,19 @@
/**
+ * Determines if every segment in a set has been called.
+ * @param {Set<CodePathSegment>} segments The segments to search.
+ * @returns {boolean} True if every segment has been called; false otherwise.
+ */
+ function isEverySegmentCalled(segments) {
+ for (const segment of segments) {
+ if (!isCalled(segment)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
* Checks whether or not this is before `super()` is called.
* @returns {boolean} `true` if this is before `super()` is called.
@@ -98,5 +113,5 @@
return (
isInConstructorOfDerivedClass() &&
- !funcInfo.codePath.currentSegments.every(isCalled)
+ !isEverySegmentCalled(funcInfo.currentSegments)
);
}
@@ -109,9 +124,7 @@
*/
function setInvalid(node) {
- const segments = funcInfo.codePath.currentSegments;
+ const segments = funcInfo.currentSegments;
- for (let i = 0; i < segments.length; ++i) {
- const segment = segments[i];
-
+ for (const segment of segments) {
if (segment.reachable) {
segInfoMap[segment.id].invalidNodes.push(node);
@@ -125,9 +138,7 @@
*/
function setSuperCalled() {
- const segments = funcInfo.codePath.currentSegments;
+ const segments = funcInfo.currentSegments;
- for (let i = 0; i < segments.length; ++i) {
- const segment = segments[i];
-
+ for (const segment of segments) {
if (segment.reachable) {
segInfoMap[segment.id].superCalled = true;
@@ -157,5 +168,6 @@
!astUtils.isNullOrUndefined(classNode.superClass)
),
- codePath
+ codePath,
+ currentSegments: new Set()
};
} else {
@@ -164,5 +176,6 @@
isConstructor: false,
hasExtends: false,
- codePath
+ codePath,
+ currentSegments: new Set()
};
}
@@ -212,4 +225,6 @@
*/
onCodePathSegmentStart(segment) {
+ funcInfo.currentSegments.add(segment);
+
if (!isInConstructorOfDerivedClass()) {
return;
@@ -226,4 +241,16 @@
},
+ onUnreachableCodePathSegmentStart(segment) {
+ funcInfo.currentSegments.add(segment);
+ },
+
+ onUnreachableCodePathSegmentEnd(segment) {
+ funcInfo.currentSegments.delete(segment);
+ },
+
+ onCodePathSegmentEnd(segment) {
+ funcInfo.currentSegments.delete(segment);
+ },
+
/**
* Update information of the code path segment when a code path was
diff --git a/lib/rules/no-unreachable-loop.js b/lib/rules/no-unreachable-loop.js
index v8.48.0..v8.50.0 100644
--- a/lib/rules/no-unreachable-loop.js
+++ b/lib/rules/no-unreachable-loop.js
@@ -13,4 +13,20 @@
/**
+ * Checks all segments in a set and returns true if any are reachable.
+ * @param {Set<CodePathSegment>} segments The segments to check.
+ * @returns {boolean} True if any segment is reachable; false otherwise.
+ */
+function isAnySegmentReachable(segments) {
+
+ for (const segment of segments) {
+ if (segment.reachable) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
* Determines whether the given node is the first node in the code path to which a loop statement
* 'loops' for the next iteration.
@@ -91,27 +107,34 @@
loopsToReport = new Set();
- let currentCodePath = null;
+ const codePathSegments = [];
+ let currentCodePathSegments = new Set();
return {
- onCodePathStart(codePath) {
- currentCodePath = codePath;
+
+ onCodePathStart() {
+ codePathSegments.push(currentCodePathSegments);
+ currentCodePathSegments = new Set();
},
onCodePathEnd() {
- currentCodePath = currentCodePath.upper;
+ currentCodePathSegments = codePathSegments.pop();
},
- [loopSelector](node) {
+ onUnreachableCodePathSegmentStart(segment) {
+ currentCodePathSegments.add(segment);
+ },
- /**
- * Ignore unreachable loop statements to avoid unnecessary complexity in the implementation, or false positives otherwise.
- * For unreachable segments, the code path analysis does not raise events required for this implementation.
- */
- if (currentCodePath.currentSegments.some(segment => segment.reachable)) {
- loopsToReport.add(node);
- }
+ onUnreachableCodePathSegmentEnd(segment) {
+ currentCodePathSegments.delete(segment);
},
+ onCodePathSegmentEnd(segment) {
+ currentCodePathSegments.delete(segment);
+ },
+
onCodePathSegmentStart(segment, node) {
+
+ currentCodePathSegments.add(segment);
+
if (isLoopingTarget(node)) {
const loop = node.parent;
@@ -141,4 +164,16 @@
},
+ [loopSelector](node) {
+
+ /**
+ * Ignore unreachable loop statements to avoid unnecessary complexity in the implementation, or false positives otherwise.
+ * For unreachable segments, the code path analysis does not raise events required for this implementation.
+ */
+ if (isAnySegmentReachable(currentCodePathSegments)) {
+ loopsToReport.add(node);
+ }
+ },
+
+
"Program:exit"() {
loopsToReport.forEach(
diff --git a/lib/rules/no-unreachable.js b/lib/rules/no-unreachable.js
index v8.48.0..v8.50.0 100644
--- a/lib/rules/no-unreachable.js
+++ b/lib/rules/no-unreachable.js
@@ -25,10 +25,17 @@
/**
- * Checks whether or not a given code path segment is unreachable.
- * @param {CodePathSegment} segment A CodePathSegment to check.
- * @returns {boolean} `true` if the segment is unreachable.
+ * Checks all segments in a set and returns true if all are unreachable.
+ * @param {Set<CodePathSegment>} segments The segments to check.
+ * @returns {boolean} True if all segments are unreachable; false otherwise.
*/
-function isUnreachable(segment) {
- return !segment.reachable;
+function areAllSegmentsUnreachable(segments) {
+
+ for (const segment of segments) {
+ if (segment.reachable) {
+ return false;
+ }
+ }
+
+ return true;
}
@@ -125,5 +132,4 @@
create(context) {
- let currentCodePath = null;
/** @type {ConstructorInfo | null} */
@@ -133,4 +139,10 @@
const range = new ConsecutiveRange(context.sourceCode);
+ /** @type {Array<Set<CodePathSegment>>} */
+ const codePathSegments = [];
+
+ /** @type {Set<CodePathSegment>} */
+ let currentCodePathSegments = new Set();
+
/**
* Reports a given node if it's unreachable.
@@ -141,5 +153,5 @@
let nextNode = null;
- if (node && (node.type === "PropertyDefinition" || currentCodePath.currentSegments.every(isUnreachable))) {
+ if (node && (node.type === "PropertyDefinition" || areAllSegmentsUnreachable(currentCodePathSegments))) {
// Store this statement to distinguish consecutive statements.
@@ -182,12 +194,29 @@
// Manages the current code path.
- onCodePathStart(codePath) {
- currentCodePath = codePath;
+ onCodePathStart() {
+ codePathSegments.push(currentCodePathSegments);
+ currentCodePathSegments = new Set();
},
onCodePathEnd() {
- currentCodePath = currentCodePath.upper;
+ currentCodePathSegments = codePathSegments.pop();
},
+ onUnreachableCodePathSegmentStart(segment) {
+ currentCodePathSegments.add(segment);
+ },
+
+ onUnreachableCodePathSegmentEnd(segment) {
+ currentCodePathSegments.delete(segment);
+ },
+
+ onCodePathSegmentEnd(segment) {
+ currentCodePathSegments.delete(segment);
+ },
+
+ onCodePathSegmentStart(segment) {
+ currentCodePathSegments.add(segment);
+ },
+
// Registers for all statement nodes (excludes FunctionDeclaration).
BlockStatement: reportIfUnreachable,
diff --git a/lib/rules/no-useless-return.js b/lib/rules/no-useless-return.js
index v8.48.0..v8.50.0 100644
--- a/lib/rules/no-useless-return.js
+++ b/lib/rules/no-useless-return.js
@@ -58,4 +58,20 @@
}
+/**
+ * Checks all segments in a set and returns true if any are reachable.
+ * @param {Set<CodePathSegment>} segments The segments to check.
+ * @returns {boolean} True if any segment is reachable; false otherwise.
+ */
+function isAnySegmentReachable(segments) {
+
+ for (const segment of segments) {
+ if (segment.reachable) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
//------------------------------------------------------------------------------
// Rule Definition
@@ -206,5 +222,4 @@
function markReturnStatementsOnCurrentSegmentsAsUsed() {
scopeInfo
- .codePath
.currentSegments
.forEach(segment => markReturnStatementsOnSegmentAsUsed(segment, new Set()));
@@ -223,5 +238,6 @@
uselessReturns: [],
traversedTryBlockStatements: [],
- codePath
+ codePath,
+ currentSegments: new Set()
};
},
@@ -260,4 +276,7 @@
*/
onCodePathSegmentStart(segment) {
+
+ scopeInfo.currentSegments.add(segment);
+
const info = {
uselessReturns: getUselessReturns([], segment.allPrevSegments),
@@ -269,4 +288,16 @@
},
+ onUnreachableCodePathSegmentStart(segment) {
+ scopeInfo.currentSegments.add(segment);
+ },
+
+ onUnreachableCodePathSegmentEnd(segment) {
+ scopeInfo.currentSegments.delete(segment);
+ },
+
+ onCodePathSegmentEnd(segment) {
+ scopeInfo.currentSegments.delete(segment);
+ },
+
// Adds ReturnStatement node to check whether it's useless or not.
ReturnStatement(node) {
@@ -280,10 +311,10 @@
// Ignore `return` statements in unreachable places (https://github.com/eslint/eslint/issues/11647).
- !scopeInfo.codePath.currentSegments.some(s => s.reachable)
+ !isAnySegmentReachable(scopeInfo.currentSegments)
) {
return;
}
- for (const segment of scopeInfo.codePath.currentSegments) {
+ for (const segment of scopeInfo.currentSegments) {
const info = segmentInfoMap.get(segment);
diff --git a/lib/rules/require-atomic-updates.js b/lib/rules/require-atomic-updates.js
index v8.48.0..v8.50.0 100644
--- a/lib/rules/require-atomic-updates.js
+++ b/lib/rules/require-atomic-updates.js
@@ -214,5 +214,6 @@
upper: stack,
codePath,
- referenceMap: shouldVerify ? createReferenceMap(scope) : null
+ referenceMap: shouldVerify ? createReferenceMap(scope) : null,
+ currentSegments: new Set()
};
},
@@ -224,9 +225,23 @@
onCodePathSegmentStart(segment) {
segmentInfo.initialize(segment);
+ stack.currentSegments.add(segment);
},
+ onUnreachableCodePathSegmentStart(segment) {
+ stack.currentSegments.add(segment);
+ },
+
+ onUnreachableCodePathSegmentEnd(segment) {
+ stack.currentSegments.delete(segment);
+ },
+
+ onCodePathSegmentEnd(segment) {
+ stack.currentSegments.delete(segment);
+ },
+
+
// Handle references to prepare verification.
Identifier(node) {
- const { codePath, referenceMap } = stack;
+ const { referenceMap } = stack;
const reference = referenceMap && referenceMap.get(node);
@@ -241,5 +256,5 @@
// Add a fresh read variable.
if (reference.isRead() && !(writeExpr && writeExpr.parent.operator === "=")) {
- segmentInfo.markAsRead(codePath.currentSegments, variable);
+ segmentInfo.markAsRead(stack.currentSegments, variable);
}
@@ -268,8 +283,7 @@
*/
":expression:exit"(node) {
- const { codePath, referenceMap } = stack;
// referenceMap exists if this is in a resumable function scope.
- if (!referenceMap) {
+ if (!stack.referenceMap) {
return;
}
@@ -277,5 +291,5 @@
// Mark the read variables on this code path as outdated.
if (node.type === "AwaitExpression" || node.type === "YieldExpression") {
- segmentInfo.makeOutdated(codePath.currentSegments);
+ segmentInfo.makeOutdated(stack.currentSegments);
}
@@ -289,5 +303,5 @@
const variable = reference.resolved;
- if (segmentInfo.isOutdated(codePath.currentSegments, variable)) {
+ if (segmentInfo.isOutdated(stack.currentSegments, variable)) {
if (node.parent.left === reference.identifier) {
context.report({
diff --git a/lib/rule-tester/rule-tester.js b/lib/rule-tester/rule-tester.js
index v8.48.0..v8.50.0 100644
--- a/lib/rule-tester/rule-tester.js
+++ b/lib/rule-tester/rule-tester.js
@@ -49,5 +49,6 @@
Traverser = require("../../lib/shared/traverser"),
{ getRuleOptionsSchema, validate } = require("../shared/config-validator"),
- { Linter, SourceCodeFixer, interpolate } = require("../linter");
+ { Linter, SourceCodeFixer, interpolate } = require("../linter"),
+ CodePath = require("../linter/code-path-analysis/code-path");
const ajv = require("../shared/ajv")({ strictDefaults: true });
@@ -163,6 +164,41 @@
const friendlySuggestionObjectParameterList = `[${[...suggestionObjectParameters].map(key => `'${key}'`).join(", ")}]`;
+const forbiddenMethods = [
+ "applyInlineConfig",
+ "applyLanguageOptions",
+ "finalize"
+];
+
const hasOwnProperty = Function.call.bind(Object.hasOwnProperty);
+const DEPRECATED_SOURCECODE_PASSTHROUGHS = {
+ getSource: "getText",
+ getSourceLines: "getLines",
+ getAllComments: "getAllComments",
+ getNodeByRangeIndex: "getNodeByRangeIndex",
+
+ // getComments: "getComments", -- already handled by a separate error
+ getCommentsBefore: "getCommentsBefore",
+ getCommentsAfter: "getCommentsAfter",
+ getCommentsInside: "getCommentsInside",
+ getJSDocComment: "getJSDocComment",
+ getFirstToken: "getFirstToken",
+ getFirstTokens: "getFirstTokens",
+ getLastToken: "getLastToken",
+ getLastTokens: "getLastTokens",
+ getTokenAfter: "getTokenAfter",
+ getTokenBefore: "getTokenBefore",
+ getTokenByRangeStart: "getTokenByRangeStart",
+ getTokens: "getTokens",
+ getTokensAfter: "getTokensAfter",
+ getTokensBefore: "getTokensBefore",
+ getTokensBetween: "getTokensBetween",
+
+ getScope: "getScope",
+ getAncestors: "getAncestors",
+ getDeclaredVariables: "getDeclaredVariables",
+ markVariableAsUsed: "markVariableAsUsed"
+};
+
/**
* Clones a given value deeply.
@@ -307,4 +343,17 @@
/**
+ * Function to replace forbidden `SourceCode` methods.
+ * @param {string} methodName The name of the method to forbid.
+ * @returns {Function} The function that throws the error.
+ */
+function throwForbiddenMethodError(methodName) {
+ return () => {
+ throw new Error(
+ `\`SourceCode#${methodName}()\` cannot be called inside a rule.`
+ );
+ };
+}
+
+/**
* Emit a deprecation warning if function-style format is being used.
* @param {string} ruleName Name of the rule.
@@ -336,4 +385,51 @@
}
+/**
+ * Emit a deprecation warning if a rule uses a deprecated `context` method.
+ * @param {string} ruleName Name of the rule.
+ * @param {string} methodName The name of the method on `context` that was used.
+ * @returns {void}
+ */
+function emitDeprecatedContextMethodWarning(ruleName, methodName) {
+ if (!emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`]) {
+ emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`] = true;
+ process.emitWarning(
+ `"${ruleName}" rule is using \`context.${methodName}()\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.${DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]}()\` instead.`,
+ "DeprecationWarning"
+ );
+ }
+}
+
+/**
+ * Emit a deprecation warning if rule uses CodePath#currentSegments.
+ * @param {string} ruleName Name of the rule.
+ * @returns {void}
+ */
+function emitCodePathCurrentSegmentsWarning(ruleName) {
+ if (!emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`]) {
+ emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`] = true;
+ process.emitWarning(
+ `"${ruleName}" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples`,
+ "DeprecationWarning"
+ );
+ }
+}
+
+/**
+ * Emit a deprecation warning if `context.parserServices` is used.
+ * @param {string} ruleName Name of the rule.
+ * @returns {void}
+ */
+function emitParserServicesWarning(ruleName) {
+ if (!emitParserServicesWarning[`warned-${ruleName}`]) {
+ emitParserServicesWarning[`warned-${ruleName}`] = true;
+ process.emitWarning(
+ `"${ruleName}" rule is using \`context.parserServices\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.parserServices\` instead.`,
+ "DeprecationWarning"
+ );
+ }
+}
+
+
//------------------------------------------------------------------------------
// Public Interface
@@ -567,5 +663,36 @@
freezeDeeply(context.parserOptions);
- return (typeof rule === "function" ? rule : rule.create)(context);
+ // wrap all deprecated methods
+ const newContext = Object.create(
+ context,
+ Object.fromEntries(Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).map(methodName => [
+ methodName,
+ {
+ value(...args) {
+
+ // emit deprecation warning
+ emitDeprecatedContextMethodWarning(ruleName, methodName);
+
+ // call the original method
+ return context[methodName].call(this, ...args);
+ },
+ enumerable: true
+ }
+ ]))
+ );
+
+ // emit warning about context.parserServices
+ const parserServices = context.parserServices;
+
+ Object.defineProperty(newContext, "parserServices", {
+ get() {
+ emitParserServicesWarning(ruleName);
+ return parserServices;
+ }
+ });
+
+ Object.freeze(newContext);
+
+ return (typeof rule === "function" ? rule : rule.create)(newContext);
}
}));
@@ -686,12 +813,28 @@
// Verify the code.
- const { getComments } = SourceCode.prototype;
+ const { getComments, applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype;
+ const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments");
let messages;
try {
SourceCode.prototype.getComments = getCommentsDeprecation;
+ Object.defineProperty(CodePath.prototype, "currentSegments", {
+ get() {
+ emitCodePathCurrentSegmentsWarning(ruleName);
+ return originalCurrentSegments.get.call(this);
+ }
+ });
+
+ forbiddenMethods.forEach(methodName => {
+ SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName);
+ });
+
messages = linter.verify(code, config, filename);
} finally {
SourceCode.prototype.getComments = getComments;
+ Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments);
+ SourceCode.prototype.applyInlineConfig = applyInlineConfig;
+ SourceCode.prototype.applyLanguageOptions = applyLanguageOptions;
+ SourceCode.prototype.finalize = finalize;
}
diff --git a/lib/config/rule-validator.js b/lib/config/rule-validator.js
index v8.48.0..v8.50.0 100644
--- a/lib/config/rule-validator.js
+++ b/lib/config/rule-validator.js
@@ -10,5 +10,6 @@
//-----------------------------------------------------------------------------
-const ajv = require("../shared/ajv")();
+const ajvImport = require("../shared/ajv");
+const ajv = ajvImport();
const {
parseRuleId,
diff --git a/lib/source-code/source-code.js b/lib/source-code/source-code.js
index v8.48.0..v8.50.0 100644
--- a/lib/source-code/source-code.js
+++ b/lib/source-code/source-code.js
@@ -13,6 +13,14 @@
TokenStore = require("./token-store"),
astUtils = require("../shared/ast-utils"),
- Traverser = require("../shared/traverser");
+ Traverser = require("../shared/traverser"),
+ globals = require("../../conf/globals"),
+ {
+ directivesPattern
+ } = require("../shared/directives"),
+ /* eslint-disable-next-line n/no-restricted-require -- Too messy to figure out right now. */
+ ConfigCommentParser = require("../linter/config-comment-parser"),
+ eslintScope = require("eslint-scope");
+
//------------------------------------------------------------------------------
// Type Definitions
@@ -25,4 +33,6 @@
//------------------------------------------------------------------------------
+const commentParser = new ConfigCommentParser();
+
/**
* Validates that the given AST has the required information.
@@ -51,4 +61,27 @@
/**
+ * Retrieves globals for the given ecmaVersion.
+ * @param {number} ecmaVersion The version to retrieve globals for.
+ * @returns {Object} The globals for the given ecmaVersion.
+ */
+function getGlobalsForEcmaVersion(ecmaVersion) {
+
+ switch (ecmaVersion) {
+ case 3:
+ return globals.es3;
+
+ case 5:
+ return globals.es5;
+
+ default:
+ if (ecmaVersion < 2015) {
+ return globals[`es${ecmaVersion + 2009}`];
+ }
+
+ return globals[`es${ecmaVersion}`];
+ }
+}
+
+/**
* Check to see if its a ES6 export declaration.
* @param {ASTNode} astNode An AST node.
@@ -85,4 +118,34 @@
/**
+ * Normalizes a value for a global in a config
+ * @param {(boolean|string|null)} configuredValue The value given for a global in configuration or in
+ * a global directive comment
+ * @returns {("readable"|"writeable"|"off")} The value normalized as a string
+ * @throws Error if global value is invalid
+ */
+function normalizeConfigGlobal(configuredValue) {
+ switch (configuredValue) {
+ case "off":
+ return "off";
+
+ case true:
+ case "true":
+ case "writeable":
+ case "writable":
+ return "writable";
+
+ case null:
+ case false:
+ case "false":
+ case "readable":
+ case "readonly":
+ return "readonly";
+
+ default:
+ throw new Error(`'${configuredValue}' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')`);
+ }
+}
+
+/**
* Determines if two nodes or tokens overlap.
* @param {ASTNode|Token} first The first node or token to check.
@@ -146,4 +209,114 @@
}
+//-----------------------------------------------------------------------------
+// Directive Comments
+//-----------------------------------------------------------------------------
+
+/**
+ * Extract the directive and the justification from a given directive comment and trim them.
+ * @param {string} value The comment text to extract.
+ * @returns {{directivePart: string, justificationPart: string}} The extracted directive and justification.
+ */
+function extractDirectiveComment(value) {
+ const match = /\s-{2,}\s/u.exec(value);
+
+ if (!match) {
+ return { directivePart: value.trim(), justificationPart: "" };
+ }
+
+ const directive = value.slice(0, match.index).trim();
+ const justification = value.slice(match.index + match[0].length).trim();
+
+ return { directivePart: directive, justificationPart: justification };
+}
+
+/**
+ * Ensures that variables representing built-in properties of the Global Object,
+ * and any globals declared by special block comments, are present in the global
+ * scope.
+ * @param {Scope} globalScope The global scope.
+ * @param {Object|undefined} configGlobals The globals declared in configuration
+ * @param {Object|undefined} inlineGlobals The globals declared in the source code
+ * @returns {void}
+ */
+function addDeclaredGlobals(globalScope, configGlobals = {}, inlineGlobals = {}) {
+
+ // Define configured global variables.
+ for (const id of new Set([...Object.keys(configGlobals), ...Object.keys(inlineGlobals)])) {
+
+ /*
+ * `normalizeConfigGlobal` will throw an error if a configured global value is invalid. However, these errors would
+ * typically be caught when validating a config anyway (validity for inline global comments is checked separately).
+ */
+ const configValue = configGlobals[id] === void 0 ? void 0 : normalizeConfigGlobal(configGlobals[id]);
+ const commentValue = inlineGlobals[id] && inlineGlobals[id].value;
+ const value = commentValue || configValue;
+ const sourceComments = inlineGlobals[id] && inlineGlobals[id].comments;
+
+ if (value === "off") {
+ continue;
+ }
+
+ let variable = globalScope.set.get(id);
+
+ if (!variable) {
+ variable = new eslintScope.Variable(id, globalScope);
+
+ globalScope.variables.push(variable);
+ globalScope.set.set(id, variable);
+ }
+
+ variable.eslintImplicitGlobalSetting = configValue;
+ variable.eslintExplicitGlobal = sourceComments !== void 0;
+ variable.eslintExplicitGlobalComments = sourceComments;
+ variable.writeable = (value === "writable");
+ }
+
+ /*
+ * "through" contains all references which definitions cannot be found.
+ * Since we augment the global scope using configuration, we need to update
+ * references and remove the ones that were added by configuration.
+ */
+ globalScope.through = globalScope.through.filter(reference => {
+ const name = reference.identifier.name;
+ const variable = globalScope.set.get(name);
+
+ if (variable) {
+
+ /*
+ * Links the variable and the reference.
+ * And this reference is removed from `Scope#through`.
+ */
+ reference.resolved = variable;
+ variable.references.push(reference);
+
+ return false;
+ }
+
+ return true;
+ });
+}
+
+/**
+ * Sets the given variable names as exported so they won't be triggered by
+ * the `no-unused-vars` rule.
+ * @param {eslint.Scope} globalScope The global scope to define exports in.
+ * @param {Record<string,string>} variables An object whose keys are the variable
+ * names to export.
+ * @returns {void}
+ */
+function markExportedVariables(globalScope, variables) {
+
+ Object.keys(variables).forEach(name => {
+ const variable = globalScope.set.get(name);
+
+ if (variable) {
+ variable.eslintUsed = true;
+ variable.eslintExported = true;
+ }
+ });
+
+}
+
//------------------------------------------------------------------------------
// Public Interface
@@ -188,5 +361,7 @@
*/
this[caches] = new Map([
- ["scopes", new WeakMap()]
+ ["scopes", new WeakMap()],
+ ["vars", new Map()],
+ ["configNodes", void 0]
]);
@@ -267,5 +442,5 @@
this._commentCache = new WeakMap();
- // don't allow modification of this object
+ // don't allow further modification of this object
Object.freeze(this);
Object.freeze(this.lines);
@@ -725,4 +900,176 @@
+ /**
+ * Returns an array of all inline configuration nodes found in the
+ * source code.
+ * @returns {Array<Token>} An array of all inline configuration nodes.
+ */
+ getInlineConfigNodes() {
+
+ // check the cache first
+ let configNodes = this[caches].get("configNodes");
+
+ if (configNodes) {
+ return configNodes;
+ }
+
+ // calculate fresh config nodes
+ configNodes = this.ast.comments.filter(comment => {
+
+ // shebang comments are never directives
+ if (comment.type === "Shebang") {
+ return false;
+ }
+
+ const { directivePart } = extractDirectiveComment(comment.value);
+
+ const directiveMatch = directivesPattern.exec(directivePart);
+
+ if (!directiveMatch) {
+ return false;
+ }
+
+ // only certain comment types are supported as line comments
+ return comment.type !== "Line" || !!/^eslint-disable-(next-)?line$/u.test(directiveMatch[1]);
+ });
+
+ this[caches].set("configNodes", configNodes);
+
+ return configNodes;
+ }
+
+ /**
+ * Applies language options sent in from the core.
+ * @param {Object} languageOptions The language options for this run.
+ * @returns {void}
+ */
+ applyLanguageOptions(languageOptions) {
+
+ /*
+ * Add configured globals and language globals
+ *
+ * Using Object.assign instead of object spread for performance reasons
+ * https://github.com/eslint/eslint/issues/16302
+ */
+ const configGlobals = Object.assign(
+ {},
+ getGlobalsForEcmaVersion(languageOptions.ecmaVersion),
+ languageOptions.sourceType === "commonjs" ? globals.commonjs : void 0,
+ languageOptions.globals
+ );
+ const varsCache = this[caches].get("vars");
+
+ varsCache.set("configGlobals", configGlobals);
+ }
+
+ /**
+ * Applies configuration found inside of the source code. This method is only
+ * called when ESLint is running with inline configuration allowed.
+ * @returns {{problems:Array<Problem>,configs:{config:FlatConfigArray,node:ASTNode}}} Information
+ * that ESLint needs to further process the inline configuration.
+ */
+ applyInlineConfig() {
+
+ const problems = [];
+ const configs = [];
+ const exportedVariables = {};
+ const inlineGlobals = Object.create(null);
+
+ this.getInlineConfigNodes().forEach(comment => {
+
+ const { directivePart } = extractDirectiveComment(comment.value);
+ const match = directivesPattern.exec(directivePart);
+ const directiveText = match[1];
+ const directiveValue = directivePart.slice(match.index + directiveText.length);
+
+ switch (directiveText) {
+ case "exported":
+ Object.assign(exportedVariables, commentParser.parseStringConfig(directiveValue, comment));
+ break;
+
+ case "globals":
+ case "global":
+ for (const [id, { value }] of Object.entries(commentParser.parseStringConfig(directiveValue, comment))) {
+ let normalizedValue;
+
+ try {
+ normalizedValue = normalizeConfigGlobal(value);
+ } catch (err) {
+ problems.push({
+ ruleId: null,
+ loc: comment.loc,
+ message: err.message
+ });
+ continue;
+ }
+
+ if (inlineGlobals[id]) {
+ inlineGlobals[id].comments.push(comment);
+ inlineGlobals[id].value = normalizedValue;
+ } else {
+ inlineGlobals[id] = {
+ comments: [comment],
+ value: normalizedValue
+ };
+ }
+ }
+ break;
+
+ case "eslint": {
+ const parseResult = commentParser.parseJsonConfig(directiveValue, comment.loc);
+
+ if (parseResult.success) {
+ configs.push({
+ config: {
+ rules: parseResult.config
+ },
+ node: comment
+ });
+ } else {
+ problems.push(parseResult.error);
+ }
+
+ break;
+ }
+
+ // no default
+ }
+ });
+
+ // save all the new variables for later
+ const varsCache = this[caches].get("vars");
+
+ varsCache.set("inlineGlobals", inlineGlobals);
+ varsCache.set("exportedVariables", exportedVariables);
+
+ return {
+ configs,
+ problems
+ };
+ }
+
+ /**
+ * Called by ESLint core to indicate that it has finished providing
+ * information. We now add in all the missing variables and ensure that
+ * state-changing methods cannot be called by rules.
+ * @returns {void}
+ */
+ finalize() {
+
+ // Step 1: ensure that all of the necessary variables are up to date
+ const varsCache = this[caches].get("vars");
+ const globalScope = this.scopeManager.scopes[0];
+ const configGlobals = varsCache.get("configGlobals");
+ const inlineGlobals = varsCache.get("inlineGlobals");
+ const exportedVariables = varsCache.get("exportedVariables");
+
+ addDeclaredGlobals(globalScope, configGlobals, inlineGlobals);
+
+ if (exportedVariables) {
+ markExportedVariables(globalScope, exportedVariables);
+ }
+
+ }
+
}
diff --git a/package.json b/package.json
index v8.48.0..v8.50.0 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
{
"name": "eslint",
- "version": "8.48.0",
+ "version": "8.50.0",
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
"description": "An AST-based pattern checker for JavaScript.",
@@ -64,6 +64,6 @@
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.2",
- "@eslint/js": "8.48.0",
- "@humanwhocodes/config-array": "^0.11.10",
+ "@eslint/js": "8.50.0",
+ "@humanwhocodes/config-array": "^0.11.11",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
@@ -102,4 +102,9 @@
"@babel/core": "^7.4.3",
"@babel/preset-env": "^7.4.3",
+ "@wdio/browser-runner": "^8.14.6",
+ "@wdio/cli": "^8.14.6",
+ "@wdio/concise-reporter": "^8.14.0",
+ "@wdio/globals": "^8.14.6",
+ "@wdio/mocha-framework": "^8.14.0",
"babel-loader": "^8.0.5",
"c8": "^7.12.0",
@@ -125,9 +130,4 @@
"got": "^11.8.3",
"gray-matter": "^4.0.3",
- "karma": "^6.1.1",
- "karma-chrome-launcher": "^3.1.0",
- "karma-mocha": "^2.0.1",
- "karma-mocha-reporter": "^2.2.5",
- "karma-webpack": "^5.0.0",
"lint-staged": "^11.0.0",
"load-perf": "^0.2.0",
@@ -149,10 +149,12 @@
"progress": "^2.0.3",
"proxyquire": "^2.0.1",
- "puppeteer": "^13.7.0",
"recast": "^0.20.4",
"regenerator-runtime": "^0.13.2",
+ "rollup-plugin-node-polyfills": "^0.2.1",
"semver": "^7.5.3",
"shelljs": "^0.8.2",
"sinon": "^11.0.0",
+ "vite-plugin-commonjs": "^0.8.2",
+ "webdriverio": "^8.14.6",
"webpack": "^5.23.0",
"webpack-cli": "^4.5.0",
diff --git a/README.md b/README.md
index v8.48.0..v8.50.0 100644
--- a/README.md
+++ b/README.md
@@ -289,6 +289,6 @@
<p><a href="#"><img src="https://images.opencollective.com/2021-frameworks-fund/logo.png" alt="Chrome Frameworks Fund" height="undefined"></a> <a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="undefined"></a></p><h3>Gold Sponsors</h3>
<p><a href="https://engineering.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a></p><h3>Silver Sponsors</h3>
-<p><a href="https://sentry.io"><img src="https://avatars.githubusercontent.com/u/1396951?v=4" alt="Sentry" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301?v=4" alt="American Express" height="64"></a></p><h3>Bronze Sponsors</h3>
-<p><a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://github.com/about"><img src="https://avatars.githubusercontent.com/u/9919?v=4" alt="GitHub" height="32"></a> <a href="https://transloadit.com/"><img src="https://avatars.githubusercontent.com/u/125754?v=4" alt="Transloadit" height="32"></a> <a href="https://www.ignitionapp.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Ignition" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a> <a href="https://quickbookstoolhub.com"><img src="https://avatars.githubusercontent.com/u/95090305?u=e5bc398ef775c9ed19f955c675cdc1fb6abf01df&v=4" alt="QuickBooks Tool hub" height="32"></a></p>
+<p><a href="https://sentry.io"><img src="https://avatars.githubusercontent.com/u/1396951?v=4" alt="Sentry" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a> <a href="https://opensource.siemens.com"><img src="https://avatars.githubusercontent.com/u/624020?v=4" alt="Siemens" height="64"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301?v=4" alt="American Express" height="64"></a></p><h3>Bronze Sponsors</h3>
+<p><a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com/"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://transloadit.com/"><img src="https://avatars.githubusercontent.com/u/125754?v=4" alt="Transloadit" height="32"></a> <a href="https://www.ignitionapp.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Ignition" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a> <a href="https://quickbookstoolhub.com"><img src="https://avatars.githubusercontent.com/u/95090305?u=e5bc398ef775c9ed19f955c675cdc1fb6abf01df&v=4" alt="QuickBooks Tool hub" height="32"></a></p>
<!--sponsorsend-->
diff --git a/lib/rules/no-object-constructor.js b/lib/rules/no-object-constructor.js
new file mode 100644
index v8.48.0..v8.50.0
--- a/lib/rules/no-object-constructor.js
+++ b/lib/rules/no-object-constructor.js
@@ -0,0 +1,118 @@
+/**
+ * @fileoverview Rule to disallow calls to the `Object` constructor without an argument
+ * @author Francesco Trotta
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const { getVariableByName, isArrowToken } = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Tests if a node appears at the beginning of an ancestor ExpressionStatement node.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} Whether the node appears at the beginning of an ancestor ExpressionStatement node.
+ */
+function isStartOfExpressionStatement(node) {
+ const start = node.range[0];
+ let ancestor = node;
+
+ while ((ancestor = ancestor.parent) && ancestor.range[0] === start) {
+ if (ancestor.type === "ExpressionStatement") {
+ return true;
+ }
+ }
+ return false;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+ meta: {
+ type: "suggestion",
+
+ docs: {
+ description: "Disallow calls to the `Object` constructor without an argument",
+ recommended: false,
+ url: "https://eslint.org/docs/latest/rules/no-object-constructor"
+ },
+
+ hasSuggestions: true,
+
+ schema: [],
+
+ messages: {
+ preferLiteral: "The object literal notation {} is preferable.",
+ useLiteral: "Replace with '{{replacement}}'."
+ }
+ },
+
+ create(context) {
+
+ const sourceCode = context.sourceCode;
+
+ /**
+ * Determines whether or not an object literal that replaces a specified node needs to be enclosed in parentheses.
+ * @param {ASTNode} node The node to be replaced.
+ * @returns {boolean} Whether or not parentheses around the object literal are required.
+ */
+ function needsParentheses(node) {
+ if (isStartOfExpressionStatement(node)) {
+ return true;
+ }
+
+ const prevToken = sourceCode.getTokenBefore(node);
+
+ if (prevToken && isArrowToken(prevToken)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Reports on nodes where the `Object` constructor is called without arguments.
+ * @param {ASTNode} node The node to evaluate.
+ * @returns {void}
+ */
+ function check(node) {
+ if (node.callee.type !== "Identifier" || node.callee.name !== "Object" || node.arguments.length) {
+ return;
+ }
+
+ const variable = getVariableByName(sourceCode.getScope(node), "Object");
+
+ if (variable && variable.identifiers.length === 0) {
+ const replacement = needsParentheses(node) ? "({})" : "{}";
+
+ context.report({
+ node,
+ messageId: "preferLiteral",
+ suggest: [
+ {
+ messageId: "useLiteral",
+ data: { replacement },
+ fix: fixer => fixer.replaceText(node, replacement)
+ }
+ ]
+ });
+ }
+ }
+
+ return {
+ CallExpression: check,
+ NewExpression: check
+ };
+
+ }
+};
Command detailsnpm diff --diff=eslint@8.48.0 --diff=eslint@8.50.0 --diff-unified=2 See also the Reported by ybiquitous/npm-diff-action@v1.5.0 (Node.js 20.8.0 and npm 10.1.0) |
Bumps [eslint](https://github.com/eslint/eslint) from 8.48.0 to 8.50.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md) - [Commits](eslint/eslint@v8.48.0...v8.50.0) --- updated-dependencies: - dependency-name: eslint dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
dependabot
bot
force-pushed
the
dependabot/npm_and_yarn/eslint-8.50.0
branch
from
October 1, 2023 04:30
8934545
to
a4cbffa
Compare
ybiquitous
changed the title
build(deps-dev): bump eslint from 8.48.0 to 8.50.0
feat(deps): bump eslint from 8.48.0 to 8.50.0
Oct 2, 2023
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Labels
dependencies
Pull requests that update a dependency file
javascript
Pull requests that update Javascript code
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Bumps eslint from 8.48.0 to 8.50.0.
Release notes
Sourced from eslint's releases.
Changelog
Sourced from eslint's changelog.
Commits
299bfae
8.50.0212687c
Build: changelog update for 8.50.0f8a8a2d
chore: upgrade@eslint/js
@8
.50.0 (#17599)38ada6d
chore: package.json update for@eslint/js
release27d5a9e
feat: add suggestions to array-callback-return (#17590)f9082ff
feat: flat-rule-tester make sure default config always matches (#17585)83914ad
feat: Implement SourceCode#applyInlineConfig() (#17351)cc4d26b
fix: Ensure deprecated context.parserServices warns (#17593)1ea4cfb
fix: Ensure all RuleTester tests all deprecated context methods (#17587)1800537
docs: Fix and standardize JSX code examples (#17591)Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase
.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebase
will rebase this PR@dependabot recreate
will recreate this PR, overwriting any edits that have been made to it@dependabot merge
will merge this PR after your CI passes on it@dependabot squash and merge
will squash and merge this PR after your CI passes on it@dependabot cancel merge
will cancel a previously requested merge and block automerging@dependabot reopen
will reopen this PR if it is closed@dependabot close
will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually@dependabot show <dependency name> ignore conditions
will show all of the ignore conditions of the specified dependency@dependabot ignore this major version
will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this minor version
will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this dependency
will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)