lint-rules-alvin 2.0.5 → 2.0.7

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.
@@ -46,7 +46,10 @@ export const custom: TSESLint.FlatConfig.Config = {
46
46
  ],
47
47
  'custom/multiline-paren-newline': [
48
48
  'error',
49
- { singleArgument: true }
49
+ {
50
+ singleArgument: true,
51
+ enforceSingleLine: true
52
+ }
50
53
  ],
51
54
  'custom/multiline-array-accessor-newline': 'error',
52
55
  'custom/destructure-newline': [
@@ -426,7 +426,7 @@ export const typescript: TSESLint.FlatConfig.Config = {
426
426
  '@typescript-eslint/no-unsafe-unary-minus': 'warn',
427
427
  '@typescript-eslint/no-unused-expressions': 'warn',
428
428
  'no-unused-vars': 'off',
429
- '@typescript-eslint/no-unused-vars': 'warn',
429
+ '@typescript-eslint/no-unused-vars': 'off', // TS already shows these warnings
430
430
  'no-use-before-define': 'off',
431
431
  '@typescript-eslint/no-use-before-define': 'error',
432
432
  'no-useless-constructor': 'off',
@@ -13,22 +13,35 @@ export const multilineParenNewlineRule = ESLintUtils.RuleCreator.withoutDocs({
13
13
  schema: [
14
14
  {
15
15
  type: 'object',
16
- properties: { singleArgument: { type: 'boolean' } },
16
+ properties: {
17
+ singleArgument: { type: 'boolean' },
18
+ enforceSingleLine: { type: 'boolean' }
19
+ },
17
20
  additionalProperties: false
18
21
  }
19
22
  ],
20
23
  messages: {
21
24
  expandAfter: 'Expected a newline after \'(\'.',
22
25
  expandBefore: 'Expected a newline before \')\'.',
26
+ collapseAfter: 'Unexpected newline after \'(\'.',
27
+ collapseBefore: 'Unexpected newline before \')\'.',
23
28
  collapseAfterSingleArgument: 'Unexpected newline after \'(\' for single object/array argument.',
24
29
  collapseBeforeSingleArgument: 'Unexpected newline before \')\' for single object/array argument.'
25
30
  }
26
31
  },
27
- defaultOptions: [ { singleArgument: false } ],
32
+ defaultOptions: [
33
+ {
34
+ singleArgument: false,
35
+ enforceSingleLine: false
36
+ }
37
+ ],
28
38
  create(context, options) {
29
39
 
30
40
  const sourceCode = context.sourceCode;
31
- const { singleArgument } = options[0];
41
+ const {
42
+ singleArgument,
43
+ enforceSingleLine
44
+ } = options[0];
32
45
 
33
46
  function collapseWhitespacePreservingEdges(start: number, end: number) {
34
47
 
@@ -190,15 +203,147 @@ export const multilineParenNewlineRule = ESLintUtils.RuleCreator.withoutDocs({
190
203
 
191
204
  }
192
205
 
193
- // Check for a newline after the opening parenthesis.
206
+ // Determine if the content inside the parentheses is single-line
194
207
  const firstArg = node.arguments[0];
208
+ const lastArg = node.arguments[node.arguments.length - 1];
209
+ let firstTokenOfFirstArg: TSESTree.Token | null = null;
210
+ let lastTokenOfLastArg: TSESTree.Token | null = null;
211
+
212
+ /* eslint-disable-next-line
213
+ @typescript-eslint/no-unnecessary-condition,
214
+ @typescript-eslint/strict-boolean-expressions */
215
+ if (firstArg) {
216
+
217
+ firstTokenOfFirstArg = sourceCode.getFirstToken(firstArg);
218
+
219
+ }
220
+
221
+ /* eslint-disable-next-line
222
+ @typescript-eslint/no-unnecessary-condition,
223
+ @typescript-eslint/strict-boolean-expressions */
224
+ if (lastArg) {
225
+
226
+ lastTokenOfLastArg = sourceCode.getLastToken(lastArg);
227
+
228
+ }
229
+ const contentIsSingleLine = node.arguments.length === 0
230
+ ? true
231
+ : Boolean(
232
+ firstTokenOfFirstArg && lastTokenOfLastArg
233
+ && firstTokenOfFirstArg
234
+ .loc
235
+ .start
236
+ .line === lastTokenOfLastArg
237
+ .loc
238
+ .end
239
+ .line
240
+ );
241
+
242
+ if (enforceSingleLine && contentIsSingleLine) {
243
+
244
+ // Collapse newline after '('
245
+ const afterStart = openParen.range[1];
246
+ const afterEnd = node.arguments.length === 0
247
+ ? closeParen.range[0]
248
+ : firstTokenOfFirstArg?.range[0] ?? openParen.range[1];
249
+ const openEndLine = openParen
250
+ .loc
251
+ .end
252
+ .line;
253
+ const firstInnerStartLine = firstTokenOfFirstArg
254
+ ? firstTokenOfFirstArg
255
+ .loc
256
+ .start
257
+ .line
258
+ : closeParen
259
+ .loc
260
+ .start
261
+ .line;
262
+ const hasNewlineAfter = openEndLine < firstInnerStartLine;
263
+ if (hasNewlineAfter) {
264
+
265
+ context.report({
266
+ node: openParen,
267
+ messageId: 'collapseAfter',
268
+ fix(fixer) {
269
+
270
+ const replacement = collapseWhitespacePreservingEdges(
271
+ afterStart,
272
+ afterEnd
273
+ );
274
+ if (replacement === null)
275
+ return null;
276
+ return fixer.replaceTextRange(
277
+ [
278
+ afterStart,
279
+ afterEnd
280
+ ],
281
+ replacement
282
+ );
283
+
284
+ }
285
+ });
286
+
287
+ }
288
+
289
+ // Collapse newline before ')'
290
+ const beforeStart = node.arguments.length === 0
291
+ ? openParen.range[1]
292
+ : lastTokenOfLastArg?.range[1] ?? closeParen.range[0];
293
+ const beforeEnd = closeParen.range[0];
294
+ const refLine = node.arguments.length === 0
295
+ ? openParen
296
+ .loc
297
+ .end
298
+ .line
299
+ : lastTokenOfLastArg
300
+ ?.loc
301
+ .end
302
+ .line ?? closeParen
303
+ .loc
304
+ .start
305
+ .line;
306
+ const closeStartLine = closeParen
307
+ .loc
308
+ .start
309
+ .line;
310
+ const hasNewlineBefore = refLine < closeStartLine;
311
+ if (hasNewlineBefore) {
312
+
313
+ context.report({
314
+ node: closeParen,
315
+ messageId: 'collapseBefore',
316
+ fix(fixer) {
317
+
318
+ const replacement = collapseWhitespacePreservingEdges(
319
+ beforeStart,
320
+ beforeEnd
321
+ );
322
+ if (replacement === null)
323
+ return null;
324
+ return fixer.replaceTextRange(
325
+ [
326
+ beforeStart,
327
+ beforeEnd
328
+ ],
329
+ replacement
330
+ );
331
+
332
+ }
333
+ });
334
+
335
+ }
336
+
337
+ return;
338
+
339
+ }
195
340
 
341
+ // Check for a newline after the opening parenthesis.
196
342
  /* eslint-disable-next-line
197
343
  @typescript-eslint/no-unnecessary-condition,
198
344
  @typescript-eslint/strict-boolean-expressions */
199
345
  if (firstArg) {
200
346
 
201
- const firstTokenOfFirstArg = sourceCode.getFirstToken(firstArg);
202
347
  if (
203
348
  firstTokenOfFirstArg && openParen
204
349
  .loc
@@ -223,14 +368,11 @@ export const multilineParenNewlineRule = ESLintUtils.RuleCreator.withoutDocs({
223
368
  }
224
369
 
225
370
  // Check for a newline before the closing parenthesis.
226
- const lastArg = node.arguments[node.arguments.length - 1];
227
-
228
371
  /* eslint-disable-next-line
229
372
  @typescript-eslint/no-unnecessary-condition,
230
373
  @typescript-eslint/strict-boolean-expressions */
231
374
  if (lastArg) {
232
375
 
233
- const lastTokenOfLastArg = sourceCode.getLastToken(lastArg);
234
376
  if (
235
377
  lastTokenOfLastArg && lastTokenOfLastArg
236
378
  .loc
@@ -332,6 +474,99 @@ export const multilineParenNewlineRule = ESLintUtils.RuleCreator.withoutDocs({
332
474
  }
333
475
 
334
476
  const firstTokenOfNode = sourceCode.getFirstToken(node);
477
+ const lastTokenOfNode = sourceCode.getLastToken(node);
478
+
479
+ const contentIsSingleLine = Boolean(
480
+ firstTokenOfNode && lastTokenOfNode
481
+ && firstTokenOfNode
482
+ .loc
483
+ .start
484
+ .line === lastTokenOfNode
485
+ .loc
486
+ .end
487
+ .line
488
+ );
489
+
490
+ if (enforceSingleLine && contentIsSingleLine && firstTokenOfNode && lastTokenOfNode) {
491
+
492
+ // Collapse newline after '('
493
+ if (
494
+ openParen
495
+ .loc
496
+ .end
497
+ .line < firstTokenOfNode
498
+ .loc
499
+ .start
500
+ .line
501
+ ) {
502
+
503
+ const start = openParen.range[1];
504
+ const end = firstTokenOfNode.range[0];
505
+ context.report({
506
+ node: openParen,
507
+ messageId: 'collapseAfter',
508
+ fix(fixer) {
509
+
510
+ const replacement = collapseWhitespacePreservingEdges(
511
+ start,
512
+ end
513
+ );
514
+ if (replacement === null)
515
+ return null;
516
+ return fixer.replaceTextRange(
517
+ [
518
+ start,
519
+ end
520
+ ],
521
+ replacement
522
+ );
523
+
524
+ }
525
+ });
526
+
527
+ }
528
+
529
+ // Collapse newline before ')'
530
+ if (
531
+ lastTokenOfNode
532
+ .loc
533
+ .end
534
+ .line < closeParen
535
+ .loc
536
+ .start
537
+ .line
538
+ ) {
539
+
540
+ const start = lastTokenOfNode.range[1];
541
+ const end = closeParen.range[0];
542
+ context.report({
543
+ node: closeParen,
544
+ messageId: 'collapseBefore',
545
+ fix(fixer) {
546
+
547
+ const replacement = collapseWhitespacePreservingEdges(
548
+ start,
549
+ end
550
+ );
551
+ if (replacement === null)
552
+ return null;
553
+ return fixer.replaceTextRange(
554
+ [
555
+ start,
556
+ end
557
+ ],
558
+ replacement
559
+ );
560
+
561
+ }
562
+ });
563
+
564
+ }
565
+
566
+ return;
567
+
568
+ }
569
+
335
570
  if (
336
571
  firstTokenOfNode && openParen
337
572
  .loc
@@ -353,7 +588,6 @@ export const multilineParenNewlineRule = ESLintUtils.RuleCreator.withoutDocs({
353
588
 
354
589
  }
355
590
 
356
- const lastTokenOfNode = sourceCode.getLastToken(node);
357
591
  if (
358
592
  lastTokenOfNode && lastTokenOfNode
359
593
  .loc
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lint-rules-alvin",
3
- "version": "2.0.5",
3
+ "version": "2.0.7",
4
4
  "description": "My own personal linting ruleset for a bunch of different plugins. Includes a few custom rules. Used in a few of my repos.",
5
5
  "keywords": [
6
6
  "eslint"
@@ -6,24 +6,85 @@ new RuleTester().run(
6
6
  multilineParenNewlineRule,
7
7
  {
8
8
  valid: [
9
- // Default, should be `singleArgument: false`
9
+ // Default, should be `singleArgument: false, enforceSingleLine: false`
10
10
  { code: 'fn( \n a,\n b \n );' },
11
11
  { code: 'fn( \n\n \n{ a: 1 }\n \n );' },
12
+ { code: 'fn(\n \n \n\n \'foo\' \n \n\n\n );' },
12
13
 
13
- // `singleArgument: true`
14
+ // `singleArgument: true, enforceSingleLine: false`
14
15
  {
15
16
  code: 'fn(\n a,\n b\n);',
16
- options: [ { singleArgument: true } ]
17
+ options: [
18
+ {
19
+ singleArgument: true,
20
+ enforceSingleLine: false
21
+ }
22
+ ]
17
23
  },
18
24
  {
19
25
  code: 'fn( { a: 1, \n b: 2 } );',
20
- options: [ { singleArgument: true } ]
26
+ options: [
27
+ {
28
+ singleArgument: true,
29
+ enforceSingleLine: false
30
+ }
31
+ ]
32
+ },
33
+
34
+ // `singleArgument: false, enforceSingleLine: true`
35
+ {
36
+ code: 'fn(\n a,\n b\n);',
37
+ options: [
38
+ {
39
+ singleArgument: false,
40
+ enforceSingleLine: true
41
+ }
42
+ ]
43
+ },
44
+ {
45
+ code: 'fn( \n \n { a: 1, \n b: 2 } \n );',
46
+ options: [
47
+ {
48
+ singleArgument: false,
49
+ enforceSingleLine: true
50
+ }
51
+ ]
52
+ },
53
+ {
54
+ code: 'fn( \'foo\' );',
55
+ options: [
56
+ {
57
+ singleArgument: false,
58
+ enforceSingleLine: true
59
+ }
60
+ ]
21
61
  }
22
62
  ],
23
63
  invalid: [
24
- // Default, should be `singleArgument: false`
64
+ // Default, should be `singleArgument: false, enforceSingleLine: false`
65
+ {
66
+ code: 'fn( a , \n b );',
67
+ errors: [
68
+ { messageId: 'expandAfter' },
69
+ { messageId: 'expandBefore' }
70
+ ],
71
+ output: 'fn(\n a , \n b \n);'
72
+ },
73
+ {
74
+ code: 'fn( \n \n\n \n a, \n b );',
75
+ errors: [ { messageId: 'expandBefore' } ],
76
+ output: 'fn( \n \n\n \n a, \n b \n);'
77
+ },
78
+
79
+ // `singleArgument: true, enforceSingleLine: false`
25
80
  {
26
81
  code: 'fn( a , \n b );',
82
+ options: [
83
+ {
84
+ singleArgument: true,
85
+ enforceSingleLine: false
86
+ }
87
+ ],
27
88
  errors: [
28
89
  { messageId: 'expandAfter' },
29
90
  { messageId: 'expandBefore' }
@@ -32,14 +93,100 @@ new RuleTester().run(
32
93
  },
33
94
  {
34
95
  code: 'fn( \n \n\n \n a, \n b );',
96
+ options: [
97
+ {
98
+ singleArgument: true,
99
+ enforceSingleLine: false
100
+ }
101
+ ],
35
102
  errors: [ { messageId: 'expandBefore' } ],
36
103
  output: 'fn( \n \n\n \n a, \n b \n);'
37
104
  },
105
+ {
106
+ code: 'fn(\n\n \n [ 1 , \n 2 ]\n \n );',
107
+ options: [
108
+ {
109
+ singleArgument: true,
110
+ enforceSingleLine: false
111
+ }
112
+ ],
113
+ errors: [
114
+ { messageId: 'collapseAfterSingleArgument' },
115
+ { messageId: 'collapseBeforeSingleArgument' }
116
+ ],
117
+ output: 'fn( [ 1 , \n 2 ] );'
118
+ },
119
+ {
120
+ code: 'fn( [ 1 , \n 2 ]\n \n );',
121
+ options: [
122
+ {
123
+ singleArgument: true,
124
+ enforceSingleLine: false
125
+ }
126
+ ],
127
+ errors: [ { messageId: 'collapseBeforeSingleArgument' } ],
128
+ output: 'fn( [ 1 , \n 2 ] );'
129
+ },
130
+ {
131
+ code: 'fn(\n\n \n { a : 1 , \n a: 2 }\n \n );',
132
+ options: [
133
+ {
134
+ singleArgument: true,
135
+ enforceSingleLine: false
136
+ }
137
+ ],
138
+ errors: [
139
+ { messageId: 'collapseAfterSingleArgument' },
140
+ { messageId: 'collapseBeforeSingleArgument' }
141
+ ],
142
+ output: 'fn( { a : 1 , \n a: 2 } );'
143
+ },
144
+ {
145
+ code: 'fn( { a: 1 , \n b : 2 }\n \n );',
146
+ options: [
147
+ {
148
+ singleArgument: true,
149
+ enforceSingleLine: false
150
+ }
151
+ ],
152
+ errors: [ { messageId: 'collapseBeforeSingleArgument' } ],
153
+ output: 'fn( { a: 1 , \n b : 2 } );'
154
+ },
38
155
 
39
- // `singleArgument: true`
156
+ // `singleArgument: true, enforceSingleLine: true`
157
+ {
158
+ code: 'fn(\n\n \n \'foo\' );',
159
+ options: [
160
+ {
161
+ singleArgument: true,
162
+ enforceSingleLine: true
163
+ }
164
+ ],
165
+ errors: [ { messageId: 'collapseAfter' } ],
166
+ output: 'fn( \'foo\' );'
167
+ },
168
+ {
169
+ code: 'fn(\n\n \n \'foo\' \n\n \n \n );',
170
+ options: [
171
+ {
172
+ singleArgument: true,
173
+ enforceSingleLine: true
174
+ }
175
+ ],
176
+ errors: [
177
+ { messageId: 'collapseAfter' },
178
+ { messageId: 'collapseBefore' }
179
+ ],
180
+ output: 'fn( \'foo\' );'
181
+ },
40
182
  {
41
183
  code: 'fn( a , \n b );',
42
- options: [ { singleArgument: true } ],
184
+ options: [
185
+ {
186
+ singleArgument: true,
187
+ enforceSingleLine: true
188
+ }
189
+ ],
43
190
  errors: [
44
191
  { messageId: 'expandAfter' },
45
192
  { messageId: 'expandBefore' }
@@ -48,13 +195,23 @@ new RuleTester().run(
48
195
  },
49
196
  {
50
197
  code: 'fn( \n \n\n \n a, \n b );',
51
- options: [ { singleArgument: true } ],
198
+ options: [
199
+ {
200
+ singleArgument: true,
201
+ enforceSingleLine: true
202
+ }
203
+ ],
52
204
  errors: [ { messageId: 'expandBefore' } ],
53
205
  output: 'fn( \n \n\n \n a, \n b \n);'
54
206
  },
55
207
  {
56
208
  code: 'fn(\n\n \n [ 1 , \n 2 ]\n \n );',
57
- options: [ { singleArgument: true } ],
209
+ options: [
210
+ {
211
+ singleArgument: true,
212
+ enforceSingleLine: true
213
+ }
214
+ ],
58
215
  errors: [
59
216
  { messageId: 'collapseAfterSingleArgument' },
60
217
  { messageId: 'collapseBeforeSingleArgument' }
@@ -63,13 +220,23 @@ new RuleTester().run(
63
220
  },
64
221
  {
65
222
  code: 'fn( [ 1 , \n 2 ]\n \n );',
66
- options: [ { singleArgument: true } ],
223
+ options: [
224
+ {
225
+ singleArgument: true,
226
+ enforceSingleLine: true
227
+ }
228
+ ],
67
229
  errors: [ { messageId: 'collapseBeforeSingleArgument' } ],
68
230
  output: 'fn( [ 1 , \n 2 ] );'
69
231
  },
70
232
  {
71
233
  code: 'fn(\n\n \n { a : 1 , \n a: 2 }\n \n );',
72
- options: [ { singleArgument: true } ],
234
+ options: [
235
+ {
236
+ singleArgument: true,
237
+ enforceSingleLine: true
238
+ }
239
+ ],
73
240
  errors: [
74
241
  { messageId: 'collapseAfterSingleArgument' },
75
242
  { messageId: 'collapseBeforeSingleArgument' }
@@ -78,7 +245,12 @@ new RuleTester().run(
78
245
  },
79
246
  {
80
247
  code: 'fn( { a: 1 , \n b : 2 }\n \n );',
81
- options: [ { singleArgument: true } ],
248
+ options: [
249
+ {
250
+ singleArgument: true,
251
+ enforceSingleLine: true
252
+ }
253
+ ],
82
254
  errors: [ { messageId: 'collapseBeforeSingleArgument' } ],
83
255
  output: 'fn( { a: 1 , \n b : 2 } );'
84
256
  }