Skip to content

Commit

Permalink
emit warning for unrecognized parameter-types
Browse files Browse the repository at this point in the history
  • Loading branch information
earloc committed Nov 12, 2022
1 parent 28acd7d commit e3196e6
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 37 deletions.
9 changes: 9 additions & 0 deletions src/TypealizR.SourceGenerators.Playground.Console/App.resx
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="?asd {:::??34}" xml:space="preserve">
<value>?asd {0}</value>
</data>
<data name="Broken {xy:z}" xml:space="preserve">
<value>Broken</value>
</data>
<data name="Hello {name:s}, today is {now:d}" xml:space="preserve">
<value>Hello {0}, today is {1}</value>
</data>
<data name="Hello, World!" xml:space="preserve">
<value>Hello, World!</value>
</data>
Expand Down
8 changes: 5 additions & 3 deletions src/TypealizR.SourceGenerators.Playground.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
Console.WriteLine(localize.Hello_World());
Console.WriteLine(localize.Hello_World1());
Console.WriteLine(localize.Hello_World2());
Console.WriteLine(localize.Hello_World2());

Console.WriteLine(localize.Hello__0(_0: "Arthur"));
Console.WriteLine(localize.Hello__UserName(UserName: "Arthur"));
Console.WriteLine(localize.Hello__0("Arthur"));
Console.WriteLine(localize.Hello__UserName(UserName: "Arthur"));
var today = DateOnly.FromDateTime(DateTime.Now);

Console.WriteLine(localize.Hello__name__today_is__now(name: "Arthur", now: today));
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,23 @@ namespace TypealizR.SourceGenerators.Tests;
public class ExtensionMethodParameterInfo_Tests
{
[Theory]
[InlineData("{0}", "object")]
[InlineData("{1}", "object")]
[InlineData("{userName}", "object")]
[InlineData("{count:int}", "int")]
[InlineData("{count:i}", "int")]
[InlineData("{userName:string}", "string")]
[InlineData("{userName:s}", "string")]
[InlineData("{now:DateTime}", "DateTime")]
[InlineData("{now:dt}", "DateTime")]
[InlineData("{now:DateTimeOffset}", "DateTimeOffset")]
[InlineData("{now:dto}", "DateTimeOffset")]
[InlineData("{today:DateOnly}", "DateOnly")]
[InlineData("{today:d}", "DateOnly")]
[InlineData("{now:TimeOnly}", "TimeOnly")]
[InlineData("{now:t}", "TimeOnly")]
[InlineData("{now:wtf}", "object")]
public void Parameter_Gets_Typed_As(string token, string expected)
[InlineData("{0}", "object", false)]
[InlineData("{1}", "object", false)]
[InlineData("{userName}", "object", false)]
[InlineData("{count:int}", "int", false)]
[InlineData("{count:i}", "int", false)]
[InlineData("{userName:string}", "string", false)]
[InlineData("{userName:s}", "string", false)]
[InlineData("{now:DateTime}", "DateTime", false)]
[InlineData("{now:dt}", "DateTime", false)]
[InlineData("{now:DateTimeOffset}", "DateTimeOffset", false)]
[InlineData("{now:dto}", "DateTimeOffset", false)]
[InlineData("{today:DateOnly}", "DateOnly", false)]
[InlineData("{today:d}", "DateOnly", false)]
[InlineData("{now:TimeOnly}", "TimeOnly", false)]
[InlineData("{now:t}", "TimeOnly", false)]
[InlineData("{now:wtf}", "object", true)]
public void Parameter_Gets_Typed_As(string token, string expected, bool expectInvalidTypeExpression)
{
var match = StringLocalizerExtensionMethodBuilder.parameterExpression.Match(token);
var name = match.Groups["name"].Value;
Expand All @@ -39,6 +39,12 @@ public void Parameter_Gets_Typed_As(string token, string expected)
var actual = sut.Type;

actual.Should().Be(expected);

if (expectInvalidTypeExpression)
{
sut.InvalidTypeExpression.Should().NotBeNullOrEmpty();
}

}
}

Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public void Keys_Ending_Up_To_Produce_Duplicate_MethodNames_Produce_Diagnostics(

var expected = new Diagnostic[]
{
ErrorCodes.AmbigiousRessourceKey_001010(SomeFileName, 20, duplicateKey, $"{firstMethod.Name}1"),
ErrorCodes.AmbigiousRessourceKey_001010(SomeFileName, duplicateKey, 20, $"{firstMethod.Name}1"),
}
.Select(x => x.ToString());

Expand Down Expand Up @@ -95,4 +95,25 @@ public void Emits_Warning_For_Generic_Parameter_Names(string input, params strin

actual.Should().BeEquivalentTo(expectedWarnings);
}

[Theory]
[InlineData("Hello {name:xyz}",
"Ressource-key 'Hello {name:xyz}' uses unrecognized parameter-type 'xyz'. Falling back to 'object'"
)]
[InlineData("Hello {0:xyz}",
"Ressource-key 'Hello {0:xyz}' uses the generic format-parameter '{0:xyz}'. Consider to to use a more meaningful name, instead",
"Ressource-key 'Hello {0:xyz}' uses unrecognized parameter-type 'xyz'. Falling back to 'object'"
)]
public void Emits_Warning_For_Unrecognized_Parameter_Type(string input, params string[] expectedWarnings)
{
var sut = new StringLocalizerExtensionClassBuilder(SomeFileName);

sut.WithMethodFor(input, "some value", 30);

var extensionClass = sut.Build(new("Name.Space", "TypeName"));

var actual = extensionClass.Warnings.Select(x => x.GetMessage());

actual.Should().BeEquivalentTo(expectedWarnings);
}
}
24 changes: 22 additions & 2 deletions src/TypealizR.SourceGenerators/ErrorCodes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ internal static Diagnostic TargetProjectRootDirectoryNotFound_000010() =>
Location.None
);

internal static Diagnostic AmbigiousRessourceKey_001010(string fileName, int lineNumber, string rawRessourceKey, string fallback) =>
internal static Diagnostic AmbigiousRessourceKey_001010(string fileName, string rawRessourceKey, int lineNumber, string fallback) =>
Diagnostic.Create(
new(id: "TYPEALIZR001010",
title: "AmbigiousRessourceKey",
Expand All @@ -41,7 +41,7 @@ internal static Diagnostic AmbigiousRessourceKey_001010(string fileName, int lin
rawRessourceKey, fallback
);

internal static Diagnostic UnnamedGenericParameter_001011(string fileName, int lineNumber, string rawRessourceKey, string parameterName) =>
internal static Diagnostic UnnamedGenericParameter_001011(string fileName, string rawRessourceKey, int lineNumber, string parameterName) =>
Diagnostic.Create(
new(id: "TYPEALIZR001011",
title: "UnnamedGenericParameter",
Expand All @@ -60,4 +60,24 @@ internal static Diagnostic UnnamedGenericParameter_001011(string fileName, int l
),
rawRessourceKey, parameterName
);

internal static Diagnostic UnrecognizedParameterType_001012(string fileName, string rawRessourceKey, int lineNumber, string parameterType) =>
Diagnostic.Create(
new(id: "TYPEALIZR001012",
title: "UnrecognizedParameterType",
messageFormat: "Ressource-key '{0}' uses unrecognized parameter-type '{1}'. Falling back to 'object'",
category: "Readability",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: "Encountered an unrecognized parameter-type"
),
Location.Create(fileName,
textSpan: new(rawRessourceKey.IndexOf(parameterType), parameterType.Length),
lineSpan: new(
start: new(line: lineNumber - 1, character: 0),
end: new(line: lineNumber - 1, character: rawRessourceKey.Length - 1)
)
),
rawRessourceKey, parameterType
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;
using TypealizR.SourceGenerators.Extensions;

namespace TypealizR.SourceGenerators.StringLocalizer;
Expand All @@ -14,16 +15,14 @@ internal class ExtensionMethodParameterInfo
public readonly string DisplayName;
public readonly string Declaration;
public readonly bool IsGeneric;
public readonly string InvalidTypeExpression;
public bool HasUnrecognizedParameterTypeExpression => !string.IsNullOrEmpty(InvalidTypeExpression);

public ExtensionMethodParameterInfo(string token, string name, string expression)
{
Token = token;
Type = "object";
Token = token;

if (!string.IsNullOrEmpty(expression))
{
Type = SanitizeType(expression);
}
(Type, InvalidTypeExpression) = TryDeriveTypeFrom(expression);

IsGeneric = int.TryParse(name, out var _);
Name = SanitizeName(name);
Expand All @@ -32,7 +31,23 @@ public ExtensionMethodParameterInfo(string token, string name, string expression
Declaration = $"{Type} {Name}";
}

private string SanitizeType(string type) => type switch
private (string, string) TryDeriveTypeFrom(string expression)
{
if (string.IsNullOrEmpty(expression))
{
return ("object", "");
}

var type = SanitizeType(expression);
if (type is not null)
{
return (type, "");
}

return ("object", expression);
}

private string? SanitizeType(string type) => type switch
{
"int" => "int",
"i" => "int",
Expand All @@ -46,7 +61,7 @@ public ExtensionMethodParameterInfo(string token, string name, string expression
"d" => "DateOnly",
"TimeOnly" => "TimeOnly",
"t" => "TimeOnly",
_ => "object"
_ => null
};

private string SanitizeName(string rawParameterName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public StringLocalizerExtensionClassBuilder(string fileName)
this.fileName = fileName;
}

private List<StringLocalizerExtensionMethodBuilder> methodBuilders = new();
private readonly List<StringLocalizerExtensionMethodBuilder> methodBuilders = new();
private readonly string fileName;

public StringLocalizerExtensionClassBuilder WithMethodFor(string key, string value, int lineNumber)
Expand All @@ -46,15 +46,26 @@ public ExtensionClassInfo Build(TypeInfo target)

var deduplicated = Deduplicate(fileName, methods);

var parameterWarnings = deduplicated
var genericParameterWarnings = deduplicated
.Methods
.SelectMany(method =>
method.Parameters
.Where(parameter => parameter.IsGeneric)
.Select(parameter => ErrorCodes.UnnamedGenericParameter_001011(fileName, method.LineNumber, method.RawRessourceName, parameter.Token))
);
.Select(parameter => ErrorCodes.UnnamedGenericParameter_001011(fileName, method.RawRessourceName, method.LineNumber, parameter.Token))
);

var allWarnings = deduplicated.Warnings.Concat(parameterWarnings);
var unrecognizedParameterTypeWarnings = deduplicated
.Methods
.SelectMany(method =>
method.Parameters
.Where(parameter => parameter.HasUnrecognizedParameterTypeExpression)
.Select(parameter => ErrorCodes.UnrecognizedParameterType_001012(fileName, method.RawRessourceName, method.LineNumber, parameter.InvalidTypeExpression))
);

var allWarnings = deduplicated.Warnings
.Concat(genericParameterWarnings)
.Concat(unrecognizedParameterTypeWarnings)
;

return new(target, deduplicated.Methods, allWarnings);
}
Expand All @@ -77,7 +88,7 @@ public ExtensionClassInfo Build(TypeInfo target)
foreach (var duplicate in methodGroup.Skip(1))
{
duplicate.DeduplicateWith(discriminator++);
warnings.Add(ErrorCodes.AmbigiousRessourceKey_001010(fileName, duplicate.LineNumber, duplicate.RawRessourceName, duplicate.Name));
warnings.Add(ErrorCodes.AmbigiousRessourceKey_001010(fileName, duplicate.RawRessourceName, duplicate.LineNumber, duplicate.Name));
}

deduplicatedMethods.AddRange(methodGroup);
Expand Down

0 comments on commit e3196e6

Please sign in to comment.