Skip to content
This repository was archived by the owner on Jul 16, 2023. It is now read-only.

feat: implement avoid-throw-in-catch-block #561

Merged
merged 3 commits into from
Nov 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Unreleased

* feat: add static code diagnostics `avoid-unnecessary-type-assertions`, `avoid-unnecessary-type-casts`
* feat: add static code diagnostics `avoid-throw-in-catch-block`, `avoid-unnecessary-type-assertions`, `avoid-unnecessary-type-casts`
* feat: introduce file metrics
* chore: activate self implemented rules: `avoid-unnecessary-type-assertions`, `avoid-unnecessary-type-casts`, `prefer-first`, `prefer-last`, `prefer-match-file-name`
* refactor: cleanup anti-patterns, metrics and rules documentation
Expand Down
3 changes: 3 additions & 0 deletions lib/src/analyzers/lint_analyzer/rules/rules_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'rules_list/avoid_nested_conditional_expressions/avoid_nested_conditional
import 'rules_list/avoid_non_null_assertion/avoid_non_null_assertion_rule.dart';
import 'rules_list/avoid_preserve_whitespace_false/avoid_preserve_whitespace_false_rule.dart';
import 'rules_list/avoid_returning_widgets/avoid_returning_widgets_rule.dart';
import 'rules_list/avoid_throw_in_catch_block/avoid_throw_in_catch_block_rule.dart';
import 'rules_list/avoid_unnecessary_setstate/avoid_unnecessary_setstate_rule.dart';
import 'rules_list/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart';
import 'rules_list/avoid_unused_parameters/avoid_unused_parameters_rule.dart';
Expand Down Expand Up @@ -52,6 +53,8 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
AvoidPreserveWhitespaceFalseRule(config),
AvoidReturningWidgetsRule.ruleId: (config) =>
AvoidReturningWidgetsRule(config),
AvoidThrowInCatchBlockRule.ruleId: (config) =>
AvoidThrowInCatchBlockRule(config),
AvoidUnnecessarySetStateRule.ruleId: (config) =>
AvoidUnnecessarySetStateRule(config),
AvoidUnnecessaryTypeAssertionsRule.ruleId: (config) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// ignore_for_file: public_member_api_docs

import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';

import '../../../../../utils/node_utils.dart';
import '../../../lint_utils.dart';
import '../../../models/internal_resolved_unit_result.dart';
import '../../../models/issue.dart';
import '../../../models/severity.dart';
import '../../models/common_rule.dart';
import '../../rule_utils.dart';

part 'visitor.dart';

class AvoidThrowInCatchBlockRule extends CommonRule {
static const String ruleId = 'avoid-throw-in-catch-block';

static const _warningMessage =
'Call throw in a catch block loses the original stack trace and the original exception.';

AvoidThrowInCatchBlockRule([Map<String, Object> config = const {}])
: super(
id: ruleId,
severity: readSeverity(config, Severity.warning),
excludes: readExcludes(config),
);

@override
Iterable<Issue> check(InternalResolvedUnitResult source) {
final _visitor = _Visitor();

source.unit.visitChildren(_visitor);

return _visitor.throwExpression.map((expression) => createIssue(
rule: this,
location: nodeLocation(
node: expression,
source: source,
),
message: _warningMessage,
));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
part of 'avoid_throw_in_catch_block_rule.dart';

class _Visitor extends RecursiveAstVisitor<void> {
final _throwExpression = <ThrowExpression>[];

Iterable<ThrowExpression> get throwExpression => _throwExpression;

@override
void visitCatchClause(CatchClause node) {
super.visitCatchClause(node);

final visitor = _CatchClauseVisitor();
node.visitChildren(visitor);

_throwExpression.addAll(visitor.throwExpression);
}
}

class _CatchClauseVisitor extends RecursiveAstVisitor<void> {
final _throwExpression = <ThrowExpression>[];

Iterable<ThrowExpression> get throwExpression => _throwExpression;

@override
void visitThrowExpression(ThrowExpression node) {
super.visitThrowExpression(node);

_throwExpression.add(node);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
@TestOn('vm')
import 'package:dart_code_metrics/src/analyzers/lint_analyzer/models/severity.dart';
import 'package:dart_code_metrics/src/analyzers/lint_analyzer/rules/rules_list/avoid_throw_in_catch_block/avoid_throw_in_catch_block_rule.dart';
import 'package:test/test.dart';

import '../../../../../helpers/rule_test_helper.dart';

const _examplePath = 'avoid_throw_in_catch_block/examples/example.dart';

void main() {
group('AvoidThrowInCatchBlockRule', () {
test('initialization', () async {
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
final issues = AvoidThrowInCatchBlockRule().check(unit);

RuleTestHelper.verifyInitialization(
issues: issues,
ruleId: 'avoid-throw-in-catch-block',
severity: Severity.warning,
);
});

test('reports about found issues', () async {
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
final issues = AvoidThrowInCatchBlockRule().check(unit);

RuleTestHelper.verifyIssues(
issues: issues,
startOffsets: [343, 465],
startLines: [17, 25],
startColumns: [5, 5],
endOffsets: [370, 494],
locationTexts: [
'throw RepositoryException()',
'throw DataProviderException()',
],
messages: [
'Call throw in a catch block loses the original stack trace and the original exception.',
'Call throw in a catch block loses the original stack trace and the original exception.',
],
);
});
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
void main() {
try {
repository();
} on Object catch (error, stackTrace) {
logError(error, stackTrace);
}
}

/// Where did the original error occur based on the log?
void logError(Object error, StackTrace stackTrace) =>
print('$error\n$stackTrace');

void repository() {
try {
networkDataProvider();
} on Object {
throw RepositoryException(); // LINT
}
}

void networkDataProvider() {
try {
networkClient();
} on Object {
throw DataProviderException(); // LINT
}
}

void networkClient() {
throw 'Some serious problem';
}

class RepositoryException {}

class DataProviderException {}
23 changes: 23 additions & 0 deletions website/docs/rules/common/avoid-throw-in-catch-block.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Avoid throw in catch block

## Rule id

avoid-throw-in-catch-block

## Description

Call throw in a catch block loses the original stack trace and the original exception.

Sine 2.16 version you can use [Error.throwWithStackTrace](https://api.dart.dev/dev/2.16.0-9.0.dev/dart-core/Error/throwWithStackTrace.html).

### Example

```dart
void repository() {
try {
networkDataProvider();
} on Object {
throw RepositoryException(); // LINT
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
avoid-unnecessary-type-assertions

## Description

Warns about unnecessary usage of 'is' and 'whereType' operators.

### Example
Expand Down
1 change: 1 addition & 0 deletions website/docs/rules/common/avoid-unnecessary-type-casts.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
avoid-unnecessary-type-casts

## Description

Warns about of unnecessary use of casting operators.

### Example
Expand Down
4 changes: 4 additions & 0 deletions website/docs/rules/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ Rules configuration is [described here](../getting-started/configuration#configu

Warns when non null assertion operator (or “bang” operator) is used for a property access or method invocation. The operator check works at runtime and it may fail and throw a runtime exception.

- [avoid-throw-in-catch-block](./common/avoid-throw-in-catch-block.md)

Warns when call `throw` in a catch block.

- [avoid-unnecessary-type-casts](./common/avoid-unnecessary-type-casts.md)

Warns about unnecessary usage of 'as' operators.
Expand Down