I have been going through a library and was creating typings for it's various different .aar files. I noticed some issues that have already been highlighted here: #83 (@NathanWalker) and #75 (@farfromrefug).
Since I had a build issue with the generated typings I set about investigating the issues. I am proficient in TypeScript, however I'm no expert in terms of Kotlin which meant I could see what the issues were in the typing files, but wasn't sure how to solve them. Therefore, I investigated the issues via Claude Code and the solutions that were generated have solved my issues. The following description was also generated by Claude Code, I did so to make sure that everything that was found is listed accurately:
Summary
While generating TypeScript definitions for Kotlin-based Android libraries (specifically MiSnap SDK v5.8.1), three critical issues were discovered that prevent proper type generation. This document outlines each issue, provides examples, and proposes fixes.
Environment
- DTS Generator Version: 4.0.0
- Test Library: MiSnap SDK v5.8.1 (Kotlin-based Android library)
- Kotlin Version: 1.8.20 (as seen in generated code)
- Java Target: 17
Issue 1: Kotlin Serializer Classes Generate Invalid TypeScript
Problem
Kotlin's kotlinx.serialization compiler generates classes with double dollar signs ($$serializer) in their bytecode names. The DTS generator processes these, creating invalid TypeScript with empty module declarations.
Example from Bytecode
com/miteksystems/misnap/core/Barcode$$serializer.class
com/miteksystems/misnap/core/MiSnapSettings$$serializer.class
Generated TypeScript (Invalid)
export module { // ❌ Empty module name!
export module Barcode {
export class serializer extends kotlinx.serialization.internal.GeneratedSerializer<com.miteksystems.misnap.core.Barcode> {
public static class: java.lang.Class<com.miteksystems.misnap.core.Barcode..serializer>; // ❌ Double dots
public static INSTANCE: com.miteksystems.misnap.core.Barcode..serializer; // ❌ Double dots
}
}
}
Root Cause
In DtsApi.java line 541, the code replaces $ with .:
String[] currParts = currClass.getClassName().replace('$', '.').split("\\.");
For Barcode$$serializer:
- Replace
$ → .: Barcode..serializer
- Split on
.: ["Barcode", "", "serializer"]
- Empty string in array creates
module { (invalid!)
Impact
- 63 empty module declarations in test file
- 126 invalid double-dot type references
- TypeScript compilation fails
Proposed Fix
Add $$serializer to the filtered class list in DtsApi.java around line 143:
if (currentFileClassname.startsWith("java.util.function") ||
currentFileClassname.startsWith("android.support.v4.media.routing.MediaRouterJellybeanMr1") ||
currentFileClassname.startsWith("android.support.v4.media.routing.MediaRouterJellybeanMr2") ||
currentFileClassname.contains(".debugger.") ||
currentFileClassname.endsWith("package-info") ||
currentFileClassname.endsWith("module-info") ||
currentFileClassname.endsWith("Kt") ||
currentFileClassname.contains("$$serializer")) { // ← NEW LINE
continue;
}
Why This Fix is Safe
$$serializer classes are Kotlin compiler internals for serialization
- They're never directly used by application code
- Filtering them is similar to filtering other compiler-generated classes (already done for
Kt suffix)
Issue 2: Kotlin Synthetic Parameter Names Are Invalid TypeScript
Problem
Kotlin compiler generates synthetic setter methods with the parameter name <set-?> in the LocalVariableTable. The DTS generator reads this directly, creating invalid TypeScript identifiers.
Example from Bytecode (javap output)
public final void setInitialDelay(java.lang.Integer);
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lcom/miteksystems/misnap/core/MiSnapSettings$Analysis;
0 6 1 <set-?> Ljava/lang/Integer;
Generated TypeScript (Invalid)
public setInitialDelay(<set-?>: java.lang.Integer): void; // ❌ Invalid identifier
public setEnableAiBasedRts(<set-?>: java.lang.Boolean): void; // ❌ Invalid identifier
public setMotionDetectorSensitivity(<set-?>: com.miteksystems.misnap.core.MiSnapSettings.Analysis.MotionDetectorSensitivity): void; // ❌ Invalid identifier
Root Cause
In DtsApi.java lines 962-969, the code reads parameter names from bytecode but doesn't sanitize Kotlin synthetic names:
if (localVariable != null) {
String name = localVariable.getName();
// No sanitization for <set-?> or other synthetic names!
if (reservedJsKeywords.contains(name)) {
sb.append(name + "_");
} else {
sb.append(name); // Writes <set-?> directly
}
}
Impact
- 68 occurrences in test file
- All Kotlin property setter methods affected
- TypeScript parser fails on angle bracket identifiers
Proposed Fix
Sanitize synthetic parameter names by deriving meaningful names from method names:
if (localVariable != null) {
String name = localVariable.getName();
// Sanitize Kotlin synthetic parameter names like <set-?>
if (name.startsWith("<") && name.endsWith(">")) {
// For setter methods, derive parameter name from method name
String methodName = m.getName();
if (methodName.startsWith("set") && methodName.length() > 3) {
// setInitialDelay -> initialDelay
name = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
} else {
// Fallback to "value" for other synthetic names
name = "value";
}
}
if (reservedJsKeywords.contains(name)) {
System.out.println(String.format("Appending _ to reserved JS keyword %s", name));
sb.append(name + "_");
} else {
sb.append(name);
}
}
Result After Fix
public setInitialDelay(initialDelay: java.lang.Integer): void; // ✅ Valid and semantic!
public setEnableAiBasedRts(enableAiBasedRts: java.lang.Boolean): void; // ✅ Valid and semantic!
public setMotionDetectorSensitivity(motionDetectorSensitivity: com.miteksystems.misnap.core.MiSnapSettings.Analysis.MotionDetectorSensitivity): void; // ✅ Valid and semantic!
Why This Fix is Safe
- Only affects synthetic Kotlin parameter names (those with
< and >)
- Generates semantically meaningful names matching the property being set
- Follows common naming conventions (same pattern as Kotlin itself uses in Kotlin files)
Issue 3: Kotlin Type Generics Create Invalid any<...> Syntax
Problem
Kotlin standard library types (kotlin.jvm.functions.*, kotlin.properties.*) are in the "ignored namespaces" list and get replaced with any. However, the regex replacement preserves generic type parameters, creating invalid TypeScript syntax (any is not a generic type).
Example from Bytecode
public final class FragmentViewBindingDelegate<T extends ViewBinding>
implements kotlin.properties.ReadOnlyProperty<androidx.fragment.app.Fragment, T>
Generated TypeScript (Invalid)
export class FragmentViewBindingDelegate<T> extends any<androidx.fragment.app.Fragment,any> { // ❌ any is not generic!
public getViewBindingFactory(): any<globalAndroid.view.View,any>; // ❌ Invalid syntax
public constructor(fragment: androidx.fragment.app.Fragment, viewBindingFactory: any<any,any>); // ❌ Invalid syntax
}
Root Cause
In DtsApi.java line 248, the regex pattern doesn't include commas and spaces in the character class for matching generic parameters:
String regexFormat = "(?<Replace>%s(?:(?:\\.[a-zA-Z\\d]*)|<[a-zA-Z\\d\\.<>]*>)*)(?<Suffix>[^a-zA-Z\\d]+)";
// ^^^^^^^^^^^^^^ Missing comma and space!
When the regex encounters kotlin.properties.ReadOnlyProperty<Fragment, T>:
- Matches up to:
kotlin.properties.ReadOnlyProperty
- Encounters
< which is [^a-zA-Z\d], so it becomes the Suffix
- Replacement:
any< + remaining text
- Result:
any<Fragment, T> (invalid!)
Impact
- ~30 occurrences across various Kotlin function and property types
- All Kotlin lambda and property delegate types affected
- TypeScript compilation fails
Proposed Fix
Update the regex to include commas, spaces, and match end-of-string:
private String replaceIgnoredNamespaces(String content) {
// Updated regex to properly capture generics with commas, spaces, and nested angle brackets
// Also matches end of string or line as valid suffix
String regexFormat = "(?<Replace>%s(?:(?:\\.[a-zA-Z\\d]*)|<[a-zA-Z\\d\\.<>, ]*>)*)(?<Suffix>[^a-zA-Z\\d]+|$)";
// ^^^^^ Added comma and space
// ^^^ Added end-of-string
for (String ignoredNamespace : this.getIgnoredNamespaces()) {
String regexString = String.format(regexFormat, ignoredNamespace.replace(".", "\\."));
content = content.replaceAll(regexString, "any$2");
regexString = String.format(regexFormat, getGlobalAliasedClassName(ignoredNamespace).replace(".", "\\."));
content = content.replaceAll(regexString, "any$2");
}
// replace "extends any" with "extends java.lang.Object"
content = content.replace(" extends any ", String.format(" extends %s ", DtsApi.JavaLangObject));
return content;
}
Result After Fix
export class FragmentViewBindingDelegate<T> extends java.lang.Object { // ✅ Valid TypeScript!
public getViewBindingFactory(): any; // ✅ Valid TypeScript!
public constructor(fragment: androidx.fragment.app.Fragment, viewBindingFactory: any); // ✅ Valid TypeScript!
}
Why This Fix is Safe
- Only affects types in the ignored namespaces list (already being replaced with
any)
- Properly strips all generic parameters when replacing, creating valid TypeScript
- The regex already existed; this just makes it work correctly for types with commas in generics
Testing Results
Test Environment
- Library: MiSnap SDK v5.8.1 (11 modules)
- Project: NativeScript mobile app
- TypeScript Version: As configured by NativeScript
Before Fixes
- ❌ Empty module declarations: 63
- ❌ Double-dot references: 126
- ❌ Invalid
<set-?> parameters: 68
- ❌ Invalid
any<...> syntax: ~30
- ❌ Total TypeScript errors: 287+
After Fixes
- ✅ Empty module declarations: 0
- ✅ Double-dot references: 0
- ✅ Invalid parameters: 0
- ✅ Invalid
any<...> syntax: 0
- ✅ All type definitions compile successfully
- ✅ NativeScript project runs without issues
Generated File Sizes (Example: misnap-core.d.ts)
- Before: 3,789 lines with errors
- After: 3,096 lines (693 lines of invalid code removed)
- Reduction: 18.3% smaller, 100% valid
Compatibility
These fixes specifically target Kotlin-generated bytecode patterns that are:
- Standard Kotlin compiler output (not library-specific)
- Present in any modern Kotlin Android library
- Not handled by the current generator logic
The fixes are backward compatible - they only affect Kotlin libraries and don't change behavior for Java-only libraries.
Proposed Changes Summary
File: dts-generator/src/main/java/com/telerik/dts/DtsApi.java
Change 1 (around line 143):
// Add to filtered classes list
currentFileClassname.contains("$$serializer")
Change 2 (lines 965-976, in getMethodParamSignature method):
// Sanitize Kotlin synthetic parameter names
if (name.startsWith("<") && name.endsWith(">")) {
String methodName = m.getName();
if (methodName.startsWith("set") && methodName.length() > 3) {
name = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
} else {
name = "value";
}
}
Change 3 (line 249, in replaceIgnoredNamespaces method):
// Update regex pattern
String regexFormat = "(?<Replace>%s(?:(?:\\.[a-zA-Z\\d]*)|<[a-zA-Z\\d\\.<>, ]*>)*)(?<Suffix>[^a-zA-Z\\d]+|$)";
References
Tested With:
- MiSnap SDK v5.8.1 (Mitek Systems)
- NativeScript 8.x
- Multiple Kotlin-based AndroidX libraries
I have been going through a library and was creating typings for it's various different .aar files. I noticed some issues that have already been highlighted here: #83 (@NathanWalker) and #75 (@farfromrefug).
Since I had a build issue with the generated typings I set about investigating the issues. I am proficient in TypeScript, however I'm no expert in terms of Kotlin which meant I could see what the issues were in the typing files, but wasn't sure how to solve them. Therefore, I investigated the issues via Claude Code and the solutions that were generated have solved my issues. The following description was also generated by Claude Code, I did so to make sure that everything that was found is listed accurately:
Let me know if I should commit the changes so that they can be tested :)