UNPKG

eslint-plugin-solid

Version:
201 lines (190 loc) 5.6 kB
import { TSESTree as T, TSESLint, ESLintUtils } from "@typescript-eslint/utils"; import { appendImports, insertImports, removeSpecifier } from "../utils"; import { getSourceCode } from "../compat"; const createRule = ESLintUtils.RuleCreator.withoutDocs; // Below: create maps of imports and types to designated import source. // We could mess with `Object.keys(require("solid-js"))` to generate this, but requiring it from // node activates the "node" export condition, which doesn't necessarily match what users will // receive i.e. through bundlers. Instead, we're manually listing all of the public exports that // should be imported from "solid-js", etc. // ============== type Source = "solid-js" | "solid-js/web" | "solid-js/store"; // Set up map of imports to module const primitiveMap = new Map<string, Source>(); 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"); } // Set up map of type imports to module const typeMap = new Map<string, Source>(); 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"); } const sourceRegex = /^solid-js(?:\/web|\/store)?$/; const isSource = (source: string): source is Source => sourceRegex.test(source); export default createRule({ 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: T.Program = sourceCode.ast; const correctDeclaration = program.body.find( (node) => node.type === "ImportDeclaration" && node.source.value === correctSource ) as T.ImportDeclaration | undefined; if (correctDeclaration) { return [ removeSpecifier(fixer, sourceCode, specifier), appendImports(fixer, sourceCode, correctDeclaration, [ sourceCode.getText(specifier), ]), ].filter(Boolean) as Array<TSESLint.RuleFix>; } const firstSolidDeclaration = program.body.find( (node) => node.type === "ImportDeclaration" && isSource(node.source.value) ) as T.ImportDeclaration | undefined; return [ removeSpecifier(fixer, sourceCode, specifier), insertImports( fixer, sourceCode, correctSource, [sourceCode.getText(specifier)], firstSolidDeclaration, isType ), ]; }, }); } } } }, }; }, });