浏览代码

Mono: Make sure the generated RootNamespace is a valid identifier

Ignacio Etcheverry 6 年之前
父节点
当前提交
5a4475fce3

+ 1 - 0
modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj

@@ -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 - 0
modules/mono/editor/GodotSharpTools/Project/IdentifierUtils.cs

@@ -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",
+        };
+    }
+}

+ 4 - 1
modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs

@@ -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");