Created
May 10, 2025 15:35
-
-
Save baronfel/df0781136177b805c37ac685f2f61f04 to your computer and use it in GitHub Desktop.
example of using the F# compiler APIs to get usage of symbols
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#r "FSharp.Compiler.Service.dll" | |
open FSharp.Compiler.CodeAnalysis | |
open FSharp.Compiler.Text | |
open FSharp.Compiler.Syntax | |
module Helpers = | |
type LetPrivateWalker() = | |
inherit SyntaxVisitorBase<range>() | |
override this.VisitBinding | |
( | |
path: SyntaxVisitorPath, | |
defaultTraverse: SynBinding -> range option, | |
synBinding: SynBinding | |
) : range option = | |
match synBinding with | |
| _ -> Some synBinding.RangeOfHeadPattern | |
// | SynBinding.SynBinding (accessibility = Some (SynAccess.Private _); kind = SynBindingKind.Normal; range = m) -> | |
// Some m | |
// | SynBinding.SynBinding (accessibility = None | |
// kind = SynBindingKind.Normal | |
// headPat = SynPat.Named(accessibility = Some (SynAccess.Private (_))) | |
// range = m) -> Some m | |
// | _ -> defaultTraverse synBinding | |
let find_privates (parseTree: ParsedInput) = | |
let privates = ResizeArray<_>() | |
(privates, parseTree) | |
||> ParsedInput.fold (fun privates _path node -> | |
match node with | |
| SyntaxNode.SynBinding (SynBinding.SynBinding (accessibility = Some (SynAccess.Private _) | |
kind = SynBindingKind.Normal | |
range = m)) -> | |
ignore (privates.Add m) | |
privates | |
| SyntaxNode.SynPat (SynPat.Named (accessibility = Some (SynAccess.Private (_)); range = m)) -> | |
ignore (privates.Add m) | |
privates | |
| _ -> privates) | |
let sourceCodeToCheck = | |
""" | |
let private myName = "John Doe" | |
let private myAge = 30 | |
let getAge() = myAge | |
printfn "My age is %d{getAge()}" | |
""" | |
let sourceText: ISourceText = SourceTextNew.ofString sourceCodeToCheck | |
let compiler = FSharpChecker.Create() | |
let checkerSnapshot, snapshotDiagnostics = | |
compiler.GetProjectOptionsFromScript("script.fsx", source = sourceText) | |
|> Async.RunSynchronously | |
if snapshotDiagnostics.Length > 0 then | |
printfn "Diagnostics: %A" snapshotDiagnostics | |
let (parseResults, checkResults) = | |
compiler.ParseAndCheckFileInProject( | |
"script.fsx", | |
fileVersion = 0, | |
sourceText = sourceText, | |
options = checkerSnapshot | |
) | |
|> Async.RunSynchronously | |
if parseResults.Diagnostics.Length > 0 then | |
printfn "Parse Diagnostics: %A" parseResults.Diagnostics | |
let privates = Helpers.find_privates parseResults.ParseTree | |
match checkResults with | |
| FSharpCheckFileAnswer.Aborted -> printfn "Typecheck aborted" | |
| FSharpCheckFileAnswer.Succeeded checkResults -> | |
for priv_symbol in privates do | |
let priv_symbol_name = sourceText.GetSubTextFromRange priv_symbol | |
let line = sourceText.GetLineString(priv_symbol.StartLine - 1) | |
printfn $"Private symbol '%s{priv_symbol_name}' found at %O{priv_symbol}" | |
match | |
checkResults.GetSymbolUseAtLocation | |
( | |
priv_symbol.StartLine, | |
priv_symbol.EndColumn, | |
line, | |
[ priv_symbol_name ] | |
) | |
with | |
| Some symbolUse -> | |
printfn "Found private symbol: %A" symbolUse.Symbol | |
// you'd have to do this for all 'files' that are downstream of this symbol/file too | |
let other_uses_in_this_file = checkResults.GetUsesOfSymbolInFile(symbolUse.Symbol) | |
let filtered_uses = | |
other_uses_in_this_file | |
|> Array.filter (fun u -> u.IsFromUse) | |
if filtered_uses.Length > 0 then | |
printfn "Other uses in this file:" | |
for usage in filtered_uses do | |
let range = usage.Range | |
let line = sourceText.GetLineString(range.StartLine - 1) | |
let symbol_name = sourceText.GetSubTextFromRange range | |
printfn $"%O{range} '%s{symbol_name}' in line '%s{line}'" | |
else | |
printfn "No other uses in this file" | |
| None -> | |
printfn $"No symbol found for private '%s{priv_symbol_name}' at range %O{priv_symbol} in line '%s{line}'" | |
() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment