npm package: @libar-dev/eslint-plugin-directives
Description: ESLint plugin for architectural directives — self-documenting, structured alternatives to eslint-disable comments
Author: Libar Dev
Latest version: 0.2.0

# @libar-dev/eslint-plugin-directives

ESLint plugin for **architectural directives** — self-documenting, structured alternatives to `eslint-disable` comments.

## What are Architectural Directives?

Instead of suppressing lint errors with `// eslint-disable-next-line`, architectural directives document **why** a deviation exists:

```typescript
// Bad: opaque suppression
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Wrapper<T extends any> = { value: T };

// Good: self-documenting directive
// @architectural-directive: library-generic-constraint
// Reason: Generic constraint requires any for library-level type abstraction
type Wrapper<T extends any> = { value: T };
```

Directives serve as both documentation and ESLint suppressions — no `eslint-disable` needed.

## Installation

```bash
npm install --save-dev @libar-dev/eslint-plugin-directives
```

**Peer dependencies:** `eslint ^8.0.0 || ^9.0.0`, `@typescript-eslint/parser ^8.18.0`

## Usage

### ESLint Flat Config

```javascript
import directivesPlugin from '@libar-dev/eslint-plugin-directives';

export default [
  {
    plugins: { directives: directivesPlugin },
    rules: {
      'directives/no-explicit-any': 'error',
      'directives/no-eslint-disable': 'error',
      'directives/no-unused-type-imports': 'warn',
    },
  },
];
```

### Using the Recommended Config

```javascript
import directivesPlugin from '@libar-dev/eslint-plugin-directives';

export default [
  {
    plugins: { directives: directivesPlugin },
    rules: {
      ...directivesPlugin.configs.recommended.rules,
    },
  },
];
```

The recommended config enables `no-explicit-any` and `no-eslint-disable` at `"error"` severity.

## Rules

### `directives/no-eslint-disable`

Prevents `// eslint-disable` comments. Requires architectural directives instead.

**Built-in allowed directives:** `adapter-contract`, `library-generic-constraint`, `spec-driven-stub`

```typescript
// Error: eslint-disable without directive
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const x: any = value;

// OK: architectural directive present
// @architectural-directive: adapter-contract
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const x: any = externalApi.getValue();
```

### `directives/no-explicit-any`

Prevents explicit `any` types in TypeScript annotations, `as any` casts, `z.any()`, and `v.any()`.

**Built-in allowed directives:** `library-generic-constraint`, `test-any-type-handling`, `intersection-fallback`, `adapter-contract`, `spec-driven-stub`

For `v.any()`: `schema-compatibility`
For `z.any()`: `test-any-type-handling`, `schema-compatibility`

```typescript
// Error
const x: any = 5;
const y = value as any;

// OK: directive present
// @architectural-directive: library-generic-constraint
type Wrapper<T extends any> = { value: T };
```

Automatically skips test files (`.test.ts`, `.spec.ts`) and generated files (`.d.ts`, `_generated/`).

### `directives/no-unused-type-imports`

Detects unused `import type` declarations.

**Built-in allowed directives:** `spec-driven-stub`

```typescript
// Error: Foo is never used
import type { Foo } from './types.js';

// OK: directive documents intentional stub
// @architectural-directive: spec-driven-stub
// Reason: Types defined per feature spec, used when implementation completes
import type { FutureEvent } from './types.js';
```

## Custom Directives

All rules accept an `additionalDirectives` option for project-specific directives:

```javascript
rules: {
  'directives/no-explicit-any': ['error', {
    additionalDirectives: ['convex-compatibility', 'legacy-api-workaround'],
  }],
  'directives/no-eslint-disable': ['error', {
    additionalDirectives: ['environment-access-safe'],
  }],
}
```

Then in your code:

```typescript
// @architectural-directive: convex-compatibility
// Reason: Convex type system requires any for this field
const schema = v.any();
```

## Built-in Directive Types

| Directive | Category | Used By |
|-----------|----------|---------|
| `library-generic-constraint` | type-level | `no-explicit-any`, `no-eslint-disable` |
| `intersection-fallback` | type-level | `no-explicit-any` |
| `test-any-type-handling` | testing | `no-explicit-any` |
| `adapter-contract` | behavioral | `no-explicit-any`, `no-eslint-disable` |
| `schema-compatibility` | validation | `no-explicit-any` |
| `spec-driven-stub` | design | `no-unused-type-imports`, `no-eslint-disable` |

## API Exports

The package also exports utilities for building custom ESLint rules with directive support:

```typescript
import {
  // Directive detection
  hasArchitecturalDirective,
  hasPrecedingDirective,

  // File context analysis
  analyzeFileContext,

  // Registry
  ARCHITECTURAL_DIRECTIVES,
  getDirectiveMetadata,
  getDirectivesByCategory,
  isValidDirectiveType,
} from '@libar-dev/eslint-plugin-directives';

// Types
import type {
  FileContext,
  DirectiveType,
  DirectiveCategory,
  DirectiveMetadata,
} from '@libar-dev/eslint-plugin-directives';
```

## License

MIT