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

Commit 2abc600

Browse files
Merge pull request #28 from episerver/user/shsh/Analyze-HtpContextBase
Added analyzer and code fixer for IHttpContextAccessor
2 parents 58f6b0e + 379da4a commit 2abc600

File tree

5 files changed

+235
-0
lines changed

5 files changed

+235
-0
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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 EpiHttpContextBaseAccessorAnalyzer : DiagnosticAnalyzer
19+
{
20+
/// <summary>
21+
/// The diagnostic ID for diagnostics produced by this analyzer.
22+
/// </summary>
23+
public const string DiagnosticId = "EP0010";
24+
25+
/// <summary>
26+
/// The diagnsotic category for diagnostics produced by this analyzer.
27+
/// </summary>
28+
private const string Category = "Upgrade";
29+
30+
31+
internal static readonly string HttpContextBaseParameterType = "ServiceAccessor<HttpContextBase>";
32+
33+
private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.EpiHttpContextBaseTitle), Resources.ResourceManager, typeof(Resources));
34+
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.EpiHttpContextBaseFormat), Resources.ResourceManager, typeof(Resources));
35+
private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.EpiHttpContextBaseDescription), Resources.ResourceManager, typeof(Resources));
36+
37+
private static readonly DiagnosticDescriptor Rule = new(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
38+
39+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
40+
41+
public override void Initialize(AnalysisContext context)
42+
{
43+
if (context is null)
44+
{
45+
throw new ArgumentNullException(nameof(context));
46+
}
47+
48+
context.EnableConcurrentExecution();
49+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
50+
51+
context.RegisterSyntaxNodeAction(AnalyzeConstructor, SyntaxKind.ClassDeclaration);
52+
}
53+
54+
private void AnalyzeConstructor(SyntaxNodeAnalysisContext context)
55+
{
56+
//var constructorDirective = (ConstructorDeclarationSyntax)context.Node;
57+
var classDirective = (ClassDeclarationSyntax)context.Node;
58+
59+
foreach(var constructorSyntax in classDirective.Members.Where(m => m is ConstructorDeclarationSyntax))
60+
{
61+
var parameters = (constructorSyntax as ConstructorDeclarationSyntax).ParameterList.Parameters;
62+
if (parameters.Any(p => p.Type != null && p.Type.ToString().Equals(HttpContextBaseParameterType, StringComparison.OrdinalIgnoreCase)))
63+
{
64+
var diagnostic = Diagnostic.Create(Rule, classDirective.GetLocation(), constructorSyntax.ToFullString());
65+
context.ReportDiagnostic(diagnostic);
66+
}
67+
}
68+
}
69+
}
70+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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 System;
11+
using System.Collections.Generic;
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 = "EP00010 CodeFix Provider")]
25+
public class EpiHttpContextBaseAccessorCodeFixProvider : CodeFixProvider
26+
{
27+
28+
// The Upgrade Assistant will only use analyzers that have an associated code fix provider registered including
29+
// the analyzer's ID in the code fix provider's FixableDiagnosticIds array.
30+
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(EpiHttpContextBaseAccessorAnalyzer.DiagnosticId);
31+
private const string MicrosoftAspNetCoreHttpNamespace = "Microsoft.AspNetCore.Http";
32+
33+
public sealed override FixAllProvider GetFixAllProvider() =>
34+
// See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers
35+
WellKnownFixAllProviders.BatchFixer;
36+
37+
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
38+
{
39+
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
40+
41+
if (root is null)
42+
{
43+
return;
44+
}
45+
46+
var diagnostic = context.Diagnostics.First();
47+
var diagnosticSpan = diagnostic.Location.SourceSpan;
48+
var classDeclarationSyntax = root.FindNode(diagnosticSpan) as ClassDeclarationSyntax;
49+
if (classDeclarationSyntax is null)
50+
{
51+
return;
52+
}
53+
54+
context.RegisterCodeFix(
55+
CodeAction.Create(
56+
Resources.EpiHttpContextBaseTitle,
57+
c => ReplaceParameterTypeAsync(context.Document, classDeclarationSyntax, c),
58+
nameof(Resources.EpiHttpContextBaseTitle)),
59+
diagnostic);
60+
}
61+
62+
private static async Task<Document> ReplaceParameterTypeAsync(Document document, ClassDeclarationSyntax classDeclarationSyntax, CancellationToken cancellationToken)
63+
{
64+
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
65+
var updatedClassDeclaration = UpdateMemberField(classDeclarationSyntax);
66+
var updatedClassDeclarationByCostr = UpdateConstructor(updatedClassDeclaration);
67+
var newRoot = root!.ReplaceNode(classDeclarationSyntax, updatedClassDeclarationByCostr);
68+
69+
// Return document with transformed tree.
70+
var updatedDocument = document.WithSyntaxRoot(newRoot);
71+
return await updatedDocument.AddUsingIfMissingAsync(cancellationToken, MicrosoftAspNetCoreHttpNamespace);
72+
}
73+
74+
private static ClassDeclarationSyntax UpdateMemberField(ClassDeclarationSyntax classDecSyntax)
75+
{
76+
var updatedMembers = new List<MemberDeclarationSyntax>();
77+
foreach (var m in classDecSyntax.Members)
78+
{
79+
if (m is FieldDeclarationSyntax field && field.Declaration.Type.ToString().Equals(EpiHttpContextBaseAccessorAnalyzer.HttpContextBaseParameterType, StringComparison.OrdinalIgnoreCase))
80+
{
81+
var svcAccessorHttpContextBaseMember = m as FieldDeclarationSyntax;
82+
var variableDeclaration = svcAccessorHttpContextBaseMember.Declaration.WithType(SyntaxFactory.ParseTypeName("IHttpContextAccessor").WithTrailingTrivia(SyntaxFactory.Whitespace(" "))).WithVariables(svcAccessorHttpContextBaseMember.Declaration.Variables);
83+
var iHttpContextBaseMember = SyntaxFactory.FieldDeclaration(svcAccessorHttpContextBaseMember.AttributeLists, (m as FieldDeclarationSyntax).Modifiers, variableDeclaration);
84+
updatedMembers.Add(iHttpContextBaseMember);
85+
}
86+
else
87+
{
88+
updatedMembers.Add(m);
89+
}
90+
}
91+
return classDecSyntax.WithMembers(SyntaxFactory.List<MemberDeclarationSyntax>().AddRange(updatedMembers));
92+
}
93+
94+
private static ClassDeclarationSyntax UpdateConstructor(ClassDeclarationSyntax classDeclarationSyntax)
95+
{
96+
ClassDeclarationSyntax updatedClassDeclaration = classDeclarationSyntax;
97+
foreach (var constructorSyntax in classDeclarationSyntax.Members.Where(m => m is ConstructorDeclarationSyntax))
98+
{
99+
var parameters = new List<ParameterSyntax>();
100+
101+
foreach (var parameter in (constructorSyntax as ConstructorDeclarationSyntax).ParameterList.Parameters)
102+
{
103+
if (parameter.Type != null && parameter.Type.ToString().Equals(EpiHttpContextBaseAccessorAnalyzer.HttpContextBaseParameterType, StringComparison.OrdinalIgnoreCase))
104+
{
105+
var updatedParameter = parameter.WithType(SyntaxFactory.ParseTypeName("IHttpContextAccessor")).WithTrailingTrivia();
106+
parameters.Add(updatedParameter);
107+
}
108+
else
109+
{
110+
parameters.Add(parameter);
111+
}
112+
}
113+
114+
var updateConstructor = (constructorSyntax as ConstructorDeclarationSyntax).WithParameterList(SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(parameters)));
115+
updatedClassDeclaration = updatedClassDeclaration.ReplaceNode(constructorSyntax, updateConstructor);
116+
}
117+
return updatedClassDeclaration;
118+
}
119+
120+
private static CompilationUnitSyntax AddUsing(CompilationUnitSyntax compilationRoot, string namespaceToAdd)
121+
{
122+
var usingDirective = SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(namespaceToAdd).WithLeadingTrivia(SyntaxFactory.Whitespace(" ")))
123+
.WithTrailingTrivia(SyntaxFactory.CarriageReturnLineFeed);
124+
return compilationRoot.AddUsings(usingDirective);
125+
}
126+
}
127+
}

src/EpiSourceUpdater/EpiSourceUpdaterServiceProvider.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public void AddServices(IExtensionServiceCollection services)
5959
services.Services.AddTransient<DiagnosticAnalyzer, EpiDisplayChannelAnalyzer>(); // EP0007
6060
services.Services.AddTransient<DiagnosticAnalyzer, EpiMetadataAwareAnalyzer>(); // EP0008
6161
services.Services.AddTransient<DiagnosticAnalyzer, EpiPartialRouterAnalyzer>(); // EP0009
62+
services.Services.AddTransient<DiagnosticAnalyzer, EpiHttpContextBaseAccessorAnalyzer>(); // EP0010
6263

6364
// Upgrade Step.
6465
services.Services.AddUpgradeStep<FindReplaceUpgradeStep>();
@@ -74,6 +75,7 @@ public void AddServices(IExtensionServiceCollection services)
7475
services.Services.AddTransient<CodeFixProvider, EpiDisplayChannelCodeFixProvider>(); // EP0007
7576
services.Services.AddTransient<CodeFixProvider, EpiMetadataAwareCodeFixProvider>(); // EP0008
7677
services.Services.AddTransient<CodeFixProvider, EpiPartialRouterCodeFixProvider>(); // EP0009
78+
services.Services.AddTransient<CodeFixProvider, EpiHttpContextBaseAccessorCodeFixProvider>(); // EP0010
7779
}
7880
}
7981
}

src/EpiSourceUpdater/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/EpiSourceUpdater/Resources.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,15 @@
153153
<data name="EpiDisplayChannelTitle" xml:space="preserve">
154154
<value>CMS display channels will be updated</value>
155155
</data>
156+
<data name="EpiHttpContextBaseDescription" xml:space="preserve">
157+
<value>ServiceAccessor&lt;HttpContextBase&gt; in constructor will changed to HttpContextAccessor.</value>
158+
</data>
159+
<data name="EpiHttpContextBaseFormat" xml:space="preserve">
160+
<value>ServiceAccessor&lt;HttpContextBase&gt; in constructor will changed to HttpContextAccessor</value>
161+
</data>
162+
<data name="EpiHttpContextBaseTitle" xml:space="preserve">
163+
<value>ServiceAccessor&lt;HttpContextBase&gt; in constructor will changed to HttpContextAccessor</value>
164+
</data>
156165
<data name="EpiMemberReplacementDescription" xml:space="preserve">
157166
<value>The Interface IFindUIConfiguration is no longer supported and will be replaced</value>
158167
</data>

0 commit comments

Comments
 (0)