Skip to content

Commit 04b4dd1

Browse files
authored
Add support for F# analyzers in project evaluation (#238)
* Add support for F# analyzers in project evaluation - Extend ProjectInfo types to include Analyzers, AllProperties, and AllItems. - Implement logic to collect all properties and items during project evaluation. - Add a test case for loading a project with NuGet analyzers. - Create sample project files for testing F# analyzers. * Refactor package references in sample15-nuget-analyzers.fsproj to streamline asset management
1 parent 39d1b85 commit 04b4dd1

File tree

7 files changed

+219
-3
lines changed

7 files changed

+219
-3
lines changed

build/Program.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ let init args =
114114

115115
exec
116116
"dotnet"
117-
$"test --blame --blame-hang-timeout 60s --framework {tfm} --logger trx --logger GitHubActions -c %s{configuration} .\\Ionide.ProjInfo.Tests\\Ionide.ProjInfo.Tests.fsproj -- %s{failedOnFocus}"
117+
$"test --blame --blame-hang-timeout 120s --framework {tfm} --logger trx --logger GitHubActions -c %s{configuration} .\\Ionide.ProjInfo.Tests\\Ionide.ProjInfo.Tests.fsproj -- %s{failedOnFocus}"
118118
"test"
119119
(Map.ofSeq [ "BuildNet9", tfmToBuildNet9Map.[tfm].ToString() ])
120120
|> ignore

src/Ionide.ProjInfo/Library.fs

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -873,7 +873,48 @@ module ProjectLoader =
873873
IsPublishable = msbuildPropBool "IsPublishable"
874874
}
875875

876-
let mapToProject (path: string) (compilerArgs: string seq) (p2p: ProjectReference seq) (compile: CompileItem seq) (nugetRefs: PackageReference seq) (sdkInfo: ProjectSdkInfo) (props: Property seq) (customProps: Property seq) =
876+
#if DEBUG
877+
let private printProperties (props: Map<string, Set<string>>) =
878+
let file = Path.Combine(__SOURCE_DIRECTORY__, "props.txt")
879+
880+
props
881+
|> Map.iter (fun key value ->
882+
IO.File.AppendAllLines(file, [ $"Property: {key}" ])
883+
884+
value
885+
|> Set.iter (fun v -> IO.File.AppendAllLines(file, [ $" Value: {v}" ]))
886+
)
887+
888+
let private printItems (items: Map<string, Set<string * Map<string, string>>>) =
889+
let file = Path.Combine(__SOURCE_DIRECTORY__, "items.txt")
890+
891+
items
892+
|> Map.iter (fun key value ->
893+
IO.File.AppendAllLines(file, [ $"ItemType: {key}" ])
894+
895+
value
896+
|> Set.iter (fun (include_, metadata) ->
897+
IO.File.AppendAllLines(file, [ $" Value: {include_}" ])
898+
899+
metadata
900+
|> Map.iter (fun mk mv -> IO.File.AppendAllLines(file, [ $" {mk}: {mv}" ]))
901+
)
902+
)
903+
#endif
904+
905+
let mapToProject
906+
(path: string)
907+
(compilerArgs: string seq)
908+
(p2p: ProjectReference seq)
909+
(compile: CompileItem seq)
910+
(nugetRefs: PackageReference seq)
911+
(sdkInfo: ProjectSdkInfo)
912+
(props: Property seq)
913+
(customProps: Property seq)
914+
(analyzers: Analyzer list)
915+
(allProps: Map<string, Set<string>>)
916+
(allItems: Map<string, Set<string * Map<string, string>>>)
917+
=
877918
let projDir = Path.GetDirectoryName path
878919

879920
let outputType, sourceFiles, otherOptions =
@@ -944,6 +985,9 @@ module ProjectLoader =
944985
Items = compileItems
945986
Properties = List.ofSeq props
946987
CustomProperties = List.ofSeq customProps
988+
Analyzers = analyzers
989+
AllProperties = allProps
990+
AllItems = allItems
947991
}
948992

949993

@@ -1005,10 +1049,78 @@ module ProjectLoader =
10051049
let sdkInfo = getSdkInfo props
10061050
let customProps = getProperties p customProperties
10071051

1052+
let allProperties =
1053+
(Map.empty, p.Properties)
1054+
||> Seq.fold (fun acc p ->
1055+
Map.change
1056+
p.Name
1057+
(function
1058+
| None ->
1059+
Set.singleton p.EvaluatedValue
1060+
|> Some
1061+
| Some v ->
1062+
v
1063+
|> Set.add p.EvaluatedValue
1064+
|> Some)
1065+
acc
1066+
)
1067+
1068+
let allItems =
1069+
(Map.empty, p.Items)
1070+
||> Seq.fold (fun acc item ->
1071+
let value = item.EvaluatedInclude
1072+
1073+
let metadata =
1074+
item.Metadata
1075+
|> Seq.map (fun m -> m.Name, m.EvaluatedValue)
1076+
|> Map.ofSeq
1077+
1078+
Map.change
1079+
item.ItemType
1080+
(function
1081+
| None ->
1082+
Set.singleton (value, metadata)
1083+
|> Some
1084+
| Some items ->
1085+
items
1086+
|> Set.add (value, metadata)
1087+
|> Some)
1088+
acc
1089+
1090+
)
1091+
1092+
1093+
let analyzers =
1094+
let analyzerLikePath = Path.Combine("analyzers", "dotnet", "fs")
1095+
1096+
allProperties
1097+
|> Map.filter (fun k v ->
1098+
k.StartsWith("Pkg")
1099+
&& v
1100+
|> Set.exists (fun s -> Path.Exists(Path.Combine(s, analyzerLikePath)))
1101+
)
1102+
|> Seq.collect (fun (KeyValue(k, v)) ->
1103+
v
1104+
|> Seq.map (fun v ->
1105+
let analyzerPath = Path.Combine(v, analyzerLikePath)
1106+
1107+
{
1108+
PropertyName = k
1109+
NugetPackageRoot = v
1110+
DllRootPath = analyzerPath
1111+
}
1112+
1113+
)
1114+
)
1115+
|> Seq.toList
1116+
1117+
10081118
if not sdkInfo.RestoreSuccess then
10091119
Error "not restored"
10101120
else
1011-
let proj = mapToProject path commandLineArgs p2pRefs compileItems nuGetRefs sdkInfo props customProps
1121+
let proj =
1122+
mapToProject path commandLineArgs p2pRefs compileItems nuGetRefs sdkInfo props customProps analyzers allProperties allItems
1123+
10121124
Ok(LoadedProjectInfo.StandardProjectInfo proj)
10131125
| LoadedProject.Other p -> Ok(LoadedProjectInfo.OtherProjectInfo p)
10141126

src/Ionide.ProjInfo/Types.fs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ module Types =
5050

5151
type ProjectItem = Compile of name: string * fullpath: string * metadata: Map<string, string> option
5252

53+
type Analyzer = {
54+
/// The name of the Property from the Project like "PkgIonide_Analyzers"
55+
PropertyName: string
56+
/// The Path to the nuget root folder for this package, like "C:\Users\username\.nuget\packages\ionide.analyzers\0.14.7"
57+
NugetPackageRoot: string
58+
/// The Path that contains the actual analyzer DLLs, like "C:\Users\username\.nuget\packages\ionide.analyzers\0.14.7\analyzers\dotnet\fs"
59+
DllRootPath: string
60+
}
61+
5362
type ProjectOptions = {
5463
ProjectId: string option
5564
ProjectFileName: string
@@ -68,6 +77,13 @@ module Types =
6877
Items: ProjectItem list
6978
Properties: Property list
7079
CustomProperties: Property list
80+
/// This contains all Project Properties seen during evaluation
81+
AllProperties: Map<string, Set<string>>
82+
/// This contains all Project Items seen during evaluation with their values and associated metadata
83+
AllItems: Map<string, Set<string * Map<string, string>>>
84+
/// Will have Key Value pairs like "PkgIonide_Analyzers" -> "C:\Users\username\.nuget\packages\ionide.analyzers\0.14.7"
85+
/// The "analyzers/dotnet/fs" subfolder is not included here, just the package root
86+
Analyzers: Analyzer list
7187
} with
7288

7389
/// ResolvedTargetPath is the path to the primary reference assembly for this project.

test/Ionide.ProjInfo.Tests/TestAssets.fs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,3 +366,13 @@ let ``sample 14 slnx solution`` = {
366366
TargetFrameworks = Map.empty
367367
ProjectReferences = []
368368
}
369+
370+
371+
/// A test with f# analyzers
372+
let ``sample 15 nuget analyzers`` = {
373+
ProjDir = "sample15-nuget-analyzers"
374+
AssemblyName = ""
375+
ProjectFile = "sample15-nuget-analyzers.fsproj"
376+
TargetFrameworks = Map.empty
377+
ProjectReferences = []
378+
}

test/Ionide.ProjInfo.Tests/Tests.fs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1506,6 +1506,9 @@ let testFCSmapManyProjCheckCaching =
15061506
Items = []
15071507
Properties = []
15081508
CustomProperties = []
1509+
Analyzers = []
1510+
AllProperties = Map.empty
1511+
AllItems = Map.empty
15091512
}
15101513

15111514
let makeReference (options: ProjectOptions) = {
@@ -2353,6 +2356,48 @@ let sample14SlnxFileTest toolsPath loaderType workspaceFactory =
23532356
| _ -> failtestf "Expected a project, but got %A" expectedProject.Kind
23542357
)
23552358

2359+
let sample15NugetAnalyzers toolsPath loaderType workspaceFactory =
2360+
testCase
2361+
|> withLog
2362+
$"Can load sample15 with nuget analyzers - {loaderType}"
2363+
(fun log fs ->
2364+
2365+
let projPath = pathForProject ``sample 15 nuget analyzers``
2366+
let projDir = Path.GetDirectoryName projPath
2367+
2368+
// need to build the projects first so that there's something to latch on to
2369+
dotnet fs [
2370+
"restore"
2371+
projPath
2372+
]
2373+
|> checkExitCodeZero
2374+
2375+
let loader: IWorkspaceLoader = workspaceFactory toolsPath
2376+
2377+
let parsed =
2378+
loader.LoadProjects [ projPath ]
2379+
|> Seq.toList
2380+
2381+
Expect.hasLength parsed 1 "Should have loaded the F# lib"
2382+
2383+
let fsharpProject = parsed[0]
2384+
2385+
let analyzerDlls = fsharpProject.Analyzers
2386+
2387+
Expect.isSome
2388+
(analyzerDlls
2389+
|> List.tryFind (fun a -> a.PropertyName = "PkgG-Research_FSharp_Analyzers"))
2390+
"Should have found the PkgG-Research_FSharp_Analyzers key in the analyzers list"
2391+
2392+
Expect.isSome
2393+
(analyzerDlls
2394+
|> List.tryFind (fun a -> a.PropertyName = "PkgIonide_Analyzers"))
2395+
"Should have found the PkgIonide_Analyzers key in the analyzers list"
2396+
2397+
2398+
)
2399+
2400+
23562401
let tests toolsPath =
23572402
let testSample3WorkspaceLoaderExpected = [
23582403
ExpectNotification.loading "c1.fsproj"
@@ -2491,4 +2536,7 @@ let tests toolsPath =
24912536

24922537
sample14SlnxFileTest toolsPath (nameof (WorkspaceLoader)) WorkspaceLoader.Create
24932538
sample14SlnxFileTest toolsPath (nameof (WorkspaceLoaderViaProjectGraph)) WorkspaceLoaderViaProjectGraph.Create
2539+
2540+
sample15NugetAnalyzers toolsPath (nameof (WorkspaceLoader)) WorkspaceLoader.Create
2541+
sample15NugetAnalyzers toolsPath (nameof (WorkspaceLoaderViaProjectGraph)) WorkspaceLoaderViaProjectGraph.Create
24942542
]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
namespace sample14_nuget_analyzers
2+
3+
module Say =
4+
let hello name =
5+
printfn "Hello %s" name
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<RootNamespace>sample14_nuget_analyzers</RootNamespace>
6+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
7+
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<Compile Include="Library.fs" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<PackageReference Include="G-Research.FSharp.Analyzers" Version="0.18.0">
16+
<PrivateAssets>all</PrivateAssets>
17+
<IncludeAssets>analyzers</IncludeAssets>
18+
</PackageReference>
19+
<PackageReference Include="Ionide.Analyzers" Version="0.14.7">
20+
<PrivateAssets>all</PrivateAssets>
21+
<IncludeAssets>analyzers</IncludeAssets>
22+
</PackageReference>
23+
</ItemGroup>
24+
25+
</Project>

0 commit comments

Comments
 (0)