@interlace/eslint-devkit
v1.2.1
Published
TypeScript utilities for building LLM-optimized ESLint plugins - AST helpers, type utilities, security benchmarks, and SARIF output
Maintainers
Readme
@interlace/eslint-devkit
Build ESLint plugins that write themselves - TypeScript utilities for creating rules that AI assistants can understand and auto-fix.
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 typescriptStep 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 automaticallyLLM-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 fixKey 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 |
Installation
npm install --save-dev @interlace/eslint-devkitPeer dependencies (required):
npm install --save-dev @typescript-eslint/parser typescript
npm install --save-dev @typescript-eslint/utilsOptional: 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-resolveimport { 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 optionscreate(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
- @forge-js/eslint-plugin-llm-optimized - Ready-to-use LLM-optimized rules built with this package
- @typescript-eslint/utils - Official TypeScript ESLint utilities
- eslint-plugin-import - Import/export validation
License
MIT © Ofri Peretz
Contributing
Contributions welcome! See CONTRIBUTING.md.
Changelog
See CHANGELOG.md for a list of changes and version history.
