Prefer Namespace Import
Enforces importing React via a namespace import
Overview
This rule enforces that React is imported via a namespace import (import * as React from 'react') instead of a default import (import React from 'react'). It provides an auto-fix that converts default imports to namespace imports.
The rule respects:
- The configured
importSourcefrom React settings (e.g.,"@pika/react") - Type imports (
import type React from 'react'→import type * as React from 'react') - Quote style (single vs double)
- Semicolon usage style
Rule
Copy the following code into your project (e.g. .config/preferNamespaceImport.ts):
import type { RuleFunction } from "@eslint-react/kit";
import type { TSESTree } from "@typescript-eslint/types";
/** Enforce importing React via a namespace import. */
export function preferNamespaceImport(): RuleFunction {
return (context, { settings }) => {
const { importSource } = settings;
return {
[`ImportDeclaration[source.value="${importSource}"] ImportDefaultSpecifier`](node: TSESTree.ImportDefaultSpecifier) {
const hasOtherSpecifiers = node.parent.specifiers.length > 1;
context.report({
data: { importSource },
fix(fixer) {
const importDeclarationText = context.sourceCode.getText(node.parent);
const semi = importDeclarationText.endsWith(";") ? ";" : "";
const quote = node.parent.source.raw?.at(0) ?? "'";
const isTypeImport = node.parent.importKind === "type";
const importStringPrefix = `import${isTypeImport ? " type" : ""}`;
const importSourceQuoted = `${quote}${importSource}${quote}`;
if (!hasOtherSpecifiers) {
return fixer.replaceText(
node.parent,
`${importStringPrefix} * as ${node.local.name} from ${importSourceQuoted}${semi}`,
);
}
// remove the default specifier and prepend the namespace import specifier
const specifiers = importDeclarationText.slice(
importDeclarationText.indexOf("{"),
importDeclarationText.indexOf("}") + 1,
);
return fixer.replaceText(
node.parent,
[
`${importStringPrefix} * as ${node.local.name} from ${importSourceQuoted}${semi}`,
`${importStringPrefix} ${specifiers} from ${importSourceQuoted}${semi}`,
].join("\n"),
);
},
message: `Prefer importing React as 'import * as React from "${importSource}"' `,
node: hasOtherSpecifiers ? node : node.parent,
});
},
};
};
}Config
import eslintReactKit from "@eslint-react/kit";
import { preferNamespaceImport } from "./.config/preferNamespaceImport";
export default [
// ... other configs
{
...eslintReactKit()
.use(preferNamespaceImport)
.getConfig(),
files: ["src/**/*.{ts,tsx}"],
},
];Examples
Invalid
// Default import — will be reported
import React from 'react';
// ^^^^ Prefer importing React as 'import * as React from "react"';// Default import with other specifiers — will be reported
import React, { useState, useEffect } from 'react';
// ^^^^ Prefer importing React as 'import * as React from "react"';Valid
// Namespace import — OK
import * as React from 'react';// Named imports only — OK
import { useState, useEffect } from 'react';Resources
- AST Explorer - A tool for exploring the abstract syntax tree (AST) of JavaScript code, which is essential for writing custom rules.
- ESLint Developer Guide - Official ESLint documentation for creating custom rules.
- Using the TypeScript Compiler API - TypeScript compiler API documentation for working with type information in custom rules.