SKILL.md
1,609 tokens · o200k_base · 6,426 bytes
Source excerpt starting at line 1.---name: n8n:create-community-node-lint-ruledescription: >- Create new ESLint rules for the @n8n/eslint-plugin-community-nodes package. Use when adding a lint rule, creating a community node lint, or working on eslint-plugin-community-nodes. Guides rule implementation, tests, docs, and plugin registration.--- # Create Community Node Lint Rule Guide for adding new ESLint rules to `packages/@n8n/eslint-plugin-community-nodes/`. All paths below are relative to `packages/@n8n/eslint-plugin-community-nodes/`. ## Step 1: Understand the Rule Before writing code, clarify:- **What** does the rule detect? (missing property, wrong pattern, bad value)- **Where** does it apply? (`.node.ts` files, credential classes, both)- **Severity**: `error` (must fix) or `warn` (should fix)?- **Fixable?** Can it be auto-fixed safely, or only suggest?- **Scope**: Both `recommended` configs, or exclude from `recommendedWithoutN8nCloudSupport`? ## Step 2: Implement the Rule Create `src/rules/<rule-name>.ts`: ```typescriptimport { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { isNodeTypeClass, // or isCredentialTypeClass findClassProperty, findObjectProperty, createRule,} from '../utils/index.js'; export const YourRuleNameRule = createRule({ name: 'rule-name', meta: { type: 'problem', // or 'suggestion' docs: { description: 'One-line description of what the rule enforces', }, messages: { messageId: 'Human-readable message. Use {{placeholder}} for dynamic data.', }, fixable: 'code', // omit if not auto-fixable hasSuggestions: true, // omit if no suggestions schema: [], // add options schema if configurable }, defaultOptions: [], create(context) { return { ClassDeclaration(node) { if (!isNodeTypeClass(node)) return; const descriptionProperty = findClassProperty(node, 'description'); if (!descriptionProperty) return; const descriptionValue = descriptionProperty.value; if (descriptionValue?.type !== AST_NODE_TYPES.ObjectExpression) return; // Rule logic here — use findObjectProperty(), getLiteralValue(), etc. context.report({ node: targetNode, messageId: 'messageId', data: { /* template vars */ }, fix(fixer) { return fixer.replaceText(targetNode, 'replacement'); }, }); }, }; },});``` **Naming**: Export as `PascalCaseRule` (e.g. `MissingPairedItemRule`). The `name` field is kebab-case. **Available AST helpers** — see [reference.md](reference.md) for the full catalog of `ast-utils` and `file-utils` exports. ## Step 3: Write Tests Create `src/rules/<rule-name>.test.ts`: ```typescriptimport { RuleTester } from '@typescript-eslint/rule-tester'; import { YourRuleNameRule } from './rule-name.js'; const ruleTester = new RuleTester(); // Helper to generate test code — keeps test cases readablefunction createNodeCode(/* parameterize the varying parts */): string { return `import type { INodeType, INodeTypeDescription } from 'n8n-workflow'; export class TestNode implements INodeType { description: INodeTypeDescription = { displayName: 'Test Node', name: 'testNode', group: ['input'], version: 1, description: 'A test node', defaults: { name: 'Test Node' }, inputs: [], outputs: [], properties: [], };}`;} ruleTester.run('rule-name', YourRuleNameRule, { valid: [ { name: 'class that does not implement INodeType', code: '...' }, { name: 'node with correct pattern', code: createNodeCode(/* correct */) }, ], invalid: [ { name: 'descriptive case name', code: createNodeCode(/* incorrect */), errors: [{ messageId: 'messageId', data: { /* expected template vars */ } }], output: createNodeCode(/* expected after fix */), // or `output: null` if no fix }, ],});``` **Test guidelines:**- Always test that non-INodeType classes are skipped (valid case)- Test both the error message and the fixed output for fixable rules- For rules with options, test each option combination- For rules using filesystem, mock with `vi.mock('../utils/file-utils.js')`- For suggestion-only rules, use `errors: [{ messageId, suggestions: [...] }]` ## Step 4: Register the Rule ### 4a. Add to `src/rules/index.ts` ```typescriptimport { YourRuleNameRule } from './rule-name.js'; // Add to the rules object:export const rules = { // ... existing rules 'rule-name': YourRuleNameRule,} satisfies Record<string, AnyRuleModule>;``` ### 4b. Add to `src/plugin.ts` configs Add to **both** config objects (unless the rule depends on n8n cloud features): ```typescript'@n8n/community-nodes/rule-name': 'error', // or 'warn'``` - Use `error` for rules that catch bugs or required patterns- Use `warn` for style/convention rules (like `options-sorted-alphabetically`)- If the rule uses `no-restricted-globals` or `no-restricted-imports` patterns, only add to `recommended` (not `recommendedWithoutN8nCloudSupport`) ## Step 5: Write Documentation Create `docs/rules/<rule-name>.md`: ```markdown# Description of what the rule does (`@n8n/community-nodes/rule-name`) <!-- end auto-generated rule header --> ## Rule Details Explain why this rule exists and what problem it prevents. ## Examples ### Incorrect \`\`\`typescript// code that triggers the rule\`\`\` ### Correct \`\`\`typescript// code that passes the rule\`\`\```` The header above `<!-- end auto-generated rule header -->` will be regenerated by `pnpm build:docs`. Write a reasonable first version — it gets overwritten. ## Step 6: Verify Run from `packages/@n8n/eslint-plugin-community-nodes/`: ```bashpushd packages/@n8n/eslint-plugin-community-nodespnpm test <rule-name>.test.ts # tests passpnpm typecheck # types are cleanpnpm build # compilespnpm build:docs # regenerates doc headers and README tablepnpm lint:docs # docs match schemapopd``` ## Checklist - [ ] Rule file: `src/rules/<rule-name>.ts`- [ ] Test file: `src/rules/<rule-name>.test.ts`- [ ] Registered in `src/rules/index.ts`- [ ] Added to configs in `src/plugin.ts`- [ ] Doc file: `docs/rules/<rule-name>.md`- [ ] README table updated via `pnpm build:docs`- [ ] All verification commands pass
Discovery context
Discovered by repository scan. No exact path reference found in the snapshot’s root AGENTS.md.