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.
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +1569 -0
- package/dist/constants-Cjkf4mJh.mjs +105 -0
- package/dist/eslint.d.mts +9 -0
- package/dist/eslint.mjs +344 -0
- package/dist/ignores-BzTRqd-5.mjs +102 -0
- package/dist/index.d.mts +13 -0
- package/dist/index.mjs +2 -0
- package/dist/lintmax-types-CJ7VY33l.d.mts +69 -0
- package/dist/path-Cu_Nf2ct.mjs +168 -0
- package/dist/src-C8jQ6tK0.mjs +784 -0
- package/oxlintrc.json +104 -0
- package/package.json +83 -0
- package/tsconfig.json +27 -0
|
@@ -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 };
|
package/dist/eslint.mjs
ADDED
|
@@ -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 };
|
package/dist/index.d.mts
ADDED
|
@@ -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,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 };
|