Skip to content

Commit ae30b53

Browse files
committed
Add new tag --check to fix command
Add --check functionality to fix command. When passed will ignore fixes and will only check if passed rule has suggested fixes.
1 parent 4886f2e commit ae30b53

File tree

2 files changed

+105
-28
lines changed

2 files changed

+105
-28
lines changed

src/FSharpLint.Console/Program.fs

Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ type private FileType =
2222

2323
type private ExitCode =
2424
| Error = -1
25-
| Success = 0
25+
| Success = 0 // for `fix` when the file is changed and for `fix --check` when there are no fixes available.
2626
| NoSuchRuleName = 1
27-
| NoSuggestedFix = 2
27+
| NoSuggestedFix = 2 // only for fix (without --check)
28+
| FixExists = 3 //only for fix --check
2829

2930
let fileTypeHelp = "Input type the linter will run against. If this is not set, the file type will be inferred from the file extension."
3031

@@ -63,13 +64,15 @@ with
6364
and private FixArgs =
6465
| [<MainCommand; Mandatory>] Fix_Target of ruleName:string * target:string
6566
| Fix_File_Type of FileType
67+
| Check
6668
// fsharplint:enable UnionDefinitionIndentation
6769
with
6870
interface IArgParserTemplate with
6971
member this.Usage =
7072
match this with
7173
| Fix_Target _ -> "Rule name to be applied with suggestedFix and input to lint."
7274
| Fix_File_Type _ -> fileTypeHelp
75+
| Check _ -> "If passed to the fix command, the linter will only check if the fix is needed."
7376
// fsharplint:enable UnionCasesNames
7477

7578
let private parserProgress (output:Output.IOutput) = function
@@ -106,7 +109,7 @@ let private start (arguments:ParseResults<ToolArgs>) (toolsPath:Ionide.ProjInfo.
106109
| Some _
107110
| None -> Output.StandardOutput() :> Output.IOutput
108111

109-
let handleError (status:ExitCode) (str:string) =
112+
let handleError (status: ExitCode) (str: string) =
110113
output.WriteError str
111114
exitCode <- status
112115

@@ -121,44 +124,64 @@ let private start (arguments:ParseResults<ToolArgs>) (toolsPath:Ionide.ProjInfo.
121124
exitCode <- ExitCode.Error
122125
| LintResult.Failure failure -> handleError ExitCode.Error failure.Description
123126

124-
let handleFixResult (ruleName: string) = function
127+
let handleFixResult (ruleName: string) (checkFlag: bool) = function
125128
| LintResult.Success warnings ->
126-
Resources.GetString "ConsoleApplyingSuggestedFixFile" |> output.WriteInfo
127-
let increment = 1
129+
if checkFlag |> not then
130+
Resources.GetString "ConsoleApplyingSuggestedFixFile" |> output.WriteInfo
128131
let noFixIncrement = 0
129-
let countSuggestedFix =
130-
List.fold (fun acc elem -> acc + elem) 0 (
132+
let foundFixIncrement = 1
133+
let noSuggestedFixIncrement = 2
134+
let countFixStatus =
135+
List.fold (fun (accNoFix, accFoundFix, accNoSuggestedFix) elem ->
136+
if elem = noFixIncrement then
137+
(accNoFix + 1, accFoundFix, accNoSuggestedFix)
138+
elif elem = foundFixIncrement then
139+
(accNoFix, accFoundFix + 1, accNoSuggestedFix)
140+
elif elem = noSuggestedFixIncrement then
141+
(accNoFix, accFoundFix, accNoSuggestedFix + 1)
142+
else
143+
failwith "Code should never reach here!") (0, 0, 0) (
131144
List.map (fun (element: Suggestion.LintWarning) ->
132145
let sourceCode = File.ReadAllText element.FilePath
133146
if String.Equals(ruleName, element.RuleName, StringComparison.InvariantCultureIgnoreCase) then
134147
match element.Details.SuggestedFix with
135148
| Some suggestedFix ->
136-
suggestedFix.Force()
137-
|> Option.map (fun suggestedFix ->
138-
let updatedSourceCode =
139-
sourceCode.Replace(
140-
suggestedFix.FromText,
141-
suggestedFix.ToText
149+
((fun checkFlag ->
150+
if not checkFlag then
151+
(suggestedFix.Force()
152+
|> Option.map (fun suggestedFix ->
153+
let updatedSourceCode =
154+
sourceCode.Replace(
155+
suggestedFix.FromText,
156+
suggestedFix.ToText
157+
)
158+
File.WriteAllText(
159+
element.FilePath,
160+
updatedSourceCode,
161+
Encoding.UTF8)
142162
)
143-
File.WriteAllText(
144-
element.FilePath,
145-
updatedSourceCode,
146-
Encoding.UTF8)
147-
)
148-
|> ignore |> fun () -> increment
149-
| None -> noFixIncrement
163+
) |> ignore
164+
else
165+
()
166+
) checkFlag)
167+
|> ignore |> fun () -> foundFixIncrement
168+
| None -> noSuggestedFixIncrement
150169
else
151170
noFixIncrement) warnings)
152171
outputWarnings warnings
153-
154-
if countSuggestedFix > 0 then
172+
let (accNoFix, accFoundFix, accNoSuggestedFix) = countFixStatus
173+
if not checkFlag && accFoundFix > 0 then
155174
exitCode <- ExitCode.Success
156-
else
175+
elif not checkFlag then
157176
exitCode <- ExitCode.NoSuggestedFix
177+
elif checkFlag && accFoundFix > 0 then
178+
exitCode <- ExitCode.FixExists
179+
elif checkFlag then
180+
exitCode <- ExitCode.Success
158181

159182
| LintResult.Failure failure -> handleError ExitCode.Error failure.Description
160183

161-
let linting fileType lintParams target toolsPath shouldFix maybeRuleName =
184+
let linting fileType lintParams target toolsPath shouldFix maybeRuleName checkFlag =
162185
try
163186
let lintResult =
164187
match fileType with
@@ -169,7 +192,7 @@ let private start (arguments:ParseResults<ToolArgs>) (toolsPath:Ionide.ProjInfo.
169192
| _ -> Lint.lintProject lintParams target toolsPath
170193
if shouldFix then
171194
match maybeRuleName with
172-
| Some ruleName -> handleFixResult ruleName lintResult
195+
| Some ruleName -> handleFixResult ruleName checkFlag lintResult
173196
| None -> exitCode <- ExitCode.NoSuchRuleName
174197
else
175198
handleLintResult lintResult
@@ -197,12 +220,13 @@ let private start (arguments:ParseResults<ToolArgs>) (toolsPath:Ionide.ProjInfo.
197220
let target = lintArgs.GetResult Target
198221
let fileType = lintArgs.TryGetResult File_Type |> Option.defaultValue (inferFileType target)
199222

200-
linting fileType lintParams target toolsPath false None
223+
linting fileType lintParams target toolsPath false None false
201224

202225
let applySuggestedFix (fixArgs: ParseResults<FixArgs>) =
203226
let fixParams = getParams None
204227
let ruleName, target = fixArgs.GetResult Fix_Target
205228
let fileType = fixArgs.TryGetResult Fix_File_Type |> Option.defaultValue (inferFileType target)
229+
let checkFlag = fixArgs.Contains Check
206230

207231
let allRules =
208232
match getConfig fixParams.Configuration with
@@ -220,7 +244,7 @@ let private start (arguments:ParseResults<ToolArgs>) (toolsPath:Ionide.ProjInfo.
220244
| _ -> Set.empty
221245

222246
if allRuleNames.Any(fun aRuleName -> String.Equals(aRuleName, ruleName, StringComparison.InvariantCultureIgnoreCase)) then
223-
linting fileType fixParams target toolsPath true (Some ruleName)
247+
linting fileType fixParams target toolsPath true (Some ruleName) checkFlag
224248
else
225249
sprintf "Rule '%s' does not exist." ruleName |> (handleError ExitCode.NoSuchRuleName)
226250

tests/FSharpLint.Console.Tests/TestApp.fs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,39 @@ printfn "Hello"
139139

140140
Assert.AreEqual(1, exitCode)
141141

142+
[<Test>]
143+
member __.``Lint source with fix check option with wrong rulename``() =
144+
let sourceCode = """
145+
printfn "Hello"
146+
"""
147+
148+
let ruleName = "ssrffss"
149+
use input = new TemporaryFile(sourceCode, "fs")
150+
let (exitCode, errors) = main [| "fix"; ruleName; input.FileName; "--check" |]
151+
152+
Assert.AreEqual(1, exitCode)
153+
154+
[<Test>]
155+
member __.``Lint source with fix check option``() =
156+
let sourceCode = """
157+
module Fass =
158+
let foo = new System.Collections.Generic.Dictionary<string, string>() |> ignore
159+
let goo = new Guid() |> ignore
160+
let ntoo = new Int32() |> ignore
161+
module Fall =
162+
let uoo = new Uid() |> ignore
163+
let version = new System.Version()
164+
let xoo = new Uint32() |> ignore
165+
"""
166+
167+
let ruleName = "RedundantNewKeyword"
168+
use input = new TemporaryFile(sourceCode, "fs")
169+
let (exitCode, errors) = main [| "fix"; ruleName; input.FileName; "--check" |]
170+
171+
Assert.AreEqual(3, exitCode)
172+
Assert.AreEqual(set ["Usage of `new` keyword here is redundant."], errors)
173+
Assert.AreEqual(sourceCode, File.ReadAllText input.FileName)
174+
142175
[<Test>]
143176
member __.``Lint source with fix option no need for fix``() =
144177
let sourceCode = """
@@ -157,3 +190,23 @@ module Fall =
157190

158191
Assert.AreEqual(2, exitCode)
159192
Assert.AreEqual(sourceCode, File.ReadAllText input.FileName)
193+
194+
Assert.AreEqual(sourceCode, File.ReadAllText input.FileName)
195+
196+
[<Test>]
197+
member __.``Lint source with fix option no need for fix with check option``() =
198+
let sourceCode = """
199+
module Fass =
200+
let foo = System.Collections.Generic.Dictionary<string, string>() |> ignore
201+
let goo = Guid() |> ignore
202+
let ntoo = Int32() |> ignore
203+
module Fall =
204+
let uoo = Uid() |> ignore
205+
let version = System.Version()
206+
let xoo = Uint32() |> ignore
207+
"""
208+
let ruleName = "RedundantNewKeyword"
209+
use input = new TemporaryFile(sourceCode, "fs")
210+
let (exitCode, errors) = main [| "fix"; ruleName; input.FileName; "--check" |]
211+
212+
Assert.AreEqual(0, exitCode)

0 commit comments

Comments
 (0)