@ncontiero/eslint-config 7.1.0 → 7.3.0-beta.0

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.mjs ADDED
@@ -0,0 +1,1642 @@
1
+ import createCommand from "eslint-plugin-command/config";
2
+ import pluginComments from "@eslint-community/eslint-plugin-eslint-comments";
3
+ import pluginAntfu from "eslint-plugin-antfu";
4
+ import pluginDeMorgan from "eslint-plugin-de-morgan";
5
+ import pluginImport from "eslint-plugin-import-x";
6
+ import pluginNode from "eslint-plugin-n";
7
+ import pluginPerfectionist from "eslint-plugin-perfectionist";
8
+ import pluginUnicorn from "eslint-plugin-unicorn";
9
+ import pluginUnusedImports from "eslint-plugin-unused-imports";
10
+ import { isPackageExists } from "local-pkg";
11
+ import globals from "globals";
12
+ import { mergeProcessors, processorPassThrough } from "eslint-merge-processors";
13
+ import { configs } from "eslint-plugin-regexp";
14
+ import path from "node:path";
15
+ //#region src/configs/command.ts
16
+ function command() {
17
+ return [{
18
+ ...createCommand(),
19
+ name: "ncontiero/command/rules"
20
+ }];
21
+ }
22
+ //#endregion
23
+ //#region src/configs/comments.ts
24
+ function comments() {
25
+ return [{
26
+ name: "ncontiero/comments/rules",
27
+ plugins: { "eslint-comments": pluginComments },
28
+ rules: {
29
+ "eslint-comments/disable-enable-pair": ["error", { allowWholeFile: true }],
30
+ "eslint-comments/no-aggregating-enable": "error",
31
+ "eslint-comments/no-duplicate-disable": "error",
32
+ "eslint-comments/no-unlimited-disable": "error",
33
+ "eslint-comments/no-unused-enable": "error"
34
+ }
35
+ }];
36
+ }
37
+ //#endregion
38
+ //#region src/configs/de-morgan.ts
39
+ function deMorgan() {
40
+ return [{
41
+ ...pluginDeMorgan.configs.recommended,
42
+ name: "ncontiero/de-morgan"
43
+ }];
44
+ }
45
+ //#endregion
46
+ //#region src/globs.ts
47
+ const GLOB_SRC_EXT = "?([cm])[jt]s?(x)";
48
+ const GLOB_SRC = "**/*.?([cm])[jt]s?(x)";
49
+ const GLOB_JS = "**/*.?([cm])js";
50
+ const GLOB_JSX = "**/*.?([cm])jsx";
51
+ const GLOB_TS = "**/*.?([cm])ts";
52
+ const GLOB_TSX = "**/*.?([cm])tsx";
53
+ const GLOB_REACT = "**/*.?([cm])?(j|t)sx";
54
+ const GLOB_STYLE = "**/*.{c,le,sc}ss";
55
+ const GLOB_CSS = "**/*.css";
56
+ const GLOB_POSTCSS = "**/*.{p,post}css";
57
+ const GLOB_LESS = "**/*.less";
58
+ const GLOB_SCSS = "**/*.scss";
59
+ const GLOB_JSON = "**/*.json";
60
+ const GLOB_JSON5 = "**/*.json5";
61
+ const GLOB_JSONC = "**/*.jsonc";
62
+ const GLOB_MARKDOWN = "**/*.md";
63
+ const GLOB_MARKDOWN_IN_MARKDOWN = "**/*.md/*.md";
64
+ const GLOB_YAML = "**/*.y?(a)ml";
65
+ const GLOB_TOML = "**/*.toml";
66
+ const GLOB_HTML = "**/*.htm?(l)";
67
+ const GLOB_MARKDOWN_CODE = `${GLOB_MARKDOWN}/${GLOB_SRC}`;
68
+ const GLOB_ALL_SRC = [
69
+ GLOB_SRC,
70
+ GLOB_STYLE,
71
+ GLOB_JSON,
72
+ GLOB_JSON5,
73
+ GLOB_MARKDOWN,
74
+ GLOB_YAML,
75
+ GLOB_HTML
76
+ ];
77
+ const GLOB_NODE_MODULES = "**/node_modules";
78
+ const GLOB_DIST = "**/dist";
79
+ const GLOB_LOCKFILE = [
80
+ "**/package-lock.json",
81
+ "**/yarn.lock",
82
+ "**/pnpm-lock.yaml",
83
+ "**/bun.lockb"
84
+ ];
85
+ const GLOB_EXCLUDE = [
86
+ GLOB_NODE_MODULES,
87
+ GLOB_DIST,
88
+ ...GLOB_LOCKFILE,
89
+ "**/output",
90
+ "**/coverage",
91
+ "**/temp",
92
+ "**/.temp",
93
+ "**/tmp",
94
+ "**/.tmp",
95
+ "**/.history",
96
+ "**/fixtures",
97
+ "**/.git",
98
+ "**/.vitepress/cache",
99
+ "**/.nuxt",
100
+ "**/.next",
101
+ "**/.vercel",
102
+ "**/.contentlayer",
103
+ "**/.changeset",
104
+ "**/.idea",
105
+ "**/.cache",
106
+ "**/.output",
107
+ "**/.vite-inspect",
108
+ "**/.yarn",
109
+ "**/next-env.d.ts",
110
+ "**/.nitro",
111
+ "**/CHANGELOG*.md",
112
+ "**/*.min.*",
113
+ "**/LICENSE*",
114
+ "**/__snapshots__",
115
+ "**/auto-import?(s).d.ts",
116
+ "**/components.d.ts"
117
+ ];
118
+ //#endregion
119
+ //#region src/utils.ts
120
+ const isCwdInScope = isPackageExists("@ncontiero/eslint-config");
121
+ const parserPlain = {
122
+ meta: { name: "parser-plain" },
123
+ parseForESLint: (code) => ({
124
+ ast: {
125
+ body: [],
126
+ comments: [],
127
+ loc: {
128
+ end: code.length,
129
+ start: 0
130
+ },
131
+ range: [0, code.length],
132
+ tokens: [],
133
+ type: "Program"
134
+ },
135
+ scopeManager: null,
136
+ services: { isPlain: true },
137
+ visitorKeys: { Program: [] }
138
+ })
139
+ };
140
+ /**
141
+ * Combine array and non-array configs into a single array.
142
+ */
143
+ async function combine(...configs) {
144
+ return (await Promise.all(configs)).flat();
145
+ }
146
+ async function interopDefault(m) {
147
+ const resolved = await m;
148
+ return resolved.default || resolved;
149
+ }
150
+ function ensurePackages(packages) {
151
+ if (process.env.CI || !process.stdout.isTTY || !isCwdInScope) return;
152
+ const nonExistingPackages = packages.filter((i) => i && !isPackageExists(i));
153
+ if (nonExistingPackages.length === 0) return;
154
+ throw new Error(`This package(s) are required for this config: ${nonExistingPackages.join(", ")}. Please install them.`);
155
+ }
156
+ async function composer(...items) {
157
+ let configs = [];
158
+ const flatResolved = (await Promise.all(items)).flat();
159
+ configs = [...configs, ...flatResolved];
160
+ return configs;
161
+ }
162
+ function toArray(value) {
163
+ return Array.isArray(value) ? value : [value];
164
+ }
165
+ //#endregion
166
+ //#region src/configs/html.ts
167
+ async function html(options) {
168
+ const { overrides = {} } = options;
169
+ const [eslintParserHTML, eslintPluginHTML] = await Promise.all([interopDefault(import("@html-eslint/parser")), interopDefault(import("@html-eslint/eslint-plugin"))]);
170
+ const templateEngineSyntax = options.templateEngineSyntax ?? {
171
+ "{{": "}}",
172
+ "{%": "%}"
173
+ };
174
+ return [{
175
+ files: [GLOB_HTML],
176
+ languageOptions: {
177
+ parser: eslintParserHTML,
178
+ parserOptions: { templateEngineSyntax }
179
+ },
180
+ name: "ncontiero/html/setup",
181
+ plugins: { html: eslintPluginHTML }
182
+ }, {
183
+ files: [GLOB_HTML],
184
+ name: "ncontiero/html/rules",
185
+ rules: {
186
+ "html/attrs-newline": ["warn", { ifAttrsMoreThan: 5 }],
187
+ "html/element-newline": ["warn", { inline: [`$inline`] }],
188
+ "html/indent": ["warn", 2],
189
+ "html/lowercase": "warn",
190
+ "html/no-aria-hidden-body": "error",
191
+ "html/no-aria-hidden-on-focusable": "warn",
192
+ "html/no-duplicate-attrs": "error",
193
+ "html/no-duplicate-class": "warn",
194
+ "html/no-duplicate-id": "error",
195
+ "html/no-duplicate-in-head": "error",
196
+ "html/no-empty-headings": "warn",
197
+ "html/no-extra-spacing-attrs": ["warn", {
198
+ disallowInAssignment: true,
199
+ disallowMissing: true,
200
+ enforceBeforeSelfClose: true
201
+ }],
202
+ "html/no-extra-spacing-text": "warn",
203
+ "html/no-heading-inside-button": "warn",
204
+ "html/no-ineffective-attrs": "warn",
205
+ "html/no-invalid-entity": "warn",
206
+ "html/no-invalid-role": "warn",
207
+ "html/no-multiple-empty-lines": ["warn", { max: 1 }],
208
+ "html/no-multiple-h1": "error",
209
+ "html/no-nested-interactive": "error",
210
+ "html/no-non-scalable-viewport": "warn",
211
+ "html/no-obsolete-tags": "error",
212
+ "html/no-redundant-role": "warn",
213
+ "html/no-script-style-type": "warn",
214
+ "html/no-target-blank": "error",
215
+ "html/no-trailing-spaces": "warn",
216
+ "html/quotes": "warn",
217
+ "html/require-button-type": "warn",
218
+ "html/require-closing-tags": ["warn", { selfClosing: "always" }],
219
+ "html/require-doctype": "error",
220
+ "html/require-form-method": "warn",
221
+ "html/require-img-alt": "warn",
222
+ "html/require-lang": "warn",
223
+ "html/require-li-container": "warn",
224
+ "html/require-meta-charset": "warn",
225
+ "html/require-meta-viewport": "warn",
226
+ "html/require-title": "warn",
227
+ "html/sort-attrs": ["warn", { priority: [
228
+ "name",
229
+ "content",
230
+ "id",
231
+ "type",
232
+ { pattern: "data-.*" },
233
+ "class",
234
+ "style"
235
+ ] }],
236
+ "html/use-baseline": "error",
237
+ ...overrides
238
+ }
239
+ }];
240
+ }
241
+ //#endregion
242
+ //#region src/configs/ignores.ts
243
+ function ignores(userIgnores = []) {
244
+ return [{
245
+ ignores: [...GLOB_EXCLUDE, ...userIgnores],
246
+ name: "ncontiero/global-ignores"
247
+ }];
248
+ }
249
+ //#endregion
250
+ //#region src/configs/imports.ts
251
+ function imports(options = {}) {
252
+ const { nextJs = false } = options;
253
+ return [{
254
+ name: "ncontiero/imports/rules",
255
+ plugins: {
256
+ antfu: pluginAntfu,
257
+ import: pluginImport
258
+ },
259
+ rules: {
260
+ "antfu/import-dedupe": "error",
261
+ "import/first": "error",
262
+ "import/newline-after-import": ["error", { count: 1 }],
263
+ "import/no-default-export": "error",
264
+ "import/no-duplicates": "error",
265
+ "import/no-mutable-exports": "error",
266
+ "import/no-named-default": "error",
267
+ "import/no-self-import": "error",
268
+ "import/no-webpack-loader-syntax": "error"
269
+ }
270
+ }, {
271
+ files: [
272
+ `**/*config*.${GLOB_SRC_EXT}`,
273
+ `**/{views,pages,routes,middleware,plugins,api,app}/${GLOB_SRC}`,
274
+ nextJs ? "{,src/}{middleware,proxy}.{ts,js}" : "",
275
+ `**/{index,vite,esbuild,rollup,rolldown,webpack,rspack}.ts`,
276
+ "**/*.d.ts",
277
+ `${GLOB_MARKDOWN}/**`,
278
+ "**/.prettierrc*"
279
+ ],
280
+ name: "ncontiero/imports/allow-default-export",
281
+ rules: { "import/no-default-export": "off" }
282
+ }];
283
+ }
284
+ //#endregion
285
+ //#region src/configs/javascript.ts
286
+ const restrictedSyntaxJs = [
287
+ "ForInStatement",
288
+ "LabeledStatement",
289
+ "WithStatement"
290
+ ];
291
+ function javascript(options = {}) {
292
+ const { overrides = {} } = options;
293
+ return [
294
+ {
295
+ languageOptions: {
296
+ ecmaVersion: "latest",
297
+ globals: {
298
+ ...globals.browser,
299
+ ...globals.es2026,
300
+ ...globals.node,
301
+ document: "readonly",
302
+ navigator: "readonly",
303
+ window: "readonly"
304
+ },
305
+ parserOptions: {
306
+ ecmaFeatures: { jsx: true },
307
+ ecmaVersion: "latest",
308
+ sourceType: "module"
309
+ },
310
+ sourceType: "module"
311
+ },
312
+ linterOptions: { reportUnusedDisableDirectives: true },
313
+ name: "ncontiero/javascript/rules",
314
+ plugins: {
315
+ antfu: pluginAntfu,
316
+ "unused-imports": pluginUnusedImports
317
+ },
318
+ rules: {
319
+ "array-callback-return": "error",
320
+ "block-scoped-var": "error",
321
+ "constructor-super": "error",
322
+ "dot-notation": "warn",
323
+ eqeqeq: ["error", "smart"],
324
+ "for-direction": "error",
325
+ "getter-return": "error",
326
+ "no-alert": "warn",
327
+ "no-async-promise-executor": "error",
328
+ "no-case-declarations": "error",
329
+ "no-class-assign": "error",
330
+ "no-compare-neg-zero": "error",
331
+ "no-cond-assign": "error",
332
+ "no-console": ["warn", { allow: ["warn", "error"] }],
333
+ "no-const-assign": "error",
334
+ "no-constant-condition": "error",
335
+ "no-control-regex": "error",
336
+ "no-debugger": "warn",
337
+ "no-delete-var": "error",
338
+ "no-dupe-args": "error",
339
+ "no-dupe-class-members": "error",
340
+ "no-dupe-else-if": "error",
341
+ "no-dupe-keys": "error",
342
+ "no-duplicate-case": "error",
343
+ "no-duplicate-imports": "error",
344
+ "no-empty": ["error", { allowEmptyCatch: true }],
345
+ "no-empty-character-class": "error",
346
+ "no-empty-pattern": "error",
347
+ "no-ex-assign": "error",
348
+ "no-extra-boolean-cast": "error",
349
+ "no-fallthrough": ["warn", { commentPattern: "break[\\s\\w]*omitted" }],
350
+ "no-func-assign": "error",
351
+ "no-global-assign": "error",
352
+ "no-import-assign": "error",
353
+ "no-inner-declarations": "error",
354
+ "no-invalid-regexp": "error",
355
+ "no-irregular-whitespace": "error",
356
+ "no-lonely-if": "error",
357
+ "no-loss-of-precision": "error",
358
+ "no-misleading-character-class": "error",
359
+ "no-multi-str": "error",
360
+ "no-new-native-nonconstructor": "error",
361
+ "no-nonoctal-decimal-escape": "error",
362
+ "no-obj-calls": "error",
363
+ "no-octal": "error",
364
+ "no-prototype-builtins": "error",
365
+ "no-redeclare": "error",
366
+ "no-regex-spaces": "error",
367
+ "no-restricted-syntax": ["error", ...restrictedSyntaxJs],
368
+ "no-self-assign": "error",
369
+ "no-setter-return": "error",
370
+ "no-shadow-restricted-names": "error",
371
+ "no-sparse-arrays": "error",
372
+ "no-this-before-super": "error",
373
+ "no-undef": "error",
374
+ "no-unexpected-multiline": "error",
375
+ "no-unreachable": "error",
376
+ "no-unsafe-finally": "error",
377
+ "no-unsafe-negation": "error",
378
+ "no-unsafe-optional-chaining": "error",
379
+ "no-unused-expressions": ["error", {
380
+ allowShortCircuit: true,
381
+ allowTaggedTemplates: true,
382
+ allowTernary: true
383
+ }],
384
+ "no-unused-labels": "error",
385
+ "no-unused-vars": "off",
386
+ "no-useless-backreference": "error",
387
+ "no-useless-catch": "error",
388
+ "no-useless-computed-key": "error",
389
+ "no-useless-constructor": "error",
390
+ "no-useless-escape": "error",
391
+ "no-useless-rename": "error",
392
+ "no-var": "error",
393
+ "no-void": "error",
394
+ "no-with": "error",
395
+ "object-shorthand": [
396
+ "error",
397
+ "always",
398
+ {
399
+ avoidQuotes: true,
400
+ ignoreConstructors: false
401
+ }
402
+ ],
403
+ "prefer-arrow-callback": ["error", {
404
+ allowNamedFunctions: false,
405
+ allowUnboundThis: true
406
+ }],
407
+ "prefer-const": ["warn", {
408
+ destructuring: "all",
409
+ ignoreReadBeforeAssign: true
410
+ }],
411
+ "prefer-exponentiation-operator": "error",
412
+ "prefer-regex-literals": ["error", { disallowRedundantWrapping: true }],
413
+ "prefer-rest-params": "error",
414
+ "prefer-spread": "error",
415
+ "prefer-template": "error",
416
+ "require-await": "error",
417
+ "require-yield": "error",
418
+ "unicode-bom": ["error", "never"],
419
+ "unused-imports/no-unused-imports": "warn",
420
+ "unused-imports/no-unused-vars": ["error", {
421
+ args: "after-used",
422
+ ignoreRestSiblings: true
423
+ }],
424
+ "use-isnan": ["error", {
425
+ enforceForIndexOf: true,
426
+ enforceForSwitchCase: true
427
+ }],
428
+ "valid-typeof": ["error", { requireStringLiterals: true }],
429
+ "vars-on-top": "error",
430
+ ...overrides
431
+ }
432
+ },
433
+ {
434
+ files: [`**/{scripts,cli}/${GLOB_SRC}`, `**/cli.${GLOB_SRC_EXT}`],
435
+ name: "ncontiero/javascript/cli-rules",
436
+ rules: { "no-console": "off" }
437
+ },
438
+ {
439
+ files: [`**/*.{test,spec}.${GLOB_SRC_EXT}`],
440
+ name: "ncontiero/javascript/test-rules",
441
+ rules: {
442
+ "no-unused-expressions": "off",
443
+ "unicorn/consistent-function-scoping": "off"
444
+ }
445
+ }
446
+ ];
447
+ }
448
+ //#endregion
449
+ //#region src/configs/jsdoc.ts
450
+ async function jsdoc() {
451
+ return [{
452
+ name: "ncontiero/jsdoc/setup",
453
+ plugins: { jsdoc: await interopDefault(import("eslint-plugin-jsdoc")) }
454
+ }, {
455
+ files: [GLOB_SRC],
456
+ name: "ncontiero/jsdoc/rules",
457
+ rules: {
458
+ "jsdoc/check-access": "warn",
459
+ "jsdoc/check-param-names": "warn",
460
+ "jsdoc/check-property-names": "warn",
461
+ "jsdoc/check-types": "warn",
462
+ "jsdoc/empty-tags": "warn",
463
+ "jsdoc/implements-on-classes": "warn",
464
+ "jsdoc/no-defaults": "warn",
465
+ "jsdoc/no-multi-asterisks": "warn",
466
+ "jsdoc/require-param-name": "warn",
467
+ "jsdoc/require-property": "warn",
468
+ "jsdoc/require-property-description": "warn",
469
+ "jsdoc/require-property-name": "warn",
470
+ "jsdoc/require-returns-check": "warn",
471
+ "jsdoc/require-returns-description": "warn",
472
+ "jsdoc/require-yields-check": "warn"
473
+ }
474
+ }];
475
+ }
476
+ //#endregion
477
+ //#region src/configs/jsonc.ts
478
+ async function jsonc(options = {}) {
479
+ const { files = [
480
+ GLOB_JSON,
481
+ GLOB_JSON5,
482
+ GLOB_JSONC
483
+ ], overrides = {}, style = true } = options;
484
+ const { indent = 2 } = typeof style === "boolean" ? {} : style;
485
+ const [pluginJsonc] = await Promise.all([interopDefault(import("eslint-plugin-jsonc"))]);
486
+ return [...pluginJsonc.configs["recommended-with-jsonc"].map((config) => ({
487
+ ...config,
488
+ name: `ncontiero/jsonc/${config.name || "recommended"}`
489
+ })), {
490
+ files,
491
+ name: "ncontiero/jsonc/rules",
492
+ rules: {
493
+ "jsonc/array-bracket-spacing": ["error", "never"],
494
+ "jsonc/comma-style": ["error", "last"],
495
+ "jsonc/indent": ["error", indent],
496
+ "jsonc/key-spacing": ["error", {
497
+ afterColon: true,
498
+ beforeColon: false
499
+ }],
500
+ "jsonc/object-curly-newline": ["error", {
501
+ consistent: true,
502
+ multiline: true
503
+ }],
504
+ "jsonc/object-curly-spacing": ["error", "always"],
505
+ "jsonc/object-property-newline": ["error", { allowAllPropertiesOnSameLine: true }],
506
+ "jsonc/quote-props": "off",
507
+ "jsonc/quotes": "off",
508
+ ...overrides
509
+ }
510
+ }];
511
+ }
512
+ //#endregion
513
+ //#region src/configs/markdown.ts
514
+ async function markdown(options = {}) {
515
+ const { files = [GLOB_MARKDOWN], overrides = {} } = options;
516
+ const pluginMarkdown = await interopDefault(import("@eslint/markdown"));
517
+ return [
518
+ {
519
+ name: "ncontiero/markdown/setup",
520
+ plugins: { markdown: pluginMarkdown }
521
+ },
522
+ {
523
+ files,
524
+ ignores: [GLOB_MARKDOWN_IN_MARKDOWN],
525
+ name: "ncontiero/markdown/processor",
526
+ processor: mergeProcessors([pluginMarkdown.processors.markdown, processorPassThrough])
527
+ },
528
+ {
529
+ files,
530
+ languageOptions: { parser: parserPlain },
531
+ name: "ncontiero/markdown/parser"
532
+ },
533
+ {
534
+ files: [GLOB_MARKDOWN_CODE],
535
+ languageOptions: { parserOptions: { ecmaFeatures: { impliedStrict: true } } },
536
+ name: "ncontiero/markdown/rules",
537
+ rules: {
538
+ "@typescript-eslint/comma-dangle": "off",
539
+ "@typescript-eslint/consistent-type-imports": "off",
540
+ "@typescript-eslint/no-extraneous-class": "off",
541
+ "@typescript-eslint/no-namespace": "off",
542
+ "@typescript-eslint/no-redeclare": "off",
543
+ "@typescript-eslint/no-require-imports": "off",
544
+ "@typescript-eslint/no-unused-expressions": "off",
545
+ "@typescript-eslint/no-unused-vars": "off",
546
+ "@typescript-eslint/no-use-before-define": "off",
547
+ "import/newline-after-import": "off",
548
+ "no-alert": "off",
549
+ "no-console": "off",
550
+ "no-restricted-imports": "off",
551
+ "no-undef": "off",
552
+ "no-unused-expressions": "off",
553
+ "no-unused-vars": "off",
554
+ "node/prefer-global/buffer": "off",
555
+ "node/prefer-global/process": "off",
556
+ "unused-imports/no-unused-imports": "off",
557
+ "unused-imports/no-unused-vars": "off",
558
+ ...overrides
559
+ }
560
+ }
561
+ ];
562
+ }
563
+ //#endregion
564
+ //#region src/configs/nextjs.ts
565
+ async function nextJs(options = {}) {
566
+ const { files = [GLOB_REACT], overrides = {} } = options;
567
+ return [{
568
+ files,
569
+ name: "ncontiero/nextjs/rules",
570
+ plugins: { nextjs: await interopDefault(import("@next/eslint-plugin-next")) },
571
+ rules: {
572
+ "nextjs/google-font-display": "warn",
573
+ "nextjs/google-font-preconnect": "warn",
574
+ "nextjs/inline-script-id": "error",
575
+ "nextjs/next-script-for-ga": "warn",
576
+ "nextjs/no-assign-module-variable": "error",
577
+ "nextjs/no-async-client-component": "warn",
578
+ "nextjs/no-before-interactive-script-outside-document": "warn",
579
+ "nextjs/no-css-tags": "warn",
580
+ "nextjs/no-document-import-in-page": "error",
581
+ "nextjs/no-duplicate-head": "error",
582
+ "nextjs/no-head-element": "warn",
583
+ "nextjs/no-head-import-in-document": "error",
584
+ "nextjs/no-html-link-for-pages": "warn",
585
+ "nextjs/no-img-element": "warn",
586
+ "nextjs/no-page-custom-font": "warn",
587
+ "nextjs/no-script-component-in-head": "error",
588
+ "nextjs/no-styled-jsx-in-document": "warn",
589
+ "nextjs/no-sync-scripts": "warn",
590
+ "nextjs/no-title-in-document-head": "warn",
591
+ "nextjs/no-typos": "warn",
592
+ "nextjs/no-unwanted-polyfillio": "warn",
593
+ ...overrides
594
+ }
595
+ }];
596
+ }
597
+ //#endregion
598
+ //#region src/configs/node.ts
599
+ function node() {
600
+ return [{
601
+ name: "ncontiero/node/setup",
602
+ plugins: { node: pluginNode }
603
+ }, {
604
+ files: [GLOB_SRC],
605
+ name: "ncontiero/node/rules",
606
+ rules: {
607
+ "node/handle-callback-err": ["error", "^(err|error)$"],
608
+ "node/no-deprecated-api": "error",
609
+ "node/no-exports-assign": "error",
610
+ "node/no-new-require": "error",
611
+ "node/no-path-concat": "error",
612
+ "node/no-unsupported-features/es-builtins": "error",
613
+ "node/no-unsupported-features/es-syntax": "error",
614
+ "node/no-unsupported-features/node-builtins": "error",
615
+ "node/prefer-global/console": ["error", "always"],
616
+ "node/prefer-global/process": ["error", "always"],
617
+ "node/prefer-global/url": ["error", "always"],
618
+ "node/prefer-global/url-search-params": ["error", "always"],
619
+ "node/process-exit-as-throw": "error"
620
+ }
621
+ }];
622
+ }
623
+ //#endregion
624
+ //#region src/configs/perfectionist.ts
625
+ /**
626
+ * Perfectionist plugin for props and items sorting.
627
+ *
628
+ * @see https://github.com/azat-io/eslint-plugin-perfectionist
629
+ */
630
+ function perfectionist() {
631
+ return [{
632
+ name: "ncontiero/perfectionist/rules",
633
+ plugins: { perfectionist: pluginPerfectionist },
634
+ rules: {
635
+ "perfectionist/sort-exports": ["warn", { type: "natural" }],
636
+ "perfectionist/sort-imports": ["warn", {
637
+ customGroups: [{
638
+ elementNamePattern: ["^react$", "^react-(?!.*.css$).+"],
639
+ groupName: "react"
640
+ }],
641
+ groups: [
642
+ "side-effect-style",
643
+ "style",
644
+ "type-import",
645
+ "type-external",
646
+ "type-internal",
647
+ [
648
+ "type-parent",
649
+ "type-sibling",
650
+ "type-index"
651
+ ],
652
+ "builtin",
653
+ "react",
654
+ "external",
655
+ "internal",
656
+ [
657
+ "parent",
658
+ "sibling",
659
+ "index"
660
+ ],
661
+ "unknown"
662
+ ],
663
+ internalPattern: ["^[~@#]/.*"],
664
+ newlinesBetween: "ignore",
665
+ type: "natural"
666
+ }],
667
+ "perfectionist/sort-named-exports": ["warn", { groups: ["type-export", "value-export"] }],
668
+ "perfectionist/sort-named-imports": ["warn", { groups: ["type-import", "value-import"] }]
669
+ }
670
+ }];
671
+ }
672
+ //#endregion
673
+ //#region src/configs/prettier.ts
674
+ async function prettier(options = {}) {
675
+ return [
676
+ {
677
+ name: "ncontiero/prettier/setup",
678
+ plugins: { prettier: await interopDefault(import("eslint-plugin-prettier")) }
679
+ },
680
+ {
681
+ files: [GLOB_SRC],
682
+ name: "ncontiero/prettier/disables",
683
+ rules: {
684
+ "antfu/consistent-list-newline": "off",
685
+ "arrow-body-style": "off",
686
+ curly: "off",
687
+ "no-unexpected-multiline": "off",
688
+ "prefer-arrow-callback": "off",
689
+ "unicorn/empty-brace-spaces": "off",
690
+ "unicorn/no-nested-ternary": "off",
691
+ "unicorn/number-literal-case": "off",
692
+ "unicorn/template-indent": "off"
693
+ }
694
+ },
695
+ {
696
+ files: [GLOB_SRC],
697
+ name: "ncontiero/prettier/rules",
698
+ rules: { "prettier/prettier": ["warn", options] }
699
+ },
700
+ {
701
+ files: [GLOB_MARKDOWN],
702
+ languageOptions: { parser: parserPlain },
703
+ name: "ncontiero/prettier/markdown",
704
+ rules: { "prettier/prettier": ["warn", {
705
+ ...options,
706
+ parser: "markdown"
707
+ }] }
708
+ },
709
+ {
710
+ files: [GLOB_CSS, GLOB_POSTCSS],
711
+ languageOptions: { parser: parserPlain },
712
+ name: "ncontiero/prettier/css",
713
+ rules: { "prettier/prettier": ["warn", {
714
+ ...options,
715
+ parser: "css"
716
+ }] }
717
+ },
718
+ {
719
+ files: [GLOB_SCSS],
720
+ languageOptions: { parser: parserPlain },
721
+ name: "ncontiero/prettier/scss",
722
+ rules: { "prettier/prettier": ["warn", {
723
+ ...options,
724
+ parser: "scss"
725
+ }] }
726
+ },
727
+ {
728
+ files: [GLOB_LESS],
729
+ languageOptions: { parser: parserPlain },
730
+ name: "ncontiero/prettier/less",
731
+ rules: { "prettier/prettier": ["warn", {
732
+ ...options,
733
+ parser: "less"
734
+ }] }
735
+ }
736
+ ];
737
+ }
738
+ //#endregion
739
+ //#region src/configs/promise.ts
740
+ async function promise() {
741
+ return [{
742
+ ...(await interopDefault(import("eslint-plugin-promise"))).configs["flat/recommended"],
743
+ name: "ncontiero/promise/setup"
744
+ }, {
745
+ name: "ncontiero/promise/rules",
746
+ rules: {
747
+ "promise/always-return": ["error", { ignoreLastCallback: true }],
748
+ "promise/no-multiple-resolved": "warn"
749
+ }
750
+ }];
751
+ }
752
+ //#endregion
753
+ //#region src/configs/react.ts
754
+ const ReactRefreshAllowPackages = ["vite"];
755
+ const ReactRouterPackages = [
756
+ "@react-router/node",
757
+ "@react-router/react",
758
+ "@react-router/serve",
759
+ "@react-router/dev"
760
+ ];
761
+ const NextJsPackages = ["next"];
762
+ async function react(options = {}) {
763
+ const { files = [GLOB_REACT], overrides = {}, reactQuery } = options;
764
+ if (reactQuery) ensurePackages(["@tanstack/eslint-plugin-query"]);
765
+ const [pluginA11y, pluginReact, pluginReactRefresh] = await Promise.all([
766
+ interopDefault(import("eslint-plugin-jsx-a11y")),
767
+ interopDefault(import("@eslint-react/eslint-plugin")),
768
+ interopDefault(import("eslint-plugin-react-refresh"))
769
+ ]);
770
+ const isAllowConstantExport = ReactRefreshAllowPackages.some((i) => isPackageExists(i));
771
+ const isUsingReactRouter = ReactRouterPackages.some((i) => isPackageExists(i));
772
+ const isUsingNextJs = NextJsPackages.some((i) => isPackageExists(i));
773
+ return [
774
+ {
775
+ name: "ncontiero/react/setup",
776
+ plugins: {
777
+ "jsx-a11y": pluginA11y,
778
+ react: pluginReact,
779
+ "react-refresh": pluginReactRefresh
780
+ }
781
+ },
782
+ reactQuery ? {
783
+ ...(await interopDefault(import("@tanstack/eslint-plugin-query"))).configs["flat/recommended"][0],
784
+ name: "ncontiero/tanstack-query"
785
+ } : {},
786
+ {
787
+ files,
788
+ languageOptions: {
789
+ parserOptions: { ecmaFeatures: { jsx: true } },
790
+ sourceType: "module"
791
+ },
792
+ name: "ncontiero/react/rules",
793
+ rules: {
794
+ ...pluginReact.configs.recommended.rules,
795
+ "jsx-a11y/alt-text": ["warn", {
796
+ area: [],
797
+ elements: [
798
+ "img",
799
+ "object",
800
+ "area",
801
+ "input[type=\"image\"]"
802
+ ],
803
+ img: [],
804
+ "input[type=\"image\"]": [],
805
+ object: []
806
+ }],
807
+ "jsx-a11y/anchor-has-content": ["warn", { components: [] }],
808
+ "jsx-a11y/anchor-is-valid": ["warn", {
809
+ aspects: [
810
+ "noHref",
811
+ "invalidHref",
812
+ "preferButton"
813
+ ],
814
+ components: ["Link"],
815
+ specialLink: ["to"]
816
+ }],
817
+ "jsx-a11y/aria-activedescendant-has-tabindex": ["warn"],
818
+ "jsx-a11y/aria-props": ["warn"],
819
+ "jsx-a11y/aria-proptypes": ["warn"],
820
+ "jsx-a11y/aria-role": ["warn", { ignoreNonDOM: false }],
821
+ "jsx-a11y/aria-unsupported-elements": ["warn"],
822
+ "jsx-a11y/autocomplete-valid": ["off", { inputComponents: [] }],
823
+ "jsx-a11y/click-events-have-key-events": ["warn"],
824
+ "jsx-a11y/control-has-associated-label": ["warn", {
825
+ controlComponents: [],
826
+ depth: 5,
827
+ ignoreElements: [
828
+ "audio",
829
+ "canvas",
830
+ "embed",
831
+ "input",
832
+ "textarea",
833
+ "tr",
834
+ "video"
835
+ ],
836
+ ignoreRoles: [
837
+ "grid",
838
+ "listbox",
839
+ "menu",
840
+ "menubar",
841
+ "radiogroup",
842
+ "row",
843
+ "tablist",
844
+ "toolbar",
845
+ "tree",
846
+ "treegrid"
847
+ ],
848
+ labelAttributes: ["label"]
849
+ }],
850
+ "jsx-a11y/heading-has-content": ["warn", { components: [""] }],
851
+ "jsx-a11y/html-has-lang": ["warn"],
852
+ "jsx-a11y/iframe-has-title": ["warn"],
853
+ "jsx-a11y/img-redundant-alt": ["warn"],
854
+ "jsx-a11y/interactive-supports-focus": ["warn"],
855
+ "jsx-a11y/label-has-associated-control": "warn",
856
+ "jsx-a11y/lang": ["warn"],
857
+ "jsx-a11y/media-has-caption": ["warn", {
858
+ audio: [],
859
+ track: [],
860
+ video: []
861
+ }],
862
+ "jsx-a11y/mouse-events-have-key-events": ["warn"],
863
+ "jsx-a11y/no-access-key": ["warn"],
864
+ "jsx-a11y/no-autofocus": ["warn", { ignoreNonDOM: true }],
865
+ "jsx-a11y/no-distracting-elements": ["warn", { elements: ["marquee", "blink"] }],
866
+ "jsx-a11y/no-interactive-element-to-noninteractive-role": ["warn", { tr: ["none", "presentation"] }],
867
+ "jsx-a11y/no-noninteractive-element-interactions": ["warn", { handlers: [
868
+ "onClick",
869
+ "onMouseDown",
870
+ "onMouseUp",
871
+ "onKeyPress",
872
+ "onKeyDown",
873
+ "onKeyUp"
874
+ ] }],
875
+ "jsx-a11y/no-noninteractive-element-to-interactive-role": ["warn", {
876
+ li: [
877
+ "menuitem",
878
+ "option",
879
+ "row",
880
+ "tab",
881
+ "treeitem"
882
+ ],
883
+ ol: [
884
+ "listbox",
885
+ "menu",
886
+ "menubar",
887
+ "radiogroup",
888
+ "tablist",
889
+ "tree",
890
+ "treegrid"
891
+ ],
892
+ table: ["grid"],
893
+ td: ["gridcell"],
894
+ ul: [
895
+ "listbox",
896
+ "menu",
897
+ "menubar",
898
+ "radiogroup",
899
+ "tablist",
900
+ "tree",
901
+ "treegrid"
902
+ ]
903
+ }],
904
+ "jsx-a11y/no-noninteractive-tabindex": ["warn", {
905
+ roles: ["tabpanel"],
906
+ tags: []
907
+ }],
908
+ "jsx-a11y/no-redundant-roles": ["warn"],
909
+ "jsx-a11y/no-static-element-interactions": ["off", { handlers: [
910
+ "onClick",
911
+ "onMouseDown",
912
+ "onMouseUp",
913
+ "onKeyPress",
914
+ "onKeyDown",
915
+ "onKeyUp"
916
+ ] }],
917
+ "jsx-a11y/role-has-required-aria-props": ["warn"],
918
+ "jsx-a11y/role-supports-aria-props": ["warn"],
919
+ "jsx-a11y/scope": ["warn"],
920
+ "jsx-a11y/tabindex-no-positive": ["warn"],
921
+ "react-refresh/only-export-components": ["warn", {
922
+ allowConstantExport: isAllowConstantExport,
923
+ allowExportNames: [...isUsingNextJs ? [
924
+ "experimental_ppr",
925
+ "dynamic",
926
+ "dynamicParams",
927
+ "revalidate",
928
+ "fetchCache",
929
+ "runtime",
930
+ "preferredRegion",
931
+ "maxDuration",
932
+ "metadata",
933
+ "generateMetadata",
934
+ "viewport",
935
+ "generateViewport",
936
+ "generateImageMetadata",
937
+ "generateSitemaps",
938
+ "generateStaticParams"
939
+ ] : [], ...isUsingReactRouter ? [
940
+ "meta",
941
+ "links",
942
+ "headers",
943
+ "loader",
944
+ "action",
945
+ "clientLoader",
946
+ "clientAction",
947
+ "handle",
948
+ "shouldRevalidate"
949
+ ] : []]
950
+ }],
951
+ "react/dom-no-missing-button-type": "warn",
952
+ "react/dom-no-missing-iframe-sandbox": "warn",
953
+ "react/dom-no-unknown-property": "warn",
954
+ "react/dom-no-unsafe-target-blank": "warn",
955
+ "react/globals": "warn",
956
+ "react/immutability": "warn",
957
+ "react/jsx-no-useless-fragment": "warn",
958
+ "react/no-duplicate-key": "warn",
959
+ "react/no-leaked-conditional-rendering": "error",
960
+ "react/no-missing-component-display-name": "warn",
961
+ "react/no-unstable-context-value": "warn",
962
+ "react/no-unstable-default-props": "error",
963
+ "react/no-unused-props": "warn",
964
+ "react/refs": "warn",
965
+ ...overrides
966
+ }
967
+ }
968
+ ];
969
+ }
970
+ //#endregion
971
+ //#region src/configs/regexp.ts
972
+ function regexp(options = {}) {
973
+ const config = configs["flat/recommended"];
974
+ const rules = {
975
+ ...config.rules,
976
+ ...options.overrides
977
+ };
978
+ return [{
979
+ ...config,
980
+ name: "ncontiero/regexp/rules",
981
+ rules
982
+ }];
983
+ }
984
+ //#endregion
985
+ //#region src/configs/sort.ts
986
+ function sortPackageJson() {
987
+ return [{
988
+ files: ["**/package.json"],
989
+ name: "ncontiero/sort/package-json",
990
+ rules: {
991
+ "jsonc/sort-array-values": ["error", {
992
+ order: { type: "asc" },
993
+ pathPattern: "^files$"
994
+ }],
995
+ "jsonc/sort-keys": [
996
+ "error",
997
+ {
998
+ order: [
999
+ "publisher",
1000
+ "name",
1001
+ "displayName",
1002
+ "type",
1003
+ "version",
1004
+ "private",
1005
+ "packageManager",
1006
+ "description",
1007
+ "author",
1008
+ "contributors",
1009
+ "license",
1010
+ "funding",
1011
+ "homepage",
1012
+ "repository",
1013
+ "bugs",
1014
+ "keywords",
1015
+ "categories",
1016
+ "sideEffects",
1017
+ "imports",
1018
+ "exports",
1019
+ "main",
1020
+ "module",
1021
+ "unpkg",
1022
+ "jsdelivr",
1023
+ "browser",
1024
+ "types",
1025
+ "typesVersions",
1026
+ "bin",
1027
+ "icon",
1028
+ "files",
1029
+ "directories",
1030
+ "publishConfig",
1031
+ "scripts",
1032
+ "peerDependencies",
1033
+ "peerDependenciesMeta",
1034
+ "optionalDependencies",
1035
+ "dependencies",
1036
+ "devDependencies",
1037
+ "engines",
1038
+ "prisma",
1039
+ "config",
1040
+ "pnpm",
1041
+ "overrides",
1042
+ "resolutions",
1043
+ "husky",
1044
+ "lint-staged",
1045
+ "eslintConfig",
1046
+ "prettier"
1047
+ ],
1048
+ pathPattern: "^$"
1049
+ },
1050
+ {
1051
+ order: { type: "asc" },
1052
+ pathPattern: "^(?:dev|peer|optional|bundled)?[Dd]ependencies(Meta)?$"
1053
+ },
1054
+ {
1055
+ order: [
1056
+ "types",
1057
+ "require",
1058
+ "import",
1059
+ "default"
1060
+ ],
1061
+ pathPattern: "^exports.*$"
1062
+ },
1063
+ {
1064
+ order: { type: "asc" },
1065
+ pathPattern: "^(?:resolutions|overrides|pnpm.overrides)$"
1066
+ },
1067
+ {
1068
+ order: { type: "asc" },
1069
+ pathPattern: "^workspaces\\.catalog$"
1070
+ },
1071
+ {
1072
+ order: { type: "asc" },
1073
+ pathPattern: "^workspaces\\.catalogs\\.[^.]+$"
1074
+ }
1075
+ ]
1076
+ }
1077
+ }];
1078
+ }
1079
+ function sortTsconfig() {
1080
+ return [{
1081
+ files: ["**/[jt]sconfig.json", "**/[jt]sconfig.*.json"],
1082
+ name: "ncontiero/sort/tsconfig",
1083
+ rules: { "jsonc/sort-keys": [
1084
+ "error",
1085
+ {
1086
+ order: [
1087
+ "extends",
1088
+ "compilerOptions",
1089
+ "references",
1090
+ "files",
1091
+ "include",
1092
+ "exclude"
1093
+ ],
1094
+ pathPattern: "^$"
1095
+ },
1096
+ {
1097
+ order: [
1098
+ "incremental",
1099
+ "composite",
1100
+ "tsBuildInfoFile",
1101
+ "disableSourceOfProjectReferenceRedirect",
1102
+ "disableSolutionSearching",
1103
+ "disableReferencedProjectLoad",
1104
+ "target",
1105
+ "jsx",
1106
+ "jsxFactory",
1107
+ "jsxFragmentFactory",
1108
+ "jsxImportSource",
1109
+ "lib",
1110
+ "moduleDetection",
1111
+ "noLib",
1112
+ "reactNamespace",
1113
+ "useDefineForClassFields",
1114
+ "emitDecoratorMetadata",
1115
+ "experimentalDecorators",
1116
+ "libReplacement",
1117
+ "baseUrl",
1118
+ "rootDir",
1119
+ "rootDirs",
1120
+ "customConditions",
1121
+ "module",
1122
+ "moduleResolution",
1123
+ "moduleSuffixes",
1124
+ "noResolve",
1125
+ "paths",
1126
+ "resolveJsonModule",
1127
+ "resolvePackageJsonExports",
1128
+ "resolvePackageJsonImports",
1129
+ "typeRoots",
1130
+ "types",
1131
+ "allowArbitraryExtensions",
1132
+ "allowImportingTsExtensions",
1133
+ "allowUmdGlobalAccess",
1134
+ "allowJs",
1135
+ "checkJs",
1136
+ "maxNodeModuleJsDepth",
1137
+ "strict",
1138
+ "strictBindCallApply",
1139
+ "strictFunctionTypes",
1140
+ "strictNullChecks",
1141
+ "strictPropertyInitialization",
1142
+ "allowUnreachableCode",
1143
+ "allowUnusedLabels",
1144
+ "alwaysStrict",
1145
+ "exactOptionalPropertyTypes",
1146
+ "noFallthroughCasesInSwitch",
1147
+ "noImplicitAny",
1148
+ "noImplicitOverride",
1149
+ "noImplicitReturns",
1150
+ "noImplicitThis",
1151
+ "noPropertyAccessFromIndexSignature",
1152
+ "noUncheckedIndexedAccess",
1153
+ "noUnusedLocals",
1154
+ "noUnusedParameters",
1155
+ "useUnknownInCatchVariables",
1156
+ "declaration",
1157
+ "declarationDir",
1158
+ "declarationMap",
1159
+ "downlevelIteration",
1160
+ "emitBOM",
1161
+ "emitDeclarationOnly",
1162
+ "importHelpers",
1163
+ "importsNotUsedAsValues",
1164
+ "inlineSourceMap",
1165
+ "inlineSources",
1166
+ "isolatedDeclarations",
1167
+ "mapRoot",
1168
+ "newLine",
1169
+ "noEmit",
1170
+ "noEmitHelpers",
1171
+ "noEmitOnError",
1172
+ "outDir",
1173
+ "outFile",
1174
+ "preserveConstEnums",
1175
+ "preserveValueImports",
1176
+ "removeComments",
1177
+ "sourceMap",
1178
+ "sourceRoot",
1179
+ "stripInternal",
1180
+ "allowSyntheticDefaultImports",
1181
+ "esModuleInterop",
1182
+ "forceConsistentCasingInFileNames",
1183
+ "isolatedModules",
1184
+ "preserveSymlinks",
1185
+ "verbatimModuleSyntax",
1186
+ "erasableSyntaxOnly",
1187
+ "skipDefaultLibCheck",
1188
+ "skipLibCheck"
1189
+ ],
1190
+ pathPattern: "^compilerOptions$"
1191
+ }
1192
+ ] }
1193
+ }];
1194
+ }
1195
+ const sortPnpmWorkspace = () => [{
1196
+ files: ["**/pnpm-workspace.yaml"],
1197
+ name: "ncontiero/sort/pnpm-workspace",
1198
+ rules: { "yml/sort-keys": [
1199
+ "error",
1200
+ {
1201
+ order: [
1202
+ "packages",
1203
+ "overrides",
1204
+ "patchedDependencies",
1205
+ "defines",
1206
+ "catalog",
1207
+ "catalogs",
1208
+ { order: { type: "asc" } }
1209
+ ],
1210
+ pathPattern: "^$"
1211
+ },
1212
+ {
1213
+ allowLineSeparatedGroups: true,
1214
+ order: { type: "asc" },
1215
+ pathPattern: "^(catalog|catalogs|overrides)$"
1216
+ },
1217
+ {
1218
+ allowLineSeparatedGroups: true,
1219
+ order: { type: "asc" },
1220
+ pathPattern: String.raw`^catalogs\..+$`
1221
+ }
1222
+ ] }
1223
+ }];
1224
+ //#endregion
1225
+ //#region src/configs/tailwindcss.ts
1226
+ async function tailwindcss(options = {}) {
1227
+ const { overrides = {} } = options;
1228
+ const resolvePath = (p) => {
1229
+ if (path.isAbsolute(p)) return p;
1230
+ return path.resolve(process.cwd(), p);
1231
+ };
1232
+ const cssGlobalPath = resolvePath(options.cssGlobalPath ?? path.join("src", "app", "globals.css"));
1233
+ const configPath = resolvePath(options.configPath ?? path.join("tailwind.config.ts"));
1234
+ const cwd = options.cwd ? resolvePath(options.cwd) : void 0;
1235
+ return [{
1236
+ name: "ncontiero/tailwindcss/setup",
1237
+ plugins: { tailwindcss: await interopDefault(import("eslint-plugin-better-tailwindcss")) },
1238
+ settings: { tailwindcss: {
1239
+ cwd,
1240
+ entryPoint: cssGlobalPath,
1241
+ tailwindConfig: configPath
1242
+ } }
1243
+ }, {
1244
+ files: [GLOB_REACT, GLOB_HTML],
1245
+ name: "ncontiero/tailwindcss/rules",
1246
+ rules: {
1247
+ "tailwindcss/enforce-consistent-class-order": "warn",
1248
+ "tailwindcss/enforce-consistent-important-position": "warn",
1249
+ "tailwindcss/enforce-consistent-line-wrapping": ["warn", {
1250
+ group: "never",
1251
+ preferSingleLine: true,
1252
+ printWidth: 120
1253
+ }],
1254
+ "tailwindcss/enforce-consistent-variable-syntax": "error",
1255
+ "tailwindcss/enforce-shorthand-classes": "warn",
1256
+ "tailwindcss/no-conflicting-classes": "error",
1257
+ "tailwindcss/no-deprecated-classes": "error",
1258
+ "tailwindcss/no-duplicate-classes": "error",
1259
+ "tailwindcss/no-restricted-classes": "error",
1260
+ "tailwindcss/no-unknown-classes": "off",
1261
+ "tailwindcss/no-unnecessary-whitespace": "warn",
1262
+ ...overrides
1263
+ }
1264
+ }];
1265
+ }
1266
+ //#endregion
1267
+ //#region src/configs/toml.ts
1268
+ async function toml(options = {}) {
1269
+ const { files = [GLOB_TOML], overrides = {}, style = true } = options;
1270
+ const { indent = 2 } = typeof style === "boolean" ? {} : style;
1271
+ const [pluginToml, parserToml] = await Promise.all([interopDefault(import("eslint-plugin-toml")), interopDefault(import("toml-eslint-parser"))]);
1272
+ return [{
1273
+ name: "ncontiero/toml/setup",
1274
+ plugins: { toml: pluginToml }
1275
+ }, {
1276
+ files,
1277
+ languageOptions: { parser: parserToml },
1278
+ name: "ncontiero/toml/rules",
1279
+ rules: {
1280
+ "toml/array-bracket-newline": "error",
1281
+ "toml/array-bracket-spacing": "error",
1282
+ "toml/array-element-newline": "off",
1283
+ "toml/comma-style": "error",
1284
+ "toml/indent": ["error", indent],
1285
+ "toml/inline-table-curly-spacing": "error",
1286
+ "toml/key-spacing": "error",
1287
+ "toml/keys-order": "error",
1288
+ "toml/no-space-dots": "error",
1289
+ "toml/no-unreadable-number-separator": "error",
1290
+ "toml/padding-line-between-pairs": "error",
1291
+ "toml/padding-line-between-tables": "error",
1292
+ "toml/precision-of-fractional-seconds": "error",
1293
+ "toml/precision-of-integer": "error",
1294
+ "toml/quoted-keys": "error",
1295
+ "toml/spaced-comment": "error",
1296
+ "toml/table-bracket-spacing": "error",
1297
+ "toml/tables-order": "error",
1298
+ "toml/vue-custom-block/no-parsing-error": "error",
1299
+ "unicorn/filename-case": "off",
1300
+ ...overrides
1301
+ }
1302
+ }];
1303
+ }
1304
+ //#endregion
1305
+ //#region src/configs/typescript.ts
1306
+ async function typescript(options = {}) {
1307
+ const { files = [GLOB_TS, GLOB_TSX], overrides = {}, parserOptions = {} } = options;
1308
+ const [pluginTs, parserTs] = await Promise.all([interopDefault(import("@typescript-eslint/eslint-plugin")), interopDefault(import("@typescript-eslint/parser"))]);
1309
+ return [
1310
+ {
1311
+ name: "ncontiero/typescript/setup",
1312
+ plugins: { "@typescript-eslint": pluginTs }
1313
+ },
1314
+ {
1315
+ files,
1316
+ languageOptions: {
1317
+ parser: parserTs,
1318
+ parserOptions: {
1319
+ sourceType: "module",
1320
+ ...parserOptions
1321
+ }
1322
+ },
1323
+ name: "ncontiero/typescript/parser"
1324
+ },
1325
+ {
1326
+ files,
1327
+ name: "ncontiero/typescript/rules",
1328
+ rules: {
1329
+ ...pluginTs.configs["eslint-recommended"].overrides[0].rules,
1330
+ ...pluginTs.configs.recommended.rules,
1331
+ "@typescript-eslint/ban-ts-comment": ["error", { "ts-ignore": "allow-with-description" }],
1332
+ "@typescript-eslint/consistent-type-assertions": ["error", {
1333
+ assertionStyle: "as",
1334
+ objectLiteralTypeAssertions: "allow-as-parameter"
1335
+ }],
1336
+ "@typescript-eslint/consistent-type-imports": ["error", {
1337
+ disallowTypeAnnotations: false,
1338
+ fixStyle: "inline-type-imports"
1339
+ }],
1340
+ "@typescript-eslint/method-signature-style": ["error", "property"],
1341
+ "@typescript-eslint/no-empty-object-type": ["error", { allowInterfaces: "always" }],
1342
+ "@typescript-eslint/no-explicit-any": "off",
1343
+ "@typescript-eslint/no-import-type-side-effects": "error",
1344
+ "@typescript-eslint/no-non-null-assertion": "off",
1345
+ "@typescript-eslint/no-redeclare": "error",
1346
+ "@typescript-eslint/no-unused-expressions": ["error", {
1347
+ allowShortCircuit: true,
1348
+ allowTaggedTemplates: false,
1349
+ allowTernary: true,
1350
+ enforceForJSX: true
1351
+ }],
1352
+ "@typescript-eslint/no-unused-vars": "off",
1353
+ "@typescript-eslint/no-useless-constructor": "error",
1354
+ "@typescript-eslint/no-wrapper-object-types": "error",
1355
+ "@typescript-eslint/prefer-as-const": "warn",
1356
+ "@typescript-eslint/prefer-literal-enum-member": ["error", { allowBitwiseExpressions: true }],
1357
+ "no-restricted-syntax": [
1358
+ "error",
1359
+ ...restrictedSyntaxJs,
1360
+ "TSEnumDeclaration[const=true]"
1361
+ ],
1362
+ ...overrides
1363
+ }
1364
+ },
1365
+ {
1366
+ files: ["**/*.d.ts"],
1367
+ name: "ncontiero/typescript/dts-rules",
1368
+ rules: {
1369
+ "eslint-comments/no-unlimited-disable": "off",
1370
+ "import/no-duplicates": "off",
1371
+ "no-restricted-syntax": ["error", ...restrictedSyntaxJs],
1372
+ "unused-imports/no-unused-vars": "off"
1373
+ }
1374
+ },
1375
+ {
1376
+ files: [GLOB_JS, "**/*.cjs"],
1377
+ name: "ncontiero/typescript/cjs-rules",
1378
+ rules: { "@typescript-eslint/no-require-imports": "off" }
1379
+ }
1380
+ ];
1381
+ }
1382
+ //#endregion
1383
+ //#region src/configs/unicorn.ts
1384
+ function unicorn(options = {}) {
1385
+ const { allRecommended, overrides = {}, regexp = false } = options;
1386
+ return [{
1387
+ name: "ncontiero/unicorn/rules",
1388
+ plugins: { unicorn: pluginUnicorn },
1389
+ rules: {
1390
+ ...allRecommended ? pluginUnicorn.configs.recommended.rules : {
1391
+ "unicorn/better-regex": regexp ? "off" : "error",
1392
+ "unicorn/catch-error-name": "error",
1393
+ "unicorn/consistent-date-clone": "error",
1394
+ "unicorn/consistent-empty-array-spread": "error",
1395
+ "unicorn/consistent-existence-index-check": "error",
1396
+ "unicorn/consistent-function-scoping": "error",
1397
+ "unicorn/custom-error-definition": "error",
1398
+ "unicorn/error-message": "error",
1399
+ "unicorn/escape-case": "error",
1400
+ "unicorn/explicit-length-check": "error",
1401
+ "unicorn/new-for-builtins": "error",
1402
+ "unicorn/no-array-callback-reference": "error",
1403
+ "unicorn/no-array-method-this-argument": "error",
1404
+ "unicorn/no-await-in-promise-methods": "error",
1405
+ "unicorn/no-console-spaces": "error",
1406
+ "unicorn/no-for-loop": "error",
1407
+ "unicorn/no-hex-escape": "error",
1408
+ "unicorn/no-instanceof-builtins": "error",
1409
+ "unicorn/no-invalid-remove-event-listener": "error",
1410
+ "unicorn/no-lonely-if": "error",
1411
+ "unicorn/no-negation-in-equality-check": "error",
1412
+ "unicorn/no-new-array": "error",
1413
+ "unicorn/no-new-buffer": "error",
1414
+ "unicorn/no-single-promise-in-promise-methods": "error",
1415
+ "unicorn/no-static-only-class": "error",
1416
+ "unicorn/no-unnecessary-array-flat-depth": "error",
1417
+ "unicorn/no-unnecessary-array-splice-count": "error",
1418
+ "unicorn/no-unnecessary-await": "error",
1419
+ "unicorn/no-unnecessary-slice-end": "error",
1420
+ "unicorn/no-useless-iterator-to-array": "error",
1421
+ "unicorn/no-zero-fractions": "error",
1422
+ "unicorn/number-literal-case": "error",
1423
+ "unicorn/prefer-add-event-listener": "error",
1424
+ "unicorn/prefer-array-find": "error",
1425
+ "unicorn/prefer-array-flat-map": "error",
1426
+ "unicorn/prefer-array-index-of": "error",
1427
+ "unicorn/prefer-array-some": "error",
1428
+ "unicorn/prefer-at": "error",
1429
+ "unicorn/prefer-blob-reading-methods": "error",
1430
+ "unicorn/prefer-class-fields": "error",
1431
+ "unicorn/prefer-date-now": "error",
1432
+ "unicorn/prefer-dom-node-append": "error",
1433
+ "unicorn/prefer-dom-node-dataset": "error",
1434
+ "unicorn/prefer-dom-node-remove": "error",
1435
+ "unicorn/prefer-dom-node-text-content": "error",
1436
+ "unicorn/prefer-includes": "error",
1437
+ "unicorn/prefer-keyboard-event-key": "error",
1438
+ "unicorn/prefer-math-min-max": "error",
1439
+ "unicorn/prefer-math-trunc": "error",
1440
+ "unicorn/prefer-modern-dom-apis": "error",
1441
+ "unicorn/prefer-modern-math-apis": "error",
1442
+ "unicorn/prefer-negative-index": "error",
1443
+ "unicorn/prefer-node-protocol": "error",
1444
+ "unicorn/prefer-number-properties": "error",
1445
+ "unicorn/prefer-optional-catch-binding": "error",
1446
+ "unicorn/prefer-prototype-methods": "error",
1447
+ "unicorn/prefer-query-selector": "error",
1448
+ "unicorn/prefer-reflect-apply": "error",
1449
+ "unicorn/prefer-regexp-test": "error",
1450
+ "unicorn/prefer-simple-condition-first": "error",
1451
+ "unicorn/prefer-single-call": "error",
1452
+ "unicorn/prefer-string-replace-all": "error",
1453
+ "unicorn/prefer-string-slice": "error",
1454
+ "unicorn/prefer-string-starts-ends-with": "error",
1455
+ "unicorn/prefer-string-trim-start-end": "error",
1456
+ "unicorn/prefer-type-error": "error",
1457
+ "unicorn/switch-case-break-position": "error",
1458
+ "unicorn/throw-new-error": "error"
1459
+ },
1460
+ ...overrides
1461
+ }
1462
+ }];
1463
+ }
1464
+ //#endregion
1465
+ //#region src/configs/yml.ts
1466
+ async function yml(options = {}) {
1467
+ const { files = [GLOB_YAML], overrides = {}, style = true } = options;
1468
+ const { indent = 2, quotes = "double" } = typeof style === "boolean" ? {} : style;
1469
+ const [pluginYml, parserYml] = await Promise.all([interopDefault(import("eslint-plugin-yml")), interopDefault(import("yaml-eslint-parser"))]);
1470
+ return [
1471
+ {
1472
+ name: "ncontiero/yml/setup",
1473
+ plugins: { yml: pluginYml }
1474
+ },
1475
+ {
1476
+ files,
1477
+ languageOptions: { parser: parserYml },
1478
+ name: "ncontiero/yml/rules",
1479
+ rules: {
1480
+ "yml/block-mapping": "error",
1481
+ "yml/block-mapping-question-indicator-newline": "error",
1482
+ "yml/block-sequence": "error",
1483
+ "yml/block-sequence-hyphen-indicator-newline": "error",
1484
+ "yml/flow-mapping-curly-newline": "error",
1485
+ "yml/flow-mapping-curly-spacing": "error",
1486
+ "yml/flow-sequence-bracket-newline": "error",
1487
+ "yml/flow-sequence-bracket-spacing": "error",
1488
+ "yml/indent": ["error", indent],
1489
+ "yml/key-spacing": "error",
1490
+ "yml/no-empty-document": "error",
1491
+ "yml/no-empty-key": "error",
1492
+ "yml/no-empty-sequence-entry": "error",
1493
+ "yml/no-irregular-whitespace": "error",
1494
+ "yml/no-tab-indent": "error",
1495
+ "yml/plain-scalar": "error",
1496
+ "yml/quotes": ["error", {
1497
+ avoidEscape: false,
1498
+ prefer: quotes
1499
+ }],
1500
+ "yml/spaced-comment": "error",
1501
+ "yml/vue-custom-block/no-parsing-error": "error",
1502
+ ...overrides
1503
+ }
1504
+ },
1505
+ {
1506
+ files: ["pnpm-workspace.yaml"],
1507
+ name: "ncontiero/yml/pnpm-workspace",
1508
+ rules: { "yml/sort-keys": [
1509
+ "error",
1510
+ {
1511
+ order: [
1512
+ "packages",
1513
+ "overrides",
1514
+ "patchedDependencies",
1515
+ "hoistPattern",
1516
+ "catalog",
1517
+ "catalogs",
1518
+ "allowedDeprecatedVersions",
1519
+ "allowNonAppliedPatches",
1520
+ "allowUnusedPatches",
1521
+ "configDependencies",
1522
+ "ignoredBuiltDependencies",
1523
+ "ignoredOptionalDependencies",
1524
+ "ignorePatchFailures",
1525
+ "neverBuiltDependencies",
1526
+ "onlyBuiltDependencies",
1527
+ "onlyBuiltDependenciesFile",
1528
+ "packageExtensions",
1529
+ "peerDependencyRules",
1530
+ "supportedArchitectures"
1531
+ ],
1532
+ pathPattern: "^$"
1533
+ },
1534
+ {
1535
+ order: { type: "asc" },
1536
+ pathPattern: ".*"
1537
+ }
1538
+ ] }
1539
+ }
1540
+ ];
1541
+ }
1542
+ //#endregion
1543
+ //#region src/env.ts
1544
+ const hasTypeScript = isPackageExists("typescript");
1545
+ const hasReact = isPackageExists("react");
1546
+ const hasNextJs = isPackageExists("next");
1547
+ const hasTailwind = isPackageExists("tailwindcss");
1548
+ const hasTanStackReactQuery = isPackageExists("@tanstack/react-query");
1549
+ //#endregion
1550
+ //#region src/factory.ts
1551
+ const flatConfigProps = [
1552
+ "name",
1553
+ "languageOptions",
1554
+ "linterOptions",
1555
+ "processor",
1556
+ "plugins",
1557
+ "rules",
1558
+ "settings"
1559
+ ];
1560
+ function resolveSubOptions(options, key) {
1561
+ return typeof options[key] === "boolean" ? {} : options[key] || {};
1562
+ }
1563
+ function getOverrides(options, key) {
1564
+ const sub = resolveSubOptions(options, key);
1565
+ return {
1566
+ ...("overrides" in options ? options.overrides : {})?.[key],
1567
+ ..."overrides" in sub ? sub.overrides : {}
1568
+ };
1569
+ }
1570
+ function getStyleOptions(options) {
1571
+ if ("tabWidth" in options || "singleQuote" in options || "semi" in options) return {
1572
+ indent: options.tabWidth || 2,
1573
+ quotes: options.singleQuote ? "single" : "double",
1574
+ semi: options.semi ?? true
1575
+ };
1576
+ return false;
1577
+ }
1578
+ /**
1579
+ * Merges ESLint configurations with optional support for Markdown, React, Next.js, TailwindCSS, and Prettier.
1580
+ *
1581
+ * @param options - Optional settings for Markdown, React, Next.js, TailwindCSS and Prettier.
1582
+ * @param userConfigs - The user configurations to be merged with the generated configurations.
1583
+ * @returns Merged ESLint configurations based on provided options.
1584
+ */
1585
+ function ncontiero(options = {}, ...userConfigs) {
1586
+ const { gitignore: enableGitignore = true, nextjs: enableNextJs = hasNextJs, react: enableReact = hasReact, reactQuery: enableTanStackReactQuery = hasTanStackReactQuery, regexp: enableRegexp = true, tailwindcss: enableTailwindCSS = hasTailwind, typescript: enableTypescript = hasTypeScript, unicorn: enableUnicorn = true } = options;
1587
+ const prettierOptions = typeof options.prettier === "object" ? options.prettier : {};
1588
+ const styleOptions = getStyleOptions(prettierOptions);
1589
+ const configs = [];
1590
+ if (enableGitignore) if (typeof enableGitignore !== "boolean") configs.push(interopDefault(import("eslint-config-flat-gitignore")).then((r) => [r({
1591
+ name: "ncontiero/gitignore",
1592
+ ...enableGitignore
1593
+ })]));
1594
+ else configs.push(interopDefault(import("eslint-config-flat-gitignore")).then((r) => [r({
1595
+ name: "ncontiero/gitignore",
1596
+ strict: false
1597
+ })]));
1598
+ if (!enableTypescript) options.ignores ? options.ignores.push(GLOB_TS, GLOB_TSX) : options.ignores = [GLOB_TS, GLOB_TSX];
1599
+ configs.push(ignores(options.ignores), javascript({ overrides: getOverrides(options, "javascript") }), comments(), jsdoc(), imports({ nextJs: !!enableNextJs }), node(), promise(), command(), perfectionist(), deMorgan());
1600
+ if (enableUnicorn) configs.push(unicorn(enableUnicorn === true ? { regexp: !!enableRegexp } : enableUnicorn));
1601
+ if (enableTypescript) configs.push(typescript({
1602
+ ...resolveSubOptions(options, "typescript"),
1603
+ overrides: getOverrides(options, "typescript")
1604
+ }));
1605
+ if (options.jsonc ?? true) configs.push(jsonc({
1606
+ overrides: getOverrides(options, "jsonc"),
1607
+ style: styleOptions
1608
+ }), sortPackageJson(), sortTsconfig());
1609
+ if (options.yaml ?? true) configs.push(yml({
1610
+ overrides: getOverrides(options, "yaml"),
1611
+ style: styleOptions
1612
+ }));
1613
+ if (options.toml ?? true) configs.push(toml({
1614
+ overrides: getOverrides(options, "toml"),
1615
+ style: styleOptions
1616
+ }));
1617
+ if (options.markdown ?? true) configs.push(markdown({ overrides: getOverrides(options, "markdown") }));
1618
+ if (enableRegexp) configs.push(regexp(typeof enableRegexp === "boolean" ? {} : enableRegexp));
1619
+ if (enableReact) configs.push(react({
1620
+ overrides: getOverrides(options, "react"),
1621
+ reactQuery: !!enableTanStackReactQuery
1622
+ }));
1623
+ if (enableNextJs) configs.push(nextJs({ overrides: getOverrides(options, "nextjs") }));
1624
+ if (options.html ?? true) configs.push(html({
1625
+ ...resolveSubOptions(options, "html"),
1626
+ overrides: getOverrides(options, "html")
1627
+ }));
1628
+ if (enableTailwindCSS) configs.push(tailwindcss({
1629
+ ...resolveSubOptions(options, "tailwindcss"),
1630
+ overrides: getOverrides(options, "tailwindcss")
1631
+ }));
1632
+ if (options.prettier ?? true) configs.push(prettier(prettierOptions));
1633
+ if ("files" in options) throw new Error("[@ncontiero/eslint-config] The first argument should not contain the \"files\" property as the options are supposed to be global. Place it in the second or later config instead.");
1634
+ const fusedConfig = flatConfigProps.reduce((acc, key) => {
1635
+ if (key in options) acc[key] = options[key];
1636
+ return acc;
1637
+ }, {});
1638
+ if (Object.keys(fusedConfig).length > 0) configs.push([fusedConfig]);
1639
+ return composer(...configs, ...userConfigs);
1640
+ }
1641
+ //#endregion
1642
+ export { GLOB_ALL_SRC, GLOB_CSS, GLOB_DIST, GLOB_EXCLUDE, GLOB_HTML, GLOB_JS, GLOB_JSON, GLOB_JSON5, GLOB_JSONC, GLOB_JSX, GLOB_LESS, GLOB_LOCKFILE, GLOB_MARKDOWN, GLOB_MARKDOWN_CODE, GLOB_MARKDOWN_IN_MARKDOWN, GLOB_NODE_MODULES, GLOB_POSTCSS, GLOB_REACT, GLOB_SCSS, GLOB_SRC, GLOB_SRC_EXT, GLOB_STYLE, GLOB_TOML, GLOB_TS, GLOB_TSX, GLOB_YAML, combine, command, comments, composer, deMorgan, ensurePackages, getOverrides, hasNextJs, hasReact, hasTailwind, hasTanStackReactQuery, hasTypeScript, html, ignores, imports, interopDefault, javascript, jsdoc, jsonc, markdown, ncontiero, nextJs, node, parserPlain, perfectionist, prettier, promise, react, regexp, resolveSubOptions, restrictedSyntaxJs, sortPackageJson, sortPnpmWorkspace, sortTsconfig, tailwindcss, toArray, toml, typescript, unicorn, yml };