Skip to content

Commit b866821

Browse files
authored
Merge PR #649 from Mersho/ImplementQuickFixes
Add quickfix suggestions for several rules.
2 parents 539e80c + bed28b2 commit b866821

File tree

10 files changed

+246
-22
lines changed

10 files changed

+246
-22
lines changed

src/FSharpLint.Core/Rules/Conventions/Binding/FavourIgnoreOverLetWild.fs

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,39 @@ open FSharp.Compiler.Syntax
77
open FSharpLint.Framework.Ast
88
open FSharpLint.Framework.Rules
99

10-
let private checkForBindingToAWildcard pattern range =
10+
let private checkForBindingToAWildcard pattern range fileContent (expr: SynExpr) letBindingRange =
1111
let rec findWildAndIgnoreParens = function
1212
| SynPat.Paren(pattern, _) -> findWildAndIgnoreParens pattern
1313
| SynPat.Wild(_) -> true
1414
| _ -> false
1515

16-
if findWildAndIgnoreParens pattern then
17-
{ Range = range
18-
Message = Resources.GetString("RulesFavourIgnoreOverLetWildError")
19-
SuggestedFix = None
20-
TypeChecks = [] } |> Array.singleton
21-
else
22-
Array.empty
16+
match ExpressionUtilities.tryFindTextOfRange expr.Range fileContent with
17+
| Some exprText ->
18+
if findWildAndIgnoreParens pattern then
19+
{ Range = range
20+
Message = Resources.GetString("RulesFavourIgnoreOverLetWildError")
21+
SuggestedFix = Some (lazy (Some({ FromRange = letBindingRange
22+
FromText = fileContent
23+
ToText = sprintf "(%s) |> ignore" exprText })))
24+
TypeChecks = [] } |> Array.singleton
25+
else
26+
Array.empty
27+
| None -> Array.empty
2328

2429
let private runner (args:AstNodeRuleParams) =
2530
match args.AstNode with
26-
| AstNode.Binding(SynBinding(_, _, _, _, _, _, _, pattern, _, _, range, _, _))
27-
when Helper.Binding.isLetBinding args.NodeIndex args.SyntaxArray ->
28-
checkForBindingToAWildcard pattern range
31+
| AstNode.Binding(SynBinding(_, _, _, _, _, _, _, pattern, _, expr, range, _, _)) ->
32+
let bindingRange =
33+
match args.GetParents(args.NodeIndex) with
34+
| AstNode.ModuleDeclaration(SynModuleDecl.Let(_, _, range)) :: _
35+
| AstNode.Expression(SynExpr.LetOrUse(_, false, _, _, range, _)) :: _ ->
36+
Some(range)
37+
| _ -> None
38+
39+
match bindingRange with
40+
| Some letBindingRange ->
41+
checkForBindingToAWildcard pattern range args.FileContent expr letBindingRange
42+
| None -> Array.empty
2943
| _ -> Array.empty
3044

3145
/// Checks if any code uses 'let _ = ...' and suggests to use the ignore function.

src/FSharpLint.Core/Rules/Conventions/Binding/UselessBinding.fs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ open FSharp.Compiler.CodeAnalysis
99
open FSharpLint.Framework.Ast
1010
open FSharpLint.Framework.Rules
1111

12-
let private checkForUselessBinding (checkInfo:FSharpCheckFileResults option) pattern expr range =
12+
let private checkForUselessBinding (checkInfo:FSharpCheckFileResults option) pattern expr range maybeSuggestedFix =
1313
match checkInfo with
1414
| Some checkInfo ->
1515
let rec findBindingIdentifier = function
@@ -42,17 +42,23 @@ let private checkForUselessBinding (checkInfo:FSharpCheckFileResults option) pat
4242
|> Option.map (fun ident ->
4343
{ Range = range
4444
Message = Resources.GetString("RulesUselessBindingError")
45-
SuggestedFix = None
45+
SuggestedFix = Some (lazy(maybeSuggestedFix))
4646
TypeChecks = [ checkNotMutable ident ] })
4747
|> Option.toArray
4848
| _ -> Array.empty
4949

5050
let private runner (args:AstNodeRuleParams) =
51+
let maybeSuggestedFix =
52+
match args.GetParents(args.NodeIndex) with
53+
| AstNode.ModuleDeclaration(SynModuleDecl.Let(_, _, range)) :: _ ->
54+
Some({ FromRange = range; FromText = "let"; ToText = "" })
55+
| AstNode.Expression(SynExpr.LetOrUse(_, false, _, _, range, _)) :: _ ->
56+
Some({ FromRange = range; FromText = "use"; ToText = "" })
57+
| _ -> None
5158
match args.AstNode with
5259
| AstNode.Binding(SynBinding(_, _, _, isMutable, _, _, _, pattern, _, expr, range, _, _))
53-
when Helper.Binding.isLetBinding args.NodeIndex args.SyntaxArray
54-
&& not isMutable ->
55-
checkForUselessBinding args.CheckInfo pattern expr range
60+
when maybeSuggestedFix.IsSome && not isMutable ->
61+
checkForUselessBinding args.CheckInfo pattern expr range maybeSuggestedFix
5662
| _ ->
5763
Array.empty
5864

src/FSharpLint.Core/Rules/Conventions/FavourConsistentThis.fs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@ let runner (config: Config) args =
2424
if identifiers.Length = 2 then
2525
match identifiers with
2626
| head::_ when isNotConsistent head.idText symbol ->
27+
let suggestedFix = lazy(Some({ FromRange = head.idRange; FromText = head.idText; ToText = symbol }))
2728
let error =
2829
{ Range = range
2930
Message = String.Format(Resources.GetString "RulesFavourConsistentThis", config.Symbol)
30-
SuggestedFix = None
31+
SuggestedFix = Some suggestedFix
3132
TypeChecks = List.Empty }
3233
|> Array.singleton
3334
error

src/FSharpLint.Core/Rules/Conventions/FavourReRaise.fs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,24 @@ open FSharpLint.Framework.Ast
88
open FSharpLint.Framework.Rules
99

1010
let private runner (args: AstNodeRuleParams) =
11-
let generateError range =
11+
let generateError suggestedFix range =
1212
{ Range = range
1313
Message = Resources.GetString "RulesFavourReRaise"
14-
SuggestedFix = None
14+
SuggestedFix = Some suggestedFix
1515
TypeChecks = List.empty }
1616
|> Array.singleton
1717

1818
let rec checkExpr (expr) maybeIdent =
1919
match expr with
2020
| SynExpr.App (_, _, SynExpr.Ident raiseId, expression, range) when raiseId.idText = "raise" ->
21+
let suggestedFix = lazy(Some({ FromRange = range; FromText = raiseId.idText; ToText = "reraise()" }))
2122
match expression with
2223
| SynExpr.Ident ident ->
2324
match maybeIdent with
2425
| Some id when id = ident.idText ->
25-
generateError range
26+
generateError suggestedFix range
2627
| _ -> Array.empty
27-
| SynExpr.LongIdent (_, SynLongIdent (id, _, _), _, range) -> generateError range
28+
| SynExpr.LongIdent (_, SynLongIdent (id, _, _), _, range) -> generateError suggestedFix range
2829
| _ -> Array.empty
2930
| SynExpr.TryWith (expressions, clauseList, _range, _, _, _) as expr ->
3031
clauseList

src/FSharpLint.Core/Rules/Typography/NoTabCharacters.fs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,15 @@ let checkNoTabCharacters literalStrings (args:LineRuleParams) =
2828
if isInLiteralString literalStrings range |> not then
2929
{ Range = range
3030
Message = Resources.GetString("RulesTypographyTabCharacterError")
31-
SuggestedFix = None
31+
SuggestedFix =
32+
Some(
33+
lazy
34+
(Some(
35+
{ FromRange = range
36+
FromText = "\t"
37+
ToText = String.replicate args.GlobalConfig.numIndentationSpaces " " }
38+
))
39+
)
3240
TypeChecks = [] } |> Array.singleton
3341
else
3442
Array.empty

tests/FSharpLint.Core.Tests/Rules/Binding/FavourIgnoreOverLetWild.fs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,40 @@ let a = List.iter (fun x -> ()) []
5050

5151
Assert.IsFalse(this.ErrorsExist)
5252

53+
[<Test>]
54+
member this.LetWildcardUnitValueSuggestedFix() =
55+
let source = """
56+
module Program
57+
58+
let _ = ()"""
59+
let expected = """
60+
module Program
61+
62+
(()) |> ignore"""
63+
this.Parse source
64+
65+
Assert.IsTrue(this.ErrorExistsAt(4, 4))
66+
67+
let result = this.ApplyQuickFix source
68+
69+
Assert.AreEqual(expected, result)
70+
71+
[<Test>]
72+
member this.LetWildCardInParanUnitValueSuggestedFix() =
73+
let source = """
74+
module Program
75+
76+
let ((((_)))) = List.iter (fun x -> ()) []"""
77+
78+
let expected = """
79+
module Program
80+
81+
(List.iter (fun x -> ()) []) |> ignore"""
82+
83+
this.Parse source
84+
85+
Assert.IsTrue(this.ErrorExistsAt(4, 4))
86+
87+
let result = this.ApplyQuickFix source
88+
89+
Assert.AreEqual(expected, result)

tests/FSharpLint.Core.Tests/Rules/Binding/UselessBinding.fs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,46 @@ type Cat() =
6666
"""
6767

6868
Assert.IsFalse(this.ErrorsExist)
69+
70+
[<Test>]
71+
member this.UselessBindingSuggestedFix() =
72+
let source = """
73+
module Program
74+
75+
let a = 10
76+
let a = a"""
77+
let expected = """
78+
module Program
79+
80+
let a = 10
81+
"""
82+
83+
this.Parse source
84+
85+
Assert.IsTrue(this.ErrorExistsAt(5, 4))
86+
87+
let result = this.ApplyQuickFix source
88+
89+
Assert.AreEqual(expected, result)
90+
91+
[<Test>]
92+
member this.UselessBindingWithParensSuggestedFix() =
93+
let source = """
94+
module Program
95+
96+
let a = 10
97+
let ((a)) = ((a))"""
98+
99+
let expected = """
100+
module Program
101+
102+
let a = 10
103+
"""
104+
105+
this.Parse source
106+
107+
Assert.IsTrue(this.ErrorExistsAt(5, 4))
108+
109+
let result = this.ApplyQuickFix source
110+
111+
Assert.AreEqual(expected, result)

tests/FSharpLint.Core.Tests/Rules/Conventions/FavourConsistentThis.fs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,77 @@ type TorMessageDigest(isSha256: bool) =
111111

112112
Assert.IsTrue this.NoErrorsExist
113113

114+
[<Test>]
115+
member this.InConsistentSelfShouldProduceErrorSuggestedFix() =
116+
let source = """
117+
type Foo =
118+
{ Bar : Baz }
119+
member self.FooBar =
120+
()
121+
member this.FooBarBaz x =
122+
failwith "foobarbaz" """
123+
124+
let expected = """
125+
type Foo =
126+
{ Bar : Baz }
127+
member this.FooBar =
128+
()
129+
member this.FooBarBaz x =
130+
failwith "foobarbaz" """
131+
132+
this.Parse source
133+
134+
Assert.IsTrue this.ErrorsExist
135+
136+
let result = this.ApplyQuickFix source
137+
138+
Assert.AreEqual(expected, result)
139+
140+
[<Test>]
141+
member this.InConsistentSelfShouldProduceErrorSuggestedFix2() =
142+
let source = """
143+
type Foo =
144+
{ Bar : Baz }
145+
member this.FooBar =
146+
()
147+
member self.FooBarBaz x =
148+
failwith "foobarbaz" """
149+
150+
let expected = """
151+
type Foo =
152+
{ Bar : Baz }
153+
member this.FooBar =
154+
()
155+
member this.FooBarBaz x =
156+
failwith "foobarbaz" """
157+
158+
this.Parse source
159+
160+
Assert.IsTrue this.ErrorsExist
161+
Assert.IsTrue(this.ErrorExistsAt(6, 11))
162+
163+
let result = this.ApplyQuickFix source
164+
165+
Assert.AreEqual(expected, result)
166+
167+
[<Test>]
168+
member this.InConsistentSelfShouldProduceErrorSuggestedFix3() =
169+
let source = """
170+
type CustomerName(firstName, middleInitial, lastName) =
171+
member this.FirstName = firstName
172+
member self.MiddleInitial = middleInitial
173+
member this.LastName = lastName """
174+
let expected = """
175+
type CustomerName(firstName, middleInitial, lastName) =
176+
member this.FirstName = firstName
177+
member this.MiddleInitial = middleInitial
178+
member this.LastName = lastName """
179+
180+
this.Parse source
181+
182+
Assert.IsTrue this.ErrorsExist
183+
Assert.IsTrue(this.ErrorExistsAt(4, 11))
184+
185+
let result = this.ApplyQuickFix source
186+
187+
Assert.AreEqual(expected, result)

tests/FSharpLint.Core.Tests/Rules/Conventions/FavourReRaise.fs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,30 @@ with
9696
"""
9797

9898
this.AssertNoWarnings()
99+
100+
[<Test>]
101+
member this.``using raise ex must generate error suggested fix``() =
102+
let source = """
103+
try
104+
foo ()
105+
with
106+
| ex ->
107+
if someCondition then
108+
raise ex """
109+
110+
let expected = """
111+
try
112+
foo ()
113+
with
114+
| ex ->
115+
if someCondition then
116+
reraise() """
117+
118+
this.Parse source
119+
120+
Assert.IsTrue(this.ErrorsExist)
121+
Assert.IsTrue(this.ErrorExistsAt(7, 8))
122+
123+
let result = this.ApplyQuickFix source
124+
125+
Assert.AreEqual(expected, result)

tests/FSharpLint.Core.Tests/Rules/Typography/NoTabCharacters.fs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,16 @@ type TestTypographyTabCharacterInFile() =
2727

2828
Assert.IsFalse(this.ErrorExistsAt(2, 23))
2929
Assert.IsFalse(this.ErrorExistsAt(4, 13))
30+
31+
[<Test>]
32+
member this.TabCharacterInFileSuggestedFix() =
33+
let source = "\t"
34+
let expected = String.replicate 4 " "
35+
this.Parse source
36+
37+
Assert.IsTrue(this.ErrorExistsAt(1, 0))
38+
39+
let result = this.ApplyQuickFix source
40+
41+
Assert.AreEqual(expected, result)
42+

0 commit comments

Comments
 (0)