Files
react-template/.config/preferNamespaceImport.ts
T
2026-06-16 21:10:36 +02:00

60 lines
1.6 KiB
TypeScript

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(" "),
);
},
});
},
};
};
}