@n8n/eslint-plugin-community-nodes 0.23.0 → 0.25.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 (108) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/README.md +39 -52
  3. package/dist/plugin.d.ts +0 -78
  4. package/dist/plugin.d.ts.map +1 -1
  5. package/dist/plugin.js +0 -26
  6. package/dist/plugin.js.map +1 -1
  7. package/dist/rules/index.d.ts +0 -13
  8. package/dist/rules/index.d.ts.map +1 -1
  9. package/dist/rules/index.js +0 -26
  10. package/dist/rules/index.js.map +1 -1
  11. package/dist/typecheck.tsbuildinfo +1 -1
  12. package/docs/rules/no-forbidden-lifecycle-scripts.md +1 -1
  13. package/package.json +4 -4
  14. package/src/plugin.ts +0 -26
  15. package/src/rules/index.ts +0 -26
  16. package/src/rules/no-forbidden-lifecycle-scripts.test.ts +2 -2
  17. package/.turbo/turbo-build$colon$unchecked.log +0 -4
  18. package/dist/rules/cred-class-name-field-conventions.d.ts +0 -2
  19. package/dist/rules/cred-class-name-field-conventions.d.ts.map +0 -1
  20. package/dist/rules/cred-class-name-field-conventions.js +0 -89
  21. package/dist/rules/cred-class-name-field-conventions.js.map +0 -1
  22. package/dist/rules/cred-class-name-suffix.d.ts +0 -2
  23. package/dist/rules/cred-class-name-suffix.d.ts.map +0 -1
  24. package/dist/rules/cred-class-name-suffix.js +0 -53
  25. package/dist/rules/cred-class-name-suffix.js.map +0 -1
  26. package/dist/rules/cred-filename-against-convention.d.ts +0 -2
  27. package/dist/rules/cred-filename-against-convention.d.ts.map +0 -1
  28. package/dist/rules/cred-filename-against-convention.js +0 -42
  29. package/dist/rules/cred-filename-against-convention.js.map +0 -1
  30. package/dist/rules/no-asterisk-in-option-names.d.ts +0 -2
  31. package/dist/rules/no-asterisk-in-option-names.d.ts.map +0 -1
  32. package/dist/rules/no-asterisk-in-option-names.js +0 -90
  33. package/dist/rules/no-asterisk-in-option-names.js.map +0 -1
  34. package/dist/rules/no-duplicate-param-options.d.ts +0 -2
  35. package/dist/rules/no-duplicate-param-options.d.ts.map +0 -1
  36. package/dist/rules/no-duplicate-param-options.js +0 -97
  37. package/dist/rules/no-duplicate-param-options.js.map +0 -1
  38. package/dist/rules/node-class-description-name-camelcase.d.ts +0 -2
  39. package/dist/rules/node-class-description-name-camelcase.d.ts.map +0 -1
  40. package/dist/rules/node-class-description-name-camelcase.js +0 -92
  41. package/dist/rules/node-class-description-name-camelcase.js.map +0 -1
  42. package/dist/rules/node-filename-against-convention.d.ts +0 -2
  43. package/dist/rules/node-filename-against-convention.d.ts.map +0 -1
  44. package/dist/rules/node-filename-against-convention.js +0 -61
  45. package/dist/rules/node-filename-against-convention.js.map +0 -1
  46. package/dist/rules/node-operation-error-itemindex.d.ts +0 -12
  47. package/dist/rules/node-operation-error-itemindex.d.ts.map +0 -1
  48. package/dist/rules/node-operation-error-itemindex.js +0 -184
  49. package/dist/rules/node-operation-error-itemindex.js.map +0 -1
  50. package/dist/rules/options-sorted-alphabetically.d.ts +0 -2
  51. package/dist/rules/options-sorted-alphabetically.d.ts.map +0 -1
  52. package/dist/rules/options-sorted-alphabetically.js +0 -95
  53. package/dist/rules/options-sorted-alphabetically.js.map +0 -1
  54. package/dist/rules/require-community-node-keyword.d.ts +0 -2
  55. package/dist/rules/require-community-node-keyword.d.ts.map +0 -1
  56. package/dist/rules/require-community-node-keyword.js +0 -65
  57. package/dist/rules/require-community-node-keyword.js.map +0 -1
  58. package/dist/rules/require-continue-on-fail.d.ts +0 -2
  59. package/dist/rules/require-continue-on-fail.d.ts.map +0 -1
  60. package/dist/rules/require-continue-on-fail.js +0 -76
  61. package/dist/rules/require-continue-on-fail.js.map +0 -1
  62. package/dist/rules/require-mit-license.d.ts +0 -2
  63. package/dist/rules/require-mit-license.d.ts.map +0 -1
  64. package/dist/rules/require-mit-license.js +0 -57
  65. package/dist/rules/require-mit-license.js.map +0 -1
  66. package/dist/rules/require-param-default.d.ts +0 -15
  67. package/dist/rules/require-param-default.d.ts.map +0 -1
  68. package/dist/rules/require-param-default.js +0 -75
  69. package/dist/rules/require-param-default.js.map +0 -1
  70. package/docs/rules/cred-class-name-field-conventions.md +0 -38
  71. package/docs/rules/cred-class-name-suffix.md +0 -46
  72. package/docs/rules/cred-filename-against-convention.md +0 -42
  73. package/docs/rules/no-asterisk-in-option-names.md +0 -61
  74. package/docs/rules/no-duplicate-param-options.md +0 -46
  75. package/docs/rules/node-class-description-name-camelcase.md +0 -41
  76. package/docs/rules/node-filename-against-convention.md +0 -50
  77. package/docs/rules/node-operation-error-itemindex.md +0 -81
  78. package/docs/rules/options-sorted-alphabetically.md +0 -63
  79. package/docs/rules/require-community-node-keyword.md +0 -45
  80. package/docs/rules/require-continue-on-fail.md +0 -56
  81. package/docs/rules/require-mit-license.md +0 -38
  82. package/docs/rules/require-param-default.md +0 -60
  83. package/src/rules/cred-class-name-field-conventions.test.ts +0 -121
  84. package/src/rules/cred-class-name-field-conventions.ts +0 -102
  85. package/src/rules/cred-class-name-suffix.test.ts +0 -74
  86. package/src/rules/cred-class-name-suffix.ts +0 -57
  87. package/src/rules/cred-filename-against-convention.test.ts +0 -72
  88. package/src/rules/cred-filename-against-convention.ts +0 -48
  89. package/src/rules/no-asterisk-in-option-names.test.ts +0 -185
  90. package/src/rules/no-asterisk-in-option-names.ts +0 -111
  91. package/src/rules/no-duplicate-param-options.test.ts +0 -125
  92. package/src/rules/no-duplicate-param-options.ts +0 -125
  93. package/src/rules/node-class-description-name-camelcase.test.ts +0 -121
  94. package/src/rules/node-class-description-name-camelcase.ts +0 -114
  95. package/src/rules/node-filename-against-convention.test.ts +0 -115
  96. package/src/rules/node-filename-against-convention.ts +0 -76
  97. package/src/rules/node-operation-error-itemindex.test.ts +0 -280
  98. package/src/rules/node-operation-error-itemindex.ts +0 -223
  99. package/src/rules/options-sorted-alphabetically.test.ts +0 -468
  100. package/src/rules/options-sorted-alphabetically.ts +0 -129
  101. package/src/rules/require-community-node-keyword.test.ts +0 -82
  102. package/src/rules/require-community-node-keyword.ts +0 -78
  103. package/src/rules/require-continue-on-fail.test.ts +0 -129
  104. package/src/rules/require-continue-on-fail.ts +0 -88
  105. package/src/rules/require-mit-license.test.ts +0 -67
  106. package/src/rules/require-mit-license.ts +0 -65
  107. package/src/rules/require-param-default.test.ts +0 -227
  108. package/src/rules/require-param-default.ts +0 -89
@@ -1,125 +0,0 @@
1
- import type { TSESTree } from '@typescript-eslint/utils';
2
- import { AST_NODE_TYPES } from '@typescript-eslint/utils';
3
-
4
- import {
5
- isNodeTypeClass,
6
- findClassProperty,
7
- findObjectProperty,
8
- getStringLiteralValue,
9
- getLiteralValue,
10
- isFileType,
11
- createRule,
12
- } from '../utils/index.js';
13
-
14
- export const NoDuplicateParamOptionsRule = createRule({
15
- name: 'no-duplicate-param-options',
16
- meta: {
17
- type: 'problem',
18
- docs: {
19
- description: 'Disallow duplicate option names or values within a single node parameter',
20
- },
21
- messages: {
22
- duplicateName: 'Duplicate option name "{{ value }}" in parameter "{{ displayName }}".',
23
- duplicateValue: 'Duplicate option value "{{ value }}" in parameter "{{ displayName }}".',
24
- },
25
- schema: [],
26
- },
27
- defaultOptions: [],
28
- create(context) {
29
- if (!isFileType(context.filename, '.node.ts')) {
30
- return {};
31
- }
32
-
33
- const checkProperty = (property: TSESTree.ObjectExpression): void => {
34
- const typeProperty = findObjectProperty(property, 'type');
35
- const type = typeProperty ? getStringLiteralValue(typeProperty.value) : null;
36
- if (type !== 'options' && type !== 'multiOptions') {
37
- return;
38
- }
39
-
40
- const optionsProperty = findObjectProperty(property, 'options');
41
- if (
42
- !optionsProperty?.value ||
43
- optionsProperty.value.type !== AST_NODE_TYPES.ArrayExpression
44
- ) {
45
- return;
46
- }
47
-
48
- const displayNameProperty = findObjectProperty(property, 'displayName');
49
- const displayName =
50
- (displayNameProperty ? getStringLiteralValue(displayNameProperty.value) : null) ??
51
- 'unknown';
52
-
53
- const seenNames = new Set<string>();
54
- const seenValues = new Set<string | number | boolean>();
55
-
56
- for (const option of optionsProperty.value.elements) {
57
- if (!option || option.type !== AST_NODE_TYPES.ObjectExpression) {
58
- continue;
59
- }
60
-
61
- const nameProperty = findObjectProperty(option, 'name');
62
- const name = nameProperty ? getStringLiteralValue(nameProperty.value) : null;
63
- if (name !== null) {
64
- if (seenNames.has(name)) {
65
- context.report({
66
- node: nameProperty!.value,
67
- messageId: 'duplicateName',
68
- data: { value: name, displayName },
69
- });
70
- }
71
- seenNames.add(name);
72
- }
73
-
74
- const valueProperty = findObjectProperty(option, 'value');
75
- const value = valueProperty ? getLiteralValue(valueProperty.value) : null;
76
- if (value !== null) {
77
- if (seenValues.has(value)) {
78
- context.report({
79
- node: valueProperty!.value,
80
- messageId: 'duplicateValue',
81
- data: { value: String(value), displayName },
82
- });
83
- }
84
- seenValues.add(value);
85
- }
86
- }
87
- };
88
-
89
- const analyzeNodeDescription = (descriptionValue: TSESTree.Expression | null): void => {
90
- if (!descriptionValue || descriptionValue.type !== AST_NODE_TYPES.ObjectExpression) {
91
- return;
92
- }
93
-
94
- const propertiesProperty = findObjectProperty(descriptionValue, 'properties');
95
- if (
96
- !propertiesProperty?.value ||
97
- propertiesProperty.value.type !== AST_NODE_TYPES.ArrayExpression
98
- ) {
99
- return;
100
- }
101
-
102
- for (const property of propertiesProperty.value.elements) {
103
- if (!property || property.type !== AST_NODE_TYPES.ObjectExpression) {
104
- continue;
105
- }
106
- checkProperty(property);
107
- }
108
- };
109
-
110
- return {
111
- ClassDeclaration(node) {
112
- if (!isNodeTypeClass(node)) {
113
- return;
114
- }
115
-
116
- const descriptionProperty = findClassProperty(node, 'description');
117
- if (!descriptionProperty) {
118
- return;
119
- }
120
-
121
- analyzeNodeDescription(descriptionProperty.value);
122
- },
123
- };
124
- },
125
- });
@@ -1,121 +0,0 @@
1
- import { RuleTester } from '@typescript-eslint/rule-tester';
2
-
3
- import { NodeClassDescriptionNameCamelCaseRule } from './node-class-description-name-camelcase.js';
4
-
5
- const ruleTester = new RuleTester();
6
-
7
- function createNodeCode(name: string): string {
8
- return `
9
- import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
10
-
11
- export class TestNode implements INodeType {
12
- description: INodeTypeDescription = {
13
- displayName: 'Test Node',
14
- name: '${name}',
15
- group: ['input'],
16
- version: 1,
17
- description: 'A test node',
18
- defaults: { name: 'Test Node' },
19
- inputs: [],
20
- outputs: [],
21
- properties: [],
22
- };
23
- }`;
24
- }
25
-
26
- function createNodeCodeWithoutName(): string {
27
- return `
28
- import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
29
-
30
- export class TestNode implements INodeType {
31
- description: INodeTypeDescription = {
32
- displayName: 'Test Node',
33
- group: ['input'],
34
- version: 1,
35
- description: 'A test node',
36
- defaults: { name: 'Test Node' },
37
- inputs: [],
38
- outputs: [],
39
- properties: [],
40
- };
41
- }`;
42
- }
43
-
44
- function createRegularClass(): string {
45
- return `
46
- export class SomeHelper {
47
- name = 'My Helper';
48
- }`;
49
- }
50
-
51
- ruleTester.run('node-class-description-name-camelcase', NodeClassDescriptionNameCamelCaseRule, {
52
- valid: [
53
- {
54
- name: 'single lowercase word',
55
- filename: '/tmp/Github.node.ts',
56
- code: createNodeCode('github'),
57
- },
58
- {
59
- name: 'camelCase multi-word name',
60
- filename: '/tmp/GoogleSheets.node.ts',
61
- code: createNodeCode('googleSheets'),
62
- },
63
- {
64
- name: 'camelCase name with digits',
65
- filename: '/tmp/Oauth2.node.ts',
66
- code: createNodeCode('myNode2'),
67
- },
68
- {
69
- name: 'class not implementing INodeType is ignored',
70
- filename: '/tmp/Github.node.ts',
71
- code: createRegularClass(),
72
- },
73
- {
74
- name: 'non-.node.ts file is ignored',
75
- filename: '/tmp/Github.ts',
76
- code: createNodeCode('My Node'),
77
- },
78
- {
79
- name: 'missing description.name is ignored',
80
- filename: '/tmp/Github.node.ts',
81
- code: createNodeCodeWithoutName(),
82
- },
83
- ],
84
- invalid: [
85
- {
86
- name: 'PascalCase first letter is lowercased',
87
- filename: '/tmp/Github.node.ts',
88
- code: createNodeCode('MyNode'),
89
- errors: [{ messageId: 'notCamelCase', data: { value: 'MyNode' } }],
90
- output: createNodeCode('myNode'),
91
- },
92
- {
93
- name: 'spaces are removed and camelCased',
94
- filename: '/tmp/Github.node.ts',
95
- code: createNodeCode('My Node'),
96
- errors: [{ messageId: 'notCamelCase', data: { value: 'My Node' } }],
97
- output: createNodeCode('myNode'),
98
- },
99
- {
100
- name: 'hyphen separators are removed and camelCased',
101
- filename: '/tmp/Github.node.ts',
102
- code: createNodeCode('my-node'),
103
- errors: [{ messageId: 'notCamelCase', data: { value: 'my-node' } }],
104
- output: createNodeCode('myNode'),
105
- },
106
- {
107
- name: 'underscore separators are removed and camelCased',
108
- filename: '/tmp/Github.node.ts',
109
- code: createNodeCode('my_cool_node'),
110
- errors: [{ messageId: 'notCamelCase', data: { value: 'my_cool_node' } }],
111
- output: createNodeCode('myCoolNode'),
112
- },
113
- {
114
- name: 'names starting with a digit are reported without an autofix',
115
- filename: '/tmp/Github.node.ts',
116
- code: createNodeCode('123node'),
117
- errors: [{ messageId: 'notCamelCase', data: { value: '123node' } }],
118
- output: null,
119
- },
120
- ],
121
- });
@@ -1,114 +0,0 @@
1
- import { TSESTree } from '@typescript-eslint/utils';
2
-
3
- import {
4
- isNodeTypeClass,
5
- findClassProperty,
6
- findObjectProperty,
7
- getStringLiteralValue,
8
- isFileType,
9
- createRule,
10
- } from '../utils/index.js';
11
-
12
- // `description.name` must be camelCase: a lowercase letter followed by
13
- // letters/digits, with no separators.
14
- const CAMEL_CASE_PATTERN = /^[a-z][a-zA-Z0-9]*$/;
15
-
16
- /**
17
- * Converts an arbitrary node name to camelCase by splitting on any
18
- * non-alphanumeric separators, upper-casing the first character of each
19
- * subsequent segment, and lower-casing the very first character.
20
- * Examples: `My Node` -> `myNode`, `my-node` -> `myNode`, `GitHub` -> `gitHub`.
21
- */
22
- function toCamelCase(value: string): string {
23
- const segments = value.split(/[^a-zA-Z0-9]+/).filter(Boolean);
24
- if (segments.length === 0) {
25
- return value;
26
- }
27
-
28
- const joined = segments
29
- .map((segment, index) =>
30
- index === 0 ? segment : segment.charAt(0).toUpperCase() + segment.slice(1),
31
- )
32
- .join('');
33
-
34
- return joined.charAt(0).toLowerCase() + joined.slice(1);
35
- }
36
-
37
- // Serialize a value as a string literal using the original quote character,
38
- // escaping any characters that would otherwise break the literal so the
39
- // autofix never emits invalid code.
40
- function toStringLiteral(value: string, quote: string): string {
41
- const escaped = value
42
- .replace(/\\/g, '\\\\')
43
- .replace(/\n/g, '\\n')
44
- .replace(/\r/g, '\\r')
45
- .split(quote)
46
- .join(`\\${quote}`);
47
- return `${quote}${escaped}${quote}`;
48
- }
49
-
50
- export const NodeClassDescriptionNameCamelCaseRule = createRule({
51
- name: 'node-class-description-name-camelcase',
52
- meta: {
53
- type: 'problem',
54
- docs: {
55
- description: 'Node class `description.name` must be camelCase',
56
- },
57
- messages: {
58
- notCamelCase: "Node `description.name` '{{value}}' must be camelCase",
59
- },
60
- fixable: 'code',
61
- schema: [],
62
- },
63
- defaultOptions: [],
64
- create(context) {
65
- if (!isFileType(context.filename, '.node.ts')) {
66
- return {};
67
- }
68
-
69
- return {
70
- ClassDeclaration(node) {
71
- if (!isNodeTypeClass(node)) {
72
- return;
73
- }
74
-
75
- const descriptionProperty = findClassProperty(node, 'description');
76
- if (descriptionProperty?.value?.type !== TSESTree.AST_NODE_TYPES.ObjectExpression) {
77
- return;
78
- }
79
-
80
- const nameProperty = findObjectProperty(descriptionProperty.value, 'name');
81
- if (!nameProperty) {
82
- return;
83
- }
84
-
85
- const nameValue = getStringLiteralValue(nameProperty.value);
86
- if (nameValue === null) {
87
- return;
88
- }
89
-
90
- if (CAMEL_CASE_PATTERN.test(nameValue)) {
91
- return;
92
- }
93
-
94
- const valueNode = nameProperty.value;
95
- const fixedValue = toCamelCase(nameValue);
96
- // Only offer an autofix when it actually yields a valid camelCase
97
- // value (e.g. names starting with a digit cannot be repaired).
98
- const canFix = fixedValue !== nameValue && CAMEL_CASE_PATTERN.test(fixedValue);
99
-
100
- context.report({
101
- node: valueNode,
102
- messageId: 'notCamelCase',
103
- data: { value: nameValue },
104
- fix: canFix
105
- ? (fixer) => {
106
- const quote = context.sourceCode.getText(valueNode).charAt(0);
107
- return fixer.replaceText(valueNode, toStringLiteral(fixedValue, quote));
108
- }
109
- : undefined,
110
- });
111
- },
112
- };
113
- },
114
- });
@@ -1,115 +0,0 @@
1
- import { RuleTester } from '@typescript-eslint/rule-tester';
2
-
3
- import { NodeFilenameAgainstConventionRule } from './node-filename-against-convention.js';
4
-
5
- const ruleTester = new RuleTester();
6
-
7
- function createNodeCode(name: string): string {
8
- return `
9
- import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
10
-
11
- export class TestNode implements INodeType {
12
- description: INodeTypeDescription = {
13
- displayName: 'Test Node',
14
- name: '${name}',
15
- group: ['input'],
16
- version: 1,
17
- description: 'A test node',
18
- defaults: { name: 'Test Node' },
19
- inputs: [],
20
- outputs: [],
21
- properties: [],
22
- };
23
- }`;
24
- }
25
-
26
- function createNodeCodeWithoutName(): string {
27
- return `
28
- import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
29
-
30
- export class TestNode implements INodeType {
31
- description: INodeTypeDescription = {
32
- displayName: 'Test Node',
33
- group: ['input'],
34
- version: 1,
35
- description: 'A test node',
36
- defaults: { name: 'Test Node' },
37
- inputs: [],
38
- outputs: [],
39
- properties: [],
40
- };
41
- }`;
42
- }
43
-
44
- function createRegularClass(): string {
45
- return `
46
- export class SomeHelper {
47
- name = 'github';
48
- }`;
49
- }
50
-
51
- ruleTester.run('node-filename-against-convention', NodeFilenameAgainstConventionRule, {
52
- valid: [
53
- {
54
- name: 'filename matches PascalCased description.name',
55
- filename: '/tmp/Github.node.ts',
56
- code: createNodeCode('github'),
57
- },
58
- {
59
- name: 'filename with version suffix is accepted',
60
- filename: '/tmp/GithubV2.node.ts',
61
- code: createNodeCode('github'),
62
- },
63
- {
64
- name: 'filename with multi-digit version suffix is accepted',
65
- filename: '/tmp/GithubV10.node.ts',
66
- code: createNodeCode('github'),
67
- },
68
- {
69
- name: 'multi-word camelCase name maps to PascalCase filename',
70
- filename: '/tmp/GoogleSheets.node.ts',
71
- code: createNodeCode('googleSheets'),
72
- },
73
- {
74
- name: 'class not implementing INodeType is ignored',
75
- filename: '/tmp/Github.node.ts',
76
- code: createRegularClass(),
77
- },
78
- {
79
- name: 'non-.node.ts file is ignored',
80
- filename: '/tmp/Github.ts',
81
- code: createNodeCode('mismatch'),
82
- },
83
- {
84
- name: 'missing description.name is ignored',
85
- filename: '/tmp/Github.node.ts',
86
- code: createNodeCodeWithoutName(),
87
- },
88
- ],
89
- invalid: [
90
- {
91
- name: 'filename does not match description.name',
92
- filename: '/tmp/Github.node.ts',
93
- code: createNodeCode('gitlab'),
94
- errors: [{ messageId: 'renameFile', data: { expected: 'Gitlab.node.ts' } }],
95
- },
96
- {
97
- name: 'filename is lowercased',
98
- filename: '/tmp/github.node.ts',
99
- code: createNodeCode('github'),
100
- errors: [{ messageId: 'renameFile', data: { expected: 'Github.node.ts' } }],
101
- },
102
- {
103
- name: 'filename has wrong internal casing',
104
- filename: '/tmp/GitHub.node.ts',
105
- code: createNodeCode('github'),
106
- errors: [{ messageId: 'renameFile', data: { expected: 'Github.node.ts' } }],
107
- },
108
- {
109
- name: 'version suffix does not excuse a mismatched base name',
110
- filename: '/tmp/GitlabV2.node.ts',
111
- code: createNodeCode('github'),
112
- errors: [{ messageId: 'renameFile', data: { expected: 'Github.node.ts' } }],
113
- },
114
- ],
115
- });
@@ -1,76 +0,0 @@
1
- import { TSESTree } from '@typescript-eslint/utils';
2
- import * as path from 'node:path';
3
-
4
- import {
5
- isNodeTypeClass,
6
- findClassProperty,
7
- findObjectProperty,
8
- getStringLiteralValue,
9
- isFileType,
10
- createRule,
11
- } from '../utils/index.js';
12
-
13
- /**
14
- * Converts a `description.name` to the expected node file basename by
15
- * upper-casing the first character. The source of truth is `description.name`,
16
- * not the class name. Example: `github` -> `Github`.
17
- */
18
- function toExpectedBaseName(name: string): string {
19
- return name.charAt(0).toUpperCase() + name.slice(1);
20
- }
21
-
22
- export const NodeFilenameAgainstConventionRule = createRule({
23
- name: 'node-filename-against-convention',
24
- meta: {
25
- type: 'problem',
26
- docs: {
27
- description: 'Node filename must match the node `description.name`',
28
- },
29
- messages: {
30
- renameFile: 'Node filename must match `description.name`. Rename file to "{{expected}}".',
31
- },
32
- schema: [],
33
- },
34
- defaultOptions: [],
35
- create(context) {
36
- if (!isFileType(context.filename, '.node.ts')) {
37
- return {};
38
- }
39
-
40
- return {
41
- ClassDeclaration(node) {
42
- if (!isNodeTypeClass(node)) {
43
- return;
44
- }
45
-
46
- const descriptionProperty = findClassProperty(node, 'description');
47
- if (descriptionProperty?.value?.type !== TSESTree.AST_NODE_TYPES.ObjectExpression) {
48
- return;
49
- }
50
-
51
- const nameProperty = findObjectProperty(descriptionProperty.value, 'name');
52
- if (!nameProperty) {
53
- return;
54
- }
55
-
56
- const name = getStringLiteralValue(nameProperty.value);
57
- if (!name) {
58
- return;
59
- }
60
-
61
- const expectedBaseName = toExpectedBaseName(name);
62
- // Strip the `.node.ts` extension and any trailing `V<digits>` version
63
- // suffix so versioned files (e.g. `GithubV2.node.ts`) are accepted.
64
- const actualBaseName = path.basename(context.filename, '.node.ts').replace(/V\d+$/, '');
65
-
66
- if (actualBaseName !== expectedBaseName) {
67
- context.report({
68
- node: nameProperty.value,
69
- messageId: 'renameFile',
70
- data: { expected: `${expectedBaseName}.node.ts` },
71
- });
72
- }
73
- },
74
- };
75
- },
76
- });