UNPKG

eslint-plugin-react-debug

Version:

ESLint React's ESLint plugin for debugging related rules.

308 lines (293 loc) • 9.87 kB
import { DEFAULT_ESLINT_REACT_SETTINGS, getSettingsFromContext } from "@eslint-react/shared"; import { ESLintUtils } from "@typescript-eslint/utils"; import * as core from "@eslint-react/core"; import { isUseRefLikeCall } from "@eslint-react/core"; import { merge } from "@eslint-react/eslint"; import { AST_NODE_TYPES } from "@typescript-eslint/types"; import { isInitializedFromReact } from "@eslint-react/var"; import { findVariable } from "@typescript-eslint/utils/ast-utils"; import { getElementFullType, isFragmentElement } from "@eslint-react/jsx"; import { P, match } from "ts-pattern"; import ts from "typescript"; //#region \0rolldown/runtime.js var __defProp = Object.defineProperty; var __exportAll = (all, no_symbols) => { let target = {}; for (var name in all) { __defProp(target, name, { get: all[name], enumerable: true }); } if (!no_symbols) { __defProp(target, Symbol.toStringTag, { value: "Module" }); } return target; }; //#endregion //#region package.json var name$1 = "eslint-plugin-react-debug"; var version = "5.18.1"; //#endregion //#region src/utils/create-rule.ts function getDocsUrl(ruleName) { return `https://eslint-react.xyz/docs/rules/debug-${ruleName}`; } const createRule = ESLintUtils.RuleCreator(getDocsUrl); //#endregion //#region src/utils/stringify.ts function stringify(value) { return JSON.stringify(value, null, 2); } //#endregion //#region src/rules/function-component/function-component.ts const RULE_NAME$4 = "function-component"; var function_component_default = createRule({ meta: { type: "problem", docs: { description: "Reports all function components in JSON format." }, messages: { default: "{{json}}" }, schema: [] }, name: RULE_NAME$4, create: create$4, defaultOptions: [] }); function create$4(context) { const { api, visitor } = core.getFunctionComponentCollector(context, { collectDisplayName: true, hint: core.DEFAULT_COMPONENT_DETECTION_HINT }); return merge(visitor, { "Program:exit"(program) { for (const { name, displayName, flag, hookCalls, node } of api.getAllComponents(program)) context.report({ data: { json: stringify({ name: name ?? "anonymous", displayName: displayName == null ? "none" : context.sourceCode.getText(displayName), forwardRef: (flag & core.FunctionComponentFlag.ForwardRef) > 0n, hookCalls: hookCalls.length, memo: (flag & core.FunctionComponentFlag.Memo) > 0n }) }, messageId: "default", node }); } }); } //#endregion //#region src/rules/hook/hook.ts const RULE_NAME$3 = "hook"; var hook_default = createRule({ meta: { type: "problem", docs: { description: "Reports all React Hooks in JSON format." }, messages: { default: "{{json}}" }, schema: [] }, name: RULE_NAME$3, create: create$3, defaultOptions: [] }); function create$3(context) { const { api, visitor } = core.getHookCollector(context); return merge(visitor, { "Program:exit"(program) { for (const { name, hookCalls, node } of api.getAllHooks(program)) context.report({ data: { json: stringify({ name, hookCalls: hookCalls.length }) }, messageId: "default", node }); } }); } //#endregion //#region src/rules/is-from-react/lib.ts /** * Check if an identifier node is initialized from React * @param node The identifier node to check * @param initialScope Initial scope to search for the identifier * @param importSource The import source to check against * @returns Whether the identifier node is initialized from React */ function isFromReact(node, initialScope, importSource = "react") { const name = node.name; switch (true) { case node.parent.type === AST_NODE_TYPES.MemberExpression && node.parent.property === node && node.parent.object.type === AST_NODE_TYPES.Identifier: return isInitializedFromReact(node.parent.object.name, initialScope, importSource); case node.parent.type === AST_NODE_TYPES.JSXMemberExpression && node.parent.property === node && node.parent.object.type === AST_NODE_TYPES.JSXIdentifier: return isInitializedFromReact(node.parent.object.name, initialScope, importSource); default: return isInitializedFromReact(name, initialScope, importSource); } } //#endregion //#region src/rules/is-from-react/is-from-react.ts const RULE_NAME$2 = "is-from-react"; var is_from_react_default = createRule({ meta: { type: "problem", docs: { description: "Reports all identifiers initialized from React in JSON format." }, messages: { default: "{{json}}" }, schema: [] }, name: RULE_NAME$2, create: create$2, defaultOptions: [] }); function create$2(context) { const { importSource } = getSettingsFromContext(context); function visit(node) { if (node.parent.type === AST_NODE_TYPES.ImportSpecifier && node.parent.imported === node && node.parent.imported.name === node.parent.local.name) return; const name = node.name; if (!isFromReact(node, context.sourceCode.getScope(node), importSource)) return; context.report({ data: { json: stringify({ name, importSource }) }, messageId: "default", node }); } return { Identifier: visit, JSXIdentifier: visit }; } //#endregion //#region src/rules/is-from-ref/lib.ts function getRefInitNode(context, node, initialScope) { const name = node.name; switch (true) { case node.parent.type === AST_NODE_TYPES.MemberExpression && node.parent.property === node && node.parent.object.type === AST_NODE_TYPES.Identifier: return getRefInit(context, node.parent.object.name, initialScope); case node.parent.type === AST_NODE_TYPES.JSXMemberExpression && node.parent.property === node && node.parent.object.type === AST_NODE_TYPES.JSXIdentifier: return getRefInit(context, node.parent.object.name, initialScope); default: return getRefInit(context, name, initialScope); } } /** * Get the init expression of a ref variable * @param context The rule context * @param name The variable name * @param initialScope The initial scope * @returns The init expression node if the variable is derived from a ref, or null otherwise */ function getRefInit(context, name, initialScope) { const { additionalRefHooks } = getSettingsFromContext(context); for (const { node } of findVariable(initialScope, name)?.defs ?? []) { if (node.type !== AST_NODE_TYPES.VariableDeclarator) continue; const init = node.init; if (init == null) continue; switch (true) { case init.type === AST_NODE_TYPES.MemberExpression && init.object.type === AST_NODE_TYPES.Identifier && (init.object.name === "ref" || init.object.name.endsWith("Ref")): return init; case init.type === AST_NODE_TYPES.CallExpression && isUseRefLikeCall(init, additionalRefHooks): return init; } } return null; } //#endregion //#region src/rules/is-from-ref/is-from-ref.ts const RULE_NAME$1 = "is-from-ref"; var is_from_ref_default = createRule({ meta: { type: "problem", docs: { description: "Reports all identifiers initialized or derived from refs in JSON format." }, messages: { default: "{{json}}" }, schema: [] }, name: RULE_NAME$1, create: create$1, defaultOptions: [] }); function create$1(context) { function visit(node) { const refInit = getRefInitNode(context, node, context.sourceCode.getScope(node)); if (refInit != null) { const json = stringify({ name: node.name, init: context.sourceCode.getText(refInit) }); context.report({ data: { json }, messageId: "default", node }); } } return { Identifier: visit, JSXIdentifier: visit }; } //#endregion //#region src/rules/jsx/jsx.ts const RULE_NAME = "jsx"; var jsx_default = createRule({ meta: { type: "problem", docs: { description: "Reports all JSX elements and fragments in JSON format." }, messages: { default: "{{json}}" }, schema: [] }, name: "jsx", create, defaultOptions: [] }); function create(context) { const jsxConfig = core.getJsxConfig(context); function visit(node) { context.report({ data: { json: stringify({ kind: match(node).with({ type: AST_NODE_TYPES.JSXElement }, (n) => isFragmentElement(n, jsxConfig.jsxFragmentFactory) ? "fragment" : "element").with({ type: AST_NODE_TYPES.JSXFragment }, () => "fragment").exhaustive(), type: getElementFullType(node), jsx: match(jsxConfig.jsx).with(ts.JsxEmit.None, () => "none").with(ts.JsxEmit.ReactJSX, () => "react-jsx").with(ts.JsxEmit.ReactJSXDev, () => "react-jsx-dev").with(ts.JsxEmit.React, () => "react").with(ts.JsxEmit.ReactNative, () => "react-native").with(ts.JsxEmit.Preserve, () => "preserve").otherwise(() => "unknown"), jsxFactory: jsxConfig.jsxFactory, jsxFragmentFactory: jsxConfig.jsxFragmentFactory, jsxImportSource: jsxConfig.jsxImportSource, jsxRuntime: match(jsxConfig.jsx).with(P.union(ts.JsxEmit.None, ts.JsxEmit.ReactJSX, ts.JsxEmit.ReactJSXDev), () => "automatic").otherwise(() => "classic") }) }, messageId: "default", node }); } return { JSXElement: visit, JSXFragment: visit }; } //#endregion //#region src/plugin.ts const plugin = { meta: { name: name$1, version }, rules: { ["function-component"]: function_component_default, ["hook"]: hook_default, ["is-from-react"]: is_from_react_default, ["is-from-ref"]: is_from_ref_default, ["jsx"]: jsx_default } }; //#endregion //#region src/configs/all.ts var all_exports = /* @__PURE__ */ __exportAll({ name: () => name, plugins: () => plugins, rules: () => rules, settings: () => settings }); const name = "react-debug/all"; const rules = { "react-debug/function-component": "warn", "react-debug/hook": "warn", "react-debug/is-from-react": "warn", "react-debug/is-from-ref": "warn", "react-debug/jsx": "warn" }; const plugins = { "react-debug": plugin }; const settings = { "react-x": DEFAULT_ESLINT_REACT_SETTINGS }; //#endregion //#region src/index.ts const finalPlugin = { ...plugin, configs: { ["all"]: all_exports } }; //#endregion export { finalPlugin as default };