Skip to content

Repository files navigation

@poupe/eslint-config

Sharable ESLint configuration preset for Poupe UI projects with TypeScript, Vue.js, and Tailwind CSS support.

✅ Includes:

Getting started

Note

This preset uses the new ESLint flat config format and supports ESLint v9 or v10 on Node.js 20.19+, 22.13+, or 24+.

ESLint parses your TypeScript with the compiler the preset brings, currently TypeScript 6 — not the one your project builds with, which is your own choice. See TypeScript for the tested versions.

eslint and @eslint/js are peer dependencies — the preset does not bundle them, so you must install them yourself alongside it:

pnpm install -D eslint @eslint/js @poupe/eslint-config

Create eslint.config.mjs in your project root:

// @ts-check
import poupeConfig from '@poupe/eslint-config';

export default poupeConfig;

For Nuxt.js applications (with @nuxt/eslint):

// @ts-check
import { withPoupe } from '@poupe/eslint-config';
import withNuxt from './.nuxt/eslint.config.mjs';

export default withPoupe(withNuxt(), {
  rules: {
    // custom rule overrides
  },
});

For Nuxt modules (with @nuxt/eslint-config):

// @ts-check
import { createConfigForNuxt } from '@nuxt/eslint-config/flat';
import { withPoupe } from '@poupe/eslint-config';

export default withPoupe(createConfigForNuxt({
  features: {
    tooling: true,    // Enables rules for module authors
    stylistic: true,  // Enables formatting rules
  },
}));

Custom configuration:

// @ts-check
import { defineConfig } from '@poupe/eslint-config';

export default defineConfig({
  rules: {
    // rule overrides
  },
});

TypeScript

The preset installs its own TypeScript, >=6.0.3 <6.1.0, and ESLint parses your sources through it. The version your project declares is the one your build and your editor use; it does not change what ESLint parses with. Nuxt is the exception: @nuxt/eslint and @nuxt/eslint-config bring their own @typescript-eslint/parser, which resolves TypeScript from your project rather than from this preset.

Your TypeScript ESLint Tested
5.9 9 Yes
6.0 10 Yes, including Nuxt applications and modules
7.0 10 Yes, with pnpm, from 0.10.2

Versions below 5.9 and combinations not listed are untested.

Caveats with TypeScript 7:

  • Use 0.10.2 or later. Earlier releases took TypeScript as a peer dependency, so the package manager handed them your TypeScript 7, and ESLint stops while loading the configuration with "typescript-eslint does not support TS 7.0".
  • Syntax is read through the TypeScript 6 API. Anything TypeScript 7 accepts that TypeScript 6 does not will not parse.
  • Nuxt is untested on TypeScript 7. Its parser would receive your TypeScript 7, not the preset's TypeScript 6.
  • npm and Yarn are untested. pnpm gives each package its own dependencies, so the preset's TypeScript 6 cannot be confused with yours. npm and Yarn hoist packages into a shared tree, and whether typescript-eslint then finds TypeScript 6 or 7 has not been checked.

CSS Support

This configuration includes advanced CSS linting with Tailwind CSS v4 syntax support. The CSS configuration uses an intelligent filtering system that:

  • Applies CSS-specific rules from @eslint/css
  • Supports Tailwind CSS v4 syntax including theme functions and modifiers
  • Automatically disables JavaScript-specific rules for CSS files
  • Keeps relevant cross-language rules (like filename-case from unicorn)

The filtering system categorizes plugins and rules to ensure only appropriate rules apply to CSS files. See AGENTS.md for implementation details.

Supported File Types

This configuration automatically lints the following file types:

  • JavaScript: .js, .mjs, .cjs
  • TypeScript: .ts, .tsx
  • Vue.js: .vue (Single File Components)
  • JSON/JSONC: .json, .jsonc, package.json
  • Markdown: .md
  • CSS: .css, .postcss, Vue <style> blocks (with Tailwind CSS v4 syntax support)

Features

Self-Dogfooding

This package uses its own ESLint configuration for validation, ensuring quality and serving as a real-world test case.

Automatic Import/Export Sorting

This configuration includes eslint-plugin-perfectionist which automatically organizes your imports and exports. The sorting happens automatically when you run eslint --fix or save files with ESLint auto-fix enabled in your editor:

// Before
import { z } from 'zod';
import React from 'react';
import { useState, useEffect } from 'react';
import { Button } from './components/Button';
import axios from 'axios';
import type { User } from '../types';
import { config } from './config';

// After (automatically fixed by ESLint)
import type { User } from '../types';

import axios from 'axios';
import React from 'react';
import { z } from 'zod';

import { config } from './config';
import { Button } from './components/Button';
import { useEffect, useState } from 'react';

Import Grouping with Empty Lines

The configuration respects empty lines as block separators, allowing you to organize imports into logical groups:

// Core React imports
import React from 'react';
import { useState, useEffect } from 'react';

// External libraries
import axios from 'axios';
import { z } from 'zod';

// Internal modules
import { config } from './config';
import { Button } from './components/Button';

Each group separated by empty lines is sorted independently, preserving your intended organization while maintaining consistent ordering within each group.

Union and Intersection Type Sorting

Union and intersection types are sorted in natural order. Use comments to create logical groups:

// Before
type Status = 'error' | 'loading' | 'success' | 'idle';

// After (automatically fixed by ESLint)
type Status = 'error' | 'idle' | 'loading' | 'success';

// With comment-based grouping
type Status =
  // Active states
  | 'loading'
  | 'processing'
  // Inactive states
  | 'error'
  | 'idle'
  | 'success';

Additional Sorting Rules

The configuration also sorts these constructs in natural alphabetical order, all auto-fixable via eslint --fix:

  • Interfaces and object types — sorted by property name, with blank-line grouping support
  • Enums — sorted by member name (numeric enums sorted by value)
  • Classes — sorted by member type and visibility (static first, then instance, public before private), with blank-line grouping
  • Heritage clauses — extends and implements lists
  • Variable declarations — destructured bindings
  • Import/export attributes — import ... with { ... } keys

Advanced Configuration

Custom Rule Overrides

// @ts-check
import { defineConfig } from '@poupe/eslint-config';

export default defineConfig({
  rules: {
    // Disable specific rules
    'unicorn/filename-case': 'off',

    // Customize rule severity and options
    '@typescript-eslint/no-unused-vars': ['warn', {
      argsIgnorePattern: '^_',
      varsIgnorePattern: '^_',
    }],
  },
});

Selective Configuration Import

// @ts-check
import { configs, withConfig } from '@poupe/eslint-config';

// Import only specific configurations
export default withConfig(
  configs.javascript,
  configs.typescript,
  configs.stylistic,
  configs.vue,
  // Add custom overrides
  {
    rules: {
      'vue/multi-word-component-names': 'off',
    },
  },
);

Combining Configs from Multiple Sources

When composing configs from different packages, use withPoupe to layer Poupe configs on top of an upstream config. It handles async resolution and deduplicates plugin instances automatically:

// @ts-check
import { withPoupe } from '@poupe/eslint-config';
import someOtherConfig from 'some-other-eslint-config';

export default withPoupe(someOtherConfig);

For lower-level control, reconcilePlugins deduplicates plugin instances in a flat config array using a first-wins strategy:

// @ts-check
import { defineConfig, reconcilePlugins } from '@poupe/eslint-config';
import someOtherConfig from 'some-other-eslint-config';

export default reconcilePlugins([
  ...someOtherConfig,
  ...defineConfig(),
]);

Poupe UI Recommended Rules

In addition to the rules from included plugins, this configuration adds these custom rules:

TypeScript Rules

  • Enforces explicit return types on exported functions
  • Requires consistent type imports (import type)
  • Disables @ts- comment directives

Vue.js Rules

  • Enforces multi-word component names
  • Requires defineEmits and defineProps type declarations
  • Ensures proper v-model usage patterns

General JavaScript Rules

  • Enforces camelCase naming convention
  • Allows i, j, k as loop counters and array indices
  • Requires explicit radix in parseInt
  • Prevents console statements in production code

Stylistic Rules

  • Single quotes for strings
  • Semicolons required
  • 1tbs (one true brace style)
  • 2-space indentation for web files
  • 80-character line limit for Markdown (tables exempt)

Migration Guide

From Legacy ESLint Config

If you're migrating from a legacy .eslintrc configuration:

  1. Remove old config files: Delete .eslintrc, .eslintrc.js, .eslintrc.json, etc.

  2. Update dependencies:

    pnpm remove eslint-config-* eslint-plugin-*
    pnpm install -D eslint @eslint/js @poupe/eslint-config
  3. Create new config: Add eslint.config.mjs as shown in Getting Started

  4. Update scripts: ESLint automatically finds eslint.config.mjs

    {
      "scripts": {
        "lint": "eslint --fix .",
        "lint:check": "eslint ."
      }
    }
  5. Handle breaking changes:

    • Some rules have been renamed or moved to different plugins
    • The flat config uses different property names (files instead of overrides)
    • Plugin names are now used as object keys instead of strings

Examples

The examples/ directory contains working examples demonstrating how to use this ESLint configuration in different scenarios:

  • playground-standard - Basic JavaScript/TypeScript projects (inherits the workspace ESLint, currently v10; pins TypeScript 6, the major the preset declares for its own parsing)
  • playground-eslint9 - Basic JavaScript/TypeScript projects (pinned to ESLint 9, the lower bound of the peer range, and to TypeScript 5.9)
  • playground-ts7 - Basic JavaScript/TypeScript projects (TypeScript 7 as the only compiler)
  • playground-nuxt - Nuxt.js applications
  • playground-nuxt-module - Nuxt module development

Running Examples

This project uses pnpm workspaces. You can run commands on all examples:

# Install dependencies for all workspaces
pnpm install

# Check all examples without fixing
pnpm -r --filter "./examples/*" lint:check

# Lint and fix all examples
pnpm -r --filter "./examples/*" lint

# Run a specific example
pnpm --filter "playground-standard" lint

Troubleshooting

Common Issues

"Cannot find module '@poupe/eslint-config'"

Ensure you've installed the package and are using the correct import path:

pnpm install -D @poupe/eslint-config

"Plugin conflict" errors with Nuxt

For Nuxt applications, use withPoupe from the root entry point:

  • Nuxt apps: withPoupe(withNuxt())
  • Nuxt modules: withPoupe(createConfigForNuxt(...))

CSS rules applying to JavaScript files

This should not happen with the default configuration. If it does, ensure you're using the latest version and report an issue.

"Unknown rule" warnings

These warnings help the CSS filtering system learn about new rules. They're informational and don't affect functionality.

Peer warning from eslint-plugin-tsdoc

eslint-plugin-tsdoc pins @typescript-eslint/utils to ~8.56.0, whose typescript peer range stops below 6.0.0, and the preset supplies TypeScript 6 to its own dependency chain. It is an install-time warning about a range, not a failure. To silence it, override the package in your project root:

// package.json — pnpm
{
  "pnpm": {
    "overrides": {
      "@typescript-eslint/utils": "^8.67.0"
    }
  }
}

npm and Yarn users can use the equivalent overrides / resolutions field.

Debugging

To debug ESLint configuration issues:

# Show resolved configuration for a specific file
eslint --print-config path/to/file.js

# Verbose per-file linting trace
env DEBUG=eslint:linter pnpm lint:check

# Debug config resolution
env DEBUG=eslint:eslint pnpm lint

Development

For AI assistants working with this codebase, refer to AGENTS.md for detailed guidance and conventions.

Project Structure

src/
├── configs/        # Individual ESLint rule configurations
├── core/           # Core utilities and type definitions
├── config.ts       # Main configuration builder
├── configs.ts      # Configuration presets
└── index.ts        # Main entry point

Contributing

  1. Make changes to configuration files in src/configs/
  2. Run pnpm build to compile the package
  3. Test with pnpm lint (self-linting)
  4. Test in examples: pnpm -r --filter "./examples/*" lint

License

MIT