Skip to content

Commit d1417e1

Browse files
authored
Merge pull request #113 from Xvezda/feature/types-test
feature/types test
2 parents fa9f738 + eaa803d commit d1417e1

15 files changed

+431
-50
lines changed

eslint.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
"use strict";
22

33
const tseslint = require("typescript-eslint");
4+
const plugin = require("./src/plugin");
45

56
module.exports = tseslint.config(
67
tseslint.configs.recommendedTypeChecked,
8+
plugin.configs.recommendedTypeChecked,
79
{
810
files: ["**/*.js"],
911
languageOptions: {

examples/fixed.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,17 @@ function promised() {
7575
}
7676
promised().catch(() => {});
7777

78+
/**
79+
* @param {string} path
80+
* @param {AbortController} controller
81+
* @throws {Promise<DOMException>}
82+
*/
83+
async function fetchAPI(path, controller) {
84+
return await fetch(path, {
85+
signal: controller.signal,
86+
});
87+
}
88+
const controller = new AbortController();
89+
fetchAPI('/test', controller).catch(console.error);
7890

7991
export {};

examples/tsconfig.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@
3232
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
3333
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
3434
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
35-
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
35+
"typeRoots": [
36+
"../node_modules/@types",
37+
"../node_modules/@types-with-exceptions"
38+
], /* Specify multiple folders that act like './node_modules/@types'. */
3639
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
3740
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
3841
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */

examples/unsafe.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,16 @@ function promised() {
5151
}
5252
promised().catch(() => {});
5353

54+
/**
55+
* @param {string} path
56+
* @param {AbortController} controller
57+
*/
58+
async function fetchAPI(path, controller) {
59+
return await fetch(path, {
60+
signal: controller.signal,
61+
});
62+
}
63+
const controller = new AbortController();
64+
fetchAPI('/test', controller).catch(console.error);
5465

5566
export {};

jsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
3434
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
3535
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
36-
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
36+
"typeRoots": ["./node_modules/@types", "./node_modules/@types-with-exceptions"], /* Specify multiple folders that act like './node_modules/@types'. */
3737
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
3838
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
3939
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"license": "MIT",
3838
"packageManager": "[email protected]",
3939
"devDependencies": {
40-
"@types/node": "^22.15.21",
40+
"@types-with-exceptions/node": "22.15.29-dev.0",
4141
"@typescript-eslint/parser": "^8.32.1",
4242
"@typescript-eslint/rule-tester": "^8.32.1",
4343
"@typescript-eslint/type-utils": "^8.32.1",

pnpm-lock.yaml

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/rules/check-throws-tag-type.js

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const {
1515
isPromiseConstructorCallbackNode,
1616
isThenableCallbackNode,
1717
isAccessorNode,
18+
appendThrowsTags,
1819
hasJSDocThrowsTag,
1920
getJSDocThrowsTags,
2021
getJSDocThrowsTagTypes,
@@ -401,20 +402,6 @@ module.exports = createRule({
401402

402403
if (documentedThrowsTags.length > 1) {
403404
const callerJSDocTSNode = lastThrowsTag.parent;
404-
/**
405-
* @param {string} jsdocString
406-
* @param {string[]} typeStrings
407-
* @returns {string}
408-
*/
409-
const appendThrowsTags = (jsdocString, typeStrings) =>
410-
typeStrings.reduce((acc, typeString) =>
411-
acc.replace(
412-
/([^*\n]+)(\*+[/])/,
413-
`$1* @throws {${typeString}}\n$1$2`
414-
),
415-
jsdocString
416-
);
417-
418405
context.report({
419406
node,
420407
messageId: 'throwTypeMismatch',

src/rules/no-undocumented-throws.js

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ const {
88
getNodeIndent,
99
getFirst,
1010
createRule,
11+
appendThrowsTags,
12+
hasJSDoc,
1113
hasJSDocThrowsTag,
1214
typesToUnionString,
1315
typeStringsToUnionString,
@@ -233,6 +235,30 @@ module.exports = createRule({
233235
fix(fixer) {
234236
const indent = getNodeIndent(sourceCode, node);
235237

238+
if (hasJSDoc(sourceCode, nodeToComment)) {
239+
const comments = sourceCode.getCommentsBefore(nodeToComment);
240+
const comment = comments
241+
.find(({ value }) => value.trim().startsWith('*'));
242+
243+
if (comment) {
244+
let newCommentText = sourceCode.getText(comment);
245+
if (!/^\/\*\*[ \t]*\n/.test(newCommentText)) {
246+
newCommentText = newCommentText
247+
.replace(/^\/\*\*\s*/, `/**\n${indent} * `)
248+
.replace(/\s*\*\/$/, `\n${indent} * @throws\n${indent} */`)
249+
} else {
250+
newCommentText = newCommentText.replace(
251+
/([^*\n]+)(\*+[/])/,
252+
`$1* @throws\n$1$2`
253+
);
254+
}
255+
return fixer.replaceTextRange(
256+
comment.range,
257+
newCommentText,
258+
);
259+
}
260+
}
261+
236262
return fixer
237263
.insertTextBefore(
238264
nodeToComment,
@@ -251,8 +277,6 @@ module.exports = createRule({
251277
node: nodeToComment,
252278
messageId: 'missingThrowsTag',
253279
fix(fixer) {
254-
const indent = getNodeIndent(sourceCode, node);
255-
256280
const newType =
257281
node.async
258282
? `Promise<${
@@ -285,6 +309,32 @@ module.exports = createRule({
285309
: [],
286310
]);
287311

312+
const indent = getNodeIndent(sourceCode, node);
313+
314+
if (hasJSDoc(sourceCode, nodeToComment)) {
315+
const comments = sourceCode.getCommentsBefore(nodeToComment);
316+
const comment = comments
317+
.find(({ value }) => value.startsWith('*'));
318+
319+
if (comment) {
320+
let newCommentText = sourceCode.getText(comment);
321+
if (!/^\/\*\*[ \t]*\n/.test(newCommentText)) {
322+
newCommentText = newCommentText
323+
.replace(/^\/\*\*\s*/, `/**\n${indent} * `)
324+
.replace(/\s*\*\/$/, `\n${indent} * @throws {${newType}}\n${indent} */`)
325+
} else {
326+
newCommentText = appendThrowsTags(
327+
newCommentText,
328+
[newType],
329+
);
330+
}
331+
return fixer.replaceTextRange(
332+
comment.range,
333+
newCommentText,
334+
);
335+
}
336+
}
337+
288338
return fixer
289339
.insertTextBefore(
290340
nodeToComment,

src/utils.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,36 @@ const getNodeIndent = (sourceCode, node) => {
106106
return indent;
107107
};
108108

109+
/**
110+
* @public
111+
* @param {string} jsdocString
112+
* @param {string[]} typeStrings
113+
* @returns {string}
114+
*/
115+
const appendThrowsTags = (jsdocString, typeStrings) =>
116+
typeStrings.reduce((acc, typeString) =>
117+
acc.replace(
118+
/([^*\n]+)(\*+[/])/,
119+
`$1* @throws {${typeString}}\n$1$2`
120+
),
121+
jsdocString
122+
);
123+
124+
/**
125+
* Check if node has any valid JSDoc.
126+
*
127+
* @public
128+
* @param {Readonly<import('@typescript-eslint/utils').TSESLint.SourceCode>} sourceCode
129+
* @param {import('@typescript-eslint/utils').TSESTree.Node} node
130+
* @return {boolean}
131+
*/
132+
const hasJSDoc = (sourceCode, node) => {
133+
const comments = sourceCode.getCommentsBefore(node);
134+
if (!comments.length) return false;
135+
136+
return comments.some(comment => comment.value.startsWith('*'));
137+
};
138+
109139
/**
110140
* Check if node has JSDoc comment with @throws or @exception tag.
111141
*
@@ -912,7 +942,9 @@ module.exports = {
912942
getNodeID,
913943
getNodeIndent,
914944
createRule,
945+
appendThrowsTags,
915946
hasThrowsTag,
947+
hasJSDoc,
916948
hasJSDocThrowsTag,
917949
typeStringsToUnionString,
918950
typesToUnionString,

tests/rules/check-throws-tag-type.test.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
// @ts-check
2+
const path = require('node:path');
23
const { RuleTester } = require('@typescript-eslint/rule-tester');
34
const rule = require('../../src/rules/check-throws-tag-type');
45

56
const ruleTester = new RuleTester({
67
languageOptions: {
78
parserOptions: {
9+
tsconfigRootDir: path.resolve(path.join(__dirname, '..')),
810
projectService: {
9-
allowDefaultProject: ['*.ts*'],
11+
allowDefaultProject: [
12+
'*.ts',
13+
'*.js',
14+
],
1015
},
1116
JSDocParsingMode: 'all',
1217
},

0 commit comments

Comments
 (0)