@brnshkr/config 0.0.1-beta.3 → 0.0.1-beta.4

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.
Files changed (37) hide show
  1. package/README.md +174 -226
  2. package/conf/.gitignore.dist +15 -0
  3. package/conf/Makefile +3778 -0
  4. package/conf/Makefile.dist +13 -0
  5. package/conf/bunfig.dist.toml +5 -0
  6. package/conf/commitlint.dist.mjs +1 -0
  7. package/conf/editorconfig.dist +19 -0
  8. package/conf/launch.dist.json +31 -0
  9. package/conf/markdownlint.dist.mjs +1 -0
  10. package/conf/spelling/defaults.json +185 -0
  11. package/conf/tsconfig.dist.json +3 -0
  12. package/conf/tsconfig.json +31 -27
  13. package/conf/vitest.dist.mjs +1 -0
  14. package/conf/vscode-css-custom-data.dist.json +95 -0
  15. package/conf/vscode-extensions.dist.json +21 -0
  16. package/conf/vscode-settings.dist.jsonc +338 -0
  17. package/dist/commitlint/index.d.mts +72 -0
  18. package/dist/commitlint/index.mjs +239 -0
  19. package/dist/eslint/index.d.mts +3890 -1387
  20. package/dist/eslint/index.mjs +2662 -471
  21. package/dist/markdownlint/index.d.mts +2304 -0
  22. package/dist/markdownlint/index.mjs +440 -0
  23. package/dist/shared.mjs +406 -64
  24. package/dist/spelling/index.d.mts +30 -0
  25. package/dist/spelling/index.mjs +2 -0
  26. package/dist/spelling/spelling.test.d.mts +1 -0
  27. package/dist/spelling/spelling.test.mjs +12 -0
  28. package/dist/stylelint/index.d.mts +47 -9
  29. package/dist/stylelint/index.mjs +145 -47
  30. package/dist/vitest/index.d.mts +73 -0
  31. package/dist/vitest/index.mjs +181 -0
  32. package/package.json +192 -105
  33. package/conf/tsconfig.json.example +0 -3
  34. package/dist/scripts/eslint.mjs +0 -16
  35. package/dist/scripts/stylelint.mjs +0 -31
  36. /package/conf/{eslint.config.mjs.example → eslint.dist.mjs} +0 -0
  37. /package/conf/{stylelint.config.mjs.example → stylelint.dist.mjs} +0 -0
@@ -1,11 +1,15 @@
1
- import { c as objectAssign, d as packageOrganization, f as packageOrganizationUpper, i as resolvePackagesSharedAsynchronously, l as objectEntries, n as GLOB_IGNORES, o as ESLINT_PACKAGES, p as version, r as isModuleEnabledByDefault, t as QUOTES, u as objectFromEntries } from "../shared.mjs";
1
+ import { C as objectAssign, D as objectValues, E as objectKeys, O as packageOrganization, S as isPlainObject, T as objectFromEntries, a as getMtime, c as toPosix$1, d as GLOB_TEST_FILES$1, f as createModuleState, h as resolvePackagesSharedAsynchronously, i as findNearestPackageJson, j as version, k as packageOrganizationUpper, l as GLOB_BENCHMARK_FILES, m as isModuleEnabledByDefault, n as QUOTES, o as readJsonObjectFile, p as doAllPackagesExist, r as doesFileExist, s as readTextFile, u as GLOB_IGNORES, v as ESLINT_PACKAGES, w as objectEntries } from "../shared.mjs";
2
2
  import { FlatConfigComposer } from "eslint-flat-config-utils";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
+ import { includeIgnoreFile } from "eslint/config";
5
6
  import jsEslint from "@eslint/js";
6
7
  import confusingBrowserGlobals from "confusing-browser-globals";
7
8
  import globals from "globals";
8
9
  //#region ../src/js/eslint/types/scopes.ts
10
+ /**
11
+ * @internal @brnshkr/config/eslint
12
+ */
9
13
  const MAIN_SCOPES = {
10
14
  [packageOrganizationUpper]: "builtin",
11
15
  COMMENTS: "comments",
@@ -33,7 +37,7 @@ const SUB_SCOPES = {
33
37
  BASE: "base",
34
38
  DEVELOPMENT: "development",
35
39
  EXAMPLES: "examples",
36
- GIT: "git",
40
+ FILES: "files",
37
41
  GLOBAL: "global",
38
42
  PARSER: "parser",
39
43
  PROCESSOR: "processor",
@@ -43,13 +47,16 @@ const SUB_SCOPES = {
43
47
  };
44
48
  //#endregion
45
49
  //#region ../src/js/eslint/utils/config.ts
50
+ /**
51
+ * @internal @brnshkr/config/eslint
52
+ */
46
53
  const buildConfigName = (mainScope, subScope) => [
47
54
  packageOrganization,
48
55
  mainScope,
49
56
  subScope
50
57
  ].filter(Boolean).join("/");
51
- const renameRules = (rules, map) => Object.fromEntries(Object.entries(rules ?? {}).map(([key, value]) => {
52
- for (const [from, to] of Object.entries(map)) if (key.startsWith(`${from}/`)) return [to + key.slice(from.length), value];
58
+ const renameRules = (rules, map) => objectFromEntries(objectEntries(rules ?? {}).map(([key, value]) => {
59
+ for (const [from, to] of objectEntries(map)) if (key.startsWith(`${from}/`)) return [to + key.slice(from.length), value];
53
60
  return [key, value];
54
61
  }));
55
62
  const isValidGlobalAdditionalConfigKey = (key) => [
@@ -64,7 +71,7 @@ const isValidGlobalAdditionalConfigKey = (key) => [
64
71
  const getGlobalAdditionalConfig = (options) => {
65
72
  const config = {};
66
73
  for (const [key, value] of objectEntries(options)) if (isValidGlobalAdditionalConfigKey(key)) config[key] = value;
67
- if (Object.keys(config).length === 0) return;
74
+ if (objectKeys(config).length === 0) return;
68
75
  config.name ??= buildConfigName(MAIN_SCOPES.USERLAND, SUB_SCOPES.GLOBAL);
69
76
  return config;
70
77
  };
@@ -76,7 +83,7 @@ const ensureNamesForSyncAdditionalConfigs = (additionalConfigs) => {
76
83
  validConfigs.push(config);
77
84
  continue;
78
85
  }
79
- validConfigs.push(Object.keys(config).length > 0 ? config : void 0);
86
+ validConfigs.push(objectKeys(config).length > 0 ? config : void 0);
80
87
  config.name = buildConfigName(MAIN_SCOPES.USERLAND, `${SUB_SCOPES.UNNAMED}-${String(currentIndex)}`);
81
88
  currentIndex += 1;
82
89
  }
@@ -85,6 +92,9 @@ const ensureNamesForSyncAdditionalConfigs = (additionalConfigs) => {
85
92
  const getUserConfigs = (resolvedOptions, additionalConfigs) => [getGlobalAdditionalConfig(resolvedOptions), ...ensureNamesForSyncAdditionalConfigs(additionalConfigs)];
86
93
  //#endregion
87
94
  //#region ../src/js/eslint/utils/globs.ts
95
+ /**
96
+ * @internal @brnshkr/config/eslint
97
+ */
88
98
  const GLOB_CJS = "**/*.cjs";
89
99
  const GLOB_TS = "**/*.?(c|m)ts?(x)";
90
100
  const GLOB_DTS = "**/*.d.?(c|m)ts";
@@ -107,18 +117,24 @@ const GLOB_SCRIPT_FILES = [
107
117
  const GLOB_SCRIPT_FILES_WITHOUT_TS = ["**/*.?(c|m)js?(x)", GLOB_DTS];
108
118
  const GLOB_DEVELOPMENT_FILES = [
109
119
  "**/*.config.?(c|m)[jt]s",
120
+ "**/__mocks__/**",
110
121
  "**/{conf,tests}/**",
111
122
  "**/types/declarations/reset.d.ts"
112
123
  ];
113
- const GLOB_TEST_FILES = [
114
- "**/__tests__/**/*.?(c|m)[jt]s",
115
- "**/*.spec.?(c|m)[jt]s",
116
- "**/*.test.?(c|m)[jt]s",
117
- "**/*.bench.?(c|m)[jt]s",
118
- "**/*.benchmark.?(c|m)[jt]s"
124
+ const GLOB_YAML_FIXED_EXTENSION_FILES = [
125
+ "**/.circleci/**/*.yml",
126
+ "**/.github/**/*.yml",
127
+ "**/.gitlab-ci.yml",
128
+ "**/.gitlab/**/*.yml",
129
+ "**/.travis.yml",
130
+ "**/appveyor.yml"
119
131
  ];
132
+ const GLOB_TEST_FILES = [...GLOB_TEST_FILES$1, ...GLOB_BENCHMARK_FILES];
120
133
  //#endregion
121
134
  //#region ../src/js/eslint/utils/tsconfig.ts
135
+ /**
136
+ * @internal @brnshkr/config/eslint
137
+ */
122
138
  const DEFAULT_TSCONFIG_FILENAME = "tsconfig.json";
123
139
  const JSONC_TOKEN_PATTERN = /"(?:[^"\\]|\\.)*"|\/\/[^\n]*|\/\*[\s\S]*?\*\//gv;
124
140
  const TRAILING_COMMA_PATTERN = /,(?=\s*[\]\}])/gv;
@@ -131,12 +147,12 @@ const parseTsConfigFile = (filePath) => {
131
147
  return;
132
148
  }
133
149
  };
134
- const resolveExtends = (extendsValue, fromDirectory) => {
135
- if (extendsValue.startsWith(".") || path.isAbsolute(extendsValue)) {
136
- const resolved = path.resolve(fromDirectory, extendsValue);
150
+ const resolveExtends = (parentConfig, fromDirectory) => {
151
+ if (parentConfig.startsWith(".") || path.isAbsolute(parentConfig)) {
152
+ const resolved = path.resolve(fromDirectory, parentConfig);
137
153
  return path.extname(resolved) === "" ? `${resolved}.json` : resolved;
138
154
  }
139
- return path.resolve(fromDirectory, "node_modules", extendsValue);
155
+ return path.resolve(fromDirectory, "node_modules", parentConfig);
140
156
  };
141
157
  const normalizeExtends = (raw) => {
142
158
  if (raw.extends === void 0) return [];
@@ -159,7 +175,7 @@ const loadInternal = (filePath, visitedPaths) => {
159
175
  } };
160
176
  };
161
177
  const cache = /* @__PURE__ */ new Map();
162
- const toPosix$1 = (filePath) => filePath.replaceAll("\\", "/");
178
+ const toPosix = (filePath) => filePath.replaceAll("\\", "/");
163
179
  const loadTsConfigPaths = (tsConfigPath) => {
164
180
  const absolutePath = path.resolve(tsConfigPath);
165
181
  if (cache.has(absolutePath)) return cache.get(absolutePath);
@@ -170,7 +186,7 @@ const loadTsConfigPaths = (tsConfigPath) => {
170
186
  return;
171
187
  }
172
188
  const baseUrl = path.resolve(path.dirname(absolutePath), mergedConfig.compilerOptions?.baseUrl ?? ".");
173
- const result = objectFromEntries(objectEntries(paths).map(([pattern, targets]) => [pattern, targets.map((target) => toPosix$1(path.resolve(baseUrl, target)))]));
189
+ const result = objectFromEntries(objectEntries(paths).map(([pattern, targets]) => [pattern, targets.map((target) => toPosix(path.resolve(baseUrl, target)))]));
174
190
  cache.set(absolutePath, result);
175
191
  return result;
176
192
  };
@@ -191,36 +207,1546 @@ const resolveTsConfigPath = (typescriptOptions) => {
191
207
  return path.resolve(process.cwd(), tsconfig);
192
208
  };
193
209
  //#endregion
210
+ //#region ../src/js/eslint/utils/ast.ts
211
+ const isFluentReturn = (returnType) => returnType?.typeAnnotation.type === "TSThisType";
212
+ const isVoidLikeReturn = (returnType) => returnType === void 0 || returnType.typeAnnotation.type === "TSVoidKeyword" || returnType.typeAnnotation.type === "TSNeverKeyword";
213
+ const resolveParameterIdentifier = (parameter) => {
214
+ if (parameter.type === "Identifier") return parameter;
215
+ if (parameter.type === "AssignmentPattern" && parameter.left.type === "Identifier") return parameter.left;
216
+ if (parameter.type === "RestElement" && parameter.argument.type === "Identifier") return parameter.argument;
217
+ if (parameter.type === "TSParameterProperty") return resolveParameterIdentifier(parameter.parameter);
218
+ };
219
+ const getParameterName = (parameter) => resolveParameterIdentifier(parameter)?.name;
220
+ const isAccessibleMethod = (method) => method.accessibility !== "private" && method.accessibility !== "protected" && method.key.type !== "PrivateIdentifier";
221
+ const isAbstractMethod = (method) => method.type === "TSAbstractMethodDefinition";
222
+ const isClassMethodLike = (node) => node.type === "MethodDefinition" || node.type === "TSAbstractMethodDefinition";
223
+ const getNamedKeyText = (key) => {
224
+ if (key.type === "Identifier" || key.type === "PrivateIdentifier") return key.name;
225
+ if (key.type === "Literal") return String(key.value);
226
+ return "<computed>";
227
+ };
228
+ const isFunctionInitializer = (candidate) => candidate?.type === "ArrowFunctionExpression" || candidate?.type === "FunctionExpression";
229
+ const resolveFunctionShape = (declaration) => {
230
+ if (declaration.type === "VariableDeclarator") {
231
+ if (!isFunctionInitializer(declaration.init)) return;
232
+ return {
233
+ parameters: declaration.init.params,
234
+ returnType: declaration.init.returnType
235
+ };
236
+ }
237
+ return {
238
+ parameters: declaration.params,
239
+ returnType: declaration.returnType
240
+ };
241
+ };
242
+ const TAG_INTERNAL = "internal";
243
+ const escapeRegExp = (value) => value.replaceAll(/[$\(\)*+.?\[\\\]^\{\|\}]/gv, String.raw`\$&`);
244
+ const hasProseAfter = (pattern, comment) => {
245
+ const prose = pattern.exec(comment)?.groups?.["prose"];
246
+ return typeof prose === "string" && /[A-Za-z]/v.test(prose);
247
+ };
248
+ const isBlockComment = (comment) => comment?.type === "Block" && comment.value.startsWith("*");
249
+ const extractBlockComment = (comments, node) => {
250
+ let expectedEndLine = node.loc.start.line - 1;
251
+ for (const comment of comments.toReversed()) {
252
+ if (comment.loc.end.line !== expectedEndLine) return;
253
+ expectedEndLine = comment.loc.start.line - 1;
254
+ if (isBlockComment(comment)) return `/*${comment.value}*/`;
255
+ }
256
+ };
257
+ const hasTag = (comment, tag) => comment !== void 0 && new RegExp(String.raw`@${tag}\b`, "v").test(comment);
258
+ const hasDescription = (comment) => {
259
+ if (comment === void 0) return false;
260
+ const beforeTags = /\/\*\*(?<body>.*?)(?:\n[\t ]*\*[\t ]+@|\*\/)/sv.exec(comment)?.groups?.["body"] ?? "";
261
+ return /^[\t ]*\*[\t ]+[[^\s*\/]--@][^\n]*/mv.test(beforeTags);
262
+ };
263
+ const hasParameterProse = (comment, parameterName) => hasProseAfter(new RegExp(String.raw`@param\b[^\n]*?\b${escapeRegExp(parameterName)}\b(?<prose>[^\n]*)`, "v"), comment);
264
+ const hasReturnsWithProse = (comment) => hasProseAfter(/@returns?\s+\S+\s+(?<prose>\S[^\n]*)/v, comment);
265
+ const getVisibilityTag = (comment) => {
266
+ if (hasTag(comment, "internal")) return TAG_INTERNAL;
267
+ if (hasTag(comment, "api")) return "api";
268
+ };
269
+ const hasConflictingVisibilityTags = (comment) => hasTag(comment, "api") && hasTag(comment, "internal");
270
+ const hasModuleSource = (node) => "source" in node && (node.source ?? void 0) !== void 0;
271
+ const findFileLevelComment$1 = (sourceCode) => {
272
+ const [firstStatement] = sourceCode.ast.body;
273
+ if (firstStatement === void 0) return;
274
+ const leadingComments = sourceCode.getCommentsBefore(firstStatement).filter(isBlockComment);
275
+ const [fileComment] = leadingComments;
276
+ if (fileComment === void 0) return;
277
+ if (leadingComments.length > 1 || hasModuleSource(firstStatement)) return fileComment;
278
+ return firstStatement.loc.start.line - fileComment.loc.end.line > 1 ? fileComment : void 0;
279
+ };
280
+ const getFileLevelBlockComment = (sourceCode) => {
281
+ const comment = findFileLevelComment$1(sourceCode);
282
+ return comment === void 0 ? void 0 : `/*${comment.value}*/`;
283
+ };
284
+ const getEffectiveVisibilityTag = (symbolComment, fileComment) => getVisibilityTag(symbolComment) ?? getVisibilityTag(fileComment);
285
+ //#endregion
286
+ //#region ../src/js/eslint/utils/exports.ts
287
+ /**
288
+ * @internal @brnshkr/config/eslint
289
+ */
290
+ const DEFAULT_NAME = "default";
291
+ const ANONYMOUS_NAME = "<anonymous>";
292
+ const ATOMIC_NAMED_KIND_MAP = {
293
+ ClassDeclaration: "Class",
294
+ FunctionDeclaration: "Function",
295
+ TSDeclareFunction: "Function",
296
+ TSEnumDeclaration: "Enum",
297
+ TSInterfaceDeclaration: "Interface",
298
+ TSTypeAliasDeclaration: "Type"
299
+ };
300
+ const DEFAULT_KIND_MAP = {
301
+ ArrowFunctionExpression: "Function",
302
+ CallExpression: "Constant",
303
+ FunctionExpression: "Function",
304
+ Identifier: "Constant",
305
+ NewExpression: "Constant",
306
+ ObjectExpression: "Constant"
307
+ };
308
+ const collectAtomicNamedDeclaration = (declaration, anchor, comment) => {
309
+ const kind = ATOMIC_NAMED_KIND_MAP[declaration.type];
310
+ if (kind === void 0) return;
311
+ return {
312
+ anchor,
313
+ declaration,
314
+ comment,
315
+ kind,
316
+ name: declaration.id?.name ?? ANONYMOUS_NAME
317
+ };
318
+ };
319
+ const collectVariableDeclaration = (declaration, anchor, comment) => declaration.declarations.filter((declarator) => declarator.id.type === "Identifier").map((declarator) => ({
320
+ anchor,
321
+ declaration: declarator,
322
+ comment,
323
+ kind: isFunctionInitializer(declarator.init) ? "Function" : "Constant",
324
+ name: declarator.id.name
325
+ }));
326
+ const collectNamedDeclaration = (declaration, anchor, comment) => {
327
+ const atomic = collectAtomicNamedDeclaration(declaration, anchor, comment);
328
+ if (atomic !== void 0) return [atomic];
329
+ if (declaration.type === "VariableDeclaration") return collectVariableDeclaration(declaration, anchor, comment);
330
+ return [];
331
+ };
332
+ const collectDefaultDeclaration = (declaration, anchor, comment) => {
333
+ if (declaration.type === "ClassDeclaration" || declaration.type === "FunctionDeclaration") return collectNamedDeclaration(declaration, anchor, comment);
334
+ const kind = DEFAULT_KIND_MAP[declaration.type];
335
+ if (kind === void 0) return [];
336
+ return [{
337
+ anchor,
338
+ declaration,
339
+ comment,
340
+ kind,
341
+ name: declaration.type === "Identifier" ? declaration.name : DEFAULT_NAME
342
+ }];
343
+ };
344
+ const buildExportVisitors = (sourceCode, onSymbol) => {
345
+ const emit = (node, collectDeclaration) => {
346
+ if (!node.declaration) return;
347
+ const comment = extractBlockComment(sourceCode.getCommentsBefore(node), node);
348
+ for (const symbol of collectDeclaration(node.declaration, node, comment)) onSymbol(symbol);
349
+ };
350
+ return {
351
+ ExportDefaultDeclaration: (node) => {
352
+ emit(node, collectDefaultDeclaration);
353
+ },
354
+ ExportNamedDeclaration: (node) => {
355
+ emit(node, collectNamedDeclaration);
356
+ }
357
+ };
358
+ };
359
+ //#endregion
360
+ //#region ../src/js/eslint/utils/package-exports.ts
361
+ /**
362
+ * @internal @brnshkr/config/eslint
363
+ */
364
+ const DEFAULT_DIST_ROOT = "./dist";
365
+ const DEFAULT_SRC_EXTENSIONS = [
366
+ ".ts",
367
+ ".tsx",
368
+ ".mts",
369
+ ".cts",
370
+ ".d.ts",
371
+ ".d.mts",
372
+ ".d.cts",
373
+ ".js",
374
+ ".jsx",
375
+ ".mjs",
376
+ ".cjs",
377
+ ".svelte",
378
+ ".svelte.ts",
379
+ ".svelte.js"
380
+ ];
381
+ const SRC_ROOT_CANDIDATES = [
382
+ "./src",
383
+ "./src/js",
384
+ "./src/main",
385
+ "./source",
386
+ "./sources",
387
+ "./lib",
388
+ "."
389
+ ];
390
+ const REEXPORT_NAME_PART = String.raw`(?:\*(?:\s+as\s+[\p{ID_Start}$_][\p{ID_Continue}$]*)?|\{[^\}]*\})`;
391
+ const normalizeRoot = (value) => toPosix$1(value).replace(/^\.\//v, "").replace(/\/$/v, "");
392
+ const collectStringEntries = (node, accumulator) => {
393
+ if (typeof node === "string") {
394
+ accumulator.push(node);
395
+ return;
396
+ }
397
+ if (Array.isArray(node)) {
398
+ for (const value of node) collectStringEntries(value, accumulator);
399
+ return;
400
+ }
401
+ if (!isPlainObject(node)) return;
402
+ for (const value of objectValues(node)) collectStringEntries(value, accumulator);
403
+ };
404
+ const findFirstExistingFile = (candidates) => {
405
+ for (const candidate of candidates) if (doesFileExist(candidate)) return toPosix$1(candidate);
406
+ };
407
+ const buildSourceCandidates = (baseAbsolute, sourceExtensions) => [...sourceExtensions.map((extension) => `${baseAbsolute}${extension}`), ...sourceExtensions.map((extension) => path.resolve(baseAbsolute, `index${extension}`))];
408
+ const resolveSourceForDistributionFile = (distributionRelativePath, packageRoot, sourceRoot, sourceExtensions) => {
409
+ const baseAbsolute = path.resolve(packageRoot, sourceRoot, distributionRelativePath.replace(/(?:\.d\.[cm]?ts|\.[cm]?jsx?)$/v, ""));
410
+ return findFirstExistingFile(buildSourceCandidates(baseAbsolute, sourceExtensions));
411
+ };
412
+ const resolveImportSpecifier = (fromFilePath, specifier, sourceExtensions) => {
413
+ if (!specifier.startsWith(".")) return;
414
+ const absoluteBase = path.resolve(path.dirname(fromFilePath), specifier);
415
+ const candidates = buildSourceCandidates(absoluteBase, sourceExtensions);
416
+ return findFirstExistingFile(path.extname(absoluteBase) === "" ? candidates : [absoluteBase, ...candidates]);
417
+ };
418
+ const collectReexports = (entryPath, sourceExtensions, visitedPaths) => {
419
+ if (visitedPaths.has(entryPath)) return;
420
+ visitedPaths.add(entryPath);
421
+ const content = readTextFile(entryPath);
422
+ if (content === void 0) return;
423
+ const reexportPattern = new RegExp(String.raw`\bexport\s+(?:type\s+)?${REEXPORT_NAME_PART}\s+from\s+["'](?<specifier>[^"']+)["']`, "gv");
424
+ for (const match of content.matchAll(reexportPattern)) {
425
+ const specifier = match.groups?.["specifier"];
426
+ if (specifier === void 0) continue;
427
+ const resolvedPath = resolveImportSpecifier(entryPath, specifier, sourceExtensions);
428
+ if (resolvedPath !== void 0) collectReexports(resolvedPath, sourceExtensions, visitedPaths);
429
+ }
430
+ };
431
+ const stripPrefix = (filePath, normalizedPrefix) => {
432
+ const normalizedFile = toPosix$1(filePath).replace(/^\.\//v, "");
433
+ if (!normalizedFile.startsWith(normalizedPrefix)) return;
434
+ return normalizedFile.slice(normalizedPrefix.length);
435
+ };
436
+ const collectApiSourceFiles = (distributionRelativePaths, packageRoot, sourceRoots, sourceExtensions) => {
437
+ const apiSourceFiles = /* @__PURE__ */ new Set();
438
+ for (const distributionRelativePath of distributionRelativePaths) {
439
+ const resolvedPath = sourceRoots.values().map((sourceRoot) => resolveSourceForDistributionFile(distributionRelativePath, packageRoot, sourceRoot, sourceExtensions)).find((candidatePath) => candidatePath !== void 0);
440
+ if (resolvedPath !== void 0) collectReexports(resolvedPath, sourceExtensions, apiSourceFiles);
441
+ }
442
+ return apiSourceFiles;
443
+ };
444
+ const resolvePackageApiSources = (options) => {
445
+ const packageJsonPath = path.resolve(options.packageJsonPath);
446
+ const manifest = readJsonObjectFile(packageJsonPath);
447
+ if (manifest?.["exports"] === void 0) return;
448
+ const packageRoot = path.dirname(packageJsonPath);
449
+ const distributionRoot = options.distRoot ?? DEFAULT_DIST_ROOT;
450
+ const sourceExtensions = options.srcExtensions ?? DEFAULT_SRC_EXTENSIONS;
451
+ const sourceRoots = options.srcRoot === void 0 ? SRC_ROOT_CANDIDATES : [options.srcRoot];
452
+ const normalizedDistribution = `${normalizeRoot(distributionRoot)}/`;
453
+ const exportEntries = [];
454
+ collectStringEntries(manifest["exports"], exportEntries);
455
+ const distributionRelativePaths = exportEntries.filter((value) => value.startsWith("./") && !value.includes("*")).map((value) => stripPrefix(value, normalizedDistribution)).filter((value) => value !== void 0);
456
+ if (distributionRelativePaths.length === 0) return;
457
+ const apiSourceFiles = collectApiSourceFiles(distributionRelativePaths, packageRoot, sourceRoots, sourceExtensions);
458
+ if (apiSourceFiles.size === 0) return;
459
+ return {
460
+ packageJsonPath: toPosix$1(packageJsonPath),
461
+ packageRoot: toPosix$1(packageRoot),
462
+ distRoot: distributionRoot,
463
+ srcRoots: sourceRoots,
464
+ apiSourceFiles
465
+ };
466
+ };
467
+ //#endregion
468
+ //#region ../src/js/eslint/utils/public-api.ts
469
+ /**
470
+ * @internal @brnshkr/config/eslint
471
+ */
472
+ const resolutionCache = /* @__PURE__ */ new Map();
473
+ const getCacheKey = (options, cwd) => JSON.stringify({
474
+ cwd,
475
+ packageJsonPath: options.packageJsonPath,
476
+ distRoot: options.distRoot,
477
+ srcRoot: options.srcRoot,
478
+ srcExtensions: options.srcExtensions
479
+ });
480
+ const loadPublicApiResolution = (options, cwd) => {
481
+ const cacheKey = getCacheKey(options, cwd);
482
+ const packageJsonPath = options.packageJsonPath ?? findNearestPackageJson(cwd);
483
+ const mtime = packageJsonPath === void 0 ? void 0 : getMtime(packageJsonPath);
484
+ const cacheEntry = resolutionCache.get(cacheKey);
485
+ if (cacheEntry !== void 0 && cacheEntry.mtime === mtime) return cacheEntry.resolution;
486
+ const resolution = packageJsonPath === void 0 ? void 0 : resolvePackageApiSources({
487
+ packageJsonPath,
488
+ distRoot: options.distRoot,
489
+ srcRoot: options.srcRoot,
490
+ srcExtensions: options.srcExtensions
491
+ });
492
+ resolutionCache.set(cacheKey, {
493
+ mtime,
494
+ resolution
495
+ });
496
+ return resolution;
497
+ };
498
+ const isPublicApiFile = (options, cwd, filename) => {
499
+ const resolution = loadPublicApiResolution(options, cwd);
500
+ return resolution === void 0 ? false : resolution.apiSourceFiles.has(toPosix$1(path.resolve(filename)));
501
+ };
502
+ //#endregion
503
+ //#region ../src/js/eslint/configs/builtin/api-or-internal-tag.ts
504
+ /**
505
+ * @internal @brnshkr/config/eslint
506
+ */
507
+ const MESSAGE_ID_MISSING_TAG = "missingTag";
508
+ const MESSAGE_ID_UNEXPECTED_TAG_CONFLICT = "unexpectedTagConflict";
509
+ const MESSAGE_ID_UNEXPECTED_FILE_TAG_CONFLICT = "unexpectedFileTagConflict";
510
+ /**
511
+ * @see https://github.com/brnshkr/config/blob/master/docs/js/eslint/rules/api-or-internal-tag.md
512
+ */
513
+ const apiOrInternalTagRule = {
514
+ meta: {
515
+ type: "suggestion",
516
+ docs: {
517
+ description: "Require every exported declaration in a public-API source file to carry either an `@api` or an `@internal` tag, and never both; a file-level docblock carrying either tag covers all symbols below it.",
518
+ url: "https://github.com/brnshkr/config/blob/master/docs/js/eslint/rules/api-or-internal-tag.md"
519
+ },
520
+ schema: [{
521
+ type: "object",
522
+ additionalProperties: false,
523
+ properties: {
524
+ packageJsonPath: { type: "string" },
525
+ distRoot: { type: "string" },
526
+ srcRoot: { type: "string" },
527
+ srcExtensions: {
528
+ type: "array",
529
+ items: { type: "string" }
530
+ }
531
+ }
532
+ }],
533
+ messages: {
534
+ [MESSAGE_ID_MISSING_TAG]: "{{ kind }} `{{ name }}` is exported from a public-API source file and must carry an `@api` or `@internal` JSDoc tag.",
535
+ [MESSAGE_ID_UNEXPECTED_TAG_CONFLICT]: "{{ kind }} `{{ name }}` must declare exactly one visibility, but carries both `@api` and `@internal`.",
536
+ [MESSAGE_ID_UNEXPECTED_FILE_TAG_CONFLICT]: "The file-level docblock must declare exactly one visibility, but carries both `@api` and `@internal`."
537
+ }
538
+ },
539
+ create: (context) => {
540
+ const options = context.options[0] ?? {};
541
+ const sourceCode = context.sourceCode;
542
+ const fileCommentNode = findFileLevelComment$1(sourceCode);
543
+ const fileComment = fileCommentNode === void 0 ? void 0 : `/*${fileCommentNode.value}*/`;
544
+ const isInPublicApiFile = isPublicApiFile(options, context.cwd, context.filename);
545
+ return {
546
+ ...buildExportVisitors(sourceCode, (symbol) => {
547
+ if (hasConflictingVisibilityTags(symbol.comment)) {
548
+ context.report({
549
+ node: symbol.anchor,
550
+ messageId: MESSAGE_ID_UNEXPECTED_TAG_CONFLICT,
551
+ data: {
552
+ kind: symbol.kind,
553
+ name: symbol.name
554
+ }
555
+ });
556
+ return;
557
+ }
558
+ if (!isInPublicApiFile || getEffectiveVisibilityTag(symbol.comment, fileComment) !== void 0) return;
559
+ context.report({
560
+ node: symbol.anchor,
561
+ messageId: MESSAGE_ID_MISSING_TAG,
562
+ data: {
563
+ kind: symbol.kind,
564
+ name: symbol.name
565
+ }
566
+ });
567
+ }),
568
+ "Program:exit": () => {
569
+ if (fileCommentNode !== void 0 && hasConflictingVisibilityTags(fileComment)) context.report({
570
+ loc: fileCommentNode.loc,
571
+ messageId: MESSAGE_ID_UNEXPECTED_FILE_TAG_CONFLICT
572
+ });
573
+ }
574
+ };
575
+ }
576
+ };
577
+ //#endregion
578
+ //#region ../src/js/eslint/utils/boolish-classification.ts
579
+ /**
580
+ * @internal @brnshkr/config/eslint
581
+ */
582
+ const TYPE_CLASSIFICATIONS = {
583
+ BOOL: "bool",
584
+ NON_BOOL: "non-bool",
585
+ UNKNOWN: "unknown"
586
+ };
587
+ const BOOLEAN_TYPE_NAMES = /* @__PURE__ */ new Set([
588
+ "boolean",
589
+ "false",
590
+ "true"
591
+ ]);
592
+ const NULLISH_TYPE_NAMES = /* @__PURE__ */ new Set(["null", "undefined"]);
593
+ const UNRESOLVED_TYPE_NAMES = /* @__PURE__ */ new Set([
594
+ "any",
595
+ "error",
596
+ "never",
597
+ "unknown"
598
+ ]);
599
+ const BOOLEAN_TYPE_NODES = /* @__PURE__ */ new Set(["TSBooleanKeyword", "TSTypePredicate"]);
600
+ const NULLISH_TYPE_NODES = /* @__PURE__ */ new Set(["TSNullKeyword", "TSUndefinedKeyword"]);
601
+ const UNRESOLVED_TYPE_NODES = /* @__PURE__ */ new Set([
602
+ "TSAnyKeyword",
603
+ "TSConditionalType",
604
+ "TSImportType",
605
+ "TSIndexedAccessType",
606
+ "TSInferType",
607
+ "TSIntersectionType",
608
+ "TSMappedType",
609
+ "TSNeverKeyword",
610
+ "TSThisType",
611
+ "TSTypeOperator",
612
+ "TSTypeQuery",
613
+ "TSTypeReference",
614
+ "TSUnknownKeyword"
615
+ ]);
616
+ const NON_BOOLEAN_EXPRESSIONS = /* @__PURE__ */ new Set([
617
+ "ArrayExpression",
618
+ "NewExpression",
619
+ "ObjectExpression",
620
+ "TemplateLiteral"
621
+ ]);
622
+ const UNWRAPPED_EXPRESSIONS = /* @__PURE__ */ new Set([
623
+ "TSAsExpression",
624
+ "TSNonNullExpression",
625
+ "TSSatisfiesExpression",
626
+ "TSTypeAssertion"
627
+ ]);
628
+ const BOOLEAN_BINARY_OPERATORS = /* @__PURE__ */ new Set([
629
+ "!=",
630
+ "!==",
631
+ "<",
632
+ "<=",
633
+ "==",
634
+ "===",
635
+ ">",
636
+ ">=",
637
+ "in",
638
+ "instanceof"
639
+ ]);
640
+ const BOOLEAN_CONSTRUCTOR_NAME = "Boolean";
641
+ const PROMISE_TYPE_NAME = "Promise";
642
+ const EXTERNAL_PATH_SEGMENT = "/node_modules/";
643
+ const combineClassifications = (classifications) => {
644
+ if (classifications.length === 0) return TYPE_CLASSIFICATIONS.UNKNOWN;
645
+ if (classifications.every((classification) => classification === TYPE_CLASSIFICATIONS.BOOL)) return TYPE_CLASSIFICATIONS.BOOL;
646
+ return classifications.every((classification) => classification === TYPE_CLASSIFICATIONS.NON_BOOL) ? TYPE_CLASSIFICATIONS.NON_BOOL : TYPE_CLASSIFICATIONS.UNKNOWN;
647
+ };
648
+ const getTypeName = (checker, type) => checker.typeToString(checker.getBaseTypeOfLiteralType(type));
649
+ const classifyPlainType = (checker, type) => {
650
+ const symbol = type.getSymbol();
651
+ if (type.isTypeParameter()) return TYPE_CLASSIFICATIONS.UNKNOWN;
652
+ if (symbol !== void 0) return TYPE_CLASSIFICATIONS.NON_BOOL;
653
+ const typeName = getTypeName(checker, type);
654
+ if (BOOLEAN_TYPE_NAMES.has(typeName)) return TYPE_CLASSIFICATIONS.BOOL;
655
+ return UNRESOLVED_TYPE_NAMES.has(typeName) ? TYPE_CLASSIFICATIONS.UNKNOWN : TYPE_CLASSIFICATIONS.NON_BOOL;
656
+ };
657
+ const classifyType = (checker, type) => {
658
+ if (!type.isUnion()) return classifyPlainType(checker, type);
659
+ return combineClassifications(type.types.filter((member) => !NULLISH_TYPE_NAMES.has(getTypeName(checker, member))).map((member) => classifyPlainType(checker, member)));
660
+ };
661
+ const unwrapPromiseType = (checker, type) => type.getSymbol()?.getName() === PROMISE_TYPE_NAME ? checker.getTypeArguments(type)[0] ?? type : type;
662
+ const resolveReturnType = (checker, type) => {
663
+ const [signature] = type.getCallSignatures();
664
+ return signature === void 0 ? void 0 : unwrapPromiseType(checker, signature.getReturnType());
665
+ };
666
+ const isExternalSymbol = (symbol) => symbol.declarations?.some((declaration) => toPosix$1(declaration.getSourceFile().fileName).includes(EXTERNAL_PATH_SEGMENT)) ?? true;
667
+ const resolveInstanceType = (type) => type.getConstructSignatures()[0]?.getReturnType() ?? type;
668
+ const hasExternalUpstreamMember = (services, enclosingClass, name) => [...enclosingClass.superClass ? [enclosingClass.superClass] : [], ...enclosingClass.implements].some((heritageNode) => {
669
+ const member = resolveInstanceType(services.getTypeAtLocation(heritageNode)).getProperty(name);
670
+ return member !== void 0 && isExternalSymbol(member);
671
+ });
672
+ const classifyTypeAnnotation = (node) => {
673
+ if (node.type === "TSUnionType") return combineClassifications(node.types.filter((member) => !NULLISH_TYPE_NODES.has(member.type)).map((member) => classifyTypeAnnotation(member)));
674
+ if (node.type === "TSLiteralType") return node.literal.type === "Literal" && typeof node.literal.value === "boolean" ? TYPE_CLASSIFICATIONS.BOOL : TYPE_CLASSIFICATIONS.NON_BOOL;
675
+ if (BOOLEAN_TYPE_NODES.has(node.type)) return TYPE_CLASSIFICATIONS.BOOL;
676
+ return UNRESOLVED_TYPE_NODES.has(node.type) ? TYPE_CLASSIFICATIONS.UNKNOWN : TYPE_CLASSIFICATIONS.NON_BOOL;
677
+ };
678
+ const classifyBooleanOperator = (node) => {
679
+ if (node.type === "UnaryExpression") return node.operator === "!" ? TYPE_CLASSIFICATIONS.BOOL : TYPE_CLASSIFICATIONS.NON_BOOL;
680
+ if (node.type === "BinaryExpression") return BOOLEAN_BINARY_OPERATORS.has(node.operator) ? TYPE_CLASSIFICATIONS.BOOL : TYPE_CLASSIFICATIONS.NON_BOOL;
681
+ if (node.type === "CallExpression" && node.callee.type === "Identifier") return node.callee.name === BOOLEAN_CONSTRUCTOR_NAME ? TYPE_CLASSIFICATIONS.BOOL : void 0;
682
+ };
683
+ const classifyExpression = (node) => {
684
+ if (node.type === "Literal") return typeof node.value === "boolean" ? TYPE_CLASSIFICATIONS.BOOL : TYPE_CLASSIFICATIONS.NON_BOOL;
685
+ if (UNWRAPPED_EXPRESSIONS.has(node.type)) return classifyExpression(node.expression);
686
+ return NON_BOOLEAN_EXPRESSIONS.has(node.type) ? TYPE_CLASSIFICATIONS.NON_BOOL : classifyBooleanOperator(node) ?? TYPE_CLASSIFICATIONS.UNKNOWN;
687
+ };
688
+ //#endregion
689
+ //#region ../src/js/eslint/utils/boolish-prefixes.ts
690
+ /**
691
+ * Auxiliary, modal, and copula verbs.
692
+ * Boolish in either direction, on any kind.
693
+ */
694
+ const AUXILIARY_PREFIXES = [
695
+ "are",
696
+ "can",
697
+ "did",
698
+ "does",
699
+ "has",
700
+ "is",
701
+ "may",
702
+ "should",
703
+ "was",
704
+ "will"
705
+ ];
706
+ /**
707
+ * Capability and need verbs.
708
+ * Read as boolean value flags too, so boolish in either direction, on any kind.
709
+ */
710
+ const CAPABILITY_PREFIXES = [
711
+ "expects",
712
+ "needs",
713
+ "prefers",
714
+ "requires",
715
+ "supports",
716
+ "wants"
717
+ ];
718
+ /**
719
+ * Object-relation predicate verbs.
720
+ * Boolish in either direction, on any kind — `allowsNull` reads as a flag, `equals()` as a predicate.
721
+ */
722
+ const RELATIONAL_PREFIXES = [
723
+ "accepts",
724
+ "allows",
725
+ "belongs",
726
+ "contains",
727
+ "covers",
728
+ "denies",
729
+ "depends",
730
+ "disallows",
731
+ "equals",
732
+ "excludes",
733
+ "exists",
734
+ "extends",
735
+ "handles",
736
+ "ignores",
737
+ "implements",
738
+ "includes",
739
+ "intersects",
740
+ "owns",
741
+ "provides",
742
+ "rejects",
743
+ "satisfies",
744
+ "uses"
745
+ ];
746
+ /**
747
+ * Verbs whose third-person form is a canonical non-boolean value (`matches`, `startsAt`, `endsAt`)
748
+ * — reserved on methods and functions only, never on value-holders.
749
+ */
750
+ const COLLIDER_PREFIXES = [
751
+ "ends",
752
+ "matches",
753
+ "starts"
754
+ ];
755
+ /**
756
+ * Command verbs — allowed on a boolean return, never reserved, since `doReset(): void` and the like
757
+ * legitimately return a non-boolean.
758
+ */
759
+ const DIRECTIVE_PREFIXES = ["do"];
760
+ /**
761
+ * Prefixes a non-boolean value-holder must not start with.
762
+ * The auxiliary, capability, and object-relation verbs — each reads as a boolean flag on a value.
763
+ */
764
+ const RESERVED_VALUE_PREFIXES = [
765
+ ...AUXILIARY_PREFIXES,
766
+ ...CAPABILITY_PREFIXES,
767
+ ...RELATIONAL_PREFIXES
768
+ ];
769
+ /**
770
+ * Prefixes a non-boolean method or function must not start with.
771
+ * The value-reserved set plus the colliders — they read as a predicate on a method, data on a value.
772
+ */
773
+ const RESERVED_METHOD_PREFIXES = [...RESERVED_VALUE_PREFIXES, ...COLLIDER_PREFIXES];
774
+ /**
775
+ * Prefixes a boolean-returning method or function may start with.
776
+ * The method-reserved set plus the `do` directive, which commands may also use on non-boolean returns.
777
+ */
778
+ const PREDICATE_PREFIXES = [...RESERVED_METHOD_PREFIXES, ...DIRECTIVE_PREFIXES];
779
+ /**
780
+ * Prefixes a boolean value-holder may start with.
781
+ * The value-reserved set plus the `do` directive and the representation flag `as`.
782
+ */
783
+ const FLAG_PREFIXES = [
784
+ ...RESERVED_VALUE_PREFIXES,
785
+ ...DIRECTIVE_PREFIXES,
786
+ "as"
787
+ ];
788
+ const KIND_CONSTANT = "Constant";
789
+ const KIND_FUNCTION = "Function";
790
+ const KIND_METHOD$1 = "Method";
791
+ const KIND_PARAMETER = "Parameter";
792
+ const KIND_PROPERTY = "Property";
793
+ const KIND_VARIABLE = "Variable";
794
+ const CALLABLE_KINDS = /* @__PURE__ */ new Set([KIND_FUNCTION, KIND_METHOD$1]);
795
+ const isCallableKind = (kind) => CALLABLE_KINDS.has(kind);
796
+ const getFirstWord = (name) => (/^(?:[0-9a-z]+|[0-9A-Z]+)/v.exec(name)?.[0] ?? "").toLowerCase();
797
+ const getBooleanConverterToken = (name, kind) => isCallableKind(kind) ? /^(?:as|to)bool(?:ean)?/iv.exec(name)?.[0] : void 0;
798
+ const getPrefixesForKind = (kind) => isCallableKind(kind) ? PREDICATE_PREFIXES : FLAG_PREFIXES;
799
+ const isBoolishName = (name, kind) => getPrefixesForKind(kind).includes(getFirstWord(name)) || getBooleanConverterToken(name, kind) !== void 0;
800
+ const getReservedToken = (name, kind) => {
801
+ const converterToken = getBooleanConverterToken(name, kind);
802
+ if (converterToken !== void 0) return converterToken;
803
+ const firstWord = getFirstWord(name);
804
+ return (isCallableKind(kind) ? RESERVED_METHOD_PREFIXES : RESERVED_VALUE_PREFIXES).includes(firstWord) ? firstWord : void 0;
805
+ };
806
+ //#endregion
807
+ //#region ../src/js/eslint/configs/builtin/boolish-prefix.ts
808
+ /**
809
+ * @internal @brnshkr/config/eslint
810
+ */
811
+ const MESSAGE_ID_MISSING_PREFIX = "missingPrefix";
812
+ const MESSAGE_ID_UNEXPECTED_PREFIX = "unexpectedPrefix";
813
+ const CLASS_MEMBER_NODES = /* @__PURE__ */ new Set([
814
+ "MethodDefinition",
815
+ "PropertyDefinition",
816
+ "TSAbstractMethodDefinition",
817
+ "TSAbstractPropertyDefinition"
818
+ ]);
819
+ const CALLABLE_INITIALIZERS = /* @__PURE__ */ new Set(["ArrowFunctionExpression", "FunctionExpression"]);
820
+ const RETURN_OWNER_NODES = /* @__PURE__ */ new Set([
821
+ "ArrowFunctionExpression",
822
+ "FunctionDeclaration",
823
+ "FunctionExpression"
824
+ ]);
825
+ const PARAMETER_OWNER_SELECTOR = `${[
826
+ "ArrowFunctionExpression",
827
+ "FunctionDeclaration",
828
+ "FunctionExpression",
829
+ "TSDeclareFunction",
830
+ "TSEmptyBodyFunctionExpression",
831
+ "TSFunctionType",
832
+ "TSMethodSignature"
833
+ ].join(", ")}:exit`;
834
+ const resolveNameNode = (node) => node.type === "Identifier" || node.type === "PrivateIdentifier" ? node : void 0;
835
+ const isSetterMember = (node) => {
836
+ if (node.type !== "MethodDefinition" && node.type !== "TSAbstractMethodDefinition") return false;
837
+ return node.kind === "set";
838
+ };
839
+ const isSkippedMember = (ruleContext, node) => {
840
+ if (!CLASS_MEMBER_NODES.has(node.type)) return false;
841
+ const member = node;
842
+ const nameNode = resolveNameNode(member.key);
843
+ if (nameNode === void 0 || isSetterMember(member)) return true;
844
+ return ruleContext.services !== void 0 && hasExternalUpstreamMember(ruleContext.services, member.parent.parent, nameNode.name);
845
+ };
846
+ const findEnclosingCallable = (node) => {
847
+ let current = node.parent;
848
+ while (current !== void 0 && !RETURN_OWNER_NODES.has(current.type)) current = current.parent;
849
+ return current;
850
+ };
851
+ const resolveCallableResult = (ruleContext, callable) => {
852
+ if (callable.returnType) return classifyTypeAnnotation(callable.returnType.typeAnnotation);
853
+ if (callable.type === "ArrowFunctionExpression" && callable.body.type !== "BlockStatement") return classifyExpression(callable.body);
854
+ return combineClassifications(ruleContext.returnClassifications.get(callable) ?? []);
855
+ };
856
+ const resolveSyntacticCallable = (target, annotation) => {
857
+ if (target.callable !== void 0) return target.callable;
858
+ if (annotation?.type === "TSFunctionType") return annotation;
859
+ return CALLABLE_INITIALIZERS.has(target.initializer?.type ?? "") ? target.initializer : void 0;
860
+ };
861
+ const resolveHeldClassification = (target, annotation) => {
862
+ if (annotation !== void 0) return classifyTypeAnnotation(annotation);
863
+ return target.initializer ? classifyExpression(target.initializer) : TYPE_CLASSIFICATIONS.UNKNOWN;
864
+ };
865
+ const resolveFromSyntax = (ruleContext, target) => {
866
+ const annotation = target.typeAnnotation?.typeAnnotation;
867
+ const callable = resolveSyntacticCallable(target, annotation);
868
+ if (callable === void 0) return {
869
+ classification: resolveHeldClassification(target, annotation),
870
+ isCallable: false
871
+ };
872
+ return {
873
+ classification: resolveCallableResult(ruleContext, callable),
874
+ isCallable: true
875
+ };
876
+ };
877
+ const resolveFromTypes = (services, checker, nameNode) => {
878
+ const type = services.getTypeAtLocation(nameNode);
879
+ const returnType = resolveReturnType(checker, type);
880
+ return {
881
+ classification: classifyType(checker, returnType ?? type),
882
+ isCallable: returnType !== void 0
883
+ };
884
+ };
885
+ const checkTarget = (ruleContext, target) => {
886
+ const { checker, services } = ruleContext;
887
+ const { classification, isCallable } = services === void 0 || checker === void 0 ? resolveFromSyntax(ruleContext, target) : resolveFromTypes(services, checker, target.nameNode);
888
+ const kind = isCallable ? target.callableKind : target.valueKind;
889
+ const { name } = target.nameNode;
890
+ if (classification === TYPE_CLASSIFICATIONS.BOOL) {
891
+ if (!isBoolishName(name, kind)) ruleContext.report(target.nameNode, MESSAGE_ID_MISSING_PREFIX, {
892
+ kind,
893
+ name,
894
+ prefixes: getPrefixesForKind(kind).join(", ")
895
+ });
896
+ return;
897
+ }
898
+ const reservedToken = classification === TYPE_CLASSIFICATIONS.NON_BOOL ? getReservedToken(name, kind) : void 0;
899
+ if (reservedToken !== void 0) ruleContext.report(target.nameNode, MESSAGE_ID_UNEXPECTED_PREFIX, {
900
+ kind,
901
+ name,
902
+ prefix: reservedToken
903
+ });
904
+ };
905
+ const checkParameters = (ruleContext, node) => {
906
+ if (isSkippedMember(ruleContext, node.parent)) return;
907
+ for (const parameter of node.params) {
908
+ const nameNode = resolveParameterIdentifier(parameter);
909
+ if (nameNode !== void 0) checkTarget(ruleContext, {
910
+ nameNode,
911
+ valueKind: parameter.type === "TSParameterProperty" ? KIND_PROPERTY : KIND_PARAMETER,
912
+ callableKind: KIND_FUNCTION,
913
+ typeAnnotation: nameNode.typeAnnotation,
914
+ initializer: parameter.type === "AssignmentPattern" ? parameter.right : void 0
915
+ });
916
+ }
917
+ };
918
+ const checkClassMember = (ruleContext, node) => {
919
+ const nameNode = resolveNameNode(node.key);
920
+ if (nameNode === void 0 || isSkippedMember(ruleContext, node)) return;
921
+ if (node.type === "PropertyDefinition" || node.type === "TSAbstractPropertyDefinition") {
922
+ checkTarget(ruleContext, {
923
+ nameNode,
924
+ valueKind: KIND_PROPERTY,
925
+ callableKind: KIND_METHOD$1,
926
+ typeAnnotation: node.typeAnnotation,
927
+ initializer: node.value
928
+ });
929
+ return;
930
+ }
931
+ if (node.kind !== "constructor") checkTarget(ruleContext, {
932
+ nameNode,
933
+ valueKind: KIND_PROPERTY,
934
+ callableKind: node.kind === "get" ? KIND_PROPERTY : KIND_METHOD$1,
935
+ callable: node.value
936
+ });
937
+ };
938
+ const collectReturnClassification = (ruleContext, node) => {
939
+ const callable = ruleContext.services === void 0 ? findEnclosingCallable(node) : void 0;
940
+ if (callable === void 0) return;
941
+ ruleContext.returnClassifications.set(callable, [...ruleContext.returnClassifications.get(callable) ?? [], node.argument ? classifyExpression(node.argument) : TYPE_CLASSIFICATIONS.NON_BOOL]);
942
+ };
943
+ const buildVisitors = (ruleContext) => ({
944
+ [PARAMETER_OWNER_SELECTOR]: (node) => {
945
+ checkParameters(ruleContext, node);
946
+ },
947
+ "FunctionDeclaration, TSDeclareFunction:exit": (node) => {
948
+ if (node.id) checkTarget(ruleContext, {
949
+ nameNode: node.id,
950
+ valueKind: KIND_FUNCTION,
951
+ callableKind: KIND_FUNCTION,
952
+ callable: node
953
+ });
954
+ },
955
+ "MethodDefinition, PropertyDefinition, TSAbstractMethodDefinition, TSAbstractPropertyDefinition:exit": (node) => {
956
+ checkClassMember(ruleContext, node);
957
+ },
958
+ "ReturnStatement:exit": (node) => {
959
+ collectReturnClassification(ruleContext, node);
960
+ },
961
+ "TSEnumMember:exit": (node) => {
962
+ const nameNode = resolveNameNode(node.id);
963
+ if (nameNode !== void 0) checkTarget(ruleContext, {
964
+ nameNode,
965
+ valueKind: KIND_CONSTANT,
966
+ callableKind: KIND_CONSTANT,
967
+ initializer: node.initializer
968
+ });
969
+ },
970
+ "TSMethodSignature:exit": (node) => {
971
+ const nameNode = resolveNameNode(node.key);
972
+ if (nameNode !== void 0) checkTarget(ruleContext, {
973
+ nameNode,
974
+ valueKind: KIND_METHOD$1,
975
+ callableKind: KIND_METHOD$1,
976
+ callable: node
977
+ });
978
+ },
979
+ "TSPropertySignature:exit": (node) => {
980
+ const nameNode = resolveNameNode(node.key);
981
+ if (nameNode !== void 0) checkTarget(ruleContext, {
982
+ nameNode,
983
+ valueKind: KIND_PROPERTY,
984
+ callableKind: KIND_METHOD$1,
985
+ typeAnnotation: node.typeAnnotation
986
+ });
987
+ },
988
+ "VariableDeclarator:exit": (node) => {
989
+ if (node.id.type === "Identifier") checkTarget(ruleContext, {
990
+ nameNode: node.id,
991
+ valueKind: KIND_VARIABLE,
992
+ callableKind: KIND_FUNCTION,
993
+ typeAnnotation: node.id.typeAnnotation,
994
+ initializer: node.init
995
+ });
996
+ }
997
+ });
998
+ /**
999
+ * @see https://github.com/brnshkr/config/blob/master/docs/js/eslint/rules/boolish-prefix.md
1000
+ */
1001
+ const boolishPrefixRule = {
1002
+ meta: {
1003
+ type: "suggestion",
1004
+ docs: {
1005
+ description: "Keep boolean-ness and names aligned in both directions.",
1006
+ url: "https://github.com/brnshkr/config/blob/master/docs/js/eslint/rules/boolish-prefix.md"
1007
+ },
1008
+ messages: {
1009
+ [MESSAGE_ID_MISSING_PREFIX]: "{{ kind }} name `{{ name }}` must have one of the following prefixes: {{ prefixes }}.",
1010
+ [MESSAGE_ID_UNEXPECTED_PREFIX]: "{{ kind }} name `{{ name }}` must not start with the boolish prefix `{{ prefix }}` because it is not boolean."
1011
+ }
1012
+ },
1013
+ create: (context) => {
1014
+ const { parserServices } = context.sourceCode;
1015
+ const services = parserServices;
1016
+ const program = services?.program;
1017
+ return buildVisitors({
1018
+ report: (nameNode, messageId, data) => {
1019
+ context.report({
1020
+ node: nameNode,
1021
+ messageId,
1022
+ data
1023
+ });
1024
+ },
1025
+ services: program === void 0 ? void 0 : services,
1026
+ checker: program?.getTypeChecker(),
1027
+ returnClassifications: /* @__PURE__ */ new Map()
1028
+ });
1029
+ }
1030
+ };
1031
+ //#endregion
1032
+ //#region ../src/js/eslint/configs/builtin/interface-suffix.ts
1033
+ const MESSAGE_ID_MISSING_SUFFIX = "missingSuffix";
1034
+ const getImplementedName = (expression) => {
1035
+ if (expression.type === "Identifier") return expression.name;
1036
+ if (expression.type === "MemberExpression" && expression.property.type === "Identifier") return expression.property.name;
1037
+ return "";
1038
+ };
1039
+ /**
1040
+ * @see https://github.com/brnshkr/config/blob/master/docs/js/eslint/rules/interface-suffix.md
1041
+ */
1042
+ const interfaceSuffixRule = {
1043
+ meta: {
1044
+ type: "suggestion",
1045
+ docs: {
1046
+ description: "Require classes that implement a single `*Interface` to end with the matching suffix.",
1047
+ url: "https://github.com/brnshkr/config/blob/master/docs/js/eslint/rules/interface-suffix.md"
1048
+ },
1049
+ messages: { [MESSAGE_ID_MISSING_SUFFIX]: "Class `{{ name }}` implements `{{ interfaceName }}` and must end with suffix `{{ suffix }}`." }
1050
+ },
1051
+ create: (context) => {
1052
+ const checkHeritage = (node) => {
1053
+ if (!node.id) return;
1054
+ const implementedNames = node.implements.map((implemented) => getImplementedName(implemented.expression)).filter((name) => name.length > 9 && name.endsWith("Interface"));
1055
+ if (implementedNames.length !== 1) return;
1056
+ const [interfaceName = ""] = implementedNames;
1057
+ const suffix = interfaceName.slice(0, -9);
1058
+ if (node.id.name.endsWith(suffix)) return;
1059
+ context.report({
1060
+ node: node.id,
1061
+ messageId: MESSAGE_ID_MISSING_SUFFIX,
1062
+ data: {
1063
+ name: node.id.name,
1064
+ interfaceName,
1065
+ suffix
1066
+ }
1067
+ });
1068
+ };
1069
+ return {
1070
+ ClassDeclaration: checkHeritage,
1071
+ ClassExpression: checkHeritage
1072
+ };
1073
+ }
1074
+ };
1075
+ //#endregion
1076
+ //#region ../src/js/eslint/configs/builtin/internal-usage.ts
1077
+ /**
1078
+ * @internal @brnshkr/config/eslint
1079
+ */
1080
+ const MESSAGE_ID_UNEXPECTED_INTERNAL_USAGE = "unexpectedInternalUsage";
1081
+ const MESSAGE_ID_UNEXPECTED_TARGETED_INTERNAL_USAGE = "unexpectedTargetedInternalUsage";
1082
+ const PLAIN_INTERNAL = "@internal";
1083
+ const PUBLIC_API = "@api";
1084
+ const NAMESPACE_SEPARATORS = [
1085
+ "/",
1086
+ "#",
1087
+ "."
1088
+ ];
1089
+ const LAYOUT_SEGMENTS = /* @__PURE__ */ new Set([
1090
+ "dist",
1091
+ "js",
1092
+ "lib",
1093
+ "src"
1094
+ ]);
1095
+ const WILDCARD_SUFFIX$1 = "/*";
1096
+ const MODULE_SPECIFIER_SEARCH_DEPTH = 4;
1097
+ const DELIMITED_PATTERN = /^(?<delimiter>[^\w\\])(?<source>.*)\k<delimiter>(?<flags>[A-Za-z]*)$/sv;
1098
+ const OPTION_NAMES = ["allowedInternals", "allowedCallers"];
1099
+ const MODULE_EXTENSIONS = [
1100
+ ".ts",
1101
+ ".tsx",
1102
+ ".mts",
1103
+ ".cts",
1104
+ ".d.ts",
1105
+ ".js",
1106
+ ".jsx",
1107
+ ".mjs",
1108
+ ".cjs"
1109
+ ];
1110
+ const IMPORT_SPECIFIER_TYPES = /* @__PURE__ */ new Set([
1111
+ "ImportDefaultSpecifier",
1112
+ "ImportNamespaceSpecifier",
1113
+ "ImportSpecifier"
1114
+ ]);
1115
+ const packageCache = /* @__PURE__ */ new Map();
1116
+ const packageJsonPathCache = /* @__PURE__ */ new Map();
1117
+ const moduleVisibilityCache = /* @__PURE__ */ new Map();
1118
+ const internalMarkerCache = /* @__PURE__ */ new WeakMap();
1119
+ const fileVisibilityCache = /* @__PURE__ */ new WeakMap();
1120
+ const trimSeparators = (value) => {
1121
+ let start = 0;
1122
+ let end = value.length;
1123
+ while (start < end && value.startsWith("/", start)) start += 1;
1124
+ while (end > start && value.endsWith("/", end)) end -= 1;
1125
+ return value.slice(start, end);
1126
+ };
1127
+ const isInSubtree = (value, prefix) => value === prefix || NAMESPACE_SEPARATORS.some((separator) => value.startsWith(`${prefix}${separator}`));
1128
+ const resolveInternalTarget = (comment) => {
1129
+ const target = /\*\s+@internal(?=\s|$)(?<target>[^\n]*)/v.exec(comment)?.groups?.["target"];
1130
+ if (target === void 0) return;
1131
+ const trimmedTarget = target.replace(/\*\/\s*$/v, "").trim();
1132
+ return /^[\w\-.\/@]+$/v.test(trimmedTarget) ? trimSeparators(trimmedTarget) : PLAIN_INTERNAL;
1133
+ };
1134
+ const findPackageJsonPath = (directory) => {
1135
+ if (packageJsonPathCache.has(directory)) return packageJsonPathCache.get(directory);
1136
+ const packageJsonPath = findNearestPackageJson(directory);
1137
+ packageJsonPathCache.set(directory, packageJsonPath);
1138
+ return packageJsonPath;
1139
+ };
1140
+ const loadPackageIdentity = (directory) => {
1141
+ const packageJsonPath = findPackageJsonPath(directory);
1142
+ if (packageJsonPath === void 0) return;
1143
+ const mtime = getMtime(packageJsonPath);
1144
+ const cacheEntry = packageCache.get(packageJsonPath);
1145
+ if (cacheEntry !== void 0 && cacheEntry.mtime === mtime) return cacheEntry.identity;
1146
+ const declaredName = readJsonObjectFile(packageJsonPath)?.["name"];
1147
+ const packageName = typeof declaredName === "string" ? declaredName : "";
1148
+ const identity = packageName.length === 0 ? void 0 : {
1149
+ packageName,
1150
+ packageRoot: toPosix$1(path.dirname(packageJsonPath))
1151
+ };
1152
+ packageCache.set(packageJsonPath, {
1153
+ mtime,
1154
+ identity
1155
+ });
1156
+ return identity;
1157
+ };
1158
+ const dropModuleExtension = (modulePath) => modulePath.replace(/(?:\.d)?\.[cm]?[jt]sx?$/v, "");
1159
+ const stripLayoutSegments = (modulePath) => {
1160
+ const segments = modulePath.split("/");
1161
+ let start = 0;
1162
+ while (start < segments.length - 1 && LAYOUT_SEGMENTS.has(segments[start] ?? "")) start += 1;
1163
+ return segments.slice(start).join("/");
1164
+ };
1165
+ const toNamespace = (moduleId) => {
1166
+ const lastSeparator = moduleId.lastIndexOf("/");
1167
+ return lastSeparator === -1 ? moduleId : moduleId.slice(0, lastSeparator);
1168
+ };
1169
+ const toAliasModuleId = (pattern, target, absolutePath) => {
1170
+ if (!pattern.endsWith(WILDCARD_SUFFIX$1) || !target.endsWith(WILDCARD_SUFFIX$1)) return dropModuleExtension(target) === dropModuleExtension(absolutePath) ? pattern : void 0;
1171
+ const targetRoot = target.slice(0, -2);
1172
+ if (!absolutePath.startsWith(`${targetRoot}/`)) return;
1173
+ const relativePath = stripLayoutSegments(dropModuleExtension(absolutePath.slice(targetRoot.length + 1)));
1174
+ return `${pattern.slice(0, -2)}/${relativePath}`;
1175
+ };
1176
+ const buildAliasNamespaces = (absolutePath, aliasPaths) => {
1177
+ const namespaces = [];
1178
+ const aliasEntries = objectEntries(aliasPaths ?? {});
1179
+ for (const [pattern, targets] of aliasEntries) for (const target of targets) {
1180
+ const aliasModuleId = toAliasModuleId(pattern, target, absolutePath);
1181
+ if (aliasModuleId !== void 0) namespaces.push(toNamespace(aliasModuleId));
1182
+ }
1183
+ return namespaces;
1184
+ };
1185
+ const buildModuleIdentity = (filePath, aliasPaths) => {
1186
+ const absolutePath = toPosix$1(path.resolve(filePath));
1187
+ const identity = loadPackageIdentity(path.dirname(absolutePath));
1188
+ if (identity === void 0 || !absolutePath.startsWith(`${identity.packageRoot}/`)) return;
1189
+ const relativePath = stripLayoutSegments(dropModuleExtension(absolutePath.slice(identity.packageRoot.length + 1)));
1190
+ const moduleId = `${identity.packageName}/${relativePath}`;
1191
+ const namespace = toNamespace(moduleId);
1192
+ return {
1193
+ moduleId,
1194
+ namespace,
1195
+ namespaces: [namespace, ...buildAliasNamespaces(absolutePath, aliasPaths)]
1196
+ };
1197
+ };
1198
+ const createExportPattern = () => new RegExp(String.raw`^\s*export\s+(?<defaultKeyword>default\s+)?` + String.raw`(?:(?:abstract|async|declare)\s+)*` + String.raw`(?<keyword>class|const|enum|function|interface|let|type|var)\s+` + String.raw`(?<name>[\p{ID_Start}$_][\p{ID_Continue}$]*)`, "v");
1199
+ const readExportName = (following) => {
1200
+ const match = createExportPattern().exec(following);
1201
+ if (!match) return;
1202
+ return match.groups?.["defaultKeyword"] === void 0 ? match.groups?.["name"] : "default";
1203
+ };
1204
+ const hasInternalMarker = (sourceFile) => {
1205
+ if (internalMarkerCache.has(sourceFile)) return internalMarkerCache.get(sourceFile) === true;
1206
+ const hasMarker = sourceFile.text.includes(`@${TAG_INTERNAL}`);
1207
+ internalMarkerCache.set(sourceFile, hasMarker);
1208
+ return hasMarker;
1209
+ };
1210
+ const resolveVisibility = (comment) => resolveInternalTarget(comment) ?? (hasTag(comment, "api") ? PUBLIC_API : void 0);
1211
+ const isCallableExport = (following) => createExportPattern().exec(following)?.groups?.["keyword"] === "function";
1212
+ const startsWithBlankLine = (following) => /^[^\n]*\n[^\S\n]*\n/v.test(following);
1213
+ const isFileLevelComment = (following) => {
1214
+ const trimmed = following.trimStart();
1215
+ const [firstLine = ""] = trimmed.split("\n", 1);
1216
+ return startsWithBlankLine(following) || trimmed.startsWith("/**") || firstLine.startsWith("import ") || firstLine.startsWith("export ") && firstLine.includes(" from ");
1217
+ };
1218
+ const resolveLexicalFileVisibility = (content, docblockMatches) => {
1219
+ const [fileComment] = docblockMatches;
1220
+ if (fileComment === void 0 || !isFileLevelComment(content.slice(fileComment.index + fileComment[0].length))) return;
1221
+ return resolveVisibility(fileComment[0]);
1222
+ };
1223
+ const buildModuleVisibility = (filePath) => {
1224
+ const content = readTextFile(filePath) ?? "";
1225
+ const docblockMatches = content.matchAll(/\/\*\*(?:[^*]|\*(?!\/))*\*\//gv).toArray();
1226
+ const visibilityByExportName = /* @__PURE__ */ new Map();
1227
+ const callableExportNames = /* @__PURE__ */ new Set();
1228
+ for (const match of docblockMatches) {
1229
+ const visibility = resolveVisibility(match[0]);
1230
+ const following = content.slice(match.index + match[0].length);
1231
+ const exportName = readExportName(following);
1232
+ if (exportName === void 0) continue;
1233
+ if (visibility !== void 0) visibilityByExportName.set(exportName, visibility);
1234
+ if (isCallableExport(following)) callableExportNames.add(exportName);
1235
+ }
1236
+ return {
1237
+ fileVisibility: resolveLexicalFileVisibility(content, docblockMatches),
1238
+ visibilityByExportName,
1239
+ callableExportNames
1240
+ };
1241
+ };
1242
+ const loadModuleVisibility = (filePath) => {
1243
+ const mtime = getMtime(filePath);
1244
+ const cacheEntry = moduleVisibilityCache.get(filePath);
1245
+ if (cacheEntry !== void 0 && cacheEntry.mtime === mtime) return cacheEntry.visibility;
1246
+ const visibility = buildModuleVisibility(filePath);
1247
+ moduleVisibilityCache.set(filePath, {
1248
+ mtime,
1249
+ visibility
1250
+ });
1251
+ return visibility;
1252
+ };
1253
+ const resolveRelativeModulePath = (fromFilePath, specifier) => {
1254
+ if (!specifier.startsWith(".")) return;
1255
+ const base = path.resolve(path.dirname(fromFilePath), specifier);
1256
+ return [...MODULE_EXTENSIONS.map((extension) => `${base}${extension}`), ...MODULE_EXTENSIONS.map((extension) => path.join(base, `index${extension}`))].find((candidate) => doesFileExist(candidate));
1257
+ };
1258
+ const toPattern = (entry) => {
1259
+ const groups = DELIMITED_PATTERN.exec(entry)?.groups;
1260
+ if (groups === void 0) return;
1261
+ const source = groups["source"] ?? "";
1262
+ const flags = (groups["flags"] ?? "").replaceAll(/[gy]/gv, "");
1263
+ try {
1264
+ return new RegExp(source, flags);
1265
+ } catch {
1266
+ return;
1267
+ }
1268
+ };
1269
+ const buildMatcher = (optionName, entries) => {
1270
+ const prefixes = [];
1271
+ const patterns = [];
1272
+ for (const entry of entries) {
1273
+ if (entry instanceof RegExp) {
1274
+ patterns.push(new RegExp(entry.source, entry.flags.replaceAll(/[gy]/gv, "")));
1275
+ continue;
1276
+ }
1277
+ const text = typeof entry === "string" ? entry : "";
1278
+ const pattern = toPattern(text);
1279
+ if (pattern !== void 0) {
1280
+ patterns.push(pattern);
1281
+ continue;
1282
+ }
1283
+ const prefix = trimSeparators(text);
1284
+ if (prefix.length === 0 || !/^[\w@]/v.test(prefix)) throw new Error(`Entry "${entry}" for option "${optionName}" is neither a namespace prefix nor a regular expression.`);
1285
+ prefixes.push(prefix);
1286
+ }
1287
+ return {
1288
+ prefixes,
1289
+ patterns
1290
+ };
1291
+ };
1292
+ const isMappedEntry = (entry) => typeof entry === "object" && (entry ?? void 0) !== void 0 && !Array.isArray(entry) && !(entry instanceof RegExp);
1293
+ const buildAllowList = (optionName, entries) => {
1294
+ const bare = [];
1295
+ const bounded = [];
1296
+ const allowEntries = entries ?? [];
1297
+ for (const entry of allowEntries) {
1298
+ if (!isMappedEntry(entry)) {
1299
+ bare.push(entry);
1300
+ continue;
1301
+ }
1302
+ for (const [subject, counterparts] of objectEntries(entry)) {
1303
+ if (!Array.isArray(counterparts)) throw new TypeError(`Entry "${subject}" for option "${optionName}" must map to a list of namespace prefixes or regular expressions.`);
1304
+ bounded.push({
1305
+ subject: buildMatcher(optionName, [subject]),
1306
+ counterparts: buildMatcher(optionName, counterparts)
1307
+ });
1308
+ }
1309
+ }
1310
+ return {
1311
+ bare: buildMatcher(optionName, bare),
1312
+ bounded
1313
+ };
1314
+ };
1315
+ const matches = (values, matcher) => values.some((value) => matcher.prefixes.some((prefix) => isInSubtree(value, prefix)) || matcher.patterns.some((pattern) => pattern.test(value)));
1316
+ const isAllowedBy = (values, counterparts, allowList) => matches(values, allowList.bare) || allowList.bounded.some((entry) => matches(values, entry.subject) && matches(counterparts, entry.counterparts));
1317
+ const hasModuleSpecifierAbove = (node) => {
1318
+ let current = node;
1319
+ for (let depth = 0; current !== void 0 && depth < MODULE_SPECIFIER_SEARCH_DEPTH; depth += 1) {
1320
+ if (current.moduleSpecifier !== void 0) return true;
1321
+ current = current.parent;
1322
+ }
1323
+ return false;
1324
+ };
1325
+ const findDeclaredVisibility = (declaration) => {
1326
+ let current = declaration;
1327
+ while (current !== void 0) {
1328
+ const commentNodes = (current.jsDoc ?? []).toReversed();
1329
+ for (const commentNode of commentNodes) {
1330
+ const visibility = resolveVisibility(commentNode.getText());
1331
+ if (visibility !== void 0) return visibility;
1332
+ }
1333
+ current = current.parent;
1334
+ }
1335
+ };
1336
+ const isSeparatedByBlankLine = (sourceFile, commentNode, statement) => startsWithBlankLine(sourceFile.text.slice(commentNode.end, statement.getStart()));
1337
+ const findFileLevelComment = (sourceFile) => {
1338
+ const statement = sourceFile.statements?.[0];
1339
+ const commentNodes = statement?.jsDoc ?? [];
1340
+ const [fileCommentNode] = commentNodes;
1341
+ if (statement === void 0 || fileCommentNode === void 0) return;
1342
+ if (commentNodes.length > 1 || statement.moduleSpecifier !== void 0) return fileCommentNode.getText();
1343
+ return isSeparatedByBlankLine(sourceFile, fileCommentNode, statement) ? fileCommentNode.getText() : void 0;
1344
+ };
1345
+ const findFileVisibility = (sourceFile) => {
1346
+ if (fileVisibilityCache.has(sourceFile)) return fileVisibilityCache.get(sourceFile);
1347
+ const fileComment = findFileLevelComment(sourceFile);
1348
+ const visibility = fileComment === void 0 ? void 0 : resolveVisibility(fileComment);
1349
+ fileVisibilityCache.set(sourceFile, visibility);
1350
+ return visibility;
1351
+ };
1352
+ const toInternalTarget = (visibility) => visibility === PUBLIC_API ? void 0 : visibility;
1353
+ const buildSymbolId = (moduleId, symbolName, declaration) => {
1354
+ const names = [symbolName];
1355
+ let current = declaration.parent;
1356
+ while (current !== void 0) {
1357
+ const name = current.name?.getText?.();
1358
+ if (name !== void 0 && name.length > 0) names.push(name);
1359
+ current = current.parent;
1360
+ }
1361
+ return `${moduleId}#${names.toReversed().join(".")}${declaration.parameters === void 0 ? "" : "()"}`;
1362
+ };
1363
+ const isPublicExportName = (node) => "exported" in node.parent && node.parent.exported === node;
1364
+ const getImportedName = (specifier) => {
1365
+ if ("imported" in specifier) return "name" in specifier.imported ? specifier.imported.name : void 0;
1366
+ return specifier.type === "ImportDefaultSpecifier" ? "default" : void 0;
1367
+ };
1368
+ /**
1369
+ * @see https://github.com/brnshkr/config/blob/master/docs/js/eslint/rules/internal-usage.md
1370
+ */
1371
+ const internalUsageRule = {
1372
+ meta: {
1373
+ type: "problem",
1374
+ docs: {
1375
+ description: "Forbid usage of an `@internal` symbol from outside the namespace it is internal to.",
1376
+ url: "https://github.com/brnshkr/config/blob/master/docs/js/eslint/rules/internal-usage.md"
1377
+ },
1378
+ schema: [{
1379
+ type: "object",
1380
+ additionalProperties: false,
1381
+ properties: {
1382
+ ...objectFromEntries(OPTION_NAMES.map((optionName) => [optionName, {
1383
+ type: "array",
1384
+ tsType: "(string | RegExp | Record<string, (string | RegExp)[]>)[]"
1385
+ }])),
1386
+ tsConfigPath: { type: "string" }
1387
+ }
1388
+ }],
1389
+ messages: {
1390
+ [MESSAGE_ID_UNEXPECTED_INTERNAL_USAGE]: "`{{ symbol }}` is internal and must not be used from `{{ caller }}`.",
1391
+ [MESSAGE_ID_UNEXPECTED_TARGETED_INTERNAL_USAGE]: "`{{ symbol }}` is internal to `{{ target }}` and must not be used from `{{ caller }}`."
1392
+ }
1393
+ },
1394
+ create: (context) => {
1395
+ const options = context.options[0] ?? {};
1396
+ const allowLists = {
1397
+ allowedCallers: buildAllowList("allowedCallers", options.allowedCallers),
1398
+ allowedInternals: buildAllowList("allowedInternals", options.allowedInternals)
1399
+ };
1400
+ const sourceCode = context.sourceCode;
1401
+ const services = sourceCode.parserServices;
1402
+ const typeChecker = (services?.program)?.getTypeChecker();
1403
+ const aliasPaths = options.tsConfigPath === void 0 ? void 0 : loadTsConfigPaths(options.tsConfigPath);
1404
+ const resolveIdentity = (filePath) => buildModuleIdentity(filePath, aliasPaths);
1405
+ const callerIdentity = resolveIdentity(context.filename);
1406
+ if (callerIdentity === void 0) return {};
1407
+ const callerNamespace = callerIdentity.namespace;
1408
+ const callerNamespaces = callerIdentity.namespaces;
1409
+ const isReachable = (internal) => {
1410
+ const roots = internal.target === PLAIN_INTERNAL ? internal.namespaces : [internal.target];
1411
+ return callerNamespaces.some((namespace) => roots.some((root) => isInSubtree(namespace, root)));
1412
+ };
1413
+ const isUsageAllowed = (internal) => {
1414
+ const internalValues = [
1415
+ internal.target,
1416
+ ...internal.namespaces,
1417
+ internal.symbolId
1418
+ ];
1419
+ return isAllowedBy(internalValues, callerNamespaces, allowLists.allowedInternals) || isAllowedBy(callerNamespaces, internalValues, allowLists.allowedCallers) || isReachable(internal);
1420
+ };
1421
+ const reportUsage = (node, internal) => {
1422
+ if (isUsageAllowed(internal)) return;
1423
+ context.report({
1424
+ node,
1425
+ messageId: internal.target === PLAIN_INTERNAL ? MESSAGE_ID_UNEXPECTED_INTERNAL_USAGE : MESSAGE_ID_UNEXPECTED_TARGETED_INTERNAL_USAGE,
1426
+ data: {
1427
+ caller: callerNamespace,
1428
+ symbol: internal.symbolId,
1429
+ target: internal.target
1430
+ }
1431
+ });
1432
+ };
1433
+ const buildInternalSymbol = (identity, symbolId, visibility) => {
1434
+ const target = toInternalTarget(visibility);
1435
+ return target === void 0 ? void 0 : {
1436
+ namespace: identity.namespace,
1437
+ namespaces: identity.namespaces,
1438
+ symbolId,
1439
+ target
1440
+ };
1441
+ };
1442
+ const checkResolvedSymbol = (node, symbol, currentSourceFile) => {
1443
+ const declaration = symbol.declarations?.[0];
1444
+ if (declaration === void 0) return;
1445
+ const sourceFile = declaration.getSourceFile();
1446
+ if (sourceFile === currentSourceFile || !hasInternalMarker(sourceFile)) return;
1447
+ const target = toInternalTarget(findDeclaredVisibility(declaration) ?? findFileVisibility(sourceFile));
1448
+ const identity = target === void 0 ? void 0 : resolveIdentity(sourceFile.fileName);
1449
+ if (identity === void 0 || target === void 0) return;
1450
+ reportUsage(node, {
1451
+ namespace: identity.namespace,
1452
+ namespaces: identity.namespaces,
1453
+ symbolId: buildSymbolId(identity.moduleId, symbol.getName(), declaration),
1454
+ target
1455
+ });
1456
+ };
1457
+ const checkTypedIdentifier = (node, checker) => {
1458
+ const tsNode = services?.esTreeNodeToTSNodeMap.get(node);
1459
+ const symbol = tsNode === void 0 ? void 0 : checker.getSymbolAtLocation(tsNode);
1460
+ const declaration = symbol?.declarations?.[0];
1461
+ if (tsNode === void 0 || symbol === void 0 || declaration === void 0) return;
1462
+ checkResolvedSymbol(node, hasModuleSpecifierAbove(declaration) ? checker.getAliasedSymbol(symbol) : symbol, tsNode.getSourceFile());
1463
+ };
1464
+ const checkTypedModule = (node, checker) => {
1465
+ const tsNode = services?.esTreeNodeToTSNodeMap.get(node);
1466
+ const sourceFile = (tsNode === void 0 ? void 0 : checker.getSymbolAtLocation(tsNode))?.declarations?.[0];
1467
+ if (sourceFile === void 0 || !hasInternalMarker(sourceFile)) return;
1468
+ const identity = resolveIdentity(sourceFile.fileName);
1469
+ const internal = identity === void 0 ? void 0 : buildInternalSymbol(identity, identity.moduleId, findFileVisibility(sourceFile));
1470
+ if (internal !== void 0) reportUsage(node, internal);
1471
+ };
1472
+ const resolveScannedSymbol = (specifier, importedName) => {
1473
+ const modulePath = resolveRelativeModulePath(context.filename, specifier);
1474
+ const identity = modulePath === void 0 ? void 0 : resolveIdentity(modulePath);
1475
+ if (modulePath === void 0 || identity === void 0) return;
1476
+ const moduleVisibility = loadModuleVisibility(modulePath);
1477
+ return buildInternalSymbol(identity, importedName === void 0 ? identity.moduleId : `${identity.moduleId}#${importedName}${moduleVisibility.callableExportNames.has(importedName) ? "()" : ""}`, (importedName === void 0 ? void 0 : moduleVisibility.visibilityByExportName.get(importedName)) ?? moduleVisibility.fileVisibility);
1478
+ };
1479
+ const checkScannedImport = (node) => {
1480
+ for (const specifier of node.specifiers) {
1481
+ const internal = resolveScannedSymbol(node.source.value, getImportedName(specifier));
1482
+ if (internal === void 0) continue;
1483
+ const references = sourceCode.getDeclaredVariables(node).filter((variable) => variable.name === specifier.local.name).flatMap((variable) => variable.references);
1484
+ for (const reference of references) reportUsage(reference.identifier, internal);
1485
+ }
1486
+ };
1487
+ const checkScannedReexport = (node) => {
1488
+ for (const specifier of node.specifiers) {
1489
+ const localName = "name" in specifier.local ? specifier.local.name : void 0;
1490
+ const internal = node.source ? resolveScannedSymbol(node.source.value, localName) : void 0;
1491
+ if (internal !== void 0) reportUsage(specifier, internal);
1492
+ }
1493
+ };
1494
+ if (services === void 0 || typeChecker === void 0) return {
1495
+ ExportAllDeclaration: (node) => {
1496
+ const internal = resolveScannedSymbol(node.source.value);
1497
+ if (internal !== void 0) reportUsage(node, internal);
1498
+ },
1499
+ ExportNamedDeclaration: (node) => {
1500
+ checkScannedReexport(node);
1501
+ },
1502
+ ImportDeclaration: (node) => {
1503
+ checkScannedImport(node);
1504
+ },
1505
+ ImportExpression: (node) => {
1506
+ const specifier = "value" in node.source && typeof node.source.value === "string" ? node.source.value : void 0;
1507
+ const internal = specifier === void 0 ? void 0 : resolveScannedSymbol(specifier);
1508
+ if (internal !== void 0) reportUsage(node, internal);
1509
+ }
1510
+ };
1511
+ return {
1512
+ ExportAllDeclaration: (node) => {
1513
+ checkTypedModule(node.source, typeChecker);
1514
+ },
1515
+ Identifier: (node) => {
1516
+ if (!IMPORT_SPECIFIER_TYPES.has(node.parent.type) && !isPublicExportName(node)) checkTypedIdentifier(node, typeChecker);
1517
+ },
1518
+ ImportExpression: (node) => {
1519
+ checkTypedModule(node.source, typeChecker);
1520
+ }
1521
+ };
1522
+ }
1523
+ };
1524
+ //#endregion
1525
+ //#region ../src/js/eslint/configs/builtin/public-api-documentation.ts
1526
+ /**
1527
+ * @internal @brnshkr/config/eslint
1528
+ */
1529
+ const MESSAGE_ID_MISSING_DESCRIPTION = "missingDescription";
1530
+ const MESSAGE_ID_MISSING_PARAM = "missingParam";
1531
+ const MESSAGE_ID_MISSING_RETURNS = "missingReturns";
1532
+ const MESSAGE_ID_MISSING_EXAMPLE = "missingExample";
1533
+ const KIND_METHOD = "Method";
1534
+ const KIND_CONSTRUCTOR = "Constructor";
1535
+ const readCommentText = (declaration) => {
1536
+ const [comment] = (declaration?.jsDoc ?? []).toReversed();
1537
+ return comment?.getText();
1538
+ };
1539
+ const hasInheritDocTag = (comment) => /\*\s+@inheritdoc\b/iv.test(comment ?? "");
1540
+ const isDocumentedByAncestor = (services, node, memberName) => {
1541
+ const program = services?.program;
1542
+ const declaration = services?.esTreeNodeToTSNodeMap.get(node);
1543
+ if (!program || declaration?.name === void 0) return false;
1544
+ const checker = program.getTypeChecker();
1545
+ return (declaration.heritageClauses ?? []).some((clause) => clause.types.some((typeNode) => hasDescription(readCommentText(checker.getTypeAtLocation(typeNode).getProperty(memberName)?.declarations?.[0]))));
1546
+ };
1547
+ const reportMissingDescription = (ruleContext, anchor, kind, name) => {
1548
+ ruleContext.context.report({
1549
+ node: anchor,
1550
+ messageId: MESSAGE_ID_MISSING_DESCRIPTION,
1551
+ data: {
1552
+ kind,
1553
+ name
1554
+ }
1555
+ });
1556
+ };
1557
+ const checkFunctionParameters = (ruleContext, functionLike, text) => {
1558
+ for (const parameter of functionLike.parameters) {
1559
+ const parameterName = getParameterName(parameter);
1560
+ if (parameterName !== void 0 && !hasParameterProse(text, parameterName)) ruleContext.context.report({
1561
+ node: functionLike.anchor,
1562
+ messageId: MESSAGE_ID_MISSING_PARAM,
1563
+ data: {
1564
+ kind: functionLike.kind,
1565
+ name: functionLike.name,
1566
+ parameterName
1567
+ }
1568
+ });
1569
+ }
1570
+ };
1571
+ const checkFunctionReturns = (ruleContext, functionLike, text, isFluent) => {
1572
+ if (isFluent || isVoidLikeReturn(functionLike.returnType) || hasReturnsWithProse(text)) return;
1573
+ ruleContext.context.report({
1574
+ node: functionLike.anchor,
1575
+ messageId: MESSAGE_ID_MISSING_RETURNS,
1576
+ data: {
1577
+ kind: functionLike.kind,
1578
+ name: functionLike.name
1579
+ }
1580
+ });
1581
+ };
1582
+ const checkFunctionExample = (ruleContext, functionLike, text, isFluent) => {
1583
+ if (isFluent || functionLike.parameters.length === 0 || functionLike.isAbstract === true || hasTag(text, "example")) return;
1584
+ ruleContext.context.report({
1585
+ node: functionLike.anchor,
1586
+ messageId: MESSAGE_ID_MISSING_EXAMPLE,
1587
+ data: {
1588
+ kind: functionLike.kind,
1589
+ name: functionLike.name
1590
+ }
1591
+ });
1592
+ };
1593
+ const checkFunctionLike = (ruleContext, functionLike) => {
1594
+ const text = functionLike.comment ?? "";
1595
+ if (functionLike.kind !== KIND_CONSTRUCTOR && !hasDescription(functionLike.comment)) reportMissingDescription(ruleContext, functionLike.anchor, functionLike.kind, functionLike.name);
1596
+ checkFunctionParameters(ruleContext, functionLike, text);
1597
+ const isFluent = isFluentReturn(functionLike.returnType);
1598
+ checkFunctionReturns(ruleContext, functionLike, text, isFluent);
1599
+ checkFunctionExample(ruleContext, functionLike, text, isFluent);
1600
+ };
1601
+ const checkClassMembers = (ruleContext, node) => {
1602
+ for (const member of node.body.body) {
1603
+ if (!isClassMethodLike(member) || !isAccessibleMethod(member)) continue;
1604
+ const methodComment = extractBlockComment(ruleContext.sourceCode.getCommentsBefore(member), member);
1605
+ if (getEffectiveVisibilityTag(methodComment, ruleContext.fileComment) === "internal" || hasInheritDocTag(methodComment) || isDocumentedByAncestor(ruleContext.services, node, getNamedKeyText(member.key))) continue;
1606
+ checkFunctionLike(ruleContext, {
1607
+ anchor: member,
1608
+ kind: member.kind === "constructor" ? KIND_CONSTRUCTOR : KIND_METHOD,
1609
+ name: getNamedKeyText(member.key),
1610
+ comment: methodComment,
1611
+ parameters: member.value.params,
1612
+ returnType: member.value.returnType,
1613
+ isAbstract: isAbstractMethod(member)
1614
+ });
1615
+ }
1616
+ };
1617
+ const checkInterfaceMembers = (ruleContext, node) => {
1618
+ for (const member of node.body.body) {
1619
+ if (member.type !== "TSMethodSignature") continue;
1620
+ const methodComment = extractBlockComment(ruleContext.sourceCode.getCommentsBefore(member), member);
1621
+ if (getEffectiveVisibilityTag(methodComment, ruleContext.fileComment) === "internal" || hasInheritDocTag(methodComment) || isDocumentedByAncestor(ruleContext.services, node, getNamedKeyText(member.key))) continue;
1622
+ checkFunctionLike(ruleContext, {
1623
+ anchor: member,
1624
+ kind: KIND_METHOD,
1625
+ name: getNamedKeyText(member.key),
1626
+ comment: methodComment,
1627
+ parameters: member.params,
1628
+ returnType: member.returnType,
1629
+ isAbstract: true
1630
+ });
1631
+ }
1632
+ };
1633
+ const checkSymbol = (ruleContext, symbol) => {
1634
+ if (symbol.kind === "Function") {
1635
+ const shape = resolveFunctionShape(symbol.declaration);
1636
+ if (shape === void 0) return;
1637
+ checkFunctionLike(ruleContext, {
1638
+ anchor: symbol.anchor,
1639
+ kind: symbol.kind,
1640
+ name: symbol.name,
1641
+ comment: symbol.comment,
1642
+ parameters: shape.parameters,
1643
+ returnType: shape.returnType
1644
+ });
1645
+ return;
1646
+ }
1647
+ if (!hasDescription(symbol.comment)) reportMissingDescription(ruleContext, symbol.anchor, symbol.kind, symbol.name);
1648
+ if (symbol.kind === "Class") checkClassMembers(ruleContext, symbol.declaration);
1649
+ else if (symbol.kind === "Interface") checkInterfaceMembers(ruleContext, symbol.declaration);
1650
+ };
1651
+ /**
1652
+ * @see https://github.com/brnshkr/config/blob/master/docs/js/eslint/rules/public-api-documentation.md
1653
+ */
1654
+ const publicApiDocumentationRule = {
1655
+ meta: {
1656
+ type: "suggestion",
1657
+ docs: {
1658
+ description: "Hold every `@api` symbol in a public-API source file to a consistent docblock standard.",
1659
+ url: "https://github.com/brnshkr/config/blob/master/docs/js/eslint/rules/public-api-documentation.md"
1660
+ },
1661
+ schema: [{
1662
+ type: "object",
1663
+ additionalProperties: false,
1664
+ properties: {
1665
+ packageJsonPath: { type: "string" },
1666
+ distRoot: { type: "string" },
1667
+ srcRoot: { type: "string" },
1668
+ srcExtensions: {
1669
+ type: "array",
1670
+ items: { type: "string" }
1671
+ }
1672
+ }
1673
+ }],
1674
+ messages: {
1675
+ [MESSAGE_ID_MISSING_DESCRIPTION]: "{{ kind }} `{{ name }}` is `@api` and must carry a description before the first JSDoc tag.",
1676
+ [MESSAGE_ID_MISSING_PARAM]: "{{ kind }} `{{ name }}` is `@api`; parameter `{{ parameterName }}` must have a `@param` tag with a description.",
1677
+ [MESSAGE_ID_MISSING_RETURNS]: "{{ kind }} `{{ name }}` is `@api` and returns a non-void type; a `@returns` tag with a description is required.",
1678
+ [MESSAGE_ID_MISSING_EXAMPLE]: "{{ kind }} `{{ name }}` is `@api` and accepts parameters; an `@example` tag is required."
1679
+ }
1680
+ },
1681
+ create: (context) => {
1682
+ const options = context.options[0] ?? {};
1683
+ if (!isPublicApiFile(options, context.cwd, context.filename)) return {};
1684
+ const sourceCode = context.sourceCode;
1685
+ const fileComment = getFileLevelBlockComment(sourceCode);
1686
+ const ruleContext = {
1687
+ context,
1688
+ sourceCode,
1689
+ fileComment,
1690
+ services: sourceCode.parserServices
1691
+ };
1692
+ return buildExportVisitors(sourceCode, (symbol) => {
1693
+ if (getEffectiveVisibilityTag(symbol.comment, fileComment) !== "api") return;
1694
+ checkSymbol(ruleContext, symbol);
1695
+ });
1696
+ }
1697
+ };
1698
+ //#endregion
194
1699
  //#region ../src/js/eslint/configs/builtin/require-import-alias.ts
195
- const MESSAGE_ID_PREFER_ALIAS = "preferAlias";
1700
+ /**
1701
+ * @internal @brnshkr/config/eslint
1702
+ */
1703
+ const MESSAGE_ID_EXPECTED_ALIAS = "expectedAlias";
196
1704
  const MESSAGE_ID_MISSING_ALIAS = "missingAlias";
197
1705
  const WILDCARD_SUFFIX = "/*";
198
- const WILDCARD_SUFFIX_LENGTH = 2;
199
1706
  const RELATIVE_SPECIFIER_PREFIXES = ["./", "../"];
200
1707
  const resolveAliases = (options) => options.aliases ?? loadTsConfigPaths(options.tsConfigPath ?? resolveTsConfigPath()) ?? {};
201
- const toPosix = (value) => value.replaceAll("\\", "/");
202
1708
  const buildAliasMappings = (aliases) => objectEntries(aliases).filter(([pattern]) => pattern.endsWith(WILDCARD_SUFFIX)).flatMap(([pattern, targets]) => {
203
- const prefix = pattern.slice(0, -WILDCARD_SUFFIX_LENGTH);
1709
+ const prefix = pattern.slice(0, -2);
204
1710
  return targets.filter((target) => target.endsWith(WILDCARD_SUFFIX)).map((target) => ({
205
1711
  prefix,
206
- baseDirectory: toPosix(target).slice(0, -WILDCARD_SUFFIX_LENGTH)
1712
+ baseDirectory: toPosix$1(target).slice(0, -2)
207
1713
  }));
208
- }).toSorted((left, right) => right.baseDirectory.length - left.baseDirectory.length);
209
- const findAliasReplacement = (mappings, absolutePath) => {
1714
+ });
1715
+ const buildAliasedSpecifier = ({ prefix, baseDirectory }, absolutePath) => {
1716
+ if (absolutePath === baseDirectory) return prefix;
1717
+ return absolutePath.startsWith(`${baseDirectory}/`) ? `${prefix}/${absolutePath.slice(baseDirectory.length + 1)}` : void 0;
1718
+ };
1719
+ const compareSpecifiers = (left, right) => {
1720
+ const segmentDifference = left.split("/").length - right.split("/").length;
1721
+ if (segmentDifference !== 0) return segmentDifference;
1722
+ const lengthDifference = left.length - right.length;
1723
+ return lengthDifference === 0 ? left.localeCompare(right) : lengthDifference;
1724
+ };
1725
+ const findAliasReplacement = (mappings, absolutePath) => mappings.map((mapping) => buildAliasedSpecifier(mapping, absolutePath)).filter((specifier) => specifier !== void 0).toSorted(compareSpecifiers)[0];
1726
+ const resolveAliasedPath = (mappings, source) => {
210
1727
  for (const { prefix, baseDirectory } of mappings) {
211
- if (absolutePath === baseDirectory) return prefix;
212
- if (absolutePath.startsWith(`${baseDirectory}/`)) return `${prefix}/${absolutePath.slice(baseDirectory.length + 1)}`;
1728
+ if (source === prefix) return baseDirectory;
1729
+ if (source.startsWith(`${prefix}/`)) return `${baseDirectory}/${source.slice(prefix.length + 1)}`;
213
1730
  }
214
1731
  };
215
1732
  const isRelativeSpecifier = (source) => RELATIVE_SPECIFIER_PREFIXES.some((prefix) => source.startsWith(prefix));
216
- const isAlreadyAliased = (source, mappings) => mappings.some(({ prefix }) => source === prefix || source.startsWith(`${prefix}/`));
217
- const isFileIgnored = (filename, patterns) => patterns.some((pattern) => path.matchesGlob(toPosix(filename), pattern) || path.matchesGlob(toPosix(path.relative(process.cwd(), filename)), pattern));
1733
+ const isFileIgnored = (filename, patterns) => {
1734
+ const absoluteFilename = toPosix$1(filename);
1735
+ const relativeFilename = toPosix$1(path.relative(process.cwd(), filename));
1736
+ return patterns.some((pattern) => path.matchesGlob(absoluteFilename, pattern) || path.matchesGlob(relativeFilename, pattern));
1737
+ };
218
1738
  const getQuote = (sourceNode) => "raw" in sourceNode && typeof sourceNode.raw === "string" && sourceNode.raw.startsWith("\"") ? "\"" : "'";
1739
+ /**
1740
+ * @see https://github.com/brnshkr/config/blob/master/docs/js/eslint/rules/require-import-alias.md
1741
+ */
219
1742
  const requireImportAliasRule = {
220
1743
  meta: {
221
1744
  type: "suggestion",
222
1745
  fixable: "code",
223
- docs: { description: "Require imports to use TypeScript path aliases when the target file is reachable through a configured alias." },
1746
+ docs: {
1747
+ description: "Require imports to use the TypeScript path alias with the fewest path segments when the target file is reachable through one.",
1748
+ url: "https://github.com/brnshkr/config/blob/master/docs/js/eslint/rules/require-import-alias.md"
1749
+ },
224
1750
  schema: [{
225
1751
  type: "object",
226
1752
  additionalProperties: false,
@@ -240,7 +1766,7 @@ const requireImportAliasRule = {
240
1766
  }
241
1767
  }],
242
1768
  messages: {
243
- [MESSAGE_ID_PREFER_ALIAS]: "Import path '{{ source }}' must use the configured alias '{{ alias }}'.",
1769
+ [MESSAGE_ID_EXPECTED_ALIAS]: "Import path '{{ source }}' must use the configured alias '{{ alias }}'.",
244
1770
  [MESSAGE_ID_MISSING_ALIAS]: "Import path '{{ source }}' resolves outside any configured TypeScript path alias. Add an alias for this location, remove all other aliases, or disable this rule."
245
1771
  }
246
1772
  },
@@ -249,12 +1775,14 @@ const requireImportAliasRule = {
249
1775
  const ignoredPaths = options.ignoredPaths ?? [];
250
1776
  const mappings = buildAliasMappings(resolveAliases(options));
251
1777
  if (mappings.length === 0 || isFileIgnored(context.filename, ignoredPaths)) return {};
252
- const fileDirectory = toPosix(path.dirname(context.filename));
1778
+ const fileDirectory = toPosix$1(path.dirname(context.filename));
253
1779
  const checkSource = (sourceNode) => {
254
1780
  if (sourceNode?.type !== "Literal" || typeof sourceNode.value !== "string") return;
255
1781
  const source = sourceNode.value;
256
- if (!isRelativeSpecifier(source) || isAlreadyAliased(source, mappings)) return;
257
- const replacement = findAliasReplacement(mappings, path.posix.normalize(`${fileDirectory}/${source}`));
1782
+ const absolutePath = isRelativeSpecifier(source) ? path.posix.normalize(`${fileDirectory}/${source}`) : resolveAliasedPath(mappings, source);
1783
+ if (absolutePath === void 0) return;
1784
+ const replacement = findAliasReplacement(mappings, absolutePath);
1785
+ if (replacement === source) return;
258
1786
  if (replacement === void 0) {
259
1787
  context.report({
260
1788
  node: sourceNode,
@@ -265,7 +1793,7 @@ const requireImportAliasRule = {
265
1793
  }
266
1794
  context.report({
267
1795
  node: sourceNode,
268
- messageId: MESSAGE_ID_PREFER_ALIAS,
1796
+ messageId: MESSAGE_ID_EXPECTED_ALIAS,
269
1797
  data: {
270
1798
  source,
271
1799
  alias: replacement
@@ -293,7 +1821,7 @@ const requireImportAliasRule = {
293
1821
  //#region ../src/js/eslint/configs/builtin/require-import-attributes.ts
294
1822
  const MESSAGE_ID_MISSING_WITH_KEYWORD = "missingWithKeyword";
295
1823
  const MESSAGE_ID_MISSING_TYPE_PROPERTY = "missingTypeProperty";
296
- const MESSAGE_ID_WRONG_TYPE_VALUE = "wrongTypeValue";
1824
+ const MESSAGE_ID_UNEXPECTED_TYPE_VALUE = "unexpectedTypeValue";
297
1825
  const FILE_TYPE_MAP = {
298
1826
  ".json": "json",
299
1827
  ".css": "css",
@@ -305,78 +1833,330 @@ const FILE_TYPE_MAP = {
305
1833
  ".oct": "bytes",
306
1834
  ".wasm": "webassembly"
307
1835
  };
308
- //#endregion
309
- //#region ../src/js/eslint/configs/builtin/index.ts
310
- const RULE_DEFINITIONS = {
311
- "require-import-attributes": {
312
- meta: {
313
- type: "problem",
314
- docs: { description: "Require non-JavaScript imports (e.g. .json and .css) to include import attributes." },
315
- messages: {
316
- [MESSAGE_ID_MISSING_WITH_KEYWORD]: "Non-JavaScript import ('{{ extension }}') requires an import attributes object with the 'type' property set to '{{ expectedValue }}'.",
317
- [MESSAGE_ID_MISSING_TYPE_PROPERTY]: "Import attributes for non-JavaScript imports must include the 'type' property.",
318
- [MESSAGE_ID_WRONG_TYPE_VALUE]: "Import attribute 'type' for '{{ file }}' must be '{{ expectedValue }}'."
319
- }
1836
+ /**
1837
+ * @see https://github.com/brnshkr/config/blob/master/docs/js/eslint/rules/require-import-attributes.md
1838
+ */
1839
+ const requireImportAttributesRule = {
1840
+ meta: {
1841
+ type: "problem",
1842
+ docs: {
1843
+ description: "Require non-JavaScript imports (e.g. .json and .css) to include import attributes.",
1844
+ url: "https://github.com/brnshkr/config/blob/master/docs/js/eslint/rules/require-import-attributes.md"
320
1845
  },
321
- create: (context) => ({ ImportDeclaration: (node) => {
322
- const sourceValue = node.source.value;
323
- const extensionIndex = sourceValue.lastIndexOf(".");
324
- if (extensionIndex === -1) return;
325
- const extension = sourceValue.slice(extensionIndex).toLowerCase();
326
- const expectedValue = FILE_TYPE_MAP[extension];
327
- if (expectedValue === void 0) return;
328
- const { attributes } = node;
329
- if (attributes.length === 0) {
330
- context.report({
331
- node,
332
- messageId: MESSAGE_ID_MISSING_WITH_KEYWORD,
333
- data: {
334
- extension,
335
- expectedValue
336
- }
337
- });
338
- return;
339
- }
340
- const typeProperty = attributes.find((attribute) => attribute.key.type === "Identifier" && "name" in attribute.key && attribute.key.name === "type" || attribute.key.type === "Literal" && "value" in attribute.key && attribute.key.value === "type");
341
- if (!typeProperty) {
342
- context.report({
343
- node,
344
- messageId: MESSAGE_ID_MISSING_TYPE_PROPERTY
345
- });
346
- return;
347
- }
348
- if (typeProperty.value.value !== expectedValue) context.report({
1846
+ messages: {
1847
+ [MESSAGE_ID_MISSING_WITH_KEYWORD]: "Non-JavaScript import ('{{ extension }}') requires an import attributes object with the 'type' property set to '{{ expectedValue }}'.",
1848
+ [MESSAGE_ID_MISSING_TYPE_PROPERTY]: "Import attributes for non-JavaScript imports must include the 'type' property.",
1849
+ [MESSAGE_ID_UNEXPECTED_TYPE_VALUE]: "Import attribute 'type' for '{{ file }}' must be '{{ expectedValue }}'."
1850
+ }
1851
+ },
1852
+ create: (context) => ({ ImportDeclaration: (node) => {
1853
+ const sourceValue = node.source.value;
1854
+ const extensionIndex = sourceValue.lastIndexOf(".");
1855
+ if (extensionIndex === -1) return;
1856
+ const extension = sourceValue.slice(extensionIndex).toLowerCase();
1857
+ const expectedValue = FILE_TYPE_MAP[extension];
1858
+ if (expectedValue === void 0) return;
1859
+ const { attributes } = node;
1860
+ if (attributes.length === 0) {
1861
+ context.report({
349
1862
  node,
350
- messageId: MESSAGE_ID_WRONG_TYPE_VALUE,
1863
+ messageId: MESSAGE_ID_MISSING_WITH_KEYWORD,
351
1864
  data: {
352
- expectedValue,
353
- file: sourceValue
1865
+ extension,
1866
+ expectedValue
354
1867
  }
355
1868
  });
356
- } })
1869
+ return;
1870
+ }
1871
+ const typeProperty = attributes.find((attribute) => attribute.key.type === "Identifier" && "name" in attribute.key && attribute.key.name === "type" || attribute.key.type === "Literal" && "value" in attribute.key && attribute.key.value === "type");
1872
+ if (!typeProperty) {
1873
+ context.report({
1874
+ node,
1875
+ messageId: MESSAGE_ID_MISSING_TYPE_PROPERTY
1876
+ });
1877
+ return;
1878
+ }
1879
+ if (typeProperty.value.value !== expectedValue) context.report({
1880
+ node,
1881
+ messageId: MESSAGE_ID_UNEXPECTED_TYPE_VALUE,
1882
+ data: {
1883
+ expectedValue,
1884
+ file: sourceValue
1885
+ }
1886
+ });
1887
+ } })
1888
+ };
1889
+ //#endregion
1890
+ //#region ../src/js/eslint/configs/builtin/resolvable-doc-reference.ts
1891
+ /**
1892
+ * @internal @brnshkr/config/eslint
1893
+ */
1894
+ const MESSAGE_ID_MISSING_REFERENCE = "missingReference";
1895
+ const createReferencePattern = () => /(?:\{@|\*\s+@)(?:link(?:code|plain)?|see)\s+(?<target>[^\s\}]+)/gv;
1896
+ const startsUpperCase = (value) => /\p{Uppercase_Letter}/v.test(value.slice(0, 1));
1897
+ const isCheckableTarget = (target) => !target.includes("/") && !target.includes("~") && !target.includes(":");
1898
+ const startsContinuation = (trailingText) => trailingText.startsWith("(") || trailingText.startsWith(":");
1899
+ const getHeadIdentifier = (target) => target.split(/[#.]/v, 1)[0] ?? "";
1900
+ const getLeftmostNameNode = (nameNode) => {
1901
+ let leftmost = nameNode;
1902
+ while (leftmost.left !== void 0) leftmost = leftmost.left;
1903
+ return leftmost;
1904
+ };
1905
+ const collectInlineReferences = (docComment) => (Array.isArray(docComment.comment) ? docComment.comment : []).filter((part) => part.name !== void 0).map((part) => ({
1906
+ nameNode: part.name,
1907
+ isInlineTag: true,
1908
+ trailingText: part.text ?? ""
1909
+ }));
1910
+ const collectBlockReferences = (docComment) => (docComment.tags ?? []).filter((tag) => tag.name?.name !== void 0).map((tag) => ({
1911
+ nameNode: tag.name?.name,
1912
+ isInlineTag: false,
1913
+ trailingText: ""
1914
+ }));
1915
+ const collectNameReferences = (node) => (node.jsDoc ?? []).flatMap((docComment) => [...collectInlineReferences(docComment), ...collectBlockReferences(docComment)]);
1916
+ const resolveNameNodeType = (checker, nameNode) => {
1917
+ if (nameNode.left === void 0 || nameNode.right === void 0) {
1918
+ const symbol = checker.getSymbolAtLocation(nameNode);
1919
+ return symbol === void 0 ? void 0 : checker.getDeclaredTypeOfSymbol(symbol);
1920
+ }
1921
+ const property = resolveNameNodeType(checker, nameNode.left)?.getProperty(nameNode.right.getText());
1922
+ return property === void 0 ? void 0 : checker.getTypeOfSymbolAtLocation(property, nameNode);
1923
+ };
1924
+ const isMemberNameResolved = (checker, nameNode) => resolveNameNodeType(checker, nameNode) !== void 0;
1925
+ const collectDeclaredNames = (sourceCode) => {
1926
+ const declaredNames = /* @__PURE__ */ new Set();
1927
+ const scopes = sourceCode.scopeManager?.scopes ?? [];
1928
+ for (const scope of scopes) for (const variable of scope.variables) declaredNames.add(variable.name);
1929
+ return declaredNames;
1930
+ };
1931
+ const collectLexicalReferences = (comment) => {
1932
+ const references = [];
1933
+ const lines = `/*${comment.value}*/`.split("\n");
1934
+ for (const [offset, line] of lines.entries()) for (const match of line.matchAll(createReferencePattern())) {
1935
+ const rawTarget = match.groups?.["target"] ?? "";
1936
+ const target = rawTarget.split("|", 1)[0] ?? "";
1937
+ const targetColumn = match.index + (match[0].length - rawTarget.length);
1938
+ references.push({
1939
+ target,
1940
+ headIdentifier: getHeadIdentifier(target),
1941
+ loc: {
1942
+ line: comment.loc.start.line + offset,
1943
+ column: offset === 0 ? comment.loc.start.column + targetColumn : targetColumn
1944
+ }
1945
+ });
1946
+ }
1947
+ return references;
1948
+ };
1949
+ /**
1950
+ * @see https://github.com/brnshkr/config/blob/master/docs/js/eslint/rules/resolvable-doc-reference.md
1951
+ */
1952
+ const resolvableDocReferenceRule = {
1953
+ meta: {
1954
+ type: "suggestion",
1955
+ docs: {
1956
+ description: "Require every `@see` and `@link` target in a JSDoc comment to name a symbol that exists.",
1957
+ url: "https://github.com/brnshkr/config/blob/master/docs/js/eslint/rules/resolvable-doc-reference.md"
1958
+ },
1959
+ messages: { [MESSAGE_ID_MISSING_REFERENCE]: "Reference `{{ name }}` does not exist." }
357
1960
  },
358
- "require-import-alias": requireImportAliasRule
1961
+ create: (context) => {
1962
+ const sourceCode = context.sourceCode;
1963
+ const services = sourceCode.parserServices;
1964
+ const typeChecker = (services?.program)?.getTypeChecker();
1965
+ const findings = /* @__PURE__ */ new Map();
1966
+ const addFinding = (target, loc) => {
1967
+ const key = `${String(loc.line)}:${String(loc.column)}:${target}`;
1968
+ findings.set(key, findings.get(key) ?? {
1969
+ target,
1970
+ loc
1971
+ });
1972
+ };
1973
+ const checkNameNode = (checker, reference) => {
1974
+ const { isInlineTag, nameNode, trailingText } = reference;
1975
+ const name = nameNode.getText();
1976
+ const target = startsContinuation(trailingText) ? `${name}${trailingText}` : name;
1977
+ if (!isCheckableTarget(target) || checker.getSymbolAtLocation(nameNode) !== void 0) return;
1978
+ if (nameNode.right !== void 0 && isMemberNameResolved(checker, nameNode)) return;
1979
+ const isHeadResolved = checker.getSymbolAtLocation(getLeftmostNameNode(nameNode)) !== void 0;
1980
+ if (!isInlineTag && !isHeadResolved && !startsUpperCase(target)) return;
1981
+ const position = nameNode.getSourceFile().getLineAndCharacterOfPosition(nameNode.getStart());
1982
+ addFinding(target, {
1983
+ line: position.line + 1,
1984
+ column: position.character
1985
+ });
1986
+ };
1987
+ return {
1988
+ "*": (node) => {
1989
+ if (services === void 0 || typeChecker === void 0) return;
1990
+ const tsNode = services.esTreeNodeToTSNodeMap.get(node);
1991
+ if (tsNode === void 0) return;
1992
+ for (const reference of collectNameReferences(tsNode)) checkNameNode(typeChecker, reference);
1993
+ },
1994
+ "Program:exit": () => {
1995
+ const declaredNames = collectDeclaredNames(sourceCode);
1996
+ for (const comment of sourceCode.getAllComments()) {
1997
+ if (!isBlockComment(comment)) continue;
1998
+ for (const reference of collectLexicalReferences(comment)) if (isCheckableTarget(reference.target) && startsUpperCase(reference.headIdentifier) && !declaredNames.has(reference.headIdentifier)) addFinding(reference.target, reference.loc);
1999
+ }
2000
+ for (const finding of findings.values()) context.report({
2001
+ loc: finding.loc,
2002
+ messageId: MESSAGE_ID_MISSING_REFERENCE,
2003
+ data: { name: finding.target }
2004
+ });
2005
+ }
2006
+ };
2007
+ }
2008
+ };
2009
+ //#endregion
2010
+ //#region ../src/js/eslint/configs/builtin/type-assertion-style.ts
2011
+ const MESSAGE_ID_EXPECTED_PARENTHESES = "expectedParentheses";
2012
+ const MESSAGE_ID_UNEXPECTED_PARENTHESES = "unexpectedParentheses";
2013
+ const MESSAGE_ID_EXPECTED_SPACE = "expectedSpace";
2014
+ const MESSAGE_ID_UNEXPECTED_SPACE = "unexpectedSpace";
2015
+ const ASSERTION_SELECTOR = "TSTypeAssertion, TSAsExpression";
2016
+ const OPERAND_KEYWORDS = /* @__PURE__ */ new Set([
2017
+ "async",
2018
+ "await",
2019
+ "class",
2020
+ "delete",
2021
+ "function",
2022
+ "new",
2023
+ "super",
2024
+ "this",
2025
+ "typeof",
2026
+ "void"
2027
+ ]);
2028
+ const REQUIREMENT_SCHEMA = {
2029
+ type: "string",
2030
+ enum: ["always", "never"]
2031
+ };
2032
+ const isKeywordOperand = (sourceCode, operand) => {
2033
+ const [firstToken, secondToken] = sourceCode.getTokens(operand);
2034
+ if (firstToken === void 0 || !OPERAND_KEYWORDS.has(firstToken.value)) return false;
2035
+ return firstToken.value !== "new" || secondToken?.value !== ".";
2036
+ };
2037
+ const isParenthesizedOperand = (sourceCode, node) => sourceCode.getTokenBefore(node)?.value === "(" && sourceCode.getTokenAfter(node)?.value === ")";
2038
+ const resolveOperandRange = (sourceCode, assertion, isAngleBracket) => {
2039
+ const [start, end] = isAngleBracket ? [sourceCode.getTokenAfter(assertion.typeAnnotation)?.range[1], assertion.range[1]] : [assertion.range[0], sourceCode.getTokenBefore(assertion.typeAnnotation)?.range[0]];
2040
+ return start === void 0 || end === void 0 ? void 0 : [start, end];
2041
+ };
2042
+ const hasCommentInAnyRange = (sourceCode, ranges) => sourceCode.getAllComments().some(({ range }) => ranges.some(([start, end]) => range[0] < end && range[1] > start));
2043
+ const resolveGapRange = (sourceCode, assertion) => {
2044
+ const closingAngle = sourceCode.getTokenAfter(assertion.typeAnnotation) ?? void 0;
2045
+ const operandStart = closingAngle === void 0 ? void 0 : sourceCode.getTokenAfter(closingAngle) ?? void 0;
2046
+ const isUnparenthesizedKeyword = isKeywordOperand(sourceCode, assertion.expression) && !isParenthesizedOperand(sourceCode, assertion.expression);
2047
+ return closingAngle === void 0 || operandStart === void 0 || isUnparenthesizedKeyword ? void 0 : [closingAngle.range[1], operandStart.range[0]];
359
2048
  };
360
- const builtin = (typescriptOptions) => [{
361
- name: buildConfigName(MAIN_SCOPES[packageOrganizationUpper], SUB_SCOPES.SETUP),
362
- plugins: { [packageOrganization]: {
2049
+ //#endregion
2050
+ //#region ../src/js/eslint/configs/builtin/index.ts
2051
+ /**
2052
+ * @internal @brnshkr/config/eslint
2053
+ */
2054
+ const RULE_DEFINITIONS = {
2055
+ "api-or-internal-tag": apiOrInternalTagRule,
2056
+ "boolish-prefix": boolishPrefixRule,
2057
+ "interface-suffix": interfaceSuffixRule,
2058
+ "internal-usage": internalUsageRule,
2059
+ "public-api-documentation": publicApiDocumentationRule,
2060
+ "require-import-attributes": requireImportAttributesRule,
2061
+ "require-import-alias": requireImportAliasRule,
2062
+ "resolvable-doc-reference": resolvableDocReferenceRule,
2063
+ "type-assertion-style": {
363
2064
  meta: {
364
- name: packageOrganization,
365
- version
2065
+ type: "layout",
2066
+ fixable: "code",
2067
+ docs: {
2068
+ description: "Require a type assertion to parenthesize an operand that reads wider than it casts, and to carry no trailing space.",
2069
+ url: "https://github.com/brnshkr/config/blob/master/docs/js/eslint/rules/type-assertion-style.md"
2070
+ },
2071
+ schema: [{ oneOf: [REQUIREMENT_SCHEMA, {
2072
+ type: "object",
2073
+ additionalProperties: false,
2074
+ properties: {
2075
+ parentheses: REQUIREMENT_SCHEMA,
2076
+ spacing: REQUIREMENT_SCHEMA
2077
+ }
2078
+ }] }],
2079
+ messages: {
2080
+ [MESSAGE_ID_EXPECTED_PARENTHESES]: "The operand of a type assertion must be parenthesized.",
2081
+ [MESSAGE_ID_UNEXPECTED_PARENTHESES]: "The operand of a type assertion must not be parenthesized.",
2082
+ [MESSAGE_ID_EXPECTED_SPACE]: "A type assertion must be followed by a space.",
2083
+ [MESSAGE_ID_UNEXPECTED_SPACE]: "A type assertion must not be followed by a space."
2084
+ }
366
2085
  },
367
- rules: RULE_DEFINITIONS
368
- } }
369
- }, {
370
- name: buildConfigName(MAIN_SCOPES[packageOrganizationUpper], SUB_SCOPES.RULES),
371
- files: GLOB_SCRIPT_FILES,
372
- rules: {
373
- [`${packageOrganization}/require-import-alias`]: ["error", { tsConfigPath: resolveTsConfigPath(typeof typescriptOptions === "object" ? typescriptOptions : void 0) }],
374
- [`${packageOrganization}/require-import-attributes`]: "error"
2086
+ create: (context) => {
2087
+ const options = context.options[0];
2088
+ const parentheses = (typeof options === "string" ? options : options?.parentheses) ?? "always";
2089
+ const spacing = (typeof options === "string" ? void 0 : options?.spacing) ?? "never";
2090
+ const sourceCode = context.sourceCode;
2091
+ const assertionsWithParenthesesFix = /* @__PURE__ */ new Set();
2092
+ const gapText = spacing === "always" ? " " : "";
2093
+ return {
2094
+ [ASSERTION_SELECTOR]: (assertion) => {
2095
+ const node = assertion.expression;
2096
+ if (!isKeywordOperand(sourceCode, node)) return;
2097
+ const isParenthesized = isParenthesizedOperand(sourceCode, node);
2098
+ if (isParenthesized === (parentheses === "always")) return;
2099
+ const isAngleBracket = assertion.typeAnnotation.range[0] < node.range[0];
2100
+ const operandRange = resolveOperandRange(sourceCode, assertion, isAngleBracket);
2101
+ if (operandRange === void 0) return;
2102
+ if (isAngleBracket) assertionsWithParenthesesFix.add(assertion);
2103
+ const operandText = sourceCode.getText(node);
2104
+ const replacementText = isParenthesized ? operandText : `(${operandText})`;
2105
+ const erasedRanges = [[operandRange[0], node.range[0]], [node.range[1], operandRange[1]]];
2106
+ context.report({
2107
+ node,
2108
+ messageId: isParenthesized ? MESSAGE_ID_UNEXPECTED_PARENTHESES : MESSAGE_ID_EXPECTED_PARENTHESES,
2109
+ fix: (fixer) => hasCommentInAnyRange(sourceCode, erasedRanges) ? [] : [fixer.replaceTextRange(operandRange, isAngleBracket ? `${gapText}${replacementText}` : `${replacementText} `)]
2110
+ });
2111
+ },
2112
+ "TSTypeAssertion:exit": (node) => {
2113
+ const gapRange = resolveGapRange(sourceCode, node);
2114
+ if (gapRange === void 0 || assertionsWithParenthesesFix.has(node)) return;
2115
+ const isSpaced = gapRange[0] !== gapRange[1];
2116
+ if (isSpaced === (spacing === "always")) return;
2117
+ context.report({
2118
+ node,
2119
+ messageId: isSpaced ? MESSAGE_ID_UNEXPECTED_SPACE : MESSAGE_ID_EXPECTED_SPACE,
2120
+ fix: (fixer) => hasCommentInAnyRange(sourceCode, [gapRange]) ? [] : [fixer.replaceTextRange(gapRange, gapText)]
2121
+ });
2122
+ }
2123
+ };
2124
+ }
375
2125
  }
376
- }];
2126
+ };
2127
+ const builtin = (typescriptOptions) => {
2128
+ const tsConfigPath = resolveTsConfigPath(typeof typescriptOptions === "object" ? typescriptOptions : void 0);
2129
+ return [{
2130
+ name: buildConfigName(MAIN_SCOPES[packageOrganizationUpper], SUB_SCOPES.SETUP),
2131
+ plugins: { [packageOrganization]: {
2132
+ meta: {
2133
+ name: packageOrganization,
2134
+ version
2135
+ },
2136
+ rules: RULE_DEFINITIONS
2137
+ } }
2138
+ }, {
2139
+ name: buildConfigName(MAIN_SCOPES[packageOrganizationUpper], SUB_SCOPES.RULES),
2140
+ files: GLOB_SCRIPT_FILES,
2141
+ rules: {
2142
+ [`${packageOrganization}/api-or-internal-tag`]: "error",
2143
+ [`${packageOrganization}/boolish-prefix`]: "error",
2144
+ [`${packageOrganization}/interface-suffix`]: "error",
2145
+ [`${packageOrganization}/internal-usage`]: ["error", { tsConfigPath }],
2146
+ [`${packageOrganization}/public-api-documentation`]: "error",
2147
+ [`${packageOrganization}/require-import-alias`]: ["error", { tsConfigPath }],
2148
+ [`${packageOrganization}/require-import-attributes`]: "error",
2149
+ [`${packageOrganization}/resolvable-doc-reference`]: "error",
2150
+ [`${packageOrganization}/type-assertion-style`]: "error"
2151
+ }
2152
+ }];
2153
+ };
377
2154
  const builtinConfig = { [packageOrganization]: builtin };
378
2155
  //#endregion
379
2156
  //#region ../src/js/eslint/utils/module.ts
2157
+ /**
2158
+ * @internal @brnshkr/config/eslint
2159
+ */
380
2160
  const MODULES = {
381
2161
  [packageOrganization]: { name: packageOrganization },
382
2162
  comments: {
@@ -387,15 +2167,11 @@ const MODULES = {
387
2167
  name: "css",
388
2168
  packages: { requiredAll: [ESLINT_PACKAGES.ESLINT_CSS] }
389
2169
  },
390
- gitignore: {
391
- name: "gitignore",
392
- packages: { requiredAll: [ESLINT_PACKAGES.ESLINT_FLAT_CONFIG_GITIGNORE] }
393
- },
394
2170
  import: {
395
2171
  name: "import",
396
2172
  packages: {
397
2173
  requiredAny: [ESLINT_PACKAGES.ESLINT_PLUGIN_IMPORT_X, ESLINT_PACKAGES.ESLINT_PLUGIN_ANTFU],
398
- optional: [ESLINT_PACKAGES.ESLINT_IMPORT_RESOVLER_TYPESCRIPT]
2174
+ optional: [ESLINT_PACKAGES.ESLINT_IMPORT_RESOLVER_TYPESCRIPT]
399
2175
  }
400
2176
  },
401
2177
  javascript: {
@@ -461,14 +2237,13 @@ const MODULES = {
461
2237
  packages: { requiredAll: [ESLINT_PACKAGES.ESLINT_PLUGIN_YML] }
462
2238
  }
463
2239
  };
464
- const resolvePackages = async (moduleInfo, type) => resolvePackagesSharedAsynchronously(moduleInfo, type);
465
- const enabledStates = {};
466
- const isModuleEnabled = (moduleInfo) => enabledStates[moduleInfo.name] ?? isModuleEnabledByDefault(moduleInfo);
467
- const setModuleEnabled = (moduleInfo, state) => {
468
- enabledStates[moduleInfo.name] = state;
469
- };
2240
+ const resolvePackages = resolvePackagesSharedAsynchronously;
2241
+ const { isModuleEnabled, setModuleEnabled } = createModuleState();
470
2242
  //#endregion
471
2243
  //#region ../src/js/eslint/configs/comments.ts
2244
+ /**
2245
+ * @internal @brnshkr/config/eslint
2246
+ */
472
2247
  const comments = async () => {
473
2248
  const { requiredAll: [pluginComments] } = await resolvePackages(MODULES.comments);
474
2249
  if (!pluginComments) return [];
@@ -482,15 +2257,72 @@ const comments = async () => {
482
2257
  files: GLOB_SCRIPT_FILES,
483
2258
  rules: {
484
2259
  ...renameRules(recommendedRules, { "@eslint-community/eslint-comments": "comments" }),
485
- "comments/require-description": "error"
2260
+ "comments/require-description": ["error", { additionalDirectives: [
2261
+ "@ts-expect-error",
2262
+ "c8 ignore",
2263
+ "istanbul ignore",
2264
+ "node:coverage ignore",
2265
+ "svelte-ignore",
2266
+ "v8 ignore"
2267
+ ] }]
486
2268
  }
487
2269
  }];
488
2270
  };
489
2271
  //#endregion
490
2272
  //#region ../src/js/eslint/configs/css.ts
491
- const css = async () => {
2273
+ /**
2274
+ * @internal @brnshkr/config/eslint
2275
+ */
2276
+ const buildTailwindSyntax = (defaultSyntax) => {
2277
+ const declarations = objectFromEntries(objectKeys(defaultSyntax.properties).map((property) => [property, "<declaration-value>"]));
2278
+ return { atrules: {
2279
+ apply: { prelude: "<any-value>" },
2280
+ config: { prelude: "<string>" },
2281
+ "custom-variant": { prelude: "<any-value>" },
2282
+ plugin: { prelude: "<string>" },
2283
+ reference: { prelude: "<string>" },
2284
+ slot: { prelude: null },
2285
+ source: { prelude: "<any-value>" },
2286
+ theme: { prelude: "<any-value>?" },
2287
+ utility: {
2288
+ prelude: "<any-value>",
2289
+ descriptors: declarations
2290
+ },
2291
+ variant: {
2292
+ prelude: "<any-value>",
2293
+ descriptors: declarations
2294
+ }
2295
+ } };
2296
+ };
2297
+ const mergeSyntaxDefinitions = (baseSyntax, overridingSyntax) => ({
2298
+ ...baseSyntax,
2299
+ ...overridingSyntax,
2300
+ atrules: {
2301
+ ...baseSyntax.atrules,
2302
+ ...overridingSyntax.atrules
2303
+ },
2304
+ properties: {
2305
+ ...baseSyntax.properties,
2306
+ ...overridingSyntax.properties
2307
+ },
2308
+ types: {
2309
+ ...baseSyntax.types,
2310
+ ...overridingSyntax.types
2311
+ }
2312
+ });
2313
+ const buildCustomSyntax = (customSyntax, isTailwindEnabled) => {
2314
+ if (!isTailwindEnabled) return customSyntax;
2315
+ return (defaultSyntax) => {
2316
+ const tailwindSyntax = buildTailwindSyntax(defaultSyntax);
2317
+ if (customSyntax === void 0) return tailwindSyntax;
2318
+ return mergeSyntaxDefinitions(tailwindSyntax, typeof customSyntax === "function" ? customSyntax(defaultSyntax) : customSyntax);
2319
+ };
2320
+ };
2321
+ const css = async (options) => {
492
2322
  const { requiredAll: [pluginCss] } = await resolvePackages(MODULES.css);
493
2323
  if (!pluginCss) return [];
2324
+ const isTailwindEnabled = options?.tailwind ?? doAllPackagesExist([ESLINT_PACKAGES.TAILWINDCSS]);
2325
+ const customSyntax = buildCustomSyntax(options?.customSyntax, isTailwindEnabled);
494
2326
  return [{
495
2327
  name: buildConfigName(MAIN_SCOPES.CSS, SUB_SCOPES.SETUP),
496
2328
  plugins: { css: pluginCss }
@@ -498,41 +2330,68 @@ const css = async () => {
498
2330
  name: buildConfigName(MAIN_SCOPES.CSS, SUB_SCOPES.RULES),
499
2331
  files: [GLOB_CSS],
500
2332
  language: "css/css",
2333
+ languageOptions: {
2334
+ ...customSyntax === void 0 ? void 0 : { customSyntax },
2335
+ tolerant: options?.tolerant ?? isTailwindEnabled
2336
+ },
501
2337
  rules: {
502
2338
  ...pluginCss.configs.recommended.rules,
2339
+ ...isTailwindEnabled ? {
2340
+ "css/no-duplicate-imports": "off",
2341
+ "css/no-invalid-properties": "off"
2342
+ } : void 0,
503
2343
  "css/prefer-logical-properties": "error",
504
2344
  "css/relative-font-units": ["error", { allowUnits: ["em", "rem"] }],
505
- "css/use-baseline": "error"
2345
+ ...isModuleEnabled(MODULES.unicorn) ? {
2346
+ "unicorn/no-missing-local-resource": "error",
2347
+ "unicorn/prefer-explicit-viewport-units": "error"
2348
+ } : void 0
506
2349
  }
507
2350
  }];
508
2351
  };
509
2352
  //#endregion
510
- //#region ../src/js/eslint/configs/gitignore.ts
511
- const gitignore = async (options) => {
512
- const { requiredAll: [pluginGitignore] } = await resolvePackages(MODULES.gitignore);
513
- if (!pluginGitignore) return [];
514
- return [pluginGitignore({
515
- name: buildConfigName(MAIN_SCOPES.IGNORES, SUB_SCOPES.GIT),
516
- ...options
517
- })];
518
- };
519
- //#endregion
520
2353
  //#region ../src/js/eslint/configs/ignores.ts
521
- const ignores = (customIgnores = []) => [{
522
- name: buildConfigName(MAIN_SCOPES.IGNORES, SUB_SCOPES.BASE),
523
- ignores: [...GLOB_IGNORES, ...customIgnores]
524
- }];
2354
+ /**
2355
+ * @internal @brnshkr/config/eslint
2356
+ */
2357
+ const DEFAULT_IGNORE_FILE = ".gitignore";
2358
+ const isIgnoreFile = (customIgnore) => /^\.[\w\-]+ignore$/v.test(path.basename(customIgnore));
2359
+ const ignores = (customIgnores = []) => {
2360
+ const existingIgnoreFiles = [.../* @__PURE__ */ new Set([DEFAULT_IGNORE_FILE, ...customIgnores.filter((customIgnore) => isIgnoreFile(customIgnore))])].map((ignoreFile) => path.resolve(ignoreFile)).filter((ignoreFile) => doesFileExist(ignoreFile));
2361
+ return [{
2362
+ name: buildConfigName(MAIN_SCOPES.IGNORES, SUB_SCOPES.BASE),
2363
+ ignores: [...GLOB_IGNORES, ...customIgnores.filter((customIgnore) => !isIgnoreFile(customIgnore))]
2364
+ }, ...existingIgnoreFiles.map((ignoreFile) => ({
2365
+ ...includeIgnoreFile(ignoreFile, { gitignoreResolution: true }),
2366
+ name: buildConfigName(MAIN_SCOPES.IGNORES, SUB_SCOPES.FILES)
2367
+ }))];
2368
+ };
525
2369
  //#endregion
526
2370
  //#region ../src/js/eslint/configs/import.ts
2371
+ /**
2372
+ * @internal @brnshkr/config/eslint
2373
+ */
527
2374
  const imports = async () => {
528
- const { requiredAny: [pluginImport, pluginAntfu], optional: [importResovlerTypescript] } = await resolvePackages(MODULES.import);
2375
+ const { requiredAny: [pluginImport, pluginAntfu], optional: [importResolverTypescript] } = await resolvePackages(MODULES.import);
529
2376
  const plugins = {};
530
2377
  const settings = {};
531
2378
  let pluginImportRules = {};
2379
+ let pluginImportTsRules = {};
532
2380
  let pluginAntfuRules = {};
533
2381
  if (pluginImport) {
534
2382
  plugins["import"] = pluginImport;
535
- settings["import-x/resolver-next"] = [pluginImport.createNodeResolver(), importResovlerTypescript === void 0 ? void 0 : importResovlerTypescript.createTypeScriptImportResolver({ bun: true })].filter(Boolean);
2383
+ settings["import-x/resolver-next"] = [pluginImport.createNodeResolver(), importResolverTypescript === void 0 ? void 0 : importResolverTypescript.createTypeScriptImportResolver({ bun: true })].filter(Boolean);
2384
+ if (isModuleEnabled(MODULES.typescript)) settings["import-x/extensions"] = [
2385
+ ".cjs",
2386
+ ".cts",
2387
+ ".js",
2388
+ ".jsx",
2389
+ ".mjs",
2390
+ ".mts",
2391
+ ".ts",
2392
+ ".tsx"
2393
+ ];
2394
+ settings["import-x/ignore"] = [String.raw`[/\\]node_modules[/\\]`];
536
2395
  settings["import-x/core-modules"] = [
537
2396
  "bun",
538
2397
  "bun:bundle",
@@ -541,33 +2400,46 @@ const imports = async () => {
541
2400
  "bun:sqlite",
542
2401
  "bun:test"
543
2402
  ];
544
- const pluginImportTsRules = isModuleEnabled(MODULES.typescript) ? renameRules(pluginImport.flatConfigs.typescript.rules, { "import-x": "import" }) : {};
2403
+ pluginImportTsRules = renameRules(pluginImport.flatConfigs.typescript.rules, { "import-x": "import" });
545
2404
  pluginImportRules = {
546
2405
  ...renameRules(pluginImport.flatConfigs.recommended.rules, { "import-x": "import" }),
547
- ...pluginImportTsRules,
548
- "import/consistent-type-specifier-style": ["error", "prefer-top-level"],
2406
+ "import/consistent-type-specifier-style": "error",
549
2407
  "import/extensions": [
550
2408
  "error",
551
2409
  "ignorePackages",
552
2410
  {
553
- js: "never",
554
- ts: "never",
555
- cts: "never",
556
- mts: "never"
2411
+ checkTypeImports: true,
2412
+ pattern: {
2413
+ js: "never",
2414
+ ts: "never",
2415
+ cts: "never",
2416
+ mts: "never"
2417
+ },
2418
+ pathGroupOverrides: [{
2419
+ pattern: "{{.,..,../..,../../..,../../../..,../../../../..}/**/declarations,$types/declarations,$declarations}{,/**}",
2420
+ action: "ignore"
2421
+ }]
557
2422
  }
558
2423
  ],
559
2424
  "import/first": "error",
560
2425
  "import/max-dependencies": ["error", { max: 15 }],
2426
+ "import/namespace": "off",
561
2427
  "import/newline-after-import": "error",
562
2428
  "import/no-absolute-path": "error",
563
2429
  "import/no-amd": "error",
564
- "import/no-cycle": ["error", { maxDepth: 3 }],
2430
+ "import/no-cycle": ["error", {
2431
+ ignoreExternal: true,
2432
+ maxDepth: 3
2433
+ }],
565
2434
  "import/no-default-export": "error",
566
2435
  "import/no-deprecated": "error",
567
2436
  "import/no-duplicates": "error",
568
2437
  "import/no-dynamic-require": "error",
569
2438
  "import/no-empty-named-blocks": "error",
570
- "import/no-extraneous-dependencies": ["error", { devDependencies: GLOB_DEVELOPMENT_FILES }],
2439
+ "import/no-extraneous-dependencies": ["error", {
2440
+ devDependencies: GLOB_DEVELOPMENT_FILES,
2441
+ includeTypes: true
2442
+ }],
571
2443
  "import/no-import-module-exports": "error",
572
2444
  "import/no-mutable-exports": "error",
573
2445
  "import/no-named-as-default-member": "error",
@@ -581,7 +2453,7 @@ const imports = async () => {
581
2453
  "import/no-useless-path-segments": ["error", { noUselessIndex: true }],
582
2454
  "import/no-unresolved": ["error", {
583
2455
  commonjs: true,
584
- caseSensitive: true
2456
+ caseSensitiveStrict: true
585
2457
  }],
586
2458
  "import/no-webpack-loader-syntax": "error",
587
2459
  "import/order": ["error", {
@@ -625,23 +2497,34 @@ const imports = async () => {
625
2497
  "antfu/no-import-node-modules-by-path": "error"
626
2498
  };
627
2499
  }
628
- if (Object.keys(plugins).length === 0) return [];
2500
+ if (objectKeys(plugins).length === 0) return [];
629
2501
  const setupConfig = {
630
2502
  name: buildConfigName(MAIN_SCOPES.IMPORT, SUB_SCOPES.SETUP),
631
2503
  plugins
632
2504
  };
633
- if (Object.keys(settings).length > 0) setupConfig.settings = settings;
634
- return [setupConfig, {
635
- name: buildConfigName(MAIN_SCOPES.IMPORT, SUB_SCOPES.RULES),
636
- files: GLOB_SCRIPT_FILES,
637
- rules: {
638
- ...pluginImportRules,
639
- ...pluginAntfuRules
640
- }
641
- }];
2505
+ if (objectKeys(settings).length > 0) setupConfig.settings = settings;
2506
+ return [
2507
+ setupConfig,
2508
+ {
2509
+ name: buildConfigName(MAIN_SCOPES.IMPORT, SUB_SCOPES.RULES),
2510
+ files: GLOB_SCRIPT_FILES,
2511
+ rules: {
2512
+ ...pluginImportRules,
2513
+ ...pluginAntfuRules
2514
+ }
2515
+ },
2516
+ ...isModuleEnabled(MODULES.typescript) ? [{
2517
+ name: buildConfigName(MAIN_SCOPES.IMPORT, `${SUB_SCOPES.RULES}-typescript`),
2518
+ files: [GLOB_TS],
2519
+ rules: pluginImportTsRules
2520
+ }] : []
2521
+ ];
642
2522
  };
643
2523
  //#endregion
644
2524
  //#region ../src/js/eslint/configs/javascript.ts
2525
+ /**
2526
+ * @internal @brnshkr/config/eslint
2527
+ */
645
2528
  const javascript = async () => {
646
2529
  const { optional: [pluginAntfu, pluginUnusedImports] } = await resolvePackages(MODULES.javascript);
647
2530
  const plugins = {};
@@ -673,16 +2556,19 @@ const javascript = async () => {
673
2556
  ecmaFeatures: { jsx: true }
674
2557
  }
675
2558
  },
676
- linterOptions: { reportUnusedDisableDirectives: "error" }
2559
+ linterOptions: {
2560
+ reportUnusedDisableDirectives: "error",
2561
+ reportUnusedInlineConfigs: "error"
2562
+ }
677
2563
  }, {
678
2564
  name: buildConfigName(MAIN_SCOPES.JAVASCRIPT, SUB_SCOPES.RULES),
679
2565
  files: GLOB_SCRIPT_FILES,
680
2566
  rules: {
681
2567
  ...jsEslint.configs.recommended.rules,
682
2568
  ...pluginUnusedImports ? { "no-unused-vars": "off" } : void 0,
683
- "accessor-pairs": "error",
2569
+ "accessor-pairs": ["error", { enforceForTSTypes: true }],
684
2570
  "array-callback-return": ["error", { checkForEach: true }],
685
- "arrow-body-style": ["error", "as-needed"],
2571
+ "arrow-body-style": "error",
686
2572
  "block-scoped-var": "error",
687
2573
  "capitalized-comments": [
688
2574
  "error",
@@ -710,8 +2596,12 @@ const javascript = async () => {
710
2596
  includeCommonJSModuleExports: true
711
2597
  }],
712
2598
  "func-names": "error",
713
- "func-style": ["error", "expression"],
714
- "grouped-accessor-pairs": "error",
2599
+ "func-style": "error",
2600
+ "grouped-accessor-pairs": [
2601
+ "error",
2602
+ "getBeforeSet",
2603
+ { enforceForTSTypes: true }
2604
+ ],
715
2605
  "guard-for-in": "error",
716
2606
  "init-declarations": "error",
717
2607
  "logical-assignment-operators": "error",
@@ -728,7 +2618,10 @@ const javascript = async () => {
728
2618
  skipBlankLines: true,
729
2619
  skipComments: true
730
2620
  }],
731
- "max-nested-callbacks": ["error", { max: 3 }],
2621
+ "max-nested-callbacks": ["error", {
2622
+ max: 3,
2623
+ checkConstructorCallCallbacks: true
2624
+ }],
732
2625
  "max-params": ["error", { max: 4 }],
733
2626
  "max-statements": ["error", { max: 30 }],
734
2627
  "new-cap": "error",
@@ -739,6 +2632,8 @@ const javascript = async () => {
739
2632
  "no-caller": "error",
740
2633
  "no-cond-assign": ["error", "always"],
741
2634
  "no-console": "error",
2635
+ "no-constant-binary-expression": ["error", { checkRelationalComparisons: true }],
2636
+ "no-constant-condition": ["error", { checkLoops: "all" }],
742
2637
  "no-constructor-return": "error",
743
2638
  "no-div-regex": "error",
744
2639
  "no-duplicate-imports": isModuleEnabled(MODULES.import) ? "off" : ["error", { allowSeparateTypeImports: true }],
@@ -748,26 +2643,39 @@ const javascript = async () => {
748
2643
  "no-eval": "error",
749
2644
  "no-extend-native": "error",
750
2645
  "no-extra-bind": "error",
2646
+ "no-extra-boolean-cast": ["error", { enforceForInnerExpressions: true }],
751
2647
  "no-extra-label": "error",
2648
+ "no-fallthrough": ["error", {
2649
+ allowEmptyCase: true,
2650
+ reportUnusedFallthroughComment: true
2651
+ }],
752
2652
  "no-implicit-coercion": ["error", { boolean: false }],
753
2653
  "no-implicit-globals": "error",
754
2654
  "no-implied-eval": "error",
755
2655
  "no-inline-comments": "error",
756
- "no-inner-declarations": "error",
2656
+ "no-inner-declarations": [
2657
+ "error",
2658
+ "functions",
2659
+ { blockScopedFunctions: "disallow" }
2660
+ ],
757
2661
  "no-invalid-this": "error",
2662
+ "no-irregular-whitespace": ["error", { skipStrings: false }],
758
2663
  "no-iterator": "error",
759
2664
  "no-label-var": "error",
760
2665
  "no-labels": "error",
761
2666
  "no-lone-blocks": "error",
762
2667
  "no-lonely-if": "error",
763
2668
  "no-loop-func": "error",
764
- "no-magic-numbers": ["error", { ignore: [
765
- -1,
766
- 0,
767
- 1,
768
- 100,
769
- 42069
770
- ] }],
2669
+ "no-magic-numbers": ["error", {
2670
+ ignore: [
2671
+ -1,
2672
+ 0,
2673
+ 1,
2674
+ 100,
2675
+ 42069
2676
+ ],
2677
+ enforceConst: true
2678
+ }],
771
2679
  "no-multi-assign": "error",
772
2680
  "no-multi-str": "error",
773
2681
  "no-negated-condition": "error",
@@ -828,14 +2736,20 @@ const javascript = async () => {
828
2736
  "no-return-assign": ["error", "always"],
829
2737
  "no-script-url": "error",
830
2738
  "no-self-compare": "error",
831
- "no-sequences": "error",
832
- "no-shadow": "error",
2739
+ "no-sequences": ["error", { allowInParentheses: false }],
2740
+ "no-shadow": ["error", { hoist: "all" }],
833
2741
  "no-template-curly-in-string": "error",
834
2742
  "no-throw-literal": "error",
835
- "no-underscore-dangle": "error",
836
- "no-unmodified-loop-condition": "error",
2743
+ "no-undef": ["error", { typeof: true }],
2744
+ "no-underscore-dangle": ["error", {
2745
+ enforceInClassFields: true,
2746
+ enforceInMethodNames: true
2747
+ }],
2748
+ "no-unmodified-loop-condition": ["error", { checkConditionalExpressions: true }],
837
2749
  "no-unneeded-ternary": ["error", { defaultAssignment: false }],
838
2750
  "no-unreachable-loop": "error",
2751
+ "no-unsafe-negation": ["error", { enforceForOrderingRelations: true }],
2752
+ "no-unsafe-optional-chaining": ["error", { disallowArithmeticOperators: true }],
839
2753
  "no-unused-expressions": "error",
840
2754
  "no-use-before-define": "error",
841
2755
  "no-useless-call": "error",
@@ -845,7 +2759,7 @@ const javascript = async () => {
845
2759
  "no-useless-rename": "error",
846
2760
  "no-useless-return": "error",
847
2761
  "no-var": "error",
848
- "no-void": "error",
2762
+ "no-void": ["error", { allowAsStatement: true }],
849
2763
  "no-warning-comments": "error",
850
2764
  "object-shorthand": [
851
2765
  "error",
@@ -856,7 +2770,7 @@ const javascript = async () => {
856
2770
  }
857
2771
  ],
858
2772
  "operator-assignment": "error",
859
- "prefer-arrow-callback": "error",
2773
+ "prefer-arrow-callback": ["error", { allowUnboundThis: false }],
860
2774
  "prefer-const": ["error", { ignoreReadBeforeAssign: true }],
861
2775
  "prefer-destructuring": "error",
862
2776
  "prefer-exponentiation-operator": "error",
@@ -885,112 +2799,93 @@ const javascript = async () => {
885
2799
  };
886
2800
  //#endregion
887
2801
  //#region ../src/js/eslint/configs/typescript.ts
2802
+ /**
2803
+ * @internal @brnshkr/config/eslint
2804
+ */
888
2805
  const DEFAULT_TYPE_AWARE_IGNORES = [`${GLOB_MD}/**`];
889
2806
  const getTsEslintParserIfExists = async () => {
890
- const isTypescriptModuleEnabled = isModuleEnabled(MODULES.typescript);
891
- let parser = void 0;
892
- if (isTypescriptModuleEnabled) {
893
- const { requiredAll: [, tsEslint] } = await resolvePackages(MODULES.typescript);
894
- if (tsEslint) ({parser} = tsEslint);
895
- }
896
- return parser;
2807
+ if (!isModuleEnabled(MODULES.typescript)) return;
2808
+ const { requiredAll: [, tsEslint] } = await resolvePackages(MODULES.typescript);
2809
+ return tsEslint?.parser;
897
2810
  };
898
- const resolveTypeAwareOptions = (resolvedOptions, files, ignores) => {
2811
+ const resolveTypeAwareOptions = (resolvedOptions, files, ignoredGlobs) => {
899
2812
  const typeAwareOptions = typeof resolvedOptions.typeAware === "object" ? resolvedOptions.typeAware : {
900
2813
  ignores: DEFAULT_TYPE_AWARE_IGNORES,
901
2814
  tsconfig: typeof resolvedOptions.typeAware === "string" ? resolvedOptions.typeAware : void 0
902
2815
  };
903
- typeAwareOptions.files = [...new Set([...typeAwareOptions.files ?? [], ...files])];
904
- typeAwareOptions.ignores = [...new Set([...typeAwareOptions.ignores ?? [], ...ignores])];
2816
+ typeAwareOptions.files = [.../* @__PURE__ */ new Set([...typeAwareOptions.files ?? [], ...files])];
2817
+ typeAwareOptions.ignores = [.../* @__PURE__ */ new Set([...typeAwareOptions.ignores ?? [], ...ignoredGlobs])];
905
2818
  return typeAwareOptions;
906
2819
  };
907
2820
  const extractRelevantRules = (configs, key) => {
908
2821
  for (const config of configs) if (config.name === `typescript-eslint/${key}` && config.rules) return renameRules(config.rules, { "@typescript-eslint": "ts" });
909
2822
  throw new Error(`Expected key "${key}" to be contained in given config.`);
910
2823
  };
911
- const getNamingConvention = (isTypeAware) => {
912
- const ruleOptions = [
913
- "error",
914
- {
915
- selector: "default",
916
- format: [
917
- "strictCamelCase",
918
- "StrictPascalCase",
919
- "UPPER_CASE"
920
- ],
921
- leadingUnderscore: "forbid",
922
- trailingUnderscore: "forbid"
923
- },
924
- {
925
- selector: ["objectLiteralProperty", "variable"],
926
- format: ["strictCamelCase", "UPPER_CASE"]
927
- },
928
- {
929
- selector: "variable",
930
- format: null,
931
- modifiers: ["destructured"]
932
- },
933
- {
934
- selector: "typeLike",
935
- format: ["StrictPascalCase"]
936
- },
937
- {
938
- selector: "parameter",
939
- format: null,
940
- filter: {
941
- regex: "^_+$",
942
- match: false
943
- }
944
- },
945
- {
946
- selector: [
947
- "classProperty",
948
- "objectLiteralProperty",
949
- "typeProperty",
950
- "classMethod",
951
- "objectLiteralMethod",
952
- "typeMethod",
953
- "accessor",
954
- "enumMember"
955
- ],
956
- format: null,
957
- modifiers: ["requiresQuotes"]
958
- },
959
- {
960
- selector: "typeParameter",
961
- format: ["StrictPascalCase"],
962
- prefix: ["T"],
963
- custom: {
964
- regex: "^[A-Z]",
965
- match: true
966
- }
967
- },
968
- {
969
- selector: "interface",
970
- format: ["StrictPascalCase"],
971
- custom: {
972
- regex: "^I[A-Z]",
973
- match: false
974
- }
975
- }
976
- ];
977
- if (isTypeAware) ruleOptions.push({
2824
+ const getNamingConvention = () => [
2825
+ "error",
2826
+ {
2827
+ selector: "default",
2828
+ format: [
2829
+ "strictCamelCase",
2830
+ "StrictPascalCase",
2831
+ "UPPER_CASE"
2832
+ ],
2833
+ leadingUnderscore: "forbid",
2834
+ trailingUnderscore: "forbid"
2835
+ },
2836
+ {
2837
+ selector: ["objectLiteralProperty", "variable"],
2838
+ format: ["strictCamelCase", "UPPER_CASE"]
2839
+ },
2840
+ {
978
2841
  selector: "variable",
979
- types: ["boolean"],
2842
+ format: null,
2843
+ modifiers: ["destructured"]
2844
+ },
2845
+ {
2846
+ selector: "typeLike",
2847
+ format: ["StrictPascalCase"]
2848
+ },
2849
+ {
2850
+ selector: "parameter",
2851
+ format: null,
2852
+ filter: {
2853
+ regex: "^_+$",
2854
+ match: false
2855
+ }
2856
+ },
2857
+ {
2858
+ selector: [
2859
+ "classProperty",
2860
+ "objectLiteralProperty",
2861
+ "typeProperty",
2862
+ "classMethod",
2863
+ "objectLiteralMethod",
2864
+ "typeMethod",
2865
+ "accessor",
2866
+ "enumMember"
2867
+ ],
2868
+ format: null,
2869
+ modifiers: ["requiresQuotes"]
2870
+ },
2871
+ {
2872
+ selector: "typeParameter",
980
2873
  format: ["StrictPascalCase"],
981
- prefix: [
982
- "as",
983
- "is",
984
- "does",
985
- "do",
986
- "did",
987
- "has",
988
- "was",
989
- "can"
990
- ]
991
- });
992
- return ruleOptions;
993
- };
2874
+ prefix: ["T"],
2875
+ custom: {
2876
+ regex: "^[A-Z]",
2877
+ match: true
2878
+ }
2879
+ },
2880
+ {
2881
+ selector: "interface",
2882
+ format: ["StrictPascalCase"],
2883
+ custom: {
2884
+ regex: "^I[A-Z]",
2885
+ match: false
2886
+ }
2887
+ }
2888
+ ];
994
2889
  const typescript = async (options) => {
995
2890
  const { requiredAll: [isTypescriptInstalled, tsEslint] } = await resolvePackages(MODULES.typescript);
996
2891
  if (!isTypescriptInstalled || !tsEslint) return [];
@@ -1002,14 +2897,14 @@ const typescript = async (options) => {
1002
2897
  typeAware: doesTsConfigExist(resolveTsConfigPath(options)),
1003
2898
  ...options
1004
2899
  };
1005
- const ignores = resolvedOptions.ignores ?? [];
1006
- const files = [...new Set([...resolvedOptions.extraFileExtensions.map((extension) => `**/*.${extension}`), ...resolvedOptions.files ?? GLOB_SCRIPT_FILES])];
2900
+ const ignoredGlobs = resolvedOptions.ignores ?? [];
2901
+ const files = [.../* @__PURE__ */ new Set([...resolvedOptions.extraFileExtensions.map((extension) => `**/*.${extension}`), ...resolvedOptions.files ?? GLOB_SCRIPT_FILES])];
1007
2902
  const hasEnabledTypeAwareness = resolvedOptions.typeAware !== false;
1008
- const typeAwareOptions = hasEnabledTypeAwareness ? resolveTypeAwareOptions(resolvedOptions, files, ignores) : {};
2903
+ const typeAwareOptions = hasEnabledTypeAwareness ? resolveTypeAwareOptions(resolvedOptions, files, ignoredGlobs) : {};
1009
2904
  const createParserConfig = (isTypeAware) => ({
1010
2905
  name: buildConfigName(MAIN_SCOPES.TYPESCRIPT, `${SUB_SCOPES.PARSER}${isTypeAware ? "-type-aware" : ""}`),
1011
2906
  files: isTypeAware ? typeAwareOptions.files : files,
1012
- ignores: isTypeAware ? typeAwareOptions.ignores : ignores,
2907
+ ignores: isTypeAware ? typeAwareOptions.ignores : ignoredGlobs,
1013
2908
  languageOptions: {
1014
2909
  parser: tsEslint.parser,
1015
2910
  parserOptions: {
@@ -1025,9 +2920,9 @@ const typescript = async (options) => {
1025
2920
  const createRulesConfig = (isTypeAware) => ({
1026
2921
  name: buildConfigName(MAIN_SCOPES.TYPESCRIPT, `${SUB_SCOPES.RULES}${isTypeAware ? "-type-aware" : ""}`),
1027
2922
  files: isTypeAware ? typeAwareOptions.files : files,
1028
- ignores: isTypeAware ? typeAwareOptions.ignores : ignores,
2923
+ ignores: isTypeAware ? typeAwareOptions.ignores : ignoredGlobs,
1029
2924
  rules: {
1030
- "ts/naming-convention": getNamingConvention(isTypeAware),
2925
+ "ts/naming-convention": getNamingConvention(),
1031
2926
  ...isTypeAware ? {
1032
2927
  ...extractRelevantRules(tsEslint.configs.recommendedTypeCheckedOnly, "recommended-type-checked-only"),
1033
2928
  ...extractRelevantRules(tsEslint.configs.strictTypeCheckedOnly, "strict-type-checked-only"),
@@ -1038,11 +2933,19 @@ const typescript = async (options) => {
1038
2933
  "ts/no-unnecessary-qualifier": "error",
1039
2934
  "prefer-destructuring": "off",
1040
2935
  "ts/prefer-destructuring": "error",
1041
- "ts/no-unnecessary-type-conversion": "error",
1042
2936
  "ts/promise-function-async": "error",
1043
2937
  "ts/prefer-readonly": "error",
1044
2938
  "ts/require-array-sort-compare": "error",
1045
- "ts/strict-boolean-expressions": "error",
2939
+ "ts/no-base-to-string": ["error", { checkUnknown: true }],
2940
+ "ts/no-floating-promises": ["error", { checkThenables: true }],
2941
+ "ts/no-unnecessary-condition": ["error", { checkTypePredicates: true }],
2942
+ "ts/only-throw-error": ["error", { allowThrowingUnknown: false }],
2943
+ "ts/prefer-nullish-coalescing": ["error", { ignoreConditionalTests: false }],
2944
+ "ts/return-await": ["error", "in-try-catch"],
2945
+ "ts/strict-boolean-expressions": ["error", {
2946
+ allowNumber: false,
2947
+ allowString: false
2948
+ }],
1046
2949
  "ts/strict-void-return": "error",
1047
2950
  "ts/switch-exhaustiveness-check": ["error", { requireDefaultForNonUnion: true }]
1048
2951
  } : {
@@ -1052,7 +2955,36 @@ const typescript = async (options) => {
1052
2955
  ...pluginUnusedImports ? { "ts/no-unused-vars": "off" } : void 0,
1053
2956
  "ts/consistent-type-assertions": ["error", { assertionStyle: "angle-bracket" }],
1054
2957
  "ts/consistent-type-imports": "error",
1055
- "ts/member-ordering": "error",
2958
+ "ts/member-ordering": ["error", { default: [
2959
+ "signature",
2960
+ "call-signature",
2961
+ ["public-field", "public-accessor"],
2962
+ ["protected-field", "protected-accessor"],
2963
+ ["private-field", "private-accessor"],
2964
+ ["#private-field", "#private-accessor"],
2965
+ "static-initialization",
2966
+ "constructor",
2967
+ [
2968
+ "public-get",
2969
+ "public-set",
2970
+ "public-method"
2971
+ ],
2972
+ [
2973
+ "protected-get",
2974
+ "protected-set",
2975
+ "protected-method"
2976
+ ],
2977
+ [
2978
+ "private-get",
2979
+ "private-set",
2980
+ "private-method"
2981
+ ],
2982
+ [
2983
+ "#private-get",
2984
+ "#private-set",
2985
+ "#private-method"
2986
+ ]
2987
+ ] }],
1056
2988
  "ts/method-signature-style": "error",
1057
2989
  "ts/no-import-type-side-effects": "error",
1058
2990
  "no-redeclare": "off",
@@ -1065,6 +2997,7 @@ const typescript = async (options) => {
1065
2997
  }
1066
2998
  }
1067
2999
  });
3000
+ const coreRulesCoveredByTypescript = objectFromEntries(objectEntries(extractRelevantRules(tsEslint.configs.recommended, "eslint-recommended")).filter(([, severity]) => severity === "off"));
1068
3001
  return [
1069
3002
  {
1070
3003
  name: buildConfigName(MAIN_SCOPES.TYPESCRIPT, SUB_SCOPES.SETUP),
@@ -1077,8 +3010,9 @@ const typescript = async (options) => {
1077
3010
  {
1078
3011
  name: buildConfigName(MAIN_SCOPES.TYPESCRIPT, `${SUB_SCOPES.RULES}-typescript`),
1079
3012
  files: [GLOB_TS],
1080
- ignores: typeAwareOptions.ignores ?? ignores,
3013
+ ignores: typeAwareOptions.ignores ?? ignoredGlobs,
1081
3014
  rules: {
3015
+ ...coreRulesCoveredByTypescript,
1082
3016
  "ts/explicit-function-return-type": "error",
1083
3017
  "ts/explicit-member-accessibility": "error"
1084
3018
  }
@@ -1087,9 +3021,129 @@ const typescript = async (options) => {
1087
3021
  };
1088
3022
  //#endregion
1089
3023
  //#region ../src/js/eslint/configs/jsdoc.ts
3024
+ /**
3025
+ * @internal @brnshkr/config/eslint
3026
+ */
3027
+ const TAGS_BY_MODULE = { test: ["vitest-environment", "vitest-environment-options"] };
3028
+ const EXAMPLE_CODE_REGEX = "/```(?:js|javascript|ts|typescript)\\s*\\n([\\s\\S]*?)\\n\\s*```/gv";
3029
+ const TAG_SEQUENCE = [
3030
+ { tags: [
3031
+ "file",
3032
+ "fileoverview",
3033
+ "overview",
3034
+ "module"
3035
+ ] },
3036
+ { tags: ["api", "internal"] },
3037
+ { tags: [
3038
+ "deprecated",
3039
+ "ignore",
3040
+ "since",
3041
+ "version",
3042
+ ["to", "do"].join("")
3043
+ ] },
3044
+ { tags: [
3045
+ "author",
3046
+ "copyright",
3047
+ "license"
3048
+ ] },
3049
+ { tags: [
3050
+ "summary",
3051
+ "typeSummary",
3052
+ "desc",
3053
+ "description",
3054
+ "classdesc"
3055
+ ] },
3056
+ { tags: [
3057
+ "namespace",
3058
+ "category",
3059
+ "package"
3060
+ ] },
3061
+ { tags: ["import"] },
3062
+ { tags: ["typedef"] },
3063
+ { tags: ["template"] },
3064
+ { tags: [
3065
+ "augments",
3066
+ "extends",
3067
+ "implements"
3068
+ ] },
3069
+ { tags: ["readonly"] },
3070
+ { tags: [
3071
+ "override",
3072
+ "requires",
3073
+ "mixes",
3074
+ "mixin",
3075
+ "mixinClass",
3076
+ "mixinFunction",
3077
+ "borrows",
3078
+ "constructs",
3079
+ "lends",
3080
+ "final",
3081
+ "global",
3082
+ "abstract",
3083
+ "virtual",
3084
+ "static",
3085
+ "private",
3086
+ "protected",
3087
+ "public",
3088
+ "access",
3089
+ "const",
3090
+ "constant",
3091
+ "variation",
3092
+ "var",
3093
+ "member",
3094
+ "memberof",
3095
+ "inner",
3096
+ "instance",
3097
+ "inheritdoc",
3098
+ "inheritDoc",
3099
+ "hideconstructor"
3100
+ ] },
3101
+ { tags: ["name"] },
3102
+ { tags: [
3103
+ "this",
3104
+ "interface",
3105
+ "enum",
3106
+ "event",
3107
+ "kind",
3108
+ "type",
3109
+ "alias",
3110
+ "external",
3111
+ "host",
3112
+ "async",
3113
+ "callback",
3114
+ "func",
3115
+ "function",
3116
+ "method",
3117
+ "class",
3118
+ "constructor",
3119
+ "generator",
3120
+ "fires",
3121
+ "emits",
3122
+ "listens"
3123
+ ] },
3124
+ { tags: ["prop", "property"] },
3125
+ { tags: [
3126
+ "param",
3127
+ "arg",
3128
+ "argument"
3129
+ ] },
3130
+ { tags: ["return", "returns"] },
3131
+ { tags: ["yield", "yields"] },
3132
+ { tags: ["throws", "exception"] },
3133
+ { tags: ["satisfies"] },
3134
+ { tags: ["default", "defaultvalue"] },
3135
+ { tags: ["exports"] },
3136
+ { tags: [
3137
+ "link",
3138
+ "see",
3139
+ "tutorial"
3140
+ ] },
3141
+ { tags: ["example"] }
3142
+ ];
1090
3143
  const jsdoc = async () => {
1091
- const { requiredAll: [pluginJsdoc], optional: [getJsdocProcessorPlugin] } = await resolvePackages(MODULES.jsdoc);
3144
+ const { requiredAll: [pluginJsdoc], optional: [jsdocProcessorModule] } = await resolvePackages(MODULES.jsdoc);
1092
3145
  if (!pluginJsdoc) return [];
3146
+ const definedTags = ["api", ...isModuleEnabled(MODULES.test) ? TAGS_BY_MODULE.test : []];
1093
3147
  const createSetupConfig = (isForTypescript) => ({
1094
3148
  name: buildConfigName(MAIN_SCOPES.JSDOC, `${SUB_SCOPES.SETUP}${isForTypescript ? "-typescript" : ""}`),
1095
3149
  ...isForTypescript ? void 0 : { plugins: { jsdoc: pluginJsdoc } },
@@ -1100,10 +3154,15 @@ const jsdoc = async () => {
1100
3154
  files: isForTypescript ? [GLOB_TS] : [GLOB_TS, ...GLOB_SCRIPT_FILES_WITHOUT_TS],
1101
3155
  rules: { ...isForTypescript ? {
1102
3156
  ...Object.fromEntries(objectEntries(pluginJsdoc.configs["flat/recommended-typescript-error"].rules ?? {}).map(([key, value]) => Object.is(pluginJsdoc.configs["flat/recommended-error"].rules?.[key], value) ? void 0 : [key, value]).filter(Boolean)),
3157
+ "jsdoc/check-tag-names": ["error", {
3158
+ definedTags,
3159
+ typed: true
3160
+ }],
1103
3161
  "jsdoc/require-param": "off",
1104
3162
  "jsdoc/require-returns": "off"
1105
3163
  } : {
1106
3164
  ...pluginJsdoc.configs["flat/recommended-error"].rules,
3165
+ "jsdoc/check-tag-names": ["error", { definedTags }],
1107
3166
  "jsdoc/check-indentation": ["error", { allowIndentedSections: true }],
1108
3167
  "jsdoc/check-line-alignment": "error",
1109
3168
  "jsdoc/check-syntax": "error",
@@ -1116,123 +3175,21 @@ const jsdoc = async () => {
1116
3175
  noSingleLineBlocks: true,
1117
3176
  singleLineTags: []
1118
3177
  }],
1119
- "jsdoc/no-bad-blocks": "error",
3178
+ "jsdoc/no-bad-blocks": ["error", { preventAllMultiAsteriskBlocks: true }],
1120
3179
  "jsdoc/no-blank-block-descriptions": "error",
1121
3180
  "jsdoc/no-blank-blocks": "error",
3181
+ "jsdoc/no-unnecessary-type-assertion": ["error", { preferConstToLiteralTuples: true }],
3182
+ "jsdoc/normalize-see-links": "error",
1122
3183
  "jsdoc/prefer-import-tag": "error",
1123
- "jsdoc/require-description-complete-sentence": "error",
1124
3184
  "jsdoc/require-asterisk-prefix": "error",
1125
- "jsdoc/require-hyphen-before-param-description": "error",
3185
+ "jsdoc/require-hyphen-before-param-description": ["error", "never"],
1126
3186
  "jsdoc/require-param-description": "off",
1127
3187
  "jsdoc/require-property-description": "off",
1128
3188
  "jsdoc/require-returns-description": "off",
1129
3189
  "jsdoc/require-jsdoc": "off",
1130
3190
  "jsdoc/require-template": "error",
1131
3191
  "jsdoc/require-throws": "error",
1132
- "jsdoc/sort-tags": ["error", { tagSequence: [
1133
- { tags: ["import"] },
1134
- { tags: [
1135
- "deprecated",
1136
- "ignore",
1137
- "since",
1138
- "version",
1139
- ["to", "do"].join("")
1140
- ] },
1141
- { tags: [
1142
- "author",
1143
- "copyright",
1144
- "license"
1145
- ] },
1146
- { tags: ["summary", "typeSummary"] },
1147
- { tags: [
1148
- "desc",
1149
- "description",
1150
- "classdesc"
1151
- ] },
1152
- { tags: [
1153
- "internal",
1154
- "namespace",
1155
- "category",
1156
- "package",
1157
- "file",
1158
- "fileoverview",
1159
- "overview",
1160
- "module",
1161
- "override",
1162
- "requires",
1163
- "implements",
1164
- "mixes",
1165
- "mixin",
1166
- "mixinClass",
1167
- "mixinFunction",
1168
- "borrows",
1169
- "constructs",
1170
- "lends",
1171
- "final",
1172
- "global",
1173
- "readonly",
1174
- "abstract",
1175
- "virtual",
1176
- "static",
1177
- "private",
1178
- "protected",
1179
- "public",
1180
- "access",
1181
- "const",
1182
- "constant",
1183
- "variation",
1184
- "var",
1185
- "member",
1186
- "memberof",
1187
- "inner",
1188
- "instance",
1189
- "inheritdoc",
1190
- "inheritDoc",
1191
- "hideconstructor"
1192
- ] },
1193
- { tags: [
1194
- "this",
1195
- "interface",
1196
- "enum",
1197
- "event",
1198
- "augments",
1199
- "extends",
1200
- "name",
1201
- "kind",
1202
- "type",
1203
- "alias",
1204
- "external",
1205
- "host",
1206
- "async",
1207
- "callback",
1208
- "func",
1209
- "function",
1210
- "method",
1211
- "class",
1212
- "constructor",
1213
- "generator",
1214
- "fires",
1215
- "emits",
1216
- "listens"
1217
- ] },
1218
- { tags: ["template"] },
1219
- { tags: ["typedef"] },
1220
- { tags: [
1221
- "param",
1222
- "arg",
1223
- "argument",
1224
- "prop",
1225
- "property"
1226
- ] },
1227
- { tags: ["return", "returns"] },
1228
- { tags: ["yield", "yields"] },
1229
- { tags: ["throws", "exception"] },
1230
- { tags: ["satisfies"] },
1231
- { tags: ["default", "defaultvalue"] },
1232
- { tags: ["exports"] },
1233
- { tags: ["see", "tutorial"] },
1234
- { tags: ["example"] }
1235
- ] }],
3192
+ "jsdoc/sort-tags": ["error", { tagSequence: TAG_SEQUENCE }],
1236
3193
  "jsdoc/tag-lines": [
1237
3194
  "error",
1238
3195
  "any",
@@ -1249,11 +3206,15 @@ const jsdoc = async () => {
1249
3206
  } }
1250
3207
  });
1251
3208
  const parser = await getTsEslintParserIfExists();
1252
- const examplePlugin = getJsdocProcessorPlugin ? getJsdocProcessorPlugin.getJsdocProcessorPlugin({
3209
+ const examplePlugin = jsdocProcessorModule ? jsdocProcessorModule.getJsdocProcessorPlugin({
1253
3210
  checkDefaults: true,
1254
3211
  checkExamples: true,
1255
3212
  checkParams: true,
1256
3213
  checkProperties: true,
3214
+ exampleCodeRegex: EXAMPLE_CODE_REGEX,
3215
+ matchingFileNameDefaults: "dummy.jsdoc-defaults.md/*.js",
3216
+ matchingFileNameParams: "dummy.jsdoc-params.md/*.js",
3217
+ matchingFileNameProperties: "dummy.jsdoc-properties.md/*.js",
1257
3218
  parser
1258
3219
  }) : void 0;
1259
3220
  return [
@@ -1273,6 +3234,9 @@ const jsdoc = async () => {
1273
3234
  };
1274
3235
  //#endregion
1275
3236
  //#region ../src/js/eslint/configs/json.ts
3237
+ /**
3238
+ * @internal @brnshkr/config/eslint
3239
+ */
1276
3240
  const TSCONFIG_FILES = ["**/tsconfig.json", "**/tsconfig.*.json"];
1277
3241
  const JSON_FILES_TO_TREAT_AS_JSONC = [...TSCONFIG_FILES, ".vscode/**/*.json"];
1278
3242
  const LANGUAGE_TO_GLOB_MAP = {
@@ -1358,6 +3322,7 @@ const getJsoncSortConfigs = () => [
1358
3322
  "lint-staged",
1359
3323
  "eslintConfig",
1360
3324
  "stylelint",
3325
+ "markdownlint-cli2",
1361
3326
  "prettier",
1362
3327
  "ava",
1363
3328
  "stackblitz",
@@ -1733,7 +3698,7 @@ const json = async () => {
1733
3698
  { emptyObjects: "never" }
1734
3699
  ],
1735
3700
  "jsonc/object-property-newline": "error",
1736
- "jsonc/quotes": ["error", "double"]
3701
+ "jsonc/quotes": "error"
1737
3702
  } : void 0
1738
3703
  }
1739
3704
  });
@@ -1756,8 +3721,14 @@ const json = async () => {
1756
3721
  };
1757
3722
  //#endregion
1758
3723
  //#region ../src/js/eslint/configs/markdown.ts
3724
+ /**
3725
+ * @internal @brnshkr/config/eslint
3726
+ */
1759
3727
  const extractRelevantValues = (identifier, configs, key) => {
1760
- for (const config of configs) if (config.name === `markdown/${key}` && config[identifier] !== void 0 && config[identifier] !== null) return config[identifier];
3728
+ for (const config of configs) {
3729
+ const value = config[identifier] ?? void 0;
3730
+ if (value !== void 0 && config.name === `markdown/${key}`) return value;
3731
+ }
1761
3732
  throw new Error(`Expected key "${key}" to be contained in given config.`);
1762
3733
  };
1763
3734
  const markdown = async (options) => {
@@ -1808,7 +3779,10 @@ const markdown = async (options) => {
1808
3779
  "summary",
1809
3780
  "sup",
1810
3781
  "var"
1811
- ] }]
3782
+ ] }],
3783
+ "markdown/no-space-in-emphasis": ["error", { checkStrikethrough: true }],
3784
+ "markdown/table-column-count": ["error", { checkMissingCells: true }],
3785
+ ...isModuleEnabled(MODULES.unicorn) ? { "unicorn/no-missing-local-resource": "error" } : void 0
1812
3786
  }
1813
3787
  },
1814
3788
  {
@@ -1822,10 +3796,12 @@ const markdown = async (options) => {
1822
3796
  "no-inline-comments": "off",
1823
3797
  "no-magic-numbers": "off",
1824
3798
  "import/no-default-export": "off",
3799
+ "import/no-extraneous-dependencies": "off",
1825
3800
  "import/unambiguous": "off",
1826
3801
  "node/no-missing-import": "off",
1827
3802
  "ts/no-redeclare": "off",
1828
3803
  "ts/no-unused-vars": "off",
3804
+ "unicorn/no-barrel-files": "off",
1829
3805
  "unused/no-unused-imports": "off",
1830
3806
  "unused/no-unused-vars": "off"
1831
3807
  }
@@ -1834,6 +3810,9 @@ const markdown = async (options) => {
1834
3810
  };
1835
3811
  //#endregion
1836
3812
  //#region ../src/js/eslint/configs/node.ts
3813
+ /**
3814
+ * @internal @brnshkr/config/eslint
3815
+ */
1837
3816
  const node = async (options) => {
1838
3817
  const { requiredAll: [pluginNode] } = await resolvePackages(MODULES.node);
1839
3818
  if (!pluginNode) return [];
@@ -1849,12 +3828,12 @@ const node = async (options) => {
1849
3828
  "node/exports-style": "error",
1850
3829
  "node/global-require": "error",
1851
3830
  "node/handle-callback-err": "error",
1852
- "node/no-missing-import": "error",
1853
3831
  "node/no-mixed-requires": "error",
1854
3832
  "node/no-new-require": "error",
1855
3833
  "node/no-path-concat": "error",
1856
3834
  "node/no-process-env": "error",
1857
3835
  "node/no-sync": "error",
3836
+ "node/no-unpublished-bin": "error",
1858
3837
  "node/prefer-global/buffer": "error",
1859
3838
  "node/prefer-global/console": "error",
1860
3839
  "node/prefer-global/crypto": "error",
@@ -1864,17 +3843,29 @@ const node = async (options) => {
1864
3843
  "node/prefer-global/timers": "error",
1865
3844
  "node/prefer-global/url-search-params": "error",
1866
3845
  "node/prefer-global/url": "error",
3846
+ "node/prefer-import/assert-strict": "error",
1867
3847
  "node/prefer-node-protocol": "error",
3848
+ "node/prefer-process-get-builtin-module": "error",
1868
3849
  "node/prefer-promises/dns": "error",
1869
- "node/prefer-promises/fs": "error"
3850
+ "node/prefer-promises/fs": "error",
3851
+ ...isModuleEnabled(MODULES.import) && doAllPackagesExist([ESLINT_PACKAGES.ESLINT_PLUGIN_IMPORT_X]) ? {
3852
+ "node/no-extraneous-import": "off",
3853
+ "node/no-extraneous-require": "off"
3854
+ } : void 0
1870
3855
  }
1871
3856
  }];
1872
3857
  };
1873
3858
  //#endregion
1874
3859
  //#region ../src/js/eslint/configs/unicorn.ts
3860
+ /**
3861
+ * @internal @brnshkr/config/eslint
3862
+ */
1875
3863
  const FILE_NAMES_TO_IGNORE = [
3864
+ "__mocks__",
3865
+ "__tests__",
1876
3866
  "ACKNOWLEDGMENTS.md",
1877
3867
  "ADOPTERS.md",
3868
+ "AGENT_INSTRUCTIONS.md",
1878
3869
  "AGENTS.md",
1879
3870
  "API_REFERENCE.md",
1880
3871
  "ARCHITECTURE.md",
@@ -1900,6 +3891,7 @@ const FILE_NAMES_TO_IGNORE = [
1900
3891
  "FAQ.md",
1901
3892
  "GOVERNANCE.md",
1902
3893
  "INSTALL.md",
3894
+ "INSTRUCTIONS.md",
1903
3895
  "ISSUE_TEMPLATE.md",
1904
3896
  "LICENSE.md",
1905
3897
  "LLMS.md",
@@ -1923,10 +3915,10 @@ const FILE_NAMES_TO_IGNORE = [
1923
3915
  "RELEASING.md",
1924
3916
  "RESEARCH.md",
1925
3917
  "ROADMAP.md",
1926
- "SKILL.md",
1927
- "SPEC.md",
1928
3918
  "SECURITY_POLICY.md",
1929
3919
  "SECURITY.md",
3920
+ "SKILL.md",
3921
+ "SPEC.md",
1930
3922
  "STYLE_GUIDE.md",
1931
3923
  "SUPPORT.md",
1932
3924
  "TESTING.md",
@@ -1936,6 +3928,21 @@ const FILE_NAMES_TO_IGNORE = [
1936
3928
  "UPGRADE_NOTES.md",
1937
3929
  "VERSIONING.md"
1938
3930
  ];
3931
+ const COMMENT_TERMS = [
3932
+ "commitlint",
3933
+ "JSDoc",
3934
+ "PHP",
3935
+ "PHPStan",
3936
+ "PHPUnit",
3937
+ "pnpm",
3938
+ "PostCSS",
3939
+ "SCSS",
3940
+ "Stylelint",
3941
+ "Symfony",
3942
+ "TOML",
3943
+ "TSDoc",
3944
+ "Vitest"
3945
+ ];
1939
3946
  const unicorn = async () => {
1940
3947
  const { requiredAll: [pluginUnicorn] } = await resolvePackages(MODULES.unicorn);
1941
3948
  if (!pluginUnicorn) return [];
@@ -1947,30 +3954,114 @@ const unicorn = async () => {
1947
3954
  files: GLOB_SCRIPT_FILES,
1948
3955
  rules: {
1949
3956
  ...pluginUnicorn.configs.recommended.rules,
1950
- "unicorn/better-regex": "error",
3957
+ "no-else-return": "off",
3958
+ "no-useless-concat": "off",
3959
+ "operator-assignment": "off",
3960
+ "unicorn/comment-content": ["error", { replacements: {
3961
+ [String.raw`\bapplication\b(?!/)`]: false,
3962
+ [String.raw`\bapplications\b`]: false,
3963
+ ...Object.fromEntries(COMMENT_TERMS.map((term) => [String.raw`\b${term}\b`, {
3964
+ replacement: term,
3965
+ caseSensitive: false
3966
+ }]))
3967
+ } }],
3968
+ "unicorn/consistent-boolean-name": "off",
3969
+ "unicorn/consistent-class-member-order": ["error", { order: [
3970
+ "public-field",
3971
+ "static-field",
3972
+ "private-field",
3973
+ "static-block",
3974
+ "constructor",
3975
+ "public-method",
3976
+ "static-method",
3977
+ "private-method"
3978
+ ] }],
3979
+ "unicorn/consistent-conditional-object-spread": ["error", "ternary"],
1951
3980
  "unicorn/consistent-destructuring": "error",
3981
+ "func-style": "off",
3982
+ "unicorn/consistent-function-style": ["error", {
3983
+ defaultExport: "arrow-function",
3984
+ namedExports: "arrow-function",
3985
+ namedFunctions: "arrow-function",
3986
+ objectProperties: "arrow-function",
3987
+ reassignedVariables: "arrow-function",
3988
+ typedVariables: "arrow-function"
3989
+ }],
1952
3990
  "unicorn/custom-error-definition": "error",
1953
3991
  "unicorn/filename-case": ["error", {
1954
3992
  case: "kebabCase",
1955
3993
  ignore: FILE_NAMES_TO_IGNORE
1956
3994
  }],
1957
3995
  "unicorn/require-post-message-target-origin": "error",
3996
+ "unicorn/iteration-fallback-style": ["error", "fallback"],
3997
+ "unicorn/name-replacements": ["error", {
3998
+ replacements: {
3999
+ application: false,
4000
+ applications: false,
4001
+ repository: false
4002
+ },
4003
+ ignore: ["[Ii]nheritDoc", String.raw`\.dist$`]
4004
+ }],
4005
+ "unicorn/no-accidental-bitwise-operator": "off",
4006
+ "unicorn/no-array-front-mutation": "error",
4007
+ "unicorn/no-array-reduce": ["error", { allowSimpleOperations: false }],
4008
+ "unicorn/no-array-reverse": ["error", { allowExpressionStatement: false }],
4009
+ "unicorn/no-array-sort": ["error", { allowExpressionStatement: false }],
4010
+ "unicorn/no-barrel-files": "error",
4011
+ "unicorn/no-instanceof-builtins": ["error", { useErrorIsError: true }],
4012
+ "unicorn/no-invalid-file-input-accept": "error",
1958
4013
  "unicorn/no-keyword-prefix": "error",
1959
- "unicorn/no-nested-ternay": "off",
1960
- "no-nested-ternary": "error",
4014
+ "unicorn/no-manually-wrapped-comments": "error",
4015
+ "unicorn/no-missing-local-resource": "error",
4016
+ "unicorn/no-negated-comparison": ["error", { checkLogicalExpressions: true }],
4017
+ "unicorn/no-null": ["error", { checkStrictEquality: true }],
4018
+ "unicorn/no-typeof-undefined": ["error", { checkGlobalVariables: true }],
4019
+ "unicorn/no-unsafe-dom-html": "error",
1961
4020
  "unicorn/no-unused-properties": "error",
1962
- "unicorn/prefer-json-parse-buffer": "error",
4021
+ "unicorn/numeric-separators-style": ["error", {
4022
+ binary: {
4023
+ minimumDigits: 9,
4024
+ groupLength: 8
4025
+ },
4026
+ hexadecimal: {
4027
+ minimumDigits: 3,
4028
+ groupLength: 2
4029
+ },
4030
+ number: {
4031
+ minimumDigits: 4,
4032
+ groupLength: 3,
4033
+ fractionGroupLength: 3
4034
+ },
4035
+ octal: {
4036
+ minimumDigits: 4,
4037
+ groupLength: 3
4038
+ }
4039
+ }],
4040
+ "unicorn/prefer-dispose": "error",
4041
+ "unicorn/prefer-error-is-error": "error",
4042
+ "unicorn/prefer-minimal-ternary": ["error", {
4043
+ checkComputedMemberAccess: true,
4044
+ checkVaryingBase: true
4045
+ }],
4046
+ "unicorn/prefer-queue-microtask": ["error", {
4047
+ checkSetImmediate: true,
4048
+ checkSetTimeout: true
4049
+ }],
4050
+ "unicorn/prefer-regexp-escape": "error",
4051
+ "unicorn/prefer-short-arrow-method": "error",
1963
4052
  "unicorn/prefer-switch": "off",
1964
- "unicorn/string-content": ["error", { patterns: {
1965
- "\\.\\.\\.": "…",
1966
- "^http:\\/\\/": String.raw`^https:\/\/`
1967
- } }],
1968
- "unicorn/text-encoding-identifier-case": ["error", { withDash: true }]
4053
+ "unicorn/require-css-escape": ["error", { checkAllSelectors: true }],
4054
+ "unicorn/string-content": ["error", { patterns: { "\\.\\.\\.": "…" } }],
4055
+ "unicorn/text-encoding-identifier-case": ["error", { withDash: true }],
4056
+ "unicorn/try-complexity": "error"
1969
4057
  }
1970
4058
  }];
1971
4059
  };
1972
4060
  //#endregion
1973
4061
  //#region ../src/js/eslint/configs/overrides.ts
4062
+ /**
4063
+ * @internal @brnshkr/config/eslint
4064
+ */
1974
4065
  const jsOverrides = [{
1975
4066
  name: buildConfigName(MAIN_SCOPES.OVERRIDES, `${MAIN_SCOPES.JAVASCRIPT}/scripts`),
1976
4067
  files: ["scripts/**/*.?(c)js"],
@@ -2018,6 +4109,25 @@ const tsOverrides = isModuleEnabled(MODULES.typescript) ? [{
2018
4109
  "import/no-named-as-default-member": "off"
2019
4110
  }
2020
4111
  }] : [];
4112
+ const importOverrides = isModuleEnabled(MODULES.import) && isModuleEnabled(MODULES.typescript) ? [{
4113
+ name: buildConfigName(MAIN_SCOPES.OVERRIDES, `${MAIN_SCOPES.IMPORT}/${MAIN_SCOPES.TYPESCRIPT}`),
4114
+ files: [GLOB_TS],
4115
+ rules: {
4116
+ "import/default": "off",
4117
+ "import/named": "off",
4118
+ "import/no-named-as-default-member": "off",
4119
+ "import/no-unresolved": "off"
4120
+ }
4121
+ }] : [];
4122
+ const buildTypeAwareImportOverrides = (typescriptOptions) => {
4123
+ const resolvedOptions = typeof typescriptOptions === "object" ? typescriptOptions : void 0;
4124
+ return isModuleEnabled(MODULES.import) && isModuleEnabled(MODULES.typescript) && (resolvedOptions?.typeAware ?? doesTsConfigExist(resolveTsConfigPath(resolvedOptions))) !== false ? [{
4125
+ name: buildConfigName(MAIN_SCOPES.OVERRIDES, `${MAIN_SCOPES.IMPORT}/type-aware`),
4126
+ files: [GLOB_TS],
4127
+ ignores: DEFAULT_TYPE_AWARE_IGNORES,
4128
+ rules: { "import/no-deprecated": "off" }
4129
+ }] : [];
4130
+ };
2021
4131
  const testOverrides = isModuleEnabled(MODULES.test) ? [{
2022
4132
  name: buildConfigName(MAIN_SCOPES.OVERRIDES, `${MAIN_SCOPES.TEST}/general`),
2023
4133
  files: GLOB_TEST_FILES,
@@ -2028,46 +4138,62 @@ const unicornOverrides = isModuleEnabled(MODULES.unicorn) ? [{
2028
4138
  files: [...GLOB_SCRIPT_FILES.map((glob) => `**/classes/${glob}`), ...GLOB_SCRIPT_FILES.map((glob) => `**/errors/${glob}`)],
2029
4139
  rules: { "unicorn/filename-case": ["error", {
2030
4140
  case: "pascalCase",
4141
+ checkDirectories: false,
2031
4142
  ignore: FILE_NAMES_TO_IGNORE
2032
4143
  }] }
2033
4144
  }] : [];
2034
- const jsdocOverrides = isModuleEnabled(MODULES.jsdoc) ? [{
2035
- name: buildConfigName(MAIN_SCOPES.OVERRIDES, `${MAIN_SCOPES.JSDOC}/${SUB_SCOPES.EXAMPLES}`),
2036
- files: [GLOB_EXAMPLES],
2037
- rules: {
2038
- "no-console": "off",
2039
- "no-undef": "off",
2040
- "no-unused-expressions": "off",
2041
- "no-unused-vars": "off",
2042
- "node/no-missing-import": "off",
2043
- "node/no-missing-require": "off",
2044
- strict: "off",
2045
- "import/no-unresolved": "off",
2046
- "import/unambiguous": "off",
2047
- "style/eol-last": "off",
2048
- "style/no-multiple-empty-lines": "off",
2049
- "ts/no-unused-expressions": "off",
2050
- "ts/no-unused-vars": "off",
2051
- "unused/no-unused-imports": "off",
2052
- "unused/no-unused-vars": "off"
2053
- }
2054
- }, {
2055
- name: buildConfigName(MAIN_SCOPES.OVERRIDES, `${MAIN_SCOPES.JSDOC}/default-expressions`),
2056
- files: [
2057
- "**/*.jsdoc-defaults",
2058
- "**/*.jsdoc-params",
2059
- "**/*.jsdoc-properties"
2060
- ],
2061
- rules: {
2062
- "no-empty-function": "off",
2063
- "no-new": "off",
2064
- strict: "off",
2065
- "import/unambiguous": "off",
2066
- "style/eol-last": "off",
2067
- "style/no-extra-parens": "off",
2068
- "style/semi": "off"
4145
+ const jsdocOverrides = isModuleEnabled(MODULES.jsdoc) ? [
4146
+ ...isModuleEnabled(MODULES[packageOrganization]) ? [{
4147
+ name: buildConfigName(MAIN_SCOPES.OVERRIDES, `${MAIN_SCOPES.JSDOC}/internal-tag`),
4148
+ files: GLOB_SCRIPT_FILES,
4149
+ settings: { jsdoc: { structuredTags: { internal: {
4150
+ name: "text",
4151
+ type: false
4152
+ } } } },
4153
+ rules: { "jsdoc/empty-tags": "off" }
4154
+ }] : [],
4155
+ {
4156
+ name: buildConfigName(MAIN_SCOPES.OVERRIDES, `${MAIN_SCOPES.JSDOC}/${SUB_SCOPES.EXAMPLES}`),
4157
+ files: [GLOB_EXAMPLES],
4158
+ rules: {
4159
+ "no-console": "off",
4160
+ "no-undef": "off",
4161
+ "no-unused-expressions": "off",
4162
+ "no-unused-vars": "off",
4163
+ "node/no-missing-import": "off",
4164
+ "node/no-missing-require": "off",
4165
+ strict: "off",
4166
+ "import/no-unresolved": "off",
4167
+ "import/no-self-import": "off",
4168
+ "import/unambiguous": "off",
4169
+ "style/eol-last": "off",
4170
+ "style/no-multiple-empty-lines": "off",
4171
+ "ts/no-unused-expressions": "off",
4172
+ "ts/no-unused-vars": "off",
4173
+ "unicorn/no-null": "off",
4174
+ "unicorn/no-useless-undefined": "off",
4175
+ "unused/no-unused-imports": "off",
4176
+ "unused/no-unused-vars": "off"
4177
+ }
4178
+ },
4179
+ {
4180
+ name: buildConfigName(MAIN_SCOPES.OVERRIDES, `${MAIN_SCOPES.JSDOC}/default-expressions`),
4181
+ files: [
4182
+ "**/*.jsdoc-defaults.md/*.js",
4183
+ "**/*.jsdoc-params.md/*.js",
4184
+ "**/*.jsdoc-properties.md/*.js"
4185
+ ],
4186
+ rules: {
4187
+ "no-empty-function": "off",
4188
+ "no-new": "off",
4189
+ strict: "off",
4190
+ "import/unambiguous": "off",
4191
+ "style/eol-last": "off",
4192
+ "style/no-extra-parens": "off",
4193
+ "style/semi": "off"
4194
+ }
2069
4195
  }
2070
- }] : [];
4196
+ ] : [];
2071
4197
  const svelteOverrides = isModuleEnabled(MODULES.svelte) ? [{
2072
4198
  name: buildConfigName(MAIN_SCOPES.OVERRIDES, `${MAIN_SCOPES.SVELTE}/general`),
2073
4199
  files: [GLOB_SVELTE],
@@ -2079,11 +4205,14 @@ const svelteOverrides = isModuleEnabled(MODULES.svelte) ? [{
2079
4205
  "svelte/comment-directive": ["error", { reportUnusedDisableDirectives: true }],
2080
4206
  "svelte/system": "error"
2081
4207
  }
2082
- }, {
4208
+ }, ...isModuleEnabled(MODULES.unicorn) ? [{
2083
4209
  name: buildConfigName(MAIN_SCOPES.OVERRIDES, `${MAIN_SCOPES.SVELTE}/components`),
2084
4210
  files: [`**/components/${GLOB_SVELTE}`],
2085
- rules: { "unicorn/filename-case": ["error", { case: "pascalCase" }] }
2086
- }] : [];
4211
+ rules: { "unicorn/filename-case": ["error", {
4212
+ case: "pascalCase",
4213
+ checkDirectories: false
4214
+ }] }
4215
+ }] : []] : [];
2087
4216
  const tomlOverrides = isModuleEnabled(MODULES.toml) ? [{
2088
4217
  name: buildConfigName(MAIN_SCOPES.OVERRIDES, `${MAIN_SCOPES.TOML}/general`),
2089
4218
  files: [GLOB_TOML],
@@ -2096,10 +4225,16 @@ const yamlOverrides = isModuleEnabled(MODULES.yaml) ? [{
2096
4225
  "no-irregular-whitespace": "off",
2097
4226
  "no-unused-vars": "off"
2098
4227
  }
4228
+ }, {
4229
+ name: buildConfigName(MAIN_SCOPES.OVERRIDES, `${MAIN_SCOPES.YAML}/extension`),
4230
+ files: GLOB_YAML_FIXED_EXTENSION_FILES,
4231
+ rules: { "yaml/file-extension": "off" }
2099
4232
  }] : [];
2100
- const overrides = () => [
4233
+ const overrides = (typescriptOptions) => [
2101
4234
  ...jsOverrides,
2102
4235
  ...tsOverrides,
4236
+ ...importOverrides,
4237
+ ...buildTypeAwareImportOverrides(typescriptOptions),
2103
4238
  ...testOverrides,
2104
4239
  ...unicornOverrides,
2105
4240
  ...jsdocOverrides,
@@ -2118,12 +4253,16 @@ const overrides = () => [
2118
4253
  "import/no-default-export": "off",
2119
4254
  "import/no-rename-default": "off",
2120
4255
  "import/no-named-as-default-member": "off",
2121
- "node/no-sync": "off"
4256
+ "node/no-sync": "off",
4257
+ "unicorn/no-barrel-files": "off"
2122
4258
  }
2123
4259
  }
2124
4260
  ];
2125
4261
  //#endregion
2126
4262
  //#region ../src/js/eslint/configs/perfectionist.ts
4263
+ /**
4264
+ * @internal @brnshkr/config/eslint
4265
+ */
2127
4266
  const perfectionist = async () => {
2128
4267
  const { requiredAll: [pluginPerfectionist] } = await resolvePackages(MODULES.perfectionist);
2129
4268
  if (!pluginPerfectionist) return [];
@@ -2145,6 +4284,9 @@ const perfectionist = async () => {
2145
4284
  };
2146
4285
  //#endregion
2147
4286
  //#region ../src/js/eslint/configs/regexp.ts
4287
+ /**
4288
+ * @internal @brnshkr/config/eslint
4289
+ */
2148
4290
  const regexp = async () => {
2149
4291
  const { requiredAll: [pluginRegExp] } = await resolvePackages(MODULES.regexp);
2150
4292
  if (!pluginRegExp) return [];
@@ -2159,10 +4301,8 @@ const regexp = async () => {
2159
4301
  "regexp/confusing-quantifier": "error",
2160
4302
  "regexp/grapheme-string-literal": "error",
2161
4303
  "regexp/letter-case": ["error", {
2162
- caseInsensitive: "lowercase",
2163
- unicodeEscape: "lowercase",
2164
- hexadecimalEscape: "lowercase",
2165
- controlEscape: "uppercase"
4304
+ unicodeEscape: "uppercase",
4305
+ hexadecimalEscape: "uppercase"
2166
4306
  }],
2167
4307
  "regexp/no-control-character": "error",
2168
4308
  "regexp/no-empty-alternative": "error",
@@ -2187,7 +4327,6 @@ const regexp = async () => {
2187
4327
  "regexp/sort-character-class-elements": "error",
2188
4328
  "regexp/unicode-escape": "error",
2189
4329
  "regexp/unicode-property": ["error", {
2190
- generalCategory: "never",
2191
4330
  key: "short",
2192
4331
  property: "long"
2193
4332
  }]
@@ -2196,21 +4335,22 @@ const regexp = async () => {
2196
4335
  };
2197
4336
  //#endregion
2198
4337
  //#region ../src/js/eslint/configs/style.ts
4338
+ /**
4339
+ * @internal @brnshkr/config/eslint
4340
+ */
2199
4341
  const style = async () => {
2200
4342
  const { requiredAll: [pluginStyle] } = await resolvePackages(MODULES.style);
2201
4343
  if (!pluginStyle) return [];
2202
4344
  const styleConfig = pluginStyle.configs.customize({
2203
- jsx: true,
2204
4345
  semi: true,
2205
4346
  indent: 2,
2206
4347
  quotes: QUOTES,
2207
4348
  quoteProps: "as-needed",
2208
4349
  arrowParens: true,
2209
- blockSpacing: true,
2210
- braceStyle: "1tbs",
2211
- commaDangle: "always-multiline"
4350
+ braceStyle: "1tbs"
2212
4351
  });
2213
- const indentRuleConfig = Array.isArray(styleConfig.rules?.["@stylistic/indent"]) && typeof styleConfig.rules["@stylistic/indent"][2] === "object" && styleConfig.rules["@stylistic/indent"][2] !== null ? styleConfig.rules["@stylistic/indent"][2] : void 0;
4352
+ const indentRuleEntry = styleConfig.rules?.["@stylistic/indent"];
4353
+ const indentRuleConfig = Array.isArray(indentRuleEntry) && isPlainObject(indentRuleEntry[2]) ? indentRuleEntry[2] : void 0;
2214
4354
  return [{
2215
4355
  name: buildConfigName(MAIN_SCOPES.STYLE, SUB_SCOPES.SETUP),
2216
4356
  plugins: { style: pluginStyle }
@@ -2517,6 +4657,9 @@ const style = async () => {
2517
4657
  };
2518
4658
  //#endregion
2519
4659
  //#region ../src/js/eslint/configs/svelte.ts
4660
+ /**
4661
+ * @internal @brnshkr/config/eslint
4662
+ */
2520
4663
  const extractRelevantConfig = (configs, key) => {
2521
4664
  for (const config of configs) if (config.name === `svelte:${key}` && config.rules) return config;
2522
4665
  throw new Error(`Expected key "${key}" to be contained in given config.`);
@@ -2557,14 +4700,11 @@ const svelte = async () => {
2557
4700
  style: ["scss", null]
2558
4701
  }],
2559
4702
  "svelte/button-has-type": "error",
2560
- "svelte/consistent-selector-style": ["error", {
2561
- checkGlobal: false,
2562
- style: [
2563
- "type",
2564
- "class",
2565
- "id"
2566
- ]
2567
- }],
4703
+ "svelte/consistent-selector-style": ["error", { style: [
4704
+ "type",
4705
+ "class",
4706
+ "id"
4707
+ ] }],
2568
4708
  "svelte/derived-has-same-inputs-outputs": "error",
2569
4709
  "svelte/experimental-require-strict-events": "error",
2570
4710
  "svelte/experimental-require-slot-types": "error",
@@ -2578,25 +4718,29 @@ const svelte = async () => {
2578
4718
  }],
2579
4719
  "style/indent": "off",
2580
4720
  "svelte/indent": ["error", { indent: 2 }],
2581
- "svelte/max-attributes-per-line": ["error", {
2582
- multiline: 1,
2583
- singleline: 3
2584
- }],
4721
+ "svelte/max-attributes-per-line": ["error", { singleline: 3 }],
2585
4722
  "svelte/mustache-spacing": "error",
2586
4723
  "svelte/no-add-event-listener": "error",
4724
+ "svelte/no-at-const-tags": "error",
2587
4725
  "svelte/no-at-debug-tags": "error",
4726
+ "svelte/no-bind-value-on-checkable-inputs": "error",
4727
+ "svelte/no-conflicting-module-names": "error",
2588
4728
  "svelte/no-extra-reactive-curlies": "error",
2589
4729
  "svelte/no-ignored-unsubscribe": "error",
2590
- "svelte/no-inline-styles": "error",
4730
+ "svelte/no-inline-styles": ["error", { allowTransitions: false }],
2591
4731
  "svelte/no-inspect": "error",
4732
+ "svelte/no-nested-style-tag": "error",
2592
4733
  "svelte/no-spaces-around-equal-signs-in-attribute": "error",
2593
4734
  "svelte/no-target-blank": "error",
2594
4735
  "svelte/no-top-level-browser-globals": "error",
2595
4736
  "style/no-trailing-spaces": "off",
2596
4737
  "svelte/no-trailing-spaces": "error",
4738
+ "svelte/no-unused-props": ["error", { checkImportedTypes: true }],
4739
+ "svelte/prefer-attribute-interpolation": "error",
2597
4740
  "svelte/prefer-class-directive": "error",
2598
4741
  "prefer-const": "off",
2599
4742
  "svelte/prefer-const": "error",
4743
+ "svelte/prefer-derived-over-derived-by": "error",
2600
4744
  "svelte/prefer-destructured-store-props": "error",
2601
4745
  "svelte/prefer-style-directive": "error",
2602
4746
  "svelte/require-event-prefix": "error",
@@ -2615,6 +4759,9 @@ const svelte = async () => {
2615
4759
  };
2616
4760
  //#endregion
2617
4761
  //#region ../src/js/eslint/configs/test.ts
4762
+ /**
4763
+ * @internal @brnshkr/config/eslint
4764
+ */
2618
4765
  const test = async () => {
2619
4766
  const { requiredAll: [pluginVitest] } = await resolvePackages(MODULES.test);
2620
4767
  if (!pluginVitest) return [];
@@ -2654,7 +4801,7 @@ const test = async () => {
2654
4801
  "test/prefer-hooks-on-top": "error",
2655
4802
  "test/prefer-import-in-mock": "error",
2656
4803
  "test/prefer-importing-vitest-globals": "error",
2657
- "test/prefer-lowercase-title": "error",
4804
+ "test/prefer-lowercase-title": ["error", { ignore: ["describe"] }],
2658
4805
  "test/prefer-mock-promise-shorthand": "error",
2659
4806
  "test/prefer-snapshot-hint": "error",
2660
4807
  "test/prefer-spy-on": "error",
@@ -2668,13 +4815,17 @@ const test = async () => {
2668
4815
  "test/prefer-vi-mocked": "error",
2669
4816
  "test/require-awaited-expect-poll": "error",
2670
4817
  "test/require-hook": "error",
2671
- "test/require-mock-type-parameters": "error",
4818
+ "test/require-mock-type-parameters": ["error", { checkImportFunctions: true }],
4819
+ "test/valid-expect": ["error", { alwaysAwait: true }],
2672
4820
  "test/warn-todo": "error"
2673
4821
  }
2674
4822
  }];
2675
4823
  };
2676
4824
  //#endregion
2677
4825
  //#region ../src/js/eslint/configs/toml.ts
4826
+ /**
4827
+ * @internal @brnshkr/config/eslint
4828
+ */
2678
4829
  const toml = async () => {
2679
4830
  const { requiredAll: [pluginToml] } = await resolvePackages(MODULES.toml);
2680
4831
  if (!pluginToml) return [];
@@ -2700,6 +4851,9 @@ const toml = async () => {
2700
4851
  };
2701
4852
  //#endregion
2702
4853
  //#region ../src/js/eslint/configs/yaml.ts
4854
+ /**
4855
+ * @internal @brnshkr/config/eslint
4856
+ */
2703
4857
  const yaml = async () => {
2704
4858
  const { requiredAll: [pluginYaml] } = await resolvePackages(MODULES.yaml);
2705
4859
  if (!pluginYaml) return [];
@@ -2712,7 +4866,6 @@ const yaml = async () => {
2712
4866
  language: "yaml/yaml",
2713
4867
  rules: {
2714
4868
  ...renameRules(pluginYaml.configs.standard[2]?.rules, { yml: "yaml" }),
2715
- "yaml/block-mapping-colon-indicator-newline": ["error", "never"],
2716
4869
  "yaml/file-extension": "error",
2717
4870
  "yaml/flow-mapping-curly-spacing": [
2718
4871
  "error",
@@ -2720,23 +4873,21 @@ const yaml = async () => {
2720
4873
  { emptyObjects: "never" }
2721
4874
  ],
2722
4875
  "yaml/indent": ["error", 2],
2723
- "yaml/no-multiple-empty-lines": "error",
2724
- "yaml/no-trailing-zeros": "error",
2725
- "yaml/quotes": ["error", {
2726
- avoidEscape: true,
2727
- prefer: QUOTES
2728
- }],
4876
+ "yaml/no-boolean-key": "error",
4877
+ "yaml/quotes": ["error", { prefer: QUOTES }],
2729
4878
  "yaml/require-string-key": "error"
2730
4879
  }
2731
4880
  }];
2732
4881
  };
2733
4882
  //#endregion
2734
4883
  //#region ../src/js/eslint/configs/index.ts
4884
+ /**
4885
+ * @internal @brnshkr/config/eslint
4886
+ */
2735
4887
  const configs = {
2736
4888
  [packageOrganization]: builtinConfig[packageOrganization],
2737
4889
  comments,
2738
4890
  css,
2739
- gitignore,
2740
4891
  ignores,
2741
4892
  import: imports,
2742
4893
  javascript,
@@ -2757,12 +4908,49 @@ const configs = {
2757
4908
  };
2758
4909
  //#endregion
2759
4910
  //#region ../src/js/eslint/index.ts
4911
+ /**
4912
+ * Build the `@brnshkr` ESLint flat config composer.
4913
+ *
4914
+ * Merges sensible defaults for every supported module (TypeScript, Svelte, JSDoc, etc.) with user
4915
+ * overrides and any additional flat configs. Modules whose optional peer dependency is not installed
4916
+ * are skipped automatically, so consumers only opt into what they use.
4917
+ *
4918
+ * @api
4919
+ *
4920
+ * @param optionsAndGlobalConfig per-module toggles and global flat-config fields (`files`,
4921
+ * `ignores`, `languageOptions`, etc.) merged with the defaults
4922
+ * @param additionalConfigs extra flat-config entries appended after the built-in ones
4923
+ *
4924
+ * @returns configured {@link FlatConfigComposer} that resolves to the final flat-config array
4925
+ *
4926
+ * @see https://github.com/brnshkr/config/blob/master/docs/js/eslint/index.md
4927
+ *
4928
+ * @example
4929
+ * ```js
4930
+ * import { getConfig } from '@brnshkr/config/eslint';
4931
+ *
4932
+ * getConfig(undefined);
4933
+ * getConfig({});
4934
+ *
4935
+ * getConfig({
4936
+ * svelte: false,
4937
+ * files: ['src/**'],
4938
+ * }, {
4939
+ * files: ['scripts/**'],
4940
+ * rules: { 'no-console': 'off' },
4941
+ * });
4942
+ *
4943
+ * getConfig(undefined, {
4944
+ * files: ['scripts/**'],
4945
+ * rules: { 'no-console': 'off' },
4946
+ * });
4947
+ * ```
4948
+ */
2760
4949
  const getConfig = (optionsAndGlobalConfig, ...additionalConfigs) => {
2761
4950
  const resolvedOptions = {
2762
4951
  [packageOrganization]: isModuleEnabledByDefault(MODULES[packageOrganization]),
2763
4952
  comments: isModuleEnabledByDefault(MODULES.comments),
2764
4953
  css: isModuleEnabledByDefault(MODULES.css),
2765
- gitignore: isModuleEnabledByDefault(MODULES.gitignore),
2766
4954
  import: isModuleEnabledByDefault(MODULES.import),
2767
4955
  jsdoc: isModuleEnabledByDefault(MODULES.jsdoc),
2768
4956
  json: isModuleEnabledByDefault(MODULES.json),
@@ -2781,8 +4969,7 @@ const getConfig = (optionsAndGlobalConfig, ...additionalConfigs) => {
2781
4969
  };
2782
4970
  setModuleEnabled(MODULES[packageOrganization], resolvedOptions[packageOrganization]);
2783
4971
  setModuleEnabled(MODULES.comments, resolvedOptions.comments);
2784
- setModuleEnabled(MODULES.css, resolvedOptions.css);
2785
- setModuleEnabled(MODULES.gitignore, resolvedOptions.gitignore !== false);
4972
+ setModuleEnabled(MODULES.css, resolvedOptions.css !== false);
2786
4973
  setModuleEnabled(MODULES.import, resolvedOptions.import);
2787
4974
  setModuleEnabled(MODULES.jsdoc, resolvedOptions.jsdoc);
2788
4975
  setModuleEnabled(MODULES.json, resolvedOptions.json);
@@ -2802,7 +4989,6 @@ const getConfig = (optionsAndGlobalConfig, ...additionalConfigs) => {
2802
4989
  composer.append(...configsToAppend);
2803
4990
  };
2804
4991
  appendToComposer(configs.ignores(resolvedOptions.ignores));
2805
- if (isModuleEnabled(MODULES.gitignore)) appendToComposer(configs.gitignore(typeof resolvedOptions.gitignore === "object" ? resolvedOptions.gitignore : { strict: false }));
2806
4992
  appendToComposer(configs.javascript());
2807
4993
  if (isModuleEnabled(MODULES.typescript)) appendToComposer(configs.typescript(typeof resolvedOptions.typescript === "object" ? resolvedOptions.typescript : void 0));
2808
4994
  if (isModuleEnabled(MODULES.test)) appendToComposer(configs.test());
@@ -2819,12 +5005,17 @@ const getConfig = (optionsAndGlobalConfig, ...additionalConfigs) => {
2819
5005
  if (isModuleEnabled(MODULES.json)) appendToComposer(configs.json());
2820
5006
  if (isModuleEnabled(MODULES.toml)) appendToComposer(configs.toml());
2821
5007
  if (isModuleEnabled(MODULES.yaml)) appendToComposer(configs.yaml());
2822
- if (isModuleEnabled(MODULES.css)) appendToComposer(configs.css());
5008
+ if (isModuleEnabled(MODULES.css)) appendToComposer(configs.css(typeof resolvedOptions.css === "object" ? resolvedOptions.css : void 0));
2823
5009
  if (isModuleEnabled(MODULES.markdown)) appendToComposer(configs.markdown(typeof resolvedOptions.markdown === "object" ? resolvedOptions.markdown : void 0));
2824
- appendToComposer(configs.overrides());
5010
+ appendToComposer(configs.overrides(resolvedOptions.typescript));
2825
5011
  appendToComposer(...getUserConfigs(resolvedOptions, additionalConfigs));
2826
5012
  return composer;
2827
5013
  };
5014
+ /**
5015
+ * Default-exported pre-built composer for direct re-export from an ESLint flat-config file.
5016
+ *
5017
+ * @api
5018
+ */
2828
5019
  var eslint_default = getConfig();
2829
5020
  //#endregion
2830
5021
  export { eslint_default as default, getConfig };