├── .gitignore ├── Util └── XmlHelper.cs ├── README.md └── Editor ├── SettingsWindow.cs └── Generator.cs /.gitignore: -------------------------------------------------------------------------------- 1 | *.meta -------------------------------------------------------------------------------- /Util/XmlHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.IO; 3 | using System.Xml.Serialization; 4 | 5 | namespace EmmyLuaSnippetGenerator 6 | { 7 | public static class XmlHelper 8 | { 9 | public static void SaveConfig(T config, string filePath) 10 | { 11 | if (!Directory.Exists(Path.GetDirectoryName(filePath))) 12 | { 13 | Directory.CreateDirectory(Path.GetDirectoryName(filePath)); 14 | } 15 | 16 | XmlSerializer serializer = new(typeof(T)); 17 | 18 | using StreamWriter writer = new(filePath); 19 | 20 | serializer.Serialize(writer, config); 21 | } 22 | 23 | public static bool TryLoadConfig(string filePath, out T config) 24 | { 25 | if (!File.Exists(filePath)) 26 | { 27 | config = default; 28 | return false; 29 | } 30 | 31 | XmlSerializer serializer = new(typeof(T)); 32 | using StreamReader reader = new(filePath); 33 | 34 | config = (T)serializer.Deserialize(reader); 35 | return true; 36 | } 37 | 38 | public static void OpenWithDefaultEditor(string filePath) 39 | { 40 | Process.Start(new ProcessStartInfo(filePath) { UseShellExecute = true }); 41 | } 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lua Type Hint Generator (Unity) 2 | 3 | 4 | 5 | 本工具用于在Unity环境下为C#类型生成Lua注解文件, 以便提供类型检查和自动提示功能. 6 | 7 | 适用于安装了**EmmyLua**或**SumnekoLua**(VSCode中称为Lua)插件的IDE, 其余类型注解格式相似的插件或许也能使用. 8 | 9 | 10 | 11 | ## 参考 12 | 13 | 本项目自以下工程fork而来, 我在此基础上增加了一些实用功能并修复了些许Bug. 14 | 15 | [ak47007tiger/EmmyLuaXLuaSnippetGenerator: generate xlua snippet for emmylua](https://github.com/ak47007tiger/EmmyLuaXLuaSnippetGenerator) 16 | 17 | 赞颂原作者的开源精神! 18 | 19 | ### 新功能 20 | 21 | - [支持推理泛型字段类型的功能.](#泛型) 22 | - [支持CS.前缀alias的生成.](#前缀) 23 | - [允许C#函数类型与Lua Function类型兼容.](#函数兼容) 24 | - [支持将类型注解文件生成为多个, 提升编辑器中类型推断的性能.](#性能) 25 | - [支持全局变量生成](#全局)和xLua的typeof函数生成. 26 | - [编辑器内的简易GUI工具](#设置), 允许将生成注解的选项保存为本地配置文件, 免去需要修改源代码的麻烦. 27 | 28 | ### 问题修复 29 | 30 | - 修复部分静态函数仍会生成self参数的问题. 31 | - 修复泛型类型信息尾缀清除错误的问题. 32 | - 不再生成匿名类型的注解. 33 | 34 | 35 | 36 | ## 使用 37 | 38 | 克隆本工程到你的项目目录下. 在Unity编辑器顶部的"LuaType"菜单中使用它. 请先 [设置] 再 [生成注解文件]. 39 | 40 | 每次生成注解文件时, 程序会自动清空目标目录, 你也可以点击 [清除类型注解] 来手动清除它们. 41 | 42 |

43 | 44 | ## 设置 45 | 46 | 在生成注解文件前必须进行设置. 你可以根据以下说明进行设置. 47 | 48 |

49 | 50 | ### 配置文件的存放路径 51 | 52 | 提供一个仅包含目录名的绝对路径, 是否以斜线/结尾都可以. 53 | 54 | 该路径的默认值为`AppDomain.CurrentDomain.BaseDirectory`, 在Unity中通常在引擎的安装目录下. 如果对此默认目录的访问被拒绝, 请尝试重新设定该目录. 55 | 56 |

57 | 58 | ### 生成类型注解文件的路径 59 | 60 | 提供一个仅包含目录名的绝对路径, 必须以斜线/结尾. 61 | 62 | 如果生成的注解较多, 可能会生成多个注解文件. 最好提供一个全新的空目录方便管理. 通常来说, 注解文件的目录需要设置在项目内, 以便你的IDE分析它们. 63 | 64 | 但如果你在使用**SumnekoLua**, 我更建议你将本目录设置在项目外, 然后在**SumnekoLua**的`workspace.library`设置中添加这个目录. 这不仅能防止注解文件对版本管理产生干扰, 也能提升类型分析的性能. 具体操作方法可以查看 [Libraries · LuaLS/lua-language-server Wiki](https://github.com/LuaLS/lua-language-server/wiki/Libraries#link-to-workspace) 65 | 66 |

67 | 68 | ### 要生成注解的C#命名空间 69 | 70 | 默认会通过 `AppDomain.CurrentDomain.GetAssemblies()` 来收集当前域下的命名空间. 如果你使用了其它库(如DOTween), 填写更多命名空间并用空格分隔即可. 如: 71 | 72 | `DG FairyGUI` 73 | 74 |

75 | 76 | ### 要生成注解的全局变量 77 | 78 | 项目中的部分全局变量可能从CSharp端设置, Lua无法识别它们. 在本配置中设置你项目中用到的全局变量及类型, 防止Lua频繁提示未定义字段的warning. 格式为`变量名:类型名`, 多个组用空格分隔. 如: 79 | 80 | `UNITY_EDITOR:boolean DEBUG_LEVEL:number` 81 | 82 |

83 | 84 | ### 使以下类型名兼容Lua function类型 85 | 86 | C#中的一些函数类型(如`System.Action`)和Lua的`function`类型间没有默认的隐式转换. 尽管xLua能够处理它们, 但类型系统可能会提示warning: 87 | 88 | ```lua 89 | local luaFunc = function() end 90 | 91 | ---@type System.Action 92 | local csharpFunc 93 | 94 | csharpFunc = luaFunc -- cause warning: cannot convert type "function" to "System.Action" 95 | ``` 96 | 97 | 你可以开启本选项以允许类型与Lua function兼容. 在文本框中输入你想兼容Lua function的类型全名, 多个类型名用空格分隔: 98 | 99 | `System.Action FairyGUI.EventCallback0` 100 | 101 | 程序将会以交联类型`t | function`的形式来处理你填写的类型名, 所以你不会因为这些兼容而丢失该类型原本的字段信息. 102 | 103 |

104 | 105 | ### 生成带CS.前缀的兼容alias 106 | 107 | 本工具生成的类型通常仅包含类型名 (如`UnityEngine.Vector3`). 108 | 109 | 如果你的项目中使用了带有`CS.`前缀版本的类型注解, 如`CS.UnityEngine.Vector3`, 或者你希望能在访问xLua的`CS`表时获得自动提示, 可以启用本选项以获得兼容. 110 | 111 | ```lua 112 | ---@type CS.UnityEngine.Vector3 113 | local vec1 = CS.UnityEngine.Vector3.zero 114 | 115 | ---@type UnityEngine.Vector3 116 | local vec2 = CS.UnityEngine.Vector3.zero 117 | 118 | -- 启用该选项后, 是否带CS.前缀的两个版本将被识别为同一个类型, 即该赋值语句不会产生warning. 119 | vec1 = vec2 120 | 121 | -- 注意! 这只适用于类型提示文件, 如果你使用xLua, 你仍然需要添加CS.前缀去在代码中真正访问C#类型. 122 | -- 以下语句将在运行时报错, 即使你启用了本选项. 123 | local vec3 = UnityEngine.Vector3.zero 124 | ``` 125 | 126 |

127 | 128 | ### 尝试推理泛型字段类型 129 | 130 | 开启后, 将会尝试推理泛型类的非泛型派生中, 继承而来的泛型字段/属性的实际类型. 如以下C#代码: 131 | 132 | ```c# 133 | class Singleton { 134 | public T Instance { get; } 135 | public T instance; 136 | } 137 | 138 | class SomeManager : Singleton { 139 | public void DoSomething() {} 140 | } 141 | ``` 142 | 143 | 在未开启本选项时, 这段代码的注解会按如下格式生成: 144 | 145 | ```lua 146 | ---@class Singleton 147 | ---@field Instance T 148 | ---@field instance T -- 未知类型的泛型参数直接生成为参数名 149 | local Singleton = {} 150 | 151 | ---@class SomeManager 152 | local SomeManager = {} 153 | 154 | -- 编写业务代码时: 155 | -- 打出Instance后不能自动提示后续字段, 因为Instance类型未被推理, 而是被识别为T 156 | local sm = SomeManager() 157 | sm.Instance.DoSomething() -- cause warning: undefied field "DoSomething" 158 | ``` 159 | 160 | 开启本选项后, 生成SomeManager类的注解时将能够分析Instance和instance的实际类型为SomeManager, 并在生成时添加它们的具体类型注释, 覆盖父类Singleton中的类型定义. 161 | 162 | ```lua 163 | ---@class Singleton 164 | ---@field Instance T 165 | ---@field instance T 166 | local Singleton = {} 167 | 168 | ---@class SomeManager 169 | ---@field Instance SomeManager -- infer from Singleton`1[SomeManager] 170 | ---@field instance SomeManager -- infer from Singleton`1[SomeManager] 171 | local SomeManager = {} 172 | 173 | -- 编写业务代码时: 174 | -- 可以提示出DoSomething方法, 因为Instance类型已经被识别为SomeManager 175 | local sm = SomeManager() 176 | sm.Instance.DoSomething() 177 | ``` 178 | 179 | 理论上, 只要派生类的泛型参数数量少于父类, 我们就能通过这个原理获得部分类型信息. 受限于时间原因, 我暂时只做了**字段**和**属性**的推理以应对大部分场景, 非泛型方法的参数和返回值类型推断应该可以用类似的办法实现. 180 | 181 | 如果你的项目中大量使用了泛型, 强烈推荐开启本功能, 因为调用链上的某个字段丢失类型信息将影响后续所有类型. 开启本选项将减慢生成注解文件的速度, 但**不会**影响编辑器分析类型的速度. 考虑到注解文件不会频繁地被生成, 这是一笔划算的开销. 182 | 183 |

184 | 185 | ### 单个注解文件的最大行数 186 | 187 | 项目较大时, 生成的类型注解文件可能非常庞大, 甚至高达数十万行. 为了避免大文件带来的卡顿问题, 程序将按你设定的行数将文件拆分为多个页. 188 | 189 | 你可以根据自己的电脑配置设定这个值. 在我的i7-11700 + 64GB RAM的Windows台式电脑上, 设定为20000能发挥较好的性能. 190 | 191 | > 如果你使用的是SumnekoLua, 合理设置本选项将大幅度提升编辑器分析类型的性能, 它的LuaLS在处理多文件时性能似乎比单文件更好. 在我的实际使用中, 这种感知也相当明显.参见: [[Question\] Large size type annotation file cause low performance. Is it possible to fix it? · Issue #2674 · LuaLS/lua-language-server](https://github.com/LuaLS/lua-language-server/issues/2674) 192 | -------------------------------------------------------------------------------- /Editor/SettingsWindow.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | using System.Xml; 4 | using System.IO; 5 | using System; 6 | using System.Linq; 7 | 8 | namespace EmmyLuaSnippetGenerator 9 | { 10 | [Serializable] 11 | public sealed class SettingOptions 12 | { 13 | public string GeneratePath; 14 | public string TargetNamespacesStr; 15 | public string GlobalVariablesStr; 16 | public string FunctionCompatibleTypesStr; 17 | public bool GenerateCSAlias = true; 18 | public bool InferGenericFieldType = true; 19 | public int SingleFileMaxLine = 5000; 20 | 21 | private static string _saveRootPath = null; 22 | public static string SaveRootPath 23 | { 24 | get => string.IsNullOrWhiteSpace(_saveRootPath) 25 | ? AppDomain.CurrentDomain.BaseDirectory 26 | : _saveRootPath; 27 | set => _saveRootPath = value; 28 | } 29 | public static string SavePath => Path.Combine(SaveRootPath, "EmmyLuaSnippetToolData", "config.xml"); 30 | 31 | public string[] GetTargetNamespaces() 32 | { 33 | if (string.IsNullOrWhiteSpace(TargetNamespacesStr)) 34 | { 35 | return Array.Empty(); 36 | } 37 | 38 | return TargetNamespacesStr.Split(' '); 39 | } 40 | 41 | // varName, typeName 42 | public (string, string)[] GetGlobalVariables() 43 | { 44 | if (string.IsNullOrWhiteSpace(GlobalVariablesStr)) 45 | { 46 | return Array.Empty<(string, string)>(); 47 | } 48 | 49 | var varInfos = GlobalVariablesStr.Split(' '); 50 | return varInfos.Select(info => info.Split(':')).Select(info => (info[0], info[1])).ToArray(); 51 | } 52 | 53 | public string[] GetFunctionCompatibleTypes() 54 | { 55 | if (string.IsNullOrWhiteSpace(FunctionCompatibleTypesStr)) 56 | { 57 | return Array.Empty(); 58 | } 59 | 60 | return FunctionCompatibleTypesStr.Split(' '); 61 | } 62 | } 63 | 64 | public sealed class SettingsWindow : EditorWindow 65 | { 66 | private SettingOptions _options; 67 | 68 | [MenuItem("LuaType/设置")] 69 | public static void ShowWindow() 70 | { 71 | GetWindow("Lua类型注解文件设置"); 72 | } 73 | 74 | private void OnEnable() 75 | { 76 | if (XmlHelper.TryLoadConfig(SettingOptions.SavePath, out SettingOptions settings)) 77 | { 78 | _options = settings; 79 | } 80 | else 81 | { 82 | _options = new(); 83 | } 84 | } 85 | 86 | private void OnGUI() 87 | { 88 | GUILayout.Space(20); 89 | 90 | GUILayout.Label( 91 | "配置文件的存放路径" 92 | ); 93 | EditorGUILayout.BeginHorizontal(); 94 | GUI.enabled = false; 95 | SettingOptions.SaveRootPath = EditorGUILayout.TextField( 96 | SettingOptions.SaveRootPath, 97 | GUILayout.MinWidth(200) 98 | ); 99 | GUI.enabled = true; 100 | if (GUILayout.Button("...", GUILayout.Width(50))) 101 | { 102 | SettingOptions.SaveRootPath = EditorUtility.OpenFolderPanel("选择配置文件存放路径", "", ""); 103 | }; 104 | EditorGUILayout.EndHorizontal(); 105 | 106 | GUILayout.Space(10); 107 | 108 | GUILayout.Label( 109 | "生成类型注解文件的路径" 110 | ); 111 | EditorGUILayout.BeginHorizontal(); 112 | GUI.enabled = false; 113 | _options.GeneratePath = EditorGUILayout.TextField( 114 | _options.GeneratePath, 115 | GUILayout.MinWidth(200) 116 | ); 117 | GUI.enabled = true; 118 | if (GUILayout.Button("...", GUILayout.Width(50))) 119 | { 120 | _options.GeneratePath = EditorUtility.OpenFolderPanel("选择生成类型注解文件路径", "", ""); 121 | }; 122 | EditorGUILayout.EndHorizontal(); 123 | 124 | GUILayout.Space(10); 125 | 126 | GUILayout.Label( 127 | "要生成注解的C#命名空间" 128 | + "\n- 多个命名空间用空格分隔" 129 | + "\n- 例如: UnityEngine DG FairyGUI" 130 | ); 131 | _options.TargetNamespacesStr = EditorGUILayout.TextField( 132 | _options.TargetNamespacesStr, 133 | GUILayout.MinWidth(200) 134 | ); 135 | 136 | GUILayout.Space(10); 137 | 138 | GUILayout.Label( 139 | "要生成注解的全局变量" 140 | + "\n- 变量名:类型名, 多个组用空格分隔" 141 | + "\n- 例如: UNITY_EDITOR:boolean DEBUG_LV:integer" 142 | ); 143 | _options.GlobalVariablesStr = EditorGUILayout.TextField( 144 | _options.GlobalVariablesStr, 145 | GUILayout.MinWidth(200) 146 | ); 147 | 148 | GUILayout.Space(10); 149 | 150 | GUILayout.Label( 151 | "使以下类型名兼容Lua function类型" 152 | + "\n- 多个类型名用空格分隔" 153 | + "\n- 例如: System.Action FairyGUI.EventCallback0" 154 | ); 155 | _options.FunctionCompatibleTypesStr = EditorGUILayout.TextField( 156 | _options.FunctionCompatibleTypesStr, 157 | GUILayout.MinWidth(200) 158 | ); 159 | 160 | GUILayout.Space(10); 161 | 162 | GUILayout.Label( 163 | "生成带CS.前缀的兼容alias" 164 | + "\n- 启用后, 将为生成的类型名额外添加带CS.前缀的版本" 165 | ); 166 | _options.GenerateCSAlias = EditorGUILayout.Toggle(_options.GenerateCSAlias); 167 | 168 | GUILayout.Space(10); 169 | 170 | GUILayout.Label( 171 | "尝试推理泛型字段类型" 172 | + "\n- 启用后, 将在继承泛型类的非泛型派生中添加泛型字段的类型" 173 | + "\n- 显著影响注解生成速度, 但不影响类型分析性能" 174 | ); 175 | _options.InferGenericFieldType = EditorGUILayout.Toggle(_options.InferGenericFieldType); 176 | 177 | GUILayout.Space(10); 178 | 179 | GUILayout.Label( 180 | "单个注解文件的最大行数" 181 | + "\n- 超过该行数时会自动拆分成多个文件" 182 | + "\n- 大幅影响类型分析性能, 请依据电脑配置设置" 183 | ); 184 | _options.SingleFileMaxLine = (int)EditorGUILayout.Slider( 185 | _options.SingleFileMaxLine, 186 | 5000, 187 | 40000, 188 | GUILayout.MinWidth(200) 189 | ); 190 | 191 | GUILayout.Space(20); 192 | 193 | if (GUILayout.Button("保存配置文件")) 194 | { 195 | try 196 | { 197 | XmlHelper.SaveConfig(_options, SettingOptions.SavePath); 198 | this.Close(); 199 | } 200 | catch (UnauthorizedAccessException e) 201 | { 202 | Debug.LogError($"错误: 没有对目录 {SettingOptions.SaveRootPath} 的操作权限. 尝试修改配置文件的存放路径.\n{e.StackTrace}"); 203 | } 204 | } 205 | 206 | if (GUILayout.Button("取消")) 207 | { 208 | this.Close(); 209 | } 210 | } 211 | } 212 | } 213 | 214 | 215 | -------------------------------------------------------------------------------- /Editor/Generator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Runtime.CompilerServices; 7 | using System.Text; 8 | using System.Text.RegularExpressions; 9 | using System.Threading; 10 | using NetFile; 11 | using OfficeOpenXml.FormulaParsing.ExpressionGraph.FunctionCompilers; 12 | using Sirenix.Utilities; 13 | using UnityEditor; 14 | using UnityEngine; 15 | 16 | namespace EmmyLuaSnippetGenerator 17 | { 18 | /// 19 | /// 该文件只用来给ide进行lua类型提示的,不要在运行时require该文件或者打包到版本中. 20 | /// 21 | public static class LuaTypeGenerator 22 | { 23 | private static SettingOptions _options; 24 | 25 | private static readonly HashSet luaNumberTypeSet = new HashSet 26 | { 27 | typeof(byte), 28 | typeof(sbyte), 29 | typeof(short), 30 | typeof(ushort), 31 | typeof(int), 32 | typeof(uint), 33 | typeof(long), 34 | typeof(ulong), 35 | typeof(float), 36 | typeof(double) 37 | }; 38 | private static readonly HashSet luaKeywordSet = new HashSet 39 | { 40 | "and", 41 | "break", 42 | "do", 43 | "else", 44 | "elseif", 45 | "end", 46 | "false", 47 | "for", 48 | "function", 49 | "if", 50 | "in", 51 | "local", 52 | "nil", 53 | "not", 54 | "or", 55 | "repeat", 56 | "return", 57 | "then", 58 | "true", 59 | "until", 60 | "while" 61 | }; 62 | private static string[] _functionCompatibleTypes; 63 | 64 | public static readonly StringBuilder sb = new StringBuilder(1024); 65 | private static readonly StringBuilder tempSb = new StringBuilder(1024); 66 | private static readonly List exportTypeList = new List(); 67 | 68 | private static readonly Dictionary> 69 | extensionMethodsDic = new Dictionary>(); 70 | 71 | [MenuItem("LuaType/生成EmmyLua类型注解")] 72 | public static void GenerateEmmyTypeFiles() 73 | { 74 | if (!XmlHelper.TryLoadConfig(SettingOptions.SavePath, out SettingOptions loaded)) 75 | { 76 | Debug.LogError("错误: 需要一份配置文件才能执行操作. 在[设置]页面中配置它然后保存!"); 77 | return; 78 | } 79 | 80 | _options = loaded; 81 | if (!Directory.Exists(_options.GeneratePath)) 82 | { 83 | Directory.CreateDirectory(_options.GeneratePath); 84 | } 85 | 86 | try 87 | { 88 | var set = CollectAllExportType(); 89 | exportTypeList.AddRange(set); 90 | 91 | _functionCompatibleTypes = _options.GetFunctionCompatibleTypes(); 92 | 93 | HandleExtensionMethods(); 94 | 95 | GenerateTypeDefines(); 96 | 97 | ClearEmmyTypeFiles(); 98 | 99 | WriteToFile(); 100 | 101 | AssetDatabase.Refresh(); 102 | } 103 | catch (Exception e) 104 | { 105 | Debug.LogError("错误: " + e.Message); 106 | return; 107 | } 108 | finally 109 | { 110 | exportTypeList.Clear(); 111 | extensionMethodsDic.Clear(); 112 | sb.Clear(); 113 | } 114 | 115 | Debug.Log("生成注解文件完毕."); 116 | } 117 | 118 | [MenuItem("LuaType/清除EmmyLua类型注解")] 119 | public static void ClearEmmyTypeFiles() 120 | { 121 | if (!XmlHelper.TryLoadConfig(SettingOptions.SavePath, out SettingOptions loaded)) 122 | { 123 | Debug.LogError("错误: 需要一份配置文件才能执行操作. 在[设置]页面中配置它然后保存!"); 124 | return; 125 | } 126 | 127 | _options = loaded; 128 | 129 | if (!Directory.Exists(_options.GeneratePath)) 130 | { 131 | return; 132 | } 133 | 134 | int count = 0; 135 | string[] files = Directory.GetFiles(_options.GeneratePath, "TypeHint_*.lua"); 136 | 137 | foreach (string file in files) 138 | { 139 | File.Delete(file); 140 | count++; 141 | } 142 | 143 | Debug.Log($"清除完毕, 删除了 {count} 份注解文件. (生成时会自动执行该清理)"); 144 | } 145 | 146 | private static HashSet CollectAllExportType() 147 | { 148 | //收集要导出的类型 149 | var allAssembly = AppDomain.CurrentDomain.GetAssemblies(); 150 | //去重复 151 | var set = new HashSet(); 152 | foreach (var assemblyInst in allAssembly) 153 | { 154 | Type[] collection = CollectType(assemblyInst); 155 | foreach (var typeInst in collection) 156 | { 157 | if (!set.Contains(typeInst)) 158 | { 159 | set.Add(typeInst); 160 | } 161 | } 162 | } 163 | return set; 164 | } 165 | 166 | public static bool IsExportType(Type item) 167 | { 168 | var targetNamespaces = _options.GetTargetNamespaces(); 169 | 170 | for (int i = 0; i < targetNamespaces.Length; i++) 171 | { 172 | string itemNamespace = item.Namespace; 173 | if (string.IsNullOrEmpty(itemNamespace)) 174 | { 175 | if (item.FullName.Contains("Interop")) 176 | { 177 | return false; 178 | } 179 | //很多i,j,p,o这样的类占用空间浪费时间,类名少于3直接干掉 180 | return item.Name.Length > 2; 181 | } 182 | else 183 | { 184 | //不要编辑器 185 | if (itemNamespace.StartsWith("UnityEditor")) 186 | { 187 | return false; 188 | } 189 | //lua里用不到 190 | if (itemNamespace.Contains("Burst")) 191 | { 192 | return false; 193 | } 194 | if (itemNamespace.StartsWith(targetNamespaces[i])) 195 | { 196 | return true; 197 | } 198 | } 199 | } 200 | return false; 201 | } 202 | 203 | private static Type[] CollectType(Assembly assembly) 204 | { 205 | var types = assembly.GetTypes(); 206 | var retTypes = new HashSet(); 207 | foreach (var item in types) 208 | { 209 | if (IsExportType(item) && !retTypes.Contains(item)) 210 | { 211 | retTypes.Add(item); 212 | } 213 | } 214 | return retTypes.ToArray(); 215 | } 216 | 217 | private static void HandleExtensionMethods() 218 | { 219 | for (var i = 0; i < exportTypeList.Count; i++) 220 | { 221 | Type type = exportTypeList[i]; 222 | 223 | MethodInfo[] publicStaticMethodInfos = 224 | type.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly); 225 | for (var j = 0; j < publicStaticMethodInfos.Length; j++) 226 | { 227 | MethodInfo methodInfo = publicStaticMethodInfos[j]; 228 | if (methodInfo.IsDefined(typeof(ExtensionAttribute), false)) 229 | { 230 | Type extensionType = methodInfo.GetParameters()[0].ParameterType; 231 | if (extensionMethodsDic.TryGetValue(extensionType, out List extensionMethodList)) 232 | { 233 | extensionMethodList.Add(methodInfo); 234 | } 235 | else 236 | { 237 | List methodList = new List { methodInfo }; 238 | extensionMethodsDic.Add(extensionType, methodList); 239 | } 240 | } 241 | } 242 | } 243 | } 244 | 245 | private static void GenerateTypeDefines() 246 | { 247 | sb.Clear(); 248 | sb.AppendLine("---@meta CSharp"); 249 | sb.AppendLine(""); 250 | sb.AppendLine("---@class NotExportType @表明该类型未导出"); 251 | sb.AppendLine(""); 252 | sb.AppendLine("---@class NotExportEnum @表明该枚举未导出"); 253 | sb.AppendLine(""); 254 | 255 | WriteGlobalVariablesDefine(); 256 | 257 | WriteXLuaDefine(); 258 | 259 | var targetNamespaces = _options.GetTargetNamespaces(); 260 | 261 | for (int i = 0; i < targetNamespaces.Length; i++) 262 | { 263 | string namespaceName = targetNamespaces[i]; 264 | 265 | sb.AppendLine(string.Format("---@class {0}", namespaceName)); 266 | sb.AppendLine(string.Format("{0} = {{}}", namespaceName)); 267 | 268 | if (_options.GenerateCSAlias) 269 | { 270 | string namespaceCSAlias = string.Format("CS.{0}", namespaceName); 271 | 272 | sb.AppendLine(string.Format("---@alias {0} {1}", namespaceCSAlias, namespaceName)); 273 | sb.AppendLine(string.Format("{0} = {{}}", namespaceCSAlias)); 274 | } 275 | 276 | sb.AppendLine(""); 277 | } 278 | 279 | for (int i = 0; i < exportTypeList.Count; i++) 280 | { 281 | Type typeInst = exportTypeList[i]; 282 | 283 | // 防止一些匿名类型的生成 284 | if (typeInst.ToString().Contains("<")) 285 | { 286 | continue; 287 | } 288 | 289 | keepStringTypeName = typeInst == typeof(string); 290 | 291 | WriteClassDefine(typeInst); 292 | WriteClassFieldDefine(typeInst); 293 | sb.AppendLine(string.Format("{0} = {{}}", typeInst.ToLuaTypeName().ReplaceDotOrPlusWithUnderscore())); 294 | 295 | if (_options.GenerateCSAlias) 296 | { 297 | WriteClassAliasDefine(typeInst); 298 | } 299 | 300 | WriteClassConstructorDefine(typeInst); 301 | WriteClassMethodDefine(typeInst); 302 | 303 | sb.AppendLine(""); 304 | } 305 | } 306 | 307 | #region TypeDefineFileGenerator 308 | 309 | public static void WriteToFile() 310 | { 311 | string[] lines = sb.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None); 312 | 313 | int fileCount = 0; 314 | int lineCount = 0; 315 | string fileName; 316 | 317 | StreamWriter writer = null; 318 | 319 | foreach (string line in lines) 320 | { 321 | if (writer == null) 322 | { 323 | fileName = _options.GeneratePath + "/" + "TypeHint_" + fileCount + ".lua"; 324 | writer = new StreamWriter(fileName); 325 | writer.WriteLine("---@meta"); 326 | writer.WriteLine(""); 327 | } 328 | 329 | if (string.IsNullOrWhiteSpace(line) && _options.SingleFileMaxLine != 0 && lineCount >= _options.SingleFileMaxLine) 330 | { 331 | writer?.Close(); 332 | fileCount++; 333 | lineCount = 0; 334 | writer = null; 335 | continue; 336 | } 337 | 338 | writer.WriteLine(line); 339 | lineCount++; 340 | } 341 | 342 | writer?.Close(); 343 | } 344 | 345 | public static void WriteGlobalVariablesDefine() 346 | { 347 | foreach (var (varName, varTypeName) in _options.GetGlobalVariables()) 348 | { 349 | sb.AppendLine(string.Format("---@type {0}", varTypeName)); 350 | sb.AppendLine(string.Format("{0} = nil", varName)); 351 | sb.AppendLine(""); 352 | } 353 | } 354 | 355 | // xLua相关的定义单独写 356 | public static void WriteXLuaDefine() 357 | { 358 | // CS table 359 | sb.AppendLine(string.Format("---@class {0}", "CS")); 360 | sb.AppendLine("CS = {}"); 361 | sb.AppendLine(""); 362 | 363 | // typeof function 364 | sb.AppendLine(@"---@param obj any"); 365 | sb.AppendLine(@"---@return System.Type"); 366 | sb.AppendLine(@"function typeof(obj) end"); 367 | sb.AppendLine(""); 368 | } 369 | 370 | public static void WriteClassDefine(Type type) 371 | { 372 | if (type.BaseType != null && !type.IsEnum) 373 | { 374 | sb.AppendLine(string.Format("---@class {0} : {1}", type.ToLuaTypeName(), type.BaseType.ToLuaTypeName())); 375 | } 376 | else 377 | { 378 | sb.AppendLine(string.Format("---@class {0}", type.ToLuaTypeName())); 379 | } 380 | } 381 | 382 | public static void WriteClassFieldDefine(Type classType) 383 | { 384 | FieldInfo[] publicInstanceFieldInfos = 385 | classType.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); 386 | 387 | FieldInfo[] publicStaticFieldInfos = 388 | classType.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly); 389 | 390 | List fieldInfoList = new List(); 391 | 392 | fieldInfoList.AddRange(publicStaticFieldInfos); 393 | 394 | if (!classType.IsEnum) 395 | { 396 | fieldInfoList.AddRange(publicInstanceFieldInfos); 397 | } 398 | 399 | for (int i = 0; i < fieldInfoList.Count; i++) 400 | { 401 | FieldInfo fieldInfo = fieldInfoList[i]; 402 | 403 | if (fieldInfo.IsMemberObsolete(classType)) 404 | { 405 | continue; 406 | } 407 | 408 | string fieldTypeName = fieldInfo.FieldType.ToLuaTypeName(); 409 | 410 | sb.AppendLine(string.Format("---@field {0} {1}", fieldInfo.Name, fieldTypeName.MakeLuaFunctionCompatible())); 411 | } 412 | 413 | PropertyInfo[] publicInstancePropertyInfo = 414 | classType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); 415 | 416 | PropertyInfo[] publicStaticPropertyInfo = 417 | classType.GetProperties(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly); 418 | 419 | List propertyInfoList = new List(); 420 | propertyInfoList.AddRange(publicStaticPropertyInfo); 421 | 422 | if (!classType.IsEnum) 423 | { 424 | propertyInfoList.AddRange(publicInstancePropertyInfo); 425 | } 426 | 427 | for (int i = 0; i < propertyInfoList.Count; i++) 428 | { 429 | PropertyInfo propertyInfo = propertyInfoList[i]; 430 | if (propertyInfo.IsMemberObsolete(classType)) 431 | { 432 | continue; 433 | } 434 | 435 | string propertyTypeName = propertyInfo.PropertyType.ToLuaTypeName(); 436 | 437 | sb.AppendLine(string.Format("---@field {0} {1}", propertyInfo.Name, propertyTypeName.MakeLuaFunctionCompatible())); 438 | } 439 | 440 | if (_options.InferGenericFieldType) 441 | { 442 | Dictionary inferedGenericFieldInfos = new(); 443 | 444 | // 如果一个类型自身不是泛型, 但其父类是泛型, 则可认为在本次继承过程中完全地提供了父类所需的泛型信息. 445 | // 这种情况下, 从父类继承而来的泛型字段可以通过该信息进行类型推断. 446 | if (!classType.IsGenericType 447 | && classType.BaseType != null 448 | && classType.BaseType.IsGenericType 449 | // 只适用于父类的泛型参数只有一个的情况 450 | // 如果有多个泛型参数, 则不能很好的分析泛型字段应使用哪一个 451 | && classType.BaseType.GetGenericArguments().Length == 1 452 | ) 453 | { 454 | var genericTypeDefinition = classType.BaseType.GetGenericTypeDefinition(); 455 | 456 | // 尝试推断泛型字段 457 | var baseClassFields = 458 | genericTypeDefinition.GetFields(BindingFlags.Public | BindingFlags.Instance) 459 | .Concat( 460 | genericTypeDefinition.GetFields(BindingFlags.Public | BindingFlags.Static) 461 | ) 462 | .ToArray(); 463 | 464 | foreach (var field in baseClassFields) 465 | { 466 | if (!field.FieldType.IsGenericParameter) 467 | { 468 | continue; 469 | } 470 | 471 | inferedGenericFieldInfos.Add(field.Name, classType); 472 | } 473 | 474 | // 尝试推断泛型属性 475 | var baseClassProperties = 476 | genericTypeDefinition.GetProperties(BindingFlags.Public | BindingFlags.Instance) 477 | .Concat( 478 | genericTypeDefinition.GetProperties(BindingFlags.Public | BindingFlags.Static) 479 | ) 480 | .ToArray(); 481 | 482 | foreach (var property in baseClassProperties) 483 | { 484 | if (!property.PropertyType.IsGenericParameter) 485 | { 486 | continue; 487 | } 488 | 489 | inferedGenericFieldInfos.Add(property.Name, classType); 490 | } 491 | 492 | // 写入 493 | foreach (var inferedGenericFieldInfo in inferedGenericFieldInfos) 494 | { 495 | sb.AppendLine(string.Format( 496 | "---@field {0} {1} -- infered from {2}", 497 | inferedGenericFieldInfo.Key, 498 | inferedGenericFieldInfo.Value.ToLuaTypeName(), 499 | classType.BaseType 500 | )); 501 | } 502 | } 503 | } 504 | } 505 | 506 | public static void WriteClassAliasDefine(Type type) 507 | { 508 | string typeName = type.ToLuaTypeName(addCSPrefix: false); 509 | string typeCSAlias = type.ToLuaTypeName(addCSPrefix: true); 510 | 511 | sb.AppendLine(string.Format("---@alias {0} {1}", typeCSAlias, typeName)); 512 | sb.AppendLine(string.Format("{0} = {1}", typeCSAlias, typeName)); 513 | sb.AppendLine(""); 514 | } 515 | 516 | public static void WriteClassConstructorDefine(Type type) 517 | { 518 | if (type == typeof(MonoBehaviour) || type.IsSubclassOf(typeof(MonoBehaviour))) 519 | { 520 | return; 521 | } 522 | 523 | string className = type.ToLuaTypeName().ReplaceDotOrPlusWithUnderscore(); 524 | ConstructorInfo[] constructorInfos = type.GetConstructors(); 525 | if (constructorInfos.Length == 0) 526 | { 527 | return; 528 | } 529 | 530 | for (int i = 0; i < constructorInfos.Length - 1; i++) 531 | { 532 | ConstructorInfo ctorInfo = constructorInfos[i]; 533 | if (ctorInfo.IsStatic || ctorInfo.IsGenericMethod) 534 | { 535 | continue; 536 | } 537 | 538 | WriteOverloadMethodCommentDecalre( 539 | parameterInfos: ctorInfo.GetParameters(), 540 | returnType: type, 541 | classType: null // constructor has no "class type", although it's a member of the class 542 | ); 543 | } 544 | 545 | ConstructorInfo lastCtorInfo = constructorInfos[constructorInfos.Length - 1]; 546 | WriteMethodFunctionDeclare(lastCtorInfo.GetParameters(), type, "New", className, true); 547 | } 548 | 549 | public static void WriteClassMethodDefine(Type type) 550 | { 551 | // string classNameWithNameSpace = type.ToLuaTypeName().Replace(".", "_"); 552 | string classNameWithNameSpace = type.ToLuaTypeName(); 553 | 554 | Dictionary> methodGroup = new Dictionary>(); 555 | MethodInfo[] publicInstanceMethodInfos = 556 | type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); 557 | MethodInfo[] publicStaticMethodInfos = 558 | type.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly); 559 | 560 | Action recordMethodGroup = methodInfo => 561 | { 562 | string methodName = methodInfo.Name; 563 | 564 | if (methodInfo.IsGenericMethod) 565 | { 566 | return; 567 | } 568 | 569 | if (methodName.StartsWith("get_") || methodName.StartsWith("set_") || methodName.StartsWith("op_")) 570 | { 571 | return; 572 | } 573 | 574 | if (methodName.StartsWith("add_") || methodName.StartsWith("remove_")) 575 | { 576 | return; 577 | } 578 | 579 | if (methodGroup.ContainsKey(methodName)) 580 | { 581 | List methodInfoList = methodGroup[methodName]; 582 | if (methodInfoList == null) 583 | { 584 | methodInfoList = new List(); 585 | } 586 | 587 | methodInfoList.Add(methodInfo); 588 | methodGroup[methodName] = methodInfoList; 589 | } 590 | else 591 | { 592 | methodGroup.Add(methodName, new List { methodInfo }); 593 | } 594 | }; 595 | 596 | for (int i = 0; i < publicStaticMethodInfos.Length; i++) 597 | { 598 | MethodInfo methodInfo = publicStaticMethodInfos[i]; 599 | if (methodInfo.IsMemberObsolete(type)) 600 | { 601 | continue; 602 | } 603 | 604 | recordMethodGroup(methodInfo); 605 | } 606 | 607 | for (int i = 0; i < publicInstanceMethodInfos.Length; i++) 608 | { 609 | MethodInfo methodInfo = publicInstanceMethodInfos[i]; 610 | if (methodInfo.IsMemberObsolete(type)) 611 | { 612 | continue; 613 | } 614 | 615 | recordMethodGroup(methodInfo); 616 | } 617 | 618 | foreach (var oneGroup in methodGroup) 619 | { 620 | List methodInfoList = oneGroup.Value; 621 | //前面的方法都是overload 622 | for (int i = 0; i < methodInfoList.Count - 1; i++) 623 | { 624 | var methodInfo = methodInfoList[i]; 625 | 626 | WriteOverloadMethodCommentDecalre( 627 | parameterInfos: methodInfo.GetParameters(), 628 | returnType: methodInfo.ReturnType, 629 | classType: methodInfo.IsStatic ? null : type 630 | ); 631 | } 632 | 633 | MethodInfo lastMethodInfo = methodInfoList[methodInfoList.Count - 1]; 634 | WriteMethodFunctionDeclare(lastMethodInfo.GetParameters(), lastMethodInfo.ReturnType, 635 | lastMethodInfo.Name, 636 | classNameWithNameSpace, lastMethodInfo.IsStatic); 637 | } 638 | 639 | WriteExtensionMethodFunctionDecalre(type); 640 | } 641 | 642 | public static void WriteOverloadMethodCommentDecalre( 643 | ParameterInfo[] parameterInfos, 644 | Type returnType, 645 | Type classType // if null, means static method 646 | ) 647 | { 648 | List outOrRefParameterInfoList = new List(); 649 | 650 | tempSb.Clear(); 651 | for (int i = 0; i < parameterInfos.Length; i++) 652 | { 653 | ParameterInfo parameterInfo = parameterInfos[i]; 654 | string parameterName = parameterInfo.Name; 655 | string parameterTypeName = parameterInfo.ParameterType.ToLuaTypeName().MakeLuaFunctionCompatible(); 656 | if (parameterInfo.IsOut) 657 | { 658 | parameterName = "out_" + parameterName; 659 | outOrRefParameterInfoList.Add(parameterInfo); 660 | 661 | parameterTypeName = parameterInfo.ParameterType.GetElementType().ToLuaTypeName().MakeLuaFunctionCompatible(); 662 | } 663 | else if (parameterInfo.ParameterType.IsByRef) 664 | { 665 | parameterName = "ref_" + parameterName; 666 | outOrRefParameterInfoList.Add(parameterInfo); 667 | 668 | parameterTypeName = parameterInfo.ParameterType.GetElementType().ToLuaTypeName().MakeLuaFunctionCompatible(); 669 | } 670 | 671 | // write self parameter 672 | if (i == 0 && classType != null) 673 | { 674 | string selfParameterName = "self"; 675 | string selfParameterTypeName = classType.ToLuaTypeName(); 676 | tempSb.Append(string.Format("{0}: {1}, ", selfParameterName, selfParameterTypeName)); 677 | } 678 | 679 | // write other parameters 680 | parameterName = EscapeLuaKeyword(parameterName); 681 | if (i == parameterInfos.Length - 1) 682 | { 683 | tempSb.Append(string.Format("{0}: {1}", parameterName, parameterTypeName)); 684 | } 685 | else 686 | { 687 | tempSb.Append(string.Format("{0}: {1}, ", parameterName, parameterTypeName)); 688 | } 689 | } 690 | 691 | //return 692 | List returnTypeList = new List(); 693 | if (returnType != null && returnType != typeof(void)) 694 | { 695 | returnTypeList.Add(returnType); 696 | } 697 | 698 | for (int i = 0; i < outOrRefParameterInfoList.Count; i++) 699 | { 700 | returnTypeList.Add(outOrRefParameterInfoList[i].ParameterType.GetElementType()); 701 | } 702 | 703 | string returnTypeString = ""; 704 | for (int i = 0; i < returnTypeList.Count; i++) 705 | { 706 | if (i == returnTypeList.Count - 1) 707 | { 708 | returnTypeString += returnTypeList[i].ToLuaTypeName(); 709 | } 710 | else 711 | { 712 | returnTypeString += returnTypeList[i].ToLuaTypeName() + ", "; 713 | } 714 | } 715 | 716 | if (returnTypeList.Count > 0) 717 | { 718 | sb.AppendLine(string.Format("---@overload fun({0}) : {1}", tempSb, returnTypeString)); 719 | } 720 | else 721 | { 722 | sb.AppendLine(string.Format("---@overload fun({0})", tempSb)); 723 | } 724 | } 725 | 726 | public static void WriteMethodFunctionDeclare(ParameterInfo[] parameterInfos, Type returnType, 727 | string methodName, 728 | string className, bool isStaticMethod) 729 | { 730 | List outOrRefParameterInfoList = new List(); 731 | 732 | tempSb.Clear(); 733 | for (int i = 0; i < parameterInfos.Length; i++) 734 | { 735 | ParameterInfo parameterInfo = parameterInfos[i]; 736 | string parameterName = parameterInfo.Name; 737 | string parameterTypeName = parameterInfo.ParameterType.ToLuaTypeName(); 738 | if (parameterInfo.IsOut) 739 | { 740 | parameterName = "out_" + parameterName; 741 | outOrRefParameterInfoList.Add(parameterInfo); 742 | 743 | parameterTypeName = parameterInfo.ParameterType.GetElementType().ToLuaTypeName(); 744 | } 745 | else if (parameterInfo.ParameterType.IsByRef) 746 | { 747 | parameterName = "ref_" + parameterName; 748 | outOrRefParameterInfoList.Add(parameterInfo); 749 | 750 | parameterTypeName = parameterInfo.ParameterType.GetElementType().ToLuaTypeName(); 751 | } 752 | 753 | parameterName = EscapeLuaKeyword(parameterName); 754 | 755 | if (i == parameterInfos.Length - 1) 756 | { 757 | tempSb.Append(parameterName); 758 | } 759 | else 760 | { 761 | tempSb.Append(string.Format("{0}, ", parameterName)); 762 | } 763 | 764 | sb.AppendLine(string.Format("---@param {0} {1}", parameterName, parameterTypeName.MakeLuaFunctionCompatible())); 765 | } 766 | 767 | //return 768 | bool haveReturen = returnType != null && returnType != typeof(void) || outOrRefParameterInfoList.Count > 0; 769 | 770 | if (haveReturen) 771 | { 772 | sb.Append("---@return "); 773 | } 774 | 775 | if (returnType != null && returnType != typeof(void)) 776 | { 777 | sb.Append(returnType.ToLuaTypeName().MakeLuaFunctionCompatible()); 778 | } 779 | 780 | for (int i = 0; i < outOrRefParameterInfoList.Count; i++) 781 | { 782 | sb.Append(string.Format(",{0}", 783 | outOrRefParameterInfoList[i].ParameterType.GetElementType().ToLuaTypeName())); 784 | } 785 | 786 | if (haveReturen) 787 | { 788 | sb.AppendLine(""); 789 | } 790 | 791 | if (isStaticMethod) 792 | { 793 | sb.AppendLine(string.Format("function {0}.{1}({2}) end", className, methodName, tempSb)); 794 | } 795 | else 796 | { 797 | sb.AppendLine(string.Format("function {0}:{1}({2}) end", className, methodName, tempSb)); 798 | } 799 | } 800 | 801 | private static void WriteExtensionMethodFunctionDecalre(Type type) 802 | { 803 | if (extensionMethodsDic.TryGetValue(type, out List extensionMethodList)) 804 | { 805 | for (var i = 0; i < extensionMethodList.Count; i++) 806 | { 807 | MethodInfo methodInfo = extensionMethodList[i]; 808 | ParameterInfo[] parameterInfos = methodInfo.GetParameters(); 809 | if (parameterInfos.Length > 0) 810 | { 811 | //第一个param是拓展类型,去掉 812 | parameterInfos = parameterInfos.ToList().GetRange(1, parameterInfos.Length - 1).ToArray(); 813 | } 814 | 815 | Type returnType = methodInfo.ReturnType; 816 | string methodName = methodInfo.Name; 817 | string classNameWithNameSpace = type.ToLuaTypeName(); 818 | 819 | WriteMethodFunctionDeclare(parameterInfos, returnType, methodName, classNameWithNameSpace, false); 820 | } 821 | } 822 | } 823 | 824 | #endregion 825 | 826 | private static string MakeLuaFunctionCompatible(this string typeName) 827 | { 828 | return _functionCompatibleTypes.Contains(typeName) 829 | ? typeName + " | function" 830 | : typeName; 831 | } 832 | 833 | private static bool keepStringTypeName; 834 | 835 | public static string ToLuaTypeName(this Type type, bool addCSPrefix = false) 836 | { 837 | string prefix = addCSPrefix ? "CS." : ""; 838 | 839 | if (type == null) 840 | { 841 | return "NullType"; 842 | } 843 | 844 | if (luaNumberTypeSet.Contains(type)) 845 | { 846 | return "number"; 847 | } 848 | 849 | if (type == typeof(string)) 850 | { 851 | return keepStringTypeName ? "System.String" : "string"; 852 | } 853 | 854 | if (type == typeof(bool)) 855 | { 856 | return "boolean"; 857 | } 858 | 859 | string typeName = type.FullName; 860 | if (typeName == null) 861 | { 862 | return prefix + type.ToString().EscapeGenericTypeSuffix(); 863 | } 864 | 865 | if (type.IsEnum) 866 | { 867 | return prefix + type.FullName.EscapeGenericTypeSuffix().Replace("+", "."); 868 | } 869 | 870 | //去除泛型后缀 871 | typeName = typeName.EscapeGenericTypeSuffix(); 872 | 873 | int bracketIndex = typeName.IndexOf("[["); 874 | if (bracketIndex > 0) 875 | { 876 | typeName = typeName.Substring(0, bracketIndex); 877 | } 878 | 879 | return prefix + typeName; 880 | } 881 | 882 | private static Dictionary CSharpTypeNameDic = new Dictionary 883 | { 884 | {typeof(byte), "byte"}, 885 | {typeof(sbyte), "sbyte"}, 886 | {typeof(short), "short"}, 887 | {typeof(ushort), "ushort"}, 888 | {typeof(int), "int"}, 889 | {typeof(uint), "uint"}, 890 | {typeof(long), "long"}, 891 | {typeof(ulong), "ulong"}, 892 | {typeof(float), "float"}, 893 | {typeof(double), "double"}, 894 | {typeof(bool), "bool"}, 895 | {typeof(string), "string"}, 896 | }; 897 | 898 | 899 | public static string EscapeLuaKeyword(string s) 900 | { 901 | if (luaKeywordSet.Contains(s)) 902 | { 903 | return "_" + s; 904 | } 905 | 906 | return s; 907 | } 908 | 909 | public static string ReplaceDotOrPlusWithUnderscore(this string s) 910 | { 911 | // return s.Replace(".", "_").Replace("+", "_"); 912 | return s.Replace("+", "_"); 913 | } 914 | 915 | public static string EscapeGenericTypeSuffix(this string s) 916 | { 917 | string result = Regex.Replace(s , @"\`[0-9]+", ""); 918 | 919 | result = result.Replace("+", "."); 920 | 921 | return result; 922 | } 923 | 924 | public static bool IsMemberObsolete(this MemberInfo memberInfo, Type type) 925 | { 926 | return memberInfo.GetCustomAttributes(typeof(ObsoleteAttribute), false).Length > 0 || 927 | IsMemberFilter(memberInfo, type); 928 | } 929 | 930 | public static bool IsMemberFilter(MemberInfo mi, Type type) 931 | { 932 | if (type.IsGenericType) 933 | { 934 | } 935 | return false; 936 | } 937 | } 938 | } --------------------------------------------------------------------------------