eslint-plugin-tyrant 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/dist/index.d.ts +105 -0
- package/dist/index.js +50 -0
- package/dist/rules/file-tsdoc-utils.d.ts +7 -0
- package/dist/rules/file-tsdoc-utils.js +33 -0
- package/dist/rules/require-empty-line-after-file-tsdoc.d.ts +2 -0
- package/dist/rules/require-empty-line-after-file-tsdoc.js +37 -0
- package/dist/rules/require-file-tsdoc.d.ts +2 -0
- package/dist/rules/require-file-tsdoc.js +33 -0
- package/dist/rules/require-tsdoc-style-comments-before-exports.d.ts +2 -0
- package/dist/rules/require-tsdoc-style-comments-before-exports.js +92 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# eslint-plugin-tyrant
|
|
2
|
+
|
|
3
|
+
Pure TypeScript ESLint plugin with a rule that requires a TSDoc file comment at the top of each TypeScript module.
|
|
4
|
+
|
|
5
|
+
## Rule
|
|
6
|
+
|
|
7
|
+
- `tyrant/require-file-tsdoc`: require every `.ts`, `.tsx`, `.mts`, and `.cts` file to begin with a `/** ... */` TSDoc comment.
|
|
8
|
+
- Accepted preamble before that TSDoc: UTF-8 BOM, a single shebang line, and optional empty lines after the shebang.
|
|
9
|
+
- `tyrant/require-empty-line-after-file-tsdoc`: require at least one empty line after the top-level file TSDoc comment.
|
|
10
|
+
- `tyrant/require-tsdoc-style-comments-before-exports`: if a comment block exists immediately before an export, every comment in that block must use `/** ... */` TSDoc style.
|
|
11
|
+
- This rule is not auto-fixable. If a file does not need module-level docs, disable the rule in that file explicitly.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import tyrant from "eslint-plugin-tyrant";
|
|
17
|
+
|
|
18
|
+
export default [
|
|
19
|
+
{
|
|
20
|
+
files: ["**/*.{ts,tsx,mts,cts}"],
|
|
21
|
+
plugins: { tyrant },
|
|
22
|
+
rules: {
|
|
23
|
+
"tyrant/require-file-tsdoc": "error"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
];
|
|
27
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export declare const rules: {
|
|
2
|
+
readonly "require-empty-line-after-file-tsdoc": import("eslint").Rule.RuleModule;
|
|
3
|
+
readonly "require-file-tsdoc": import("eslint").Rule.RuleModule;
|
|
4
|
+
readonly "require-tsdoc-style-comments-before-exports": import("eslint").Rule.RuleModule;
|
|
5
|
+
};
|
|
6
|
+
export declare const configs: {
|
|
7
|
+
recommended: {
|
|
8
|
+
files: string[];
|
|
9
|
+
plugins: {
|
|
10
|
+
tyrant: {
|
|
11
|
+
meta: {
|
|
12
|
+
readonly name: "eslint-plugin-tyrant";
|
|
13
|
+
readonly version: "0.1.0";
|
|
14
|
+
};
|
|
15
|
+
rules: {
|
|
16
|
+
readonly "require-empty-line-after-file-tsdoc": import("eslint").Rule.RuleModule;
|
|
17
|
+
readonly "require-file-tsdoc": import("eslint").Rule.RuleModule;
|
|
18
|
+
readonly "require-tsdoc-style-comments-before-exports": import("eslint").Rule.RuleModule;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
rules: {
|
|
23
|
+
"tyrant/require-empty-line-after-file-tsdoc": "error";
|
|
24
|
+
"tyrant/require-file-tsdoc": "error";
|
|
25
|
+
"tyrant/require-tsdoc-style-comments-before-exports": "error";
|
|
26
|
+
};
|
|
27
|
+
}[];
|
|
28
|
+
recommendedTypeScript: {
|
|
29
|
+
files: string[];
|
|
30
|
+
plugins: {
|
|
31
|
+
tyrant: {
|
|
32
|
+
meta: {
|
|
33
|
+
readonly name: "eslint-plugin-tyrant";
|
|
34
|
+
readonly version: "0.1.0";
|
|
35
|
+
};
|
|
36
|
+
rules: {
|
|
37
|
+
readonly "require-empty-line-after-file-tsdoc": import("eslint").Rule.RuleModule;
|
|
38
|
+
readonly "require-file-tsdoc": import("eslint").Rule.RuleModule;
|
|
39
|
+
readonly "require-tsdoc-style-comments-before-exports": import("eslint").Rule.RuleModule;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
rules: {
|
|
44
|
+
"tyrant/require-empty-line-after-file-tsdoc": "error";
|
|
45
|
+
"tyrant/require-file-tsdoc": "error";
|
|
46
|
+
"tyrant/require-tsdoc-style-comments-before-exports": "error";
|
|
47
|
+
};
|
|
48
|
+
}[];
|
|
49
|
+
};
|
|
50
|
+
declare const plugin: {
|
|
51
|
+
meta: {
|
|
52
|
+
readonly name: "eslint-plugin-tyrant";
|
|
53
|
+
readonly version: "0.1.0";
|
|
54
|
+
};
|
|
55
|
+
rules: {
|
|
56
|
+
readonly "require-empty-line-after-file-tsdoc": import("eslint").Rule.RuleModule;
|
|
57
|
+
readonly "require-file-tsdoc": import("eslint").Rule.RuleModule;
|
|
58
|
+
readonly "require-tsdoc-style-comments-before-exports": import("eslint").Rule.RuleModule;
|
|
59
|
+
};
|
|
60
|
+
configs: {
|
|
61
|
+
recommended: {
|
|
62
|
+
files: string[];
|
|
63
|
+
plugins: {
|
|
64
|
+
tyrant: {
|
|
65
|
+
meta: {
|
|
66
|
+
readonly name: "eslint-plugin-tyrant";
|
|
67
|
+
readonly version: "0.1.0";
|
|
68
|
+
};
|
|
69
|
+
rules: {
|
|
70
|
+
readonly "require-empty-line-after-file-tsdoc": import("eslint").Rule.RuleModule;
|
|
71
|
+
readonly "require-file-tsdoc": import("eslint").Rule.RuleModule;
|
|
72
|
+
readonly "require-tsdoc-style-comments-before-exports": import("eslint").Rule.RuleModule;
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
rules: {
|
|
77
|
+
"tyrant/require-empty-line-after-file-tsdoc": "error";
|
|
78
|
+
"tyrant/require-file-tsdoc": "error";
|
|
79
|
+
"tyrant/require-tsdoc-style-comments-before-exports": "error";
|
|
80
|
+
};
|
|
81
|
+
}[];
|
|
82
|
+
recommendedTypeScript: {
|
|
83
|
+
files: string[];
|
|
84
|
+
plugins: {
|
|
85
|
+
tyrant: {
|
|
86
|
+
meta: {
|
|
87
|
+
readonly name: "eslint-plugin-tyrant";
|
|
88
|
+
readonly version: "0.1.0";
|
|
89
|
+
};
|
|
90
|
+
rules: {
|
|
91
|
+
readonly "require-empty-line-after-file-tsdoc": import("eslint").Rule.RuleModule;
|
|
92
|
+
readonly "require-file-tsdoc": import("eslint").Rule.RuleModule;
|
|
93
|
+
readonly "require-tsdoc-style-comments-before-exports": import("eslint").Rule.RuleModule;
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
rules: {
|
|
98
|
+
"tyrant/require-empty-line-after-file-tsdoc": "error";
|
|
99
|
+
"tyrant/require-file-tsdoc": "error";
|
|
100
|
+
"tyrant/require-tsdoc-style-comments-before-exports": "error";
|
|
101
|
+
};
|
|
102
|
+
}[];
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
export default plugin;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { requireEmptyLineAfterFileTSDocRule } from "./rules/require-empty-line-after-file-tsdoc.js";
|
|
2
|
+
import { requireFileTSDocRule } from "./rules/require-file-tsdoc.js";
|
|
3
|
+
import { requireTSDocStyleCommentsBeforeExportsRule } from "./rules/require-tsdoc-style-comments-before-exports.js";
|
|
4
|
+
export const rules = {
|
|
5
|
+
"require-empty-line-after-file-tsdoc": requireEmptyLineAfterFileTSDocRule,
|
|
6
|
+
"require-file-tsdoc": requireFileTSDocRule,
|
|
7
|
+
"require-tsdoc-style-comments-before-exports": requireTSDocStyleCommentsBeforeExportsRule
|
|
8
|
+
};
|
|
9
|
+
const pluginMeta = {
|
|
10
|
+
name: "eslint-plugin-tyrant",
|
|
11
|
+
version: "0.1.0"
|
|
12
|
+
};
|
|
13
|
+
const pluginObject = {
|
|
14
|
+
meta: pluginMeta,
|
|
15
|
+
rules
|
|
16
|
+
};
|
|
17
|
+
export const configs = {
|
|
18
|
+
recommended: [
|
|
19
|
+
{
|
|
20
|
+
files: ["**/*.{ts,tsx,mts,cts}"],
|
|
21
|
+
plugins: {
|
|
22
|
+
tyrant: pluginObject
|
|
23
|
+
},
|
|
24
|
+
rules: {
|
|
25
|
+
"tyrant/require-empty-line-after-file-tsdoc": "error",
|
|
26
|
+
"tyrant/require-file-tsdoc": "error",
|
|
27
|
+
"tyrant/require-tsdoc-style-comments-before-exports": "error"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
recommendedTypeScript: [
|
|
32
|
+
{
|
|
33
|
+
files: ["**/*.{ts,tsx,mts,cts}"],
|
|
34
|
+
plugins: {
|
|
35
|
+
tyrant: pluginObject
|
|
36
|
+
},
|
|
37
|
+
rules: {
|
|
38
|
+
"tyrant/require-empty-line-after-file-tsdoc": "error",
|
|
39
|
+
"tyrant/require-file-tsdoc": "error",
|
|
40
|
+
"tyrant/require-tsdoc-style-comments-before-exports": "error"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
};
|
|
45
|
+
const plugin = {
|
|
46
|
+
meta: pluginMeta,
|
|
47
|
+
rules,
|
|
48
|
+
configs
|
|
49
|
+
};
|
|
50
|
+
export default plugin;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SourceCode } from "eslint";
|
|
2
|
+
export interface FileTSDocComment {
|
|
3
|
+
end: number;
|
|
4
|
+
start: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function isTypeScriptFilename(filename: string): boolean;
|
|
7
|
+
export declare function getTopLevelTSDocComment(sourceCode: Readonly<SourceCode>): FileTSDocComment | null;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const TS_FILE_PATTERN = /\.(?:[cm]?ts|tsx)$/i;
|
|
2
|
+
export function isTypeScriptFilename(filename) {
|
|
3
|
+
return filename !== "<input>" && TS_FILE_PATTERN.test(filename);
|
|
4
|
+
}
|
|
5
|
+
function getFileBodyStart(text) {
|
|
6
|
+
let index = text.charCodeAt(0) === 0xfeff ? 1 : 0;
|
|
7
|
+
if (text.startsWith("#!", index)) {
|
|
8
|
+
const shebangEnd = text.indexOf("\n", index);
|
|
9
|
+
if (shebangEnd === -1) {
|
|
10
|
+
return text.length;
|
|
11
|
+
}
|
|
12
|
+
index = shebangEnd + 1;
|
|
13
|
+
}
|
|
14
|
+
while (index < text.length && text.slice(index, index + 1).trim() === "") {
|
|
15
|
+
index += 1;
|
|
16
|
+
}
|
|
17
|
+
return index;
|
|
18
|
+
}
|
|
19
|
+
export function getTopLevelTSDocComment(sourceCode) {
|
|
20
|
+
const text = sourceCode.getText();
|
|
21
|
+
const expectedStart = getFileBodyStart(text);
|
|
22
|
+
const firstComment = sourceCode
|
|
23
|
+
.getAllComments()
|
|
24
|
+
.find((comment) => comment.range?.[0] !== 0 || !text.startsWith("#!"));
|
|
25
|
+
const [start, end] = firstComment?.range ?? [];
|
|
26
|
+
if (!firstComment || start === undefined || end === undefined || start !== expectedStart) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
if (firstComment.type !== "Block" || !text.slice(start, end).startsWith("/**")) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return { end, start };
|
|
33
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { getTopLevelTSDocComment, isTypeScriptFilename } from "./file-tsdoc-utils.js";
|
|
2
|
+
const MESSAGE_ID = "missingEmptyLineAfterFileTSDoc";
|
|
3
|
+
export const requireEmptyLineAfterFileTSDocRule = {
|
|
4
|
+
meta: {
|
|
5
|
+
type: "layout",
|
|
6
|
+
docs: {
|
|
7
|
+
description: "Require an empty line after the top-level file TSDoc comment."
|
|
8
|
+
},
|
|
9
|
+
hasSuggestions: false,
|
|
10
|
+
schema: [],
|
|
11
|
+
messages: {
|
|
12
|
+
[MESSAGE_ID]: "There should be at least one empty line after the top-level file TSDoc comment."
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
create(context) {
|
|
16
|
+
if (!isTypeScriptFilename(context.filename)) {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
Program(node) {
|
|
21
|
+
const fileTSDocComment = getTopLevelTSDocComment(context.sourceCode);
|
|
22
|
+
if (!fileTSDocComment) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const textAfterComment = context.sourceCode.getText().slice(fileTSDocComment.end);
|
|
26
|
+
if (textAfterComment.startsWith("\n\n") || textAfterComment.startsWith("\r\n\r\n")) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
context.report({
|
|
30
|
+
node,
|
|
31
|
+
loc: context.sourceCode.getLocFromIndex(fileTSDocComment.end),
|
|
32
|
+
messageId: MESSAGE_ID
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { getTopLevelTSDocComment, isTypeScriptFilename } from "./file-tsdoc-utils.js";
|
|
2
|
+
const MESSAGE_ID = "missingFileTSDoc";
|
|
3
|
+
export const requireFileTSDocRule = {
|
|
4
|
+
meta: {
|
|
5
|
+
type: "suggestion",
|
|
6
|
+
docs: {
|
|
7
|
+
description: "Require a TSDoc block comment at the start of every TypeScript file."
|
|
8
|
+
},
|
|
9
|
+
hasSuggestions: false,
|
|
10
|
+
schema: [],
|
|
11
|
+
messages: {
|
|
12
|
+
[MESSAGE_ID]: "Missing a top-level /** ... */ TSDoc block as a file comment. You can disable this rule in the file if it is not necessary."
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
create(context) {
|
|
16
|
+
const filename = context.filename;
|
|
17
|
+
if (!isTypeScriptFilename(filename)) {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
Program(node) {
|
|
22
|
+
if (getTopLevelTSDocComment(context.sourceCode)) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
context.report({
|
|
26
|
+
node,
|
|
27
|
+
loc: { line: 1, column: 0 },
|
|
28
|
+
messageId: MESSAGE_ID
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { isTypeScriptFilename } from "./file-tsdoc-utils.js";
|
|
2
|
+
const MESSAGE_ID = "nonTSDocCommentBeforeExport";
|
|
3
|
+
function hasOnlyWhitespaceBetween(sourceCode, start, end) {
|
|
4
|
+
return sourceCode.getText().slice(start, end).trim() === "";
|
|
5
|
+
}
|
|
6
|
+
function getLeadingCommentBlock(sourceCode, node) {
|
|
7
|
+
if (!node.range) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
const comments = sourceCode.getCommentsBefore(node);
|
|
11
|
+
if (comments.length === 0) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
const relevantComments = [];
|
|
15
|
+
let nextStart = node.range[0];
|
|
16
|
+
for (let index = comments.length - 1; index >= 0; index -= 1) {
|
|
17
|
+
const comment = comments[index];
|
|
18
|
+
const [start, end] = comment.range ?? [];
|
|
19
|
+
if (start === undefined || end === undefined) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (!hasOnlyWhitespaceBetween(sourceCode, end, nextStart)) {
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
relevantComments.unshift(comment);
|
|
26
|
+
nextStart = start;
|
|
27
|
+
}
|
|
28
|
+
return relevantComments;
|
|
29
|
+
}
|
|
30
|
+
function isTSDocStyleComment(sourceCode, comment) {
|
|
31
|
+
const [start, end] = comment.range ?? [];
|
|
32
|
+
if (start === undefined || end === undefined) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
if (comment.type !== "Block") {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return sourceCode.getText().slice(start, end).startsWith("/**");
|
|
39
|
+
}
|
|
40
|
+
export const requireTSDocStyleCommentsBeforeExportsRule = {
|
|
41
|
+
meta: {
|
|
42
|
+
type: "suggestion",
|
|
43
|
+
docs: {
|
|
44
|
+
description: "Require existing comments before exports to use TSDoc block comment style."
|
|
45
|
+
},
|
|
46
|
+
hasSuggestions: false,
|
|
47
|
+
schema: [],
|
|
48
|
+
messages: {
|
|
49
|
+
[MESSAGE_ID]: "Comments immediately before exported declarations must use TSDoc /** ... */ style."
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
create(context) {
|
|
53
|
+
if (!isTypeScriptFilename(context.filename)) {
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
ExportAllDeclaration(node) {
|
|
58
|
+
for (const comment of getLeadingCommentBlock(context.sourceCode, node)) {
|
|
59
|
+
if (isTSDocStyleComment(context.sourceCode, comment)) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
context.report({
|
|
63
|
+
messageId: MESSAGE_ID,
|
|
64
|
+
node
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
ExportDefaultDeclaration(node) {
|
|
69
|
+
for (const comment of getLeadingCommentBlock(context.sourceCode, node)) {
|
|
70
|
+
if (isTSDocStyleComment(context.sourceCode, comment)) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
context.report({
|
|
74
|
+
messageId: MESSAGE_ID,
|
|
75
|
+
node
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
ExportNamedDeclaration(node) {
|
|
80
|
+
for (const comment of getLeadingCommentBlock(context.sourceCode, node)) {
|
|
81
|
+
if (isTSDocStyleComment(context.sourceCode, comment)) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
context.report({
|
|
85
|
+
messageId: MESSAGE_ID,
|
|
86
|
+
node
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "eslint-plugin-tyrant",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "ESLint plugin with TypeScript-focused rules.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"format": "prettier --check .",
|
|
19
|
+
"build": "tsc -p tsconfig.json",
|
|
20
|
+
"lint": "eslint .",
|
|
21
|
+
"test": "node --import tsx --test tests/**/*.test.ts"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"eslint",
|
|
25
|
+
"eslintplugin",
|
|
26
|
+
"typescript",
|
|
27
|
+
"tsdoc"
|
|
28
|
+
],
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"eslint": "^9.0.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@eslint/js": "^9.35.0",
|
|
35
|
+
"@types/node": "^24.5.2",
|
|
36
|
+
"@typescript-eslint/parser": "^8.43.0",
|
|
37
|
+
"eslint": "^9.35.0",
|
|
38
|
+
"eslint-plugin-tsdoc": "^0.4.0",
|
|
39
|
+
"globals": "^16.4.0",
|
|
40
|
+
"jiti": "^2.6.1",
|
|
41
|
+
"prettier": "^3.6.2",
|
|
42
|
+
"prettier-plugin-package": "^2.0.0",
|
|
43
|
+
"tsx": "^4.20.5",
|
|
44
|
+
"typescript": "^5.9.2",
|
|
45
|
+
"typescript-eslint": "^8.43.0"
|
|
46
|
+
}
|
|
47
|
+
}
|