In JS enriched String/Object types with ES5.1 unique members added in .d.ts files

This commit is contained in:
bigguy345 committed 2026-03-06 01:55:06 +02:00
1 parent 1d1962603b
commit c1b298e46d
9 files changed
+71 -62

No files matched your search

@@ -4887,7 +4887,7 @@ public class ScriptDocument {
// String literals
if (expr.startsWith("\"") && expr.endsWith("\"")) {
return resolveType("String");
return TypeInfo.string();
}
// Character literals
@@ -5728,7 +5728,7 @@ public class ScriptDocument {
private TypeInfo resolveSimpleExpression(String expr, int position) {
// String literals
if (expr.startsWith("\"") && expr.endsWith("\"")) {
return resolveType("String");
return TypeInfo.string();
}
// Character literals
@@ -54,7 +54,7 @@ public abstract class ExpressionNode {
private final String value;
public StringLiteralNode(String value, int start, int end) { super(start, end); this.value = value; }
public String getValue() { return value; }
public TypeInfo resolveType(TypeResolverContext resolver) { return TypeInfo.fromClass(String.class); }
public TypeInfo resolveType(TypeResolverContext resolver) { return TypeInfo.string(); }
}
public static class NullLiteralNode extends ExpressionNode {
@@ -88,7 +88,7 @@ public class ExpressionTypeResolver {
if (node instanceof ExpressionNode.DoubleLiteralNode) return TypeInfo.fromPrimitive("double");
if (node instanceof ExpressionNode.BooleanLiteralNode) return TypeInfo.fromPrimitive("boolean");
if (node instanceof ExpressionNode.CharLiteralNode) return TypeInfo.fromPrimitive("char");
if (node instanceof ExpressionNode.StringLiteralNode) return TypeInfo.fromClass(String.class);
if (node instanceof ExpressionNode.StringLiteralNode) return TypeInfo.string();
if (node instanceof ExpressionNode.NullLiteralNode) return TypeInfo.unresolved("null", "<null>");
if (node instanceof ExpressionNode.IdentifierNode) {
@@ -94,7 +94,7 @@ public class TypeRules {
switch (op.getCategory()) {
case ARITHMETIC:
if (op == OperatorType.ADD && (isString(left) || isString(right))) {
return validateAgainstExpectedType(TypeInfo.fromClass(String.class));
return validateAgainstExpectedType(TypeInfo.string());
}
if (isAny(left) || isAny(right)) {
return validateAgainstExpectedType(TypeInfo.ANY);
@@ -19,7 +19,7 @@ public final class TypeChecker {
CURRENT_JAVASCRIPT_MODE = javaScriptMode;
}
private static boolean isJavaScriptMode() {
static boolean isJavaScriptMode() {
return CURRENT_JAVASCRIPT_MODE;
}
@@ -43,6 +43,18 @@ public class TypeInfo {
public static final TypeInfo BOOLEAN = fromPrimitive("boolean");
public static final TypeInfo STRING = TypeInfo.fromClass(String.class);
static TypeInfo jsString = null;
public static final TypeInfo OBJECT = TypeInfo.fromClass(Object.class);
static TypeInfo jsObject = null;
public static TypeInfo string() {
return TypeChecker.isJavaScriptMode() && jsString != null ? jsString : STRING;
}
public static TypeInfo object() {
return TypeChecker.isJavaScriptMode() && jsObject != null ? jsObject : OBJECT;
}
public static final TypeInfo NULL = TypeInfo.unresolved("null", "<null>");
@@ -317,6 +329,13 @@ public class TypeInfo {
Kind.INTERFACE, null, true, null, jsType);
}
public static void injectJSMembers(TypeInfo target, JSTypeInfo jsExtensions) {
for (JSFieldInfo jsField : jsExtensions.getFields().values())
target.syntheticFields.add(FieldInfo.fromJSField(jsField, target));
for (JSMethodInfo jsMethod : jsExtensions.getMethods().values())
target.syntheticMethods.add(MethodInfo.fromJSMethod(jsMethod, target));
}
/**
* Create an array type wrapping the given element type.
*
@@ -24,6 +24,9 @@ public class TypeResolver {
// Cache: fully-qualified class name -> TypeInfo
private final Map<String, TypeInfo> typeCache = new HashMap<>();
// Cache: Java types that have been enriched with JS members from .d.ts types (e.g., String, Object)
private final Map<String, TypeInfo> jsMergeCache = new HashMap<>();
// Cache: validated package paths
private final Set<String> validPackages = new HashSet<>();
@@ -418,6 +421,8 @@ public class TypeResolver {
// Before primitive mapping, to allow .d.ts types to override primitive names for ECMAScript 5.1 globals (e.g., "String" in JS is not the same as "String" in Java))
JSTypeInfo jsTypeInfo = getJSTypeRegistry().getType(baseName);
if (jsTypeInfo != null) {
TypeInfo merged = tryMergeJSWithJava(baseName, jsTypeInfo);
if (merged != null) return merged;
return TypeInfo.fromJSTypeInfo(jsTypeInfo);
}
@@ -449,7 +454,38 @@ public class TypeResolver {
// Unresolved
return TypeInfo.unresolved(baseName, baseName);
}
private static TypeInfo getJSJavaMergeBase(String baseName) {
switch (baseName) {
case "String":
return TypeInfo.STRING;
case "Object":
return TypeInfo.OBJECT;
default:
return null;
}
}
/**
* For certain core types (e.g., String, Object), we want to merge JS type info from .d.ts with the Java TypeInfo to create an enriched type that has both Java and JS members.
*/
private TypeInfo tryMergeJSWithJava(String baseName, JSTypeInfo jsTypeInfo) {
TypeInfo javaBase = getJSJavaMergeBase(baseName);
if (javaBase == null) return null;
return jsMergeCache.computeIfAbsent(baseName, k -> {
TypeInfo result = TypeInfo.fromClass(javaBase.getJavaClass());
if (result == null) result = javaBase;
TypeInfo.injectJSMembers(result, jsTypeInfo);
if (javaBase == TypeInfo.STRING) {
TypeInfo.jsString = result;
} else if (javaBase == TypeInfo.OBJECT) {
TypeInfo.jsObject = result;
}
return result;
});
}
/**
* Check if a JS type name is a primitive.
*/