@tnnquang/eslint
| Install | |
|---|---|
npm install @tnnquang/eslint |
|
| Latest Version: | 3.1.0 |
| License: | MIT |
| Last Updated: | Jul 20, 2026 |
| Links: | GitHub · npm |
@tnnquang/eslint
Opinionated ESLint plugin for modern TypeScript projects. Enforces explicit imports, function declarations, path aliases, and consistent type imports.
Supports ESLint v7–v10+ (both legacy eslintrc and flat config formats).
Philosophy
This plugin enforces patterns that improve code quality at scale:
- Explicit over implicit — Named imports make dependencies visible; function declarations are hoisted and produce better stack traces.
- Consistency over flexibility — Path aliases eliminate fragile relative paths; type imports clarify the value/type boundary.
- Performance-aware — Named imports enable tree-shaking; avoiding barrel files prevents bundler slowdowns and circular deps.
- Framework-aware — Rules adapt to your framework's conventions automatically.
Rules
| Rule | Fixable | Description |
|---|---|---|
no-arrow-components |
✅ | Enforce function declarations for top-level functions and components |
no-namespace-import |
✅ | Disallow import * as X and default-as-namespace usage |
enforce-path-alias |
✅ | Enforce path aliases instead of deep relative imports |
no-barrel-imports |
— | Disallow importing from barrel/index files within the same module |
consistent-type-imports |
✅ | Enforce consistent import type usage in TypeScript |
arrow-in-body |
✅ | Enforce arrow functions inside function/component bodies |
Installation
npm install --save-dev @tnnquang/eslint
# or
yarn add -D @tnnquang/eslint
# or
pnpm add -D @tnnquang/eslint
Usage
Flat Config (ESLint v9+ — recommended)
// eslint.config.js
import tnnquang from "@tnnquang/eslint";
export default [
tnnquang.configs["flat/recommended"],
// or framework-specific:
// tnnquang.configs["flat/react"],
// tnnquang.configs["flat/vue"],
// tnnquang.configs["flat/typescript"],
];
Available flat configs: flat/recommended, flat/strict, flat/react, flat/vue, flat/angular, flat/nestjs, flat/nextjs, flat/nuxt, flat/typescript.
Legacy Config (eslintrc)
{
"extends": ["plugin:@tnnquang/eslint/recommended"]
}
Available legacy configs: recommended, strict, react, vue, angular, nestjs, nextjs, nuxt, typescript.
Manual Configuration
// eslint.config.js (flat)
import tnnquang from "@tnnquang/eslint";
export default [
{
plugins: { "@tnnquang/eslint": tnnquang },
rules: {
"@tnnquang/eslint/no-arrow-components": "warn",
"@tnnquang/eslint/no-namespace-import": ["warn", {
allowedLibraries: ["*.module.css", "@/config/*"],
}],
"@tnnquang/eslint/enforce-path-alias": ["warn", {
minRelativeDepth: 2,
}],
"@tnnquang/eslint/arrow-in-body": "warn",
"@tnnquang/eslint/no-barrel-imports": "warn",
"@tnnquang/eslint/consistent-type-imports": ["warn", {
prefer: "type-imports",
}],
},
},
];
Rule Details
no-arrow-components
Enforces function declarations for top-level functions and React components. Provides autofix.
Options
{
allowArrowFunctions?: boolean; // default: false - allow for non-components
allowInCallExpressions?: boolean; // default: true - allow in HOCs (memo, forwardRef, styled)
allowedWrappers?: string[]; // HOC names that can wrap arrow functions
allowTypedFunctionExpressions?: boolean; // default: true - allow with type annotation
allowExportDefault?: boolean; // default: false - allow `export default () => {}`
exclude?: string[]; // regex patterns to exclude
include?: string[]; // only check matching patterns
}
Examples
// ❌ Incorrect
const MyComponent = () => <div>Hello</div>;
const fetchData = async (url) => { return fetch(url); };
// ✅ Correct
function MyComponent() { return <div>Hello</div>; }
async function fetchData(url) { return fetch(url); }
// ✅ Allowed (HOC wrappers)
const MemoComponent = memo(() => <div/>);
const StyledButton = styled.button`color: red`;
arrow-in-body
Enforces arrow functions for inner function declarations inside function/component bodies to prevent hoisting issues. Provides autofix.
Note: This rule does NOT apply to class methods — they should use standard method syntax.
Options
{
allowNamedFunctions?: boolean; // default: false - allow if used for recursion
onlyInComponents?: boolean; // default: false - only enforce in PascalCase (components)
exclude?: string[]; // regex patterns to exclude
includeObjectMethods?: boolean; // default: false - also check { key: function() {} }
checkCallbacks?: boolean; // default: false - also check callbacks like arr.map(function() {})
}
Examples
function MyComponent() {
// ❌ Incorrect - function declaration inside body (hoisted)
function handleClick() { console.log("click"); }
// ❌ Incorrect - function expression
const handleHover = function() { console.log("hover"); };
// ❌ Incorrect (with checkCallbacks: true)
const doubled = items.map(function(x) { return x * 2; });
// ✅ Correct - arrow function (not hoisted, clear scope)
const handleClick = () => { console.log("click"); };
const doubled = items.map((x) => x * 2);
return <button onClick={handleClick} />;
}
// With onlyInComponents: true, this is allowed:
function utilityFunction() {
function helper() { ... } // OK - parent is not a component
}
no-namespace-import
Disallows namespace imports (import * as X) and default imports used as namespaces (import X; X.method()). Provides autofix.
Default behavior: Only checks a curated list of ~100 popular libraries (React, Vue, Angular, NestJS, lodash-es, AWS SDK v3, etc.) where named imports enable better tree-shaking.
Default Target Libraries
The rule automatically checks these library categories:
- React ecosystem: react, react-dom, react-router, redux, zustand, jotai, recoil
- Next.js: next, next/router, next/navigation, next/link, next/image
- Vue ecosystem: vue, vue-router, pinia, vuex
- Angular: @angular/core, @angular/common, @angular/router, @angular/forms
- NestJS: @nestjs/common, @nestjs/core, @nestjs/config, @nestjs/jwt, @nestjs/typeorm, etc.
- Express/Fastify: express, fastify, @fastify/*
- Database/ORM: typeorm, @prisma/client, mongoose, sequelize, drizzle-orm
- Utilities: lodash-es, ramda, date-fns, rxjs, zod, yup
- UI libraries: @mui/material, antd, @chakra-ui/react, @radix-ui/*
- AWS SDK v3: @aws-sdk/client-, @aws-sdk/lib-
- Firebase: firebase/*, firebase-admin
- Testing: vitest, jest, @testing-library/*
Options
{
// Exclude libraries from checking (supports glob patterns)
allowedLibraries?: string[]; // e.g., ["*.module.css", "@/config/*", "axios"]
// Add more libraries to check (in addition to defaults)
targetLibraries?: string[]; // e.g., ["my-internal-lib"]
// Set to ['*'] to check ALL libraries
targetLibraries?: ["*"];
// Disable default target list, only use your custom targetLibraries
useDefaultTargets?: boolean; // default: true
// Specific controls for star imports vs default-as-namespace
allowStarImports?: boolean; // default: false
allowDefaultAsNamespace?: boolean; // default: false
allowedStarImports?: string[]; // e.g., ["@sentry/*"]
allowedDefaultAsNamespace?: string[];
// TypeScript options
allowTypeNamespaces?: boolean; // default: false
}
Examples
// ❌ Incorrect (react is in default targets)
import React from "react";
React.useState(0);
import * as Sentry from "@sentry/browser"; // unless in allowedStarImports
// ✅ Correct
import { useState } from "react";
// With config: allowedStarImports: ["@sentry/*"]
import * as Sentry from "@sentry/browser"; // now allowed
Configuration Examples
// Check only default libraries, exclude some
"@tnnquang/eslint/no-namespace-import": ["warn", {
allowedLibraries: ["axios", "mongoose", "*.module.css"],
}]
// Check ALL libraries (like v2 behavior)
"@tnnquang/eslint/no-namespace-import": ["warn", {
targetLibraries: ["*"],
}]
// Disable all checks
"@tnnquang/eslint/no-namespace-import": ["warn", {
useDefaultTargets: false,
}]
// Only check custom libraries
"@tnnquang/eslint/no-namespace-import": ["warn", {
useDefaultTargets: false,
targetLibraries: ["my-internal-lib", "company-utils"],
}]
// Add to defaults + allow some star imports
"@tnnquang/eslint/no-namespace-import": ["warn", {
targetLibraries: ["my-internal-lib"],
allowedStarImports: ["@sentry/*", "protobufjs"],
}]
enforce-path-alias
Enforces path aliases for imports instead of deep relative paths. Auto-detects from tsconfig.json, vite.config.*. Provides autofix.
Options
{
mode?: "all" | "direct-children"; // default: "direct-children"
minRelativeDepth?: number; // default: 1 - minimum ../ count to trigger
configFile?: string; // default: "tsconfig.json"
paths?: Record<string, string[]>; // manual aliases (overrides auto-detect)
baseUrl?: string; // auto-detected if omitted
exclude?: string[]; // folder patterns to exclude
excludeFiles?: string[]; // file patterns to skip
allowRelativeInSameFolder?: boolean; // default: true
includeDynamicImports?: boolean; // default: true
}
Examples
// tsconfig.json: { "paths": { "@/*": ["./src/*"] } }
// ❌ Incorrect (in src/components/Button.tsx)
import { validate } from "../../utils/validation";
// ✅ Correct
import { validate } from "@/utils/validation";
// ✅ Allowed (same folder)
import { ButtonProps } from "./Button.types";
no-barrel-imports
Disallows importing from barrel/index files within the same module to prevent circular dependencies. Not auto-fixable (target file is ambiguous).
Options
{
scope?: "same-module" | "same-parent" | "all-relative"; // default: "same-parent"
barrelFiles?: string[]; // default: ["index"]
allowedPatterns?: string[]; // patterns to allow
excludeFolders?: string[]; // default: ["node_modules"]
}
consistent-type-imports
Enforces consistent import type usage in TypeScript. Only active for .ts/.tsx files. Provides autofix.
Options
{
prefer?: "type-imports" | "inline-type-imports" | "no-type-imports"; // default: "type-imports"
allowedLibraries?: string[]; // libraries to exclude
}
Examples
// prefer: "type-imports" (default)
// ❌ Incorrect
import { type User, type Post } from "./types";
// ✅ Correct
import type { User, Post } from "./types";
Configuration Recipes
React + TypeScript (strict)
import tnnquang from "@tnnquang/eslint";
export default [
tnnquang.configs["flat/strict"],
{
rules: {
"@tnnquang/eslint/no-namespace-import": ["error", {
allowedStarImports: ["@testing-library/*"],
allowedLibraries: ["*.module.css", "*.module.scss"],
}],
"@tnnquang/eslint/no-arrow-components": ["error", {
exclude: ["^use[A-Z]"], // allow hooks as arrow functions
}],
},
},
];
Vue/Nuxt
import tnnquang from "@tnnquang/eslint";
export default [
tnnquang.configs["flat/vue"],
{
rules: {
"@tnnquang/eslint/enforce-path-alias": ["warn", {
exclude: ["composables", "stores"],
}],
},
},
];
NestJS Backend
import tnnquang from "@tnnquang/eslint";
export default [
tnnquang.configs["flat/nestjs"],
{
rules: {
"@tnnquang/eslint/no-namespace-import": ["error", {
allowedLibraries: ["class-validator", "class-transformer"],
}],
"@tnnquang/eslint/consistent-type-imports": ["warn", {
prefer: "inline-type-imports",
}],
},
},
];
Migration from v2
Breaking Changes in v3
no-namespace-importnow only checks default target libraries — previously checked ALL imports- Add
targetLibraries: ["*"]to restore v2 behavior
- Add
- New rules added:
no-barrel-imports,consistent-type-imports,arrow-in-body no-arrow-componentsnow has autofix- Node.js 18+ required
- Flat config support — use
flat/*config names for ESLint v9+
Migration Steps
// If you want v2 behavior (check ALL libraries):
"@tnnquang/eslint/no-namespace-import": ["error", {
+ targetLibraries: ["*"],
}]
// If you have libraries that need namespace imports:
"@tnnquang/eslint/no-namespace-import": ["error", {
+ allowedStarImports: ["@sentry/*", "protobufjs"],
+ allowedLibraries: ["*.module.css"],
}]
License
MIT
Author
Tran Ngoc Nhat Quang
Contributing
Issues and pull requests are welcome at github.com/tnnquang/eslint-plugin.
Changelog
v3.1.0
no-namespace-importnow has smart defaults — only checks ~100 popular libraries by default instead of all imports- Added
useDefaultTargetsoption to control default library list - Added
targetLibraries: ["*"]to check all libraries (v2 behavior) - Expanded default targets to include backend libraries (NestJS, Express, Fastify, TypeORM, Prisma, AWS SDK v3, Firebase)
- Use
allowedLibrariesto exclude specific libraries from checking
Improvements:
enforce-path-alias: Windows path separator fix, Vite array-style alias support, recursive nested extends in tsconfig with cycle detection, addedExportNamedDeclarationandExportAllDeclarationsupportno-barrel-imports: Added TTL cache (30s), addedExportNamedDeclarationandExportAllDeclarationsupportconsistent-type-imports: Handleimport React, { type FC }(default + inline type mixed), addedmixedDefaultAndTypemessageno-namespace-import: Handle template literal property access, destructuring from namespace, JSX spread attributesarrow-in-body: Handle unary IIFE patterns (!function(){}(),void function(){}())
v3.0.0
- New rule:
arrow-in-body— enforce arrow functions inside function bodies - New rule:
no-barrel-imports— prevent circular dependencies from barrel files - New rule:
consistent-type-imports— enforceimport typein TypeScript no-arrow-componentsnow has autofixno-namespace-importcoversimport * as— add exceptions withallowedStarImports- Flat config support — use
flat/*config names for ESLint v9+ - ESLint v10 compatible — uses new context API
- Node.js 18+ required
Related Packages
Shared ESLint, TypeScript, Prettier, Vitest and Commitlint configurations for No...
Modern ESLint config with flat config support for Vue, React, Svelte, TypeScript...
Shared ESLint (flat config) and Prettier configs for TypeScript, React, Vue and...
Shared ESLint flat config for Vasek's Vue/Nuxt/TS projects. Wraps @antfu/eslint-...
Version History
| Version | Released | License |
|---|---|---|
| 3.1.0 | MIT | |
| 3.0.1 | MIT | |
| 3.0.0 | MIT | |
| 2.0.4 | MIT | |
| 2.0.3 | MIT | |
| 2.0.2 | MIT | |
| 2.0.1 | MIT | |
| 2.0.0 | MIT | |
| 1.0.1 | MIT | |
| 0.1.1 | MIT |