eslint-plugin

Write custom ESLint rules using TDD. This skill covers rule creation, testing, and plugin packaging.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "eslint-plugin" with this command: npx skills add third774/dotfiles/third774-dotfiles-eslint-plugin

ESLint Plugin Author

Write custom ESLint rules using TDD. This skill covers rule creation, testing, and plugin packaging.

When to Use

  • Enforcing project-specific coding standards

  • Creating rules with auto-fix or suggestions

  • Building TypeScript-aware rules using type information

  • Migrating from deprecated rules

Workflow

Copy and track:

ESLint Rule Progress:

  • Clarify transformation (before/after examples)
  • Ask edge case questions (see below)
  • Detect project setup (config format, test runner)
  • Write failing tests first
  • Implement rule to pass tests
  • Add edge case tests
  • Document the rule

Edge Case Discovery

CRITICAL: Ask these BEFORE writing code.

Always Ask

  • Should the rule apply to all file types or specific extensions?

  • Should it be auto-fixable, provide suggestions, or just report?

  • Are any patterns exempt (test files, generated code)?

By Rule Type

Type Key Questions

Identifiers Variables, functions, classes, or all? Destructured? Renamed imports?

Imports Re-exports? Dynamic imports? Type-only? Side-effect imports?

Functions Arrow vs declaration? Methods vs standalone? Async? Generators?

JSX JSX and createElement? Fragments? Self-closing? Spread props?

TypeScript Require type info? Handle any ? Generics? Type assertions?

Project Setup Detection

Config Format

Files Present Format

eslint.config.js/mjs/cjs/ts

Flat config (ESLint 9+)

.eslintrc.* or eslintConfig in package.json Legacy

Test Runner

Check package.json devDependencies:

  • Bun: bun:test or bun

  • Vitest: vitest

  • Jest: jest

Rule Template

// src/rules/rule-name.ts import { ESLintUtils } from "@typescript-eslint/utils";

const createRule = ESLintUtils.RuleCreator( (name) => https://example.com/rules/${name} );

type Options = [{ optionName?: boolean }]; type MessageIds = "errorId" | "suggestionId";

export default createRule<Options, MessageIds>({ name: "rule-name", meta: { type: "problem", // "problem" | "suggestion" | "layout" docs: { description: "What this rule does" }, fixable: "code", // Only if auto-fixable hasSuggestions: true, // Only if has suggestions messages: { errorId: "Error: {{ placeholder }}", suggestionId: "Try this instead", }, schema: [{ type: "object", properties: { optionName: { type: "boolean" } }, additionalProperties: false, }], }, defaultOptions: [{ optionName: false }],

create(context, [options]) { return { // Use AST selectors - see references/code-patterns.md "CallExpression[callee.name='forbidden']"(node) { context.report({ node, messageId: "errorId", fix(fixer) { return fixer.replaceText(node, "replacement"); }, }); }, }; }, });

Test Template

// src/rules/tests/rule-name.test.ts import { afterAll, describe, it } from "bun:test"; // or vitest import { RuleTester } from "@typescript-eslint/rule-tester"; import rule from "../rule-name";

// Configure BEFORE creating instance RuleTester.afterAll = afterAll; RuleTester.describe = describe; RuleTester.it = it; RuleTester.itOnly = it.only;

const ruleTester = new RuleTester({ languageOptions: { parserOptions: { ecmaVersion: "latest", sourceType: "module", }, }, });

ruleTester.run("rule-name", rule, { valid: [ const allowed = 1;, { code: const exempt = 1;, name: "ignores exempt pattern", }, ], invalid: [ { code: const bad = 1;, output: const good = 1;, errors: [{ messageId: "errorId" }], name: "fixes main case", }, ], });

For other test runners and patterns, see references/test-patterns.md.

Type-Aware Rules

For rules needing TypeScript type information:

import { ESLintUtils } from "@typescript-eslint/utils";

create(context) { const services = ESLintUtils.getParserServices(context);

return { CallExpression(node) { // v6+ simplified API - direct call const type = services.getTypeAtLocation(node);

  if (type.symbol?.flags &#x26; ts.SymbolFlags.Enum) {
    context.report({ node, messageId: "enumError" });
  }
},

}; }

Test config for type-aware rules:

import parser from "@typescript-eslint/parser";

const ruleTester = new RuleTester({ languageOptions: { parser, parserOptions: { projectService: { allowDefaultProject: [".ts"] }, tsconfigRootDir: import.meta.dirname, }, }, });

Plugin Structure (Flat Config)

// src/index.ts import { defineConfig } from "eslint/config"; import rule1 from "./rules/rule1";

const plugin = { meta: { name: "eslint-plugin-my-plugin", version: "1.0.0" }, configs: {} as Record<string, unknown>, rules: { "rule1": rule1 }, };

Object.assign(plugin.configs, { recommended: defineConfig([{ plugins: { "my-plugin": plugin }, rules: { "my-plugin/rule1": "error" }, }]), });

export default plugin;

For legacy and dual-format plugins, see references/plugin-templates.md.

Required Test Coverage

Category Purpose

Main case Core transformation

No-op Unrelated code unchanged

Idempotency Already-fixed code stays fixed

Edge cases Variations from spec

Options Different configurations

Quick Reference

Rule Types

Type Use Case

problem

Code that causes errors

suggestion

Style improvements

layout

Whitespace/formatting

Fixer Methods

fixer.replaceText(node, "new") fixer.insertTextBefore(node, "prefix") fixer.insertTextAfter(node, "suffix") fixer.remove(node) fixer.replaceTextRange([start, end], "new")

Common Selectors

"CallExpression[callee.name='target']" // Function call by name "MemberExpression[property.name='prop']" // Property access "ImportDeclaration[source.value='pkg']" // Import from package "Identifier[name='forbidden']" // Identifier by name ":not(CallExpression)" // Negation "FunctionDeclaration:exit" // Exit visitor

References

  • Code Patterns - AST selectors, fixer API, reporting, context API

  • Test Patterns - RuleTester setup for Bun/Vitest/Jest, test cases

  • Plugin Templates - Flat config, legacy, dual-format structures

  • Troubleshooting - Common issues, debugging techniques

External Tools

  • AST Explorer: https://astexplorer.net (select @typescript-eslint/parser)

  • ast-grep: sg --lang ts -p 'pattern' for structural searches

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

opensrc

No summary provided by upstream source.

Repository SourceNeeds Review
General

natural-writing

No summary provided by upstream source.

Repository SourceNeeds Review
General

verification-before-completion

No summary provided by upstream source.

Repository SourceNeeds Review
General

visualizing-with-mermaid

No summary provided by upstream source.

Repository SourceNeeds Review