|
| 1 | +module FSharpLint.Rules.FailwithBadUsage |
| 2 | + |
| 3 | +open FSharpLint.Framework |
| 4 | +open FSharpLint.Framework.Suggestion |
| 5 | +open FSharp.Compiler.Syntax |
| 6 | +open FSharpLint.Framework.Ast |
| 7 | +open FSharpLint.Framework.Rules |
| 8 | +open System |
| 9 | +open System.Collections.Generic |
| 10 | + |
| 11 | +let mutable failwithErrorMessageList = Set.empty |
| 12 | + |
| 13 | +type private BadUsageType = |
| 14 | + | EmptyMessage |
| 15 | + | DuplicateMessage |
| 16 | + | SwallowedException |
| 17 | + |
| 18 | +let private runner (args: AstNodeRuleParams) = |
| 19 | + let generateError |
| 20 | + failwithKeyword |
| 21 | + failwithErrorMessage |
| 22 | + range |
| 23 | + (badUsageType: BadUsageType) |
| 24 | + (exceptionParam: Option<string>) |
| 25 | + = |
| 26 | + let suggestedFix = |
| 27 | + match exceptionParam with |
| 28 | + | Some param -> |
| 29 | + Some( |
| 30 | + lazy |
| 31 | + (Some |
| 32 | + { FromText = sprintf "%s %s" failwithKeyword failwithErrorMessage |
| 33 | + FromRange = range |
| 34 | + ToText = sprintf "raise <| Exception(\"%s\", %s)" failwithErrorMessage param }) |
| 35 | + ) |
| 36 | + | _ -> None |
| 37 | + |
| 38 | + let message = |
| 39 | + match badUsageType with |
| 40 | + | EmptyMessage -> "Consider using non-empty error messages with failwith" |
| 41 | + | DuplicateMessage -> "Consider using unique error messages with failwith" |
| 42 | + | SwallowedException -> |
| 43 | + "failwith must not swallow exception details with it, rather use raise passing the current exception as innerException (2nd parameter of Exception constructor)" |
| 44 | + |
| 45 | + let error = |
| 46 | + { Range = range |
| 47 | + Message = String.Format(Resources.GetString "RulesFailwithBadUsage", message) |
| 48 | + SuggestedFix = suggestedFix |
| 49 | + TypeChecks = List.Empty } |
| 50 | + |> Array.singleton |
| 51 | + |
| 52 | + error |
| 53 | + |
| 54 | + let rec checkExpr node maybeIdentifier = |
| 55 | + match node with |
| 56 | + | SynExpr.App (_, _, SynExpr.Ident failwithId, expression, range) when |
| 57 | + failwithId.idText = "failwith" |
| 58 | + || failwithId.idText = "failwithf" |
| 59 | + -> |
| 60 | + match expression with |
| 61 | + | SynExpr.Const (SynConst.String (id, _, _), _) when id = "" -> |
| 62 | + generateError failwithId.idText id range BadUsageType.EmptyMessage maybeIdentifier |
| 63 | + | SynExpr.Const (SynConst.String (id, _, _), _) -> |
| 64 | + if Set.contains id failwithErrorMessageList then |
| 65 | + generateError failwithId.idText id range BadUsageType.DuplicateMessage maybeIdentifier |
| 66 | + else |
| 67 | + match maybeIdentifier with |
| 68 | + | Some maybeId -> |
| 69 | + generateError failwithId.idText id range BadUsageType.SwallowedException (Some maybeId) |
| 70 | + | _ -> |
| 71 | + failwithErrorMessageList <- failwithErrorMessageList.Add(id) |
| 72 | + Array.empty |
| 73 | + | SynExpr.LongIdent (_, LongIdentWithDots (id, _), _, _) when |
| 74 | + (ExpressionUtilities.longIdentToString id) = "String.Empty" |
| 75 | + -> |
| 76 | + generateError |
| 77 | + failwithId.idText |
| 78 | + (ExpressionUtilities.longIdentToString id) |
| 79 | + range |
| 80 | + (BadUsageType.EmptyMessage) |
| 81 | + (None) |
| 82 | + | _ -> Array.empty |
| 83 | + | SynExpr.TryWith (_, _, clauseList, _expression, _range, _, _) -> |
| 84 | + clauseList |
| 85 | + |> List.toArray |
| 86 | + |> Array.collect (fun clause -> |
| 87 | + match clause with |
| 88 | + | SynMatchClause (pat, _, app, _, _) -> |
| 89 | + match pat with |
| 90 | + | SynPat.Named (_, id, _, _, _) -> checkExpr app (Some id.idText) |
| 91 | + | _ -> checkExpr app None) |
| 92 | + | _ -> Array.empty |
| 93 | + | _ -> Array.empty |
| 94 | + |
| 95 | + match args.AstNode with |
| 96 | + | AstNode.Expression expr -> checkExpr expr None |
| 97 | + | _ -> Array.empty |
| 98 | + |
| 99 | +let cleanup () = failwithErrorMessageList <- Set.empty |
| 100 | + |
| 101 | +let rule = |
| 102 | + { Name = "FailwithBadUsage" |
| 103 | + Identifier = Identifiers.FailwithBadUsage |
| 104 | + RuleConfig = |
| 105 | + { AstNodeRuleConfig.Runner = runner |
| 106 | + Cleanup = cleanup } } |
| 107 | + |> AstNodeRule |
0 commit comments