diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 800edcd9c0..66f3e372d3 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -33,7 +33,7 @@ jobs: - name: Update bundles if: ${{ github.repository == 'Anuken/Mindustry' }} run: | - ./gradlew updateBundles + ./gradlew -DcompileMethodParams updateBundles if [ -n "$(git status --porcelain)" ]; then git config --global user.name "Github Actions" @@ -45,7 +45,7 @@ jobs: - name: Update scripts, class mappings and map names if: ${{ github.repository == 'Anuken/Mindustry' }} run: | - ./gradlew updateScripts fixMaps + ./gradlew -DcompileMethodParams updateScripts fixMaps if [ -n "$(git status --porcelain)" ]; then git config --global user.name "Github Actions" diff --git a/build.gradle b/build.gradle index 33d832b58a..7ccf160d83 100644 --- a/build.gradle +++ b/build.gradle @@ -534,3 +534,9 @@ task resolveDependencies{ } } } + +if(System.getProperty("compileMethodParams") != null){ + project(":core").tasks.withType(JavaCompile).configureEach{ + options.compilerArgs << '-parameters' + } +} \ No newline at end of file diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 5dfe12572e..ed67f3a7de 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -321,8 +321,8 @@ server.admins.none = No admins found! server.add = Add Server server.delete = Are you sure you want to delete this server? server.edit = Edit Server -server.outdated = [scarlet]Outdated Server![] -server.outdated.client = [scarlet]Outdated Client![] +server.outdated = [red]⚠ Outdated Server![] +server.outdated.client = [red]⚠ Outdated Client![] server.version = [gray]v{0} {1} server.custombuild = [accent]Custom Build confirmban = Are you sure you want to ban "{0}[white]"? diff --git a/core/assets/bundles/bundle_bg.properties b/core/assets/bundles/bundle_bg.properties index 4a38654bff..9725ecd569 100644 --- a/core/assets/bundles/bundle_bg.properties +++ b/core/assets/bundles/bundle_bg.properties @@ -319,8 +319,8 @@ server.admins.none = Няма намерени администратори! server.add = Добави Сървър server.delete = Сигурни ли сте, че искате да изтриете този сървър? server.edit = Редактирай Сървър -server.outdated = [scarlet]Остарял Сървър![] -server.outdated.client = [scarlet]Остарял Клиент![] +server.outdated = [red]⚠ Остарял Сървър![] +server.outdated.client = [red]⚠ Остарял Клиент![] server.version = [gray]в{0} {1} server.custombuild = [accent]Персонализирана компилация confirmban = Сигурни ли сте, че искате да баннете "{0}[white]"? diff --git a/core/assets/bundles/bundle_ca.properties b/core/assets/bundles/bundle_ca.properties index 361243530e..237369c037 100644 --- a/core/assets/bundles/bundle_ca.properties +++ b/core/assets/bundles/bundle_ca.properties @@ -319,8 +319,8 @@ server.admins.none = No s’ha trobat cap administrador! server.add = Afegeix un servidor server.delete = Esteu segur que voleu esborrar aquest servidor? server.edit = Edita el servidor -server.outdated = [scarlet]Servidor desactualitzat![] -server.outdated.client = [scarlet]Client desactualitzat![] +server.outdated = [red]⚠ Servidor desactualitzat![] +server.outdated.client = [red]⚠ Client desactualitzat![] server.version = [gray]v{0} {1} server.custombuild = [accent]Versió personalitzada confirmban = Esteu segur que voleu bandejar a «{0}[white]»? diff --git a/core/assets/bundles/bundle_cs.properties b/core/assets/bundles/bundle_cs.properties index 6e022dd708..2b0886e15f 100644 --- a/core/assets/bundles/bundle_cs.properties +++ b/core/assets/bundles/bundle_cs.properties @@ -319,8 +319,8 @@ server.admins.none = Žádní správci nebyli nalezeni. server.add = Přidat server server.delete = Jsi si jistý, že chceš smazat tento server? server.edit = Upravit server -server.outdated = [scarlet]Zastaralá verze serveru![] -server.outdated.client = [scarlet]Zastaralá verze klienta![] +server.outdated = [red]⚠ Zastaralá verze serveru![] +server.outdated.client = [red]⚠ Zastaralá verze klienta![] server.version = [gray]Verze: {0} {1}[] server.custombuild = [accent]Upravená verze hry[] confirmban = Jsi si jistý, že chceš zakázat hráči "{0}[white]" přístup na server?[] diff --git a/core/assets/bundles/bundle_es.properties b/core/assets/bundles/bundle_es.properties index 902fdaefb1..1915cc493a 100644 --- a/core/assets/bundles/bundle_es.properties +++ b/core/assets/bundles/bundle_es.properties @@ -319,8 +319,8 @@ server.admins.none = ¡No hay ningún administrador! server.add = Agregar servidor server.delete = ¿Realmente quieres borrar este servidor? server.edit = Editar Servidor -server.outdated = [scarlet]¡Servidor Desactualizado![] -server.outdated.client = [scarlet]¡Cliente Desactualizado![] +server.outdated = [red]⚠ ¡Servidor Desactualizado![] +server.outdated.client = [red]⚠ ¡Cliente Desactualizado![] server.version = [gray]v{0} {1} server.custombuild = [accent]Versión personalizada confirmban = ¿Quieres vetar a "{0}[white]"? diff --git a/core/assets/bundles/bundle_fil.properties b/core/assets/bundles/bundle_fil.properties index bbea5d27bc..4c0666bb4e 100644 --- a/core/assets/bundles/bundle_fil.properties +++ b/core/assets/bundles/bundle_fil.properties @@ -319,8 +319,8 @@ server.admins.none = Walang nahnap na admin! server.add = Maglagay ng Server server.delete = Sigurado ka bang gusto mong tanggalin ang server na ito? server.edit = Ipalit ang Server -server.outdated = [scarlet]Di-bagong Server![] -server.outdated.client = [scarlet]Di-bagong Client![] +server.outdated = [red]⚠ Di-bagong Server![] +server.outdated.client = [red]⚠ Di-bagong Client![] server.version = [gray]v{0} {1} server.custombuild = [accent]Di-karaniwang Build confirmban = Sigurado ka bang gusto mong itanggal si "{0}[white]"? diff --git a/core/assets/bundles/bundle_fr.properties b/core/assets/bundles/bundle_fr.properties index b2ed8ed4cb..a2d2ad5449 100644 --- a/core/assets/bundles/bundle_fr.properties +++ b/core/assets/bundles/bundle_fr.properties @@ -319,8 +319,8 @@ server.admins.none = Aucun administrateur trouvé ! server.add = Ajouter un serveur server.delete = Êtes-vous sûr de vouloir supprimer ce serveur ? server.edit = Modifier le serveur -server.outdated = [scarlet]Serveur obsolète ![] -server.outdated.client = [scarlet]Client obsolète ![] +server.outdated = [red]⚠ Serveur obsolète ![] +server.outdated.client = [red]⚠ Client obsolète ![] server.version = [gray]Version : {0} {1} server.custombuild = [accent]Version personnalisée confirmban = Êtes-vous sûr de vouloir bannir "{0}[white]" ? diff --git a/core/assets/bundles/bundle_hu.properties b/core/assets/bundles/bundle_hu.properties index 8b3cabf853..db33e236c0 100644 --- a/core/assets/bundles/bundle_hu.properties +++ b/core/assets/bundles/bundle_hu.properties @@ -319,8 +319,8 @@ server.admins.none = Nem találhatók adminisztrátorok! server.add = Kiszolgáló hozzáadása server.delete = Biztosan törlöd ezt a kiszolgálót? server.edit = Kiszolgáló szerkesztése -server.outdated = [scarlet]Elavult kiszolgáló![] -server.outdated.client = [scarlet]Elavult kliens![] +server.outdated = [red]⚠ Elavult kiszolgáló![] +server.outdated.client = [red]⚠ Elavult kliens![] server.version = [gray]v{0} {1} server.custombuild = [accent]Egyéni összeállítás confirmban = Biztosan kitiltod a(z) „{0}[white]” nevű játékost? diff --git a/core/assets/bundles/bundle_id_ID.properties b/core/assets/bundles/bundle_id_ID.properties index f181fd4fdf..ded42ddc12 100644 --- a/core/assets/bundles/bundle_id_ID.properties +++ b/core/assets/bundles/bundle_id_ID.properties @@ -319,8 +319,8 @@ server.admins.none = Tidak ada admin ditemukan! server.add = Tambah Server server.delete = Anda yakin ingin menghapus server ini? server.edit = Sunting Server -server.outdated = [scarlet]Server Kedaluwarsa![] -server.outdated.client = [scarlet]Klien Kedaluwarsa![] +server.outdated = [red]⚠ Server Kedaluwarsa![] +server.outdated.client = [red]⚠ Klien Kedaluwarsa![] server.version = [gray]v{0} {1} server.custombuild = [accent]Bentuk Kustom confirmban = Anda yakin ingin melarang pemain ini untuk masuk lagi? diff --git a/core/assets/bundles/bundle_it.properties b/core/assets/bundles/bundle_it.properties index ed38dff930..ae915b85cc 100644 --- a/core/assets/bundles/bundle_it.properties +++ b/core/assets/bundles/bundle_it.properties @@ -319,8 +319,8 @@ server.admins.none = Nessun amministratore trovato! server.add = Aggiungi Server server.delete = Sei sicuro di voler eliminare questo server? server.edit = Modifica Server -server.outdated = [scarlet]Server Obsoleto![] -server.outdated.client = [scarlet]Client Obsoleto![] +server.outdated = [red]⚠ Server Obsoleto![] +server.outdated.client = [red]⚠ Client Obsoleto![] server.version = [gray]v{0} {1} server.custombuild = [accent]Build Personalizzata confirmban = Sei sicuro di voler bandire "{0}[white]"? diff --git a/core/assets/bundles/bundle_ko.properties b/core/assets/bundles/bundle_ko.properties index cfb559dbb9..78a4a3c297 100644 --- a/core/assets/bundles/bundle_ko.properties +++ b/core/assets/bundles/bundle_ko.properties @@ -319,8 +319,8 @@ server.admins.none = 관리자를 찾을 수 없습니다! server.add = 서버 추가 server.delete = 정말로 이 서버를 삭제하시겠습니까? server.edit = 서버 편집 -server.outdated = [scarlet]서버 버전이 낮습니다![] -server.outdated.client = [scarlet]클라이언트 버전이 낮습니다![] +server.outdated = [red]⚠ 서버 버전이 낮습니다![] +server.outdated.client = [red]⚠ 클라이언트 버전이 낮습니다![] server.version = [gray]v{0} {1} server.custombuild = [accent]사용자 정의 서버 confirmban = 정말로 "{0}[white]" 을(를) 차단하시겠습니까? diff --git a/core/assets/bundles/bundle_pt_BR.properties b/core/assets/bundles/bundle_pt_BR.properties index 747fdd76b2..0cc61cd07b 100644 --- a/core/assets/bundles/bundle_pt_BR.properties +++ b/core/assets/bundles/bundle_pt_BR.properties @@ -319,8 +319,8 @@ server.admins.none = Nenhum administrador encontrado! server.add = Adicionar servidor server.delete = Você tem certeza que quer deletar esse servidor? server.edit = Editar Servidor -server.outdated = [scarlet]Servidor Desatualizado![] -server.outdated.client = [scarlet]Cliente Desatualizado![] +server.outdated = [red]⚠ Servidor Desatualizado![] +server.outdated.client = [red]⚠ Cliente Desatualizado![] server.version = [gray]v{0} {1} server.custombuild = [accent]Versão Customizada confirmban = Você tem certeza que quer banir "{0}[white]"? diff --git a/core/assets/bundles/bundle_ro.properties b/core/assets/bundles/bundle_ro.properties index 74dedd1ebf..720d157cc7 100644 --- a/core/assets/bundles/bundle_ro.properties +++ b/core/assets/bundles/bundle_ro.properties @@ -319,8 +319,8 @@ server.admins.none = Nu s-au găsit admini! server.add = Adăugă server server.delete = Sigur vrei să ștergi acest server? server.edit = Editează Server -server.outdated = [scarlet]Server Învechit![] -server.outdated.client = [scarlet]Client Învechit![] +server.outdated = [red]⚠ Server Învechit![] +server.outdated.client = [red]⚠ Client Învechit![] server.version = [gray]v{0} {1} server.custombuild = [accent]Build Personalizat confirmban = Sigur vrei să interzici jucătorul "{0}[white]"? diff --git a/core/assets/bundles/bundle_ru.properties b/core/assets/bundles/bundle_ru.properties index 82f61c3f17..e8051e46b2 100644 --- a/core/assets/bundles/bundle_ru.properties +++ b/core/assets/bundles/bundle_ru.properties @@ -319,8 +319,8 @@ server.admins.none = Администраторов нет! server.add = Добавить сервер server.delete = Вы действительно хотите удалить этот сервер? server.edit = Редактировать сервер -server.outdated = [scarlet]Устаревший сервер![] -server.outdated.client = [scarlet]Устаревший клиент![] +server.outdated = [red]⚠ Устаревший сервер![] +server.outdated.client = [red]⚠ Устаревший клиент![] server.version = [gray]Версия: {0} {1} server.custombuild = [accent]Пользовательская сборка confirmban = Вы действительно хотите заблокировать игрока «{0}[white]»? diff --git a/core/assets/bundles/bundle_sr.properties b/core/assets/bundles/bundle_sr.properties index 7da3075b1d..80ea09311b 100644 --- a/core/assets/bundles/bundle_sr.properties +++ b/core/assets/bundles/bundle_sr.properties @@ -319,8 +319,8 @@ server.admins.none = Nema pronađenih administratora! server.add = Dodaj Server server.delete = Da li želite da obrišete ovaj server? server.edit = Izmeni Server -server.outdated = [scarlet]Zastareli Server![] -server.outdated.client = [scarlet]Zastareli Klijent![] +server.outdated = [red]⚠ Zastareli Server![] +server.outdated.client = [red]⚠ Zastareli Klijent![] server.version = [gray]v{0} {1} server.custombuild = [accent]Svjojehodna Verzija confirmban = Da li ste sigurni da želite da [scarlet]trajno[] izbacite "{0}[white]"? diff --git a/core/assets/bundles/bundle_vi.properties b/core/assets/bundles/bundle_vi.properties index 3f4d3e9c14..5ccec5a3bf 100644 --- a/core/assets/bundles/bundle_vi.properties +++ b/core/assets/bundles/bundle_vi.properties @@ -319,8 +319,8 @@ server.admins.none = Không tìm thấy quản trị viên nào! server.add = Thêm máy chủ server.delete = Bạn có chắc chắn muốn xóa máy chủ này không? server.edit = Chỉnh sửa máy chủ -server.outdated = [scarlet]Máy chủ lỗi thời![] -server.outdated.client = [scarlet]Máy khách lỗi thời![] +server.outdated = [red]⚠ Máy chủ lỗi thời![] +server.outdated.client = [red]⚠ Máy khách lỗi thời![] server.version = [gray]v{0} {1} server.custombuild = [accent]Bản dựng tùy chỉnh confirmban = Bạn có chắc chắn muốn cấm "{0}[white]"? diff --git a/core/assets/bundles/bundle_zh_CN.properties b/core/assets/bundles/bundle_zh_CN.properties index f0e85383bd..e059c5785c 100644 --- a/core/assets/bundles/bundle_zh_CN.properties +++ b/core/assets/bundles/bundle_zh_CN.properties @@ -319,8 +319,8 @@ server.admins.none = 找不到管理员! server.add = 添加服务器 server.delete = 确定要删除这个服务器吗? server.edit = 编辑服务器 -server.outdated = [scarlet]服务器版本过低![] -server.outdated.client = [scarlet]客户端版本过低![] +server.outdated = [red]⚠ 服务器版本过低![] +server.outdated.client = [red]⚠ 客户端版本过低![] server.version = [gray]v{0} {1} server.custombuild = [accent]自行编译 confirmban = 确定要封禁“{0}[white]”吗? diff --git a/core/assets/scripts/global.js b/core/assets/scripts/global.js index b4eccafc8c..5dfc485df4 100755 --- a/core/assets/scripts/global.js +++ b/core/assets/scripts/global.js @@ -48,16 +48,28 @@ const Arrays = Packages.java.util.Arrays const TextureFilter = Packages.arc.graphics.Texture.TextureFilter const TextureWrap = Packages.arc.graphics.Texture.TextureWrap importPackage(Packages.arc) +importPackage(Packages.arc.assets) +importPackage(Packages.arc.assets.loaders) importPackage(Packages.arc.audio) +importPackage(Packages.arc.backend.headless) importPackage(Packages.arc.files) importPackage(Packages.arc.flabel) +importPackage(Packages.arc.freetype) importPackage(Packages.arc.func) +importPackage(Packages.arc.fx) +importPackage(Packages.arc.fx.filters) +importPackage(Packages.arc.fx.util) importPackage(Packages.arc.graphics) importPackage(Packages.arc.graphics.g2d) +importPackage(Packages.arc.graphics.g3d) importPackage(Packages.arc.graphics.gl) importPackage(Packages.arc.input) importPackage(Packages.arc.math) importPackage(Packages.arc.math.geom) +importPackage(Packages.arc.mock) +importPackage(Packages.arc.net) +importPackage(Packages.arc.net.dns) +importPackage(Packages.arc.packer) importPackage(Packages.arc.scene) importPackage(Packages.arc.scene.actions) importPackage(Packages.arc.scene.event) diff --git a/core/src/mindustry/ui/dialogs/JoinDialog.java b/core/src/mindustry/ui/dialogs/JoinDialog.java index 8a73de0282..50e4f8d84f 100644 --- a/core/src/mindustry/ui/dialogs/JoinDialog.java +++ b/core/src/mindustry/ui/dialogs/JoinDialog.java @@ -779,11 +779,9 @@ public class JoinDialog extends BaseDialog{ }else if(host.version == 0){ return Core.bundle.get("server.outdated"); }else if(host.version < Version.build && Version.build != -1){ - return Core.bundle.get("server.outdated") + "\n" + - Core.bundle.format("server.version", host.version, host.versionType); + return "\n" + Core.bundle.format("server.version", host.version, host.versionType) + " " + Core.bundle.get("server.outdated"); }else if(host.version > Version.build && Version.build != -1){ - return Core.bundle.get("server.outdated.client") + "\n" + - Core.bundle.format("server.version", host.version, host.versionType); + return "\n" + Core.bundle.format("server.version", host.version, host.versionType) + " " + Core.bundle.get("server.outdated.client"); }else if(host.version == Version.build && Version.type.equals(host.versionType)){ //not important return ""; diff --git a/gradle.properties b/gradle.properties index 1f535da091..27df25a245 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,4 +26,4 @@ org.gradle.caching=true org.gradle.internal.http.socketTimeout=100000 org.gradle.internal.http.connectionTimeout=100000 android.enableR8.fullMode=false -archash=c47b7010b5 +archash=05ecc2c2b1 diff --git a/tools/src/mindustry/tools/ScriptMainGenerator.java b/tools/src/mindustry/tools/ScriptMainGenerator.java index daf57d629c..8c6695cda2 100644 --- a/tools/src/mindustry/tools/ScriptMainGenerator.java +++ b/tools/src/mindustry/tools/ScriptMainGenerator.java @@ -34,6 +34,7 @@ public class ScriptMainGenerator{ Seq> classes = Seq.withArrays( getClasses("mindustry"), + getClasses("arc"), getClasses("arc.func"), getClasses("arc.struct"), getClasses("arc.scene"), @@ -118,6 +119,8 @@ public class ScriptMainGenerator{ Log.info("Generated @ class mappings.", mapped.size); SchemaGenerator.writeDefaultContent(); + + TypescriptDtsGenerator.writeDts(classes, new Fi("../../build/mindusty.d.ts").absolutePath()); } public static Seq getClasses(String packageName) throws Exception{ final ClassLoader loader = Thread.currentThread().getContextClassLoader(); diff --git a/tools/src/mindustry/tools/TypescriptDtsGenerator.java b/tools/src/mindustry/tools/TypescriptDtsGenerator.java new file mode 100644 index 0000000000..fd67f134f3 --- /dev/null +++ b/tools/src/mindustry/tools/TypescriptDtsGenerator.java @@ -0,0 +1,264 @@ +package mindustry.tools; + +import arc.files.*; +import arc.struct.*; + +import java.lang.reflect.*; +import java.util.*; + +/** Generates a .d.ts file for JS type hints. */ +public class TypescriptDtsGenerator{ + + /** Java types with no reasonable TS analogue - erased to `any`. */ + private static final Set> erase = new HashSet<>(Arrays.asList( + Class.class, Object.class // Object is intentionally erased, not mapped to `object`, since Java's Object methods (equals/hashCode/toString) are rarely what someone typing `.` after an `any`-ish value wants + )); + + private static final String rhinoPrelude = + "// --- Rhino script-runtime globals (not reflected - hand-written) ---\n" + + "declare function extend(base: any, ...args: any[]): any;\n" + + "declare function extendContent(base: any, name: string, fields: object): any;\n" + + "declare function require(module: string): any;\n" + + "declare function print(text: string): void;\n" + + "declare function output(...args: any[]): void;\n" + + "declare function importPackage(...args: any[]): void;\n" + + "declare function importClass(...args: any[]): void;\n" + + "declare const Packages: any;\n\n"; + + public static void writeDts(Seq> classes, String outPath) throws Exception{ + //keep only public top-level-ish members; also drop the couple of guaranteed-duplicate + //simple names up front, otherwise later `declare class Foo` collides with an earlier one. + var byName = new HashMap>(); + var dropped = new Seq(); + + for(Class c : classes){ + if(!Modifier.isPublic(c.getModifiers())) continue; + if(c.getSimpleName().isEmpty()) continue; //anonymous, shouldn't happen post-filter but be safe + + Class existing = byName.get(c.getSimpleName()); + if(existing != null){ + //keep whichever one is NOT in the more "internal" looking package, as a heuristic; + //in practice these collisions are rare enough to just log & keep the first seen. + dropped.add(c.getName() + " (duplicate simple name of " + existing.getName() + ")"); + continue; + } + byName.put(c.getSimpleName(), c); + } + + if(dropped.any()){ + arc.util.Log.warn("Dropped @ classes from .d.ts due to simple-name collisions:\n@", + dropped.size, dropped.toString("\n")); + } + + Set known = byName.keySet(); + StringBuilder sb = new StringBuilder(); + sb.append("// Generated file. Do not modify.\n"); + sb.append("// Ambient global declarations for Mindustry/Arc JS modding (Rhino).\n"); + sb.append("// This file is for editor tooling ONLY - it is never compiled or executed.\n"); + sb.append("// Classes are declared as globals (not modules) because importPackage()\n"); + sb.append("// puts them directly in global scope at runtime.\n\n"); + + sb.append(rhinoPrelude); + + var sorted = Seq.with(byName.values()); + sorted.sortComparing(Class::getName); + + for(Class c : sorted){ + try{ + emitType(sb, c, known); + }catch(Throwable t){ + //never let one weird class (e.g. something with exotic generics) kill the whole run + arc.util.Log.warn("Skipped @ in .d.ts generation: @", c.getName(), t.toString()); + } + } + + new Fi(outPath).writeString(sb.toString()); + arc.util.Log.info("Generated .d.ts @ (@ classes, @ dropped).", outPath, sorted.size, dropped.size); + } + + private static void emitType(StringBuilder sb, Class c, Set known){ + boolean isInterface = c.isInterface(); + boolean isEnum = c.isEnum(); + + sb.append("declare ").append(isInterface ? "interface" : "class").append(" ").append(c.getSimpleName()); + appendTypeParams(sb, c.getTypeParameters()); + + if(!isInterface){ + Class sup = c.getSuperclass(); + if(sup != null && sup != Object.class && known.contains(sup.getSimpleName())){ + sb.append(" extends ").append(sup.getSimpleName()); + } + //TS `declare class` can't implement multiple interfaces meaningfully for hint purposes; + //skip `implements` entirely rather than emit something that reads as a real contract. + }else{ + var exts = new Seq(); + for(Class i : c.getInterfaces()){ + if(known.contains(i.getSimpleName())) exts.add(i.getSimpleName()); + } + if(exts.any()) sb.append(" extends ").append(exts.toString(", ")); + } + + sb.append("{\n"); + + if(isEnum){ + for(Object constant : c.getEnumConstants()){ + sb.append(" static readonly ").append(((Enum)constant).name()) + .append(": ").append(c.getSimpleName()).append(";\n"); + } + sb.append(" static readonly values: () => ").append(c.getSimpleName()).append("[];\n"); + sb.append(" readonly name: string;\n"); + sb.append(" readonly ordinal: number;\n"); + } + + var seenSigs = new ObjectSet(); + + //fields + for(Field f : c.getDeclaredFields()){ + if(!Modifier.isPublic(f.getModifiers()) || f.isSynthetic()) continue; + if(isEnum && f.isEnumConstant()) continue; + + String mod = Modifier.isStatic(f.getModifiers()) ? "static " : ""; + String ro = Modifier.isFinal(f.getModifiers()) ? "readonly " : ""; + String sig = mod + ro + f.getName(); + if(!seenSigs.add(sig)) continue; + + sb.append(" ").append(mod).append(ro).append(sanitizeName(f.getName())) + .append(": ").append(typeName(f.getGenericType(), known)).append(";\n"); + } + + //constructors (skipped for interfaces) + if(!isInterface){ + for(Constructor ctor : c.getDeclaredConstructors()){ + if(!Modifier.isPublic(ctor.getModifiers())) continue; + String params = paramList(ctor.getParameters(), known); + String sig = "new(" + params + ")"; + if(!seenSigs.add(sig)) continue; + sb.append(" constructor(").append(params).append(");\n"); + } + } + + //methods + for(Method m : c.getDeclaredMethods()){ + if(!Modifier.isPublic(m.getModifiers()) || m.isSynthetic() || m.isBridge()) continue; + //skip Object-inherited noise that clutters completion (still callable at runtime, just not worth listing) + if(isObjectMethod(m)) continue; + + String mod = Modifier.isStatic(m.getModifiers()) ? "static " : ""; + String params = paramList(m.getParameters(), known); + String ret = typeName(m.getGenericReturnType(), known); + + String sig = mod + m.getName() + "(" + params + ")"; + if(!seenSigs.add(sig)) continue; //identical erasure to an already-emitted overload + + sb.append(" ").append(mod).append(sanitizeName(m.getName())) + .append("(").append(params).append("): ").append(ret).append(";\n"); + } + + sb.append("}\n\n"); + } + + private static boolean isObjectMethod(Method m){ + try{ + Object.class.getDeclaredMethod(m.getName(), m.getParameterTypes()); + return true; + }catch(NoSuchMethodException e){ + return false; + } + } + + private static void appendTypeParams(StringBuilder sb, TypeVariable[] params){ + if(params.length == 0) return; + sb.append("<"); + for(int i = 0; i < params.length; i++){ + if(i > 0) sb.append(", "); + sb.append(params[i].getName()); + } + sb.append(">"); + } + + private static String paramList(Parameter[] params, Set known){ + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < params.length; i++){ + if(i > 0) sb.append(", "); + Parameter p = params[i]; + String name = p.isNamePresent() ? sanitizeName(p.getName()) : "arg" + i; + if(p.isVarArgs()){ + Class comp = p.getType().getComponentType(); + sb.append("...").append(name).append(": ").append(typeName(comp, known)).append("[]"); + }else{ + sb.append(name).append(": ").append(typeName(p.getParameterizedType(), known)); + } + } + return sb.toString(); + } + + private static String sanitizeName(String name){ + return switch(name){ + case "in", "function", "package", "class", "new", "delete", "export", + "import", "typeof", "var", "constructor" -> "_" + name; + default -> name; + }; + } + + private static String typeName(Type t, Set known){ + if(t instanceof Class){ + return className((Class)t, known); + } + if(t instanceof GenericArrayType){ + return typeName(((GenericArrayType)t).getGenericComponentType(), known) + "[]"; + } + if(t instanceof TypeVariable){ + return "any"; //bounded type vars erased - not worth reproducing bounds for hint purposes + } + if(t instanceof WildcardType){ + return "any"; + } + if(t instanceof ParameterizedType){ + ParameterizedType pt = (ParameterizedType)t; + Class raw = (Class)pt.getRawType(); + Type[] args = pt.getActualTypeArguments(); + + //common Arc/Java containers get mapped to native TS shapes + String rawName = raw.getSimpleName(); + if(isCollectionLike(raw) && args.length == 1){ + return typeName(args[0], known) + "[]"; + } + if(isMapLike(raw) && args.length == 2){ + return "Record"; //key type erased to string, close enough for hints + } + + if(!known.contains(rawName)) return "any"; + StringBuilder sb = new StringBuilder(rawName).append("<"); + for(int i = 0; i < args.length; i++){ + if(i > 0) sb.append(", "); + sb.append(typeName(args[i], known)); + } + return sb.append(">").toString(); + } + return "any"; + } + + private static boolean isCollectionLike(Class raw){ + String n = raw.getSimpleName(); + return n.equals("Seq") || n.equals("Array") || n.equals("List") || n.equals("ArrayList") + || n.equals("ObjectSet") || n.equals("Set") || n.equals("Queue") || n.equals("SnapshotSeq"); + } + + private static boolean isMapLike(Class raw){ + String n = raw.getSimpleName(); + return n.equals("ObjectMap") || n.equals("Map") || n.equals("HashMap") || n.equals("IntMap"); + } + + private static String className(Class c, Set known){ + if(c == void.class || c == Void.class) return "void"; + if(c == boolean.class || c == Boolean.class) return "boolean"; + if(c == int.class || c == Integer.class || c == float.class || c == Float.class + || c == double.class || c == Double.class || c == long.class || c == Long.class + || c == short.class || c == Short.class || c == byte.class || c == Byte.class) return "number"; + if(c == char.class || c == Character.class || c == String.class || c == CharSequence.class) return "string"; + if(c.isArray()) return className(c.getComponentType(), known) + "[]"; + if(erase.contains(c)) return "any"; + if(c.isPrimitive()) return "any"; //shouldn't hit, but stay safe + return known.contains(c.getSimpleName()) ? c.getSimpleName() : "any"; + } +} \ No newline at end of file