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