diff --git a/samples/OptionAnalyzer/Library.fs b/samples/OptionAnalyzer/Library.fs index 6667c55..042869f 100644 --- a/samples/OptionAnalyzer/Library.fs +++ b/samples/OptionAnalyzer/Library.fs @@ -6,88 +6,84 @@ open FSharp.Compiler.Symbols open FSharp.Compiler.Symbols.FSharpExprPatterns open FSharp.Compiler.Text -let rec visitExpr memberCallHandler (e:FSharpExpr) = +let rec visitExpr memberCallHandler (e: FSharpExpr) = match e with - | AddressOf(lvalueExpr) -> - visitExpr memberCallHandler lvalueExpr + | AddressOf(lvalueExpr) -> visitExpr memberCallHandler lvalueExpr | AddressSet(lvalueExpr, rvalueExpr) -> - visitExpr memberCallHandler lvalueExpr; visitExpr memberCallHandler rvalueExpr + visitExpr memberCallHandler lvalueExpr + visitExpr memberCallHandler rvalueExpr | Application(funcExpr, typeArgs, argExprs) -> - visitExpr memberCallHandler funcExpr; visitExprs memberCallHandler argExprs + visitExpr memberCallHandler funcExpr + visitExprs memberCallHandler argExprs | Call(objExprOpt, memberOrFunc, typeArgs1, typeArgs2, argExprs) -> memberCallHandler e.Range memberOrFunc - visitObjArg memberCallHandler objExprOpt; visitExprs memberCallHandler argExprs - | Coerce(targetType, inpExpr) -> - visitExpr memberCallHandler inpExpr - | FastIntegerForLoop(startExpr, limitExpr, consumeExpr, isUp, debugPointAtFor, debugPointAtInOrTo) -> - visitExpr memberCallHandler startExpr; visitExpr memberCallHandler limitExpr; visitExpr memberCallHandler consumeExpr - | ILAsm(asmCode, typeArgs, argExprs) -> - visitExprs memberCallHandler argExprs - | ILFieldGet (objExprOpt, fieldType, fieldName) -> - visitObjArg memberCallHandler objExprOpt - | ILFieldSet (objExprOpt, fieldType, fieldName, valueExpr) -> visitObjArg memberCallHandler objExprOpt - | IfThenElse (guardExpr, thenExpr, elseExpr) -> - visitExpr memberCallHandler guardExpr; visitExpr memberCallHandler thenExpr; visitExpr memberCallHandler elseExpr - | Lambda(lambdaVar, bodyExpr) -> - visitExpr memberCallHandler bodyExpr + visitExprs memberCallHandler argExprs + | Coerce(targetType, inpExpr) -> visitExpr memberCallHandler inpExpr + | FastIntegerForLoop(startExpr, limitExpr, consumeExpr, isUp, debugPointAtFor, debugPointAtInOrTo) -> + visitExpr memberCallHandler startExpr + visitExpr memberCallHandler limitExpr + visitExpr memberCallHandler consumeExpr + | ILAsm(asmCode, typeArgs, argExprs) -> visitExprs memberCallHandler argExprs + | ILFieldGet(objExprOpt, fieldType, fieldName) -> visitObjArg memberCallHandler objExprOpt + | ILFieldSet(objExprOpt, fieldType, fieldName, valueExpr) -> visitObjArg memberCallHandler objExprOpt + | IfThenElse(guardExpr, thenExpr, elseExpr) -> + visitExpr memberCallHandler guardExpr + visitExpr memberCallHandler thenExpr + visitExpr memberCallHandler elseExpr + | Lambda(lambdaVar, bodyExpr) -> visitExpr memberCallHandler bodyExpr | Let((bindingVar, bindingExpr, debugPointAtBinding), bodyExpr) -> - visitExpr memberCallHandler bindingExpr; visitExpr memberCallHandler bodyExpr + visitExpr memberCallHandler bindingExpr + visitExpr memberCallHandler bodyExpr | LetRec(recursiveBindings, bodyExpr) -> - let recursiveBindings' = recursiveBindings |> List.map (fun (mfv, expr, dp) -> (mfv, expr)) - List.iter (snd >> visitExpr memberCallHandler) recursiveBindings'; visitExpr memberCallHandler bodyExpr - | NewArray(arrayType, argExprs) -> - visitExprs memberCallHandler argExprs - | NewDelegate(delegateType, delegateBodyExpr) -> - visitExpr memberCallHandler delegateBodyExpr - | NewObject(objType, typeArgs, argExprs) -> - visitExprs memberCallHandler argExprs - | NewRecord(recordType, argExprs) -> - visitExprs memberCallHandler argExprs - | NewTuple(tupleType, argExprs) -> - visitExprs memberCallHandler argExprs - | NewUnionCase(unionType, unionCase, argExprs) -> - visitExprs memberCallHandler argExprs - | Quote(quotedExpr) -> - visitExpr memberCallHandler quotedExpr - | FSharpFieldGet(objExprOpt, recordOrClassType, fieldInfo) -> - visitObjArg memberCallHandler objExprOpt + let recursiveBindings' = + recursiveBindings |> List.map (fun (mfv, expr, dp) -> (mfv, expr)) + + List.iter (snd >> visitExpr memberCallHandler) recursiveBindings' + visitExpr memberCallHandler bodyExpr + | NewArray(arrayType, argExprs) -> visitExprs memberCallHandler argExprs + | NewDelegate(delegateType, delegateBodyExpr) -> visitExpr memberCallHandler delegateBodyExpr + | NewObject(objType, typeArgs, argExprs) -> visitExprs memberCallHandler argExprs + | NewRecord(recordType, argExprs) -> visitExprs memberCallHandler argExprs + | NewTuple(tupleType, argExprs) -> visitExprs memberCallHandler argExprs + | NewUnionCase(unionType, unionCase, argExprs) -> visitExprs memberCallHandler argExprs + | Quote(quotedExpr) -> visitExpr memberCallHandler quotedExpr + | FSharpFieldGet(objExprOpt, recordOrClassType, fieldInfo) -> visitObjArg memberCallHandler objExprOpt | FSharpFieldSet(objExprOpt, recordOrClassType, fieldInfo, argExpr) -> - visitObjArg memberCallHandler objExprOpt; visitExpr memberCallHandler argExpr + visitObjArg memberCallHandler objExprOpt + visitExpr memberCallHandler argExpr | Sequential(firstExpr, secondExpr) -> - visitExpr memberCallHandler firstExpr; visitExpr memberCallHandler secondExpr + visitExpr memberCallHandler firstExpr + visitExpr memberCallHandler secondExpr | TryFinally(bodyExpr, finalizeExpr, debugPointAtTry, debugPointAtFinally) -> - visitExpr memberCallHandler bodyExpr; visitExpr memberCallHandler finalizeExpr + visitExpr memberCallHandler bodyExpr + visitExpr memberCallHandler finalizeExpr | TryWith(bodyExpr, _, _, catchVar, catchExpr, debugPointAtTry, debugPointAtWith) -> - visitExpr memberCallHandler bodyExpr; visitExpr memberCallHandler catchExpr - | TupleGet(tupleType, tupleElemIndex, tupleExpr) -> - visitExpr memberCallHandler tupleExpr - | DecisionTree(decisionExpr, decisionTargets) -> - visitExpr memberCallHandler decisionExpr; List.iter (snd >> visitExpr memberCallHandler) decisionTargets - | DecisionTreeSuccess (decisionTargetIdx, decisionTargetExprs) -> - visitExprs memberCallHandler decisionTargetExprs - | TypeLambda(genericParam, bodyExpr) -> visitExpr memberCallHandler bodyExpr - | TypeTest(ty, inpExpr) -> - visitExpr memberCallHandler inpExpr + visitExpr memberCallHandler catchExpr + | TupleGet(tupleType, tupleElemIndex, tupleExpr) -> visitExpr memberCallHandler tupleExpr + | DecisionTree(decisionExpr, decisionTargets) -> + visitExpr memberCallHandler decisionExpr + List.iter (snd >> visitExpr memberCallHandler) decisionTargets + | DecisionTreeSuccess(decisionTargetIdx, decisionTargetExprs) -> visitExprs memberCallHandler decisionTargetExprs + | TypeLambda(genericParam, bodyExpr) -> visitExpr memberCallHandler bodyExpr + | TypeTest(ty, inpExpr) -> visitExpr memberCallHandler inpExpr | UnionCaseSet(unionExpr, unionType, unionCase, unionCaseField, valueExpr) -> - visitExpr memberCallHandler unionExpr; visitExpr memberCallHandler valueExpr - | UnionCaseGet(unionExpr, unionType, unionCase, unionCaseField) -> - visitExpr memberCallHandler unionExpr - | UnionCaseTest(unionExpr, unionType, unionCase) -> - visitExpr memberCallHandler unionExpr - | UnionCaseTag(unionExpr, unionType) -> visitExpr memberCallHandler unionExpr + visitExpr memberCallHandler valueExpr + | UnionCaseGet(unionExpr, unionType, unionCase, unionCaseField) -> visitExpr memberCallHandler unionExpr + | UnionCaseTest(unionExpr, unionType, unionCase) -> visitExpr memberCallHandler unionExpr + | UnionCaseTag(unionExpr, unionType) -> visitExpr memberCallHandler unionExpr | ObjectExpr(objType, baseCallExpr, overrides, interfaceImplementations) -> visitExpr memberCallHandler baseCallExpr List.iter (visitObjMember memberCallHandler) overrides List.iter (snd >> List.iter (visitObjMember memberCallHandler)) interfaceImplementations | TraitCall(sourceTypes, traitName, typeArgs, typeInstantiation, argTypes, argExprs) -> visitExprs memberCallHandler argExprs - | ValueSet(valToSet, valueExpr) -> - visitExpr memberCallHandler valueExpr + | ValueSet(valToSet, valueExpr) -> visitExpr memberCallHandler valueExpr | WhileLoop(guardExpr, bodyExpr, debugPointAtWhile) -> - visitExpr memberCallHandler guardExpr; visitExpr memberCallHandler bodyExpr + visitExpr memberCallHandler guardExpr + visitExpr memberCallHandler bodyExpr | BaseValue baseType -> () | DefaultValue defaultType -> () | ThisValue thisType -> () @@ -95,45 +91,46 @@ let rec visitExpr memberCallHandler (e:FSharpExpr) = | Value(valueToGet) -> () | _ -> () -and visitExprs f exprs = - List.iter (visitExpr f) exprs +and visitExprs f exprs = List.iter (visitExpr f) exprs -and visitObjArg f objOpt = - Option.iter (visitExpr f) objOpt +and visitObjArg f objOpt = Option.iter (visitExpr f) objOpt -and visitObjMember f memb = - visitExpr f memb.Body +and visitObjMember f memb = visitExpr f memb.Body let rec visitDeclaration f d = match d with - | FSharpImplementationFileDeclaration.Entity (e, subDecls) -> + | FSharpImplementationFileDeclaration.Entity(e, subDecls) -> for subDecl in subDecls do visitDeclaration f subDecl - | FSharpImplementationFileDeclaration.MemberOrFunctionOrValue(v, vs, e) -> - visitExpr f e - | FSharpImplementationFileDeclaration.InitAction(e) -> - visitExpr f e + | FSharpImplementationFileDeclaration.MemberOrFunctionOrValue(v, vs, e) -> visitExpr f e + | FSharpImplementationFileDeclaration.InitAction(e) -> visitExpr f e -let notUsed() = - let option : Option = None +let notUsed () = + let option: Option = None option.Value [] -let optionValueAnalyzer : Analyzer = +let optionValueAnalyzer: Analyzer = fun ctx -> let state = ResizeArray() + let handler (range: range) (m: FSharpMemberOrFunctionOrValue) = let name = String.Join(".", m.DeclaringEntity.Value.FullName, m.DisplayName) + if name = "Microsoft.FSharp.Core.FSharpOption`1.Value" then state.Add range + ctx.TypedTree.Declarations |> List.iter (visitDeclaration handler) + state |> Seq.map (fun r -> - { Type = "Option.Value analyzer" - Message = "Option.Value shouldn't be used" - Code = "OV001" - Severity = Warning - Range = r - Fixes = []} + { + Type = "Option.Value analyzer" + Message = "Option.Value shouldn't be used" + Code = "OV001" + Severity = Warning + Range = r + Fixes = [] + } ) - |> Seq.toList \ No newline at end of file + |> Seq.toList diff --git a/src/FSharp.Analyzers.Cli/Program.fs b/src/FSharp.Analyzers.Cli/Program.fs index c9288fd..6956a40 100644 --- a/src/FSharp.Analyzers.Cli/Program.fs +++ b/src/FSharp.Analyzers.Cli/Program.fs @@ -14,26 +14,27 @@ type Arguments = | Fail_On_Warnings of string list | Ignore_Files of string list | Verbose -with + interface IArgParserTemplate with member s.Usage = "" let mutable verbose = false - let createFCS () = - let checker = FSharpChecker.Create(projectCacheSize = 200, keepAllBackgroundResolutions = true, keepAssemblyContents = true) + let checker = + FSharpChecker.Create(projectCacheSize = 200, keepAllBackgroundResolutions = true, keepAssemblyContents = true) // checker.ImplicitlyStartBackgroundWork <- true checker + let fcs = createFCS () -let parser = ArgumentParser.Create(errorHandler = ProcessExiter ()) +let parser = ArgumentParser.Create(errorHandler = ProcessExiter()) let rec mkKn (ty: System.Type) = if Reflection.FSharpType.IsFunction(ty) then let _, ran = Reflection.FSharpType.GetFunctionElements(ty) let f = mkKn ran - Reflection.FSharpValue.MakeFunction(ty, fun _ -> f) + Reflection.FSharpValue.MakeFunction(ty, (fun _ -> f)) else box () @@ -61,72 +62,88 @@ let loadProject toolsPath projPath = let fcsPo = FCS.mapToFSharpProjectOptions parsed.Head parsed return fcsPo - } |> Async.RunSynchronously + } + |> Async.RunSynchronously -let typeCheckFile (file,opts) = +let typeCheckFile (file, opts) = let text = File.ReadAllText file let st = SourceText.ofString text - let (parseRes, checkAnswer) = fcs.ParseAndCheckFileInProject(file,0,st,opts) |> Async.RunSynchronously //ToDo: Validate if 0 is ok + + let (parseRes, checkAnswer) = + fcs.ParseAndCheckFileInProject(file, 0, st, opts) |> Async.RunSynchronously //ToDo: Validate if 0 is ok + match checkAnswer with | FSharpCheckFileAnswer.Aborted -> printError "Checking of file %s aborted" file None - | FSharpCheckFileAnswer.Succeeded result -> - Some (file, text, parseRes, result) + | FSharpCheckFileAnswer.Succeeded result -> Some(file, text, parseRes, result) let entityCache = EntityCache() let getAllEntities (checkResults: FSharpCheckFileResults) (publicOnly: bool) : AssemblySymbol list = try - let res = [ - yield! AssemblyContent.GetAssemblySignatureContent AssemblyContentType.Full checkResults.PartialAssemblySignature - let ctx = checkResults.ProjectContext - let assembliesByFileName = - ctx.GetReferencedAssemblies() - |> Seq.groupBy (fun asm -> asm.FileName) - |> Seq.map (fun (fileName, asms) -> fileName, List.ofSeq asms) - |> Seq.toList - |> List.rev // if mscorlib.dll is the first then FSC raises exception when we try to - // get Content.Entities from it. - - for fileName, signatures in assembliesByFileName do - let contentType = if publicOnly then AssemblyContentType.Public else AssemblyContentType.Full - let content = AssemblyContent.GetAssemblyContent entityCache.Locking contentType fileName signatures - yield! content - ] + let res = + [ + yield! + AssemblyContent.GetAssemblySignatureContent + AssemblyContentType.Full + checkResults.PartialAssemblySignature + let ctx = checkResults.ProjectContext + + let assembliesByFileName = + ctx.GetReferencedAssemblies() + |> Seq.groupBy (fun asm -> asm.FileName) + |> Seq.map (fun (fileName, asms) -> fileName, List.ofSeq asms) + |> Seq.toList + |> List.rev // if mscorlib.dll is the first then FSC raises exception when we try to + // get Content.Entities from it. + + for fileName, signatures in assembliesByFileName do + let contentType = + if publicOnly then + AssemblyContentType.Public + else + AssemblyContentType.Full + + let content = + AssemblyContent.GetAssemblyContent entityCache.Locking contentType fileName signatures + + yield! content + ] + res - with - | _ -> [] + with _ -> + [] let createContext (checkProjectResults: FSharpCheckProjectResults, allSymbolUses: FSharpSymbolUse array) - (file, text: string, p: FSharpParseFileResults, c: FSharpCheckFileResults) = + (file, text: string, p: FSharpParseFileResults, c: FSharpCheckFileResults) + = match c.ImplementationFile with | Some tast -> - let context : Context = { - ParseFileResults = p - CheckFileResults = c - CheckProjectResults = checkProjectResults - FileName = file - Content = text.Split([|'\n'|]) - TypedTree = tast - GetAllEntities = getAllEntities c - AllSymbolUses = allSymbolUses - SymbolUsesOfFile = allSymbolUses |> Array.filter (fun s -> s.FileName = file) - } + let context: Context = + { + ParseFileResults = p + CheckFileResults = c + CheckProjectResults = checkProjectResults + FileName = file + Content = text.Split([| '\n' |]) + TypedTree = tast + GetAllEntities = getAllEntities c + AllSymbolUses = allSymbolUses + SymbolUsesOfFile = allSymbolUses |> Array.filter (fun s -> s.FileName = file) + } + Some context | _ -> None - -let runProject toolsPath proj (globs: Glob list) = - let path = - Path.Combine(Environment.CurrentDirectory, proj) - |> Path.GetFullPath +let runProject toolsPath proj (globs: Glob list) = + let path = Path.Combine(Environment.CurrentDirectory, proj) |> Path.GetFullPath let opts = loadProject toolsPath path - + let checkProjectResults = fcs.ParseAndCheckProject(opts) |> Async.RunSynchronously let allSymbolUses = checkProjectResults.GetAllUsesOfAllSymbols() - + opts.SourceFiles |> Array.filter (fun file -> match Path.GetExtension(file).ToLowerInvariant() with @@ -142,22 +159,28 @@ let runProject toolsPath proj (globs: Glob list) = false | None -> true ) - |> Array.choose (fun f -> typeCheckFile (f, opts) |> Option.map (createContext (checkProjectResults, allSymbolUses))) + |> Array.choose (fun f -> + typeCheckFile (f, opts) + |> Option.map (createContext (checkProjectResults, allSymbolUses)) + ) |> Array.collect (fun ctx -> match ctx with | Some c -> printInfo "Running analyzers for %s" c.FileName Client.runAnalyzers c | None -> failwithf "could not get context for file %s" path - ) - |> Some + ) + |> Some let printMessages failOnWarnings (msgs: Message array) = - if verbose then printfn "" - if verbose && Array.isEmpty msgs then printfn "No messages found from the analyzer(s)" + if verbose then + printfn "" + + if verbose && Array.isEmpty msgs then + printfn "No messages found from the analyzer(s)" msgs - |> Seq.iter(fun m -> + |> Seq.iter (fun m -> let color = match m.Severity with | Error -> ConsoleColor.Red @@ -166,18 +189,31 @@ let printMessages failOnWarnings (msgs: Message array) = | Info -> ConsoleColor.Blue Console.ForegroundColor <- color - printfn "%s(%d,%d): %s %s - %s" m.Range.FileName m.Range.StartLine m.Range.StartColumn (m.Severity.ToString()) m.Code m.Message + + printfn + "%s(%d,%d): %s %s - %s" + m.Range.FileName + m.Range.StartLine + m.Range.StartColumn + (m.Severity.ToString()) + m.Code + m.Message + Console.ForegroundColor <- origForegroundColor ) + msgs -let calculateExitCode failOnWarnings (msgs: Message array option): int = +let calculateExitCode failOnWarnings (msgs: Message array option) : int = match msgs with | None -> -1 | Some msgs -> let check = msgs - |> Array.exists (fun n -> n.Severity = Error || (n.Severity = Warning && failOnWarnings |> List.contains n.Code) ) + |> Array.exists (fun n -> + n.Severity = Error + || (n.Severity = Warning && failOnWarnings |> List.contains n.Code) + ) if check then -2 else 0 @@ -197,10 +233,12 @@ let main argv = let ignoreFiles = ignoreFiles |> List.map Glob let analyzersPath = - let path = results.GetResult (<@ Analyzers_Path @>, "packages/Analyzers") - if System.IO.Path.IsPathRooted path - then path - else Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, path)) + let path = results.GetResult(<@ Analyzers_Path @>, "packages/Analyzers") + + if System.IO.Path.IsPathRooted path then + path + else + Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, path)) printInfo "Loading analyzers from %s" analyzersPath @@ -208,6 +246,7 @@ let main argv = printInfo "Registered %d analyzers from %d dlls" analyzers dlls let projOpt = results.TryGetResult <@ Project @> + let results = match projOpt with | None -> @@ -215,9 +254,10 @@ let main argv = None | Some proj -> let project = - if System.IO.Path.IsPathRooted proj - then proj - else Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, proj)) + if System.IO.Path.IsPathRooted proj then + proj + else + Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, proj)) runProject toolsPath project ignoreFiles |> Option.map (printMessages failOnWarnings) diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs index 2f89344..1a9a096 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs @@ -7,134 +7,167 @@ open System.Runtime.Loader open McMaster.NETCore.Plugins open System.Collections.Concurrent -type AnalysisResult = { - AnalyzerName : string - Output : Result -} +type AnalysisResult = + { + AnalyzerName: string + Output: Result + } module Client = - let internal attributeName = "AnalyzerAttribute" - - let internal isAnalyzer (mi: MemberInfo) = - mi.GetCustomAttributes true - |> Seq.tryFind (fun n -> n.GetType().Name = attributeName) - |> Option.map unbox - - let internal analyzerFromMember (mi: MemberInfo) : (string * Analyzer) option = - let inline unboxAnalyzer v = - if isNull v then - failwith "Analyzer is null" - else unbox v - let getAnalyzerFromMemberInfo mi = - match box mi with - | :? FieldInfo as m -> - if m.FieldType = typeof then Some(m.GetValue(null) |> unboxAnalyzer) - else None - | :? MethodInfo as m -> - if m.ReturnType = typeof - then Some(m.Invoke(null, null) |> unboxAnalyzer) - elif m.ReturnType.FullName.StartsWith "Microsoft.FSharp.Collections.FSharpList`1[[FSharp.Analyzers.SDK.Message" then - try - let analyzer : Analyzer = fun ctx -> m.Invoke(null, [|ctx|]) |> unbox - Some analyzer - with - | ex -> None - else None - | :? PropertyInfo as m -> - if m.PropertyType = typeof then Some(m.GetValue(null, null) |> unboxAnalyzer) - else None - | _ -> None - - match isAnalyzer mi with - | Some analyzerAttribute -> - match getAnalyzerFromMemberInfo mi with - | Some analyzer -> Some (analyzerAttribute.Name, analyzer) + let internal attributeName = "AnalyzerAttribute" + + let internal isAnalyzer (mi: MemberInfo) = + mi.GetCustomAttributes true + |> Seq.tryFind (fun n -> n.GetType().Name = attributeName) + |> Option.map unbox + + let internal analyzerFromMember (mi: MemberInfo) : (string * Analyzer) option = + let inline unboxAnalyzer v = + if isNull v then failwith "Analyzer is null" else unbox v + + let getAnalyzerFromMemberInfo mi = + match box mi with + | :? FieldInfo as m -> + if m.FieldType = typeof then + Some(m.GetValue(null) |> unboxAnalyzer) + else + None + | :? MethodInfo as m -> + if m.ReturnType = typeof then + Some(m.Invoke(null, null) |> unboxAnalyzer) + elif + m.ReturnType.FullName.StartsWith + "Microsoft.FSharp.Collections.FSharpList`1[[FSharp.Analyzers.SDK.Message" + then + try + let analyzer: Analyzer = fun ctx -> m.Invoke(null, [| ctx |]) |> unbox + Some analyzer + with ex -> + None + else + None + | :? PropertyInfo as m -> + if m.PropertyType = typeof then + Some(m.GetValue(null, null) |> unboxAnalyzer) + else + None + | _ -> None + + match isAnalyzer mi with + | Some analyzerAttribute -> + match getAnalyzerFromMemberInfo mi with + | Some analyzer -> Some(analyzerAttribute.Name, analyzer) + | None -> None | None -> None - | None -> None - - let internal analyzersFromType (t: Type) = - let asMembers x = Seq.map (fun m -> m :> MemberInfo) x - let bindingFlags = BindingFlags.Public ||| BindingFlags.Static - - let members = - [ t.GetTypeInfo().GetMethods bindingFlags |> asMembers - t.GetTypeInfo().GetProperties bindingFlags |> asMembers - t.GetTypeInfo().GetFields bindingFlags |> asMembers ] - |> Seq.collect id - members - |> Seq.choose analyzerFromMember - |> Seq.toList - - let registeredAnalyzers: ConcurrentDictionary = ConcurrentDictionary() - - ///Loads into private state any analyzers defined in any assembly - ///matching `*Analyzer*.dll` in given directory (and any subdirectories) - ///Returns number of found dlls matching `*Analyzer*.dll` and number of registered analyzers - let loadAnalyzers (printError: string -> unit) (dir: string): (int*int) = - if Directory.Exists dir then - let analyzerAssemblies = - Directory.GetFiles(dir, "*Analyzer*.dll", SearchOption.AllDirectories) - |> Array.filter(fun a -> not (a.EndsWith("FSharp.Analyzers.SDK.dll"))) - |> Array.choose (fun analyzerDll -> + + let internal analyzersFromType (t: Type) = + let asMembers x = Seq.map (fun m -> m :> MemberInfo) x + let bindingFlags = BindingFlags.Public ||| BindingFlags.Static + + let members = + [ + t.GetTypeInfo().GetMethods bindingFlags |> asMembers + t.GetTypeInfo().GetProperties bindingFlags |> asMembers + t.GetTypeInfo().GetFields bindingFlags |> asMembers + ] + |> Seq.collect id + + members |> Seq.choose analyzerFromMember |> Seq.toList + + let registeredAnalyzers: ConcurrentDictionary = + ConcurrentDictionary() + + ///Loads into private state any analyzers defined in any assembly + ///matching `*Analyzer*.dll` in given directory (and any subdirectories) + ///Returns number of found dlls matching `*Analyzer*.dll` and number of registered analyzers + let loadAnalyzers (printError: string -> unit) (dir: string) : (int * int) = + if Directory.Exists dir then + let analyzerAssemblies = + Directory.GetFiles(dir, "*Analyzer*.dll", SearchOption.AllDirectories) + |> Array.filter (fun a -> not (a.EndsWith("FSharp.Analyzers.SDK.dll"))) + |> Array.choose (fun analyzerDll -> + try + // loads an assembly and all of it's dependencies + let analyzerLoader = + PluginLoader.CreateFromAssemblyFile( + analyzerDll, + fun config -> + config.DefaultContext <- AssemblyLoadContext.Default + config.PreferSharedTypes <- true + ) + + Some(analyzerDll, analyzerLoader.LoadDefaultAssembly()) + with _ -> + None + ) + + let currentFSharpAnalyzersSDKVersion = + Assembly.GetExecutingAssembly().GetName().Version + + let findFSharpAnalyzerSDKVersion (assembly: Assembly) = + let references = assembly.GetReferencedAssemblies() + let fas = references |> Array.find (fun ra -> ra.Name = "FSharp.Analyzers.SDK") + fas.Version + + let analyzers = + analyzerAssemblies + |> Array.filter (fun (name, analyzerAssembly) -> + let version = findFSharpAnalyzerSDKVersion analyzerAssembly + + if version = currentFSharpAnalyzersSDKVersion then + true + else + printError + $"Trying to load %s{name} which was built using SDK version %A{version}. Expect %A{currentFSharpAnalyzersSDKVersion} instead. Assembly will be skipped." + + false + ) + |> Array.map (fun (path, assembly) -> + let analyzers = assembly.GetExportedTypes() |> Seq.collect (analyzersFromType) + path, analyzers + ) + + analyzers + |> Seq.iter (fun (path, analyzers) -> + let analyzers = Seq.toList analyzers + + registeredAnalyzers.AddOrUpdate(path, analyzers, (fun _ _ -> analyzers)) + |> ignore + ) + + Seq.length analyzers, analyzers |> Seq.collect (snd) |> Seq.length + else + 0, 0 + + ///Runs all registered analyzers for given context (file). + ///Returns list of messages. Ignores errors from the analyzers + let runAnalyzers (ctx: Context) : Message[] = + let analyzers = registeredAnalyzers.Values |> Seq.collect id + + analyzers + |> Seq.collect (fun (analyzerName, analyzer) -> try - // loads an assembly and all of it's dependencies - let analyzerLoader = PluginLoader.CreateFromAssemblyFile(analyzerDll, fun config -> config.DefaultContext <- AssemblyLoadContext.Default; config.PreferSharedTypes <- true) - Some (analyzerDll, analyzerLoader.LoadDefaultAssembly()) - with - | _ -> None) - - let currentFSharpAnalyzersSDKVersion = Assembly.GetExecutingAssembly().GetName().Version - - let findFSharpAnalyzerSDKVersion (assembly: Assembly) = - let references = assembly.GetReferencedAssemblies() - let fas = - references - |> Array.find (fun ra -> ra.Name = "FSharp.Analyzers.SDK") - fas.Version - - let analyzers = - analyzerAssemblies - |> Array.filter (fun (name, analyzerAssembly) -> - let version = findFSharpAnalyzerSDKVersion analyzerAssembly - if version = currentFSharpAnalyzersSDKVersion then - true - else - printError $"Trying to load %s{name} which was built using SDK version %A{version}. Expect %A{currentFSharpAnalyzersSDKVersion} instead. Assembly will be skipped." - false + analyzer ctx + with error -> + [] + ) + |> Seq.toArray + + /// Runs all registered analyzers for given context (file). + /// Returns list of results per analyzer which can ei + let runAnalyzersSafely (ctx: Context) : AnalysisResult list = + let analyzers = registeredAnalyzers.Values |> Seq.collect id + + analyzers + |> Seq.map (fun (analyzerName, analyzer) -> + { + AnalyzerName = analyzerName + Output = + try + Ok(analyzer ctx) + with error -> + Result.Error error + } ) - |> Array.map (fun (path,assembly) -> - let analyzers = assembly.GetExportedTypes() |> Seq.collect (analyzersFromType) - path, analyzers) - - analyzers - |> Seq.iter (fun (path, analyzers) -> - let analyzers = Seq.toList analyzers - registeredAnalyzers.AddOrUpdate(path, analyzers, (fun _ _ -> analyzers)) - |> ignore - ) - - Seq.length analyzers, - analyzers |> Seq.collect (snd) |> Seq.length - else - 0,0 - - ///Runs all registered analyzers for given context (file). - ///Returns list of messages. Ignores errors from the analyzers - let runAnalyzers (ctx: Context) : Message [] = - let analyzers = registeredAnalyzers.Values |> Seq.collect id - analyzers - |> Seq.collect (fun (analyzerName, analyzer) -> try analyzer ctx with error -> [ ]) - |> Seq.toArray - - /// Runs all registered analyzers for given context (file). - /// Returns list of results per analyzer which can ei - let runAnalyzersSafely (ctx: Context) : AnalysisResult list = - let analyzers = registeredAnalyzers.Values |> Seq.collect id - analyzers - |> Seq.map (fun (analyzerName, analyzer) -> - { - AnalyzerName = analyzerName - Output = try Ok (analyzer ctx) with error -> Result.Error error - }) - |> Seq.toList \ No newline at end of file + |> Seq.toList diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs index c90d595..152ab88 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs @@ -1,47 +1,53 @@ -namespace FSharp.Analyzers.SDK - -open System -open FSharp.Compiler -open FSharp.Compiler.CodeAnalysis -open FSharp.Compiler.Syntax -open FSharp.Compiler.Symbols -open FSharp.Compiler.EditorServices -open System.Runtime.InteropServices - -/// Marks an analyzer for scanning -[] -type AnalyzerAttribute([] name: string) = - inherit Attribute() - - member _.Name = name - -type Context = - { ParseFileResults: FSharpParseFileResults - CheckFileResults: FSharpCheckFileResults - CheckProjectResults: FSharpCheckProjectResults - FileName: string - Content: string[] - TypedTree: FSharpImplementationFileContents - GetAllEntities: bool -> AssemblySymbol list - AllSymbolUses: FSharpSymbolUse array - SymbolUsesOfFile: FSharpSymbolUse array } - -type Fix = - { FromRange : Text.Range - FromText : string - ToText : string } - -type Severity = - | Info - | Warning - | Error - -type Message = - { Type: string - Message: string - Code: string - Severity: Severity - Range: Text.Range - Fixes: Fix list } - -type Analyzer = Context -> Message list \ No newline at end of file +namespace FSharp.Analyzers.SDK + +open System +open FSharp.Compiler +open FSharp.Compiler.CodeAnalysis +open FSharp.Compiler.Syntax +open FSharp.Compiler.Symbols +open FSharp.Compiler.EditorServices +open System.Runtime.InteropServices + +/// Marks an analyzer for scanning +[] +type AnalyzerAttribute([] name: string) = + inherit Attribute() + + member _.Name = name + +type Context = + { + ParseFileResults: FSharpParseFileResults + CheckFileResults: FSharpCheckFileResults + CheckProjectResults: FSharpCheckProjectResults + FileName: string + Content: string[] + TypedTree: FSharpImplementationFileContents + GetAllEntities: bool -> AssemblySymbol list + AllSymbolUses: FSharpSymbolUse array + SymbolUsesOfFile: FSharpSymbolUse array + } + +type Fix = + { + FromRange: Text.Range + FromText: string + ToText: string + } + +type Severity = + | Info + | Warning + | Error + +type Message = + { + Type: string + Message: string + Code: string + Severity: Severity + Range: Text.Range + Fixes: Fix list + } + +type Analyzer = Context -> Message list