@fabdeh/eslint-config 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +115 -60
- package/dist/index.d.mts +660 -345
- package/dist/index.mjs +209 -241
- package/package.json +21 -34
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import tseslint from "typescript-eslint";
|
|
2
2
|
import { statSync } from "node:fs";
|
|
3
|
-
import {
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, join, normalize, resolve } from "node:path";
|
|
4
5
|
import process from "node:process";
|
|
6
|
+
import { glob } from "glob";
|
|
5
7
|
import { isPackageExists } from "local-pkg";
|
|
6
8
|
import eslintComments from "@eslint-community/eslint-plugin-eslint-comments";
|
|
7
9
|
import * as importX from "eslint-plugin-import-x";
|
|
@@ -31,22 +33,6 @@ const GLOB_TS_EXT = "?([cm])ts?(x)";
|
|
|
31
33
|
*/
|
|
32
34
|
const GLOB_TS = `**/*.${GLOB_TS_EXT}`;
|
|
33
35
|
/**
|
|
34
|
-
* A glob pattern that matches all CSS files.
|
|
35
|
-
*/
|
|
36
|
-
const GLOB_CSS = "**/*.css";
|
|
37
|
-
/**
|
|
38
|
-
* A glob pattern that matches all PostCSS files.
|
|
39
|
-
*/
|
|
40
|
-
const GLOB_POSTCSS = "**/*.{p,post}css";
|
|
41
|
-
/**
|
|
42
|
-
* A glob pattern that matches all LESS files.
|
|
43
|
-
*/
|
|
44
|
-
const GLOB_LESS = "**/*.less";
|
|
45
|
-
/**
|
|
46
|
-
* A glob pattern that matches all SCSS files.
|
|
47
|
-
*/
|
|
48
|
-
const GLOB_SCSS = "**/*.scss";
|
|
49
|
-
/**
|
|
50
36
|
* A glob pattern that matches all JSON files.
|
|
51
37
|
*/
|
|
52
38
|
const GLOB_JSON = "**/*.json";
|
|
@@ -79,29 +65,14 @@ const GLOB_YAML = "**/*.y?(a)ml";
|
|
|
79
65
|
*/
|
|
80
66
|
const GLOB_TOML = "**/*.toml";
|
|
81
67
|
/**
|
|
82
|
-
* A glob pattern that matches XML files.
|
|
83
|
-
*/
|
|
84
|
-
const GLOB_XML = "**/*.xml";
|
|
85
|
-
/**
|
|
86
|
-
* A glob pattern that matches SVG files.
|
|
87
|
-
*/
|
|
88
|
-
const GLOB_SVG = "**/*.svg";
|
|
89
|
-
/**
|
|
90
|
-
* A glob pattern that matches GraphQL files.
|
|
91
|
-
*/
|
|
92
|
-
const GLOB_GRAPHQL = "**/*.{g,graph}ql";
|
|
93
|
-
/**
|
|
94
68
|
* A glob pattern that matches HTML files.
|
|
95
69
|
*/
|
|
96
70
|
const GLOB_HTML = "**/*.htm?(l)";
|
|
97
71
|
/**
|
|
98
72
|
* A glob pattern that matches test files.
|
|
99
73
|
*/
|
|
100
|
-
const GLOB_TESTS = [
|
|
101
|
-
|
|
102
|
-
`**/*.test.?([cm])[jt]s`,
|
|
103
|
-
`**/test-setup.?([cm])[jt]s`
|
|
104
|
-
];
|
|
74
|
+
const GLOB_TESTS = [`**/*.spec.?([cm])[jt]s`, `**/*.test.?([cm])[jt]s`];
|
|
75
|
+
const GLOB_VITEST = [...GLOB_TESTS, `**/test-setup.?([cm])[jt]s`];
|
|
105
76
|
/**
|
|
106
77
|
* A glob pattern that matches files to exclude from linting.
|
|
107
78
|
*/
|
|
@@ -111,7 +82,7 @@ const GLOB_EXCLUDE = [
|
|
|
111
82
|
"**/package-lock.json",
|
|
112
83
|
"**/yarn.lock",
|
|
113
84
|
"**/pnpm-lock.yaml",
|
|
114
|
-
"**/bun.
|
|
85
|
+
"**/bun.lock?(b)",
|
|
115
86
|
"**/output",
|
|
116
87
|
"**/coverage",
|
|
117
88
|
"**/temp",
|
|
@@ -137,8 +108,7 @@ const GLOB_EXCLUDE = [
|
|
|
137
108
|
"**/LICENSE*",
|
|
138
109
|
"**/__snapshots__",
|
|
139
110
|
"**/auto-import?(s).d.ts",
|
|
140
|
-
"**/components.d.ts"
|
|
141
|
-
"**/prettier-types.ts"
|
|
111
|
+
"**/components.d.ts"
|
|
142
112
|
];
|
|
143
113
|
//#endregion
|
|
144
114
|
//#region src/shared/utils.ts
|
|
@@ -172,6 +142,19 @@ function fileExists(path) {
|
|
|
172
142
|
}
|
|
173
143
|
}
|
|
174
144
|
/**
|
|
145
|
+
* Checks whether the given path points to an existing directory.
|
|
146
|
+
*
|
|
147
|
+
* @param path - Filesystem path to validate.
|
|
148
|
+
* @returns `true` when the path exists and is a directory; otherwise `false`.
|
|
149
|
+
*/
|
|
150
|
+
function isDirectory(path) {
|
|
151
|
+
try {
|
|
152
|
+
return statSync(path).isDirectory();
|
|
153
|
+
} catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
175
158
|
* Retrieves the root directory of the workspace.
|
|
176
159
|
*
|
|
177
160
|
* This function determines the root directory of the workspace by checking for specific files
|
|
@@ -236,7 +219,7 @@ async function ensurePackages(packages) {
|
|
|
236
219
|
/**
|
|
237
220
|
* Returns the object representation of the configuration or an empty object if it is a boolean.
|
|
238
221
|
*
|
|
239
|
-
* @param options - The {@link
|
|
222
|
+
* @param options - The {@link DefineConfigOptions} object to extract the sub-options from.
|
|
240
223
|
* @param key - The property name.
|
|
241
224
|
* @returns An object representing the required options.
|
|
242
225
|
*/
|
|
@@ -245,6 +228,67 @@ function resolveSubOptions(options, key) {
|
|
|
245
228
|
if (typeof option === "boolean") return {};
|
|
246
229
|
return option ?? {};
|
|
247
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* Replace Windows with posix style paths
|
|
233
|
+
*
|
|
234
|
+
* @param filePath - Path to convert
|
|
235
|
+
* @returns Converted filepath
|
|
236
|
+
*/
|
|
237
|
+
function convertPathToPosix(filePath) {
|
|
238
|
+
return normalize(filePath).replaceAll("\\", "/");
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Checks if a provided path is a directory and returns a glob string matching
|
|
242
|
+
* all files under that directory if so, the path itself otherwise.
|
|
243
|
+
*
|
|
244
|
+
* Reason for this is that `glob` needs `/**` to collect all the files under a
|
|
245
|
+
* directory where as our previous implementation without `glob` simply walked
|
|
246
|
+
* a directory that is passed. So this is to maintain backwards compatibility.
|
|
247
|
+
*
|
|
248
|
+
* Also makes sure all path separators are POSIX style for `glob` compatibility.
|
|
249
|
+
*
|
|
250
|
+
* @param [options] - An options object
|
|
251
|
+
* @param [options.extensions] - An array of accepted extensions
|
|
252
|
+
* @param [options.cwd] - The cwd to use to resolve relative pathnames
|
|
253
|
+
* @returns A function that takes a pathname and returns a glob that
|
|
254
|
+
* matches all files with the provided extensions if
|
|
255
|
+
* pathname is a directory.
|
|
256
|
+
*/
|
|
257
|
+
function pathToGlobPattern(options) {
|
|
258
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
259
|
+
const extensions = options?.extensions?.map((ext) => ext.replace(/^\./, "")) ?? [];
|
|
260
|
+
const suffix = extensions.length === 0 ? "/**/*" : extensions.length === 1 ? `/**/*.${extensions[0]}` : `/**/*.{${extensions.join(",")}}`;
|
|
261
|
+
return (path) => {
|
|
262
|
+
let newPath = path;
|
|
263
|
+
if (isDirectory(resolve(cwd, path))) newPath = path.replace(/[/\\]$/, "") + suffix;
|
|
264
|
+
return convertPathToPosix(newPath);
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Finds the first valid Playwright test directory declared via `testDir` in any
|
|
269
|
+
* `playwright.config.ts` file under the current workspace.
|
|
270
|
+
*
|
|
271
|
+
* The function:
|
|
272
|
+
* - Locates Playwright config files, excluding `node_modules`.
|
|
273
|
+
* - Reads each config as text.
|
|
274
|
+
* - Extracts `testDir` with a regex.
|
|
275
|
+
* - Resolves `testDir` relative to the config file location.
|
|
276
|
+
* - Returns the first resolved path that exists as a directory.
|
|
277
|
+
*
|
|
278
|
+
* @returns Absolute path to the first existing Playwright test directory, or `undefined` if none is found.
|
|
279
|
+
*/
|
|
280
|
+
async function getPlaywrightDirectory() {
|
|
281
|
+
const playwrightConfigs = await glob("**/playwright.config.ts", { ignore: ["**/node_modules/**"] });
|
|
282
|
+
for (const config of playwrightConfigs) try {
|
|
283
|
+
const configContent = await readFile(config, "utf-8");
|
|
284
|
+
const match = /testDir:\s*["'](.+)["'],?/.exec(configContent);
|
|
285
|
+
if (match) {
|
|
286
|
+
const testDir = match[1];
|
|
287
|
+
const resolvedTestDir = resolve(dirname(config), testDir);
|
|
288
|
+
if (isDirectory(resolvedTestDir)) return resolvedTestDir;
|
|
289
|
+
}
|
|
290
|
+
} catch {}
|
|
291
|
+
}
|
|
248
292
|
//#endregion
|
|
249
293
|
//#region src/configs/angular.ts
|
|
250
294
|
/**
|
|
@@ -377,195 +421,6 @@ function comments() {
|
|
|
377
421
|
});
|
|
378
422
|
}
|
|
379
423
|
//#endregion
|
|
380
|
-
//#region src/configs/stylistic.ts
|
|
381
|
-
const STYLISTIC_CONFIG_DEFAULT = {
|
|
382
|
-
semi: true,
|
|
383
|
-
arrowParens: true,
|
|
384
|
-
braceStyle: "1tbs",
|
|
385
|
-
quoteProps: "as-needed"
|
|
386
|
-
};
|
|
387
|
-
/**
|
|
388
|
-
* Generates a stylistic ESLint configuration.
|
|
389
|
-
*
|
|
390
|
-
* @param [options] - Optional configuration options to customize the stylistic rules.
|
|
391
|
-
* @returns A promise that resolves to an array of ESLint configurations.
|
|
392
|
-
* @example
|
|
393
|
-
* const config = await stylistic({ semi: false });
|
|
394
|
-
*/
|
|
395
|
-
async function stylistic(options = {}) {
|
|
396
|
-
const stylisticOptions = {
|
|
397
|
-
...STYLISTIC_CONFIG_DEFAULT,
|
|
398
|
-
...typeof options.stylistic === "boolean" ? {} : options.stylistic
|
|
399
|
-
};
|
|
400
|
-
const stylisticPlugin = await interopDefault(import("@stylistic/eslint-plugin"));
|
|
401
|
-
const config = stylisticPlugin.configs.customize(stylisticOptions);
|
|
402
|
-
return tseslint.config({
|
|
403
|
-
name: "fabdeh/stylistic/rules",
|
|
404
|
-
files: [GLOB_SRC],
|
|
405
|
-
plugins: { "@stylistic": stylisticPlugin },
|
|
406
|
-
rules: {
|
|
407
|
-
...config.rules,
|
|
408
|
-
"@stylistic/comma-dangle": ["error", {
|
|
409
|
-
arrays: "always-multiline",
|
|
410
|
-
objects: "always-multiline"
|
|
411
|
-
}],
|
|
412
|
-
"@stylistic/no-extra-semi": "error",
|
|
413
|
-
"@stylistic/operator-linebreak": [
|
|
414
|
-
"error",
|
|
415
|
-
"after",
|
|
416
|
-
{ overrides: {
|
|
417
|
-
"?": "before",
|
|
418
|
-
":": "before"
|
|
419
|
-
} }
|
|
420
|
-
]
|
|
421
|
-
}
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
//#endregion
|
|
425
|
-
//#region src/configs/formatters.ts
|
|
426
|
-
/**
|
|
427
|
-
* Merges the provided Prettier options with any overrides.
|
|
428
|
-
*
|
|
429
|
-
* @param options - The base Prettier options to merge.
|
|
430
|
-
* @param overrides - Required overrides for the Prettier options. Must include a `parser` property.
|
|
431
|
-
* @returns The merged Prettier options.
|
|
432
|
-
*/
|
|
433
|
-
function mergePrettierOptions(options, overrides) {
|
|
434
|
-
return {
|
|
435
|
-
...options,
|
|
436
|
-
...overrides,
|
|
437
|
-
plugins: [...overrides.plugins ?? [], ...options.plugins ?? []]
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
/**
|
|
441
|
-
* Configures and returns an array of formatters based on the provided options.
|
|
442
|
-
*
|
|
443
|
-
* @param options - The options for configuring the formatters. If set to `true`, default options will be used.
|
|
444
|
-
* @param stylistic - The stylistic options for the formatters.
|
|
445
|
-
* @param hasAngularTemplateParser - Optional argument which indicates whether angular rules already registered the template parser or not.
|
|
446
|
-
* @returns A promise that resolves to a `TypedConfigArray` containing the configured formatters.
|
|
447
|
-
*/
|
|
448
|
-
async function formatters(options = {}, stylistic = {}, hasAngularTemplateParser = false) {
|
|
449
|
-
if (options === true) {
|
|
450
|
-
const isPrettierPluginXmlInScope = isPackageInScope("@prettier/plugin-xml");
|
|
451
|
-
options = {
|
|
452
|
-
css: true,
|
|
453
|
-
graphql: true,
|
|
454
|
-
html: true,
|
|
455
|
-
markdown: true,
|
|
456
|
-
slidev: isPackageExists("@slidev/cli"),
|
|
457
|
-
svg: isPrettierPluginXmlInScope,
|
|
458
|
-
xml: isPrettierPluginXmlInScope
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
|
-
await ensurePackages([
|
|
462
|
-
"eslint-plugin-format",
|
|
463
|
-
options.markdown && options.slidev ? "prettier-plugin-slidev" : void 0,
|
|
464
|
-
options.xml || options.svg ? "@prettier/plugin-xml" : void 0
|
|
465
|
-
]);
|
|
466
|
-
const { indent, quotes, semi } = {
|
|
467
|
-
...STYLISTIC_CONFIG_DEFAULT,
|
|
468
|
-
...stylistic
|
|
469
|
-
};
|
|
470
|
-
const prettierOptions = {
|
|
471
|
-
endOfLine: "auto",
|
|
472
|
-
printWidth: 120,
|
|
473
|
-
semi,
|
|
474
|
-
singleQuote: quotes === "single",
|
|
475
|
-
tabWidth: typeof indent === "number" ? indent : 2,
|
|
476
|
-
trailingComma: "all",
|
|
477
|
-
useTabs: indent === "tab",
|
|
478
|
-
...options.options
|
|
479
|
-
};
|
|
480
|
-
const prettierXmlOptions = {
|
|
481
|
-
xmlQuoteAttributes: "double",
|
|
482
|
-
xmlSelfClosingSpace: true,
|
|
483
|
-
xmlSortAttributesByKey: false,
|
|
484
|
-
xmlWhitespaceSensitivity: "ignore"
|
|
485
|
-
};
|
|
486
|
-
const formatPlugin = await interopDefault(import("eslint-plugin-format"));
|
|
487
|
-
const configs = [{
|
|
488
|
-
name: "fabdeh/formatter/setup",
|
|
489
|
-
plugins: { format: formatPlugin }
|
|
490
|
-
}];
|
|
491
|
-
if (options.css) configs.push({
|
|
492
|
-
name: "fabdeh/formatter/css",
|
|
493
|
-
languageOptions: { parser: formatPlugin.parserPlain },
|
|
494
|
-
files: [GLOB_CSS, GLOB_POSTCSS],
|
|
495
|
-
rules: { "format/prettier": ["error", mergePrettierOptions(prettierOptions, { parser: "css" })] }
|
|
496
|
-
}, {
|
|
497
|
-
name: "fabdeh/formatter/scss",
|
|
498
|
-
languageOptions: { parser: formatPlugin.parserPlain },
|
|
499
|
-
files: [GLOB_SCSS],
|
|
500
|
-
rules: { "format/prettier": ["error", mergePrettierOptions(prettierOptions, { parser: "scss" })] }
|
|
501
|
-
}, {
|
|
502
|
-
name: "fabdeh/formatter/less",
|
|
503
|
-
languageOptions: { parser: formatPlugin.parserPlain },
|
|
504
|
-
files: [GLOB_LESS],
|
|
505
|
-
rules: { "format/prettier": ["error", mergePrettierOptions(prettierOptions, { parser: "less" })] }
|
|
506
|
-
});
|
|
507
|
-
if (options.html) configs.push({
|
|
508
|
-
name: "fabdeh/formatter/html",
|
|
509
|
-
...hasAngularTemplateParser ? {} : { languageOptions: { parser: formatPlugin.parserPlain } },
|
|
510
|
-
files: [GLOB_HTML],
|
|
511
|
-
rules: { "format/prettier": ["error", mergePrettierOptions(prettierOptions, { parser: "html" })] }
|
|
512
|
-
});
|
|
513
|
-
if (options.xml) configs.push({
|
|
514
|
-
name: "fabdeh/formatter/xml",
|
|
515
|
-
languageOptions: { parser: formatPlugin.parserPlain },
|
|
516
|
-
files: [GLOB_XML],
|
|
517
|
-
rules: { "format/prettier": ["error", mergePrettierOptions({
|
|
518
|
-
...prettierXmlOptions,
|
|
519
|
-
...prettierOptions
|
|
520
|
-
}, {
|
|
521
|
-
parser: "xml",
|
|
522
|
-
plugins: ["@prettier/plugin-xml"]
|
|
523
|
-
})] }
|
|
524
|
-
});
|
|
525
|
-
if (options.svg) configs.push({
|
|
526
|
-
name: "fabdeh/formatter/svg",
|
|
527
|
-
languageOptions: { parser: formatPlugin.parserPlain },
|
|
528
|
-
files: [GLOB_SVG],
|
|
529
|
-
rules: { "format/prettier": ["error", mergePrettierOptions({
|
|
530
|
-
...prettierXmlOptions,
|
|
531
|
-
...prettierOptions
|
|
532
|
-
}, {
|
|
533
|
-
parser: "xml",
|
|
534
|
-
plugins: ["@prettier/plugin-xml"]
|
|
535
|
-
})] }
|
|
536
|
-
});
|
|
537
|
-
if (options.markdown) {
|
|
538
|
-
const GLOB_SLIDEV = options.slidev ? options.slidev === true ? ["**/.slides.md"] : options.slidev.files : [];
|
|
539
|
-
configs.push({
|
|
540
|
-
name: "fabdeh/formatter/markdown",
|
|
541
|
-
languageOptions: { parser: formatPlugin.parserPlain },
|
|
542
|
-
files: [GLOB_MARKDOWN],
|
|
543
|
-
ignores: GLOB_SLIDEV,
|
|
544
|
-
rules: { "format/prettier": ["error", mergePrettierOptions(prettierOptions, {
|
|
545
|
-
parser: "markdown",
|
|
546
|
-
embeddedLanguageFormatting: "off"
|
|
547
|
-
})] }
|
|
548
|
-
});
|
|
549
|
-
if (options.slidev) configs.push({
|
|
550
|
-
name: "fabdeh/formatter/slidev",
|
|
551
|
-
languageOptions: { parser: formatPlugin.parserPlain },
|
|
552
|
-
files: GLOB_SLIDEV,
|
|
553
|
-
rules: { "format/prettier": ["error", mergePrettierOptions(prettierOptions, {
|
|
554
|
-
embeddedLanguageFormatting: "off",
|
|
555
|
-
parser: "slidev",
|
|
556
|
-
plugins: ["prettier-plugin-slidev"]
|
|
557
|
-
})] }
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
if (options.graphql) configs.push({
|
|
561
|
-
name: "fabdeh/formatter/graphql",
|
|
562
|
-
languageOptions: { parser: formatPlugin.parserPlain },
|
|
563
|
-
files: [GLOB_GRAPHQL],
|
|
564
|
-
rules: { "format/prettier": ["error", mergePrettierOptions(prettierOptions, { parser: "graphql" })] }
|
|
565
|
-
});
|
|
566
|
-
return tseslint.config(configs);
|
|
567
|
-
}
|
|
568
|
-
//#endregion
|
|
569
424
|
//#region src/configs/ignore.ts
|
|
570
425
|
/**
|
|
571
426
|
* Generates a configuration array for ESLint with default and user-defined ignore patterns.
|
|
@@ -1515,6 +1370,64 @@ function perfectionist() {
|
|
|
1515
1370
|
});
|
|
1516
1371
|
}
|
|
1517
1372
|
//#endregion
|
|
1373
|
+
//#region src/configs/playwright.ts
|
|
1374
|
+
/**
|
|
1375
|
+
* Configures and returns an ESLint flat config for Playwright test files.
|
|
1376
|
+
*
|
|
1377
|
+
* @param [options] - Options used to customize the Playwright config.
|
|
1378
|
+
* @param [options.e2eFolderPath] - The path to the e2e folder to prepend to each `options.files` glob pattern (defaults to `e2e`).
|
|
1379
|
+
* @param [options.files] - File globs to target (defaults to `GLOB_TESTS`).
|
|
1380
|
+
* @param [options.overrides] - Custom ESLint rule overrides merged last.
|
|
1381
|
+
* @returns A promise that resolves to a single-entry ESLint config array.
|
|
1382
|
+
* @example
|
|
1383
|
+
* const config = await playwright({
|
|
1384
|
+
* e2eFolderPath: 'tests/e2e',
|
|
1385
|
+
* files: ['**\/*.test.ts'], // will become 'tests\/e2e\/**\/*.test.ts' because of the e2eFolderPath option
|
|
1386
|
+
* overrides: {
|
|
1387
|
+
* 'playwright/no-page-pause': 'off',
|
|
1388
|
+
* },
|
|
1389
|
+
* });
|
|
1390
|
+
*/
|
|
1391
|
+
async function playwright(options = {}) {
|
|
1392
|
+
const { e2eFolderPath = "e2e", files = GLOB_TESTS.map((glob) => convertPathToPosix(join(e2eFolderPath, glob))), overrides = {} } = options;
|
|
1393
|
+
if (!isDirectory(e2eFolderPath)) return [];
|
|
1394
|
+
const playwrightPlugin = await interopDefault(import("eslint-plugin-playwright"));
|
|
1395
|
+
return [{
|
|
1396
|
+
name: "fabdeh/playwright/rules",
|
|
1397
|
+
plugins: { playwright: playwrightPlugin },
|
|
1398
|
+
files,
|
|
1399
|
+
rules: {
|
|
1400
|
+
...playwrightPlugin.configs.recommended.rules,
|
|
1401
|
+
"max-lines": "off",
|
|
1402
|
+
"@typescript-eslint/consistent-type-assertions": "off",
|
|
1403
|
+
"@typescript-eslint/no-empty-function": "off",
|
|
1404
|
+
"@typescript-eslint/no-unsafe-assignment": "off",
|
|
1405
|
+
"@typescript-eslint/no-unsafe-call": "off",
|
|
1406
|
+
"@typescript-eslint/no-unsafe-member-access": "off",
|
|
1407
|
+
"@typescript-eslint/unbound-method": "off",
|
|
1408
|
+
"unicorn/no-null": "off",
|
|
1409
|
+
"playwright/no-get-by-title": "error",
|
|
1410
|
+
"playwright/no-nth-methods": "error",
|
|
1411
|
+
"playwright/prefer-comparison-matcher": "error",
|
|
1412
|
+
"playwright/prefer-equality-matcher": "error",
|
|
1413
|
+
"playwright/prefer-lowercase-title": ["error", { allowedPrefixes: [
|
|
1414
|
+
"GET",
|
|
1415
|
+
"POST",
|
|
1416
|
+
"PUT",
|
|
1417
|
+
"DELETE",
|
|
1418
|
+
"PATCH",
|
|
1419
|
+
"HEAD",
|
|
1420
|
+
"OPTIONS"
|
|
1421
|
+
] }],
|
|
1422
|
+
"playwright/prefer-native-locators": "error",
|
|
1423
|
+
"playwright/prefer-to-be": "error",
|
|
1424
|
+
"playwright/prefer-to-contain": "error",
|
|
1425
|
+
...getJsDocRules("off", true, "both"),
|
|
1426
|
+
...overrides
|
|
1427
|
+
}
|
|
1428
|
+
}];
|
|
1429
|
+
}
|
|
1430
|
+
//#endregion
|
|
1518
1431
|
//#region src/configs/pnpm.ts
|
|
1519
1432
|
/**
|
|
1520
1433
|
* Generates an array containing configuration settings for PNPM workspace-specific rules and plugins.
|
|
@@ -1807,6 +1720,51 @@ function sortTsConfig() {
|
|
|
1807
1720
|
});
|
|
1808
1721
|
}
|
|
1809
1722
|
//#endregion
|
|
1723
|
+
//#region src/configs/stylistic.ts
|
|
1724
|
+
const STYLISTIC_CONFIG_DEFAULT = {
|
|
1725
|
+
semi: true,
|
|
1726
|
+
arrowParens: true,
|
|
1727
|
+
braceStyle: "1tbs",
|
|
1728
|
+
quoteProps: "as-needed"
|
|
1729
|
+
};
|
|
1730
|
+
/**
|
|
1731
|
+
* Generates a stylistic ESLint configuration.
|
|
1732
|
+
*
|
|
1733
|
+
* @param [options] - Optional configuration options to customize the stylistic rules.
|
|
1734
|
+
* @returns A promise that resolves to an array of ESLint configurations.
|
|
1735
|
+
* @example
|
|
1736
|
+
* const config = await stylistic({ semi: false });
|
|
1737
|
+
*/
|
|
1738
|
+
async function stylistic(options = {}) {
|
|
1739
|
+
const stylisticOptions = {
|
|
1740
|
+
...STYLISTIC_CONFIG_DEFAULT,
|
|
1741
|
+
...typeof options.stylistic === "boolean" ? {} : options.stylistic
|
|
1742
|
+
};
|
|
1743
|
+
const stylisticPlugin = await interopDefault(import("@stylistic/eslint-plugin"));
|
|
1744
|
+
const config = stylisticPlugin.configs.customize(stylisticOptions);
|
|
1745
|
+
return tseslint.config({
|
|
1746
|
+
name: "fabdeh/stylistic/rules",
|
|
1747
|
+
files: [GLOB_SRC],
|
|
1748
|
+
plugins: { "@stylistic": stylisticPlugin },
|
|
1749
|
+
rules: {
|
|
1750
|
+
...config.rules,
|
|
1751
|
+
"@stylistic/comma-dangle": ["error", {
|
|
1752
|
+
arrays: "always-multiline",
|
|
1753
|
+
objects: "always-multiline"
|
|
1754
|
+
}],
|
|
1755
|
+
"@stylistic/no-extra-semi": "error",
|
|
1756
|
+
"@stylistic/operator-linebreak": [
|
|
1757
|
+
"error",
|
|
1758
|
+
"after",
|
|
1759
|
+
{ overrides: {
|
|
1760
|
+
"?": "before",
|
|
1761
|
+
":": "before"
|
|
1762
|
+
} }
|
|
1763
|
+
]
|
|
1764
|
+
}
|
|
1765
|
+
});
|
|
1766
|
+
}
|
|
1767
|
+
//#endregion
|
|
1810
1768
|
//#region src/configs/tailwindcss.ts
|
|
1811
1769
|
/**
|
|
1812
1770
|
* Generates an ESLint configuration array for Tailwind CSS using better-tailwindcss plugin.
|
|
@@ -2186,21 +2144,24 @@ function unicorn(options = {}) {
|
|
|
2186
2144
|
* Configures and returns an ESLint configuration array for Vitest.
|
|
2187
2145
|
*
|
|
2188
2146
|
* @param [options] - The options to customize the configuration.
|
|
2147
|
+
* @param [options.files] - File globs to target (defaults to `GLOB_VITEST`).
|
|
2189
2148
|
* @param [options.overrides] - Custom rule overrides.
|
|
2190
2149
|
* @param [options.useJestDom] - Whether to use the `@testing-library/jest-dom` plugin.
|
|
2191
2150
|
* @param [options.useTestingLibrary] - Whether to use the `@testing-library/angular` plugin.
|
|
2151
|
+
* @param [options.e2eFolderPath] - The path to the e2e folder to ignore (this is only used internally by the `defineConfig` function, not by the user).
|
|
2192
2152
|
* @returns A promise that resolves to the ESLint configuration array.
|
|
2193
2153
|
* @example
|
|
2194
2154
|
* const config = await vitest({
|
|
2155
|
+
* files: ['**\/*.spec.ts'],
|
|
2195
2156
|
* overrides: {
|
|
2196
|
-
* '
|
|
2157
|
+
* 'vitest/prefer-lowercase-title': 'warn',
|
|
2197
2158
|
* },
|
|
2198
2159
|
* useJestDom: true,
|
|
2199
2160
|
* useTestingLibrary: false,
|
|
2200
2161
|
* });
|
|
2201
2162
|
*/
|
|
2202
2163
|
async function vitest(options = {}) {
|
|
2203
|
-
const { overrides = {}, useJestDom = isPackageExists("@testing-library/jest-dom"), useTestingLibrary = isPackageExists("@testing-library/angular") } = options;
|
|
2164
|
+
const { files = GLOB_VITEST, overrides = {}, useJestDom = isPackageExists("@testing-library/jest-dom"), useTestingLibrary = isPackageExists("@testing-library/angular"), enableVitestGlobals = true, e2eFolderPath } = options;
|
|
2204
2165
|
const [vitestPlugin, jestDomPlugin, testingLibraryPlugin] = await Promise.all([
|
|
2205
2166
|
interopDefault(import("@vitest/eslint-plugin")),
|
|
2206
2167
|
useJestDom ? interopDefault(import("eslint-plugin-jest-dom")) : Promise.resolve(void 0),
|
|
@@ -2215,11 +2176,11 @@ async function vitest(options = {}) {
|
|
|
2215
2176
|
},
|
|
2216
2177
|
languageOptions: { globals: {
|
|
2217
2178
|
...globals.node,
|
|
2218
|
-
...globals
|
|
2219
|
-
...vitestPlugin.environments.env.globals
|
|
2179
|
+
...enableVitestGlobals ? vitestPlugin.environments.env.globals : {}
|
|
2220
2180
|
} },
|
|
2221
2181
|
settings: { vitest: { typecheck: true } },
|
|
2222
|
-
files
|
|
2182
|
+
files,
|
|
2183
|
+
...e2eFolderPath ? { ignores: [pathToGlobPattern()(e2eFolderPath)] } : {},
|
|
2223
2184
|
rules: {
|
|
2224
2185
|
...vitestPlugin.configs.recommended.rules,
|
|
2225
2186
|
...jestDomPlugin?.configs["flat/recommended"].rules,
|
|
@@ -2316,6 +2277,7 @@ const NGRX_PACKAGES = [
|
|
|
2316
2277
|
"@ngrx/signals",
|
|
2317
2278
|
"@ngrx/operators"
|
|
2318
2279
|
];
|
|
2280
|
+
const PLAYWRIGHT_PACKAGES = ["@playwright/test", "playwright"];
|
|
2319
2281
|
const OPTIONS_SYMBOL = Symbol("options");
|
|
2320
2282
|
//#endregion
|
|
2321
2283
|
//#region src/factories/project-config.ts
|
|
@@ -2354,10 +2316,6 @@ async function defineProjectConfig(baseConfig, options = {}, ...userConfigs) {
|
|
|
2354
2316
|
if (enableAngular) {
|
|
2355
2317
|
const angularOptions = resolveSubOptions(options, "angular");
|
|
2356
2318
|
configs.push(angular(angularOptions));
|
|
2357
|
-
if (workspaceOptions.formatters) {
|
|
2358
|
-
const htmlFormatters = resolvedBaseConfig.find((c) => c.name === "fabdeh/formatter/html");
|
|
2359
|
-
if (htmlFormatters) delete htmlFormatters.languageOptions;
|
|
2360
|
-
}
|
|
2361
2319
|
}
|
|
2362
2320
|
if (enableNgrx) {
|
|
2363
2321
|
const ngrxOptions = resolveSubOptions(options, "ngrx");
|
|
@@ -2395,7 +2353,7 @@ async function defineProjectConfig(baseConfig, options = {}, ...userConfigs) {
|
|
|
2395
2353
|
* ```
|
|
2396
2354
|
*/
|
|
2397
2355
|
async function defineConfig(options = {}, ...userConfigs) {
|
|
2398
|
-
const { angular: enableAngular = isPackageExists("@angular/core"), gitignore: enableGitignore = true, jsdoc: enableJsdoc = options.type === "lib", ngrx: enableNgrx = NGRX_PACKAGES.some((p) => isPackageExists(p)), pnpm: enableCatalogs = false, regexp: enableRegexp = true, tailwindcss: enableTailwind = false, typescript: enableTypescript = isPackageExists("typescript"), unicorn: enableUnicorn = true, vitest: enableVitest = isPackageExists("vitest") } = options;
|
|
2356
|
+
const { angular: enableAngular = isPackageExists("@angular/core"), gitignore: enableGitignore = true, jsdoc: enableJsdoc = options.type === "lib", ngrx: enableNgrx = NGRX_PACKAGES.some((p) => isPackageExists(p)), playwright: enablePlaywright = PLAYWRIGHT_PACKAGES.some((p) => isPackageExists(p)), pnpm: enableCatalogs = false, regexp: enableRegexp = true, tailwindcss: enableTailwind = false, typescript: enableTypescript = isPackageExists("typescript"), unicorn: enableUnicorn = true, vitest: enableVitest = isPackageExists("vitest") } = options;
|
|
2399
2357
|
if (enableNgrx && !enableAngular) throw new Error("NgRx rules can only be enabled if Angular rules are also enabled.");
|
|
2400
2358
|
const stylisticOptions = options.stylistic === false ? false : typeof options.stylistic === "object" ? options.stylistic : {};
|
|
2401
2359
|
const configs = [];
|
|
@@ -2438,9 +2396,21 @@ async function defineConfig(options = {}, ...userConfigs) {
|
|
|
2438
2396
|
useRelaxedNamingConventionForCamelAndPascalCases: typescriptOptions.useRelaxedNamingConventionForCamelAndPascalCases
|
|
2439
2397
|
}));
|
|
2440
2398
|
}
|
|
2399
|
+
let e2eFolderPath;
|
|
2400
|
+
if (enablePlaywright) {
|
|
2401
|
+
const playwrightOptions = resolveSubOptions(options, "playwright");
|
|
2402
|
+
e2eFolderPath = playwrightOptions.e2eFolderPath ?? await getPlaywrightDirectory() ?? "e2e";
|
|
2403
|
+
configs.push(playwright({
|
|
2404
|
+
...playwrightOptions,
|
|
2405
|
+
e2eFolderPath
|
|
2406
|
+
}));
|
|
2407
|
+
}
|
|
2441
2408
|
if (enableVitest) {
|
|
2442
2409
|
const vitestOptions = resolveSubOptions(options, "vitest");
|
|
2443
|
-
configs.push(vitest(
|
|
2410
|
+
configs.push(vitest({
|
|
2411
|
+
...vitestOptions,
|
|
2412
|
+
e2eFolderPath
|
|
2413
|
+
}));
|
|
2444
2414
|
}
|
|
2445
2415
|
if (enableTailwind) {
|
|
2446
2416
|
const tailwindcssOptions = resolveSubOptions(options, "tailwindcss");
|
|
@@ -2475,7 +2445,6 @@ async function defineConfig(options = {}, ...userConfigs) {
|
|
|
2475
2445
|
const markdownOptions = resolveSubOptions(options, "markdown");
|
|
2476
2446
|
configs.push(markdown(markdownOptions));
|
|
2477
2447
|
}
|
|
2478
|
-
if (options.formatters) configs.push(formatters(options.formatters, typeof stylisticOptions === "boolean" ? {} : stylisticOptions, Boolean(enableAngular)));
|
|
2479
2448
|
return tseslint.config(...await Promise.all(configs), ...await Promise.all(userConfigs));
|
|
2480
2449
|
}
|
|
2481
2450
|
//#endregion
|
|
@@ -2549,10 +2518,9 @@ async function defineWorkspaceConfig(options = {}, ...userConfigs) {
|
|
|
2549
2518
|
const markdownOptions = resolveSubOptions(options, "markdown");
|
|
2550
2519
|
configs.push(markdown(markdownOptions));
|
|
2551
2520
|
}
|
|
2552
|
-
if (options.formatters) configs.push(formatters(options.formatters, typeof stylisticOptions === "boolean" ? {} : stylisticOptions, false));
|
|
2553
2521
|
const eslintConfigWithOptions = tseslint.config(...await Promise.all(configs), ...await Promise.all(userConfigs));
|
|
2554
2522
|
eslintConfigWithOptions[OPTIONS_SYMBOL] = options;
|
|
2555
2523
|
return eslintConfigWithOptions;
|
|
2556
2524
|
}
|
|
2557
2525
|
//#endregion
|
|
2558
|
-
export { GLOB_HTML, GLOB_JS, GLOB_SRC, GLOB_TESTS, GLOB_TS, STYLISTIC_CONFIG_DEFAULT, angular, comments, defineConfig, defineProjectConfig, defineWorkspaceConfig, ensurePackages,
|
|
2526
|
+
export { GLOB_HTML, GLOB_JS, GLOB_SRC, GLOB_TESTS, GLOB_TS, STYLISTIC_CONFIG_DEFAULT, angular, comments, convertPathToPosix, defineConfig, defineProjectConfig, defineWorkspaceConfig, ensurePackages, getPlaywrightDirectory, getTsConfigFileName, getWorkspaceRoot, ignores, imports, interopDefault, isDirectory, isPackageInScope, javascript, jsdoc, jsonc, markdown, ngrx, node, pathToGlobPattern, perfectionist, playwright, pnpm, regexp, resolveSubOptions, sortPackageJson, sortTsConfig, stylistic, tailwindcss, toml, typescript, unicorn, vitest, yaml };
|