Skip to content

Commit 4634194

Browse files
su8898knocte
authored andcommitted
Add new rule FavourReRaise
Fixes #509
1 parent 3b87c69 commit 4634194

File tree

9 files changed

+180
-1
lines changed

9 files changed

+180
-1
lines changed

docs/content/how-tos/rule-configuration.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,5 @@ The following rules can be specified for linting.
112112
- [GenericTypesNames (FL0069)](rules/FL0069.html)
113113
- [FavourTypedIgnore (FL0070)](rules/FL0070.html)
114114
- [CyclomaticComplexity (FL0071)](rules/FL0071.html)
115-
- [FailwithBadUsage (FL0072)](rules/FL0072.html)
115+
- [FailwithBadUsage (FL0072)](rules/FL0072.html)
116+
- [FavourReRaise (FL0073)](rules/FL0073.html)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
title: FL0073
3+
category: how-to
4+
hide_menu: true
5+
---
6+
7+
# FailwithBadUsage (FL0073)
8+
9+
*Introduced in `0.20.3`*
10+
11+
## Cause
12+
13+
Rule to detect usage of `raise ex` instead of `reraise()` inside `try...with|ex` blocks.
14+
15+
## Rationale
16+
17+
Using `raise ex` to re-raise an exception is bad as it loses valuable stacktrace information from the original exception.
18+
19+
## How To Fix
20+
21+
Use `reraise()` instead of `raise ex` inside `try...with|ex` blocks.
22+
23+
## Rule Settings
24+
25+
{
26+
"favourReRaise": {
27+
"enabled": true
28+
}
29+
}

src/FSharpLint.Core/FSharpLint.Core.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
<Compile Include="Rules\Conventions\NestedStatements.fs" />
4848
<Compile Include="Rules\Conventions\NoPartialFunctions.fs" />
4949
<Compile Include="Rules\Conventions\CyclomaticComplexity.fs" />
50+
<Compile Include="Rules\Conventions\FavourReRaise.fs" />
5051
<Compile Include="Rules\Conventions\RaiseWithTooManyArguments\RaiseWithTooManyArgumentsHelper.fs" />
5152
<Compile Include="Rules\Conventions\RaiseWithTooManyArguments\FailwithWithSingleArgument.fs" />
5253
<Compile Include="Rules\Conventions\RaiseWithTooManyArguments\RaiseWithSingleArgument.fs" />
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
module FSharpLint.Rules.FavourReRaise
2+
3+
open System
4+
open FSharpLint.Framework
5+
open FSharpLint.Framework.Suggestion
6+
open FSharp.Compiler.Syntax
7+
open FSharpLint.Framework.Ast
8+
open FSharpLint.Framework.Rules
9+
10+
let private runner (args: AstNodeRuleParams) =
11+
let generateError range =
12+
{ Range = range
13+
Message = Resources.GetString "RulesFavourReRaise"
14+
SuggestedFix = None
15+
TypeChecks = List.empty }
16+
|> Array.singleton
17+
18+
let rec checkExpr (expr) maybeIdent =
19+
match expr with
20+
| SynExpr.App (_, _, SynExpr.Ident raiseId, expression, range) when raiseId.idText = "raise" ->
21+
match expression with
22+
| SynExpr.Ident ident ->
23+
match maybeIdent with
24+
| Some id when id = ident.idText ->
25+
generateError range
26+
| _ -> Array.empty
27+
| SynExpr.LongIdent (_, LongIdentWithDots (id, _), _, range) -> generateError range
28+
| _ -> Array.empty
29+
| SynExpr.TryWith (expressions, _, clauseList, _expression, _range, _, _) as expr ->
30+
clauseList
31+
|> List.toArray
32+
|> Array.collect (fun clause ->
33+
match clause with
34+
| SynMatchClause (pat, _, app, _, _) ->
35+
match pat with
36+
| SynPat.Named (_, id, _, _, _) -> checkExpr app (Some id.idText)
37+
| _ -> checkExpr app None)
38+
| SynExpr.IfThenElse (_, expr, _, _, _, _, _) -> checkExpr expr maybeIdent
39+
| _ -> Array.empty
40+
41+
match args.AstNode with
42+
| AstNode.Expression expr -> checkExpr expr None
43+
| _ -> Array.empty
44+
45+
let rule =
46+
{ Name = "FavourReRaise"
47+
Identifier = Identifiers.FavourReRaise
48+
RuleConfig =
49+
{ AstNodeRuleConfig.Runner = runner
50+
Cleanup = ignore } }
51+
|> AstNodeRule

src/FSharpLint.Core/Rules/Identifiers.fs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,4 @@ let GenericTypesNames = identifier 69
7777
let FavourTypedIgnore = identifier 70
7878
let CyclomaticComplexity = identifier 71
7979
let FailwithBadUsage = identifier 72
80+
let FavourReRaise = identifier 73

src/FSharpLint.Core/Text.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,4 +327,7 @@
327327
<data name="RulesFailwithBadUsage" xml:space="preserve">
328328
<value>Bad usage of failwith: {0}.</value>
329329
</data>
330+
<data name="RulesFavourReRaise" xml:space="preserve">
331+
<value>Rather use reraise() instead of using raise with the same captured exception.</value>
332+
</data>
330333
</root>

src/FSharpLint.Core/fsharplint.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@
265265
"uselessBinding": { "enabled": true },
266266
"tupleOfWildcards": { "enabled": true },
267267
"favourTypedIgnore": { "enabled": false },
268+
"favourReRaise": { "enabled": true },
268269
"indentation": {
269270
"enabled": false
270271
},

tests/FSharpLint.Core.Tests/FSharpLint.Core.Tests.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
<Compile Include="Rules\Conventions\FunctionReimplementation.fs" />
3636
<Compile Include="Rules\Conventions\SourceLength.fs" />
3737
<Compile Include="Rules\Conventions\NoPartialFunctions.fs" />
38+
<Compile Include="Rules\Conventions\FavourReRaise.fs" />
3839
<Compile Include="Rules\Conventions\Naming\NamingHelpers.fs" />
3940
<Compile Include="Rules\Conventions\Naming\InterfaceNames.fs" />
4041
<Compile Include="Rules\Conventions\Naming\ExceptionNames.fs" />
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
module FSharpLint.Core.Tests.Rules.Conventions.FavourReRaise
2+
3+
open NUnit.Framework
4+
open FSharpLint.Rules
5+
6+
[<TestFixture>]
7+
type TestBindingFavourReRaise() =
8+
inherit TestAstNodeRuleBase.TestAstNodeRuleBase(FavourReRaise.rule)
9+
10+
[<Test>]
11+
member this.``favour reraise() instead of raise``() =
12+
this.Parse
13+
"""
14+
try
15+
foo ()
16+
with
17+
| ex ->
18+
if someCondition then
19+
reraise() """
20+
21+
this.AssertNoWarnings()
22+
23+
[<Test>]
24+
member this.``using raise ex must generate error``() =
25+
this.Parse
26+
"""
27+
try
28+
foo ()
29+
with
30+
| ex ->
31+
if someCondition then
32+
raise ex """
33+
34+
Assert.IsTrue(this.ErrorsExist)
35+
Assert.IsTrue(this.ErrorExistsAt(7, 9))
36+
37+
[<Test>]
38+
member this.``using raise outside with block must not generate error``() =
39+
this.Parse
40+
"""
41+
let function1 x y =
42+
try
43+
try
44+
if x = y then raise (InnerError("inner"))
45+
else raise (OuterError("outer"))
46+
with
47+
| InnerError(str) -> printfn "Error1 %s" str
48+
finally
49+
printfn "Always print this." """
50+
51+
this.AssertNoWarnings()
52+
53+
[<Test>]
54+
member this.``using raise with an exception that's not in the with block must not generate error``() =
55+
this.Parse
56+
"""
57+
try
58+
foo bar
59+
with
60+
| ex ->
61+
let e = ex.InnerException
62+
if null <> e then
63+
raise e """
64+
65+
this.AssertNoWarnings()
66+
67+
[<Test>]
68+
member this.``using raise with an exception that's not in the with block must not generate error (2)``() =
69+
this.Parse
70+
"""
71+
try
72+
foo bar
73+
with
74+
| ex ->
75+
let e = Exception("baz", ex)
76+
raise e """
77+
78+
this.AssertNoWarnings()
79+
80+
[<Test>]
81+
member this.``using raise with an exception that's not in the with block must not generate error (3)``() =
82+
this.Parse
83+
"""
84+
try
85+
foo bar
86+
with
87+
| e ->
88+
let ex = Exception("baz", e)
89+
raise ex """
90+
91+
this.AssertNoWarnings()

0 commit comments

Comments
 (0)