Skip to content

Commit a7a2f9f

Browse files
authored
Merge pull request #1545 from riganti/fix/tobrowserlocaltime-issues
Added `UnsupportedCallSiteAttribute` and an analyzer
2 parents f02cfc5 + 0b9055c commit a7a2f9f

File tree

14 files changed

+284
-4
lines changed

14 files changed

+284
-4
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
using System.Threading.Tasks;
2+
using DotVVM.Analyzers.ApiUsage;
3+
using Xunit;
4+
using VerifyCS = DotVVM.Analyzers.Tests.CSharpAnalyzerVerifier<
5+
DotVVM.Analyzers.ApiUsage.UnsupportedCallSiteAttributeAnalyzer>;
6+
7+
namespace DotVVM.Analyzers.Tests.ApiUsage
8+
{
9+
public class UnsupportedCallSiteAttributeTests
10+
{
11+
[Fact]
12+
public async Task Test_NoDiagnostics_InvokeMethod_WithoutUnsupportedCallSiteAttribute()
13+
{
14+
var test = @"
15+
using System;
16+
using System.IO;
17+
18+
namespace ConsoleApplication1
19+
{
20+
public class RegularClass
21+
{
22+
public void Target()
23+
{
24+
25+
}
26+
27+
public void CallSite()
28+
{
29+
Target();
30+
}
31+
}
32+
}";
33+
34+
await VerifyCS.VerifyAnalyzerAsync(test);
35+
}
36+
37+
[Fact]
38+
public async Task Test_Warning_InvokeMethod_WithUnsupportedCallSiteAttribute()
39+
{
40+
await VerifyCS.VerifyAnalyzerAsync(@"
41+
using System;
42+
using System.IO;
43+
using DotVVM.Framework.CodeAnalysis;
44+
45+
namespace ConsoleApplication1
46+
{
47+
public class RegularClass
48+
{
49+
[UnsupportedCallSite(CallSiteType.ServerSide)]
50+
public void Target()
51+
{
52+
53+
}
54+
55+
public void CallSite()
56+
{
57+
{|#0:Target()|};
58+
}
59+
}
60+
}",
61+
62+
VerifyCS.Diagnostic(UnsupportedCallSiteAttributeAnalyzer.DoNotInvokeMethodFromUnsupportedCallSite)
63+
.WithLocation(0).WithArguments("Target", string.Empty));
64+
}
65+
66+
[Fact]
67+
public async Task Test_Warning_InvokeMethod_WithUnsupportedCallSiteAttribute_WithReason()
68+
{
69+
await VerifyCS.VerifyAnalyzerAsync(@"
70+
using System;
71+
using System.IO;
72+
using DotVVM.Framework.CodeAnalysis;
73+
74+
namespace ConsoleApplication1
75+
{
76+
public class RegularClass
77+
{
78+
[UnsupportedCallSite(CallSiteType.ServerSide, ""REASON"")]
79+
public void Target()
80+
{
81+
82+
}
83+
84+
public void CallSite()
85+
{
86+
{|#0:Target()|};
87+
}
88+
}
89+
}",
90+
91+
VerifyCS.Diagnostic(UnsupportedCallSiteAttributeAnalyzer.DoNotInvokeMethodFromUnsupportedCallSite)
92+
.WithLocation(0).WithArguments("Target", "due to: \"REASON\""));
93+
}
94+
}
95+
}

src/Analyzers/Analyzers/AnalyzerReleases.Unshipped.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ Rule ID | Category | Severity | Notes
66
--------|----------|----------|-------
77
DotVVM02 | Serializability | Warning | ViewModelSerializabilityAnalyzer
88
DotVVM03 | Serializability | Warning | ViewModelSerializabilityAnalyzer
9+
DotVVM04 | ApiUsage | Warning | UnsupportedCallSiteAttributeAnalyzer
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using System.Collections.Immutable;
2+
using System.Linq;
3+
using Microsoft.CodeAnalysis;
4+
using Microsoft.CodeAnalysis.Diagnostics;
5+
using Microsoft.CodeAnalysis.Operations;
6+
7+
namespace DotVVM.Analyzers.ApiUsage
8+
{
9+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
10+
public sealed class UnsupportedCallSiteAttributeAnalyzer : DiagnosticAnalyzer
11+
{
12+
private static readonly LocalizableResourceString unsupportedCallSiteTitle = new(nameof(Resources.ApiUsage_UnsupportedCallSite_Title), Resources.ResourceManager, typeof(Resources));
13+
private static readonly LocalizableResourceString unsupportedCallSiteMessage = new(nameof(Resources.ApiUsage_UnsupportedCallSite_Message), Resources.ResourceManager, typeof(Resources));
14+
private static readonly LocalizableResourceString unsupportedCallSiteDescription = new(nameof(Resources.ApiUsage_UnsupportedCallSite_Description), Resources.ResourceManager, typeof(Resources));
15+
private const string unsupportedCallSiteAttributeMetadataName = "DotVVM.Framework.CodeAnalysis.UnsupportedCallSiteAttribute";
16+
private const int callSiteTypeServerUnderlyingValue = 0;
17+
18+
public static DiagnosticDescriptor DoNotInvokeMethodFromUnsupportedCallSite = new(
19+
DotvvmDiagnosticIds.DoNotInvokeMethodFromUnsupportedCallSiteRuleId,
20+
unsupportedCallSiteTitle,
21+
unsupportedCallSiteMessage,
22+
DiagnosticCategory.ApiUsage,
23+
DiagnosticSeverity.Warning,
24+
isEnabledByDefault: true,
25+
unsupportedCallSiteDescription);
26+
27+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
28+
=> ImmutableArray.Create(DoNotInvokeMethodFromUnsupportedCallSite);
29+
30+
public override void Initialize(AnalysisContext context)
31+
{
32+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
33+
context.EnableConcurrentExecution();
34+
35+
context.RegisterOperationAction(context =>
36+
{
37+
var unsupportedCallSiteAttribute = context.Compilation.GetTypeByMetadataName(unsupportedCallSiteAttributeMetadataName);
38+
if (unsupportedCallSiteAttribute is null)
39+
return;
40+
41+
if (context.Operation is IInvocationOperation invocation)
42+
{
43+
var method = invocation.TargetMethod;
44+
var attribute = method.GetAttributes().FirstOrDefault(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, unsupportedCallSiteAttribute));
45+
if (attribute is null || !attribute.ConstructorArguments.Any())
46+
return;
47+
48+
if (attribute.ConstructorArguments.First().Value is not int callSiteType || callSiteTypeServerUnderlyingValue != callSiteType)
49+
return;
50+
51+
var reason = (string?)attribute.ConstructorArguments.Skip(1).First().Value;
52+
context.ReportDiagnostic(
53+
Diagnostic.Create(
54+
DoNotInvokeMethodFromUnsupportedCallSite,
55+
invocation.Syntax.GetLocation(),
56+
invocation.TargetMethod.Name,
57+
(reason != null) ? $"due to: \"{reason}\"" : string.Empty));
58+
}
59+
}, OperationKind.Invocation);
60+
}
61+
}
62+
}

src/Analyzers/Analyzers/DiagnosticCategory.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ internal static class DiagnosticCategory
88
{
99
public const string Serializability = nameof(Serializability);
1010
public const string StaticCommands = nameof(StaticCommands);
11+
public const string ApiUsage = nameof(ApiUsage);
1112
}
1213
}

src/Analyzers/Analyzers/DotvvmDiagnosticIds.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ public static class DotvvmDiagnosticIds
1111

1212
public const string UseSerializablePropertiesInViewModelRuleId = "DotVVM02";
1313
public const string DoNotUseFieldsInViewModelRuleId = "DotVVM03";
14+
public const string DoNotInvokeMethodFromUnsupportedCallSiteRuleId = "DotVVM04";
1415
}
1516
}

src/Analyzers/Analyzers/Resources.Designer.cs

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Analyzers/Analyzers/Resources.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,15 @@
117117
<resheader name="writer">
118118
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
119119
</resheader>
120+
<data name="ApiUsage_UnsupportedCallSite_Description" xml:space="preserve">
121+
<value>Method that declares that it should not be called on server is only meant to be invoked on client.</value>
122+
</data>
123+
<data name="ApiUsage_UnsupportedCallSite_Message" xml:space="preserve">
124+
<value>Method '{0}' invocation is not supported on server {1}</value>
125+
</data>
126+
<data name="ApiUsage_UnsupportedCallSite_Title" xml:space="preserve">
127+
<value>Unsupported call site</value>
128+
</data>
120129
<data name="Serializability_DoNotUseFields_Description" xml:space="preserve">
121130
<value>Fields are not supported in viewmodels. Use properties to save state of viewmodels instead.</value>
122131
</data>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace DotVVM.Framework.CodeAnalysis
2+
{
3+
public enum CallSiteType
4+
{
5+
ServerSide,
6+
ClientSide
7+
}
8+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
3+
namespace DotVVM.Framework.CodeAnalysis
4+
{
5+
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false)]
6+
public class UnsupportedCallSiteAttribute : Attribute
7+
{
8+
public readonly CallSiteType Type;
9+
public readonly string? Reason;
10+
11+
public UnsupportedCallSiteAttribute(CallSiteType type, string? reason = null)
12+
{
13+
Type = type;
14+
Reason = reason;
15+
}
16+
}
17+
}

src/Framework/Core/DotVVM.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<Description>This package contains base classes and interfaces of DotVVM that might be useful in a business layer.
1111
DotVVM is an open source ASP.NET-based framework which allows to build modern web apps without writing any JavaScript code.</Description>
1212
<Nullable>enable</Nullable>
13+
<RootNamespace>DotVVM.Framework</RootNamespace>
1314
</PropertyGroup>
1415
<ItemGroup>
1516
<EmbeddedResource Include="compiler\resources\**\*" />

0 commit comments

Comments
 (0)