Skip to content

Commit 83207e6

Browse files
authored
Handle attributes (#1)
Handling for attributes Note: method parameter attributes are not supported
1 parent ca0f32b commit 83207e6

38 files changed

+211
-82
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,3 +368,4 @@ FodyWeavers.xsd
368368
/Documentation/.$CSharpAnalyst.drawio.bkp
369369
*.dtmp
370370
/tmp
371+
/SampleProject/project.json

CSharpCodeAnalyst/GraphArea/MsaglBuilder.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,11 @@ private static string GetLabelText(Dependency dependency)
274274
return string.Empty;
275275
}
276276

277+
if (dependency.Type == DependencyType.UsesAttribute)
278+
{
279+
return string.Empty;
280+
}
281+
277282
return dependency.Type.ToString();
278283
}
279284

CSharpCodeAnalyst/MainViewModel.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ internal class MainViewModel : INotifyPropertyChanged
3535
private readonly MessageBus _messaging;
3636

3737
private readonly ProjectExclusionRegExCollection _projectExclusionFilters;
38-
private readonly int _warningCodeElementLimit;
3938
private CodeGraph? _codeGraph;
4039
private CycleSummaryViewModel? _cycleSummaryViewModel;
4140

@@ -67,7 +66,6 @@ internal MainViewModel(MessageBus messaging, ApplicationSettings? settings)
6766
{
6867
_isInfoPanelVisible = settings.DefaultShowQuickHelp;
6968
_projectExclusionFilters.Initialize(settings.DefaultProjectExcludeFilter, ";");
70-
_warningCodeElementLimit = settings.WarningCodeElementLimit;
7169
}
7270

7371
_messaging = messaging;
@@ -614,7 +612,7 @@ private void LoadProject()
614612
throw new NullReferenceException();
615613
}
616614

617-
var codeGraph = projectData.CreateCodeStructure();
615+
var codeGraph = projectData.CreateCodeGraph();
618616

619617

620618
// Load settings
@@ -666,7 +664,7 @@ private void SaveProject()
666664
}
667665

668666
var projectData = new ProjectData();
669-
projectData.AddCodeStructure(_codeGraph);
667+
projectData.AddCodeGraph(_codeGraph);
670668
projectData.Settings[nameof(IsInfoPanelVisible)] = IsInfoPanelVisible.ToString();
671669
projectData.Settings[nameof(GraphViewModel.ShowFlatGraph)] = _graphViewModel.ShowFlatGraph.ToString();
672670
projectData.Settings[nameof(ProjectExclusionRegExCollection)] = _projectExclusionFilters.ToString();
@@ -713,7 +711,7 @@ internal bool OnClosing()
713711
{
714712
if (_isSaved is false)
715713
{
716-
if (MessageBox.Show("Do you wan't to save the project so you don't have to import it again?", "Save",
714+
if (MessageBox.Show("Do you want to save the project so you don't have to import it again?", "Save",
717715
MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
718716
{
719717
SaveProject();

CSharpCodeAnalyst/Project/ProjectData.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ public class ProjectData
1515
/// <summary>
1616
/// Flatten the recursive structures.
1717
/// </summary>
18-
public void AddCodeStructure(CodeGraph codeGraph)
18+
public void AddCodeGraph(CodeGraph codeGraph)
1919
{
2020
CodeElements = codeGraph.Nodes.Values
21-
.Select(n => new SerializableCodeElement(n.Id, n.Name, n.FullName, n.ElementType, n.SourceLocations)
21+
.Select(n => new SerializableCodeElement(n.Id, n.Name, n.FullName, n.ElementType, n.SourceLocations, n.Attributes)
2222
{ SourceLocations = n.SourceLocations }).ToList();
2323

2424
// We iterate over children, so we expect to have a parent
@@ -33,7 +33,7 @@ public void AddCodeStructure(CodeGraph codeGraph)
3333
.ToList();
3434
}
3535

36-
public CodeGraph CreateCodeStructure()
36+
public CodeGraph CreateCodeGraph()
3737
{
3838
var codeStructure = new CodeGraph();
3939

@@ -42,6 +42,7 @@ public CodeGraph CreateCodeStructure()
4242
{
4343
var element = new CodeElement(se.Id, se.ElementType, se.Name, se.FullName, null!);
4444
element.SourceLocations = se.SourceLocations;
45+
element.Attributes = se.Attributes;
4546
codeStructure.Nodes.Add(element.Id, element);
4647
}
4748

CSharpCodeAnalyst/Project/SerializableCodeElement.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ public class SerializableCodeElement(
88
string name,
99
string fullName,
1010
CodeElementType elementType,
11-
List<SourceLocation> sourceLocations)
11+
List<SourceLocation> sourceLocations,
12+
HashSet<string> attributes)
1213
{
1314
public string Id { get; set; } = id;
1415
public string Name { get; set; } = name;
1516
public string FullName { get; set; } = fullName;
1617
public CodeElementType ElementType { get; set; } = elementType;
1718
public List<SourceLocation> SourceLocations { get; set; } = sourceLocations;
19+
public HashSet<string> Attributes { get; set; } = attributes;
1820
}

CSharpCodeAnalyst/board.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
IMPROVEMENTS
22
---------------------
33

4-
- Recognize Attributes
5-
6-
- Do not collapse single node in cycle graph
74
- Highlight of aggregated edges takes too long. If quick help not visible skip?
85
- Performance in general
96

107

8+
- Attributes are caught at class or method level. Not for the parameters like [CallerMemberName]
9+
10+
1111
BUGS
1212
---------------------
1313

CodeParser/Export/DgmlExport.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public void Export(string fileName, CodeGraph graph)
4040
foreach (var edge in normal)
4141
{
4242
// Omit the calls label for better readability.
43-
var edgeLabel = edge.Type == DependencyType.Calls ? string.Empty : edge.Type.ToString();
43+
var edgeLabel = GetEdgeLabel(edge);
4444
builder.AddEdgeById(edge.SourceId, edge.TargetId, edgeLabel);
4545
}
4646

@@ -60,6 +60,33 @@ public void Export(string fileName, CodeGraph graph)
6060
builder.WriteOutput(fileName);
6161
}
6262

63+
private static string GetEdgeLabel(Dependency dependency)
64+
{
65+
// Omit the label text for now. The color makes it clear that it is a call dependency
66+
if (dependency.Type == DependencyType.Calls)
67+
{
68+
return string.Empty;
69+
}
70+
71+
// We can see this by the dotted line
72+
if (dependency.Type == DependencyType.Implements || dependency.Type == DependencyType.Inherits)
73+
{
74+
return string.Empty;
75+
}
76+
77+
if (dependency.Type == DependencyType.Uses)
78+
{
79+
return string.Empty;
80+
}
81+
82+
if (dependency.Type == DependencyType.UsesAttribute)
83+
{
84+
return string.Empty;
85+
}
86+
87+
return dependency.Type.ToString();
88+
}
89+
6390
private static void WriteCategories(DgmlFileBuilder writer)
6491
{
6592
var elementTypes = Enum.GetValues(typeof(CodeElementType)).Cast<CodeElementType>();

CodeParser/Parser/Parser.Phase1.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace CodeParser.Parser;
77

88
public partial class Parser
99
{
10-
private readonly List<INamedTypeSymbol> AllNamedTypesInSolution = new();
10+
private readonly List<INamedTypeSymbol> _allNamedTypesInSolution = new();
1111

1212
private async Task BuildHierarchy(Solution solution)
1313
{
@@ -28,7 +28,7 @@ private async Task BuildHierarchy(Solution solution)
2828
// Build also a list of all named types in the solution
2929
// We need this in phase 2 to resolve dependencies
3030
var types = compilation.GetSymbolsWithName(_ => true, SymbolFilter.Type).OfType<INamedTypeSymbol>();
31-
AllNamedTypesInSolution.AddRange(types);
31+
_allNamedTypesInSolution.AddRange(types);
3232

3333

3434
BuildHierarchy(compilation);
@@ -145,8 +145,6 @@ private void ProcessNodeForHierarchy(SyntaxNode node, SemanticModel semanticMode
145145
case EventDeclarationSyntax:
146146
symbol = semanticModel.GetDeclaredSymbol(node) as IEventSymbol;
147147
elementType = CodeElementType.Event;
148-
149-
150148
break;
151149
// Add more cases as needed (e.g., for events, delegates, etc.)
152150
}
@@ -161,6 +159,7 @@ private void ProcessNodeForHierarchy(SyntaxNode node, SemanticModel semanticMode
161159
}
162160
else
163161
{
162+
// The parent gets the indirect children assigned as children
164163
foreach (var childNode in node.ChildNodes())
165164
{
166165
ProcessNodeForHierarchy(childNode, semanticModel, parent);

CodeParser/Parser/Parser.Phase2.cs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ private void AnalyzeDependencies(Solution solution)
4444
AnalyzeFieldDependencies(element, fieldSymbol);
4545
}
4646

47+
// For all type of symbols check if decorated with an attribute.
48+
AnalyzeAttributeDependencies(element, symbol);
49+
4750
if (loop % 10 == 0)
4851
{
4952
ParserProgress?.Invoke(this, new ParserProgressArg
@@ -56,6 +59,22 @@ private void AnalyzeDependencies(Solution solution)
5659
}
5760
}
5861

62+
private void AnalyzeAttributeDependencies(CodeElement element, ISymbol symbol)
63+
{
64+
foreach (var attributeData in symbol.GetAttributes())
65+
{
66+
if (attributeData.AttributeClass != null)
67+
{
68+
var location = attributeData.ApplicationSyntaxReference != null
69+
? GetLocation(attributeData.ApplicationSyntaxReference.GetSyntax())
70+
: null;
71+
72+
element.Attributes.Add(attributeData.AttributeClass.Name);
73+
AddTypeDependency(element, attributeData.AttributeClass, DependencyType.UsesAttribute, location);
74+
}
75+
}
76+
}
77+
5978
private void AnalyzeDelegateDependencies(CodeElement delegateElement, INamedTypeSymbol delegateSymbol)
6079
{
6180
var methodSymbol = delegateSymbol.DelegateInvokeMethod;
@@ -119,7 +138,7 @@ private void AnalyzeMethodDependencies(Solution solution, CodeElement methodElem
119138
// If this method is an interface method or an abstract method, find its implementations
120139
if (methodSymbol.IsAbstract || methodSymbol.ContainingType.TypeKind == TypeKind.Interface)
121140
{
122-
FindImplementations(solution, methodElement, methodSymbol);
141+
FindImplementations(methodElement, methodSymbol);
123142
}
124143

125144
// Check for method override
@@ -150,7 +169,7 @@ private void AnalyzeMethodDependencies(Solution solution, CodeElement methodElem
150169
}
151170

152171

153-
private void FindImplementations(Solution solution, CodeElement methodElement, IMethodSymbol methodSymbol)
172+
private void FindImplementations(CodeElement methodElement, IMethodSymbol methodSymbol)
154173
{
155174
var implementingTypes = new HashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default);
156175

@@ -183,13 +202,13 @@ private void FindImplementations(Solution solution, CodeElement methodElement, I
183202

184203
private IEnumerable<INamedTypeSymbol> FindTypesImplementingInterface(INamedTypeSymbol interfaceSymbol)
185204
{
186-
return AllNamedTypesInSolution
205+
return _allNamedTypesInSolution
187206
.Where(type => type.AllInterfaces.Any(i => SymbolEqualityComparer.Default.Equals(i, interfaceSymbol)));
188207
}
189208

190209
private IEnumerable<INamedTypeSymbol> FindTypesDerivedFrom(INamedTypeSymbol baseType)
191210
{
192-
return AllNamedTypesInSolution
211+
return _allNamedTypesInSolution
193212
.Where(type => IsTypeDerivedFrom(type, baseType));
194213
}
195214

CodeParserTests/CodeParserApprovalTests.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ public void FindsAllFunctions()
7777
"CSharpLanguage.CSharpLanguage.MissingInterface.IStorage.Load",
7878
"CSharpLanguage.CSharpLanguage.NS_Parent.NS_Child.ClassNsChild.Method",
7979

80+
"CSharpLanguage.CSharpLanguage.IStructInterface.Method",
81+
"CSharpLanguage.CSharpLanguage.StructWithInterface.Method",
82+
8083
// Extension method
8184
"CSharpLanguage.CSharpLanguage.Extensions.Slice",
8285

@@ -177,7 +180,8 @@ public void FindsAllMethodImplementations()
177180
var expected = new HashSet<string>
178181
{
179182
"ModuleLevel1.ModuleLevel1.ServiceBase.Do -> ModuleLevel1.ModuleLevel1.IServiceC.Do",
180-
"CSharpLanguage.CSharpLanguage.MissingInterface.BaseStorage.Load -> CSharpLanguage.CSharpLanguage.MissingInterface.IStorage.Load"
183+
"CSharpLanguage.CSharpLanguage.MissingInterface.BaseStorage.Load -> CSharpLanguage.CSharpLanguage.MissingInterface.IStorage.Load",
184+
"CSharpLanguage.CSharpLanguage.StructWithInterface.Method -> CSharpLanguage.CSharpLanguage.IStructInterface.Method"
181185
};
182186

183187

0 commit comments

Comments
 (0)