@lzear/eslint-config 3.0.1 → 4.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,17 +1,11 @@
1
1
  import { Linter } from 'eslint';
2
- declare const CONFIG_OPTIONS: readonly ["perfectionist", "typescript", "vitest", "react", "node"];
3
- type ConfigOptionKeys = (typeof CONFIG_OPTIONS)[number];
4
- type ConfigOptionFlags = Record<ConfigOptionKeys, boolean>;
5
- export type ConfigOptions = Required<ConfigOptionFlags>;
6
- type RawConfigOptions = {
7
- extends?: Linter.Config[] | Linter.Config;
8
- } & Partial<ConfigOptionFlags>;
9
- declare const configGenerator: ({ extends: customExtends, ...rawConfig }?: RawConfigOptions) => Promise<Linter.Config[]>;
10
- export default configGenerator;
11
- export declare const defaultOptions: {
12
- perfectionist: boolean;
2
+
3
+ interface ConfigOptions {
4
+ node: boolean;
5
+ react: boolean;
13
6
  typescript: boolean;
14
7
  vitest: boolean;
15
- react: boolean;
16
- node: boolean;
17
- };
8
+ }
9
+ declare const configGenerator: (options?: Partial<ConfigOptions>) => Promise<Linter.Config[]>;
10
+
11
+ export { type ConfigOptions, configGenerator as default };
package/dist/index.js ADDED
@@ -0,0 +1,622 @@
1
+ // configs/a11y.ts
2
+ import jsxA11y from "eslint-plugin-jsx-a11y";
3
+ var a11y = (config) => {
4
+ if (!config.react) {
5
+ return {};
6
+ }
7
+ const files = ["**/*.jsx"];
8
+ if (config.typescript) {
9
+ files.push("**/*.tsx");
10
+ }
11
+ return {
12
+ name: "lzear/a11y",
13
+ files,
14
+ plugins: {
15
+ "jsx-a11y": jsxA11y
16
+ },
17
+ rules: {
18
+ ...jsxA11y.flatConfigs.recommended.rules
19
+ }
20
+ };
21
+ };
22
+
23
+ // configs/core.ts
24
+ import js from "@eslint/js";
25
+ import eslintCommentsPlugin from "@eslint-community/eslint-plugin-eslint-comments";
26
+ import deMorganPlugin from "eslint-plugin-de-morgan";
27
+ import importXPlugin from "eslint-plugin-import-x";
28
+ import preferArrowPlugin from "eslint-plugin-prefer-arrow";
29
+ import promisePlugin from "eslint-plugin-promise";
30
+ import regexpPlugin from "eslint-plugin-regexp";
31
+ import sonarjsPlugin from "eslint-plugin-sonarjs";
32
+ import unicornPlugin from "eslint-plugin-unicorn";
33
+ import globals from "globals";
34
+
35
+ // plugin/rules/major-version-only.ts
36
+ var DEP_FIELDS = /* @__PURE__ */ new Set([
37
+ "dependencies",
38
+ "devDependencies",
39
+ "optionalDependencies",
40
+ "peerDependencies"
41
+ ]);
42
+ var VERSION_RE = /^([~^])(\d+)\.(\d+)(?:\.\d+)?$/;
43
+ var simplify = (version) => {
44
+ const match = VERSION_RE.exec(version);
45
+ if (!match) {
46
+ return null;
47
+ }
48
+ const [, operator, major, minor] = match;
49
+ if (major === "0" && operator === "^") {
50
+ const target2 = `^0.${minor}`;
51
+ return target2 === version ? null : target2;
52
+ }
53
+ const target = `${operator}${major}`;
54
+ return target === version ? null : target;
55
+ };
56
+ var isIgnored = (pkgName, ignore) => ignore.some(
57
+ (pattern) => pattern instanceof RegExp ? pattern.test(pkgName) : pattern === pkgName
58
+ );
59
+ var majorVersionOnly = {
60
+ meta: {
61
+ type: "suggestion",
62
+ docs: {
63
+ description: "Enforce major-only version specifiers in package.json"
64
+ },
65
+ fixable: "code",
66
+ schema: [
67
+ {
68
+ type: "object",
69
+ properties: {
70
+ ignore: {
71
+ type: "array",
72
+ items: {
73
+ oneOf: [
74
+ { type: "string" },
75
+ {
76
+ type: "object",
77
+ properties: { regex: { type: "string" } },
78
+ required: ["regex"],
79
+ additionalProperties: false
80
+ }
81
+ ]
82
+ }
83
+ }
84
+ },
85
+ additionalProperties: false
86
+ }
87
+ ],
88
+ messages: {
89
+ useMajorOnly: 'Use "{{suggested}}" instead of "{{current}}"'
90
+ }
91
+ },
92
+ create: (context) => {
93
+ const opts = context.options[0];
94
+ const ignore = (opts?.ignore ?? []).map(
95
+ (item) => typeof item === "string" ? item : new RegExp(item.regex)
96
+ );
97
+ return {
98
+ JSONProperty: (rawNode) => {
99
+ const node2 = rawNode;
100
+ const keyName = node2.key.type === "JSONLiteral" ? String(node2.key.value) : node2.key.name ?? "";
101
+ if (!DEP_FIELDS.has(keyName)) {
102
+ return;
103
+ }
104
+ const depsNode = node2.value;
105
+ if (depsNode.type !== "JSONObjectExpression") {
106
+ return;
107
+ }
108
+ for (const prop of depsNode.properties) {
109
+ const pkgName = prop.key.type === "JSONLiteral" ? String(prop.key.value) : prop.key.name ?? "";
110
+ if (isIgnored(pkgName, ignore)) {
111
+ continue;
112
+ }
113
+ const versionNode = prop.value;
114
+ if (versionNode.type !== "JSONLiteral") {
115
+ continue;
116
+ }
117
+ const version = versionNode.value;
118
+ if (typeof version !== "string") {
119
+ continue;
120
+ }
121
+ const suggested = simplify(version);
122
+ if (!suggested) {
123
+ continue;
124
+ }
125
+ context.report({
126
+ node: versionNode,
127
+ messageId: "useMajorOnly",
128
+ data: { current: version, suggested },
129
+ fix: (fixer) => fixer.replaceText(versionNode, JSON.stringify(suggested))
130
+ });
131
+ }
132
+ }
133
+ };
134
+ }
135
+ };
136
+
137
+ // plugin/rules/prefer-relative-imports.ts
138
+ import path from "path";
139
+
140
+ // utils.ts
141
+ var interopDefault = async (m) => {
142
+ const resolved = await m;
143
+ const mod = resolved.default ?? resolved;
144
+ if (mod.__esModule) {
145
+ const inner = mod.default;
146
+ if (inner !== void 0) return inner;
147
+ }
148
+ return mod;
149
+ };
150
+
151
+ // plugin/rules/prefer-relative-imports.ts
152
+ var moduleVisitor = await interopDefault(
153
+ import("eslint-module-utils/moduleVisitor")
154
+ );
155
+ var resolve = await interopDefault(import("eslint-module-utils/resolve"));
156
+ var toRelative = (from, importPath, context) => {
157
+ const resolved = resolve(importPath, context);
158
+ if (!resolved) {
159
+ return null;
160
+ }
161
+ let rel = path.relative(path.dirname(from), resolved);
162
+ if (!rel) {
163
+ return null;
164
+ }
165
+ if (!rel.startsWith(".")) {
166
+ rel = `./${rel}`;
167
+ }
168
+ if (!path.extname(importPath)) {
169
+ const ext = path.extname(rel);
170
+ if (ext) {
171
+ const withoutExt = rel.slice(0, -ext.length);
172
+ rel = withoutExt.endsWith("/index") ? withoutExt.slice(0, -"/index".length) || "." : withoutExt;
173
+ if (!rel.startsWith(".")) {
174
+ rel = `./${rel}`;
175
+ }
176
+ }
177
+ }
178
+ return rel;
179
+ };
180
+ var countParentPrefixes = (p) => {
181
+ const match = /^(?:\.\.\/)+/.exec(p);
182
+ return match ? match[0].length / 3 : 0;
183
+ };
184
+ var preferRelativeImports = {
185
+ meta: {
186
+ type: "suggestion",
187
+ docs: { description: "Prefer relative imports when a shorter path exists" },
188
+ fixable: "code",
189
+ schema: [
190
+ {
191
+ type: "object",
192
+ properties: { maxParentPrefixes: { type: "number", minimum: 0 } },
193
+ additionalProperties: false
194
+ }
195
+ ],
196
+ messages: {
197
+ preferRelative: 'Use relative import "{{relative}}" instead of "{{original}}"'
198
+ }
199
+ },
200
+ create: (context) => {
201
+ const opts = context.options[0];
202
+ const maxParentPrefixes = opts?.maxParentPrefixes ?? 1;
203
+ const filename = context.physicalFilename;
204
+ const check = (source) => {
205
+ const importPath = source.value;
206
+ if (typeof importPath !== "string" || importPath.startsWith(".")) {
207
+ return;
208
+ }
209
+ const relative = toRelative(filename, importPath, context);
210
+ if (!relative || relative.length >= importPath.length) {
211
+ return;
212
+ }
213
+ if (countParentPrefixes(relative) > maxParentPrefixes) {
214
+ return;
215
+ }
216
+ context.report({
217
+ node: source,
218
+ messageId: "preferRelative",
219
+ data: { relative, original: importPath },
220
+ fix: (fixer) => fixer.replaceText(source, JSON.stringify(relative))
221
+ });
222
+ };
223
+ return moduleVisitor(check, { commonjs: false });
224
+ }
225
+ };
226
+
227
+ // plugin/index.ts
228
+ var plugin = {
229
+ meta: {
230
+ name: "lzear",
231
+ version: "1.0.0"
232
+ },
233
+ rules: {
234
+ "major-version-only": majorVersionOnly,
235
+ "prefer-relative-imports": preferRelativeImports
236
+ }
237
+ };
238
+
239
+ // configs/core.ts
240
+ var core = () => {
241
+ const files = [
242
+ "**/*.js",
243
+ "**/*.cjs",
244
+ "**/*.mjs",
245
+ "**/*.ts",
246
+ "**/*.cts",
247
+ "**/*.mts",
248
+ "**/*.jsx",
249
+ "**/*.tsx"
250
+ ];
251
+ const sonarRules = (sonarjsPlugin.configs?.recommended).rules;
252
+ const rules = {
253
+ ...js.configs.recommended.rules,
254
+ ...eslintCommentsPlugin.configs.recommended.rules,
255
+ "@eslint-community/eslint-comments/disable-enable-pair": 0,
256
+ "@eslint-community/eslint-comments/no-unlimited-disable": 0,
257
+ ...deMorganPlugin.configs.recommended.rules,
258
+ ...importXPlugin.configs.recommended.rules,
259
+ "import-x/no-named-as-default-member": 0,
260
+ "import-x/no-unresolved": 0,
261
+ "import-x/order": [
262
+ 2,
263
+ {
264
+ alphabetize: { order: "asc", orderImportKind: "asc" },
265
+ "newlines-between": "never"
266
+ }
267
+ ],
268
+ "lzear/prefer-relative-imports": 2,
269
+ "prefer-arrow/prefer-arrow-functions": [
270
+ 2,
271
+ {
272
+ classPropertiesAllowed: false,
273
+ disallowPrototype: true,
274
+ singleReturnOnly: false
275
+ }
276
+ ],
277
+ ...promisePlugin.configs.recommended.rules,
278
+ ...regexpPlugin.configs.recommended.rules,
279
+ ...sonarRules,
280
+ "sonarjs/fixme-tag": 0,
281
+ "sonarjs/no-invariant-returns": 0,
282
+ // annoying
283
+ "sonarjs/no-os-command-from-path": 0,
284
+ // annoying
285
+ "sonarjs/os-command": 0,
286
+ // annoying
287
+ "sonarjs/prefer-read-only-props": 0,
288
+ // annoying
289
+ "sonarjs/pseudo-random": 0,
290
+ "sonarjs/todo-tag": 0,
291
+ "sonarjs/void-use": 0,
292
+ ...unicornPlugin.configs.recommended.rules,
293
+ "unicorn/no-abusive-eslint-disable": 0,
294
+ "unicorn/no-null": 0,
295
+ "unicorn/prevent-abbreviations": 0
296
+ };
297
+ return {
298
+ name: "lzear/core",
299
+ files,
300
+ languageOptions: {
301
+ globals: {
302
+ ...globals.browser,
303
+ ...globals.es2025,
304
+ ...globals.node
305
+ },
306
+ parserOptions: {
307
+ ecmaVersion: "latest",
308
+ sourceType: "module"
309
+ }
310
+ },
311
+ plugins: {
312
+ "@eslint-community/eslint-comments": eslintCommentsPlugin,
313
+ "de-morgan": deMorganPlugin,
314
+ "import-x": importXPlugin,
315
+ lzear: plugin,
316
+ "prefer-arrow": preferArrowPlugin,
317
+ promise: promisePlugin,
318
+ regexp: regexpPlugin,
319
+ sonarjs: sonarjsPlugin,
320
+ unicorn: unicornPlugin
321
+ },
322
+ rules,
323
+ settings: {
324
+ // import-x uses import-x/resolver; eslint-module-utils (used by lzear/prefer-relative-imports) reads import/resolver
325
+ "import-x/resolver": { typescript: true },
326
+ "import/resolver": { typescript: true }
327
+ }
328
+ };
329
+ };
330
+
331
+ // configs/ignores.ts
332
+ var ignores = [
333
+ "**/.eslint-config-inspector/**",
334
+ "**/vite.config.*.timestamp-*",
335
+ "**/.vitepress/cache/**",
336
+ "**/node_modules/**",
337
+ "**/coverage/**",
338
+ "**/.history/**",
339
+ "**/.netlify/**",
340
+ "**/.vercel/**",
341
+ "**/.output/**",
342
+ "**/output/**",
343
+ "**/.cache/**",
344
+ "**/.temp/**",
345
+ "**/build/**",
346
+ "**/.nuxt/**",
347
+ "**/.next/**",
348
+ "**/dist/**",
349
+ "**/temp/**",
350
+ "**/.tmp/**",
351
+ "**/tmp/**"
352
+ ];
353
+
354
+ // configs/node.ts
355
+ var node = async (config) => {
356
+ if (!config.node) {
357
+ return {};
358
+ }
359
+ const nodePlugin = await interopDefault(import("eslint-plugin-n"));
360
+ const files = ["**/*.js", "**/*.cjs", "**/*.mjs"];
361
+ if (config.typescript) {
362
+ files.push("**/*.ts", "**/*.cts", "**/*.mts");
363
+ }
364
+ if (config.react) {
365
+ files.push("**/*.jsx");
366
+ if (config.typescript) {
367
+ files.push("**/*.tsx");
368
+ }
369
+ }
370
+ return {
371
+ name: "lzear/node",
372
+ files,
373
+ plugins: {
374
+ n: nodePlugin
375
+ },
376
+ rules: {
377
+ ...nodePlugin.configs["flat/recommended"].rules,
378
+ "n/hashbang": 0,
379
+ // n resolver doesn't understand TypeScript imports; TypeScript handles this
380
+ "n/no-extraneous-import": 0,
381
+ "n/no-missing-import": 0,
382
+ "n/no-process-exit": 0
383
+ },
384
+ settings: {
385
+ // Align with the minimum version where import.meta.dirname is stable
386
+ node: { version: ">=22.16.0" }
387
+ }
388
+ };
389
+ };
390
+
391
+ // configs/package-json.ts
392
+ import * as packageJsonPlugin from "eslint-plugin-package-json";
393
+ import * as jsoncParser from "jsonc-eslint-parser";
394
+ var packageJson = () => ({
395
+ name: "lzear/package-json",
396
+ files: ["**/package.json"],
397
+ plugins: {
398
+ lzear: plugin,
399
+ "package-json": packageJsonPlugin
400
+ },
401
+ languageOptions: {
402
+ parser: jsoncParser
403
+ },
404
+ rules: {
405
+ ...packageJsonPlugin.configs.recommended.rules,
406
+ "lzear/major-version-only": 2,
407
+ "package-json/repository-shorthand": [2, { form: "shorthand" }]
408
+ }
409
+ });
410
+
411
+ // configs/prettier.ts
412
+ import prettierConfig from "eslint-plugin-prettier/recommended";
413
+ var prettier = {
414
+ ...prettierConfig,
415
+ rules: {
416
+ ...prettierConfig.rules,
417
+ "prettier/prettier": [
418
+ 2,
419
+ {
420
+ semi: false,
421
+ singleQuote: true
422
+ }
423
+ ]
424
+ }
425
+ };
426
+
427
+ // configs/react.ts
428
+ var react = async (config) => {
429
+ if (!config.react) {
430
+ return {};
431
+ }
432
+ const [
433
+ reactCompilerPlugin,
434
+ reactHooksPlugin,
435
+ reactPerfPlugin,
436
+ reactPlugin,
437
+ nextPlugin,
438
+ reactXPlugin,
439
+ reactDomPlugin,
440
+ reactWebApiPlugin
441
+ ] = await Promise.all([
442
+ interopDefault(import("eslint-plugin-react-compiler")),
443
+ interopDefault(import("eslint-plugin-react-hooks")),
444
+ interopDefault(import("eslint-plugin-react-perf")),
445
+ interopDefault(import("eslint-plugin-react")),
446
+ interopDefault(import("@next/eslint-plugin-next")),
447
+ interopDefault(import("eslint-plugin-react-x")),
448
+ interopDefault(import("eslint-plugin-react-dom")),
449
+ interopDefault(import("eslint-plugin-react-web-api"))
450
+ ]);
451
+ const files = ["**/*.jsx"];
452
+ if (config.typescript) {
453
+ files.push("**/*.tsx");
454
+ }
455
+ return {
456
+ name: "lzear/react",
457
+ files,
458
+ plugins: {
459
+ "@next/next": nextPlugin,
460
+ react: reactPlugin,
461
+ "react-compiler": reactCompilerPlugin,
462
+ "react-dom": reactDomPlugin,
463
+ "react-hooks": reactHooksPlugin,
464
+ "react-perf": reactPerfPlugin,
465
+ "react-web-api": reactWebApiPlugin,
466
+ "react-x": reactXPlugin
467
+ },
468
+ rules: {
469
+ "react-compiler/react-compiler": 2,
470
+ ...reactHooksPlugin.configs.recommended.rules,
471
+ ...reactPerfPlugin.configs.recommended.rules,
472
+ "react-perf/jsx-no-new-function-as-prop": 0,
473
+ "react-perf/jsx-no-new-object-as-prop": 0,
474
+ ...reactPlugin.configs.recommended.rules,
475
+ ...nextPlugin.configs.recommended.rules,
476
+ ...nextPlugin.configs["core-web-vitals"].rules,
477
+ "react/react-in-jsx-scope": 0,
478
+ "react/no-unknown-property": ["error", { ignore: ["jsx", "global"] }],
479
+ ...config.typescript ? reactXPlugin.configs["recommended-typescript"].rules : reactXPlugin.configs.recommended.rules,
480
+ ...reactDomPlugin.configs.recommended.rules,
481
+ ...reactWebApiPlugin.configs.recommended.rules
482
+ },
483
+ settings: {
484
+ react: {
485
+ fragment: "Fragment",
486
+ pragma: "React",
487
+ version: "19.0"
488
+ },
489
+ "react-x": {
490
+ importSource: "react",
491
+ polymorphicPropName: "as",
492
+ version: "19.0"
493
+ }
494
+ }
495
+ };
496
+ };
497
+
498
+ // configs/typescript.ts
499
+ import eslint from "@eslint/js";
500
+ import { defineConfig } from "eslint/config";
501
+ import tseslint from "typescript-eslint";
502
+ var typescript = async (config) => {
503
+ if (!config.typescript) {
504
+ return [];
505
+ }
506
+ const { parser: typescriptParser } = await interopDefault(
507
+ import("typescript-eslint")
508
+ );
509
+ const files = ["**/*.ts", "**/*.tsx", "**/*.cts", "**/*.mts"];
510
+ return defineConfig({
511
+ name: "lzear/typescript",
512
+ extends: [
513
+ eslint.configs.recommended,
514
+ tseslint.configs.strictTypeChecked,
515
+ tseslint.configs.stylisticTypeChecked
516
+ ],
517
+ files,
518
+ rules: {
519
+ "@typescript-eslint/no-unnecessary-condition": [
520
+ 2,
521
+ { allowConstantLoopConditions: "only-allowed-literals" }
522
+ ],
523
+ "@typescript-eslint/no-unused-expressions": [2, { allowTernary: true }],
524
+ "@typescript-eslint/restrict-template-expressions": [
525
+ 2,
526
+ { allowNumber: true }
527
+ ]
528
+ },
529
+ languageOptions: {
530
+ parser: typescriptParser,
531
+ parserOptions: {
532
+ ecmaFeatures: { jsx: config.react },
533
+ ecmaVersion: "latest",
534
+ projectService: true,
535
+ sourceType: "module",
536
+ tsconfigRootDir: process.cwd()
537
+ }
538
+ }
539
+ });
540
+ };
541
+
542
+ // configs/vitest.ts
543
+ var vitest = async (config) => {
544
+ if (!config.vitest) {
545
+ return {};
546
+ }
547
+ const vitestPlugin = await interopDefault(import("@vitest/eslint-plugin"));
548
+ const files = [
549
+ "**/test/*.js",
550
+ "**/test/*.cjs",
551
+ "**/test/*.mjs",
552
+ "**/*.test.js",
553
+ "**/*.test.cjs",
554
+ "**/*.test.mjs"
555
+ ];
556
+ if (config.typescript) {
557
+ files.push(
558
+ "**/test/*.ts",
559
+ "**/test/*.cts",
560
+ "**/test/*.mts",
561
+ "**/*.test.ts",
562
+ "**/*.test.cts",
563
+ "**/*.test.mts"
564
+ );
565
+ }
566
+ if (config.react) {
567
+ files.push("**/test/*.jsx", "**/*.test.jsx");
568
+ if (config.typescript) {
569
+ files.push("**/test/*.tsx", "**/*.test.tsx");
570
+ }
571
+ }
572
+ return {
573
+ name: "lzear/vitest",
574
+ files,
575
+ plugins: {
576
+ vitest: vitestPlugin
577
+ },
578
+ rules: {
579
+ ...vitestPlugin.configs.recommended.rules,
580
+ "vitest/consistent-test-it": [2, { fn: "it" }],
581
+ // Using Vitest globals mode — explicit imports not required
582
+ "vitest/prefer-importing-vitest-globals": 0
583
+ },
584
+ settings: {
585
+ vitest: {
586
+ typecheck: true
587
+ }
588
+ }
589
+ };
590
+ };
591
+
592
+ // index.ts
593
+ var configGenerator = async (options = {}) => {
594
+ const config = {
595
+ node: true,
596
+ react: true,
597
+ typescript: true,
598
+ vitest: true,
599
+ ...options
600
+ };
601
+ const [reactConfig, nodeConfig, typescriptConfig, vitestConfig] = await Promise.all([
602
+ react(config),
603
+ node(config),
604
+ typescript(config),
605
+ vitest(config)
606
+ ]);
607
+ return [
608
+ { name: "lzear/ignores", ignores },
609
+ core(),
610
+ a11y(config),
611
+ reactConfig,
612
+ nodeConfig,
613
+ typescriptConfig,
614
+ vitestConfig,
615
+ packageJson(),
616
+ prettier
617
+ ].flat();
618
+ };
619
+ var index_default = configGenerator;
620
+ export {
621
+ index_default as default
622
+ };