@favorodera/eslint-config 0.0.3 → 0.0.5

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 CHANGED
@@ -1,21 +1,34 @@
1
1
  import { FlatConfigComposer, renamePluginsInRules } from "eslint-flat-config-utils";
2
2
  import { defu } from "defu";
3
+ import globals from "globals";
3
4
  import { mergeProcessors, processorPassThrough } from "eslint-merge-processors";
4
5
  import vueBlocksProcessor from "eslint-processor-vue-blocks";
5
- import globals from "globals";
6
6
  //#region src/globs.ts
7
+ /** Glob pattern for matching JavaScript files */
7
8
  const jsGlob = "**/*.{js,cjs,mjs}";
9
+ /** Glob pattern for matching TypeScript files */
8
10
  const tsGlob = "**/*.{ts,cts,mts}";
11
+ /** Glob pattern for matching Vue single-file components */
9
12
  const vueGlob = "**/*.vue";
13
+ /** Glob pattern for matching Markdown files */
10
14
  const mdGlob = "**/*.md";
15
+ /** Glob pattern for matching virtual files extracted from Markdown */
11
16
  const mdInMdGlob = "**/*.md/*.md";
17
+ /** Glob pattern for matching code blocks embedded in Markdown files */
12
18
  const codeInMdGlob = "**/*.md/**/*.{js,cjs,mjs,ts,cts,mts,vue}";
19
+ /** Glob pattern for matching JSON files */
13
20
  const jsonGlob = "**/*.json";
21
+ /** Glob pattern for matching JSON5 files */
14
22
  const json5Glob = "**/*.json5";
23
+ /** Glob pattern for matching JSON with Comments files */
15
24
  const jsoncGlob = "**/*.jsonc";
25
+ /** Glob patterns for matching TypeScript configuration files */
16
26
  const tsConfigGlob = ["**/tsconfig.json", "**/tsconfig.*.json"];
27
+ /** Glob pattern for matching package.json files */
17
28
  const packageJsonGlob = "**/package.json";
18
- const ignoresGlob = [
29
+ //#endregion
30
+ //#region src/configs/ignores.ts
31
+ const defaultPatterns = [
19
32
  "**/node_modules/**",
20
33
  "**/dist/**",
21
34
  "**/package-lock.json",
@@ -52,17 +65,36 @@ const ignoresGlob = [
52
65
  "**/.agents",
53
66
  "**/.*/skills"
54
67
  ];
68
+ /**
69
+ * Globs for ignoring files and directories from ESLint scanning.
70
+ * @param patterns Additional ignore patterns or a function to modify the defaults.
71
+ * @returns An array containing the ignore flat config item.
72
+ */
73
+ function ignores(patterns = []) {
74
+ return [{
75
+ ignores: typeof patterns === "function" ? patterns(defaultPatterns) : [...defaultPatterns, ...patterns],
76
+ name: "favorodera/ignores"
77
+ }];
78
+ }
55
79
  //#endregion
56
80
  //#region src/utils.ts
57
81
  /**
82
+ * Extracts and merges the rules from multiple ESLint configuration arrays.
83
+ * @param configArrays Rest parameter representing multiple arrays of ESLint flat config items.
84
+ * @returns A single object containing all the merged rules from the provided configurations.
85
+ */
86
+ function extractRules(...configArrays) {
87
+ return Object.assign({}, ...configArrays.flat().map((config) => config?.rules || {}));
88
+ }
89
+ /**
58
90
  * Resolves a module (or a promise of one) and returns its default export
59
91
  * if present, otherwise returns the module itself.
60
92
  *
61
93
  * Handles both ESM modules (which wrap exports under `.default`) and
62
94
  * CJS / plain-object modules uniformly.
63
- *
64
- * @param module - A module or a promise that resolves to one
65
- * @returns The `.default` export if it exists, otherwise the module itself
95
+ * @template TModule The type of the module being imported.
96
+ * @param module A module or a promise that resolves to one.
97
+ * @returns The `.default` export if it exists, otherwise the module itself.
66
98
  */
67
99
  async function importModule(module) {
68
100
  const resolved = await module;
@@ -72,106 +104,15 @@ async function importModule(module) {
72
104
  /**
73
105
  * Normalize boolean or options value into merged options or null.
74
106
  * Returns null when the input is false or undefined.
75
- *
76
- * @param value - boolean, options object, or undefined
77
- * @param defaults - defaults to merge with
78
- * @returns merged options or false
107
+ * @template TOptions The type of the options object.
108
+ * @param value The configuration value, which can be a boolean, an options object, or undefined.
109
+ * @param defaults The default options to merge with if `value` is true or an object.
110
+ * @returns The merged options object, or false if the feature is disabled.
79
111
  */
80
112
  function resolveOptions(value, defaults) {
81
113
  if (!value) return false;
82
114
  return defu(value === true ? {} : value, defaults);
83
115
  }
84
- function extractRules(...configArrays) {
85
- return Object.assign({}, ...configArrays.flat().map((config) => config?.rules || {}));
86
- }
87
- //#endregion
88
- //#region src/configs/vue.ts
89
- const sfcBlocksDefaults = { blocks: {
90
- styles: true,
91
- customBlocks: true,
92
- template: false
93
- } };
94
- const vueDefaults = {
95
- files: [vueGlob],
96
- sfcBlocks: sfcBlocksDefaults
97
- };
98
- async function vue(options) {
99
- const resolved = defu(options, vueDefaults);
100
- const sfcBlocks = resolveOptions(resolved.sfcBlocks, sfcBlocksDefaults);
101
- const [vuePlugin, vueParser, tsEsLint] = await Promise.all([
102
- importModule(import("eslint-plugin-vue")),
103
- importModule(import("vue-eslint-parser")),
104
- importModule(import("typescript-eslint"))
105
- ]);
106
- const baseRules = extractRules(vuePlugin.configs["flat/essential"], vuePlugin.configs["flat/strongly-recommended"], vuePlugin.configs["flat/recommended"]);
107
- const processor = sfcBlocks === false ? vuePlugin.processors[".vue"] : mergeProcessors([vuePlugin.processors[".vue"], vueBlocksProcessor(sfcBlocks)]);
108
- return [{
109
- name: "favorodera/vue/setup",
110
- plugins: { vue: vuePlugin },
111
- languageOptions: { globals: {
112
- computed: "readonly",
113
- defineEmits: "readonly",
114
- defineExpose: "readonly",
115
- defineProps: "readonly",
116
- onMounted: "readonly",
117
- onUnmounted: "readonly",
118
- reactive: "readonly",
119
- ref: "readonly",
120
- shallowReactive: "readonly",
121
- shallowRef: "readonly",
122
- toRef: "readonly",
123
- toRefs: "readonly",
124
- watch: "readonly",
125
- watchEffect: "readonly"
126
- } }
127
- }, {
128
- name: "favorodera/vue/rules",
129
- files: resolved.files,
130
- languageOptions: {
131
- parser: vueParser,
132
- parserOptions: {
133
- parser: tsEsLint.parser,
134
- extraFileExtensions: [".vue"],
135
- sourceType: "module"
136
- }
137
- },
138
- processor,
139
- rules: {
140
- ...baseRules,
141
- "vue/block-order": ["error", { order: [
142
- "script",
143
- "template",
144
- "style"
145
- ] }],
146
- "vue/component-name-in-template-casing": ["error", "PascalCase"],
147
- "vue/component-options-name-casing": ["error", "PascalCase"],
148
- "vue/multi-word-component-names": "off",
149
- "vue/block-tag-newline": ["error", {
150
- multiline: "ignore",
151
- singleline: "ignore"
152
- }],
153
- "vue/multiline-html-element-content-newline": ["error", {
154
- allowEmptyLines: true,
155
- ignores: ["pre", "textarea"]
156
- }],
157
- ...resolved.overrides
158
- }
159
- }];
160
- }
161
- //#endregion
162
- //#region src/configs/ignores.ts
163
- const defaultPatterns = ignoresGlob;
164
- /**
165
- * Globs for ignoring files and directories from ESLint scanning.
166
- * @param patterns - Additional ignore patterns or a function to modify the defaults.
167
- * @returns An array containing the ignore flat config item.
168
- */
169
- function ignores(patterns = []) {
170
- return [{
171
- name: "favorodera/ignores",
172
- ignores: typeof patterns === "function" ? patterns(defaultPatterns) : [...defaultPatterns, ...patterns]
173
- }];
174
- }
175
116
  //#endregion
176
117
  //#region src/configs/imports.ts
177
118
  const importsDefaults = { files: [
@@ -179,6 +120,12 @@ const importsDefaults = { files: [
179
120
  tsGlob,
180
121
  vueGlob
181
122
  ] };
123
+ /**
124
+ * Constructs the flat config items for imports linting, providing plugin setup and
125
+ * specific rules to enforce consistent import ordering and unused variable checks.
126
+ * @param options Configuration options for imports linting.
127
+ * @returns Promise resolving to imports ESLint config items.
128
+ */
182
129
  async function imports(options) {
183
130
  const resolved = defu(options, importsDefaults);
184
131
  const [importPlugin, unusedImportsPlugin] = await Promise.all([importModule(import("eslint-plugin-import-lite")), importModule(import("eslint-plugin-unused-imports"))]);
@@ -190,16 +137,16 @@ async function imports(options) {
190
137
  "unused-imports": unusedImportsPlugin
191
138
  }
192
139
  }, {
193
- name: "favorodera/imports/rules",
194
140
  files: resolved.files,
141
+ name: "favorodera/imports/rules",
195
142
  rules: {
196
143
  ...renamePluginsInRules(baseRules, { "import-lite": "import" }),
197
- "import/consistent-type-specifier-style": ["error", "top-level"],
144
+ "import/consistent-type-specifier-style": ["error", "prefer-top-level"],
198
145
  "import/first": "error",
146
+ "import/newline-after-import": ["error", { count: 1 }],
199
147
  "import/no-duplicates": "error",
200
148
  "import/no-mutable-exports": "error",
201
149
  "import/no-named-default": "error",
202
- "import/newline-after-import": ["error", { count: 1 }],
203
150
  "unused-imports/no-unused-imports": "error",
204
151
  "unused-imports/no-unused-vars": ["error", {
205
152
  args: "after-used",
@@ -220,18 +167,17 @@ const javascriptDefaults = { files: [
220
167
  vueGlob
221
168
  ] };
222
169
  /**
223
- * Javascript linting via `eslint`
224
- * @param options - Javascript configuration options
225
- * @returns Promise resolving to javascript ESLint config items
170
+ * Constructs the flat config items for core JavaScript linting, setting up the required
171
+ * language options, globals, and recommended baseline rules.
172
+ * @param options Javascript configuration options.
173
+ * @returns Promise resolving to javascript ESLint config items.
226
174
  */
227
175
  async function javascript(options) {
228
176
  const resolved = defu(options, javascriptDefaults);
229
177
  const baseRules = (await importModule(import("@eslint/js"))).configs.recommended.rules;
230
178
  return [{
231
- name: "favorodera/javascript/setup",
232
179
  languageOptions: {
233
180
  ecmaVersion: "latest",
234
- sourceType: "module",
235
181
  globals: {
236
182
  ...globals.browser,
237
183
  ...globals.es2021,
@@ -239,12 +185,14 @@ async function javascript(options) {
239
185
  document: "readonly",
240
186
  navigator: "readonly",
241
187
  window: "readonly"
242
- }
188
+ },
189
+ sourceType: "module"
243
190
  },
244
- linterOptions: { reportUnusedDisableDirectives: true }
191
+ linterOptions: { reportUnusedDisableDirectives: true },
192
+ name: "favorodera/javascript/setup"
245
193
  }, {
246
- name: "favorodera/javascript/rules",
247
194
  files: resolved.files,
195
+ name: "favorodera/javascript/rules",
248
196
  rules: {
249
197
  ...baseRules,
250
198
  "accessor-pairs": ["error", {
@@ -258,189 +206,50 @@ async function javascript(options) {
258
206
  }];
259
207
  }
260
208
  //#endregion
261
- //#region src/configs/markdown.ts
262
- const markdownDefaults = {
263
- files: [mdGlob],
264
- gfm: true
265
- };
266
- async function markdown(options) {
267
- const resolved = defu(options, markdownDefaults);
268
- const markdownPlugin = await importModule(import("@eslint/markdown"));
269
- const baseRules = extractRules(markdownPlugin.configs.recommended);
270
- return [
271
- {
272
- name: "favorodera/markdown/setup",
273
- plugins: { md: markdownPlugin }
274
- },
275
- {
276
- name: "favorodera/markdown/rules",
277
- files: resolved.files,
278
- language: resolved.gfm ? "md/gfm" : "md/commonmark",
279
- ignores: [mdInMdGlob],
280
- processor: mergeProcessors([markdownPlugin.processors?.markdown, processorPassThrough]),
281
- rules: {
282
- ...renamePluginsInRules(baseRules, { markdown: "md" }),
283
- "md/fenced-code-language": "off",
284
- "md/no-missing-label-refs": "off",
285
- ...resolved.overrides
286
- }
287
- },
288
- {
289
- name: "favorodera/markdown/disables/code",
290
- files: [codeInMdGlob],
291
- languageOptions: { parserOptions: { ecmaFeatures: { impliedStrict: true } } },
292
- rules: {
293
- "no-alert": "off",
294
- "no-console": "off",
295
- "no-labels": "off",
296
- "no-lone-blocks": "off",
297
- "no-restricted-syntax": "off",
298
- "no-undef": "off",
299
- "no-unused-expressions": "off",
300
- "no-unused-labels": "off",
301
- "no-unused-vars": "off",
302
- "node/prefer-global/process": "off",
303
- "style/comma-dangle": "off",
304
- "style/eol-last": "off",
305
- "style/padding-line-between-statements": "off",
306
- "ts/consistent-type-imports": "off",
307
- "ts/explicit-function-return-type": "off",
308
- "ts/no-namespace": "off",
309
- "ts/no-redeclare": "off",
310
- "ts/no-require-imports": "off",
311
- "ts/no-unused-expressions": "off",
312
- "ts/no-unused-vars": "off",
313
- "ts/no-use-before-define": "off",
314
- "unicode-bom": "off",
315
- "unused-imports/no-unused-imports": "off",
316
- "unused-imports/no-unused-vars": "off"
317
- }
318
- }
319
- ];
320
- }
321
- //#endregion
322
- //#region src/configs/stylistic.ts
323
- const stylisticDefaults = {
324
- settings: {
325
- indent: 2,
326
- experimental: false,
327
- quotes: "single",
328
- semi: false,
329
- jsx: false
330
- },
331
- files: [
332
- jsGlob,
333
- tsGlob,
334
- vueGlob
335
- ]
336
- };
337
- async function stylistic(options) {
338
- const resolved = defu(options, stylisticDefaults);
339
- const stylePlugin = await importModule(import("@stylistic/eslint-plugin"));
340
- const baseRules = stylePlugin.configs.customize({
341
- pluginName: "style",
342
- ...resolved.settings
343
- })?.rules || {};
344
- return [{
345
- name: "favorodera/stylistic/setup",
346
- plugins: { style: stylePlugin }
347
- }, {
348
- name: "favorodera/stylistic/rules",
349
- files: resolved.files,
350
- rules: {
351
- ...baseRules,
352
- "style/quotes": [
353
- "error",
354
- "single",
355
- { avoidEscape: true }
356
- ],
357
- "style/no-multiple-empty-lines": ["error", {
358
- max: 2,
359
- maxEOF: 2,
360
- maxBOF: 0
361
- }],
362
- "style/padded-blocks": "off",
363
- "style/no-trailing-spaces": ["error", { skipBlankLines: true }],
364
- "style/brace-style": "off",
365
- "style/generator-star-spacing": ["error", {
366
- after: true,
367
- before: false
368
- }],
369
- "style/yield-star-spacing": ["error", {
370
- after: true,
371
- before: false
372
- }],
373
- ...resolved.overrides
374
- }
375
- }];
376
- }
377
- //#endregion
378
- //#region src/configs/tailwind.ts
379
- const tailwindDefaults = {
380
- files: [
381
- jsGlob,
382
- tsGlob,
383
- vueGlob
384
- ],
385
- settings: { detectComponentClasses: true }
386
- };
209
+ //#region src/configs/jsdoc.ts
210
+ const jsdocDefaults = { files: [
211
+ jsGlob,
212
+ tsGlob,
213
+ vueGlob
214
+ ] };
387
215
  /**
388
- * Tailwind linting via `eslint-plugin-better-tailwindcss`.
389
- * @param options - Tailwind configuration options
390
- * @returns Promise resolving to Tailwind ESLint config items
216
+ * Constructs the flat config items for JSDoc linting, ensuring comments follow
217
+ * proper styling, parameter checks, and types alignment.
218
+ * @param options JSDoc configuration options.
219
+ * @returns Promise resolving to JSDoc ESLint config items.
391
220
  */
392
- async function tailwind(options) {
393
- const resolved = defu(options, tailwindDefaults);
394
- const tailwindPlugin = await importModule(import("eslint-plugin-better-tailwindcss"));
221
+ async function jsdoc(options) {
222
+ const resolved = defu(options, jsdocDefaults);
223
+ const jsdocPlugin = await importModule(import("eslint-plugin-jsdoc"));
395
224
  const baseRules = {
396
- ...tailwindPlugin.configs["recommended-error"].rules,
397
- ...tailwindPlugin.configs["stylistic-error"].rules
225
+ ...jsdocPlugin.configs["flat/recommended-typescript-error"]?.rules,
226
+ ...jsdocPlugin.configs["flat/stylistic-typescript-error"]?.rules
398
227
  };
399
228
  return [{
400
- name: "favorodera/tailwind/setup",
401
- plugins: { tailwind: tailwindPlugin },
402
- settings: { tailwindcss: resolved.settings }
229
+ name: "favorodera/jsdoc/setup",
230
+ plugins: { jsdoc: jsdocPlugin }
403
231
  }, {
404
- name: "favorodera/tailwind/rules",
405
232
  files: resolved.files,
233
+ name: "favorodera/tailwind/rules",
406
234
  rules: {
407
- ...renamePluginsInRules(baseRules, { "better-tailwindcss": "tailwind" }),
408
- "tailwind/no-unregistered-classes": "off",
409
- "tailwind/enforce-consistent-line-wrapping": ["error", { group: "emptyLine" }],
410
- ...resolved.overrides
411
- }
412
- }];
413
- }
414
- //#endregion
415
- //#region src/configs/typescript.ts
416
- const typescriptDefaults = { files: [tsGlob] };
417
- /**
418
- * Typescript linting via `typescript-eslint`.
419
- * @param options - TypeScript configuration options
420
- * @returns Array of TypeScript ESLint config items
421
- */
422
- async function typescript(options) {
423
- const resolved = defu(options, typescriptDefaults);
424
- const tsEsLint = await importModule(import("typescript-eslint"));
425
- const baseRules = extractRules(tsEsLint.configs.recommended, tsEsLint.configs.strict);
426
- return [{
427
- name: "favorodera/typescript/setup",
428
- plugins: { ts: tsEsLint.plugin }
429
- }, {
430
- name: "favorodera/typescript/rules",
431
- files: resolved.files,
432
- languageOptions: {
433
- parser: tsEsLint.parser,
434
- parserOptions: { sourceType: "module" }
435
- },
436
- rules: {
437
- ...renamePluginsInRules(baseRules, { "@typescript-eslint": "ts" }),
438
- "ts/consistent-type-imports": ["error", { prefer: "type-imports" }],
439
- "ts/no-empty-object-type": ["error", { allowInterfaces: "with-single-extends" }],
440
- "ts/array-type": ["error", {
441
- default: "generic",
442
- readonly: "generic"
443
- }],
235
+ ...baseRules,
236
+ "jsdoc/check-access": "warn",
237
+ "jsdoc/check-alignment": "warn",
238
+ "jsdoc/check-param-names": "warn",
239
+ "jsdoc/check-property-names": "warn",
240
+ "jsdoc/check-types": "warn",
241
+ "jsdoc/empty-tags": "warn",
242
+ "jsdoc/implements-on-classes": "warn",
243
+ "jsdoc/multiline-blocks": "warn",
244
+ "jsdoc/no-defaults": "warn",
245
+ "jsdoc/no-multi-asterisks": "warn",
246
+ "jsdoc/require-param-name": "warn",
247
+ "jsdoc/require-property": "warn",
248
+ "jsdoc/require-property-description": "warn",
249
+ "jsdoc/require-property-name": "warn",
250
+ "jsdoc/require-returns-check": "warn",
251
+ "jsdoc/require-returns-description": "warn",
252
+ "jsdoc/require-yields-check": "warn",
444
253
  ...resolved.overrides
445
254
  }
446
255
  }];
@@ -452,6 +261,12 @@ const jsoncDefaults = { files: [
452
261
  jsoncGlob,
453
262
  jsonGlob
454
263
  ] };
264
+ /**
265
+ * Constructs the flat config items for JSON, JSON5, and JSONC linting, setting up
266
+ * the custom parser, rule validations, and sorting rules for package.json/tsconfig.json.
267
+ * @param options JSONC configuration options.
268
+ * @returns Promise resolving to JSONC ESLint config items.
269
+ */
455
270
  async function jsonc(options) {
456
271
  const resolved = defu(options, jsoncDefaults);
457
272
  return [
@@ -460,10 +275,18 @@ async function jsonc(options) {
460
275
  plugins: { jsonc: await importModule(import("eslint-plugin-jsonc")) }
461
276
  },
462
277
  {
463
- name: "favorodera/jsonc/rules",
464
278
  files: resolved.files,
465
279
  language: "jsonc/x",
280
+ name: "favorodera/jsonc/rules",
466
281
  rules: {
282
+ "jsonc/array-bracket-spacing": ["error", "never"],
283
+ "jsonc/comma-dangle": ["error", "never"],
284
+ "jsonc/comma-style": ["error", "last"],
285
+ "jsonc/indent": ["error", 2],
286
+ "jsonc/key-spacing": ["error", {
287
+ afterColon: true,
288
+ beforeColon: false
289
+ }],
467
290
  "jsonc/no-bigint-literals": "error",
468
291
  "jsonc/no-binary-expression": "error",
469
292
  "jsonc/no-binary-numeric-literals": "error",
@@ -487,17 +310,6 @@ async function jsonc(options) {
487
310
  "jsonc/no-undefined-value": "error",
488
311
  "jsonc/no-unicode-codepoint-escapes": "error",
489
312
  "jsonc/no-useless-escape": "error",
490
- "jsonc/space-unary-ops": "error",
491
- "jsonc/valid-json-number": "error",
492
- "jsonc/vue-custom-block/no-parsing-error": "error",
493
- "jsonc/array-bracket-spacing": ["error", "never"],
494
- "jsonc/comma-dangle": ["error", "never"],
495
- "jsonc/comma-style": ["error", "last"],
496
- "jsonc/indent": ["error", 2],
497
- "jsonc/key-spacing": ["error", {
498
- afterColon: true,
499
- beforeColon: false
500
- }],
501
313
  "jsonc/object-curly-newline": ["error", {
502
314
  consistent: true,
503
315
  multiline: true
@@ -506,12 +318,15 @@ async function jsonc(options) {
506
318
  "jsonc/object-property-newline": ["error", { allowAllPropertiesOnSameLine: true }],
507
319
  "jsonc/quote-props": "error",
508
320
  "jsonc/quotes": "error",
321
+ "jsonc/space-unary-ops": "error",
322
+ "jsonc/valid-json-number": "error",
323
+ "jsonc/vue-custom-block/no-parsing-error": "error",
509
324
  ...resolved.overrides
510
325
  }
511
326
  },
512
327
  {
513
- name: "favorodera/jsonc/sort/package-json",
514
328
  files: [packageJsonGlob],
329
+ name: "favorodera/jsonc/sort/package-json",
515
330
  rules: {
516
331
  "jsonc/sort-array-values": ["error", {
517
332
  order: { type: "asc" },
@@ -579,11 +394,11 @@ async function jsonc(options) {
579
394
  },
580
395
  {
581
396
  order: { type: "asc" },
582
- pathPattern: "^workspaces\\.catalog$"
397
+ pathPattern: String.raw`^workspaces\.catalog$`
583
398
  },
584
399
  {
585
400
  order: { type: "asc" },
586
- pathPattern: "^workspaces\\.catalogs\\.[^.]+$"
401
+ pathPattern: String.raw`^workspaces\.catalogs\.[^.]+$`
587
402
  },
588
403
  {
589
404
  order: [
@@ -613,8 +428,8 @@ async function jsonc(options) {
613
428
  }
614
429
  },
615
430
  {
616
- name: "favorodera/jsonc/sort/tsconfig-json",
617
431
  files: tsConfigGlob,
432
+ name: "favorodera/jsonc/sort/tsconfig-json",
618
433
  rules: { "jsonc/sort-keys": [
619
434
  "error",
620
435
  {
@@ -729,24 +544,461 @@ async function jsonc(options) {
729
544
  ];
730
545
  }
731
546
  //#endregion
547
+ //#region src/configs/markdown.ts
548
+ const markdownDefaults = {
549
+ files: [mdGlob],
550
+ gfm: true
551
+ };
552
+ /**
553
+ * Constructs the flat config items for Markdown linting, extracting and linting
554
+ * embedded code blocks within the document according to specified rules.
555
+ * @param options Markdown configuration options.
556
+ * @returns Promise resolving to Markdown ESLint config items.
557
+ */
558
+ async function markdown(options) {
559
+ const resolved = defu(options, markdownDefaults);
560
+ const markdownPlugin = await importModule(import("@eslint/markdown"));
561
+ const baseRules = extractRules(markdownPlugin.configs.recommended);
562
+ return [
563
+ {
564
+ name: "favorodera/markdown/setup",
565
+ plugins: { md: markdownPlugin }
566
+ },
567
+ {
568
+ files: resolved.files,
569
+ ignores: [mdInMdGlob],
570
+ language: resolved.gfm ? "md/gfm" : "md/commonmark",
571
+ name: "favorodera/markdown/rules",
572
+ processor: mergeProcessors([markdownPlugin.processors?.markdown, processorPassThrough]),
573
+ rules: {
574
+ ...renamePluginsInRules(baseRules, { markdown: "md" }),
575
+ "md/fenced-code-language": "off",
576
+ "md/no-missing-label-refs": "off",
577
+ ...resolved.overrides
578
+ }
579
+ },
580
+ {
581
+ files: [codeInMdGlob],
582
+ languageOptions: { parserOptions: { ecmaFeatures: { impliedStrict: true } } },
583
+ name: "favorodera/markdown/disables/code",
584
+ rules: {
585
+ "no-alert": "off",
586
+ "no-console": "off",
587
+ "no-labels": "off",
588
+ "no-lone-blocks": "off",
589
+ "no-restricted-syntax": "off",
590
+ "no-undef": "off",
591
+ "no-unused-expressions": "off",
592
+ "no-unused-labels": "off",
593
+ "no-unused-vars": "off",
594
+ "node/prefer-global/process": "off",
595
+ "style/comma-dangle": "off",
596
+ "style/eol-last": "off",
597
+ "style/padding-line-between-statements": "off",
598
+ "ts/consistent-type-imports": "off",
599
+ "ts/explicit-function-return-type": "off",
600
+ "ts/no-namespace": "off",
601
+ "ts/no-redeclare": "off",
602
+ "ts/no-require-imports": "off",
603
+ "ts/no-unused-expressions": "off",
604
+ "ts/no-unused-vars": "off",
605
+ "ts/no-use-before-define": "off",
606
+ "unicode-bom": "off",
607
+ "unused-imports/no-unused-imports": "off",
608
+ "unused-imports/no-unused-vars": "off"
609
+ }
610
+ }
611
+ ];
612
+ }
613
+ //#endregion
614
+ //#region src/configs/node.ts
615
+ const nodeDefaults = { files: [
616
+ jsGlob,
617
+ tsGlob,
618
+ vueGlob
619
+ ] };
620
+ /**
621
+ * Constructs the flat config items for Node.js linting, providing rules
622
+ * to enforce best practices for Node.js environments.
623
+ * @param options Node configuration options.
624
+ * @returns Promise resolving to Node ESLint config items.
625
+ */
626
+ async function node(options) {
627
+ const resolved = defu(options, nodeDefaults);
628
+ const nodePlugin = await importModule(import("eslint-plugin-n"));
629
+ const baseRules = nodePlugin.configs?.["flat/recommended"]?.rules || {};
630
+ return [{
631
+ name: "favorodera/node/setup",
632
+ plugins: { node: nodePlugin }
633
+ }, {
634
+ files: resolved.files,
635
+ name: "favorodera/node/rules",
636
+ rules: {
637
+ ...renamePluginsInRules(baseRules, { n: "node" }),
638
+ "node/handle-callback-err": ["error", "^(err|error)$"],
639
+ "node/no-deprecated-api": "error",
640
+ "node/no-exports-assign": "error",
641
+ "node/no-missing-import": "off",
642
+ "node/no-new-require": "error",
643
+ "node/no-path-concat": "error",
644
+ "node/no-unpublished-import": "off",
645
+ "node/prefer-global/buffer": ["error", "never"],
646
+ "node/prefer-global/process": ["error", "never"],
647
+ "node/process-exit-as-throw": "error",
648
+ ...resolved.overrides
649
+ }
650
+ }];
651
+ }
652
+ //#endregion
653
+ //#region src/configs/perfectionist.ts
654
+ const perfectionistDefaults = { files: [
655
+ jsGlob,
656
+ tsGlob,
657
+ vueGlob
658
+ ] };
659
+ /**
660
+ * Constructs the flat config items for Perfectionist linting, providing rules
661
+ * to naturally sort objects, imports, classes, and other elements in your code.
662
+ * @param options Perfectionist configuration options.
663
+ * @returns Promise resolving to Perfectionist ESLint config items.
664
+ */
665
+ async function perfectionist(options) {
666
+ const resolved = defu(options, perfectionistDefaults);
667
+ const perfectionistPlugin = await importModule(import("eslint-plugin-perfectionist"));
668
+ const baseRules = perfectionistPlugin.configs["recommended-natural"]?.rules || {};
669
+ return [{
670
+ name: "favorodera/perfectionist/setup",
671
+ plugins: { perfectionist: perfectionistPlugin }
672
+ }, {
673
+ files: resolved.files,
674
+ name: "favorodera/perfectionist/rules",
675
+ rules: {
676
+ ...baseRules,
677
+ "perfectionist/sort-exports": ["error", {
678
+ order: "asc",
679
+ type: "natural"
680
+ }],
681
+ "perfectionist/sort-imports": ["error", {
682
+ groups: [
683
+ "type-import",
684
+ [
685
+ "type-parent",
686
+ "type-sibling",
687
+ "type-index",
688
+ "type-internal"
689
+ ],
690
+ "value-builtin",
691
+ "value-external",
692
+ "value-internal",
693
+ [
694
+ "value-parent",
695
+ "value-sibling",
696
+ "value-index"
697
+ ],
698
+ "side-effect",
699
+ "ts-equals-import",
700
+ "unknown"
701
+ ],
702
+ newlinesBetween: "ignore",
703
+ newlinesInside: "ignore",
704
+ order: "asc",
705
+ type: "natural"
706
+ }],
707
+ "perfectionist/sort-named-exports": ["error", {
708
+ order: "asc",
709
+ type: "natural"
710
+ }],
711
+ "perfectionist/sort-named-imports": ["error", {
712
+ order: "asc",
713
+ type: "natural"
714
+ }],
715
+ ...resolved.overrides
716
+ }
717
+ }];
718
+ }
719
+ //#endregion
720
+ //#region src/configs/stylistic.ts
721
+ const stylisticDefaults = {
722
+ files: [
723
+ jsGlob,
724
+ tsGlob,
725
+ vueGlob
726
+ ],
727
+ settings: {
728
+ experimental: false,
729
+ indent: 2,
730
+ jsx: false,
731
+ quotes: "single",
732
+ semi: false
733
+ }
734
+ };
735
+ /**
736
+ * Constructs the flat config items for Stylistic linting, applying customized
737
+ * formatting settings such as quotes, indentation, and spacing.
738
+ * @param options Stylistic configuration options.
739
+ * @returns Promise resolving to stylistic ESLint config items.
740
+ */
741
+ async function stylistic(options) {
742
+ const resolved = defu(options, stylisticDefaults);
743
+ const stylePlugin = await importModule(import("@stylistic/eslint-plugin"));
744
+ const baseRules = stylePlugin.configs.customize({
745
+ pluginName: "style",
746
+ ...resolved.settings
747
+ })?.rules || {};
748
+ return [{
749
+ name: "favorodera/stylistic/setup",
750
+ plugins: { style: stylePlugin }
751
+ }, {
752
+ files: resolved.files,
753
+ name: "favorodera/stylistic/rules",
754
+ rules: {
755
+ ...baseRules,
756
+ "style/brace-style": "off",
757
+ "style/generator-star-spacing": ["error", {
758
+ after: true,
759
+ before: false
760
+ }],
761
+ "style/no-multiple-empty-lines": ["error", {
762
+ max: 2,
763
+ maxBOF: 0,
764
+ maxEOF: 2
765
+ }],
766
+ "style/no-trailing-spaces": ["error", { skipBlankLines: true }],
767
+ "style/padded-blocks": "off",
768
+ "style/quotes": [
769
+ "error",
770
+ "single",
771
+ { avoidEscape: true }
772
+ ],
773
+ "style/yield-star-spacing": ["error", {
774
+ after: true,
775
+ before: false
776
+ }],
777
+ ...resolved.overrides
778
+ }
779
+ }];
780
+ }
781
+ //#endregion
782
+ //#region src/configs/tailwind.ts
783
+ const tailwindDefaults = {
784
+ files: [
785
+ jsGlob,
786
+ tsGlob,
787
+ vueGlob
788
+ ],
789
+ settings: { detectComponentClasses: true }
790
+ };
791
+ /**
792
+ * Constructs the flat config items for Tailwind CSS linting, providing custom settings
793
+ * to parse and lint utility classes, and enforcing consistent ordering.
794
+ * @param options Tailwind configuration options.
795
+ * @returns Promise resolving to Tailwind ESLint config items.
796
+ */
797
+ async function tailwind(options) {
798
+ const resolved = defu(options, tailwindDefaults);
799
+ const tailwindPlugin = await importModule(import("eslint-plugin-better-tailwindcss"));
800
+ const baseRules = {
801
+ ...tailwindPlugin.configs["recommended-error"].rules,
802
+ ...tailwindPlugin.configs["stylistic-error"].rules
803
+ };
804
+ return [{
805
+ name: "favorodera/tailwind/setup",
806
+ plugins: { tailwind: tailwindPlugin },
807
+ settings: { tailwindcss: resolved.settings }
808
+ }, {
809
+ files: resolved.files,
810
+ name: "favorodera/tailwind/rules",
811
+ rules: {
812
+ ...renamePluginsInRules(baseRules, { "better-tailwindcss": "tailwind" }),
813
+ "tailwind/enforce-consistent-line-wrapping": ["error", { group: "emptyLine" }],
814
+ "tailwind/no-unregistered-classes": "off",
815
+ ...resolved.overrides
816
+ }
817
+ }];
818
+ }
819
+ //#endregion
820
+ //#region src/configs/typescript.ts
821
+ const typescriptDefaults = { files: [tsGlob] };
822
+ /**
823
+ * Constructs the flat config items for TypeScript linting, initializing the parser
824
+ * and extending the recommended and strict type-aware rule sets.
825
+ * @param options TypeScript configuration options.
826
+ * @returns Promise resolving to TypeScript ESLint config items.
827
+ */
828
+ async function typescript(options) {
829
+ const resolved = defu(options, typescriptDefaults);
830
+ const tsEsLint = await importModule(import("typescript-eslint"));
831
+ const baseRules = extractRules(tsEsLint.configs.recommended, tsEsLint.configs.strict);
832
+ return [{
833
+ name: "favorodera/typescript/setup",
834
+ plugins: { ts: tsEsLint.plugin }
835
+ }, {
836
+ files: resolved.files,
837
+ languageOptions: {
838
+ parser: tsEsLint.parser,
839
+ parserOptions: { sourceType: "module" }
840
+ },
841
+ name: "favorodera/typescript/rules",
842
+ rules: {
843
+ ...renamePluginsInRules(baseRules, { "@typescript-eslint": "ts" }),
844
+ "ts/array-type": ["error", {
845
+ default: "generic",
846
+ readonly: "generic"
847
+ }],
848
+ "ts/consistent-type-imports": ["error", { prefer: "type-imports" }],
849
+ "ts/no-empty-object-type": ["error", { allowInterfaces: "with-single-extends" }],
850
+ ...resolved.overrides
851
+ }
852
+ }];
853
+ }
854
+ //#endregion
855
+ //#region src/configs/unicorn.ts
856
+ const unicornDefaults = { files: [
857
+ jsGlob,
858
+ tsGlob,
859
+ vueGlob
860
+ ] };
861
+ /**
862
+ * Constructs the flat config items for Unicorn linting, providing a collection of
863
+ * awesome ESLint rules to improve code quality and enforce best practices.
864
+ * @param options Unicorn configuration options.
865
+ * @returns Promise resolving to Unicorn ESLint config items.
866
+ */
867
+ async function unicorn(options) {
868
+ const resolved = defu(options, unicornDefaults);
869
+ const unicornPlugin = await importModule(import("eslint-plugin-unicorn"));
870
+ const baseRules = unicornPlugin.configs["unopinionated"]?.rules || {};
871
+ return [{
872
+ name: "favorodera/unicorn/setup",
873
+ plugins: { unicorn: unicornPlugin }
874
+ }, {
875
+ files: resolved.files,
876
+ languageOptions: { globals: globals.builtin },
877
+ name: "favorodera/unicorn/rules",
878
+ rules: {
879
+ ...baseRules,
880
+ "unicorn/consistent-empty-array-spread": "error",
881
+ "unicorn/error-message": "error",
882
+ "unicorn/escape-case": "error",
883
+ "unicorn/new-for-builtins": "error",
884
+ "unicorn/no-instanceof-builtins": "error",
885
+ "unicorn/no-new-array": "error",
886
+ "unicorn/no-new-buffer": "error",
887
+ "unicorn/number-literal-case": "error",
888
+ "unicorn/prefer-dom-node-text-content": "error",
889
+ "unicorn/prefer-includes": "error",
890
+ "unicorn/prefer-node-protocol": "error",
891
+ "unicorn/prefer-number-properties": "error",
892
+ "unicorn/prefer-string-starts-ends-with": "error",
893
+ "unicorn/prefer-type-error": "error",
894
+ "unicorn/throw-new-error": "error",
895
+ ...resolved.overrides
896
+ }
897
+ }];
898
+ }
899
+ //#endregion
900
+ //#region src/configs/vue.ts
901
+ const sfcBlocksDefaults = { blocks: {
902
+ customBlocks: true,
903
+ styles: true,
904
+ template: false
905
+ } };
906
+ const vueDefaults = {
907
+ files: [vueGlob],
908
+ sfcBlocks: sfcBlocksDefaults
909
+ };
910
+ /**
911
+ * Constructs the flat config items for Vue linting, setting up the custom template parser,
912
+ * Vue block processors, and rules to enforce recommended component syntax.
913
+ * @param options Vue configuration options.
914
+ * @returns Promise resolving to Vue ESLint config items.
915
+ */
916
+ async function vue(options) {
917
+ const resolved = defu(options, vueDefaults);
918
+ const sfcBlocks = resolveOptions(resolved.sfcBlocks, sfcBlocksDefaults);
919
+ const [vuePlugin, vueParser, tsEsLint] = await Promise.all([
920
+ importModule(import("eslint-plugin-vue")),
921
+ importModule(import("vue-eslint-parser")),
922
+ importModule(import("typescript-eslint"))
923
+ ]);
924
+ const baseRules = extractRules(vuePlugin.configs["flat/essential"], vuePlugin.configs["flat/strongly-recommended"], vuePlugin.configs["flat/recommended"]);
925
+ const processor = sfcBlocks === false ? vuePlugin.processors[".vue"] : mergeProcessors([vuePlugin.processors[".vue"], vueBlocksProcessor(sfcBlocks)]);
926
+ return [{
927
+ languageOptions: { globals: {
928
+ computed: "readonly",
929
+ defineEmits: "readonly",
930
+ defineExpose: "readonly",
931
+ defineProps: "readonly",
932
+ onMounted: "readonly",
933
+ onUnmounted: "readonly",
934
+ reactive: "readonly",
935
+ ref: "readonly",
936
+ shallowReactive: "readonly",
937
+ shallowRef: "readonly",
938
+ toRef: "readonly",
939
+ toRefs: "readonly",
940
+ watch: "readonly",
941
+ watchEffect: "readonly"
942
+ } },
943
+ name: "favorodera/vue/setup",
944
+ plugins: { vue: vuePlugin }
945
+ }, {
946
+ files: resolved.files,
947
+ languageOptions: {
948
+ parser: vueParser,
949
+ parserOptions: {
950
+ extraFileExtensions: [".vue"],
951
+ parser: tsEsLint.parser,
952
+ sourceType: "module"
953
+ }
954
+ },
955
+ name: "favorodera/vue/rules",
956
+ processor,
957
+ rules: {
958
+ ...baseRules,
959
+ "vue/block-order": ["error", { order: [
960
+ "script",
961
+ "template",
962
+ "style"
963
+ ] }],
964
+ "vue/block-tag-newline": ["error", {
965
+ multiline: "ignore",
966
+ singleline: "ignore"
967
+ }],
968
+ "vue/component-name-in-template-casing": ["error", "PascalCase"],
969
+ "vue/component-options-name-casing": ["error", "PascalCase"],
970
+ "vue/multi-word-component-names": "off",
971
+ "vue/multiline-html-element-content-newline": ["error", {
972
+ allowEmptyLines: true,
973
+ ignores: ["pre", "textarea"]
974
+ }],
975
+ ...resolved.overrides
976
+ }
977
+ }];
978
+ }
979
+ //#endregion
732
980
  //#region src/factory.ts
733
981
  /**
734
- * Factory to create a flat ESLint config
735
- * @param options - Configuration options for the ESLint config
736
- * @returns Flat ESLint config composer
982
+ * Factory to create a flat ESLint config.
983
+ * It builds an ESLint config by sequentially adding sub-configs based on the provided options.
984
+ * @param options Configuration options for enabling/disabling or configuring specific rule sets.
985
+ * @returns A flat config composer instance that can be exported directly or further modified.
737
986
  */
738
987
  function factory(options = {}) {
739
- const configs = [];
740
- configs.push(ignores(options.ignores));
988
+ const configs = [ignores(options.ignores)];
741
989
  const configFunctions = {
742
- vue,
743
- typescript,
744
- stylistic,
745
- tailwind,
746
990
  imports,
747
- markdown,
748
991
  javascript,
749
- jsonc
992
+ jsdoc,
993
+ jsonc,
994
+ markdown,
995
+ node,
996
+ perfectionist,
997
+ stylistic,
998
+ tailwind,
999
+ typescript,
1000
+ unicorn,
1001
+ vue
750
1002
  };
751
1003
  for (const [key, configFunction] of Object.entries(configFunctions)) {
752
1004
  const configOption = options[key];
@@ -755,10 +1007,11 @@ function factory(options = {}) {
755
1007
  }
756
1008
  let composer = new FlatConfigComposer();
757
1009
  composer = composer.append(...configs).renamePlugins({
758
- "better-tailwindcss": "tailwind",
759
1010
  "@typescript-eslint": "ts",
1011
+ "better-tailwindcss": "tailwind",
1012
+ "import-lite": "import",
760
1013
  "markdown": "md",
761
- "import-lite": "import"
1014
+ "n": "node"
762
1015
  });
763
1016
  return composer;
764
1017
  }