fix: Report resource-operation violations as errors (#40830)

Co-authored-by: n8n-cat-bot[bot] <n8n-cat-bot[bot]@users.noreply.github.com>
This commit is contained in:
n8n-cat-bot[bot] 2026-10-12 09:52:15 +00:00 committed by GitHub
parent 6b77613ee8
commit a1800105ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 66 additions and 5 deletions

View File

@ -83,7 +83,7 @@ export default [
| [require-node-api-error](docs/rules/require-node-api-error.md) | Require NodeApiError or NodeOperationError for error wrapping in catch blocks. Raw errors lose HTTP context in the n8n UI. | ✅ ☑️ | | | | |
| [require-node-description-fields](docs/rules/require-node-description-fields.md) | Node class description must define all required fields: icon, subtitle | ✅ ☑️ | | | | |
| [require-version](docs/rules/require-version.md) | Require a valid "version" field in community node package.json | ✅ ☑️ | | | | |
| [resource-operation-pattern](docs/rules/resource-operation-pattern.md) | Enforce proper resource/operation pattern for better UX in n8n nodes | | ✅ ☑️ | | | |
| [resource-operation-pattern](docs/rules/resource-operation-pattern.md) | Enforce proper resource/operation pattern for better UX in n8n nodes | ✅ ☑️ | | | | |
| [trigger-node-conventions](docs/rules/trigger-node-conventions.md) | Trigger nodes (class name ends with `Trigger`) must label themselves consistently as triggers | ✅ ☑️ | | | | |
| [valid-author](docs/rules/valid-author.md) | Require a non-empty author name and email in package.json | ✅ ☑️ | | | | |
| [valid-credential-references](docs/rules/valid-credential-references.md) | Ensure credentials referenced in node descriptions exist as credential classes in the package | ✅ ☑️ | | | 💡 | |

View File

@ -1,12 +1,12 @@
# Enforce proper resource/operation pattern for better UX in n8n nodes (`@n8n/community-nodes/resource-operation-pattern`)
⚠️ This rule _warns_ in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
💼 This rule is enabled in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
<!-- end auto-generated rule header -->
## Rule Details
Warns when a node has more than 5 operations without organizing them into resources. The resource/operation pattern improves user experience by grouping related operations together, making complex nodes easier to navigate.
Reports an error when a node has more than 5 operations without resources. Use resources to group related operations. This pattern makes complex nodes easier to navigate.
When you have many operations, users benefit from having them organized into logical resource groups (e.g., "User", "Project", "File") rather than seeing a long flat list of operations.

View File

@ -44,7 +44,7 @@ const configs = {
'@n8n/community-nodes/no-unsafe-connection-type-cast': 'error',
'@n8n/community-nodes/icon-validation': 'error',
'@n8n/community-nodes/icon-prefer-themed-variants': 'warn',
'@n8n/community-nodes/resource-operation-pattern': 'warn',
'@n8n/community-nodes/resource-operation-pattern': 'error',
'@n8n/community-nodes/trigger-node-conventions': 'error',
'@n8n/community-nodes/credential-documentation-url': 'error',
'@n8n/community-nodes/cred-class-field-icon-missing': 'error',
@ -95,7 +95,7 @@ const configs = {
'@n8n/community-nodes/icon-validation': 'error',
'@n8n/community-nodes/icon-prefer-themed-variants': 'warn',
'@n8n/community-nodes/credential-documentation-url': 'error',
'@n8n/community-nodes/resource-operation-pattern': 'warn',
'@n8n/community-nodes/resource-operation-pattern': 'error',
'@n8n/community-nodes/trigger-node-conventions': 'error',
'@n8n/community-nodes/cred-class-field-icon-missing': 'error',
'@n8n/community-nodes/cred-class-oauth2-naming': 'error',

View File

@ -1,5 +1,8 @@
import { RuleTester } from '@typescript-eslint/rule-tester';
import { Linter } from 'eslint';
import { describe, expect, it } from 'vitest';
import { configs } from '../plugin.js';
import { ResourceOperationPatternRule } from './resource-operation-pattern.js';
const ruleTester = new RuleTester();
@ -215,3 +218,61 @@ ruleTester.run('resource-operation-pattern', ResourceOperationPatternRule, {
},
],
});
const resourceOperationRuleId = '@n8n/community-nodes/resource-operation-pattern';
function nodeWithSixOperations(hasResource: boolean): string {
return `
class TestNode extends Node {
description = {
properties: [
${hasResource ? "{ name: 'resource', type: 'options', options: [{ name: 'User', value: 'user' }] }," : ''}
{
name: 'operation',
type: 'options',
options: [
{ name: 'Get', value: 'get' },
{ name: 'Create', value: 'create' },
{ name: 'Update', value: 'update' },
{ name: 'Delete', value: 'delete' },
{ name: 'List', value: 'list' },
{ name: 'Search', value: 'search' },
],
},
],
};
}
`;
}
describe.each([
['recommended', configs.recommended],
['recommendedWithoutN8nCloudSupport', configs.recommendedWithoutN8nCloudSupport],
] as const)('%s config', (_name, config) => {
const linter = new Linter();
const lint = (code: string) =>
linter.verify(
code,
{
files: ['**/*.node.ts'],
plugins: config.plugins,
rules: { [resourceOperationRuleId]: config.rules[resourceOperationRuleId] },
},
{ filename: 'TestNode.node.ts' },
);
// CE-3284: The resource finding must fail lint even when it is the only finding.
it('reports missing resources as the only error', () => {
expect(lint(nodeWithSixOperations(false))).toEqual([
expect.objectContaining({
ruleId: resourceOperationRuleId,
messageId: 'tooManyOperationsWithoutResources',
severity: 2,
}),
]);
});
it('accepts operations organized by resource', () => {
expect(lint(nodeWithSixOperations(true))).toEqual([]);
});
});