Loaded all .d.ts files in assets/modId/api directory of ALL loaded mods

This commit is contained in:
bigguy345 committed 2026-01-24 06:42:38 +02:00
1 parent 0d1520d951
commit b86c0284ac
2 files changed
+298 -24

No files matched your search

@@ -0,0 +1,197 @@
package noppes.npcs.client.gui.util.script.interpreter.js_parser;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.ModContainer;
import java.io.*;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
final class DtsModScanner {
private static final Pattern MOD_DTS_PATH_PATTERN = Pattern.compile("^assets/([^/]+)/api/(.+\\.d\\.ts)$");
private static final List<String> DOMAIN_PRIORITY = Arrays.asList("customnpcs", "npcdbc");
private DtsModScanner() {}
static List<DtsFileRef> collectDtsFilesFromMods() {
List<DtsFileRef> dtsFiles = new ArrayList<>();
for (ModContainer mod : Loader.instance().getModList()) {
File source = mod.getSource();
if (source == null || !source.exists()) {
continue;
}
if (source.isDirectory()) {
scanDirectoryForModDts(source, mod.getModId(), dtsFiles);
} else if (source.getName().endsWith(".jar") || source.getName().endsWith(".zip")) {
scanJarForModDts(source, mod.getModId(), dtsFiles);
}
}
return dtsFiles;
}
static void sortDtsFiles(List<DtsFileRef> dtsFiles) {
Collections.sort(dtsFiles, new DtsFileRefComparator());
}
static void logSummary(List<DtsFileRef> dtsFiles) {
Map<String, Integer> domainCounts = new LinkedHashMap<>();
for (DtsFileRef ref : dtsFiles) {
domainCounts.put(ref.getDomain(), domainCounts.getOrDefault(ref.getDomain(), 0) + 1);
}
System.out.println("[JSTypeRegistry] Found " + dtsFiles.size() + " .d.ts files across " + domainCounts.size() + " domains");
for (Map.Entry<String, Integer> entry : domainCounts.entrySet()) {
System.out.println("[JSTypeRegistry] Domain " + entry.getKey() + ": " + entry.getValue() + " files");
}
}
private static void scanDirectoryForModDts(File baseDir, String modId, List<DtsFileRef> dtsFiles) {
scanDirectoryForModDts(baseDir, baseDir, modId, dtsFiles);
}
private static void scanDirectoryForModDts(File baseDir, File directory, String modId, List<DtsFileRef> dtsFiles) {
File[] files = directory.listFiles();
if (files == null) {
return;
}
for (File file : files) {
if (file.isDirectory()) {
scanDirectoryForModDts(baseDir, file, modId, dtsFiles);
continue;
}
if (!file.getName().endsWith(".d.ts")) {
continue;
}
String relativePath = baseDir.toURI().relativize(file.toURI()).getPath();
if (relativePath == null) {
continue;
}
String normalized = relativePath.replace('\\', '/');
Matcher matcher = MOD_DTS_PATH_PATTERN.matcher(normalized);
if (matcher.matches()) {
String domain = matcher.group(1);
String apiPath = matcher.group(2);
dtsFiles.add(DtsFileRef.forFile(modId, domain, apiPath, file));
}
}
}
private static void scanJarForModDts(File jarFile, String modId, List<DtsFileRef> dtsFiles) {
try {
JarFile jar = new JarFile(jarFile);
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String entryName = entry.getName();
Matcher matcher = MOD_DTS_PATH_PATTERN.matcher(entryName);
if (matcher.matches()) {
String domain = matcher.group(1);
String apiPath = matcher.group(2);
dtsFiles.add(DtsFileRef.forJar(modId, domain, apiPath, jarFile, entryName));
}
}
jar.close();
} catch (Exception e) {
System.err.println("[JSTypeRegistry] Error scanning mod jar for .d.ts files: " + e.getMessage());
}
}
static final class DtsFileRef {
private final String modId;
private final String domain;
private final String relativePath;
private final File file;
private final File jarFile;
private final String jarEntryName;
private DtsFileRef(String modId, String domain, String relativePath, File file, File jarFile, String jarEntryName) {
this.modId = modId;
this.domain = domain;
this.relativePath = relativePath;
this.file = file;
this.jarFile = jarFile;
this.jarEntryName = jarEntryName;
}
static DtsFileRef forFile(String modId, String domain, String relativePath, File file) {
return new DtsFileRef(modId, domain, relativePath, file, null, null);
}
static DtsFileRef forJar(String modId, String domain, String relativePath, File jarFile, String jarEntryName) {
return new DtsFileRef(modId, domain, relativePath, null, jarFile, jarEntryName);
}
InputStream openStream() throws IOException {
if (file != null) {
return new FileInputStream(file);
}
if (jarFile != null && jarEntryName != null) {
final JarFile jar = new JarFile(jarFile);
JarEntry entry = jar.getJarEntry(jarEntryName);
if (entry == null) {
jar.close();
return null;
}
InputStream is = jar.getInputStream(entry);
return new FilterInputStream(is) {
@Override
public void close() throws IOException {
super.close();
jar.close();
}
};
}
return null;
}
String getDomain() {
return domain;
}
String getRelativePath() {
return relativePath;
}
String getOrigin() {
return modId + ":" + domain + ":" + relativePath;
}
}
private static class DtsFileRefComparator implements Comparator<DtsFileRef> {
@Override
public int compare(DtsFileRef a, DtsFileRef b) {
int domainCompare = Integer.compare(getDomainRank(a.getDomain()), getDomainRank(b.getDomain()));
if (domainCompare != 0) {
return domainCompare;
}
int domainNameCompare = a.getDomain().compareTo(b.getDomain());
if (domainNameCompare != 0) {
return domainNameCompare;
}
int filePriorityCompare = Integer.compare(getFilePriority(a.getRelativePath()), getFilePriority(b.getRelativePath()));
if (filePriorityCompare != 0) {
return filePriorityCompare;
}
return a.getRelativePath().compareTo(b.getRelativePath());
}
private int getDomainRank(String domain) {
int idx = DOMAIN_PRIORITY.indexOf(domain);
return idx >= 0 ? idx : DOMAIN_PRIORITY.size();
}
private int getFilePriority(String relativePath) {
if (relativePath.endsWith("hooks.d.ts")) {
return 0;
}
if (relativePath.endsWith("index.d.ts")) {
return 1;
}
return 2;
}
}
}
@@ -6,6 +6,7 @@ import noppes.npcs.scripted.NpcAPI;
import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
@@ -18,9 +19,6 @@ public class JSTypeRegistry {
private static JSTypeRegistry INSTANCE;
// Resource location for the API definitions
private static final String API_RESOURCE_PATH = "customnpcs:api/";
// All registered types by full name (e.g., "IPlayerEvent.InteractEvent")
private final Map<String, JSTypeInfo> types = new LinkedHashMap<>();
@@ -29,6 +27,12 @@ public class JSTypeRegistry {
// Type aliases (simple name -> full type name)
private final Map<String, String> typeAliases = new HashMap<>();
// Type full name -> origin string (modId:domain:relativePath)
private final Map<String, String> typeOrigins = new HashMap<>();
// Alias name -> origin string (modId:domain:relativePath)
private final Map<String, String> aliasOrigins = new HashMap<>();
// Context-aware hook function signatures: namespace -> functionName -> list of signatures
// The namespace is the event interface name (e.g., "INpcEvent", "IPlayerEvent")
@@ -53,6 +57,8 @@ public class JSTypeRegistry {
private boolean initialized = false;
private boolean initializationAttempted = false;
private String currentSource = null;
public static JSTypeRegistry getInstance() {
if (INSTANCE == null) {
@@ -73,25 +79,28 @@ public class JSTypeRegistry {
try {
TypeScriptDefinitionParser parser = new TypeScriptDefinitionParser(this);
// Recursively load all .d.ts files from the api directory
Set<String> dtsFiles = findAllDtsFilesInResources("assets/customnpcs/api");
System.out.println("[JSTypeRegistry] Found " + dtsFiles.size() + " .d.ts files in resources");
// Load hooks.d.ts and index.d.ts first if they exist (defines core types)
if (dtsFiles.contains("hooks.d.ts")) {
loadResourceFile(parser, "hooks.d.ts");
}
if (dtsFiles.contains("index.d.ts")) {
loadResourceFile(parser, "index.d.ts");
}
// Load all other .d.ts files
for (String filePath : dtsFiles) {
if (!filePath.equals("hooks.d.ts") && !filePath.equals("index.d.ts")) {
loadResourceFile(parser, filePath);
List<DtsModScanner.DtsFileRef> dtsFiles = DtsModScanner.collectDtsFilesFromMods();
if (dtsFiles.isEmpty()) {
Set<String> fallbackFiles = findAllDtsFilesInResources("assets/customnpcs/api");
System.out.println("[JSTypeRegistry] Found " + fallbackFiles.size() + " .d.ts files in resources");
if (fallbackFiles.contains("hooks.d.ts")) {
loadResourceFile(parser, "hooks.d.ts");
}
if (fallbackFiles.contains("index.d.ts")) {
loadResourceFile(parser, "index.d.ts");
}
for (String filePath : fallbackFiles) {
if (!filePath.equals("hooks.d.ts") && !filePath.equals("index.d.ts")) {
loadResourceFile(parser, filePath);
}
}
} else {
DtsModScanner.logSummary(dtsFiles);
loadDtsFiles(parser, dtsFiles);
}
// Phase 2: Resolve all type parameters now that all types are loaded
@@ -113,6 +122,33 @@ public class JSTypeRegistry {
e.printStackTrace();
}
}
private void loadDtsFiles(TypeScriptDefinitionParser parser, List<DtsModScanner.DtsFileRef> dtsFiles) {
DtsModScanner.sortDtsFiles(dtsFiles);
for (DtsModScanner.DtsFileRef ref : dtsFiles) {
try (InputStream is = ref.openStream()) {
if (is == null) {
continue;
}
String content = readFully(new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)));
setCurrentSource(ref.getOrigin());
parser.parseDefinitionFile(content, ref.getRelativePath());
} catch (Exception e) {
System.err.println("[JSTypeRegistry] Failed to load " + ref.getOrigin() + ": " + e.getMessage());
} finally {
setCurrentSource(null);
}
}
}
private String readFully(BufferedReader reader) throws IOException {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
return sb.toString();
}
/**
* Recursively find all .d.ts files in the resources directory.
@@ -210,7 +246,7 @@ public class JSTypeRegistry {
System.err.println("[JSTypeRegistry] Error scanning JAR for .d.ts files: " + e.getMessage());
}
}
/**
* Load a specific .d.ts file from resources.
*/
@@ -280,9 +316,20 @@ public class JSTypeRegistry {
* Register a type.
*/
public void registerType(JSTypeInfo type) {
types.put(type.getFullName(), type);
String fullName = type.getFullName();
if (types.containsKey(fullName)) {
logTypeCollision(fullName, getCurrentSource());
return;
}
types.put(fullName, type);
typeOrigins.put(fullName, getCurrentSource());
if (type.getJavaFqn() != null && !type.getJavaFqn().isEmpty()) {
typesByJavaFqn.put(type.getJavaFqn(), type);
String javaFqn = type.getJavaFqn();
if (!typesByJavaFqn.containsKey(javaFqn)) {
typesByJavaFqn.put(javaFqn, type);
} else {
System.out.println("[JSTypeRegistry] Duplicate javaFqn " + javaFqn + " from " + getCurrentSource());
}
}
}
@@ -298,7 +345,15 @@ public class JSTypeRegistry {
* Register a type alias.
*/
public void registerTypeAlias(String alias, String fullType) {
if (typeAliases.containsKey(alias)) {
String existing = typeAliases.get(alias);
if (!Objects.equals(existing, fullType)) {
logAliasCollision(alias, existing, fullType, getCurrentSource());
}
return;
}
typeAliases.put(alias, fullType);
aliasOrigins.put(alias, getCurrentSource());
}
/**
@@ -755,13 +810,35 @@ public class JSTypeRegistry {
*/
public void clear() {
types.clear();
typesByJavaFqn.clear();
typeAliases.clear();
typeOrigins.clear();
aliasOrigins.clear();
hooks.clear();
contextHooks.clear();
globalEngineObjects.clear();
initialized = false;
}
private void setCurrentSource(String source) {
currentSource = source;
}
private String getCurrentSource() {
return currentSource == null ? "unknown" : currentSource;
}
private void logTypeCollision(String fullName, String incomingSource) {
String existingSource = typeOrigins.getOrDefault(fullName, "unknown");
System.out.println("[JSTypeRegistry] Duplicate type " + fullName + " from " + incomingSource + " (kept " + existingSource + ")");
}
private void logAliasCollision(String alias, String existingType, String incomingType, String incomingSource) {
String existingSource = aliasOrigins.getOrDefault(alias, "unknown");
System.out.println("[JSTypeRegistry] Duplicate alias " + alias + " from " + incomingSource + " (kept " + existingSource + ")");
System.out.println("[JSTypeRegistry] Alias " + alias + " existing=" + existingType + " incoming=" + incomingType);
}
/**
* Represents a hook function signature with its namespace.
*/