linter-bundle 7.11.0 → 7.12.1

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/.linter-bundle.js CHANGED
@@ -16,7 +16,7 @@ export default {
16
16
  basePath: '.',
17
17
  allowed: [
18
18
  '.git/**',
19
- '.github/FUNDING.yml',
19
+ '.github/**',
20
20
  '.vscode/settings.json',
21
21
  `eslint/rules/${snippets.kebabCase}.{js,mjs,md}`,
22
22
  `eslint/rules/helper/${snippets.kebabCase}.{js,mjs,md}`,
package/CHANGELOG.md CHANGED
@@ -6,7 +6,60 @@ 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.11.0...HEAD)
9
+ [Show all code changes](https://github.com/jens-duttke/linter-bundle/compare/v7.12.1...HEAD)
10
+
11
+ ## [7.12.1] - 2026-02-21
12
+
13
+ ### Fixed
14
+
15
+ - [stylelint] Fix stylistic plugins crashing with `TypeError: selector.startsWith is not a function` due to `parseSelector` API change in stylelint 17 (callback parameter removed, now returns AST instead of string)
16
+
17
+ [Show all code changes](https://github.com/jens-duttke/linter-bundle/compare/v7.12.0...v7.12.1)
18
+
19
+ ## [7.12.0] - 2026-02-20
20
+
21
+ ### Breaking changes
22
+
23
+ - [general] Drop support for Node.js versions less than 20.19.0, 22.0.0-22.12.x as some of the dependencies are not supporting them anymore
24
+ - [stylelint] Replaced `stylelint-use-logical-spec` with [`stylelint-use-logical`](https://github.com/csstools/stylelint-use-logical). The rule name changed from `liberty/use-logical-spec` to `csstools/use-logical`.
25
+
26
+ ### Changed
27
+
28
+ - [eslint] Updated `@stylistic/eslint-plugin` from `5.6.1` to `5.9.0`
29
+ - [eslint] Updated `eslint` from `9.39.1` to `9.39.3`
30
+ - [eslint] Updated `eslint-plugin-jest` from `29.1.0` to `29.15.0`
31
+ - [eslint] Updated `eslint-plugin-jsdoc` from `61.3.0` to `62.7.0`
32
+ - [eslint] Updated `eslint-plugin-n` from `17.23.1` to `17.24.0`
33
+ - [eslint] Updated `eslint-plugin-unicorn` from `62.0.0` to `63.0.0`
34
+ - [eslint] Updated `globals` from `16.5.0` to `17.3.0`
35
+ - [eslint] Updated `typescript-eslint` from `8.47.0` to `8.56.0`
36
+ - [stylelint] Updated `stylelint` from `16.25.0` to `17.3.0`
37
+ - [stylelint] Updated `stylelint-declaration-block-no-ignored-properties` from `2.8.0` to `3.0.0`
38
+ - [stylelint] Updated `stylelint-high-performance-animation` from `1.11.0` to `2.0.0`
39
+ - [stylelint] Updated `stylelint-order` from `7.0.0` to `7.0.1`
40
+ - [stylelint] Updated `stylelint-scss` from `6.12.1` to `7.0.0`
41
+ - [markdown] Updated `markdownlint-cli` from `0.46.0` to `0.47.0`
42
+ - [eslint] Make use of new [`jsdoc/require-rejects`](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-rejects.md) rule
43
+ - [eslint] Make use of new [`unicorn/isolated-functions`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/isolated-functions.md) rule
44
+ - [eslint] Make use of new [`@typescript-eslint/no-unused-private-class-members`](https://typescript-eslint.io/rules/no-unused-private-class-members/) rule
45
+ - [eslint] Make use of new [`@typescript-eslint/no-useless-default-assignment`](https://typescript-eslint.io/rules/no-useless-default-assignment/) rule
46
+ - [eslint] Make use of new [`jest/no-error-equal`](https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/no-error-equal.md) rule
47
+ - [eslint] Make use of new [`jest/no-unneeded-async-expect-function`](https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/no-unneeded-async-expect-function.md) rule
48
+ - [eslint] Make use of new [`jest/no-unnecessary-assertion`](https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/no-unnecessary-assertion.md) rule
49
+ - [eslint] Make use of new [`jest/prefer-mock-return-shorthand`](https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/prefer-mock-return-shorthand.md) rule
50
+ - [eslint] Make use of new [`jest/prefer-to-have-been-called`](https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/prefer-to-have-been-called.md) rule
51
+ - [eslint] Make use of new [`jest/prefer-to-have-been-called-times`](https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/prefer-to-have-been-called-times.md) rule
52
+ - [eslint] Make use of new [`jest/valid-expect-with-promise`](https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/valid-expect-with-promise.md) rule
53
+ - [eslint] Make use of new [`jest/valid-mock-module-path`](https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/valid-mock-module-path.md) rule
54
+ - [eslint] Make use of new [`n/prefer-global/crypto`](https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/prefer-global/crypto.md) rule
55
+ - [eslint] Make use of new [`n/prefer-global/timers`](https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/prefer-global/timers.md) rule
56
+ - [eslint] Make use of new [`@typescript-eslint/strict-void-return`](https://typescript-eslint.io/rules/strict-void-return/) rule
57
+ - [eslint] Added but disabled [`@stylistic/jsx-props-style`](https://eslint.style/rules/jsx-props-style) rule, as it is an experimental feature
58
+ - [stylelint] Added but disabled [`display-notation`](https://stylelint.io/user-guide/rules/display-notation/) rule
59
+ - [stylelint] Added but disabled [`scss/selector-class-pattern`](https://github.com/stylelint-scss/stylelint-scss/tree/master/src/rules/selector-class-pattern) rule
60
+ - [stylelint] Updated forked `plugin/selector-tag-no-without-class` for stylelint 17 compatibility (adapted `parseSelector` API change)
61
+
62
+ [Show all code changes](https://github.com/jens-duttke/linter-bundle/compare/v7.11.0...v7.12.0)
10
63
 
11
64
  ## [7.11.0] - 2025-11-19
12
65
 
package/README.md CHANGED
@@ -56,7 +56,7 @@ Beside that, the following additional rules are part of this bundle:
56
56
  - [stylelint-scss](https://www.npmjs.com/package/stylelint-scss)
57
57
  - [stylelint-selector-no-empty](https://www.npmjs.com/package/stylelint-selector-no-empty) (Forked version)
58
58
  - [stylelint-selector-tag-no-without-class](https://www.npmjs.com/package/stylelint-selector-tag-no-without-class) (Forked version)
59
- - [stylelint-use-logical-spec](https://www.npmjs.com/package/stylelint-use-logical-spec)
59
+ - [stylelint-use-logical](https://www.npmjs.com/package/stylelint-use-logical)
60
60
 
61
61
  Beside that [72 stylistic rules](https://github.com/jens-duttke/linter-bundle/tree/main/stylelint/plugins/stylelint-15.11.0-stylistic/rules) has been forked from `stylelint@15.11.0`, which have been removed in `stylelint@16.0.0`, are part of this bundle.
62
62
 
@@ -113,8 +113,8 @@ export default [
113
113
  ...reactConfig,
114
114
  ...storybookConfig,
115
115
  ...typeDeclarationsConfig,
116
- ...workerConfig
117
- ...jestConfig,
116
+ ...workerConfig,
117
+ ...jestConfig
118
118
  ]
119
119
  ```
120
120
 
@@ -668,7 +668,7 @@ To ensure the stylelint plugins are correctly loaded, you need to adjust the set
668
668
  ```json
669
669
  {
670
670
  "stylelint.enable": true,
671
- "stylelint.validate": [,
671
+ "stylelint.validate": [
672
672
  "css",
673
673
  "scss"
674
674
  ],
@@ -685,8 +685,8 @@ In order to fix the code according to the ESLint/stylelint rules when saving, th
685
685
  ```json
686
686
  {
687
687
  "editor.codeActionsOnSave": {
688
- "source.fixAll.eslint": true,
689
- "source.fixAll.stylelint": true
688
+ "source.fixAll.eslint": "explicit",
689
+ "source.fixAll.stylelint": "explicit"
690
690
  }
691
691
  }
692
692
  ```
package/eslint/index.mjs CHANGED
@@ -465,6 +465,7 @@ export default [
465
465
  ignoredNodes: ['ConditionalExpression']
466
466
  }
467
467
  ],
468
+ '@stylistic/jsx-props-style': 'off', // Experimental feature
468
469
  '@stylistic/key-spacing': 'error',
469
470
  '@stylistic/member-delimiter-style': ['error', { multiline: { delimiter: 'semi', requireLast: true }, singleline: { delimiter: 'semi', requireLast: true } }],
470
471
  '@stylistic/no-extra-semi': 'error',
@@ -795,6 +796,7 @@ export default [
795
796
  '@typescript-eslint/no-unsafe-unary-minus': 'error',
796
797
  '@typescript-eslint/no-unused-expressions': 'error',
797
798
  '@typescript-eslint/no-unsafe-type-assertion': 'error',
799
+ '@typescript-eslint/no-unused-private-class-members': 'error',
798
800
  '@typescript-eslint/no-unused-vars': ['error', {
799
801
  args: 'all',
800
802
  argsIgnorePattern: '^_',
@@ -806,6 +808,7 @@ export default [
806
808
  }],
807
809
  '@typescript-eslint/no-use-before-define': ['error', { functions: false }],
808
810
  '@typescript-eslint/no-useless-constructor': 'error',
811
+ '@typescript-eslint/no-useless-default-assignment': 'error',
809
812
  '@typescript-eslint/no-useless-empty-export': 'error',
810
813
  '@typescript-eslint/no-var-requires': 'error',
811
814
  '@typescript-eslint/non-nullable-type-assertion-style': 'off', // Conflicts with `no-non-null-assertion`, which we prefer
@@ -841,6 +844,7 @@ export default [
841
844
  '@typescript-eslint/sort-type-constituents': 'off', // Types should be sorted and grouped by priority and their meaning, not alphabetically
842
845
  '@typescript-eslint/sort-type-union-intersection-members': 'off', // Types should be sorted and grouped by priority and their meaning, not alphabetically
843
846
  '@typescript-eslint/strict-boolean-expressions': ['off', { allowNullable: true, allowSafe: true, ignoreRhs: true }], // @todo Doesn't work for specific code, check later after all linter warnings are fixed, maybe at some positions we can use '??'
847
+ '@typescript-eslint/strict-void-return': 'error',
844
848
  '@typescript-eslint/switch-exhaustiveness-check': ['error', { considerDefaultExhaustiveForUnions: true }],
845
849
  '@typescript-eslint/triple-slash-reference': 'error',
846
850
  '@typescript-eslint/typedef': 'off', // We are using "noImplicitAny" in tsconfig.json instead
@@ -958,6 +962,7 @@ export default [
958
962
 
959
963
  /**
960
964
  * eslint-plugin-eslint-comments
965
+ *
961
966
  * @see https://mysticatea.github.io/eslint-plugin-eslint-comments/
962
967
  */
963
968
  'eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
@@ -1047,6 +1052,7 @@ export default [
1047
1052
  }
1048
1053
  }
1049
1054
  }],
1055
+ 'unicorn/isolated-functions': 'error',
1050
1056
  'unicorn/new-for-builtins': 'error',
1051
1057
  'unicorn/no-abusive-eslint-disable': 'error',
1052
1058
  'unicorn/no-accessor-recursion': 'error',
@@ -86,9 +86,11 @@ export default [
86
86
  'n/no-top-level-await': 'off', // Conflicts with unicorn/prefer-await
87
87
  'n/prefer-global/buffer': 'error',
88
88
  'n/prefer-global/console': 'error',
89
+ 'n/prefer-global/crypto': 'error',
89
90
  'n/prefer-global/process': 'error',
90
91
  'n/prefer-global/text-decoder': 'error',
91
92
  'n/prefer-global/text-encoder': 'error',
93
+ 'n/prefer-global/timers': 'error',
92
94
  'n/prefer-global/url-search-params': 'error',
93
95
  'n/prefer-global/url': 'error',
94
96
  'n/prefer-promises/dns': 'error',
package/eslint/jest.mjs CHANGED
@@ -71,6 +71,7 @@ export default [
71
71
  'jest/no-disabled-tests': 'error',
72
72
  'jest/no-done-callback': 'error',
73
73
  'jest/no-duplicate-hooks': 'error',
74
+ 'jest/no-error-equal': 'error',
74
75
  'jest/no-export': 'error',
75
76
  'jest/no-focused-tests': 'error',
76
77
  'jest/no-hooks': ['error', { allow: ['beforeEach', 'afterEach', 'afterAll'] }],
@@ -90,6 +91,8 @@ export default [
90
91
  'jest/no-standalone-expect': 'error',
91
92
  'jest/no-test-prefixes': 'error',
92
93
  'jest/no-test-return-statement': 'error',
94
+ 'jest/no-unneeded-async-expect-function': 'error',
95
+ 'jest/no-unnecessary-assertion': 'error',
93
96
  'jest/prefer-called-with': 'error',
94
97
  'jest/prefer-expect-assertions': ['error', { onlyFunctionsWithAsyncKeyword: true }],
95
98
  'jest/prefer-expect-resolves': 'off', // We prefer `expect(await promise)` enforced by 'jest/no-restricted-matchers'
@@ -102,11 +105,14 @@ export default [
102
105
  'jest/prefer-importing-jest-globals': 'error',
103
106
  'jest/prefer-jest-mocked': 'error',
104
107
  'jest/prefer-mock-promise-shorthand': 'error',
108
+ 'jest/prefer-mock-return-shorthand': 'error',
105
109
  'jest/prefer-snapshot-hint': 'error',
106
110
  'jest/prefer-spy-on': 'error',
107
111
  'jest/prefer-strict-equal': 'error',
108
112
  'jest/prefer-to-be': 'error',
109
113
  'jest/prefer-to-contain': 'error',
114
+ 'jest/prefer-to-have-been-called': 'error',
115
+ 'jest/prefer-to-have-been-called-times': 'error',
110
116
  'jest/prefer-to-have-length': 'error',
111
117
  'jest/prefer-todo': 'error',
112
118
  'jest/require-hook': 'error',
@@ -116,6 +122,8 @@ export default [
116
122
  'jest/valid-describe-callback': 'error',
117
123
  'jest/valid-expect-in-promise': 'error',
118
124
  'jest/valid-expect': 'error',
125
+ 'jest/valid-expect-with-promise': 'error',
126
+ 'jest/valid-mock-module-path': 'error',
119
127
  'jest/valid-title': 'error'
120
128
  }
121
129
  },
package/eslint/jsdoc.mjs CHANGED
@@ -77,6 +77,7 @@ export default [
77
77
  'jsdoc/require-returns-description': 'error',
78
78
  'jsdoc/require-returns-type': 'error',
79
79
  'jsdoc/require-returns': ['error', { forceReturnsWithAsync: true }],
80
+ 'jsdoc/require-rejects': 'error',
80
81
  'jsdoc/require-template-description': 'off',
81
82
  'jsdoc/require-template': 'error',
82
83
  'jsdoc/require-throws-description': 'error',
@@ -12,7 +12,6 @@ export default {
12
12
  type: 'problem',
13
13
  docs: {
14
14
  description: 'Add parentheses around logical operations if not already present',
15
- category: 'Best Practices',
16
15
  recommended: true
17
16
  },
18
17
  fixable: 'code'
@@ -13,7 +13,6 @@ export default {
13
13
  fixable: 'code',
14
14
  docs: {
15
15
  description: 'Requires ternary expressions to be wrapped in parentheses.',
16
- category: 'Styling',
17
16
  recommended: true
18
17
  }
19
18
  },
@@ -10,7 +10,6 @@ export default {
10
10
  type: 'suggestion',
11
11
  docs: {
12
12
  description: 'Disallow ternary expressions as return values for better readability',
13
- category: 'Stylistic Issues',
14
13
  recommended: true
15
14
  },
16
15
  fixable: 'code',
@@ -34,12 +34,12 @@ export async function getGitFiles () {
34
34
  };
35
35
 
36
36
  if (!gitFiles) {
37
- const deletedFiles = gitProcessResult.deleted.stdout.trim().split('\0');
37
+ const deletedFiles = new Set(gitProcessResult.deleted.stdout.trim().split('\0'));
38
38
 
39
39
  gitFiles = [
40
40
  ...gitProcessResult.diff.stdout.trim().split('\0'),
41
41
  ...gitProcessResult.modified.stdout.trim().split('\0')
42
- ].filter((file, index, self) => (!deletedFiles.includes(file) && self.indexOf(file) === index));
42
+ ].filter((file, index, self) => (!deletedFiles.has(file) && self.indexOf(file) === index));
43
43
  }
44
44
 
45
45
  return gitFiles;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linter-bundle",
3
- "version": "7.11.0",
3
+ "version": "7.12.1",
4
4
  "type": "module",
5
5
  "description": "Ready-to use bundle of linting tools, containing configurations for ESLint, stylelint and markdownlint.",
6
6
  "keywords": [
@@ -24,7 +24,7 @@
24
24
  "lint": "lint.js"
25
25
  },
26
26
  "engines": {
27
- "node": "^20.12.0 || ^22.0.0 || >=24.0.0"
27
+ "node": "^20.19.0 || ^22.13.0 || >=24.0.0"
28
28
  },
29
29
  "repository": {
30
30
  "type": "git",
@@ -35,40 +35,40 @@
35
35
  "publish:minor": "npm version minor",
36
36
  "publish:patch": "npm version patch",
37
37
  "lint": "npm run _test-stylelint && npm run _stylelint-find-rules && node ./lint files tsc ts md audit --min-severity=critical",
38
- "preversion": "npm run check-outdated && npm run lint",
38
+ "preversion": "npm whoami && npm run check-outdated && npm run lint",
39
39
  "postversion": "git push && git push --tags && npm publish",
40
- "check-outdated": "npx --yes -- check-outdated --ignore-pre-releases",
40
+ "check-outdated": "npx --yes -- check-outdated --ignore-pre-releases --min-age 30 --min-age-patch 1",
41
41
  "_stylelint-find-rules": "stylelint-find-new-rules ./stylelint/index.mjs",
42
42
  "_test-stylelint": "node ./test-stylelint.js"
43
43
  },
44
44
  "dependencies": {
45
- "@stylistic/eslint-plugin": "5.6.1",
46
- "eslint": "9.39.1",
45
+ "@stylistic/eslint-plugin": "5.9.0",
46
+ "eslint": "9.39.3",
47
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.1.0",
54
- "eslint-plugin-jsdoc": "61.3.0",
53
+ "eslint-plugin-jest": "29.15.0",
54
+ "eslint-plugin-jsdoc": "62.7.0",
55
55
  "eslint-plugin-jsx-a11y": "6.10.2",
56
- "eslint-plugin-n": "17.23.1",
56
+ "eslint-plugin-n": "17.24.0",
57
57
  "eslint-plugin-promise": "7.2.1",
58
58
  "eslint-plugin-react": "7.37.5",
59
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",
60
+ "eslint-plugin-unicorn": "63.0.0",
61
+ "globals": "17.3.0",
62
+ "markdownlint-cli": "0.47.0",
63
63
  "micromatch": "4.0.8",
64
64
  "postcss-scss": "4.0.9",
65
- "stylelint": "16.25.0",
66
- "stylelint-declaration-block-no-ignored-properties": "2.8.0",
67
- "stylelint-high-performance-animation": "1.11.0",
68
- "stylelint-order": "7.0.0",
69
- "stylelint-scss": "6.12.1",
70
- "stylelint-use-logical-spec": "5.0.1",
71
- "typescript-eslint": "8.47.0"
65
+ "stylelint": "17.3.0",
66
+ "stylelint-declaration-block-no-ignored-properties": "3.0.0",
67
+ "stylelint-high-performance-animation": "2.0.0",
68
+ "stylelint-order": "7.0.1",
69
+ "stylelint-scss": "7.0.0",
70
+ "stylelint-use-logical": "2.1.3",
71
+ "typescript-eslint": "8.56.0"
72
72
  },
73
73
  "peerDependencies": {
74
74
  "@typescript-eslint/utils": "*",
@@ -78,8 +78,8 @@
78
78
  "devDependencies": {
79
79
  "@types/eslint": "9.6.1",
80
80
  "@types/micromatch": "4.0.10",
81
- "@types/node": "24.10.1",
82
- "stylelint-find-new-rules": "5.0.0",
81
+ "@types/node": "25.3.0",
82
+ "stylelint-find-new-rules": "6.0.0",
83
83
  "typescript": "5.9.3"
84
84
  }
85
85
  }
@@ -16,7 +16,7 @@ export default {
16
16
  'stylelint-declaration-block-no-ignored-properties',
17
17
  'stylelint-order',
18
18
  'stylelint-scss',
19
- 'stylelint-use-logical-spec',
19
+ 'stylelint-use-logical',
20
20
  'stylelint-high-performance-animation',
21
21
  await import('./plugins/stylelint-selector-no-empty.js'),
22
22
  await import('./plugins/stylelint-selector-tag-no-without-class.js'),
@@ -329,6 +329,7 @@ export default {
329
329
  'declaration-property-value-disallowed-list': null,
330
330
  'declaration-property-value-keyword-no-deprecated': true,
331
331
  'declaration-property-value-no-unknown': true,
332
+ 'display-notation': null, // Not yet enforcing short vs. long display notation (e.g. "block flow" vs. "block")
332
333
  'font-family-name-quotes': 'always-where-recommended',
333
334
  'font-family-no-duplicate-names': true,
334
335
  'font-family-no-missing-generic-family-keyword': true,
@@ -1139,6 +1140,7 @@ export default {
1139
1140
  'scss/partial-no-import': null,
1140
1141
  'scss/percent-placeholder-pattern': null,
1141
1142
  'scss/property-no-unknown': [true, { ignoreProperties: ['composes'] }],
1143
+ 'scss/selector-class-pattern': null, // Already handled by core selector-class-pattern rule
1142
1144
  'scss/selector-nest-combinators': null, // Sometimes nesting does not make sense
1143
1145
  'scss/selector-no-redundant-nesting-selector': true,
1144
1146
  'scss/selector-no-union-class-name': null,
@@ -1158,11 +1160,11 @@ export default {
1158
1160
  'plugin/selector-tag-no-without-class': null,
1159
1161
 
1160
1162
  /**
1161
- * stylelint-use-logical-spec
1163
+ * stylelint-use-logical
1162
1164
  *
1163
- * @see https://github.com/Jordan-Hall/stylelint-use-logical-spec
1165
+ * @see https://github.com/csstools/stylelint-use-logical
1164
1166
  */
1165
- 'liberty/use-logical-spec': ['always', { except: [
1167
+ 'csstools/use-logical': ['always', { except: [
1166
1168
  'float',
1167
1169
 
1168
1170
  'inset',
@@ -39,7 +39,7 @@ const rule = (primary, _secondaryOptions) => {
39
39
  /** @type {number[]} */
40
40
  const fixOperatorIndices = [];
41
41
  findMediaOperator(atRule, (match, parameters, node) => {
42
- checkBeforeOperator(match, parameters, node, (index) => fixOperatorIndices.push(index));
42
+ checkBeforeOperator(match, parameters, node, (index) => { fixOperatorIndices.push(index); });
43
43
  });
44
44
 
45
45
  if (fixOperatorIndices.length > 0) {
@@ -47,88 +47,92 @@ const rule = (primary, _secondaryOptions) => {
47
47
  const selector = ruleNode.raws.selector ? ruleNode.raws.selector.raw : ruleNode.selector;
48
48
 
49
49
  let hasFixed;
50
- const fixedSelector = parseSelector(selector, result, ruleNode, (selectorTree) => {
51
- selectorTree.walkAttributes((attributeNode) => {
52
- const attributeSelectorString = attributeNode.toString();
53
-
54
- styleSearch({ source: attributeSelectorString, target: '[' }, (match) => {
55
- const nextCharIsSpace = attributeSelectorString[match.startIndex + 1] === ' ';
56
- const index = attributeNode.sourceIndex + match.startIndex + 1;
57
-
58
- if (nextCharIsSpace && primary === 'never') {
59
- report({
60
- message: messages.rejectedOpening,
61
- index,
62
- endIndex: index,
63
- result,
64
- ruleName,
65
- node: ruleNode,
66
- fix: () => {
67
- hasFixed = true;
68
- fixBefore(attributeNode);
69
- }
70
- });
71
- }
72
-
73
- if (!nextCharIsSpace && primary === 'always') {
74
- report({
75
- message: messages.expectedOpening,
76
- index,
77
- endIndex: index,
78
- result,
79
- ruleName,
80
- node: ruleNode,
81
- fix: () => {
82
- hasFixed = true;
83
- fixBefore(attributeNode);
84
- }
85
- });
86
- }
87
- });
88
-
89
- styleSearch({ source: attributeSelectorString, target: ']' }, (match) => {
90
- const previousCharIsSpace = attributeSelectorString[match.startIndex - 1] === ' ';
91
- const index = attributeNode.sourceIndex + match.startIndex - 1;
92
-
93
- if (previousCharIsSpace && primary === 'never') {
94
- report({
95
- message: messages.rejectedClosing,
96
- index,
97
- endIndex: index,
98
- result,
99
- ruleName,
100
- node: ruleNode,
101
- fix: () => {
102
- hasFixed = true;
103
- fixAfter(attributeNode);
104
- }
105
- });
106
- }
107
-
108
- if (!previousCharIsSpace && primary === 'always') {
109
- report({
110
- message: messages.expectedClosing,
111
- index,
112
- endIndex: index,
113
- result,
114
- ruleName,
115
- node: ruleNode,
116
- fix: () => {
117
- hasFixed = true;
118
- fixAfter(attributeNode);
119
- }
120
- });
121
- }
122
- });
50
+ const selectorTree = parseSelector(selector, result, ruleNode);
51
+
52
+ if (!selectorTree) {
53
+ return;
54
+ }
55
+
56
+ selectorTree.walkAttributes((attributeNode) => {
57
+ const attributeSelectorString = attributeNode.toString();
58
+
59
+ styleSearch({ source: attributeSelectorString, target: '[' }, (match) => {
60
+ const nextCharIsSpace = attributeSelectorString[match.startIndex + 1] === ' ';
61
+ const index = attributeNode.sourceIndex + match.startIndex + 1;
62
+
63
+ if (nextCharIsSpace && primary === 'never') {
64
+ report({
65
+ message: messages.rejectedOpening,
66
+ index,
67
+ endIndex: index,
68
+ result,
69
+ ruleName,
70
+ node: ruleNode,
71
+ fix: () => {
72
+ hasFixed = true;
73
+ fixBefore(attributeNode);
74
+ }
75
+ });
76
+ }
77
+
78
+ if (!nextCharIsSpace && primary === 'always') {
79
+ report({
80
+ message: messages.expectedOpening,
81
+ index,
82
+ endIndex: index,
83
+ result,
84
+ ruleName,
85
+ node: ruleNode,
86
+ fix: () => {
87
+ hasFixed = true;
88
+ fixBefore(attributeNode);
89
+ }
90
+ });
91
+ }
92
+ });
93
+
94
+ styleSearch({ source: attributeSelectorString, target: ']' }, (match) => {
95
+ const previousCharIsSpace = attributeSelectorString[match.startIndex - 1] === ' ';
96
+ const index = attributeNode.sourceIndex + match.startIndex - 1;
97
+
98
+ if (previousCharIsSpace && primary === 'never') {
99
+ report({
100
+ message: messages.rejectedClosing,
101
+ index,
102
+ endIndex: index,
103
+ result,
104
+ ruleName,
105
+ node: ruleNode,
106
+ fix: () => {
107
+ hasFixed = true;
108
+ fixAfter(attributeNode);
109
+ }
110
+ });
111
+ }
112
+
113
+ if (!previousCharIsSpace && primary === 'always') {
114
+ report({
115
+ message: messages.expectedClosing,
116
+ index,
117
+ endIndex: index,
118
+ result,
119
+ ruleName,
120
+ node: ruleNode,
121
+ fix: () => {
122
+ hasFixed = true;
123
+ fixAfter(attributeNode);
124
+ }
125
+ });
126
+ }
123
127
  });
124
128
  });
125
129
 
126
- if (hasFixed && fixedSelector) {
130
+ if (hasFixed) {
127
131
  if (!ruleNode.raws.selector) {
128
- ruleNode.selector = fixedSelector;
132
+ ruleNode.selector = selectorTree.toString();
129
133
  }
130
134
  else {
131
- ruleNode.raws.selector.raw = fixedSelector;
135
+ ruleNode.raws.selector.raw = selectorTree.toString();
132
136
  }
133
137
  }
134
138
  });
@@ -40,47 +40,51 @@ const rule = (primary, _secondaryOptions) => (root, result) => {
40
40
  // @todo re-enable when parser and stylelint are compatible
41
41
  if (selector.includes('/*')) { return; }
42
42
 
43
- const fixedSelector = parseSelector(selector, result, ruleNode, (fullSelector) => {
44
- fullSelector.walkCombinators((combinatorNode) => {
45
- if (combinatorNode.value !== ' ') {
46
- return;
47
- }
48
-
49
- const value = combinatorNode.toString();
50
-
51
- if (
52
- value.includes(' ') ||
53
- value.includes('\t') ||
54
- value.includes('\n') ||
55
- value.includes('\r')
56
- ) {
57
- report({
58
- result,
59
- ruleName,
60
- message: messages.rejected(value),
61
- node: ruleNode,
62
- index: combinatorNode.sourceIndex,
63
- endIndex: combinatorNode.sourceIndex,
64
- fix: ((/^\s+$/u).test(value) ? () => {
65
- hasFixed = true;
66
-
67
- if (!combinatorNode.raws) { combinatorNode.raws = {}; }
68
-
69
- combinatorNode.raws.value = ' ';
70
- combinatorNode.rawSpaceBefore = combinatorNode.rawSpaceBefore.replace(/^\s+/u, '');
71
- combinatorNode.rawSpaceAfter = combinatorNode.rawSpaceAfter.replace(/\s+$/u, '');
72
- } : undefined)
73
- });
74
- }
75
- });
43
+ const selectorTree = parseSelector(selector, result, ruleNode);
44
+
45
+ if (!selectorTree) {
46
+ return;
47
+ }
48
+
49
+ selectorTree.walkCombinators((combinatorNode) => {
50
+ if (combinatorNode.value !== ' ') {
51
+ return;
52
+ }
53
+
54
+ const value = combinatorNode.toString();
55
+
56
+ if (
57
+ value.includes(' ') ||
58
+ value.includes('\t') ||
59
+ value.includes('\n') ||
60
+ value.includes('\r')
61
+ ) {
62
+ report({
63
+ result,
64
+ ruleName,
65
+ message: messages.rejected(value),
66
+ node: ruleNode,
67
+ index: combinatorNode.sourceIndex,
68
+ endIndex: combinatorNode.sourceIndex,
69
+ fix: ((/^\s+$/u).test(value) ? () => {
70
+ hasFixed = true;
71
+
72
+ if (!combinatorNode.raws) { combinatorNode.raws = {}; }
73
+
74
+ combinatorNode.raws.value = ' ';
75
+ combinatorNode.rawSpaceBefore = combinatorNode.rawSpaceBefore.replace(/^\s+/u, '');
76
+ combinatorNode.rawSpaceAfter = combinatorNode.rawSpaceAfter.replace(/\s+$/u, '');
77
+ } : undefined)
78
+ });
79
+ }
76
80
  });
77
81
 
78
- if (hasFixed && fixedSelector) {
82
+ if (hasFixed) {
79
83
  if (!ruleNode.raws.selector) {
80
- ruleNode.selector = fixedSelector;
84
+ ruleNode.selector = selectorTree.toString();
81
85
  }
82
86
  else {
83
- ruleNode.raws.selector.raw = fixedSelector;
87
+ ruleNode.raws.selector.raw = selectorTree.toString();
84
88
  }
85
89
  }
86
90
  });
@@ -44,54 +44,57 @@ const rule = (primary, _secondaryOptions) => (root, result) => {
44
44
  }
45
45
 
46
46
  let hasFixed = false;
47
- const fixedSelector = parseSelector(
47
+ const selectorTree = parseSelector(
48
48
  ruleNode.raws.selector ? ruleNode.raws.selector.raw : ruleNode.selector,
49
49
  result,
50
- ruleNode,
51
- (selectorTree) => {
52
- selectorTree.walkPseudos((pseudoNode) => {
53
- const pseudo = pseudoNode.value;
54
-
55
- if (!isStandardSyntaxSelector(pseudo)) {
56
- return;
57
- }
58
-
59
- if (
60
- pseudo.includes('::') ||
61
- levelOneAndTwoPseudoElements.has(pseudo.toLowerCase().slice(1))
62
- ) {
63
- return;
64
- }
65
-
66
- const expectedPseudo =
67
- primary === 'lower' ? pseudo.toLowerCase() : pseudo.toUpperCase();
68
-
69
- if (pseudo === expectedPseudo) {
70
- return;
71
- }
72
-
73
- report({
74
- message: messages.expected(pseudo, expectedPseudo),
75
- node: ruleNode,
76
- index: pseudoNode.sourceIndex,
77
- endIndex: pseudoNode.sourceIndex,
78
- ruleName,
79
- result,
80
- fix: () => {
81
- hasFixed = true;
82
- pseudoNode.value = expectedPseudo;
83
- }
84
- });
85
- });
86
- }
50
+ ruleNode
87
51
  );
88
52
 
89
- if (fixedSelector) {
53
+ if (!selectorTree) {
54
+ return;
55
+ }
56
+
57
+ selectorTree.walkPseudos((pseudoNode) => {
58
+ const pseudo = pseudoNode.value;
59
+
60
+ if (!isStandardSyntaxSelector(pseudo)) {
61
+ return;
62
+ }
63
+
64
+ if (
65
+ pseudo.includes('::') ||
66
+ levelOneAndTwoPseudoElements.has(pseudo.toLowerCase().slice(1))
67
+ ) {
68
+ return;
69
+ }
70
+
71
+ const expectedPseudo =
72
+ primary === 'lower' ? pseudo.toLowerCase() : pseudo.toUpperCase();
73
+
74
+ if (pseudo === expectedPseudo) {
75
+ return;
76
+ }
77
+
78
+ report({
79
+ message: messages.expected(pseudo, expectedPseudo),
80
+ node: ruleNode,
81
+ index: pseudoNode.sourceIndex,
82
+ endIndex: pseudoNode.sourceIndex,
83
+ ruleName,
84
+ result,
85
+ fix: () => {
86
+ hasFixed = true;
87
+ pseudoNode.value = expectedPseudo;
88
+ }
89
+ });
90
+ });
91
+
92
+ if (hasFixed) {
90
93
  if (ruleNode.raws.selector) {
91
- ruleNode.raws.selector.raw = fixedSelector;
94
+ ruleNode.raws.selector.raw = selectorTree.toString();
92
95
  }
93
96
  else {
94
- ruleNode.selector = fixedSelector;
97
+ ruleNode.selector = selectorTree.toString();
95
98
  }
96
99
  }
97
100
  });
@@ -43,87 +43,91 @@ const rule = (primary, _secondaryOptions) => (root, result) => {
43
43
 
44
44
  let hasFixed = false;
45
45
  const selector = ruleNode.raws.selector ? ruleNode.raws.selector.raw : ruleNode.selector;
46
- const fixedSelector = parseSelector(selector, result, ruleNode, (selectorTree) => {
47
- selectorTree.walkPseudos((pseudoNode) => {
48
- if (pseudoNode.length === 0) {
49
- return;
50
- }
51
-
52
- const paramString = pseudoNode.map((node) => String(node)).join(',');
53
- const nextCharIsSpace = paramString.startsWith(' ');
54
- const openIndex = pseudoNode.sourceIndex + pseudoNode.value.length + 1;
55
-
56
- if (nextCharIsSpace && primary === 'never') {
57
- report({
58
- message: messages.rejectedOpening,
59
- index: openIndex,
60
- endIndex: openIndex,
61
- result,
62
- ruleName,
63
- node: ruleNode,
64
- fix: () => {
65
- hasFixed = true;
66
- setFirstNodeSpaceBefore(pseudoNode, '');
67
- }
68
- });
69
- }
70
-
71
- if (!nextCharIsSpace && primary === 'always') {
72
- report({
73
- message: messages.expectedOpening,
74
- index: openIndex,
75
- endIndex: openIndex,
76
- result,
77
- ruleName,
78
- node: ruleNode,
79
- fix: () => {
80
- hasFixed = true;
81
- setFirstNodeSpaceBefore(pseudoNode, ' ');
82
- }
83
- });
84
- }
85
-
86
- const previousCharIsSpace = paramString.endsWith(' ');
87
- const closeIndex = openIndex + paramString.length - 1;
88
-
89
- if (previousCharIsSpace && primary === 'never') {
90
- report({
91
- message: messages.rejectedClosing,
92
- index: closeIndex,
93
- endIndex: closeIndex,
94
- result,
95
- ruleName,
96
- node: ruleNode,
97
- fix: () => {
98
- hasFixed = true;
99
- setLastNodeSpaceAfter(pseudoNode, '');
100
- }
101
- });
102
- }
103
-
104
- if (!previousCharIsSpace && primary === 'always') {
105
- report({
106
- message: messages.expectedClosing,
107
- index: closeIndex,
108
- endIndex: closeIndex,
109
- result,
110
- ruleName,
111
- node: ruleNode,
112
- fix: () => {
113
- hasFixed = true;
114
- setLastNodeSpaceAfter(pseudoNode, ' ');
115
- }
116
- });
117
- }
118
- });
46
+ const selectorTree = parseSelector(selector, result, ruleNode);
47
+
48
+ if (!selectorTree) {
49
+ return;
50
+ }
51
+
52
+ selectorTree.walkPseudos((pseudoNode) => {
53
+ if (pseudoNode.length === 0) {
54
+ return;
55
+ }
56
+
57
+ const paramString = pseudoNode.map((node) => String(node)).join(',');
58
+ const nextCharIsSpace = paramString.startsWith(' ');
59
+ const openIndex = pseudoNode.sourceIndex + pseudoNode.value.length + 1;
60
+
61
+ if (nextCharIsSpace && primary === 'never') {
62
+ report({
63
+ message: messages.rejectedOpening,
64
+ index: openIndex,
65
+ endIndex: openIndex,
66
+ result,
67
+ ruleName,
68
+ node: ruleNode,
69
+ fix: () => {
70
+ hasFixed = true;
71
+ setFirstNodeSpaceBefore(pseudoNode, '');
72
+ }
73
+ });
74
+ }
75
+
76
+ if (!nextCharIsSpace && primary === 'always') {
77
+ report({
78
+ message: messages.expectedOpening,
79
+ index: openIndex,
80
+ endIndex: openIndex,
81
+ result,
82
+ ruleName,
83
+ node: ruleNode,
84
+ fix: () => {
85
+ hasFixed = true;
86
+ setFirstNodeSpaceBefore(pseudoNode, ' ');
87
+ }
88
+ });
89
+ }
90
+
91
+ const previousCharIsSpace = paramString.endsWith(' ');
92
+ const closeIndex = openIndex + paramString.length - 1;
93
+
94
+ if (previousCharIsSpace && primary === 'never') {
95
+ report({
96
+ message: messages.rejectedClosing,
97
+ index: closeIndex,
98
+ endIndex: closeIndex,
99
+ result,
100
+ ruleName,
101
+ node: ruleNode,
102
+ fix: () => {
103
+ hasFixed = true;
104
+ setLastNodeSpaceAfter(pseudoNode, '');
105
+ }
106
+ });
107
+ }
108
+
109
+ if (!previousCharIsSpace && primary === 'always') {
110
+ report({
111
+ message: messages.expectedClosing,
112
+ index: closeIndex,
113
+ endIndex: closeIndex,
114
+ result,
115
+ ruleName,
116
+ node: ruleNode,
117
+ fix: () => {
118
+ hasFixed = true;
119
+ setLastNodeSpaceAfter(pseudoNode, ' ');
120
+ }
121
+ });
122
+ }
119
123
  });
120
124
 
121
- if (hasFixed && fixedSelector) {
125
+ if (hasFixed) {
122
126
  if (!ruleNode.raws.selector) {
123
- ruleNode.selector = fixedSelector;
127
+ ruleNode.selector = selectorTree.toString();
124
128
  }
125
129
  else {
126
- ruleNode.raws.selector.raw = fixedSelector;
130
+ ruleNode.raws.selector.raw = selectorTree.toString();
127
131
  }
128
132
  }
129
133
  });
@@ -30,30 +30,34 @@ export default function selectorAttributeOperatorSpaceChecker (options) {
30
30
  let hasFixed = false;
31
31
  const selector = rule.raws.selector ? rule.raws.selector.raw : rule.selector;
32
32
 
33
- const fixedSelector = parseSelector(selector, options.result, rule, (selectorTree) => {
34
- selectorTree.walkAttributes((attributeNode) => {
35
- const operator = attributeNode.operator;
33
+ const selectorTree = parseSelector(selector, options.result, rule);
36
34
 
37
- if (!operator) {
38
- return;
39
- }
35
+ if (!selectorTree) {
36
+ return;
37
+ }
38
+
39
+ selectorTree.walkAttributes((attributeNode) => {
40
+ const operator = attributeNode.operator;
41
+
42
+ if (!operator) {
43
+ return;
44
+ }
40
45
 
41
- const attributeNodeString = attributeNode.toString();
46
+ const attributeNodeString = attributeNode.toString();
42
47
 
43
- styleSearch({ source: attributeNodeString, target: operator }, (match) => {
44
- const index = options.checkBeforeOperator ? match.startIndex : match.endIndex - 1;
48
+ styleSearch({ source: attributeNodeString, target: operator }, (match) => {
49
+ const index = options.checkBeforeOperator ? match.startIndex : match.endIndex - 1;
45
50
 
46
- checkOperator(attributeNodeString, index, rule, attributeNode, operator);
47
- });
51
+ checkOperator(attributeNodeString, index, rule, attributeNode, operator);
48
52
  });
49
53
  });
50
54
 
51
- if (hasFixed && fixedSelector) {
55
+ if (hasFixed) {
52
56
  if (!rule.raws.selector) {
53
- rule.selector = fixedSelector;
57
+ rule.selector = selectorTree.toString();
54
58
  }
55
59
  else {
56
- rule.raws.selector.raw = fixedSelector;
60
+ rule.raws.selector.raw = selectorTree.toString();
57
61
  }
58
62
  }
59
63
 
@@ -28,47 +28,51 @@ export default function selectorCombinatorSpaceChecker (options) {
28
28
  hasFixed = false;
29
29
  const selector = rule.raws.selector ? rule.raws.selector.raw : rule.selector;
30
30
 
31
- const fixedSelector = parseSelector(selector, options.result, rule, (selectorTree) => {
32
- selectorTree.walkCombinators((node) => {
33
- // Ignore non-standard combinators
34
- if (!isStandardSyntaxCombinator(node)) {
35
- return;
36
- }
31
+ const selectorTree = parseSelector(selector, options.result, rule);
37
32
 
38
- // Ignore spaced descendant combinator
39
- if ((/\s/u).test(node.value)) {
40
- return;
41
- }
33
+ if (!selectorTree) {
34
+ return;
35
+ }
36
+
37
+ selectorTree.walkCombinators((node) => {
38
+ // Ignore non-standard combinators
39
+ if (!isStandardSyntaxCombinator(node)) {
40
+ return;
41
+ }
42
42
 
43
- // Check the exist of node in prev of the combinator.
44
- // in case some that aren't the first begin with combinators (nesting syntax)
45
- if (options.locationType === 'before' && !node.prev()) {
46
- return;
47
- }
43
+ // Ignore spaced descendant combinator
44
+ if ((/\s/u).test(node.value)) {
45
+ return;
46
+ }
47
+
48
+ // Check the exist of node in prev of the combinator.
49
+ // in case some that aren't the first begin with combinators (nesting syntax)
50
+ if (options.locationType === 'before' && !node.prev()) {
51
+ return;
52
+ }
48
53
 
49
- const parentParentNode = node.parent?.parent;
54
+ const parentParentNode = node.parent?.parent;
50
55
 
51
- // Ignore pseudo-classes selector like `.foo:nth-child(2n + 1) {}`
52
- if (parentParentNode && parentParentNode.type === 'pseudo') {
53
- return;
54
- }
56
+ // Ignore pseudo-classes selector like `.foo:nth-child(2n + 1) {}`
57
+ if (parentParentNode && parentParentNode.type === 'pseudo') {
58
+ return;
59
+ }
55
60
 
56
- const sourceIndex = node.sourceIndex;
57
- const index =
58
- node.value.length > 1 && options.locationType === 'before' ?
59
- sourceIndex
60
- : sourceIndex + node.value.length - 1;
61
+ const sourceIndex = node.sourceIndex;
62
+ const index =
63
+ node.value.length > 1 && options.locationType === 'before' ?
64
+ sourceIndex
65
+ : sourceIndex + node.value.length - 1;
61
66
 
62
- check(selector, node, index, rule, sourceIndex);
63
- });
67
+ check(selector, node, index, rule, sourceIndex);
64
68
  });
65
69
 
66
- if (hasFixed && fixedSelector) {
70
+ if (hasFixed) {
67
71
  if (!rule.raws.selector) {
68
- rule.selector = fixedSelector;
72
+ rule.selector = selectorTree.toString();
69
73
  }
70
74
  else {
71
- rule.raws.selector.raw = fixedSelector;
75
+ rule.raws.selector.raw = selectorTree.toString();
72
76
  }
73
77
  }
74
78
  });
@@ -86,7 +86,9 @@ const rule = (primary, secondaryOptions) => {
86
86
  /** @type {number[]} */
87
87
  const fixPositions = [];
88
88
 
89
- parseSelector(ruleNode.selector, result, ruleNode, (selectorTree) => {
89
+ const selectorTree = parseSelector(ruleNode.selector, result, ruleNode);
90
+
91
+ if (selectorTree) {
90
92
  let selectorFixed = false;
91
93
 
92
94
  selectorTree.walkAttributes((attributeNode) => {
@@ -165,7 +167,7 @@ const rule = (primary, secondaryOptions) => {
165
167
  if (selectorFixed) {
166
168
  ruleNode.selector = selectorTree.toString();
167
169
  }
168
- });
170
+ }
169
171
 
170
172
  for (const fixIndex of fixPositions) {
171
173
  ruleNode.selector = replaceQuote(ruleNode.selector, fixIndex, correctQuote);
@@ -188,10 +188,11 @@ function rule (primaryOption, secondaryOptions = {}) {
188
188
  }
189
189
 
190
190
  for (const selector of ruleNode.selectors) {
191
- // @ts-expect-error -- Parameter 'container' implicitly has an 'any' type.
192
- parseSelector(selector, result, ruleNode, (container) => {
191
+ const container = parseSelector(selector, result, ruleNode);
192
+
193
+ if (container) {
193
194
  checkSelectorRoot(container, ruleNode);
194
- });
195
+ }
195
196
  }
196
197
  });
197
198
  };