@n8n/eslint-plugin-community-nodes 0.4.0 → 0.6.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.
- package/.turbo/turbo-build.log +2 -2
- package/README.md +60 -0
- package/dist/plugin.d.ts +144 -28
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +28 -25
- package/dist/plugin.js.map +1 -1
- package/dist/rules/credential-documentation-url.d.ts +7 -0
- package/dist/rules/credential-documentation-url.d.ts.map +1 -0
- package/dist/rules/credential-documentation-url.js +100 -0
- package/dist/rules/credential-documentation-url.js.map +1 -0
- package/dist/rules/credential-password-field.d.ts +1 -2
- package/dist/rules/credential-password-field.d.ts.map +1 -1
- package/dist/rules/credential-password-field.js +25 -14
- package/dist/rules/credential-password-field.js.map +1 -1
- package/dist/rules/credential-test-required.d.ts +1 -2
- package/dist/rules/credential-test-required.d.ts.map +1 -1
- package/dist/rules/credential-test-required.js +43 -5
- package/dist/rules/credential-test-required.js.map +1 -1
- package/dist/rules/icon-validation.d.ts +1 -2
- package/dist/rules/icon-validation.d.ts.map +1 -1
- package/dist/rules/icon-validation.js +81 -15
- package/dist/rules/icon-validation.js.map +1 -1
- package/dist/rules/index.d.ts +9 -5
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +7 -5
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/no-credential-reuse.d.ts +1 -2
- package/dist/rules/no-credential-reuse.d.ts.map +1 -1
- package/dist/rules/no-credential-reuse.js +33 -4
- package/dist/rules/no-credential-reuse.js.map +1 -1
- package/dist/rules/no-deprecated-workflow-functions.d.ts +1 -2
- package/dist/rules/no-deprecated-workflow-functions.d.ts.map +1 -1
- package/dist/rules/no-deprecated-workflow-functions.js +38 -10
- package/dist/rules/no-deprecated-workflow-functions.js.map +1 -1
- package/dist/rules/no-restricted-globals.d.ts +2 -2
- package/dist/rules/no-restricted-globals.d.ts.map +1 -1
- package/dist/rules/no-restricted-globals.js +5 -3
- package/dist/rules/no-restricted-globals.js.map +1 -1
- package/dist/rules/no-restricted-imports.d.ts +1 -2
- package/dist/rules/no-restricted-imports.d.ts.map +1 -1
- package/dist/rules/no-restricted-imports.js +3 -3
- package/dist/rules/no-restricted-imports.js.map +1 -1
- package/dist/rules/node-usable-as-tool.d.ts +1 -2
- package/dist/rules/node-usable-as-tool.d.ts.map +1 -1
- package/dist/rules/node-usable-as-tool.js +6 -5
- package/dist/rules/node-usable-as-tool.js.map +1 -1
- package/dist/rules/package-name-convention.d.ts +1 -2
- package/dist/rules/package-name-convention.d.ts.map +1 -1
- package/dist/rules/package-name-convention.js +38 -2
- package/dist/rules/package-name-convention.js.map +1 -1
- package/dist/rules/resource-operation-pattern.d.ts +1 -2
- package/dist/rules/resource-operation-pattern.d.ts.map +1 -1
- package/dist/rules/resource-operation-pattern.js +9 -7
- package/dist/rules/resource-operation-pattern.js.map +1 -1
- package/dist/utils/ast-utils.d.ts +2 -1
- package/dist/utils/ast-utils.d.ts.map +1 -1
- package/dist/utils/ast-utils.js +37 -19
- package/dist/utils/ast-utils.js.map +1 -1
- package/dist/utils/file-utils.d.ts +1 -0
- package/dist/utils/file-utils.d.ts.map +1 -1
- package/dist/utils/file-utils.js +54 -14
- package/dist/utils/file-utils.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/rule-creator.d.ts +3 -0
- package/dist/utils/rule-creator.d.ts.map +1 -0
- package/dist/utils/rule-creator.js +5 -0
- package/dist/utils/rule-creator.js.map +1 -0
- package/docs/rules/credential-documentation-url.md +94 -0
- package/docs/rules/credential-password-field.md +45 -0
- package/docs/rules/credential-test-required.md +58 -0
- package/docs/rules/icon-validation.md +67 -0
- package/docs/rules/no-credential-reuse.md +82 -0
- package/docs/rules/no-deprecated-workflow-functions.md +61 -0
- package/docs/rules/no-restricted-globals.md +44 -0
- package/docs/rules/no-restricted-imports.md +47 -0
- package/docs/rules/node-usable-as-tool.md +43 -0
- package/docs/rules/package-name-convention.md +52 -0
- package/docs/rules/resource-operation-pattern.md +84 -0
- package/eslint.config.mjs +27 -0
- package/package.json +25 -4
- package/src/plugin.ts +30 -26
- package/src/rules/credential-documentation-url.test.ts +306 -0
- package/src/rules/credential-documentation-url.ts +129 -0
- package/src/rules/credential-password-field.test.ts +1 -0
- package/src/rules/credential-password-field.ts +34 -16
- package/src/rules/credential-test-required.test.ts +84 -57
- package/src/rules/credential-test-required.ts +51 -5
- package/src/rules/icon-validation.test.ts +97 -14
- package/src/rules/icon-validation.ts +95 -14
- package/src/rules/index.ts +8 -5
- package/src/rules/no-credential-reuse.test.ts +306 -58
- package/src/rules/no-credential-reuse.ts +43 -3
- package/src/rules/no-deprecated-workflow-functions.test.ts +70 -0
- package/src/rules/no-deprecated-workflow-functions.ts +44 -10
- package/src/rules/no-restricted-globals.test.ts +1 -0
- package/src/rules/no-restricted-globals.ts +6 -3
- package/src/rules/no-restricted-imports.test.ts +1 -0
- package/src/rules/no-restricted-imports.ts +8 -3
- package/src/rules/node-usable-as-tool.test.ts +1 -0
- package/src/rules/node-usable-as-tool.ts +8 -6
- package/src/rules/package-name-convention.test.ts +82 -5
- package/src/rules/package-name-convention.ts +46 -2
- package/src/rules/resource-operation-pattern.test.ts +1 -0
- package/src/rules/resource-operation-pattern.ts +13 -6
- package/src/utils/ast-utils.ts +47 -19
- package/src/utils/file-utils.ts +69 -14
- package/src/utils/index.ts +1 -0
- package/src/utils/rule-creator.ts +6 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.eslint.json +5 -0
- package/tsconfig.json +1 -2
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { RuleTester } from '@typescript-eslint/rule-tester';
|
|
2
|
+
|
|
3
|
+
import { CredentialDocumentationUrlRule } from './credential-documentation-url.js';
|
|
4
|
+
|
|
5
|
+
const ruleTester = new RuleTester();
|
|
6
|
+
|
|
7
|
+
function createCredentialCode(documentationUrl: string): string {
|
|
8
|
+
return `
|
|
9
|
+
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
|
|
10
|
+
|
|
11
|
+
export class TestCredential implements ICredentialType {
|
|
12
|
+
name = 'testApi';
|
|
13
|
+
displayName = 'Test API';
|
|
14
|
+
documentationUrl = '${documentationUrl}';
|
|
15
|
+
|
|
16
|
+
properties: INodeProperties[] = [];
|
|
17
|
+
}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createCredentialWithoutDocUrl(): string {
|
|
21
|
+
return `
|
|
22
|
+
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
|
|
23
|
+
|
|
24
|
+
export class TestCredential implements ICredentialType {
|
|
25
|
+
name = 'testApi';
|
|
26
|
+
displayName = 'Test API';
|
|
27
|
+
|
|
28
|
+
properties: INodeProperties[] = [];
|
|
29
|
+
}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function createRegularClass(): string {
|
|
33
|
+
return `
|
|
34
|
+
export class RegularClass {
|
|
35
|
+
documentationUrl = 'invalid-url';
|
|
36
|
+
}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
ruleTester.run('credential-documentation-url', CredentialDocumentationUrlRule, {
|
|
40
|
+
valid: [
|
|
41
|
+
{
|
|
42
|
+
name: 'valid URL with default options (URLs only)',
|
|
43
|
+
code: createCredentialCode('https://example.com/docs'),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'valid URL with explicit options',
|
|
47
|
+
code: createCredentialCode('https://example.com/docs'),
|
|
48
|
+
options: [{ allowUrls: true, allowSlugs: false }],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'valid lowercase slug when slugs are allowed',
|
|
52
|
+
code: createCredentialCode('myservice'),
|
|
53
|
+
options: [{ allowUrls: false, allowSlugs: true }],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'valid lowercase slug with slashes when slugs are allowed',
|
|
57
|
+
code: createCredentialCode('myservice/advanced/config'),
|
|
58
|
+
options: [{ allowUrls: false, allowSlugs: true }],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'valid URL when both URLs and slugs are allowed',
|
|
62
|
+
code: createCredentialCode('https://example.com/docs'),
|
|
63
|
+
options: [{ allowUrls: true, allowSlugs: true }],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'valid lowercase slug when both URLs and slugs are allowed',
|
|
67
|
+
code: createCredentialCode('myservice/config'),
|
|
68
|
+
options: [{ allowUrls: true, allowSlugs: true }],
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'credential without documentationUrl should not trigger',
|
|
72
|
+
code: createCredentialWithoutDocUrl(),
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'class not implementing ICredentialType should be ignored',
|
|
76
|
+
code: createRegularClass(),
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'valid lowercase slug with multiple segments',
|
|
80
|
+
code: createCredentialCode('myservice/somefeature/advancedconfig'),
|
|
81
|
+
options: [{ allowUrls: false, allowSlugs: true }],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'valid lowercase alphanumeric slug',
|
|
85
|
+
code: createCredentialCode('myservice123'),
|
|
86
|
+
options: [{ allowUrls: false, allowSlugs: true }],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'valid lowercase alphanumeric slug with slashes',
|
|
90
|
+
code: createCredentialCode('myservice123/config456'),
|
|
91
|
+
options: [{ allowUrls: false, allowSlugs: true }],
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
invalid: [
|
|
95
|
+
{
|
|
96
|
+
name: 'invalid URL with default options',
|
|
97
|
+
code: createCredentialCode('invalid-url'),
|
|
98
|
+
errors: [
|
|
99
|
+
{
|
|
100
|
+
messageId: 'invalidDocumentationUrl',
|
|
101
|
+
data: {
|
|
102
|
+
value: 'invalid-url',
|
|
103
|
+
expectedFormats: 'a valid URL',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'slug not allowed with default options',
|
|
110
|
+
code: createCredentialCode('myservice'),
|
|
111
|
+
errors: [
|
|
112
|
+
{
|
|
113
|
+
messageId: 'invalidDocumentationUrl',
|
|
114
|
+
data: {
|
|
115
|
+
value: 'myservice',
|
|
116
|
+
expectedFormats: 'a valid URL',
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: 'slug with special characters should not be autofixable',
|
|
123
|
+
code: createCredentialCode('My-Service'),
|
|
124
|
+
options: [{ allowUrls: false, allowSlugs: true }],
|
|
125
|
+
errors: [
|
|
126
|
+
{
|
|
127
|
+
messageId: 'invalidDocumentationUrl',
|
|
128
|
+
data: {
|
|
129
|
+
value: 'My-Service',
|
|
130
|
+
expectedFormats: 'a lowercase alphanumeric slug (can contain slashes)',
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: 'uppercase slug should be autofixable',
|
|
137
|
+
code: createCredentialCode('MyService'),
|
|
138
|
+
options: [{ allowUrls: false, allowSlugs: true }],
|
|
139
|
+
output: createCredentialCode('myservice'),
|
|
140
|
+
errors: [
|
|
141
|
+
{
|
|
142
|
+
messageId: 'invalidDocumentationUrl',
|
|
143
|
+
data: {
|
|
144
|
+
value: 'MyService',
|
|
145
|
+
expectedFormats: 'a lowercase alphanumeric slug (can contain slashes)',
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: 'invalid URL when only URLs are allowed',
|
|
152
|
+
code: createCredentialCode('not-a-valid-url'),
|
|
153
|
+
options: [{ allowUrls: true, allowSlugs: false }],
|
|
154
|
+
errors: [
|
|
155
|
+
{
|
|
156
|
+
messageId: 'invalidDocumentationUrl',
|
|
157
|
+
data: {
|
|
158
|
+
value: 'not-a-valid-url',
|
|
159
|
+
expectedFormats: 'a valid URL',
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'invalid when neither URLs nor slugs are allowed',
|
|
166
|
+
code: createCredentialCode('https://example.com'),
|
|
167
|
+
options: [{ allowUrls: false, allowSlugs: false }],
|
|
168
|
+
errors: [
|
|
169
|
+
{
|
|
170
|
+
messageId: 'invalidDocumentationUrl',
|
|
171
|
+
data: {
|
|
172
|
+
value: 'https://example.com',
|
|
173
|
+
expectedFormats: 'a valid format (none configured)',
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: 'slug with invalid characters (special chars) should not be autofixable',
|
|
180
|
+
code: createCredentialCode('my@service/config'),
|
|
181
|
+
options: [{ allowUrls: false, allowSlugs: true }],
|
|
182
|
+
errors: [
|
|
183
|
+
{
|
|
184
|
+
messageId: 'invalidDocumentationUrl',
|
|
185
|
+
data: {
|
|
186
|
+
value: 'my@service/config',
|
|
187
|
+
expectedFormats: 'a lowercase alphanumeric slug (can contain slashes)',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: 'slug with uppercase segment should be autofixable',
|
|
194
|
+
code: createCredentialCode('myService/Config'),
|
|
195
|
+
options: [{ allowUrls: false, allowSlugs: true }],
|
|
196
|
+
output: createCredentialCode('myservice/config'),
|
|
197
|
+
errors: [
|
|
198
|
+
{
|
|
199
|
+
messageId: 'invalidDocumentationUrl',
|
|
200
|
+
data: {
|
|
201
|
+
value: 'myService/Config',
|
|
202
|
+
expectedFormats: 'a lowercase alphanumeric slug (can contain slashes)',
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
name: 'slug with hyphens should not be autofixable',
|
|
209
|
+
code: createCredentialCode('myservice/advanced-config'),
|
|
210
|
+
options: [{ allowUrls: false, allowSlugs: true }],
|
|
211
|
+
errors: [
|
|
212
|
+
{
|
|
213
|
+
messageId: 'invalidDocumentationUrl',
|
|
214
|
+
data: {
|
|
215
|
+
value: 'myservice/advanced-config',
|
|
216
|
+
expectedFormats: 'a lowercase alphanumeric slug (can contain slashes)',
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: 'slug with underscores should not be autofixable',
|
|
223
|
+
code: createCredentialCode('my_service/config_advanced'),
|
|
224
|
+
options: [{ allowUrls: false, allowSlugs: true }],
|
|
225
|
+
errors: [
|
|
226
|
+
{
|
|
227
|
+
messageId: 'invalidDocumentationUrl',
|
|
228
|
+
data: {
|
|
229
|
+
value: 'my_service/config_advanced',
|
|
230
|
+
expectedFormats: 'a lowercase alphanumeric slug (can contain slashes)',
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
name: 'invalid value when both formats are allowed - shows both in error message',
|
|
237
|
+
code: createCredentialCode('Invalid-Value!'),
|
|
238
|
+
options: [{ allowUrls: true, allowSlugs: true }],
|
|
239
|
+
errors: [
|
|
240
|
+
{
|
|
241
|
+
messageId: 'invalidDocumentationUrl',
|
|
242
|
+
data: {
|
|
243
|
+
value: 'Invalid-Value!',
|
|
244
|
+
expectedFormats: 'a valid URL or a lowercase alphanumeric slug (can contain slashes)',
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: 'empty string should be invalid with default options',
|
|
251
|
+
code: createCredentialCode(''),
|
|
252
|
+
errors: [
|
|
253
|
+
{
|
|
254
|
+
messageId: 'invalidDocumentationUrl',
|
|
255
|
+
data: {
|
|
256
|
+
value: '',
|
|
257
|
+
expectedFormats: 'a valid URL',
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: 'empty string should be invalid when slugs are allowed',
|
|
264
|
+
code: createCredentialCode(''),
|
|
265
|
+
options: [{ allowUrls: false, allowSlugs: true }],
|
|
266
|
+
errors: [
|
|
267
|
+
{
|
|
268
|
+
messageId: 'invalidDocumentationUrl',
|
|
269
|
+
data: {
|
|
270
|
+
value: '',
|
|
271
|
+
expectedFormats: 'a lowercase alphanumeric slug (can contain slashes)',
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: 'mixed case slug with numbers should be autofixable',
|
|
278
|
+
code: createCredentialCode('MyService123/Config456'),
|
|
279
|
+
options: [{ allowUrls: false, allowSlugs: true }],
|
|
280
|
+
output: createCredentialCode('myservice123/config456'),
|
|
281
|
+
errors: [
|
|
282
|
+
{
|
|
283
|
+
messageId: 'invalidDocumentationUrl',
|
|
284
|
+
data: {
|
|
285
|
+
value: 'MyService123/Config456',
|
|
286
|
+
expectedFormats: 'a lowercase alphanumeric slug (can contain slashes)',
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
name: 'slug starting with number should be invalid and not autofixable',
|
|
293
|
+
code: createCredentialCode('123service/config'),
|
|
294
|
+
options: [{ allowUrls: false, allowSlugs: true }],
|
|
295
|
+
errors: [
|
|
296
|
+
{
|
|
297
|
+
messageId: 'invalidDocumentationUrl',
|
|
298
|
+
data: {
|
|
299
|
+
value: '123service/config',
|
|
300
|
+
expectedFormats: 'a lowercase alphanumeric slug (can contain slashes)',
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isCredentialTypeClass,
|
|
3
|
+
findClassProperty,
|
|
4
|
+
getStringLiteralValue,
|
|
5
|
+
createRule,
|
|
6
|
+
} from '../utils/index.js';
|
|
7
|
+
|
|
8
|
+
type RuleOptions = {
|
|
9
|
+
allowUrls?: boolean;
|
|
10
|
+
allowSlugs?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const DEFAULT_OPTIONS: RuleOptions = {
|
|
14
|
+
allowUrls: true,
|
|
15
|
+
allowSlugs: false,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function isValidUrl(value: string): boolean {
|
|
19
|
+
try {
|
|
20
|
+
new URL(value);
|
|
21
|
+
return true;
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isValidSlug(value: string): boolean {
|
|
28
|
+
// TODO: Remove this special case once these slugs are updated
|
|
29
|
+
if (
|
|
30
|
+
['google/service-account', 'google/oauth-single-service', 'google/oauth-generic'].includes(
|
|
31
|
+
value,
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
return true;
|
|
35
|
+
|
|
36
|
+
return value.split('/').every((segment) => /^[a-z][a-z0-9]*$/.test(segment));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function hasOnlyCaseIssues(value: string): boolean {
|
|
40
|
+
return value.split('/').every((segment) => /^[a-zA-Z][a-zA-Z0-9]*$/.test(segment));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function validateDocumentationUrl(value: string, options: RuleOptions): boolean {
|
|
44
|
+
return (!!options.allowUrls && isValidUrl(value)) || (!!options.allowSlugs && isValidSlug(value));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getExpectedFormatsMessage(options: RuleOptions): string {
|
|
48
|
+
const formats = [
|
|
49
|
+
...(options.allowUrls ? ['a valid URL'] : []),
|
|
50
|
+
...(options.allowSlugs ? ['a lowercase alphanumeric slug (can contain slashes)'] : []),
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
if (formats.length === 0) return 'a valid format (none configured)';
|
|
54
|
+
if (formats.length === 1) return formats[0]!;
|
|
55
|
+
return formats.slice(0, -1).join(', ') + ' or ' + formats[formats.length - 1];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const CredentialDocumentationUrlRule = createRule({
|
|
59
|
+
name: 'credential-documentation-url',
|
|
60
|
+
meta: {
|
|
61
|
+
type: 'problem',
|
|
62
|
+
docs: {
|
|
63
|
+
description:
|
|
64
|
+
'Enforce valid credential documentationUrl format (URL or lowercase alphanumeric slug)',
|
|
65
|
+
},
|
|
66
|
+
messages: {
|
|
67
|
+
invalidDocumentationUrl: "documentationUrl '{{ value }}' must be {{ expectedFormats }}",
|
|
68
|
+
},
|
|
69
|
+
fixable: 'code',
|
|
70
|
+
schema: [
|
|
71
|
+
{
|
|
72
|
+
type: 'object',
|
|
73
|
+
properties: {
|
|
74
|
+
allowUrls: {
|
|
75
|
+
type: 'boolean',
|
|
76
|
+
description: 'Whether to allow valid URLs',
|
|
77
|
+
},
|
|
78
|
+
allowSlugs: {
|
|
79
|
+
type: 'boolean',
|
|
80
|
+
description: 'Whether to allow lowercase alphanumeric slugs with slashes',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
additionalProperties: false,
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
defaultOptions: [DEFAULT_OPTIONS],
|
|
88
|
+
create(context, [options = {}]) {
|
|
89
|
+
const mergedOptions = { ...DEFAULT_OPTIONS, ...options };
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
ClassDeclaration(node) {
|
|
93
|
+
if (!isCredentialTypeClass(node)) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const documentationUrlProperty = findClassProperty(node, 'documentationUrl');
|
|
98
|
+
if (!documentationUrlProperty?.value) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const documentationUrl = getStringLiteralValue(documentationUrlProperty.value);
|
|
103
|
+
if (documentationUrl === null) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!validateDocumentationUrl(documentationUrl, mergedOptions)) {
|
|
108
|
+
const canAutofix = !!mergedOptions.allowSlugs && hasOnlyCaseIssues(documentationUrl);
|
|
109
|
+
|
|
110
|
+
context.report({
|
|
111
|
+
node: documentationUrlProperty.value,
|
|
112
|
+
messageId: 'invalidDocumentationUrl',
|
|
113
|
+
data: {
|
|
114
|
+
value: documentationUrl,
|
|
115
|
+
expectedFormats: getExpectedFormatsMessage(mergedOptions),
|
|
116
|
+
},
|
|
117
|
+
fix: canAutofix
|
|
118
|
+
? (fixer) =>
|
|
119
|
+
fixer.replaceText(
|
|
120
|
+
documentationUrlProperty.value!,
|
|
121
|
+
`'${documentationUrl.toLowerCase()}'`,
|
|
122
|
+
)
|
|
123
|
+
: undefined,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
});
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TSESTree } from '@typescript-eslint/types';
|
|
2
|
+
import type { ReportFixFunction } from '@typescript-eslint/utils/ts-eslint';
|
|
3
|
+
|
|
2
4
|
import {
|
|
3
5
|
isCredentialTypeClass,
|
|
4
6
|
findClassProperty,
|
|
5
7
|
findObjectProperty,
|
|
6
8
|
getStringLiteralValue,
|
|
7
9
|
getBooleanLiteralValue,
|
|
10
|
+
createRule,
|
|
8
11
|
} from '../utils/index.js';
|
|
9
12
|
|
|
10
13
|
const SENSITIVE_PATTERNS = [
|
|
@@ -31,10 +34,10 @@ function isSensitiveFieldName(name: string): boolean {
|
|
|
31
34
|
return SENSITIVE_PATTERNS.some((pattern) => lowerName.includes(pattern));
|
|
32
35
|
}
|
|
33
36
|
|
|
34
|
-
function hasPasswordTypeOption(element:
|
|
37
|
+
function hasPasswordTypeOption(element: TSESTree.ObjectExpression): boolean {
|
|
35
38
|
const typeOptionsProperty = findObjectProperty(element, 'typeOptions');
|
|
36
39
|
|
|
37
|
-
if (typeOptionsProperty?.value
|
|
40
|
+
if (typeOptionsProperty?.value.type !== TSESTree.AST_NODE_TYPES.ObjectExpression) {
|
|
38
41
|
return false;
|
|
39
42
|
}
|
|
40
43
|
|
|
@@ -44,31 +47,43 @@ function hasPasswordTypeOption(element: any): boolean {
|
|
|
44
47
|
return passwordValue === true;
|
|
45
48
|
}
|
|
46
49
|
|
|
47
|
-
function createPasswordFix(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
function createPasswordFix(
|
|
51
|
+
element: TSESTree.ObjectExpression,
|
|
52
|
+
typeOptionsProperty: TSESTree.Property | null,
|
|
53
|
+
): ReportFixFunction {
|
|
54
|
+
return (fixer) => {
|
|
55
|
+
if (typeOptionsProperty?.value.type === TSESTree.AST_NODE_TYPES.ObjectExpression) {
|
|
50
56
|
const passwordProperty = findObjectProperty(typeOptionsProperty.value, 'password');
|
|
51
57
|
|
|
52
58
|
if (passwordProperty) {
|
|
53
59
|
return fixer.replaceText(passwordProperty.value, 'true');
|
|
54
60
|
}
|
|
55
61
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
const objectValue = typeOptionsProperty.value;
|
|
63
|
+
if (objectValue.properties.length > 0) {
|
|
64
|
+
const lastProperty = objectValue.properties[objectValue.properties.length - 1];
|
|
65
|
+
if (lastProperty) {
|
|
66
|
+
return fixer.insertTextAfter(lastProperty, ', password: true');
|
|
67
|
+
}
|
|
60
68
|
} else {
|
|
61
|
-
const
|
|
62
|
-
|
|
69
|
+
const range = objectValue.range;
|
|
70
|
+
if (range) {
|
|
71
|
+
const openBrace = range[0] + 1;
|
|
72
|
+
return fixer.insertTextAfterRange([openBrace, openBrace], ' password: true ');
|
|
73
|
+
}
|
|
63
74
|
}
|
|
64
75
|
}
|
|
65
76
|
|
|
66
77
|
const lastProperty = element.properties[element.properties.length - 1];
|
|
67
|
-
|
|
78
|
+
if (lastProperty) {
|
|
79
|
+
return fixer.insertTextAfter(lastProperty, ',\n\t\t\ttypeOptions: { password: true }');
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
68
82
|
};
|
|
69
83
|
}
|
|
70
84
|
|
|
71
|
-
export const CredentialPasswordFieldRule =
|
|
85
|
+
export const CredentialPasswordFieldRule = createRule({
|
|
86
|
+
name: 'credential-password-field',
|
|
72
87
|
meta: {
|
|
73
88
|
type: 'problem',
|
|
74
89
|
docs: {
|
|
@@ -90,12 +105,15 @@ export const CredentialPasswordFieldRule = ESLintUtils.RuleCreator.withoutDocs({
|
|
|
90
105
|
}
|
|
91
106
|
|
|
92
107
|
const propertiesProperty = findClassProperty(node, 'properties');
|
|
93
|
-
if (
|
|
108
|
+
if (
|
|
109
|
+
!propertiesProperty?.value ||
|
|
110
|
+
propertiesProperty.value.type !== TSESTree.AST_NODE_TYPES.ArrayExpression
|
|
111
|
+
) {
|
|
94
112
|
return;
|
|
95
113
|
}
|
|
96
114
|
|
|
97
115
|
for (const element of propertiesProperty.value.elements) {
|
|
98
|
-
if (element?.type !==
|
|
116
|
+
if (element?.type !== TSESTree.AST_NODE_TYPES.ObjectExpression) {
|
|
99
117
|
continue;
|
|
100
118
|
}
|
|
101
119
|
|