@brnshkr/config 0.0.1-beta.2 → 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 +177 -228
  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 +3897 -1383
  20. package/dist/eslint/index.mjs +2784 -419
  21. package/dist/markdownlint/index.d.mts +2304 -0
  22. package/dist/markdownlint/index.mjs +440 -0
  23. package/dist/shared.mjs +405 -65
  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 -29
  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 objectFromEntries, f as objectKeys, h as version, i as resolvePackagesSharedAsynchronously, l as objectEntries, m as packageOrganizationUpper, n as GLOB_IGNORES, o as ESLINT_PACKAGES, p as packageOrganization, r as isModuleEnabledByDefault, t as QUOTES, u as objectFreeze } 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
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { includeIgnoreFile } from "eslint/config";
3
6
  import jsEslint from "@eslint/js";
4
7
  import confusingBrowserGlobals from "confusing-browser-globals";
5
8
  import globals from "globals";
6
- import fs from "node:fs/promises";
7
- import path from "node:path";
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,1711 @@ 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"
131
+ ];
132
+ const GLOB_TEST_FILES = [...GLOB_TEST_FILES$1, ...GLOB_BENCHMARK_FILES];
133
+ //#endregion
134
+ //#region ../src/js/eslint/utils/tsconfig.ts
135
+ /**
136
+ * @internal @brnshkr/config/eslint
137
+ */
138
+ const DEFAULT_TSCONFIG_FILENAME = "tsconfig.json";
139
+ const JSONC_TOKEN_PATTERN = /"(?:[^"\\]|\\.)*"|\/\/[^\n]*|\/\*[\s\S]*?\*\//gv;
140
+ const TRAILING_COMMA_PATTERN = /,(?=\s*[\]\}])/gv;
141
+ const stripJsonc = (input) => input.replaceAll(JSONC_TOKEN_PATTERN, (match) => match.startsWith("\"") ? match : "").replaceAll(TRAILING_COMMA_PATTERN, "");
142
+ const parseTsConfigFile = (filePath) => {
143
+ try {
144
+ const content = fs.readFileSync(filePath, "utf-8");
145
+ return JSON.parse(stripJsonc(content));
146
+ } catch {
147
+ return;
148
+ }
149
+ };
150
+ const resolveExtends = (parentConfig, fromDirectory) => {
151
+ if (parentConfig.startsWith(".") || path.isAbsolute(parentConfig)) {
152
+ const resolved = path.resolve(fromDirectory, parentConfig);
153
+ return path.extname(resolved) === "" ? `${resolved}.json` : resolved;
154
+ }
155
+ return path.resolve(fromDirectory, "node_modules", parentConfig);
156
+ };
157
+ const normalizeExtends = (raw) => {
158
+ if (raw.extends === void 0) return [];
159
+ return Array.isArray(raw.extends) ? raw.extends : [raw.extends];
160
+ };
161
+ const loadInternal = (filePath, visitedPaths) => {
162
+ if (visitedPaths.has(filePath)) return;
163
+ visitedPaths.add(filePath);
164
+ const parsedConfig = parseTsConfigFile(filePath);
165
+ if (!parsedConfig) return;
166
+ const fromDirectory = path.dirname(filePath);
167
+ let compilerOptions = {};
168
+ for (const value of normalizeExtends(parsedConfig)) compilerOptions = {
169
+ ...compilerOptions,
170
+ ...loadInternal(resolveExtends(value, fromDirectory), visitedPaths)?.compilerOptions
171
+ };
172
+ return { compilerOptions: {
173
+ ...compilerOptions,
174
+ ...parsedConfig.compilerOptions
175
+ } };
176
+ };
177
+ const cache = /* @__PURE__ */ new Map();
178
+ const toPosix = (filePath) => filePath.replaceAll("\\", "/");
179
+ const loadTsConfigPaths = (tsConfigPath) => {
180
+ const absolutePath = path.resolve(tsConfigPath);
181
+ if (cache.has(absolutePath)) return cache.get(absolutePath);
182
+ const mergedConfig = loadInternal(absolutePath, /* @__PURE__ */ new Set());
183
+ const paths = mergedConfig?.compilerOptions?.paths;
184
+ if (!paths) {
185
+ cache.set(absolutePath, void 0);
186
+ return;
187
+ }
188
+ const baseUrl = path.resolve(path.dirname(absolutePath), mergedConfig.compilerOptions?.baseUrl ?? ".");
189
+ const result = objectFromEntries(objectEntries(paths).map(([pattern, targets]) => [pattern, targets.map((target) => toPosix(path.resolve(baseUrl, target)))]));
190
+ cache.set(absolutePath, result);
191
+ return result;
192
+ };
193
+ const doesTsConfigExist = (tsConfigPath) => {
194
+ try {
195
+ fs.accessSync(tsConfigPath, fs.constants.R_OK);
196
+ return true;
197
+ } catch {
198
+ return false;
199
+ }
200
+ };
201
+ const resolveTsConfigPath = (typescriptOptions) => {
202
+ let tsconfig = DEFAULT_TSCONFIG_FILENAME;
203
+ if (typescriptOptions) {
204
+ if (typeof typescriptOptions.typeAware === "string") tsconfig = typescriptOptions.typeAware;
205
+ else if (typeof typescriptOptions.typeAware === "object") tsconfig = typescriptOptions.typeAware.tsconfig ?? DEFAULT_TSCONFIG_FILENAME;
206
+ }
207
+ return path.resolve(process.cwd(), tsconfig);
208
+ };
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"
119
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
1699
+ //#region ../src/js/eslint/configs/builtin/require-import-alias.ts
1700
+ /**
1701
+ * @internal @brnshkr/config/eslint
1702
+ */
1703
+ const MESSAGE_ID_EXPECTED_ALIAS = "expectedAlias";
1704
+ const MESSAGE_ID_MISSING_ALIAS = "missingAlias";
1705
+ const WILDCARD_SUFFIX = "/*";
1706
+ const RELATIVE_SPECIFIER_PREFIXES = ["./", "../"];
1707
+ const resolveAliases = (options) => options.aliases ?? loadTsConfigPaths(options.tsConfigPath ?? resolveTsConfigPath()) ?? {};
1708
+ const buildAliasMappings = (aliases) => objectEntries(aliases).filter(([pattern]) => pattern.endsWith(WILDCARD_SUFFIX)).flatMap(([pattern, targets]) => {
1709
+ const prefix = pattern.slice(0, -2);
1710
+ return targets.filter((target) => target.endsWith(WILDCARD_SUFFIX)).map((target) => ({
1711
+ prefix,
1712
+ baseDirectory: toPosix$1(target).slice(0, -2)
1713
+ }));
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) => {
1727
+ for (const { prefix, baseDirectory } of mappings) {
1728
+ if (source === prefix) return baseDirectory;
1729
+ if (source.startsWith(`${prefix}/`)) return `${baseDirectory}/${source.slice(prefix.length + 1)}`;
1730
+ }
1731
+ };
1732
+ const isRelativeSpecifier = (source) => RELATIVE_SPECIFIER_PREFIXES.some((prefix) => source.startsWith(prefix));
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
+ };
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
+ */
1742
+ const requireImportAliasRule = {
1743
+ meta: {
1744
+ type: "suggestion",
1745
+ fixable: "code",
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
+ },
1750
+ schema: [{
1751
+ type: "object",
1752
+ additionalProperties: false,
1753
+ properties: {
1754
+ aliases: {
1755
+ type: "object",
1756
+ additionalProperties: {
1757
+ type: "array",
1758
+ items: { type: "string" }
1759
+ }
1760
+ },
1761
+ tsConfigPath: { type: "string" },
1762
+ ignoredPaths: {
1763
+ type: "array",
1764
+ items: { type: "string" }
1765
+ }
1766
+ }
1767
+ }],
1768
+ messages: {
1769
+ [MESSAGE_ID_EXPECTED_ALIAS]: "Import path '{{ source }}' must use the configured alias '{{ alias }}'.",
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."
1771
+ }
1772
+ },
1773
+ create: (context) => {
1774
+ const options = context.options[0] ?? {};
1775
+ const ignoredPaths = options.ignoredPaths ?? [];
1776
+ const mappings = buildAliasMappings(resolveAliases(options));
1777
+ if (mappings.length === 0 || isFileIgnored(context.filename, ignoredPaths)) return {};
1778
+ const fileDirectory = toPosix$1(path.dirname(context.filename));
1779
+ const checkSource = (sourceNode) => {
1780
+ if (sourceNode?.type !== "Literal" || typeof sourceNode.value !== "string") return;
1781
+ const source = sourceNode.value;
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;
1786
+ if (replacement === void 0) {
1787
+ context.report({
1788
+ node: sourceNode,
1789
+ messageId: MESSAGE_ID_MISSING_ALIAS,
1790
+ data: { source }
1791
+ });
1792
+ return;
1793
+ }
1794
+ context.report({
1795
+ node: sourceNode,
1796
+ messageId: MESSAGE_ID_EXPECTED_ALIAS,
1797
+ data: {
1798
+ source,
1799
+ alias: replacement
1800
+ },
1801
+ fix: (fixer) => fixer.replaceText(sourceNode, `${getQuote(sourceNode)}${replacement}${getQuote(sourceNode)}`)
1802
+ });
1803
+ };
1804
+ return {
1805
+ ImportDeclaration: (node) => {
1806
+ checkSource(node.source);
1807
+ },
1808
+ ExportNamedDeclaration: (node) => {
1809
+ checkSource(node.source);
1810
+ },
1811
+ ExportAllDeclaration: (node) => {
1812
+ checkSource(node.source);
1813
+ },
1814
+ ImportExpression: (node) => {
1815
+ checkSource(node.source);
1816
+ }
1817
+ };
1818
+ }
1819
+ };
120
1820
  //#endregion
121
- //#region ../src/js/eslint/configs/builtin.ts
1821
+ //#region ../src/js/eslint/configs/builtin/require-import-attributes.ts
1822
+ const MESSAGE_ID_MISSING_WITH_KEYWORD = "missingWithKeyword";
1823
+ const MESSAGE_ID_MISSING_TYPE_PROPERTY = "missingTypeProperty";
1824
+ const MESSAGE_ID_UNEXPECTED_TYPE_VALUE = "unexpectedTypeValue";
122
1825
  const FILE_TYPE_MAP = {
123
1826
  ".json": "json",
124
1827
  ".css": "css",
@@ -130,17 +1833,20 @@ const FILE_TYPE_MAP = {
130
1833
  ".oct": "bytes",
131
1834
  ".wasm": "webassembly"
132
1835
  };
133
- const MESSAGE_ID_MISSING_WITH_KEYWORD = "missingWithKeyword";
134
- const MESSAGE_ID_MISSING_TYPE_PROPERTY = "missingTypeProperty";
135
- const MESSAGE_ID_WRONG_TYPE_VALUE = "wrongTypeValue";
136
- const RULE_DEFINITIONS = { "require-import-attributes": {
1836
+ /**
1837
+ * @see https://github.com/brnshkr/config/blob/master/docs/js/eslint/rules/require-import-attributes.md
1838
+ */
1839
+ const requireImportAttributesRule = {
137
1840
  meta: {
138
1841
  type: "problem",
139
- docs: { description: "Require non-JavaScript imports (e.g. .json and .css) to include import attributes." },
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"
1845
+ },
140
1846
  messages: {
141
1847
  [MESSAGE_ID_MISSING_WITH_KEYWORD]: "Non-JavaScript import ('{{ extension }}') requires an import attributes object with the 'type' property set to '{{ expectedValue }}'.",
142
1848
  [MESSAGE_ID_MISSING_TYPE_PROPERTY]: "Import attributes for non-JavaScript imports must include the 'type' property.",
143
- [MESSAGE_ID_WRONG_TYPE_VALUE]: "Import attribute 'type' for '{{ file }}' must be '{{ expectedValue }}'."
1849
+ [MESSAGE_ID_UNEXPECTED_TYPE_VALUE]: "Import attribute 'type' for '{{ file }}' must be '{{ expectedValue }}'."
144
1850
  }
145
1851
  },
146
1852
  create: (context) => ({ ImportDeclaration: (node) => {
@@ -172,32 +1878,285 @@ const RULE_DEFINITIONS = { "require-import-attributes": {
172
1878
  }
173
1879
  if (typeProperty.value.value !== expectedValue) context.report({
174
1880
  node,
175
- messageId: MESSAGE_ID_WRONG_TYPE_VALUE,
1881
+ messageId: MESSAGE_ID_UNEXPECTED_TYPE_VALUE,
176
1882
  data: {
177
1883
  expectedValue,
178
1884
  file: sourceValue
179
1885
  }
180
1886
  });
181
1887
  } })
182
- } };
183
- const RULES = objectFreeze(objectFromEntries(objectKeys(RULE_DEFINITIONS).map((ruleName) => [`${packageOrganization}/${ruleName}`, "error"])));
184
- const builtin = () => [{
185
- name: buildConfigName(MAIN_SCOPES[packageOrganizationUpper], SUB_SCOPES.SETUP),
186
- plugins: { [packageOrganization]: {
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." }
1960
+ },
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]];
2048
+ };
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": {
187
2064
  meta: {
188
- name: packageOrganization,
189
- 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
+ }
190
2085
  },
191
- rules: RULE_DEFINITIONS
192
- } }
193
- }, {
194
- name: buildConfigName(MAIN_SCOPES[packageOrganizationUpper], SUB_SCOPES.RULES),
195
- files: GLOB_SCRIPT_FILES,
196
- rules: RULES
197
- }];
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
+ }
2125
+ }
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
+ };
198
2154
  const builtinConfig = { [packageOrganization]: builtin };
199
2155
  //#endregion
200
2156
  //#region ../src/js/eslint/utils/module.ts
2157
+ /**
2158
+ * @internal @brnshkr/config/eslint
2159
+ */
201
2160
  const MODULES = {
202
2161
  [packageOrganization]: { name: packageOrganization },
203
2162
  comments: {
@@ -208,15 +2167,11 @@ const MODULES = {
208
2167
  name: "css",
209
2168
  packages: { requiredAll: [ESLINT_PACKAGES.ESLINT_CSS] }
210
2169
  },
211
- gitignore: {
212
- name: "gitignore",
213
- packages: { requiredAll: [ESLINT_PACKAGES.ESLINT_FLAT_CONFIG_GITIGNORE] }
214
- },
215
2170
  import: {
216
2171
  name: "import",
217
2172
  packages: {
218
2173
  requiredAny: [ESLINT_PACKAGES.ESLINT_PLUGIN_IMPORT_X, ESLINT_PACKAGES.ESLINT_PLUGIN_ANTFU],
219
- optional: [ESLINT_PACKAGES.ESLINT_IMPORT_RESOVLER_TYPESCRIPT]
2174
+ optional: [ESLINT_PACKAGES.ESLINT_IMPORT_RESOLVER_TYPESCRIPT]
220
2175
  }
221
2176
  },
222
2177
  javascript: {
@@ -282,14 +2237,13 @@ const MODULES = {
282
2237
  packages: { requiredAll: [ESLINT_PACKAGES.ESLINT_PLUGIN_YML] }
283
2238
  }
284
2239
  };
285
- const resolvePackages = async (moduleInfo, type) => resolvePackagesSharedAsynchronously(moduleInfo, type);
286
- const enabledStates = {};
287
- const isModuleEnabled = (moduleInfo) => enabledStates[moduleInfo.name] ?? isModuleEnabledByDefault(moduleInfo);
288
- const setModuleEnabled = (moduleInfo, state) => {
289
- enabledStates[moduleInfo.name] = state;
290
- };
2240
+ const resolvePackages = resolvePackagesSharedAsynchronously;
2241
+ const { isModuleEnabled, setModuleEnabled } = createModuleState();
291
2242
  //#endregion
292
2243
  //#region ../src/js/eslint/configs/comments.ts
2244
+ /**
2245
+ * @internal @brnshkr/config/eslint
2246
+ */
293
2247
  const comments = async () => {
294
2248
  const { requiredAll: [pluginComments] } = await resolvePackages(MODULES.comments);
295
2249
  if (!pluginComments) return [];
@@ -303,15 +2257,72 @@ const comments = async () => {
303
2257
  files: GLOB_SCRIPT_FILES,
304
2258
  rules: {
305
2259
  ...renameRules(recommendedRules, { "@eslint-community/eslint-comments": "comments" }),
306
- "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
+ ] }]
307
2268
  }
308
2269
  }];
309
2270
  };
310
2271
  //#endregion
311
2272
  //#region ../src/js/eslint/configs/css.ts
312
- 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) => {
313
2322
  const { requiredAll: [pluginCss] } = await resolvePackages(MODULES.css);
314
2323
  if (!pluginCss) return [];
2324
+ const isTailwindEnabled = options?.tailwind ?? doAllPackagesExist([ESLINT_PACKAGES.TAILWINDCSS]);
2325
+ const customSyntax = buildCustomSyntax(options?.customSyntax, isTailwindEnabled);
315
2326
  return [{
316
2327
  name: buildConfigName(MAIN_SCOPES.CSS, SUB_SCOPES.SETUP),
317
2328
  plugins: { css: pluginCss }
@@ -319,41 +2330,68 @@ const css = async () => {
319
2330
  name: buildConfigName(MAIN_SCOPES.CSS, SUB_SCOPES.RULES),
320
2331
  files: [GLOB_CSS],
321
2332
  language: "css/css",
2333
+ languageOptions: {
2334
+ ...customSyntax === void 0 ? void 0 : { customSyntax },
2335
+ tolerant: options?.tolerant ?? isTailwindEnabled
2336
+ },
322
2337
  rules: {
323
2338
  ...pluginCss.configs.recommended.rules,
2339
+ ...isTailwindEnabled ? {
2340
+ "css/no-duplicate-imports": "off",
2341
+ "css/no-invalid-properties": "off"
2342
+ } : void 0,
324
2343
  "css/prefer-logical-properties": "error",
325
2344
  "css/relative-font-units": ["error", { allowUnits: ["em", "rem"] }],
326
- "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
327
2349
  }
328
2350
  }];
329
2351
  };
330
2352
  //#endregion
331
- //#region ../src/js/eslint/configs/gitignore.ts
332
- const gitignore = async (options) => {
333
- const { requiredAll: [pluginGitignore] } = await resolvePackages(MODULES.gitignore);
334
- if (!pluginGitignore) return [];
335
- return [pluginGitignore({
336
- name: buildConfigName(MAIN_SCOPES.IGNORES, SUB_SCOPES.GIT),
337
- ...options
338
- })];
339
- };
340
- //#endregion
341
2353
  //#region ../src/js/eslint/configs/ignores.ts
342
- const ignores = (customIgnores = []) => [{
343
- name: buildConfigName(MAIN_SCOPES.IGNORES, SUB_SCOPES.BASE),
344
- ignores: [...GLOB_IGNORES, ...customIgnores]
345
- }];
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
+ };
346
2369
  //#endregion
347
2370
  //#region ../src/js/eslint/configs/import.ts
2371
+ /**
2372
+ * @internal @brnshkr/config/eslint
2373
+ */
348
2374
  const imports = async () => {
349
- const { requiredAny: [pluginImport, pluginAntfu], optional: [importResovlerTypescript] } = await resolvePackages(MODULES.import);
2375
+ const { requiredAny: [pluginImport, pluginAntfu], optional: [importResolverTypescript] } = await resolvePackages(MODULES.import);
350
2376
  const plugins = {};
351
2377
  const settings = {};
352
2378
  let pluginImportRules = {};
2379
+ let pluginImportTsRules = {};
353
2380
  let pluginAntfuRules = {};
354
2381
  if (pluginImport) {
355
2382
  plugins["import"] = pluginImport;
356
- 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[/\\]`];
357
2395
  settings["import-x/core-modules"] = [
358
2396
  "bun",
359
2397
  "bun:bundle",
@@ -362,33 +2400,46 @@ const imports = async () => {
362
2400
  "bun:sqlite",
363
2401
  "bun:test"
364
2402
  ];
365
- const pluginImportTsRules = isModuleEnabled(MODULES.typescript) ? renameRules(pluginImport.flatConfigs.typescript.rules, { "import-x": "import" }) : {};
2403
+ pluginImportTsRules = renameRules(pluginImport.flatConfigs.typescript.rules, { "import-x": "import" });
366
2404
  pluginImportRules = {
367
2405
  ...renameRules(pluginImport.flatConfigs.recommended.rules, { "import-x": "import" }),
368
- ...pluginImportTsRules,
369
- "import/consistent-type-specifier-style": ["error", "prefer-top-level"],
2406
+ "import/consistent-type-specifier-style": "error",
370
2407
  "import/extensions": [
371
2408
  "error",
372
2409
  "ignorePackages",
373
2410
  {
374
- js: "never",
375
- ts: "never",
376
- cts: "never",
377
- 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
+ }]
378
2422
  }
379
2423
  ],
380
2424
  "import/first": "error",
381
2425
  "import/max-dependencies": ["error", { max: 15 }],
2426
+ "import/namespace": "off",
382
2427
  "import/newline-after-import": "error",
383
2428
  "import/no-absolute-path": "error",
384
2429
  "import/no-amd": "error",
385
- "import/no-cycle": ["error", { maxDepth: 3 }],
2430
+ "import/no-cycle": ["error", {
2431
+ ignoreExternal: true,
2432
+ maxDepth: 3
2433
+ }],
386
2434
  "import/no-default-export": "error",
387
2435
  "import/no-deprecated": "error",
388
2436
  "import/no-duplicates": "error",
389
2437
  "import/no-dynamic-require": "error",
390
2438
  "import/no-empty-named-blocks": "error",
391
- "import/no-extraneous-dependencies": ["error", { devDependencies: GLOB_DEVELOPMENT_FILES }],
2439
+ "import/no-extraneous-dependencies": ["error", {
2440
+ devDependencies: GLOB_DEVELOPMENT_FILES,
2441
+ includeTypes: true
2442
+ }],
392
2443
  "import/no-import-module-exports": "error",
393
2444
  "import/no-mutable-exports": "error",
394
2445
  "import/no-named-as-default-member": "error",
@@ -402,7 +2453,7 @@ const imports = async () => {
402
2453
  "import/no-useless-path-segments": ["error", { noUselessIndex: true }],
403
2454
  "import/no-unresolved": ["error", {
404
2455
  commonjs: true,
405
- caseSensitive: true
2456
+ caseSensitiveStrict: true
406
2457
  }],
407
2458
  "import/no-webpack-loader-syntax": "error",
408
2459
  "import/order": ["error", {
@@ -446,23 +2497,34 @@ const imports = async () => {
446
2497
  "antfu/no-import-node-modules-by-path": "error"
447
2498
  };
448
2499
  }
449
- if (Object.keys(plugins).length === 0) return [];
2500
+ if (objectKeys(plugins).length === 0) return [];
450
2501
  const setupConfig = {
451
2502
  name: buildConfigName(MAIN_SCOPES.IMPORT, SUB_SCOPES.SETUP),
452
2503
  plugins
453
2504
  };
454
- if (Object.keys(settings).length > 0) setupConfig.settings = settings;
455
- return [setupConfig, {
456
- name: buildConfigName(MAIN_SCOPES.IMPORT, SUB_SCOPES.RULES),
457
- files: GLOB_SCRIPT_FILES,
458
- rules: {
459
- ...pluginImportRules,
460
- ...pluginAntfuRules
461
- }
462
- }];
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
+ ];
463
2522
  };
464
2523
  //#endregion
465
2524
  //#region ../src/js/eslint/configs/javascript.ts
2525
+ /**
2526
+ * @internal @brnshkr/config/eslint
2527
+ */
466
2528
  const javascript = async () => {
467
2529
  const { optional: [pluginAntfu, pluginUnusedImports] } = await resolvePackages(MODULES.javascript);
468
2530
  const plugins = {};
@@ -494,16 +2556,19 @@ const javascript = async () => {
494
2556
  ecmaFeatures: { jsx: true }
495
2557
  }
496
2558
  },
497
- linterOptions: { reportUnusedDisableDirectives: "error" }
2559
+ linterOptions: {
2560
+ reportUnusedDisableDirectives: "error",
2561
+ reportUnusedInlineConfigs: "error"
2562
+ }
498
2563
  }, {
499
2564
  name: buildConfigName(MAIN_SCOPES.JAVASCRIPT, SUB_SCOPES.RULES),
500
2565
  files: GLOB_SCRIPT_FILES,
501
2566
  rules: {
502
2567
  ...jsEslint.configs.recommended.rules,
503
2568
  ...pluginUnusedImports ? { "no-unused-vars": "off" } : void 0,
504
- "accessor-pairs": "error",
2569
+ "accessor-pairs": ["error", { enforceForTSTypes: true }],
505
2570
  "array-callback-return": ["error", { checkForEach: true }],
506
- "arrow-body-style": ["error", "as-needed"],
2571
+ "arrow-body-style": "error",
507
2572
  "block-scoped-var": "error",
508
2573
  "capitalized-comments": [
509
2574
  "error",
@@ -531,8 +2596,12 @@ const javascript = async () => {
531
2596
  includeCommonJSModuleExports: true
532
2597
  }],
533
2598
  "func-names": "error",
534
- "func-style": ["error", "expression"],
535
- "grouped-accessor-pairs": "error",
2599
+ "func-style": "error",
2600
+ "grouped-accessor-pairs": [
2601
+ "error",
2602
+ "getBeforeSet",
2603
+ { enforceForTSTypes: true }
2604
+ ],
536
2605
  "guard-for-in": "error",
537
2606
  "init-declarations": "error",
538
2607
  "logical-assignment-operators": "error",
@@ -549,7 +2618,10 @@ const javascript = async () => {
549
2618
  skipBlankLines: true,
550
2619
  skipComments: true
551
2620
  }],
552
- "max-nested-callbacks": ["error", { max: 3 }],
2621
+ "max-nested-callbacks": ["error", {
2622
+ max: 3,
2623
+ checkConstructorCallCallbacks: true
2624
+ }],
553
2625
  "max-params": ["error", { max: 4 }],
554
2626
  "max-statements": ["error", { max: 30 }],
555
2627
  "new-cap": "error",
@@ -560,6 +2632,8 @@ const javascript = async () => {
560
2632
  "no-caller": "error",
561
2633
  "no-cond-assign": ["error", "always"],
562
2634
  "no-console": "error",
2635
+ "no-constant-binary-expression": ["error", { checkRelationalComparisons: true }],
2636
+ "no-constant-condition": ["error", { checkLoops: "all" }],
563
2637
  "no-constructor-return": "error",
564
2638
  "no-div-regex": "error",
565
2639
  "no-duplicate-imports": isModuleEnabled(MODULES.import) ? "off" : ["error", { allowSeparateTypeImports: true }],
@@ -569,26 +2643,39 @@ const javascript = async () => {
569
2643
  "no-eval": "error",
570
2644
  "no-extend-native": "error",
571
2645
  "no-extra-bind": "error",
2646
+ "no-extra-boolean-cast": ["error", { enforceForInnerExpressions: true }],
572
2647
  "no-extra-label": "error",
2648
+ "no-fallthrough": ["error", {
2649
+ allowEmptyCase: true,
2650
+ reportUnusedFallthroughComment: true
2651
+ }],
573
2652
  "no-implicit-coercion": ["error", { boolean: false }],
574
2653
  "no-implicit-globals": "error",
575
2654
  "no-implied-eval": "error",
576
2655
  "no-inline-comments": "error",
577
- "no-inner-declarations": "error",
2656
+ "no-inner-declarations": [
2657
+ "error",
2658
+ "functions",
2659
+ { blockScopedFunctions: "disallow" }
2660
+ ],
578
2661
  "no-invalid-this": "error",
2662
+ "no-irregular-whitespace": ["error", { skipStrings: false }],
579
2663
  "no-iterator": "error",
580
2664
  "no-label-var": "error",
581
2665
  "no-labels": "error",
582
2666
  "no-lone-blocks": "error",
583
2667
  "no-lonely-if": "error",
584
2668
  "no-loop-func": "error",
585
- "no-magic-numbers": ["error", { ignore: [
586
- -1,
587
- 0,
588
- 1,
589
- 100,
590
- 42069
591
- ] }],
2669
+ "no-magic-numbers": ["error", {
2670
+ ignore: [
2671
+ -1,
2672
+ 0,
2673
+ 1,
2674
+ 100,
2675
+ 42069
2676
+ ],
2677
+ enforceConst: true
2678
+ }],
592
2679
  "no-multi-assign": "error",
593
2680
  "no-multi-str": "error",
594
2681
  "no-negated-condition": "error",
@@ -649,14 +2736,20 @@ const javascript = async () => {
649
2736
  "no-return-assign": ["error", "always"],
650
2737
  "no-script-url": "error",
651
2738
  "no-self-compare": "error",
652
- "no-sequences": "error",
653
- "no-shadow": "error",
2739
+ "no-sequences": ["error", { allowInParentheses: false }],
2740
+ "no-shadow": ["error", { hoist: "all" }],
654
2741
  "no-template-curly-in-string": "error",
655
2742
  "no-throw-literal": "error",
656
- "no-underscore-dangle": "error",
657
- "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 }],
658
2749
  "no-unneeded-ternary": ["error", { defaultAssignment: false }],
659
2750
  "no-unreachable-loop": "error",
2751
+ "no-unsafe-negation": ["error", { enforceForOrderingRelations: true }],
2752
+ "no-unsafe-optional-chaining": ["error", { disallowArithmeticOperators: true }],
660
2753
  "no-unused-expressions": "error",
661
2754
  "no-use-before-define": "error",
662
2755
  "no-useless-call": "error",
@@ -666,7 +2759,7 @@ const javascript = async () => {
666
2759
  "no-useless-rename": "error",
667
2760
  "no-useless-return": "error",
668
2761
  "no-var": "error",
669
- "no-void": "error",
2762
+ "no-void": ["error", { allowAsStatement: true }],
670
2763
  "no-warning-comments": "error",
671
2764
  "object-shorthand": [
672
2765
  "error",
@@ -677,7 +2770,7 @@ const javascript = async () => {
677
2770
  }
678
2771
  ],
679
2772
  "operator-assignment": "error",
680
- "prefer-arrow-callback": "error",
2773
+ "prefer-arrow-callback": ["error", { allowUnboundThis: false }],
681
2774
  "prefer-const": ["error", { ignoreReadBeforeAssign: true }],
682
2775
  "prefer-destructuring": "error",
683
2776
  "prefer-exponentiation-operator": "error",
@@ -706,136 +2799,112 @@ const javascript = async () => {
706
2799
  };
707
2800
  //#endregion
708
2801
  //#region ../src/js/eslint/configs/typescript.ts
2802
+ /**
2803
+ * @internal @brnshkr/config/eslint
2804
+ */
709
2805
  const DEFAULT_TYPE_AWARE_IGNORES = [`${GLOB_MD}/**`];
710
2806
  const getTsEslintParserIfExists = async () => {
711
- const isTypescriptModuleEnabled = isModuleEnabled(MODULES.typescript);
712
- let parser = void 0;
713
- if (isTypescriptModuleEnabled) {
714
- const { requiredAll: [, tsEslint] } = await resolvePackages(MODULES.typescript);
715
- if (tsEslint) ({parser} = tsEslint);
716
- }
717
- return parser;
2807
+ if (!isModuleEnabled(MODULES.typescript)) return;
2808
+ const { requiredAll: [, tsEslint] } = await resolvePackages(MODULES.typescript);
2809
+ return tsEslint?.parser;
718
2810
  };
719
- const resolveTypeAwareOptions = (resolvedOptions, files, ignores) => {
2811
+ const resolveTypeAwareOptions = (resolvedOptions, files, ignoredGlobs) => {
720
2812
  const typeAwareOptions = typeof resolvedOptions.typeAware === "object" ? resolvedOptions.typeAware : {
721
2813
  ignores: DEFAULT_TYPE_AWARE_IGNORES,
722
2814
  tsconfig: typeof resolvedOptions.typeAware === "string" ? resolvedOptions.typeAware : void 0
723
2815
  };
724
- typeAwareOptions.files = [...new Set([...typeAwareOptions.files ?? [], ...files])];
725
- 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])];
726
2818
  return typeAwareOptions;
727
2819
  };
728
2820
  const extractRelevantRules = (configs, key) => {
729
2821
  for (const config of configs) if (config.name === `typescript-eslint/${key}` && config.rules) return renameRules(config.rules, { "@typescript-eslint": "ts" });
730
2822
  throw new Error(`Expected key "${key}" to be contained in given config.`);
731
2823
  };
732
- const getNamingConvention = (isTypeAware) => {
733
- const ruleOptions = [
734
- "error",
735
- {
736
- selector: "default",
737
- format: [
738
- "strictCamelCase",
739
- "StrictPascalCase",
740
- "UPPER_CASE"
741
- ],
742
- leadingUnderscore: "forbid",
743
- trailingUnderscore: "forbid"
744
- },
745
- {
746
- selector: ["objectLiteralProperty", "variable"],
747
- format: ["strictCamelCase", "UPPER_CASE"]
748
- },
749
- {
750
- selector: "variable",
751
- format: null,
752
- modifiers: ["destructured"]
753
- },
754
- {
755
- selector: "typeLike",
756
- format: ["StrictPascalCase"]
757
- },
758
- {
759
- selector: "parameter",
760
- format: null,
761
- filter: {
762
- regex: "^_+$",
763
- match: false
764
- }
765
- },
766
- {
767
- selector: [
768
- "classProperty",
769
- "objectLiteralProperty",
770
- "typeProperty",
771
- "classMethod",
772
- "objectLiteralMethod",
773
- "typeMethod",
774
- "accessor",
775
- "enumMember"
776
- ],
777
- format: null,
778
- modifiers: ["requiresQuotes"]
779
- },
780
- {
781
- selector: "typeParameter",
782
- format: ["StrictPascalCase"],
783
- prefix: ["T"],
784
- custom: {
785
- regex: "^[A-Z]",
786
- match: true
787
- }
788
- },
789
- {
790
- selector: "interface",
791
- format: ["StrictPascalCase"],
792
- custom: {
793
- regex: "^I[A-Z]",
794
- match: false
795
- }
796
- }
797
- ];
798
- 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
+ {
799
2841
  selector: "variable",
800
- 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",
801
2873
  format: ["StrictPascalCase"],
802
- prefix: [
803
- "as",
804
- "is",
805
- "does",
806
- "do",
807
- "did",
808
- "has",
809
- "was",
810
- "can"
811
- ]
812
- });
813
- return ruleOptions;
814
- };
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
+ ];
815
2889
  const typescript = async (options) => {
816
2890
  const { requiredAll: [isTypescriptInstalled, tsEslint] } = await resolvePackages(MODULES.typescript);
817
2891
  if (!isTypescriptInstalled || !tsEslint) return [];
818
2892
  const { optional: [, pluginUnusedImports] } = await resolvePackages(MODULES.javascript);
819
2893
  const cwd = process.cwd();
820
- let hasTsConfig = false;
821
- try {
822
- await fs.access(path.resolve(cwd, "tsconfig.json"), fs.constants.R_OK);
823
- hasTsConfig = true;
824
- } catch {}
825
2894
  const resolvedOptions = {
826
2895
  extraFileExtensions: [],
827
2896
  parserOptions: {},
828
- typeAware: hasTsConfig,
2897
+ typeAware: doesTsConfigExist(resolveTsConfigPath(options)),
829
2898
  ...options
830
2899
  };
831
- const ignores = resolvedOptions.ignores ?? [];
832
- 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])];
833
2902
  const hasEnabledTypeAwareness = resolvedOptions.typeAware !== false;
834
- const typeAwareOptions = hasEnabledTypeAwareness ? resolveTypeAwareOptions(resolvedOptions, files, ignores) : {};
2903
+ const typeAwareOptions = hasEnabledTypeAwareness ? resolveTypeAwareOptions(resolvedOptions, files, ignoredGlobs) : {};
835
2904
  const createParserConfig = (isTypeAware) => ({
836
2905
  name: buildConfigName(MAIN_SCOPES.TYPESCRIPT, `${SUB_SCOPES.PARSER}${isTypeAware ? "-type-aware" : ""}`),
837
2906
  files: isTypeAware ? typeAwareOptions.files : files,
838
- ignores: isTypeAware ? typeAwareOptions.ignores : ignores,
2907
+ ignores: isTypeAware ? typeAwareOptions.ignores : ignoredGlobs,
839
2908
  languageOptions: {
840
2909
  parser: tsEslint.parser,
841
2910
  parserOptions: {
@@ -851,9 +2920,9 @@ const typescript = async (options) => {
851
2920
  const createRulesConfig = (isTypeAware) => ({
852
2921
  name: buildConfigName(MAIN_SCOPES.TYPESCRIPT, `${SUB_SCOPES.RULES}${isTypeAware ? "-type-aware" : ""}`),
853
2922
  files: isTypeAware ? typeAwareOptions.files : files,
854
- ignores: isTypeAware ? typeAwareOptions.ignores : ignores,
2923
+ ignores: isTypeAware ? typeAwareOptions.ignores : ignoredGlobs,
855
2924
  rules: {
856
- "ts/naming-convention": getNamingConvention(isTypeAware),
2925
+ "ts/naming-convention": getNamingConvention(),
857
2926
  ...isTypeAware ? {
858
2927
  ...extractRelevantRules(tsEslint.configs.recommendedTypeCheckedOnly, "recommended-type-checked-only"),
859
2928
  ...extractRelevantRules(tsEslint.configs.strictTypeCheckedOnly, "strict-type-checked-only"),
@@ -864,11 +2933,19 @@ const typescript = async (options) => {
864
2933
  "ts/no-unnecessary-qualifier": "error",
865
2934
  "prefer-destructuring": "off",
866
2935
  "ts/prefer-destructuring": "error",
867
- "ts/no-unnecessary-type-conversion": "error",
868
2936
  "ts/promise-function-async": "error",
869
2937
  "ts/prefer-readonly": "error",
870
2938
  "ts/require-array-sort-compare": "error",
871
- "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
+ }],
872
2949
  "ts/strict-void-return": "error",
873
2950
  "ts/switch-exhaustiveness-check": ["error", { requireDefaultForNonUnion: true }]
874
2951
  } : {
@@ -878,7 +2955,36 @@ const typescript = async (options) => {
878
2955
  ...pluginUnusedImports ? { "ts/no-unused-vars": "off" } : void 0,
879
2956
  "ts/consistent-type-assertions": ["error", { assertionStyle: "angle-bracket" }],
880
2957
  "ts/consistent-type-imports": "error",
881
- "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
+ ] }],
882
2988
  "ts/method-signature-style": "error",
883
2989
  "ts/no-import-type-side-effects": "error",
884
2990
  "no-redeclare": "off",
@@ -891,6 +2997,7 @@ const typescript = async (options) => {
891
2997
  }
892
2998
  }
893
2999
  });
3000
+ const coreRulesCoveredByTypescript = objectFromEntries(objectEntries(extractRelevantRules(tsEslint.configs.recommended, "eslint-recommended")).filter(([, severity]) => severity === "off"));
894
3001
  return [
895
3002
  {
896
3003
  name: buildConfigName(MAIN_SCOPES.TYPESCRIPT, SUB_SCOPES.SETUP),
@@ -903,8 +3010,9 @@ const typescript = async (options) => {
903
3010
  {
904
3011
  name: buildConfigName(MAIN_SCOPES.TYPESCRIPT, `${SUB_SCOPES.RULES}-typescript`),
905
3012
  files: [GLOB_TS],
906
- ignores: typeAwareOptions.ignores ?? ignores,
3013
+ ignores: typeAwareOptions.ignores ?? ignoredGlobs,
907
3014
  rules: {
3015
+ ...coreRulesCoveredByTypescript,
908
3016
  "ts/explicit-function-return-type": "error",
909
3017
  "ts/explicit-member-accessibility": "error"
910
3018
  }
@@ -913,9 +3021,129 @@ const typescript = async (options) => {
913
3021
  };
914
3022
  //#endregion
915
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
+ ];
916
3143
  const jsdoc = async () => {
917
- const { requiredAll: [pluginJsdoc], optional: [getJsdocProcessorPlugin] } = await resolvePackages(MODULES.jsdoc);
3144
+ const { requiredAll: [pluginJsdoc], optional: [jsdocProcessorModule] } = await resolvePackages(MODULES.jsdoc);
918
3145
  if (!pluginJsdoc) return [];
3146
+ const definedTags = ["api", ...isModuleEnabled(MODULES.test) ? TAGS_BY_MODULE.test : []];
919
3147
  const createSetupConfig = (isForTypescript) => ({
920
3148
  name: buildConfigName(MAIN_SCOPES.JSDOC, `${SUB_SCOPES.SETUP}${isForTypescript ? "-typescript" : ""}`),
921
3149
  ...isForTypescript ? void 0 : { plugins: { jsdoc: pluginJsdoc } },
@@ -926,10 +3154,15 @@ const jsdoc = async () => {
926
3154
  files: isForTypescript ? [GLOB_TS] : [GLOB_TS, ...GLOB_SCRIPT_FILES_WITHOUT_TS],
927
3155
  rules: { ...isForTypescript ? {
928
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
+ }],
929
3161
  "jsdoc/require-param": "off",
930
3162
  "jsdoc/require-returns": "off"
931
3163
  } : {
932
3164
  ...pluginJsdoc.configs["flat/recommended-error"].rules,
3165
+ "jsdoc/check-tag-names": ["error", { definedTags }],
933
3166
  "jsdoc/check-indentation": ["error", { allowIndentedSections: true }],
934
3167
  "jsdoc/check-line-alignment": "error",
935
3168
  "jsdoc/check-syntax": "error",
@@ -942,123 +3175,21 @@ const jsdoc = async () => {
942
3175
  noSingleLineBlocks: true,
943
3176
  singleLineTags: []
944
3177
  }],
945
- "jsdoc/no-bad-blocks": "error",
3178
+ "jsdoc/no-bad-blocks": ["error", { preventAllMultiAsteriskBlocks: true }],
946
3179
  "jsdoc/no-blank-block-descriptions": "error",
947
3180
  "jsdoc/no-blank-blocks": "error",
3181
+ "jsdoc/no-unnecessary-type-assertion": ["error", { preferConstToLiteralTuples: true }],
3182
+ "jsdoc/normalize-see-links": "error",
948
3183
  "jsdoc/prefer-import-tag": "error",
949
- "jsdoc/require-description-complete-sentence": "error",
950
3184
  "jsdoc/require-asterisk-prefix": "error",
951
- "jsdoc/require-hyphen-before-param-description": "error",
3185
+ "jsdoc/require-hyphen-before-param-description": ["error", "never"],
952
3186
  "jsdoc/require-param-description": "off",
953
3187
  "jsdoc/require-property-description": "off",
954
3188
  "jsdoc/require-returns-description": "off",
955
3189
  "jsdoc/require-jsdoc": "off",
956
3190
  "jsdoc/require-template": "error",
957
3191
  "jsdoc/require-throws": "error",
958
- "jsdoc/sort-tags": ["error", { tagSequence: [
959
- { tags: ["import"] },
960
- { tags: [
961
- "deprecated",
962
- "ignore",
963
- "since",
964
- "version",
965
- ["to", "do"].join("")
966
- ] },
967
- { tags: [
968
- "author",
969
- "copyright",
970
- "license"
971
- ] },
972
- { tags: ["summary", "typeSummary"] },
973
- { tags: [
974
- "desc",
975
- "description",
976
- "classdesc"
977
- ] },
978
- { tags: [
979
- "internal",
980
- "namespace",
981
- "category",
982
- "package",
983
- "file",
984
- "fileoverview",
985
- "overview",
986
- "module",
987
- "override",
988
- "requires",
989
- "implements",
990
- "mixes",
991
- "mixin",
992
- "mixinClass",
993
- "mixinFunction",
994
- "borrows",
995
- "constructs",
996
- "lends",
997
- "final",
998
- "global",
999
- "readonly",
1000
- "abstract",
1001
- "virtual",
1002
- "static",
1003
- "private",
1004
- "protected",
1005
- "public",
1006
- "access",
1007
- "const",
1008
- "constant",
1009
- "variation",
1010
- "var",
1011
- "member",
1012
- "memberof",
1013
- "inner",
1014
- "instance",
1015
- "inheritdoc",
1016
- "inheritDoc",
1017
- "hideconstructor"
1018
- ] },
1019
- { tags: [
1020
- "this",
1021
- "interface",
1022
- "enum",
1023
- "event",
1024
- "augments",
1025
- "extends",
1026
- "name",
1027
- "kind",
1028
- "type",
1029
- "alias",
1030
- "external",
1031
- "host",
1032
- "async",
1033
- "callback",
1034
- "func",
1035
- "function",
1036
- "method",
1037
- "class",
1038
- "constructor",
1039
- "generator",
1040
- "fires",
1041
- "emits",
1042
- "listens"
1043
- ] },
1044
- { tags: ["template"] },
1045
- { tags: ["typedef"] },
1046
- { tags: [
1047
- "param",
1048
- "arg",
1049
- "argument",
1050
- "prop",
1051
- "property"
1052
- ] },
1053
- { tags: ["return", "returns"] },
1054
- { tags: ["yield", "yields"] },
1055
- { tags: ["throws", "exception"] },
1056
- { tags: ["satisfies"] },
1057
- { tags: ["default", "defaultvalue"] },
1058
- { tags: ["exports"] },
1059
- { tags: ["see", "tutorial"] },
1060
- { tags: ["example"] }
1061
- ] }],
3192
+ "jsdoc/sort-tags": ["error", { tagSequence: TAG_SEQUENCE }],
1062
3193
  "jsdoc/tag-lines": [
1063
3194
  "error",
1064
3195
  "any",
@@ -1075,11 +3206,15 @@ const jsdoc = async () => {
1075
3206
  } }
1076
3207
  });
1077
3208
  const parser = await getTsEslintParserIfExists();
1078
- const examplePlugin = getJsdocProcessorPlugin ? getJsdocProcessorPlugin.getJsdocProcessorPlugin({
3209
+ const examplePlugin = jsdocProcessorModule ? jsdocProcessorModule.getJsdocProcessorPlugin({
1079
3210
  checkDefaults: true,
1080
3211
  checkExamples: true,
1081
3212
  checkParams: true,
1082
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",
1083
3218
  parser
1084
3219
  }) : void 0;
1085
3220
  return [
@@ -1099,6 +3234,9 @@ const jsdoc = async () => {
1099
3234
  };
1100
3235
  //#endregion
1101
3236
  //#region ../src/js/eslint/configs/json.ts
3237
+ /**
3238
+ * @internal @brnshkr/config/eslint
3239
+ */
1102
3240
  const TSCONFIG_FILES = ["**/tsconfig.json", "**/tsconfig.*.json"];
1103
3241
  const JSON_FILES_TO_TREAT_AS_JSONC = [...TSCONFIG_FILES, ".vscode/**/*.json"];
1104
3242
  const LANGUAGE_TO_GLOB_MAP = {
@@ -1184,6 +3322,7 @@ const getJsoncSortConfigs = () => [
1184
3322
  "lint-staged",
1185
3323
  "eslintConfig",
1186
3324
  "stylelint",
3325
+ "markdownlint-cli2",
1187
3326
  "prettier",
1188
3327
  "ava",
1189
3328
  "stackblitz",
@@ -1559,7 +3698,7 @@ const json = async () => {
1559
3698
  { emptyObjects: "never" }
1560
3699
  ],
1561
3700
  "jsonc/object-property-newline": "error",
1562
- "jsonc/quotes": ["error", "double"]
3701
+ "jsonc/quotes": "error"
1563
3702
  } : void 0
1564
3703
  }
1565
3704
  });
@@ -1582,8 +3721,14 @@ const json = async () => {
1582
3721
  };
1583
3722
  //#endregion
1584
3723
  //#region ../src/js/eslint/configs/markdown.ts
3724
+ /**
3725
+ * @internal @brnshkr/config/eslint
3726
+ */
1585
3727
  const extractRelevantValues = (identifier, configs, key) => {
1586
- 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
+ }
1587
3732
  throw new Error(`Expected key "${key}" to be contained in given config.`);
1588
3733
  };
1589
3734
  const markdown = async (options) => {
@@ -1634,7 +3779,10 @@ const markdown = async (options) => {
1634
3779
  "summary",
1635
3780
  "sup",
1636
3781
  "var"
1637
- ] }]
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
1638
3786
  }
1639
3787
  },
1640
3788
  {
@@ -1648,10 +3796,12 @@ const markdown = async (options) => {
1648
3796
  "no-inline-comments": "off",
1649
3797
  "no-magic-numbers": "off",
1650
3798
  "import/no-default-export": "off",
3799
+ "import/no-extraneous-dependencies": "off",
1651
3800
  "import/unambiguous": "off",
1652
3801
  "node/no-missing-import": "off",
1653
3802
  "ts/no-redeclare": "off",
1654
3803
  "ts/no-unused-vars": "off",
3804
+ "unicorn/no-barrel-files": "off",
1655
3805
  "unused/no-unused-imports": "off",
1656
3806
  "unused/no-unused-vars": "off"
1657
3807
  }
@@ -1660,6 +3810,9 @@ const markdown = async (options) => {
1660
3810
  };
1661
3811
  //#endregion
1662
3812
  //#region ../src/js/eslint/configs/node.ts
3813
+ /**
3814
+ * @internal @brnshkr/config/eslint
3815
+ */
1663
3816
  const node = async (options) => {
1664
3817
  const { requiredAll: [pluginNode] } = await resolvePackages(MODULES.node);
1665
3818
  if (!pluginNode) return [];
@@ -1675,12 +3828,12 @@ const node = async (options) => {
1675
3828
  "node/exports-style": "error",
1676
3829
  "node/global-require": "error",
1677
3830
  "node/handle-callback-err": "error",
1678
- "node/no-missing-import": "error",
1679
3831
  "node/no-mixed-requires": "error",
1680
3832
  "node/no-new-require": "error",
1681
3833
  "node/no-path-concat": "error",
1682
3834
  "node/no-process-env": "error",
1683
3835
  "node/no-sync": "error",
3836
+ "node/no-unpublished-bin": "error",
1684
3837
  "node/prefer-global/buffer": "error",
1685
3838
  "node/prefer-global/console": "error",
1686
3839
  "node/prefer-global/crypto": "error",
@@ -1690,17 +3843,29 @@ const node = async (options) => {
1690
3843
  "node/prefer-global/timers": "error",
1691
3844
  "node/prefer-global/url-search-params": "error",
1692
3845
  "node/prefer-global/url": "error",
3846
+ "node/prefer-import/assert-strict": "error",
1693
3847
  "node/prefer-node-protocol": "error",
3848
+ "node/prefer-process-get-builtin-module": "error",
1694
3849
  "node/prefer-promises/dns": "error",
1695
- "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
1696
3855
  }
1697
3856
  }];
1698
3857
  };
1699
3858
  //#endregion
1700
3859
  //#region ../src/js/eslint/configs/unicorn.ts
3860
+ /**
3861
+ * @internal @brnshkr/config/eslint
3862
+ */
1701
3863
  const FILE_NAMES_TO_IGNORE = [
3864
+ "__mocks__",
3865
+ "__tests__",
1702
3866
  "ACKNOWLEDGMENTS.md",
1703
3867
  "ADOPTERS.md",
3868
+ "AGENT_INSTRUCTIONS.md",
1704
3869
  "AGENTS.md",
1705
3870
  "API_REFERENCE.md",
1706
3871
  "ARCHITECTURE.md",
@@ -1726,6 +3891,7 @@ const FILE_NAMES_TO_IGNORE = [
1726
3891
  "FAQ.md",
1727
3892
  "GOVERNANCE.md",
1728
3893
  "INSTALL.md",
3894
+ "INSTRUCTIONS.md",
1729
3895
  "ISSUE_TEMPLATE.md",
1730
3896
  "LICENSE.md",
1731
3897
  "LLMS.md",
@@ -1749,10 +3915,10 @@ const FILE_NAMES_TO_IGNORE = [
1749
3915
  "RELEASING.md",
1750
3916
  "RESEARCH.md",
1751
3917
  "ROADMAP.md",
1752
- "SKILL.md",
1753
- "SPEC.md",
1754
3918
  "SECURITY_POLICY.md",
1755
3919
  "SECURITY.md",
3920
+ "SKILL.md",
3921
+ "SPEC.md",
1756
3922
  "STYLE_GUIDE.md",
1757
3923
  "SUPPORT.md",
1758
3924
  "TESTING.md",
@@ -1762,6 +3928,21 @@ const FILE_NAMES_TO_IGNORE = [
1762
3928
  "UPGRADE_NOTES.md",
1763
3929
  "VERSIONING.md"
1764
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
+ ];
1765
3946
  const unicorn = async () => {
1766
3947
  const { requiredAll: [pluginUnicorn] } = await resolvePackages(MODULES.unicorn);
1767
3948
  if (!pluginUnicorn) return [];
@@ -1773,30 +3954,114 @@ const unicorn = async () => {
1773
3954
  files: GLOB_SCRIPT_FILES,
1774
3955
  rules: {
1775
3956
  ...pluginUnicorn.configs.recommended.rules,
1776
- "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"],
1777
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
+ }],
1778
3990
  "unicorn/custom-error-definition": "error",
1779
3991
  "unicorn/filename-case": ["error", {
1780
3992
  case: "kebabCase",
1781
3993
  ignore: FILE_NAMES_TO_IGNORE
1782
3994
  }],
1783
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",
1784
4013
  "unicorn/no-keyword-prefix": "error",
1785
- "unicorn/no-nested-ternay": "off",
1786
- "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",
1787
4020
  "unicorn/no-unused-properties": "error",
1788
- "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",
1789
4052
  "unicorn/prefer-switch": "off",
1790
- "unicorn/string-content": ["error", { patterns: {
1791
- "\\.\\.\\.": "…",
1792
- "^http:\\/\\/": String.raw`^https:\/\/`
1793
- } }],
1794
- "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"
1795
4057
  }
1796
4058
  }];
1797
4059
  };
1798
4060
  //#endregion
1799
4061
  //#region ../src/js/eslint/configs/overrides.ts
4062
+ /**
4063
+ * @internal @brnshkr/config/eslint
4064
+ */
1800
4065
  const jsOverrides = [{
1801
4066
  name: buildConfigName(MAIN_SCOPES.OVERRIDES, `${MAIN_SCOPES.JAVASCRIPT}/scripts`),
1802
4067
  files: ["scripts/**/*.?(c)js"],
@@ -1844,6 +4109,25 @@ const tsOverrides = isModuleEnabled(MODULES.typescript) ? [{
1844
4109
  "import/no-named-as-default-member": "off"
1845
4110
  }
1846
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
+ };
1847
4131
  const testOverrides = isModuleEnabled(MODULES.test) ? [{
1848
4132
  name: buildConfigName(MAIN_SCOPES.OVERRIDES, `${MAIN_SCOPES.TEST}/general`),
1849
4133
  files: GLOB_TEST_FILES,
@@ -1854,46 +4138,62 @@ const unicornOverrides = isModuleEnabled(MODULES.unicorn) ? [{
1854
4138
  files: [...GLOB_SCRIPT_FILES.map((glob) => `**/classes/${glob}`), ...GLOB_SCRIPT_FILES.map((glob) => `**/errors/${glob}`)],
1855
4139
  rules: { "unicorn/filename-case": ["error", {
1856
4140
  case: "pascalCase",
4141
+ checkDirectories: false,
1857
4142
  ignore: FILE_NAMES_TO_IGNORE
1858
4143
  }] }
1859
4144
  }] : [];
1860
- const jsdocOverrides = isModuleEnabled(MODULES.jsdoc) ? [{
1861
- name: buildConfigName(MAIN_SCOPES.OVERRIDES, `${MAIN_SCOPES.JSDOC}/${SUB_SCOPES.EXAMPLES}`),
1862
- files: [GLOB_EXAMPLES],
1863
- rules: {
1864
- "no-console": "off",
1865
- "no-undef": "off",
1866
- "no-unused-expressions": "off",
1867
- "no-unused-vars": "off",
1868
- "node/no-missing-import": "off",
1869
- "node/no-missing-require": "off",
1870
- strict: "off",
1871
- "import/no-unresolved": "off",
1872
- "import/unambiguous": "off",
1873
- "style/eol-last": "off",
1874
- "style/no-multiple-empty-lines": "off",
1875
- "ts/no-unused-expressions": "off",
1876
- "ts/no-unused-vars": "off",
1877
- "unused/no-unused-imports": "off",
1878
- "unused/no-unused-vars": "off"
1879
- }
1880
- }, {
1881
- name: buildConfigName(MAIN_SCOPES.OVERRIDES, `${MAIN_SCOPES.JSDOC}/default-expressions`),
1882
- files: [
1883
- "**/*.jsdoc-defaults",
1884
- "**/*.jsdoc-params",
1885
- "**/*.jsdoc-properties"
1886
- ],
1887
- rules: {
1888
- "no-empty-function": "off",
1889
- "no-new": "off",
1890
- strict: "off",
1891
- "import/unambiguous": "off",
1892
- "style/eol-last": "off",
1893
- "style/no-extra-parens": "off",
1894
- "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
+ }
1895
4195
  }
1896
- }] : [];
4196
+ ] : [];
1897
4197
  const svelteOverrides = isModuleEnabled(MODULES.svelte) ? [{
1898
4198
  name: buildConfigName(MAIN_SCOPES.OVERRIDES, `${MAIN_SCOPES.SVELTE}/general`),
1899
4199
  files: [GLOB_SVELTE],
@@ -1905,11 +4205,14 @@ const svelteOverrides = isModuleEnabled(MODULES.svelte) ? [{
1905
4205
  "svelte/comment-directive": ["error", { reportUnusedDisableDirectives: true }],
1906
4206
  "svelte/system": "error"
1907
4207
  }
1908
- }, {
4208
+ }, ...isModuleEnabled(MODULES.unicorn) ? [{
1909
4209
  name: buildConfigName(MAIN_SCOPES.OVERRIDES, `${MAIN_SCOPES.SVELTE}/components`),
1910
4210
  files: [`**/components/${GLOB_SVELTE}`],
1911
- rules: { "unicorn/filename-case": ["error", { case: "pascalCase" }] }
1912
- }] : [];
4211
+ rules: { "unicorn/filename-case": ["error", {
4212
+ case: "pascalCase",
4213
+ checkDirectories: false
4214
+ }] }
4215
+ }] : []] : [];
1913
4216
  const tomlOverrides = isModuleEnabled(MODULES.toml) ? [{
1914
4217
  name: buildConfigName(MAIN_SCOPES.OVERRIDES, `${MAIN_SCOPES.TOML}/general`),
1915
4218
  files: [GLOB_TOML],
@@ -1922,10 +4225,16 @@ const yamlOverrides = isModuleEnabled(MODULES.yaml) ? [{
1922
4225
  "no-irregular-whitespace": "off",
1923
4226
  "no-unused-vars": "off"
1924
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" }
1925
4232
  }] : [];
1926
- const overrides = () => [
4233
+ const overrides = (typescriptOptions) => [
1927
4234
  ...jsOverrides,
1928
4235
  ...tsOverrides,
4236
+ ...importOverrides,
4237
+ ...buildTypeAwareImportOverrides(typescriptOptions),
1929
4238
  ...testOverrides,
1930
4239
  ...unicornOverrides,
1931
4240
  ...jsdocOverrides,
@@ -1944,12 +4253,16 @@ const overrides = () => [
1944
4253
  "import/no-default-export": "off",
1945
4254
  "import/no-rename-default": "off",
1946
4255
  "import/no-named-as-default-member": "off",
1947
- "node/no-sync": "off"
4256
+ "node/no-sync": "off",
4257
+ "unicorn/no-barrel-files": "off"
1948
4258
  }
1949
4259
  }
1950
4260
  ];
1951
4261
  //#endregion
1952
4262
  //#region ../src/js/eslint/configs/perfectionist.ts
4263
+ /**
4264
+ * @internal @brnshkr/config/eslint
4265
+ */
1953
4266
  const perfectionist = async () => {
1954
4267
  const { requiredAll: [pluginPerfectionist] } = await resolvePackages(MODULES.perfectionist);
1955
4268
  if (!pluginPerfectionist) return [];
@@ -1971,6 +4284,9 @@ const perfectionist = async () => {
1971
4284
  };
1972
4285
  //#endregion
1973
4286
  //#region ../src/js/eslint/configs/regexp.ts
4287
+ /**
4288
+ * @internal @brnshkr/config/eslint
4289
+ */
1974
4290
  const regexp = async () => {
1975
4291
  const { requiredAll: [pluginRegExp] } = await resolvePackages(MODULES.regexp);
1976
4292
  if (!pluginRegExp) return [];
@@ -1985,10 +4301,8 @@ const regexp = async () => {
1985
4301
  "regexp/confusing-quantifier": "error",
1986
4302
  "regexp/grapheme-string-literal": "error",
1987
4303
  "regexp/letter-case": ["error", {
1988
- caseInsensitive: "lowercase",
1989
- unicodeEscape: "lowercase",
1990
- hexadecimalEscape: "lowercase",
1991
- controlEscape: "uppercase"
4304
+ unicodeEscape: "uppercase",
4305
+ hexadecimalEscape: "uppercase"
1992
4306
  }],
1993
4307
  "regexp/no-control-character": "error",
1994
4308
  "regexp/no-empty-alternative": "error",
@@ -2013,7 +4327,6 @@ const regexp = async () => {
2013
4327
  "regexp/sort-character-class-elements": "error",
2014
4328
  "regexp/unicode-escape": "error",
2015
4329
  "regexp/unicode-property": ["error", {
2016
- generalCategory: "never",
2017
4330
  key: "short",
2018
4331
  property: "long"
2019
4332
  }]
@@ -2022,21 +4335,22 @@ const regexp = async () => {
2022
4335
  };
2023
4336
  //#endregion
2024
4337
  //#region ../src/js/eslint/configs/style.ts
4338
+ /**
4339
+ * @internal @brnshkr/config/eslint
4340
+ */
2025
4341
  const style = async () => {
2026
4342
  const { requiredAll: [pluginStyle] } = await resolvePackages(MODULES.style);
2027
4343
  if (!pluginStyle) return [];
2028
4344
  const styleConfig = pluginStyle.configs.customize({
2029
- jsx: true,
2030
4345
  semi: true,
2031
4346
  indent: 2,
2032
4347
  quotes: QUOTES,
2033
4348
  quoteProps: "as-needed",
2034
4349
  arrowParens: true,
2035
- blockSpacing: true,
2036
- braceStyle: "1tbs",
2037
- commaDangle: "always-multiline"
4350
+ braceStyle: "1tbs"
2038
4351
  });
2039
- 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;
2040
4354
  return [{
2041
4355
  name: buildConfigName(MAIN_SCOPES.STYLE, SUB_SCOPES.SETUP),
2042
4356
  plugins: { style: pluginStyle }
@@ -2343,6 +4657,9 @@ const style = async () => {
2343
4657
  };
2344
4658
  //#endregion
2345
4659
  //#region ../src/js/eslint/configs/svelte.ts
4660
+ /**
4661
+ * @internal @brnshkr/config/eslint
4662
+ */
2346
4663
  const extractRelevantConfig = (configs, key) => {
2347
4664
  for (const config of configs) if (config.name === `svelte:${key}` && config.rules) return config;
2348
4665
  throw new Error(`Expected key "${key}" to be contained in given config.`);
@@ -2383,14 +4700,11 @@ const svelte = async () => {
2383
4700
  style: ["scss", null]
2384
4701
  }],
2385
4702
  "svelte/button-has-type": "error",
2386
- "svelte/consistent-selector-style": ["error", {
2387
- checkGlobal: false,
2388
- style: [
2389
- "type",
2390
- "class",
2391
- "id"
2392
- ]
2393
- }],
4703
+ "svelte/consistent-selector-style": ["error", { style: [
4704
+ "type",
4705
+ "class",
4706
+ "id"
4707
+ ] }],
2394
4708
  "svelte/derived-has-same-inputs-outputs": "error",
2395
4709
  "svelte/experimental-require-strict-events": "error",
2396
4710
  "svelte/experimental-require-slot-types": "error",
@@ -2404,25 +4718,29 @@ const svelte = async () => {
2404
4718
  }],
2405
4719
  "style/indent": "off",
2406
4720
  "svelte/indent": ["error", { indent: 2 }],
2407
- "svelte/max-attributes-per-line": ["error", {
2408
- multiline: 1,
2409
- singleline: 3
2410
- }],
4721
+ "svelte/max-attributes-per-line": ["error", { singleline: 3 }],
2411
4722
  "svelte/mustache-spacing": "error",
2412
4723
  "svelte/no-add-event-listener": "error",
4724
+ "svelte/no-at-const-tags": "error",
2413
4725
  "svelte/no-at-debug-tags": "error",
4726
+ "svelte/no-bind-value-on-checkable-inputs": "error",
4727
+ "svelte/no-conflicting-module-names": "error",
2414
4728
  "svelte/no-extra-reactive-curlies": "error",
2415
4729
  "svelte/no-ignored-unsubscribe": "error",
2416
- "svelte/no-inline-styles": "error",
4730
+ "svelte/no-inline-styles": ["error", { allowTransitions: false }],
2417
4731
  "svelte/no-inspect": "error",
4732
+ "svelte/no-nested-style-tag": "error",
2418
4733
  "svelte/no-spaces-around-equal-signs-in-attribute": "error",
2419
4734
  "svelte/no-target-blank": "error",
2420
4735
  "svelte/no-top-level-browser-globals": "error",
2421
4736
  "style/no-trailing-spaces": "off",
2422
4737
  "svelte/no-trailing-spaces": "error",
4738
+ "svelte/no-unused-props": ["error", { checkImportedTypes: true }],
4739
+ "svelte/prefer-attribute-interpolation": "error",
2423
4740
  "svelte/prefer-class-directive": "error",
2424
4741
  "prefer-const": "off",
2425
4742
  "svelte/prefer-const": "error",
4743
+ "svelte/prefer-derived-over-derived-by": "error",
2426
4744
  "svelte/prefer-destructured-store-props": "error",
2427
4745
  "svelte/prefer-style-directive": "error",
2428
4746
  "svelte/require-event-prefix": "error",
@@ -2441,6 +4759,9 @@ const svelte = async () => {
2441
4759
  };
2442
4760
  //#endregion
2443
4761
  //#region ../src/js/eslint/configs/test.ts
4762
+ /**
4763
+ * @internal @brnshkr/config/eslint
4764
+ */
2444
4765
  const test = async () => {
2445
4766
  const { requiredAll: [pluginVitest] } = await resolvePackages(MODULES.test);
2446
4767
  if (!pluginVitest) return [];
@@ -2480,7 +4801,7 @@ const test = async () => {
2480
4801
  "test/prefer-hooks-on-top": "error",
2481
4802
  "test/prefer-import-in-mock": "error",
2482
4803
  "test/prefer-importing-vitest-globals": "error",
2483
- "test/prefer-lowercase-title": "error",
4804
+ "test/prefer-lowercase-title": ["error", { ignore: ["describe"] }],
2484
4805
  "test/prefer-mock-promise-shorthand": "error",
2485
4806
  "test/prefer-snapshot-hint": "error",
2486
4807
  "test/prefer-spy-on": "error",
@@ -2494,13 +4815,17 @@ const test = async () => {
2494
4815
  "test/prefer-vi-mocked": "error",
2495
4816
  "test/require-awaited-expect-poll": "error",
2496
4817
  "test/require-hook": "error",
2497
- "test/require-mock-type-parameters": "error",
4818
+ "test/require-mock-type-parameters": ["error", { checkImportFunctions: true }],
4819
+ "test/valid-expect": ["error", { alwaysAwait: true }],
2498
4820
  "test/warn-todo": "error"
2499
4821
  }
2500
4822
  }];
2501
4823
  };
2502
4824
  //#endregion
2503
4825
  //#region ../src/js/eslint/configs/toml.ts
4826
+ /**
4827
+ * @internal @brnshkr/config/eslint
4828
+ */
2504
4829
  const toml = async () => {
2505
4830
  const { requiredAll: [pluginToml] } = await resolvePackages(MODULES.toml);
2506
4831
  if (!pluginToml) return [];
@@ -2526,6 +4851,9 @@ const toml = async () => {
2526
4851
  };
2527
4852
  //#endregion
2528
4853
  //#region ../src/js/eslint/configs/yaml.ts
4854
+ /**
4855
+ * @internal @brnshkr/config/eslint
4856
+ */
2529
4857
  const yaml = async () => {
2530
4858
  const { requiredAll: [pluginYaml] } = await resolvePackages(MODULES.yaml);
2531
4859
  if (!pluginYaml) return [];
@@ -2538,7 +4866,6 @@ const yaml = async () => {
2538
4866
  language: "yaml/yaml",
2539
4867
  rules: {
2540
4868
  ...renameRules(pluginYaml.configs.standard[2]?.rules, { yml: "yaml" }),
2541
- "yaml/block-mapping-colon-indicator-newline": ["error", "never"],
2542
4869
  "yaml/file-extension": "error",
2543
4870
  "yaml/flow-mapping-curly-spacing": [
2544
4871
  "error",
@@ -2546,23 +4873,21 @@ const yaml = async () => {
2546
4873
  { emptyObjects: "never" }
2547
4874
  ],
2548
4875
  "yaml/indent": ["error", 2],
2549
- "yaml/no-multiple-empty-lines": "error",
2550
- "yaml/no-trailing-zeros": "error",
2551
- "yaml/quotes": ["error", {
2552
- avoidEscape: true,
2553
- prefer: QUOTES
2554
- }],
4876
+ "yaml/no-boolean-key": "error",
4877
+ "yaml/quotes": ["error", { prefer: QUOTES }],
2555
4878
  "yaml/require-string-key": "error"
2556
4879
  }
2557
4880
  }];
2558
4881
  };
2559
4882
  //#endregion
2560
4883
  //#region ../src/js/eslint/configs/index.ts
4884
+ /**
4885
+ * @internal @brnshkr/config/eslint
4886
+ */
2561
4887
  const configs = {
2562
4888
  [packageOrganization]: builtinConfig[packageOrganization],
2563
4889
  comments,
2564
4890
  css,
2565
- gitignore,
2566
4891
  ignores,
2567
4892
  import: imports,
2568
4893
  javascript,
@@ -2583,12 +4908,49 @@ const configs = {
2583
4908
  };
2584
4909
  //#endregion
2585
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
+ */
2586
4949
  const getConfig = (optionsAndGlobalConfig, ...additionalConfigs) => {
2587
4950
  const resolvedOptions = {
2588
4951
  [packageOrganization]: isModuleEnabledByDefault(MODULES[packageOrganization]),
2589
4952
  comments: isModuleEnabledByDefault(MODULES.comments),
2590
4953
  css: isModuleEnabledByDefault(MODULES.css),
2591
- gitignore: isModuleEnabledByDefault(MODULES.gitignore),
2592
4954
  import: isModuleEnabledByDefault(MODULES.import),
2593
4955
  jsdoc: isModuleEnabledByDefault(MODULES.jsdoc),
2594
4956
  json: isModuleEnabledByDefault(MODULES.json),
@@ -2607,8 +4969,7 @@ const getConfig = (optionsAndGlobalConfig, ...additionalConfigs) => {
2607
4969
  };
2608
4970
  setModuleEnabled(MODULES[packageOrganization], resolvedOptions[packageOrganization]);
2609
4971
  setModuleEnabled(MODULES.comments, resolvedOptions.comments);
2610
- setModuleEnabled(MODULES.css, resolvedOptions.css);
2611
- setModuleEnabled(MODULES.gitignore, resolvedOptions.gitignore !== false);
4972
+ setModuleEnabled(MODULES.css, resolvedOptions.css !== false);
2612
4973
  setModuleEnabled(MODULES.import, resolvedOptions.import);
2613
4974
  setModuleEnabled(MODULES.jsdoc, resolvedOptions.jsdoc);
2614
4975
  setModuleEnabled(MODULES.json, resolvedOptions.json);
@@ -2628,13 +4989,12 @@ const getConfig = (optionsAndGlobalConfig, ...additionalConfigs) => {
2628
4989
  composer.append(...configsToAppend);
2629
4990
  };
2630
4991
  appendToComposer(configs.ignores(resolvedOptions.ignores));
2631
- if (isModuleEnabled(MODULES.gitignore)) appendToComposer(configs.gitignore(typeof resolvedOptions.gitignore === "object" ? resolvedOptions.gitignore : { strict: false }));
2632
4992
  appendToComposer(configs.javascript());
2633
4993
  if (isModuleEnabled(MODULES.typescript)) appendToComposer(configs.typescript(typeof resolvedOptions.typescript === "object" ? resolvedOptions.typescript : void 0));
2634
4994
  if (isModuleEnabled(MODULES.test)) appendToComposer(configs.test());
2635
4995
  if (isModuleEnabled(MODULES.jsdoc)) appendToComposer(configs.jsdoc());
2636
4996
  if (isModuleEnabled(MODULES.node)) appendToComposer(configs.node(typeof resolvedOptions.node === "object" ? resolvedOptions.node : void 0));
2637
- if (isModuleEnabled(MODULES[packageOrganization])) appendToComposer(configs[packageOrganization]());
4997
+ if (isModuleEnabled(MODULES[packageOrganization])) appendToComposer(configs[packageOrganization](resolvedOptions.typescript));
2638
4998
  if (isModuleEnabled(MODULES.comments)) appendToComposer(configs.comments());
2639
4999
  if (isModuleEnabled(MODULES.regexp)) appendToComposer(configs.regexp());
2640
5000
  if (isModuleEnabled(MODULES.unicorn)) appendToComposer(configs.unicorn());
@@ -2645,12 +5005,17 @@ const getConfig = (optionsAndGlobalConfig, ...additionalConfigs) => {
2645
5005
  if (isModuleEnabled(MODULES.json)) appendToComposer(configs.json());
2646
5006
  if (isModuleEnabled(MODULES.toml)) appendToComposer(configs.toml());
2647
5007
  if (isModuleEnabled(MODULES.yaml)) appendToComposer(configs.yaml());
2648
- if (isModuleEnabled(MODULES.css)) appendToComposer(configs.css());
5008
+ if (isModuleEnabled(MODULES.css)) appendToComposer(configs.css(typeof resolvedOptions.css === "object" ? resolvedOptions.css : void 0));
2649
5009
  if (isModuleEnabled(MODULES.markdown)) appendToComposer(configs.markdown(typeof resolvedOptions.markdown === "object" ? resolvedOptions.markdown : void 0));
2650
- appendToComposer(configs.overrides());
5010
+ appendToComposer(configs.overrides(resolvedOptions.typescript));
2651
5011
  appendToComposer(...getUserConfigs(resolvedOptions, additionalConfigs));
2652
5012
  return composer;
2653
5013
  };
5014
+ /**
5015
+ * Default-exported pre-built composer for direct re-export from an ESLint flat-config file.
5016
+ *
5017
+ * @api
5018
+ */
2654
5019
  var eslint_default = getConfig();
2655
5020
  //#endregion
2656
5021
  export { eslint_default as default, getConfig };