@n8n/eslint-plugin-community-nodes 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/README.md +38 -35
  3. package/dist/plugin.d.ts +12 -0
  4. package/dist/plugin.d.ts.map +1 -1
  5. package/dist/plugin.js +4 -0
  6. package/dist/plugin.js.map +1 -1
  7. package/dist/rules/cred-class-name-field-conventions.d.ts +2 -0
  8. package/dist/rules/cred-class-name-field-conventions.d.ts.map +1 -0
  9. package/dist/rules/cred-class-name-field-conventions.js +89 -0
  10. package/dist/rules/cred-class-name-field-conventions.js.map +1 -0
  11. package/dist/rules/credential-password-field.js +1 -1
  12. package/dist/rules/index.d.ts +3 -1
  13. package/dist/rules/index.d.ts.map +1 -1
  14. package/dist/rules/index.js +4 -0
  15. package/dist/rules/index.js.map +1 -1
  16. package/dist/rules/no-credential-reuse.js +1 -1
  17. package/dist/rules/no-restricted-globals.js +1 -1
  18. package/dist/rules/no-template-placeholders.d.ts.map +1 -1
  19. package/dist/rules/no-template-placeholders.js +3 -1
  20. package/dist/rules/no-template-placeholders.js.map +1 -1
  21. package/dist/rules/node-usable-as-tool.js +1 -1
  22. package/dist/rules/package-name-convention.d.ts +1 -1
  23. package/dist/rules/package-name-convention.d.ts.map +1 -1
  24. package/dist/rules/package-name-convention.js +19 -0
  25. package/dist/rules/package-name-convention.js.map +1 -1
  26. package/dist/rules/require-node-api-error.d.ts +2 -1
  27. package/dist/rules/require-node-api-error.d.ts.map +1 -1
  28. package/dist/rules/require-node-api-error.js +2 -3
  29. package/dist/rules/require-node-api-error.js.map +1 -1
  30. package/dist/rules/valid-credential-references.js +1 -1
  31. package/dist/rules/valid-description.d.ts +2 -0
  32. package/dist/rules/valid-description.d.ts.map +1 -0
  33. package/dist/rules/valid-description.js +48 -0
  34. package/dist/rules/valid-description.js.map +1 -0
  35. package/docs/rules/cred-class-name-field-conventions.md +38 -0
  36. package/docs/rules/no-builder-hint-leakage.md +38 -0
  37. package/docs/rules/package-name-convention.md +14 -0
  38. package/docs/rules/valid-description.md +37 -0
  39. package/package.json +5 -4
  40. package/src/plugin.ts +4 -0
  41. package/src/rules/cred-class-name-field-conventions.test.ts +121 -0
  42. package/src/rules/cred-class-name-field-conventions.ts +102 -0
  43. package/src/rules/credential-password-field.ts +1 -1
  44. package/src/rules/index.ts +4 -0
  45. package/src/rules/no-credential-reuse.ts +1 -1
  46. package/src/rules/no-restricted-globals.ts +1 -1
  47. package/src/rules/no-template-placeholders.test.ts +18 -0
  48. package/src/rules/no-template-placeholders.ts +3 -1
  49. package/src/rules/node-usable-as-tool.ts +1 -1
  50. package/src/rules/package-name-convention.test.ts +32 -5
  51. package/src/rules/package-name-convention.ts +23 -0
  52. package/src/rules/require-node-api-error.ts +4 -3
  53. package/src/rules/valid-credential-references.ts +1 -1
  54. package/src/rules/valid-description.test.ts +56 -0
  55. package/src/rules/valid-description.ts +57 -0
  56. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,4 +1,4 @@
1
- import { TSESTree } from '@typescript-eslint/types';
1
+ import { TSESTree } from '@typescript-eslint/utils';
2
2
 
3
3
  import {
4
4
  isNodeTypeClass,
@@ -26,11 +26,6 @@ ruleTester.run('package-name-convention', PackageNameConventionRule, {
26
26
  filename: 'package.json',
27
27
  code: '{ "name": "@author/n8n-nodes-service", "version": "1.0.0" }',
28
28
  },
29
- {
30
- name: 'object without name property',
31
- filename: 'package.json',
32
- code: '{ "version": "1.0.0", "description": "test" }',
33
- },
34
29
  {
35
30
  name: 'non-package.json file ignored',
36
31
  filename: 'some-config.json',
@@ -185,5 +180,37 @@ ruleTester.run('package-name-convention', PackageNameConventionRule, {
185
180
  },
186
181
  ],
187
182
  },
183
+ {
184
+ name: 'missing name property',
185
+ filename: 'package.json',
186
+ code: '{ "version": "1.0.0", "description": "test" }',
187
+ errors: [
188
+ {
189
+ messageId: 'missingName',
190
+ },
191
+ ],
192
+ },
193
+ {
194
+ name: 'default placeholder name',
195
+ filename: 'package.json',
196
+ code: '{ "name": "n8n-nodes-<...>", "version": "1.0.0" }',
197
+ errors: [
198
+ {
199
+ messageId: 'defaultPlaceholderName',
200
+ data: { packageName: 'n8n-nodes-<...>' },
201
+ },
202
+ ],
203
+ },
204
+ {
205
+ name: 'name containing default placeholder',
206
+ filename: 'package.json',
207
+ code: '{ "name": "@company/n8n-nodes-<...>", "version": "1.0.0" }',
208
+ errors: [
209
+ {
210
+ messageId: 'defaultPlaceholderName',
211
+ data: { packageName: '@company/n8n-nodes-<...>' },
212
+ },
213
+ ],
214
+ },
188
215
  ],
189
216
  });
@@ -15,6 +15,10 @@ export const PackageNameConventionRule = createRule({
15
15
  renameTo: "Rename to '{{suggestedName}}'",
16
16
  invalidPackageName:
17
17
  'Package name "{{ packageName }}" must follow the convention "n8n-nodes-[PACKAGE-NAME]" or "@[AUTHOR]/n8n-nodes-[PACKAGE-NAME]"',
18
+ missingName:
19
+ 'Package name is missing. Add a "name" field following the convention "n8n-nodes-[PACKAGE-NAME]" or "@[AUTHOR]/n8n-nodes-[PACKAGE-NAME]"',
20
+ defaultPlaceholderName:
21
+ 'Package name "{{ packageName }}" still contains the default placeholder. Replace "<...>" with your package name',
18
22
  },
19
23
  schema: [],
20
24
  hasSuggestions: true,
@@ -34,6 +38,10 @@ export const PackageNameConventionRule = createRule({
34
38
  const nameProperty = findJsonProperty(node, 'name');
35
39
 
36
40
  if (!nameProperty) {
41
+ context.report({
42
+ node,
43
+ messageId: 'missingName',
44
+ });
37
45
  return;
38
46
  }
39
47
 
@@ -44,6 +52,17 @@ export const PackageNameConventionRule = createRule({
44
52
  const packageName = nameProperty.value.value;
45
53
  const packageNameStr = typeof packageName === 'string' ? packageName : null;
46
54
 
55
+ if (packageNameStr && isDefaultPlaceholderName(packageNameStr)) {
56
+ context.report({
57
+ node: nameProperty,
58
+ messageId: 'defaultPlaceholderName',
59
+ data: {
60
+ packageName: packageNameStr,
61
+ },
62
+ });
63
+ return;
64
+ }
65
+
47
66
  if (!packageNameStr || !isValidPackageName(packageNameStr)) {
48
67
  const suggestions: ReportSuggestionArray<'invalidPackageName' | 'renameTo'> = [];
49
68
 
@@ -75,6 +94,10 @@ export const PackageNameConventionRule = createRule({
75
94
  },
76
95
  });
77
96
 
97
+ function isDefaultPlaceholderName(name: string): boolean {
98
+ return name.includes('<...>');
99
+ }
100
+
78
101
  function isValidPackageName(name: string): boolean {
79
102
  const unscoped = /^n8n-nodes-.+$/;
80
103
  const scoped = /^@.+\/n8n-nodes-.+$/;
@@ -1,5 +1,4 @@
1
- import { DefinitionType } from '@typescript-eslint/scope-manager';
2
- import { AST_NODE_TYPES, type TSESTree } from '@typescript-eslint/utils';
1
+ import { AST_NODE_TYPES, TSESLint, type TSESTree } from '@typescript-eslint/utils';
3
2
 
4
3
  import { isFileType } from '../utils/index.js';
5
4
  import { createRule } from '../utils/rule-creator.js';
@@ -68,7 +67,9 @@ export const RequireNodeApiErrorRule = createRule({
68
67
  const scope = context.sourceCode.getScope(node);
69
68
  const ref = scope.references.find((r) => r.identifier === argument);
70
69
  const isCatchParam =
71
- ref?.resolved?.defs.some((def) => def.type === DefinitionType.CatchClause) ?? false;
70
+ ref?.resolved?.defs.some(
71
+ (def) => def.type === TSESLint.Scope.DefinitionType.CatchClause,
72
+ ) ?? false;
72
73
 
73
74
  if (isCatchParam) {
74
75
  context.report({ node, messageId: 'useNodeApiError' });
@@ -1,4 +1,4 @@
1
- import { TSESTree } from '@typescript-eslint/types';
1
+ import { TSESTree } from '@typescript-eslint/utils';
2
2
  import type { ReportSuggestionArray } from '@typescript-eslint/utils/ts-eslint';
3
3
 
4
4
  import {
@@ -0,0 +1,56 @@
1
+ import { RuleTester } from '@typescript-eslint/rule-tester';
2
+
3
+ import { ValidDescriptionRule } from './valid-description.js';
4
+
5
+ const ruleTester = new RuleTester();
6
+
7
+ ruleTester.run('valid-description', ValidDescriptionRule, {
8
+ valid: [
9
+ {
10
+ name: 'description is a non-empty string',
11
+ filename: 'package.json',
12
+ code: '{ "name": "n8n-nodes-example", "description": "Nodes for talking to Acme Corp\'s API" }',
13
+ },
14
+ {
15
+ name: 'non-package.json file is ignored',
16
+ filename: 'some-config.json',
17
+ code: '{ "name": "n8n-nodes-example" }',
18
+ },
19
+ {
20
+ name: 'nested objects with empty description are not checked',
21
+ filename: 'package.json',
22
+ code: '{ "name": "n8n-nodes-example", "description": "Real description", "config": { "description": "" } }',
23
+ },
24
+ {
25
+ name: 'objects inside arrays (e.g. contributors) are not flagged',
26
+ filename: 'package.json',
27
+ code: `{
28
+ "name": "n8n-nodes-example",
29
+ "description": "Real description",
30
+ "contributors": [
31
+ { "name": "Alice", "description": "" }
32
+ ]
33
+ }`,
34
+ },
35
+ ],
36
+ invalid: [
37
+ {
38
+ name: 'description field is missing entirely',
39
+ filename: 'package.json',
40
+ code: '{ "name": "n8n-nodes-example", "version": "1.0.0" }',
41
+ errors: [{ messageId: 'missingDescription' }],
42
+ },
43
+ {
44
+ name: 'description is an empty string (starter template default)',
45
+ filename: 'package.json',
46
+ code: '{ "name": "n8n-nodes-example", "description": "" }',
47
+ errors: [{ messageId: 'emptyDescription' }],
48
+ },
49
+ {
50
+ name: 'empty package.json object',
51
+ filename: 'package.json',
52
+ code: '{}',
53
+ errors: [{ messageId: 'missingDescription' }],
54
+ },
55
+ ],
56
+ });
@@ -0,0 +1,57 @@
1
+ import type { TSESTree } from '@typescript-eslint/utils';
2
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
3
+
4
+ import { createRule, findJsonProperty } from '../utils/index.js';
5
+
6
+ export const ValidDescriptionRule = createRule({
7
+ name: 'valid-description',
8
+ meta: {
9
+ type: 'problem',
10
+ docs: {
11
+ description: 'Require a non-empty "description" field in community node package.json',
12
+ },
13
+ messages: {
14
+ missingDescription:
15
+ 'The package.json must have a "description" field describing what the community node package does.',
16
+ emptyDescription:
17
+ 'The "description" field must be a non-empty string. Replace the placeholder with a real description of your community node package.',
18
+ },
19
+ schema: [],
20
+ },
21
+ defaultOptions: [],
22
+ create(context) {
23
+ if (!context.filename.endsWith('package.json')) {
24
+ return {};
25
+ }
26
+
27
+ return {
28
+ ObjectExpression(node: TSESTree.ObjectExpression) {
29
+ if (node.parent?.type !== AST_NODE_TYPES.ExpressionStatement) {
30
+ return;
31
+ }
32
+
33
+ const descriptionProp = findJsonProperty(node, 'description');
34
+
35
+ if (!descriptionProp) {
36
+ context.report({
37
+ node,
38
+ messageId: 'missingDescription',
39
+ });
40
+ return;
41
+ }
42
+
43
+ if (descriptionProp.value.type !== AST_NODE_TYPES.Literal) {
44
+ return;
45
+ }
46
+
47
+ const value = descriptionProp.value.value;
48
+ if (typeof value !== 'string' || value === '') {
49
+ context.report({
50
+ node: descriptionProp,
51
+ messageId: 'emptyDescription',
52
+ });
53
+ }
54
+ },
55
+ };
56
+ },
57
+ });