lintmax 0.1.15

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.

Potentially problematic release.


This version of lintmax might be problematic. Click here for more details.

@@ -0,0 +1,105 @@
1
+ //#region src/constants.ts
2
+ const BIOME_RULES_OFF = [
3
+ "noBarrelFile",
4
+ "noConditionalExpect",
5
+ "noConsole",
6
+ "noDefaultExport",
7
+ "noExcessiveCognitiveComplexity",
8
+ "noExcessiveLinesPerFile",
9
+ "noExcessiveLinesPerFunction",
10
+ "noExportedImports",
11
+ "noImplicitBoolean",
12
+ "noImportantStyles",
13
+ "noInlineStyles",
14
+ "noLeakedRender",
15
+ "useConsistentCurlyBraces",
16
+ "noJsxLiterals",
17
+ "noJsxPropsBind",
18
+ "noMagicNumbers",
19
+ "noNestedTernary",
20
+ "noNodejsModules",
21
+ "noProcessGlobal",
22
+ "noReactSpecificProps",
23
+ "noSecrets",
24
+ "noSolidDestructuredProps",
25
+ "noTernary",
26
+ "noUndeclaredDependencies",
27
+ "noUnresolvedImports",
28
+ "useBaseline",
29
+ "useBlockStatements",
30
+ "useComponentExportOnlyModules",
31
+ "useDestructuring",
32
+ "useConsistentTestIt",
33
+ "useExplicitReturnType",
34
+ "useExplicitType",
35
+ "useImportExtensions",
36
+ "useNamingConvention",
37
+ "useQwikValidLexicalScope",
38
+ "useSingleVarDeclarator",
39
+ "useSolidForComponent",
40
+ "useSortedClasses"
41
+ ];
42
+ const BIOME_IGNORE_PATTERNS = [
43
+ "!!**/.build",
44
+ "!!**/.cache",
45
+ "!!**/.source",
46
+ "!!**/.next",
47
+ "!!**/.output",
48
+ "!!**/.turbo",
49
+ "!!**/.venv",
50
+ "!!**/.wxt",
51
+ "!!**/_generated",
52
+ "!!**/Android",
53
+ "!!**/Darwin",
54
+ "!!**/dist",
55
+ "!!**/maestro",
56
+ "!!**/module_bindings",
57
+ "!!**/playwright-report",
58
+ "!!**/test-results",
59
+ "!!**/*.xcassets"
60
+ ];
61
+ const DEFAULT_SHARED_IGNORE_PATTERNS = [
62
+ "_generated/**",
63
+ ".next/**",
64
+ ".source/**",
65
+ "dist/**",
66
+ "generated/**",
67
+ "module_bindings/**",
68
+ "next-env.d.ts",
69
+ "readonly/**",
70
+ "expo/**/babel.config.js",
71
+ "expo/**/global.css",
72
+ "expo/**/metro.config.js",
73
+ "expo/**/uniwind-env.d.ts",
74
+ "expo/**/uniwind-types.d.ts"
75
+ ].map((p) => p.startsWith("**/") ? p : `**/${p}`);
76
+ const BIOME_PATTERN_RULE_OVERRIDES = [{
77
+ includes: ["**/expo/**"],
78
+ rules: ["style/noProcessEnv"]
79
+ }, {
80
+ includes: ["**/maestro/**"],
81
+ rules: ["performance/noAwaitInLoops"]
82
+ }];
83
+ const OXLINT_PATTERN_RULE_OVERRIDES = [{
84
+ files: ["**/expo/**/*.tsx", "**/expo/**/*.ts"],
85
+ rules: { "react-perf/jsx-no-new-object-as-prop": "off" }
86
+ }];
87
+ const TAILWIND_ENTRY_CANDIDATES = [
88
+ "ui/src/styles/globals.css",
89
+ "src/styles/globals.css",
90
+ "app/globals.css",
91
+ "web/global.css",
92
+ "styles/globals.css",
93
+ "global.css"
94
+ ];
95
+ const ESLINT_TEST_FILE_PATTERNS = [
96
+ "**/*.test.ts",
97
+ "**/*.test.tsx",
98
+ "**/*.spec.ts",
99
+ "**/*.spec.tsx",
100
+ "**/__tests__/**/*.ts",
101
+ "**/__tests__/**/*.tsx"
102
+ ];
103
+ const SHARED_OVERRIDE_SYMBOL_KEY = "lintmax.sharedOverride";
104
+ //#endregion
105
+ export { ESLINT_TEST_FILE_PATTERNS as a, TAILWIND_ENTRY_CANDIDATES as c, DEFAULT_SHARED_IGNORE_PATTERNS as i, BIOME_PATTERN_RULE_OVERRIDES as n, OXLINT_PATTERN_RULE_OVERRIDES as o, BIOME_RULES_OFF as r, SHARED_OVERRIDE_SYMBOL_KEY as s, BIOME_IGNORE_PATTERNS as t };
@@ -0,0 +1,9 @@
1
+ import { n as EslintOptions } from "./lintmax-types-CJ7VY33l.mjs";
2
+ import * as _$eslint_config0 from "eslint/config";
3
+ import { defineConfig } from "eslint/config";
4
+
5
+ //#region src/eslint.d.ts
6
+ declare const eslintFactory: (options?: EslintOptions) => ReturnType<typeof defineConfig>;
7
+ declare const defaultConfig: _$eslint_config0.Config[];
8
+ //#endregion
9
+ export { type EslintOptions, defaultConfig as default, eslintFactory as eslint };
@@ -0,0 +1,344 @@
1
+ import { a as ESLINT_TEST_FILE_PATTERNS, c as TAILWIND_ENTRY_CANDIDATES, i as DEFAULT_SHARED_IGNORE_PATTERNS, s as SHARED_OVERRIDE_SYMBOL_KEY } from "./constants-Cjkf4mJh.mjs";
2
+ import { c as findUnknownRules, d as normalizeObjectListInput, f as normalizePathListInput, g as warnToError, i as joinPath, l as isRecord, m as normalizeTailwindOption, p as normalizeRulesOffInput, r as isAbsolutePath } from "./path-Cu_Nf2ct.mjs";
3
+ import eslintReact from "@eslint-react/eslint-plugin";
4
+ import { includeIgnoreFile } from "@eslint/compat";
5
+ import eslint from "@eslint/js";
6
+ import nextPlugin from "@next/eslint-plugin-next";
7
+ import eslintPluginBetterTailwindcss from "eslint-plugin-better-tailwindcss";
8
+ import { configs } from "eslint-plugin-perfectionist";
9
+ import preferArrow from "eslint-plugin-prefer-arrow-functions";
10
+ import reactPlugin from "eslint-plugin-react";
11
+ import reactHooks from "eslint-plugin-react-hooks";
12
+ import turbo from "eslint-plugin-turbo";
13
+ import { defineConfig, globalIgnores } from "eslint/config";
14
+ import { existsSync } from "node:fs";
15
+ import tseslint from "typescript-eslint";
16
+ //#region src/eslint.ts
17
+ const sharedOverrideMarker = Symbol.for(SHARED_OVERRIDE_SYMBOL_KEY);
18
+ const normalizeAppendInput = ({ append }) => {
19
+ const out = [];
20
+ for (const value of normalizeObjectListInput({
21
+ allowNonPlain: true,
22
+ label: "eslint.append",
23
+ value: append
24
+ })) out.push(value);
25
+ return out;
26
+ };
27
+ const validateEslintOptions = ({ options }) => {
28
+ if (!options) return;
29
+ if (options.ignores !== void 0) normalizePathListInput({
30
+ allowUndefined: true,
31
+ label: "eslint.ignores",
32
+ value: options.ignores
33
+ });
34
+ if (options.off !== void 0) normalizeRulesOffInput({
35
+ label: "eslint.off",
36
+ value: options.off
37
+ });
38
+ if (options.append !== void 0) normalizeAppendInput({ append: options.append });
39
+ const tailwindEntrySetting = normalizeTailwindOption({
40
+ label: "eslint.tailwind",
41
+ value: options.tailwind
42
+ });
43
+ if (typeof tailwindEntrySetting === "string") {
44
+ const root = options.tsconfigRootDir ?? process.cwd();
45
+ const resolved = isAbsolutePath(tailwindEntrySetting) ? tailwindEntrySetting : joinPath(root, tailwindEntrySetting);
46
+ if (!existsSync(resolved)) throw new Error(`eslint.tailwind file not found: ${resolved}. Use an existing path, set eslint.tailwind to false, or remove eslint.tailwind to use auto-detection.`);
47
+ }
48
+ if (options.tsconfigRootDir !== void 0 && typeof options.tsconfigRootDir !== "string") throw new Error("eslint.tsconfigRootDir must be a string");
49
+ };
50
+ const collectKnownEslintRuleNames = ({ appendConfigs, baseConfigs }) => {
51
+ const knownRuleNames = /* @__PURE__ */ new Set();
52
+ const seenConfigs = /* @__PURE__ */ new WeakSet();
53
+ const collectPluginRuleNames = ({ plugins }) => {
54
+ if (!isRecord(plugins)) return;
55
+ for (const [pluginName, plugin] of Object.entries(plugins)) if (isRecord(plugin) && isRecord(plugin.rules)) for (const ruleName of Object.keys(plugin.rules)) knownRuleNames.add(`${pluginName}/${ruleName}`);
56
+ };
57
+ const collectFromConfig = ({ config, includeRules }) => {
58
+ if (typeof config !== "object" || config === null) return;
59
+ if (seenConfigs.has(config)) return;
60
+ seenConfigs.add(config);
61
+ if (Array.isArray(config)) {
62
+ for (const entry of config) collectFromConfig({
63
+ config: entry,
64
+ includeRules
65
+ });
66
+ return;
67
+ }
68
+ if (!isRecord(config)) return;
69
+ if (includeRules && isRecord(config.rules)) for (const ruleName of Object.keys(config.rules)) knownRuleNames.add(ruleName);
70
+ if ("plugins" in config) collectPluginRuleNames({ plugins: config.plugins });
71
+ if ("extends" in config) collectFromConfig({
72
+ config: config.extends,
73
+ includeRules: true
74
+ });
75
+ };
76
+ for (const config of baseConfigs) collectFromConfig({
77
+ config,
78
+ includeRules: true
79
+ });
80
+ for (const config of appendConfigs) collectFromConfig({
81
+ config,
82
+ includeRules: false
83
+ });
84
+ return knownRuleNames;
85
+ };
86
+ const getSharedAppendConfig = ({ config }) => {
87
+ return config[sharedOverrideMarker] === true ? config : null;
88
+ };
89
+ const resolveTailwindEntry = ({ root, tailwind }) => {
90
+ const tailwindSetting = tailwind ?? true;
91
+ if (tailwindSetting === false) return;
92
+ if (typeof tailwindSetting === "string") return isAbsolutePath(tailwindSetting) ? tailwindSetting : joinPath(root, tailwindSetting);
93
+ const matches = [];
94
+ for (const candidate of TAILWIND_ENTRY_CANDIDATES) {
95
+ const resolved = joinPath(root, candidate);
96
+ if (existsSync(resolved)) matches.push(resolved);
97
+ }
98
+ if (matches.length <= 1) return matches[0];
99
+ const preferredOnAmbiguous = [joinPath(root, "ui/src/styles/globals.css")];
100
+ for (const preferred of preferredOnAmbiguous) if (matches.includes(preferred)) return preferred;
101
+ const relMatches = matches.map((path) => path.slice(root.length + 1));
102
+ throw new Error(`Multiple Tailwind entry files found: ${relMatches.join(", ")}. Set eslint.tailwind to an explicit path.`);
103
+ };
104
+ const tailwindRules = (entryPoint) => entryPoint ? eslintPluginBetterTailwindcss.configs["recommended-error"].rules : {};
105
+ const eslintFactory = (options) => {
106
+ validateEslintOptions({ options });
107
+ const opts = options ?? {};
108
+ const root = opts.tsconfigRootDir ?? process.cwd();
109
+ const configs$1 = [];
110
+ const gitignorePath = joinPath(root, ".gitignore");
111
+ const normalizedIgnores = normalizePathListInput({
112
+ allowUndefined: true,
113
+ label: "eslint.ignores",
114
+ value: opts.ignores
115
+ });
116
+ const tailwindEntry = resolveTailwindEntry({
117
+ root,
118
+ tailwind: opts.tailwind
119
+ });
120
+ const tailwindSettings = {};
121
+ if (tailwindEntry) tailwindSettings["better-tailwindcss"] = { entryPoint: tailwindEntry };
122
+ configs$1.push(globalIgnores([...DEFAULT_SHARED_IGNORE_PATTERNS, ...normalizedIgnores]));
123
+ try {
124
+ configs$1.push(includeIgnoreFile(gitignorePath));
125
+ } catch (error) {
126
+ if (error instanceof Error) {
127
+ const message = error.message.toLowerCase();
128
+ if (!(message.includes("enoent") || message.includes("no such file"))) throw error;
129
+ } else throw error;
130
+ }
131
+ configs$1.push(...defineConfig(configs["recommended-natural"], { ignores: ["**/postcss.config.mjs"] }, {
132
+ extends: [
133
+ eslint.configs.recommended,
134
+ eslint.configs.all,
135
+ ...tseslint.configs.all,
136
+ ...tseslint.configs.recommended,
137
+ ...tseslint.configs.recommendedTypeChecked,
138
+ ...tseslint.configs.stylisticTypeChecked,
139
+ eslintReact.configs["strict-type-checked"],
140
+ eslintReact.configs.recommended
141
+ ],
142
+ files: [
143
+ "**/*.js",
144
+ "**/*.ts",
145
+ "**/*.tsx"
146
+ ],
147
+ plugins: {
148
+ preferArrow,
149
+ turbo
150
+ },
151
+ rules: {
152
+ "@eslint-react/avoid-shorthand-boolean": "off",
153
+ "@eslint-react/avoid-shorthand-fragment": "off",
154
+ "@eslint-react/jsx-dollar": "error",
155
+ "@eslint-react/jsx-shorthand-boolean": "error",
156
+ "@eslint-react/jsx-shorthand-fragment": "error",
157
+ "@eslint-react/naming-convention/component-name": "error",
158
+ "@eslint-react/naming-convention/ref-name": "error",
159
+ "@eslint-react/no-duplicate-key": "error",
160
+ "@eslint-react/no-missing-component-display-name": "error",
161
+ "@eslint-react/no-missing-context-display-name": "off",
162
+ "@eslint-react/no-unnecessary-key": "error",
163
+ "@typescript-eslint/consistent-return": "off",
164
+ "@typescript-eslint/consistent-type-imports": ["error", {
165
+ fixStyle: "separate-type-imports",
166
+ prefer: "type-imports"
167
+ }],
168
+ "@typescript-eslint/explicit-function-return-type": "off",
169
+ "@typescript-eslint/explicit-member-accessibility": "off",
170
+ "@typescript-eslint/explicit-module-boundary-types": "off",
171
+ "@typescript-eslint/init-declarations": "off",
172
+ "@typescript-eslint/naming-convention": ["error", {
173
+ format: [
174
+ "camelCase",
175
+ "UPPER_CASE",
176
+ "PascalCase"
177
+ ],
178
+ selector: "variable"
179
+ }],
180
+ "@typescript-eslint/no-confusing-void-expression": "off",
181
+ "@typescript-eslint/no-floating-promises": "off",
182
+ "@typescript-eslint/no-magic-numbers": "off",
183
+ "@typescript-eslint/no-misused-promises": [2, { checksVoidReturn: { attributes: false } }],
184
+ "@typescript-eslint/no-unnecessary-condition": ["error", { allowConstantLoopConditions: true }],
185
+ "@typescript-eslint/no-unsafe-type-assertion": "off",
186
+ "@typescript-eslint/no-useless-default-assignment": "off",
187
+ "@typescript-eslint/prefer-destructuring": ["error", {
188
+ array: false,
189
+ object: true
190
+ }],
191
+ "@typescript-eslint/prefer-readonly-parameter-types": "off",
192
+ "@typescript-eslint/strict-boolean-expressions": "off",
193
+ camelcase: "off",
194
+ "capitalized-comments": [
195
+ "error",
196
+ "always",
197
+ { ignorePattern: "oxlint|biome|console|let|const|return|if|for|throw" }
198
+ ],
199
+ curly: ["error", "multi"],
200
+ "id-length": "off",
201
+ "max-lines": "off",
202
+ "max-lines-per-function": "off",
203
+ "max-statements": "off",
204
+ "new-cap": ["error", { capIsNewExceptionPattern: ".*" }],
205
+ "no-duplicate-imports": ["error", { allowSeparateTypeImports: true }],
206
+ "no-magic-numbers": "off",
207
+ "no-nested-ternary": "off",
208
+ "no-ternary": "off",
209
+ "no-undefined": "off",
210
+ "no-underscore-dangle": "off",
211
+ "one-var": ["error", "never"],
212
+ "perfectionist/sort-imports": ["error", {
213
+ newlinesBetween: 0,
214
+ order: "asc",
215
+ type: "natural"
216
+ }],
217
+ "perfectionist/sort-objects": "off",
218
+ "perfectionist/sort-variable-declarations": "off",
219
+ "preferArrow/prefer-arrow-functions": ["error", { returnStyle: "implicit" }],
220
+ "require-atomic-updates": "off",
221
+ "sort-imports": "off",
222
+ "sort-keys": "off",
223
+ "sort-vars": "off"
224
+ }
225
+ }, {
226
+ plugins: eslintReact.configs["strict-type-checked"].plugins,
227
+ rules: {
228
+ ...warnToError({
229
+ ...eslintReact.configs["strict-type-checked"].rules,
230
+ ...eslintReact.configs.recommended.rules
231
+ }),
232
+ "@eslint-react/dom/no-string-style-prop": "error",
233
+ "@eslint-react/dom/no-unknown-property": "error",
234
+ "@eslint-react/jsx-no-undef": "error"
235
+ }
236
+ }), ...defineConfig(reactHooks.configs.flat["recommended-latest"], {
237
+ files: ["**/*.ts", "**/*.tsx"],
238
+ ...reactPlugin.configs.flat.all,
239
+ ...reactPlugin.configs.flat["jsx-runtime"],
240
+ languageOptions: {
241
+ ...reactPlugin.configs.flat.all?.languageOptions,
242
+ ...reactPlugin.configs.flat["jsx-runtime"]?.languageOptions,
243
+ globals: { React: "writable" }
244
+ },
245
+ plugins: {
246
+ "better-tailwindcss": eslintPluginBetterTailwindcss,
247
+ react: reactPlugin
248
+ },
249
+ rules: {
250
+ ...reactPlugin.configs["jsx-runtime"].rules,
251
+ ...reactPlugin.configs.all.rules,
252
+ ...tailwindRules(tailwindEntry),
253
+ "better-tailwindcss/enforce-consistent-line-wrapping": "off",
254
+ "react-hooks/exhaustive-deps": "error",
255
+ "react-hooks/incompatible-library": "error",
256
+ "react-hooks/preserve-manual-memoization": "off",
257
+ "react-hooks/set-state-in-effect": "off",
258
+ "react-hooks/unsupported-syntax": "error",
259
+ "react/forbid-component-props": "off",
260
+ "react/function-component-definition": "off",
261
+ "react/jsx-child-element-spacing": "off",
262
+ "react/jsx-closing-bracket-location": "off",
263
+ "react/jsx-curly-newline": "off",
264
+ "react/jsx-filename-extension": ["error", { extensions: [".tsx"] }],
265
+ "react/jsx-handler-names": "off",
266
+ "react/jsx-indent": "off",
267
+ "react/jsx-indent-props": "off",
268
+ "react/jsx-max-depth": "off",
269
+ "react/jsx-max-props-per-line": "off",
270
+ "react/jsx-newline": "off",
271
+ "react/jsx-no-bind": "off",
272
+ "react/jsx-no-literals": "off",
273
+ "react/jsx-one-expression-per-line": "off",
274
+ "react/jsx-props-no-spreading": "off",
275
+ "react/jsx-sort-props": ["error", { ignoreCase: true }],
276
+ "react/no-multi-comp": "off",
277
+ "react/prefer-read-only-props": "off",
278
+ "react/require-default-props": "off"
279
+ },
280
+ settings: tailwindSettings
281
+ }), ...defineConfig({
282
+ files: ["**/*.ts", "**/*.tsx"],
283
+ plugins: { "@next/next": nextPlugin },
284
+ rules: {
285
+ ...warnToError({
286
+ ...nextPlugin.configs.recommended.rules,
287
+ ...nextPlugin.configs["core-web-vitals"].rules
288
+ }),
289
+ "@next/next/no-duplicate-head": "off",
290
+ "@next/next/no-html-link-for-pages": "off"
291
+ }
292
+ }), ...defineConfig({
293
+ files: [...ESLINT_TEST_FILE_PATTERNS],
294
+ rules: { "@typescript-eslint/require-await": "off" }
295
+ }));
296
+ const appendConfigs = normalizeAppendInput({ append: opts.append });
297
+ const knownRuleNames = collectKnownEslintRuleNames({
298
+ appendConfigs,
299
+ baseConfigs: configs$1
300
+ });
301
+ const normalizedRules = normalizeRulesOffInput({
302
+ label: "eslint.off",
303
+ value: opts.off
304
+ });
305
+ if (normalizedRules) {
306
+ const unknownRules = findUnknownRules({
307
+ knownRules: knownRuleNames,
308
+ rules: normalizedRules
309
+ });
310
+ if (unknownRules.length > 0) throw new Error(`eslint.off contains unknown eslint rules: ${unknownRules.join(", ")}`);
311
+ const overrideRules = {};
312
+ for (const [key, value] of Object.entries(normalizedRules)) overrideRules[key] = value;
313
+ configs$1.push({ rules: overrideRules });
314
+ }
315
+ for (const config of appendConfigs) {
316
+ const shared = getSharedAppendConfig({ config });
317
+ const unknownRules = shared && config.rules ? findUnknownRules({
318
+ knownRules: knownRuleNames,
319
+ rules: config.rules
320
+ }) : [];
321
+ if (unknownRules.length > 0) throw new Error(`overrides.eslint contains unknown eslint rules: ${unknownRules.join(", ")}`);
322
+ const sanitized = shared === null ? config : (() => {
323
+ const out = {};
324
+ if (shared.files !== void 0) out.files = shared.files;
325
+ if (shared.rules !== void 0) out.rules = shared.rules;
326
+ return out;
327
+ })();
328
+ configs$1.push(sanitized.rules ? {
329
+ ...sanitized,
330
+ rules: warnToError(sanitized.rules)
331
+ } : sanitized);
332
+ }
333
+ configs$1.push({
334
+ languageOptions: { parserOptions: {
335
+ projectService: true,
336
+ tsconfigRootDir: root
337
+ } },
338
+ linterOptions: { reportUnusedDisableDirectives: true }
339
+ });
340
+ return defineConfig(...configs$1);
341
+ };
342
+ const defaultConfig = eslintFactory();
343
+ //#endregion
344
+ export { defaultConfig as default, eslintFactory as eslint };
@@ -0,0 +1,102 @@
1
+ import { a as ESLINT_TEST_FILE_PATTERNS, i as DEFAULT_SHARED_IGNORE_PATTERNS } from "./constants-Cjkf4mJh.mjs";
2
+ import { $, Glob } from "bun";
3
+ //#region src/ignores.ts
4
+ /** biome-ignore-all lint/nursery/useNamedCaptureGroup: not needed */
5
+ const eslintDisableRe = /eslint-disable(?:-next-line)?\s+(.+?)(?:\s*\*\/|\s*$)/gu;
6
+ const oxlintDisableRe = /oxlint-disable(?:-next-line)?\s+(.+?)(?:\s*\*\/|\s*$)/gu;
7
+ const biomeIgnoreRe = /biome-ignore(?:-all)?\s+([\w/]+)/gu;
8
+ const tsIgnoreRe = /@ts-(?:ignore|expect-error|nocheck)/gu;
9
+ const trailingCommentRe = /\s*--.*$/u;
10
+ const trailingCloseRe = /\s*\*\/$/u;
11
+ const tsInlineRe = /@ts-(?:ignore|expect-error|nocheck)/u;
12
+ const DANGEROUS_PATTERNS = [
13
+ "no-unsafe-",
14
+ "no-non-null-assertion",
15
+ "@ts-ignore",
16
+ "@ts-nocheck",
17
+ "noNonNullAssertion"
18
+ ];
19
+ const DANGEROUS_NON_TEST_PATTERNS = [
20
+ "@ts-expect-error",
21
+ "no-explicit-any",
22
+ "noExplicitAny"
23
+ ];
24
+ const isTestFile = (f) => ESLINT_TEST_FILE_PATTERNS.some((p) => new Glob(p).match(f));
25
+ const isDangerousEntry = (rule, files) => {
26
+ if (DANGEROUS_PATTERNS.some((p) => rule.includes(p))) return true;
27
+ if (DANGEROUS_NON_TEST_PATTERNS.some((p) => rule.includes(p))) return files.some((f) => !isTestFile(f));
28
+ return false;
29
+ };
30
+ const parseRules = (line, re) => {
31
+ const rules = [];
32
+ re.lastIndex = 0;
33
+ let match = re.exec(line);
34
+ while (match) {
35
+ const raw = match[1];
36
+ if (raw) for (const r of raw.split(",")) {
37
+ const trimmed = r.trim().replace(trailingCommentRe, "").replace(trailingCloseRe, "");
38
+ if (trimmed) rules.push(trimmed);
39
+ }
40
+ match = re.exec(line);
41
+ }
42
+ return rules;
43
+ };
44
+ const scanIgnores = async (cwd) => {
45
+ const ruleFiles = /* @__PURE__ */ new Map();
46
+ const add = (rule, file) => {
47
+ const existing = ruleFiles.get(rule);
48
+ if (existing) existing.add(file);
49
+ else ruleFiles.set(rule, new Set([file]));
50
+ };
51
+ const lines = (await $`rg -n "^\s*//\s*eslint-disable|^\s*/\*\s*eslint-disable|^\s*//\s*oxlint-disable|^\s*/\*\s*oxlint-disable|^\s*/\*\*\s*biome-ignore|^\s*//\s*@ts-ignore|^\s*//\s*@ts-expect-error|^\s*//\s*@ts-nocheck|^\s*/\*\s*@ts-nocheck" ${cwd} -g '*.ts' -g '*.tsx' -g '!node_modules' -g '!*.d.ts' ${DEFAULT_SHARED_IGNORE_PATTERNS.flatMap((p) => ["-g", `!${p}`])}`.quiet().nothrow()).stdout.toString().trim().split("\n").filter(Boolean);
52
+ for (const line of lines) {
53
+ const firstColon = line.indexOf(":");
54
+ const secondColon = line.indexOf(":", firstColon + 1);
55
+ const file = line.slice(0, firstColon).replace(`${cwd}/`, "");
56
+ const content = secondColon > firstColon ? line.slice(secondColon + 1) : line.slice(firstColon + 1);
57
+ for (const rule of parseRules(content, eslintDisableRe)) add(rule, file);
58
+ for (const rule of parseRules(content, oxlintDisableRe)) add(rule, file);
59
+ for (const rule of parseRules(content, biomeIgnoreRe)) add(rule, file);
60
+ tsIgnoreRe.lastIndex = 0;
61
+ if (tsIgnoreRe.test(content)) {
62
+ const tsMatch = tsInlineRe.exec(content);
63
+ if (tsMatch) add(tsMatch[0], file);
64
+ }
65
+ }
66
+ return ruleFiles;
67
+ };
68
+ const formatIgnores = (ruleFiles, verbose) => {
69
+ if (ruleFiles.size === 0) return "no suppressions";
70
+ const entries = [...ruleFiles.entries()].map(([rule, files]) => ({
71
+ count: files.size,
72
+ files: [...files],
73
+ rule
74
+ })).toSorted((a, b) => b.count - a.count);
75
+ const dangerous = entries.filter((e) => isDangerousEntry(e.rule, e.files));
76
+ const safe = entries.filter((e) => !isDangerousEntry(e.rule, e.files));
77
+ const total = entries.reduce((sum, e) => sum + e.count, 0);
78
+ const lines = [];
79
+ if (dangerous.length > 0) {
80
+ const dangerousCount = dangerous.reduce((sum, e) => sum + e.count, 0);
81
+ lines.push(`!! ${dangerousCount} dangerous suppressions — fix these:`, "");
82
+ const maxRule = Math.max(...dangerous.map((e) => e.rule.length));
83
+ for (const e of dangerous) {
84
+ lines.push(` ${e.rule.padEnd(maxRule)} ${String(e.count).padStart(3)}`);
85
+ if (verbose) for (const f of e.files) lines.push(` ${f}`);
86
+ }
87
+ lines.push("");
88
+ }
89
+ lines.push(`${total} suppressions across ${entries.length} rules`, "");
90
+ const maxRule = Math.max(...entries.map((e) => e.rule.length));
91
+ for (const e of safe) {
92
+ lines.push(` ${e.rule.padEnd(maxRule)} ${String(e.count).padStart(3)}`);
93
+ if (verbose) for (const f of e.files) lines.push(` ${f}`);
94
+ }
95
+ return lines.join("\n");
96
+ };
97
+ const runIgnores = async (verbose) => {
98
+ const ruleFiles = await scanIgnores(process.cwd());
99
+ process.stdout.write(`${formatIgnores(ruleFiles, verbose)}\n`);
100
+ };
101
+ //#endregion
102
+ export { runIgnores };
@@ -0,0 +1,13 @@
1
+ import { r as SyncOptions, t as EslintImportAppendEntry } from "./lintmax-types-CJ7VY33l.mjs";
2
+
3
+ //#region src/index.d.ts
4
+ declare const sync: (options?: SyncOptions) => Promise<void>;
5
+ declare const eslintImport: ({
6
+ files,
7
+ from,
8
+ ignores,
9
+ name
10
+ }: Omit<EslintImportAppendEntry, "$lintmax">) => EslintImportAppendEntry;
11
+ declare const defineConfig: <T extends SyncOptions>(options: T) => T;
12
+ //#endregion
13
+ export { type EslintImportAppendEntry, type SyncOptions, defineConfig, eslintImport, sync };
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import { n as eslintImport, r as sync, t as defineConfig } from "./src-C8jQ6tK0.mjs";
2
+ export { defineConfig, eslintImport, sync };
@@ -0,0 +1,69 @@
1
+ import { Linter } from "eslint";
2
+
3
+ //#region src/lintmax-types.d.ts
4
+ interface BiomeOptions {
5
+ ignores?: PathListInput;
6
+ off?: RulesOffInput;
7
+ overrides?: {
8
+ includes: PathListInput;
9
+ off: RulesOffInput;
10
+ }[];
11
+ }
12
+ interface EslintImportAppendEntry {
13
+ $lintmax: 'eslint-import';
14
+ files?: PathListInput;
15
+ from: string;
16
+ ignores?: PathListInput;
17
+ name?: string;
18
+ }
19
+ interface EslintOptions {
20
+ append?: Linter.Config[];
21
+ ignores?: PathListInput;
22
+ off?: RulesOffInput;
23
+ tailwind?: TailwindOption;
24
+ tsconfigRootDir?: string;
25
+ }
26
+ interface JsonObject {
27
+ [key: string]: JsonValue;
28
+ }
29
+ type JsonPrimitive = boolean | null | number | string;
30
+ type JsonValue = JsonObject | JsonPrimitive | JsonValue[];
31
+ interface OxlintOptions {
32
+ ignores?: PathListInput;
33
+ off?: RulesOffInput;
34
+ overrides?: {
35
+ files: PathListInput;
36
+ off: RulesOffInput;
37
+ }[];
38
+ }
39
+ type PathListInput = readonly string[];
40
+ type RulesOffInput = readonly string[];
41
+ type SharedOverrideMapRuleOptions = {
42
+ biome: RulesOffInput;
43
+ eslint?: RulesOffInput;
44
+ oxlint?: RulesOffInput;
45
+ } | {
46
+ biome?: RulesOffInput;
47
+ eslint: RulesOffInput;
48
+ oxlint?: RulesOffInput;
49
+ } | {
50
+ biome?: RulesOffInput;
51
+ eslint?: RulesOffInput;
52
+ oxlint: RulesOffInput;
53
+ };
54
+ interface SyncOptions {
55
+ biome?: BiomeOptions;
56
+ comments?: boolean;
57
+ compact?: boolean;
58
+ eslint?: Omit<EslintOptions, 'append' | 'tailwind' | 'tsconfigRootDir'> & {
59
+ append?: readonly (EslintImportAppendEntry | JsonObject)[];
60
+ };
61
+ ignores?: PathListInput;
62
+ overrides?: Record<string, SharedOverrideMapRuleOptions>;
63
+ oxlint?: OxlintOptions;
64
+ tailwind?: TailwindOption;
65
+ tsconfigRootDir?: string;
66
+ }
67
+ type TailwindOption = boolean | string;
68
+ //#endregion
69
+ export { EslintOptions as n, SyncOptions as r, EslintImportAppendEntry as t };