22
33open Argu
44open System
5+ open System.IO
6+ open System.Text
57open FSharpLint.Framework
68open FSharpLint.Application
79
@@ -17,17 +19,21 @@ type private FileType =
1719 | File = 3
1820 | Source = 4
1921
22+ let fileTypeHelp = " Input type the linter will run against. If this is not set, the file type will be inferred from the file extension."
23+
2024// Allowing underscores in union case names for proper Argu command line option formatting.
2125// fsharplint:disable UnionCasesNames
2226type private ToolArgs =
2327 | [<AltCommandLine( " -f" ) >] Format of OutputFormat
2428 | [<CliPrefix( CliPrefix.None) >] Lint of ParseResults < LintArgs >
29+ | [<CliPrefix( CliPrefix.None) >] Fix of ParseResults < FixArgs >
2530with
2631 interface IArgParserTemplate with
2732 member this.Usage =
2833 match this with
2934 | Format _ -> " Output format of the linter."
3035 | Lint _ -> " Runs FSharpLint against a file or a collection of files."
36+ | Fix _ -> " Apply quickfixes for specified rule name or names (comma separated)."
3137
3238// TODO: investigate erroneous warning on this type definition
3339// fsharplint:disable UnionDefinitionIndentation
4147 member this.Usage =
4248 match this with
4349 | Target _ -> " Input to lint."
44- | File_ Type _ -> " Input type the linter will run against. If this is not set, the file type will be inferred from the file extension. "
50+ | File_ Type _ -> fileTypeHelp
4551 | Lint_ Config _ -> " Path to the config for the lint."
4652// fsharplint:enable UnionCasesNames
4753
54+ // TODO: investigate erroneous warning on this type definition
55+ // fsharplint:disable UnionDefinitionIndentation
56+ and private FixArgs =
57+ | [<MainCommand; Mandatory>] Fix_ Target of ruleName : string * target : string
58+ | Fix_ File_ Type of FileType
59+ // fsharplint:enable UnionDefinitionIndentation
60+ with
61+ interface IArgParserTemplate with
62+ member this.Usage =
63+ match this with
64+ | Fix_ Target _ -> " Rule name to be applied with suggestedFix and input to lint."
65+ | Fix_ File_ Type _ -> fileTypeHelp
66+ // fsharplint:enable UnionCasesNames
67+
4868let private parserProgress ( output : Output.IOutput ) = function
4969 | Starting file ->
5070 String.Format( Resources.GetString( " ConsoleStartingFile" ), file) |> output.WriteInfo
@@ -83,34 +103,35 @@ let private start (arguments:ParseResults<ToolArgs>) (toolsPath:Ionide.ProjInfo.
83103 output.WriteError str
84104 exitCode <- - 1
85105
86- match arguments.GetSubCommand() with
87- | Lint lintArgs ->
88-
89- let handleLintResult = function
90- | LintResult.Success( warnings) ->
91- String.Format( Resources.GetString( " ConsoleFinished" ), List.length warnings)
92- |> output.WriteInfo
93- if not ( List.isEmpty warnings) then exitCode <- - 1
94- | LintResult.Failure( failure) ->
95- handleError failure.Description
96-
97- let lintConfig = lintArgs.TryGetResult Lint_ Config
98-
99- let configParam =
100- match lintConfig with
101- | Some configPath -> FromFile configPath
102- | None -> Default
103-
104-
105- let lintParams =
106- { CancellationToken = None
107- ReceivedWarning = Some output.WriteWarning
108- Configuration = configParam
109- ReportLinterProgress = Some ( parserProgress output) }
110-
111- let target = lintArgs.GetResult Target
112- let fileType = lintArgs.TryGetResult File_ Type |> Option.defaultValue ( inferFileType target)
113-
106+ let outputWarnings ( warnings : List < Suggestion.LintWarning >) =
107+ String.Format( Resources.GetString " ConsoleFinished" , List.length warnings)
108+ |> output.WriteInfo
109+
110+ let handleLintResult = function
111+ | LintResult.Success warnings ->
112+ outputWarnings warnings
113+ if List.isEmpty warnings |> not then
114+ exitCode <- - 1
115+ | LintResult.Failure failure -> handleError failure.Description
116+
117+ let handleFixResult ( ruleName : string ) = function
118+ | LintResult.Success warnings ->
119+ Resources.GetString " ConsoleApplyingSuggestedFixFile" |> output.WriteInfo
120+ List.iter ( fun ( element : Suggestion.LintWarning ) ->
121+ let sourceCode = File.ReadAllText element.FilePath
122+ match element.Details.SuggestedFix with
123+ | Some suggestedFix when String.Equals( ruleName, element.RuleName, StringComparison.InvariantCultureIgnoreCase) ->
124+ suggestedFix.Force()
125+ |> Option.map ( fun suggestedFix ->
126+ let updatedSourceCode = sourceCode.Replace( suggestedFix.FromText, suggestedFix.ToText)
127+ File.WriteAllText( element.FilePath, updatedSourceCode, Encoding.UTF8)) |> ignore
128+ | _ -> ()) warnings
129+ outputWarnings warnings
130+ if List.isEmpty warnings |> not then
131+ exitCode <- 0
132+ | LintResult.Failure failure -> handleError failure.Description
133+
134+ let linting fileType lintParams target toolsPath shouldFix maybeRuleName =
114135 try
115136 let lintResult =
116137 match fileType with
@@ -119,12 +140,48 @@ let private start (arguments:ParseResults<ToolArgs>) (toolsPath:Ionide.ProjInfo.
119140 | FileType.Solution -> Lint.lintSolution lintParams target toolsPath
120141 | FileType.Project
121142 | _ -> Lint.lintProject lintParams target toolsPath
122- handleLintResult lintResult
143+ if shouldFix then
144+ match maybeRuleName with
145+ | Some ruleName -> handleFixResult ruleName lintResult
146+ | None -> exitCode <- 1
147+ else
148+ handleLintResult lintResult
123149 with
124150 | e ->
125151 let target = if fileType = FileType.Source then " source" else target
126152 sprintf " Lint failed while analysing %s .\n Failed with: %s \n Stack trace: %s " target e.Message e.StackTrace
127153 |> handleError
154+
155+ let getParams config =
156+ let paramConfig =
157+ match config with
158+ | Some configPath -> FromFile configPath
159+ | None -> Default
160+
161+ { CancellationToken = None
162+ ReceivedWarning = Some output.WriteWarning
163+ Configuration = paramConfig
164+ ReportLinterProgress = parserProgress output |> Some }
165+
166+ let applyLint ( lintArgs : ParseResults < LintArgs >) =
167+ let lintConfig = lintArgs.TryGetResult Lint_ Config
168+
169+ let lintParams = getParams lintConfig
170+ let target = lintArgs.GetResult Target
171+ let fileType = lintArgs.TryGetResult File_ Type |> Option.defaultValue ( inferFileType target)
172+
173+ linting fileType lintParams target toolsPath false None
174+
175+ let applySuggestedFix ( fixArgs : ParseResults < FixArgs >) =
176+ let fixParams = getParams None
177+ let ruleName , target = fixArgs.GetResult Fix_ Target
178+ let fileType = fixArgs.TryGetResult Fix_ File_ Type |> Option.defaultValue ( inferFileType target)
179+
180+ linting fileType fixParams target toolsPath true ( Some ruleName)
181+
182+ match arguments.GetSubCommand() with
183+ | Lint lintArgs -> applyLint lintArgs
184+ | Fix fixArgs -> applySuggestedFix fixArgs
128185 | _ -> ()
129186
130187 exitCode
0 commit comments