Skip to content

Commit 1b056cf

Browse files
authored
Merge pull request #25 from Flow-Launcher/old_translation1
Support string.Format culture info parameter analyzer & code fixer
2 parents f9a5720 + 780eb9f commit 1b056cf

File tree

2 files changed

+108
-49
lines changed

2 files changed

+108
-49
lines changed

Flow.Launcher.Localization.Analyzers/Localize/OldGetTranslateAnalyzer.cs

+60-26
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ namespace Flow.Launcher.Localization.Analyzers.Localize
1111
[DiagnosticAnalyzer(LanguageNames.CSharp)]
1212
public class OldGetTranslateAnalyzer : DiagnosticAnalyzer
1313
{
14+
#region DiagnosticAnalyzer
15+
1416
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
1517
AnalyzerDiagnostics.OldLocalizationApiUsed
1618
);
@@ -22,29 +24,42 @@ public override void Initialize(AnalysisContext context)
2224
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.InvocationExpression);
2325
}
2426

27+
#endregion
28+
29+
#region Analyze Methods
30+
2531
private static void AnalyzeNode(SyntaxNodeAnalysisContext context)
2632
{
2733
var invocationExpr = (InvocationExpressionSyntax)context.Node;
2834
var semanticModel = context.SemanticModel;
2935
var symbolInfo = semanticModel.GetSymbolInfo(invocationExpr);
3036

37+
// Check if the method is a format string call
3138
if (!(symbolInfo.Symbol is IMethodSymbol methodSymbol)) return;
3239

33-
if (IsFormatStringCall(methodSymbol) &&
34-
GetFirstArgumentInvocationExpression(invocationExpr) is InvocationExpressionSyntax innerInvocationExpr)
40+
// First branch: detect a call to string.Format containing a translate call anywhere in its arguments.
41+
if (IsFormatStringCall(methodSymbol))
3542
{
36-
if (!IsTranslateCall(semanticModel.GetSymbolInfo(innerInvocationExpr)) ||
37-
!(GetFirstArgumentStringValue(innerInvocationExpr) is string translationKey))
38-
return;
39-
40-
var diagnostic = Diagnostic.Create(
41-
AnalyzerDiagnostics.OldLocalizationApiUsed,
42-
invocationExpr.GetLocation(),
43-
translationKey,
44-
GetInvocationArguments(invocationExpr)
45-
);
46-
context.ReportDiagnostic(diagnostic);
43+
var arguments = invocationExpr.ArgumentList.Arguments;
44+
// Check all arguments is an invocation (i.e. a candidate for Context.API.GetTranslation(…))
45+
for (int i = 0; i < arguments.Count; i++)
46+
{
47+
if (GetArgumentInvocationExpression(invocationExpr, i) is InvocationExpressionSyntax innerInvocationExpr &&
48+
IsTranslateCall(semanticModel.GetSymbolInfo(innerInvocationExpr)) &&
49+
GetFirstArgumentStringValue(innerInvocationExpr) is string translationKey)
50+
{
51+
var diagnostic = Diagnostic.Create(
52+
AnalyzerDiagnostics.OldLocalizationApiUsed,
53+
invocationExpr.GetLocation(),
54+
translationKey,
55+
GetInvocationArguments(invocationExpr, i)
56+
);
57+
context.ReportDiagnostic(diagnostic);
58+
return;
59+
}
60+
}
4761
}
62+
// Second branch: direct translate call (outside of a Format call)
4863
else if (IsTranslateCall(methodSymbol) && GetFirstArgumentStringValue(invocationExpr) is string translationKey)
4964
{
5065
if (IsParentFormatStringCall(semanticModel, invocationExpr)) return;
@@ -59,27 +74,42 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context)
5974
}
6075
}
6176

62-
private static string GetInvocationArguments(InvocationExpressionSyntax invocationExpr) =>
63-
string.Join(", ", invocationExpr.ArgumentList.Arguments.Skip(1));
77+
#region Utils
6478

65-
private static bool IsParentFormatStringCall(SemanticModel semanticModel, SyntaxNode syntaxNode) =>
66-
syntaxNode is InvocationExpressionSyntax invocationExpressionSyntax &&
67-
invocationExpressionSyntax.Parent?.Parent?.Parent is SyntaxNode parent &&
68-
IsFormatStringCall(semanticModel?.GetSymbolInfo(parent));
79+
private static string GetInvocationArguments(InvocationExpressionSyntax invocationExpr, int translateArgIndex) =>
80+
string.Join(", ", invocationExpr.ArgumentList.Arguments.Skip(translateArgIndex + 1));
6981

70-
private static bool IsFormatStringCall(SymbolInfo? symbolInfo) =>
71-
symbolInfo is SymbolInfo info && IsFormatStringCall(info.Symbol as IMethodSymbol);
82+
/// <summary>
83+
/// Walk up the tree to see if we're already inside a Format call
84+
/// </summary>
85+
private static bool IsParentFormatStringCall(SemanticModel semanticModel, SyntaxNode syntaxNode)
86+
{
87+
var parent = syntaxNode.Parent;
88+
while (parent != null)
89+
{
90+
if (parent is InvocationExpressionSyntax parentInvocation)
91+
{
92+
var symbol = semanticModel.GetSymbolInfo(parentInvocation).Symbol as IMethodSymbol;
93+
if (IsFormatStringCall(symbol))
94+
{
95+
return true;
96+
}
97+
}
98+
parent = parent.Parent;
99+
}
100+
return false;
101+
}
72102

73103
private static bool IsFormatStringCall(IMethodSymbol methodSymbol) =>
74-
methodSymbol?.Name is Constants.StringFormatMethodName &&
75-
methodSymbol.ContainingType.ToDisplayString() is Constants.StringFormatTypeName;
104+
methodSymbol?.Name == Constants.StringFormatMethodName &&
105+
methodSymbol.ContainingType.ToDisplayString() == Constants.StringFormatTypeName;
76106

77-
private static InvocationExpressionSyntax GetFirstArgumentInvocationExpression(InvocationExpressionSyntax invocationExpr) =>
78-
invocationExpr.ArgumentList.Arguments.FirstOrDefault()?.Expression as InvocationExpressionSyntax;
107+
private static InvocationExpressionSyntax GetArgumentInvocationExpression(InvocationExpressionSyntax invocationExpr, int index) =>
108+
invocationExpr.ArgumentList.Arguments[index].Expression as InvocationExpressionSyntax;
79109

80110
private static bool IsTranslateCall(SymbolInfo symbolInfo) =>
81111
symbolInfo.Symbol is IMethodSymbol innerMethodSymbol &&
82-
innerMethodSymbol.Name is Constants.OldLocalizationMethodName &&
112+
innerMethodSymbol.Name == Constants.OldLocalizationMethodName &&
83113
Constants.OldLocalizationClasses.Contains(innerMethodSymbol.ContainingType.Name);
84114

85115
private static bool IsTranslateCall(IMethodSymbol methodSymbol) =>
@@ -92,5 +122,9 @@ private static string GetFirstArgumentStringValue(InvocationExpressionSyntax inv
92122
return syntax.Token.ValueText;
93123
return null;
94124
}
125+
126+
#endregion
127+
128+
#endregion
95129
}
96130
}

Flow.Launcher.Localization.Analyzers/Localize/OldGetTranslateAnalyzerCodeFixProvider.cs

+48-23
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ namespace Flow.Launcher.Localization.Analyzers.Localize
1414
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(OldGetTranslateAnalyzerCodeFixProvider)), Shared]
1515
public class OldGetTranslateAnalyzerCodeFixProvider : CodeFixProvider
1616
{
17+
#region CodeFixProvider
18+
1719
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(
1820
AnalyzerDiagnostics.OldLocalizationApiUsed.Id
1921
);
@@ -39,44 +41,63 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
3941
);
4042
}
4143

44+
#endregion
45+
46+
#region Fix Methods
47+
4248
private static Document FixOldTranslation(CodeFixContext context, SyntaxNode root, Diagnostic diagnostic)
4349
{
4450
var diagnosticSpan = diagnostic.Location.SourceSpan;
4551

52+
if (root is null) return context.Document;
53+
4654
var invocationExpr = root
47-
?.FindToken(diagnosticSpan.Start).Parent
48-
?.AncestorsAndSelf()
55+
.FindToken(diagnosticSpan.Start).Parent
56+
.AncestorsAndSelf()
4957
.OfType<InvocationExpressionSyntax>()
50-
.First();
58+
.FirstOrDefault();
5159

52-
if (invocationExpr is null || root is null) return context.Document;
60+
if (invocationExpr is null) return context.Document;
5361

5462
var argumentList = invocationExpr.ArgumentList.Arguments;
55-
var argument = argumentList.First().Expression;
56-
57-
if (GetTranslationKey(argument) is string translationKey)
58-
return FixOldTranslationWithoutStringFormat(context, translationKey, root, invocationExpr);
5963

60-
if (GetTranslationKeyFromInnerInvocation(argument) is string translationKeyInside)
61-
return FixOldTranslationWithStringFormat(context, argumentList, translationKeyInside, root, invocationExpr);
64+
// Loop through the arguments to find the translation key.
65+
for (int i = 0; i < argumentList.Count; i++)
66+
{
67+
var argument = argumentList[i].Expression;
68+
69+
// Case 1: The argument is a literal (direct GetTranslation("key"))
70+
if (GetTranslationKey(argument) is string translationKey)
71+
return FixOldTranslationWithoutStringFormat(context, translationKey, root, invocationExpr);
72+
73+
// Case 2: The argument is itself an invocation (nested GetTranslation)
74+
if (GetTranslationKeyFromInnerInvocation(argument) is string translationKeyInside)
75+
{
76+
// If there are arguments following this translation call, treat as a Format call.
77+
if (i < argumentList.Count - 1)
78+
return FixOldTranslationWithStringFormat(context, argumentList, translationKeyInside, root, invocationExpr, i);
79+
80+
// Otherwise, treat it as a direct translation call.
81+
return FixOldTranslationWithoutStringFormat(context, translationKeyInside, root, invocationExpr);
82+
}
83+
}
6284

6385
return context.Document;
6486
}
6587

88+
#region Utils
6689

6790
private static string GetTranslationKey(ExpressionSyntax syntax)
6891
{
69-
if (
70-
syntax is LiteralExpressionSyntax literalExpressionSyntax &&
71-
literalExpressionSyntax.Token.Value is string translationKey
72-
)
92+
if (syntax is LiteralExpressionSyntax literalExpressionSyntax &&
93+
literalExpressionSyntax.Token.Value is string translationKey)
7394
return translationKey;
7495
return null;
7596
}
7697

7798
private static Document FixOldTranslationWithoutStringFormat(
78-
CodeFixContext context, string translationKey, SyntaxNode root, InvocationExpressionSyntax invocationExpr
79-
) {
99+
CodeFixContext context, string translationKey, SyntaxNode root, InvocationExpressionSyntax invocationExpr)
100+
{
80101
var newInvocationExpr = SyntaxFactory.ParseExpression(
81102
$"{Constants.ClassName}.{translationKey}()"
82103
);
@@ -88,10 +109,8 @@ private static Document FixOldTranslationWithoutStringFormat(
88109

89110
private static string GetTranslationKeyFromInnerInvocation(ExpressionSyntax syntax)
90111
{
91-
if (
92-
syntax is InvocationExpressionSyntax invocationExpressionSyntax &&
93-
invocationExpressionSyntax.ArgumentList.Arguments.Count is 1
94-
)
112+
if (syntax is InvocationExpressionSyntax invocationExpressionSyntax &&
113+
invocationExpressionSyntax.ArgumentList.Arguments.Count == 1)
95114
{
96115
var firstArgument = invocationExpressionSyntax.ArgumentList.Arguments.First().Expression;
97116
return GetTranslationKey(firstArgument);
@@ -104,13 +123,19 @@ private static Document FixOldTranslationWithStringFormat(
104123
SeparatedSyntaxList<ArgumentSyntax> argumentList,
105124
string translationKey2,
106125
SyntaxNode root,
107-
InvocationExpressionSyntax invocationExpr
108-
) {
109-
var newArguments = string.Join(", ", argumentList.Skip(1).Select(a => a.Expression));
126+
InvocationExpressionSyntax invocationExpr,
127+
int translationArgIndex)
128+
{
129+
// Skip all arguments before and including the translation call
130+
var newArguments = string.Join(", ", argumentList.Skip(translationArgIndex + 1).Select(a => a.Expression));
110131
var newInnerInvocationExpr = SyntaxFactory.ParseExpression($"{Constants.ClassName}.{translationKey2}({newArguments})");
111132

112133
var newRoot = root.ReplaceNode(invocationExpr, newInnerInvocationExpr);
113134
return context.Document.WithSyntaxRoot(newRoot);
114135
}
136+
137+
#endregion
138+
139+
#endregion
115140
}
116141
}

0 commit comments

Comments
 (0)