eslint-plugin-ai-rules
v0.2.1
Published
ESLint plugin com regras úteis para projetos com ESLint - AI Rules
Maintainers
Readme
eslint-plugin-ai-rules
Flat-config only ESLint plugin that bundles useful rules for AI-generated code. Ships a recommended preset with all rules enabled as error — zero config for consumers.
ESLint 9+ only. Uses flat config (
eslint.config.{ts,js}) andeslint/configdefineConfig. No legacy.eslintrcsupport.
Features
- AI Rules rules — catches typical AI drift: wrong filename casing, unsorted/multiline imports
- Bundled preset —
aiRules.configs.recommendedenables custom +import-x,@stylistic,typescript-eslintrules out of the box - Autofix —
sort-importsandno-multiline-importsarefixable: code(eslint --fix) - Dual build —
dist/index.js(ESM) +dist/index.cjs(CJS) viatsup, single-file bundle,type: module
Requirements
- Node
>=18(package.jsonengines) - ESLint
^9.0.0(peer) — flat config only - TypeScript
>=5.0.0(peer)
@eslint/js, eslint-plugin-import-x, @stylistic/eslint-plugin, typescript-eslint, globals are already dependencies — you don't need to install them.
Installation
# npm
npm i -D eslint-plugin-ai-rules eslint typescript
# yarn
yarn add -D eslint-plugin-ai-rules eslint typescript
# pnpm
pnpm add -D eslint-plugin-ai-rules eslint typescript
# bun
bun add -d eslint-plugin-ai-rules eslint typescriptQuick Start
eslint.config.ts (recommended) — you define files
recommended is file-agnostic (only plugins + settings + rules) and does not configure a parser. Pair it with typescript-eslint (or @typescript-eslint/parser) and define files:
import aiRules from 'eslint-plugin-ai-rules'
import tseslint from 'typescript-eslint'
export default [
...tseslint.configs.recommended,
{ files: ['**/*.{ts,tsx}'], ...aiRules.configs.recommended[0] },
]Alternative with defineConfig (ESLint >= 9.22):
import { defineConfig } from 'eslint/config'
import aiRules from 'eslint-plugin-ai-rules'
import tseslint from 'typescript-eslint'
export default defineConfig([
...tseslint.configs.recommended,
{ files: ['**/*.{ts,tsx}'], extends: aiRules.configs.recommended },
])eslint.config.js / eslint.config.mjs
import aiRules from 'eslint-plugin-ai-rules'
import tseslint from 'typescript-eslint'
export default [
...tseslint.configs.recommended,
{ files: ['**/*.{js,ts,tsx}'], ...aiRules.configs.recommended[0] },
]With overrides
import aiRules from 'eslint-plugin-ai-rules'
import { defineConfig } from 'eslint/config'
export default defineConfig([
{ files: ['**/*.{ts,tsx}'], ...aiRules.configs.recommended[0] },
{
// turn off filename check for tests or generated code
files: ['**/*.test.{ts,tsx}', '**/*.spec.{ts,tsx}', 'scripts/**/*'],
rules: {
'ai-rules/filename-pascal-case': 'off',
},
},
{
// or disable a bundled rule
rules: {
'@stylistic/semi': 'off',
},
},
])Run:
npx eslint .
npx eslint --fix . # autofix for sort-imports / no-multiline-importsConfiguration
recommended preset
src/configs/recommended.ts exports defineConfig([...]) with only plugins + settings + rules (file-agnostic) — you define files/ignores/languageOptions:
plugins: { '@stylistic': stylistic, 'import-x': importX, 'ai-rules': plugin }settings: { 'import-x/resolver-next': ... }withextensions: ['.ts','.tsx','.d.ts','.js','.jsx']- Rules (all
errorunless noted):
| Rule | Default | Notes |
|------|---------|-------|
| ai-rules/filename-pascal-case | error | File-agnostic: checks every file ESLint lints. See below |
| ai-rules/sort-imports | error | Groups + length desc, collapses multiline |
| ai-rules/no-multiline-imports | error | Single-line imports |
| import-x/no-duplicates | error | |
| import-x/no-default-export | off | |
| max-len | off | |
| @stylistic/semi | ['error','never'] | No semicolons |
| @typescript-eslint/consistent-type-imports | ['error',{prefer:'type-imports'}] | |
| @typescript-eslint/no-unused-vars | ['error',{argsIgnorePattern:'^_',varsIgnorePattern:'^_'}] | |
| @typescript-eslint/naming-convention | ['error', {interface:'PascalCase' prefix I}, {typeAlias:'PascalCase' prefix T}] | Disable per-project for **/*.d.ts via your own override if needed |
recommended has no files/ignores — add them in your eslint.config.* (see Quick Start). This repo's eslint.config.mjs adds globalIgnores, files: ['**/*.{ts,tsx}'] with extends: [js.configs.recommended, tseslint.configs.recommended] + globals.browser locally.
Custom setup (without recommended)
import { plugin } from 'eslint-plugin-ai-rules'
import { defineConfig } from 'eslint/config'
export default defineConfig([
{
files: ['**/*.{ts,tsx}'],
plugins: { 'ai-rules': plugin },
rules: {
'ai-rules/sort-imports': 'error',
'ai-rules/no-multiline-imports': 'warn',
'ai-rules/filename-pascal-case': 'off',
},
},
])You can also import rules individually:
import { filenamePascalCase, sortImports, noMultilineImports } from 'eslint-plugin-ai-rules'Rules
Brief overview. See source in src/rules/ for full logic.
ai-rules/filename-pascal-case
Enforces PascalCase for file names. The rule is file-agnostic: it validates every file ESLint decides to lint, following the flat-config files/ignores defined in the consumer's config (same pattern as other filename rules).
- Type:
suggestion, no autofix,messageId: invalid - Ignores:
index.{ts,tsx,js,jsx},*.d.ts,*.css, dotfiles - Checks:
path.basenamefromgetPhysicalFilename()/context.filename,^[A-Z][a-zA-Z0-9]*$,kebab/snake -> PascalCaseconversion - Options:
ignore— array of regex strings matched against the basename and the path relative tocwd(same idea asunicorn/filename-case). Use it for tool-mandated names like*.config.*, tests/specs, generated or vendored code.ignoreMiddleExtensions—boolean, defaulttrue. Whentrue, only the base name before the first dot is validated, soValidationRanges.test.tsis valid andmy-component.test.tsis renamed toMyComponent.test.ts. Whenfalse, every dot-separated segment must be PascalCase, soValidationRanges.test.tsis renamed toValidationRanges.Test.ts(same idea asunicorn/filename-case).
// my-component.ts ❌ Rename to "MyComponent.ts"
// ValidationRanges.test.ts ✅ (ignoreMiddleExtensions: true, default)
// ValidationRanges.test.ts ❌ Rename to "ValidationRanges.Test.ts" (ignoreMiddleExtensions: false)
// MyComponent.ts ✅'ai-rules/filename-pascal-case': [
'error',
{
ignore: ['\\.config\\.', '^generated/'],
ignoreMiddleExtensions: false,
},
]To scope or disable it, use your own config (as this repo does in eslint.config.mjs):
import { defineConfig } from 'eslint/config'
import { recommended } from './src/configs/recommended'
export default defineConfig([
...recommended,
{ files: ['src/**/*'], rules: { 'ai-rules/filename-pascal-case': 'off' } },
])ai-rules/no-multiline-imports
Disallows multiline ImportDeclaration.
- Type:
layout,fixable: code,messageId: multilineImport - Fix: collapses
{,},,and whitespace to single line:import { b, a } from 'lodash'
// ❌
import {
b,
a
} from 'lodash'
// ✅ after --fix
import { b, a } from 'lodash'Note: ai-rules/sort-imports also collapses multiline; both fixers may run, but sort-imports handles the full Program sort.
ai-rules/sort-imports
Sorts imports by group and line-length descending, collapsing multiline imports.
- Type:
layout,fixable: code(Programfixer),messageId: sortRequired - Groups (in order, each separated by one blank line):
- Directives (
'use client','use server','use strict') - External (
reactfirst, then other externals sorted byline.lengthdesc →localeCompare) - Internal (
.,@/sorted same) - Re-exports (
export { ... } from,export * fromsorted same)
- Directives (
- Rest: code after imports, joined with
\n\n\n(two blank lines between imports and code) - Directives: preserved at top while
canAcceptDirectivesis true
// ❌ before --fix
import { local } from './local'
import { a } from 'lodash'
import React from 'react'
// ✅ after --fix
import React from 'react'
import { a } from 'lodash'
import { local } from './local'Supports side-effect import 'polyfill', import type, export * from, export { ... } from.
TypeScript
Plugin itself uses module: ESNext + moduleResolution: bundler (tsconfig.json) and no .js extensions in source imports (e.g., from './plugin'). Output is bundled to single dist/index.js via tsup, so consumers don't need to handle internal paths. For your project, any TS config works; the preset sets languageOptions.globals: globals.browser and import-x/resolver-next.
Troubleshooting
Missing dist before lint (repo uses eslint.config.mjs → dist)
Error: Cannot find module './dist/index.js'Fix: npm run build first. This repo's eslint.config.mjs imports from dist (no jiti), so prelint runs build automatically (package.json prelint).
File ignored because outside of base path
You passed an absolute path outside the config's basePath. Run npx eslint . from project root or use npx eslint --no-config-lookup -c ./eslint.config.js ./src/file.ts with correct cwd.
Flat config only
This plugin exports flat config arrays. It does not support legacy .eslintrc. Use eslint.config.{js,ts}.
Filename rule too strict for tests/configs
The rule checks every linted file. Test/spec files with a PascalCase base (e.g. ValidationRanges.test.ts) pass by default via ignoreMiddleExtensions: true. For tool-mandated names like *.config.*, generated or vendored code, pass the ignore option ({ ignore: ['\\.config\\.', '^generated/'] }) or disable ai-rules/filename-pascal-case for those files via an override (see Quick Start).
Development
git clone <repo>
cd eslint-ai-guard
npm install
npm run typecheck # tsc --noEmit
npm test # node:test + dist build
npm run lint # eslint .
npm run lint -- --fix
npm run build # tsup -> dist/index.js + dist/index.cjs + dist/index.d.ts
npm run dev # tsup --watch
npm run verify # typecheck + test + lint + pack
npm pack --dry-run # verify files: dist onlyProject structure
eslint-ai-guard/ # package: eslint-plugin-ai-rules
├── src/
│ ├── index.ts # re-exports plugin + recommended
│ ├── plugin.ts # plugin.meta + rules
│ ├── configs/
│ │ ├── recommended.ts # flat-config preset (bundled rules)
│ │ └── index.ts
│ └── rules/
│ ├── FilenamePascalCase.ts
│ ├── NoMultilineImports.ts
│ ├── SortImports.ts # also exports getSortedContent
│ └── index.ts
├── test/ # node:test suites (run against dist)
├── eslint/ # deprecated original implementations
├── eslint.config.mjs # dogfooding: ...aiRules.configs.recommended from dist
├── tsconfig.json # ESNext/bundler, no .js extensions
├── tsup.config.ts # entry src/index.ts, format cjs+esm, dts, splitting:false
├── package.json # type module, exports ., files [dist]
└── dist/ # build output (gitignored)Adding a new rule
- Create
src/rules/MyRule.ts(Rule.RuleModule,meta.messages,fixableif needed) - Export it from
src/rules/index.ts - Register in
src/plugin.ts(rules: { 'my-rule': myRule }) - Enable in
src/configs/recommended.ts('ai-rules/my-rule': 'error') - Add a
RuleTestercase intest/(add a fixture if needed) npm run verify
Publishing
npm version patch|minor|major
npm publish --access public # prepublishOnly runs verify (typecheck+test+lint+pack)
# or manually: npm run verify && npm publish --access publicLegacy
eslint/ keeps the three original rule files before packaging (deprecated). Source of truth is now src/rules/.
License
MIT
