import type { RuleFunction } from "@eslint-react/kit"; import { type TSESTree, AST_NODE_TYPES } from "@typescript-eslint/types"; /** Enforce `import * as React from "react"` only. */ export function preferNamespaceImport(): RuleFunction { return (context, { settings }) => { const { importSource } = settings; return { [`ImportDeclaration[source.value="${importSource}"]`]( node: TSESTree.ImportDeclaration, ) { const specifiers = node.specifiers; // already valid if ( specifiers.length === 1 && specifiers[0]?.type === AST_NODE_TYPES.ImportNamespaceSpecifier ) { return; } // choose React identifier: // import React from "react" -> React // import { useState } -> React // import Foo, { useState } -> Foo const localName = specifiers.find( (s) => s.type === AST_NODE_TYPES.ImportDefaultSpecifier || s.type === AST_NODE_TYPES.ImportNamespaceSpecifier, )?.local.name ?? "React"; context.report({ node, data: { importSource }, message: `Prefer importing React only as 'import * as ${localName} from "${importSource}"'`, fix(fixer) { const original = context.sourceCode.getText(node); const semi = original.endsWith(";") ? ";" : ""; const quote = node.source.raw.at(0) ?? "'"; const isTypeImport = node.importKind === "type"; return fixer.replaceText( node, [ `import${isTypeImport ? " type" : ""}`, `* as ${localName}`, `from ${quote}${importSource}${quote}${semi}`, ].join(" "), ); }, }); }, }; }; }