TypeScript utilities for building LLM-optimized ESLint plugins - AST helpers, type utilities, security benchmarks, and SARIF output
npm install @interlace/eslint-devkitBuild 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
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.
---
Create your first LLM-optimized rule in 2 minutes:
``bash`
npm install --save-dev @interlace/eslint-devkit @typescript-eslint/parser typescriptor
npm add -D @interlace/eslint-devkit @typescript-eslint/parser typescriptor
yarn add -D @interlace/eslint-devkit @typescript-eslint/parser typescript
`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.
`typescript
// eslint.config.js
import myPlugin from './my-plugin';
export default [
{
plugins: {
'my-plugin': myPlugin,
},
rules: {
'my-plugin/no-console-log': 'warn',
},
},
];
`
---
`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
`
`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
---
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% |
| 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 β
---
`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
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.
---
#### 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:
`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',
// ...
});
`
---
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-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 ');
},
});
}
}
},
};
}
`
---
`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';
`
`typescript`
context.report({
node,
message: 'Use const instead of let for immutable variables',
fix(fixer) {
return fixer.replaceText(letToken, 'const');
},
});
`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',
},
});
`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
}
}
`typescript
// β Missing context
message: 'Use logger instead';
// β
Includes context
message: 'Replace console.log with logger.debug() on line {{line}} in function {{functionName}}';
`
---
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
},
};
},
});
``
---
| 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 |
---
- 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
---
MIT Β© Ofri Peretz
---
Contributions welcome! See CONTRIBUTING.md.
---
See CHANGELOG.md for a list of changes and version history.