From 3d0ed615259c51ecd2ebc607e08c79409d835c16 Mon Sep 17 00:00:00 2001
From: Medeni Baykal <433724+Haplois@users.noreply.github.com>
Date: Tue, 16 Feb 2021 09:56:34 +0100
Subject: [PATCH] Added support for spaces and other special characters in
method names into the ManagedNameUtilities (#2738)
---
TestPlatform.sln | 14 +-
scripts/build.ps1 | 6 +
scripts/build/TestPlatform.Dependencies.props | 1 +
scripts/test.ps1 | 1 +
.../Helpers/ReflectionHelpers.cs | 77 +++++
.../ManagedNameHelper.Reflection.cs | 182 +++++++++++-
.../ManagedNameUtilities/ManagedNameParser.cs | 50 +++-
.../Resources/Resources.Designer.cs | 22 ++
.../Resources/Resources.resx | 8 +
.../Resources/xlf/Resources.cs.xlf | 10 +
.../Resources/xlf/Resources.de.xlf | 10 +
.../Resources/xlf/Resources.es.xlf | 10 +
.../Resources/xlf/Resources.fr.xlf | 10 +
.../Resources/xlf/Resources.it.xlf | 10 +
.../Resources/xlf/Resources.ja.xlf | 10 +
.../Resources/xlf/Resources.ko.xlf | 10 +
.../Resources/xlf/Resources.pl.xlf | 10 +
.../Resources/xlf/Resources.pt-BR.xlf | 10 +
.../Resources/xlf/Resources.ru.xlf | 10 +
.../Resources/xlf/Resources.tr.xlf | 10 +
.../Resources/xlf/Resources.xlf | 10 +
.../Resources/xlf/Resources.zh-Hans.xlf | 10 +
.../Resources/xlf/Resources.zh-Hant.xlf | 10 +
src/package/external/external.csproj | 3 +
.../ManagedNameParserTests.cs | 1 -
.../ManagedNameRoundTripTests.cs | 6 +-
.../ManagedNameUtilities/SpecialNameTests.cs | 43 +++
...Platform.AdapterUtilities.UnitTests.csproj | 1 +
test/TestAssets/CILProject/CILProject.proj | 68 +++++
test/TestAssets/CILProject/OrphanMethod.il | 279 ++++++++++++++++++
30 files changed, 875 insertions(+), 27 deletions(-)
create mode 100644 test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/ManagedNameUtilities/SpecialNameTests.cs
create mode 100644 test/TestAssets/CILProject/CILProject.proj
create mode 100644 test/TestAssets/CILProject/OrphanMethod.il
diff --git a/TestPlatform.sln b/TestPlatform.sln
index 321f401295..ce073a3a41 100644
--- a/TestPlatform.sln
+++ b/TestPlatform.sln
@@ -82,17 +82,17 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{EE49F5DC-5835-4AE3-B3BA-8BDE0AD56330}"
ProjectSection(SolutionItems) = preProject
scripts\build.ps1 = scripts\build.ps1
+ scripts\build.sh = scripts\build.sh
+ scripts\PortableToFullPdb.ps1 = scripts\PortableToFullPdb.ps1
scripts\stylecop.json = scripts\stylecop.json
scripts\stylecop.ruleset = scripts\stylecop.ruleset
scripts\stylecop.test.ruleset = scripts\stylecop.test.ruleset
scripts\test.ps1 = scripts\test.ps1
- scripts\verify-sign.ps1 = scripts\verify-sign.ps1
+ scripts\test.sh = scripts\test.sh
scripts\verify-nupkgs.ps1 = scripts\verify-nupkgs.ps1
- scripts\PortableToFullPdb.ps1 = scripts\PortableToFullPdb.ps1
+ scripts\verify-sign.ps1 = scripts\verify-sign.ps1
scripts\vsts-prebuild.ps1 = scripts\vsts-prebuild.ps1
scripts\write-release-notes.ps1 = scripts\write-release-notes.ps1
- scripts\build.sh = scripts\build.sh
- scripts\test.sh = scripts\test.sh
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E344E0A2-7715-4C7F-BAF7-D64EA94CB19B}"
@@ -818,6 +818,8 @@ Global
{9EFCEFB5-253E-4DE2-8A70-821D7B8189DF} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6}
{3A8080FB-9C93-45B9-8EB5-828DDC31FDF0} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6}
{BFF7714C-E5A3-4EEB-B04B-5FA47F29AD03} = {020E15EA-731F-4667-95AF-226671E0C3AE}
+ {0D4DF78D-7E5F-4516-B19F-E6AA71A1DBF4} = {EE49F5DC-5835-4AE3-B3BA-8BDE0AD56330}
+ {DED1590A-ED25-413B-8590-006A4DD5B2FD} = {EE49F5DC-5835-4AE3-B3BA-8BDE0AD56330}
{E344E0A2-7715-4C7F-BAF7-D64EA94CB19B} = {EE49F5DC-5835-4AE3-B3BA-8BDE0AD56330}
{DD9382B5-5EC4-4B3D-BEB7-95423731AE29} = {46250E12-4CF1-4051-B4A7-80C8C06E0068}
{156F8811-28BB-4EC7-87D9-434F10FB7DBE} = {46250E12-4CF1-4051-B4A7-80C8C06E0068}
@@ -829,7 +831,6 @@ Global
{CAE652AF-6801-425E-AAF3-AB20DE7DF88E} = {7D4082EA-7AC9-4DFB-98E8-C5E08BDC0EC3}
{FF80D706-8309-4E02-BAC0-D28B4CBCF600} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6}
{7D4082EA-7AC9-4DFB-98E8-C5E08BDC0EC3} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959}
- {DED1590A-ED25-413B-8590-006A4DD5B2FD} = {EE49F5DC-5835-4AE3-B3BA-8BDE0AD56330}
{E141A226-CC0A-4F26-BD17-4AE427D81C3B} = {D27E1CB4-C641-4C6C-A140-EF5F6215AE29}
{376C19DE-31E2-4FF6-88FC-0D0D6233C999} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6}
{3E698655-0701-482E-9AA7-F956F6337FC7} = {376C19DE-31E2-4FF6-88FC-0D0D6233C999}
@@ -842,7 +843,6 @@ Global
{65A25D6E-C9CC-4F45-8925-04087AC82634} = {B705537C-B82C-4A30-AFA5-6244D9A7DAEB}
{D9A30E32-D466-4EC5-B4F2-62E17562279B} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6}
{21DB138B-85B7-479E-91FE-01E0F972EC56} = {D9A30E32-D466-4EC5-B4F2-62E17562279B}
- {0D4DF78D-7E5F-4516-B19F-E6AA71A1DBF4} = {EE49F5DC-5835-4AE3-B3BA-8BDE0AD56330}
{8C068694-23A2-47A2-A0DD-DB82D0AF0142} = {376C19DE-31E2-4FF6-88FC-0D0D6233C999}
{24C7683D-2607-4901-B8EB-83A57E49E93D} = {376C19DE-31E2-4FF6-88FC-0D0D6233C999}
{69F5FF81-5615-4F06-B83C-FCF979BB84CA} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959}
@@ -850,7 +850,7 @@ Global
{236A71E3-01DA-4679-9DFF-16A8E079ACFF} = {5E7F18A8-F843-4C8A-AB02-4C7D9205C6CF}
{41248B96-6E15-4E5E-A78F-859897676814} = {020E15EA-731F-4667-95AF-226671E0C3AE}
{074F5BD6-DC05-460B-B78F-044D125FD787} = {D9A30E32-D466-4EC5-B4F2-62E17562279B}
- {DCD0C39E-C78C-4A44-B0BD-7325254A2E97} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6}
+ {DCD0C39E-C78C-4A44-B0BD-7325254A2E97} = {376C19DE-31E2-4FF6-88FC-0D0D6233C999}
{2DE99835-A3A3-4922-82AD-6D10D284816D} = {7D4082EA-7AC9-4DFB-98E8-C5E08BDC0EC3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
diff --git a/scripts/build.ps1 b/scripts/build.ps1
index d7af546be0..de6ebdf051 100644
--- a/scripts/build.ps1
+++ b/scripts/build.ps1
@@ -122,6 +122,7 @@ $env:MSBUILD_VERSION = "15.0"
Write-Verbose "Setup build configuration."
$TPB_Solution = "TestPlatform.sln"
$TPB_TestAssets_Solution = Join-Path $env:TP_ROOT_DIR "test\TestAssets\TestAssets.sln"
+$TPB_TestAssets_CILAssets = Join-Path $env:TP_ROOT_DIR "test\TestAssets\CILProject\CILProject.proj"
$TPB_TargetFramework45 = "net45"
$TPB_TargetFramework451 = "net451"
$TPB_TargetFramework472 = "net472"
@@ -277,6 +278,11 @@ function Invoke-Build
& $dotnetExe build $TPB_Solution --configuration $TPB_Configuration -v:minimal -p:Version=$TPB_Version -p:CIBuild=$TPB_CIBuild -p:LocalizedBuild=$TPB_LocalizedBuild -bl:TestPlatform.binlog
Write-Log ".. .. Build: Complete."
+ Write-Log ".. .. Build: Source: $TPB_TestAssets_CILAssets"
+ Write-Verbose "$dotnetExe build $TPB_TestAssets_CILAssets --configuration $TPB_Configuration -v:minimal -p:Version=$TPB_Version -p:CIBuild=$TPB_CIBuild"
+ & $dotnetExe build $TPB_TestAssets_CILAssets --configuration $TPB_Configuration -v:minimal -p:CIBuild=$TPB_CIBuild -p:LocalizedBuild=$TPB_LocalizedBuild -bl:"$($env:TP_ROOT_DIR)\CILAssets.binlog"
+ Write-Log ".. .. Build: Complete."
+
Set-ScriptFailedOnError
Write-Log "Invoke-Build: Complete. {$(Get-ElapsedTime($timer))}"
diff --git a/scripts/build/TestPlatform.Dependencies.props b/scripts/build/TestPlatform.Dependencies.props
index 6c49704de9..9d1b7bb08d 100644
--- a/scripts/build/TestPlatform.Dependencies.props
+++ b/scripts/build/TestPlatform.Dependencies.props
@@ -29,6 +29,7 @@
1.1.1
5.0.0
+ 5.0.0
9.0.1
4.7.63
16.9.0-preview-4267359
diff --git a/scripts/test.ps1 b/scripts/test.ps1
index 576849ddf3..62dd097e33 100644
--- a/scripts/test.ps1
+++ b/scripts/test.ps1
@@ -207,6 +207,7 @@ function Invoke-Test
}
else
{
+ Write-Log ".. . $testContainerName test container found. ($testOutputPath)"
$testContainers += ,"$testContainerPath"
}
}
diff --git a/src/Microsoft.TestPlatform.AdapterUtilities/Helpers/ReflectionHelpers.cs b/src/Microsoft.TestPlatform.AdapterUtilities/Helpers/ReflectionHelpers.cs
index 2b30b8c129..41631c5ef5 100644
--- a/src/Microsoft.TestPlatform.AdapterUtilities/Helpers/ReflectionHelpers.cs
+++ b/src/Microsoft.TestPlatform.AdapterUtilities/Helpers/ReflectionHelpers.cs
@@ -3,9 +3,12 @@
namespace Microsoft.TestPlatform.AdapterUtilities.Helpers
{
+ using Microsoft.TestPlatform.AdapterUtilities.ManagedNameUtilities;
using Microsoft.TestPlatform.AdapterUtilities.Resources;
using System;
+ using System.Globalization;
+ using System.Text;
internal static partial class ReflectionHelpers
{
@@ -17,5 +20,79 @@ private static void AssertSupport(T obj, string methodName, string className)
throw new NotImplementedException(string.Format(Resources.MethodNotImplementedOnPlatform, className, methodName));
}
}
+
+ internal static string ParseEscapedString(string escapedString)
+ {
+ var stringBuilder = new StringBuilder();
+ var end = 0;
+ for (int i = 0; i < escapedString.Length; i++)
+ {
+ if (escapedString[i] == '\'')
+ {
+ stringBuilder.Append(escapedString, end, i - end);
+ end = i = ParseEscapedStringSegment(escapedString, i + 1, stringBuilder);
+ }
+ }
+
+ if (stringBuilder.Length == 0)
+ {
+ return escapedString;
+ }
+
+ if (end != 0 && end < escapedString.Length)
+ {
+ stringBuilder.Append(escapedString, end, escapedString.Length - end);
+ }
+
+ return stringBuilder.ToString();
+ }
+
+ // Unescapes a C# style escaped string.
+ private static int ParseEscapedStringSegment(string escapedStringSegment, int pos, StringBuilder stringBuilder)
+ {
+ for (int i = pos; i < escapedStringSegment.Length; i++)
+ {
+ switch (escapedStringSegment[i])
+ {
+ case '\\':
+ if (escapedStringSegment[i + 1] == 'u')
+ {
+ char c;
+
+ try
+ {
+ var code = escapedStringSegment.Substring(i + 2, 4);
+ c = (char)Convert.ToInt32(code, 16);
+ }
+ catch
+ {
+ throw new InvalidManagedNameException(
+ string.Format(CultureInfo.CurrentCulture, Resources.ErrorInvalidSequenceAt, escapedStringSegment, i)
+ );
+ }
+
+ stringBuilder.Append(c);
+ i += 5;
+ }
+ else
+ {
+ stringBuilder.Append(escapedStringSegment[i + 1]);
+ i += 1;
+ }
+
+ break;
+
+ case '\'':
+ return i + 1;
+
+ default:
+ stringBuilder.Append(escapedStringSegment[i]);
+ break;
+ }
+ }
+
+ string message = string.Format(CultureInfo.CurrentCulture, Resources.ErrorNoClosingQuote, escapedStringSegment);
+ throw new InvalidManagedNameException(message);
+ }
}
}
diff --git a/src/Microsoft.TestPlatform.AdapterUtilities/ManagedNameUtilities/ManagedNameHelper.Reflection.cs b/src/Microsoft.TestPlatform.AdapterUtilities/ManagedNameUtilities/ManagedNameHelper.Reflection.cs
index 93a0defca5..92421b6dff 100644
--- a/src/Microsoft.TestPlatform.AdapterUtilities/ManagedNameUtilities/ManagedNameHelper.Reflection.cs
+++ b/src/Microsoft.TestPlatform.AdapterUtilities/ManagedNameUtilities/ManagedNameHelper.Reflection.cs
@@ -80,15 +80,15 @@ public static void GetManagedName(MethodBase method, out string managedTypeName,
method = ((MethodInfo)method).GetGenericMethodDefinition();
}
- var methodBuilder = new StringBuilder();
var typeBuilder = new StringBuilder();
+ var methodBuilder = new StringBuilder();
// Namespace and Type Name (with arity designation)
AppendTypeString(typeBuilder, semanticType, closedType: false);
// Method Name with method arity
- methodBuilder.Append(method.Name);
var arity = method.GetGenericArguments().Length;
+ AppendMethodString(methodBuilder, method.Name, arity);
if (arity > 0)
{
methodBuilder.Append('`');
@@ -143,12 +143,14 @@ public static MethodBase GetMethod(Assembly assembly, string managedTypeName, st
{
Type type;
+ var parsedManagedTypeName = ReflectionHelpers.ParseEscapedString(managedTypeName);
+
#if !NETSTANDARD1_0 && !NETSTANDARD1_3 && !WINDOWS_UWP
- type = assembly.GetType(managedTypeName, throwOnError: false, ignoreCase: false);
+ type = assembly.GetType(parsedManagedTypeName, throwOnError: false, ignoreCase: false);
#else
try
{
- type = assembly.GetType(managedTypeName);
+ type = assembly.GetType(parsedManagedTypeName);
}
catch
{
@@ -158,14 +160,14 @@ public static MethodBase GetMethod(Assembly assembly, string managedTypeName, st
if (type == null)
{
- string message = string.Format(CultureInfo.CurrentCulture, Resources.ErrorTypeNotFound, managedTypeName);
+ string message = string.Format(CultureInfo.CurrentCulture, Resources.ErrorTypeNotFound, parsedManagedTypeName);
throw new InvalidManagedNameException(message);
}
MethodInfo method = null;
ManagedNameParser.ParseManagedMethodName(managedMethodName, out var methodName, out var methodArity, out var parameterTypes);
-#if NET20 || NET35
+#if NET20 || NET35
if (!IsNullOrWhiteSpace(methodName))
#else
if (!string.IsNullOrWhiteSpace(methodName))
@@ -205,7 +207,7 @@ bool filter(MemberInfo mbr, object param)
for (int i = 0; i < paramList.Length; i++)
{
- if (TypeString(paramList[i].ParameterType, closedType: true) != parameterTypes[i])
+ if (GetTypeString(paramList[i].ParameterType, closedType: true) != parameterTypes[i])
{
return false;
}
@@ -254,7 +256,7 @@ private static void AppendTypeString(StringBuilder b, Type type, bool closedType
}
else
{
- b.Append(type.Namespace);
+ AppendNamespace(b, type.Namespace);
b.Append('.');
AppendNestedTypeName(b, type);
@@ -266,14 +268,131 @@ private static void AppendTypeString(StringBuilder b, Type type, bool closedType
}
}
- private static void AppendNestedTypeName(StringBuilder b, Type type)
+ private static void AppendNamespace(StringBuilder b, string namespaceString)
+ {
+ int start = 0;
+ bool shouldEscape = false;
+
+ for (int i = 0; i <= namespaceString.Length; i++)
+ {
+ if (i == namespaceString.Length || namespaceString[i] == '.')
+ {
+ if (start != 0)
+ {
+ b.Append('.');
+ }
+
+ var part = namespaceString.Substring(start, i - start);
+ if (shouldEscape)
+ {
+ NormalizeAndAppendString(b, part);
+ shouldEscape = false;
+ }
+ else
+ {
+ b.Append(part);
+ }
+
+ start = i + 1;
+ continue;
+ }
+
+ shouldEscape = shouldEscape || NeedsEscaping(namespaceString[i], i - start);
+ }
+ }
+
+ private static void AppendMethodString(StringBuilder methodBuilder, string name, int methodArity)
{
+ var arityStart = name.LastIndexOf('`');
+ var arity = 0;
+ if (arityStart > 0)
+ {
+ arityStart++;
+ var arityString = name.Substring(arityStart, name.Length - arityStart);
+ if (int.TryParse(arityString, out arity))
+ {
+ if (arity == methodArity)
+ {
+ name = name.Substring(0, arityStart - 1);
+ }
+ }
+ }
+
+ if (IsNormalized(name))
+ {
+ methodBuilder.Append(name);
+ }
+ else
+ {
+ NormalizeAndAppendString(methodBuilder, name);
+ }
+
+ if (arity > 0 && methodArity == arity)
+ {
+ methodBuilder.Append($"`{arity}");
+ }
+ }
+
+ private static void NormalizeAndAppendString(StringBuilder b, string name)
+ {
+ b.Append("'");
+ for (int i = 0; i < name.Length; i++)
+ {
+ char c = name[i];
+ if (NeedsEscaping(c, i))
+ {
+ if (c == '\\' || c == '\'')
+ {
+ // var encoded = Convert.ToString(((uint)c), 16);
+ // b.Append("\\u");
+ // b.Append('0', 4 - encoded.Length);
+ // b.Append(encoded);
+
+ b.Append("\\");
+ b.Append(c);
+ continue;
+ }
+ }
+
+ b.Append(c);
+ }
+ b.Append("'");
+ }
+
+ private static int AppendNestedTypeName(StringBuilder b, Type type)
+ {
+ var outerArity = 0;
if (type.IsNested)
{
- AppendNestedTypeName(b, type.DeclaringType);
+ outerArity = AppendNestedTypeName(b, type.DeclaringType);
b.Append('+');
}
- b.Append(type.Name);
+
+ var typeName = type.Name;
+ var stars = 0;
+ if (type.IsPointer)
+ {
+ for (int i = typeName.Length - 1; i > 0; i--)
+ {
+ if (typeName[i] != '*')
+ {
+ stars = typeName.Length - i - 1;
+ typeName = typeName.Substring(0, i + 1);
+ break;
+ }
+ }
+ }
+
+ var info = type.GetTypeInfo();
+ var arity = !info.IsGenericType
+ ? 0
+ : info.GenericTypeParameters.Length > 0
+ ? info.GenericTypeParameters.Length
+ : info.GenericTypeArguments.Length;
+
+ AppendMethodString(b, typeName, arity - outerArity);
+ b.Append('*', stars);
+ return arity;
}
private static void AppendGenericTypeParameters(StringBuilder b, Type type)
@@ -299,7 +418,46 @@ private static void AppendGenericTypeParameters(StringBuilder b, Type type)
}
}
- private static string TypeString(Type type, bool closedType)
+ private static bool IsNormalized(string s)
+ {
+ for (int i = 0; i < s.Length; i++)
+ {
+ if (NeedsEscaping(s[i], i) && s[i] != '.')
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static bool NeedsEscaping(char c, int pos)
+ {
+ if (pos == 0 && char.IsDigit(c))
+ {
+ return true;
+ }
+
+ if (c == '_'
+ || char.IsLetterOrDigit(c) // Lu, Ll, Lt, Lm, Lo, or Nl
+ )
+ {
+ return false;
+ }
+
+ var category = CharUnicodeInfo.GetUnicodeCategory(c);
+ if (category == UnicodeCategory.NonSpacingMark // Mn
+ || category == UnicodeCategory.SpacingCombiningMark // Mc
+ || category == UnicodeCategory.ConnectorPunctuation // Pc
+ || category == UnicodeCategory.Format) // Cf
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private static string GetTypeString(Type type, bool closedType)
{
var builder = new StringBuilder();
AppendTypeString(builder, type, closedType);
diff --git a/src/Microsoft.TestPlatform.AdapterUtilities/ManagedNameUtilities/ManagedNameParser.cs b/src/Microsoft.TestPlatform.AdapterUtilities/ManagedNameUtilities/ManagedNameParser.cs
index bf707f74c8..e61537ab75 100644
--- a/src/Microsoft.TestPlatform.AdapterUtilities/ManagedNameUtilities/ManagedNameParser.cs
+++ b/src/Microsoft.TestPlatform.AdapterUtilities/ManagedNameUtilities/ManagedNameParser.cs
@@ -3,6 +3,7 @@
namespace Microsoft.TestPlatform.AdapterUtilities.ManagedNameUtilities
{
+ using Microsoft.TestPlatform.AdapterUtilities.Helpers;
using Microsoft.TestPlatform.AdapterUtilities.Resources;
using System.Collections.Generic;
@@ -66,7 +67,8 @@ public static void ParseManagedTypeName(string managedTypeName, out string names
///
public static void ParseManagedMethodName(string managedMethodName, out string methodName, out int arity, out string[] parameterTypes)
{
- int pos = ParseMethodName(managedMethodName, 0, out methodName, out arity);
+ int pos = ParseMethodName(managedMethodName, 0, out var escapedMethodName, out arity);
+ methodName = ReflectionHelpers.ParseEscapedString(escapedMethodName);
pos = ParseParameterTypeList(managedMethodName, pos, out parameterTypes);
if (pos != managedMethodName.Length)
{
@@ -80,17 +82,27 @@ private static string Capture(string managedMethodName, int start, int end)
private static int ParseMethodName(string managedMethodName, int start, out string methodName, out int arity)
{
- int i = start;
+ var i = start;
+ var quoted = false;
for (; i < managedMethodName.Length; i++)
{
- switch (managedMethodName[i])
+ var c = managedMethodName[i];
+ if (c == '\'' || quoted)
+ {
+ quoted = c == '\'' ? !quoted : quoted;
+ continue;
+ }
+
+ switch (c)
{
case var w when char.IsWhiteSpace(w):
string message = string.Format(CultureInfo.CurrentCulture, Resources.ErrorWhitespaceNotValid, i);
throw new InvalidManagedNameException(message);
+
case '`':
methodName = Capture(managedMethodName, start, i);
return ParseArity(managedMethodName, i, out arity);
+
case '(':
methodName = Capture(managedMethodName, start, i);
arity = 0;
@@ -143,8 +155,10 @@ private static int ParseParameterTypeList(string managedMethodName, int start, o
parameterTypes = types.ToArray();
}
return i + 1; // consume right parens
+
case ',':
break;
+
default:
i = ParseParameterType(managedMethodName, i, out var parameterType);
types.Add(parameterType);
@@ -159,22 +173,32 @@ private static int ParseParameterTypeList(string managedMethodName, int start, o
private static int ParseParameterType(string managedMethodName, int start, out string parameterType)
{
parameterType = string.Empty;
+ var quoted = false;
int i = start;
- for (; i < managedMethodName.Length; i++)
+ for (i = start; i < managedMethodName.Length; i++)
{
+ if (managedMethodName[i] == '\'' || quoted)
+ {
+ quoted = managedMethodName[i] == '\'' ? !quoted : quoted;
+ continue;
+ }
+
switch (managedMethodName[i])
{
case '<':
i = ParseGenericBrackets(managedMethodName, i + 1);
break;
+
case '[':
i = ParseArrayBrackets(managedMethodName, i + 1);
break;
+
case ',':
case ')':
parameterType = Capture(managedMethodName, start, i);
return i - 1;
+
case var w when char.IsWhiteSpace(w):
string message = string.Format(CultureInfo.CurrentCulture, Resources.ErrorWhitespaceNotValid, i);
throw new InvalidManagedNameException(message);
@@ -185,8 +209,16 @@ private static int ParseParameterType(string managedMethodName, int start, out s
private static int ParseArrayBrackets(string managedMethodName, int start)
{
+ var quoted = false;
+
for (int i = start; i < managedMethodName.Length; i++)
{
+ if (managedMethodName[i] == '\'' || quoted)
+ {
+ quoted = managedMethodName[i] == '\'' ? !quoted : quoted;
+ continue;
+ }
+
switch (managedMethodName[i])
{
case ']':
@@ -203,15 +235,25 @@ private static int ParseArrayBrackets(string managedMethodName, int start)
private static int ParseGenericBrackets(string managedMethodName, int start)
{
+ var quoted = false;
+
for (int i = start; i < managedMethodName.Length; i++)
{
+ if (managedMethodName[i] == '\'' || quoted)
+ {
+ quoted = managedMethodName[i] == '\'' ? !quoted : quoted;
+ continue;
+ }
+
switch (managedMethodName[i])
{
case '<':
i = ParseGenericBrackets(managedMethodName, i + 1);
break;
+
case '>':
return i;
+
case var w when char.IsWhiteSpace(w):
string msg = string.Format(CultureInfo.CurrentCulture, Resources.ErrorWhitespaceNotValid, i);
throw new InvalidManagedNameException(msg);
diff --git a/src/Microsoft.TestPlatform.AdapterUtilities/Resources/Resources.Designer.cs b/src/Microsoft.TestPlatform.AdapterUtilities/Resources/Resources.Designer.cs
index 039f006b5e..0239a66dd2 100644
--- a/src/Microsoft.TestPlatform.AdapterUtilities/Resources/Resources.Designer.cs
+++ b/src/Microsoft.TestPlatform.AdapterUtilities/Resources/Resources.Designer.cs
@@ -131,5 +131,27 @@ internal static string MethodNotImplementedOnPlatform
return ResourceManager.GetString("MethodNotImplementedOnPlatform", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to A closing single quote was expected at the end of the segment! (segment: {0}).
+ ///
+ internal static string ErrorNoClosingQuote
+ {
+ get
+ {
+ return ResourceManager.GetString("ErrorNoClosingQuote", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Invalid escape sequence! (segment: {0}, pos: {1})
+ ///
+ internal static string ErrorInvalidSequenceAt
+ {
+ get
+ {
+ return ResourceManager.GetString("ErrorInvalidSequenceAt", resourceCulture);
+ }
+ }
}
}
diff --git a/src/Microsoft.TestPlatform.AdapterUtilities/Resources/Resources.resx b/src/Microsoft.TestPlatform.AdapterUtilities/Resources/Resources.resx
index da81725acf..52fe4e0b37 100644
--- a/src/Microsoft.TestPlatform.AdapterUtilities/Resources/Resources.resx
+++ b/src/Microsoft.TestPlatform.AdapterUtilities/Resources/Resources.resx
@@ -120,6 +120,10 @@
ManagedName is incomplete
+
+ Invalid escape sequence! (segment: {0}, pos: {1})
+ An escape sequence started with '\', but it wasn't legal. {0}: Invalid string, {1}: starting postion of invalid sequence.
+
Method arity must be numeric
@@ -127,6 +131,10 @@
Method '{0}' not found on type '{1}'
{0} is the method name, {1} is the full type name.
+
+ A closing single quote was expected at the end of the segment! (segment: {0})
+ An error thrown when the type name ended but opened single qoute was not matched.
+
Type '{0}' not found
{0} is the full type name.
diff --git a/src/Microsoft.TestPlatform.AdapterUtilities/Resources/xlf/Resources.cs.xlf b/src/Microsoft.TestPlatform.AdapterUtilities/Resources/xlf/Resources.cs.xlf
index 77e0cf29b0..9c66651779 100644
--- a/src/Microsoft.TestPlatform.AdapterUtilities/Resources/xlf/Resources.cs.xlf
+++ b/src/Microsoft.TestPlatform.AdapterUtilities/Resources/xlf/Resources.cs.xlf
@@ -39,6 +39,16 @@
Example: 'System.Reflection.MethodBase' is not implemented on this platform!
+
+
+ Invalid escape sequence! (segment: {0}, pos: {1})
+ An escape sequence started with '\', but it wasn't legal. {0}: Invalid string, {1}: starting postion of invalid sequence.
+
+
+
+ A closing single quote was expected at the end of the segment! (segment: {0})
+ An error thrown when the type name ended but opened single qoute was not matched.
+