code-standards 7.0.0__py3-none-any.whl
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.
- code_standards-7.0.0.dist-info/METADATA +53 -0
- code_standards-7.0.0.dist-info/RECORD +99 -0
- code_standards-7.0.0.dist-info/WHEEL +4 -0
- code_standards-7.0.0.dist-info/entry_points.txt +3 -0
- code_standards-7.0.0.dist-info/licenses/LICENSE +21 -0
- sarj_standards/__init__.py +30 -0
- sarj_standards/__main__.py +5 -0
- sarj_standards/_meta.py +22 -0
- sarj_standards/api.py +890 -0
- sarj_standards/cli/__init__.py +0 -0
- sarj_standards/cli/main.py +2466 -0
- sarj_standards/configs/cli-reference.v1.json +1 -0
- sarj_standards/configs/doctor.config.json +22 -0
- sarj_standards/configs/eslint.application.mjs +1366 -0
- sarj_standards/configs/eslint.peers.json +44 -0
- sarj_standards/configs/eslint.strict.mjs +1060 -0
- sarj_standards/configs/markdownlint.strict.yaml +12 -0
- sarj_standards/configs/pyright.strict.json +96 -0
- sarj_standards/configs/ruff.application.toml +363 -0
- sarj_standards/configs/ruff.strict.toml +338 -0
- sarj_standards/configs/rule-inventory.v1.json +1 -0
- sarj_standards/configs/rule-ledger.json +846 -0
- sarj_standards/configs/rule-warning-levels.v1.json +1 -0
- sarj_standards/configs/taplo.strict.toml +14 -0
- sarj_standards/configs/yamllint.strict.yaml +25 -0
- sarj_standards/libs/__init__.py +0 -0
- sarj_standards/libs/adoption/__init__.py +0 -0
- sarj_standards/libs/adoption/configs.py +36 -0
- sarj_standards/libs/adoption/doctor.py +1346 -0
- sarj_standards/libs/adoption/exclusions.py +66 -0
- sarj_standards/libs/adoption/hooks.py +423 -0
- sarj_standards/libs/adoption/launcher.py +240 -0
- sarj_standards/libs/adoption/lifecycle.py +493 -0
- sarj_standards/libs/adoption/manifest.py +550 -0
- sarj_standards/libs/adoption/packagemanager.py +285 -0
- sarj_standards/libs/adoption/retired_suppressions.py +371 -0
- sarj_standards/libs/adoption/scaffold.py +1660 -0
- sarj_standards/libs/adoption/service.py +441 -0
- sarj_standards/libs/adoption/transaction.py +274 -0
- sarj_standards/libs/adoption/upgrade.py +516 -0
- sarj_standards/libs/adoption/uvtool.py +62 -0
- sarj_standards/libs/catalogs/__init__.py +9 -0
- sarj_standards/libs/catalogs/slack_automations.py +627 -0
- sarj_standards/libs/corpus/__init__.py +25 -0
- sarj_standards/libs/corpus/manifest.py +211 -0
- sarj_standards/libs/corpus/snapshot.py +222 -0
- sarj_standards/libs/diagnostics/__init__.py +65 -0
- sarj_standards/libs/diagnostics/analysis.schema.json +161 -0
- sarj_standards/libs/diagnostics/baseline.py +131 -0
- sarj_standards/libs/diagnostics/models.py +574 -0
- sarj_standards/libs/diagnostics/serialize.py +290 -0
- sarj_standards/libs/diagnostics/source.py +172 -0
- sarj_standards/libs/filesystem.py +11 -0
- sarj_standards/libs/linting/__init__.py +0 -0
- sarj_standards/libs/linting/analysis.py +422 -0
- sarj_standards/libs/linting/external.py +1454 -0
- sarj_standards/libs/linting/library_policy.py +688 -0
- sarj_standards/libs/linting/policy.py +152 -0
- sarj_standards/libs/linting/runner.py +442 -0
- sarj_standards/libs/linting/textlint.py +1605 -0
- sarj_standards/libs/release/__init__.py +98 -0
- sarj_standards/libs/release/_values.py +24 -0
- sarj_standards/libs/release/artifacts.py +191 -0
- sarj_standards/libs/release/causality.py +80 -0
- sarj_standards/libs/release/changes.py +48 -0
- sarj_standards/libs/release/process.py +128 -0
- sarj_standards/libs/release/publish.py +85 -0
- sarj_standards/libs/release/registry.py +271 -0
- sarj_standards/libs/release/release_age.py +218 -0
- sarj_standards/libs/release/rollout.py +1163 -0
- sarj_standards/libs/release/tags.py +373 -0
- sarj_standards/libs/release/typescript.py +191 -0
- sarj_standards/libs/repository/__init__.py +0 -0
- sarj_standards/libs/repository/cli_reference_artifact.py +324 -0
- sarj_standards/libs/repository/comment_corpus.py +536 -0
- sarj_standards/libs/repository/config_generation.py +146 -0
- sarj_standards/libs/repository/docs.py +347 -0
- sarj_standards/libs/repository/hooks.py +118 -0
- sarj_standards/libs/repository/ledger.py +99 -0
- sarj_standards/libs/repository/repository.py +744 -0
- sarj_standards/libs/repository/rule_authoring.py +246 -0
- sarj_standards/libs/repository/rule_catalog_artifact.py +479 -0
- sarj_standards/libs/repository/rule_changes.py +318 -0
- sarj_standards/libs/repository/rule_inventory_artifact.py +142 -0
- sarj_standards/libs/repository/rule_lifecycle.py +167 -0
- sarj_standards/libs/repository/rule_maintenance.py +225 -0
- sarj_standards/libs/rules/__init__.py +74 -0
- sarj_standards/libs/rules/catalog.py +145 -0
- sarj_standards/libs/rules/contracts.py +382 -0
- sarj_standards/libs/rules/corpus_runner.py +365 -0
- sarj_standards/libs/rules/evaluation.py +177 -0
- sarj_standards/libs/setup/__init__.py +4 -0
- sarj_standards/libs/setup/repository.py +40 -0
- sarj_standards/py.typed +0 -0
- sarj_standards/schemas/__init__.py +4 -0
- sarj_standards/schemas/_paths.py +7 -0
- sarj_standards/schemas/rule-catalog.v1.json +1 -0
- sarj_standards/schemas/rule-catalog.v1.schema.json +112 -0
- sarj_standards/schemas/slack-automations.v1.schema.json +1751 -0
|
@@ -0,0 +1,1060 @@
|
|
|
1
|
+
import { readdirSync } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
import tseslint from "typescript-eslint";
|
|
6
|
+
import react from "eslint-plugin-react";
|
|
7
|
+
import { fixupPluginRules } from "@eslint/compat";
|
|
8
|
+
import reactHooks from "eslint-plugin-react-hooks";
|
|
9
|
+
import unicorn from "eslint-plugin-unicorn";
|
|
10
|
+
import eslintComments from "@eslint-community/eslint-plugin-eslint-comments";
|
|
11
|
+
import perfectionist from "eslint-plugin-perfectionist";
|
|
12
|
+
import promise from "eslint-plugin-promise";
|
|
13
|
+
import simpleImportSort from "eslint-plugin-simple-import-sort";
|
|
14
|
+
import betterTailwindcss from "eslint-plugin-better-tailwindcss";
|
|
15
|
+
import sarj from "@sarj/eslint-plugin";
|
|
16
|
+
import zod from "eslint-plugin-zod";
|
|
17
|
+
|
|
18
|
+
const CONFIG_DIRECTORY = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const TYPE_PROJECT_FILES = new Set(["tsconfig.json", "jsconfig.json"]);
|
|
20
|
+
const TYPE_PROJECT_SEARCH_DEPTH = 8;
|
|
21
|
+
const TYPE_PROJECT_SEARCH_LIMIT = 2_000;
|
|
22
|
+
const TYPE_PROJECT_SKIPPED_DIRECTORIES = new Set([
|
|
23
|
+
".git",
|
|
24
|
+
".next",
|
|
25
|
+
".turbo",
|
|
26
|
+
"build",
|
|
27
|
+
"coverage",
|
|
28
|
+
"dist",
|
|
29
|
+
"lib",
|
|
30
|
+
"node_modules",
|
|
31
|
+
"vendor",
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Find a root or nested workspace type project without crawling dependencies.
|
|
36
|
+
*
|
|
37
|
+
* A direct `existsSync(root/tsconfig.json)` check silently disabled every typed
|
|
38
|
+
* rule in monorepos whose configs live under `apps/*` or `packages/*`. Keep the
|
|
39
|
+
* search bounded so config loading has deterministic cost even in huge repos.
|
|
40
|
+
*/
|
|
41
|
+
const hasTypeProject = (root) => {
|
|
42
|
+
const pending = [[root, 0]];
|
|
43
|
+
let inspected = 0;
|
|
44
|
+
|
|
45
|
+
while (pending.length > 0 && inspected < TYPE_PROJECT_SEARCH_LIMIT) {
|
|
46
|
+
const [directory, depth] = pending.shift();
|
|
47
|
+
inspected += 1;
|
|
48
|
+
let entries;
|
|
49
|
+
try {
|
|
50
|
+
entries = readdirSync(directory, { withFileTypes: true });
|
|
51
|
+
} catch {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (entries.some((entry) => entry.isFile() && TYPE_PROJECT_FILES.has(entry.name))) return true;
|
|
55
|
+
if (depth >= TYPE_PROJECT_SEARCH_DEPTH) continue;
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
if (
|
|
58
|
+
entry.isDirectory() &&
|
|
59
|
+
!entry.name.startsWith(".") &&
|
|
60
|
+
!TYPE_PROJECT_SKIPPED_DIRECTORIES.has(entry.name)
|
|
61
|
+
) {
|
|
62
|
+
pending.push([join(directory, entry.name), depth + 1]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const normalizeRoot = (root) => {
|
|
70
|
+
const value = root instanceof URL ? fileURLToPath(root) : root;
|
|
71
|
+
return isAbsolute(value) ? value : resolve(value);
|
|
72
|
+
};
|
|
73
|
+
const UNTYPED_RULE_OVERRIDES = Object.fromEntries(
|
|
74
|
+
Object.entries(tseslint.plugin.rules)
|
|
75
|
+
.filter(([, rule]) => rule.meta?.docs?.requiresTypeChecking === true)
|
|
76
|
+
.map(([name]) => [`@typescript-eslint/${name}`, "off"]),
|
|
77
|
+
);
|
|
78
|
+
const DEFAULT_SYNTAX_ONLY_CONFIG_FILES = [
|
|
79
|
+
"**/vite.config.ts",
|
|
80
|
+
"**/.dependency-cruiser.{js,cjs,mjs,ts,cts,mts}",
|
|
81
|
+
"**/eslint.config*.{js,cjs,mjs,ts,cts,mts}",
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
// Unicorn ships a broad rule set. The enabled subset below was selected by
|
|
85
|
+
// evaluating each non-deprecated rule for correctness, runtime compatibility,
|
|
86
|
+
// overlap with existing authorities, and whether its fix preserves semantics.
|
|
87
|
+
// Correctness rules also act as ratchets against newly introduced defects.
|
|
88
|
+
//
|
|
89
|
+
// Rules are declared as two objects rather than inline so the version guard
|
|
90
|
+
// below can check them, and so the two intents stay separable.
|
|
91
|
+
//
|
|
92
|
+
// REQUIRES eslint-plugin-unicorn >= 72 (and therefore eslint >= 10.4). 121 of
|
|
93
|
+
// these rules do not exist in unicorn 64 and 96 do not exist in 65; on an older
|
|
94
|
+
// plugin ESLint would emit "Definition for rule ... was not found" once per rule
|
|
95
|
+
// per file. The guard below turns that into one actionable line.
|
|
96
|
+
|
|
97
|
+
// Correctness. These catch BUGS, not style: useless/unnecessary constructs that
|
|
98
|
+
// are almost always a symptom of a wrong edit (`no-unnecessary-await`,
|
|
99
|
+
// `no-useless-fallback-in-spread`), calls that silently do nothing
|
|
100
|
+
// (`no-single-promise-in-promise-methods`, `no-invalid-fetch-options`), and
|
|
101
|
+
// footguns with real production failure modes (`no-array-fill-with-reference-type`,
|
|
102
|
+
// `no-unsafe-string-replacement`).
|
|
103
|
+
//
|
|
104
|
+
// DOM-family entries are included UNSCOPED on purpose. They are receiver-matched
|
|
105
|
+
// — they only fire on `document.*`, an `Element`, or an event API — so they are
|
|
106
|
+
// inert in a Node package and cost nothing there. Scoping them to `**/*.tsx`
|
|
107
|
+
// would have been worse than useless: measured, DOM code lives in plain `.ts`
|
|
108
|
+
// hooks and utility modules too, so a `.tsx` glob silently drops the findings.
|
|
109
|
+
// The DOM *modernisation* rules (prefer-query-selector, prefer-dom-node-append,
|
|
110
|
+
// dom-node-dataset, …) are rejected instead — see the rejection list below.
|
|
111
|
+
const UNICORN_CORRECTNESS_RULES = {
|
|
112
|
+
"unicorn/class-reference-in-static-methods": "error",
|
|
113
|
+
"unicorn/consistent-assert": "error",
|
|
114
|
+
"unicorn/consistent-date-clone": "error",
|
|
115
|
+
"unicorn/consistent-empty-array-spread": "error",
|
|
116
|
+
"unicorn/consistent-json-file-read": "error",
|
|
117
|
+
"unicorn/error-message": "error",
|
|
118
|
+
"unicorn/explicit-timer-delay": "error",
|
|
119
|
+
"unicorn/new-for-builtins": "error",
|
|
120
|
+
"unicorn/no-accessor-recursion": "error",
|
|
121
|
+
"unicorn/no-accidental-bitwise-operator": "error",
|
|
122
|
+
"unicorn/no-array-concat-in-loop": "error",
|
|
123
|
+
"unicorn/no-array-fill-with-reference-type": "error",
|
|
124
|
+
"unicorn/no-array-from-fill": "error",
|
|
125
|
+
"unicorn/no-array-method-this-argument": "error",
|
|
126
|
+
"unicorn/no-array-sort-for-min-max": "error",
|
|
127
|
+
"unicorn/no-async-promise-finally": "error",
|
|
128
|
+
"unicorn/no-await-in-promise-methods": "error",
|
|
129
|
+
"unicorn/no-blob-to-file": "error",
|
|
130
|
+
"unicorn/no-boolean-sort-comparator": "error",
|
|
131
|
+
"unicorn/no-canvas-to-image": "error",
|
|
132
|
+
"unicorn/no-chained-comparison": "error",
|
|
133
|
+
"unicorn/no-collection-bracket-access": "error",
|
|
134
|
+
"unicorn/no-confusing-array-splice": "error",
|
|
135
|
+
"unicorn/no-confusing-array-with": "error",
|
|
136
|
+
"unicorn/no-constant-zero-expression": "error",
|
|
137
|
+
"unicorn/no-document-cookie": "error",
|
|
138
|
+
"unicorn/no-double-comparison": "error",
|
|
139
|
+
"unicorn/no-duplicate-if-branches": "error",
|
|
140
|
+
"unicorn/no-duplicate-logical-operands": "error",
|
|
141
|
+
"unicorn/no-duplicate-loops": "error",
|
|
142
|
+
"unicorn/no-duplicate-set-values": "error",
|
|
143
|
+
"unicorn/no-empty-file": "error",
|
|
144
|
+
"unicorn/no-error-property-assignment": "error",
|
|
145
|
+
"unicorn/no-exports-in-scripts": "error",
|
|
146
|
+
"unicorn/no-global-object-property-assignment": "error",
|
|
147
|
+
"unicorn/no-immediate-mutation": "error",
|
|
148
|
+
"unicorn/no-impossible-length-comparison": "error",
|
|
149
|
+
"unicorn/no-incorrect-query-selector": "error",
|
|
150
|
+
"unicorn/no-incorrect-template-string-interpolation": "error",
|
|
151
|
+
"unicorn/no-instanceof-builtins": "error",
|
|
152
|
+
"unicorn/no-invalid-argument-count": "error",
|
|
153
|
+
"unicorn/no-invalid-character-comparison": "error",
|
|
154
|
+
"unicorn/no-invalid-fetch-options": "error",
|
|
155
|
+
"unicorn/no-invalid-file-input-accept": "error",
|
|
156
|
+
"unicorn/no-invalid-remove-event-listener": "error",
|
|
157
|
+
"unicorn/no-invalid-well-known-symbol-methods": "error",
|
|
158
|
+
"unicorn/no-late-current-target-access": "error",
|
|
159
|
+
"unicorn/no-late-event-control": "error",
|
|
160
|
+
"unicorn/no-loop-iterable-mutation": "error",
|
|
161
|
+
"unicorn/no-magic-array-flat-depth": "error",
|
|
162
|
+
"unicorn/no-mismatched-map-key": "error",
|
|
163
|
+
"unicorn/no-misrefactored-assignment": "error",
|
|
164
|
+
"unicorn/no-missing-local-resource": "error",
|
|
165
|
+
"unicorn/no-multiple-promise-resolver-calls": "error",
|
|
166
|
+
"unicorn/no-negation-in-equality-check": "error",
|
|
167
|
+
"unicorn/no-new-array": "error",
|
|
168
|
+
"unicorn/no-new-buffer": "error",
|
|
169
|
+
"unicorn/no-nonstandard-builtin-properties": "error",
|
|
170
|
+
"unicorn/no-object-methods-with-collections": "error",
|
|
171
|
+
"unicorn/no-optional-chaining-on-undeclared-variable": "error",
|
|
172
|
+
"unicorn/no-redundant-comparison": "error",
|
|
173
|
+
"unicorn/no-return-array-push": "error",
|
|
174
|
+
"unicorn/no-selector-as-dom-name": "error",
|
|
175
|
+
"unicorn/no-shorthand-property-overrides": "error",
|
|
176
|
+
"unicorn/no-single-promise-in-promise-methods": "error",
|
|
177
|
+
"unicorn/no-subtraction-comparison": "error",
|
|
178
|
+
// JSON Schema requires a `then` property. The syntax-only rule rejects that
|
|
179
|
+
// standards-compliant data shape even when the key is computed.
|
|
180
|
+
"unicorn/no-thenable": "off",
|
|
181
|
+
"unicorn/no-this-assignment": "error",
|
|
182
|
+
"unicorn/no-this-outside-of-class": "error",
|
|
183
|
+
"unicorn/no-typeof-undefined": "error",
|
|
184
|
+
"unicorn/no-uncalled-method": "error",
|
|
185
|
+
"unicorn/no-undeclared-class-members": "error",
|
|
186
|
+
"unicorn/no-unnecessary-array-flat-depth": "error",
|
|
187
|
+
"unicorn/no-unnecessary-array-flat-map": "error",
|
|
188
|
+
"unicorn/no-unnecessary-array-splice-count": "error",
|
|
189
|
+
"unicorn/no-unnecessary-await": "error",
|
|
190
|
+
"unicorn/no-unnecessary-boolean-comparison": "error",
|
|
191
|
+
"unicorn/no-unnecessary-fetch-options": "error",
|
|
192
|
+
"unicorn/no-unnecessary-global-this": "error",
|
|
193
|
+
"unicorn/no-unnecessary-nested-ternary": "error",
|
|
194
|
+
"unicorn/no-unnecessary-polyfills": "error",
|
|
195
|
+
"unicorn/no-unnecessary-slice-end": "error",
|
|
196
|
+
"unicorn/no-unnecessary-splice": "error",
|
|
197
|
+
"unicorn/no-unnecessary-string-trim": "error",
|
|
198
|
+
"unicorn/no-unsafe-buffer-conversion": "error",
|
|
199
|
+
"unicorn/no-unsafe-dom-html": "error",
|
|
200
|
+
"unicorn/no-unsafe-promise-all-settled-values": "error",
|
|
201
|
+
"unicorn/no-unsafe-property-key": "error",
|
|
202
|
+
"unicorn/no-unsafe-string-replacement": "error",
|
|
203
|
+
"unicorn/no-unused-array-method-return": "error",
|
|
204
|
+
"unicorn/no-useless-boolean-cast": "error",
|
|
205
|
+
"unicorn/no-useless-collection-argument": "error",
|
|
206
|
+
"unicorn/no-useless-compound-assignment": "error",
|
|
207
|
+
"unicorn/no-useless-concat": "error",
|
|
208
|
+
"unicorn/no-useless-continue": "error",
|
|
209
|
+
"unicorn/no-useless-delete-check": "error",
|
|
210
|
+
"unicorn/no-useless-else": "error",
|
|
211
|
+
"unicorn/no-useless-error-capture-stack-trace": "error",
|
|
212
|
+
"unicorn/no-useless-fallback-in-spread": "error",
|
|
213
|
+
"unicorn/no-useless-iterator-to-array": "error",
|
|
214
|
+
"unicorn/no-useless-length-check": "error",
|
|
215
|
+
"unicorn/no-useless-logical-operand": "error",
|
|
216
|
+
"unicorn/no-useless-override": "error",
|
|
217
|
+
"unicorn/no-useless-promise-resolve-reject": "error",
|
|
218
|
+
"unicorn/no-useless-re-export": "error",
|
|
219
|
+
"unicorn/no-useless-recursion": "error",
|
|
220
|
+
"unicorn/no-useless-spread": "error",
|
|
221
|
+
// Explicit union cases are required by switch-exhaustiveness-check even when
|
|
222
|
+
// they share the default branch behavior.
|
|
223
|
+
"unicorn/no-useless-switch-case": "off",
|
|
224
|
+
"unicorn/no-xor-as-exponentiation": "error",
|
|
225
|
+
"unicorn/prefer-add-event-listener": "error",
|
|
226
|
+
"unicorn/prefer-add-event-listener-options": "error",
|
|
227
|
+
"unicorn/prefer-keyboard-event-key": "error",
|
|
228
|
+
"unicorn/require-array-join-separator": "error",
|
|
229
|
+
"unicorn/require-css-escape": "error",
|
|
230
|
+
"unicorn/require-module-attributes": "error",
|
|
231
|
+
"unicorn/require-module-specifiers": "error",
|
|
232
|
+
"unicorn/require-number-to-fixed-digits-argument": "error",
|
|
233
|
+
"unicorn/require-passive-events": "error",
|
|
234
|
+
"unicorn/require-post-message-target-origin": "error",
|
|
235
|
+
"unicorn/require-proxy-trap-boolean-return": "error",
|
|
236
|
+
"unicorn/text-encoding-identifier-case": "error",
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// Modernisation. Enabled only where the fix is mechanical (autofix or a
|
|
240
|
+
// one-line suggestion), semantics-preserving, and reaches a platform API that
|
|
241
|
+
// exists on the declared floor (Node 22 / `lib: ES2025`). For example,
|
|
242
|
+
// `prefer-error-is-error` remains disabled because `Error.isError` is absent
|
|
243
|
+
// on Node 22 and its autofix would turn valid checks into runtime TypeErrors.
|
|
244
|
+
//
|
|
245
|
+
// Two of these need `lib` >= ES2025 in the CONSUMER to typecheck after the fix
|
|
246
|
+
// (`prefer-iterator-to-array` -> `Iterator#toArray`, `prefer-set-methods` ->
|
|
247
|
+
// `Set#union` and friends). Both are ES2025 library types available in TS today;
|
|
248
|
+
// a consumer still on `lib: ES2024` bumps `lib`, it does not bump its runtime.
|
|
249
|
+
const UNICORN_MODERNISATION_RULES = {
|
|
250
|
+
"unicorn/no-array-reverse": "error",
|
|
251
|
+
"unicorn/no-array-sort": "error",
|
|
252
|
+
"unicorn/no-for-loop": "error",
|
|
253
|
+
"unicorn/prefer-abort-signal-any": "error",
|
|
254
|
+
"unicorn/prefer-abort-signal-timeout": "error",
|
|
255
|
+
"unicorn/prefer-aggregate-error": "error",
|
|
256
|
+
"unicorn/prefer-array-flat": "error",
|
|
257
|
+
"unicorn/prefer-array-flat-map": "error",
|
|
258
|
+
"unicorn/prefer-array-from-async": "error",
|
|
259
|
+
"unicorn/prefer-array-from-map": "error",
|
|
260
|
+
"unicorn/prefer-array-from-range": "error",
|
|
261
|
+
"unicorn/prefer-array-index-of": "error",
|
|
262
|
+
"unicorn/prefer-array-iterable-methods": "error",
|
|
263
|
+
"unicorn/prefer-array-last-methods": "error",
|
|
264
|
+
"unicorn/prefer-array-slice": "error",
|
|
265
|
+
"unicorn/prefer-array-some": "error",
|
|
266
|
+
"unicorn/prefer-at": "error",
|
|
267
|
+
"unicorn/prefer-bigint-literals": "error",
|
|
268
|
+
"unicorn/prefer-blob-reading-methods": "error",
|
|
269
|
+
"unicorn/prefer-class-fields": "error",
|
|
270
|
+
"unicorn/prefer-code-point": "error",
|
|
271
|
+
"unicorn/prefer-date-now": "error",
|
|
272
|
+
"unicorn/prefer-default-parameters": "error",
|
|
273
|
+
"unicorn/prefer-direct-iteration": "error",
|
|
274
|
+
"unicorn/prefer-event-target": "error",
|
|
275
|
+
"unicorn/prefer-export-from": "error",
|
|
276
|
+
"unicorn/prefer-flat-math-min-max": "error",
|
|
277
|
+
"unicorn/prefer-global-number-constants": "error",
|
|
278
|
+
"unicorn/prefer-group-by": "error",
|
|
279
|
+
"unicorn/prefer-has-check": "error",
|
|
280
|
+
"unicorn/prefer-https": "error",
|
|
281
|
+
"unicorn/prefer-identifier-import-export-specifiers": "error",
|
|
282
|
+
"unicorn/prefer-import-meta-properties": "error",
|
|
283
|
+
"unicorn/prefer-iterable-in-constructor": "error",
|
|
284
|
+
"unicorn/prefer-iterator-concat": "error",
|
|
285
|
+
"unicorn/prefer-iterator-to-array": "error",
|
|
286
|
+
"unicorn/prefer-map-from-entries": "error",
|
|
287
|
+
"unicorn/prefer-math-abs": "error",
|
|
288
|
+
"unicorn/prefer-math-constants": "error",
|
|
289
|
+
"unicorn/prefer-math-min-max": "error",
|
|
290
|
+
"unicorn/prefer-math-trunc": "error",
|
|
291
|
+
"unicorn/prefer-modern-math-apis": "error",
|
|
292
|
+
"unicorn/prefer-module": "error",
|
|
293
|
+
"unicorn/prefer-native-coercion-functions": "error",
|
|
294
|
+
"unicorn/prefer-negative-index": "error",
|
|
295
|
+
"unicorn/prefer-number-is-safe-integer": "error",
|
|
296
|
+
"unicorn/prefer-number-properties": "error",
|
|
297
|
+
"unicorn/prefer-object-define-properties": "error",
|
|
298
|
+
"unicorn/prefer-object-destructuring-defaults": "error",
|
|
299
|
+
"unicorn/prefer-object-from-entries": "error",
|
|
300
|
+
"unicorn/prefer-object-iterable-methods": "error",
|
|
301
|
+
"unicorn/prefer-optional-catch-binding": "error",
|
|
302
|
+
"unicorn/prefer-promise-with-resolvers": "error",
|
|
303
|
+
"unicorn/prefer-queue-microtask": "error",
|
|
304
|
+
"unicorn/prefer-regexp-test": "error",
|
|
305
|
+
"unicorn/prefer-response-static-json": "error",
|
|
306
|
+
"unicorn/prefer-set-has": "error",
|
|
307
|
+
"unicorn/prefer-set-methods": "error",
|
|
308
|
+
"unicorn/prefer-set-size": "error",
|
|
309
|
+
"unicorn/prefer-simple-sort-comparator": "error",
|
|
310
|
+
"unicorn/prefer-simplified-conditions": "error",
|
|
311
|
+
"unicorn/prefer-single-array-predicate": "error",
|
|
312
|
+
"unicorn/prefer-single-replace": "error",
|
|
313
|
+
"unicorn/prefer-split-limit": "error",
|
|
314
|
+
"unicorn/prefer-spread": "error",
|
|
315
|
+
"unicorn/prefer-string-match-all": "error",
|
|
316
|
+
"unicorn/prefer-string-pad-start-end": "error",
|
|
317
|
+
"unicorn/prefer-string-raw": "error",
|
|
318
|
+
"unicorn/prefer-string-repeat": "error",
|
|
319
|
+
"unicorn/prefer-string-slice": "error",
|
|
320
|
+
"unicorn/prefer-string-trim-start-end": "error",
|
|
321
|
+
"unicorn/prefer-then-catch": "error",
|
|
322
|
+
"unicorn/prefer-type-error": "error",
|
|
323
|
+
"unicorn/prefer-unary-minus": "error",
|
|
324
|
+
"unicorn/prefer-unicode-code-point-escapes": "error",
|
|
325
|
+
"unicorn/prefer-url-can-parse": "error",
|
|
326
|
+
"unicorn/prefer-url-search-parameters": "error",
|
|
327
|
+
"unicorn/prefer-while-loop-condition": "error",
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// One actionable line instead of N x M "Definition for rule ... was not found".
|
|
331
|
+
// Self-maintaining: it re-derives the required names from the objects above, so
|
|
332
|
+
// adding a rule that a pinned consumer's plugin lacks fails loudly at config
|
|
333
|
+
// load rather than silently linting nothing.
|
|
334
|
+
const missingUnicornRules = [
|
|
335
|
+
...Object.keys(UNICORN_CORRECTNESS_RULES),
|
|
336
|
+
...Object.keys(UNICORN_MODERNISATION_RULES),
|
|
337
|
+
]
|
|
338
|
+
.map((key) => key.slice("unicorn/".length))
|
|
339
|
+
.filter((name) => !(name in unicorn.rules));
|
|
340
|
+
|
|
341
|
+
if (missingUnicornRules.length > 0) {
|
|
342
|
+
throw new Error(
|
|
343
|
+
`code-standards: ${String(missingUnicornRules.length)} rule(s) this config enables do not exist ` +
|
|
344
|
+
`in the installed eslint-plugin-unicorn (${missingUnicornRules.slice(0, 5).join(", ")}). ` +
|
|
345
|
+
`Either the plugin is older than the required >= 72 (which also needs eslint >= 10.4), ` +
|
|
346
|
+
`or a rule name in this config is a typo or was renamed upstream.`,
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Rules deliberately not enabled are grouped by durable reason:
|
|
351
|
+
//
|
|
352
|
+
// 1. APIs unavailable on Node 22 or `lib: ES2025`, including Error.isError,
|
|
353
|
+
// Temporal, newer Uint8Array helpers, RegExp.escape, Promise.try, and explicit
|
|
354
|
+
// resource management. Autofixes must not produce unsupported runtime calls.
|
|
355
|
+
//
|
|
356
|
+
// 2. Duplicate authorities already owned by typescript-eslint, eslint-comments,
|
|
357
|
+
// perfectionist, or a narrower Sarj rule. One concern should yield one
|
|
358
|
+
// diagnostic, and enabled rules must not prescribe contradictory rewrites.
|
|
359
|
+
//
|
|
360
|
+
// 3. Naming, null, formatting, and readability preferences that create broad
|
|
361
|
+
// churn without establishing correctness. Prettier owns formatting, while
|
|
362
|
+
// domain models retain authority over names, nullable values, and control flow.
|
|
363
|
+
//
|
|
364
|
+
// 4. Rules whose suggestions can change short-circuiting, TypeScript narrowing,
|
|
365
|
+
// async error boundaries, memoization, or other observable behavior. These
|
|
366
|
+
// require human design review rather than a shared autofix.
|
|
367
|
+
//
|
|
368
|
+
// 5. Rules that need project-specific vocabulary, entrypoint, or TODO policy.
|
|
369
|
+
// Shared configuration cannot infer those boundaries safely.
|
|
370
|
+
//
|
|
371
|
+
// 6. DOM modernisation rules. Direct DOM access in component applications is an
|
|
372
|
+
// intentional escape hatch; DOM correctness rules remain enabled above.
|
|
373
|
+
//
|
|
374
|
+
// 7. Rules deprecated by Unicorn, plus CSS-language rules that cannot run in a
|
|
375
|
+
// JavaScript configuration.
|
|
376
|
+
|
|
377
|
+
// eslint-plugin-react 7 still uses APIs removed by ESLint 10. ESLint's official
|
|
378
|
+
// compatibility layer restores those APIs, keeping every React rule active
|
|
379
|
+
// instead of silently weakening the strict configuration on newer ESLint.
|
|
380
|
+
const compatibleReact = fixupPluginRules(react);
|
|
381
|
+
|
|
382
|
+
// Build output is not authored code, and this config had NO `ignores` at all —
|
|
383
|
+
// the single string "ignores" in the whole file was a word in a comment. ESLint
|
|
384
|
+
// 9/10 ignore only `node_modules/` and `.git/` by default, so `eslint .` in an
|
|
385
|
+
// adopting repo lints its own compiled output at `error` severity.
|
|
386
|
+
//
|
|
387
|
+
// Generated output contains transformed syntax and duplicated source that the
|
|
388
|
+
// author cannot fix directly. Excluding it keeps diagnostics attached to the
|
|
389
|
+
// source or generator that owns the change.
|
|
390
|
+
//
|
|
391
|
+
// The Python CLI has skipped exactly these directories since it shipped
|
|
392
|
+
// (`SKIP_DIR_NAMES` in `sarj_python_lint/__main__.py`), so the two halves of the
|
|
393
|
+
// same standard disagreed; this closes that gap rather than inventing a policy.
|
|
394
|
+
//
|
|
395
|
+
// `lib/` is included deliberately and is the only entry that can shadow authored
|
|
396
|
+
// code. It is the conventional Babel/tsc output directory for a published
|
|
397
|
+
// package, which is where the 21,284 came from. A repo that keeps SOURCE in
|
|
398
|
+
// `lib/` re-enables it in its own `eslint.config.mjs` override block, which is
|
|
399
|
+
// what that block is for:
|
|
400
|
+
// { ignores: ["!lib/**"] }
|
|
401
|
+
const BUILD_OUTPUT_IGNORES = [
|
|
402
|
+
"**/dist/**",
|
|
403
|
+
"**/build/**",
|
|
404
|
+
"**/lib/**",
|
|
405
|
+
"**/out/**",
|
|
406
|
+
"**/esm/**",
|
|
407
|
+
"**/cjs/**",
|
|
408
|
+
"**/umd/**",
|
|
409
|
+
"**/coverage/**",
|
|
410
|
+
"**/.next/**",
|
|
411
|
+
"**/.nuxt/**",
|
|
412
|
+
"**/.output/**",
|
|
413
|
+
"**/.turbo/**",
|
|
414
|
+
"**/.svelte-kit/**",
|
|
415
|
+
"**/.astro/**",
|
|
416
|
+
"**/.wrangler/**",
|
|
417
|
+
"**/.pnp.cjs",
|
|
418
|
+
"**/.pnp.loader.mjs",
|
|
419
|
+
"**/storybook-static/**",
|
|
420
|
+
"**/__generated__/**",
|
|
421
|
+
"**/generated/**",
|
|
422
|
+
"**/eslint.config.js",
|
|
423
|
+
"**/eslint.config.cjs",
|
|
424
|
+
"**/eslint.config.mjs",
|
|
425
|
+
"**/eslint.config.ts",
|
|
426
|
+
"**/eslint.strict.mjs",
|
|
427
|
+
"**/*.min.js",
|
|
428
|
+
"**/*.min.mjs",
|
|
429
|
+
"**/*.min.cjs",
|
|
430
|
+
];
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Build the config at call time, so an import cached from another working
|
|
434
|
+
* directory cannot freeze type-aware linting off for the real project.
|
|
435
|
+
*
|
|
436
|
+
* @param {{ tsconfigRootDir?: string | URL, projectService?: boolean | object, syntaxOnlyConfigFiles?: string[] }} [options]
|
|
437
|
+
* @returns {import("eslint").Linter.Config[]}
|
|
438
|
+
*/
|
|
439
|
+
export function createConfig(options = {}) {
|
|
440
|
+
const explicitRoot = options.tsconfigRootDir === undefined
|
|
441
|
+
? undefined
|
|
442
|
+
: normalizeRoot(options.tsconfigRootDir);
|
|
443
|
+
const candidates = explicitRoot === undefined
|
|
444
|
+
? [CONFIG_DIRECTORY, process.cwd()].map(normalizeRoot)
|
|
445
|
+
: [explicitRoot];
|
|
446
|
+
const detectedRoot = candidates.find(hasTypeProject);
|
|
447
|
+
const TYPE_PROJECT_ROOT = detectedRoot ?? candidates[0];
|
|
448
|
+
const PROJECT_SERVICE = options.projectService ?? detectedRoot !== undefined;
|
|
449
|
+
const HAS_TYPE_PROJECT = PROJECT_SERVICE !== false;
|
|
450
|
+
const SYNTAX_ONLY_CONFIG_FILES = options.syntaxOnlyConfigFiles ?? DEFAULT_SYNTAX_ONLY_CONFIG_FILES;
|
|
451
|
+
|
|
452
|
+
return [
|
|
453
|
+
// A config entry carrying ONLY `ignores` is a global ignore — it must stay
|
|
454
|
+
// first and must not grow a `files` key, or it silently degrades into a
|
|
455
|
+
// per-file entry that ignores nothing.
|
|
456
|
+
{ ignores: BUILD_OUTPUT_IGNORES },
|
|
457
|
+
|
|
458
|
+
...tseslint.configs.strictTypeChecked,
|
|
459
|
+
...tseslint.configs.stylisticTypeChecked,
|
|
460
|
+
|
|
461
|
+
{
|
|
462
|
+
// Dead eslint-disable directives are an error (parity with ruff RUF100).
|
|
463
|
+
linterOptions: {
|
|
464
|
+
reportUnusedDisableDirectives: "error",
|
|
465
|
+
reportUnusedInlineConfigs: "error",
|
|
466
|
+
},
|
|
467
|
+
plugins: {
|
|
468
|
+
"@typescript-eslint": tseslint.plugin,
|
|
469
|
+
react: compatibleReact,
|
|
470
|
+
"react-hooks": reactHooks,
|
|
471
|
+
unicorn,
|
|
472
|
+
"@eslint-community/eslint-comments": eslintComments,
|
|
473
|
+
perfectionist,
|
|
474
|
+
promise,
|
|
475
|
+
"simple-import-sort": simpleImportSort,
|
|
476
|
+
"@sarj": sarj,
|
|
477
|
+
zod,
|
|
478
|
+
},
|
|
479
|
+
languageOptions: {
|
|
480
|
+
parser: tseslint.parser,
|
|
481
|
+
parserOptions: {
|
|
482
|
+
projectService: PROJECT_SERVICE,
|
|
483
|
+
tsconfigRootDir: TYPE_PROJECT_ROOT,
|
|
484
|
+
ecmaFeatures: { jsx: true },
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
settings: { react: { version: "detect" } },
|
|
488
|
+
rules: {
|
|
489
|
+
"@typescript-eslint/no-explicit-any": "error",
|
|
490
|
+
"@typescript-eslint/no-non-null-assertion": "error",
|
|
491
|
+
"@typescript-eslint/no-deprecated": "error",
|
|
492
|
+
"@typescript-eslint/only-throw-error": [
|
|
493
|
+
"error",
|
|
494
|
+
{
|
|
495
|
+
allow: [
|
|
496
|
+
{
|
|
497
|
+
from: "package",
|
|
498
|
+
package: "@tanstack/react-router",
|
|
499
|
+
name: ["redirect"],
|
|
500
|
+
},
|
|
501
|
+
],
|
|
502
|
+
},
|
|
503
|
+
],
|
|
504
|
+
"@typescript-eslint/prefer-promise-reject-errors": "error",
|
|
505
|
+
"@typescript-eslint/no-meaningless-void-operator": "error",
|
|
506
|
+
"@typescript-eslint/no-mixed-enums": "error",
|
|
507
|
+
"@typescript-eslint/prefer-find": "error",
|
|
508
|
+
"@typescript-eslint/prefer-readonly": "error",
|
|
509
|
+
"@typescript-eslint/no-unsafe-assignment": "error",
|
|
510
|
+
"@typescript-eslint/no-unsafe-member-access": "error",
|
|
511
|
+
"@typescript-eslint/no-unsafe-argument": "error",
|
|
512
|
+
"@typescript-eslint/no-unsafe-call": "error",
|
|
513
|
+
"@typescript-eslint/no-unsafe-return": "error",
|
|
514
|
+
"@typescript-eslint/no-floating-promises": "error",
|
|
515
|
+
"@typescript-eslint/await-thenable": "error",
|
|
516
|
+
"@typescript-eslint/no-misused-promises": "error",
|
|
517
|
+
// The upstream rule flags every nested then/catch/finally call, including
|
|
518
|
+
// deliberate fire-and-forget work and synchronous framework callbacks.
|
|
519
|
+
// The typed @sarj rule below owns only semantics-preserving async returns.
|
|
520
|
+
"promise/prefer-await-to-then": "off",
|
|
521
|
+
"@typescript-eslint/require-await": "error",
|
|
522
|
+
// `isolatedDeclarations` requires annotations on exported values that
|
|
523
|
+
// cannot be declaration-emitted in isolation, even when the initializer
|
|
524
|
+
// looks inferable. The compiler owns that boundary.
|
|
525
|
+
"@typescript-eslint/no-inferrable-types": "off",
|
|
526
|
+
"@typescript-eslint/restrict-template-expressions": "error",
|
|
527
|
+
"@typescript-eslint/no-unused-vars": [
|
|
528
|
+
"error",
|
|
529
|
+
{
|
|
530
|
+
argsIgnorePattern: "^_",
|
|
531
|
+
varsIgnorePattern: "^_",
|
|
532
|
+
caughtErrorsIgnorePattern: "^_",
|
|
533
|
+
ignoreRestSiblings: true,
|
|
534
|
+
},
|
|
535
|
+
],
|
|
536
|
+
"@typescript-eslint/consistent-indexed-object-style": ["error", "record"],
|
|
537
|
+
"@typescript-eslint/consistent-type-imports": [
|
|
538
|
+
"error",
|
|
539
|
+
{
|
|
540
|
+
prefer: "type-imports",
|
|
541
|
+
fixStyle: "inline-type-imports",
|
|
542
|
+
},
|
|
543
|
+
],
|
|
544
|
+
"@typescript-eslint/switch-exhaustiveness-check": "error",
|
|
545
|
+
"@typescript-eslint/consistent-type-assertions": [
|
|
546
|
+
"error",
|
|
547
|
+
{
|
|
548
|
+
assertionStyle: "never",
|
|
549
|
+
},
|
|
550
|
+
],
|
|
551
|
+
"@typescript-eslint/naming-convention": [
|
|
552
|
+
"error",
|
|
553
|
+
{
|
|
554
|
+
selector: "default",
|
|
555
|
+
format: ["camelCase"],
|
|
556
|
+
leadingUnderscore: "allow",
|
|
557
|
+
trailingUnderscore: "allow",
|
|
558
|
+
filter: {
|
|
559
|
+
regex: "^(UNSAFE_|__)",
|
|
560
|
+
match: false,
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
selector: "variable",
|
|
565
|
+
format: ["camelCase", "UPPER_CASE", "PascalCase"],
|
|
566
|
+
leadingUnderscore: "allow",
|
|
567
|
+
},
|
|
568
|
+
{ selector: "typeLike", format: ["PascalCase"] },
|
|
569
|
+
{
|
|
570
|
+
selector: "import",
|
|
571
|
+
format: ["camelCase", "PascalCase", "UPPER_CASE"],
|
|
572
|
+
},
|
|
573
|
+
{ selector: "objectLiteralProperty", format: null },
|
|
574
|
+
{ selector: "typeProperty", format: null },
|
|
575
|
+
{
|
|
576
|
+
selector: "parameter",
|
|
577
|
+
format: ["camelCase", "snake_case"],
|
|
578
|
+
leadingUnderscore: "allow",
|
|
579
|
+
},
|
|
580
|
+
],
|
|
581
|
+
// Only accessibility bands are ordered. Field-vs-method and static-vs-
|
|
582
|
+
// instance layout remain unconstrained, while public methods cannot be
|
|
583
|
+
// buried below implementation-private methods. `@sarj/stepdown` then
|
|
584
|
+
// orders sole-caller private helpers within the private band.
|
|
585
|
+
"@typescript-eslint/member-ordering": [
|
|
586
|
+
"error",
|
|
587
|
+
{
|
|
588
|
+
default: "never",
|
|
589
|
+
interfaces: "never",
|
|
590
|
+
typeLiterals: "never",
|
|
591
|
+
classes: {
|
|
592
|
+
memberTypes: [
|
|
593
|
+
["public-constructor", "public-accessor", "public-get", "public-set", "public-method", "public-static-method", "public-instance-method", "public-decorated-method"],
|
|
594
|
+
["protected-constructor", "protected-accessor", "protected-get", "protected-set", "protected-method", "protected-static-method", "protected-instance-method", "protected-decorated-method"],
|
|
595
|
+
["private-constructor", "private-accessor", "#private-accessor", "private-get", "#private-get", "private-set", "#private-set", "private-method", "private-static-method", "private-instance-method", "private-decorated-method", "#private-method"],
|
|
596
|
+
],
|
|
597
|
+
order: "as-written",
|
|
598
|
+
},
|
|
599
|
+
classExpressions: {
|
|
600
|
+
memberTypes: [
|
|
601
|
+
["public-constructor", "public-accessor", "public-get", "public-set", "public-method", "public-static-method", "public-instance-method", "public-decorated-method"],
|
|
602
|
+
["protected-constructor", "protected-accessor", "protected-get", "protected-set", "protected-method", "protected-static-method", "protected-instance-method", "protected-decorated-method"],
|
|
603
|
+
["private-constructor", "private-accessor", "#private-accessor", "private-get", "#private-get", "private-set", "#private-set", "private-method", "private-static-method", "private-instance-method", "private-decorated-method", "#private-method"],
|
|
604
|
+
],
|
|
605
|
+
order: "as-written",
|
|
606
|
+
},
|
|
607
|
+
},
|
|
608
|
+
],
|
|
609
|
+
|
|
610
|
+
// Additional type-aware strictness incorporated from a first-party base config.
|
|
611
|
+
"@typescript-eslint/prefer-as-const": "error",
|
|
612
|
+
"@typescript-eslint/no-unnecessary-condition": "error",
|
|
613
|
+
"@typescript-eslint/prefer-nullish-coalescing": [
|
|
614
|
+
"error",
|
|
615
|
+
{ ignorePrimitives: { number: true, string: true, boolean: true } },
|
|
616
|
+
],
|
|
617
|
+
"@typescript-eslint/prefer-optional-chain": "error",
|
|
618
|
+
// `require-await` rejects async functions without awaits, while this rule
|
|
619
|
+
// requires `async` on functions that directly return a Promise. The two
|
|
620
|
+
// rules deadlock on `() => Promise.resolve(value)`; require-await wins.
|
|
621
|
+
"@typescript-eslint/promise-function-async": "off",
|
|
622
|
+
"@typescript-eslint/no-confusing-void-expression": [
|
|
623
|
+
"error",
|
|
624
|
+
{ ignoreArrowShorthand: true },
|
|
625
|
+
],
|
|
626
|
+
"@typescript-eslint/no-non-null-asserted-optional-chain": "error",
|
|
627
|
+
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
|
628
|
+
"@typescript-eslint/no-redundant-type-constituents": "error",
|
|
629
|
+
"@typescript-eslint/require-array-sort-compare": "error",
|
|
630
|
+
"@typescript-eslint/no-unsafe-type-assertion": "error",
|
|
631
|
+
"@typescript-eslint/no-unsafe-enum-comparison": "error",
|
|
632
|
+
"@typescript-eslint/no-base-to-string": "error",
|
|
633
|
+
"@typescript-eslint/no-misused-spread": "error",
|
|
634
|
+
"@typescript-eslint/no-unnecessary-type-conversion": "error",
|
|
635
|
+
"@typescript-eslint/prefer-includes": "error",
|
|
636
|
+
"@typescript-eslint/prefer-string-starts-ends-with": "error",
|
|
637
|
+
"@typescript-eslint/no-confusing-non-null-assertion": "error",
|
|
638
|
+
"@typescript-eslint/no-duplicate-type-constituents": "error",
|
|
639
|
+
"@typescript-eslint/no-invalid-void-type": "error",
|
|
640
|
+
"@typescript-eslint/no-unnecessary-template-expression": "error",
|
|
641
|
+
"@typescript-eslint/no-import-type-side-effects": "error",
|
|
642
|
+
"@typescript-eslint/consistent-type-exports": "error",
|
|
643
|
+
"@typescript-eslint/array-type": "error",
|
|
644
|
+
// `no-else-return` used to sit here. It is gone because
|
|
645
|
+
// `unicorn/no-useless-else` (enabled below) is a strict superset: it flags
|
|
646
|
+
// `else` after `throw`, `break` and `continue` as well as after `return`.
|
|
647
|
+
// Keeping both would double-report the return case, which this config
|
|
648
|
+
// treats as a bug: one diagnostic per concern.
|
|
649
|
+
|
|
650
|
+
"react/jsx-no-leaked-render": [
|
|
651
|
+
"error",
|
|
652
|
+
{ validStrategies: ["ternary", "coerce"] },
|
|
653
|
+
],
|
|
654
|
+
"react/no-unstable-nested-components": "error",
|
|
655
|
+
"react-hooks/exhaustive-deps": "error",
|
|
656
|
+
"react-hooks/rules-of-hooks": "error",
|
|
657
|
+
// Runtime correctness rules that do not require enabling React Compiler.
|
|
658
|
+
"react-hooks/error-boundaries": "error",
|
|
659
|
+
"react-hooks/globals": "error",
|
|
660
|
+
"react-hooks/immutability": "error",
|
|
661
|
+
"react-hooks/purity": "error",
|
|
662
|
+
"react-hooks/refs": "error",
|
|
663
|
+
"react-hooks/set-state-in-render": "error",
|
|
664
|
+
// These rules cannot distinguish a raw inline style from the CSS custom
|
|
665
|
+
// properties their own message recommends for dynamic utility values.
|
|
666
|
+
// Semantic-color and design-system rules remain the style authorities.
|
|
667
|
+
"react/forbid-component-props": "off",
|
|
668
|
+
"react/forbid-dom-props": "off",
|
|
669
|
+
"react/jsx-pascal-case": "error",
|
|
670
|
+
"react/no-danger": "error",
|
|
671
|
+
"react/no-this-in-sfc": "error",
|
|
672
|
+
"react/jsx-no-comment-textnodes": "error",
|
|
673
|
+
"react/jsx-no-duplicate-props": "error",
|
|
674
|
+
"react/jsx-no-target-blank": "error",
|
|
675
|
+
"react/jsx-no-undef": "error",
|
|
676
|
+
"react/no-object-type-as-default-prop": "error",
|
|
677
|
+
"react/no-unknown-property": "error",
|
|
678
|
+
"react/void-dom-elements-no-children": "error",
|
|
679
|
+
"react/jsx-fragments": "error",
|
|
680
|
+
"react/jsx-no-script-url": "error",
|
|
681
|
+
"react/self-closing-comp": "error",
|
|
682
|
+
"react/jsx-no-useless-fragment": "error",
|
|
683
|
+
"react/jsx-key": "error",
|
|
684
|
+
"react/no-children-prop": "error",
|
|
685
|
+
"react/no-invalid-html-attribute": "error",
|
|
686
|
+
"react/style-prop-object": "error",
|
|
687
|
+
"react/button-has-type": "error",
|
|
688
|
+
"react/jsx-boolean-value": ["error", "never"],
|
|
689
|
+
|
|
690
|
+
"unicorn/consistent-function-scoping": "error",
|
|
691
|
+
// Kebab-case filenames. unicorn handles most framework shapes for free:
|
|
692
|
+
// brackets and parens are "ignored characters" so `[id].tsx`,
|
|
693
|
+
// `[...slug].tsx` and `(marketing)/` only have their inner word checked,
|
|
694
|
+
// and `multipleFileExtensions` (default true) checks only the segment
|
|
695
|
+
// before the FIRST dot, so `vite.config.ts`, `foo.test.ts` and
|
|
696
|
+
// `app.module.css.ts` all pass on the stem alone.
|
|
697
|
+
//
|
|
698
|
+
// Framework-reserved names, config files, declarations, and barrel files
|
|
699
|
+
// already satisfy the stem check or are covered by the explicit exceptions.
|
|
700
|
+
//
|
|
701
|
+
// The `ignore` list below is therefore short and each entry is earned:
|
|
702
|
+
// - `^__root\.` TanStack Router's root route (a rename breaks routing)
|
|
703
|
+
// - `^_` TanStack `_layout` / pathless routes, and this repo's
|
|
704
|
+
// own `_paths.ts` / `_comments.ts` private helpers
|
|
705
|
+
// - `^\$` TanStack dynamic segments, e.g. `$benchmarkId.tsx`
|
|
706
|
+
// - `^\+` Expo Router specials, e.g. `+not-found.tsx`
|
|
707
|
+
// - `\.gen\.` generated output — the generator owns the name, so a
|
|
708
|
+
// rename is undone on the next codegen run
|
|
709
|
+
// `\.d\.ts$` is deliberately DROPPED: it was redundant (the stem check
|
|
710
|
+
// already ignores the `.d` middle segment) and over-broad — it let
|
|
711
|
+
// `apiTypes.d.ts` through, which is a genuine violation.
|
|
712
|
+
//
|
|
713
|
+
// `checkDirectories` is deliberately omitted because route-directory
|
|
714
|
+
// names are often public URLs; renaming one can change user-visible behavior.
|
|
715
|
+
"unicorn/filename-case": [
|
|
716
|
+
"error",
|
|
717
|
+
{
|
|
718
|
+
cases: { kebabCase: true },
|
|
719
|
+
ignore: [
|
|
720
|
+
String.raw`^__root\.`,
|
|
721
|
+
String.raw`^_`,
|
|
722
|
+
String.raw`^\$`,
|
|
723
|
+
String.raw`^\+`,
|
|
724
|
+
String.raw`\.gen\.`,
|
|
725
|
+
],
|
|
726
|
+
},
|
|
727
|
+
],
|
|
728
|
+
"unicorn/prefer-switch": "error",
|
|
729
|
+
// Its `() => undefined` fix produces `() => {}`, which no-empty-function
|
|
730
|
+
// rejects. Explicit undefined is the single authority for no-op arrows.
|
|
731
|
+
"unicorn/no-useless-undefined": "off",
|
|
732
|
+
"unicorn/prefer-node-protocol": "error",
|
|
733
|
+
"unicorn/prefer-string-replace-all": "error",
|
|
734
|
+
"unicorn/prefer-top-level-await": "error",
|
|
735
|
+
"unicorn/no-await-expression-member": "error",
|
|
736
|
+
"unicorn/prefer-structured-clone": "error",
|
|
737
|
+
"unicorn/prefer-logical-operator-over-ternary": "error",
|
|
738
|
+
"unicorn/relative-url-style": ["error", "never"],
|
|
739
|
+
"unicorn/throw-new-error": "error",
|
|
740
|
+
|
|
741
|
+
// The unicorn 72 expansion, declared and explained above the config.
|
|
742
|
+
...UNICORN_CORRECTNESS_RULES,
|
|
743
|
+
...UNICORN_MODERNISATION_RULES,
|
|
744
|
+
|
|
745
|
+
"zod/prefer-enum-over-literal-union": "error",
|
|
746
|
+
// A type hand-written beside the Zod schema it restates drifts when the
|
|
747
|
+
// schema changes. Requiring an identical shape keeps the diagnostic tied
|
|
748
|
+
// to structural evidence instead of name correlation.
|
|
749
|
+
"@sarj/prefer-zod-infer": "error",
|
|
750
|
+
|
|
751
|
+
// Maintained upstream rules own these concerns instead of local copies.
|
|
752
|
+
// `.nullable().optional()` is exactly `.nullish()`, so prefer-nullish
|
|
753
|
+
// provides a semantics-preserving autofix. `z.any()` leaks `any` through
|
|
754
|
+
// inference where no-explicit-any cannot see it; `z.unknown()` accepts the
|
|
755
|
+
// same inputs while requiring explicit narrowing.
|
|
756
|
+
"zod/prefer-nullish": "error",
|
|
757
|
+
"zod/no-any-schema": "error",
|
|
758
|
+
|
|
759
|
+
// Deterministic ordering (incorporated from a first-party config).
|
|
760
|
+
// simple-import-sort owns import/export ordering
|
|
761
|
+
// (chosen over eslint-plugin-import to avoid Next.js resolver conflicts).
|
|
762
|
+
// Object insertion order is observable through Object.keys/entries and
|
|
763
|
+
// is commonly used for UI presentation. Sorting can silently change
|
|
764
|
+
// behavior, so semantic order remains authoritative.
|
|
765
|
+
"perfectionist/sort-objects": "off",
|
|
766
|
+
"perfectionist/sort-interfaces": "error",
|
|
767
|
+
// Alphabetical class sorting contradicts both accessibility bands and
|
|
768
|
+
// caller-before-helper stepdown order. The two rules above own classes.
|
|
769
|
+
"perfectionist/sort-classes": "off",
|
|
770
|
+
"perfectionist/sort-jsx-props": "error",
|
|
771
|
+
"perfectionist/sort-union-types": "error",
|
|
772
|
+
// The rule skips imports instead of treating them as partitions, so its
|
|
773
|
+
// fixer can move declarations across imports and directly violate
|
|
774
|
+
// `@sarj/enforce-file-structure`. Keep imports-first as the authority.
|
|
775
|
+
"perfectionist/sort-modules": "off",
|
|
776
|
+
"simple-import-sort/imports": "error",
|
|
777
|
+
"simple-import-sort/exports": "error",
|
|
778
|
+
|
|
779
|
+
// Upstream rules require a description on eslint-disable and
|
|
780
|
+
// @ts-expect-error directives. @sarj/no-vague-suppression-description
|
|
781
|
+
// separately owns descriptions that exist but do not explain why;
|
|
782
|
+
// ban-ts-comment remains the sole owner of forbidden @ts-ignore usage.
|
|
783
|
+
"@eslint-community/eslint-comments/require-description": [
|
|
784
|
+
"error",
|
|
785
|
+
{ ignore: [] },
|
|
786
|
+
],
|
|
787
|
+
// ...and a suppression must name the rule it suppresses. A bare
|
|
788
|
+
// `/* eslint-disable */` at the top of a file silently switches off EVERY
|
|
789
|
+
// rule for the whole file — including ones added later — which is the
|
|
790
|
+
// file-level-suppression escape hatch flagged repeatedly in review.
|
|
791
|
+
"@eslint-community/eslint-comments/no-unlimited-disable": "error",
|
|
792
|
+
"@eslint-community/eslint-comments/disable-enable-pair": [
|
|
793
|
+
"error",
|
|
794
|
+
{ allowWholeFile: false },
|
|
795
|
+
],
|
|
796
|
+
"@eslint-community/eslint-comments/no-aggregating-enable": "error",
|
|
797
|
+
"@eslint-community/eslint-comments/no-duplicate-disable": "error",
|
|
798
|
+
"@eslint-community/eslint-comments/no-unused-enable": "error",
|
|
799
|
+
"@eslint-community/eslint-comments/no-restricted-disable": [
|
|
800
|
+
"error",
|
|
801
|
+
"no-console",
|
|
802
|
+
"react-hooks/exhaustive-deps",
|
|
803
|
+
"@sarj/no-vague-suppression-description",
|
|
804
|
+
],
|
|
805
|
+
|
|
806
|
+
// Dedup: TS-enum ban → @sarj/no-enum, oversized-try-block ban →
|
|
807
|
+
// @sarj/no-fat-try-blocks, and process.env ban → @sarj/no-raw-env (all
|
|
808
|
+
// added below). Only the selectors WITHOUT a @sarj equivalent stay here,
|
|
809
|
+
// so each concern fires exactly one diagnostic.
|
|
810
|
+
"no-restricted-syntax": [
|
|
811
|
+
"error",
|
|
812
|
+
{
|
|
813
|
+
selector: "CallExpression[callee.property.name='forEach']",
|
|
814
|
+
message: "Prefer a for-of loop over forEach.",
|
|
815
|
+
},
|
|
816
|
+
{
|
|
817
|
+
selector: "TSModuleDeclaration[kind='namespace']",
|
|
818
|
+
message: "Use ES modules instead of namespaces.",
|
|
819
|
+
},
|
|
820
|
+
],
|
|
821
|
+
"no-restricted-imports": [
|
|
822
|
+
"error",
|
|
823
|
+
{
|
|
824
|
+
paths: [
|
|
825
|
+
{
|
|
826
|
+
name: "@clerk/nextjs",
|
|
827
|
+
importNames: ["auth", "currentUser"],
|
|
828
|
+
message: "Prefer an internal user-service wrapper.",
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
name: "@clerk/nextjs/server",
|
|
832
|
+
message: "Prefer an internal user-service wrapper.",
|
|
833
|
+
},
|
|
834
|
+
],
|
|
835
|
+
patterns: ["*/index", "*/index.ts"],
|
|
836
|
+
},
|
|
837
|
+
],
|
|
838
|
+
|
|
839
|
+
"object-shorthand": ["error", "always"],
|
|
840
|
+
"no-return-await": "error",
|
|
841
|
+
eqeqeq: ["error", "always"],
|
|
842
|
+
"no-await-in-loop": "error",
|
|
843
|
+
"no-param-reassign": "error",
|
|
844
|
+
"array-callback-return": "error",
|
|
845
|
+
"no-fallthrough": "error",
|
|
846
|
+
"no-console": ["error", { allow: ["warn", "error"] }],
|
|
847
|
+
"prefer-const": "error",
|
|
848
|
+
"prefer-template": "error",
|
|
849
|
+
"no-var": "error",
|
|
850
|
+
"no-shadow": "off",
|
|
851
|
+
"@typescript-eslint/no-shadow": "error",
|
|
852
|
+
|
|
853
|
+
// The COMPLETE @sarj/eslint-plugin strict ruleset at each rule's declared strict severity.
|
|
854
|
+
//
|
|
855
|
+
// No version pin and no per-rule notes: a hand-written "@2.7.0" claim went
|
|
856
|
+
// stale twice, and a declared list of tier deviations outlived the last
|
|
857
|
+
// deviation it described. Both are now assertions instead of prose —
|
|
858
|
+
// packages/typescript/tests/strict-config-sync.test.ts fails if this block
|
|
859
|
+
// omits a shipped rule, names one that does not exist, or sets a tier the
|
|
860
|
+
// plugin's own `configs.strict` does not. Each rule's measurements live in
|
|
861
|
+
// the paired tests, which its `meta.docs.url` points at.
|
|
862
|
+
//
|
|
863
|
+
"@sarj/zod-naming-convention": "error",
|
|
864
|
+
"@sarj/require-assert-never": "error",
|
|
865
|
+
"@sarj/require-static-next-matcher": "error",
|
|
866
|
+
"@sarj/require-zod-form-validation": "error",
|
|
867
|
+
"@sarj/prefer-schema-for-api-payload": "error",
|
|
868
|
+
"@sarj/no-client-side-data-fetching": "error",
|
|
869
|
+
"@sarj/prefer-server-actions": "error",
|
|
870
|
+
"@sarj/no-unnecessary-use-client": "error",
|
|
871
|
+
"@sarj/no-enum": "error",
|
|
872
|
+
"@sarj/no-raw-env": "error",
|
|
873
|
+
"@sarj/no-sentinel-return-on-catch": "error",
|
|
874
|
+
"@sarj/no-log-only-catch": "error",
|
|
875
|
+
"@sarj/no-bare-return-from-test-catch": "warn",
|
|
876
|
+
"@sarj/no-dangerously-allow-svg": "warn",
|
|
877
|
+
"@sarj/no-duplicate-lifecycle-refresh-listeners": "warn",
|
|
878
|
+
"@sarj/no-production-browser-source-maps": "warn",
|
|
879
|
+
"@sarj/no-router-refresh-polling": "warn",
|
|
880
|
+
"@sarj/no-server-env-in-client-component": "warn",
|
|
881
|
+
"@sarj/no-long-comment": "error",
|
|
882
|
+
"@sarj/no-vague-suppression-description": "error",
|
|
883
|
+
"@sarj/no-generic-single-export-module": "error",
|
|
884
|
+
"@sarj/no-insecure-random-id": "error",
|
|
885
|
+
"@sarj/no-json-stringify-error": "error",
|
|
886
|
+
"@sarj/no-string-concat-in-loop": "error",
|
|
887
|
+
"@sarj/prefer-discriminated-union": "error",
|
|
888
|
+
"@sarj/no-comment-cruft": "error",
|
|
889
|
+
"@sarj/no-fat-try-blocks": ["error", { max: 5 }],
|
|
890
|
+
"@sarj/no-cors-wildcard-with-credentials": "error",
|
|
891
|
+
"@sarj/no-secret-in-log": "error",
|
|
892
|
+
"@sarj/no-hand-rolled-sleep": "error",
|
|
893
|
+
"@sarj/no-hand-rolled-spinner": "error",
|
|
894
|
+
"@sarj/prefer-input-group-search": "error",
|
|
895
|
+
"@sarj/prefer-immutable-module-constant": "error",
|
|
896
|
+
"@sarj/require-fetch-timeout": "error",
|
|
897
|
+
"@sarj/no-silent-promise-catch": "error",
|
|
898
|
+
"@sarj/enforce-file-structure": "error",
|
|
899
|
+
"@sarj/prefer-semantic-colors": [
|
|
900
|
+
"error",
|
|
901
|
+
{ requireSemanticTokens: true },
|
|
902
|
+
],
|
|
903
|
+
"@sarj/prefer-constant-time-secret-compare": "error",
|
|
904
|
+
"@sarj/no-dynamic-sql": "error",
|
|
905
|
+
"@sarj/store-insert-requires-on-conflict": "error",
|
|
906
|
+
"@sarj/stepdown": "error",
|
|
907
|
+
"@sarj/no-offset-pagination": "error",
|
|
908
|
+
"@sarj/no-select-star": "error",
|
|
909
|
+
"@sarj/no-zod-native-enum": "error",
|
|
910
|
+
"@sarj/no-impossible-zod-literal-bounds": "error",
|
|
911
|
+
"@sarj/prefer-module-level-constant": "error",
|
|
912
|
+
"@sarj/prefer-module-level-schema": "error",
|
|
913
|
+
"@sarj/prefer-non-nullable-collection": "error",
|
|
914
|
+
"@sarj/prefer-await-in-async-return": "error",
|
|
915
|
+
"@sarj/no-sleep-in-test-body": "error",
|
|
916
|
+
"@sarj/iac-source-coupled-test": "warn",
|
|
917
|
+
"@sarj/repeated-static-call-cases": "warn",
|
|
918
|
+
"@sarj/require-use-form-default-values": "warn",
|
|
919
|
+
"@sarj/require-use-server-in-actions-file": "warn",
|
|
920
|
+
"@sarj/source-coupled-test": "warn",
|
|
921
|
+
"@sarj/no-positional-tuple-return": "error",
|
|
922
|
+
"@sarj/no-restated-comment": "error",
|
|
923
|
+
"@sarj/no-restated-jsdoc": "error",
|
|
924
|
+
"@sarj/no-trailing-value-narration": "error",
|
|
925
|
+
"@sarj/no-declaration-comment-wall": "error",
|
|
926
|
+
"@sarj/no-union-in-comment": "error",
|
|
927
|
+
"@sarj/no-type-member-comment-wall": "error",
|
|
928
|
+
"@sarj/no-repeated-string-literal": "error",
|
|
929
|
+
"@sarj/no-tautological-expect": "error",
|
|
930
|
+
"@sarj/no-typed-doc-sections": "error",
|
|
931
|
+
"@sarj/require-port-for-service": "error",
|
|
932
|
+
"@sarj/no-unsafe-mock-casting": "error",
|
|
933
|
+
"@sarj/prefer-whole-object-assertion": "error",
|
|
934
|
+
"@sarj/duplicate-test-body": "error",
|
|
935
|
+
"@sarj/test-loops-over-literal-cases": "error",
|
|
936
|
+
"@sarj/test-phase-label-comment": "warn",
|
|
937
|
+
// Both architectural rules stay enabled in the shared baseline. The
|
|
938
|
+
// fetch rule ships conservative client/service defaults; consumers can
|
|
939
|
+
// replace its `allow` list. The storage rule is intentionally inert until
|
|
940
|
+
// a consumer declares its stateless module paths, but keeping it present
|
|
941
|
+
// guarantees that the canonical config never silently omits a shipped
|
|
942
|
+
// custom rule:
|
|
943
|
+
// "@sarj/no-storage-in-stateless-modules": ["error", { modules: [...] }],
|
|
944
|
+
// "@sarj/no-raw-fetch-outside-clients": ["error", { allow: [...] }],
|
|
945
|
+
...(HAS_TYPE_PROJECT ? {} : UNTYPED_RULE_OVERRIDES),
|
|
946
|
+
},
|
|
947
|
+
},
|
|
948
|
+
|
|
949
|
+
...(SYNTAX_ONLY_CONFIG_FILES.length === 0
|
|
950
|
+
? []
|
|
951
|
+
: [{
|
|
952
|
+
// Project service rejects conventional tool configs outside a tsconfig.
|
|
953
|
+
// Pass [] when the project owns them; name other exceptions explicitly.
|
|
954
|
+
files: SYNTAX_ONLY_CONFIG_FILES,
|
|
955
|
+
languageOptions: {
|
|
956
|
+
parserOptions: {
|
|
957
|
+
program: null,
|
|
958
|
+
project: false,
|
|
959
|
+
projectService: false,
|
|
960
|
+
},
|
|
961
|
+
},
|
|
962
|
+
rules: UNTYPED_RULE_OVERRIDES,
|
|
963
|
+
}]),
|
|
964
|
+
|
|
965
|
+
{
|
|
966
|
+
files: [
|
|
967
|
+
"**/*.test.ts",
|
|
968
|
+
"**/*.test.tsx",
|
|
969
|
+
"**/test/**/*",
|
|
970
|
+
"**/tests/**/*",
|
|
971
|
+
"**/__tests__/**/*",
|
|
972
|
+
],
|
|
973
|
+
rules: {
|
|
974
|
+
// Test doubles and partial external payload fixtures intentionally cross
|
|
975
|
+
// type boundaries. Production keeps every rule below at error; tests use
|
|
976
|
+
// runtime assertions to verify the boundary instead of reconstructing an
|
|
977
|
+
// entire third-party object graph solely to satisfy static analysis.
|
|
978
|
+
"@typescript-eslint/consistent-type-assertions": "off",
|
|
979
|
+
"@typescript-eslint/no-unsafe-assignment": "off",
|
|
980
|
+
"@typescript-eslint/no-unsafe-type-assertion": "off",
|
|
981
|
+
"@typescript-eslint/no-unsafe-member-access": "off",
|
|
982
|
+
"@typescript-eslint/no-non-null-assertion": "off",
|
|
983
|
+
"@typescript-eslint/promise-function-async": "off",
|
|
984
|
+
"@typescript-eslint/require-await": "off",
|
|
985
|
+
"no-await-in-loop": "off",
|
|
986
|
+
"unicorn/consistent-function-scoping": "off",
|
|
987
|
+
},
|
|
988
|
+
},
|
|
989
|
+
|
|
990
|
+
{
|
|
991
|
+
files: ["**/components/ui/**", "**/components/design-system/**"],
|
|
992
|
+
rules: {
|
|
993
|
+
"react/forbid-elements": "off",
|
|
994
|
+
// Prevent design-system primitives from becoming implicit submit buttons.
|
|
995
|
+
"react/button-has-type": "error",
|
|
996
|
+
},
|
|
997
|
+
},
|
|
998
|
+
|
|
999
|
+
// better-tailwindcss: class-string hygiene for Tailwind repos. Include plain
|
|
1000
|
+
// JS/TS because class helpers and variant definitions commonly live there.
|
|
1001
|
+
// these three rules only inspect literal class strings, so non-Tailwind repos
|
|
1002
|
+
// simply see zero findings. Kept in its own block so the plugin is only wired
|
|
1003
|
+
// where it applies.
|
|
1004
|
+
{
|
|
1005
|
+
files: ["**/*.{js,jsx,ts,tsx}"],
|
|
1006
|
+
plugins: {
|
|
1007
|
+
"better-tailwindcss": betterTailwindcss,
|
|
1008
|
+
},
|
|
1009
|
+
rules: {
|
|
1010
|
+
"better-tailwindcss/no-conflicting-classes": "error",
|
|
1011
|
+
"better-tailwindcss/no-duplicate-classes": "error",
|
|
1012
|
+
"better-tailwindcss/no-deprecated-classes": "error",
|
|
1013
|
+
"better-tailwindcss/no-unnecessary-whitespace": "error",
|
|
1014
|
+
"better-tailwindcss/enforce-shorthand-classes": "error",
|
|
1015
|
+
},
|
|
1016
|
+
},
|
|
1017
|
+
// React component IDENTIFIERS must be PascalCase for JSX to distinguish them
|
|
1018
|
+
// from intrinsic elements. Filenames remain kebab-case under the base policy.
|
|
1019
|
+
{
|
|
1020
|
+
files: ["**/*.tsx"],
|
|
1021
|
+
rules: {
|
|
1022
|
+
// PascalCase function and variable names are the React component
|
|
1023
|
+
// convention. The base TypeScript policy remains camelCase-only; widen
|
|
1024
|
+
// it only for TSX instead of rejecting every valid component or assuming
|
|
1025
|
+
// a particular framework/compiler setup.
|
|
1026
|
+
"@typescript-eslint/naming-convention": [
|
|
1027
|
+
"error",
|
|
1028
|
+
{
|
|
1029
|
+
selector: "default",
|
|
1030
|
+
format: ["camelCase", "PascalCase"],
|
|
1031
|
+
leadingUnderscore: "allow",
|
|
1032
|
+
trailingUnderscore: "allow",
|
|
1033
|
+
filter: { regex: "^(UNSAFE_|__)", match: false },
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
selector: "variable",
|
|
1037
|
+
format: ["camelCase", "UPPER_CASE", "PascalCase"],
|
|
1038
|
+
leadingUnderscore: "allow",
|
|
1039
|
+
},
|
|
1040
|
+
{ selector: "typeLike", format: ["PascalCase"] },
|
|
1041
|
+
{
|
|
1042
|
+
selector: "import",
|
|
1043
|
+
format: ["camelCase", "PascalCase", "UPPER_CASE"],
|
|
1044
|
+
},
|
|
1045
|
+
{ selector: "objectLiteralProperty", format: null },
|
|
1046
|
+
{ selector: "typeProperty", format: null },
|
|
1047
|
+
{
|
|
1048
|
+
selector: "parameter",
|
|
1049
|
+
format: ["camelCase", "snake_case"],
|
|
1050
|
+
leadingUnderscore: "allow",
|
|
1051
|
+
},
|
|
1052
|
+
],
|
|
1053
|
+
},
|
|
1054
|
+
},
|
|
1055
|
+
|
|
1056
|
+
];
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
const config = createConfig();
|
|
1060
|
+
export default config;
|