Mono: Make sure the generated RootNamespace is a valid identifier
This commit is contained in:
parent
b69569415f
commit
5a4475fce3
3 changed files with 204 additions and 1 deletions
|
@ -41,6 +41,7 @@
|
|||
<Compile Include="Build\BuildSystem.cs" />
|
||||
<Compile Include="Editor\MonoDevelopInstance.cs" />
|
||||
<Compile Include="Project\ProjectExtensions.cs" />
|
||||
<Compile Include="Project\IdentifierUtils.cs" />
|
||||
<Compile Include="Project\ProjectGenerator.cs" />
|
||||
<Compile Include="Project\ProjectUtils.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
|
|
199
modules/mono/editor/GodotSharpTools/Project/IdentifierUtils.cs
Normal file
199
modules/mono/editor/GodotSharpTools/Project/IdentifierUtils.cs
Normal file
|
@ -0,0 +1,199 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace GodotSharpTools.Project
|
||||
{
|
||||
public static class IdentifierUtils
|
||||
{
|
||||
public static string SanitizeQualifiedIdentifier(string qualifiedIdentifier, bool allowEmptyIdentifiers)
|
||||
{
|
||||
if (string.IsNullOrEmpty(qualifiedIdentifier))
|
||||
throw new ArgumentException($"{nameof(qualifiedIdentifier)} cannot be empty", nameof(qualifiedIdentifier));
|
||||
|
||||
string[] identifiers = qualifiedIdentifier.Split(new[] { '.' });
|
||||
|
||||
for (int i = 0; i < identifiers.Length; i++)
|
||||
{
|
||||
identifiers[i] = SanitizeIdentifier(identifiers[i], allowEmpty: allowEmptyIdentifiers);
|
||||
}
|
||||
|
||||
return string.Join(".", identifiers);
|
||||
}
|
||||
|
||||
public static string SanitizeIdentifier(string identifier, bool allowEmpty)
|
||||
{
|
||||
if (string.IsNullOrEmpty(identifier))
|
||||
{
|
||||
if (allowEmpty)
|
||||
return "Empty"; // Default value for empty identifiers
|
||||
|
||||
throw new ArgumentException($"{nameof(identifier)} cannot be empty if {nameof(allowEmpty)} is false", nameof(identifier));
|
||||
}
|
||||
|
||||
if (identifier.Length > 511)
|
||||
identifier = identifier.Substring(0, 511);
|
||||
|
||||
var identifierBuilder = new StringBuilder();
|
||||
int startIndex = 0;
|
||||
|
||||
if (identifier[0] == '@')
|
||||
{
|
||||
identifierBuilder.Append('@');
|
||||
startIndex += 1;
|
||||
}
|
||||
|
||||
for (int i = startIndex; i < identifier.Length; i++)
|
||||
{
|
||||
char @char = identifier[i];
|
||||
|
||||
switch (Char.GetUnicodeCategory(@char))
|
||||
{
|
||||
case UnicodeCategory.UppercaseLetter:
|
||||
case UnicodeCategory.LowercaseLetter:
|
||||
case UnicodeCategory.TitlecaseLetter:
|
||||
case UnicodeCategory.ModifierLetter:
|
||||
case UnicodeCategory.LetterNumber:
|
||||
case UnicodeCategory.OtherLetter:
|
||||
identifierBuilder.Append(@char);
|
||||
break;
|
||||
case UnicodeCategory.NonSpacingMark:
|
||||
case UnicodeCategory.SpacingCombiningMark:
|
||||
case UnicodeCategory.ConnectorPunctuation:
|
||||
case UnicodeCategory.DecimalDigitNumber:
|
||||
// Identifiers may start with underscore
|
||||
if (identifierBuilder.Length > startIndex || @char == '_')
|
||||
identifierBuilder.Append(@char);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (identifierBuilder.Length == startIndex)
|
||||
{
|
||||
// All characters were invalid so now it's empty. Fill it with something.
|
||||
identifierBuilder.Append("Empty");
|
||||
}
|
||||
|
||||
identifier = identifierBuilder.ToString();
|
||||
|
||||
if (identifier[0] != '@' && IsKeyword(identifier, anyDoubleUnderscore: true))
|
||||
identifier = '@' + identifier;
|
||||
|
||||
return identifier;
|
||||
}
|
||||
|
||||
static bool IsKeyword(string value, bool anyDoubleUnderscore)
|
||||
{
|
||||
// Identifiers that start with double underscore are meant to be used for reserved keywords.
|
||||
// Only existing keywords are enforced, but it may be useful to forbid any identifier
|
||||
// that begins with double underscore to prevent issues with future C# versions.
|
||||
if (anyDoubleUnderscore)
|
||||
{
|
||||
if (value.Length > 2 && value[0] == '_' && value[1] == '_' && value[2] != '_')
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_doubleUnderscoreKeywords.Contains(value))
|
||||
return true;
|
||||
}
|
||||
|
||||
return _keywords.Contains(value);
|
||||
}
|
||||
|
||||
static HashSet<string> _doubleUnderscoreKeywords = new HashSet<string>
|
||||
{
|
||||
"__arglist",
|
||||
"__makeref",
|
||||
"__reftype",
|
||||
"__refvalue",
|
||||
};
|
||||
|
||||
static HashSet<string> _keywords = new HashSet<string>
|
||||
{
|
||||
"as",
|
||||
"do",
|
||||
"if",
|
||||
"in",
|
||||
"is",
|
||||
"for",
|
||||
"int",
|
||||
"new",
|
||||
"out",
|
||||
"ref",
|
||||
"try",
|
||||
"base",
|
||||
"bool",
|
||||
"byte",
|
||||
"case",
|
||||
"char",
|
||||
"else",
|
||||
"enum",
|
||||
"goto",
|
||||
"lock",
|
||||
"long",
|
||||
"null",
|
||||
"this",
|
||||
"true",
|
||||
"uint",
|
||||
"void",
|
||||
"break",
|
||||
"catch",
|
||||
"class",
|
||||
"const",
|
||||
"event",
|
||||
"false",
|
||||
"fixed",
|
||||
"float",
|
||||
"sbyte",
|
||||
"short",
|
||||
"throw",
|
||||
"ulong",
|
||||
"using",
|
||||
"where",
|
||||
"while",
|
||||
"yield",
|
||||
"double",
|
||||
"extern",
|
||||
"object",
|
||||
"params",
|
||||
"public",
|
||||
"return",
|
||||
"sealed",
|
||||
"sizeof",
|
||||
"static",
|
||||
"string",
|
||||
"struct",
|
||||
"switch",
|
||||
"typeof",
|
||||
"unsafe",
|
||||
"ushort",
|
||||
"checked",
|
||||
"decimal",
|
||||
"default",
|
||||
"finally",
|
||||
"foreach",
|
||||
"partial",
|
||||
"private",
|
||||
"virtual",
|
||||
"abstract",
|
||||
"continue",
|
||||
"delegate",
|
||||
"explicit",
|
||||
"implicit",
|
||||
"internal",
|
||||
"operator",
|
||||
"override",
|
||||
"readonly",
|
||||
"volatile",
|
||||
"interface",
|
||||
"namespace",
|
||||
"protected",
|
||||
"unchecked",
|
||||
"stackalloc",
|
||||
};
|
||||
}
|
||||
}
|
|
@ -140,6 +140,9 @@ namespace GodotSharpTools.Project
|
|||
|
||||
public static ProjectRootElement CreateLibraryProject(string name, out ProjectPropertyGroupElement mainGroup)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new ArgumentException($"{nameof(name)} cannot be empty", nameof(name));
|
||||
|
||||
var root = ProjectRootElement.Create();
|
||||
root.DefaultTargets = "Build";
|
||||
|
||||
|
@ -149,7 +152,7 @@ namespace GodotSharpTools.Project
|
|||
mainGroup.AddProperty("ProjectGuid", "{" + Guid.NewGuid().ToString().ToUpper() + "}");
|
||||
mainGroup.AddProperty("OutputType", "Library");
|
||||
mainGroup.AddProperty("OutputPath", Path.Combine("bin", "$(Configuration)"));
|
||||
mainGroup.AddProperty("RootNamespace", name);
|
||||
mainGroup.AddProperty("RootNamespace", IdentifierUtils.SanitizeQualifiedIdentifier(name, allowEmptyIdentifiers: true));
|
||||
mainGroup.AddProperty("AssemblyName", name);
|
||||
mainGroup.AddProperty("TargetFrameworkVersion", "v4.5");
|
||||
|
||||
|
|
Loading…
Reference in a new issue