60 lines
1.6 KiB
TypeScript
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(" "),
|
|
);
|
|
},
|
|
});
|
|
},
|
|
};
|
|
};
|
|
}
|