@@ -43,117 +43,118 @@ public override FixAllProvider GetFixAllProvider() =>
4343
4444 public override async Task RegisterCodeFixesAsync ( CodeFixContext context )
4545 {
46- // only handle the first diagnostic for this registration
47- var diagnostic = context . Diagnostics . FirstOrDefault ( ) ;
48- if ( diagnostic == null )
46+ Diagnostic ? diagnostic = context . Diagnostics . FirstOrDefault ( ) ;
47+ if ( diagnostic is null )
4948 return ;
5049
51- var root = await context . Document . GetSyntaxRootAsync ( context . CancellationToken ) . ConfigureAwait ( false ) ;
50+ SyntaxNode ? root = await context . Document . GetSyntaxRootAsync ( context . CancellationToken ) . ConfigureAwait ( false ) ;
5251 if ( root is null )
5352 return ;
5453
5554 // the diagnostic in the analyzer is reported on the member access (e.g. "x.ToString")
5655 // but we need the whole invocation (e.g. "x.ToString(...)"). So find the invocation
5756 // by walking ancestors if needed.
58- var node = root . FindNode ( diagnostic . Location . SourceSpan ) ;
57+ SyntaxNode ? node = root . FindNode ( diagnostic . Location . SourceSpan ) ;
5958 if ( node is null )
6059 return ;
6160
62- var invocation = node as InvocationExpressionSyntax
63- ?? node . AncestorsAndSelf ( ) . OfType < InvocationExpressionSyntax > ( ) . FirstOrDefault ( ) ;
64-
65- if ( invocation is null )
61+ if ( node is not ExpressionSyntax exprNode )
6662 return ;
6763
68- var semanticModel = await context . Document . GetSemanticModelAsync ( context . CancellationToken ) . ConfigureAwait ( false ) ;
64+ SemanticModel ? semanticModel = await context . Document . GetSemanticModelAsync ( context . CancellationToken ) . ConfigureAwait ( false ) ;
6965 if ( semanticModel is null )
7066 return ;
7167
72- var memberAccess = node as MemberAccessExpressionSyntax
73- ?? node . AncestorsAndSelf ( ) . OfType < MemberAccessExpressionSyntax > ( ) . FirstOrDefault ( ) ;
74-
75- if ( memberAccess is null )
68+ if ( ! TryGetJ2NTypeAndMember ( semanticModel , exprNode , out var j2nTypeName , out var memberAccess ) )
7669 return ;
7770
78- // Determine the type name for J2N (Single or Double)
79- var typeInfo = semanticModel . GetTypeInfo ( memberAccess . Expression , context . CancellationToken ) ;
80- var type = typeInfo . Type ;
81-
82- string ? j2nTypeName = type ? . SpecialType switch
83- {
84- SpecialType . System_Single => "Single" ,
85- SpecialType . System_Double => "Double" ,
86- _ => null
87- } ;
88-
89- if ( j2nTypeName == null )
90- return ;
91-
92- // Build the code element string
9371 string codeElement = $ "J2N.Numerics.{ j2nTypeName } .ToString(...)";
9472
95- // Use the helper to register the code fix
9673 context . RegisterCodeFix (
9774 CodeActionHelper . CreateFromResource (
9875 CodeFixResources . UseX ,
99- c => ReplaceWithJ2NToStringAsync ( context . Document , invocation , c ) ,
76+ c => ReplaceWithJ2NToStringAsync ( context . Document , memberAccess , c ) ,
10077 "UseJ2NToString" ,
10178 codeElement ) ,
10279 diagnostic ) ;
103-
10480 }
10581
10682 private async Task < Document > ReplaceWithJ2NToStringAsync (
10783 Document document ,
108- InvocationExpressionSyntax invocation ,
84+ MemberAccessExpressionSyntax memberAccess ,
10985 CancellationToken cancellationToken )
11086 {
87+ SemanticModel ? semanticModel = await document . GetSemanticModelAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
88+ if ( semanticModel is null )
89+ return document ;
11190
112- if ( invocation . Expression is not MemberAccessExpressionSyntax memberAccess )
91+ if ( ! TryGetJ2NTypeAndMember ( semanticModel , memberAccess , out var j2nTypeName , out _ ) )
11392 return document ;
11493
115- var semanticModel = await document . GetSemanticModelAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
116- if ( semanticModel is null )
94+ // Build J2N.Numerics.Single/Double.ToString
95+ MemberAccessExpressionSyntax j2nToStringAccess = SyntaxFactory . MemberAccessExpression (
96+ SyntaxKind . SimpleMemberAccessExpression ,
97+ SyntaxFactory . MemberAccessExpression (
98+ SyntaxKind . SimpleMemberAccessExpression ,
99+ SyntaxFactory . IdentifierName ( "J2N" ) ,
100+ SyntaxFactory . IdentifierName ( "Numerics" ) ) ,
101+ SyntaxFactory . IdentifierName ( j2nTypeName ) )
102+ . WithAdditionalAnnotations ( Formatter . Annotation ) ;
103+
104+ MemberAccessExpressionSyntax fullAccess = SyntaxFactory . MemberAccessExpression (
105+ SyntaxKind . SimpleMemberAccessExpression ,
106+ j2nToStringAccess ,
107+ SyntaxFactory . IdentifierName ( "ToString" ) ) ;
108+
109+ // Build invocation: J2N.Numerics.<Single|Double>.ToString(<expr>, <original args...>)
110+ if ( memberAccess . Parent is not InvocationExpressionSyntax invocation )
117111 return document ;
118112
119- var typeInfo = semanticModel . GetTypeInfo ( memberAccess . Expression , cancellationToken ) ;
120- var type = typeInfo . Type ;
113+ var newArgs = new List < ArgumentSyntax > { SyntaxFactory . Argument ( memberAccess . Expression ) } ;
114+ if ( invocation . ArgumentList != null )
115+ newArgs . AddRange ( invocation . ArgumentList . Arguments ) ;
121116
122- string ? j2nTypeName = type ? . SpecialType switch
123- {
124- SpecialType . System_Single => "Single" ,
125- SpecialType . System_Double => "Double" ,
126- _ => null
127- } ;
117+ InvocationExpressionSyntax newInvocation = SyntaxFactory . InvocationExpression (
118+ fullAccess ,
119+ SyntaxFactory . ArgumentList ( SyntaxFactory . SeparatedList ( newArgs ) ) )
120+ . WithTriviaFrom ( invocation ) // safe now
121+ . WithAdditionalAnnotations ( Formatter . Annotation ) ;
128122
129- if ( j2nTypeName == null )
130- return document ; // unsupported type
123+ DocumentEditor editor = await DocumentEditor . CreateAsync ( document , cancellationToken ) . ConfigureAwait ( false ) ;
124+ editor . ReplaceNode ( memberAccess . Parent , newInvocation ) ;
131125
132- // Build J2N.Numerics.Single.ToString
133- var j2n = SyntaxFactory . IdentifierName ( "J2N" ) ;
134- var numerics = SyntaxFactory . IdentifierName ( "Numerics" ) ;
135- var single = SyntaxFactory . IdentifierName ( j2nTypeName ) ;
136- var toString = SyntaxFactory . IdentifierName ( "ToString" ) ;
126+ return editor . GetChangedDocument ( ) ;
127+ }
137128
138- // Build the chain: J2N.Numerics.Single.ToString
139- var j2nNumerics = SyntaxFactory . MemberAccessExpression ( SyntaxKind . SimpleMemberAccessExpression , j2n , numerics ) ;
140- var j2nNumericsSingle = SyntaxFactory . MemberAccessExpression ( SyntaxKind . SimpleMemberAccessExpression , j2nNumerics , single ) ;
141- var j2nToStringAccess = SyntaxFactory . MemberAccessExpression ( SyntaxKind . SimpleMemberAccessExpression , j2nNumericsSingle , toString ) ;
129+ private static bool TryGetJ2NTypeAndMember (
130+ SemanticModel semanticModel ,
131+ ExpressionSyntax expr ,
132+ out string j2nTypeName ,
133+ out MemberAccessExpressionSyntax memberAccess )
134+ {
135+ memberAccess = expr as MemberAccessExpressionSyntax
136+ ?? expr . AncestorsAndSelf ( ) . OfType < MemberAccessExpressionSyntax > ( ) . FirstOrDefault ( ) ;
142137
138+ if ( memberAccess is null )
139+ {
140+ j2nTypeName = null ! ; // we always return false when the value is null, so we can ignore it here.
141+ return false ;
142+ }
143143
144- // Build invocation: J2N.Numerics.<Single|Double>.ToString(<expr>, <original args...>)
145- var newArgs = new List < ArgumentSyntax > { SyntaxFactory . Argument ( memberAccess . Expression ) } ;
146- if ( invocation . ArgumentList != null )
147- newArgs . AddRange ( invocation . ArgumentList . Arguments ) ;
144+ var typeInfo = semanticModel . GetTypeInfo ( memberAccess . Expression ) ;
145+ var type = typeInfo . Type ;
148146
149- var newInvocation = SyntaxFactory . InvocationExpression ( j2nToStringAccess , SyntaxFactory . ArgumentList ( SyntaxFactory . SeparatedList ( newArgs ) ) )
150- . WithTriviaFrom ( invocation )
151- . WithAdditionalAnnotations ( Formatter . Annotation ) ;
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+ } ;
152153
153- var editor = await DocumentEditor . CreateAsync ( document , cancellationToken ) . ConfigureAwait ( false ) ;
154- editor . ReplaceNode ( invocation , newInvocation ) ;
154+ if ( j2nTypeName is null )
155+ return false ;
155156
156- return editor . GetChangedDocument ( ) ;
157+ return true ;
157158 }
158159 }
159160}
0 commit comments