// Tencent is pleased to support the open source community by making UnLua available. // // Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. // // Licensed under the MIT License (the "License"); // you may not use this file except in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and limitations under the License. #include "UnLuaIntelliSense.h" #include "Binding.h" #include "ObjectEditorUtils.h" #include "UnLuaInterface.h" namespace UnLua { namespace IntelliSense { static const FName NAME_ToolTip(TEXT("ToolTip")); // key of ToolTip meta data static const FName NAME_LatentInfo = TEXT("LatentInfo"); // tag of latent function static FString LuaKeywords[] = {TEXT("local"), TEXT("function"), TEXT("end")}; static FString GetCommentBlock(const UField* Field) { const FString ToolTip = Field->GetMetaData(NAME_ToolTip); if (ToolTip.IsEmpty()) return ""; return "---" + EscapeComments(ToolTip, false) + "\r\n"; } FString Get(const UBlueprint* Blueprint) { return Get(Blueprint->GeneratedClass); } FString Get(const UField* Field) { const UStruct* Struct = Cast(Field); if (Struct) return Get(Struct); const UEnum* Enum = Cast(Field); if (Enum) return Get(Enum); return ""; } FString Get(const UEnum* Enum) { FString Ret = GetCommentBlock(Enum); FString TypeName = GetTypeName(Enum); Ret += FString::Printf(TEXT("---@class %s"), *TypeName); // fields const int32 Num = Enum->NumEnums(); for (int32 i = 0; i < Num; ++i) { Ret += FString::Printf(TEXT("\r\n---@field %s %s %s"), TEXT("public"), *Enum->GetNameStringByIndex(i), TEXT("integer")); } // declaration Ret += FString::Printf(TEXT("\r\nlocal %s = {}\r\n"), *EscapeSymbolName(TypeName)); return Ret; } FString Get(const UStruct* Struct) { const UClass* Class = Cast(Struct); if (Class) return Get(Class); const UScriptStruct* ScriptStruct = Cast(Struct); if (ScriptStruct) return Get(ScriptStruct); const UFunction* Function = Cast(Struct); if (Function) return Get(Function); return ""; } FString Get(const UScriptStruct* ScriptStruct) { FString Ret = GetCommentBlock(ScriptStruct); FString TypeName = GetTypeName(ScriptStruct); Ret += "---@class " + TypeName; UStruct* SuperStruct = ScriptStruct->GetSuperStruct(); if (SuperStruct) Ret += " : " + GetTypeName(SuperStruct); Ret += "\r\n"; // fields for (TFieldIterator It(ScriptStruct, EFieldIteratorFlags::ExcludeSuper, EFieldIteratorFlags::ExcludeDeprecated); It; ++It) { const FProperty* Property = *It; Ret += Get(Property) += "\r\n"; } // declaration Ret += FString::Printf(TEXT("local %s = {}\r\n"), *EscapeSymbolName(TypeName)); // exported functions const auto Exported = GetExportedReflectedClasses().Find(TypeName); if (Exported) { TArray ExportedFunctions; (*Exported)->GetFunctions(ExportedFunctions); for (const auto Function : ExportedFunctions) Function->GenerateIntelliSense(Ret); } return Ret; } FString Get(const UClass* Class) { FString Ret = GetCommentBlock(Class); const FString TypeName = GetTypeName(Class); Ret += "---@class " + TypeName; UStruct* SuperStruct = Class->GetSuperStruct(); if (SuperStruct) Ret += " : " + GetTypeName(SuperStruct); Ret += "\r\n"; // fields for (TFieldIterator It(Class, EFieldIteratorFlags::ExcludeSuper, EFieldIteratorFlags::ExcludeDeprecated); It; ++It) { const FProperty* Property = *It; Ret += Get(Property) += "\r\n"; } // declaration const auto EscapedClassName = EscapeSymbolName(TypeName); Ret += FString::Printf(TEXT("local %s = {}\r\n\r\n"), *EscapedClassName); TArray GenFunctionNames; // functions for (TFieldIterator FunctionIt(Class, EFieldIteratorFlags::ExcludeSuper, EFieldIteratorFlags::ExcludeDeprecated, EFieldIteratorFlags::ExcludeInterfaces); FunctionIt; ++FunctionIt) { const UFunction* Function = *FunctionIt; if (!IsValid(Function)) continue; if (FObjectEditorUtils::IsFunctionHiddenFromClass(Function, Class)) continue; Ret += Get(Function) + "\r\n"; GenFunctionNames.Add(Function->GetName()); } // interface functions for (TFieldIterator FunctionIt(Class, EFieldIteratorFlags::IncludeSuper, EFieldIteratorFlags::ExcludeDeprecated, EFieldIteratorFlags::IncludeInterfaces); FunctionIt; ++FunctionIt) { const UFunction* Function = *FunctionIt; if (!Function->GetOwnerClass()->IsChildOf(UInterface::StaticClass())) continue; if (!IsValid(Function)) continue; if (FObjectEditorUtils::IsFunctionHiddenFromClass(Function, Class)) continue; if (GenFunctionNames.Contains(Function->GetName())) continue; Ret += Get(Function, EscapedClassName) + "\r\n"; } // exported functions const auto Exported = GetExportedReflectedClasses().Find(TypeName); if (Exported) { TArray ExportedFunctions; (*Exported)->GetFunctions(ExportedFunctions); for (const auto Function : ExportedFunctions) Function->GenerateIntelliSense(Ret); } return Ret; } FString Get(const UFunction* Function, FString ForceClassName) { FString Ret = GetCommentBlock(Function); FString Properties; for (TFieldIterator It(Function); It && (It->PropertyFlags & CPF_Parm); ++It) { const FProperty* Property = *It; if (Property->GetFName() == NAME_LatentInfo) continue; FString TypeName = EscapeSymbolName(GetTypeName(Property)); const FString& PropertyComment = Property->GetMetaData(NAME_ToolTip); FString ExtraDesc; if (Property->HasAnyPropertyFlags(CPF_ReturnParm)) { Ret += FString::Printf(TEXT("---@return %s"), *TypeName); // return parameter } else { FString PropertyName = EscapeSymbolName(*Property->GetName()); if (Properties.IsEmpty()) Properties = PropertyName; else Properties += FString::Printf(TEXT(", %s"), *PropertyName); FName KeyName = FName(*FString::Printf(TEXT("CPP_Default_%s"), *Property->GetName())); const FString& Value = Function->GetMetaData(KeyName); if (!Value.IsEmpty()) { ExtraDesc = TEXT("[opt]"); // default parameter } else if (Property->HasAnyPropertyFlags(CPF_OutParm) && !Property->HasAnyPropertyFlags(CPF_ConstParm)) { ExtraDesc = TEXT("[out]"); // non-const reference } Ret += FString::Printf(TEXT("---@param %s %s"), *PropertyName, *TypeName); } if (ExtraDesc.Len() > 0 || PropertyComment.Len() > 0) { Ret += TEXT(" @"); if (ExtraDesc.Len() > 0) Ret += FString::Printf(TEXT("%s "), *ExtraDesc); } Ret += TEXT("\r\n"); } const auto ClassName = ForceClassName.IsEmpty() ? EscapeSymbolName(GetTypeName(Function->GetOwnerClass())) : ForceClassName; const auto FunctionName = Function->GetName(); const auto bIsStatic = Function->HasAnyFunctionFlags(FUNC_Static); if (IsValidFunctionName(FunctionName)) { Ret += FString::Printf(TEXT("function %s%s%s(%s) end\r\n"), *ClassName, bIsStatic ? TEXT(".") : TEXT(":"), *FunctionName, *Properties); } else { const auto Self = bIsStatic ? TEXT("") : TEXT("self"); const auto Comma = bIsStatic || Properties.IsEmpty() ? TEXT("") : TEXT(", "); Ret += FString::Printf(TEXT("%s[\"%s\"] = function(%s%s%s) end\r\n"), *ClassName, *FunctionName, Self, Comma, *Properties); } return Ret; } FString Get(const FProperty* Property) { FString Ret; const UStruct* Struct = Property->GetOwnerStruct(); // access level FString AccessLevel; if (Property->HasAnyPropertyFlags(CPF_NativeAccessSpecifierPublic)) AccessLevel = TEXT("public"); else if (Property->HasAllPropertyFlags(CPF_NativeAccessSpecifierProtected)) AccessLevel = TEXT("protected"); else if (Property->HasAllPropertyFlags(CPF_NativeAccessSpecifierPrivate)) AccessLevel = TEXT("private"); else AccessLevel = Struct->IsNative() ? "private" : "public"; FString TypeName = IntelliSense::GetTypeName(Property); Ret += FString::Printf(TEXT("---@field %s %s %s"), *AccessLevel, *Property->GetName(), *TypeName); // comment const FString& ToolTip = Property->GetMetaData(NAME_ToolTip); if (!ToolTip.IsEmpty()) Ret += " @" + EscapeComments(ToolTip, true); return Ret; } FString GetUE(const TArray AllTypes) { FString Content = "UE = {"; for (const auto Type : AllTypes) { if (!Type->IsNative()) continue; const auto Name = GetTypeName(Type); Content += FString::Printf(TEXT("\r\n ---@type %s\r\n"), *Name); Content += FString::Printf(TEXT(" %s = nil,\r\n"), *Name); } Content += "}\r\n"; return Content; } FString GetTypeName(const UObject* Field) { if (!Field) return ""; if (!Field->IsNative() && Field->GetName().EndsWith("_C")) return Field->GetName(); const UStruct* Struct = Cast(Field); if (Struct) return Struct->GetPrefixCPP() + Struct->GetName(); return Field->GetName(); } FString GetTypeName(const FProperty* Property) { if (!Property) return "Unknown"; if (CastField(Property)) return "integer"; if (CastField(Property)) return "integer"; if (CastField(Property)) return "integer"; if (CastField(Property)) return "integer"; if (CastField(Property)) return "integer"; if (CastField(Property)) return "integer"; if (CastField(Property)) return "integer"; if (CastField(Property)) return "integer"; if (CastField(Property)) return "number"; if (CastField(Property)) return "number"; if (CastField(Property)) return ((FEnumProperty*)Property)->GetEnum()->GetName(); if (CastField(Property)) return TEXT("boolean"); if (CastField(Property)) { const UClass* Class = ((FClassProperty*)Property)->MetaClass; return FString::Printf(TEXT("TSubclassOf<%s%s>"), Class->GetPrefixCPP(), *Class->GetName()); } if (CastField(Property)) { if (((FSoftObjectProperty*)Property)->PropertyClass->IsChildOf(UClass::StaticClass())) { const UClass* Class = ((FSoftClassProperty*)Property)->MetaClass; return FString::Printf(TEXT("TSoftClassPtr<%s%s>"), Class->GetPrefixCPP(), *Class->GetName()); } const UClass* Class = ((FSoftObjectProperty*)Property)->PropertyClass; return FString::Printf(TEXT("TSoftObjectPtr<%s%s>"), Class->GetPrefixCPP(), *Class->GetName()); } if (CastField(Property)) { const UClass* Class = ((FObjectProperty*)Property)->PropertyClass; if (Cast(Class)) { return FString::Printf(TEXT("%s"), *Class->GetName()); } return FString::Printf(TEXT("%s%s"), Class->GetPrefixCPP(), *Class->GetName()); } if (CastField(Property)) { const UClass* Class = ((FWeakObjectProperty*)Property)->PropertyClass; return FString::Printf(TEXT("TWeakObjectPtr<%s%s>"), Class->GetPrefixCPP(), *Class->GetName()); } if (CastField(Property)) { const UClass* Class = ((FLazyObjectProperty*)Property)->PropertyClass; return FString::Printf(TEXT("TLazyObjectPtr<%s%s>"), Class->GetPrefixCPP(), *Class->GetName()); } if (CastField(Property)) { const UClass* Class = ((FInterfaceProperty*)Property)->InterfaceClass; return FString::Printf(TEXT("TScriptInterface<%s%s>"), Class->GetPrefixCPP(), *Class->GetName()); } if (CastField(Property)) return "string"; if (CastField(Property)) return "string"; if (CastField(Property)) return "string"; if (CastField(Property)) { const FProperty* Inner = ((FArrayProperty*)Property)->Inner; return FString::Printf(TEXT("TArray<%s>"), *GetTypeName(Inner)); } if (CastField(Property)) { const FProperty* KeyProp = ((FMapProperty*)Property)->KeyProp; const FProperty* ValueProp = ((FMapProperty*)Property)->ValueProp; return FString::Printf(TEXT("TMap<%s, %s>"), *GetTypeName(KeyProp), *GetTypeName(ValueProp)); } if (CastField(Property)) { const FProperty* ElementProp = ((FSetProperty*)Property)->ElementProp; return FString::Printf(TEXT("TSet<%s>"), *GetTypeName(ElementProp)); } if (CastField(Property)) return ((FStructProperty*)Property)->Struct->GetStructCPPName(); if (CastField(Property)) return "Delegate"; if (CastField(Property)) return "MulticastDelegate"; return "Unknown"; } FString EscapeComments(const FString Comments, const bool bSingleLine) { if (Comments.IsEmpty()) return Comments; auto Filter = [](const FString Prefix, const FString Line) { if (Line.StartsWith("@")) return FString(); return Prefix + Line; }; TArray Lines; Comments.Replace(TEXT("@"), TEXT("@@")).ParseIntoArray(Lines, TEXT("\n")); FString Ret = Filter("", Lines[0]); if (bSingleLine) { for (int i = 1; i < Lines.Num(); i++) Ret += Filter(" ", Lines[i]); } else { for (int i = 1; i < Lines.Num(); i++) Ret += Filter("\r\n---", Lines[i]); } return Ret; } FString EscapeSymbolName(const FString InName) { FString Name = InName; // 和Lua关键字重名就加前缀 for (const auto& Keyword : LuaKeywords) { if (Name.Equals(Keyword, ESearchCase::CaseSensitive)) { Name = TEXT("_") + Name; break; } } // 替换掉除中文外的特殊字符 for (int32 Index = 0; Index < Name.Len(); Index++) { const auto Char = Name[Index]; if ((Char < '0' || Char > '9') && (Char < 'a' || Char > 'z') && (Char < 'A' || Char > 'Z') && Char != '_' && Char < 0x100) { Name[Index] = TCHAR('_'); } } // 数字开头就加前缀 const auto PrefixChar = Name[0]; if (PrefixChar >= '0' && PrefixChar <= '9') { Name = TEXT("_") + Name; } return Name; } bool IsValid(const UFunction* Function) { if (!Function) return false; if (Function->HasAnyFunctionFlags(FUNC_UbergraphFunction)) return false; // 这个会导致USubsystemBlueprintLibrary.GetWorldSubsystem之类的被过滤掉 // if (!UEdGraphSchema_K2::CanUserKismetCallFunction(Function)) // return false; const FString Name = Function->GetName(); if (Name.IsEmpty()) return false; // 避免运行时生成智能提示,把覆写的函数也生成了 if (Name.EndsWith("__Overridden")) return false; return true; } bool IsValidFunctionName(const FString Name) { if (Name.IsEmpty()) return false; // 不能有Lua关键字 for (const auto& Keyword : LuaKeywords) { if (Name.Equals(Keyword, ESearchCase::CaseSensitive)) return false; } // 不能有中文和特殊字符 for (const auto& Char : Name) { if ((Char < '0' || Char > '9') && (Char < 'a' || Char > 'z') && (Char < 'A' || Char > 'Z') && Char != '_') return false; } // 不能以数字开头 const auto& PrefixChar = Name[0]; if (PrefixChar >= '0' && PrefixChar <= '9') return false; return true; } } }