Skip to content

Commit 9cf76b5

Browse files
authored
feat: new max-unique-line-heights rule (#154)
closes #141
1 parent ef863cc commit 9cf76b5

8 files changed

Lines changed: 453 additions & 92 deletions

File tree

CONTRIBUTING.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,8 @@
22

33
## Developing
44

5-
Before committing, run the linter:
6-
7-
```sh
8-
npm run lint
9-
```
5+
- Before committing, run the linter: `npm run lint`
6+
- This project uses ESM only, do not use CommonJS
107

118
## Adding a new rule
129

src/configs/design-tokens.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export default {
1313
recommended.rules['projectwallace/max-unique-font-families'],
1414
'projectwallace/max-unique-font-sizes':
1515
recommended.rules['projectwallace/max-unique-font-sizes'],
16+
'projectwallace/max-unique-line-heights':
17+
recommended.rules['projectwallace/max-unique-line-heights'],
1618
'projectwallace/max-unique-gradients': recommended.rules['projectwallace/max-unique-gradients'],
1719
},
1820
}

src/configs/recommended.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export default {
3030
'projectwallace/max-unique-durations': 8,
3131
'projectwallace/max-unique-font-families': 4,
3232
'projectwallace/max-unique-font-sizes': 16,
33+
'projectwallace/max-unique-line-heights': 12,
3334
'projectwallace/max-unique-gradients': 8,
3435
'projectwallace/max-unique-units': 10,
3536
'projectwallace/min-selector-uniqueness-ratio': 0.66,

src/index.test.ts

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,42 +8,43 @@ test('exports an array of stylelint rules', () => {
88
return plugin.ruleName
99
})
1010
expect(names).toStrictEqual([
11-
'projectwallace/max-selector-complexity',
12-
'projectwallace/max-lines-of-code',
13-
'projectwallace/no-unused-custom-properties',
14-
'projectwallace/no-unknown-custom-property',
15-
'projectwallace/no-property-browserhacks',
16-
'projectwallace/no-unused-layers',
17-
'projectwallace/no-unused-container-names',
18-
'projectwallace/no-unknown-container-names',
19-
'projectwallace/no-anonymous-layers',
20-
'projectwallace/no-useless-custom-property-assignment',
21-
'projectwallace/no-unreachable-media-conditions',
22-
'projectwallace/no-static-media-query',
23-
'projectwallace/no-static-container-query',
24-
'projectwallace/max-file-size',
25-
'projectwallace/max-embedded-content-size',
26-
'projectwallace/max-comment-size',
27-
'projectwallace/max-average-selectors-per-rule',
2811
'projectwallace/max-average-declarations-per-rule',
2912
'projectwallace/max-average-selector-complexity',
30-
'projectwallace/max-important-ratio',
31-
'projectwallace/no-duplicate-data-urls',
32-
'projectwallace/max-unique-units',
33-
'projectwallace/min-selector-uniqueness-ratio',
34-
'projectwallace/min-declaration-uniqueness-ratio',
13+
'projectwallace/max-average-selectors-per-rule',
3514
'projectwallace/max-average-specificity',
36-
'projectwallace/max-selectors-per-rule',
15+
'projectwallace/max-comment-size',
3716
'projectwallace/max-declarations-per-rule',
38-
'projectwallace/no-invalid-z-index',
39-
'projectwallace/no-property-shorthand',
17+
'projectwallace/max-embedded-content-size',
18+
'projectwallace/max-file-size',
19+
'projectwallace/max-important-ratio',
20+
'projectwallace/max-lines-of-code',
21+
'projectwallace/max-selector-complexity',
22+
'projectwallace/max-selectors-per-rule',
23+
'projectwallace/max-spacing-resets',
24+
'projectwallace/max-unique-animation-functions',
25+
'projectwallace/max-unique-box-shadows',
4026
'projectwallace/max-unique-colors',
27+
'projectwallace/max-unique-durations',
4128
'projectwallace/max-unique-font-families',
4229
'projectwallace/max-unique-font-sizes',
4330
'projectwallace/max-unique-gradients',
44-
'projectwallace/max-unique-box-shadows',
45-
'projectwallace/max-unique-durations',
46-
'projectwallace/max-unique-animation-functions',
47-
'projectwallace/max-spacing-resets',
31+
'projectwallace/max-unique-line-heights',
32+
'projectwallace/max-unique-units',
33+
'projectwallace/min-declaration-uniqueness-ratio',
34+
'projectwallace/min-selector-uniqueness-ratio',
35+
'projectwallace/no-anonymous-layers',
36+
'projectwallace/no-duplicate-data-urls',
37+
'projectwallace/no-invalid-z-index',
38+
'projectwallace/no-property-browserhacks',
39+
'projectwallace/no-property-shorthand',
40+
'projectwallace/no-static-container-query',
41+
'projectwallace/no-static-media-query',
42+
'projectwallace/no-unknown-container-names',
43+
'projectwallace/no-unknown-custom-property',
44+
'projectwallace/no-unreachable-media-conditions',
45+
'projectwallace/no-unused-container-names',
46+
'projectwallace/no-unused-custom-properties',
47+
'projectwallace/no-unused-layers',
48+
'projectwallace/no-useless-custom-property-assignment',
4849
])
4950
})

src/index.ts

Lines changed: 61 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,83 @@
11
import type stylelint from 'stylelint'
2-
import max_selector_complexity from './rules/max-selector-complexity/index.js'
3-
import no_unused_custom_properties from './rules/no-unused-custom-properties/index.js'
4-
import no_unknown_custom_property from './rules/no-unknown-custom-property/index.js'
5-
import no_property_browserhacks from './rules/no-property-browserhacks/index.js'
6-
import max_lines_of_code from './rules/max-lines-of-code/index.js'
7-
import no_unused_layers from './rules/no-unused-layers/index.js'
8-
import no_unused_container_names from './rules/no-unused-container-names/index.js'
9-
import no_unknown_container_names from './rules/no-unknown-container-names/index.js'
10-
import no_anonymous_layers from './rules/no-anonymous-layers/index.js'
11-
import no_useless_custom_property_assignment from './rules/no-useless-custom-property-assignment/index.js'
12-
import no_unreachable_media_conditions from './rules/no-unreachable-media-conditions/index.js'
13-
import no_static_media_query from './rules/no-static-media-query/index.js'
14-
import no_static_container_query from './rules/no-static-container-query/index.js'
15-
import max_file_size from './rules/max-file-size/index.js'
16-
import max_embedded_content_size from './rules/max-embedded-content-size/index.js'
17-
import max_comment_size from './rules/max-comment-size/index.js'
18-
import max_average_selectors_per_rule from './rules/max-average-selectors-per-rule/index.js'
192
import max_average_declarations_per_rule from './rules/max-average-declarations-per-rule/index.js'
203
import max_average_selector_complexity from './rules/max-average-selector-complexity/index.js'
21-
import max_important_ratio from './rules/max-important-ratio/index.js'
22-
import no_duplicate_data_urls from './rules/no-duplicate-data-urls/index.js'
23-
import max_unique_units from './rules/max-unique-units/index.js'
24-
import min_selector_uniqueness_ratio from './rules/min-selector-uniqueness-ratio/index.js'
25-
import min_declaration_uniqueness_ratio from './rules/min-declaration-uniqueness-ratio/index.js'
4+
import max_average_selectors_per_rule from './rules/max-average-selectors-per-rule/index.js'
265
import max_average_specificity from './rules/max-average-specificity/index.js'
27-
import max_selectors_per_rule from './rules/max-selectors-per-rule/index.js'
6+
import max_comment_size from './rules/max-comment-size/index.js'
287
import max_declarations_per_rule from './rules/max-declarations-per-rule/index.js'
29-
import no_invalid_z_index from './rules/no-invalid-z-index/index.js'
30-
import no_property_shorthand from './rules/no-property-shorthand/index.js'
8+
import max_embedded_content_size from './rules/max-embedded-content-size/index.js'
9+
import max_file_size from './rules/max-file-size/index.js'
10+
import max_important_ratio from './rules/max-important-ratio/index.js'
11+
import max_lines_of_code from './rules/max-lines-of-code/index.js'
12+
import max_selector_complexity from './rules/max-selector-complexity/index.js'
13+
import max_selectors_per_rule from './rules/max-selectors-per-rule/index.js'
14+
import max_spacing_resets from './rules/max-spacing-resets/index.js'
15+
import max_unique_animation_functions from './rules/max-unique-animation-functions/index.js'
16+
import max_unique_box_shadows from './rules/max-unique-box-shadows/index.js'
3117
import max_unique_colors from './rules/max-unique-colors/index.js'
18+
import max_unique_durations from './rules/max-unique-durations/index.js'
3219
import max_unique_font_families from './rules/max-unique-font-families/index.js'
3320
import max_unique_font_sizes from './rules/max-unique-font-sizes/index.js'
3421
import max_unique_gradients from './rules/max-unique-gradients/index.js'
35-
import max_unique_box_shadows from './rules/max-unique-box-shadows/index.js'
36-
import max_unique_durations from './rules/max-unique-durations/index.js'
37-
import max_unique_animation_functions from './rules/max-unique-animation-functions/index.js'
38-
import max_spacing_resets from './rules/max-spacing-resets/index.js'
22+
import max_unique_line_heights from './rules/max-unique-line-heights/index.js'
23+
import max_unique_units from './rules/max-unique-units/index.js'
24+
import min_declaration_uniqueness_ratio from './rules/min-declaration-uniqueness-ratio/index.js'
25+
import min_selector_uniqueness_ratio from './rules/min-selector-uniqueness-ratio/index.js'
26+
import no_anonymous_layers from './rules/no-anonymous-layers/index.js'
27+
import no_duplicate_data_urls from './rules/no-duplicate-data-urls/index.js'
28+
import no_invalid_z_index from './rules/no-invalid-z-index/index.js'
29+
import no_property_browserhacks from './rules/no-property-browserhacks/index.js'
30+
import no_property_shorthand from './rules/no-property-shorthand/index.js'
31+
import no_static_container_query from './rules/no-static-container-query/index.js'
32+
import no_static_media_query from './rules/no-static-media-query/index.js'
33+
import no_unknown_container_names from './rules/no-unknown-container-names/index.js'
34+
import no_unknown_custom_property from './rules/no-unknown-custom-property/index.js'
35+
import no_unreachable_media_conditions from './rules/no-unreachable-media-conditions/index.js'
36+
import no_unused_container_names from './rules/no-unused-container-names/index.js'
37+
import no_unused_custom_properties from './rules/no-unused-custom-properties/index.js'
38+
import no_unused_layers from './rules/no-unused-layers/index.js'
39+
import no_useless_custom_property_assignment from './rules/no-useless-custom-property-assignment/index.js'
3940

41+
// Alphabetically ordered list of all plugins
4042
const plugins: stylelint.Plugin[] = [
41-
max_selector_complexity,
42-
max_lines_of_code,
43-
no_unused_custom_properties,
44-
no_unknown_custom_property,
45-
no_property_browserhacks,
46-
no_unused_layers,
47-
no_unused_container_names,
48-
no_unknown_container_names,
49-
no_anonymous_layers,
50-
no_useless_custom_property_assignment,
51-
no_unreachable_media_conditions,
52-
no_static_media_query,
53-
no_static_container_query,
54-
max_file_size,
55-
max_embedded_content_size,
56-
max_comment_size,
57-
max_average_selectors_per_rule,
5843
max_average_declarations_per_rule,
5944
max_average_selector_complexity,
60-
max_important_ratio,
61-
no_duplicate_data_urls,
62-
max_unique_units,
63-
min_selector_uniqueness_ratio,
64-
min_declaration_uniqueness_ratio,
45+
max_average_selectors_per_rule,
6546
max_average_specificity,
66-
max_selectors_per_rule,
47+
max_comment_size,
6748
max_declarations_per_rule,
68-
no_invalid_z_index,
69-
no_property_shorthand,
49+
max_embedded_content_size,
50+
max_file_size,
51+
max_important_ratio,
52+
max_lines_of_code,
53+
max_selector_complexity,
54+
max_selectors_per_rule,
55+
max_spacing_resets,
56+
max_unique_animation_functions,
57+
max_unique_box_shadows,
7058
max_unique_colors,
59+
max_unique_durations,
7160
max_unique_font_families,
7261
max_unique_font_sizes,
7362
max_unique_gradients,
74-
max_unique_box_shadows,
75-
max_unique_durations,
76-
max_unique_animation_functions,
77-
max_spacing_resets,
63+
max_unique_line_heights,
64+
max_unique_units,
65+
min_declaration_uniqueness_ratio,
66+
min_selector_uniqueness_ratio,
67+
no_anonymous_layers,
68+
no_duplicate_data_urls,
69+
no_invalid_z_index,
70+
no_property_browserhacks,
71+
no_property_shorthand,
72+
no_static_container_query,
73+
no_static_media_query,
74+
no_unknown_container_names,
75+
no_unknown_custom_property,
76+
no_unreachable_media_conditions,
77+
no_unused_container_names,
78+
no_unused_custom_properties,
79+
no_unused_layers,
80+
no_useless_custom_property_assignment,
7881
]
7982

8083
export default plugins
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Max unique line heights
2+
3+
Limit the number of unique line height values used across the stylesheet.
4+
5+
<!-- prettier-ignore -->
6+
```css
7+
a { line-height: 1.5; }
8+
/* ↑↑↑
9+
* This value counts as one unique line height */
10+
```
11+
12+
Using too many different line height values can indicate an inconsistent design system. This rule helps enforce a controlled typographic scale.
13+
14+
A unique line height is the entire value string of a `line-height` declaration or the line-height portion extracted from a `font` shorthand. The same string used in multiple places counts only once.
15+
16+
The rule inspects both the `line-height` property and the `font` shorthand property.
17+
18+
## Options
19+
20+
### `Number` (required)
21+
22+
The maximum number of unique line height values allowed. Must be a non-negative integer. Setting `0` enforces that no line heights are used at all.
23+
24+
Given:
25+
26+
`2`
27+
28+
the following are considered violations:
29+
30+
<!-- prettier-ignore -->
31+
```css
32+
a { line-height: 1; }
33+
b { line-height: 1.5; }
34+
c { line-height: 2; }
35+
```
36+
37+
The following patterns are _not_ considered violations:
38+
39+
<!-- prettier-ignore -->
40+
```css
41+
a { line-height: 1.5; }
42+
b { font: bold 16px/1.5 Arial, sans-serif; }
43+
/* Both declarations share the same line-height value → only 1 unique entry */
44+
```
45+
46+
### `ignore` (optional)
47+
48+
Type: `Array<string | RegExp>`
49+
50+
A list of line height values to exclude from the count. Each entry can be an exact string or a regular expression matched against the full value string.
51+
52+
Given:
53+
54+
`[2, { "ignore": ["1.5"] }]`
55+
56+
the following are _not_ considered violations:
57+
58+
<!-- prettier-ignore -->
59+
```css
60+
a { line-height: 1.5; } /* ignored */
61+
b { line-height: 2; }
62+
c { line-height: 3; }
63+
```

0 commit comments

Comments
 (0)