Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions tests/scripts/identifierAnalysisByType.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// Print some stats about identifiers grouped by type
//

#r "nuget: Ionide.ProjInfo"
#I @"..\..\artifacts\bin\fsc\Debug\net6.0\"
#r "FSharp.Compiler.Service.dll"

open System
open System.IO
open Ionide.ProjInfo
open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.Symbols
open Ionide.ProjInfo.Types

let argv = fsi.CommandLineArgs

if argv.Length = 1 then
eprintfn "usage:"
eprintfn " dotnet fsi tests/scripts/identifierAnalysisByType.fsx <project-file>"
eprintfn ""
eprintfn "examples:"
eprintfn " dotnet fsi tests/scripts/identifierAnalysisByType.fsx src/FSharp.Build/FSharp.Build.fsproj"
eprintfn " dotnet fsi tests/scripts/identifierAnalysisByType.fsx src/Compiler/FSharp.Compiler.Service.fsproj"
eprintfn ""
eprintfn "Sample output is at https://gist.github.com/dsyme/abfa11bebf0713251418906d55c08804"

//let projectFile = Path.Combine(__SOURCE_DIRECTORY__, @"..\..\src\Compiler\FSharp.Compiler.Service.fsproj")
//let projectFile = Path.Combine(__SOURCE_DIRECTORY__, @"..\..\src\FSharp.Build\FSharp.Build.fsproj")
let projectFile = Path.GetFullPath(argv[1])

let cwd = System.Environment.CurrentDirectory |> System.IO.DirectoryInfo

let _toolsPath = Init.init cwd None

printfn "Cracking project options...."
let opts =
match ProjectLoader.getProjectInfo projectFile [] BinaryLogGeneration.Off [] with
| Result.Ok res -> res
| Result.Error err -> failwithf "%s" err

let checker = FSharpChecker.Create()

let checkerOpts = checker.GetProjectOptionsFromCommandLineArgs(projectFile, [| yield! opts.SourceFiles; yield! opts.OtherOptions |] )

printfn "Checking project...."
let results = checker.ParseAndCheckProject(checkerOpts) |> Async.RunSynchronously

printfn "Grouping symbol uses...."
let symbols = results.GetAllUsesOfAllSymbols()

let rec stripTy (ty: FSharpType) =
if ty.IsAbbreviation then stripTy ty.AbbreviatedType else ty

let getTypeText (sym: FSharpMemberOrFunctionOrValue) =
let ty = stripTy sym.FullType
FSharpType.Prettify(ty).Format(FSharpDisplayContext.Empty)

symbols
|> Array.choose (fun vUse -> match vUse.Symbol with :? FSharpMemberOrFunctionOrValue as v -> Some (v, vUse.Range) | _ -> None)
|> Array.filter (fun (v, _) -> v.GenericParameters.Count = 0)
|> Array.filter (fun (v, _) -> v.CurriedParameterGroups.Count = 0)
|> Array.filter (fun (v, _) -> not v.FullType.IsGenericParameter)
|> Array.map (fun (v, vUse) -> getTypeText v, v, vUse)
|> Array.filter (fun (vTypeText, v, _) ->
match vTypeText with
| "System.String" -> false
| "System.Boolean" -> false
| "System.Int32" -> false
| "System.Int64" -> false
| "System.Object" -> false
| "Microsoft.FSharp.Collections.List<Microsoft.FSharp.Core.string>" -> false
| "Microsoft.FSharp.Core.Option<Microsoft.FSharp.Core.string>" -> false
| s when s.EndsWith(" Microsoft.FSharp.Core.[]") -> false // for now filter array types
| _ when v.DisplayName.StartsWith "_" -> false
| _ -> true)
|> Array.groupBy (fun (vTypeText, _, _) -> vTypeText)
|> Array.map (fun (key, g) ->
key,
(g
|> Array.groupBy (fun (_, v, _) -> v.DisplayName)
|> Array.sortByDescending (snd >> Array.length)))
|> Array.filter (fun (_, g) -> g.Length > 1)
|> Array.sortByDescending (fun (key, g) -> Array.length g)
|> Array.iter (fun (key, g) ->
let key = key.Replace("Microsoft.FSharp", "FSharp").Replace("FSharp.Core.", "")
printfn "Type: %s" key
for (nm, entries) in g do
printfn " %s (%d times)" nm (Array.length entries)
for (_, _, vUse) in entries do
printfn " %s" (vUse.ToString())
printfn "")

(*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be deleted?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably. I hacked it across from https://github.com/fsprojects/FSharp.Formatting/blob/main/src/fsdocs-tool/ProjectCracker.fs and I think it was all a workaround for something, I'm not sure. Or else maybe it was there to cope with non-standard install locations for dotnet

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW Ionide.ProjInfo is here: https://github.com/ionide/proj-info

It's the project cracking code used by the Ionide tooling for F# in VSCode

let isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)

let dotnet =
if isWindows then
"dotnet.exe"
else
"dotnet"
let fileExists pathToFile =
try
File.Exists(pathToFile)
with _ ->
false
// Look for global install of dotnet sdk
let getDotnetGlobalHostPath () =
let pf = Environment.GetEnvironmentVariable("ProgramW6432")

let pf =
if String.IsNullOrEmpty(pf) then
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)
else
pf

let candidate = Path.Combine(pf, "dotnet", dotnet)

if fileExists candidate then
Some candidate
else
// Can't find it --- give up
None

let getDotnetHostPath () =
let probePathForDotnetHost () =
let paths =
let p = Environment.GetEnvironmentVariable("PATH")

if not (isNull p) then
p.Split(Path.PathSeparator)
else
[||]

paths |> Array.tryFind (fun f -> fileExists (Path.Combine(f, dotnet)))

match (Environment.GetEnvironmentVariable("DOTNET_HOST_PATH")) with
// Value set externally
| value when not (String.IsNullOrEmpty(value)) && fileExists value -> Some value
| _ ->
// Probe for netsdk install, dotnet. and dotnet.exe is a constant offset from the location of System.Int32
let candidate =
let assemblyLocation = Path.GetDirectoryName(typeof<Int32>.Assembly.Location)
Path.GetFullPath(Path.Combine(assemblyLocation, "..", "..", "..", dotnet))

if fileExists candidate then
Some candidate
else
match probePathForDotnetHost () with
| Some f -> Some(Path.Combine(f, dotnet))
| None -> getDotnetGlobalHostPath ()
let dotnetExe = getDotnetHostPath () |> Option.map System.IO.FileInfo
*)