npm package: @interlace/eslint-devkit
Description: TypeScript utilities for building LLM-optimized ESLint plugins - AST helpers, type utilities, security benchmarks, and SARIF output
Author: Ofri Peretz
Latest version: 1.3.3

# @interlace/eslint-devkit

**Build ESLint plugins that write themselves** - TypeScript utilities for creating rules that AI assistants can understand and auto-fix.

[![npm version](https://img.shields.io/npm/v/@interlace/eslint-devkit.svg)](https://www.npmjs.com/package/@interlace/eslint-devkit)
[![npm downloads](https://img.shields.io/npm/dm/@interlace/eslint-devkit.svg)](https://www.npmjs.com/package/@interlace/eslint-devkit)
[![codecov](https://codecov.io/gh/ofri-peretz/eslint/graph/badge.svg?component=eslint-devkit)](https://app.codecov.io/gh/ofri-peretz/eslint/components?components%5B0%5D=eslint-devkit)

> **Keywords:** ESLint utilities, LLM-optimized, AI assistant, auto-fix, TypeScript ESLint, AST utilities, type checking, rule creation, GitHub Copilot, Cursor AI, Claude AI, structured error messages, deterministic fixes

## What is this?

Most ESLint utilities help you **write rules**. This package helps you write rules that **LLMs can fix automatically**.

**Core principle:** Every error message should teach, not just warn.

Inspired by [@typescript-eslint/utils](https://typescript-eslint.io/packages/utils/), enhanced for the AI-assisted development era.

---

## 🚀 Quick Start

Create your first LLM-optimized rule in 2 minutes:

### Step 1: Install

```bash
npm install --save-dev @interlace/eslint-devkit @typescript-eslint/parser typescript
# or
npm add -D @interlace/eslint-devkit @typescript-eslint/parser typescript
# or
yarn add -D @interlace/eslint-devkit @typescript-eslint/parser typescript
```

### Step 2: Create Your Rule

```typescript
import { createRule, isMemberExpression } from '@interlace/eslint-devkit';

export default createRule({
  name: 'no-console-log',
  meta: {
    type: 'problem',
    docs: {
      description: 'Disallow console.log - use logger.debug() instead',
      recommended: 'warn',
    },
    fixable: 'code',
    messages: {
      useLogger: 'Replace console.log with logger.debug() on line {{line}}',
    },
    schema: [],
  },
  defaultOptions: [],
  create(context) {
    return {
      CallExpression(node) {
        if (isMemberExpression(node.callee, 'console', 'log')) {
          context.report({
            node,
            messageId: 'useLogger',
            data: { line: node.loc.start.line },
            fix(fixer) {
              return fixer.replaceText(node.callee, 'logger.debug');
            },
          });
        }
      },
    };
  },
});
```

**That's it!** Your rule now provides structured error messages that AI assistants can automatically fix.

### Step 3: Use in ESLint Config

```typescript
// eslint.config.js
import myPlugin from './my-plugin';

export default [
  {
    plugins: {
      'my-plugin': myPlugin,
    },
    rules: {
      'my-plugin/no-console-log': 'warn',
    },
  },
];
```

---

## Why LLM-Optimized Matters

### Traditional ESLint Rule

```typescript
// Error output
{
  message: "Unexpected console statement",
  line: 42
}

// AI Assistant thinks: "Remove it? Comment it? Replace with what?"
// Result: ❌ AI can't fix it automatically
```

### LLM-Optimized Rule

```typescript
// Error output
{
  message: "Replace console.log with logger.debug() on line 42",
  line: 42,
  fix: { /* auto-fix available */ }
}

// AI Assistant thinks: "Replace with logger.debug(), add import if needed"
// Result: ✅ AI auto-applies fix
```

**Key Benefits:**

- **60-80% auto-fix rate** vs 20-30% for traditional rules
- **Deterministic fixes** - Same violation = same fix every time
- **Lower review burden** - Most violations fixed before human review
- **Faster onboarding** - Developers learn patterns from error messages

---

## 📈 Benchmarks

Rules built with this utility package achieve:

| Metric                  | LLM-Optimized Rules | Standard ESLint Rules |
| ----------------------- | ------------------- | --------------------- |
| **AI Fix Success Rate** | 94%                 | 67%                   |
| **First Attempt Fix**   | 89%                 | 52%                   |
| **Parse Success Rate**  | 100%                | 100%                  |
| **Field Extraction**    | 100%                | 23%                   |

### Enterprise Features Included

| Feature                 | Support Level  | Description                           |
| ----------------------- | -------------- | ------------------------------------- |
| **SARIF Export**        | ✅ Full        | GitHub Advanced Security integration  |
| **CWE Auto-Enrichment** | ✅ Automatic   | Security benchmarks from CWE ID       |
| **OWASP Mapping**       | ✅ 2021 + 2025 | Forward-compatible security standards |
| **Compliance Tags**     | ✅ Auto        | SOC2, HIPAA, PCI-DSS, GDPR, ISO27001  |

> 📊 **[Full Benchmarks →](https://github.com/ofri-peretz/eslint/blob/main/docs/BENCHMARK.md)**

---

## Installation

```bash
npm install --save-dev @interlace/eslint-devkit
```

**Peer dependencies (required):**

```bash
npm install --save-dev @typescript-eslint/parser typescript
npm install --save-dev @typescript-eslint/utils
```

### Optional: Resolver helpers

If you need module resolution utilities, use the dedicated entry point and opt into the resolver peer deps:

```bash
npm install --save-dev get-tsconfig enhanced-resolve
```

```ts
import { createResolver } from '@interlace/eslint-devkit/resolver';
```

If you don’t import `@interlace/eslint-devkit/resolver`, the resolver code and peer deps stay out of your bundle/install surface.

---

## API Reference

### Rule Creation

#### `createRule(options)`

Creates a well-typed ESLint rule with automatic documentation links.

**Parameters:**

- `name` (string): Rule name (e.g., 'no-console-log')
- `meta` (object): Rule metadata (type, docs, messages, schema)
- `defaultOptions` (array): Default rule options
- `create` (function): Rule implementation function

**Returns:** ESLint rule object

**Example:**

```typescript
import { createRule } from '@interlace/eslint-devkit';

const rule = createRule({
  name: 'my-rule',
  meta: {
    type: 'problem',
    docs: { description: 'My custom rule' },
    messages: { error: 'Error message' },
    schema: [],
  },
  defaultOptions: [],
  create(context) {
    return {
      // Your rule implementation
    };
  },
});
```

#### `createRuleCreator(urlCreator)`

Creates a custom rule factory with your documentation URL pattern.

**Parameters:**

- `urlCreator` (function): Function that takes rule name and returns documentation URL

**Returns:** Rule creation function

**Example:**

```typescript
import { createRuleCreator } from '@interlace/eslint-devkit';

const createRule = createRuleCreator(
  (ruleName) => `https://your-plugin.dev/rules/${ruleName}`,
);

export default createRule({
  name: 'my-rule',
  // ...
});
```

---

### AST Utilities

Helper functions for traversing and analyzing ESTree/TSESTree nodes.

#### Node Type Checks

| Function                   | Description                        | Example                            |
| -------------------------- | ---------------------------------- | ---------------------------------- |
| `isNodeOfType(node, type)` | Type guard for AST nodes           | `isNodeOfType(node, 'Identifier')` |
| `isFunctionNode(node)`     | Check if node is any function type | `isFunctionNode(node)`             |
| `isClassNode(node)`        | Check if node is a class           | `isClassNode(node)`                |
| `isLiteral(node)`          | Check if literal value             | `isLiteral(node)`                  |
| `isTemplateLiteral(node)`  | Check if template literal          | `isTemplateLiteral(node)`          |

#### Pattern Matching

| Function                                     | Description                       | Example                                      |
| -------------------------------------------- | --------------------------------- | -------------------------------------------- |
| `isMemberExpression(node, object, property)` | Match patterns like `console.log` | `isMemberExpression(node, 'console', 'log')` |
| `isCallExpression(node, name)`               | Check function call by name       | `isCallExpression(node, 'fetch')`            |

#### Value Extraction

| Function                  | Description             | Example                              |
| ------------------------- | ----------------------- | ------------------------------------ |
| `getIdentifierName(node)` | Extract identifier name | `getIdentifierName(node) // 'myVar'` |
| `getFunctionName(node)`   | Get function name       | `getFunctionName(node) // 'myFunc'`  |
| `getStaticValue(node)`    | Extract static value    | `getStaticValue(node) // 'hello'`    |

#### Ancestor Navigation

| Function                                    | Description                     | Example                                               |
| ------------------------------------------- | ------------------------------- | ----------------------------------------------------- |
| `isInsideNode(node, parentType, ancestors)` | Check if inside specific parent | `isInsideNode(node, 'TryStatement', ancestors)`       |
| `getAncestorOfType(type, ancestors)`        | Find first ancestor of type     | `getAncestorOfType('FunctionDeclaration', ancestors)` |

**Complete Example:**

```typescript
import {
  isMemberExpression,
  isInsideNode,
  getAncestorOfType,
  getIdentifierName,
} from '@interlace/eslint-devkit';

create(context) {
  return {
    CallExpression(node) {
      // Detect console.log() calls
      if (isMemberExpression(node.callee, 'console', 'log')) {
        // Check if inside try-catch (might be intentional logging)
        const ancestors = context.getAncestors();
        const insideTry = isInsideNode(node, 'TryStatement', ancestors);

        if (!insideTry) {
          const functionAncestor = getAncestorOfType('FunctionDeclaration', ancestors);
          const functionName = functionAncestor
            ? getIdentifierName(functionAncestor.id)
            : 'anonymous';

          context.report({
            node,
            message: `Avoid console.log outside error handlers in ${functionName}`,
          });
        }
      }
    },
  };
}
```

---

### Type Utilities

Type-aware analysis using TypeScript compiler API. These utilities require TypeScript parser services.

#### Service Access

| Function                        | Description                                 | Example                                       |
| ------------------------------- | ------------------------------------------- | --------------------------------------------- |
| `hasParserServices(context)`    | Check if type info available                | `if (hasParserServices(context))`             |
| `getParserServices(context)`    | Get parser services (throws if unavailable) | `const services = getParserServices(context)` |
| `getTypeOfNode(node, services)` | Get TypeScript type of node                 | `const type = getTypeOfNode(node, services)`  |

#### Type Checks

| Function                          | Description                | Example                           |
| --------------------------------- | -------------------------- | --------------------------------- |
| `isStringType(type)`              | Check if type is string    | `isStringType(type)`              |
| `isNumberType(type)`              | Check if type is number    | `isNumberType(type)`              |
| `isBooleanType(type)`             | Check if type is boolean   | `isBooleanType(type)`             |
| `isArrayType(type, checker)`      | Check if type is array     | `isArrayType(type, checker)`      |
| `isPromiseType(type, checker)`    | Check if type is Promise   | `isPromiseType(type, checker)`    |
| `isAnyType(type)`                 | Check if type is any       | `isAnyType(type)`                 |
| `isUnknownType(type)`             | Check if type is unknown   | `isUnknownType(type)`             |
| `isNullableType(type)`            | Check if type is nullable  | `isNullableType(type)`            |
| `getTypeArguments(type, checker)` | Get generic type arguments | `getTypeArguments(type, checker)` |

**Complete Example:**

```typescript
import {
  hasParserServices,
  getParserServices,
  getTypeOfNode,
  isPromiseType,
} from '@interlace/eslint-devkit';

create(context) {
  // Gracefully handle projects without TypeScript
  if (!hasParserServices(context)) {
    return {};
  }

  const services = getParserServices(context);
  const checker = services.program.getTypeChecker();

  return {
    CallExpression(node) {
      const type = getTypeOfNode(node, services);

      // Detect unawaited promises by TYPE, not syntax
      if (isPromiseType(type, checker)) {
        const parent = node.parent;
        const isAwaited = parent?.type === 'AwaitExpression';

        if (!isAwaited) {
          context.report({
            node,
            message: 'Promise is not awaited - add "await" or handle with .then()',
            fix(fixer) {
              return fixer.insertTextBefore(node, 'await ');
            },
          });
        }
      }
    },
  };
}
```

---

## Best Practices

### 1. Provide Specific Error Messages

```typescript
// ❌ Vague - AI can't determine fix
message: 'Invalid usage';

// ✅ Specific - AI knows exactly what to do
message: 'Replace fetch() with apiClient.get() for automatic error handling';
```

### 2. Include Auto-Fixes When Possible

```typescript
context.report({
  node,
  message: 'Use const instead of let for immutable variables',
  fix(fixer) {
    return fixer.replaceText(letToken, 'const');
  },
});
```

### 3. Structure Error Data for AI

```typescript
context.report({
  node,
  messageId: 'circularDependency',
  data: {
    chain: 'A.ts → B.ts → C.ts → A.ts',
    breakAt: 'C.ts',
    suggestion: 'Extract shared types to types.ts',
  },
});
```

### 4. Use Type Information When Available

```typescript
// Detect issues semantically, not just syntactically
if (hasParserServices(context)) {
  const type = getTypeOfNode(node, services);
  if (isPromiseType(type, checker)) {
    // Type-aware detection is more accurate
  }
}
```

### 5. Provide Context in Messages

```typescript
// ❌ Missing context
message: 'Use logger instead';

// ✅ Includes context
message: 'Replace console.log with logger.debug() on line {{line}} in function {{functionName}}';
```

---

## TypeScript Support

Full TypeScript support with comprehensive type definitions:

```typescript
import type { TSESTree } from '@typescript-eslint/utils';
import {
  createRule,
  isMemberExpression,
  type RuleContext,
} from '@interlace/eslint-devkit';

// Fully typed rule creation
const rule = createRule<[], 'messageId'>({
  name: 'my-rule',
  meta: {
    type: 'problem',
    messages: {
      messageId: 'Error message',
    },
    schema: [],
  },
  defaultOptions: [],
  create(context: RuleContext<'messageId', []>) {
    return {
      Identifier(node: TSESTree.Identifier) {
        // Fully typed node visitors
      },
    };
  },
});
```

---

## Compatibility

| Package                   | Version            |
| ------------------------- | ------------------ |
| ESLint                    | ^8.0.0 \|\| ^9.0.0 |
| TypeScript                | >=4.0.0            |
| @typescript-eslint/parser | >=6.0.0            |
| @typescript-eslint/utils  | ^8.0.0             |
| Node.js                   | >=18.0.0           |

---

## Related Packages

- **[eslint-plugin-llm-optimized](https://www.npmjs.com/package/eslint-plugin-llm-optimized)** - Ready-to-use LLM-optimized rules built with this package
- **[@typescript-eslint/utils](https://www.npmjs.com/package/@typescript-eslint/utils)** - Official TypeScript ESLint utilities
- **[eslint-plugin-import](https://www.npmjs.com/package/eslint-plugin-import)** - Import/export validation

---

## License

MIT © [Ofri Peretz](https://github.com/ofri-peretz)

---

## Contributing

Contributions welcome! See [CONTRIBUTING.md](https://github.com/ofri-peretz/eslint/blob/main/CONTRIBUTING.md).

---

## Changelog

See [CHANGELOG.md](./CHANGELOG.md) for a list of changes and version history.