kensington-eslint-plugin 0.2.2 → 0.3.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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ESLint rules for [kensington](https://github.com/beezwax/kensington) signal correctness.
4
4
 
5
- Catches common reactive programming mistakes read/write loops, writes inside computed derivations, orphaned effects, and async subscription pitfalls at lint time rather than at runtime.
5
+ Catches common reactive programming mistakes (read/write loops, writes inside computed derivations, orphaned effects, and async subscription pitfalls) at lint time rather than at runtime.
6
6
 
7
7
  ## Installation
8
8
 
@@ -34,7 +34,7 @@ export default [
34
34
  {
35
35
  plugins: { kensington },
36
36
  rules: {
37
- 'kensington/no-set-in-computed': 'error',
37
+ 'kensington/no-set-in-derivation': 'error',
38
38
  'kensington/no-self-read-write': 'error',
39
39
  // ...
40
40
  },
@@ -42,22 +42,54 @@ export default [
42
42
  ];
43
43
  ```
44
44
 
45
+ The `style` config is opt-in and bundles the formatting rules at `warn` level:
46
+
47
+ ```js
48
+ import kensington from 'kensington-eslint-plugin';
49
+
50
+ export default [
51
+ kensington.configs.recommended,
52
+ kensington.configs.style,
53
+ ];
54
+ ```
55
+
56
+ The formatting rules match calls on `t.<tag>(...)` by default. If your Kensington
57
+ instance is bound to a different name, set it once via plugin-level settings:
58
+
59
+ ```js
60
+ import kensington from 'kensington-eslint-plugin';
61
+
62
+ export default [
63
+ kensington.configs.recommended,
64
+ kensington.configs.style,
65
+ {
66
+ settings: { kensington: { objectNames: ['t', 'tag'] } },
67
+ },
68
+ ];
69
+ ```
70
+
71
+ Per-rule options override the shared setting:
72
+
73
+ ```js
74
+ 'kensington/prefer-camelcase-attrs': ['warn', { objectNames: ['k'] }],
75
+ ```
76
+
45
77
  ## Editor and tooling support
46
78
 
47
- Because this is a standard ESLint plugin, it works anywhere ESLint runs no extra configuration needed:
79
+ Because this is a standard ESLint plugin, it works anywhere ESLint runs with no extra configuration needed.
48
80
 
49
- - **Editors** VS Code, JetBrains IDEs (RubyMine, WebStorm, etc.), Neovim, and any editor with an ESLint language server show inline errors automatically once the plugin is configured.
50
- - **CI** run `eslint --max-warnings 0` in any pipeline to enforce rules on every push.
51
- - **Pre-commit hooks** works with `lint-staged` or any hook runner that invokes ESLint.
52
- - **Programmatic use** available via the ESLint Node.js API (`new ESLint()`) for custom tooling.
81
+ - **Editors.** VS Code, JetBrains IDEs (RubyMine, WebStorm, etc.), Neovim, and any editor with an ESLint language server show inline errors automatically once the plugin is configured.
82
+ - **CI.** Run `eslint --max-warnings 0` in any pipeline to enforce rules on every push.
83
+ - **Pre-commit hooks.** Works with `lint-staged` or any hook runner that invokes ESLint.
84
+ - **Programmatic use.** Available via the ESLint Node.js API (`new ESLint()`) for custom tooling.
53
85
 
54
86
  ## Rules
55
87
 
56
88
  | Rule | Description | Recommended |
57
89
  |------|-------------|-------------|
58
- | [`no-set-in-computed`](#no-set-in-computed) | Disallow `.set()` inside a `computed()` body | error |
90
+ | [`no-set-in-derivation`](#no-set-in-derivation) | Disallow `.set()` inside a `computed()` body or `.transform()` callback | error |
59
91
  | [`no-self-read-write`](#no-self-read-write) | Disallow reading and writing the same signal in the same reactive run | error |
60
- | [`no-set-on-computed`](#no-set-on-computed) | Disallow `.set()` on a computed signal | error |
92
+ | [`no-set-on-derived-signal`](#no-set-on-derived-signal) | Disallow `.set()` on a derived (computed or transform) signal | error |
61
93
  | [`no-new-signal-in-effect`](#no-new-signal-in-effect) | Disallow creating a new `signal()` inside an `effect()` body | error |
62
94
  | [`no-effect-in-computed`](#no-effect-in-computed) | Disallow calling `effect()` inside a `computed()` body | error |
63
95
  | [`no-signal-async-write`](#no-signal-async-write) | Disallow writing a signal in an async callback when it was read in the enclosing `effect()` | warn |
@@ -70,23 +102,35 @@ Because this is a standard ESLint plugin, it works anywhere ESLint runs — no e
70
102
  | [`no-effect-in-effect`](#no-effect-in-effect) | Disallow creating a new `effect()` inside an `effect()` body | error |
71
103
  | [`no-async-effect`](#no-async-effect) | Disallow async callbacks passed to `effect()` | error |
72
104
  | [`no-async-computed`](#no-async-computed) | Disallow async callbacks passed to `computed()` | error |
73
- | [`no-set-in-transform`](#no-set-in-transform) | Disallow `.set()` inside a `.transform()` callback | error |
74
- | [`no-set-on-transform`](#no-set-on-transform) | Disallow `.set()` on a transform-derived signal | error |
105
+ | [`prefer-boolean-attribute-true`](#prefer-boolean-attribute-true) | Prefer `true` over `''` for boolean HTML attributes | style |
106
+ | [`prefer-camelcase-attrs`](#prefer-camelcase-attrs) | Prefer camelCase identifier keys over quoted kebab-case | style |
107
+ | [`prefer-style-object`](#prefer-style-object) | Prefer a `style` object over a CSS string | style |
108
+ | [`prefer-nested-attr-groups`](#prefer-nested-attr-groups) | Prefer nested form when attrs share a kebab prefix | style |
109
+ | [`prefer-array-for-multiline-content`](#prefer-array-for-multiline-content) | Require array brackets around multi-line tag content | style |
110
+ | [`attrs-on-call-line`](#attrs-on-call-line) | Attributes object must hug the tag call on both ends | style |
111
+ | [`attrs-canonical-shape`](#attrs-canonical-shape) | Attributes object must be inline or canonically stacked | style |
112
+ | [`consistent-content-layout`](#consistent-content-layout) | Tag content must hug the attrs `}` and the call's `)` | style |
75
113
 
76
114
  ---
77
115
 
78
- ### `no-set-in-computed`
116
+ ### `no-set-in-derivation`
79
117
 
80
- Computed functions must be pure derivations. Calling `.set()` inside one causes a write during a read pass.
118
+ Derivations are `computed()` bodies and `.transform()` callbacks. They must be pure. Calling `.set()` inside one causes a write during a read pass.
81
119
 
82
120
  ```js
83
- // Bad
121
+ // Bad. Write inside computed().
84
122
  const doubled = computed(() => {
85
123
  sideEffect.set(true); // error
86
124
  return count.get() * 2;
87
125
  });
88
126
 
89
- // Good move the write into an effect
127
+ // Bad. Write inside .transform().
128
+ const rows = items.transform(list => {
129
+ selectedId.set(null); // error
130
+ return list.map(item => t.li(item.name));
131
+ });
132
+
133
+ // Good. Move the write into a separate effect.
90
134
  effect(() => {
91
135
  sideEffect.set(doubled.get() > 10);
92
136
  });
@@ -102,10 +146,10 @@ Reading a signal with `.get()` subscribes to it. Writing it with `.set()` in the
102
146
  // Bad
103
147
  effect(() => {
104
148
  const val = count.get();
105
- count.set(val + 1); // error triggers the effect again
149
+ count.set(val + 1); // error. Triggers the effect again.
106
150
  });
107
151
 
108
- // Good use .value to read without subscribing
152
+ // Good. Use .value to read without subscribing.
109
153
  effect(() => {
110
154
  const val = count.value;
111
155
  count.set(val + 1);
@@ -114,13 +158,18 @@ effect(() => {
114
158
 
115
159
  ---
116
160
 
117
- ### `no-set-on-computed`
161
+ ### `no-set-on-derived-signal`
118
162
 
119
- Computed signals are read-only. Kensington throws at runtime if you call `.set()` on one; this catches it statically.
163
+ Derived signals are those produced by `computed()` or `.transform()`. They are read-only. Kensington throws at runtime if you call `.set()` on one; this catches it statically.
120
164
 
121
165
  ```js
166
+ // computed
122
167
  const doubled = computed(() => count.get() * 2);
123
- doubled.set(10); // error use signal() for writable state
168
+ doubled.set(10); // error. Use signal() for writable state.
169
+
170
+ // transform
171
+ const rows = items.transform(v => v.map(x => x.id));
172
+ rows.set([]); // error. Write to the source signal instead.
124
173
  ```
125
174
 
126
175
  ---
@@ -132,7 +181,7 @@ Each effect run creates a fresh signal with no cleanup path. The signal should b
132
181
  ```js
133
182
  // Bad
134
183
  effect(() => {
135
- const local = signal(0); // error orphaned on every run
184
+ const local = signal(0); // error. Orphaned on every run.
136
185
  });
137
186
 
138
187
  // Good
@@ -167,11 +216,11 @@ If a signal is read via `.get()` in an effect and then written in an async callb
167
216
  effect(() => {
168
217
  const val = count.get(); // subscribes
169
218
  setTimeout(() => {
170
- count.set(val + 1); // error — re-triggers the effect
219
+ count.set(val + 1); // error. Re-triggers the effect.
171
220
  }, 100);
172
221
  });
173
222
 
174
- // Good use .value to read without subscribing
223
+ // Good. Use .value to read without subscribing.
175
224
  effect(() => {
176
225
  setTimeout(() => {
177
226
  count.set(count.value + 1);
@@ -188,7 +237,7 @@ effect(() => {
188
237
  ```js
189
238
  // Bad
190
239
  function setup() {
191
- effect(() => console.log(count.get())); // warn — can't stop it
240
+ effect(() => console.log(count.get())); // warn. Can't stop it.
192
241
  }
193
242
 
194
243
  // Good
@@ -204,13 +253,13 @@ Module-level effects are intentionally long-lived and are not flagged.
204
253
 
205
254
  ### `prefer-value-in-async`
206
255
 
207
- Once an effect's synchronous body completes, async callbacks run outside its reactive context. `.get()` registers no subscription there `.value` makes that explicit.
256
+ Once an effect's synchronous body completes, async callbacks run outside its reactive context. `.get()` registers no subscription there. `.value` makes that explicit.
208
257
 
209
258
  ```js
210
259
  // Bad
211
260
  effect(() => {
212
261
  fetch('/api').then(() => {
213
- console.log(count.get()); // warn no subscription is registered
262
+ console.log(count.get()); // warn. No subscription is registered.
214
263
  });
215
264
  });
216
265
 
@@ -231,7 +280,7 @@ Creating `computed()` inside an `effect()` creates a new orphaned derived signal
231
280
  ```js
232
281
  // Bad
233
282
  effect(() => {
234
- const doubled = computed(() => count.get() * 2); // error orphaned every run
283
+ const doubled = computed(() => count.get() * 2); // error. Orphaned every run.
235
284
  console.log(doubled.get());
236
285
  });
237
286
 
@@ -249,7 +298,7 @@ Creating `signal()` inside `computed()` creates a new orphaned signal on every r
249
298
  ```js
250
299
  // Bad
251
300
  const c = computed(() => {
252
- const temp = signal(0); // error orphaned every recompute
301
+ const temp = signal(0); // error. Orphaned every recompute.
253
302
  return temp.get() + base.get();
254
303
  });
255
304
 
@@ -266,7 +315,7 @@ const c = computed(() => temp.get() + base.get());
266
315
 
267
316
  ```js
268
317
  // Bad
269
- t.unsafeLiteral(userContent); // error bypasses XSS protection
318
+ t.unsafeLiteral(userContent); // error. Bypasses XSS protection.
270
319
 
271
320
  // Good
272
321
  t.literal(userContent);
@@ -281,7 +330,7 @@ Creating `computed()` inside a `computed()` body creates a new orphaned derived
281
330
  ```js
282
331
  // Bad
283
332
  const outer = computed(() => {
284
- const inner = computed(() => count.get() * 2); // error orphaned every recompute
333
+ const inner = computed(() => count.get() * 2); // error. Orphaned every recompute.
285
334
  return inner.get() + 1;
286
335
  });
287
336
 
@@ -294,16 +343,16 @@ const outer = computed(() => inner.get() + 1);
294
343
 
295
344
  ### `no-effect-in-effect`
296
345
 
297
- Creating `effect()` inside an `effect()` body means every re-run of the outer effect adds a new inner effect without stopping the previous one subscriptions accumulate indefinitely. Capturing the return handle does not fix this; the previous handle would need to be explicitly stopped at the top of each run.
346
+ Creating `effect()` inside an `effect()` body means every re-run of the outer effect adds a new inner effect without stopping the previous one. Subscriptions accumulate indefinitely. Capturing the return handle does not fix this; the previous handle would need to be explicitly stopped at the top of each run.
298
347
 
299
348
  ```js
300
349
  // Bad
301
350
  effect(() => {
302
351
  const items = list.get();
303
- effect(() => console.log(items)); // error previous inner effect never stopped
352
+ effect(() => console.log(items)); // error. Previous inner effect never stopped.
304
353
  });
305
354
 
306
- // Good restructure as a single effect
355
+ // Good. Restructure as a single effect.
307
356
  effect(() => {
308
357
  console.log(list.get());
309
358
  });
@@ -322,7 +371,7 @@ effect(async () => { // error
322
371
  title.set(data.title); // runs outside reactive context
323
372
  });
324
373
 
325
- // Good keep reactive reads synchronous, push async work into .then()
374
+ // Good. Keep reactive reads synchronous, push async work into .then().
326
375
  effect(() => {
327
376
  fetch(`/api/${id.get()}`).then(r => r.json()).then(data => title.set(data.title));
328
377
  });
@@ -335,13 +384,13 @@ effect(() => {
335
384
  The reactive system runs `computed()` callbacks synchronously. An async callback returns a `Promise` immediately, so the computed value is always a `Promise` object rather than the intended derived value.
336
385
 
337
386
  ```js
338
- // Bad computed value is a Promise, not the resolved data
387
+ // Bad. Computed value is a Promise, not the resolved data.
339
388
  const data = computed(async () => { // error
340
389
  return await fetch('/api').then(r => r.json());
341
390
  });
342
391
  t.p(data); // renders "[object Promise]"
343
392
 
344
- // Good signal for the result, effect to populate it
393
+ // Good. Signal for the result, effect to populate it.
345
394
  const data = signal(null);
346
395
  effect(() => {
347
396
  fetch('/api').then(r => r.json()).then(v => data.set(v));
@@ -350,34 +399,201 @@ effect(() => {
350
399
 
351
400
  ---
352
401
 
353
- ### `no-set-in-transform`
402
+ ### `prefer-boolean-attribute-true`
354
403
 
355
- Transform callbacks must be pure derivations. Calling `.set()` inside one causes a write during a read pass, the same class of bug as `.set()` inside `computed()`.
404
+ The HTML spec lists ~30 boolean attributes (`disabled`, `checked`, `hidden`, `selected`, etc.). Kensington treats `true` as "present" and `false`/`null`/`undefined` as "absent". An empty string is a confusing way to spell the same thing.
356
405
 
357
406
  ```js
358
407
  // Bad
359
- const rows = items.transform(list => {
360
- selectedId.set(null); // error
361
- return list.map(item => t.li(item.name));
408
+ t.input({ disabled: '' });
409
+
410
+ // Good
411
+ t.input({ disabled: true });
412
+ ```
413
+
414
+ Auto-fixable. Extend the recognised set with the `extraBooleanAttrs` option.
415
+
416
+ ---
417
+
418
+ ### `prefer-camelcase-attrs`
419
+
420
+ Quoted kebab-case keys (`'aria-label'`, `'data-key'`) work, but the camelCase identifier form is the idiomatic Kensington style and matches what `html-to-kensington` emits.
421
+
422
+ ```js
423
+ // Bad
424
+ t.div({ 'aria-label': 'foo', 'data-key': k });
425
+
426
+ // Good
427
+ t.div({ ariaLabel: 'foo', dataKey: k });
428
+ ```
429
+
430
+ Auto-fixable.
431
+
432
+ ---
433
+
434
+ ### `prefer-style-object`
435
+
436
+ A `style` object lets TypeScript validate CSS property names and lets Kensington skip runtime string parsing. The fixer parses the CSS string and converts each declaration to a camelCase property.
437
+
438
+ ```js
439
+ // Bad
440
+ t.div({ style: 'background-color: red; z-index: 2' });
441
+
442
+ // Good
443
+ t.div({ style: { backgroundColor: 'red', zIndex: '2' } });
444
+ ```
445
+
446
+ Auto-fixable. The rule does **not** trigger when any property name fails to round-trip cleanly through Kensington's kebab/camelCase conversion. The two notable cases:
447
+
448
+ - CSS custom properties (`--bg-color`) would require quoted JS keys.
449
+ - Vendor prefixes (`-webkit-appearance`) lose their leading dash when Kensington serialises the object back to CSS.
450
+
451
+ Strings containing template expressions (`style: \`color: ${c}\``) are also skipped.
452
+
453
+ ---
454
+
455
+ ### `prefer-nested-attr-groups`
456
+
457
+ When two or more keys share the same kebab prefix (`data-*`, `aria-*`, `hx-*`, etc.), the nested form is shorter and groups related attributes visually.
458
+
459
+ ```js
460
+ // Bad
461
+ t.input({ hxGet: '/x', hxTrigger: 'change', hxTarget: '#y' });
462
+
463
+ // Good
464
+ t.input({ hx: { get: '/x', trigger: 'change', target: '#y' } });
465
+ ```
466
+
467
+ Auto-fixable when the group's members are contiguous in the source. Non-contiguous members and groups whose prefix is already in use by a sibling key are reported but not fixed.
468
+
469
+ ---
470
+
471
+ ### `prefer-array-for-multiline-content`
472
+
473
+ Mirrors what `html-to-kensington` emits: when a tag's content can't fit on the same line as the opening paren, it goes in an array, even when it's the only item. The array form makes line-by-line edits easier (no need to add `[ ]` when adding a sibling).
474
+
475
+ ```js
476
+ // Bad
477
+ t.div({ class: 'x' },
478
+ t.p('only')
479
+ );
480
+
481
+ // Good
482
+ t.div({ class: 'x' }, [
483
+ t.p('only'),
484
+ ]);
485
+ ```
486
+
487
+ Auto-fixable. Single-line calls (`t.div({…}, t.p('inner'))`) are left alone.
488
+
489
+ ---
490
+
491
+ ### `attrs-on-call-line`
492
+
493
+ The attributes object hugs the call on both ends:
494
+
495
+ - the opening `{` sits on the same line as the call's `(`, and
496
+ - the closing `}` sits on the same line as the content (or its `[`) when there is one, or on the same line as the call's `)` when there isn't.
497
+
498
+ ```js
499
+ // Bad. { not on call line.
500
+ t.div(
501
+ { class: 'x' }
502
+ );
503
+
504
+ // Bad. } not on the ) line.
505
+ t.div({
506
+ class: 'x',
507
+ }
508
+ );
509
+
510
+ // Bad. } not on the content's line.
511
+ t.div({
512
+ class: 'x',
513
+ },
514
+ t.p('inner')
515
+ );
516
+
517
+ // Good
518
+ t.div({ class: 'x' });
519
+ t.div({
520
+ class: 'x',
362
521
  });
522
+ t.div({
523
+ class: 'x',
524
+ }, [
525
+ t.p('a'),
526
+ ]);
527
+ t.div({ class: 'x' }, t.p('inner'));
528
+ ```
363
529
 
364
- // Good move the write into a separate effect
365
- effect(() => {
366
- if (!items.get().length) { selectedId.set(null); }
530
+ Auto-fixable. A comment in the gap blocks the auto-fix for that side; the issue is still reported.
531
+
532
+ ---
533
+
534
+ ### `attrs-canonical-shape`
535
+
536
+ The attributes object is either fully inline or fully stacked with one property per line and the braces on their own lines. Mixed forms are inconsistent and harder to diff.
537
+
538
+ ```js
539
+ // Bad. Open brace shares a line with the first prop.
540
+ t.div({ class: 'x',
541
+ id: 'y',
542
+ });
543
+
544
+ // Bad. Close brace shares a line with the last prop.
545
+ t.div({
546
+ class: 'x',
547
+ id: 'y' });
548
+
549
+ // Bad. Two props on the same line in an otherwise multi-line object.
550
+ t.div({
551
+ class: 'x', id: 'y',
552
+ role: 'button',
553
+ });
554
+
555
+ // Good
556
+ t.div({ class: 'x', id: 'y' });
557
+ t.div({
558
+ class: 'x',
559
+ id: 'y',
367
560
  });
368
561
  ```
369
562
 
563
+ Auto-fixable when the object contains no interior comments. The fixer rewrites to the stacked form using the call's leading indentation as the base.
564
+
370
565
  ---
371
566
 
372
- ### `no-set-on-transform`
567
+ ### `consistent-content-layout`
373
568
 
374
- Transform-derived signals are read-only. Calling `.set()` on one throws at runtime; this rule catches it statically.
569
+ The content argument (or its opening `[`) hugs the attrs object's `}` (or the call's `(` when there are no attrs), and the content's last token (or `]`) hugs the closing `)`. This is the shape `html-to-kensington` emits and what makes line-by-line diffs clean.
375
570
 
376
571
  ```js
377
- // Bad
378
- const doubled = count.transform(v => v * 2);
379
- doubled.set(10); // error — transform results are read-only
572
+ // Bad. Content on a separate line from the attrs/call.
573
+ t.div({ class: 'x' },
574
+ t.p('only')
575
+ );
576
+
577
+ // Bad. [ on its own line.
578
+ t.div({ class: 'x' },
579
+ [
580
+ t.p('a'),
581
+ ]);
582
+
583
+ // Bad. ] not on the closing-paren line.
584
+ t.div({ class: 'x' }, [
585
+ t.p('a'),
586
+ ]
587
+ );
380
588
 
381
- // Good — write to the source signal instead
382
- count.set(5);
589
+ // Good
590
+ t.div({ class: 'x' }, t.p('only'));
591
+ t.div({ class: 'x' }, [
592
+ t.p('a'),
593
+ ]);
594
+ t.div([
595
+ t.p('a'),
596
+ ]);
383
597
  ```
598
+
599
+ Auto-fixable. A comment between the anchor and the content (or between the content and `)`) suppresses the auto-fix for that side.
package/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import noSetInComputed from './rules/no-set-in-computed.js';
1
+ import noSetInDerivation from './rules/no-set-in-derivation.js';
2
2
  import noSelfReadWrite from './rules/no-self-read-write.js';
3
3
  import noSignalAsyncWrite from './rules/no-signal-async-write.js';
4
- import noSetOnComputed from './rules/no-set-on-computed.js';
4
+ import noSetOnDerivedSignal from './rules/no-set-on-derived-signal.js';
5
5
  import noNewSignalInEffect from './rules/no-new-signal-in-effect.js';
6
6
  import noEffectInComputed from './rules/no-effect-in-computed.js';
7
7
  import noIgnoredEffectReturn from './rules/no-ignored-effect-return.js';
@@ -13,16 +13,22 @@ import noNewComputedInComputed from './rules/no-new-computed-in-computed.js';
13
13
  import noEffectInEffect from './rules/no-effect-in-effect.js';
14
14
  import noAsyncEffect from './rules/no-async-effect.js';
15
15
  import noAsyncComputed from './rules/no-async-computed.js';
16
- import noSetInTransform from './rules/no-set-in-transform.js';
17
- import noSetOnTransform from './rules/no-set-on-transform.js';
16
+ import preferBooleanAttributeTrue from './rules/prefer-boolean-attribute-true.js';
17
+ import preferCamelcaseAttrs from './rules/prefer-camelcase-attrs.js';
18
+ import preferStyleObject from './rules/prefer-style-object.js';
19
+ import preferNestedAttrGroups from './rules/prefer-nested-attr-groups.js';
20
+ import preferArrayForMultilineContent from './rules/prefer-array-for-multiline-content.js';
21
+ import attrsOnCallLine from './rules/attrs-on-call-line.js';
22
+ import attrsCanonicalShape from './rules/attrs-canonical-shape.js';
23
+ import consistentContentLayout from './rules/consistent-content-layout.js';
18
24
 
19
25
  const plugin = {
20
26
  meta: { name: 'eslint-plugin-kensington' },
21
27
  rules: {
22
- 'no-set-in-computed': noSetInComputed,
28
+ 'no-set-in-derivation': noSetInDerivation,
23
29
  'no-self-read-write': noSelfReadWrite,
24
30
  'no-signal-async-write': noSignalAsyncWrite,
25
- 'no-set-on-computed': noSetOnComputed,
31
+ 'no-set-on-derived-signal': noSetOnDerivedSignal,
26
32
  'no-new-signal-in-effect': noNewSignalInEffect,
27
33
  'no-effect-in-computed': noEffectInComputed,
28
34
  'no-ignored-effect-return': noIgnoredEffectReturn,
@@ -34,8 +40,14 @@ const plugin = {
34
40
  'no-effect-in-effect': noEffectInEffect,
35
41
  'no-async-effect': noAsyncEffect,
36
42
  'no-async-computed': noAsyncComputed,
37
- 'no-set-in-transform': noSetInTransform,
38
- 'no-set-on-transform': noSetOnTransform,
43
+ 'prefer-boolean-attribute-true': preferBooleanAttributeTrue,
44
+ 'prefer-camelcase-attrs': preferCamelcaseAttrs,
45
+ 'prefer-style-object': preferStyleObject,
46
+ 'prefer-nested-attr-groups': preferNestedAttrGroups,
47
+ 'prefer-array-for-multiline-content': preferArrayForMultilineContent,
48
+ 'attrs-on-call-line': attrsOnCallLine,
49
+ 'attrs-canonical-shape': attrsCanonicalShape,
50
+ 'consistent-content-layout': consistentContentLayout,
39
51
  },
40
52
  configs: {},
41
53
  };
@@ -43,10 +55,10 @@ const plugin = {
43
55
  plugin.configs.recommended = {
44
56
  plugins: { kensington: plugin },
45
57
  rules: {
46
- 'kensington/no-set-in-computed': 'error',
58
+ 'kensington/no-set-in-derivation': 'error',
47
59
  'kensington/no-self-read-write': 'error',
48
60
  'kensington/no-signal-async-write': 'warn',
49
- 'kensington/no-set-on-computed': 'error',
61
+ 'kensington/no-set-on-derived-signal': 'error',
50
62
  'kensington/no-new-signal-in-effect': 'error',
51
63
  'kensington/no-effect-in-computed': 'error',
52
64
  'kensington/no-ignored-effect-return': 'warn',
@@ -58,8 +70,20 @@ plugin.configs.recommended = {
58
70
  'kensington/no-effect-in-effect': 'error',
59
71
  'kensington/no-async-effect': 'error',
60
72
  'kensington/no-async-computed': 'error',
61
- 'kensington/no-set-in-transform': 'error',
62
- 'kensington/no-set-on-transform': 'error',
73
+ },
74
+ };
75
+
76
+ plugin.configs.style = {
77
+ plugins: { kensington: plugin },
78
+ rules: {
79
+ 'kensington/prefer-boolean-attribute-true': 'warn',
80
+ 'kensington/prefer-camelcase-attrs': 'warn',
81
+ 'kensington/prefer-style-object': 'warn',
82
+ 'kensington/prefer-nested-attr-groups': 'warn',
83
+ 'kensington/prefer-array-for-multiline-content': 'warn',
84
+ 'kensington/attrs-on-call-line': 'warn',
85
+ 'kensington/attrs-canonical-shape': 'warn',
86
+ 'kensington/consistent-content-layout': 'warn',
63
87
  },
64
88
  };
65
89
 
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "kensington-eslint-plugin",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "ESLint rules for kensington signal correctness",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "scripts": {
8
- "test": "node --test tests/no-set-in-computed.test.js tests/no-self-read-write.test.js tests/no-signal-async-write.test.js tests/no-set-on-computed.test.js tests/no-new-signal-in-effect.test.js tests/no-effect-in-computed.test.js tests/no-ignored-effect-return.test.js tests/prefer-value-in-async.test.js tests/no-new-computed-in-effect.test.js tests/no-new-signal-in-computed.test.js tests/no-unsafe-literal.test.js tests/no-new-computed-in-computed.test.js tests/no-effect-in-effect.test.js tests/no-async-effect.test.js tests/no-async-computed.test.js tests/no-set-in-transform.test.js tests/no-set-on-transform.test.js"
8
+ "test": "node --test tests/no-set-in-derivation.test.js tests/no-self-read-write.test.js tests/no-signal-async-write.test.js tests/no-set-on-derived-signal.test.js tests/no-new-signal-in-effect.test.js tests/no-effect-in-computed.test.js tests/no-ignored-effect-return.test.js tests/prefer-value-in-async.test.js tests/no-new-computed-in-effect.test.js tests/no-new-signal-in-computed.test.js tests/no-unsafe-literal.test.js tests/no-new-computed-in-computed.test.js tests/no-effect-in-effect.test.js tests/no-async-effect.test.js tests/no-async-computed.test.js tests/prefer-boolean-attribute-true.test.js tests/prefer-camelcase-attrs.test.js tests/prefer-style-object.test.js tests/prefer-nested-attr-groups.test.js tests/prefer-array-for-multiline-content.test.js tests/attrs-on-call-line.test.js tests/attrs-canonical-shape.test.js tests/consistent-content-layout.test.js"
9
9
  },
10
10
  "peerDependencies": {
11
11
  "eslint": ">=9"