Skip to content
Merged
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
56 changes: 54 additions & 2 deletions src/Neo.SmartContract.Analyzer/CollectionTypesUsageAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using System.Collections.Immutable;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand Down Expand Up @@ -132,10 +133,61 @@ private void ReportIfUnsupportedCollectionType(SyntaxNodeAnalysisContext context

private string? GetUnsupportedCollectionType(ITypeSymbol? type)
{
return GetUnsupportedCollectionType(
type,
new HashSet<ITypeSymbol>(SymbolEqualityComparer.Default));
}

private string? GetUnsupportedCollectionType(
ITypeSymbol? type,
HashSet<ITypeSymbol> visitedTypes)
{
if (type is null || !visitedTypes.Add(type)) return null;

if (type is IArrayTypeSymbol arrayType)
return GetUnsupportedCollectionType(arrayType.ElementType, visitedTypes);

if (type is IPointerTypeSymbol pointerType)
return GetUnsupportedCollectionType(pointerType.PointedAtType, visitedTypes);

if (type is IFunctionPointerTypeSymbol functionPointerType)
{
var returnType = GetUnsupportedCollectionType(
functionPointerType.Signature.ReturnType,
visitedTypes);
if (returnType is not null) return returnType;

foreach (var parameter in functionPointerType.Signature.Parameters)
{
var parameterType = GetUnsupportedCollectionType(parameter.Type, visitedTypes);
if (parameterType is not null) return parameterType;
}

return null;
}

if (type is not INamedTypeSymbol namedType) return null;

var originalType = namedType.OriginalDefinition.ToString() ?? throw new ArgumentNullException("originalType is null");
return _unsupportedCollectionTypes.Contains(originalType) ? originalType : null;
var originalType = namedType.OriginalDefinition.ToString() ??
throw new ArgumentNullException(nameof(type));
if (_unsupportedCollectionTypes.Contains(originalType))
return originalType;

if (namedType.ContainingType is not null)
{
var containingType = GetUnsupportedCollectionType(
namedType.ContainingType,
visitedTypes);
if (containingType is not null) return containingType;
}

foreach (var typeArgument in namedType.TypeArguments)
{
var unsupportedType = GetUnsupportedCollectionType(typeArgument, visitedTypes);
if (unsupportedType is not null) return unsupportedType;
}

return null;
}

private static string GetSuggestedType(string originalType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,42 @@ class TestClass
await VerifyCS.VerifyAnalyzerAsync(test, returnDiagnostic, parameterDiagnostic, propertyDiagnostic);
}

[TestMethod]
public async Task NestedUnsupportedCollectionTypes_ShouldReportDiagnostics()
{
var test = TestNamespace + """

class TestClass
{
public {|#0:System.Tuple<int, System.Collections.Generic.List<string>>|} GetItems() => null;
public void SetItems({|#1:System.Collections.Generic.Stack<int>[]|} values) { }
}
""";

var returnDiagnostic = VerifyCS.Diagnostic(CollectionTypesUsageAnalyzer.DiagnosticId)
.WithLocation(0)
.WithArguments("System.Collections.Generic.List<T>", "List<T>");
var parameterDiagnostic = VerifyCS.Diagnostic(CollectionTypesUsageAnalyzer.DiagnosticId)
.WithLocation(1)
.WithArguments("System.Collections.Generic.Stack<T>", "List<T>");

await VerifyCS.VerifyAnalyzerAsync(test, returnDiagnostic, parameterDiagnostic);
}

[TestMethod]
public async Task NestedSupportedCollectionTypes_ShouldNotReportDiagnostics()
{
var test = TestNamespace + """

class TestClass
{
public System.Tuple<int, List<string>> GetItems() => null;
}
""";

await VerifyCS.VerifyAnalyzerAsync(test);
}

[TestMethod]
public void CollectionDiagnostic_ShouldNotOfferAutomaticCodeFixes()
{
Expand Down
Loading