Skip to content
This repository was archived by the owner on Nov 12, 2025. It is now read-only.

Commit fa3a76d

Browse files
committed
Added analyzer/codefixer for display channels
1 parent ca72922 commit fa3a76d

File tree

6 files changed

+227
-3
lines changed

6 files changed

+227
-3
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.CSharp;
6+
using Microsoft.CodeAnalysis.CSharp.Syntax;
7+
using Microsoft.CodeAnalysis.Diagnostics;
8+
using System;
9+
using System.Collections.Immutable;
10+
using System.Linq;
11+
12+
namespace Epi.Source.Updater
13+
{
14+
/// <summary>
15+
/// Analyzer for identifying and removing obsolet types or methods.
16+
/// </summary>
17+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
18+
public sealed class EpiDisplayChannelAnalyzer : DiagnosticAnalyzer
19+
{
20+
/// <summary>
21+
/// The diagnostic ID for diagnostics produced by this analyzer.
22+
/// </summary>
23+
public const string DiagnosticId = "EP0007";
24+
25+
/// <summary>
26+
/// The diagnsotic category for diagnostics produced by this analyzer.
27+
/// </summary>
28+
private const string Category = "Upgrade";
29+
30+
31+
private static readonly string MethodName = "IsActive";
32+
private static readonly string HttpContextBase = "HttpContextBase";
33+
private static readonly string[] BaseTypes = new[] { "DisplayChannel" };
34+
35+
private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.EpiDisplayChannelTitle), Resources.ResourceManager, typeof(Resources));
36+
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.EpiDisplayChannelFormat), Resources.ResourceManager, typeof(Resources));
37+
private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.EpiDisplayChannelDescription), Resources.ResourceManager, typeof(Resources));
38+
39+
private static readonly DiagnosticDescriptor Rule = new(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
40+
41+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
42+
43+
public override void Initialize(AnalysisContext context)
44+
{
45+
if (context is null)
46+
{
47+
throw new ArgumentNullException(nameof(context));
48+
}
49+
50+
context.EnableConcurrentExecution();
51+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
52+
53+
context.RegisterSyntaxNodeAction(AnalyzeIsActiveMethod, SyntaxKind.MethodDeclaration);
54+
}
55+
56+
private void AnalyzeIsActiveMethod(SyntaxNodeAnalysisContext context)
57+
{
58+
var methodDirective = (MethodDeclarationSyntax)context.Node;
59+
60+
var namespaceName = methodDirective.Identifier.Text?.ToString();
61+
62+
if (namespaceName is null)
63+
{
64+
return;
65+
}
66+
67+
if (namespaceName.Equals(MethodName, StringComparison.Ordinal))
68+
{
69+
var parameters = methodDirective.ParameterList.Parameters;
70+
if (parameters.Count == 1 && HttpContextBase.Equals(parameters.Single().Type.ToString()) && IsDisplayChannel(methodDirective.Parent as ClassDeclarationSyntax))
71+
{
72+
var diagnostic = Diagnostic.Create(Rule, methodDirective.GetLocation(), methodDirective.ToFullString());
73+
context.ReportDiagnostic(diagnostic);
74+
}
75+
}
76+
}
77+
78+
private static bool IsDisplayChannel(ClassDeclarationSyntax classDirective)
79+
{
80+
if (classDirective is null || classDirective.BaseList is null)
81+
{
82+
return false;
83+
}
84+
85+
foreach (var baseType in classDirective.BaseList.Types)
86+
{
87+
if (baseType.Type is IdentifierNameSyntax identifierNameSyntax)
88+
{
89+
if (BaseTypes.Contains(identifierNameSyntax.Identifier.Text, StringComparer.OrdinalIgnoreCase))
90+
{
91+
return true;
92+
}
93+
}
94+
}
95+
96+
return false;
97+
}
98+
}
99+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.CodeActions;
6+
using Microsoft.CodeAnalysis.CodeFixes;
7+
using Microsoft.CodeAnalysis.CSharp;
8+
using Microsoft.CodeAnalysis.CSharp.Syntax;
9+
using Microsoft.CodeAnalysis.Editing;
10+
using Microsoft.CodeAnalysis.Simplification;
11+
using System;
12+
using System.Collections.Immutable;
13+
using System.Linq;
14+
using System.Threading;
15+
using System.Threading.Tasks;
16+
17+
namespace Epi.Source.Updater
18+
{
19+
/// <summary>
20+
/// As with the analzyers, code fix providers that are registered into Upgrade Assistant's
21+
/// dependency injection container (by IExtensionServiceProvider) will be used during
22+
/// the source update step.
23+
/// </summary>
24+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = "EP0007 CodeFix Provider")]
25+
public class EpiDisplayChannelCodeFixProvider : CodeFixProvider
26+
{
27+
// The Upgrade Assistant will only use analyzers that have an associated code fix provider registered including
28+
// the analyzer's ID in the code fix provider's FixableDiagnosticIds array.
29+
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(EpiDisplayChannelAnalyzer.DiagnosticId);
30+
private const string HttpNamespace = "Microsoft.AspNetCore.Http";
31+
32+
public sealed override FixAllProvider GetFixAllProvider() =>
33+
// See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers
34+
WellKnownFixAllProviders.BatchFixer;
35+
36+
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
37+
{
38+
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
39+
40+
if (root is null)
41+
{
42+
return;
43+
}
44+
45+
var diagnostic = context.Diagnostics.First();
46+
var diagnosticSpan = diagnostic.Location.SourceSpan;
47+
var methodDeclaration = root.FindNode(diagnosticSpan) as MethodDeclarationSyntax;
48+
if (methodDeclaration is null)
49+
{
50+
return;
51+
}
52+
53+
context.RegisterCodeFix(
54+
CodeAction.Create(
55+
Resources.EpiDisplayChannelTitle,
56+
c => ReplaceNameAndReturnParameterAsync(context.Document, methodDeclaration, c),
57+
nameof(Resources.EpiDisplayChannelTitle)),
58+
diagnostic);
59+
}
60+
61+
private static async Task<Document> ReplaceNameAndReturnParameterAsync(Document document, MethodDeclarationSyntax localDeclaration, CancellationToken cancellationToken)
62+
{
63+
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
64+
65+
var updatedParameter = localDeclaration.ParameterList.Parameters[0].WithType(SyntaxFactory.ParseTypeName("HttpContext")).WithTrailingTrivia();
66+
var updatedParameterList = localDeclaration.WithParameterList(SyntaxFactory.ParameterList(SyntaxFactory.SingletonSeparatedList<ParameterSyntax>(updatedParameter)));
67+
var newRoot = root!.ReplaceNode(localDeclaration, updatedParameterList);
68+
69+
// Return document with transformed tree.
70+
var updatedDocument = document.WithSyntaxRoot(newRoot);
71+
72+
var compilationRoot = (await updatedDocument.GetSyntaxTreeAsync()).GetCompilationUnitRoot();
73+
if (!compilationRoot.Usings.Any(u => u.Name.ToString() == HttpNamespace))
74+
{
75+
var editor = await DocumentEditor.CreateAsync(updatedDocument, cancellationToken).ConfigureAwait(false);
76+
var documentRoot = (CompilationUnitSyntax)editor.OriginalRoot;
77+
var usingDirective = SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(HttpNamespace).WithLeadingTrivia(SyntaxFactory.Whitespace(" ")))
78+
.WithTrailingTrivia(SyntaxFactory.CarriageReturnLineFeed);
79+
documentRoot = compilationRoot.AddUsings(usingDirective);
80+
editor.ReplaceNode(editor.OriginalRoot, documentRoot);
81+
updatedDocument = editor.GetChangedDocument();
82+
}
83+
84+
return updatedDocument;
85+
}
86+
}
87+
}

src/EpiSourceUpdater/EpiPartialControllerCodeFixProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ private static async Task<Document> ReplaceNameAndReturnParameterAsync(Document
8484
var updatedDocument = document.WithSyntaxRoot(newRoot);
8585

8686
var compilationRoot = (await updatedDocument.GetSyntaxTreeAsync()).GetCompilationUnitRoot();
87-
if (compilationRoot.Usings.Any(u => u.Name.ToString() == MvcNamespace))
87+
if (!compilationRoot.Usings.Any(u => u.Name.ToString() == MvcNamespace))
8888
{
8989
var editor = await DocumentEditor.CreateAsync(updatedDocument, cancellationToken).ConfigureAwait(false);
9090
var documentRoot = (CompilationUnitSyntax)editor.OriginalRoot;

src/EpiSourceUpdater/EpiSourceUpdaterServiceProvider.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ public void AddServices(IExtensionServiceCollection services)
5656
services.Services.AddTransient<DiagnosticAnalyzer, EpiObsoleteTypesAnalyzer>(); // EP0004
5757
services.Services.AddTransient<DiagnosticAnalyzer, EpiObsoleteUsingAnalyzer>(); // EP0005
5858
services.Services.AddTransient<DiagnosticAnalyzer, EpiPartialControllerAnalyzer>(); // EP0006
59+
services.Services.AddTransient<DiagnosticAnalyzer, EpiDisplayChannelAnalyzer>(); // EP0007
5960

6061
// Upgrade Step.
6162
services.Services.AddUpgradeStep<FindReplaceUpgradeStep>();
@@ -68,6 +69,7 @@ public void AddServices(IExtensionServiceCollection services)
6869
services.Services.AddTransient<CodeFixProvider, EpiObsoleteTypesCodeFixProvider>(); // EP0004
6970
services.Services.AddTransient<CodeFixProvider, EpiObsoleteUsingCodeFixProvider>(); // EP0005
7071
services.Services.AddTransient<CodeFixProvider, EpiPartialControllerCodeFixProvider>(); // EP0006
72+
services.Services.AddTransient<CodeFixProvider, EpiDisplayChannelCodeFixProvider>(); // EP0007
7173
}
7274
}
7375
}

src/EpiSourceUpdater/Resources.Designer.cs

Lines changed: 28 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/EpiSourceUpdater/Resources.resx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,15 @@
144144
<data name="EpiDisallowedTypesTitle" xml:space="preserve">
145145
<value>CMS 12 projects should no longer use overrides for ParseToObject</value>
146146
</data>
147+
<data name="EpiDisplayChannelDescription" xml:space="preserve">
148+
<value>CMS Display Channels will be updated.</value>
149+
</data>
150+
<data name="EpiDisplayChannelFormat" xml:space="preserve">
151+
<value>Argument 'HttpContextBase' will be replaced with 'HttpContext'</value>
152+
</data>
153+
<data name="EpiDisplayChannelTitle" xml:space="preserve">
154+
<value>CMS display channels will be updated</value>
155+
</data>
147156
<data name="EpiMemberReplacementDescription" xml:space="preserve">
148157
<value>The Interface IFindUIConfiguration is no longer supported and will be replaced</value>
149158
</data>
@@ -163,7 +172,7 @@
163172
<value>Commerce: Remove obsolete usings like Mediachase.BusinessFoundation</value>
164173
</data>
165174
<data name="EpiPartialControllerDescription" xml:space="preserve">
166-
<value>CMS Classes like PartialContentController or BlockController will be replaced.</value>
175+
<value>CMS partial controllers will be updated.</value>
167176
</data>
168177
<data name="EpiPartialControllerFormat" xml:space="preserve">
169178
<value>Method 'Index' will be replaced with 'InvokeComponent'</value>

0 commit comments

Comments
 (0)