npm package: @roflans/eslint-plugin-fsd
Description: Feature-Sliced Design guardrails for enforcing public API and layer boundaries.
Author: Roflans
Latest version: 1.0.2

# @roflans/eslint-plugin-fsd

[![npm version](https://img.shields.io/npm/v/%40roflans%2Feslint-plugin-fsd.svg?label=@roflans/eslint-plugin-fsd)](https://www.npmjs.com/package/@roflans/eslint-plugin-fsd)
[![license](https://img.shields.io/npm/l/%40roflans%2Feslint-plugin-fsd.svg)](LICENSE)
[![node](https://img.shields.io/badge/node-%3E%3D18.18-3c873a.svg)](https://nodejs.org)

Production-grade ESLint rules that keep Feature-Sliced Design (FSD) architectures under control:

- **Public API imports only** – consuming layers can’t reach into slice internals
- **Layer dependency order** – `app → widgets → features → entities → shared`
- Built for monorepos: caching, memoized FS lookups, TypeScript `paths` awareness, auto-fixes, and actionable suggestions

---

## Table of Contents

- [Installation](#installation)
- [Usage](#usage)
  - [Flat Config (ESLint 9+)](#flat-config-eslint9)
  - [Legacy config (.eslintrc)](#legacy-config-eslintrc)
- [Rule Reference](#rule-reference)
  - [`fsd/public-api-imports`](#fsdpublic-api-imports)
  - [`fsd/layer-imports`](#fsdlayer-imports)
  - [Shared Options](#shared-options)
- [Advanced Topics](#advanced-topics)
  - [TypeScript Awareness](#typescript-awareness)
  - [Monorepos & Multiple Roots](#monorepos--multiple-roots)
- [Building & Publishing](#building--publishing)
- [Contributing](#contributing)

---

## Installation

```bash
npm install --save-dev @roflans/eslint-plugin-fsd
# or
pnpm add -D @roflans/eslint-plugin-fsd
# or
yarn add -D @roflans/eslint-plugin-fsd
```

> Requirements: **Node.js ≥ 18.18** and **ESLint ≥ 9**. The package is ESM-only and ships both JS + d.ts output.

---

## Usage

### Flat Config (ESLint 9+) <a name="flat-config-eslint9"></a>

```ts
// eslint.config.mjs
import { defineConfig } from 'eslint/config';
import fsd from '@roflans/eslint-plugin-fsd';

export default defineConfig([
  fsd.configs.recommended, // enables both rules
  {
    rules: {
      'fsd/public-api-imports': [
        'error',
        {
          publicApiFiles: ['index.ts', 'public.ts'],
          allowInternalImports: ['@/shared/ui/**'],
        },
      ],
    },
  },
]);
```

### Legacy config (.eslintrc) <a name="legacy-config-eslintrc"></a>

```json
{
  "plugins": ["@roflans/eslint-plugin-fsd"],
  "extends": ["plugin:@roflans/eslint-plugin-fsd/recommended"],
  "rules": {
    "fsd/layer-imports": [
      "error",
      { "srcRoots": ["src", "packages/web/src", "packages/ui/src"] }
    ]
  }
}
```

---

## Rule Reference

| Rule ID                  | Purpose                                                                                  | Fixable |
| ------------------------ | ---------------------------------------------------------------------------------------- | ------- |
| `fsd/public-api-imports` | Blocks deep imports (e.g. `@/entities/user/ui/card`). Suggests the closest public entry. | Yes     |
| `fsd/layer-imports`      | Ensures layers only depend on lower layers. Suggests valid alternatives when possible.   | Yes     |

### `fsd/public-api-imports`

- Detects imports that bypass the slice’s public API (`index.ts`, `public.ts`, etc.).
- Auto-fix rewrites the path to the discovered public entry (prefers `index.ts`).
- Additional suggestion text highlights which public file exports the desired symbol.
- `allowInternalImports` lets you whitelist globs (e.g. shared design-system internals).

### `fsd/layer-imports`

- Compares importer/target layers and blocks “upward” imports (`entities` → `widgets`, etc.).
- Auto-fix mirrors the same slice path in the nearest allowed layer, when it exists on disk.
- Error messages list the exact layers your current layer *is* allowed to target.

### Shared Options

Both rules accept the same configuration object (all fields optional):

| Option                 | Type                    | Default                                                     | Description |
| ---------------------- | ----------------------- | ----------------------------------------------------------- | ----------- |
| `aliasPrefix`          | `string`                | `'@'`                                                       | Root alias prefix used inside imports. |
| `layers`               | `string[]`              | `['shared','entities','features','widgets','pages','app']`  | Custom order from lowest → highest layer. |
| `publicApiFiles`       | `string[]`              | `['index.ts','index.tsx','public.ts','public.tsx']`         | File names treated as public entry points. |
| `ignorePatterns`       | `string[]`              | `['**/*.test.*','**/*.spec.*','**/*.stories.*','**/*.story.*']` | File globs that bypass both rules entirely. |
| `allowInternalImports` | `string[]`              | `[]`                                                        | Import globs that skip public API enforcement. |
| `layerAliases`         | `Record<string,string>` | `{}`                                                        | Maps aliases (e.g. `@shared`) directly to a layer. |
| `srcRoots`             | `string[]`              | `['src','app']`                                             | Root folders that contain layered slices. |

**Performance niceties**

- Per-file layer detection, FS existence checks, and glob lookups are memoized.
- TypeScript path information is cached once per ESLint run.
- Both rules share the same service instances to avoid duplicated work.

---

## Advanced Topics

### TypeScript Awareness

If ESLint runs with `parserOptions.project`, the plugin consumes the underlying TypeScript Program to:

- Respect `baseUrl` and `paths` aliases automatically.
- Resolve package-specific `tsconfig.json` files (useful for monorepos).
- Produce more accurate auto-fix targets because absolute paths are known.

No special configuration is required—just point ESLint at the relevant `tsconfig`.

### Monorepos & Multiple Roots

Use `srcRoots` to enumerate every package that follows FSD layering:

```js
{
  rules: {
    'fsd/layer-imports': [
      'error',
      { srcRoots: ['apps/web/src', 'packages/ui/src', 'packages/features/src'] },
    ],
  },
}
```

You can combine absolute and relative paths; everything is resolved against `process.cwd()`.