Skip to content

Commit d9a66cf

Browse files
author
MPCoreDeveloper
committed
new version with comunity requests
1 parent 51a22e6 commit d9a66cf

14 files changed

+752
-100
lines changed

Posseth.NamedArguments.AnalyzerAndFixer.CodeFixes/Posseth.NamedArguments.AnalyzerAndFixer.CodeFixes.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFramework>netstandard2.0</TargetFramework>
@@ -7,7 +7,7 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.13.0" />
10+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.14.0" />
1111
</ItemGroup>
1212

1313
<ItemGroup>
Lines changed: 130 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1-

1+
//Michel Posseth 2025-05-17 last mod
2+
//multiple code fixes due to feedback from the community
3+
using System.Linq;
24
using System.Composition;
35
using System.Threading;
46
using System.Threading.Tasks;
57
using Microsoft.CodeAnalysis;
6-
using Microsoft.CodeAnalysis.Text;
8+
using System.Collections.Generic;
79
using System.Collections.Immutable;
8-
using Microsoft.CodeAnalysis.Rename;
10+
using Microsoft.CodeAnalysis.CSharp;
911
using Microsoft.CodeAnalysis.CodeFixes;
10-
using Microsoft.CodeAnalysis.CSharp.Syntax;
1112
using Microsoft.CodeAnalysis.CodeActions;
12-
using Microsoft.CodeAnalysis.CSharp;
13-
using System.Linq;
14-
using System.Xml.Linq;
13+
using Microsoft.CodeAnalysis.CSharp.Syntax;
1514
namespace Posseth.NamedArguments.AnalyzerAndFixer
1615
{
17-
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(NamedArgumentsCodeFixProvider)), Shared]
16+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(NamedArgumentsCodeFixProvider)), Shared]
1817
public class NamedArgumentsCodeFixProvider : CodeFixProvider
1918
{
2019
private const string Title = "Use named argument";
@@ -43,41 +42,148 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
4342
private async Task<Document> UseNamedArgumentAsync(Document document, ArgumentSyntax argument, CancellationToken cancellationToken)
4443
{
4544
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
45+
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
46+
47+
// First, process any nested invocations within this argument
48+
var processedRoot = ProcessNestedInvocations(root, argument, semanticModel);
49+
50+
// Then process the current argument
4651
var argumentList = argument.Parent as ArgumentListSyntax;
47-
var invocation = argumentList?.Parent as InvocationExpressionSyntax;
48-
49-
if (invocation == null)
52+
if (argumentList == null)
53+
return document.WithSyntaxRoot(processedRoot);
54+
55+
var invocation = argumentList.Parent;
56+
57+
// Handle both direct invocations and object creation expressions
58+
if (invocation == null ||
59+
!(invocation is InvocationExpressionSyntax ||
60+
invocation is ObjectCreationExpressionSyntax))
5061
{
51-
// If the parent is not an InvocationExpressionSyntax, we cannot apply the fix
52-
return document;
62+
return document.WithSyntaxRoot(processedRoot);
5363
}
5464

55-
var methodSymbol = semanticModel.GetSymbolInfo(invocation).Symbol as IMethodSymbol;
65+
// Find the corresponding argument in the processed root
66+
var currentArgument = FindCorrespondingNode(processedRoot, argument);
67+
if (currentArgument == null)
68+
return document.WithSyntaxRoot(processedRoot);
69+
70+
IMethodSymbol methodSymbol = null;
71+
72+
if (invocation is InvocationExpressionSyntax invocationExpr)
73+
{
74+
methodSymbol = semanticModel.GetSymbolInfo(invocationExpr).Symbol as IMethodSymbol;
75+
}
76+
else if (invocation is ObjectCreationExpressionSyntax creationExpr)
77+
{
78+
methodSymbol = semanticModel.GetSymbolInfo(creationExpr).Symbol as IMethodSymbol;
79+
}
5680

5781
if (methodSymbol == null)
5882
{
59-
// If methodSymbol is null, we cannot apply the fix
60-
return document;
83+
return document.WithSyntaxRoot(processedRoot);
84+
}
85+
86+
int parameterIndex = argumentList.Arguments.IndexOf(argument);
87+
if (parameterIndex >= methodSymbol.Parameters.Length)
88+
{
89+
return document.WithSyntaxRoot(processedRoot);
6190
}
6291

63-
var parameter = methodSymbol.Parameters[argumentList.Arguments.IndexOf(argument)];
92+
var parameter = methodSymbol.Parameters[parameterIndex];
6493

6594
if (parameter == null)
6695
{
67-
// If parameter is null, we cannot apply the fix
68-
return document;
96+
return document.WithSyntaxRoot(processedRoot);
6997
}
7098

7199
// Create a new argument node with a NameColon
72100
var namedArgument = SyntaxFactory.Argument(
73-
SyntaxFactory.NameColon(parameter.Name), // Here you add the name of the parameter
74-
argument.RefOrOutKeyword,
75-
argument.Expression);
101+
SyntaxFactory.NameColon(parameter.Name),
102+
currentArgument.RefOrOutKeyword,
103+
currentArgument.Expression);
76104

77-
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
78-
var newRoot = root.ReplaceNode(argument, namedArgument);
105+
var newRoot = processedRoot.ReplaceNode(currentArgument, namedArgument);
79106

80107
return document.WithSyntaxRoot(newRoot);
81108
}
109+
110+
// Helper method to find a corresponding node in a new syntax tree
111+
private ArgumentSyntax FindCorrespondingNode(SyntaxNode root, ArgumentSyntax originalNode)
112+
{
113+
// Find the node at the same position
114+
var nodeAtSamePosition = root.FindNode(originalNode.Span);
115+
if (nodeAtSamePosition is ArgumentSyntax arg)
116+
return arg;
117+
118+
// If location-based search failed, try finding by structure
119+
var parentList = originalNode.Parent as ArgumentListSyntax;
120+
if (parentList != null)
121+
{
122+
int index = parentList.Arguments.IndexOf(originalNode);
123+
var newParentList = root.DescendantNodes()
124+
.OfType<ArgumentListSyntax>()
125+
.FirstOrDefault(a => a.Span.Contains(parentList.Span));
126+
127+
if (newParentList != null && index >= 0 && index < newParentList.Arguments.Count)
128+
return newParentList.Arguments[index];
129+
}
130+
131+
return null;
132+
}
133+
134+
// New method to process nested invocations
135+
private SyntaxNode ProcessNestedInvocations(SyntaxNode root, ArgumentSyntax argument, SemanticModel semanticModel)
136+
{
137+
// Find all nested invocations within this argument
138+
var nestedInvocations = argument.DescendantNodes()
139+
.OfType<InvocationExpressionSyntax>()
140+
.ToList();
141+
142+
if (nestedInvocations.Count == 0)
143+
return root;
144+
145+
// Process each nested invocation
146+
return root.ReplaceNodes(
147+
nestedInvocations,
148+
(original, _) => ProcessNestedInvocation(original, original, semanticModel));
149+
}
150+
151+
private SyntaxNode ProcessNestedInvocation(SyntaxNode original, SyntaxNode rewritten, SemanticModel semanticModel)
152+
{
153+
var invocation = rewritten as InvocationExpressionSyntax;
154+
if (invocation == null)
155+
return rewritten;
156+
157+
var methodSymbol = semanticModel.GetSymbolInfo(invocation).Symbol as IMethodSymbol;
158+
if (methodSymbol == null)
159+
return rewritten;
160+
161+
var argList = invocation.ArgumentList;
162+
var newArgs = new List<ArgumentSyntax>();
163+
bool changed = false;
164+
165+
for (int i = 0; i < argList.Arguments.Count; i++)
166+
{
167+
var arg = argList.Arguments[i];
168+
if (arg.NameColon == null && i < methodSymbol.Parameters.Length)
169+
{
170+
changed = true;
171+
newArgs.Add(SyntaxFactory.Argument(
172+
SyntaxFactory.NameColon(methodSymbol.Parameters[i].Name),
173+
arg.RefOrOutKeyword,
174+
arg.Expression));
175+
}
176+
else
177+
{
178+
newArgs.Add(arg);
179+
}
180+
}
181+
182+
if (!changed)
183+
return rewritten;
184+
185+
return invocation.WithArgumentList(
186+
SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(newArgs)));
187+
}
82188
}
83189
}

Posseth.NamedArguments.AnalyzerAndFixer.Package/Posseth.NamedArguments.AnalyzerAndFixer.Package.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,22 @@
99

1010
<PropertyGroup>
1111
<PackageId>Posseth.NamedArguments.AnalyzerAndFixer</PackageId>
12-
<PackageVersion>1.0.0.1</PackageVersion>
12+
<PackageVersion>1.0.1.0</PackageVersion>
1313
<Authors>MPCoreDeveloper</Authors>
1414
<PackageLicenseExpression>MIT</PackageLicenseExpression>
1515
<PackageProjectUrl>https://github.com/MPCoreDeveloper/Posseth.NamedArguments.AnalyzerAndFixer</PackageProjectUrl>
1616
<RepositoryUrl>https://github.com/MPCoreDeveloper/Posseth.NamedArguments.AnalyzerAndFixer</RepositoryUrl>
1717
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
1818
<Description>This tool analyzes method calls to ensure they use named arguments and provides an option to automatically convert positional arguments to named arguments. For example, it can transform `TestMethod(1, 2)` into `TestMethod(x: 1, y: 2)`.
1919
</Description>
20-
<PackageReleaseNotes>initial release</PackageReleaseNotes>
20+
<PackageReleaseNotes>release with comunity feedback improvements</PackageReleaseNotes>
2121
<Copyright>Free to use , distribute and modify </Copyright>
2222
<PackageTags>NamedArguments;AnalyzerAndFixer;analyzers;</PackageTags>
2323
<DevelopmentDependency>true</DevelopmentDependency>
2424
<NoPackageAnalysis>true</NoPackageAnalysis>
2525
<TargetsForTfmSpecificContentInPackage>$(TargetsForTfmSpecificContentInPackage);_AddAnalyzersToOutput</TargetsForTfmSpecificContentInPackage>
2626
<Title>Named Arguments Analyzer and Fixer</Title>
27-
<Version>1.0.1</Version>
27+
<Version>1.1.0</Version>
2828
<Company>Posseth Software</Company>
2929
<Product>Posseth.NamedArguments.AnalyzerAndFixer</Product>
3030
<PackageIcon>logo.jpeg</PackageIcon>
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
using Microsoft.CodeAnalysis.CSharp.Testing;
2+
using Microsoft.CodeAnalysis.Testing;
3+
using Microsoft.VisualStudio.TestTools.UnitTesting;
4+
using System.Threading.Tasks;
5+
using VerifyCS = Posseth.NamedArguments.AnalyzerAndFixer.Test.CSharpCodeFixVerifier<
6+
Posseth.NamedArguments.AnalyzerAndFixer.NamedArgumentsAnalyzer,
7+
Posseth.NamedArguments.AnalyzerAndFixer.NamedArgumentsCodeFixProvider>;
8+
9+
namespace Posseth.NamedArguments.AnalyzerAndFixer.Test
10+
{
11+
[TestClass]
12+
public class NestedInvocationTests
13+
{
14+
[TestMethod]
15+
public async Task NestedInvocations_AreFixed()
16+
{
17+
// Create code with nested method calls
18+
var test = @"
19+
using System;
20+
21+
class TimeProvider
22+
{
23+
public DateTime GetLocalNow() => DateTime.Now;
24+
}
25+
26+
class S3Provider
27+
{
28+
public string GetGetPresignedUrl(string bucketName, string objectKey, DateTime expires) => $""{bucketName}/{objectKey}/{expires}"";
29+
}
30+
31+
class AppConfiguration
32+
{
33+
public class AmazonConfig
34+
{
35+
public class S3Config
36+
{
37+
public string BucketName { get; set; } = ""my-bucket"";
38+
}
39+
40+
public S3Config S3 { get; set; } = new S3Config();
41+
}
42+
43+
public AmazonConfig Amazon { get; set; } = new AmazonConfig();
44+
}
45+
46+
class Program
47+
{
48+
public Uri GetGetPresignedUrl(string objectKey)
49+
{
50+
var s3Provider = new S3Provider();
51+
var appConfiguration = new AppConfiguration();
52+
var timeProvider = new TimeProvider();
53+
54+
return new Uri(
55+
s3Provider.GetGetPresignedUrl(
56+
appConfiguration.Amazon.S3.BucketName,
57+
objectKey,
58+
timeProvider.GetLocalNow().AddHours(24)
59+
)
60+
);
61+
}
62+
}";
63+
64+
var fixedTest = @"
65+
using System;
66+
67+
class TimeProvider
68+
{
69+
public DateTime GetLocalNow() => DateTime.Now;
70+
}
71+
72+
class S3Provider
73+
{
74+
public string GetGetPresignedUrl(string bucketName, string objectKey, DateTime expires) => $""{bucketName}/{objectKey}/{expires}"";
75+
}
76+
77+
class AppConfiguration
78+
{
79+
public class AmazonConfig
80+
{
81+
public class S3Config
82+
{
83+
public string BucketName { get; set; } = ""my-bucket"";
84+
}
85+
86+
public S3Config S3 { get; set; } = new S3Config();
87+
}
88+
89+
public AmazonConfig Amazon { get; set; } = new AmazonConfig();
90+
}
91+
92+
class Program
93+
{
94+
public Uri GetGetPresignedUrl(string objectKey)
95+
{
96+
var s3Provider = new S3Provider();
97+
var appConfiguration = new AppConfiguration();
98+
var timeProvider = new TimeProvider();
99+
100+
return new Uri(
101+
s3Provider.GetGetPresignedUrl(
102+
bucketName: appConfiguration.Amazon.S3.BucketName,
103+
objectKey: objectKey,
104+
expires: timeProvider.GetLocalNow().AddHours(value: 24))
105+
);
106+
}
107+
}";
108+
109+
// Expected diagnostics
110+
var expected = new[]
111+
{
112+
// Arguments in the GetGetPresignedUrl method
113+
VerifyCS.Diagnostic(NamedArgumentsAnalyzer.DiagnosticId).WithLocation(39, 17).WithArguments("bucketName"),
114+
VerifyCS.Diagnostic(NamedArgumentsAnalyzer.DiagnosticId).WithLocation(40, 17).WithArguments("objectKey"),
115+
VerifyCS.Diagnostic(NamedArgumentsAnalyzer.DiagnosticId).WithLocation(41, 17).WithArguments("expires"),
116+
117+
// Arguments in the AddHours method
118+
VerifyCS.Diagnostic(NamedArgumentsAnalyzer.DiagnosticId).WithLocation(41, 53).WithArguments("value"),
119+
};
120+
121+
await VerifyCS.VerifyCodeFixAsync(test, expected, fixedTest);
122+
}
123+
}
124+
}

Posseth.NamedArguments.AnalyzerAndFixer.Test/Posseth.NamedArguments.AnalyzerAndFixer.Test.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@
1111
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
1212
<PackageReference Include="MSTest.TestAdapter" Version="3.8.3" />
1313
<PackageReference Include="MSTest.TestFramework" Version="3.8.3" />
14-
<PackageReference Include="Microsoft.CodeAnalysis" Version="4.13.0" />
14+
<PackageReference Include="Microsoft.CodeAnalysis" Version="4.14.0" />
1515
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest" Version="1.1.2" />
1616
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest" Version="1.1.2" />
1717
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest" Version="1.1.2" />
1818
<PackageReference Include="Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest" Version="1.1.2" />
1919
<PackageReference Include="Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest" Version="1.1.2" />
2020
<PackageReference Include="Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest" Version="1.1.2" />
21-
<PackageReference Include="System.Formats.Asn1" Version="9.0.4" />
21+
<PackageReference Include="System.Formats.Asn1" Version="9.0.5" />
2222
</ItemGroup>
2323

2424
<ItemGroup>

0 commit comments

Comments
 (0)