linter-bundle 7.8.0 → 7.10.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.
Files changed (28) hide show
  1. package/CHANGELOG.md +57 -2
  2. package/README.md +2 -2
  3. package/eslint/index.mjs +9 -1
  4. package/eslint/jsdoc.mjs +19 -2
  5. package/eslint/rules/no-ternary-return.mjs +1 -1
  6. package/lint.js +1 -0
  7. package/markdownlint/base.json +2 -1
  8. package/package.json +16 -16
  9. package/stylelint/index.mjs +6 -4
  10. package/stylelint/plugins/stylelint-15.11.0-stylistic/rules/declaration-colon-space-after/index.mjs +2 -4
  11. package/stylelint/plugins/stylelint-15.11.0-stylistic/rules/declaration-colon-space-before/index.mjs +2 -4
  12. package/stylelint/plugins/stylelint-15.11.0-stylistic/rules/max-line-length/index.mjs +1 -1
  13. package/stylelint/plugins/stylelint-15.11.0-stylistic/rules/media-feature-colon-space-after/index.mjs +1 -1
  14. package/stylelint/plugins/stylelint-15.11.0-stylistic/rules/media-feature-colon-space-before/index.mjs +1 -1
  15. package/stylelint/plugins/stylelint-15.11.0-stylistic/rules/media-feature-range-operator-space-before/index.mjs +1 -1
  16. package/stylelint/plugins/stylelint-15.11.0-stylistic/rules/media-query-list-comma-newline-after/index.mjs +1 -1
  17. package/stylelint/plugins/stylelint-15.11.0-stylistic/rules/media-query-list-comma-space-after/index.mjs +1 -1
  18. package/stylelint/plugins/stylelint-15.11.0-stylistic/rules/media-query-list-comma-space-before/index.mjs +1 -1
  19. package/stylelint/plugins/stylelint-15.11.0-stylistic/rules/no-eol-whitespace/index.mjs +2 -2
  20. package/stylelint/plugins/stylelint-15.11.0-stylistic/rules/no-extra-semicolons/index.mjs +6 -6
  21. package/stylelint/plugins/stylelint-15.11.0-stylistic/rules/selector-attribute-operator-space-after/index.mjs +1 -2
  22. package/stylelint/plugins/stylelint-15.11.0-stylistic/rules/selector-list-comma-newline-after/index.mjs +1 -1
  23. package/stylelint/plugins/stylelint-15.11.0-stylistic/rules/selector-list-comma-newline-before/index.mjs +1 -1
  24. package/stylelint/plugins/stylelint-15.11.0-stylistic/rules/selector-list-comma-space-after/index.mjs +1 -1
  25. package/stylelint/plugins/stylelint-15.11.0-stylistic/rules/selector-list-comma-space-before/index.mjs +1 -1
  26. package/stylelint/plugins/stylelint-15.11.0-stylistic/rules/value-list-comma-space-after/index.mjs +1 -1
  27. package/stylelint/plugins/stylelint-15.11.0-stylistic/rules/value-list-comma-space-before/index.mjs +1 -1
  28. package/stylelint/plugins/stylelint-selector-tag-no-without-class.js +106 -11
package/CHANGELOG.md CHANGED
@@ -6,7 +6,62 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
- [Show all code changes](https://github.com/jens-duttke/linter-bundle/compare/v7.8.0...HEAD)
9
+ [Show all code changes](https://github.com/jens-duttke/linter-bundle/compare/v7.10.0...HEAD)
10
+
11
+ ## [7.10.0] - 2025-11-19
12
+
13
+ - [eslint] Updated `@stylistic/eslint-plugin` from `5.6.0` to `5.6.1`
14
+ - [markdown] Updated `markdownlint-cli` from `0.45.0` to `0.46.0``
15
+ - [markdown] Added but disabled [`MD060 - Table column style`](https://github.com/DavidAnson/markdownlint/blob/main/doc/md060.md) rule
16
+ - [stylelint] Added configurable `allowCombinators` option to the `plugin/selector-tag-no-without-class` rule and enabled `>`, `+`, `~` combinators for CSS Modules to avoid false positives
17
+
18
+ [Show all code changes](https://github.com/jens-duttke/linter-bundle/compare/v7.9.0...v7.10.0)
19
+
20
+ ## [7.9.0] - 2025-11-18
21
+
22
+ - [eslint] Updated `@stylistic/eslint-plugin` from `5.2.3` to `5.6.0`
23
+ - [eslint] Updated `eslint` from `9.33.0` to `9.39.1`
24
+ - [eslint] Updated `eslint-formatter-unix` from `8.40.0` to `9.0.1`
25
+ - [eslint] Updated `eslint-plugin-jest` from `29.0.1` to `29.1.0`
26
+ - [eslint] Updated `eslint-plugin-jsdoc` from `54.1.1` to `61.2.1`
27
+ - [eslint] Updated `eslint-plugin-n` from `17.21.3` to `17.23.1`
28
+ - [eslint] Updated `eslint-plugin-react-hooks` from `5.2.0` to `7.0.1`
29
+ - [eslint] Updated `eslint-plugin-unicorn` from `60.0.0` to `62.0.0`
30
+ - [eslint] Updated `globals` from `16.3.0` to `16.5.0`
31
+ - [eslint] Updated `typescript-eslint` from `8.40.0` to `8.47.0`
32
+ - [stylelint] Updated `stylelint` from `16.23.1` to `16.25.0`
33
+ - [eslint] Make use of new [`preserve-caught-error`](https://github.com/eslint/eslint/blob/main/docs/src/rules/preserve-caught-error.md) rule
34
+ - [eslint] Make use of new [`jsdoc/require-throws-description`](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-throws-description.md) rule
35
+ - [eslint] Make use of new [`jsdoc/require-throws-type`](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-throws-type.md) rule
36
+ - [eslint] Make use of new [`jsdoc/require-yields-description`](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-yields-description.md) rule
37
+ - [eslint] Make use of new [`jsdoc/require-yields-type`](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-yields-type.md) rule
38
+ - [eslint] Make use of new [`jsdoc/ts-method-signature-style`](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/ts-method-signature-style.md) rule with value `"property"`
39
+ - [eslint] Make use of new [`jsdoc/ts-no-empty-object-type`](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/ts-no-empty-object-type.md) rule
40
+ - [eslint] Make use of new [`jsdoc/ts-no-unnecessary-template-expression`](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/ts-no-unnecessary-template-expression.md) rule
41
+ - [eslint] Make use of new [`jsdoc/ts-prefer-function-type`](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/ts-prefer-function-type.md) rule
42
+ - [eslint] Added but disabled [`jsdoc/escape-inline-tags`](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/escape-inline-tags.md) rule
43
+ - [eslint] Added but disabled [`jsdoc/prefer-import-tag`](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/prefer-import-tag.md) rule
44
+ - [eslint] Added but disabled [`jsdoc/reject-any-type`](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/reject-any-type.md) rule
45
+ - [eslint] Added but disabled [`jsdoc/reject-function-type`](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/reject-function-type.md) rule
46
+ - [eslint] Added but disabled [`jsdoc/require-next-description`](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-next-description.md) rule
47
+ - [eslint] Added but disabled [`jsdoc/require-next-type`](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-next-type.md) rule
48
+ - [eslint] Added but disabled [`jsdoc/required-tags`](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/required-tags.md) rule
49
+ - [eslint] Added but disabled [`jsdoc/require-template-description`](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-template-description.md) rule
50
+ - [eslint] Added but disabled [`jsdoc/type-formatting`](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/type-formatting.md) rule
51
+ - [eslint] Make use of new [`unicorn/prefer-bigint-literals`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-bigint-literals.md) rule
52
+ - [eslint] Make use of new [`unicorn/prefer-classlist-toggle`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-classlist-toggle.md) rule
53
+ - [eslint] Make use of new [`unicorn/require-module-attributes`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/require-module-attributes.md) rule
54
+ - [eslint] Make use of new [`unicorn/no-array-sort`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-sort.md) rule
55
+ - [eslint] Make use of new [`unicorn/no-immediate-mutation`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/30f08471987e620755963ef2809ecc3dbcdb21d5/docs/rules/no-immediate-mutation.md) rule
56
+ - [eslint] Make use of new [`unicorn/no-useless-collection-argument`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/30f08471987e620755963ef2809ecc3dbcdb21d5/docs/rules/no-useless-collection-argument.md) rule
57
+ - [eslint] Make use of new [`unicorn/prefer-response-static-json`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/30f08471987e620755963ef2809ecc3dbcdb21d5/docs/rules/prefer-response-static-json.md) rule
58
+ - [stylelint] Added but disabled [`rule-nesting-at-rule-required-list`](https://github.com/stylelint/stylelint/tree/main/lib/rules/rule-nesting-at-rule-required-list) rule
59
+ - [eslint] Activate [multithread linting](https://eslint.org/blog/2025/08/multithread-linting/) using `--concurrency auto`
60
+ - [eslint] Change `import/dynamic-import-chunkname` pattern to `"([0-9a-zA-Z-_/.+]*(\\[(?:index|request)\\])?)+"`
61
+ - [stylelint] Ignore property casing for `composes` in `value-keyword-case`rule
62
+ - [stylelint] Add `composes` to the ignored property list of the `scss/property-no-unknown` rule
63
+
64
+ [Show all code changes](https://github.com/jens-duttke/linter-bundle/compare/v7.8.0...v7.9.0)
10
65
 
11
66
  ## [7.8.0] - 2025-08-21
12
67
 
@@ -1613,7 +1668,7 @@ Beside these changes:
1613
1668
  ### Changed
1614
1669
 
1615
1670
  - [eslint] Updated `@typescript-eslint` from v4.24.0 to v4.25.0
1616
- - [eslint] Updated `eslint-plugin-jsdoc` from v34.8.2 to v35.0.0
1671
+ - [eslint] Updated `eslint-plugin-jsdoc` from v34.8.2 to v35.0.0
1617
1672
  - [eslint] Activated `ignoreNonDOM` option for [`jsx-a11y/no-autofocus`](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-autofocus.md) rule
1618
1673
 
1619
1674
  [Show all code changes](https://github.com/jens-duttke/linter-bundle/compare/v1.20.0...v1.21.0)
package/README.md CHANGED
@@ -71,7 +71,7 @@ If these plugins are maintained again, the plugins will also be used again.
71
71
 
72
72
  ## Install
73
73
 
74
- Ensure you are using atleast Node.js version 20.9.0/21.1.0.
74
+ Ensure you are using atleast Node.js version 20.12.0+, 22.0.0+, or 24+.
75
75
 
76
76
  ```sh
77
77
  npm install linter-bundle --save-dev
@@ -580,7 +580,7 @@ Argument | Description | Example
580
580
  Will execute:
581
581
 
582
582
  ```sh
583
- eslint "./**/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx}" --format unix
583
+ eslint "./**/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx}" --concurrency auto --format unix
584
584
  ```
585
585
 
586
586
  Additionally, the environment variable `TIMING` is set to `10`, to show timing information about the 10 slowest rules.
package/eslint/index.mjs CHANGED
@@ -411,6 +411,7 @@ export default [
411
411
  'prefer-rest-params': 'error',
412
412
  'prefer-spread': 'error',
413
413
  'prefer-template': 'error',
414
+ 'preserve-caught-error': 'error',
414
415
  'quote-props': ['error', 'consistent-as-needed'],
415
416
  'quotes': 'off', // Covered by @stylistic/quotes
416
417
  'radix': 'error',
@@ -881,7 +882,7 @@ export default [
881
882
  'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
882
883
  'import/default': 'error',
883
884
  'import/dynamic-import-chunkname': ['error', {
884
- webpackChunknameFormat: '[0-9a-zA-Z-_/.+]+'
885
+ webpackChunknameFormat: '([0-9a-zA-Z-_/.+]*(\\[(?:index|request)\\])?)+'
885
886
  }],
886
887
  'import/export': 'error',
887
888
  'import/exports-last': 'off', // Exports should be declared first; helper functions last
@@ -1054,12 +1055,14 @@ export default [
1054
1055
  'unicorn/no-array-method-this-argument': 'error',
1055
1056
  'unicorn/no-array-reduce': ['error', { allowSimpleOperations: true }],
1056
1057
  'unicorn/no-array-reverse': 'error',
1058
+ 'unicorn/no-array-sort': 'error',
1057
1059
  'unicorn/no-await-expression-member': 'error',
1058
1060
  'unicorn/no-console-spaces': 'error',
1059
1061
  'unicorn/no-document-cookie': 'error',
1060
1062
  'unicorn/no-empty-file': 'error',
1061
1063
  'unicorn/no-for-loop': 'off', // @typescript-eslint/prefer-for-of
1062
1064
  'unicorn/no-hex-escape': 'error',
1065
+ 'unicorn/no-immediate-mutation': 'error',
1063
1066
  'unicorn/no-instanceof-builtins': 'error',
1064
1067
  'unicorn/no-invalid-fetch-options': 'error',
1065
1068
  'unicorn/no-invalid-remove-event-listener': 'error',
@@ -1087,6 +1090,7 @@ export default [
1087
1090
  'unicorn/no-unreadable-array-destructuring': 'error',
1088
1091
  'unicorn/no-unreadable-iife': 'error',
1089
1092
  'unicorn/no-unused-properties': 'error',
1093
+ 'unicorn/no-useless-collection-argument': 'error',
1090
1094
  'unicorn/no-useless-error-capture-stack-trace': 'error',
1091
1095
  'unicorn/no-useless-fallback-in-spread': 'error',
1092
1096
  'unicorn/no-useless-promise-resolve-reject': 'error',
@@ -1105,7 +1109,9 @@ export default [
1105
1109
  'unicorn/prefer-array-some': 'error',
1106
1110
  'unicorn/prefer-at': 'off', // @todo Disabled for now, since `at` is not supported by TypeScript type definitions yet.
1107
1111
  'unicorn/prefer-blob-reading-methods': 'off', // @todo Disabled for now, since it's only supported in Safari 14+. Activate in 2025
1112
+ 'unicorn/prefer-bigint-literals': 'error',
1108
1113
  'unicorn/prefer-class-fields': 'error',
1114
+ 'unicorn/prefer-classlist-toggle': 'error',
1109
1115
  'unicorn/prefer-code-point': 'error',
1110
1116
  'unicorn/prefer-date-now': 'error',
1111
1117
  'unicorn/prefer-default-parameters': 'error',
@@ -1134,6 +1140,7 @@ export default [
1134
1140
  'unicorn/prefer-prototype-methods': 'error',
1135
1141
  'unicorn/prefer-query-selector': 'off', // document.getElementById() is much faster
1136
1142
  'unicorn/prefer-reflect-apply': 'error',
1143
+ 'unicorn/prefer-response-static-json': 'error',
1137
1144
  'unicorn/prefer-set-has': 'error',
1138
1145
  'unicorn/prefer-set-size': 'error',
1139
1146
  'unicorn/prefer-single-call': 'error',
@@ -1152,6 +1159,7 @@ export default [
1152
1159
  'unicorn/prevent-abbreviations': ['error', { ignore: ['args', 'i', 'j', 'i18n', /[Rr]ef/u, /[Pp]arams/u, /[Pp]rops/u] }],
1153
1160
  'unicorn/relative-url-style': 'error',
1154
1161
  'unicorn/require-array-join-separator': 'error',
1162
+ 'unicorn/require-module-attributes': 'error',
1155
1163
  'unicorn/require-module-specifiers': 'error',
1156
1164
  'unicorn/require-number-to-fixed-digits-argument': 'error',
1157
1165
  'unicorn/require-post-message-target-origin': 'off', // False-positive with Workers which don't support a `targetOrigin`
package/eslint/jsdoc.mjs CHANGED
@@ -32,15 +32,16 @@ export default [
32
32
  'jsdoc/check-alignment': 'error',
33
33
  'jsdoc/check-examples': 'off', // @todo Need to be configured to allow text-based examples
34
34
  'jsdoc/check-indentation': ['off', { excludeTags: ['typedef'] }], // @todo Why is this disabled?
35
+ 'jsdoc/check-line-alignment': ['error', 'never'],
35
36
  'jsdoc/check-param-names': 'error',
36
37
  'jsdoc/check-syntax': 'error',
37
- 'jsdoc/check-line-alignment': ['error', 'never'],
38
38
  'jsdoc/check-tag-names': 'error',
39
39
  'jsdoc/check-template-names': 'error',
40
40
  'jsdoc/check-types': 'error',
41
41
  'jsdoc/check-values': 'error',
42
42
  'jsdoc/convert-to-jsdoc-comments': 'off',
43
43
  'jsdoc/empty-tags': 'error',
44
+ 'jsdoc/escape-inline-tags': 'off',
44
45
  'jsdoc/implements-on-classes': 'error',
45
46
  'jsdoc/imports-as-dependencies': 'off', // @todo Doesn't cover `peerDependencies` yet. As of v46.2.5 it should cover Node.js modules (like `child_process`), which must be checked, before activation.
46
47
  'jsdoc/informative-docs': 'error',
@@ -56,12 +57,17 @@ export default [
56
57
  'jsdoc/no-restricted-syntax': 'off',
57
58
  'jsdoc/no-types': 'off',
58
59
  'jsdoc/no-undefined-types': ['error', { definedTypes: ['void', 'never', 'Readonly'] }],
60
+ 'jsdoc/prefer-import-tag': 'off',
61
+ 'jsdoc/reject-any-type': 'off',
62
+ 'jsdoc/reject-function-type': 'off',
59
63
  'jsdoc/require-description-complete-sentence': 'off',
60
64
  'jsdoc/require-description': 'error',
61
65
  'jsdoc/require-example': 'off',
62
66
  'jsdoc/require-file-overview': 'error',
63
67
  'jsdoc/require-hyphen-before-param-description': 'error',
64
68
  'jsdoc/require-jsdoc': ['error', { minLineCount: 2 }],
69
+ 'jsdoc/require-next-description': 'off',
70
+ 'jsdoc/require-next-type': 'off',
65
71
  'jsdoc/require-param-description': 'error',
66
72
  'jsdoc/require-param-name': 'error',
67
73
  'jsdoc/require-param-type': 'error',
@@ -71,10 +77,16 @@ export default [
71
77
  'jsdoc/require-returns-description': 'error',
72
78
  'jsdoc/require-returns-type': 'error',
73
79
  'jsdoc/require-returns': ['error', { forceReturnsWithAsync: true }],
80
+ 'jsdoc/require-template-description': 'off',
74
81
  'jsdoc/require-template': 'error',
82
+ 'jsdoc/require-throws-description': 'error',
83
+ 'jsdoc/require-throws-type': 'error',
75
84
  'jsdoc/require-throws': 'error',
76
- 'jsdoc/require-yields': 'error',
77
85
  'jsdoc/require-yields-check': 'error',
86
+ 'jsdoc/require-yields-description': 'error',
87
+ 'jsdoc/require-yields-type': 'error',
88
+ 'jsdoc/require-yields': 'error',
89
+ 'jsdoc/required-tags': 'off',
78
90
  'jsdoc/tag-lines': ['error', 'always', {
79
91
  tags: {
80
92
  example: { lines: 'always' },
@@ -97,6 +109,11 @@ export default [
97
109
  applyToEndTag: false
98
110
  }],
99
111
  'jsdoc/text-escaping': 'off', // Right now, there is no description of this rule available on the page. Only the discussion here: https://github.com/gajus/eslint-plugin-jsdoc/issues/864
112
+ 'jsdoc/ts-method-signature-style': ['error', 'property'],
113
+ 'jsdoc/ts-no-empty-object-type': 'error',
114
+ 'jsdoc/ts-no-unnecessary-template-expression': 'error',
115
+ 'jsdoc/ts-prefer-function-type': 'error',
116
+ 'jsdoc/type-formatting': 'off', // Either false-positives or very configuration-heavy to avoid them
100
117
  'jsdoc/valid-types': 'off' // Checked by TypeScript
101
118
  }
102
119
  }
@@ -19,7 +19,7 @@ export default {
19
19
  create (context) {
20
20
  return {
21
21
  ReturnStatement (node) {
22
- if (node.argument && node.argument.type === 'ConditionalExpression') {
22
+ if (node.argument?.type === 'ConditionalExpression') {
23
23
  context.report({
24
24
  node,
25
25
  message: 'Avoid using ternary expressions as return values; use an if-else statement instead.',
package/lint.js CHANGED
@@ -202,6 +202,7 @@ async function runESLintTask (taskName, taskConfig) {
202
202
  `"${path.join(path.dirname(require.resolve('eslint')), '../bin/eslint.js')}"`,
203
203
  includes,
204
204
  newTaskConfig.exclude?.map((exclude) => `--ignore-pattern ${exclude}`).join(' '),
205
+ '--concurrency auto',
205
206
  '--format unix'
206
207
  ].filter((argument) => Boolean(argument)).join(' '),
207
208
  taskConfig: newTaskConfig,
@@ -17,5 +17,6 @@
17
17
  "span"
18
18
  ]
19
19
  },
20
- "MD046": false
20
+ "MD046": false,
21
+ "MD060": false
21
22
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linter-bundle",
3
- "version": "7.8.0",
3
+ "version": "7.10.0",
4
4
  "type": "module",
5
5
  "description": "Ready-to use bundle of linting tools, containing configurations for ESLint, stylelint and markdownlint.",
6
6
  "keywords": [
@@ -42,33 +42,33 @@
42
42
  "_test-stylelint": "node ./test-stylelint.js"
43
43
  },
44
44
  "dependencies": {
45
- "@stylistic/eslint-plugin": "5.2.3",
46
- "eslint": "9.33.0",
47
- "eslint-formatter-unix": "8.40.0",
45
+ "@stylistic/eslint-plugin": "5.6.1",
46
+ "eslint": "9.39.1",
47
+ "eslint-formatter-unix": "9.0.1",
48
48
  "eslint-import-resolver-typescript": "4.4.4",
49
49
  "eslint-import-resolver-webpack": "0.13.10",
50
50
  "eslint-plugin-eslint-comments": "3.2.0",
51
51
  "eslint-plugin-functional": "9.0.2",
52
52
  "eslint-plugin-import": "2.32.0",
53
- "eslint-plugin-jest": "29.0.1",
54
- "eslint-plugin-jsdoc": "54.1.1",
53
+ "eslint-plugin-jest": "29.1.0",
54
+ "eslint-plugin-jsdoc": "61.2.1",
55
55
  "eslint-plugin-jsx-a11y": "6.10.2",
56
- "eslint-plugin-n": "17.21.3",
56
+ "eslint-plugin-n": "17.23.1",
57
57
  "eslint-plugin-promise": "7.2.1",
58
58
  "eslint-plugin-react": "7.37.5",
59
- "eslint-plugin-react-hooks": "5.2.0",
60
- "eslint-plugin-unicorn": "60.0.0",
61
- "globals": "16.3.0",
62
- "markdownlint-cli": "0.45.0",
59
+ "eslint-plugin-react-hooks": "7.0.1",
60
+ "eslint-plugin-unicorn": "62.0.0",
61
+ "globals": "16.5.0",
62
+ "markdownlint-cli": "0.46.0",
63
63
  "micromatch": "4.0.8",
64
64
  "postcss-scss": "4.0.9",
65
- "stylelint": "16.23.1",
65
+ "stylelint": "16.25.0",
66
66
  "stylelint-declaration-block-no-ignored-properties": "2.8.0",
67
67
  "stylelint-high-performance-animation": "1.11.0",
68
68
  "stylelint-order": "7.0.0",
69
69
  "stylelint-scss": "6.12.1",
70
70
  "stylelint-use-logical-spec": "5.0.1",
71
- "typescript-eslint": "8.40.0"
71
+ "typescript-eslint": "8.47.0"
72
72
  },
73
73
  "peerDependencies": {
74
74
  "@typescript-eslint/utils": "*",
@@ -77,9 +77,9 @@
77
77
  },
78
78
  "devDependencies": {
79
79
  "@types/eslint": "9.6.1",
80
- "@types/micromatch": "4.0.9",
81
- "@types/node": "24.3.0",
80
+ "@types/micromatch": "4.0.10",
81
+ "@types/node": "24.10.1",
82
82
  "stylelint-find-new-rules": "5.0.0",
83
- "typescript": "5.9.2"
83
+ "typescript": "5.9.3"
84
84
  }
85
85
  }
@@ -130,7 +130,7 @@ export default {
130
130
  *
131
131
  * @see https://github.com/Moxio/stylelint-selector-tag-no-without-class
132
132
  */
133
- 'plugin/selector-tag-no-without-class': ['/./']
133
+ 'plugin/selector-tag-no-without-class': [['/./'], { allowCombinators: ['>', '+', '~'] }]
134
134
  }
135
135
  },
136
136
  {
@@ -383,7 +383,7 @@ export default {
383
383
  'font', // Shorthand property is to complex
384
384
  'grid-gap' // @deprecated Use gap.
385
385
  ],
386
- 'property-no-unknown': true,
386
+ 'property-no-unknown': null, // Covered by scss/property-no-unknown
387
387
  'property-no-vendor-prefix': true,
388
388
  'rule-selector-property-disallowed-list': null,
389
389
  'rule-empty-line-before': [
@@ -393,6 +393,7 @@ export default {
393
393
  ignore: ['after-comment']
394
394
  }
395
395
  ],
396
+ 'rule-nesting-at-rule-required-list': null,
396
397
  'selector-anb-no-unmatchable': true,
397
398
  'selector-attribute-name-disallowed-list': null,
398
399
  'selector-attribute-operator-allowed-list': null,
@@ -440,7 +441,8 @@ export default {
440
441
  'value-keyword-case': [
441
442
  'lower',
442
443
  {
443
- camelCaseSvgKeywords: true
444
+ camelCaseSvgKeywords: true,
445
+ ignoreProperties: ['composes']
444
446
  }
445
447
  ],
446
448
  'value-no-vendor-prefix': true,
@@ -1134,7 +1136,7 @@ export default {
1134
1136
  'scss/operator-no-unspaced': true,
1135
1137
  'scss/partial-no-import': null,
1136
1138
  'scss/percent-placeholder-pattern': null,
1137
- 'scss/property-no-unknown': true,
1139
+ 'scss/property-no-unknown': [true, { ignoreProperties: ['composes'] }],
1138
1140
  'scss/selector-nest-combinators': null, // Sometimes nesting does not make sense
1139
1141
  'scss/selector-no-redundant-nesting-selector': true,
1140
1142
  'scss/selector-no-union-class-name': null,
@@ -47,15 +47,13 @@ const rule = (primary, _secondaryOptions) => {
47
47
  if (between == null) { throw new Error('`between` must be present'); }
48
48
 
49
49
  if (primary.startsWith('always')) {
50
- decl.raws.between =
51
- between.slice(0, colonIndex) + between.slice(colonIndex).replace(/^:\s*/u, ': ');
50
+ decl.raws.between = between.slice(0, colonIndex) + between.slice(colonIndex).replace(/^:\s*/u, ': ');
52
51
 
53
52
  return true;
54
53
  }
55
54
 
56
55
  if (primary === 'never') {
57
- decl.raws.between =
58
- between.slice(0, colonIndex) + between.slice(colonIndex).replace(/^:\s*/u, ':');
56
+ decl.raws.between = between.slice(0, colonIndex) + between.slice(colonIndex).replace(/^:\s*/u, ':');
59
57
 
60
58
  return true;
61
59
  }
@@ -46,15 +46,13 @@ const rule = (primary, _secondaryOptions) => {
46
46
  if (between == null) { throw new Error('`between` must be present'); }
47
47
 
48
48
  if (primary === 'always') {
49
- decl.raws.between =
50
- between.slice(0, colonIndex).replace(/\s*$/u, ' ') + between.slice(colonIndex);
49
+ decl.raws.between = between.slice(0, colonIndex).replace(/\s*$/u, ' ') + between.slice(colonIndex);
51
50
 
52
51
  return true;
53
52
  }
54
53
 
55
54
  if (primary === 'never') {
56
- decl.raws.between =
57
- between.slice(0, colonIndex).replace(/\s*$/u, '') + between.slice(colonIndex);
55
+ decl.raws.between = between.slice(0, colonIndex).replace(/\s*$/u, '') + between.slice(colonIndex);
58
56
 
59
57
  return true;
60
58
  }
@@ -68,7 +68,7 @@ const rule = (primary, secondaryOptions) => (root, result) => {
68
68
  }
69
69
  }
70
70
 
71
- skippedSubStrings = skippedSubStrings.sort((a, b) => a[0] - b[0]);
71
+ skippedSubStrings = skippedSubStrings.toSorted((a, b) => a[0] - b[0]);
72
72
 
73
73
  // Check first line
74
74
  checkNewline({ endIndex: 0 });
@@ -59,7 +59,7 @@ const rule = (primary, _secondaryOptions) => {
59
59
  for (const [atRule, colonIndices] of fixData.entries()) {
60
60
  let parameters = atRule.raws.params ? atRule.raws.params.raw : atRule.params;
61
61
 
62
- for (const index of colonIndices.sort((a, b) => b - a)) {
62
+ for (const index of colonIndices.toSorted((a, b) => b - a)) {
63
63
  const beforeColon = parameters.slice(0, index + 1);
64
64
  const afterColon = parameters.slice(index + 1);
65
65
 
@@ -59,7 +59,7 @@ const rule = (primary, _secondaryOptions) => {
59
59
  for (const [atRule, colonIndices] of fixData.entries()) {
60
60
  let parameters = atRule.raws.params ? atRule.raws.params.raw : atRule.params;
61
61
 
62
- for (const index of colonIndices.sort((a, b) => b - a)) {
62
+ for (const index of colonIndices.toSorted((a, b) => b - a)) {
63
63
  const beforeColon = parameters.slice(0, index);
64
64
  const afterColon = parameters.slice(index);
65
65
 
@@ -45,7 +45,7 @@ const rule = (primary, _secondaryOptions) => {
45
45
  if (fixOperatorIndices.length > 0) {
46
46
  let parameters = atRule.raws.params ? atRule.raws.params.raw : atRule.params;
47
47
 
48
- for (const index of fixOperatorIndices.sort((a, b) => b - a)) {
48
+ for (const index of fixOperatorIndices.toSorted((a, b) => b - a)) {
49
49
  const beforeOperator = parameters.slice(0, index);
50
50
  const afterOperator = parameters.slice(index);
51
51
 
@@ -63,7 +63,7 @@ const rule = (primary, _secondaryOptions, context) => {
63
63
  for (const [atRule, commaIndices] of fixData.entries()) {
64
64
  let parameters = atRule.raws.params ? atRule.raws.params.raw : atRule.params;
65
65
 
66
- for (const index of commaIndices.sort((a, b) => b - a)) {
66
+ for (const index of commaIndices.toSorted((a, b) => b - a)) {
67
67
  const beforeComma = parameters.slice(0, index + 1);
68
68
  const afterComma = parameters.slice(index + 1);
69
69
 
@@ -61,7 +61,7 @@ const rule = (primary, _secondaryOptions) => {
61
61
  for (const [atRule, commaIndices] of fixData.entries()) {
62
62
  let parameters = atRule.raws.params ? atRule.raws.params.raw : atRule.params;
63
63
 
64
- for (const index of commaIndices.sort((a, b) => b - a)) {
64
+ for (const index of commaIndices.toSorted((a, b) => b - a)) {
65
65
  const beforeComma = parameters.slice(0, index + 1);
66
66
  const afterComma = parameters.slice(index + 1);
67
67
 
@@ -61,7 +61,7 @@ const rule = (primary, _secondaryOptions) => {
61
61
  for (const [atRule, commaIndices] of fixData.entries()) {
62
62
  let parameters = atRule.raws.params ? atRule.raws.params.raw : atRule.params;
63
63
 
64
- for (const index of commaIndices.sort((a, b) => b - a)) {
64
+ for (const index of commaIndices.toSorted((a, b) => b - a)) {
65
65
  const beforeComma = parameters.slice(0, index);
66
66
  const afterComma = parameters.slice(index);
67
67
 
@@ -257,8 +257,8 @@ const rule = (primary, secondaryOptions) => (root, result) => {
257
257
 
258
258
  if (lastEOL !== rootNode.raws.after.length - 1) {
259
259
  rootNode.raws.after =
260
- rootNode.raws.after.slice(0, lastEOL + 1) +
261
- fixString(rootNode.raws.after.slice(lastEOL + 1));
260
+ rootNode.raws.after.slice(0, lastEOL + 1) +
261
+ fixString(rootNode.raws.after.slice(lastEOL + 1));
262
262
  }
263
263
  }
264
264
  }
@@ -164,11 +164,11 @@ const rule = (primary, _secondaryOptions) => (root, result) => {
164
164
 
165
165
  styleSearch({ source: rawAfterNode, target: ';' }, (match) => {
166
166
  const index =
167
- getOffsetByNode(node) +
168
- node.toString().length -
169
- 1 -
170
- rawAfterNode.length +
171
- match.startIndex;
167
+ getOffsetByNode(node) +
168
+ node.toString().length -
169
+ 1 -
170
+ rawAfterNode.length +
171
+ match.startIndex;
172
172
 
173
173
  report({
174
174
  message: messages.rejected,
@@ -202,7 +202,7 @@ const rule = (primary, _secondaryOptions) => (root, result) => {
202
202
  }
203
203
 
204
204
  const index =
205
- getOffsetByNode(node) +
205
+ getOffsetByNode(node) +
206
206
  node.toString().length -
207
207
  rawOwnSemicolon.length +
208
208
  match.startIndex;
@@ -59,8 +59,7 @@ const rule = (primary, _secondaryOptions) => (root, result) => {
59
59
  };
60
60
  }
61
61
 
62
- const rawSpacesOperator =
63
- attributeNode.raws.spaces?.operator;
62
+ const rawSpacesOperator = attributeNode.raws.spaces?.operator;
64
63
  const rawOperatorAfter = rawSpacesOperator?.after;
65
64
 
66
65
  if (rawOperatorAfter) {
@@ -92,7 +92,7 @@ const rule = (primary, _secondaryOptions, context) => {
92
92
  if (fixIndices.length > 0) {
93
93
  let fixedSelector = selector;
94
94
 
95
- for (const index of fixIndices.sort((a, b) => b - a)) {
95
+ for (const index of fixIndices.toSorted((a, b) => b - a)) {
96
96
  const beforeSelector = fixedSelector.slice(0, index);
97
97
  let afterSelector = fixedSelector.slice(index);
98
98
 
@@ -57,7 +57,7 @@ const rule = (primary, _secondaryOptions, context) => {
57
57
  for (const [ruleNode, commaIndices] of fixData.entries()) {
58
58
  let selector = ruleNode.raws.selector ? ruleNode.raws.selector.raw : ruleNode.selector;
59
59
 
60
- for (const index of commaIndices.sort((a, b) => b - a)) {
60
+ for (const index of commaIndices.toSorted((a, b) => b - a)) {
61
61
  let beforeSelector = selector.slice(0, index);
62
62
  const afterSelector = selector.slice(index);
63
63
 
@@ -58,7 +58,7 @@ const rule = (primary, _secondaryOptions) => {
58
58
  for (const [ruleNode, commaIndices] of fixData.entries()) {
59
59
  let selector = ruleNode.raws.selector ? ruleNode.raws.selector.raw : ruleNode.selector;
60
60
 
61
- for (const index of commaIndices.sort((a, b) => b - a)) {
61
+ for (const index of commaIndices.toSorted((a, b) => b - a)) {
62
62
  const beforeSelector = selector.slice(0, index + 1);
63
63
  let afterSelector = selector.slice(index + 1);
64
64
 
@@ -58,7 +58,7 @@ const rule = (primary, _secondaryOptions) => {
58
58
  for (const [ruleNode, commaIndices] of fixData.entries()) {
59
59
  let selector = ruleNode.raws.selector ? ruleNode.raws.selector.raw : ruleNode.selector;
60
60
 
61
- for (const index of commaIndices.sort((a, b) => b - a)) {
61
+ for (const index of commaIndices.toSorted((a, b) => b - a)) {
62
62
  let beforeSelector = selector.slice(0, index);
63
63
  const afterSelector = selector.slice(index);
64
64
 
@@ -65,7 +65,7 @@ const rule = (primary, _secondaryOptions) => {
65
65
 
66
66
  if (fixData) {
67
67
  for (const [decl, commaIndices] of fixData.entries()) {
68
- for (const index of commaIndices.sort((a, b) => b - a)) {
68
+ for (const index of commaIndices.toSorted((a, b) => b - a)) {
69
69
  const value = getDeclarationValue(decl);
70
70
  const valueIndex = index - declarationValueIndex(decl);
71
71
  const beforeValue = value.slice(0, valueIndex + 1);
@@ -65,7 +65,7 @@ const rule = (primary, _secondaryOptions) => {
65
65
 
66
66
  if (fixData) {
67
67
  for (const [decl, commaIndices] of fixData.entries()) {
68
- for (const index of commaIndices.sort((a, b) => b - a)) {
68
+ for (const index of commaIndices.toSorted((a, b) => b - a)) {
69
69
  const value = getDeclarationValue(decl);
70
70
  const valueIndex = index - declarationValueIndex(decl);
71
71
  let beforeValue = value.slice(0, valueIndex);
@@ -22,26 +22,121 @@ const messages = stylelint.utils.ruleMessages(ruleName, {
22
22
  });
23
23
 
24
24
  // @ts-expect-error -- Parameter 'primaryOption' implicitly has an 'any' type.
25
- function rule (primaryOption) {
25
+ function rule (primaryOption, secondaryOptions = {}) {
26
26
  // @ts-expect-error -- Parameter 'root' implicitly has an 'any' type. / Parameter 'result' implicitly has an 'any' type.
27
27
  return (root, result) => {
28
- const validOptions = stylelint.utils.validateOptions(result, ruleName, {
29
- actual: primaryOption,
30
- possible: [(string) => (typeof string === 'string' || Object.prototype.toString.call(string) === '[object String]')]
31
- });
28
+ const validOptions = stylelint.utils.validateOptions(
29
+ result,
30
+ ruleName,
31
+ {
32
+ actual: primaryOption,
33
+ possible: [(string) => (typeof string === 'string' || Object.prototype.toString.call(string) === '[object String]')]
34
+ },
35
+ {
36
+ actual: secondaryOptions,
37
+ possible: {
38
+ allowCombinators: [' ', '+', '>', '~', '||']
39
+ },
40
+ optional: true
41
+ }
42
+ );
32
43
 
33
44
  if (!validOptions) {
34
45
  return;
35
46
  }
36
47
 
48
+ /** @type {{ allowCombinators?: string | string[] }} */
49
+ const typedSecondaryOptions = secondaryOptions;
50
+ const allowCombinatorsOption = typedSecondaryOptions.allowCombinators;
51
+ const allowCombinatorValues = allowCombinatorsOption === undefined ? [] : (Array.isArray(allowCombinatorsOption) ? allowCombinatorsOption : [allowCombinatorsOption]);
52
+ /** @type {string[]} */
53
+ const normalizedAllowedCombinators = [];
54
+ for (const combinator of allowCombinatorValues) {
55
+ if (combinator.length === 0) {
56
+ continue;
57
+ }
58
+
59
+ normalizedAllowedCombinators.push(normalizeCombinatorValue(combinator));
60
+ }
61
+
62
+ const allowedCombinators = new Set(normalizedAllowedCombinators);
63
+
64
+ /**
65
+ * @param {string | undefined} value
66
+ * @returns {string}
67
+ */
68
+ function normalizeCombinatorValue (value) {
69
+ if (typeof value !== 'string') {
70
+ return '';
71
+ }
72
+
73
+ return value.trim() === '' ? ' ' : value.trim();
74
+ }
75
+
76
+ /**
77
+ * @param {import('postcss-selector-parser').Selector} selectorNode
78
+ */
79
+ function splitSelectorIntoSegments (selectorNode) {
80
+ /** @type {{ nodes: import('postcss-selector-parser').Node[], leadingCombinator?: string }[]} */
81
+ const segments = [];
82
+ /** @type {import('postcss-selector-parser').Node[]} */
83
+ let currentSegment = [];
84
+ /** @type {string | undefined} */
85
+ let leadingCombinator;
86
+
87
+ function pushCurrentSegment () {
88
+ if (currentSegment.length === 0) {
89
+ return;
90
+ }
91
+
92
+ const segmentNodes = currentSegment;
93
+ currentSegment = [];
94
+
95
+ /** @type {{ nodes: import('postcss-selector-parser').Node[], leadingCombinator?: string }} */
96
+ const segment = { nodes: segmentNodes };
97
+
98
+ if (leadingCombinator !== undefined) {
99
+ segment.leadingCombinator = leadingCombinator;
100
+ leadingCombinator = undefined;
101
+ }
102
+
103
+ segments.push(segment);
104
+ }
105
+
106
+ selectorNode.each((/** @type {import('postcss-selector-parser').Node} */ child) => {
107
+ if (child.type === 'combinator') {
108
+ pushCurrentSegment();
109
+ leadingCombinator = normalizeCombinatorValue(child.value);
110
+
111
+ return;
112
+ }
113
+
114
+ currentSegment.push(child);
115
+ });
116
+
117
+ pushCurrentSegment();
118
+
119
+ return segments;
120
+ }
121
+
122
+ /**
123
+ * @param {string | undefined} leadingCombinator
124
+ */
125
+ function shouldIgnoreSegment (leadingCombinator) {
126
+ if (!leadingCombinator || allowedCombinators.size === 0) {
127
+ return false;
128
+ }
129
+
130
+ return allowedCombinators.has(leadingCombinator);
131
+ }
132
+
37
133
  // @ts-expect-error -- Parameter 'selectorNode' implicitly has an 'any' type. / Parameter 'ruleNode' implicitly has an 'any' type.
38
134
  function checkSelector (selectorNode, ruleNode) {
39
- // @ts-expect-error -- Parameter 'node' implicitly has an 'any' type.
40
- const combinedSegments = selectorNode.split((node) => (node.type === 'combinator'));
135
+ const segments = splitSelectorIntoSegments(selectorNode);
41
136
 
42
- for (const segment of combinedSegments) {
137
+ for (const segment of segments) {
43
138
  let unqualifiedTagNode;
44
- for (const node of segment) {
139
+ for (const node of segment.nodes) {
45
140
  if (node.type === 'tag' && matchesStringOrRegExp(node.value, primaryOption)) {
46
141
  unqualifiedTagNode = node;
47
142
  }
@@ -50,13 +145,13 @@ function rule (primaryOption) {
50
145
  }
51
146
  }
52
147
 
53
- if (unqualifiedTagNode) {
148
+ if (unqualifiedTagNode && !shouldIgnoreSegment(segment.leadingCombinator)) {
54
149
  stylelint.utils.report({
55
150
  ruleName,
56
151
  result,
57
152
  node: ruleNode,
58
153
  message: messages.unexpected(unqualifiedTagNode.value),
59
- word: unqualifiedTagNode
154
+ word: unqualifiedTagNode.value
60
155
  });
61
156
  }
62
157
  }