mirror of
https://github.com/n8n-io/n8n.git
synced 2026-06-04 02:37:46 +02:00
feat: Add cred-class-name-field-conventions ESLint rule for community nodes (no-changelog) (#31472)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9dd966b658
commit
8b718d2aae
|
|
@ -44,41 +44,43 @@ export default [
|
|||
💡 Manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).\
|
||||
❌ Deprecated.
|
||||
|
||||
| Name | Description | 💼 | ⚠️ | 🔧 | 💡 | ❌ |
|
||||
| :--------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------ | :--- | :--- | :- | :- | :- |
|
||||
| [ai-node-package-json](docs/rules/ai-node-package-json.md) | Enforce consistency between n8n.aiNodeSdkVersion and ai-node-sdk peer dependency in community node packages | ✅ ☑️ | | | | |
|
||||
| [cred-class-field-icon-missing](docs/rules/cred-class-field-icon-missing.md) | Credential class must have an `icon` property defined | ✅ ☑️ | | | 💡 | |
|
||||
| [cred-class-name-suffix](docs/rules/cred-class-name-suffix.md) | Credential class names must be suffixed with `Api` | ✅ ☑️ | | 🔧 | | |
|
||||
| [cred-class-oauth2-naming](docs/rules/cred-class-oauth2-naming.md) | OAuth2 credentials must include `OAuth2` in the class name, `name`, and `displayName` | ✅ ☑️ | | 🔧 | | |
|
||||
| [credential-documentation-url](docs/rules/credential-documentation-url.md) | Enforce valid credential documentationUrl format (URL or lowercase alphanumeric slug) | ✅ ☑️ | | 🔧 | | |
|
||||
| [credential-password-field](docs/rules/credential-password-field.md) | Ensure credential fields with sensitive names have typeOptions.password = true | ✅ ☑️ | | 🔧 | | |
|
||||
| [credential-test-required](docs/rules/credential-test-required.md) | Ensure credentials have a credential test | ✅ ☑️ | | | 💡 | |
|
||||
| [icon-validation](docs/rules/icon-validation.md) | Validate node and credential icon files exist, are SVG format, and light/dark icons are different | ✅ ☑️ | | | 💡 | |
|
||||
| [missing-paired-item](docs/rules/missing-paired-item.md) | Require pairedItem on INodeExecutionData objects in execute() methods to preserve item linking. | ✅ ☑️ | | | | |
|
||||
| [n8n-object-validation](docs/rules/n8n-object-validation.md) | Validate the structure of the "n8n" object in community node package.json (required keys, types, and dist/ paths) | ✅ ☑️ | | | | |
|
||||
| [no-credential-reuse](docs/rules/no-credential-reuse.md) | Prevent credential re-use security issues by ensuring nodes only reference credentials from the same package | ✅ ☑️ | | | 💡 | |
|
||||
| [no-deprecated-workflow-functions](docs/rules/no-deprecated-workflow-functions.md) | Disallow usage of deprecated functions and types from n8n-workflow package | ✅ ☑️ | | | 💡 | |
|
||||
| [no-forbidden-lifecycle-scripts](docs/rules/no-forbidden-lifecycle-scripts.md) | Ban lifecycle scripts (prepare, preinstall, postinstall, etc.) in community node packages | ✅ ☑️ | | | | |
|
||||
| [no-http-request-with-manual-auth](docs/rules/no-http-request-with-manual-auth.md) | Disallow this.helpers.httpRequest() in functions that call this.getCredentials(). Use this.helpers.httpRequestWithAuthentication() instead. | ✅ ☑️ | | | | |
|
||||
| [no-overrides-field](docs/rules/no-overrides-field.md) | Ban the "overrides" field in community node package.json | ✅ ☑️ | | | | |
|
||||
| [no-restricted-globals](docs/rules/no-restricted-globals.md) | Disallow usage of restricted global variables in community nodes. | ✅ | | | | |
|
||||
| [no-restricted-imports](docs/rules/no-restricted-imports.md) | Disallow usage of restricted imports in community nodes. | ✅ | | | | |
|
||||
| [no-runtime-dependencies](docs/rules/no-runtime-dependencies.md) | Disallow non-empty "dependencies" in community node package.json | ✅ ☑️ | | | | |
|
||||
| [no-template-placeholders](docs/rules/no-template-placeholders.md) | Disallow unresolved template placeholders in package.json | ✅ ☑️ | | | | |
|
||||
| [node-class-description-icon-missing](docs/rules/node-class-description-icon-missing.md) | Node class description must have an `icon` property defined. Deprecated: use `require-node-description-fields` instead. | | | | 💡 | ❌ |
|
||||
| [node-connection-type-literal](docs/rules/node-connection-type-literal.md) | Disallow string literals in node description `inputs`/`outputs` — use `NodeConnectionTypes` enum instead | ✅ ☑️ | | 🔧 | | |
|
||||
| [node-operation-error-itemindex](docs/rules/node-operation-error-itemindex.md) | Require { itemIndex } in NodeOperationError / NodeApiError options inside item loops | ✅ ☑️ | | | | |
|
||||
| [node-usable-as-tool](docs/rules/node-usable-as-tool.md) | Ensure node classes have usableAsTool property | ✅ ☑️ | | 🔧 | | |
|
||||
| [options-sorted-alphabetically](docs/rules/options-sorted-alphabetically.md) | Enforce alphabetical ordering of options arrays in n8n node properties | | ✅ ☑️ | | | |
|
||||
| [package-name-convention](docs/rules/package-name-convention.md) | Enforce correct package naming convention for n8n community nodes | ✅ ☑️ | | | 💡 | |
|
||||
| [require-community-node-keyword](docs/rules/require-community-node-keyword.md) | Require the "n8n-community-node-package" keyword in community node package.json | | ✅ ☑️ | 🔧 | | |
|
||||
| [require-continue-on-fail](docs/rules/require-continue-on-fail.md) | Require continueOnFail() handling in execute() methods of node classes | ✅ ☑️ | | | | |
|
||||
| [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 | ✅ ☑️ | | | | |
|
||||
| [resource-operation-pattern](docs/rules/resource-operation-pattern.md) | Enforce proper resource/operation pattern for better UX in n8n nodes | | ✅ ☑️ | | | |
|
||||
| [valid-credential-references](docs/rules/valid-credential-references.md) | Ensure credentials referenced in node descriptions exist as credential classes in the package | ✅ ☑️ | | | 💡 | |
|
||||
| [valid-description](docs/rules/valid-description.md) | Require a non-empty "description" field in community node package.json | ✅ ☑️ | | | | |
|
||||
| [valid-peer-dependencies](docs/rules/valid-peer-dependencies.md) | Require community node package.json peerDependencies to contain only "n8n-workflow": "*" (and optionally "ai-node-sdk") | ✅ ☑️ | | 🔧 | | |
|
||||
| [webhook-lifecycle-complete](docs/rules/webhook-lifecycle-complete.md) | Require webhook trigger nodes to implement the complete webhookMethods lifecycle (checkExists, create, delete) | ✅ ☑️ | | | | |
|
||||
| Name | Description | 💼 | ⚠️ | 🔧 | 💡 | ❌ |
|
||||
| :--------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--- | :--- | :- | :- | :- |
|
||||
| [ai-node-package-json](docs/rules/ai-node-package-json.md) | Enforce consistency between n8n.aiNodeSdkVersion and ai-node-sdk peer dependency in community node packages | ✅ ☑️ | | | | |
|
||||
| [cred-class-field-icon-missing](docs/rules/cred-class-field-icon-missing.md) | Credential class must have an `icon` property defined | ✅ ☑️ | | | 💡 | |
|
||||
| [cred-class-name-field-conventions](docs/rules/cred-class-name-field-conventions.md) | Credential `name` field must end with `Api` and start with a lowercase letter | ✅ ☑️ | | 🔧 | | |
|
||||
| [cred-class-name-suffix](docs/rules/cred-class-name-suffix.md) | Credential class names must be suffixed with `Api` | ✅ ☑️ | | 🔧 | | |
|
||||
| [cred-class-oauth2-naming](docs/rules/cred-class-oauth2-naming.md) | OAuth2 credentials must include `OAuth2` in the class name, `name`, and `displayName` | ✅ ☑️ | | 🔧 | | |
|
||||
| [credential-documentation-url](docs/rules/credential-documentation-url.md) | Enforce valid credential documentationUrl format (URL or lowercase alphanumeric slug) | ✅ ☑️ | | 🔧 | | |
|
||||
| [credential-password-field](docs/rules/credential-password-field.md) | Ensure credential fields with sensitive names have typeOptions.password = true | ✅ ☑️ | | 🔧 | | |
|
||||
| [credential-test-required](docs/rules/credential-test-required.md) | Ensure credentials have a credential test | ✅ ☑️ | | | 💡 | |
|
||||
| [icon-validation](docs/rules/icon-validation.md) | Validate node and credential icon files exist, are SVG format, and light/dark icons are different | ✅ ☑️ | | | 💡 | |
|
||||
| [missing-paired-item](docs/rules/missing-paired-item.md) | Require pairedItem on INodeExecutionData objects in execute() methods to preserve item linking. | ✅ ☑️ | | | | |
|
||||
| [n8n-object-validation](docs/rules/n8n-object-validation.md) | Validate the structure of the "n8n" object in community node package.json (required keys, types, and dist/ paths) | ✅ ☑️ | | | | |
|
||||
| [no-builder-hint-leakage](docs/rules/no-builder-hint-leakage.md) | Disallow wire-format expression syntax (={{...}}) and NodeConnectionType string literals in builderHint texts and AI-builder prompts. Use expr() and SDK-canonical references instead. | ✅ ☑️ | | | | |
|
||||
| [no-credential-reuse](docs/rules/no-credential-reuse.md) | Prevent credential re-use security issues by ensuring nodes only reference credentials from the same package | ✅ ☑️ | | | 💡 | |
|
||||
| [no-deprecated-workflow-functions](docs/rules/no-deprecated-workflow-functions.md) | Disallow usage of deprecated functions and types from n8n-workflow package | ✅ ☑️ | | | 💡 | |
|
||||
| [no-forbidden-lifecycle-scripts](docs/rules/no-forbidden-lifecycle-scripts.md) | Ban lifecycle scripts (prepare, preinstall, postinstall, etc.) in community node packages | ✅ ☑️ | | | | |
|
||||
| [no-http-request-with-manual-auth](docs/rules/no-http-request-with-manual-auth.md) | Disallow this.helpers.httpRequest() in functions that call this.getCredentials(). Use this.helpers.httpRequestWithAuthentication() instead. | ✅ ☑️ | | | | |
|
||||
| [no-overrides-field](docs/rules/no-overrides-field.md) | Ban the "overrides" field in community node package.json | ✅ ☑️ | | | | |
|
||||
| [no-restricted-globals](docs/rules/no-restricted-globals.md) | Disallow usage of restricted global variables in community nodes. | ✅ | | | | |
|
||||
| [no-restricted-imports](docs/rules/no-restricted-imports.md) | Disallow usage of restricted imports in community nodes. | ✅ | | | | |
|
||||
| [no-runtime-dependencies](docs/rules/no-runtime-dependencies.md) | Disallow non-empty "dependencies" in community node package.json | ✅ ☑️ | | | | |
|
||||
| [no-template-placeholders](docs/rules/no-template-placeholders.md) | Disallow unresolved template placeholders in package.json | ✅ ☑️ | | | | |
|
||||
| [node-class-description-icon-missing](docs/rules/node-class-description-icon-missing.md) | Node class description must have an `icon` property defined. Deprecated: use `require-node-description-fields` instead. | | | | 💡 | ❌ |
|
||||
| [node-connection-type-literal](docs/rules/node-connection-type-literal.md) | Disallow string literals in node description `inputs`/`outputs` — use `NodeConnectionTypes` enum instead | ✅ ☑️ | | 🔧 | | |
|
||||
| [node-operation-error-itemindex](docs/rules/node-operation-error-itemindex.md) | Require { itemIndex } in NodeOperationError / NodeApiError options inside item loops | ✅ ☑️ | | | | |
|
||||
| [node-usable-as-tool](docs/rules/node-usable-as-tool.md) | Ensure node classes have usableAsTool property | ✅ ☑️ | | 🔧 | | |
|
||||
| [options-sorted-alphabetically](docs/rules/options-sorted-alphabetically.md) | Enforce alphabetical ordering of options arrays in n8n node properties | | ✅ ☑️ | | | |
|
||||
| [package-name-convention](docs/rules/package-name-convention.md) | Enforce correct package naming convention for n8n community nodes | ✅ ☑️ | | | 💡 | |
|
||||
| [require-community-node-keyword](docs/rules/require-community-node-keyword.md) | Require the "n8n-community-node-package" keyword in community node package.json | | ✅ ☑️ | 🔧 | | |
|
||||
| [require-continue-on-fail](docs/rules/require-continue-on-fail.md) | Require continueOnFail() handling in execute() methods of node classes | ✅ ☑️ | | | | |
|
||||
| [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 | ✅ ☑️ | | | | |
|
||||
| [resource-operation-pattern](docs/rules/resource-operation-pattern.md) | Enforce proper resource/operation pattern for better UX in n8n nodes | | ✅ ☑️ | | | |
|
||||
| [valid-credential-references](docs/rules/valid-credential-references.md) | Ensure credentials referenced in node descriptions exist as credential classes in the package | ✅ ☑️ | | | 💡 | |
|
||||
| [valid-description](docs/rules/valid-description.md) | Require a non-empty "description" field in community node package.json | ✅ ☑️ | | | | |
|
||||
| [valid-peer-dependencies](docs/rules/valid-peer-dependencies.md) | Require community node package.json peerDependencies to contain only "n8n-workflow": "*" (and optionally "ai-node-sdk") | ✅ ☑️ | | 🔧 | | |
|
||||
| [webhook-lifecycle-complete](docs/rules/webhook-lifecycle-complete.md) | Require webhook trigger nodes to implement the complete webhookMethods lifecycle (checkExists, create, delete) | ✅ ☑️ | | | | |
|
||||
|
||||
<!-- end auto-generated rules list -->
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
# Credential `name` field must end with `Api` and start with a lowercase letter (`@n8n/community-nodes/cred-class-name-field-conventions`)
|
||||
|
||||
💼 This rule is enabled in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
|
||||
|
||||
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
|
||||
|
||||
<!-- end auto-generated rule header -->
|
||||
|
||||
## Rule Details
|
||||
|
||||
The `name` field of a credential class (those implementing `ICredentialType` in `*.credentials.ts` files) is the internal identifier referenced by nodes and stored in the credential registry. n8n convention requires this identifier to:
|
||||
|
||||
- End with `Api` (e.g. `githubApi`), so credentials are easily recognisable across the codebase.
|
||||
- Start with a lowercase letter (camelCase), since it is an identifier value rather than a class name.
|
||||
|
||||
Both checks are automatically fixable.
|
||||
|
||||
## Examples
|
||||
|
||||
### ❌ Incorrect
|
||||
|
||||
```typescript
|
||||
export class GithubApi implements ICredentialType {
|
||||
name = 'Github';
|
||||
displayName = 'GitHub API';
|
||||
properties: INodeProperties[] = [];
|
||||
}
|
||||
```
|
||||
|
||||
### ✅ Correct
|
||||
|
||||
```typescript
|
||||
export class GithubApi implements ICredentialType {
|
||||
name = 'githubApi';
|
||||
displayName = 'GitHub API';
|
||||
properties: INodeProperties[] = [];
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# Disallow wire-format expression syntax (={{...}}) and NodeConnectionType string literals in builderHint texts and AI-builder prompts. Use expr() and SDK-canonical references instead (`@n8n/community-nodes/no-builder-hint-leakage`)
|
||||
|
||||
💼 This rule is enabled in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
|
||||
|
||||
<!-- end auto-generated rule header -->
|
||||
|
||||
## Rule Details
|
||||
|
||||
`builderHint` texts and AI-builder prompts are authored as human-facing guidance, but they sometimes leak n8n's internal wire format:
|
||||
|
||||
- Raw expression syntax such as `={{ $json.foo }}`, which only makes sense inside the execution engine.
|
||||
- `NodeConnectionType` string literals such as `ai_languageModel` or `ai_tool`, which are structured connection identifiers rather than prose.
|
||||
|
||||
When these leak into hints and prompts, they confuse the builder experience and the AI assistant. Use the `expr()` SDK helper for expressions and the SDK-canonical reference helpers (e.g. `languageModel()`, `tool()`, `memory()`) instead.
|
||||
|
||||
## Options
|
||||
|
||||
This rule accepts an options object:
|
||||
|
||||
- `scope` (`'builderHint' | 'all'`, default `'builderHint'`) — `builderHint` only scans string values inside `builderHint` property values. `all` scans every string in the file, intended for AI-builder prompt files.
|
||||
|
||||
## Examples
|
||||
|
||||
### ❌ Incorrect
|
||||
|
||||
```typescript
|
||||
const node = {
|
||||
builderHint: 'Set the model with ={{ $json.model }} and connect an ai_languageModel.',
|
||||
};
|
||||
```
|
||||
|
||||
### ✅ Correct
|
||||
|
||||
```typescript
|
||||
const node = {
|
||||
builderHint: 'Set the model with expr($json.model) and connect a languageModel().',
|
||||
};
|
||||
```
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
"dev": "pnpm watch",
|
||||
"format": "biome format --write .",
|
||||
"format:check": "biome ci .",
|
||||
"lint": "eslint src",
|
||||
"lint": "eslint src && pnpm lint:docs",
|
||||
"lint:fix": "eslint src --fix",
|
||||
"lint:docs": "pnpm build && eslint-doc-generator --check",
|
||||
"test": "vitest run",
|
||||
|
|
@ -27,6 +27,7 @@
|
|||
"watch": "tsc --watch --project tsconfig.build.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "^8.35.0",
|
||||
"@typescript-eslint/utils": "^8.35.0",
|
||||
"fastest-levenshtein": "catalog:"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ const configs = {
|
|||
'@n8n/community-nodes/resource-operation-pattern': 'warn',
|
||||
'@n8n/community-nodes/credential-documentation-url': 'error',
|
||||
'@n8n/community-nodes/cred-class-field-icon-missing': 'error',
|
||||
'@n8n/community-nodes/cred-class-name-field-conventions': 'error',
|
||||
'@n8n/community-nodes/cred-class-name-suffix': 'error',
|
||||
'@n8n/community-nodes/cred-class-oauth2-naming': 'error',
|
||||
'@n8n/community-nodes/node-connection-type-literal': 'error',
|
||||
|
|
@ -80,6 +81,7 @@ const configs = {
|
|||
'@n8n/community-nodes/credential-documentation-url': 'error',
|
||||
'@n8n/community-nodes/resource-operation-pattern': 'warn',
|
||||
'@n8n/community-nodes/cred-class-field-icon-missing': 'error',
|
||||
'@n8n/community-nodes/cred-class-name-field-conventions': 'error',
|
||||
'@n8n/community-nodes/cred-class-name-suffix': 'error',
|
||||
'@n8n/community-nodes/cred-class-oauth2-naming': 'error',
|
||||
'@n8n/community-nodes/node-connection-type-literal': 'error',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,121 @@
|
|||
import { RuleTester } from '@typescript-eslint/rule-tester';
|
||||
|
||||
import { CredClassNameFieldConventionsRule } from './cred-class-name-field-conventions.js';
|
||||
|
||||
const ruleTester = new RuleTester();
|
||||
|
||||
const credFilePath = '/tmp/TestCredential.credentials.ts';
|
||||
const nonCredFilePath = '/tmp/SomeHelper.ts';
|
||||
|
||||
function createCredentialCode(name: string): string {
|
||||
return `
|
||||
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export class TestApi implements ICredentialType {
|
||||
name = '${name}';
|
||||
displayName = 'Test API';
|
||||
properties: INodeProperties[] = [];
|
||||
}`;
|
||||
}
|
||||
|
||||
function createRegularClass(name: string): string {
|
||||
return `
|
||||
export class SomeHelper {
|
||||
name = '${name}';
|
||||
}`;
|
||||
}
|
||||
|
||||
// Embeds the raw literal text (including its quotes) verbatim, so tests can
|
||||
// exercise names containing quote characters that need escaping.
|
||||
function createCredentialCodeWithLiteral(literal: string): string {
|
||||
return `
|
||||
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export class TestApi implements ICredentialType {
|
||||
name = ${literal};
|
||||
displayName = 'Test API';
|
||||
properties: INodeProperties[] = [];
|
||||
}`;
|
||||
}
|
||||
|
||||
ruleTester.run('cred-class-name-field-conventions', CredClassNameFieldConventionsRule, {
|
||||
valid: [
|
||||
{
|
||||
name: 'name field with Api suffix and lowercase first char',
|
||||
filename: credFilePath,
|
||||
code: createCredentialCode('githubApi'),
|
||||
},
|
||||
{
|
||||
name: 'OAuth2 name field is also valid',
|
||||
filename: credFilePath,
|
||||
code: createCredentialCode('githubOAuth2Api'),
|
||||
},
|
||||
{
|
||||
name: 'class not implementing ICredentialType is ignored',
|
||||
filename: credFilePath,
|
||||
code: createRegularClass('Github'),
|
||||
},
|
||||
{
|
||||
name: 'non-.credentials.ts file is ignored',
|
||||
filename: nonCredFilePath,
|
||||
code: createCredentialCode('Github'),
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
name: 'name field missing Api suffix',
|
||||
filename: credFilePath,
|
||||
code: createCredentialCode('github'),
|
||||
errors: [{ messageId: 'missingSuffix', data: { value: 'github' } }],
|
||||
output: createCredentialCode('githubApi'),
|
||||
},
|
||||
{
|
||||
name: 'name field with uppercase first char',
|
||||
filename: credFilePath,
|
||||
code: createCredentialCode('GithubApi'),
|
||||
errors: [{ messageId: 'uppercaseFirstChar', data: { value: 'GithubApi' } }],
|
||||
output: createCredentialCode('githubApi'),
|
||||
},
|
||||
{
|
||||
name: 'name field with both uppercase first char and missing suffix',
|
||||
filename: credFilePath,
|
||||
code: createCredentialCode('Github'),
|
||||
errors: [
|
||||
{ messageId: 'uppercaseFirstChar', data: { value: 'Github' } },
|
||||
{ messageId: 'missingSuffix', data: { value: 'Github' } },
|
||||
],
|
||||
output: createCredentialCode('githubApi'),
|
||||
},
|
||||
{
|
||||
name: 'name field ending in Ap',
|
||||
filename: credFilePath,
|
||||
code: createCredentialCode('githubAp'),
|
||||
errors: [{ messageId: 'missingSuffix', data: { value: 'githubAp' } }],
|
||||
output: createCredentialCode('githubApi'),
|
||||
},
|
||||
{
|
||||
name: 'name field ending in A',
|
||||
filename: credFilePath,
|
||||
code: createCredentialCode('githubA'),
|
||||
errors: [{ messageId: 'missingSuffix', data: { value: 'githubA' } }],
|
||||
output: createCredentialCode('githubApi'),
|
||||
},
|
||||
{
|
||||
name: 'autofix escapes single quotes in the name value',
|
||||
filename: credFilePath,
|
||||
code: createCredentialCodeWithLiteral("'git\\'hub'"),
|
||||
errors: [{ messageId: 'missingSuffix', data: { value: "git'hub" } }],
|
||||
output: createCredentialCodeWithLiteral("'git\\'hubApi'"),
|
||||
},
|
||||
{
|
||||
name: 'autofix preserves double quotes and escapes them in the name value',
|
||||
filename: credFilePath,
|
||||
code: createCredentialCodeWithLiteral('"Git\\"hub"'),
|
||||
errors: [
|
||||
{ messageId: 'uppercaseFirstChar', data: { value: 'Git"hub' } },
|
||||
{ messageId: 'missingSuffix', data: { value: 'Git"hub' } },
|
||||
],
|
||||
output: createCredentialCodeWithLiteral('"git\\"hubApi"'),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
import {
|
||||
createRule,
|
||||
findClassProperty,
|
||||
getStringLiteralValue,
|
||||
isCredentialTypeClass,
|
||||
isFileType,
|
||||
} from '../utils/index.js';
|
||||
|
||||
function lowercaseFirstChar(name: string): string {
|
||||
return name.charAt(0).toLowerCase() + name.slice(1);
|
||||
}
|
||||
|
||||
function addApiSuffix(name: string): string {
|
||||
if (name.endsWith('Api')) return name;
|
||||
if (name.endsWith('Ap')) return `${name}i`;
|
||||
if (name.endsWith('A')) return `${name}pi`;
|
||||
return `${name}Api`;
|
||||
}
|
||||
|
||||
// Serialize a value as a string literal using the original quote character,
|
||||
// escaping any characters that would otherwise break the literal so the
|
||||
// autofix never emits invalid code (e.g. names containing quotes).
|
||||
function toStringLiteral(value: string, quote: string): string {
|
||||
const escaped = value
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r')
|
||||
.split(quote)
|
||||
.join(`\\${quote}`);
|
||||
return `${quote}${escaped}${quote}`;
|
||||
}
|
||||
|
||||
export const CredClassNameFieldConventionsRule = createRule({
|
||||
name: 'cred-class-name-field-conventions',
|
||||
meta: {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description: 'Credential `name` field must end with `Api` and start with a lowercase letter',
|
||||
},
|
||||
messages: {
|
||||
missingSuffix: "Credential `name` field '{{value}}' must end with 'Api'",
|
||||
uppercaseFirstChar: "Credential `name` field '{{value}}' must start with a lowercase letter",
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [],
|
||||
},
|
||||
defaultOptions: [],
|
||||
create(context) {
|
||||
if (!isFileType(context.filename, '.credentials.ts')) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
ClassDeclaration(node) {
|
||||
if (!isCredentialTypeClass(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nameProperty = findClassProperty(node, 'name');
|
||||
if (!nameProperty?.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nameValue = getStringLiteralValue(nameProperty.value);
|
||||
if (nameValue === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const startsLowercase = !/^[A-Z]/.test(nameValue);
|
||||
const endsWithApi = nameValue.endsWith('Api');
|
||||
if (startsLowercase && endsWithApi) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute the fully-corrected value once so each fix yields the
|
||||
// same result in a single pass, regardless of which one applies.
|
||||
const fixedValue = addApiSuffix(lowercaseFirstChar(nameValue));
|
||||
const valueNode = nameProperty.value;
|
||||
const quote = context.sourceCode.getText(valueNode).charAt(0);
|
||||
const replacement = toStringLiteral(fixedValue, quote);
|
||||
|
||||
if (!startsLowercase) {
|
||||
context.report({
|
||||
node: valueNode,
|
||||
messageId: 'uppercaseFirstChar',
|
||||
data: { value: nameValue },
|
||||
fix: (fixer) => fixer.replaceText(valueNode, replacement),
|
||||
});
|
||||
}
|
||||
|
||||
if (!endsWithApi) {
|
||||
context.report({
|
||||
node: valueNode,
|
||||
messageId: 'missingSuffix',
|
||||
data: { value: nameValue },
|
||||
fix: (fixer) => fixer.replaceText(valueNode, replacement),
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { TSESTree } from '@typescript-eslint/types';
|
||||
import { TSESTree } from '@typescript-eslint/utils';
|
||||
import type { ReportFixFunction } from '@typescript-eslint/utils/ts-eslint';
|
||||
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import type { AnyRuleModule } from '@typescript-eslint/utils/ts-eslint';
|
|||
|
||||
import { AiNodePackageJsonRule } from './ai-node-package-json.js';
|
||||
import { CredClassFieldIconMissingRule } from './cred-class-field-icon-missing.js';
|
||||
import { CredClassNameFieldConventionsRule } from './cred-class-name-field-conventions.js';
|
||||
import { CredClassNameSuffixRule } from './cred-class-name-suffix.js';
|
||||
import { CredClassOAuth2NamingRule } from './cred-class-oauth2-naming.js';
|
||||
import { CredentialDocumentationUrlRule } from './credential-documentation-url.js';
|
||||
|
|
@ -57,6 +58,7 @@ export const rules = {
|
|||
'credential-documentation-url': CredentialDocumentationUrlRule,
|
||||
'node-class-description-icon-missing': NodeClassDescriptionIconMissingRule,
|
||||
'cred-class-field-icon-missing': CredClassFieldIconMissingRule,
|
||||
'cred-class-name-field-conventions': CredClassNameFieldConventionsRule,
|
||||
'cred-class-name-suffix': CredClassNameSuffixRule,
|
||||
'cred-class-oauth2-naming': CredClassOAuth2NamingRule,
|
||||
'node-connection-type-literal': NodeConnectionTypeLiteralRule,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { TSESTree } from '@typescript-eslint/types';
|
||||
import { TSESTree } from '@typescript-eslint/utils';
|
||||
import type { ReportSuggestionArray } from '@typescript-eslint/utils/ts-eslint';
|
||||
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { TSESTree } from '@typescript-eslint/types';
|
||||
import { TSESTree } from '@typescript-eslint/utils';
|
||||
import type { TSESLint } from '@typescript-eslint/utils';
|
||||
|
||||
import { createRule } from '../utils/index.js';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { TSESTree } from '@typescript-eslint/types';
|
||||
import { TSESTree } from '@typescript-eslint/utils';
|
||||
|
||||
import {
|
||||
isNodeTypeClass,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { DefinitionType } from '@typescript-eslint/scope-manager';
|
||||
import { AST_NODE_TYPES, type TSESTree } from '@typescript-eslint/utils';
|
||||
import { AST_NODE_TYPES, TSESLint, type TSESTree } from '@typescript-eslint/utils';
|
||||
|
||||
import { isFileType } from '../utils/index.js';
|
||||
import { createRule } from '../utils/rule-creator.js';
|
||||
|
|
@ -68,7 +67,9 @@ export const RequireNodeApiErrorRule = createRule({
|
|||
const scope = context.sourceCode.getScope(node);
|
||||
const ref = scope.references.find((r) => r.identifier === argument);
|
||||
const isCatchParam =
|
||||
ref?.resolved?.defs.some((def) => def.type === DefinitionType.CatchClause) ?? false;
|
||||
ref?.resolved?.defs.some(
|
||||
(def) => def.type === TSESLint.Scope.DefinitionType.CatchClause,
|
||||
) ?? false;
|
||||
|
||||
if (isCatchParam) {
|
||||
context.report({ node, messageId: 'useNodeApiError' });
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { TSESTree } from '@typescript-eslint/types';
|
||||
import { TSESTree } from '@typescript-eslint/utils';
|
||||
import type { ReportSuggestionArray } from '@typescript-eslint/utils/ts-eslint';
|
||||
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -1643,6 +1643,9 @@ importers:
|
|||
|
||||
packages/@n8n/eslint-plugin-community-nodes:
|
||||
dependencies:
|
||||
'@typescript-eslint/typescript-estree':
|
||||
specifier: ^8.35.0
|
||||
version: 8.35.0(typescript@6.0.2)
|
||||
'@typescript-eslint/utils':
|
||||
specifier: ^8.35.0
|
||||
version: 8.35.0(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.2)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user