UNPKG

eslint-plugin-solid

Version:
1,303 lines (1,293 loc) 133 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); // src/compat.ts import { ASTUtils } from "@typescript-eslint/utils"; function getSourceCode(context) { if (typeof context.getSourceCode === "function") { return context.getSourceCode(); } return context.sourceCode; } function getScope(context, node) { const sourceCode = getSourceCode(context); if (typeof sourceCode.getScope === "function") { return sourceCode.getScope(node); } if (typeof context.getScope === "function") { return context.getScope(); } return context.sourceCode.getScope(node); } function findVariable(context, node) { return ASTUtils.findVariable(getScope(context, node), node); } function markVariableAsUsed(context, name2, node) { if (typeof context.markVariableAsUsed === "function") { context.markVariableAsUsed(name2); } else { getSourceCode(context).markVariableAsUsed(name2, node); } } var init_compat = __esm({ "src/compat.ts"() { "use strict"; } }); // src/utils.ts function findParent(node, predicate) { return node.parent ? find(node.parent, predicate) : null; } function trace(node, context) { if (node.type === "Identifier") { const variable = findVariable(context, node); if (!variable) return node; const def = variable.defs[0]; switch (def?.type) { case "FunctionName": case "ClassName": case "ImportBinding": return def.node; case "Variable": if ((def.node.parent.kind === "const" || variable.references.every((ref) => ref.init || ref.isReadOnly())) && def.node.id.type === "Identifier" && def.node.init) { return trace(def.node.init, context); } } } return node; } function ignoreTransparentWrappers(node, up = false) { if (node.type === "TSAsExpression" || node.type === "TSNonNullExpression" || node.type === "TSSatisfiesExpression") { const next = up ? node.parent : node.expression; if (next) { return ignoreTransparentWrappers(next, up); } } return node; } function findInScope(node, scope, predicate) { const found = find(node, (node2) => node2 === scope || predicate(node2)); return found === scope && !predicate(node) ? null : found; } function appendImports(fixer, sourceCode, importNode, identifiers) { const identifiersString = identifiers.join(", "); const reversedSpecifiers = importNode.specifiers.slice().reverse(); const lastSpecifier = reversedSpecifiers.find((s) => s.type === "ImportSpecifier"); if (lastSpecifier) { return fixer.insertTextAfter(lastSpecifier, `, ${identifiersString}`); } const otherSpecifier = importNode.specifiers.find( (s) => s.type === "ImportDefaultSpecifier" || s.type === "ImportNamespaceSpecifier" ); if (otherSpecifier) { return fixer.insertTextAfter(otherSpecifier, `, { ${identifiersString} }`); } if (importNode.specifiers.length === 0) { const [importToken, maybeBrace] = sourceCode.getFirstTokens(importNode, { count: 2 }); if (maybeBrace?.value === "{") { return fixer.insertTextAfter(maybeBrace, ` ${identifiersString} `); } else { return importToken ? fixer.insertTextAfter(importToken, ` { ${identifiersString} } from`) : null; } } return null; } function insertImports(fixer, sourceCode, source, identifiers, aboveImport, isType = false) { const identifiersString = identifiers.join(", "); const programNode = sourceCode.ast; const firstImport = aboveImport ?? programNode.body.find((n) => n.type === "ImportDeclaration"); if (firstImport) { return fixer.insertTextBeforeRange( (getCommentBefore(firstImport, sourceCode) ?? firstImport).range, `import ${isType ? "type " : ""}{ ${identifiersString} } from "${source}"; ` ); } return fixer.insertTextBeforeRange( [0, 0], `import ${isType ? "type " : ""}{ ${identifiersString} } from "${source}"; ` ); } function removeSpecifier(fixer, sourceCode, specifier, pure = true) { const declaration = specifier.parent; if (declaration.specifiers.length === 1 && pure) { return fixer.remove(declaration); } const maybeComma = sourceCode.getTokenAfter(specifier); if (maybeComma?.value === ",") { return fixer.removeRange([specifier.range[0], maybeComma.range[1]]); } return fixer.remove(specifier); } function jsxPropName(prop) { if (prop.name.type === "JSXNamespacedName") { return `${prop.name.namespace.name}:${prop.name.name.name}`; } return prop.name.name; } function* jsxGetAllProps(props) { for (const attr of props) { if (attr.type === "JSXSpreadAttribute" && attr.argument.type === "ObjectExpression") { for (const property of attr.argument.properties) { if (property.type === "Property") { if (property.key.type === "Identifier") { yield [property.key.name, property.key]; } else if (property.key.type === "Literal") { yield [String(property.key.value), property.key]; } } } } else if (attr.type === "JSXAttribute") { yield [jsxPropName(attr), attr.name]; } } } function jsxHasProp(props, prop) { for (const [p] of jsxGetAllProps(props)) { if (p === prop) return true; } return false; } function jsxGetProp(props, prop) { return props.find( (attribute) => attribute.type !== "JSXSpreadAttribute" && prop === jsxPropName(attribute) ); } var domElementRegex, isDOMElementName, propsRegex, isPropsByName, formatList, find, FUNCTION_TYPES, isFunctionNode, PROGRAM_OR_FUNCTION_TYPES, isProgramOrFunctionNode, isJSXElementOrFragment, getFunctionName, getCommentBefore, trackImports; var init_utils = __esm({ "src/utils.ts"() { "use strict"; init_compat(); domElementRegex = /^[a-z]/; isDOMElementName = (name2) => domElementRegex.test(name2); propsRegex = /[pP]rops/; isPropsByName = (name2) => propsRegex.test(name2); formatList = (strings) => { if (strings.length === 0) { return ""; } else if (strings.length === 1) { return `'${strings[0]}'`; } else if (strings.length === 2) { return `'${strings[0]}' and '${strings[1]}'`; } else { const last = strings.length - 1; return `${strings.slice(0, last).map((s) => `'${s}'`).join(", ")}, and '${strings[last]}'`; } }; find = (node, predicate) => { let n = node; while (n) { const result = predicate(n); if (result) { return n; } n = n.parent; } return null; }; FUNCTION_TYPES = ["FunctionExpression", "ArrowFunctionExpression", "FunctionDeclaration"]; isFunctionNode = (node) => !!node && FUNCTION_TYPES.includes(node.type); PROGRAM_OR_FUNCTION_TYPES = ["Program"].concat(FUNCTION_TYPES); isProgramOrFunctionNode = (node) => !!node && PROGRAM_OR_FUNCTION_TYPES.includes(node.type); isJSXElementOrFragment = (node) => node?.type === "JSXElement" || node?.type === "JSXFragment"; getFunctionName = (node) => { if ((node.type === "FunctionDeclaration" || node.type === "FunctionExpression") && node.id != null) { return node.id.name; } if (node.parent?.type === "VariableDeclarator" && node.parent.id.type === "Identifier") { return node.parent.id.name; } return null; }; getCommentBefore = (node, sourceCode) => sourceCode.getCommentsBefore(node).find((comment) => comment.loc.end.line >= node.loc.start.line - 1); trackImports = (fromModule = /^solid-js(?:\/?|\b)/) => { const importMap = /* @__PURE__ */ new Map(); const handleImportDeclaration = (node) => { if (fromModule.test(node.source.value)) { for (const specifier of node.specifiers) { if (specifier.type === "ImportSpecifier") { importMap.set(specifier.imported.name, specifier.local.name); } } } }; const matchImport = (imports, str) => { const importArr = Array.isArray(imports) ? imports : [imports]; return importArr.find((i) => importMap.get(i) === str); }; return { matchImport, handleImportDeclaration }; }; } }); // src/rules/components-return-once.ts import { ESLintUtils } from "@typescript-eslint/utils"; var createRule, isNothing, getLineLength, components_return_once_default; var init_components_return_once = __esm({ "src/rules/components-return-once.ts"() { "use strict"; init_utils(); init_compat(); createRule = ESLintUtils.RuleCreator.withoutDocs; isNothing = (node) => { if (!node) { return true; } switch (node.type) { case "Literal": return [null, void 0, false, ""].includes(node.value); case "JSXFragment": return !node.children || node.children.every(isNothing); default: return false; } }; getLineLength = (loc) => loc.end.line - loc.start.line + 1; components_return_once_default = createRule({ meta: { type: "problem", docs: { description: "Disallow early returns in components. Solid components only run once, and so conditionals should be inside JSX.", url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/components-return-once.md" }, fixable: "code", schema: [], messages: { noEarlyReturn: "Solid components run once, so an early return breaks reactivity. Move the condition inside a JSX element, such as a fragment or <Show />.", noConditionalReturn: "Solid components run once, so a conditional return breaks reactivity. Move the condition inside a JSX element, such as a fragment or <Show />." } }, defaultOptions: [], create(context) { const functionStack = []; const putIntoJSX = (node) => { const text = getSourceCode(context).getText(node); return node.type === "JSXElement" || node.type === "JSXFragment" ? text : `{${text}}`; }; const currentFunction = () => functionStack[functionStack.length - 1]; const onFunctionEnter = (node) => { let lastReturn; if (node.body.type === "BlockStatement") { const last = node.body.body.findLast((node2) => !node2.type.endsWith("Declaration")); if (last && last.type === "ReturnStatement") { lastReturn = last; } } functionStack.push({ isComponent: false, lastReturn, earlyReturns: [] }); }; const onFunctionExit = (node) => { if ( // "render props" aren't components getFunctionName(node)?.match(/^[a-z]/) || node.parent?.type === "JSXExpressionContainer" || // ignore createMemo(() => conditional JSX), report HOC(() => conditional JSX) node.parent?.type === "CallExpression" && node.parent.arguments.some((n) => n === node) && !node.parent.callee.name?.match(/^[A-Z]/) ) { currentFunction().isComponent = false; } if (currentFunction().isComponent) { currentFunction().earlyReturns.forEach((earlyReturn) => { context.report({ node: earlyReturn, messageId: "noEarlyReturn" }); }); const argument = currentFunction().lastReturn?.argument; if (argument?.type === "ConditionalExpression") { const sourceCode = getSourceCode(context); context.report({ node: argument.parent, messageId: "noConditionalReturn", fix: (fixer) => { const { test, consequent, alternate } = argument; const conditions = [{ test, consequent }]; let fallback = alternate; while (fallback.type === "ConditionalExpression") { conditions.push({ test: fallback.test, consequent: fallback.consequent }); fallback = fallback.alternate; } if (conditions.length >= 2) { const fallbackStr = !isNothing(fallback) ? ` fallback={${sourceCode.getText(fallback)}}` : ""; return fixer.replaceText( argument, `<Switch${fallbackStr}> ${conditions.map( ({ test: test2, consequent: consequent2 }) => `<Match when={${sourceCode.getText(test2)}}>${putIntoJSX( consequent2 )}</Match>` ).join("\n")} </Switch>` ); } if (isNothing(consequent)) { return fixer.replaceText( argument, `<Show when={!(${sourceCode.getText(test)})}>${putIntoJSX(alternate)}</Show>` ); } if (isNothing(fallback) || getLineLength(consequent.loc) >= getLineLength(alternate.loc) * 1.5) { const fallbackStr = !isNothing(fallback) ? ` fallback={${sourceCode.getText(fallback)}}` : ""; return fixer.replaceText( argument, `<Show when={${sourceCode.getText(test)}}${fallbackStr}>${putIntoJSX( consequent )}</Show>` ); } return fixer.replaceText(argument, `<>${putIntoJSX(argument)}</>`); } }); } else if (argument?.type === "LogicalExpression") { if (argument.operator === "&&") { const sourceCode = getSourceCode(context); context.report({ node: argument, messageId: "noConditionalReturn", fix: (fixer) => { const { left: test, right: consequent } = argument; return fixer.replaceText( argument, `<Show when={${sourceCode.getText(test)}}>${putIntoJSX(consequent)}</Show>` ); } }); } else { context.report({ node: argument, messageId: "noConditionalReturn" }); } } } functionStack.pop(); }; return { FunctionDeclaration: onFunctionEnter, FunctionExpression: onFunctionEnter, ArrowFunctionExpression: onFunctionEnter, "FunctionDeclaration:exit": onFunctionExit, "FunctionExpression:exit": onFunctionExit, "ArrowFunctionExpression:exit": onFunctionExit, JSXElement() { if (functionStack.length) { currentFunction().isComponent = true; } }, JSXFragment() { if (functionStack.length) { currentFunction().isComponent = true; } }, ReturnStatement(node) { if (functionStack.length && node !== currentFunction().lastReturn) { currentFunction().earlyReturns.push(node); } } }; } }); } }); // src/rules/event-handlers.ts import { ESLintUtils as ESLintUtils2, ASTUtils as ASTUtils2 } from "@typescript-eslint/utils"; var createRule2, getStaticValue, COMMON_EVENTS, COMMON_EVENTS_MAP, NONSTANDARD_EVENTS_MAP, isCommonHandlerName, getCommonEventHandlerName, isNonstandardEventName, getStandardEventHandlerName, event_handlers_default; var init_event_handlers = __esm({ "src/rules/event-handlers.ts"() { "use strict"; init_utils(); init_compat(); createRule2 = ESLintUtils2.RuleCreator.withoutDocs; ({ getStaticValue } = ASTUtils2); COMMON_EVENTS = [ "onAnimationEnd", "onAnimationIteration", "onAnimationStart", "onBeforeInput", "onBlur", "onChange", "onClick", "onContextMenu", "onCopy", "onCut", "onDblClick", "onDrag", "onDragEnd", "onDragEnter", "onDragExit", "onDragLeave", "onDragOver", "onDragStart", "onDrop", "onError", "onFocus", "onFocusIn", "onFocusOut", "onGotPointerCapture", "onInput", "onInvalid", "onKeyDown", "onKeyPress", "onKeyUp", "onLoad", "onLostPointerCapture", "onMouseDown", "onMouseEnter", "onMouseLeave", "onMouseMove", "onMouseOut", "onMouseOver", "onMouseUp", "onPaste", "onPointerCancel", "onPointerDown", "onPointerEnter", "onPointerLeave", "onPointerMove", "onPointerOut", "onPointerOver", "onPointerUp", "onReset", "onScroll", "onSelect", "onSubmit", "onToggle", "onTouchCancel", "onTouchEnd", "onTouchMove", "onTouchStart", "onTransitionEnd", "onWheel" ]; COMMON_EVENTS_MAP = new Map( function* () { for (const event of COMMON_EVENTS) { yield [event.toLowerCase(), event]; } }() ); NONSTANDARD_EVENTS_MAP = { ondoubleclick: "onDblClick" }; isCommonHandlerName = (lowercaseHandlerName) => COMMON_EVENTS_MAP.has(lowercaseHandlerName); getCommonEventHandlerName = (lowercaseHandlerName) => COMMON_EVENTS_MAP.get(lowercaseHandlerName); isNonstandardEventName = (lowercaseEventName) => Boolean(NONSTANDARD_EVENTS_MAP[lowercaseEventName]); getStandardEventHandlerName = (lowercaseEventName) => NONSTANDARD_EVENTS_MAP[lowercaseEventName]; event_handlers_default = createRule2({ meta: { type: "problem", docs: { description: "Enforce naming DOM element event handlers consistently and prevent Solid's analysis from misunderstanding whether a prop should be an event handler.", url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/event-handlers.md" }, fixable: "code", hasSuggestions: true, schema: [ { type: "object", properties: { ignoreCase: { type: "boolean", description: "if true, don't warn on ambiguously named event handlers like `onclick` or `onchange`", default: false }, warnOnSpread: { type: "boolean", description: "if true, warn when spreading event handlers onto JSX. Enable for Solid < v1.6.", default: false } }, additionalProperties: false } ], messages: { "detected-attr": 'The {{name}} prop is named as an event handler (starts with "on"), but Solid knows its value ({{staticValue}}) is a string or number, so it will be treated as an attribute. If this is intentional, name this prop attr:{{name}}.', naming: "The {{name}} prop is ambiguous. If it is an event handler, change it to {{handlerName}}. If it is an attribute, change it to {{attrName}}.", capitalization: "The {{name}} prop should be renamed to {{fixedName}} for readability.", nonstandard: "The {{name}} prop should be renamed to {{fixedName}}, because it's not a standard event handler.", "make-handler": "Change the {{name}} prop to {{handlerName}}.", "make-attr": "Change the {{name}} prop to {{attrName}}.", "spread-handler": "The {{name}} prop should be added as a JSX attribute, not spread in. Solid doesn't add listeners when spreading into JSX." } }, defaultOptions: [], create(context) { const sourceCode = getSourceCode(context); return { JSXAttribute(node) { const openingElement = node.parent; if (openingElement.name.type !== "JSXIdentifier" || !isDOMElementName(openingElement.name.name)) { return; } if (node.name.type === "JSXNamespacedName") { return; } const { name: name2 } = node.name; if (!/^on[a-zA-Z]/.test(name2)) { return; } let staticValue = null; if (node.value?.type === "JSXExpressionContainer" && node.value.expression.type !== "JSXEmptyExpression" && node.value.expression.type !== "ArrayExpression" && // array syntax prevents inlining (staticValue = getStaticValue(node.value.expression, getScope(context, node))) !== null && (typeof staticValue.value === "string" || typeof staticValue.value === "number")) { context.report({ node, messageId: "detected-attr", data: { name: name2, staticValue: staticValue.value } }); } else if (node.value === null || node.value?.type === "Literal") { context.report({ node, messageId: "detected-attr", data: { name: name2, staticValue: node.value !== null ? node.value.value : true } }); } else if (!context.options[0]?.ignoreCase) { const lowercaseHandlerName = name2.toLowerCase(); if (isNonstandardEventName(lowercaseHandlerName)) { const fixedName = getStandardEventHandlerName(lowercaseHandlerName); context.report({ node: node.name, messageId: "nonstandard", data: { name: name2, fixedName }, fix: (fixer) => fixer.replaceText(node.name, fixedName) }); } else if (isCommonHandlerName(lowercaseHandlerName)) { const fixedName = getCommonEventHandlerName(lowercaseHandlerName); if (fixedName !== name2) { context.report({ node: node.name, messageId: "capitalization", data: { name: name2, fixedName }, fix: (fixer) => fixer.replaceText(node.name, fixedName) }); } } else if (name2[2] === name2[2].toLowerCase()) { const handlerName = `on${name2[2].toUpperCase()}${name2.slice(3)}`; const attrName = `attr:${name2}`; context.report({ node: node.name, messageId: "naming", data: { name: name2, attrName, handlerName }, suggest: [ { messageId: "make-handler", data: { name: name2, handlerName }, fix: (fixer) => fixer.replaceText(node.name, handlerName) }, { messageId: "make-attr", data: { name: name2, attrName }, fix: (fixer) => fixer.replaceText(node.name, attrName) } ] }); } } }, Property(node) { if (context.options[0]?.warnOnSpread && node.parent?.type === "ObjectExpression" && node.parent.parent?.type === "JSXSpreadAttribute" && node.parent.parent.parent?.type === "JSXOpeningElement") { const openingElement = node.parent.parent.parent; if (openingElement.name.type === "JSXIdentifier" && isDOMElementName(openingElement.name.name)) { if (node.key.type === "Identifier" && /^on/.test(node.key.name)) { const handlerName = node.key.name; context.report({ node, messageId: "spread-handler", data: { name: node.key.name }, *fix(fixer) { const commaAfter = sourceCode.getTokenAfter(node); yield fixer.remove( node.parent.properties.length === 1 ? node.parent.parent : node ); if (commaAfter?.value === ",") { yield fixer.remove(commaAfter); } yield fixer.insertTextAfter( node.parent.parent, ` ${handlerName}={${sourceCode.getText(node.value)}}` ); } }); } } } } }; } }); } }); // src/rules/imports.ts import { ESLintUtils as ESLintUtils3 } from "@typescript-eslint/utils"; var createRule3, primitiveMap, typeMap, sourceRegex, isSource, imports_default; var init_imports = __esm({ "src/rules/imports.ts"() { "use strict"; init_utils(); init_compat(); createRule3 = ESLintUtils3.RuleCreator.withoutDocs; primitiveMap = /* @__PURE__ */ new Map(); for (const primitive of [ "createSignal", "createEffect", "createMemo", "createResource", "onMount", "onCleanup", "onError", "untrack", "batch", "on", "createRoot", "getOwner", "runWithOwner", "mergeProps", "splitProps", "useTransition", "observable", "from", "mapArray", "indexArray", "createContext", "useContext", "children", "lazy", "createUniqueId", "createDeferred", "createRenderEffect", "createComputed", "createReaction", "createSelector", "DEV", "For", "Show", "Switch", "Match", "Index", "ErrorBoundary", "Suspense", "SuspenseList" ]) { primitiveMap.set(primitive, "solid-js"); } for (const primitive of [ "Portal", "render", "hydrate", "renderToString", "renderToStream", "isServer", "renderToStringAsync", "generateHydrationScript", "HydrationScript", "Dynamic" ]) { primitiveMap.set(primitive, "solid-js/web"); } for (const primitive of [ "createStore", "produce", "reconcile", "unwrap", "createMutable", "modifyMutable" ]) { primitiveMap.set(primitive, "solid-js/store"); } typeMap = /* @__PURE__ */ new Map(); for (const type of [ "Signal", "Accessor", "Setter", "Resource", "ResourceActions", "ResourceOptions", "ResourceReturn", "ResourceFetcher", "InitializedResourceReturn", "Component", "VoidProps", "VoidComponent", "ParentProps", "ParentComponent", "FlowProps", "FlowComponent", "ValidComponent", "ComponentProps", "Ref", "MergeProps", "SplitPrips", "Context", "JSX", "ResolvedChildren", "MatchProps" ]) { typeMap.set(type, "solid-js"); } for (const type of [ /* "JSX", */ "MountableElement" ]) { typeMap.set(type, "solid-js/web"); } for (const type of ["StoreNode", "Store", "SetStoreFunction"]) { typeMap.set(type, "solid-js/store"); } sourceRegex = /^solid-js(?:\/web|\/store)?$/; isSource = (source) => sourceRegex.test(source); imports_default = createRule3({ meta: { type: "suggestion", docs: { description: 'Enforce consistent imports from "solid-js", "solid-js/web", and "solid-js/store".', url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/imports.md" }, fixable: "code", schema: [], messages: { "prefer-source": 'Prefer importing {{name}} from "{{source}}".' } }, defaultOptions: [], create(context) { return { ImportDeclaration(node) { const source = node.source.value; if (!isSource(source)) return; for (const specifier of node.specifiers) { if (specifier.type === "ImportSpecifier") { const isType = specifier.importKind === "type" || node.importKind === "type"; const map = isType ? typeMap : primitiveMap; const correctSource = map.get(specifier.imported.name); if (correctSource != null && correctSource !== source) { context.report({ node: specifier, messageId: "prefer-source", data: { name: specifier.imported.name, source: correctSource }, fix(fixer) { const sourceCode = getSourceCode(context); const program = sourceCode.ast; const correctDeclaration = program.body.find( (node2) => node2.type === "ImportDeclaration" && node2.source.value === correctSource ); if (correctDeclaration) { return [ removeSpecifier(fixer, sourceCode, specifier), appendImports(fixer, sourceCode, correctDeclaration, [ sourceCode.getText(specifier) ]) ].filter(Boolean); } const firstSolidDeclaration = program.body.find( (node2) => node2.type === "ImportDeclaration" && isSource(node2.source.value) ); return [ removeSpecifier(fixer, sourceCode, specifier), insertImports( fixer, sourceCode, correctSource, [sourceCode.getText(specifier)], firstSolidDeclaration, isType ) ]; } }); } } } } }; } }); } }); // src/rules/jsx-no-duplicate-props.ts import { ESLintUtils as ESLintUtils4 } from "@typescript-eslint/utils"; var createRule4, jsx_no_duplicate_props_default; var init_jsx_no_duplicate_props = __esm({ "src/rules/jsx-no-duplicate-props.ts"() { "use strict"; init_utils(); createRule4 = ESLintUtils4.RuleCreator.withoutDocs; jsx_no_duplicate_props_default = createRule4({ meta: { type: "problem", docs: { description: "Disallow passing the same prop twice in JSX.", url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/jsx-no-duplicate-props.md" }, schema: [ { type: "object", properties: { ignoreCase: { type: "boolean", description: "Consider two prop names differing only by case to be the same.", default: false } } } ], messages: { noDuplicateProps: "Duplicate props are not allowed.", noDuplicateClass: "Duplicate `class` props are not allowed; while it might seem to work, it can break unexpectedly. Use `classList` instead.", noDuplicateChildren: "Using {{used}} at the same time is not allowed." } }, defaultOptions: [], create(context) { return { JSXOpeningElement(node) { const ignoreCase = context.options[0]?.ignoreCase ?? false; const props = /* @__PURE__ */ new Set(); const checkPropName = (name2, node2) => { if (ignoreCase || name2.startsWith("on")) { name2 = name2.toLowerCase().replace(/^on(?:capture)?:/, "on").replace(/^(?:attr|prop):/, ""); } if (props.has(name2)) { context.report({ node: node2, messageId: name2 === "class" ? "noDuplicateClass" : "noDuplicateProps" }); } props.add(name2); }; for (const [name2, propNode] of jsxGetAllProps(node.attributes)) { checkPropName(name2, propNode); } const hasChildrenProp = props.has("children"); const hasChildren = node.parent.children.length > 0; const hasInnerHTML = props.has("innerHTML") || props.has("innerhtml"); const hasTextContent = props.has("textContent") || props.has("textContent"); const used = [ hasChildrenProp && "`props.children`", hasChildren && "JSX children", hasInnerHTML && "`props.innerHTML`", hasTextContent && "`props.textContent`" ].filter(Boolean); if (used.length > 1) { context.report({ node, messageId: "noDuplicateChildren", data: { used: used.join(", ") } }); } } }; } }); } }); // src/rules/jsx-no-script-url.ts import { ESLintUtils as ESLintUtils5, ASTUtils as ASTUtils3 } from "@typescript-eslint/utils"; var createRule5, getStaticValue2, isJavaScriptProtocol, jsx_no_script_url_default; var init_jsx_no_script_url = __esm({ "src/rules/jsx-no-script-url.ts"() { "use strict"; init_compat(); createRule5 = ESLintUtils5.RuleCreator.withoutDocs; ({ getStaticValue: getStaticValue2 } = ASTUtils3); isJavaScriptProtocol = /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i; jsx_no_script_url_default = createRule5({ meta: { type: "problem", docs: { description: "Disallow javascript: URLs.", url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/jsx-no-script-url.md" }, schema: [], messages: { noJSURL: "For security, don't use javascript: URLs. Use event handlers instead if you can." } }, defaultOptions: [], create(context) { return { JSXAttribute(node) { if (node.name.type === "JSXIdentifier" && node.value) { const link = getStaticValue2( node.value.type === "JSXExpressionContainer" ? node.value.expression : node.value, getScope(context, node) ); if (link && typeof link.value === "string" && isJavaScriptProtocol.test(link.value)) { context.report({ node: node.value, messageId: "noJSURL" }); } } } }; } }); } }); // src/rules/jsx-no-undef.ts import { ESLintUtils as ESLintUtils6 } from "@typescript-eslint/utils"; var createRule6, AUTO_COMPONENTS, SOURCE_MODULE, jsx_no_undef_default; var init_jsx_no_undef = __esm({ "src/rules/jsx-no-undef.ts"() { "use strict"; init_utils(); init_compat(); createRule6 = ESLintUtils6.RuleCreator.withoutDocs; AUTO_COMPONENTS = ["Show", "For", "Index", "Switch", "Match"]; SOURCE_MODULE = "solid-js"; jsx_no_undef_default = createRule6({ meta: { type: "problem", docs: { description: "Disallow references to undefined variables in JSX. Handles custom directives.", url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/jsx-no-undef.md" }, fixable: "code", schema: [ { type: "object", properties: { allowGlobals: { type: "boolean", description: "When true, the rule will consider the global scope when checking for defined components.", default: false }, autoImport: { type: "boolean", description: 'Automatically import certain components from `"solid-js"` if they are undefined.', default: true }, typescriptEnabled: { type: "boolean", description: "Adjusts behavior not to conflict with TypeScript's type checking.", default: false } }, additionalProperties: false } ], messages: { undefined: "'{{identifier}}' is not defined.", customDirectiveUndefined: "Custom directive '{{identifier}}' is not defined.", autoImport: "{{imports}} should be imported from '{{source}}'." } }, defaultOptions: [], create(context) { const allowGlobals = context.options[0]?.allowGlobals ?? false; const autoImport = context.options[0]?.autoImport !== false; const isTypeScriptEnabled = context.options[0]?.typescriptEnabled ?? false; const missingComponentsSet = /* @__PURE__ */ new Set(); function checkIdentifierInJSX(node, { isComponent: isComponent2, isCustomDirective } = {}) { let scope = getScope(context, node); const sourceCode = getSourceCode(context); const sourceType = sourceCode.ast.sourceType; const scopeUpperBound = !allowGlobals && sourceType === "module" ? "module" : "global"; const variables = [...scope.variables]; if (node.name === "this") { return; } while (scope.type !== scopeUpperBound && scope.type !== "global" && scope.upper) { scope = scope.upper; variables.push(...scope.variables); } if (scope.childScopes.length) { variables.push(...scope.childScopes[0].variables); if (scope.childScopes[0].childScopes.length) { variables.push(...scope.childScopes[0].childScopes[0].variables); } } if (variables.find((variable) => variable.name === node.name)) { return; } if (isComponent2 && autoImport && AUTO_COMPONENTS.includes(node.name) && !missingComponentsSet.has(node.name)) { missingComponentsSet.add(node.name); } else if (isCustomDirective) { context.report({ node, messageId: "customDirectiveUndefined", data: { identifier: node.name } }); } else if (!isTypeScriptEnabled) { context.report({ node, messageId: "undefined", data: { identifier: node.name } }); } } return { JSXOpeningElement(node) { let n; switch (node.name.type) { case "JSXIdentifier": if (!isDOMElementName(node.name.name)) { checkIdentifierInJSX(node.name, { isComponent: true }); } break; case "JSXMemberExpression": n = node.name; do { n = n.object; } while (n && n.type !== "JSXIdentifier"); if (n) { checkIdentifierInJSX(n); } break; default: break; } }, "JSXAttribute > JSXNamespacedName": (node) => { if (node.namespace?.type === "JSXIdentifier" && node.namespace.name === "use" && node.name?.type === "JSXIdentifier") { checkIdentifierInJSX(node.name, { isCustomDirective: true }); } }, "Program:exit": (programNode) => { const missingComponents = Array.from(missingComponentsSet.values()); if (autoImport && missingComponents.length) { const importNode = programNode.body.find( (n) => n.type === "ImportDeclaration" && n.importKind !== "type" && n.source.type === "Literal" && n.source.value === SOURCE_MODULE ); if (importNode) { context.report({ node: importNode, messageId: "autoImport", data: { imports: formatList(missingComponents), // "Show, For, and Switch" source: SOURCE_MODULE }, fix: (fixer) => { return appendImports(fixer, getSourceCode(context), importNode, missingComponents); } }); } else { context.report({ node: programNode, messageId: "autoImport", data: { imports: formatList(missingComponents), source: SOURCE_MODULE }, fix: (fixer) => { return insertImports(fixer, getSourceCode(context), "solid-js", missingComponents); } }); } } } }; } }); } }); // src/rules/jsx-uses-vars.ts import { ESLintUtils as ESLintUtils7 } from "@typescript-eslint/utils"; var createRule7, jsx_uses_vars_default; var init_jsx_uses_vars = __esm({ "src/rules/jsx-uses-vars.ts"() { "use strict"; init_compat(); createRule7 = ESLintUtils7.RuleCreator.withoutDocs; jsx_uses_vars_default = createRule7({ meta: { type: "problem", docs: { // eslint-disable-next-line eslint-plugin/require-meta-docs-description description: "Prevent variables used in JSX from being marked as unused.", url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/jsx-uses-vars.md" }, schema: [], // eslint-disable-next-line eslint-plugin/prefer-message-ids messages: {} }, defaultOptions: [], create(context) { return { JSXOpeningElement(node) { let parent; switch (node.name.type) { case "JSXNamespacedName": return; case "JSXIdentifier": markVariableAsUsed(context, node.name.name, node.name); break; case "JSXMemberExpression": parent = node.name.object; while (parent?.type === "JSXMemberExpression") { parent = parent.object; } if (parent.type === "JSXIdentifier") { markVariableAsUsed(context, parent.name, parent); } break; } }, "JSXAttribute > JSXNamespacedName": (node) => { if (node.namespace?.type === "JSXIdentifier" && node.namespace.name === "use" && node.name?.type === "JSXIdentifier") { markVariableAsUsed(context, node.name.name, node.name); } } }; } }); } }); // src/rules/no-destructure.ts import { ESLintUtils as ESLintUtils8, ASTUtils as ASTUtils4 } from "@typescript-eslint/utils"; var createRule8, getStringIfConstant, getName, getPropertyInfo, no_destructure_default; var init_no_destructure = __esm({ "src/rules/no-destructure.ts"() { "use strict"; init_compat(); createRule8 = ESLintUtils8.RuleCreator.withoutDocs; ({ getStringIfConstant } = ASTUtils4); getName = (node) => { switch (node.type) { case "Literal": return typeof node.value === "string" ? node.value : null; case "Identifier": return node.name; case "AssignmentPattern": return getName(node.left); default: return getStringIfConstant(node); } }; getPropertyInfo = (prop) => { const valueName = getName(prop.value); if (valueName !== null) { return { real: prop.key, var: valueName, computed: prop.computed, init: prop.value.type === "AssignmentPattern" ? prop.value.right : void 0 }; } else { return null; } }; no_destructure_default = createRule8({ meta: { type: "problem", docs: { description: "Disallow destructuring props. In Solid, props must be used with property accesses (`props.foo`) to preserve reactivity. This rule only tracks destructuring in the parameter list.", url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/no-destructure.md" }, fixable: "code", schema: [], messages: { noDestructure: "Destructuring component props breaks Solid's reactivity; use property access instead." // noWriteToProps: "Component props are readonly, writing to props is not supported.", } }, defaultOptions: [], create(context) { const functionStack = []; const currentFunction = () => functionStack[functionStack.length - 1]; const onFunctionEnter = () => { functionStack.push({ hasJSX: false }); }; const onFunctionExit = (node) => { if (node.params.length === 1) { const props = node.params[0]; if (props.type === "ObjectPattern" && currentFunction().hasJSX && node.parent?.type !== "JSXExpressionContainer") { context.report({ node: props, messageId: "noDestructure", fix: (fixer) => fixDestructure(node, props, fixer) }); } } functionStack.pop(); }; function* fixDestructure(func, props, fixer) { const propsName = "props"; const properties = props.properties; const propertyInfo = []; let rest = null; for (const property of properties) { if (property.type === "RestElement") { rest = property; } else { const info = getPropertyInfo(property); if (info === null) { continue; } propertyInfo.push(info); } } const hasDefaults = propertyInfo.some((info) => info.init); const origProps = !(hasDefaults || rest) ? propsName : "_" + propsName; if (props.typeAnnotation) { const range = [props.range[0], props.typeAnnotation.range[0]]; yield fixer.replaceTextRange(range, origProps); } else { yield fixer.replaceText(props, origProps); } const sourceCode = getSourceCode(context); const defaultsObjectString = () => propertyInfo.filter((info) => info.init).map( (info) => `${info.computed ? "[" : ""}${sourceCode.getText(info.real)}${info.computed ? "]" : ""}: ${sourceCode.getText(info.init)}` ).join(", "); const splitPropsArray = () => `[${propertyInfo.map( (info) => info.real.type === "Identifier" ? JSON.stringify(info.real.name) : sourceCode.getText(info.real) ).join(", ")}]`; let lineToInsert = ""; if (hasDefaults && rest) { lineToInsert = ` const [${propsName}, ${rest.argument.type === "Identifier" && rest.argument.name || "rest"}] = splitProps(mergeProps({ ${defaultsObjectString()} }, ${origProps}), ${splitPropsArray()});`; } else if (hasDefaults) { lineToInsert = ` const ${propsName} = mergeProps({ ${defaultsObjectString()} }, ${origProps}); `; } else if (rest) { lineToInsert = ` const [${propsName}, ${rest.argument.type === "Identifier" && rest.argument.name || "rest"}] = splitProps(${origProps}, ${splitPropsArray()}); `; } if (lineToInsert) { const body = func.body; if (body.type === "BlockStatement") { if (body.body.length > 0) { yield fixer.insertTextBefore(body.body[0], lineToInsert); } } el