Skip to content

Commit 9d02888

Browse files
author
MPCoreDeveloper
committed
add revisited record support , added unit test for records
1 parent e9674ea commit 9d02888

File tree

8 files changed

+278
-95
lines changed

8 files changed

+278
-95
lines changed

NestedInvocationTests.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
FixedCode = @"
2+
using System;
3+
4+
class TimeProvider
5+
{
6+
public DateTime GetLocalNow() => DateTime.Now;
7+
}
8+
9+
class S3Provider
10+
{
11+
public string GetGetPresignedUrl(string bucketName, string objectKey, DateTime expires) => $""{bucketName}/{objectKey}/{expires}"";
12+
}
13+
14+
class AppConfiguration
15+
{
16+
public class AmazonConfig
17+
{
18+
public class S3Config
19+
{
20+
public string BucketName { get; set; } = ""my-bucket"";
21+
}
22+
23+
public S3Config S3 { get; set; } = new S3Config();
24+
}
25+
26+
public AmazonConfig Amazon { get; set; } = new AmazonConfig();
27+
}
28+
29+
class Program
30+
{
31+
public Uri GetGetPresignedUrl(string objectKey)
32+
{
33+
var s3Provider = new S3Provider();
34+
var appConfiguration = new AppConfiguration();
35+
var timeProvider = new TimeProvider();
36+
37+
return new Uri(
38+
uriString: s3Provider.GetGetPresignedUrl(
39+
bucketName: appConfiguration.Amazon.S3.BucketName,
40+
objectKey: objectKey,
41+
expires: timeProvider.GetLocalNow().AddHours(value: 24)
42+
)
43+
);
44+
}
45+
}",

Posseth.NamedArguments.AnalyzerAndFixer.CodeFixes/PossethNamedArgumentsAnalyzerAndFixerCodeFixProvider.cs

Lines changed: 32 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Threading;
66
using System.Threading.Tasks;
77
using Microsoft.CodeAnalysis;
8-
using System.Collections.Generic;
98
using System.Collections.Immutable;
109
using Microsoft.CodeAnalysis.CSharp;
1110
using Microsoft.CodeAnalysis.CodeFixes;
@@ -43,32 +42,28 @@ private async Task<Document> UseNamedArgumentAsync(Document document, ArgumentSy
4342
{
4443
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
4544
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
45+
5146
var argumentList = argument.Parent as ArgumentListSyntax;
5247
if (argumentList == null)
53-
return document.WithSyntaxRoot(processedRoot);
54-
48+
return document.WithSyntaxRoot(root);
49+
5550
var invocation = argumentList.Parent;
56-
51+
5752
// Handle both direct invocations and object creation expressions
58-
if (invocation == null ||
59-
!(invocation is InvocationExpressionSyntax ||
53+
if (invocation == null ||
54+
!(invocation is InvocationExpressionSyntax ||
6055
invocation is ObjectCreationExpressionSyntax))
6156
{
62-
return document.WithSyntaxRoot(processedRoot);
57+
return document.WithSyntaxRoot(root);
6358
}
6459

65-
// Find the corresponding argument in the processed root
66-
var currentArgument = FindCorrespondingNode(processedRoot, argument);
60+
// Find the corresponding argument in the current root
61+
var currentArgument = FindCorrespondingNode(root, argument);
6762
if (currentArgument == null)
68-
return document.WithSyntaxRoot(processedRoot);
63+
return document.WithSyntaxRoot(root);
6964

7065
IMethodSymbol methodSymbol = null;
71-
66+
7267
if (invocation is InvocationExpressionSyntax invocationExpr)
7368
{
7469
methodSymbol = semanticModel.GetSymbolInfo(invocationExpr).Symbol as IMethodSymbol;
@@ -80,29 +75,38 @@ private async Task<Document> UseNamedArgumentAsync(Document document, ArgumentSy
8075

8176
if (methodSymbol == null)
8277
{
83-
return document.WithSyntaxRoot(processedRoot);
78+
return document.WithSyntaxRoot(root);
8479
}
8580

8681
int parameterIndex = argumentList.Arguments.IndexOf(argument);
8782
if (parameterIndex >= methodSymbol.Parameters.Length)
8883
{
89-
return document.WithSyntaxRoot(processedRoot);
84+
return document.WithSyntaxRoot(root);
9085
}
9186

9287
var parameter = methodSymbol.Parameters[parameterIndex];
9388

9489
if (parameter == null)
9590
{
96-
return document.WithSyntaxRoot(processedRoot);
91+
return document.WithSyntaxRoot(root);
9792
}
9893

99-
// Create a new argument node with a NameColon
94+
// Preserve the original trivia (whitespace, indentation, etc.)
95+
SyntaxTriviaList leadingTrivia = currentArgument.GetLeadingTrivia();
96+
SyntaxTriviaList trailingTrivia = currentArgument.GetTrailingTrivia();
97+
98+
// Create the name colon with no trivia - trivia will be applied to the entire argument
99+
var nameColon = SyntaxFactory.NameColon(parameter.Name);
100+
101+
// Create a new argument node with a NameColon, preserving original trivia
100102
var namedArgument = SyntaxFactory.Argument(
101-
SyntaxFactory.NameColon(parameter.Name),
103+
nameColon,
102104
currentArgument.RefOrOutKeyword,
103-
currentArgument.Expression);
105+
currentArgument.Expression)
106+
.WithLeadingTrivia(leadingTrivia)
107+
.WithTrailingTrivia(trailingTrivia);
104108

105-
var newRoot = processedRoot.ReplaceNode(currentArgument, namedArgument);
109+
var newRoot = root.ReplaceNode(currentArgument, namedArgument);
106110

107111
return document.WithSyntaxRoot(newRoot);
108112
}
@@ -114,7 +118,7 @@ private ArgumentSyntax FindCorrespondingNode(SyntaxNode root, ArgumentSyntax ori
114118
var nodeAtSamePosition = root.FindNode(originalNode.Span);
115119
if (nodeAtSamePosition is ArgumentSyntax arg)
116120
return arg;
117-
121+
118122
// If location-based search failed, try finding by structure
119123
var parentList = originalNode.Parent as ArgumentListSyntax;
120124
if (parentList != null)
@@ -123,67 +127,15 @@ private ArgumentSyntax FindCorrespondingNode(SyntaxNode root, ArgumentSyntax ori
123127
var newParentList = root.DescendantNodes()
124128
.OfType<ArgumentListSyntax>()
125129
.FirstOrDefault(a => a.Span.Contains(parentList.Span));
126-
130+
127131
if (newParentList != null && index >= 0 && index < newParentList.Arguments.Count)
128132
return newParentList.Arguments[index];
129133
}
130-
131-
return null;
132-
}
133134

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));
135+
return null;
149136
}
150137

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-
}
188138
}
139+
140+
189141
}

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

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

1010
<PropertyGroup>
1111
<PackageId>Posseth.NamedArguments.AnalyzerAndFixer</PackageId>
12-
<PackageVersion>1.0.1.1</PackageVersion>
12+
<PackageVersion>1.0.1.2</PackageVersion>
1313
<Authors>MPCoreDeveloper</Authors>
1414
<PackageLicenseExpression>MIT</PackageLicenseExpression>
1515
<PackageProjectUrl>https://github.com/MPCoreDeveloper/Posseth.NamedArguments.AnalyzerAndFixer</PackageProjectUrl>
@@ -24,7 +24,7 @@
2424
<NoPackageAnalysis>true</NoPackageAnalysis>
2525
<TargetsForTfmSpecificContentInPackage>$(TargetsForTfmSpecificContentInPackage);_AddAnalyzersToOutput</TargetsForTfmSpecificContentInPackage>
2626
<Title>Named Arguments Analyzer and Fixer</Title>
27-
<Version>1.1.1</Version>
27+
<Version>1.1.2</Version>
2828
<Company>Posseth Software</Company>
2929
<Product>Posseth.NamedArguments.AnalyzerAndFixer</Product>
3030
<PackageIcon>logo.jpeg</PackageIcon>

Posseth.NamedArguments.AnalyzerAndFixer.Test/NestedInvocationTests.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,21 +99,20 @@ public Uri GetGetPresignedUrl(string objectKey)
9999
var timeProvider = new TimeProvider();
100100
101101
return new Uri(
102-
s3Provider.GetGetPresignedUrl(
103-
bucketName: appConfiguration.Amazon.S3.BucketName,
104-
objectKey: objectKey,
105-
expires: timeProvider.GetLocalNow().AddHours(value: 24))
102+
uriString: s3Provider.GetGetPresignedUrl(
103+
bucketName: appConfiguration.Amazon.S3.BucketName,
104+
objectKey: objectKey,
105+
expires: timeProvider.GetLocalNow().AddHours(value: 24)
106+
)
106107
);
107108
}
108109
}",
109110
ExpectedDiagnostics = {
110-
// Arguments in the GetGetPresignedUrl method
111111
VerifyCS.Diagnostic(NamedArgumentsAnalyzer.DiagnosticId).WithLocation(39, 17).WithArguments("bucketName"),
112112
VerifyCS.Diagnostic(NamedArgumentsAnalyzer.DiagnosticId).WithLocation(40, 17).WithArguments("objectKey"),
113113
VerifyCS.Diagnostic(NamedArgumentsAnalyzer.DiagnosticId).WithLocation(41, 17).WithArguments("expires"),
114-
115-
// Arguments in the AddHours method
116114
VerifyCS.Diagnostic(NamedArgumentsAnalyzer.DiagnosticId).WithLocation(41, 53).WithArguments("value"),
115+
VerifyCS.Diagnostic(NamedArgumentsAnalyzer.DiagnosticId).WithLocation(38, 13).WithArguments("uriString"),
117116
},
118117
};
119118

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

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

33
<PropertyGroup>
4-
<TargetFramework>net8.0</TargetFramework>
5-
4+
<TargetFramework>net9.0</TargetFramework>
5+
<LangVersion>latest</LangVersion>
66
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
77
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
88
</PropertyGroup>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using Microsoft.CodeAnalysis.Testing;
2+
using Microsoft.VisualStudio.TestTools.UnitTesting;
3+
using System.Collections.Generic;
4+
using System.Threading.Tasks;
5+
using VerifyCS = Posseth.NamedArguments.AnalyzerAndFixer.Test.CSharpCodeFixVerifier<
6+
Posseth.NamedArguments.AnalyzerAndFixer.NamedArgumentsAnalyzer,
7+
Posseth.NamedArguments.AnalyzerAndFixer.NamedArgumentsCodeFixProvider>;
8+
namespace Posseth.NamedArguments.AnalyzerAndFixer.Test
9+
{
10+
[TestClass]
11+
public class RecordAnalyzerTests
12+
{
13+
14+
15+
[TestMethod]
16+
public async Task Test_RecordUsage_ShouldRequireNamedArguments()
17+
{
18+
var testCode = @"
19+
using System;
20+
21+
class Program
22+
{
23+
public record Person(string Name, int Age);
24+
25+
void TestMethod()
26+
{
27+
var person = new Person(""John"", 30);
28+
}
29+
}";
30+
31+
// Voeg de IsExternalInit-stub toe
32+
var isExternalInitStub = @"
33+
namespace System.Runtime.CompilerServices
34+
{
35+
internal static class IsExternalInit { }
36+
}";
37+
38+
// Stel de test in
39+
var test = new VerifyCS.Test
40+
{
41+
TestCode = testCode,
42+
};
43+
test.TestState.Sources.Add(isExternalInitStub);
44+
45+
// Verwacht diagnostics
46+
test.ExpectedDiagnostics.Add(
47+
VerifyCS.Diagnostic(NamedArgumentsAnalyzer.DiagnosticId).WithSpan(10, 33, 10, 39).WithArguments("Name"));
48+
test.ExpectedDiagnostics.Add(
49+
VerifyCS.Diagnostic(NamedArgumentsAnalyzer.DiagnosticId).WithSpan(10, 41, 10, 43).WithArguments("Age"));
50+
51+
await test.RunAsync();
52+
}
53+
}
54+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#if NETSTANDARD2_0
2+
namespace System.Runtime.CompilerServices
3+
{
4+
internal static class IsExternalInit { }
5+
}
6+
#endif

0 commit comments

Comments
 (0)