UNPKG

eslint-plugin-react-x

Version:

A set of composable linting rules for libraries and frameworks that use React as a UI runtime.

1,618 lines (1,610 loc) • 115 kB
import { createRuleForPlugin, getSettingsFromContext, unsafeDecodeSettings, DEFAULT_ESLINT_REACT_SETTINGS } from '@eslint-react/shared'; import * as JSX8 from '@eslint-react/jsx'; import * as AST12 from '@eslint-react/ast'; import { isForwardRefCall, isClassComponent, isThisSetState, isChildrenCount, isChildrenForEach, isChildrenMap, isChildrenOnly, isChildrenToArray, useComponentCollectorLegacy, isComponentDidCatch, isGetDerivedStateFromError, isCloneElementCall, isCreateRefCall, isComponentName, isAssignmentToThisState, useComponentCollector, DEFAULT_COMPONENT_HINT, ERComponentFlag, getInstanceId, isInstanceIdEqual, DISPLAY_NAME_ASSIGNMENT_SELECTOR, isCreateContextCall, isChildrenToArrayCall, ERComponentHint, isDirectValueOfRenderPropertyLoose, isDeclaredInRenderPropLoose, isInsideRenderMethod, isCreateElementCall, isReactHookCall, isReactHookCallWithNameAlias, isGetDerivedStateFromProps, isInitializedFromReact } from '@eslint-react/core'; import { _, getOrUpdate, constTrue, constFalse, identity } from '@eslint-react/eff'; import { AST_NODE_TYPES } from '@typescript-eslint/types'; import { isMatching, match, P } from 'ts-pattern'; import { compare } from 'compare-versions'; import * as VAR from '@eslint-react/var'; import { getConstrainedTypeAtLocation, isTypeReadonly } from '@typescript-eslint/type-utils'; import { ESLintUtils } from '@typescript-eslint/utils'; import { unionTypeParts, isTypeFlagSet, isTrueLiteralType, isFalseLiteralType } from 'ts-api-utils'; import ts from 'typescript'; var __defProp = Object.defineProperty; var __export = (target, all) => { for (var name5 in all) __defProp(target, name5, { get: all[name5], enumerable: true }); }; // src/configs/recommended.ts var recommended_exports = {}; __export(recommended_exports, { name: () => name, rules: () => rules, settings: () => settings }); var name = "react-x/recommended"; var rules = { "react-x/ensure-forward-ref-using-ref": "warn", "react-x/no-access-state-in-setstate": "error", "react-x/no-array-index-key": "warn", "react-x/no-children-count": "warn", "react-x/no-children-for-each": "warn", "react-x/no-children-map": "warn", "react-x/no-children-only": "warn", "react-x/no-children-to-array": "warn", "react-x/no-clone-element": "warn", "react-x/no-comment-textnodes": "warn", "react-x/no-component-will-mount": "error", "react-x/no-component-will-receive-props": "error", "react-x/no-component-will-update": "error", "react-x/no-context-provider": "warn", "react-x/no-create-ref": "error", "react-x/no-default-props": "error", "react-x/no-direct-mutation-state": "error", "react-x/no-duplicate-jsx-props": "warn", "react-x/no-duplicate-key": "warn", "react-x/no-forward-ref": "warn", "react-x/no-implicit-key": "warn", "react-x/no-missing-key": "error", "react-x/no-nested-components": "error", "react-x/no-prop-types": "error", "react-x/no-redundant-should-component-update": "error", "react-x/no-set-state-in-component-did-mount": "warn", "react-x/no-set-state-in-component-did-update": "warn", "react-x/no-set-state-in-component-will-update": "warn", "react-x/no-string-refs": "error", "react-x/no-unsafe-component-will-mount": "warn", "react-x/no-unsafe-component-will-receive-props": "warn", "react-x/no-unsafe-component-will-update": "warn", "react-x/no-unstable-context-value": "warn", "react-x/no-unstable-default-props": "warn", "react-x/no-unused-class-component-members": "warn", "react-x/no-unused-state": "warn", "react-x/no-use-context": "warn", "react-x/use-jsx-vars": "warn" }; var settings = { "react-x": DEFAULT_ESLINT_REACT_SETTINGS }; // src/configs/recommended-type-checked.ts var recommended_type_checked_exports = {}; __export(recommended_type_checked_exports, { name: () => name3, rules: () => rules3, settings: () => settings3 }); // src/configs/recommended-typescript.ts var recommended_typescript_exports = {}; __export(recommended_typescript_exports, { name: () => name2, rules: () => rules2, settings: () => settings2 }); var name2 = "react-x/recommended-typescript"; var rules2 = { ...rules, "react-x/dom/no-unknown-property": "off", "react-x/no-duplicate-jsx-props": "off", "react-x/use-jsx-vars": "off" }; var settings2 = { ...settings }; // src/configs/recommended-type-checked.ts var name3 = "react-x/recommended-type-checked"; var rules3 = { ...rules2, "react-x/no-leaked-conditional-rendering": "warn" // "react-x/prefer-read-only-props": "warn", }; var settings3 = { ...settings2 }; // package.json var name4 = "eslint-plugin-react-x"; var version = "1.31.0"; var createRule = createRuleForPlugin("x"); // src/rules/avoid-shorthand-boolean.ts var RULE_NAME = "avoid-shorthand-boolean"; var RULE_FEATURES = [ "CHK" ]; var avoid_shorthand_boolean_default = createRule({ meta: { type: "problem", docs: { description: "disallow using shorthand boolean attributes", [Symbol.for("rule_features")]: RULE_FEATURES }, fixable: "code", messages: { avoidShorthandBoolean: "Avoid using shorthand boolean attribute '{{propName}}'. Use '{{propName}}={true}' instead." }, schema: [] }, name: RULE_NAME, create(context) { return { JSXAttribute(node) { if (node.value === null) { context.report({ messageId: "avoidShorthandBoolean", node, data: { propName: JSX8.getAttributeName(node) }, fix: (fixer) => fixer.insertTextAfter(node.name, `={true}`) }); } } }; }, defaultOptions: [] }); // src/rules/avoid-shorthand-fragment.ts var RULE_NAME2 = "avoid-shorthand-fragment"; var RULE_FEATURES2 = [ "CHK" ]; var avoid_shorthand_fragment_default = createRule({ meta: { type: "problem", docs: { description: "disallow using shorthand fragment syntax", [Symbol.for("rule_features")]: RULE_FEATURES2 }, messages: { avoidShorthandFragment: "Avoid using shorthand fragment syntax. Use 'Fragment' component instead." }, schema: [] }, name: RULE_NAME2, create(context) { return { JSXFragment(node) { context.report({ messageId: "avoidShorthandFragment", node }); } }; }, defaultOptions: [] }); var RULE_NAME3 = "ensure-forward-ref-using-ref"; var RULE_FEATURES3 = [ "CHK" ]; var ensure_forward_ref_using_ref_default = createRule({ meta: { type: "problem", docs: { description: "require a 'ref' parameter to be set when using 'forwardRef'", [Symbol.for("rule_features")]: RULE_FEATURES3 }, messages: { ensureForwardRefUsingRef: "A 'forwardRef' is used with this component but no 'ref' parameter is set." }, schema: [] }, name: RULE_NAME3, create(context) { return { CallExpression(node) { if (!isForwardRefCall(context, node)) { return; } const [component] = node.arguments; if (component == null || !AST12.isFunction(component)) { return; } const ref = component.params[1]; if (ref != null) { return; } context.report({ messageId: "ensureForwardRefUsingRef", node: component }); } }; }, defaultOptions: [] }); var RULE_NAME4 = "no-access-state-in-setstate"; var RULE_FEATURES4 = [ "CHK" ]; function isKeyLiteral(node, key) { return match(key).with({ type: AST_NODE_TYPES.Literal }, constTrue).with({ type: AST_NODE_TYPES.TemplateLiteral, expressions: [] }, constTrue).with({ type: AST_NODE_TYPES.Identifier }, () => !node.computed).otherwise(constFalse); } var no_access_state_in_setstate_default = createRule({ meta: { type: "problem", docs: { description: "disallow accessing 'this.state' within 'setState'", [Symbol.for("rule_features")]: RULE_FEATURES4 }, messages: { noAccessStateInSetstate: "Do not access 'this.state' within 'setState'. Use the update function instead." }, schema: [] }, name: RULE_NAME4, create(context) { if (!context.sourceCode.text.includes("setState")) { return {}; } const classEntries = []; const methodEntries = []; const setStateEntries = []; return { CallExpression(node) { if (!isThisSetState(node)) { return; } setStateEntries.push([node, false]); }, "CallExpression:exit"(node) { if (!isThisSetState(node)) { return; } setStateEntries.pop(); }, ClassDeclaration(node) { classEntries.push([node, isClassComponent(node)]); }, "ClassDeclaration:exit"() { classEntries.pop(); }, ClassExpression(node) { classEntries.push([node, isClassComponent(node)]); }, "ClassExpression:exit"() { classEntries.pop(); }, MemberExpression(node) { if (!AST12.isThisExpression(node.object)) { return; } const [currClass, isComponent = false] = classEntries.at(-1) ?? []; if (currClass == null || !isComponent) { return; } const [currMethod, isStatic = false] = methodEntries.at(-1) ?? []; if (currMethod == null || isStatic) { return; } const [setState, hasThisState = false] = setStateEntries.at(-1) ?? []; if (setState == null || hasThisState) { return; } if (AST12.getPropertyName(node.property) !== "state") { return; } context.report({ messageId: "noAccessStateInSetstate", node }); }, MethodDefinition(node) { methodEntries.push([node, node.static]); }, "MethodDefinition:exit"() { methodEntries.pop(); }, PropertyDefinition(node) { methodEntries.push([node, node.static]); }, "PropertyDefinition:exit"() { methodEntries.pop(); }, VariableDeclarator(node) { const [currClass, isComponent = false] = classEntries.at(-1) ?? []; if (currClass == null || !isComponent) { return; } const [currMethod, isStatic = false] = methodEntries.at(-1) ?? []; if (currMethod == null || isStatic) { return; } const [setState, hasThisState = false] = setStateEntries.at(-1) ?? []; if (setState == null || hasThisState) { return; } if (node.init == null || !AST12.isThisExpression(node.init) || node.id.type !== AST_NODE_TYPES.ObjectPattern) { return; } const hasState = node.id.properties.some( (prop) => prop.type === AST_NODE_TYPES.Property && isKeyLiteral(prop, prop.key) && AST12.getPropertyName(prop.key) === "state" ); if (!hasState) { return; } context.report({ messageId: "noAccessStateInSetstate", node }); } }; }, defaultOptions: [] }); var RULE_NAME5 = "no-array-index-key"; var RULE_FEATURES5 = [ "CHK" ]; var reactChildrenMethod = ["forEach", "map"]; var iteratorFunctionIndexParamPosition = /* @__PURE__ */ new Map([ ["every", 1], ["filter", 1], ["find", 1], ["findIndex", 1], ["findLast", 1], ["findLastIndex", 1], ["flatMap", 1], ["forEach", 1], ["map", 1], ["reduce", 2], ["reduceRight", 2], ["some", 1] ]); function isReactChildrenMethod(name5) { return reactChildrenMethod.some((method) => method === name5); } function isUsingReactChildren(context, node) { const { importSource = "react" } = unsafeDecodeSettings(context.settings); const { callee } = node; if (!("property" in callee) || !("object" in callee) || !("name" in callee.property)) { return false; } if (!isReactChildrenMethod(callee.property.name)) { return false; } const initialScope = context.sourceCode.getScope(node); if (callee.object.type === AST_NODE_TYPES.Identifier && callee.object.name === "Children") { return true; } if (callee.object.type === AST_NODE_TYPES.MemberExpression && "name" in callee.object.object) { return isInitializedFromReact(callee.object.object.name, importSource, initialScope); } return false; } function getMapIndexParamName(context, node) { const { callee } = node; if (callee.type !== AST_NODE_TYPES.MemberExpression) { return _; } if (callee.property.type !== AST_NODE_TYPES.Identifier) { return _; } const { name: name5 } = callee.property; if (!iteratorFunctionIndexParamPosition.has(name5)) { return _; } const callbackArg = node.arguments[isUsingReactChildren(context, node) ? 1 : 0]; if (callbackArg == null) { return _; } if (!AST12.isOneOf([AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression])(callbackArg)) { return _; } const { params } = callbackArg; const indexParamPosition = iteratorFunctionIndexParamPosition.get(name5); if (indexParamPosition == null) { return _; } if (params.length < indexParamPosition + 1) { return _; } const param = params.at(indexParamPosition); return param != null && "name" in param ? param.name : _; } var no_array_index_key_default = createRule({ meta: { type: "problem", docs: { description: "disallow using an item's index in the array as its key", [Symbol.for("rule_features")]: RULE_FEATURES5 }, messages: { noArrayIndexKey: "Do not use item index in the array as its key." }, schema: [] }, name: RULE_NAME5, create(context) { const indexParamNames = []; function isArrayIndex(node) { return node.type === AST_NODE_TYPES.Identifier && indexParamNames.some((name5) => name5 != null && name5 === node.name); } function isCreateOrCloneElementCall(node) { return isCreateElementCall(context, node) || isCloneElementCall(context, node); } function getReportDescriptors(node) { switch (node.type) { // key={bar} case AST_NODE_TYPES.Identifier: { if (indexParamNames.some((name5) => name5 != null && name5 === node.name)) { return [{ messageId: "noArrayIndexKey", node }]; } return []; } // key={`foo-${bar}`} or key={'foo' + bar} case AST_NODE_TYPES.TemplateLiteral: case AST_NODE_TYPES.BinaryExpression: { const descriptors = []; const expressions = node.type === AST_NODE_TYPES.TemplateLiteral ? node.expressions : getIdentifiersFromBinaryExpression(node); for (const expression of expressions) { if (isArrayIndex(expression)) { descriptors.push({ messageId: "noArrayIndexKey", node: expression }); } } return descriptors; } // key={bar.toString()} or key={String(bar)} case AST_NODE_TYPES.CallExpression: { switch (true) { // key={bar.toString()} case (node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === "toString" && isArrayIndex(node.callee.object)): { return [{ messageId: "noArrayIndexKey", node: node.callee.object }]; } // key={String(bar)} case (node.callee.type === AST_NODE_TYPES.Identifier && node.callee.name === "String" && node.arguments[0] != null && isArrayIndex(node.arguments[0])): { return [{ messageId: "noArrayIndexKey", node: node.arguments[0] }]; } } } } return []; } return { CallExpression(node) { indexParamNames.push(getMapIndexParamName(context, node)); if (node.arguments.length === 0) { return; } if (!isCreateOrCloneElementCall(node)) { return; } const [, props] = node.arguments; if (props?.type !== AST_NODE_TYPES.ObjectExpression) { return; } for (const prop of props.properties) { if (!isMatching({ key: { name: "key" } })(prop)) { continue; } if (!("value" in prop)) { continue; } const descriptors = getReportDescriptors(prop.value); for (const descriptor of descriptors) { context.report(descriptor); } } }, "CallExpression:exit"() { indexParamNames.pop(); }, JSXAttribute(node) { if (node.name.name !== "key") { return; } if (indexParamNames.length === 0) { return; } if (node.value?.type !== AST_NODE_TYPES.JSXExpressionContainer) { return; } const descriptors = getReportDescriptors(node.value.expression); for (const descriptor of descriptors) { context.report(descriptor); } } }; }, defaultOptions: [] }); function getIdentifiersFromBinaryExpression(side) { if (side.type === AST_NODE_TYPES.Identifier) { return [side]; } if (side.type === AST_NODE_TYPES.BinaryExpression) { return [ ...getIdentifiersFromBinaryExpression(side.left), ...getIdentifiersFromBinaryExpression(side.right) ]; } return []; } var RULE_NAME6 = "no-children-count"; var RULE_FEATURES6 = [ "CHK" ]; var no_children_count_default = createRule({ meta: { type: "problem", docs: { description: "disallow using 'Children.count'", [Symbol.for("rule_features")]: RULE_FEATURES6 }, messages: { noChildrenCount: "Using 'Children.count' is uncommon and can lead to fragile code. Use alternatives instead." }, schema: [] }, name: RULE_NAME6, create(context) { return { MemberExpression(node) { if (isChildrenCount(context, node)) { context.report({ messageId: "noChildrenCount", node: node.property }); } } }; }, defaultOptions: [] }); var RULE_NAME7 = "no-children-for-each"; var RULE_FEATURES7 = [ "CHK" ]; var no_children_for_each_default = createRule({ meta: { type: "problem", docs: { description: "disallow using 'Children.forEach'", [Symbol.for("rule_features")]: RULE_FEATURES7 }, messages: { noChildrenForEach: "Using 'Children.forEach' is uncommon and can lead to fragile code. Use alternatives instead." }, schema: [] }, name: RULE_NAME7, create(context) { return { MemberExpression(node) { if (isChildrenForEach(context, node)) { context.report({ messageId: "noChildrenForEach", node: node.property }); } } }; }, defaultOptions: [] }); var RULE_NAME8 = "no-children-map"; var RULE_FEATURES8 = [ "CHK" ]; var no_children_map_default = createRule({ meta: { type: "problem", docs: { description: "disallow using 'Children.map'", [Symbol.for("rule_features")]: RULE_FEATURES8 }, messages: { noChildrenMap: "Using 'Children.map' is uncommon and can lead to fragile code. Use alternatives instead." }, schema: [] }, name: RULE_NAME8, create(context) { return { MemberExpression(node) { if (isChildrenMap(context, node)) { context.report({ messageId: "noChildrenMap", node: node.property }); } } }; }, defaultOptions: [] }); var RULE_NAME9 = "no-children-only"; var RULE_FEATURES9 = [ "CHK" ]; var no_children_only_default = createRule({ meta: { type: "problem", docs: { description: "disallow using 'Children.only'", [Symbol.for("rule_features")]: RULE_FEATURES9 }, messages: { noChildrenOnly: "Using 'Children.only' is uncommon and can lead to fragile code. Use alternatives instead." }, schema: [] }, name: RULE_NAME9, create(context) { return { MemberExpression(node) { if (isChildrenOnly(context, node)) { context.report({ messageId: "noChildrenOnly", node: node.property }); } } }; }, defaultOptions: [] }); var RULE_NAME10 = "no-children-prop"; var RULE_FEATURES10 = [ "CHK" ]; var no_children_prop_default = createRule({ meta: { type: "problem", docs: { description: "disallow passing 'children' as props", [Symbol.for("rule_features")]: RULE_FEATURES10 }, messages: { noChildrenProp: "Do not pass 'children' as props." }, schema: [] }, name: RULE_NAME10, create(context) { return { JSXElement(node) { const attribute = JSX8.getAttribute( "children", node.openingElement.attributes, context.sourceCode.getScope(node) ); if (attribute != null) { context.report({ messageId: "noChildrenProp", node: attribute }); } } }; }, defaultOptions: [] }); var RULE_NAME11 = "no-children-to-array"; var RULE_FEATURES11 = [ "CHK" ]; var no_children_to_array_default = createRule({ meta: { type: "problem", docs: { description: "disallow using 'Children.toArray'", [Symbol.for("rule_features")]: RULE_FEATURES11 }, messages: { noChildrenToArray: "Using 'Children.toArray' is uncommon and can lead to fragile code. Use alternatives instead." }, schema: [] }, name: RULE_NAME11, create(context) { return { MemberExpression(node) { if (isChildrenToArray(context, node)) { context.report({ messageId: "noChildrenToArray", node: node.property }); } } }; }, defaultOptions: [] }); var RULE_NAME12 = "no-class-component"; var RULE_FEATURES12 = [ "CHK" ]; var no_class_component_default = createRule({ meta: { type: "problem", docs: { description: "disallow using class components", [Symbol.for("rule_features")]: RULE_FEATURES12 }, messages: { noClassComponent: "Do not use class components. Use function components instead." }, schema: [] }, name: RULE_NAME12, create(context) { if (!context.sourceCode.text.includes("Component")) { return {}; } const { ctx, listeners } = useComponentCollectorLegacy(); return { ...listeners, "Program:exit"(node) { const components = ctx.getAllComponents(node); for (const { name: name5 = "anonymous", node: component } of components.values()) { if (component.body.body.some((m) => isComponentDidCatch(m) || isGetDerivedStateFromError(m))) { continue; } context.report({ messageId: "noClassComponent", node: component, data: { name: name5 } }); } } }; }, defaultOptions: [] }); var RULE_NAME13 = "no-clone-element"; var RULE_FEATURES13 = [ "CHK" ]; var no_clone_element_default = createRule({ meta: { type: "problem", docs: { description: "disallow using 'cloneElement'", [Symbol.for("rule_features")]: RULE_FEATURES13 }, messages: { noCloneElement: "Using 'cloneElement' is uncommon and can lead to fragile code. Use alternatives instead." }, schema: [] }, name: RULE_NAME13, create(context) { return { CallExpression(node) { if (!isCloneElementCall(context, node)) { return; } context.report({ messageId: "noCloneElement", node }); } }; }, defaultOptions: [] }); var RULE_NAME14 = "no-comment-textnodes"; var RULE_FEATURES14 = [ "CHK" ]; var no_comment_textnodes_default = createRule({ meta: { type: "problem", docs: { description: "disallow comments from being inserted as text nodes", [Symbol.for("rule_features")]: RULE_FEATURES14 }, messages: { noCommentTextnodes: "Possible misused comment in text node. Comments inside children section of tag should be placed inside braces." }, schema: [] }, name: RULE_NAME14, create(context) { function hasCommentLike(node) { if (AST12.isOneOf([AST_NODE_TYPES.JSXAttribute, AST_NODE_TYPES.JSXExpressionContainer])(node.parent)) { return false; } const rawValue = context.sourceCode.getText(node); return /^\s*\/(?:\/|\*)/mu.test(rawValue); } const visitorFunction = (node) => { if (!AST12.isOneOf([AST_NODE_TYPES.JSXElement, AST_NODE_TYPES.JSXFragment])(node.parent)) { return; } if (!hasCommentLike(node)) { return; } if (!node.parent.type.includes("JSX")) { return; } context.report({ messageId: "noCommentTextnodes", node }); }; return { JSXText: visitorFunction, Literal: visitorFunction }; }, defaultOptions: [] }); var RULE_NAME15 = "no-complex-conditional-rendering"; var RULE_FEATURES15 = [ "CHK" ]; var no_complex_conditional_rendering_default = createRule({ meta: { type: "problem", docs: { description: "disallow complex conditional rendering", [Symbol.for("rule_features")]: RULE_FEATURES15 }, messages: { noComplexConditionalRendering: "Avoid complex conditional rendering. Extract the logic into separate elements or components." }, schema: [] }, name: RULE_NAME15, create(context) { const visitorFunction = (node) => { const jsxExpContainer = node.parent?.parent; if (!AST12.is(AST_NODE_TYPES.JSXExpressionContainer)(jsxExpContainer)) { return; } if (!AST12.isOneOf([AST_NODE_TYPES.JSXElement, AST_NODE_TYPES.JSXFragment])(jsxExpContainer.parent)) { return; } if (!jsxExpContainer.parent.children.includes(jsxExpContainer)) { return; } context.report({ messageId: "noComplexConditionalRendering", node: jsxExpContainer }); }; return { "JSXExpressionContainer > ConditionalExpression > ConditionalExpression": visitorFunction, "JSXExpressionContainer > ConditionalExpression > LogicalExpression": visitorFunction, "JSXExpressionContainer > LogicalExpression > ConditionalExpression": visitorFunction, "JSXExpressionContainer > LogicalExpression[operator='&&'] > LogicalExpression[operator='||']": visitorFunction, "JSXExpressionContainer > LogicalExpression[operator='||'] > LogicalExpression[operator='&&']": visitorFunction }; }, defaultOptions: [] }); var RULE_NAME16 = "no-component-will-mount"; var RULE_FEATURES16 = [ "CHK", "MOD" ]; function isComponentWillMount(node) { return AST12.isMethodOrProperty(node) && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === "componentWillMount"; } var no_component_will_mount_default = createRule({ meta: { type: "problem", docs: { description: "replace 'componentWillMount' with 'UNSAFE_componentWillMount'", [Symbol.for("rule_features")]: RULE_FEATURES16 }, fixable: "code", messages: { noComponentWillMount: "[Deprecated] Use 'UNSAFE_componentWillMount' instead." }, schema: [] }, name: RULE_NAME16, create(context) { if (!context.sourceCode.text.includes("componentWillMount")) { return {}; } const { ctx, listeners } = useComponentCollectorLegacy(); return { ...listeners, "Program:exit"(node) { const components = ctx.getAllComponents(node); for (const { node: component } of components.values()) { const { body } = component.body; for (const member of body) { if (isComponentWillMount(member)) { context.report({ messageId: "noComponentWillMount", node: member, fix(fixer) { if (!("key" in member)) { return null; } return fixer.replaceText(member.key, "UNSAFE_componentWillMount"); } }); } } } } }; }, defaultOptions: [] }); var RULE_NAME17 = "no-component-will-receive-props"; var RULE_FEATURES17 = [ "CHK", "MOD" ]; function isComponentWillUpdate(node) { return AST12.isMethodOrProperty(node) && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === "componentWillReceiveProps"; } var no_component_will_receive_props_default = createRule({ meta: { type: "problem", docs: { description: "replace 'componentWillReceiveProps' with 'UNSAFE_componentWillReceiveProps'", [Symbol.for("rule_features")]: RULE_FEATURES17 }, fixable: "code", messages: { noComponentWillReceiveProps: "[Deprecated] Use 'UNSAFE_componentWillReceiveProps' instead." }, schema: [] }, name: RULE_NAME17, create(context) { if (!context.sourceCode.text.includes("componentWillReceiveProps")) { return {}; } const { ctx, listeners } = useComponentCollectorLegacy(); return { ...listeners, "Program:exit"(node) { const components = ctx.getAllComponents(node); for (const { node: component } of components.values()) { const { body } = component.body; for (const member of body) { if (isComponentWillUpdate(member)) { context.report({ messageId: "noComponentWillReceiveProps", node: member, fix(fixer) { if (!("key" in member)) { return null; } return fixer.replaceText(member.key, "UNSAFE_componentWillReceiveProps"); } }); } } } } }; }, defaultOptions: [] }); var RULE_NAME18 = "no-component-will-update"; var RULE_FEATURES18 = [ "CHK", "MOD" ]; function isComponentWillUpdate2(node) { return AST12.isMethodOrProperty(node) && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === "componentWillUpdate"; } var no_component_will_update_default = createRule({ meta: { type: "problem", docs: { description: "replace 'componentWillUpdate' with 'UNSAFE_componentWillUpdate'", [Symbol.for("rule_features")]: RULE_FEATURES18 }, fixable: "code", messages: { noComponentWillUpdate: "[Deprecated] Use 'UNSAFE_componentWillUpdate' instead." }, schema: [] }, name: RULE_NAME18, create(context) { if (!context.sourceCode.text.includes("componentWillUpdate")) { return {}; } const { ctx, listeners } = useComponentCollectorLegacy(); return { ...listeners, "Program:exit"(node) { const components = ctx.getAllComponents(node); for (const { node: component } of components.values()) { const { body } = component.body; for (const member of body) { if (isComponentWillUpdate2(member)) { context.report({ messageId: "noComponentWillUpdate", node: member, fix(fixer) { if (!("key" in member)) { return null; } return fixer.replaceText(member.key, "UNSAFE_componentWillUpdate"); } }); } } } } }; }, defaultOptions: [] }); var RULE_NAME19 = "no-context-provider"; var RULE_FEATURES19 = [ "CHK", "MOD" ]; var no_context_provider_default = createRule({ meta: { type: "problem", docs: { description: "replace '<Context.Provider>' with '<Context>'", [Symbol.for("rule_features")]: RULE_FEATURES19 }, fixable: "code", messages: { noContextProvider: "In React 19, you can render '<{{contextName}}>' as a provider instead of '<{{contextName}}.Provider>'." }, schema: [] }, name: RULE_NAME19, create(context) { if (!context.sourceCode.text.includes(".Provider")) return {}; const { version: version2 } = getSettingsFromContext(context); if (compare(version2, "19.0.0", "<")) { return {}; } return { JSXElement(node) { const [name5, ...rest] = JSX8.getElementType(node).split(".").reverse(); if (name5 !== "Provider") return; const contextName = rest.reverse().join("."); context.report({ messageId: "noContextProvider", node, data: { contextName }, fix(fixer) { const openingElement = node.openingElement; const closingElement = node.closingElement; if (closingElement == null) { return fixer.replaceText(openingElement.name, contextName); } return [ fixer.replaceText(openingElement.name, contextName), fixer.replaceText(closingElement.name, contextName) ]; } }); } }; }, defaultOptions: [] }); var RULE_NAME20 = "no-create-ref"; var RULE_FEATURES20 = [ "CHK" ]; var no_create_ref_default = createRule({ meta: { type: "problem", docs: { description: "disallow using 'createRef' in function components", [Symbol.for("rule_features")]: RULE_FEATURES20 }, messages: { noCreateRef: "[Deprecated] Use 'useRef' instead." }, schema: [] }, name: RULE_NAME20, create(context) { return { CallExpression(node) { if (isCreateRefCall(context, node) && AST12.findParentNode(node, isClassComponent) == null) { context.report({ messageId: "noCreateRef", node }); } } }; }, defaultOptions: [] }); var RULE_NAME21 = "no-default-props"; var RULE_FEATURES21 = [ "CHK" ]; var no_default_props_default = createRule({ meta: { type: "problem", docs: { description: "disallow using 'defaultProps' property in components", [Symbol.for("rule_features")]: RULE_FEATURES21 }, messages: { noDefaultProps: "[Deprecated] Use ES6 default parameters instead." }, schema: [] }, name: RULE_NAME21, create(context) { if (!context.sourceCode.text.includes("defaultProps")) { return {}; } return { AssignmentExpression(node) { if (node.operator !== "=" || node.left.type !== AST_NODE_TYPES.MemberExpression) { return; } const { object, property } = node.left; if (object.type !== AST_NODE_TYPES.Identifier) { return; } if (property.type !== AST_NODE_TYPES.Identifier || property.name !== "defaultProps") { return; } if (!isComponentName(object.name)) { return; } const variable = VAR.findVariable(object.name, context.sourceCode.getScope(node)); const variableNode = VAR.getVariableInitNode(variable, 0); if (variableNode == null) return; if (!AST12.isFunction(variableNode) && !isClassComponent(variableNode)) return; context.report({ messageId: "noDefaultProps", node: property }); }, PropertyDefinition(node) { if (!isClassComponent(node.parent.parent)) { return; } if (!node.static || node.key.type !== AST_NODE_TYPES.Identifier || node.key.name !== "defaultProps") { return; } context.report({ messageId: "noDefaultProps", node }); } }; }, defaultOptions: [] }); var RULE_NAME22 = "no-direct-mutation-state"; var RULE_FEATURES22 = [ "CHK" ]; function isConstructorFunction(node) { return AST12.isOneOf([AST_NODE_TYPES.FunctionDeclaration, AST_NODE_TYPES.FunctionExpression])(node) && AST12.isMethodOrProperty(node.parent) && node.parent.key.type === AST_NODE_TYPES.Identifier && node.parent.key.name === "constructor"; } var no_direct_mutation_state_default = createRule({ meta: { type: "problem", docs: { description: "disallow direct mutation of state", [Symbol.for("rule_features")]: RULE_FEATURES22 }, messages: { noDirectMutationState: "Do not mutate state directly. Use 'setState()' instead." }, schema: [] }, name: RULE_NAME22, create(context) { return { AssignmentExpression(node) { if (!isAssignmentToThisState(node)) { return; } const parentClass = AST12.findParentNode( node, AST12.isOneOf([ AST_NODE_TYPES.ClassDeclaration, AST_NODE_TYPES.ClassExpression ]) ); if (parentClass == null) return; if (isClassComponent(parentClass) && context.sourceCode.getScope(node).block !== AST12.findParentNode(node, isConstructorFunction)) { context.report({ messageId: "noDirectMutationState", node }); } } }; }, defaultOptions: [] }); var RULE_NAME23 = "no-duplicate-jsx-props"; var RULE_FEATURES23 = [ "CHK" ]; var no_duplicate_jsx_props_default = createRule({ meta: { type: "problem", docs: { description: "disallow duplicate props", [Symbol.for("rule_features")]: RULE_FEATURES23 }, messages: { noDuplicateJsxProps: "This JSX property is assigned multiple times." }, schema: [] }, name: RULE_NAME23, create(context) { return { JSXOpeningElement(node) { const props = []; for (const attr of node.attributes) { if (attr.type === AST_NODE_TYPES.JSXSpreadAttribute) { continue; } const name5 = attr.name.name; if (typeof name5 !== "string") { continue; } if (!props.includes(name5)) { props.push(name5); continue; } context.report({ messageId: "noDuplicateJsxProps", node: attr }); } } }; }, defaultOptions: [] }); var RULE_NAME24 = "no-duplicate-key"; var RULE_FEATURES24 = [ "CHK" ]; var no_duplicate_key_default = createRule({ meta: { type: "problem", docs: { description: "disallow duplicate keys when rendering list", [Symbol.for("rule_features")]: RULE_FEATURES24 }, messages: { noDuplicateKey: "A key must be unique. '{{value}}' is duplicated." }, schema: [] }, name: RULE_NAME24, create(context) { if (!context.sourceCode.getText().includes("key=")) { return {}; } const keyedEntries = /* @__PURE__ */ new Map(); function isKeyValueEqual(a, b) { const aValue = a.value; const bValue = b.value; if (aValue == null || bValue == null) { return false; } return AST12.isNodeEqual(aValue, bValue); } return { "JSXAttribute[name.name='key']"(node) { const jsxElement = node.parent.parent; switch (jsxElement.parent.type) { case AST_NODE_TYPES.ArrayExpression: case AST_NODE_TYPES.JSXElement: case AST_NODE_TYPES.JSXFragment: { const root = jsxElement.parent; const prevKeys = keyedEntries.get(root)?.keys ?? []; keyedEntries.set(root, { hasDuplicate: prevKeys.some((prevKey) => isKeyValueEqual(prevKey, node)), keys: [...prevKeys, node], root: jsxElement.parent }); break; } default: { const call = AST12.findParentNode(jsxElement, AST12.isMapCallLoose); const iter = AST12.findParentNode(jsxElement, (n) => n === call || AST12.isFunction(n)); if (!AST12.isFunction(iter)) return; const arg0 = call?.arguments[0]; if (call == null || arg0 == null) return; if (AST12.getEcmaExpression(arg0) !== iter) { return; } keyedEntries.set(call, { hasDuplicate: node.value?.type === AST_NODE_TYPES.Literal, keys: [node], root: call }); } } }, "Program:exit"() { for (const { hasDuplicate, keys } of keyedEntries.values()) { if (!hasDuplicate) { continue; } for (const key of keys) { context.report({ messageId: "noDuplicateKey", node: key, data: { value: context.sourceCode.getText(key) } }); } } } }; }, defaultOptions: [] }); var RULE_NAME25 = "no-forward-ref"; var RULE_FEATURES25 = [ "CHK", "MOD" ]; var no_forward_ref_default = createRule({ meta: { type: "problem", docs: { description: "replace 'forwardRef' with passing 'ref' as a prop", [Symbol.for("rule_features")]: RULE_FEATURES25 }, fixable: "code", messages: { noForwardRef: "In React 19, 'forwardRef' is no longer necessary. Pass 'ref' as a prop instead." }, schema: [] }, name: RULE_NAME25, create(context) { if (!context.sourceCode.text.includes("forwardRef")) { return {}; } const { version: version2 } = getSettingsFromContext(context); if (compare(version2, "19.0.0", "<")) { return {}; } return { CallExpression(node) { if (!isForwardRefCall(context, node)) { return; } context.report({ messageId: "noForwardRef", node, fix: getFix(context, node) }); } }; }, defaultOptions: [] }); function getFix(context, node) { return (fixer) => { const [componentNode] = node.arguments; if (componentNode == null || !AST12.isFunction(componentNode)) { return []; } return [ // unwrap component from forwardRef call fixer.removeRange([node.range[0], componentNode.range[0]]), fixer.removeRange([componentNode.range[1], node.range[1]]), // update component props and ref arguments to match the new signature ...getComponentPropsFixes( context, fixer, componentNode, node.typeArguments?.params ?? [] ) ]; }; } function getComponentPropsFixes(context, fixer, node, typeArguments) { const getText = (node2) => context.sourceCode.getText(node2); const [arg0, arg1] = node.params; const [typeArg0, typeArg1] = typeArguments; if (arg0 == null) { return []; } const fixedArg0Text = match(arg0).with({ type: AST_NODE_TYPES.Identifier }, (n) => `...${n.name}`).with({ type: AST_NODE_TYPES.ObjectPattern }, (n) => n.properties.map(getText).join(", ")).otherwise(() => _); const fixedArg1Text = match(arg1).with(P.nullish, () => "ref").with({ type: AST_NODE_TYPES.Identifier, name: "ref" }, () => "ref").with({ type: AST_NODE_TYPES.Identifier, name: P.string }, (n) => `ref: ${n.name}`).otherwise(() => _); if (fixedArg0Text == null || fixedArg1Text == null) { return []; } if (typeArg0 == null || typeArg1 == null) { return [ fixer.replaceText( arg0, [ "{", fixedArg1Text + ",", fixedArg0Text, "}" ].join(" ") ), ...arg1 == null ? [] : [fixer.remove(arg1), fixer.removeRange([arg0.range[1], arg1.range[0]])] ]; } const typeArg0Text = getText(typeArg0); const typeArg1Text = getText(typeArg1); return [ fixer.replaceText( arg0, [ "{", fixedArg1Text + ",", fixedArg0Text, "}:", typeArg1Text, "&", "{", `ref?:`, `React.RefObject<${typeArg0Text} | null>`, "}" ].join(" ") ), ...arg1 == null ? [] : [fixer.remove(arg1), fixer.removeRange([arg0.range[1], arg1.range[0]])] ]; } var RULE_NAME26 = "no-implicit-key"; var RULE_FEATURES26 = [ "CHK" ]; var no_implicit_key_default = createRule({ meta: { type: "problem", docs: { description: "disallow implicit 'key' props", [Symbol.for("rule_features")]: RULE_FEATURES26 }, messages: { noImplicitKey: "Do not use implicit 'key' props." }, schema: [] }, name: RULE_NAME26, create(context) { return { JSXOpeningElement(node) { const initialScope = context.sourceCode.getScope(node); const keyPropFound = JSX8.getAttribute("key", node.attributes, initialScope); const keyPropOnElement = node.attributes.some( (n) => n.type === AST_NODE_TYPES.JSXAttribute && n.name.type === AST_NODE_TYPES.JSXIdentifier && n.name.name === "key" ); if (keyPropFound != null && !keyPropOnElement) { context.report({ messageId: "noImplicitKey", node: keyPropFound }); } } }; }, defaultOptions: [] }); var RULE_NAME27 = "no-leaked-conditional-rendering"; var RULE_FEATURES27 = [ "CHK", "TSC" ]; var tsHelpers = { isAnyType: (type) => isTypeFlagSet(type, ts.TypeFlags.TypeParameter | ts.TypeFlags.Any), isBigIntType: (type) => isTypeFlagSet(type, ts.TypeFlags.BigIntLike), isBooleanType: (type) => isTypeFlagSet(type, ts.TypeFlags.BooleanLike), isEnumType: (type) => isTypeFlagSet(type, ts.TypeFlags.EnumLike), isFalsyBigIntType: (type) => type.isLiteral() && isMatching({ value: { base10Value: "0" } }, type), isFalsyNumberType: (type) => type.isNumberLiteral() && type.value === 0, isFalsyStringType: (type) => type.isStringLiteral() && type.value === "", isNeverType: (type) => isTypeFlagSet(type, ts.TypeFlags.Never), isNullishType: (type) => isTypeFlagSet( type, ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.VoidLike ), isNumberType: (type) => isTypeFlagSet(type, ts.TypeFlags.NumberLike), isObjectType: (type) => !isTypeFlagSet( type, ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.VoidLike | ts.TypeFlags.BooleanLike | ts.TypeFlags.StringLike | ts.TypeFlags.NumberLike | ts.TypeFlags.BigIntLike | ts.TypeFlags.TypeParameter | ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Never ), isStringType: (type) => isTypeFlagSet(type, ts.TypeFlags.StringLike), isTruthyBigIntType: (type) => type.isLiteral() && isMatching({ value: { base10Value: P.not("0") } }, type), isTruthyNumberType: (type) => type.isNumberLiteral() && type.value !== 0, isTruthyStringType: (type) => type.isStringLiteral() && type.value !== "", isUnknownType: (type) => isTypeFlagSet(type, ts.TypeFlags.Unknown) }; function inspectVariantTypes(types) { const variantTypes = /* @__PURE__ */ new Set(); if (types.some(tsHelpers.isUnknownType)) { variantTypes.add("unknown"); return variantTypes; } if (types.some(tsHelpers.isNullishType)) { variantTypes.add("nullish"); } const booleans = types.filter(tsHelpers.isBooleanType); switch (true) { case (booleans.length === 1 && booleans[0] != null): { const first = booleans[0]; if (isTrueLiteralType(first)) { variantTypes.add("truthy boolean"); } else if (isFalseLiteralType(first)) { variantTypes.add("falsy boolean"); } break; } case booleans.length === 2: { variantTypes.add("boolean"); break; } } const strings = types.filter(tsHelpers.isStringType); if (strings.length > 0) { const evaluated = match(strings).when((types2) => types2.every(tsHelpers.isTruthyStringType), () => "truthy string").when((types2) => types2.every(tsHelpers.isFalsyStringType), () => "falsy string").otherwise(() => "string"); variantTypes.add(evaluated); } const bigints = types.filter(tsHelpers.isBigIntType); if (bigints.length > 0) { const evaluated = match(bigints).when((types2) => types2.every(tsHelpers.isTruthyBigIntType), () => "truthy bigint").when((types2) => types2.every(tsHelpers.isFalsyBigIntType), () => "falsy bigint").otherwise(() => "bigint"); variantTypes.add(evaluated); } const numbers = types.filter(tsHelpers.isNumberType); if (numbers.length > 0) { const evaluated = match(numbers).when((types2) => types2.every(tsHelpers.isTruthyNumberType), () => "truthy number").when((types2) => types2.every(tsHelpers.isFalsyNumberType), () => "falsy number").otherwise(() => "number"); variantTypes.add(evaluated); } if (types.some(tsHelpers.isEnumType)) { variantTypes.add("enum"); } if (types.some(tsHelpers.isObjectType)) { variantTypes.add("object"); } if (types.some(tsHelpers.isAnyType)) { variantTypes.add("any"); } if (types.some(tsHelpers.isNeverType)) { variantTypes.add("never"); } return variantTypes; } var no_leaked_conditional_rendering_default = createRule({ meta: { type: "problem", docs: { description: "disallow problematic leaked values from being rendered", [Symbol.for("rule_features")]: RULE_FEATURES27 }, messages: { noLeakedConditionalRendering: "Potential leaked value {{value}} that might cause unintentionally rendered values or rendering crashes." }, schema: [] }, name: RULE_NAME27, create(context) { if (!context.sourceCode.text.includes("&&") && !context.sourceCode.text.includes("?")) { return {}; } const { version: version2 } = getSettingsFromContext(context); const allowedVariants = [ "any", "boolean", "nullish", "object", "falsy boolean", "truthy bigint", "truthy boolean", "truthy number", "truthy string", ...compare(version2, "18.0.0", "<") ? [] : ["string", "falsy string"] ]; const services = ESLintUtils.getParserServices(context, false); function getReportDescriptor(node) { if (node == null) return _; return match(node).when(AST12.isJSX, () => _).with({ type: AST_NODE_TYPES.LogicalExpression, operator: "&&" }, ({ left, right }) => { const isLeftUnaryNot = left.ty