From 4392044c35e7cb339cff28dd5f6eee2d9ff1bae9 Mon Sep 17 00:00:00 2001 From: ijanus Date: Thu, 30 Jun 2022 13:28:44 +0100 Subject: [PATCH 1/2] Add fix command-line to apply quickfixes and unit test Co-authored-by: Parham Saremi --- src/FSharpLint.Console/Program.fs | 172 ++++++++++++++---- .../Application/Configuration.fs | 27 ++- src/FSharpLint.Core/Application/Lint.fs | 4 +- src/FSharpLint.Core/Application/Lint.fsi | 4 +- src/FSharpLint.Core/Text.resx | 3 + tests/FSharpLint.Console.Tests/TestApp.fs | 71 +++++++- 6 files changed, 232 insertions(+), 49 deletions(-) diff --git a/src/FSharpLint.Console/Program.fs b/src/FSharpLint.Console/Program.fs index 9c8dce072..d3aea33e5 100644 --- a/src/FSharpLint.Console/Program.fs +++ b/src/FSharpLint.Console/Program.fs @@ -2,8 +2,11 @@ open Argu open System +open System.IO +open System.Text open FSharpLint.Framework open FSharpLint.Application +open System.Linq /// Output format the linter will use. type private OutputFormat = @@ -17,17 +20,27 @@ type private FileType = | File = 3 | Source = 4 +type ExitCode = + | Error = -1 + | Success = 0 + | NoSuchRuleName = 1 + | NoSuggestedFix = 2 + +let fileTypeHelp = "Input type the linter will run against. If this is not set, the file type will be inferred from the file extension." + // Allowing underscores in union case names for proper Argu command line option formatting. // fsharplint:disable UnionCasesNames type private ToolArgs = | [] Format of OutputFormat | [] Lint of ParseResults + | [] Fix of ParseResults with interface IArgParserTemplate with member this.Usage = match this with | Format _ -> "Output format of the linter." | Lint _ -> "Runs FSharpLint against a file or a collection of files." + | Fix _ -> "Apply quickfixes for specified rule name or names (comma separated)." // TODO: investigate erroneous warning on this type definition // fsharplint:disable UnionDefinitionIndentation @@ -41,10 +54,24 @@ with member this.Usage = match this with | Target _ -> "Input to lint." - | File_Type _ -> "Input type the linter will run against. If this is not set, the file type will be inferred from the file extension." + | File_Type _ -> fileTypeHelp | Lint_Config _ -> "Path to the config for the lint." // fsharplint:enable UnionCasesNames +// TODO: investigate erroneous warning on this type definition +// fsharplint:disable UnionDefinitionIndentation +and private FixArgs = + | [] Fix_Target of ruleName:string * target:string + | Fix_File_Type of FileType +// fsharplint:enable UnionDefinitionIndentation +with + interface IArgParserTemplate with + member this.Usage = + match this with + | Fix_Target _ -> "Rule name to be applied with suggestedFix and input to lint." + | Fix_File_Type _ -> fileTypeHelp +// fsharplint:enable UnionCasesNames + let private parserProgress (output:Output.IOutput) = function | Starting file -> String.Format(Resources.GetString("ConsoleStartingFile"), file) |> output.WriteInfo @@ -70,7 +97,7 @@ let private inferFileType (target:string) = FileType.Source let private start (arguments:ParseResults) (toolsPath:Ionide.ProjInfo.Types.ToolsPath) = - let mutable exitCode = 0 + let mutable exitCode = ExitCode.Success let output = match arguments.TryGetResult Format with @@ -79,38 +106,59 @@ let private start (arguments:ParseResults) (toolsPath:Ionide.ProjInfo. | Some _ | None -> Output.StandardOutput() :> Output.IOutput - let handleError (str:string) = + let handleError (status:ExitCode) (str:string) = output.WriteError str - exitCode <- -1 - - match arguments.GetSubCommand() with - | Lint lintArgs -> - - let handleLintResult = function - | LintResult.Success(warnings) -> - String.Format(Resources.GetString("ConsoleFinished"), List.length warnings) - |> output.WriteInfo - if not (List.isEmpty warnings) then exitCode <- -1 - | LintResult.Failure(failure) -> - handleError failure.Description - - let lintConfig = lintArgs.TryGetResult Lint_Config - - let configParam = - match lintConfig with - | Some configPath -> FromFile configPath - | None -> Default - - - let lintParams = - { CancellationToken = None - ReceivedWarning = Some output.WriteWarning - Configuration = configParam - ReportLinterProgress = Some (parserProgress output) } - - let target = lintArgs.GetResult Target - let fileType = lintArgs.TryGetResult File_Type |> Option.defaultValue (inferFileType target) + exitCode <- status + let outputWarnings (warnings: List) = + String.Format(Resources.GetString "ConsoleFinished", List.length warnings) + |> output.WriteInfo + + let handleLintResult = function + | LintResult.Success warnings -> + outputWarnings warnings + if List.isEmpty warnings |> not then + exitCode <- ExitCode.Error + | LintResult.Failure failure -> handleError ExitCode.Error failure.Description + + let handleFixResult (ruleName: string) = function + | LintResult.Success warnings -> + Resources.GetString "ConsoleApplyingSuggestedFixFile" |> output.WriteInfo + let increment = 1 + let noFixIncrement = 0 + let countSuggestedFix = + List.fold (fun acc elem -> acc + elem) 0 ( + List.map (fun (element: Suggestion.LintWarning) -> + let sourceCode = File.ReadAllText element.FilePath + if String.Equals(ruleName, element.RuleName, StringComparison.InvariantCultureIgnoreCase) then + match element.Details.SuggestedFix with + | Some suggestedFix -> + suggestedFix.Force() + |> Option.map (fun suggestedFix -> + let updatedSourceCode = + sourceCode.Replace( + suggestedFix.FromText, + suggestedFix.ToText + ) + File.WriteAllText( + element.FilePath, + updatedSourceCode, + Encoding.UTF8) + ) + |> ignore |> fun () -> increment + | None -> noFixIncrement + else + noFixIncrement) warnings) + outputWarnings warnings + + if countSuggestedFix > 0 then + exitCode <- ExitCode.Success + else + exitCode <- ExitCode.NoSuggestedFix + + | LintResult.Failure failure -> handleError ExitCode.Error failure.Description + + let linting fileType lintParams target toolsPath shouldFix maybeRuleName = try let lintResult = match fileType with @@ -119,15 +167,69 @@ let private start (arguments:ParseResults) (toolsPath:Ionide.ProjInfo. | FileType.Solution -> Lint.lintSolution lintParams target toolsPath | FileType.Project | _ -> Lint.lintProject lintParams target toolsPath - handleLintResult lintResult + if shouldFix then + match maybeRuleName with + | Some ruleName -> handleFixResult ruleName lintResult + | None -> exitCode <- ExitCode.NoSuchRuleName + else + handleLintResult lintResult with | e -> let target = if fileType = FileType.Source then "source" else target sprintf "Lint failed while analysing %s.\nFailed with: %s\nStack trace: %s" target e.Message e.StackTrace - |> handleError + |> (handleError ExitCode.Error) + + let getParams config = + let paramConfig = + match config with + | Some configPath -> FromFile configPath + | None -> Default + + { CancellationToken = None + ReceivedWarning = Some output.WriteWarning + Configuration = paramConfig + ReportLinterProgress = parserProgress output |> Some } + + let applyLint (lintArgs: ParseResults) = + let lintConfig = lintArgs.TryGetResult Lint_Config + + let lintParams = getParams lintConfig + let target = lintArgs.GetResult Target + let fileType = lintArgs.TryGetResult File_Type |> Option.defaultValue (inferFileType target) + + linting fileType lintParams target toolsPath false None + + let applySuggestedFix (fixArgs: ParseResults) = + let fixParams = getParams None + let ruleName, target = fixArgs.GetResult Fix_Target + let fileType = fixArgs.TryGetResult Fix_File_Type |> Option.defaultValue (inferFileType target) + + let allRules = + match getConfig fixParams.Configuration with + | Ok config -> Some (Configuration.flattenConfig config false) + | _ -> None + + let allRuleNames = + match allRules with + | Some rules -> (fun (loadedRules:Configuration.LoadedRules) -> ([| + loadedRules.LineRules.IndentationRule |> Option.map (fun rule -> rule.Name) |> Option.toArray + loadedRules.LineRules.NoTabCharactersRule |> Option.map (fun rule -> rule.Name) |> Option.toArray + loadedRules.LineRules.GenericLineRules |> Array.map (fun rule -> rule.Name) + loadedRules.AstNodeRules |> Array.map (fun rule -> rule.Name) + |] |> Array.concat |> Set.ofArray)) rules + | _ -> Set.empty + + if allRuleNames.Any(fun aRuleName -> String.Equals(aRuleName, ruleName, StringComparison.InvariantCultureIgnoreCase)) then + linting fileType fixParams target toolsPath true (Some ruleName) + else + sprintf "Rule '%s' does not exist." ruleName |> (handleError ExitCode.NoSuchRuleName) + + match arguments.GetSubCommand() with + | Lint lintArgs -> applyLint lintArgs + | Fix fixArgs -> applySuggestedFix fixArgs | _ -> () - exitCode + int exitCode /// Must be called only once per process. /// We're calling it globally so we can call main multiple times from our tests. diff --git a/src/FSharpLint.Core/Application/Configuration.fs b/src/FSharpLint.Core/Application/Configuration.fs index 7762fe4f7..13d982aaf 100644 --- a/src/FSharpLint.Core/Application/Configuration.fs +++ b/src/FSharpLint.Core/Application/Configuration.fs @@ -129,13 +129,20 @@ type RuleConfig<'Config> = { type EnabledConfig = RuleConfig -let constructRuleIfEnabled rule ruleConfig = if ruleConfig.Enabled then Some rule else None +let constructRuleIfEnabledBase (onlyEnabled: bool) rule ruleConfig = + if not onlyEnabled || ruleConfig.Enabled then Some rule else None -let constructRuleWithConfig rule ruleConfig = - if ruleConfig.Enabled then - ruleConfig.Config |> Option.map rule - else - None +let constructRuleIfEnabled rule ruleConfig = + constructRuleIfEnabledBase true rule ruleConfig + +let constructRuleWithConfigBase (onlyEnabled: bool) (rule: 'TRuleConfig -> 'TRule) (ruleConfig: RuleConfig<'TRuleConfig>): Option<'TRule> = + if not onlyEnabled || ruleConfig.Enabled then + ruleConfig.Config |> Option.map rule + else + None + +let constructRuleWithConfig (rule: 'TRuleConfig -> 'TRule) (ruleConfig: RuleConfig<'TRuleConfig>): Option<'TRule> = + constructRuleWithConfigBase true rule ruleConfig type TupleFormattingConfig = { tupleCommaSpacing:EnabledConfig option @@ -600,7 +607,7 @@ let private parseHints (hints:string []) = |> Array.toList |> MergeSyntaxTrees.mergeHints -let flattenConfig (config:Configuration) = +let flattenConfig (config:Configuration) (onlyEnabled:bool) = let deprecatedAllRules = [| config.formatting |> Option.map (fun config -> config.Flatten()) |> Option.toArray |> Array.concat @@ -609,6 +616,12 @@ let flattenConfig (config:Configuration) = config.Hints |> Option.map (fun config -> HintMatcher.rule { HintMatcher.Config.HintTrie = parseHints (getOrEmptyList config.add) }) |> Option.toArray |] |> Array.concat + let constructRuleIfEnabled rule ruleConfig = + constructRuleIfEnabledBase onlyEnabled rule ruleConfig + + let constructRuleWithConfig (rule: 'TRuleConfig -> 'TRule) (ruleConfig: RuleConfig<'TRuleConfig>): Option<'TRule> = + constructRuleWithConfigBase onlyEnabled rule ruleConfig + let allRules = [| config.TypedItemSpacing |> Option.bind (constructRuleWithConfig TypedItemSpacing.rule) diff --git a/src/FSharpLint.Core/Application/Lint.fs b/src/FSharpLint.Core/Application/Lint.fs index b2686aad8..72ba9acb6 100644 --- a/src/FSharpLint.Core/Application/Lint.fs +++ b/src/FSharpLint.Core/Application/Lint.fs @@ -206,7 +206,7 @@ module Lint = | Some(x) -> not x.IsCancellationRequested | None -> true - let enabledRules = Configuration.flattenConfig lintInfo.Configuration + let enabledRules = Configuration.flattenConfig lintInfo.Configuration true let lines = String.toLines fileInfo.Text |> Array.map (fun (line, _, _) -> line) let allRuleNames = @@ -371,7 +371,7 @@ module Lint = } /// Gets a FSharpLint Configuration based on the provided ConfigurationParam. - let private getConfig (configParam:ConfigurationParam) = + let getConfig (configParam:ConfigurationParam) = match configParam with | Configuration config -> Ok config | FromFile filePath -> diff --git a/src/FSharpLint.Core/Application/Lint.fsi b/src/FSharpLint.Core/Application/Lint.fsi index 655c184b4..479a897f0 100644 --- a/src/FSharpLint.Core/Application/Lint.fsi +++ b/src/FSharpLint.Core/Application/Lint.fsi @@ -144,4 +144,6 @@ module Lint = /// Lints an F# file that has already been parsed using /// `FSharp.Compiler.Services` in the calling application. - val lintParsedFile : optionalParams:OptionalLintParameters -> parsedFileInfo:ParsedFileInformation -> filePath:string -> LintResult \ No newline at end of file + val lintParsedFile : optionalParams:OptionalLintParameters -> parsedFileInfo:ParsedFileInformation -> filePath:string -> LintResult + + val getConfig : ConfigurationParam -> Result diff --git a/src/FSharpLint.Core/Text.resx b/src/FSharpLint.Core/Text.resx index 1c90e1adc..a51376d1c 100644 --- a/src/FSharpLint.Core/Text.resx +++ b/src/FSharpLint.Core/Text.resx @@ -138,6 +138,9 @@ ========== Linting {0} ========== + + ========== Applying fixes ========== + MSBuild could not load the project file {0} because: {1} diff --git a/tests/FSharpLint.Console.Tests/TestApp.fs b/tests/FSharpLint.Console.Tests/TestApp.fs index 1402915b8..378002e9a 100644 --- a/tests/FSharpLint.Console.Tests/TestApp.fs +++ b/tests/FSharpLint.Console.Tests/TestApp.fs @@ -3,6 +3,7 @@ open System open System.IO open NUnit.Framework +open FSharpLint.Console.Program let getErrorsFromOutput (output:string) = let splitOutput = output.Split([|Environment.NewLine|], StringSplitOptions.None) @@ -44,7 +45,7 @@ type TestConsoleApplication() = let (returnCode, errors) = main [| "lint"; input.FileName |] - Assert.AreEqual(-1, returnCode) + Assert.AreEqual(int ExitCode.Error, returnCode) Assert.AreEqual(set ["Consider changing `Signature` to be prefixed with `I`."], errors) [] @@ -57,7 +58,7 @@ type TestConsoleApplication() = let (returnCode, errors) = main [| "lint"; input |] - Assert.AreEqual(-1, returnCode) + Assert.AreEqual(int ExitCode.Error, returnCode) Assert.AreEqual(set ["Consider changing `Signature` to be prefixed with `I`."], errors) [] @@ -79,7 +80,7 @@ type TestConsoleApplication() = let (returnCode, errors) = main [| "lint"; "--lint-config"; config.FileName; input |] - Assert.AreEqual(0, returnCode) + Assert.AreEqual(int ExitCode.Success, returnCode) Assert.AreEqual(Set.empty, errors) [] @@ -93,5 +94,67 @@ type TestConsoleApplication() = let (returnCode, errors) = main [| "lint"; input |] - Assert.AreEqual(0, returnCode) + Assert.AreEqual(int ExitCode.Success, returnCode) Assert.AreEqual(Set.empty, errors) + + [] + member __.``Lint source with fix option``() = + let sourceCode = """ +module Fass = + let foo = new System.Collections.Generic.Dictionary() |> ignore + let goo = new Guid() |> ignore + let ntoo = new Int32() |> ignore +module Fall = + let uoo = new Uid() |> ignore + let version = new System.Version() + let xoo = new Uint32() |> ignore + """ + + let expected = """ +module Fass = + let foo = System.Collections.Generic.Dictionary() |> ignore + let goo = Guid() |> ignore + let ntoo = Int32() |> ignore +module Fall = + let uoo = Uid() |> ignore + let version = System.Version() + let xoo = Uint32() |> ignore + """ + let ruleName = "RedundantNewKeyword" + use input = new TemporaryFile(sourceCode, "fs") + let (exitCode, errors) = main [| "fix"; ruleName; input.FileName |] + + Assert.AreEqual(int ExitCode.Success, exitCode) + Assert.AreEqual(set ["Usage of `new` keyword here is redundant."], errors) + Assert.AreEqual(expected, File.ReadAllText input.FileName) + + [] + member __.``Lint source with fix option with wrong rulename``() = + let sourceCode = """ +printfn "Hello" + """ + + let ruleName = "ssrffss" + use input = new TemporaryFile(sourceCode, "fs") + let (exitCode, errors) = main [| "fix"; ruleName; input.FileName |] + + Assert.AreEqual(int ExitCode.NoSuchRuleName, exitCode) + + [] + member __.``Lint source with fix option no need for fix``() = + let sourceCode = """ +module Fass = + let foo = System.Collections.Generic.Dictionary() |> ignore + let goo = Guid() |> ignore + let ntoo = Int32() |> ignore +module Fall = + let uoo = Uid() |> ignore + let version = System.Version() + let xoo = Uint32() |> ignore + """ + let ruleName = "RedundantNewKeyword" + use input = new TemporaryFile(sourceCode, "fs") + let (exitCode, errors) = main [| "fix"; ruleName; input.FileName |] + + Assert.AreEqual(int ExitCode.NoSuggestedFix, exitCode) + Assert.AreEqual(sourceCode, File.ReadAllText input.FileName) From 100a8399692adf2c3b4633b9318291f2aae2edad Mon Sep 17 00:00:00 2001 From: Parham Saremi Date: Fri, 22 Jul 2022 17:08:11 +0430 Subject: [PATCH 2/2] 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. --- src/FSharpLint.Console/Program.fs | 81 +++++++++++++++-------- tests/FSharpLint.Console.Tests/TestApp.fs | 53 +++++++++++++++ 2 files changed, 107 insertions(+), 27 deletions(-) diff --git a/src/FSharpLint.Console/Program.fs b/src/FSharpLint.Console/Program.fs index d3aea33e5..14a4ad90f 100644 --- a/src/FSharpLint.Console/Program.fs +++ b/src/FSharpLint.Console/Program.fs @@ -22,9 +22,13 @@ type private FileType = type ExitCode = | Error = -1 - | Success = 0 + // for `fix` when the file is changed and for `fix --check` when there are no fixes available. + | Success = 0 | NoSuchRuleName = 1 + // only for fix (without --check) | NoSuggestedFix = 2 + //only for fix --check + | FixExists = 3 let fileTypeHelp = "Input type the linter will run against. If this is not set, the file type will be inferred from the file extension." @@ -63,6 +67,7 @@ with and private FixArgs = | [] Fix_Target of ruleName:string * target:string | Fix_File_Type of FileType + | Check // fsharplint:enable UnionDefinitionIndentation with interface IArgParserTemplate with @@ -70,6 +75,7 @@ with match this with | Fix_Target _ -> "Rule name to be applied with suggestedFix and input to lint." | Fix_File_Type _ -> fileTypeHelp + | Check _ -> "If passed to the fix command, the linter will only check if the fix is needed." // fsharplint:enable UnionCasesNames let private parserProgress (output:Output.IOutput) = function @@ -106,7 +112,7 @@ let private start (arguments:ParseResults) (toolsPath:Ionide.ProjInfo. | Some _ | None -> Output.StandardOutput() :> Output.IOutput - let handleError (status:ExitCode) (str:string) = + let handleError (status: ExitCode) (str: string) = output.WriteError str exitCode <- status @@ -121,44 +127,64 @@ let private start (arguments:ParseResults) (toolsPath:Ionide.ProjInfo. exitCode <- ExitCode.Error | LintResult.Failure failure -> handleError ExitCode.Error failure.Description - let handleFixResult (ruleName: string) = function + let handleFixResult (ruleName: string) (checkFlag: bool) = function | LintResult.Success warnings -> - Resources.GetString "ConsoleApplyingSuggestedFixFile" |> output.WriteInfo - let increment = 1 + if not checkFlag then + Resources.GetString "ConsoleApplyingSuggestedFixFile" |> output.WriteInfo let noFixIncrement = 0 - let countSuggestedFix = - List.fold (fun acc elem -> acc + elem) 0 ( + let foundFixIncrement = 1 + let noSuggestedFixIncrement = 2 + let countFixStatus = + List.fold (fun (accNoFix, accFoundFix, accNoSuggestedFix) elem -> + if elem = noFixIncrement then + (accNoFix + 1, accFoundFix, accNoSuggestedFix) + elif elem = foundFixIncrement then + (accNoFix, accFoundFix + 1, accNoSuggestedFix) + elif elem = noSuggestedFixIncrement then + (accNoFix, accFoundFix, accNoSuggestedFix + 1) + else + failwith "Code should never reach here!") (0, 0, 0) ( List.map (fun (element: Suggestion.LintWarning) -> let sourceCode = File.ReadAllText element.FilePath if String.Equals(ruleName, element.RuleName, StringComparison.InvariantCultureIgnoreCase) then match element.Details.SuggestedFix with | Some suggestedFix -> - suggestedFix.Force() - |> Option.map (fun suggestedFix -> - let updatedSourceCode = - sourceCode.Replace( - suggestedFix.FromText, - suggestedFix.ToText + ((fun checkFlag -> + if not checkFlag then + (suggestedFix.Force() + |> Option.map (fun suggestedFix -> + let updatedSourceCode = + sourceCode.Replace( + suggestedFix.FromText, + suggestedFix.ToText + ) + File.WriteAllText( + element.FilePath, + updatedSourceCode, + Encoding.UTF8) ) - File.WriteAllText( - element.FilePath, - updatedSourceCode, - Encoding.UTF8) - ) - |> ignore |> fun () -> increment - | None -> noFixIncrement + ) |> ignore + else + () + ) checkFlag) + |> ignore |> fun () -> foundFixIncrement + | None -> noSuggestedFixIncrement else noFixIncrement) warnings) outputWarnings warnings - - if countSuggestedFix > 0 then + let (accNoFix, accFoundFix, accNoSuggestedFix) = countFixStatus + if not checkFlag && accFoundFix > 0 then exitCode <- ExitCode.Success - else + elif not checkFlag then exitCode <- ExitCode.NoSuggestedFix + elif checkFlag && accFoundFix > 0 then + exitCode <- ExitCode.FixExists + elif checkFlag then + exitCode <- ExitCode.Success | LintResult.Failure failure -> handleError ExitCode.Error failure.Description - let linting fileType lintParams target toolsPath shouldFix maybeRuleName = + let linting fileType lintParams target toolsPath shouldFix maybeRuleName checkFlag = try let lintResult = match fileType with @@ -169,7 +195,7 @@ let private start (arguments:ParseResults) (toolsPath:Ionide.ProjInfo. | _ -> Lint.lintProject lintParams target toolsPath if shouldFix then match maybeRuleName with - | Some ruleName -> handleFixResult ruleName lintResult + | Some ruleName -> handleFixResult ruleName checkFlag lintResult | None -> exitCode <- ExitCode.NoSuchRuleName else handleLintResult lintResult @@ -197,12 +223,13 @@ let private start (arguments:ParseResults) (toolsPath:Ionide.ProjInfo. let target = lintArgs.GetResult Target let fileType = lintArgs.TryGetResult File_Type |> Option.defaultValue (inferFileType target) - linting fileType lintParams target toolsPath false None + linting fileType lintParams target toolsPath false None false let applySuggestedFix (fixArgs: ParseResults) = let fixParams = getParams None let ruleName, target = fixArgs.GetResult Fix_Target let fileType = fixArgs.TryGetResult Fix_File_Type |> Option.defaultValue (inferFileType target) + let checkFlag = fixArgs.Contains Check let allRules = match getConfig fixParams.Configuration with @@ -220,7 +247,7 @@ let private start (arguments:ParseResults) (toolsPath:Ionide.ProjInfo. | _ -> Set.empty if allRuleNames.Any(fun aRuleName -> String.Equals(aRuleName, ruleName, StringComparison.InvariantCultureIgnoreCase)) then - linting fileType fixParams target toolsPath true (Some ruleName) + linting fileType fixParams target toolsPath true (Some ruleName) checkFlag else sprintf "Rule '%s' does not exist." ruleName |> (handleError ExitCode.NoSuchRuleName) diff --git a/tests/FSharpLint.Console.Tests/TestApp.fs b/tests/FSharpLint.Console.Tests/TestApp.fs index 378002e9a..97692e8e5 100644 --- a/tests/FSharpLint.Console.Tests/TestApp.fs +++ b/tests/FSharpLint.Console.Tests/TestApp.fs @@ -140,6 +140,39 @@ printfn "Hello" Assert.AreEqual(int ExitCode.NoSuchRuleName, exitCode) + [] + member __.``Lint source with fix check option with wrong rulename``() = + let sourceCode = """ +printfn "Hello" + """ + + let ruleName = "ssrffss" + use input = new TemporaryFile(sourceCode, "fs") + let (exitCode, errors) = main [| "fix"; ruleName; input.FileName; "--check" |] + + Assert.AreEqual(int ExitCode.NoSuchRuleName, exitCode) + + [] + member __.``Lint source with fix check option``() = + let sourceCode = """ +module Fass = + let foo = new System.Collections.Generic.Dictionary() |> ignore + let goo = new Guid() |> ignore + let ntoo = new Int32() |> ignore +module Fall = + let uoo = new Uid() |> ignore + let version = new System.Version() + let xoo = new Uint32() |> ignore + """ + + let ruleName = "RedundantNewKeyword" + use input = new TemporaryFile(sourceCode, "fs") + let (exitCode, errors) = main [| "fix"; ruleName; input.FileName; "--check" |] + + Assert.AreEqual(int ExitCode.FixExists, exitCode) + Assert.AreEqual(set ["Usage of `new` keyword here is redundant."], errors) + Assert.AreEqual(sourceCode, File.ReadAllText input.FileName) + [] member __.``Lint source with fix option no need for fix``() = let sourceCode = """ @@ -158,3 +191,23 @@ module Fall = Assert.AreEqual(int ExitCode.NoSuggestedFix, exitCode) Assert.AreEqual(sourceCode, File.ReadAllText input.FileName) + + Assert.AreEqual(sourceCode, File.ReadAllText input.FileName) + + [] + member __.``Lint source with fix option no need for fix with check option``() = + let sourceCode = """ +module Fass = + let foo = System.Collections.Generic.Dictionary() |> ignore + let goo = Guid() |> ignore + let ntoo = Int32() |> ignore +module Fall = + let uoo = Uid() |> ignore + let version = System.Version() + let xoo = Uint32() |> ignore + """ + let ruleName = "RedundantNewKeyword" + use input = new TemporaryFile(sourceCode, "fs") + let (exitCode, errors) = main [| "fix"; ruleName; input.FileName; "--check" |] + + Assert.AreEqual(int ExitCode.Success, exitCode)