Commit:
2a73392Parent:
5723d01Follow properties and indexers
Assisted-by: GitHub Copilot:gpt-6.1-sol
Directory.Packages.props
+4
-0
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 29a8326..c152db4 100644
@@ -13,5 +13,9 @@
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="5.9.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="5.9.0" />
<PackageVersion Include="System.CommandLine" Version="2.0.14" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageVersion Include="Shouldly" Version="4.3.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.4" />
</ItemGroup>
</Project>
code-flow-analysis.slnx
+1
-0
diff --git a/code-flow-analysis.slnx b/code-flow-analysis.slnx
index 5e0d5f4..4a8922c 100644
@@ -4,5 +4,6 @@
</Folder>
<Folder Name="/core/">
<Project Path="core/src/Cfa.csproj" />
<Project Path="core/tests/Cfa.Tests.csproj" />
</Folder>
</Solution>
core/src/CalleBuilder.cs
+39
-7
diff --git a/core/src/CalleBuilder.cs b/core/src/CalleBuilder.cs
index 2f3919b..ce496e5 100644
@@ -25,18 +25,50 @@ internal sealed class CalleeBuilder(IReadOnlyCollection<Compilation> compilation
{
var syntax = reference.GetSyntax(cancellationToken);
var semanticModelForSyntax = compilations.GetSemanticModel(syntax.SyntaxTree);
var invocations = syntax.DescendantNodesAndSelf().OfType<InvocationExpressionSyntax>();
foreach (var invocation in invocations)
var accesses = syntax
.DescendantNodesAndSelf()
.Where(node =>
node is SimpleNameSyntax or ElementAccessExpressionSyntax or ElementBindingExpressionSyntax
);
foreach (var access in accesses)
{
var invokedSymbol = semanticModelForSyntax.GetSymbolInfo(invocation, cancellationToken).Symbol;
if (invokedSymbol is { DeclaringSyntaxReferences.Length: > 0 } && seen.Add(invokedSymbol))
var accessedSymbol = semanticModelForSyntax.GetSymbolInfo(access, cancellationToken).Symbol;
if (
accessedSymbol is not (IMethodSymbol or IPropertySymbol)
|| !seen.Add(accessedSymbol)
|| !IsDeclaredInSource(accessedSymbol)
)
{
callees.Add(
new Callee(invokedSymbol, LinkKind.Direct, FindCallees(invokedSymbol, cancellationToken))
);
continue;
}
if (accessedSymbol is IPropertySymbol property && !HasBody(property, cancellationToken))
{
continue;
}
callees.Add(
new Callee(accessedSymbol, LinkKind.Direct, FindCallees(accessedSymbol, cancellationToken))
);
}
}
return callees.ToImmutable();
}
private static bool IsDeclaredInSource(ISymbol symbol) =>
symbol.DeclaringSyntaxReferences.Length > 0 && !symbol.IsImplicitlyDeclared;
private static bool HasBody(IPropertySymbol property, CancellationToken cancellationToken) =>
property.DeclaringSyntaxReferences.Any(reference =>
reference.GetSyntax(cancellationToken) switch
{
PropertyDeclarationSyntax { ExpressionBody: not null } => true,
IndexerDeclarationSyntax { ExpressionBody: not null } => true,
BasePropertyDeclarationSyntax declaration => declaration.AccessorList?.Accessors.Any(accessor =>
accessor.Body is not null || accessor.ExpressionBody is not null
)
is true,
_ => false,
}
);
}
core/src/Scanner.cs
+2
-1
diff --git a/core/src/Scanner.cs b/core/src/Scanner.cs
index 9053e48..716327f 100644
@@ -29,7 +29,7 @@ public sealed class Scanner : IDisposable
private readonly Solution solution;
private readonly IReadOnlyCollection<Compilation> compilations;
private Scanner(Workspace workspace, Solution solution, IReadOnlyCollection<Compilation> compilations)
internal Scanner(Workspace workspace, Solution solution, IReadOnlyCollection<Compilation> compilations)
{
this.workspace = workspace;
this.solution = solution;
@@ -66,6 +66,7 @@ public sealed class Scanner : IDisposable
is MethodDeclarationSyntax
or ConstructorDeclarationSyntax
or PropertyDeclarationSyntax
or IndexerDeclarationSyntax
or LambdaExpressionSyntax
&& m.GetLocation().GetLineSpan() is var lineSpan
&& lineSpan.StartLinePosition.Line + 1 <= startLine
core/tests/CalleeBuilderTests/FindCalleesShould.cs
+184
-0
diff --git a/core/tests/CalleeBuilderTests/FindCalleesShould.cs b/core/tests/CalleeBuilderTests/FindCalleesShould.cs
new file mode 100644
index 0000000..35a1998
@@ -0,0 +1,184 @@
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Shouldly;
using Xunit;
namespace Cfa.Tests.CalleeBuilderTests;
public sealed class FindCalleesShould
{
[Theory]
[InlineData("Value")]
[InlineData("this.Value")]
public void FindExpressionBodiedProperties(string access)
{
// Arrange
var source = $$"""
class Example
{
int Value => 1;
void Start() { _ = {{access}}; }
}
""";
// Act
var callees = FindCallees(source);
// Assert
callees.ShouldHaveSingleItem().Target.Name.ShouldBe("Value");
}
[Fact]
public void FollowMethodCallsInsideProperties()
{
// Arrange
const string source = """
class Example
{
int Value => GetValue();
int GetValue() => 1;
void Start() { _ = Value; }
}
""";
// Act
var callees = FindCallees(source);
// Assert
var property = callees.ShouldHaveSingleItem();
property.Target.Name.ShouldBe("Value");
property.Callees.ShouldHaveSingleItem().Target.Name.ShouldBe("GetValue");
}
[Theory]
[InlineData("int Value { get { return 1; } }", "_ = Value;")]
[InlineData("int Value { get => 1; }", "_ = Value;")]
[InlineData("int Value { set { } }", "Value = 1;")]
public void FindPropertiesWithAccessorBodies(string declaration, string access)
{
// Arrange
var source = $$"""
class Example
{
{{declaration}}
void Start() { {{access}} }
}
""";
// Act
var callees = FindCallees(source);
// Assert
callees.ShouldHaveSingleItem().Target.Name.ShouldBe("Value");
}
[Theory]
[InlineData("set { Save(value); }", "Value = 1;")]
[InlineData("set { Save(value); }", "this.Value = 1;")]
[InlineData("set => Save(value);", "Value = 1;")]
[InlineData("set => Save(value);", "this.Value = 1;")]
public void FollowAssignedPropertiesAndMethodCallsInsideTheirSetters(string setter, string assignment)
{
// Arrange
var source = $$"""
class Example
{
int Value { {{setter}} }
void Save(int value) { }
void Start() { {{assignment}} }
}
""";
// Act
var callees = FindCallees(source);
// Assert
var property = callees.ShouldHaveSingleItem();
property.Target.Name.ShouldBe("Value");
property.Callees.ShouldHaveSingleItem().Target.Name.ShouldBe("Save");
}
[Theory]
[InlineData("_ = this.Value;")]
[InlineData("this.Value = 2;")]
public void IgnoreAutoPropertiesEvenWhenTheyHaveInitializers(string access)
{
// Arrange
var source = $$"""
class Example
{
int Value { get; set; } = 1;
void Start() { {{access}} }
}
""";
// Act
var callees = FindCallees(source);
// Assert
callees.ShouldBeEmpty();
}
[Theory]
[InlineData("int this[int index] => Read();", "_ = this[0];", "Read")]
[InlineData("int this[int index] => Read();", "_ = this?[0];", "Read")]
[InlineData("int this[int index] { get { return Read(); } }", "_ = this[0];", "Read")]
[InlineData("int this[int index] { get => Read(); }", "_ = this[0];", "Read")]
[InlineData("int this[int index] { set { Save(value); } }", "this[0] = 1;", "Save")]
[InlineData("int this[int index] { set => Save(value); }", "this[0] = 1;", "Save")]
public void FollowIndexersWithBodies(string declaration, string access, string method)
{
// Arrange
var source = $$"""
class Example
{
{{declaration}}
int Read() => 1;
void Save(int value) { }
void Start() { {{access}} }
}
""";
// Act
var callees = FindCallees(source);
// Assert
var indexer = callees.ShouldHaveSingleItem();
indexer.Target.ShouldBeAssignableTo<IPropertySymbol>().IsIndexer.ShouldBeTrue();
indexer.Callees.ShouldHaveSingleItem().Target.Name.ShouldBe(method);
}
[Fact]
public void IgnoreIndexersWithoutBodies()
{
// Arrange
const string source = """
abstract class Example
{
public abstract int this[int index] { get; set; }
void Start() { _ = this[0]; this[0] = 1; }
}
""";
// Act
var callees = FindCallees(source);
// Assert
callees.ShouldBeEmpty();
}
private static IImmutableList<Callee> FindCallees(string source)
{
var compilation = CSharpCompilation.Create(
"Example",
[CSharpSyntaxTree.ParseText(source)],
[MetadataReference.CreateFromFile(typeof(object).Assembly.Location)],
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
);
var start = compilation.GetTypeByMetadataName("Example")!.GetMembers("Start").Single();
return new CalleeBuilder([compilation]).FindCallees(start, CancellationToken.None);
}
}
core/tests/Cfa.Tests.csproj
+18
-0
diff --git a/core/tests/Cfa.Tests.csproj b/core/tests/Cfa.Tests.csproj
new file mode 100644
index 0000000..7aa48a4
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>Cfa.Tests</RootNamespace>
<AssemblyName>Cfa.Tests</AssemblyName>
<IsTestProject>true</IsTestProject>
<IsPackable>false</IsPackable>
<IsTrimmable>false</IsTrimmable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../src/Cfa.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Shouldly" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" PrivateAssets="all" />
</ItemGroup>
</Project>
core/tests/packages.lock.json
+287
-0
diff --git a/core/tests/packages.lock.json b/core/tests/packages.lock.json
new file mode 100644
index 0000000..3640dce
@@ -0,0 +1,287 @@
{
"version": 2,
"dependencies": {
"net11.0": {
"CSharpier.MsBuild": {
"type": "Direct",
"requested": "[1.3.0, )",
"resolved": "1.3.0",
"contentHash": "jzzLpmz7F6LyBh5FTZy8/4dEYVcAG9VjBPKryZ82LIoUuupcBkCSXzaeS/4oYcp/rBC8yVLcDstJ8CIv9n2IFw=="
},
"Microsoft.NET.Test.Sdk": {
"type": "Direct",
"requested": "[17.14.1, )",
"resolved": "17.14.1",
"contentHash": "HJKqKOE+vshXra2aEHpi2TlxYX7Z9VFYkr+E5rwEvHC8eIXiyO+K9kNm8vmNom3e2rA56WqxU+/N9NJlLGXsJQ==",
"dependencies": {
"Microsoft.CodeCoverage": "17.14.1",
"Microsoft.TestPlatform.TestHost": "17.14.1"
}
},
"Shouldly": {
"type": "Direct",
"requested": "[4.3.0, )",
"resolved": "4.3.0",
"contentHash": "sDetrWXrl6YXZ4HeLsdBoNk3uIa7K+V4uvIJ+cqdRa5DrFxeTED7VkjoxCuU1kJWpUuBDZz2QXFzSxBtVXLwRQ==",
"dependencies": {
"DiffEngine": "11.3.0",
"EmptyFiles": "4.4.0"
}
},
"xunit": {
"type": "Direct",
"requested": "[2.9.3, )",
"resolved": "2.9.3",
"contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==",
"dependencies": {
"xunit.analyzers": "1.18.0",
"xunit.assert": "2.9.3",
"xunit.core": "[2.9.3]"
}
},
"xunit.runner.visualstudio": {
"type": "Direct",
"requested": "[3.1.4, )",
"resolved": "3.1.4",
"contentHash": "5mj99LvCqrq3CNi06xYdyIAXOEh+5b33F2nErCzI5zWiDdLHXiPXEWFSUAF8zlIv0ZWqjZNCwHTQeAPYbF3pCg=="
},
"DiffEngine": {
"type": "Transitive",
"resolved": "11.3.0",
"contentHash": "k0ZgZqd09jLZQjR8FyQbSQE86Q7QZnjEzq1LPHtj1R2AoWO8sjV5x+jlSisL7NZAbUOI4y+7Bog8gkr9WIRBGw==",
"dependencies": {
"EmptyFiles": "4.4.0",
"System.Management": "6.0.1"
}
},
"EmptyFiles": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "gwJEfIGS7FhykvtZoscwXj/XwW+mJY6UbAZk+qtLKFUGWC95kfKXnj8VkxsZQnWBxJemM/q664rGLN5nf+OHZw=="
},
"Humanizer.Core": {
"type": "Transitive",
"resolved": "2.14.1",
"contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw=="
},
"Microsoft.Build.Framework": {
"type": "Transitive",
"resolved": "17.11.48",
"contentHash": "C3WIMt2wBl4++NX3jSEpTq5KXBhvAV154R4JrYHkfy9JSBcXWiL0mkgpspk5xSdOj+fS/uz7zluIy6bMM1fkkQ=="
},
"Microsoft.CodeAnalysis.Analyzers": {
"type": "Transitive",
"resolved": "5.9.0-1.26328.17",
"contentHash": "HP9NNk8ZjOSI2hgOyXnQg+kv7/X837Vr2nAlXiGAtqtYnYKjRRa1UmQFr8KFs5ynGYKqfbb8zB9APoWjiAGdMg=="
},
"Microsoft.CodeAnalysis.Common": {
"type": "Transitive",
"resolved": "5.9.0",
"contentHash": "IYaIaUWdIx539AReKZOBEqTskFusZfCh/wFSPilDvCn5Say8MegLw2LONcSIcVy+v3Gzv53qYBspgvBGSErfbQ==",
"dependencies": {
"Microsoft.CodeAnalysis.Analyzers": "5.9.0-1.26328.17"
}
},
"Microsoft.CodeAnalysis.CSharp": {
"type": "Transitive",
"resolved": "5.9.0",
"contentHash": "7JGDA0UT1+h7k9ZcA3rF4eFC8+QPq1xyYaXxag4p8r/zzPurEJxvdi7aM+MRL/SfP7XADXpWF/pl/eUYXOq/ww==",
"dependencies": {
"Microsoft.CodeAnalysis.Analyzers": "5.9.0-1.26328.17",
"Microsoft.CodeAnalysis.Common": "[5.9.0]"
}
},
"Microsoft.CodeAnalysis.Workspaces.Common": {
"type": "Transitive",
"resolved": "5.9.0",
"contentHash": "1A6jz50NG4nOEW8tX5+h+MyHqjWL0mPGwrUdwu+OlTfyknLo0GfxSqj4zEks8uVUdHdo9v8Ir9dHxACf8iYNEA==",
"dependencies": {
"Humanizer.Core": "2.14.1",
"Microsoft.CodeAnalysis.Analyzers": "5.9.0-1.26328.17",
"Microsoft.CodeAnalysis.Common": "[5.9.0]",
"System.Composition": "10.0.1"
}
},
"Microsoft.CodeCoverage": {
"type": "Transitive",
"resolved": "17.14.1",
"contentHash": "pmTrhfFIoplzFVbhVwUquT+77CbGH+h4/3mBpdmIlYtBi9nAB+kKI6dN3A/nV4DFi3wLLx/BlHIPK+MkbQ6Tpg=="
},
"Microsoft.Extensions.DependencyInjection": {
"type": "Transitive",
"resolved": "10.0.1",
"contentHash": "zerXV0GAR9LCSXoSIApbWn+Dq1/T+6vbXMHGduq1LoVQRHT0BXsGQEau0jeLUBUcsoF/NaUT8ADPu8b+eNcIyg=="
},
"Microsoft.Extensions.Logging": {
"type": "Transitive",
"resolved": "10.0.1",
"contentHash": "9ItMpMLFZFJFqCuHLLbR3LiA4ahA8dMtYuXpXl2YamSDWZhYS9BruPprkftY0tYi2bQ0slNrixdFm+4kpz1g5w==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "10.0.1"
}
},
"Microsoft.TestPlatform.ObjectModel": {
"type": "Transitive",
"resolved": "17.14.1",
"contentHash": "xTP1W6Mi6SWmuxd3a+jj9G9UoC850WGwZUps1Wah9r1ZxgXhdJfj1QqDLJkFjHDCvN42qDL2Ps5KjQYWUU0zcQ=="
},
"Microsoft.TestPlatform.TestHost": {
"type": "Transitive",
"resolved": "17.14.1",
"contentHash": "d78LPzGKkJwsJXAQwsbJJ7LE7D1wB+rAyhHHAaODF+RDSQ0NgMjDFkSA1Djw18VrxO76GlKAjRUhl+H8NL8Z+Q==",
"dependencies": {
"Microsoft.TestPlatform.ObjectModel": "17.14.1",
"Newtonsoft.Json": "13.0.3"
}
},
"Microsoft.VisualStudio.SolutionPersistence": {
"type": "Transitive",
"resolved": "1.0.52",
"contentHash": "oNv2JtYXhpdJrX63nibx1JT3uCESOBQ1LAk7Dtz/sr0+laW0KRM6eKp4CZ3MHDR2siIkKsY8MmUkeP5DKkQQ5w=="
},
"Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.3",
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
},
"System.CodeDom": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA=="
},
"System.Composition": {
"type": "Transitive",
"resolved": "10.0.1",
"contentHash": "DJkqaWQfho/ReTzKcZD3zJJ6K4GcS154k+T0UCPMBNIOZ2U/lNpyiiWZ6Etw0onWyTH1K+yhICsdmwA5xy2aPQ==",
"dependencies": {
"System.Composition.AttributedModel": "10.0.1",
"System.Composition.Convention": "10.0.1",
"System.Composition.Hosting": "10.0.1",
"System.Composition.Runtime": "10.0.1",
"System.Composition.TypedParts": "10.0.1"
}
},
"System.Composition.AttributedModel": {
"type": "Transitive",
"resolved": "10.0.1",
"contentHash": "mRxYvpCVPAeuLEk0c0kxWJVjbW1/HUoxCgYotOj9eDeQiYcTDOMdCQApsTrHYMN3pHBA8WoF00KGolG632Etaw=="
},
"System.Composition.Convention": {
"type": "Transitive",
"resolved": "10.0.1",
"contentHash": "dYynUByfVBzYDheNPGxS8UN8AvG/4tXf/coSs1odHOyoh4etv1kad/FrLWLMq4f8NO49NV20Xu+0/y613woTUA==",
"dependencies": {
"System.Composition.AttributedModel": "10.0.1"
}
},
"System.Composition.Hosting": {
"type": "Transitive",
"resolved": "10.0.1",
"contentHash": "4UGmyBdKWEN1nkqspJlji/nV7XIVm6KGlOC2So0mtM/gKvaNgLz+tUkcbY+6Zpr7dr6ohX1S5yl0RLID5otRHw==",
"dependencies": {
"System.Composition.Runtime": "10.0.1"
}
},
"System.Composition.Runtime": {
"type": "Transitive",
"resolved": "10.0.1",
"contentHash": "TVfys1gnUIhmXuYfFzyez0fOkDyELe9UwlxYeVlq6FmqmWmt1ouF0OQJ+6ozkHbkaop7uBUaXw7Qb+/o0m+nMg=="
},
"System.Composition.TypedParts": {
"type": "Transitive",
"resolved": "10.0.1",
"contentHash": "koSfjkdQZfgQr3SyiSIBboDn+GiR0vZ3x9Uek9FJbXK0w5AiATV8KrnMEP8B8OAlO+Y3zQf0CPCNzwH+VIYDKg==",
"dependencies": {
"System.Composition.AttributedModel": "10.0.1",
"System.Composition.Hosting": "10.0.1",
"System.Composition.Runtime": "10.0.1"
}
},
"System.Management": {
"type": "Transitive",
"resolved": "6.0.1",
"contentHash": "10J1D0h/lioojphfJ4Fuh5ZUThT/xOVHdV9roGBittKKNP2PMjrvibEdbVTGZcPra1399Ja3tqIJLyQrc5Wmhg==",
"dependencies": {
"System.CodeDom": "6.0.0"
}
},
"xunit.abstractions": {
"type": "Transitive",
"resolved": "2.0.3",
"contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg=="
},
"xunit.analyzers": {
"type": "Transitive",
"resolved": "1.18.0",
"contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ=="
},
"xunit.assert": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA=="
},
"xunit.core": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==",
"dependencies": {
"xunit.extensibility.core": "[2.9.3]",
"xunit.extensibility.execution": "[2.9.3]"
}
},
"xunit.extensibility.core": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==",
"dependencies": {
"xunit.abstractions": "2.0.3"
}
},
"xunit.extensibility.execution": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==",
"dependencies": {
"xunit.extensibility.core": "[2.9.3]"
}
},
"cfa": {
"type": "Project",
"dependencies": {
"Microsoft.CodeAnalysis.CSharp.Workspaces": "[5.9.0, )",
"Microsoft.CodeAnalysis.Workspaces.MSBuild": "[5.9.0, )"
}
},
"Microsoft.CodeAnalysis.CSharp.Workspaces": {
"type": "CentralTransitive",
"requested": "[5.9.0, )",
"resolved": "5.9.0",
"contentHash": "D2zqK/k16fto0yMz0hcXMTkzOxEwMDJyA1mu/KXF9Befwz4zub3MpHQD8FeRxJtVSSsC3dQFYBw7zu7r/pfO7g==",
"dependencies": {
"Humanizer.Core": "2.14.1",
"Microsoft.CodeAnalysis.Analyzers": "5.9.0-1.26328.17",
"Microsoft.CodeAnalysis.CSharp": "[5.9.0]",
"Microsoft.CodeAnalysis.Common": "[5.9.0]",
"Microsoft.CodeAnalysis.Workspaces.Common": "[5.9.0]",
"System.Composition": "10.0.1"
}
},
"Microsoft.CodeAnalysis.Workspaces.MSBuild": {
"type": "CentralTransitive",
"requested": "[5.9.0, )",
"resolved": "5.9.0",
"contentHash": "BBux6hhD4wXt4lYN2oaY+jV4PnIj5plyqohijgSZtxJQqsvsldPEdii2q9lGrJbNjKEALnldDHz1rmGKG0PeeA==",
"dependencies": {
"Humanizer.Core": "2.14.1",
"Microsoft.Build.Framework": "17.11.48",
"Microsoft.CodeAnalysis.Analyzers": "5.9.0-1.26328.17",
"Microsoft.CodeAnalysis.Workspaces.Common": "[5.9.0]",
"Microsoft.Extensions.DependencyInjection": "10.0.1",
"Microsoft.Extensions.Logging": "10.0.1",
"Microsoft.VisualStudio.SolutionPersistence": "1.0.52",
"System.Composition": "10.0.1"
}
}
}
}
}
\ No newline at end of file