mirror of
				https://github.com/godotengine/godot.git
				synced 2025-11-03 23:21:15 +00:00 
			
		
		
		
	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…
	
	Add table
		Add a link
		
	
		Reference in a new issue