NativeScript / NativeScript/android-dts-generator
Kotlin Library Compatibility Issues and Proposed Fixes
Nessuno ha ancora preso questa issue.
- Lingua principale
- Java
- Stelle
- 96
- Fork
- 25
- Metriche di merge delle PR
- Nessuna PR unita negli ultimi 30g
Descrizione
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: https://github.com/NativeScript/android-dts-generator/issues/83 (@NathanWalker) and https://github.com/NativeScript/android-dts-generator/issues/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.serializationcompiler 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.classGenerated 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.javaline 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
$$serializerto the filtered class list inDtsApi.javaaround 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
$$serializerclasses 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
Ktsuffix)
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 identifierRoot Cause
In
DtsApi.javalines 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<...>SyntaxProblem
Kotlin standard library types (
kotlin.jvm.functions.*,kotlin.properties.*) are in the "ignored namespaces" list and get replaced withany. However, the regex replacement preserves generic type parameters, creating invalid TypeScript syntax (anyis 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.javaline 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.javaChange 1 (around line 143):
// Add to filtered classes list currentFileClassname.contains("$$serializer")Change 2 (lines 965-976, in
getMethodParamSignaturemethod):// 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
replaceIgnoredNamespacesmethod):// Update regex pattern String regexFormat = "(?<Replace>%s(?:(?:\\.[a-zA-Z\\d]*)|<[a-zA-Z\\d\\.<>, ]*>)*)(?<Suffix>[^a-zA-Z\\d]+|$)";
References
- Kotlin Serialization Docs: https://kotlinlang.org/docs/serialization.html
- Kotlin Bytecode Conventions: https://kotlinlang.org/spec/
- Related Issue: Similar to the 2016 fix for interface static initializers (commit 26a0870)
Tested With:
- MiSnap SDK v5.8.1 (Mitek Systems)
- NativeScript 8.x
- Multiple Kotlin-based AndroidX libraries
Let me know if I should commit the changes so that they can be tested :)
Guida per i contributori
Apri la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Direzione di ricerca
Inizia in DtsApi.java, nella logica di filtraggio intorno alla riga 143, nella gestione dei parametri intorno alle righe 962-969 e in replaceIgnoredNamespaces intorno alla riga 248. Riproduci la generazione con MiSnap SDK v5.8.1 e analizza le dichiarazioni generate. Il lavoro è completato quando non ci sono moduli vuoti, nomi di parametri sintetici non validi o generici any non validi, e le definizioni vengono compilate correttamente.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Valutazione
- Stack tecnologico
- java, kotlin, typescript
- Ambito
- tooling
- Tipo di issue
- Bug
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Stato di attività
- Ferma
- Chiarezza
- Specificata chiaramente
- Idoneità per principianti
- 45/100