@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.
- package/README.md +177 -228
- package/conf/.gitignore.dist +15 -0
- package/conf/Makefile +3778 -0
- package/conf/Makefile.dist +13 -0
- package/conf/bunfig.dist.toml +5 -0
- package/conf/commitlint.dist.mjs +1 -0
- package/conf/editorconfig.dist +19 -0
- package/conf/launch.dist.json +31 -0
- package/conf/markdownlint.dist.mjs +1 -0
- package/conf/spelling/defaults.json +185 -0
- package/conf/tsconfig.dist.json +3 -0
- package/conf/tsconfig.json +31 -27
- package/conf/vitest.dist.mjs +1 -0
- package/conf/vscode-css-custom-data.dist.json +95 -0
- package/conf/vscode-extensions.dist.json +21 -0
- package/conf/vscode-settings.dist.jsonc +338 -0
- package/dist/commitlint/index.d.mts +72 -0
- package/dist/commitlint/index.mjs +239 -0
- package/dist/eslint/index.d.mts +3897 -1383
- package/dist/eslint/index.mjs +2784 -419
- package/dist/markdownlint/index.d.mts +2304 -0
- package/dist/markdownlint/index.mjs +440 -0
- package/dist/shared.mjs +405 -65
- package/dist/spelling/index.d.mts +30 -0
- package/dist/spelling/index.mjs +2 -0
- package/dist/spelling/spelling.test.d.mts +1 -0
- package/dist/spelling/spelling.test.mjs +12 -0
- package/dist/stylelint/index.d.mts +47 -9
- package/dist/stylelint/index.mjs +145 -47
- package/dist/vitest/index.d.mts +73 -0
- package/dist/vitest/index.mjs +181 -0
- package/package.json +192 -105
- package/conf/tsconfig.json.example +0 -3
- package/dist/scripts/eslint.mjs +0 -16
- package/dist/scripts/stylelint.mjs +0 -29
- /package/conf/{eslint.config.mjs.example → eslint.dist.mjs} +0 -0
- /package/conf/{stylelint.config.mjs.example → stylelint.dist.mjs} +0 -0
package/dist/eslint/index.mjs
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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) =>
|
|
52
|
-
for (const [from, to] of
|
|
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 (
|
|
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(
|
|
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
|
|
114
|
-
"
|
|
115
|
-
"
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
"
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const
|
|
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: {
|
|
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
|
-
[
|
|
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:
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
189
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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.
|
|
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 =
|
|
286
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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: [
|
|
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(),
|
|
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
|
-
|
|
2403
|
+
pluginImportTsRules = renameRules(pluginImport.flatConfigs.typescript.rules, { "import-x": "import" });
|
|
366
2404
|
pluginImportRules = {
|
|
367
2405
|
...renameRules(pluginImport.flatConfigs.recommended.rules, { "import-x": "import" }),
|
|
368
|
-
|
|
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
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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", {
|
|
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", {
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
455
|
-
return [
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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: {
|
|
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":
|
|
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":
|
|
535
|
-
"grouped-accessor-pairs":
|
|
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", {
|
|
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":
|
|
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", {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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-
|
|
657
|
-
"no-
|
|
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
|
-
|
|
712
|
-
|
|
713
|
-
|
|
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,
|
|
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 = [
|
|
725
|
-
typeAwareOptions.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 = (
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
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
|
-
|
|
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
|
-
|
|
804
|
-
"
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
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:
|
|
2897
|
+
typeAware: doesTsConfigExist(resolveTsConfigPath(options)),
|
|
829
2898
|
...options
|
|
830
2899
|
};
|
|
831
|
-
const
|
|
832
|
-
const 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,
|
|
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 :
|
|
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 :
|
|
2923
|
+
ignores: isTypeAware ? typeAwareOptions.ignores : ignoredGlobs,
|
|
855
2924
|
rules: {
|
|
856
|
-
"ts/naming-convention": getNamingConvention(
|
|
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/
|
|
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 ??
|
|
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: [
|
|
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 =
|
|
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":
|
|
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)
|
|
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
|
-
"
|
|
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-
|
|
1786
|
-
"no-
|
|
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/
|
|
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/
|
|
1791
|
-
|
|
1792
|
-
|
|
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
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
"
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
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", {
|
|
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
|
-
|
|
1989
|
-
|
|
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
|
-
|
|
2036
|
-
braceStyle: "1tbs",
|
|
2037
|
-
commaDangle: "always-multiline"
|
|
4350
|
+
braceStyle: "1tbs"
|
|
2038
4351
|
});
|
|
2039
|
-
const
|
|
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
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
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-
|
|
2550
|
-
"yaml/
|
|
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 };
|