npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@interlace/eslint-devkit

v1.2.1

Published

TypeScript utilities for building LLM-optimized ESLint plugins - AST helpers, type utilities, security benchmarks, and SARIF output

Readme

@interlace/eslint-devkit

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

npm version npm downloads

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, enhanced for the AI-assisted development era.


🚀 Quick Start

Create your first LLM-optimized rule in 2 minutes:

Step 1: Install

npm install --save-dev @interlace/eslint-devkit @typescript-eslint/parser typescript
# or
pnpm 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

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

// 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

// 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

// 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 →


Installation

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

Peer dependencies (required):

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:

npm install --save-dev get-tsconfig enhanced-resolve
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:

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:

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:

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:

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

// ❌ 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

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

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

// 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

// ❌ 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:

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


License

MIT © Ofri Peretz


Contributing

Contributions welcome! See CONTRIBUTING.md.


Changelog

See CHANGELOG.md for a list of changes and version history.