Skip to content

Commit 876ab18

Browse files
authored
Merge pull request #12 from Xvezda/feature/implicit-propagation-rule
Change implicit propagation rules behavior (breaking change)
2 parents 1560619 + a12ae6c commit 876ab18

File tree

5 files changed

+156
-177
lines changed

5 files changed

+156
-177
lines changed

docs/rules/no-implicit-propagation.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,8 @@ This rule reports any function that calls a function documented with `@throws` (
77

88
## Fixer
99

10-
Wraps the code that throws into `try…catch` block.
10+
Inserts a `@throws` JSDoc tag (matching the delegated thrown type) immediately above the function declaration.
1111

1212
## Options
1313

14-
### `tabLength`
15-
16-
Default: `4`
17-
18-
Controls the number of spaces used for indentation in the automatically inserted `try…catch` block.
14+
(TODO)

src/rules/no-implicit-propagation.js

Lines changed: 38 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
// @ts-check
2-
const { ESLintUtils, AST_NODE_TYPES } = require('@typescript-eslint/utils');
2+
const { ESLintUtils } = require('@typescript-eslint/utils');
33
const utils = require('@typescript-eslint/type-utils');
44
const {
55
createRule,
6-
findClosest,
6+
isInHandledContext,
7+
typesToUnionString,
78
hasThrowsTag,
8-
getOptionsFromContext,
99
getCalleeDeclaration,
1010
getDeclarationTSNodeOfESTreeNode,
1111
getJSDocThrowsTags,
@@ -28,35 +28,23 @@ module.exports = createRule({
2828
fixable: 'code',
2929
messages: {
3030
implicitPropagation:
31-
'Implicit propagation of exceptions is not allowed. Use try/catch to handle exceptions.',
31+
'Implicit propagation of exceptions is not allowed. Add JSDoc comment with @throws (or @exception) tag.',
3232
throwTypeMismatch:
3333
'The type of the exception thrown does not match the type specified in the @throws (or @exception) tag.',
3434
},
35-
defaultOptions: [
36-
{ tabLength: 4 },
37-
],
38-
schema: [
39-
{
40-
type: 'object',
41-
properties: {
42-
tabLength: {
43-
type: 'number',
44-
default: 4,
45-
},
46-
},
47-
additionalProperties: false,
48-
},
49-
],
35+
schema: [],
5036
},
5137
create(context) {
52-
const options = getOptionsFromContext(context);
53-
5438
const sourceCode = context.sourceCode;
5539
const services = ESLintUtils.getParserServices(context);
5640
const checker = services.program.getTypeChecker();
5741

42+
const warnedNodes = new Set();
43+
5844
/** @param {import('@typescript-eslint/utils').TSESTree.ExpressionStatement} node */
5945
const visitExpressionStatement = (node) => {
46+
if (isInHandledContext(node)) return;
47+
6048
const callerDeclaration = findClosestFunctionNode(node);
6149
if (!callerDeclaration) return;
6250

@@ -67,8 +55,8 @@ module.exports = createRule({
6755
const isCommented =
6856
comments.length &&
6957
comments
70-
.map(({ value }) => value)
71-
.some(hasThrowsTag);
58+
.map(({ value }) => value)
59+
.some(hasThrowsTag);
7260

7361
// TODO: Branching type checking or not
7462
if (isCommented) {
@@ -146,55 +134,49 @@ module.exports = createRule({
146134
return;
147135
}
148136

137+
if (warnedNodes.has(node.range[0])) return;
138+
warnedNodes.add(node.range[0]);
139+
149140
const calleeDeclaration = getCalleeDeclaration(services, node);
150141
if (!calleeDeclaration) return;
151142

152143
const calleeTags = getJSDocThrowsTags(calleeDeclaration);
153-
const isCalleeThrows = calleeTags.length > 0;
154144

145+
const isCalleeThrows = calleeTags.length > 0;
155146
if (!isCalleeThrows) return;
156-
157-
const lines = sourceCode.getLines();
158-
159-
const currentLine = lines[node.loc.start.line - 1];
160-
const prevLine = lines[node.loc.start.line - 2];
161-
162-
const indent = currentLine.match(/^\s*/)?.[0] ?? '';
163-
const newIndent = indent + ' '.repeat(options.tabLength);
164-
165-
// TODO: Better way to handle this?
166-
if (/^\s*try\s*\{/.test(prevLine)) return;
167-
168-
const nodeToWrap = findClosest(node, (n) =>
169-
n.type === AST_NODE_TYPES.BlockStatement ||
170-
n.type === AST_NODE_TYPES.ExpressionStatement ||
171-
n.type === AST_NODE_TYPES.VariableDeclaration
172-
);
173-
if (!nodeToWrap) return;
147+
148+
const calleeThrowsTypes = getJSDocThrowsTagTypes(checker, calleeDeclaration);
174149

175150
context.report({
176151
node,
177152
messageId: 'implicitPropagation',
178153
fix(fixer) {
179-
return [
180-
fixer.insertTextBefore(nodeToWrap, `try {\n${newIndent}`),
181-
fixer.insertTextAfter(nodeToWrap, `\n${indent}} catch {}`),
182-
];
154+
const lines = sourceCode.getLines();
155+
const currentLine = lines[nodeToComment.loc.start.line - 1];
156+
const indent = currentLine.match(/^\s*/)?.[0] ?? '';
157+
return fixer
158+
.insertTextBefore(
159+
nodeToComment,
160+
`/**\n` +
161+
`${indent} * @throws {${typesToUnionString(checker, calleeThrowsTypes)}}\n` +
162+
`${indent} */\n` +
163+
`${indent}`
164+
);
183165
},
184166
});
185167
};
186168

187169
return {
188-
'ArrowFunctionExpression :not(TryStatement[handler!=null]) ExpressionStatement MemberExpression[property.type="Identifier"]': visitExpressionStatement,
189-
'FunctionDeclaration :not(TryStatement[handler!=null]) ExpressionStatement MemberExpression[property.type="Identifier"]': visitExpressionStatement,
190-
'FunctionExpression :not(TryStatement[handler!=null]) ExpressionStatement MemberExpression[property.type="Identifier"]': visitExpressionStatement,
191-
'ArrowFunctionExpression :not(TryStatement[handler!=null]) ExpressionStatement CallExpression[callee.type="Identifier"]': visitExpressionStatement,
192-
'FunctionDeclaration :not(TryStatement[handler!=null]) ExpressionStatement CallExpression[callee.type="Identifier"]': visitExpressionStatement,
193-
'FunctionExpression :not(TryStatement[handler!=null]) ExpressionStatement CallExpression[callee.type="Identifier"]': visitExpressionStatement,
194-
'ArrowFunctionExpression :not(TryStatement[handler!=null]) ExpressionStatement:has(> AssignmentExpression[left.type="MemberExpression"])': visitExpressionStatement,
195-
'FunctionDeclaration :not(TryStatement[handler!=null]) ExpressionStatement:has(> AssignmentExpression[left.type="MemberExpression"])': visitExpressionStatement,
196-
'FunctionExpression :not(TryStatement[handler!=null]) ExpressionStatement:has(> AssignmentExpression[left.type="MemberExpression"])': visitExpressionStatement,
170+
'ArrowFunctionExpression ExpressionStatement MemberExpression[property.type="Identifier"]': visitExpressionStatement,
171+
'FunctionDeclaration ExpressionStatement MemberExpression[property.type="Identifier"]': visitExpressionStatement,
172+
'FunctionExpression ExpressionStatement MemberExpression[property.type="Identifier"]': visitExpressionStatement,
173+
'ArrowFunctionExpression ExpressionStatement CallExpression[callee.type="Identifier"]': visitExpressionStatement,
174+
'FunctionDeclaration ExpressionStatement CallExpression[callee.type="Identifier"]': visitExpressionStatement,
175+
'FunctionExpression ExpressionStatement CallExpression[callee.type="Identifier"]': visitExpressionStatement,
176+
'ArrowFunctionExpression ExpressionStatement AssignmentExpression[left.type="MemberExpression"]': visitExpressionStatement,
177+
'FunctionDeclaration ExpressionStatement AssignmentExpression[left.type="MemberExpression"]': visitExpressionStatement,
178+
'FunctionExpression ExpressionStatement AssignmentExpression[left.type="MemberExpression"]': visitExpressionStatement,
197179
};
198180
},
199-
defaultOptions: [{ tabLength: 4 }],
181+
defaultOptions: [],
200182
});

0 commit comments

Comments
 (0)