Skip to content

Commit

Permalink
Merge pull request #60 from smoogipoo/settingsource-attribute
Browse files Browse the repository at this point in the history
Add analyser + codefix for SettingSourceAttribute
  • Loading branch information
peppy authored Nov 17, 2023
2 parents c424c56 + 5ee272d commit 52bb29a
Show file tree
Hide file tree
Showing 14 changed files with 166 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ protected async Task Check(string name)
$"{resources_namespace}.ResolvedAttribute.txt",
$"{resources_namespace}.LocalisableString.txt",
$"{resources_namespace}.TranslatableString.txt",
$"{resources_namespace}.SettingSourceAttribute.txt",
};

foreach (var f in resourceNames.Where(n => n.StartsWith($"{resources_namespace}.Analysers.{name}")))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class StringCanBeLocalisedAnalyserTests : AbstractAnalyserTests<StringCan
[Theory]
[InlineData("Attribute")]
[InlineData("DescriptionAttribute")]
[InlineData("SettingSourceAttribute")]
[InlineData("BasicString")]
[InlineData("EmptyString")]
[InlineData("InterpolatedString")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ public async Task RunTest(string name, bool brokenAnalyserConfigFiles = false)
{
("LocalisableString.cs", readResourceStream(assembly, $"{resources_namespace}.LocalisableString.txt")),
("TranslatableString.cs", readResourceStream(assembly, $"{resources_namespace}.TranslatableString.txt")),
("LocalisableDescriptionAttribute.cs", readResourceStream(assembly, $"{resources_namespace}.LocalisableDescriptionAttribute.txt"))
("LocalisableDescriptionAttribute.cs", readResourceStream(assembly, $"{resources_namespace}.LocalisableDescriptionAttribute.txt")),
("SettingSourceAttribute.cs", readResourceStream(assembly, $"{resources_namespace}.SettingSourceAttribute.txt")),
};

var sourceFiles = new List<(string filename, string content)>(requiredFiles);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class LocaliseClassStringCodeFixProviderTests : AbstractCodeFixProviderTe
[InlineData("DescriptionAttribute")]
[InlineData("StringWithApostrophe")]
[InlineData("SequentialCapitals")]
[InlineData("SettingSourceAttribute")]
public async Task Check(string name) => await RunTest(name);

[Theory]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using osu.Game.Configuration;

namespace Test
{
class Program
{
[SettingSource(typeof(object), [|"a"|])]
[SettingSource(typeof(object), [|"a"|], [|"b"|])]
[SettingSource([|"a"|])]
[SettingSource([|"a"|], [|"b"|])]
[SettingSource(typeof(object), [|"a"|], [|"b"|], 0)]
[SettingSource([|"a"|], [|"b"|], 0)]
static void Main()
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using osu.Framework.Localisation;

namespace TestProject.Localisation
{
public static class Class1Strings
{
private const string prefix = @"TestProject.Localisation.Class1";

/// <summary>
/// "abc"
/// </summary>
public static LocalisableString Abc => new TranslatableString(getKey(@"abc"), @"abc");

/// <summary>
/// "def"
/// </summary>
public static LocalisableString Def => new TranslatableString(getKey(@"def"), @"def");

private static string getKey(string key) => $@"{prefix}:{key}";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.ComponentModel;
using osu.Game.Configuration;
using TestProject.Localisation;

namespace Test
{
[SettingSource(typeof(Class1Strings), nameof(Class1Strings.Abc))]
[SettingSource(typeof(Class1Strings), nameof(Class1Strings.Abc), nameof(Class1Strings.Def))]
[SettingSource(typeof(Class1Strings), nameof(Class1Strings.Abc), nameof(Class1Strings.Def), 1)]
class Class1
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using osu.Framework.Localisation;

namespace TestProject.Localisation
{
public static class Class1Strings
{
private const string prefix = @"TestProject.Localisation.Class1";

/// <summary>
/// "abc"
/// </summary>
public static LocalisableString Abc => new TranslatableString(getKey(@"abc"), @"abc");

/// <summary>
/// "def"
/// </summary>
public static LocalisableString Def => new TranslatableString(getKey(@"def"), @"def");

private static string getKey(string key) => $@"{prefix}:{key}";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.ComponentModel;
using osu.Game.Configuration;

namespace Test
{
[SettingSource([|"abc"|])]
[SettingSource([|"abc"|], [|"def"|])]
[SettingSource([|"abc"|], [|"def"|], 1)]
class Class1
{
}
}
28 changes: 28 additions & 0 deletions LocalisationAnalyser.Tests/Resources/SettingSourceAttribute.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace osu.Game.Configuration
{
[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = true)]
public class SettingSourceAttribute : System.Attribute
{
public string Label { get; }

public string Description { get; }

public SettingSourceAttribute(System.Type declaringType, string label, string? description = null)
{
}

public SettingSourceAttribute(string? label, string? description = null)
{
}

public SettingSourceAttribute(System.Type declaringType, string label, string description, int orderPosition)
: this(declaringType, label, description)
{
}

public SettingSourceAttribute(string label, string description, int orderPosition)
: this(label, description)
{
}
}
}
11 changes: 8 additions & 3 deletions LocalisationAnalyser/Analysers/StringCanBeLocalisedAnalyser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.

using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
Expand All @@ -19,6 +18,12 @@ public class StringCanBeLocalisedAnalyser : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(DiagnosticRules.STRING_CAN_BE_LOCALISED);

Check warning on line 19 in LocalisationAnalyser/Analysers/StringCanBeLocalisedAnalyser.cs

View workflow job for this annotation

GitHub Actions / Deploy

Missing XML comment for publicly visible type or member 'StringCanBeLocalisedAnalyser.SupportedDiagnostics'

private static readonly string[] localisable_attributes =
{
"System.ComponentModel.DescriptionAttribute",
"osu.Game.Configuration.SettingSourceAttribute"
};

public override void Initialize(AnalysisContext context)

Check warning on line 27 in LocalisationAnalyser/Analysers/StringCanBeLocalisedAnalyser.cs

View workflow job for this annotation

GitHub Actions / Deploy

Missing XML comment for publicly visible type or member 'StringCanBeLocalisedAnalyser.Initialize(AnalysisContext)'
{
// See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/Analyzer%20Actions%20Semantics.md for more information
Expand All @@ -43,9 +48,9 @@ private void analyseString(SyntaxNodeAnalysisContext context)
if (literal.Parent?.Kind() == SyntaxKind.AttributeArgument)
{
SyntaxNode attributeSyntax = literal.FirstAncestorOrSelf<AttributeSyntax>();
string attributeName = context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol.ContainingType.ToString();
string attributeName = context.SemanticModel.GetTypeInfo(attributeSyntax).Type.ToString();

if (attributeName != typeof(DescriptionAttribute).FullName)
if (!localisable_attributes.Contains(attributeName))
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,22 @@ private async Task<Solution> addMember(Document document, SyntaxNode nodeToRepla

if (nodeToReplace.Parent.Kind() == SyntaxKind.AttributeArgument)
{
rootNode = rootNode.ReplaceNode(nodeToReplace.FirstAncestorOrSelf<AttributeSyntax>(), SyntaxGenerators.GenerateAttributeAccessSyntax(memberAccess));
rootNode = SyntaxGenerators.AddUsingDirectiveIfNotExisting((CompilationUnitSyntax)rootNode, SyntaxTemplates.FRAMEWORK_LOCALISATION_NAMESPACE);
AttributeArgumentSyntax argSyntax = (AttributeArgumentSyntax)nodeToReplace.Parent;
AttributeSyntax attributeSyntax = nodeToReplace.FirstAncestorOrSelf<AttributeSyntax>();
SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken);
string attributeName = semanticModel.GetTypeInfo(attributeSyntax).Type.ToString();

switch (attributeName)
{
case "System.ComponentModel.DescriptionAttribute":
rootNode = rootNode.ReplaceNode(attributeSyntax, SyntaxGenerators.GenerateLocalisableDescriptionAttributeAccessSyntax(memberAccess));
rootNode = SyntaxGenerators.AddUsingDirectiveIfNotExisting((CompilationUnitSyntax)rootNode, SyntaxTemplates.FRAMEWORK_LOCALISATION_NAMESPACE);
break;

case "osu.Game.Configuration.SettingSourceAttribute":
rootNode = rootNode.ReplaceNode(attributeSyntax, SyntaxGenerators.GenerateSettingSourceAttributeAccessSyntax(attributeSyntax, argSyntax, memberAccess));
break;
}
}
else
rootNode = rootNode.ReplaceNode(nodeToReplace, SyntaxGenerators.GenerateDirectAccessSyntax(memberAccess, parameterValues));
Expand Down
25 changes: 22 additions & 3 deletions LocalisationAnalyser/Localisation/SyntaxGenerators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,20 +173,39 @@ public static ExpressionSyntax GenerateDirectAccessSyntax(MemberAccessExpression
/// </summary>
/// <param name="memberAccess">The member to access.</param>
/// <returns>The <see cref="AttributeSyntax"/>.</returns>
public static AttributeSyntax GenerateAttributeAccessSyntax(MemberAccessExpressionSyntax memberAccess)
public static AttributeSyntax GenerateLocalisableDescriptionAttributeAccessSyntax(MemberAccessExpressionSyntax memberAccess)
=> SyntaxFactory.Attribute(
SyntaxFactory.IdentifierName(SyntaxTemplates.ATTRIBUTE_CONSTRUCTION_TYPE))
SyntaxFactory.IdentifierName(SyntaxTemplates.LOCALISABLE_DESCRIPTION_ATTRIBUTE_CONSTRUCTION_TYPE))
.WithArgumentList(
SyntaxFactory.AttributeArgumentList(
SyntaxFactory.SeparatedList(new[]
{
SyntaxFactory.AttributeArgument(
SyntaxFactory.TypeOfExpression(((IdentifierNameSyntax)memberAccess.Expression))),
SyntaxFactory.TypeOfExpression((IdentifierNameSyntax)memberAccess.Expression)),
SyntaxFactory.AttributeArgument(
SyntaxFactory.ParseExpression($"nameof({memberAccess.Expression}.{memberAccess.Name})"))
}
)));

public static AttributeSyntax GenerateSettingSourceAttributeAccessSyntax(AttributeSyntax attributeToReplace, AttributeArgumentSyntax argToReplace, MemberAccessExpressionSyntax memberAccess)
{
List<AttributeArgumentSyntax> newArguments =
attributeToReplace.ArgumentList.Arguments
.Select(arg => arg == argToReplace
? SyntaxFactory.AttributeArgument(
SyntaxFactory.ParseExpression($"nameof({memberAccess.Expression}.{memberAccess.Name})"))
: arg).ToList();

if (newArguments.First().Expression is not TypeOfExpressionSyntax)
newArguments.Insert(0, SyntaxFactory.AttributeArgument(SyntaxFactory.TypeOfExpression((IdentifierNameSyntax)memberAccess.Expression)));

return SyntaxFactory.Attribute(
SyntaxFactory.IdentifierName(SyntaxTemplates.SETTING_SOURCE_ATTRIBUTE_CONSTRUCTION_TYPE))
.WithArgumentList(
SyntaxFactory.AttributeArgumentList(
SyntaxFactory.SeparatedList(newArguments)));
}

/// <summary>
/// Checks for and adds a new using directive to the given <see cref="CompilationUnitSyntax"/> if required.
/// </summary>
Expand Down
4 changes: 3 additions & 1 deletion LocalisationAnalyser/Localisation/SyntaxTemplates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ public static class SyntaxTemplates
/// <summary>
/// The construction type of the localisable attribute which replaces <see cref="System.ComponentModel.DescriptionAttribute"/>.
/// </summary>
public const string ATTRIBUTE_CONSTRUCTION_TYPE = "LocalisableDescription";
public const string LOCALISABLE_DESCRIPTION_ATTRIBUTE_CONSTRUCTION_TYPE = "LocalisableDescription";

public const string SETTING_SOURCE_ATTRIBUTE_CONSTRUCTION_TYPE = "SettingSource";

/// <summary>
/// The path to localisations relative to the project directory.
Expand Down

0 comments on commit 52bb29a

Please sign in to comment.