1- /*
1+ /*
22 * Licensed to the Apache Software Foundation (ASF) under one or more
33 * contributor license agreements. See the NOTICE file distributed with
44 * this work for additional information regarding copyright ownership.
@@ -36,94 +36,239 @@ namespace Lucene.Net.CodeAnalysis.Dev.CodeFixes
3636 public class LuceneDev1001_FloatingPointFormattingCSCodeFixProvider : CodeFixProvider
3737 {
3838 public override ImmutableArray < string > FixableDiagnosticIds =>
39- [ Descriptors . LuceneDev1001_FloatingPointFormatting . Id ] ;
39+ [
40+ Descriptors . LuceneDev1001_FloatingPointFormatting . Id ,
41+ Descriptors . LuceneDev1006_FloatingPointFormatting . Id
42+ ] ;
4043
4144 public override FixAllProvider GetFixAllProvider ( ) =>
4245 WellKnownFixAllProviders . BatchFixer ;
4346
4447 public override async Task RegisterCodeFixesAsync ( CodeFixContext context )
4548 {
46- Diagnostic ? diagnostic = context . Diagnostics . FirstOrDefault ( ) ;
47- if ( diagnostic is null )
48- return ;
49-
5049 SyntaxNode ? root = await context . Document . GetSyntaxRootAsync ( context . CancellationToken ) . ConfigureAwait ( false ) ;
5150 if ( root is null )
5251 return ;
5352
54- // the diagnostic in the analyzer is reported on the member access (e.g. "x.ToString")
55- // but we need the whole invocation (e.g. "x.ToString(...)"). So find the invocation
56- // by walking ancestors if needed.
57- SyntaxNode ? node = root . FindNode ( diagnostic . Location . SourceSpan ) ;
58- if ( node is null )
53+ SemanticModel ? semanticModel = await context . Document . GetSemanticModelAsync ( context . CancellationToken ) . ConfigureAwait ( false ) ;
54+ if ( semanticModel is null )
5955 return ;
6056
61- if ( node is not ExpressionSyntax exprNode )
57+ foreach ( Diagnostic diagnostic in context . Diagnostics )
58+ {
59+ SyntaxNode ? node = root . FindNode ( diagnostic . Location . SourceSpan , getInnermostNodeForTie : true ) ;
60+ if ( node is null )
61+ continue ;
62+
63+ if ( diagnostic . Id == Descriptors . LuceneDev1001_FloatingPointFormatting . Id )
64+ {
65+ RegisterExplicitToStringFix ( context , semanticModel , diagnostic , node ) ;
66+ }
67+ else if ( diagnostic . Id == Descriptors . LuceneDev1006_FloatingPointFormatting . Id )
68+ {
69+ RegisterStringEmbeddingFix ( context , semanticModel , diagnostic , node ) ;
70+ }
71+ }
72+ }
73+
74+ private void RegisterExplicitToStringFix (
75+ CodeFixContext context ,
76+ SemanticModel semanticModel ,
77+ Diagnostic diagnostic ,
78+ SyntaxNode node )
79+ {
80+ if ( node is not ExpressionSyntax expression )
6281 return ;
6382
64- SemanticModel ? semanticModel = await context . Document . GetSemanticModelAsync ( context . CancellationToken ) . ConfigureAwait ( false ) ;
65- if ( semanticModel is null )
83+ if ( ! TryGetJ2NTypeAndMember ( semanticModel , expression , out var j2nTypeName , out var memberAccess ) )
6684 return ;
6785
68- if ( ! TryGetJ2NTypeAndMember ( semanticModel , exprNode , out var j2nTypeName , out var memberAccess ) )
86+ string codeElement = $ "J2N.Numerics.{ j2nTypeName } .ToString(...)";
87+
88+ context . RegisterCodeFix (
89+ CodeActionHelper . CreateFromResource (
90+ CodeFixResources . UseX ,
91+ c => ReplaceExplicitToStringAsync ( context . Document , memberAccess , j2nTypeName , c ) ,
92+ "UseJ2NToString" ,
93+ codeElement ) ,
94+ diagnostic ) ;
95+ }
96+
97+ private void RegisterStringEmbeddingFix (
98+ CodeFixContext context ,
99+ SemanticModel semanticModel ,
100+ Diagnostic diagnostic ,
101+ SyntaxNode node )
102+ {
103+ ExpressionSyntax ? expression = node as ExpressionSyntax ?? node . AncestorsAndSelf ( ) . OfType < ExpressionSyntax > ( ) . FirstOrDefault ( ) ;
104+ if ( expression is null )
105+ return ;
106+
107+ if ( ! TryGetFloatingPointTypeName ( semanticModel . GetTypeInfo ( expression , context . CancellationToken ) , out var j2nTypeName ) )
69108 return ;
70109
71110 string codeElement = $ "J2N.Numerics.{ j2nTypeName } .ToString(...)";
72111
112+ InterpolationSyntax ? interpolation = expression . AncestorsAndSelf ( ) . OfType < InterpolationSyntax > ( ) . FirstOrDefault ( ) ;
113+ if ( interpolation is not null )
114+ {
115+ context . RegisterCodeFix (
116+ CodeActionHelper . CreateFromResource (
117+ CodeFixResources . UseX ,
118+ c => ReplaceInterpolationExpressionAsync ( context . Document , interpolation , expression , j2nTypeName , c ) ,
119+ "UseJ2NToString" ,
120+ codeElement ) ,
121+ diagnostic ) ;
122+
123+ return ;
124+ }
125+
73126 context . RegisterCodeFix (
74127 CodeActionHelper . CreateFromResource (
75128 CodeFixResources . UseX ,
76- c => ReplaceWithJ2NToStringAsync ( context . Document , memberAccess , c ) ,
129+ c => ReplaceConcatenationExpressionAsync ( context . Document , expression , j2nTypeName , c ) ,
77130 "UseJ2NToString" ,
78131 codeElement ) ,
79132 diagnostic ) ;
80133 }
81134
82- private async Task < Document > ReplaceWithJ2NToStringAsync (
135+ private async Task < Document > ReplaceExplicitToStringAsync (
83136 Document document ,
84137 MemberAccessExpressionSyntax memberAccess ,
138+ string j2nTypeName ,
85139 CancellationToken cancellationToken )
86140 {
87- SemanticModel ? semanticModel = await document . GetSemanticModelAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
88- if ( semanticModel is null )
141+ if ( memberAccess . Parent is not InvocationExpressionSyntax invocation )
89142 return document ;
90143
91- if ( ! TryGetJ2NTypeAndMember ( semanticModel , memberAccess , out var j2nTypeName , out _ ) )
92- return document ;
144+ var newArguments = new List < ArgumentSyntax >
145+ {
146+ SyntaxFactory . Argument ( memberAccess . Expression . WithoutTrivia ( ) )
147+ } ;
148+
149+ if ( invocation . ArgumentList is not null )
150+ newArguments . AddRange ( invocation . ArgumentList . Arguments ) ;
151+
152+ InvocationExpressionSyntax replacement = CreateJ2NToStringInvocation ( j2nTypeName , newArguments )
153+ . WithTriviaFrom ( invocation ) ;
154+
155+ DocumentEditor editor = await DocumentEditor . CreateAsync ( document , cancellationToken ) . ConfigureAwait ( false ) ;
156+ editor . ReplaceNode ( invocation , replacement ) ;
157+
158+ return editor . GetChangedDocument ( ) ;
159+ }
160+
161+ private async Task < Document > ReplaceConcatenationExpressionAsync (
162+ Document document ,
163+ ExpressionSyntax expression ,
164+ string j2nTypeName ,
165+ CancellationToken cancellationToken )
166+ {
167+ var arguments = new List < ArgumentSyntax >
168+ {
169+ SyntaxFactory . Argument ( expression . WithoutTrivia ( ) )
170+ } ;
171+
172+ InvocationExpressionSyntax replacement = CreateJ2NToStringInvocation ( j2nTypeName , arguments )
173+ . WithLeadingTrivia ( expression . GetLeadingTrivia ( ) )
174+ . WithTrailingTrivia ( expression . GetTrailingTrivia ( ) ) ;
93175
94- // Build J2N.Numerics.Single/Double.ToString
95- MemberAccessExpressionSyntax j2nToStringAccess = SyntaxFactory . MemberAccessExpression (
176+ DocumentEditor editor = await DocumentEditor . CreateAsync ( document , cancellationToken ) . ConfigureAwait ( false ) ;
177+ editor . ReplaceNode ( expression , replacement ) ;
178+
179+ return editor . GetChangedDocument ( ) ;
180+ }
181+
182+ private async Task < Document > ReplaceInterpolationExpressionAsync (
183+ Document document ,
184+ InterpolationSyntax interpolation ,
185+ ExpressionSyntax expression ,
186+ string j2nTypeName ,
187+ CancellationToken cancellationToken )
188+ {
189+ var arguments = new List < ArgumentSyntax >
190+ {
191+ SyntaxFactory . Argument ( expression . WithoutTrivia ( ) )
192+ } ;
193+
194+ var updatedInterpolation = interpolation ;
195+ var alignmentClause = interpolation . AlignmentClause ;
196+
197+ if ( interpolation . FormatClause is not null )
198+ {
199+ var formatToken = interpolation . FormatClause . FormatStringToken ;
200+ var formatLiteral = SyntaxFactory . LiteralExpression (
201+ SyntaxKind . StringLiteralExpression ,
202+ SyntaxFactory . Literal ( formatToken . ValueText ) ) ;
203+ arguments . Add ( SyntaxFactory . Argument ( formatLiteral ) ) ;
204+ updatedInterpolation = updatedInterpolation . WithFormatClause ( null ) ;
205+ }
206+
207+ InvocationExpressionSyntax replacementExpression = CreateJ2NToStringInvocation ( j2nTypeName , arguments )
208+ . WithLeadingTrivia ( expression . GetLeadingTrivia ( ) )
209+ . WithTrailingTrivia ( expression . GetTrailingTrivia ( ) ) ;
210+
211+ updatedInterpolation = updatedInterpolation . WithExpression ( replacementExpression ) ;
212+
213+ if ( alignmentClause is not null )
214+ {
215+ updatedInterpolation = updatedInterpolation . WithAlignmentClause ( alignmentClause ) ;
216+ }
217+
218+ updatedInterpolation = updatedInterpolation . WithAdditionalAnnotations ( Formatter . Annotation ) ;
219+
220+ DocumentEditor editor = await DocumentEditor . CreateAsync ( document , cancellationToken ) . ConfigureAwait ( false ) ;
221+ editor . ReplaceNode ( interpolation , updatedInterpolation ) ;
222+
223+ return editor . GetChangedDocument ( ) ;
224+ }
225+
226+ private static InvocationExpressionSyntax CreateJ2NToStringInvocation (
227+ string j2nTypeName ,
228+ IEnumerable < ArgumentSyntax > arguments )
229+ {
230+ MemberAccessExpressionSyntax j2nTypeAccess = SyntaxFactory . MemberAccessExpression (
96231 SyntaxKind . SimpleMemberAccessExpression ,
97232 SyntaxFactory . MemberAccessExpression (
98233 SyntaxKind . SimpleMemberAccessExpression ,
99234 SyntaxFactory . IdentifierName ( "J2N" ) ,
100235 SyntaxFactory . IdentifierName ( "Numerics" ) ) ,
101- SyntaxFactory . IdentifierName ( j2nTypeName ) )
102- . WithAdditionalAnnotations ( Formatter . Annotation ) ;
236+ SyntaxFactory . IdentifierName ( j2nTypeName ) ) ;
103237
104- MemberAccessExpressionSyntax fullAccess = SyntaxFactory . MemberAccessExpression (
238+ MemberAccessExpressionSyntax toStringAccess = SyntaxFactory . MemberAccessExpression (
105239 SyntaxKind . SimpleMemberAccessExpression ,
106- j2nToStringAccess ,
240+ j2nTypeAccess ,
107241 SyntaxFactory . IdentifierName ( "ToString" ) ) ;
108242
109- // Build invocation: J2N.Numerics.<Single|Double>.ToString(<expr>, <original args...>)
110- if ( memberAccess . Parent is not InvocationExpressionSyntax invocation )
111- return document ;
243+ return SyntaxFactory . InvocationExpression (
244+ toStringAccess ,
245+ SyntaxFactory . ArgumentList ( SyntaxFactory . SeparatedList ( arguments ) ) )
246+ . WithAdditionalAnnotations ( Formatter . Annotation ) ;
247+ }
112248
113- var newArgs = new List < ArgumentSyntax > { SyntaxFactory . Argument ( memberAccess . Expression ) } ;
114- if ( invocation . ArgumentList != null )
115- newArgs . AddRange ( invocation . ArgumentList . Arguments ) ;
116249
117- InvocationExpressionSyntax newInvocation = SyntaxFactory . InvocationExpression (
118- fullAccess ,
119- SyntaxFactory . ArgumentList ( SyntaxFactory . SeparatedList ( newArgs ) ) )
120- . WithTriviaFrom ( invocation ) // safe now
121- . WithAdditionalAnnotations ( Formatter . Annotation ) ;
250+ private static bool TryGetFloatingPointTypeName ( TypeInfo typeInfo , out string typeName )
251+ {
252+ if ( TryGetFloatingPointTypeName ( typeInfo . Type , out typeName ) )
253+ return true ;
122254
123- DocumentEditor editor = await DocumentEditor . CreateAsync ( document , cancellationToken ) . ConfigureAwait ( false ) ;
124- editor . ReplaceNode ( memberAccess . Parent , newInvocation ) ;
255+ if ( TryGetFloatingPointTypeName ( typeInfo . ConvertedType , out typeName ) )
256+ return true ;
125257
126- return editor . GetChangedDocument ( ) ;
258+ typeName = null ! ;
259+ return false ;
260+ }
261+
262+ private static bool TryGetFloatingPointTypeName ( ITypeSymbol ? typeSymbol , out string typeName )
263+ {
264+ typeName = typeSymbol ? . SpecialType switch
265+ {
266+ SpecialType . System_Single => "Single" ,
267+ SpecialType . System_Double => "Double" ,
268+ _ => null !
269+ } ;
270+
271+ return typeName is not null ;
127272 }
128273
129274 private static bool TryGetJ2NTypeAndMember (
@@ -137,21 +282,11 @@ private static bool TryGetJ2NTypeAndMember(
137282
138283 if ( memberAccess is null )
139284 {
140- j2nTypeName = null ! ; // we always return false when the value is null, so we can ignore it here.
285+ j2nTypeName = null ! ;
141286 return false ;
142287 }
143288
144- var typeInfo = semanticModel . GetTypeInfo ( memberAccess . Expression ) ;
145- var type = typeInfo . Type ;
146-
147- j2nTypeName = type ? . SpecialType switch
148- {
149- SpecialType . System_Single => "Single" ,
150- SpecialType . System_Double => "Double" ,
151- _ => null ! // we always return false when the value is null, so we can ignore it here.
152- } ;
153-
154- if ( j2nTypeName is null )
289+ if ( ! TryGetFloatingPointTypeName ( semanticModel . GetTypeInfo ( memberAccess . Expression ) , out j2nTypeName ) )
155290 return false ;
156291
157292 return true ;
0 commit comments