lint-suite 1.6.2 → 1.6.3

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 (63) hide show
  1. package/README.md +149 -2
  2. package/eslint.cjs +1049 -309
  3. package/eslint.cjs.map +4 -4
  4. package/eslint.js +938 -175
  5. package/eslint.js.map +4 -4
  6. package/lib/angular-template.d.ts +1 -0
  7. package/lib/angular-template.d.ts.map +1 -1
  8. package/lib/angular.d.ts.map +1 -1
  9. package/lib/base-practices.d.ts.map +1 -1
  10. package/lib/line-length.const.d.ts +2 -0
  11. package/lib/line-length.const.d.ts.map +1 -0
  12. package/lib/rules/common/class-usage.type.d.ts +25 -0
  13. package/lib/rules/common/class-usage.type.d.ts.map +1 -0
  14. package/lib/rules/component-metadata.d.ts +5 -0
  15. package/lib/rules/component-metadata.d.ts.map +1 -0
  16. package/lib/rules/no-unstyled-classes/common/no-unstyled-classes.type.d.ts +33 -0
  17. package/lib/rules/no-unstyled-classes/common/no-unstyled-classes.type.d.ts.map +1 -0
  18. package/lib/rules/no-unstyled-classes/no-unstyled-classes.d.ts +8 -0
  19. package/lib/rules/no-unstyled-classes/no-unstyled-classes.d.ts.map +1 -0
  20. package/lib/rules/no-unstyled-classes/styles/component-stylesheets.d.ts +4 -0
  21. package/lib/rules/no-unstyled-classes/styles/component-stylesheets.d.ts.map +1 -0
  22. package/lib/rules/no-unstyled-classes/styles/stylesheet-classes.d.ts +3 -0
  23. package/lib/rules/no-unstyled-classes/styles/stylesheet-classes.d.ts.map +1 -0
  24. package/lib/rules/no-unstyled-classes/styles/stylesheet-collection.d.ts +4 -0
  25. package/lib/rules/no-unstyled-classes/styles/stylesheet-collection.d.ts.map +1 -0
  26. package/lib/rules/no-unstyled-classes/styles/stylesheet-imports.d.ts +2 -0
  27. package/lib/rules/no-unstyled-classes/styles/stylesheet-imports.d.ts.map +1 -0
  28. package/lib/rules/no-unstyled-classes/styles/template-stylesheets.d.ts +3 -0
  29. package/lib/rules/no-unstyled-classes/styles/template-stylesheets.d.ts.map +1 -0
  30. package/lib/rules/no-unused-angular-instance-fields/rule/angular/angular-metadata-literals.d.ts.map +1 -1
  31. package/lib/rules/no-unused-classes/common/no-unused-classes.type.d.ts +24 -0
  32. package/lib/rules/no-unused-classes/common/no-unused-classes.type.d.ts.map +1 -0
  33. package/lib/rules/no-unused-classes/no-unused-classes.d.ts +8 -0
  34. package/lib/rules/no-unused-classes/no-unused-classes.d.ts.map +1 -0
  35. package/lib/rules/no-unused-classes/styles/component-descriptors.d.ts +3 -0
  36. package/lib/rules/no-unused-classes/styles/component-descriptors.d.ts.map +1 -0
  37. package/lib/rules/no-unused-classes/styles/stylesheet-components.d.ts +3 -0
  38. package/lib/rules/no-unused-classes/styles/stylesheet-components.d.ts.map +1 -0
  39. package/lib/rules/no-unused-classes/template/template-usage.d.ts +3 -0
  40. package/lib/rules/no-unused-classes/template/template-usage.d.ts.map +1 -0
  41. package/lib/rules/one-line-guard/one-line-guard.d.ts +10 -0
  42. package/lib/rules/one-line-guard/one-line-guard.d.ts.map +1 -0
  43. package/lib/rules/utils/class-expression-literals.util.d.ts +4 -0
  44. package/lib/rules/utils/class-expression-literals.util.d.ts.map +1 -0
  45. package/lib/rules/utils/resolved-rules.util.d.ts +5 -0
  46. package/lib/rules/utils/resolved-rules.util.d.ts.map +1 -0
  47. package/lib/rules/utils/selector-classes.util.d.ts +7 -0
  48. package/lib/rules/utils/selector-classes.util.d.ts.map +1 -0
  49. package/lib/rules/utils/template-classes.util.d.ts +3 -0
  50. package/lib/rules/utils/template-classes.util.d.ts.map +1 -0
  51. package/lib/typescript-local-plugin.d.ts.map +1 -1
  52. package/lib/typescript-safety.d.ts.map +1 -1
  53. package/package.json +4 -1
  54. package/prettier.cjs +6 -1
  55. package/prettier.cjs.map +3 -3
  56. package/prettier.d.ts.map +1 -1
  57. package/prettier.js +4 -1
  58. package/prettier.js.map +3 -3
  59. package/stylelint.cjs +680 -4
  60. package/stylelint.cjs.map +4 -4
  61. package/stylelint.d.ts.map +1 -1
  62. package/stylelint.js +708 -4
  63. package/stylelint.js.map +4 -4
package/README.md CHANGED
@@ -12,7 +12,7 @@ A comprehensive collection of ESLint Flat configurations for modern web applicat
12
12
  - **Testing**: Vitest and Playwright configurations with best-practice rules
13
13
  - **Prettier**: Automatic disabling of formatting rules that conflict with Prettier (`eslint-config-prettier`)
14
14
  - **Prettier config**: Standalone formatting preset (subpath `lint-suite/prettier`) with the suite's house defaults and Angular/HTML overrides
15
- - **Stylelint**: Standalone SCSS/CSS preset (subpath `lint-suite/stylelint`) with standard + recess-order + BEM selector enforcement
15
+ - **Stylelint**: Standalone SCSS/CSS preset (subpath `lint-suite/stylelint`) with standard + recess-order + BEM selector enforcement and `lint-suite/no-unused-classes`
16
16
  - **Architecture**: Module boundary enforcement with `eslint-plugin-boundaries`
17
17
  - **Additional Support**: JSON (with comment support for tsconfig), Storybook CSF enforcement
18
18
 
@@ -89,7 +89,7 @@ export default [
89
89
  | `javascript` | JavaScript-specific rules via `@nx/eslint-plugin` |
90
90
  | `typescript` | TypeScript strict typing, imports, and naming conventions |
91
91
  | `angular` | Angular component best practices with Signal support |
92
- | `angularTemplate` | HTML template rules with accessibility and performance |
92
+ | `angularTemplate` | HTML template rules with accessibility, performance, and `lint-suite-angular-template/no-unstyled-classes` |
93
93
  | `rxjs` | Observable patterns, operator safety, and subscriptions |
94
94
  | `vitest` | Vitest testing rules and matcher improvements |
95
95
  | `playwright` | Playwright e2e locator and matcher best practices |
@@ -177,6 +177,120 @@ templates can read a component or directive member:
177
177
  every matching component or directive in the Program is a candidate. Extra
178
178
  candidates can only add reads. Metadata strings may be constants.
179
179
 
180
+ ### No unstyled classes
181
+
182
+ The `angularTemplate` config enables
183
+ `lint-suite-angular-template/no-unstyled-classes`, which reports a class name
184
+ used in an Angular HTML template that no stylesheet of that component selects.
185
+ It reads three sources in the template: the static `class="a b"` attribute
186
+ (each token reported at its own column), `[class.name]` bindings, and the
187
+ literal class names inside `[class]="..."` expressions and `class="a {{ b }}"`
188
+ interpolations. String literals, object-literal keys, array elements, and both
189
+ branches of a ternary contribute names; identifiers, calls, pipes, and `+`
190
+ concatenations contribute nothing, so a class the rule cannot see is never
191
+ reported. `[ngClass]` is deliberately not analysed.
192
+
193
+ Stylesheets come from the component beside the template: `styleUrl`,
194
+ `styleUrls`, and inline `styles` read as string or template literals from the
195
+ `@Component` metadata, falling back to a sibling `.scss` or `.css` file when
196
+ the metadata declares none. Each stylesheet is parsed with `postcss-scss`, so
197
+ `&__element`, `&--modifier`, `&.other`, `& > .child`, `.wrapper &`, and rules
198
+ nested inside `@media` all resolve against their parent selector, and a
199
+ selector list such as `.a, .b { &__x {} }` yields both `.a__x` and `.b__x`.
200
+ `@use`, `@import`, and `@forward` are followed to their partials
201
+ (`_name.scss`, `name/index.scss`, `name/_index.scss`). A selector built with
202
+ interpolation (`.icon-#{$size}`) becomes a pattern, so `icon-lg` counts as
203
+ styled and bare `icon` does not.
204
+
205
+ ```js
206
+ {
207
+ files: ['**/*.html'],
208
+ rules: {
209
+ 'lint-suite-angular-template/no-unstyled-classes': [
210
+ 'error',
211
+ {
212
+ ignoreClassPatterns: ['^(js|qa|mat|cdk|mdc)-', '^u-'],
213
+ globalStyles: ['src/styles.scss']
214
+ }
215
+ ]
216
+ }
217
+ }
218
+ ```
219
+
220
+ - `ignoreClassPatterns` defaults to `['^(js|qa|mat|cdk|mdc)-']`. Each entry is
221
+ compiled with `new RegExp(pattern, 'u')`, and a class matching any of them is
222
+ never reported. A configured list replaces the default one instead of
223
+ extending it.
224
+ - `globalStyles` defaults to `[]`. Paths are resolved against the ESLint
225
+ working directory and merged into the known classes of every template. A path
226
+ that does not exist is ignored.
227
+ - Elements whose tag name contains a dash are skipped: the classes on a child
228
+ component, `ng-container`, or `ng-template` may be styled by that component's
229
+ own `:host(.x)`, which this rule cannot see.
230
+ - A template with no stylesheet reports nothing. The same holds when the only
231
+ stylesheet found fails to parse: with nothing to compare against, the rule has
232
+ no opinion.
233
+
234
+ ### No unused classes
235
+
236
+ The `stylelint` preset enables `lint-suite/no-unused-classes`, the dual of the
237
+ rule above: it reports a class selector in a component stylesheet that no
238
+ template of that component uses.
239
+
240
+ Templates are found from the stylesheet. Every `.ts` file beside it is read for
241
+ `@Component` metadata whose `styleUrl` or `styleUrls` resolves to the linted
242
+ file; each matching component contributes its `templateUrl` file or its inline
243
+ `template` literal, and their classes are merged, so a stylesheet shared by two
244
+ components is judged against both templates. When no component declares the
245
+ stylesheet, a sibling template of the same name (`card.component.scss` →
246
+ `card.component.html`) is used instead. A partial (`_tokens.scss`) or a global
247
+ `styles.scss` that no component declares has no template, and the rule stays
248
+ silent.
249
+
250
+ Selectors resolve through the same parser as the ESLint rule, so `&__element`,
251
+ `&--modifier`, `&.other`, `& > .child`, `.wrapper &`, `@media` blocks, and
252
+ selector lists all report the resolved name on the rule that declares it: in
253
+ `.panel { .inner {} }` only `inner` is checked on the inner rule, never `panel`
254
+ twice. Arguments of `:host(.dark)` and `:host-context(.rtl)` are skipped, and
255
+ everything after `::ng-deep`, `/deep/`, or `>>>` is skipped too, because those
256
+ classes live in other templates. A selector built with interpolation
257
+ (`.icon-#{$size}`) is never reported, and `@extend .base` counts `base` as
258
+ used.
259
+
260
+ The template side reads the same sources as `no-unstyled-classes` plus
261
+ `[ngClass]`, and it does not skip custom elements: a class on
262
+ `<app-child class="foo">` is written by this template, so `.foo` counts as
263
+ used. When any template of the stylesheet holds a class source the rule cannot
264
+ read — `[class]="classes()"`, `[ngClass]="map"`, a whole token that is
265
+ `{{ expr }}`, an unparseable template — the rule reports nothing for that
266
+ stylesheet rather than guessing.
267
+
268
+ ```js
269
+ // stylelint.config.mjs
270
+ import { stylelint } from 'lint-suite/stylelint';
271
+
272
+ export default {
273
+ ...stylelint,
274
+ overrides: [
275
+ ...stylelint.overrides,
276
+ {
277
+ files: ['**/*.scss', '**/*.css'],
278
+ rules: {
279
+ 'lint-suite/no-unused-classes': [
280
+ true,
281
+ { ignoreClassPatterns: ['^(js|qa|mat|cdk|mdc)-', '^u-'] }
282
+ ]
283
+ }
284
+ }
285
+ ]
286
+ };
287
+ ```
288
+
289
+ - `ignoreClassPatterns` defaults to `['^(js|qa|mat|cdk|mdc)-']`. Each entry is
290
+ compiled with `new RegExp(pattern, 'u')`, and a class matching any of them is
291
+ never reported. A configured list replaces the default one instead of
292
+ extending it.
293
+
180
294
  ### Explicit accessibility
181
295
 
182
296
  The `typescript` preset enables `local/explicit-accessibility`, which reports
@@ -256,6 +370,38 @@ type LineItem = { readonly name: string; readonly item: Item };
256
370
  - Declare the shape as `type Item = {...}` and reference it instead of
257
371
  inlining the object type.
258
372
 
373
+ ### One-line guard
374
+
375
+ The `typescript` preset enables `local/one-line-guard` (with
376
+ `maxLineLength` set to the preset print width, 135), which reports an
377
+ `if` statement whose braced body is a lone `return`, `throw`, `continue`,
378
+ or `break` when the whole statement would fit on one line. It is
379
+ auto-fixable: the fix drops the braces and joins the guard onto the `if`
380
+ line.
381
+
382
+ ```ts
383
+ // Before
384
+ if (!user) {
385
+ return null;
386
+ }
387
+
388
+ // After
389
+ if (!user) return null;
390
+ ```
391
+
392
+ - Only a block body containing exactly one `return`, `throw`, `continue`,
393
+ or `break` statement is considered a guard; any other body is left
394
+ alone.
395
+ - The rule bails out (no report, no fix) when the `if` has an `else`,
396
+ the block holds a comment, the condition spans multiple lines, the
397
+ guard statement spans multiple lines, or the collapsed line would
398
+ exceed `maxLineLength`.
399
+ - Pass a different width with the rule's options:
400
+ `'local/one-line-guard': ['error', { maxLineLength: 80 }]`.
401
+ - Complements `curly: multi-line`: that rule tolerates a brace-less
402
+ single-line guard once it exists, while `local/one-line-guard` is what
403
+ collapses a braced guard down to one line in the first place.
404
+
259
405
  ## Stylelint and Prettier presets
260
406
 
261
407
  These are standalone configs exported as subpaths — they are not part of the `recommended` ESLint array.
@@ -344,6 +490,7 @@ The Prettier preset is published with `prettier` as a peer dependency. The Style
344
490
  - Extends `stylelint-config-standard`, `stylelint-config-standard-scss`, and `stylelint-config-recess-order`
345
491
  - `selector-class-pattern`: BEM-aware class names with ITCSS-style namespace prefixes (`o-`, `c-`, `u-`, `is-`, `has-`, `js-`, `qa-`, etc.)
346
492
  - `plugin/selector-bem-pattern`: enforces BEM selectors, treats `*.component.scss`/`*.component.css` as implicit components, ignores `--mdc`/`--sys` custom properties
493
+ - `lint-suite/no-unused-classes`: reports a class selector no template of the component uses (see [No unused classes](#no-unused-classes))
347
494
  - `no-descending-specificity`: disabled
348
495
 
349
496
  ### prettier (format config)