@favorodera/eslint-config 0.0.5 → 0.0.6

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Favour Emeka
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -32,6 +32,7 @@ The following configurations are bundled and enabled by default (they can be ind
32
32
  - **Vue** — Single-file component support (`eslint-plugin-vue`, `vue-eslint-parser`)
33
33
  - **Stylistic** — Formatting rules (`@stylistic/eslint-plugin`)
34
34
  - **Tailwind CSS** — Utility class linting and sorting (`eslint-plugin-better-tailwindcss`)
35
+ - **Test** — Test and Vitest specific linting (`@vitest/eslint-plugin`)
35
36
  - **Imports** — Auto-sorting and unused imports removal (`eslint-plugin-import-lite`, `eslint-plugin-unused-imports`)
36
37
  - **Markdown** — Linting for markdown files and embedded code blocks (`@eslint/markdown`)
37
38
  - **JSON/JSONC/JSON5** — Formatting and sorting for JSON files like `package.json` (`eslint-plugin-jsonc`)
@@ -79,9 +80,9 @@ bun add -D eslint @favorodera/eslint-config
79
80
 
80
81
  ### Configure
81
82
 
82
- Create an `eslint.config.mjs` (or `eslint.config.ts`) file in the root of your project. Since all configurations are enabled by default, you only need to pass options if you want to disable or customize something:
83
+ Create an `eslint.config.ts` file in the root of your project. Since all configurations are enabled by default, you only need to pass options if you want to disable or customize something:
83
84
 
84
- ```javascript
85
+ ```ts
85
86
  import { factory } from '@favorodera/eslint-config'
86
87
 
87
88
  export default factory({
package/dist/index.cjs CHANGED
@@ -41,6 +41,8 @@ const mdGlob = "**/*.md";
41
41
  const mdInMdGlob = "**/*.md/*.md";
42
42
  /** Glob pattern for matching code blocks embedded in Markdown files */
43
43
  const codeInMdGlob = "**/*.md/**/*.{js,cjs,mjs,ts,cts,mts,vue}";
44
+ /** Glob pattern for matching test files */
45
+ const testsGlob = ["**/*.{tests,specs,benchmark,bench}.{js,cjs,mjs,ts,cts,mts}", "**/__tests__/**/*.{js,cjs,mjs,ts,cts,mts}"];
44
46
  /** Glob pattern for matching JSON files */
45
47
  const jsonGlob = "**/*.json";
46
48
  /** Glob pattern for matching JSON5 files */
@@ -49,6 +51,10 @@ const json5Glob = "**/*.json5";
49
51
  const jsoncGlob = "**/*.jsonc";
50
52
  /** Glob patterns for matching TypeScript configuration files */
51
53
  const tsConfigGlob = ["**/tsconfig.json", "**/tsconfig.*.json"];
54
+ /** Glob pattern for matching YAML files */
55
+ const yamlGlob = "**/*.{yml,yaml}";
56
+ /** Glob pattern for matching pnpm-workspace.yaml file */
57
+ const pnpmWorkspaceGlob = "pnpm-workspace.yaml";
52
58
  /** Glob pattern for matching package.json files */
53
59
  const packageJsonGlob = "**/package.json";
54
60
  //#endregion
@@ -220,12 +226,182 @@ async function javascript(options) {
220
226
  name: "favorodera/javascript/rules",
221
227
  rules: {
222
228
  ...baseRules,
223
- "accessor-pairs": ["error", {
224
- enforceForClassMembers: true,
225
- setWithoutGet: true
226
- }],
227
229
  "array-callback-return": "error",
228
230
  "block-scoped-var": "error",
231
+ "constructor-super": "error",
232
+ "default-case-last": "error",
233
+ "dot-notation": ["error", { allowKeywords: true }],
234
+ "eqeqeq": ["error", "smart"],
235
+ "new-cap": ["error", {
236
+ capIsNew: false,
237
+ newIsCap: true,
238
+ properties: true
239
+ }],
240
+ "no-alert": "error",
241
+ "no-array-constructor": "error",
242
+ "no-async-promise-executor": "error",
243
+ "no-caller": "error",
244
+ "no-case-declarations": "error",
245
+ "no-class-assign": "error",
246
+ "no-compare-neg-zero": "error",
247
+ "no-cond-assign": ["error", "always"],
248
+ "no-console": ["error", { allow: ["warn", "error"] }],
249
+ "no-const-assign": "error",
250
+ "no-control-regex": "error",
251
+ "no-debugger": "error",
252
+ "no-delete-var": "error",
253
+ "no-dupe-args": "error",
254
+ "no-dupe-class-members": "error",
255
+ "no-dupe-keys": "error",
256
+ "no-duplicate-case": "error",
257
+ "no-empty": ["error", { allowEmptyCatch: true }],
258
+ "no-empty-character-class": "error",
259
+ "no-empty-pattern": "error",
260
+ "no-eval": "error",
261
+ "no-ex-assign": "error",
262
+ "no-extend-native": "error",
263
+ "no-extra-bind": "error",
264
+ "no-extra-boolean-cast": "error",
265
+ "no-fallthrough": "error",
266
+ "no-func-assign": "error",
267
+ "no-global-assign": "error",
268
+ "no-implied-eval": "error",
269
+ "no-import-assign": "error",
270
+ "no-invalid-regexp": "error",
271
+ "no-irregular-whitespace": "error",
272
+ "no-iterator": "error",
273
+ "no-labels": ["error", {
274
+ allowLoop: false,
275
+ allowSwitch: false
276
+ }],
277
+ "no-lone-blocks": "error",
278
+ "no-loss-of-precision": "error",
279
+ "no-misleading-character-class": "error",
280
+ "no-multi-str": "error",
281
+ "no-new": "error",
282
+ "no-new-func": "error",
283
+ "no-new-native-nonconstructor": "error",
284
+ "no-new-wrappers": "error",
285
+ "no-obj-calls": "error",
286
+ "no-octal": "error",
287
+ "no-octal-escape": "error",
288
+ "no-proto": "error",
289
+ "no-prototype-builtins": "error",
290
+ "no-redeclare": ["error", { builtinGlobals: false }],
291
+ "no-regex-spaces": "error",
292
+ "no-restricted-globals": [
293
+ "error",
294
+ {
295
+ message: "Use `globalThis` instead.",
296
+ name: "global"
297
+ },
298
+ {
299
+ message: "Use `globalThis` instead.",
300
+ name: "self"
301
+ }
302
+ ],
303
+ "no-restricted-properties": [
304
+ "error",
305
+ {
306
+ message: "Use `Object.getPrototypeOf` or `Object.setPrototypeOf` instead.",
307
+ property: "__proto__"
308
+ },
309
+ {
310
+ message: "Use `Object.defineProperty` instead.",
311
+ property: "__defineGetter__"
312
+ },
313
+ {
314
+ message: "Use `Object.defineProperty` instead.",
315
+ property: "__defineSetter__"
316
+ },
317
+ {
318
+ message: "Use `Object.getOwnPropertyDescriptor` instead.",
319
+ property: "__lookupGetter__"
320
+ },
321
+ {
322
+ message: "Use `Object.getOwnPropertyDescriptor` instead.",
323
+ property: "__lookupSetter__"
324
+ }
325
+ ],
326
+ "no-restricted-syntax": [
327
+ "error",
328
+ "TSEnumDeclaration[const=true]",
329
+ "TSExportAssignment"
330
+ ],
331
+ "no-self-assign": ["error", { props: true }],
332
+ "no-self-compare": "error",
333
+ "no-sequences": "error",
334
+ "no-shadow-restricted-names": "error",
335
+ "no-sparse-arrays": "error",
336
+ "no-template-curly-in-string": "error",
337
+ "no-this-before-super": "error",
338
+ "no-throw-literal": "error",
339
+ "no-undef": "error",
340
+ "no-undef-init": "error",
341
+ "no-unexpected-multiline": "error",
342
+ "no-unmodified-loop-condition": "error",
343
+ "no-unneeded-ternary": ["error", { defaultAssignment: false }],
344
+ "no-unreachable": "error",
345
+ "no-unreachable-loop": "error",
346
+ "no-unsafe-finally": "error",
347
+ "no-unsafe-negation": "error",
348
+ "no-unused-expressions": ["error", {
349
+ allowShortCircuit: true,
350
+ allowTaggedTemplates: true,
351
+ allowTernary: true
352
+ }],
353
+ "no-unused-vars": ["error", {
354
+ args: "none",
355
+ caughtErrors: "none",
356
+ ignoreRestSiblings: true,
357
+ vars: "all"
358
+ }],
359
+ "no-use-before-define": ["error", {
360
+ classes: false,
361
+ functions: false,
362
+ variables: true
363
+ }],
364
+ "no-useless-backreference": "error",
365
+ "no-useless-call": "error",
366
+ "no-useless-catch": "error",
367
+ "no-useless-computed-key": "error",
368
+ "no-useless-constructor": "error",
369
+ "no-useless-rename": "error",
370
+ "no-useless-return": "error",
371
+ "no-var": "error",
372
+ "no-with": "error",
373
+ "object-shorthand": [
374
+ "error",
375
+ "always",
376
+ {
377
+ avoidQuotes: true,
378
+ ignoreConstructors: false
379
+ }
380
+ ],
381
+ "one-var": ["error", { initialized: "never" }],
382
+ "prefer-arrow-callback": ["error", {
383
+ allowNamedFunctions: false,
384
+ allowUnboundThis: true
385
+ }],
386
+ "prefer-const": ["error", {
387
+ destructuring: "all",
388
+ ignoreReadBeforeAssign: true
389
+ }],
390
+ "prefer-exponentiation-operator": "error",
391
+ "prefer-promise-reject-errors": "error",
392
+ "prefer-regex-literals": ["error", { disallowRedundantWrapping: true }],
393
+ "prefer-rest-params": "error",
394
+ "prefer-spread": "error",
395
+ "prefer-template": "error",
396
+ "symbol-description": "error",
397
+ "unicode-bom": ["error", "never"],
398
+ "use-isnan": ["error", {
399
+ enforceForIndexOf: true,
400
+ enforceForSwitchCase: true
401
+ }],
402
+ "valid-typeof": ["error", { requireStringLiterals: true }],
403
+ "vars-on-top": "error",
404
+ "yoda": ["error", "never"],
229
405
  ...resolved.overrides
230
406
  }
231
407
  }];
@@ -255,7 +431,7 @@ async function jsdoc(options) {
255
431
  plugins: { jsdoc: jsdocPlugin }
256
432
  }, {
257
433
  files: resolved.files,
258
- name: "favorodera/tailwind/rules",
434
+ name: "favorodera/jsdoc/rules",
259
435
  rules: {
260
436
  ...baseRules,
261
437
  "jsdoc/check-access": "warn",
@@ -605,7 +781,7 @@ async function markdown(options) {
605
781
  {
606
782
  files: [codeInMdGlob],
607
783
  languageOptions: { parserOptions: { ecmaFeatures: { impliedStrict: true } } },
608
- name: "favorodera/markdown/disables/code",
784
+ name: "favorodera/markdown/code-in-md/disables",
609
785
  rules: {
610
786
  "no-alert": "off",
611
787
  "no-console": "off",
@@ -742,6 +918,47 @@ async function perfectionist(options) {
742
918
  }];
743
919
  }
744
920
  //#endregion
921
+ //#region src/configs/pnpm.ts
922
+ /**
923
+ * Constructs the flat config items for pnpm linting.
924
+ * @returns Promise resolving to pnpm ESLint config items.
925
+ */
926
+ async function pnpm() {
927
+ const [pnpmPlugin, yamlParser] = await Promise.all([importModule(import("eslint-plugin-pnpm")), importModule(import("yaml-eslint-parser"))]);
928
+ return [
929
+ {
930
+ name: "favorodera/pnpm/setup",
931
+ plugins: { pnpm: pnpmPlugin }
932
+ },
933
+ {
934
+ files: [packageJsonGlob],
935
+ language: "jsonc/x",
936
+ name: "favorodera/pnpm/package-json",
937
+ rules: {
938
+ "pnpm/json-enforce-catalog": ["error", {
939
+ autofix: true,
940
+ ignores: ["@types/vscode"]
941
+ }],
942
+ "pnpm/json-prefer-workspace-settings": ["error", { autofix: true }],
943
+ "pnpm/json-valid-catalog": ["error", { autofix: true }]
944
+ }
945
+ },
946
+ {
947
+ files: [pnpmWorkspaceGlob],
948
+ languageOptions: { parser: yamlParser },
949
+ name: "favorodera/pnpm/pnpm-workspace-yaml",
950
+ rules: {
951
+ "pnpm/yaml-enforce-settings": ["error", { settings: {
952
+ shellEmulator: true,
953
+ trustPolicy: "no-downgrade"
954
+ } }],
955
+ "pnpm/yaml-no-duplicate-catalog-item": ["error", { checkDuplicates: "exact-version" }],
956
+ "pnpm/yaml-no-unused-catalog-item": "error"
957
+ }
958
+ }
959
+ ];
960
+ }
961
+ //#endregion
745
962
  //#region src/configs/stylistic.ts
746
963
  const stylisticDefaults = {
747
964
  files: [
@@ -836,12 +1053,55 @@ async function tailwind(options) {
836
1053
  rules: {
837
1054
  ...(0, eslint_flat_config_utils.renamePluginsInRules)(baseRules, { "better-tailwindcss": "tailwind" }),
838
1055
  "tailwind/enforce-consistent-line-wrapping": ["error", { group: "emptyLine" }],
839
- "tailwind/no-unregistered-classes": "off",
840
1056
  ...resolved.overrides
841
1057
  }
842
1058
  }];
843
1059
  }
844
1060
  //#endregion
1061
+ //#region src/configs/test.ts
1062
+ const testDefaults = { files: testsGlob };
1063
+ /**
1064
+ * Constructs the flat config items for test linting, extending
1065
+ * the recommended Vitest rule sets.
1066
+ * @param options Test configuration options.
1067
+ * @returns Promise resolving to test ESLint config items.
1068
+ */
1069
+ async function test(options) {
1070
+ const resolved = (0, defu.defu)(options, testDefaults);
1071
+ const testPlugin = await importModule(import("@vitest/eslint-plugin"));
1072
+ const baseRules = extractRules(testPlugin.configs.recommended);
1073
+ return [
1074
+ {
1075
+ name: "favorodera/test/setup",
1076
+ plugins: { test: testPlugin }
1077
+ },
1078
+ {
1079
+ files: resolved.files,
1080
+ name: "favorodera/test/rules",
1081
+ rules: {
1082
+ ...(0, eslint_flat_config_utils.renamePluginsInRules)(baseRules, { vitest: "test" }),
1083
+ "test/consistent-test-it": ["error", {
1084
+ fn: "it",
1085
+ withinDescribe: "it"
1086
+ }],
1087
+ "test/no-identical-title": "error",
1088
+ "test/no-import-node-test": "error",
1089
+ "test/prefer-hooks-in-order": "error",
1090
+ "test/prefer-lowercase-title": "error",
1091
+ ...resolved.overrides
1092
+ }
1093
+ },
1094
+ {
1095
+ files: resolved.files,
1096
+ name: "favorodera/test/disables",
1097
+ rules: {
1098
+ "no-unused-expressions": "off",
1099
+ "node/prefer-global/process": "off"
1100
+ }
1101
+ }
1102
+ ];
1103
+ }
1104
+ //#endregion
845
1105
  //#region src/configs/typescript.ts
846
1106
  const typescriptDefaults = { files: [tsGlob] };
847
1107
  /**
@@ -892,7 +1152,7 @@ const unicornDefaults = { files: [
892
1152
  async function unicorn(options) {
893
1153
  const resolved = (0, defu.defu)(options, unicornDefaults);
894
1154
  const unicornPlugin = await importModule(import("eslint-plugin-unicorn"));
895
- const baseRules = unicornPlugin.configs["unopinionated"]?.rules || {};
1155
+ const baseRules = unicornPlugin.configs.unopinionated?.rules || {};
896
1156
  return [{
897
1157
  name: "favorodera/unicorn/setup",
898
1158
  plugins: { unicorn: unicornPlugin }
@@ -1002,6 +1262,130 @@ async function vue(options) {
1002
1262
  }];
1003
1263
  }
1004
1264
  //#endregion
1265
+ //#region src/configs/yaml.ts
1266
+ const yamlDefaults = { files: [yamlGlob] };
1267
+ /**
1268
+ * Constructs the flat config items for YAML linting, setting up
1269
+ * the custom parser and rule validations.
1270
+ * @param options YAML configuration options.
1271
+ * @returns Promise resolving to YAML ESLint config items.
1272
+ */
1273
+ async function yaml(options) {
1274
+ const resolved = (0, defu.defu)(options, yamlDefaults);
1275
+ const [yamlPlugin, yamlParser] = await Promise.all([importModule(import("eslint-plugin-yml")), importModule(import("yaml-eslint-parser"))]);
1276
+ const baseRules = extractRules(yamlPlugin.configs.recommended);
1277
+ return [
1278
+ {
1279
+ name: "favorodera/yaml/setup",
1280
+ plugins: { yaml: yamlPlugin }
1281
+ },
1282
+ {
1283
+ files: resolved.files,
1284
+ languageOptions: { parser: yamlParser },
1285
+ name: "favorodera/yaml/rules",
1286
+ rules: {
1287
+ ...(0, eslint_flat_config_utils.renamePluginsInRules)(baseRules, { yml: "yaml" }),
1288
+ "style/spaced-comment": "off",
1289
+ "yaml/block-mapping": "error",
1290
+ "yaml/block-mapping-question-indicator-newline": "error",
1291
+ "yaml/block-sequence": "error",
1292
+ "yaml/block-sequence-hyphen-indicator-newline": "error",
1293
+ "yaml/flow-mapping-curly-newline": "error",
1294
+ "yaml/flow-mapping-curly-spacing": "error",
1295
+ "yaml/flow-sequence-bracket-newline": "error",
1296
+ "yaml/flow-sequence-bracket-spacing": "error",
1297
+ "yaml/indent": ["error", 2],
1298
+ "yaml/key-spacing": "error",
1299
+ "yaml/no-empty-key": "error",
1300
+ "yaml/no-empty-sequence-entry": "error",
1301
+ "yaml/no-irregular-whitespace": "error",
1302
+ "yaml/no-tab-indent": "error",
1303
+ "yaml/plain-scalar": "error",
1304
+ "yaml/quotes": ["error", {
1305
+ avoidEscape: true,
1306
+ prefer: "single"
1307
+ }],
1308
+ "yaml/spaced-comment": "error",
1309
+ "yaml/vue-custom-block/no-parsing-error": "error",
1310
+ ...resolved.overrides
1311
+ }
1312
+ },
1313
+ {
1314
+ files: [pnpmWorkspaceGlob],
1315
+ languageOptions: { parser: yamlParser },
1316
+ name: "favorodera/yaml/sort/pnpm-workspace-yaml",
1317
+ rules: { "yaml/sort-keys": [
1318
+ "error",
1319
+ {
1320
+ order: [
1321
+ "cacheDir",
1322
+ "catalogMode",
1323
+ "cleanupUnusedCatalogs",
1324
+ "dedupeDirectDeps",
1325
+ "deployAllFiles",
1326
+ "enablePrePostScripts",
1327
+ "engineStrict",
1328
+ "extendNodePath",
1329
+ "hoist",
1330
+ "hoistPattern",
1331
+ "hoistWorkspacePackages",
1332
+ "ignoreCompatibilityDb",
1333
+ "ignoreDepScripts",
1334
+ "ignoreScripts",
1335
+ "ignoreWorkspaceRootCheck",
1336
+ "managePackageManagerVersions",
1337
+ "minimumReleaseAge",
1338
+ "minimumReleaseAgeExclude",
1339
+ "modulesDir",
1340
+ "nodeLinker",
1341
+ "nodeVersion",
1342
+ "optimisticRepeatInstall",
1343
+ "packageManagerStrict",
1344
+ "packageManagerStrictVersion",
1345
+ "preferSymlinkedExecutables",
1346
+ "preferWorkspacePackages",
1347
+ "publicHoistPattern",
1348
+ "registrySupportsTimeField",
1349
+ "requiredScripts",
1350
+ "resolutionMode",
1351
+ "savePrefix",
1352
+ "scriptShell",
1353
+ "shamefullyHoist",
1354
+ "shellEmulator",
1355
+ "stateDir",
1356
+ "supportedArchitectures",
1357
+ "symlink",
1358
+ "tag",
1359
+ "trustPolicy",
1360
+ "trustPolicyExclude",
1361
+ "updateNotifier",
1362
+ "packages",
1363
+ "overrides",
1364
+ "patchedDependencies",
1365
+ "catalog",
1366
+ "catalogs",
1367
+ "allowedDeprecatedVersions",
1368
+ "allowNonAppliedPatches",
1369
+ "configDependencies",
1370
+ "ignoredBuiltDependencies",
1371
+ "ignoredOptionalDependencies",
1372
+ "neverBuiltDependencies",
1373
+ "onlyBuiltDependencies",
1374
+ "onlyBuiltDependenciesFile",
1375
+ "packageExtensions",
1376
+ "peerDependencyRules"
1377
+ ],
1378
+ pathPattern: "^$"
1379
+ },
1380
+ {
1381
+ order: { type: "asc" },
1382
+ pathPattern: ".*"
1383
+ }
1384
+ ] }
1385
+ }
1386
+ ];
1387
+ }
1388
+ //#endregion
1005
1389
  //#region src/factory.ts
1006
1390
  /**
1007
1391
  * Factory to create a flat ESLint config.
@@ -1019,11 +1403,14 @@ function factory(options = {}) {
1019
1403
  markdown,
1020
1404
  node,
1021
1405
  perfectionist,
1406
+ pnpm,
1022
1407
  stylistic,
1023
1408
  tailwind,
1409
+ test,
1024
1410
  typescript,
1025
1411
  unicorn,
1026
- vue
1412
+ vue,
1413
+ yaml
1027
1414
  };
1028
1415
  for (const [key, configFunction] of Object.entries(configFunctions)) {
1029
1416
  const configOption = options[key];
@@ -1036,10 +1423,13 @@ function factory(options = {}) {
1036
1423
  "better-tailwindcss": "tailwind",
1037
1424
  "import-lite": "import",
1038
1425
  "markdown": "md",
1039
- "n": "node"
1426
+ "n": "node",
1427
+ "vitest": "test",
1428
+ "yml": "yaml"
1040
1429
  });
1041
1430
  return composer;
1042
1431
  }
1043
1432
  //#endregion
1433
+ exports.extractRules = extractRules;
1044
1434
  exports.factory = factory;
1045
1435
  exports.importModule = importModule;