-
Notifications
You must be signed in to change notification settings - Fork 311
Expand file tree
/
Copy pathIronPythonDiagnosticAnalyzer.cs
More file actions
142 lines (120 loc) · 11.5 KB
/
IronPythonDiagnosticAnalyzer.cs
File metadata and controls
142 lines (120 loc) · 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
namespace IronPython.Analyzer {
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class IronPythonDiagnosticAnalyzer : DiagnosticAnalyzer {
public const string DiagnosticId = "IronPythonAnalyzer";
#pragma warning disable RS2008 // Enable analyzer release tracking
private static readonly DiagnosticDescriptor Rule1 = new DiagnosticDescriptor("IPY01", title: "Parameter which is marked not nullable does not have the NotNoneAttribute", messageFormat: "Parameter '{0}' does not have the NotNoneAttribute", category: "Usage", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: "Non-nullable reference type parameters should have the NotNoneAttribute.");
private static readonly DiagnosticDescriptor Rule2 = new DiagnosticDescriptor("IPY02", title: "Parameter which is marked nullable has the NotNoneAttribute", messageFormat: "Parameter '{0}' should not have the NotNoneAttribute", category: "Usage", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: "Nullable reference type parameters should not have the NotNoneAttribute.");
private static readonly DiagnosticDescriptor Rule3 = new DiagnosticDescriptor("IPY03", title: "BytesLikeAttribute used on a not supported type", messageFormat: "Parameter '{0}' declared bytes-like on unsupported type '{1}'", category: "Usage", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: "BytesLikeAttribute is only allowed on parameters of type IReadOnlyList<byte>, or IList<byte>.");
private static readonly DiagnosticDescriptor Rule4 = new DiagnosticDescriptor("IPY04", title: "Call to PythonTypeOps.GetName", messageFormat: "Direct call to PythonTypeOps.GetName", category: "Usage", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: "To obtain a name of a python type of a given object to display to a user, use PythonOps.GetPythonTypeName.");
private static readonly DiagnosticDescriptor Rule5 = new DiagnosticDescriptor("IPY05", title: "DLR NotNullAttribute accessed without an alias", messageFormat: "Microsoft.Scripting.Runtime.NotNullAttribute should be accessed though alias 'NotNone'", category: "Usage", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: "NotNullAttribute is ambiguous between 'System.Diagnostics.CodeAnalysis.NotNullAttribute' and 'Microsoft.Scripting.Runtime.NotNullAttribute'. The latter should be accesses as 'NotNoneAttribute'.");
private static readonly DiagnosticDescriptor Rule6 = new DiagnosticDescriptor("IPY06", title: "Unnecessary NotNoneAttribute", messageFormat: "Parameter '{0}' has an unnecessary NotNoneAttribute", category: "Usage", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: "ParamDictionary do not require a NotNoneAttribute.");
private static readonly DiagnosticDescriptor Rule7 = new DiagnosticDescriptor("IPY07", title: "Parameters with params does not have the NotNoneAttribute", messageFormat: "Parameter '{0}' does not have the NotNoneAttribute", category: "Usage", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: "Parameters with params should use the NotNoneAttribute to prevent binding to null.");
#pragma warning restore RS2008 // Enable analyzer release tracking
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule1, Rule2, Rule3, Rule4, Rule5, Rule6, Rule7); } }
public override void Initialize(AnalysisContext context) {
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Method);
context.RegisterOperationAction(AnalyzeInvocation, OperationKind.Invocation);
}
private static void AnalyzeSymbol(SymbolAnalysisContext context) {
var methodSymbol = (IMethodSymbol)context.Symbol;
if (methodSymbol.DeclaredAccessibility != Accessibility.Public) return;
var pythonTypeAttributeSymbol = context.Compilation.GetTypeByMetadataName("IronPython.Runtime.PythonTypeAttribute");
var pythonModuleAttributeSymbol = context.Compilation.GetTypeByMetadataName("IronPython.Runtime.PythonModuleAttribute");
if (methodSymbol.ContainingType.GetAttributes()
.Any(x => x.AttributeClass.Equals(pythonTypeAttributeSymbol, SymbolEqualityComparer.Default)) ||
methodSymbol.ContainingAssembly.GetAttributes()
.Where(x => x.AttributeClass.Equals(pythonModuleAttributeSymbol, SymbolEqualityComparer.Default))
.Select(x => (INamedTypeSymbol)x.ConstructorArguments[1].Value)
.Any(x => x.Equals(methodSymbol.ContainingType, SymbolEqualityComparer.Default))) {
var pythonHiddenAttributeSymbol = context.Compilation.GetTypeByMetadataName("IronPython.Runtime.PythonHiddenAttribute");
if (methodSymbol.GetAttributes().Any(x => x.AttributeClass.Equals(pythonHiddenAttributeSymbol, SymbolEqualityComparer.Default))) return;
var codeContextSymbol = context.Compilation.GetTypeByMetadataName("IronPython.Runtime.CodeContext");
var siteLocalStorageSymbol = context.Compilation.GetTypeByMetadataName("IronPython.Runtime.SiteLocalStorage");
var notNoneAttributeSymbol = context.Compilation.GetTypeByMetadataName("Microsoft.Scripting.Runtime.NotNullAttribute");
var bytesLikeAttributeSymbol = context.Compilation.GetTypeByMetadataName("IronPython.Runtime.BytesLikeAttribute");
var byteType = context.Compilation.GetTypeByMetadataName("System.Byte");
var ireadOnlyListType = context.Compilation.GetTypeByMetadataName("System.Collections.Generic.IReadOnlyList`1");
var ilistType = context.Compilation.GetTypeByMetadataName("System.Collections.Generic.IList`1");
var ireadOnlyListOfByteType = ireadOnlyListType.Construct(byteType);
var ilistOfByteType = ilistType.Construct(byteType);
var paramDictionaryAttributeSymbol = context.Compilation.GetTypeByMetadataName("Microsoft.Scripting.ParamDictionaryAttribute");
var paramArrayAttributeSymbol = context.Compilation.GetTypeByMetadataName("System.ParamArrayAttribute");
// PerformModuleReload is special and we don't need NotNone annotations
if (methodSymbol.Name == "PerformModuleReload") return;
foreach (IParameterSymbol parameterSymbol in methodSymbol.Parameters) {
var attributes = parameterSymbol.GetAttributes();
if (attributes.Any(x => x.AttributeClass.Equals(bytesLikeAttributeSymbol, SymbolEqualityComparer.Default))
&& !parameterSymbol.Type.Equals(ireadOnlyListOfByteType, SymbolEqualityComparer.Default)
&& !parameterSymbol.Type.Equals(ilistOfByteType, SymbolEqualityComparer.Default)) {
var diagnostic = Diagnostic.Create(Rule3, parameterSymbol.Locations[0], parameterSymbol.Name, parameterSymbol.Type.MetadataName);
context.ReportDiagnostic(diagnostic);
continue;
}
if (parameterSymbol.IsParams && attributes.All(x => !x.AttributeClass.Equals(notNoneAttributeSymbol, SymbolEqualityComparer.Default))) {
var diagnostic = Diagnostic.Create(Rule7, parameterSymbol.Locations[0], parameterSymbol.Name);
context.ReportDiagnostic(diagnostic);
}
if (attributes.Any(x => x.AttributeClass.Equals(paramDictionaryAttributeSymbol, SymbolEqualityComparer.Default))) {
if (attributes.Any(x => x.AttributeClass.Equals(notNoneAttributeSymbol, SymbolEqualityComparer.Default))) {
var diagnostic = Diagnostic.Create(Rule6, parameterSymbol.Locations[0], parameterSymbol.Name);
context.ReportDiagnostic(diagnostic);
}
continue;
}
if (attributes.FirstOrDefault(x => x.AttributeClass.Equals(notNoneAttributeSymbol, SymbolEqualityComparer.Default)) is AttributeData attr) {
SyntaxNode node = attr.ApplicationSyntaxReference.GetSyntax(); // Async?
if (node.GetLastToken().Text != "NotNone") {
var diagnostic = Diagnostic.Create(Rule5, node.GetLocation());
context.ReportDiagnostic(diagnostic);
}
}
if (parameterSymbol.Type.IsValueType) continue;
if (parameterSymbol.Type.Equals(codeContextSymbol, SymbolEqualityComparer.Default)) continue;
if (SymbolEqualityComparer.Default.Equals(parameterSymbol.Type.BaseType, siteLocalStorageSymbol)) continue;
if (parameterSymbol.NullableAnnotation == NullableAnnotation.NotAnnotated) {
if (!attributes.Any(x => x.AttributeClass.Equals(notNoneAttributeSymbol, SymbolEqualityComparer.Default))
&& !attributes.Any(x => IsAllowNull(x.AttributeClass))) {
var diagnostic = Diagnostic.Create(Rule1, parameterSymbol.Locations[0], parameterSymbol.Name);
context.ReportDiagnostic(diagnostic);
}
} else if (parameterSymbol.NullableAnnotation == NullableAnnotation.Annotated) {
if (attributes.Any(x => x.AttributeClass.Equals(notNoneAttributeSymbol, SymbolEqualityComparer.Default))
&& !attributes.Any(x => IsDisallowNull(x.AttributeClass))) {
var diagnostic = Diagnostic.Create(Rule2, parameterSymbol.Locations[0], parameterSymbol.Name);
context.ReportDiagnostic(diagnostic);
}
}
}
bool IsAllowNull(INamedTypeSymbol symbol) {
return symbol.ToString() == "System.Diagnostics.CodeAnalysis.AllowNullAttribute";
}
bool IsDisallowNull(INamedTypeSymbol symbol) {
return symbol.ToString() == "System.Diagnostics.CodeAnalysis.DisallowNullAttribute";
}
}
}
private static void AnalyzeInvocation(OperationAnalysisContext context) {
var invocationOperation = (IInvocationOperation)context.Operation;
if (invocationOperation.Instance is null) { // static invocation
var pythonTypeOps = context.Compilation.GetTypeByMetadataName("IronPython.Runtime.Operations.PythonTypeOps");
IMethodSymbol methodSymbol = invocationOperation.TargetMethod;
if (methodSymbol.Name == "GetName" && methodSymbol.ContainingType.Equals(pythonTypeOps, SymbolEqualityComparer.Default)) {
var diagnostic = Diagnostic.Create(Rule4, invocationOperation.Syntax.GetLocation());
context.ReportDiagnostic(diagnostic);
}
}
}
}
}