@averay/codeformat 0.2.0 → 0.2.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/.editorconfig CHANGED
@@ -11,5 +11,5 @@ max_line_length = 120
11
11
  trim_trailing_whitespace = true
12
12
 
13
13
  [*.md]
14
- max_line_length = 0
14
+ max_line_length = 999999999
15
15
  trim_trailing_whitespace = false
package/README.md CHANGED
@@ -35,13 +35,11 @@ export default [
35
35
 
36
36
  ### Stylelint
37
37
 
38
- Create a `stylelint.config.cjs` file and create the configuration:
38
+ Create a `stylelint.config.js` file and create the configuration:
39
39
 
40
40
  ```js
41
- // stylelint.config.cjs
42
- const { makeStylelintConfig } = require('@averay/codeformat');
41
+ // stylelint.config.js
42
+ import { makeStylelintConfig } from '@averay/codeformat';
43
43
 
44
- module.exports = makeStylelintConfig();
44
+ export default makeStylelintConfig();
45
45
  ```
46
-
47
- _(Stylelint does not currently support ESM so a `.cjs` file with CommonJS import & export syntax must be used)_
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { existsSync } from 'node:fs';
4
+ import path from 'node:path';
5
+ import { parseArgs } from 'node:util';
6
+
7
+ import { spawn } from 'bun';
8
+
9
+ type Action = 'check' | 'fix';
10
+
11
+ // Parse input
12
+ const { values: options, positionals } = parseArgs({
13
+ args: Bun.argv,
14
+ options: {
15
+ verbose: { type: 'boolean', default: false },
16
+ debug: { type: 'boolean', default: false },
17
+ help: { type: 'boolean', default: false },
18
+ dir: { type: 'string', short: 'd', default: process.cwd() },
19
+ },
20
+ strict: true,
21
+ allowPositionals: true,
22
+ });
23
+ const { dir: projectDir } = options;
24
+ const [, scriptName, selectedAction, ...undefinedArgs] = positionals as [string, string, ...string[]];
25
+
26
+ const output = {
27
+ usage(exitCode: number = 0): never {
28
+ console.error(`Usage:
29
+ ${scriptName} check [root-path]
30
+ ${scriptName} fix [root-path]`);
31
+ process.exit(exitCode);
32
+ },
33
+
34
+ debug(message: string, additionalValues: unknown[] = []): void {
35
+ if (options.debug) {
36
+ console.debug(message, ...additionalValues);
37
+ }
38
+ },
39
+
40
+ verbose(message: string, additionalValues: unknown[] = []): void {
41
+ if (options.verbose) {
42
+ console.debug(message, ...additionalValues);
43
+ }
44
+ },
45
+
46
+ error(message: string, additionalValues: unknown[] = [], exitCode: number = 1): never {
47
+ console.error(message, ...additionalValues);
48
+ process.exit(exitCode);
49
+ },
50
+ };
51
+
52
+ async function bunx(command: string, args: string[]): Promise<void> {
53
+ output.verbose('Running command:', [command, ...args]);
54
+
55
+ const proc = spawn(['bun', '--bun', 'x', command, ...args], {
56
+ cwd: projectDir,
57
+ stdio: ['inherit', 'inherit', 'inherit'],
58
+ });
59
+ const exitCode = await proc.exited;
60
+ if (exitCode !== 0) {
61
+ process.exit(exitCode);
62
+ }
63
+ }
64
+
65
+ function withExts(base: string, exts: string[]): string[] {
66
+ return exts.map((ext) => `${base}.${ext}`);
67
+ }
68
+
69
+ function findFirstFile(suffixes: string[]): string | undefined {
70
+ for (const suffix of suffixes) {
71
+ const filePath = path.join(projectDir, suffix);
72
+ if (existsSync(filePath)) {
73
+ const relativeFilePath = path.isAbsolute(projectDir) ? path.relative(projectDir, filePath) : filePath;
74
+ output.debug('Found config file:', [filePath]);
75
+ return relativeFilePath;
76
+ }
77
+ }
78
+ return undefined;
79
+ }
80
+
81
+ function ifConfig<T>(pathname: string | undefined, callback: (pathname: string) => T): T | undefined {
82
+ return pathname == null ? undefined : callback(pathname);
83
+ }
84
+
85
+ if (options.help) {
86
+ output.usage();
87
+ }
88
+ if (undefinedArgs.length > 0) {
89
+ output.error('Unexpected additional arguments.', [undefinedArgs]);
90
+ }
91
+
92
+ const configPaths = {
93
+ prettier: findFirstFile([
94
+ '.prettierrc',
95
+ ...withExts('prettier.config', ['js', 'ts', 'mjs', 'mts', 'cjs', 'cts']),
96
+ ...withExts('.prettierrc', ['json', 'yml', 'yaml', 'json5', 'js', 'ts', 'mjs', 'mts', 'cjs', 'cts', 'toml']),
97
+ ]),
98
+ eslint: findFirstFile(withExts('eslint.config', ['js', 'mjs', 'cjs', 'ts', 'mts', 'cts'])),
99
+ typeScript: findFirstFile(['tsconfig.json']),
100
+ stylelint: findFirstFile(withExts('stylelint.config', ['cjs', 'mjs', 'js'])),
101
+ };
102
+
103
+ interface Tool {
104
+ actions: Partial<Record<Action, string[]>> | undefined;
105
+ args: Partial<{
106
+ debug: string[];
107
+ }>;
108
+ }
109
+
110
+ const tools: Record<string, Tool> = {
111
+ prettier: {
112
+ actions: ifConfig(configPaths.prettier, (configPath) => ({
113
+ check: ['--check', '--config', configPath, projectDir],
114
+ fix: ['--write', '--config', configPath, projectDir],
115
+ })),
116
+ args: {
117
+ debug: ['--log-level', 'debug'],
118
+ },
119
+ },
120
+ tsc: {
121
+ actions: ifConfig(configPaths.typeScript, (configPath) => ({
122
+ check: ['--noEmit', '--project', configPath],
123
+ fix: ['--project', configPath],
124
+ })),
125
+ args: {},
126
+ },
127
+ eslint: {
128
+ actions: ifConfig(configPaths.eslint, (configPath) => ({
129
+ check: ['--config', configPath, projectDir],
130
+ fix: ['--fix', '--config', configPath, projectDir],
131
+ })),
132
+ args: {
133
+ debug: ['--debug'],
134
+ },
135
+ },
136
+ stylelint: {
137
+ actions: ifConfig(configPaths.stylelint, (configPath) => ({
138
+ check: ['--allow-empty-input', '--config', configPath, `${projectDir}/**/*.{css,sass,scss}`],
139
+ fix: ['--fix', '--allow-empty-input', '--config', configPath, `${projectDir}/**/*.{css,sass,scss}`],
140
+ })),
141
+ args: {
142
+ debug: ['--formatter', 'verbose'],
143
+ },
144
+ },
145
+ };
146
+
147
+ async function runTools(action: Action): Promise<void> {
148
+ for (const [toolName, { actions, args: additionalArgs }] of Object.entries(tools)) {
149
+ const actionArgs = actions?.[action];
150
+ if (actionArgs != null) {
151
+ let args = [...actionArgs];
152
+ if (options.debug) {
153
+ args = [...args, ...(additionalArgs.debug ?? [])];
154
+ }
155
+ // eslint-disable-next-line no-await-in-loop -- Must be run in series
156
+ await bunx(toolName, args);
157
+ }
158
+ }
159
+ }
160
+
161
+ try {
162
+ switch (selectedAction) {
163
+ case 'check': {
164
+ await runTools('check');
165
+ break;
166
+ }
167
+
168
+ case 'fix': {
169
+ await runTools('fix');
170
+ break;
171
+ }
172
+
173
+ default: {
174
+ output.usage(1);
175
+ }
176
+ }
177
+ } catch (error) {
178
+ output.error('Error:', [error]);
179
+ }
package/eslint.config.js CHANGED
@@ -1,3 +1,5 @@
1
+ import globals from 'globals';
2
+
1
3
  import makeEslintConfig from './src/makeEslintConfig.ts';
2
4
 
3
5
  export default [
@@ -5,4 +7,26 @@ export default [
5
7
  ignores: ['dist/**/*.*'],
6
8
  },
7
9
  ...makeEslintConfig({ tsconfigPath: './tsconfig.json' }),
10
+ {
11
+ languageOptions: {
12
+ globals: { Bun: true, ...globals.node },
13
+ },
14
+ },
15
+
16
+ // Rulesets
17
+ {
18
+ files: ['rulesets/**/*.ts'],
19
+ rules: {
20
+ 'sort-keys': 'error', // Organise rules.
21
+ 'unicorn/no-useless-spread': 'off', // Keep the unprefixed core rules together.
22
+ },
23
+ },
24
+
25
+ // CLI
26
+ {
27
+ files: ['bin-*.ts'],
28
+ rules: {
29
+ 'no-process-exit': 'off',
30
+ },
31
+ },
8
32
  ];
@@ -0,0 +1,7 @@
1
+ /* eslint require-unicode-regexp: 'off' -- Expressions are passed to Stylelint as strings so cannot use any flags in order to match their behaviour. */
2
+ export default {
3
+ bem: /^[a-z]+(?:(?:-|--|__)[a-z]+)*$/.source,
4
+ bemWithOptionalSingleUnderscorePrefix: /^_?[a-z]+(?:(?:-|--|__)[a-z]+)*$/.source,
5
+ bemWithOptionalUnderscoresPrefix: /^(?:__)?[a-z]+(?:(?:-|--|__)[a-z]+)*$/.source,
6
+ kebab: /^[a-z]+(?:-[a-z]+)*$/.source,
7
+ } satisfies Record<string, string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@averay/codeformat",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "author": "Adam Averay (https://adamaveray.com.au/)",
5
5
  "homepage": "https://github.com/adamaveray/codeformat",
6
6
  "repository": {
@@ -15,46 +15,48 @@
15
15
  },
16
16
  "source": "./src/index.ts",
17
17
  "bin": {
18
- "codeformat": "bin-codeformat.sh"
18
+ "codeformat": "bin-codeformat.ts"
19
19
  },
20
20
  "main": "./src/index.ts",
21
21
  "scripts": {
22
22
  "test": "bun test",
23
- "format": "./bin-codeformat.sh fix",
24
- "lint": "./bin-codeformat.sh check",
25
- "prepare": "bun --bun x husky"
23
+ "format": "./bin-codeformat.ts fix",
24
+ "lint": "./bin-codeformat.ts check",
25
+ "prepare": "bun --bun x husky",
26
+ "release": "bun run lint && bun --bun x bumpp && bun publish"
26
27
  },
27
28
  "dependencies": {
28
- "@averay/css-properties-sort-order": "^1.0.1",
29
- "@eslint/eslintrc": "3.2.0",
30
- "@eslint/js": "9.20.0",
31
- "@stylistic/eslint-plugin": "^3.1.0",
32
- "@typescript-eslint/eslint-plugin": "8.24.0",
33
- "@typescript-eslint/parser": "8.24.0",
34
- "eslint-config-prettier": "10.0.1",
35
- "eslint-import-resolver-typescript": "^3.8.3",
29
+ "@averay/css-properties-sort-order": "^1.0.3",
30
+ "@eslint/eslintrc": "^3.3.1",
31
+ "@eslint/js": "^9.29.0",
32
+ "@stylistic/eslint-plugin": "^4.4.1",
33
+ "@typescript-eslint/eslint-plugin": "^8.34.0",
34
+ "@typescript-eslint/parser": "^8.34.0",
35
+ "eslint-config-prettier": "^10.1.5",
36
+ "eslint-import-resolver-typescript": "^4.4.3",
36
37
  "eslint-plugin-eslint-comments": "^3.2.0",
37
38
  "eslint-plugin-import": "^2.31.0",
38
- "eslint-plugin-jsdoc": "50.6.3",
39
- "eslint-plugin-promise": "7.2.1",
40
- "eslint-plugin-regexp": "2.7.0",
41
- "eslint-plugin-sonarjs": "3.0.2",
42
- "eslint-plugin-unicorn": "56.0.1",
43
- "globals": "15.15.0",
39
+ "eslint-plugin-jsdoc": "^51.0.1",
40
+ "eslint-plugin-promise": "^7.2.1",
41
+ "eslint-plugin-regexp": "^2.9.0",
42
+ "eslint-plugin-sonarjs": "^3.0.2",
43
+ "eslint-plugin-unicorn": "^59.0.1",
44
+ "globals": "^16.2.0",
44
45
  "postcss-scss": "^4.0.9",
45
- "prettier": "3.5.1",
46
- "stylelint": "16.14.1",
47
- "stylelint-config-recommended": "15.0.0",
48
- "stylelint-config-recommended-scss": "14.1.0",
49
- "stylelint-config-standard": "37.0.0",
50
- "stylelint-config-standard-scss": "14.0.0",
51
- "stylelint-order": "^6.0.4",
52
- "stylelint-scss": "6.11.0",
53
- "typescript-eslint": "^8.25.0"
46
+ "prettier": "^3.5.3",
47
+ "stylelint": "^16.20.0",
48
+ "stylelint-config-recommended": "^16.0.0",
49
+ "stylelint-config-recommended-scss": "^15.0.1",
50
+ "stylelint-config-standard": "^38.0.0",
51
+ "stylelint-config-standard-scss": "^15.0.1",
52
+ "stylelint-order": "^7.0.0",
53
+ "stylelint-scss": "^6.12.1",
54
+ "typescript-eslint": "^8.34.0"
54
55
  },
55
56
  "devDependencies": {
56
57
  "@types/bun": "latest",
57
- "husky": "9.1.7",
58
- "typescript": "^5.8.2"
58
+ "bumpp": "^10.1.1",
59
+ "husky": "^9.1.7",
60
+ "typescript": "^5.8.3"
59
61
  }
60
62
  }
@@ -1,5 +1,3 @@
1
- /* eslint sort-keys: 'error' -- Organise rules. */
2
- /* eslint unicorn/no-useless-spread: "off" -- Keep the unprefixed core rules together. */
3
1
  /* eslint import/no-named-as-default-member: "off" -- All plugins follow the same naming conventions. */
4
2
 
5
3
  import stylisticPlugin from '@stylistic/eslint-plugin';
@@ -21,6 +19,7 @@ export default {
21
19
  'consistent-return': 'error',
22
20
  'consistent-this': ['error', '_this'],
23
21
  'constructor-super': 'error',
22
+ curly: ['error', 'all'],
24
23
  'default-case': 'error',
25
24
  'default-case-last': 'error',
26
25
  'default-param-last': 'error',
@@ -239,7 +238,7 @@ export default {
239
238
  ],
240
239
  'import/no-commonjs': 'error',
241
240
  'import/no-cycle': ['error', { ignoreExternal: true }],
242
- 'import/no-duplicates': ['error', { 'prefer-inline': true }],
241
+ 'import/no-duplicates': 'error',
243
242
  'import/no-dynamic-require': 'error',
244
243
  'import/no-empty-named-blocks': 'error',
245
244
  'import/no-extraneous-dependencies': 'error',
@@ -259,7 +258,7 @@ export default {
259
258
  'newlines-between': 'always',
260
259
  },
261
260
  ],
262
- 'import/prefer-default-export': 'error',
261
+ 'import/prefer-default-export': 'off', // Causes friction when creating a utilities file with a single export in advance of adding additional exports.
263
262
 
264
263
  ...jsdocPlugin.configs.recommended.rules,
265
264
  'jsdoc/check-indentation': 'error',
@@ -283,6 +282,7 @@ export default {
283
282
  'jsdoc/tag-lines': ['error', 'any', { startLines: 1 }],
284
283
 
285
284
  ...promisePlugin.configs.recommended.rules,
285
+ 'promise/always-return': ['error', { ignoreAssignmentVariable: ['globalThis', 'window'], ignoreLastCallback: true }],
286
286
  'promise/no-multiple-resolved': 'error',
287
287
 
288
288
  ...regexpPlugin.configs.recommended.rules,
@@ -305,11 +305,14 @@ export default {
305
305
 
306
306
  ...sonarjsPlugin.configs.recommended.rules,
307
307
  'sonarjs/cognitive-complexity': 'off',
308
+ 'sonarjs/function-return-type': 'off', // Overly restrictive.
308
309
  'sonarjs/max-switch-cases': 'off',
309
310
  'sonarjs/no-inverted-boolean-check': 'error',
310
311
  'sonarjs/no-nested-template-literals': 'off',
312
+ 'sonarjs/no-selector-parameter': 'off', // Overly restrictive.
311
313
  'sonarjs/no-small-switch': 'off',
312
314
  'sonarjs/prefer-immediate-return': 'off',
315
+ 'sonarjs/prefer-regexp-exec': 'off',
313
316
  'sonarjs/prefer-single-boolean-return': 'off',
314
317
 
315
318
  ...stylisticPlugin.configs['recommended-flat'].rules,
@@ -336,9 +339,10 @@ export default {
336
339
  '@stylistic/semi': 'off',
337
340
 
338
341
  ...unicornPlugin.configs.recommended.rules,
342
+ 'unicorn/consistent-function-scoping': ['error', { checkArrowFunctions: false }],
339
343
  'unicorn/filename-case': 'off',
340
344
  'unicorn/no-null': 'off',
341
- 'unicorn/no-unsafe-regex': 'error',
345
+ 'unicorn/no-useless-undefined': 'off', // Conflicts with `consistent-return`.
342
346
  'unicorn/prefer-event-target': 'error',
343
347
  'unicorn/prefer-query-selector': 'off',
344
348
  'unicorn/prevent-abbreviations': 'off',
@@ -1,6 +1,3 @@
1
- /* eslint unicorn/no-useless-spread: "off" -- Keep the unprefixed core rules together. */
2
- /* eslint sort-keys: "error" -- Organise rules */
3
-
4
1
  import js from '@eslint/js';
5
2
  import typescriptPlugin from '@typescript-eslint/eslint-plugin';
6
3
  import { type TSESLint } from '@typescript-eslint/utils';
@@ -32,6 +29,7 @@ export default {
32
29
  'no-magic-numbers': 'off',
33
30
  'no-redeclare': 'off',
34
31
  'no-restricted-imports': 'off',
32
+ 'no-restricted-syntax': ['error', 'WithStatement'],
35
33
  'no-return-await': 'off',
36
34
  'no-shadow': 'off',
37
35
  'no-throw-literal': 'off',
@@ -67,57 +65,61 @@ export default {
67
65
  /* eslint-disable sort-keys -- Logically ordered */
68
66
  {
69
67
  selector: 'default',
70
- format: ['camelCase'],
68
+ format: ['strictCamelCase'],
69
+ },
70
+ {
71
+ selector: 'import',
72
+ format: ['strictCamelCase', 'StrictPascalCase', 'UPPER_CASE'],
71
73
  },
72
74
  {
73
75
  selector: 'variable',
74
76
  modifiers: ['const'],
75
- format: ['camelCase', 'PascalCase', 'UPPER_CASE'],
77
+ format: ['strictCamelCase', 'StrictPascalCase', 'UPPER_CASE'],
76
78
  },
77
79
  {
78
80
  selector: 'variable',
79
81
  modifiers: ['const'],
80
82
  filter: { regex: /^_(static|\d+)?$/u.source, match: true },
81
- format: ['camelCase'],
83
+ format: ['strictCamelCase'],
82
84
  leadingUnderscore: 'allow',
83
85
  },
84
86
  {
85
87
  selector: 'parameter',
86
- format: ['camelCase'],
88
+ format: ['strictCamelCase'],
87
89
  leadingUnderscore: 'allow',
88
90
  },
89
91
  {
90
92
  selector: 'property',
91
- format: ['camelCase', 'UPPER_CASE'],
93
+ format: ['strictCamelCase', 'UPPER_CASE'],
92
94
  },
93
95
  {
94
96
  selector: 'classProperty',
95
97
  modifiers: ['static'],
96
- format: ['camelCase', 'PascalCase', 'UPPER_CASE'],
98
+ format: ['strictCamelCase', 'StrictPascalCase', 'UPPER_CASE'],
97
99
  },
98
100
  {
99
101
  selector: 'enumMember',
100
- format: ['camelCase', 'PascalCase', 'UPPER_CASE'],
102
+ format: ['strictCamelCase', 'StrictPascalCase', 'UPPER_CASE'],
101
103
  },
102
104
  {
103
105
  selector: 'function',
104
- format: ['camelCase', 'PascalCase'],
106
+ format: ['strictCamelCase', 'StrictPascalCase'],
105
107
  },
106
108
  {
107
109
  selector: 'typeLike',
108
- format: ['PascalCase'],
110
+ format: ['StrictPascalCase'],
109
111
  },
110
112
  {
111
113
  selector: ['objectLiteralProperty'],
112
- format: [],
114
+ format: null,
113
115
  },
114
116
  {
115
117
  selector: ['classProperty', 'objectLiteralMethod'],
116
- format: ['camelCase', 'UPPER_CASE'],
118
+ format: ['strictCamelCase', 'UPPER_CASE'],
117
119
  },
118
120
  {
119
121
  selector: 'typeParameter',
120
- format: ['PascalCase'],
122
+ format: ['StrictPascalCase'],
121
123
  custom: { regex: /^([A-Z]|T[A-Z][a-zA-Z]+|key)$/u.source, match: true },
122
124
  },
123
125
  /* eslint-enable sort-keys -- Logically ordered */
@@ -146,7 +148,7 @@ export default {
146
148
  {
147
149
  detectObjects: true,
148
150
  enforceConst: true,
149
- ignore: [-1, 0, 1],
151
+ ignore: [-1, 0, 1, 2, 100, '0n'],
150
152
  ignoreArrayIndexes: true,
151
153
  ignoreClassFieldInitialValues: true,
152
154
  ignoreDefaultValues: true,
@@ -211,14 +213,25 @@ export default {
211
213
  'error',
212
214
  { allowNullableObject: false, allowNullableString: false, allowNumber: false, allowString: false },
213
215
  ],
214
- '@typescript-eslint/switch-exhaustiveness-check': 'error',
216
+ '@typescript-eslint/switch-exhaustiveness-check': [
217
+ 'error',
218
+ {
219
+ allowDefaultCaseForExhaustiveSwitch: false,
220
+ considerDefaultExhaustiveForUnions: true,
221
+ requireDefaultForNonUnion: true,
222
+ },
223
+ ],
215
224
  '@typescript-eslint/triple-slash-reference': 'error',
216
225
  '@typescript-eslint/unbound-method': 'off', // Does not support @autobind nor recognise binding in constructors
217
226
  '@typescript-eslint/unified-signatures': 'off',
218
227
 
228
+ 'import/no-duplicates': 'off', // Breaks if importing both default and named exports as types (https://github.com/import-js/eslint-plugin-import/issues/2007).
229
+
219
230
  'jsdoc/no-types': 'error',
220
231
  'jsdoc/require-param-type': 'off',
221
232
  'jsdoc/require-returns-type': 'off',
233
+
234
+ 'sonarjs/super-invocation': 'off', // Causes issues and is enforced by TypeScript already.
222
235
  } satisfies TSESLint.FlatConfig.Rules;
223
236
 
224
237
  export const moduleDeclarations = {
@@ -1,22 +1,38 @@
1
- /* eslint sort-keys: "error" -- Organise rules. */
2
- /* eslint unicorn/no-useless-spread: "off" -- Keep the unprefixed core rules together. */
3
-
4
1
  import propertiesOrder from '@averay/css-properties-sort-order';
5
2
  import recommended from 'stylelint-config-recommended';
6
3
  import standard from 'stylelint-config-standard';
7
4
 
5
+ import patterns from '../../lib/cssPatterns.ts';
6
+
8
7
  export default {
9
8
  ...recommended.rules,
10
9
  ...standard.rules,
11
10
 
11
+ // Core rules
12
12
  ...{
13
- 'at-rule-empty-line-before': null,
14
13
  'color-named': 'never',
15
- 'comment-empty-line-before': null,
16
14
  'function-url-no-scheme-relative': true,
15
+ },
16
+
17
+ // Disable Prettier conflicts
18
+ ...{
19
+ 'at-rule-empty-line-before': null,
20
+ 'comment-empty-line-before': null,
17
21
  'rule-empty-line-before': null,
18
22
  },
19
23
 
24
+ // Patterns
25
+ ...{
26
+ 'container-name-pattern': patterns.bem,
27
+ 'custom-media-pattern': patterns.bem,
28
+ 'custom-property-pattern': patterns.bem,
29
+ 'keyframes-name-pattern': patterns.bem,
30
+ 'layer-name-pattern': patterns.bem,
31
+ 'selector-class-pattern': patterns.bemWithOptionalUnderscoresPrefix,
32
+ 'selector-id-pattern': patterns.kebab,
33
+ },
34
+
35
+ // Extensions
20
36
  'order/order': ['custom-properties', 'declarations'],
21
37
  'order/properties-order': [propertiesOrder, { unspecified: 'bottomAlphabetical' }],
22
38
  };
@@ -1,16 +1,13 @@
1
- /* eslint sort-keys: "error" -- Organise rules. */
2
- /* eslint unicorn/no-useless-spread: "off" -- Keep the unprefixed core rules together. */
3
-
4
1
  import recommended from 'stylelint-config-recommended-scss';
5
2
  import standard from 'stylelint-config-standard-scss';
6
3
 
7
- const CUSTOM_KEYWORD_PATTERN = /^_?[a-z][\da-z]*((-|--|__)[\da-z]+)*$/u;
4
+ import patterns from '../../lib/cssPatterns.ts';
8
5
 
9
6
  export default {
10
7
  ...recommended.rules,
11
8
  ...standard.rules,
12
9
 
13
- // Disable Prettier-conflicting legacy rules.
10
+ // Disable Prettier conflicts
14
11
  ...{
15
12
  'scss/at-else-closing-brace-newline-after': null,
16
13
  'scss/at-else-closing-brace-space-after': null,
@@ -27,7 +24,9 @@ export default {
27
24
  'scss/operator-no-newline-before': null,
28
25
  'scss/operator-no-unspaced': null,
29
26
  },
30
- 'scss/at-function-pattern': CUSTOM_KEYWORD_PATTERN.source,
31
- 'scss/at-mixin-pattern': CUSTOM_KEYWORD_PATTERN.source,
32
- 'scss/dollar-variable-pattern': CUSTOM_KEYWORD_PATTERN.source,
27
+
28
+ 'scss/at-function-pattern': patterns.bemWithOptionalSingleUnderscorePrefix,
29
+ 'scss/at-mixin-pattern': patterns.bemWithOptionalSingleUnderscorePrefix,
30
+ 'scss/dollar-variable-pattern': patterns.bemWithOptionalSingleUnderscorePrefix,
31
+ 'scss/percent-placeholder-pattern': patterns.bemWithOptionalSingleUnderscorePrefix,
33
32
  };
@@ -1,7 +1,7 @@
1
1
  /* eslint sort-keys: "error" -- Organise rules */
2
2
 
3
3
  import postcssScss from 'postcss-scss';
4
- import { type Config } from 'stylelint';
4
+ import { type Config, type CustomSyntax } from 'stylelint';
5
5
  import orderPlugin from 'stylelint-order';
6
6
  import scssPlugin from 'stylelint-scss';
7
7
 
@@ -10,11 +10,13 @@ import rulesetStylelintScss from '../rulesets/stylelint/ruleset-scss.ts';
10
10
 
11
11
  import extensions from './extensions.ts';
12
12
 
13
+ type ConfigRules = Config['rules'];
14
+
13
15
  /**
14
16
  * @returns The complete Stylelint config.
15
17
  */
16
18
  // eslint-disable-next-line @typescript-eslint/explicit-function-return-type,@typescript-eslint/explicit-module-boundary-types -- Preserve specific object shape.
17
- export default function makeStylelintConfig() {
19
+ export default function makeStylelintConfig(cssRules: ConfigRules = {}, scssRules: ConfigRules = {}) {
18
20
  return {
19
21
  defaultSeverity: 'error',
20
22
  ignoreFiles: ['**/*.min.*'],
@@ -22,15 +24,21 @@ export default function makeStylelintConfig() {
22
24
  reportDescriptionlessDisables: true,
23
25
  reportInvalidScopeDisables: true,
24
26
  reportNeedlessDisables: true,
25
- rules: rulesetStylelintCss,
27
+ rules: {
28
+ ...rulesetStylelintCss,
29
+ ...cssRules,
30
+ },
26
31
 
27
32
  // eslint-disable-next-line sort-keys -- Logically positioned.
28
33
  overrides: [
29
34
  {
30
- customSyntax: postcssScss,
35
+ customSyntax: postcssScss as unknown as CustomSyntax,
31
36
  files: extensions.scss.map((ext) => `**/*.${ext}`), // Does not support glob braces
32
37
  plugins: [scssPlugin],
33
- rules: rulesetStylelintScss,
38
+ rules: {
39
+ ...rulesetStylelintScss,
40
+ ...scssRules,
41
+ },
34
42
  },
35
43
  ],
36
44
  } satisfies Config;
@@ -1,7 +1,7 @@
1
1
  /* eslint import/no-commonjs: "off" -- Unsupported by Stylelint */
2
2
 
3
- const { makeStylelintConfig } = require('./src/index.ts');
3
+ import { makeStylelintConfig } from './src/index.ts';
4
4
 
5
- module.exports = {
5
+ export default {
6
6
  ...makeStylelintConfig(),
7
7
  };
package/bin-codeformat.sh DELETED
@@ -1,35 +0,0 @@
1
- #!/bin/sh
2
-
3
- set -e
4
-
5
- bunx() {
6
- bun --bun x "$@"
7
- }
8
-
9
- # Parse arguments
10
- ACTION="$1"
11
- DIR="$2"
12
- if [ "$DIR" = '' ]; then
13
- DIR='.'
14
- fi
15
-
16
- CONFIG_PATH_TYPESCRIPT="$DIR/tsconfig.json"
17
-
18
- if [ "$ACTION" = 'check' ]; then
19
- bunx prettier --check "$DIR"
20
- bunx eslint "$DIR"
21
- if [ -f "$CONFIG_PATH_TYPESCRIPT" ]; then
22
- bunx tsc --noEmit --project "$DIR/tsconfig.json"
23
- fi
24
- bunx stylelint --allow-empty-input "$DIR/**/*.{css,sass,scss}"
25
- elif [ "$ACTION" = 'fix' ]; then
26
- bunx prettier --write "$DIR"
27
- bunx eslint --fix "$DIR"
28
- bunx stylelint --fix --allow-empty-input "$DIR/**/*.{css,sass,scss}"
29
- else
30
- # Invalid/unset arguments
31
- echo "Usage:
32
- $0 check [root-path]
33
- $0 fix [root-path]" >&2
34
- exit 1
35
- fi
File without changes