diff --git a/README.md b/README.md
index 13109c8..9a7eb15 100644
--- a/README.md
+++ b/README.md
@@ -73,6 +73,8 @@ OrchardCoreContrib.PoExtractor assumes, the code follows several conventions:
* `IStringLocalizer` or a derived class is accessed via a field named `S` (This is a convention used in Orchard Core)
* `IHtmlLocalizer` or a derived class is accessed via a field named `H` (This is a convention used in Orchard Core)
* `IStringLocalizer` or `IHtmlLocalizer` is accessed via a field named `T` (This is a older convention used in Orchard Core)
+* `LocalizedString` or `LocalizedHtmlString` instances created directly with a string literal name, e.g., `new LocalizedString("Text", "Text")` or `new LocalizedHtmlString("Text", "Text")`, are extracted using the name as the message ID. The single-argument form, e.g., `new LocalizedString("Text")` or `new LocalizedHtmlString("Text")`, is also supported
+* `LocalizedString.Create("Text")` and `LocalizedHtmlString.Create("Text")` factory calls (and the `LocalizedStringExtensions.Create("Text")` and `LocalizedHtmlStringExtensions.Create("Text")` forms) are extracted using the first argument as the message ID. Any other arguments, such as format arguments, e.g., `LocalizedString.Create("Hello {0}", name)`, are ignored
* Liquid templates use the filter named `t` (This is a convention used in Fluid)
* context of the localizable string is the full name (with namespace) of the containing class for C# or VB code
* context of the localizable string is the dot-delimited relative path the to view for Razor templates
diff --git a/src/OrchardCoreContrib.PoExtractor.DotNet.CS/CSharpProjectProcessor.cs b/src/OrchardCoreContrib.PoExtractor.DotNet.CS/CSharpProjectProcessor.cs
index 72e9a0c..22b3731 100644
--- a/src/OrchardCoreContrib.PoExtractor.DotNet.CS/CSharpProjectProcessor.cs
+++ b/src/OrchardCoreContrib.PoExtractor.DotNet.CS/CSharpProjectProcessor.cs
@@ -33,6 +33,7 @@ public virtual void Process(string path, string basePath, LocalizableStringColle
[
new SingularStringExtractor(csharpMetadataProvider),
new PluralStringExtractor(csharpMetadataProvider),
+ new LocalizedStringExtractor(csharpMetadataProvider),
new ErrorMessageAnnotationStringExtractor(csharpMetadataProvider),
new DisplayAttributeDescriptionStringExtractor(csharpMetadataProvider),
new DisplayAttributeNameStringExtractor(csharpMetadataProvider),
diff --git a/src/OrchardCoreContrib.PoExtractor.DotNet.CS/ExpressionSyntaxExtensions.cs b/src/OrchardCoreContrib.PoExtractor.DotNet.CS/ExpressionSyntaxExtensions.cs
new file mode 100644
index 0000000..1df85cc
--- /dev/null
+++ b/src/OrchardCoreContrib.PoExtractor.DotNet.CS/ExpressionSyntaxExtensions.cs
@@ -0,0 +1,37 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace OrchardCoreContrib.PoExtractor.DotNet.CS;
+
+internal static class ExpressionSyntaxExtensions
+{
+ ///
+ /// Gets the value of a string literal, or of string literals joined with the + operator.
+ ///
+ /// The .
+ /// The string value.
+ public static bool TryGetString(this ExpressionSyntax expression, out string value)
+ {
+ if (expression is LiteralExpressionSyntax literal && literal.IsKind(SyntaxKind.StringLiteralExpression))
+ {
+ value = literal.Token.ValueText;
+
+ return true;
+ }
+
+ if (expression is BinaryExpressionSyntax binary &&
+ binary.IsKind(SyntaxKind.AddExpression) &&
+ binary.Left.TryGetString(out var left) &&
+ binary.Right.TryGetString(out var right))
+ {
+ value = left + right;
+
+ return true;
+ }
+
+ value = null;
+
+ return false;
+ }
+}
diff --git a/src/OrchardCoreContrib.PoExtractor.DotNet.CS/LocalizedStringExtractor.cs b/src/OrchardCoreContrib.PoExtractor.DotNet.CS/LocalizedStringExtractor.cs
new file mode 100644
index 0000000..2fbce7d
--- /dev/null
+++ b/src/OrchardCoreContrib.PoExtractor.DotNet.CS/LocalizedStringExtractor.cs
@@ -0,0 +1,69 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System;
+using System.Linq;
+
+namespace OrchardCoreContrib.PoExtractor.DotNet.CS;
+
+///
+/// Extracts with the singular text from the C# AST node
+///
+///
+/// The localizable string is identified by the constructor call - new LocalizedString("TEXT TO TRANSLATE", "TEXT TO TRANSLATE")
+/// or new LocalizedHtmlString("TEXT TO TRANSLATE", "TEXT TO TRANSLATE"). The single argument form
+/// new LocalizedString("TEXT TO TRANSLATE") or new LocalizedHtmlString("TEXT TO TRANSLATE") is also supported.
+/// The factory method call - LocalizedString.Create("TEXT TO TRANSLATE") or LocalizedHtmlString.Create("TEXT TO TRANSLATE")
+/// is also supported, including the LocalizedStringExtensions.Create("TEXT TO TRANSLATE") and
+/// LocalizedHtmlStringExtensions.Create("TEXT TO TRANSLATE") forms.
+///
+///
+/// Creates a new instance of a .
+///
+/// The .
+public class LocalizedStringExtractor(IMetadataProvider metadataProvider) : LocalizableStringExtractor(metadataProvider)
+{
+ ///
+ public override bool TryExtract(SyntaxNode node, out LocalizableStringOccurence result)
+ {
+ ArgumentNullException.ThrowIfNull(node);
+
+ result = null;
+
+ var argumentList = node switch
+ {
+ ObjectCreationExpressionSyntax creation when IsTypeName(creation.Type, LocalizedStringTypes.TypeNames) => creation.ArgumentList,
+ InvocationExpressionSyntax invocation when IsFactoryMethod(invocation.Expression) => invocation.ArgumentList,
+ _ => null
+ };
+
+ if (argumentList != null &&
+ argumentList.Arguments.Count > 0 &&
+ argumentList.Arguments[0].Expression.TryGetString(out var value))
+ {
+ result = CreateLocalizedString(value, null, node);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool IsFactoryMethod(ExpressionSyntax expression)
+ => expression is MemberAccessExpressionSyntax memberAccess &&
+ memberAccess.Name.Identifier.Text == LocalizedStringTypes.FactoryMethodName &&
+ IsTypeName(memberAccess.Expression, LocalizedStringTypes.FactoryTypeNames);
+
+ private static bool IsTypeName(ExpressionSyntax expression, string[] typeNames)
+ {
+ var name = expression switch
+ {
+ IdentifierNameSyntax identifierName => identifierName.Identifier.Text,
+ QualifiedNameSyntax qualifiedName => qualifiedName.Right.Identifier.Text,
+ AliasQualifiedNameSyntax aliasQualifiedName => aliasQualifiedName.Name.Identifier.Text,
+ MemberAccessExpressionSyntax memberAccess => memberAccess.Name.Identifier.Text,
+ _ => null
+ };
+
+ return name != null && typeNames.Contains(name);
+ }
+}
diff --git a/src/OrchardCoreContrib.PoExtractor.DotNet.CS/SingularStringExtractor.cs b/src/OrchardCoreContrib.PoExtractor.DotNet.CS/SingularStringExtractor.cs
index 43e1432..cb85e1e 100644
--- a/src/OrchardCoreContrib.PoExtractor.DotNet.CS/SingularStringExtractor.cs
+++ b/src/OrchardCoreContrib.PoExtractor.DotNet.CS/SingularStringExtractor.cs
@@ -1,5 +1,4 @@
using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Linq;
@@ -33,7 +32,7 @@ accessor.Expression is IdentifierNameSyntax identifierName &&
{
var argument = accessor.ArgumentList.Arguments.FirstOrDefault();
- if (argument != null && TryGetString(argument.Expression, out var value))
+ if (argument != null && argument.Expression.TryGetString(out var value))
{
result = CreateLocalizedString(value, null, node);
return true;
@@ -42,28 +41,4 @@ accessor.Expression is IdentifierNameSyntax identifierName &&
return false;
}
-
- private static bool TryGetString(ExpressionSyntax expression, out string value)
- {
- if (expression is LiteralExpressionSyntax literal && literal.IsKind(SyntaxKind.StringLiteralExpression))
- {
- value = literal.Token.ValueText;
-
- return true;
- }
-
- if (expression is BinaryExpressionSyntax binary &&
- binary.IsKind(SyntaxKind.AddExpression) &&
- TryGetString(binary.Left, out var left) &&
- TryGetString(binary.Right, out var right))
- {
- value = left + right;
-
- return true;
- }
-
- value = null;
-
- return false;
- }
}
diff --git a/src/OrchardCoreContrib.PoExtractor.DotNet.VB/LocalizedStringExtractor.cs b/src/OrchardCoreContrib.PoExtractor.DotNet.VB/LocalizedStringExtractor.cs
new file mode 100644
index 0000000..f026581
--- /dev/null
+++ b/src/OrchardCoreContrib.PoExtractor.DotNet.VB/LocalizedStringExtractor.cs
@@ -0,0 +1,74 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.VisualBasic;
+using Microsoft.CodeAnalysis.VisualBasic.Syntax;
+using System;
+using System.Linq;
+
+namespace OrchardCoreContrib.PoExtractor.DotNet.VB;
+
+///
+/// Extracts with the singular text from the VB AST node
+///
+///
+/// The localizable string is identified by the constructor call - New LocalizedString("TEXT TO TRANSLATE", "TEXT TO TRANSLATE")
+/// or New LocalizedHtmlString("TEXT TO TRANSLATE", "TEXT TO TRANSLATE"). The single argument form
+/// New LocalizedString("TEXT TO TRANSLATE") or New LocalizedHtmlString("TEXT TO TRANSLATE") is also supported.
+/// The factory method call - LocalizedString.Create("TEXT TO TRANSLATE") or LocalizedHtmlString.Create("TEXT TO TRANSLATE")
+/// is also supported, including the LocalizedStringExtensions.Create("TEXT TO TRANSLATE") and
+/// LocalizedHtmlStringExtensions.Create("TEXT TO TRANSLATE") forms.
+///
+///
+/// Creates a new instance of a .
+///
+/// The .
+public class LocalizedStringExtractor(IMetadataProvider metadataProvider) : LocalizableStringExtractor(metadataProvider)
+{
+ ///
+ public override bool TryExtract(SyntaxNode node, out LocalizableStringOccurence result)
+ {
+ ArgumentNullException.ThrowIfNull(node);
+
+ result = null;
+
+ var argumentList = node switch
+ {
+ ObjectCreationExpressionSyntax creation when IsTypeName(creation.Type, LocalizedStringTypes.TypeNames) => creation.ArgumentList,
+ InvocationExpressionSyntax invocation when IsFactoryMethod(invocation.Expression) => invocation.ArgumentList,
+ _ => null
+ };
+
+ if (argumentList != null &&
+ argumentList.Arguments.Count > 0 &&
+ argumentList.Arguments[0].GetExpression() is LiteralExpressionSyntax literal &&
+ literal.IsKind(SyntaxKind.StringLiteralExpression))
+ {
+ result = CreateLocalizedString(literal.Token.ValueText, null, node);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool IsFactoryMethod(ExpressionSyntax expression)
+ => expression is MemberAccessExpressionSyntax memberAccess &&
+ IsName(memberAccess.Name.Identifier.ValueText, LocalizedStringTypes.FactoryMethodName) &&
+ IsTypeName(memberAccess.Expression, LocalizedStringTypes.FactoryTypeNames);
+
+ private static bool IsTypeName(ExpressionSyntax expression, string[] typeNames)
+ {
+ var name = expression switch
+ {
+ IdentifierNameSyntax identifierName => identifierName.Identifier.ValueText,
+ QualifiedNameSyntax qualifiedName => qualifiedName.Right.Identifier.ValueText,
+ MemberAccessExpressionSyntax memberAccess => memberAccess.Name.Identifier.ValueText,
+ _ => null
+ };
+
+ return name != null && typeNames.Any(typeName => IsName(name, typeName));
+ }
+
+ // Visual Basic identifiers are case-insensitive.
+ private static bool IsName(string name, string expected)
+ => string.Equals(name, expected, StringComparison.OrdinalIgnoreCase);
+}
diff --git a/src/OrchardCoreContrib.PoExtractor.DotNet.VB/VisualBasicProjectProcessor.cs b/src/OrchardCoreContrib.PoExtractor.DotNet.VB/VisualBasicProjectProcessor.cs
index dc7c97f..e353933 100644
--- a/src/OrchardCoreContrib.PoExtractor.DotNet.VB/VisualBasicProjectProcessor.cs
+++ b/src/OrchardCoreContrib.PoExtractor.DotNet.VB/VisualBasicProjectProcessor.cs
@@ -33,6 +33,7 @@ public void Process(string path, string basePath, LocalizableStringCollection lo
[
new SingularStringExtractor(visualBasicMetadataProvider),
new PluralStringExtractor(visualBasicMetadataProvider),
+ new LocalizedStringExtractor(visualBasicMetadataProvider),
new ErrorMessageAnnotationStringExtractor(visualBasicMetadataProvider),
new DisplayAttributeDescriptionStringExtractor(visualBasicMetadataProvider),
new DisplayAttributeNameStringExtractor(visualBasicMetadataProvider),
diff --git a/src/OrchardCoreContrib.PoExtractor.DotNet/LocalizedStringTypes.cs b/src/OrchardCoreContrib.PoExtractor.DotNet/LocalizedStringTypes.cs
new file mode 100644
index 0000000..27eefb3
--- /dev/null
+++ b/src/OrchardCoreContrib.PoExtractor.DotNet/LocalizedStringTypes.cs
@@ -0,0 +1,52 @@
+namespace OrchardCoreContrib.PoExtractor.DotNet;
+
+///
+/// Represents a class that contains the names of the localized string types and their factory methods.
+///
+public static class LocalizedStringTypes
+{
+ ///
+ /// Gets the name of the type that represents a localized string.
+ ///
+ public static readonly string LocalizedStringTypeName = "LocalizedString";
+
+ ///
+ /// Gets the name of the type that represents a localized HTML string.
+ ///
+ public static readonly string LocalizedHtmlStringTypeName = "LocalizedHtmlString";
+
+ ///
+ /// Gets the name of the class that contains the LocalizedString factory method.
+ ///
+ public static readonly string LocalizedStringExtensionsTypeName = "LocalizedStringExtensions";
+
+ ///
+ /// Gets the name of the class that contains the LocalizedHtmlString factory method.
+ ///
+ public static readonly string LocalizedHtmlStringExtensionsTypeName = "LocalizedHtmlStringExtensions";
+
+ ///
+ /// Gets the name of the factory method that creates a localized string from its name.
+ ///
+ public static readonly string FactoryMethodName = "Create";
+
+ ///
+ /// Gets the names of the localized string types that can be created with a constructor.
+ ///
+ public static readonly string[] TypeNames =
+ [
+ LocalizedStringTypeName,
+ LocalizedHtmlStringTypeName
+ ];
+
+ ///
+ /// Gets the names of the types that contain the factory method.
+ ///
+ public static readonly string[] FactoryTypeNames =
+ [
+ LocalizedStringTypeName,
+ LocalizedHtmlStringTypeName,
+ LocalizedStringExtensionsTypeName,
+ LocalizedHtmlStringExtensionsTypeName
+ ];
+}
diff --git a/test/OrchardCoreContrib.PoExtractor.DotNet.CS.Tests/LocalizedStringExtractorTests.cs b/test/OrchardCoreContrib.PoExtractor.DotNet.CS.Tests/LocalizedStringExtractorTests.cs
new file mode 100644
index 0000000..788ba08
--- /dev/null
+++ b/test/OrchardCoreContrib.PoExtractor.DotNet.CS.Tests/LocalizedStringExtractorTests.cs
@@ -0,0 +1,92 @@
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using OrchardCoreContrib.PoExtractor.DotNet.CS.MetadataProviders;
+
+namespace OrchardCoreContrib.PoExtractor.DotNet.CS.Tests;
+
+public class LocalizedStringExtractorTests
+{
+ [Theory]
+ [InlineData("""new LocalizedString("Thing", "Thing");""", "Thing")]
+ [InlineData("""new LocalizedString("Thing");""", "Thing")]
+ [InlineData("""new LocalizedHtmlString("Thing");""", "Thing")]
+ [InlineData("""new LocalizedString("Thing", "Other thing");""", "Thing")]
+ [InlineData("""new LocalizedString("Thing", "Thing", true);""", "Thing")]
+ [InlineData("""new Microsoft.Extensions.Localization.LocalizedString("Thing", "Thing");""", "Thing")]
+ [InlineData("""new global::Microsoft.Extensions.Localization.LocalizedString("Thing", "Thing");""", "Thing")]
+ [InlineData("""new LocalizedString("my " + "text", "my text");""", "my text")]
+ [InlineData("""new LocalizedHtmlString("Thing", "Thing");""", "Thing")]
+ [InlineData("""new LocalizedHtmlString("Thing {0}", "Thing {0}", false, 1);""", "Thing {0}")]
+ [InlineData("""new Microsoft.AspNetCore.Mvc.Localization.LocalizedHtmlString("Thing", "Thing");""", "Thing")]
+ [InlineData("""LocalizedString.Create("Thing");""", "Thing")]
+ [InlineData("""LocalizedHtmlString.Create("Thing");""", "Thing")]
+ [InlineData("""LocalizedString.Create("my " + "text");""", "my text")]
+ [InlineData("""Microsoft.Extensions.Localization.LocalizedString.Create("Thing");""", "Thing")]
+ [InlineData("""global::Microsoft.AspNetCore.Mvc.Localization.LocalizedHtmlString.Create("Thing");""", "Thing")]
+ [InlineData("""LocalizedStringExtensions.Create("Thing");""", "Thing")]
+ [InlineData("""LocalizedHtmlStringExtensions.Create("Thing");""", "Thing")]
+ [InlineData("""LocalizedString.Create("Hello {0}", name);""", "Hello {0}")]
+ [InlineData("""LocalizedHtmlString.Create("{0} and {1}", first, second);""", "{0} and {1}")]
+ [InlineData("""LocalizedString.Create("Manage things", typeof(Permissions));""", "Manage things")]
+ [InlineData(
+ """
+ new LocalizedString(@"This is a multi-line
+ string.", "value");
+ """,
+ """
+ This is a multi-line
+ string.
+ """)]
+ public void ExtractString(string source, string expected)
+ {
+ // Arrange
+ var metadataProvider = new CSharpMetadataProvider("DummyBasePath");
+ var extractor = new LocalizedStringExtractor(metadataProvider);
+
+ var node = GetNode(source);
+
+ // Act
+ var extracted = extractor.TryExtract(node, out var result);
+
+ // Assert
+ Assert.True(extracted);
+ Assert.Equal(expected, result.Text);
+ Assert.Null(result.TextPlural);
+ }
+
+ [Theory]
+ [InlineData("""new LocalizedString(nameof(Thing), "Thing");""")]
+ [InlineData("""new LocalizedString(name, "Thing");""")]
+ [InlineData("""new LocalizedString();""")]
+ [InlineData("""new LocalizedHtmlString();""")]
+ [InlineData("""new LocalizedHtmlString(nameof(Thing), "Thing");""")]
+ [InlineData("""new Thing("Thing", "Thing");""")]
+ [InlineData("""new LocalizedStringExtensions("Thing");""")]
+ [InlineData("""LocalizedString.Create(nameof(Thing));""")]
+ [InlineData("""LocalizedString.Create(name);""")]
+ [InlineData("""LocalizedString.Create();""")]
+ [InlineData("""LocalizedString.Parse("Thing");""")]
+ [InlineData("""Thing.Create("Thing");""")]
+ public void ExtractString_NotLocalizedStringWithLiteralName_ReturnsFalse(string source)
+ {
+ // Arrange
+ var metadataProvider = new CSharpMetadataProvider("DummyBasePath");
+ var extractor = new LocalizedStringExtractor(metadataProvider);
+
+ var node = GetNode(source);
+
+ // Act
+ var extracted = extractor.TryExtract(node, out var result);
+
+ // Assert
+ Assert.False(extracted);
+ Assert.Null(result);
+ }
+
+ private static ExpressionSyntax GetNode(string source)
+ => CSharpSyntaxTree.ParseText(source, path: "DummyPath")
+ .GetRoot()
+ .DescendantNodes()
+ .OfType()
+ .First(node => node is ObjectCreationExpressionSyntax or InvocationExpressionSyntax);
+}
diff --git a/test/OrchardCoreContrib.PoExtractor.DotNet.VB.Tests/LocalizedStringExtractorTests.cs b/test/OrchardCoreContrib.PoExtractor.DotNet.VB.Tests/LocalizedStringExtractorTests.cs
new file mode 100644
index 0000000..2f6429d
--- /dev/null
+++ b/test/OrchardCoreContrib.PoExtractor.DotNet.VB.Tests/LocalizedStringExtractorTests.cs
@@ -0,0 +1,77 @@
+using Microsoft.CodeAnalysis.VisualBasic;
+using Microsoft.CodeAnalysis.VisualBasic.Syntax;
+using OrchardCoreContrib.PoExtractor.DotNet.VB.MetadataProviders;
+
+namespace OrchardCoreContrib.PoExtractor.DotNet.VB.Tests;
+
+public class LocalizedStringExtractorTests
+{
+ [Theory]
+ [InlineData("""New LocalizedString("Thing", "Thing")""", "Thing")]
+ [InlineData("""New LocalizedString("Thing")""", "Thing")]
+ [InlineData("""New LocalizedHtmlString("Thing")""", "Thing")]
+ [InlineData("""New LocalizedString("Thing", "Other thing")""", "Thing")]
+ [InlineData("""New localizedstring("Thing", "Thing")""", "Thing")]
+ [InlineData("""New Microsoft.Extensions.Localization.LocalizedString("Thing", "Thing")""", "Thing")]
+ [InlineData("""New LocalizedHtmlString("Thing", "Thing")""", "Thing")]
+ [InlineData("""New Microsoft.AspNetCore.Mvc.Localization.LocalizedHtmlString("Thing", "Thing")""", "Thing")]
+ [InlineData("""LocalizedString.Create("Thing")""", "Thing")]
+ [InlineData("""LocalizedHtmlString.Create("Thing")""", "Thing")]
+ [InlineData("""localizedstring.create("Thing")""", "Thing")]
+ [InlineData("""Microsoft.Extensions.Localization.LocalizedString.Create("Thing")""", "Thing")]
+ [InlineData("""LocalizedStringExtensions.Create("Thing")""", "Thing")]
+ [InlineData("""LocalizedHtmlStringExtensions.Create("Thing")""", "Thing")]
+ [InlineData("""LocalizedStringExtensions.Create("Hello {0}", name)""", "Hello {0}")]
+ [InlineData("""LocalizedStringExtensions.Create("Manage things", GetType(Permissions))""", "Manage things")]
+ public void ExtractString(string source, string expected)
+ {
+ // Arrange
+ var metadataProvider = new VisualBasicMetadataProvider("DummyBasePath");
+ var extractor = new LocalizedStringExtractor(metadataProvider);
+
+ var node = GetNode(source);
+
+ // Act
+ var extracted = extractor.TryExtract(node, out var result);
+
+ // Assert
+ Assert.True(extracted);
+ Assert.Equal(expected, result.Text);
+ Assert.Null(result.TextPlural);
+ }
+
+ [Theory]
+ [InlineData("""New LocalizedString(NameOf(Thing), "Thing")""")]
+ [InlineData("""New LocalizedString(name, "Thing")""")]
+ [InlineData("""New LocalizedString()""")]
+ [InlineData("""New LocalizedString""")]
+ [InlineData("""New LocalizedHtmlString(NameOf(Thing), "Thing")""")]
+ [InlineData("""New Thing("Thing", "Thing")""")]
+ [InlineData("""LocalizedString.Create(NameOf(Thing))""")]
+ [InlineData("""LocalizedString.Create(name)""")]
+ [InlineData("""LocalizedString.Create()""")]
+ [InlineData("""LocalizedString.Parse("Thing")""")]
+ [InlineData("""Thing.Create("Thing")""")]
+ public void ExtractString_NotLocalizedStringWithLiteralName_ReturnsFalse(string source)
+ {
+ // Arrange
+ var metadataProvider = new VisualBasicMetadataProvider("DummyBasePath");
+ var extractor = new LocalizedStringExtractor(metadataProvider);
+
+ var node = GetNode(source);
+
+ // Act
+ var extracted = extractor.TryExtract(node, out var result);
+
+ // Assert
+ Assert.False(extracted);
+ Assert.Null(result);
+ }
+
+ private static ExpressionSyntax GetNode(string source)
+ => VisualBasicSyntaxTree.ParseText($"Dim value = {source}", path: "DummyPath")
+ .GetRoot()
+ .DescendantNodes()
+ .OfType()
+ .First(node => node is ObjectCreationExpressionSyntax or InvocationExpressionSyntax);
+}