Skip to content

Call type refactoring #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
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
6,146 changes: 6,146 additions & 0 deletions ApprovalTestTool/References/446736ffcda573c08f80d95b8ecf13675bd6a486.txt

Large diffs are not rendered by default.

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions CSharpCodeAnalyst/Project/ProjectData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public void SetCodeGraph(CodeGraph codeGraph)
.SelectMany(element => element.Relationships)
.Select(relationship => new SerializableRelationship(relationship.SourceId, relationship.TargetId,
relationship.Type,
(uint)relationship.Attributes,
relationship.SourceLocations))
.ToList();
}
Expand Down Expand Up @@ -78,8 +79,11 @@ public CodeGraph GetCodeGraph()
foreach (var sd in Relationships)
{
var source = codeStructure.Nodes[sd.SourceId];
var relationship = new Relationship(sd.SourceId, sd.TargetId, sd.Type);
relationship.SourceLocations = sd.SourceLocations;
var relationship = new Relationship(sd.SourceId, sd.TargetId, sd.Type)
{
Attributes = (RelationshipAttribute)sd.Attributes,
SourceLocations = sd.SourceLocations
};
source.Relationships.Add(relationship);
}

Expand Down
2 changes: 2 additions & 0 deletions CSharpCodeAnalyst/Project/SerializableRelationship.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ public class SerializableRelationship(
string sourceId,
string targetId,
RelationshipType type,
uint attributes,
List<SourceLocation> sourceLocations)
{
public uint Attributes { get; set; } = attributes;
public string SourceId { get; set; } = sourceId;
public string TargetId { get; set; } = targetId;
public RelationshipType Type { get; set; } = type;
Expand Down
133 changes: 111 additions & 22 deletions CodeParser/Parser/RelationshipAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ private void FindImplementationsForInterfaceMember(CodeElement element, ISymbol
{
// Note: Implementations for external methods are not in our map
var locations = implementingSymbol.GetSymbolLocations();
AddRelationship(implementingElement, RelationshipType.Implements, element, locations);
AddRelationship(implementingElement, RelationshipType.Implements, element, locations, RelationshipAttribute.None);
}
}
// That's ok, even interfaces are tested here
Expand Down Expand Up @@ -421,7 +421,7 @@ private void AddMethodOverrideRelationship(CodeElement sourceElement, IMethodSym
// If we don't have the method itself in our map, add a relationship to its containing type
// Maybe we override a framework method. Happens also if the base method is a generic one.
// In this case the GetSymbolKey is different. One uses T, the overriding method uses the actual type.
AddRelationshipWithFallbackToContainingType(sourceElement, methodSymbol, RelationshipType.Overrides, locations);
AddRelationshipWithFallbackToContainingType(sourceElement, methodSymbol, RelationshipType.Overrides, locations, RelationshipAttribute.None);
}

private void AnalyzeFieldRelationships(CodeElement fieldElement, IFieldSymbol fieldSymbol)
Expand Down Expand Up @@ -475,7 +475,10 @@ private void AnalyzeInvocation(CodeElement sourceElement, InvocationExpressionSy
if (symbolInfo.Symbol is IMethodSymbol calledMethod)
{
var location = invocationSyntax.GetSyntaxLocation();
AddCallsRelationship(sourceElement, calledMethod, location);

var attributes = DetermineCallAttributes(invocationSyntax, calledMethod, semanticModel);
AddCallsRelationship(sourceElement, calledMethod, location, attributes);


// Handle generic method invocations
if (calledMethod.IsGenericMethod)
Expand Down Expand Up @@ -529,12 +532,12 @@ private void AnalyzeInvocation(CodeElement sourceElement, InvocationExpressionSy
private void AddEventInvocationRelationship(CodeElement sourceElement, IEventSymbol eventSymbol,
SourceLocation location)
{
AddRelationshipWithFallbackToContainingType(sourceElement, eventSymbol, RelationshipType.Invokes, [location]);
AddRelationshipWithFallbackToContainingType(sourceElement, eventSymbol, RelationshipType.Invokes, [location], RelationshipAttribute.None);
}

private void AddEventUsageRelationship(CodeElement sourceElement, IEventSymbol eventSymbol, SourceLocation location)
{
AddRelationshipWithFallbackToContainingType(sourceElement, eventSymbol, RelationshipType.Uses, [location]);
AddRelationshipWithFallbackToContainingType(sourceElement, eventSymbol, RelationshipType.Uses, [location], RelationshipAttribute.None);
}

private void AnalyzeAssignment(CodeElement sourceElement, AssignmentExpressionSyntax assignmentExpression,
Expand Down Expand Up @@ -574,7 +577,7 @@ private void AddEventHandlerRelationship(IMethodSymbol handlerMethod, IEventSymb

if (handlerElement != null && eventElement != null)
{
AddRelationship(handlerElement, RelationshipType.Handles, eventElement, [location]);
AddRelationship(handlerElement, RelationshipType.Handles, eventElement, [location], RelationshipAttribute.None);
}
//Trace.WriteLine(
// $"Unable to add 'Handles' relationship: Handler {handlerMethod.Name} or Event {eventSymbol.Name} not found in codebase.");
Expand All @@ -595,7 +598,7 @@ private void AnalyzeExpressionForPropertyAccess(CodeElement sourceElement, Expre
}
}

private void AddCallsRelationship(CodeElement sourceElement, IMethodSymbol methodSymbol, SourceLocation location)
private void AddCallsRelationship(CodeElement sourceElement, IMethodSymbol methodSymbol, SourceLocation location, RelationshipAttribute attributes)
{
//Debug.Assert(FindCodeElement(methodSymbol)!= null);
//Trace.WriteLine($"Adding call relationship: {sourceElement.Name} -> {methodSymbol.Name}");
Expand All @@ -612,7 +615,7 @@ private void AddCallsRelationship(CodeElement sourceElement, IMethodSymbol metho
}

// If the method is not in our map, we might want to add a relationship to its containing type
AddRelationshipWithFallbackToContainingType(sourceElement, methodSymbol, RelationshipType.Calls, [location]);
AddRelationshipWithFallbackToContainingType(sourceElement, methodSymbol, RelationshipType.Calls, [location], attributes);
}

/// <summary>
Expand Down Expand Up @@ -667,16 +670,17 @@ private void AddTypeRelationship(CodeElement sourceElement, ITypeSymbol typeSymb
var symbolKey = typeSymbol.Key();
if (_artifacts!.SymbolKeyToElementMap.TryGetValue(symbolKey, out var targetElement))
{
AddRelationship(sourceElement, relationshipType, targetElement, location != null ? [location] : []);
AddRelationship(sourceElement, relationshipType, targetElement, location != null ? [location] : [], RelationshipAttribute.None);
}

break;
}
}


private void AddRelationship(CodeElement source, RelationshipType type,
CodeElement target,
List<SourceLocation> sourceLocations)
List<SourceLocation> sourceLocations, RelationshipAttribute attributes)
{
lock (_lock)
{
Expand All @@ -689,11 +693,16 @@ private void AddRelationship(CodeElement source, RelationshipType type,
// For example identifier and member access of field.
var newLocations = sourceLocations.Except(existingRelationship.SourceLocations);
existingRelationship.SourceLocations.AddRange(newLocations);


// We may get different attributes from different calls.
existingRelationship.Attributes |= attributes;
}
else
{
var newRelationship = new Relationship(source.Id, target.Id, type);
newRelationship.SourceLocations.AddRange(sourceLocations);
newRelationship.Attributes = attributes;
source.Relationships.Add(newRelationship);
}
}
Expand All @@ -707,7 +716,7 @@ private void AddNamedTypeRelationship(CodeElement sourceElement, INamedTypeSymbo
if (targetElement != null)
{
// The type is internal (part of our codebase)
AddRelationship(sourceElement, relationshipType, targetElement, location != null ? [location] : []);
AddRelationship(sourceElement, relationshipType, targetElement, location != null ? [location] : [], RelationshipAttribute.None);
}
else
{
Expand All @@ -721,7 +730,7 @@ private void AddNamedTypeRelationship(CodeElement sourceElement, INamedTypeSymbo
{
// We found the original definition, add relationship to it
AddRelationship(sourceElement, relationshipType, originalTargetElement,
location != null ? [location] : []);
location != null ? [location] : [], RelationshipAttribute.None);
}
// The type is truly external, you might want to log this or handle it differently
// AddExternalRelationship(sourceElement, namedTypeSymbol, relationshipType, location);
Expand All @@ -746,11 +755,11 @@ private void AnalyzeIdentifier(CodeElement sourceElement, IdentifierNameSyntax i
if (symbol is IPropertySymbol propertySymbol)
{
var location = identifierSyntax.GetSyntaxLocation();
AddPropertyCallRelationship(sourceElement, propertySymbol, [location]);
AddPropertyCallRelationship(sourceElement, propertySymbol, [location], RelationshipAttribute.None);
}
else if (symbol is IFieldSymbol fieldSymbol)
{
AddRelationshipWithFallbackToContainingType(sourceElement, fieldSymbol, RelationshipType.Uses);
AddRelationshipWithFallbackToContainingType(sourceElement, fieldSymbol, RelationshipType.Uses, [], RelationshipAttribute.None);
}
}

Expand All @@ -766,11 +775,11 @@ private void AnalyzeMemberAccess(CodeElement sourceElement, MemberAccessExpressi
if (symbol is IPropertySymbol propertySymbol)
{
var location = memberAccessSyntax.GetSyntaxLocation();
AddPropertyCallRelationship(sourceElement, propertySymbol, [location]);
AddPropertyCallRelationship(sourceElement, propertySymbol, [location], RelationshipAttribute.None);
}
else if (symbol is IFieldSymbol fieldSymbol)
{
AddRelationshipWithFallbackToContainingType(sourceElement, fieldSymbol, RelationshipType.Uses);
AddRelationshipWithFallbackToContainingType(sourceElement, fieldSymbol, RelationshipType.Uses, [], RelationshipAttribute.None);
}
else if (symbol is IEventSymbol eventSymbol)
{
Expand All @@ -789,13 +798,13 @@ private void AnalyzeMemberAccess(CodeElement sourceElement, MemberAccessExpressi
/// Calling a property is treated like calling a method.
/// </summary>
private void AddPropertyCallRelationship(CodeElement sourceElement, IPropertySymbol propertySymbol,
List<SourceLocation> locations)
List<SourceLocation> locations, RelationshipAttribute attributes)
{
AddRelationshipWithFallbackToContainingType(sourceElement, propertySymbol, RelationshipType.Calls, locations);
AddRelationshipWithFallbackToContainingType(sourceElement, propertySymbol, RelationshipType.Calls, locations, attributes);
}

private void AddRelationshipWithFallbackToContainingType(CodeElement sourceElement, ISymbol targetSymbol,
RelationshipType relationshipType, List<SourceLocation>? locations = null)
RelationshipType relationshipType, List<SourceLocation>? locations, RelationshipAttribute attributes)
{
// If we don't have the property itself in our map, add a relationship to its containing type
if (locations == null)
Expand All @@ -806,14 +815,14 @@ private void AddRelationshipWithFallbackToContainingType(CodeElement sourceEleme
var targetElement = FindCodeElement(targetSymbol);
if (targetElement != null)
{
AddRelationship(sourceElement, relationshipType, targetElement, locations);
AddRelationship(sourceElement, relationshipType, targetElement, locations, attributes);
return;
}

var containingTypeElement = FindCodeElement(targetSymbol.ContainingType);
if (containingTypeElement != null)
{
AddRelationship(sourceElement, relationshipType, containingTypeElement, locations);
AddRelationship(sourceElement, relationshipType, containingTypeElement, locations, attributes);
}
}

Expand Down Expand Up @@ -897,6 +906,86 @@ private void AnalyzePropertyBody(Solution solution, CodeElement propertyElement,
private void AddPropertyRelationship(CodeElement sourceElement, IPropertySymbol propertySymbol,
RelationshipType relationshipType, List<SourceLocation> locations)
{
AddRelationshipWithFallbackToContainingType(sourceElement, propertySymbol, relationshipType, locations);
AddRelationshipWithFallbackToContainingType(sourceElement, propertySymbol, relationshipType, locations, RelationshipAttribute.None);
}




private RelationshipAttribute DetermineCallAttributes(InvocationExpressionSyntax invocation,
IMethodSymbol method, SemanticModel semanticModel)
{
if (method.IsExtensionMethod)
{
return RelationshipAttribute.IsExtensionMethodCall;
}

switch (invocation.Expression)
{
case MemberAccessExpressionSyntax memberAccess:
return AnalyzeMemberAccessCallType(memberAccess, method, semanticModel);

case IdentifierNameSyntax identifier:
// Direct method call - could be this.Method() or static
return method.IsStatic ? RelationshipAttribute.IsStaticCall : RelationshipAttribute.None;

case MemberBindingExpressionSyntax:
// Conditional access: obj?.Method()
return RelationshipAttribute.IsInstanceCall;

default:
// Fallback for complex expressions
return RelationshipAttribute.None;
}
}

private RelationshipAttribute AnalyzeMemberAccessCallType(MemberAccessExpressionSyntax memberAccess,
IMethodSymbol method, SemanticModel semanticModel)
{
switch (memberAccess.Expression)
{
case BaseExpressionSyntax:
// base.Method()
return RelationshipAttribute.IsBaseCall;

case ThisExpressionSyntax:
// this.Method()
return RelationshipAttribute.IsThisCall;

case IdentifierNameSyntax identifier:
var symbolInfo = semanticModel.GetSymbolInfo(identifier);
if (symbolInfo.Symbol is INamedTypeSymbol)
{
// Type.StaticMethod()
return RelationshipAttribute.IsStaticCall;
}
else if (symbolInfo.Symbol is IFieldSymbol || symbolInfo.Symbol is IPropertySymbol)
{
// field.Method() or property.Method()
return RelationshipAttribute.IsInstanceCall;
}
else
{
// Local variable or parameter
return RelationshipAttribute.IsInstanceCall;
}

case MemberAccessExpressionSyntax:
// Chained calls: obj.Property.Method()
return RelationshipAttribute.IsInstanceCall;

case InvocationExpressionSyntax:
// Method call result: GetObject().Method()
return RelationshipAttribute.IsInstanceCall;

case ObjectCreationExpressionSyntax:
// new Object().Method()
return RelationshipAttribute.IsInstanceCall;

default:
// Complex expression - default to instance call
return RelationshipAttribute.IsInstanceCall;
}
}

}
7 changes: 5 additions & 2 deletions Contracts/Graph/CodeGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,14 @@ public List<CodeElement> GetRoots()

public string ToDebug()
{
var relationships = GetAllRelationships().Select(d => (Nodes[d.SourceId].FullName, d.Type.ToString(), Nodes[d.TargetId].FullName));

var relationships = GetAllRelationships().Select(d => (Nodes[d.SourceId].FullName, d.Type.ToString(), Nodes[d.TargetId].FullName, d.Attributes.FormatAttributes()));

var elementNames = Nodes.Values.Select(e => $"{e.ElementType}: {e.FullName}");
var relationshipNames = relationships.Select(d => $"{d.Item1} -({d.Item2})-> {d.Item3}");
var relationshipNames = relationships.Select(d => $"{d.Item1} -({d.Item2})-> {d.Item3} {d.Item4}");
return string.Join("\n", elementNames.OrderBy(x => x)) + "\n" +
string.Join("\n", relationshipNames.OrderBy(x => x));
}


}
21 changes: 21 additions & 0 deletions Contracts/Graph/Relationship.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,30 @@ namespace Contracts.Graph;
[DebuggerDisplay("{Type}")]
public class Relationship(string sourceId, string targetId, RelationshipType type)
{
public RelationshipAttribute Attributes { get; set; } = RelationshipAttribute.None;
public string SourceId { get; } = sourceId;
public string TargetId { get; } = targetId;
public RelationshipType Type { get; } = type;

public List<SourceLocation> SourceLocations { get; set; } = [];

public bool GetAttribute(RelationshipAttribute attribute)
{
return Attributes.HasFlag(attribute);
}

public void SetAttribute(RelationshipAttribute attribute, bool value = true)
{
if (value)
{
Attributes |= attribute;
}
else
{
Attributes &= ~attribute;
}
}

public override bool Equals(object? obj)
{
if (obj is not Relationship other)
Expand All @@ -34,6 +52,9 @@ public Relationship Clone()
{
var newRelationship = new Relationship(SourceId, TargetId, Type);
newRelationship.SourceLocations.AddRange(SourceLocations);
newRelationship.Attributes = Attributes;
return newRelationship;
}


}
Loading