From 9db00f4135bae22c54c96d2008611e027b4ed8fe Mon Sep 17 00:00:00 2001 From: Martin Guillon Date: Thu, 3 Mar 2022 21:05:10 +0100 Subject: [PATCH 01/17] chore: mave central --- dts-generator/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dts-generator/build.gradle b/dts-generator/build.gradle index ff872e6..b874e2b 100644 --- a/dts-generator/build.gradle +++ b/dts-generator/build.gradle @@ -12,7 +12,7 @@ if(project.hasProperty("jarsOutput")) { repositories { google() - jcenter() + mavenCentral() } allprojects { From 10bbe5df9f13b9d0bfa7d7123f08f441738aea1b Mon Sep 17 00:00:00 2001 From: Martin Guillon Date: Thu, 3 Mar 2022 21:24:15 +0100 Subject: [PATCH 02/17] feat: add aar support --- dts-generator/.idea/runConfigurations.xml | 12 ---------- .../main/java/com/telerik/dts/Generator.java | 23 +++++++++++++++++++ .../main/java/com/telerik/dts/JarFile.java | 8 +++++-- 3 files changed, 29 insertions(+), 14 deletions(-) delete mode 100644 dts-generator/.idea/runConfigurations.xml diff --git a/dts-generator/.idea/runConfigurations.xml b/dts-generator/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460..0000000 --- a/dts-generator/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/dts-generator/src/main/java/com/telerik/dts/Generator.java b/dts-generator/src/main/java/com/telerik/dts/Generator.java index 90a9ed3..5ab5942 100644 --- a/dts-generator/src/main/java/com/telerik/dts/Generator.java +++ b/dts-generator/src/main/java/com/telerik/dts/Generator.java @@ -5,11 +5,17 @@ import org.apache.bcel.classfile.JavaClass; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; +import java.util.Enumeration; import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; /** * Created by plamen5kov on 6/17/16. @@ -94,6 +100,23 @@ private void writeDeclarations() { private void loadJavaClasses(List jars) throws IOException { for (File file : jars) { if (file.exists()) { + if (file.isFile() && file.getName().endsWith(".aar")) { + try { + ZipFile zipFile = new ZipFile(file); + + Enumeration entries = zipFile.entries(); + while(entries.hasMoreElements()){ + ZipEntry entry = entries.nextElement(); + if (entry.getName().equals("classes.jar")) { + JarFile jar = JarFile.readJarInputStream(file.getAbsolutePath(), zipFile.getInputStream(entry)); + ClassRepo.cacheJarFile(jar); + break; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } if (file.isFile() && file.getName().endsWith(".jar")) { JarFile jar = JarFile.readJar(file.getAbsolutePath()); ClassRepo.cacheJarFile(jar); diff --git a/dts-generator/src/main/java/com/telerik/dts/JarFile.java b/dts-generator/src/main/java/com/telerik/dts/JarFile.java index cf8aef0..46cf20b 100644 --- a/dts-generator/src/main/java/com/telerik/dts/JarFile.java +++ b/dts-generator/src/main/java/com/telerik/dts/JarFile.java @@ -2,6 +2,7 @@ import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.util.HashMap; import java.util.Map; import java.util.jar.JarInputStream; @@ -29,11 +30,11 @@ public Map getClassMap() { return classMap; } - public static JarFile readJar(String path) throws ClassFormatException,IOException { + public static JarFile readJarInputStream(String path, InputStream stream) throws ClassFormatException,IOException { JarFile jar; JarInputStream jis = null; try { - jis = new JarInputStream(new FileInputStream(path)); + jis = new JarInputStream(stream); jar = new JarFile(path); @@ -53,4 +54,7 @@ public static JarFile readJar(String path) throws ClassFormatException,IOExcepti } return jar; } + public static JarFile readJar(String path) throws ClassFormatException,IOException { + return readJarInputStream(path, new FileInputStream(path)); + } } From 9761582fe30af674ff7c2e1cfe200376c1da2381 Mon Sep 17 00:00:00 2001 From: Martin Guillon Date: Thu, 3 Mar 2022 22:07:07 +0100 Subject: [PATCH 03/17] fix: only warn once for each class --- dts-generator/src/main/java/com/telerik/dts/DtsApi.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dts-generator/src/main/java/com/telerik/dts/DtsApi.java b/dts-generator/src/main/java/com/telerik/dts/DtsApi.java index ad93d0e..9abf426 100644 --- a/dts-generator/src/main/java/com/telerik/dts/DtsApi.java +++ b/dts-generator/src/main/java/com/telerik/dts/DtsApi.java @@ -64,6 +64,7 @@ public class DtsApi { private Pattern methodSignature = Pattern.compile("\\((?.*)\\)(?.*)"); private Pattern isWordPattern = Pattern.compile("^[\\w\\d]+$"); private Pattern isVoid = Pattern.compile("V(\\^.*\\;)?"); + private HashSet warnedMissing = new HashSet<>(); public DtsApi(boolean allGenericImplements) { this.allGenericImplements = allGenericImplements; @@ -592,7 +593,10 @@ private List getInterfaces(JavaClass classInterface) { // Added guard to prevent NullPointerExceptions in case libs are not provided - the dev can choose to include it and rerun the generator if (clazz1 == null) { - System.out.println("ignoring definitions in missing dependency: " + intface); + if (!warnedMissing.contains(intface)) { + warnedMissing.add(intface); + System.out.println("ignoring definitions in missing dependency: " + intface); + } continue; } From 9b769bd7a4f99fd88165a1d2b096fb3164d43b96 Mon Sep 17 00:00:00 2001 From: Martin Guillon Date: Thu, 3 Mar 2022 22:17:50 +0100 Subject: [PATCH 04/17] feat: default generics if no -input-generics passed --- dts-generator/build.gradle | 4 ++- .../src/main/java/com/telerik/dts/DtsApi.java | 34 ++++++++++++++----- .../main/java/com/telerik/dts/Generator.java | 8 ++++- .../src/main/resources}/generics.txt | 0 4 files changed, 36 insertions(+), 10 deletions(-) rename {libs => dts-generator/src/main/resources}/generics.txt (100%) diff --git a/dts-generator/build.gradle b/dts-generator/build.gradle index b874e2b..27904d0 100644 --- a/dts-generator/build.gradle +++ b/dts-generator/build.gradle @@ -65,7 +65,9 @@ jar { //pack jar dependencies into the final jar configurations.implementation.setCanBeResolved(true) from configurations.implementation.collect { it.isDirectory() ? it : zipTree(it) } - + from('src/main/resources') { + include 'generics.txt' + } //set main class for the jar manifest { attributes 'Main-Class': 'com.telerik.Main' diff --git a/dts-generator/src/main/java/com/telerik/dts/DtsApi.java b/dts-generator/src/main/java/com/telerik/dts/DtsApi.java index 9abf426..5c438cb 100644 --- a/dts-generator/src/main/java/com/telerik/dts/DtsApi.java +++ b/dts-generator/src/main/java/com/telerik/dts/DtsApi.java @@ -13,7 +13,11 @@ import org.apache.bcel.generic.Type; import org.apache.bcel.util.BCELComparator; +import java.io.BufferedReader; import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; @@ -78,7 +82,7 @@ public DtsApi(boolean allGenericImplements) { this.aliasedTypes = new HashMap<>(); } - public String generateDtsContent(List javaClasses){ + public String generateDtsContent(List javaClasses) { this.prevClass = null; if ((javaClasses != null) && (javaClasses.size() > 0)) { @@ -96,7 +100,7 @@ public String generateDtsContent(List javaClasses){ Signature signature = this.getSignature(currClass); TypeDefinition typeDefinition = null; - if(signature != null) { + if (signature != null) { typeDefinition = new TypeDefinition(signature.getSignature(), currentFileClassname); } @@ -112,7 +116,7 @@ public String generateDtsContent(List javaClasses){ // TODO: optimize this.namespaceParts = currentFileClassname.split("\\."); - if(isIgnoredNamespace()) { + if (isIgnoredNamespace()) { continue; } @@ -208,7 +212,7 @@ public String generateDtsContent(List javaClasses){ private String replaceIgnoredNamespaces(String content) { String regexFormat = "(?%s(?:(?:\\.[a-zA-Z\\d]*)|<[a-zA-Z\\d\\.<>]*>)*)(?[^a-zA-Z\\d]+)"; // these namespaces are not known in some android api levels, so we cannot use them in android-support for instance, so we are replacing them with any - for (String ignoredNamespace: this.getIgnoredNamespaces()) { + 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(".", "\\.")); @@ -224,14 +228,19 @@ private String replaceIgnoredNamespaces(String content) { public static String serializeGenerics() { StringBuilder sb = new StringBuilder(); sb.append("//Generics information:\n"); - for(Tuple generic: generics) { + for (Tuple generic : generics) { sb.append(String.format("//%s:%s\n", generic.x, generic.y)); } return sb.toString(); } - public static void loadGenerics(File inputFile) throws Exception { - List lines = Files.readAllLines(inputFile.toPath()); + public static void loadGenericsFromStream(InputStream stream) throws Exception { + List doc = + new BufferedReader(new InputStreamReader(stream, + StandardCharsets.UTF_8)).lines().collect(Collectors.toList()); + loadGenericsLines(doc); + } + public static void loadGenericsLines(List lines) throws Exception { for(String line: lines) { if(!line.equals("")) { while(line.startsWith("/")){ @@ -239,12 +248,21 @@ public static void loadGenerics(File inputFile) throws Exception { } String[] parts = line.split(":"); if (parts.length != 2) { - throw new Exception(String.format("Invalid generic info(%s) in file %s", line, inputFile)); + throw new Exception(String.format("Invalid generic info(%s)", line)); } externalGenerics.add(new Tuple<>(parts[0], Integer.parseInt(parts[1]))); } } } + public static void loadGenerics(File inputFile) throws Exception { + System.out.println("loadGenerics from file: " + inputFile.getAbsolutePath()); + try { + List lines = Files.readAllLines(inputFile.toPath()); + loadGenericsLines(lines); + } catch (Exception e) { + throw new Exception(String.format("%s in file %s", e.getMessage(), inputFile)); + } + } // Adds javalangObject types to all generics which are used without types public static String replaceGenericsInText(String content) { diff --git a/dts-generator/src/main/java/com/telerik/dts/Generator.java b/dts-generator/src/main/java/com/telerik/dts/Generator.java index 5ab5942..7192bb0 100644 --- a/dts-generator/src/main/java/com/telerik/dts/Generator.java +++ b/dts-generator/src/main/java/com/telerik/dts/Generator.java @@ -1,6 +1,7 @@ package com.telerik.dts; import com.telerik.InputParameters; +import com.telerik.Main; import org.apache.bcel.classfile.JavaClass; @@ -8,7 +9,9 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URL; import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; @@ -49,7 +52,10 @@ public void start(InputParameters inputParameters) throws Exception { } private void generateDts() throws Exception { - + if(inputGenericsFile == null) { + InputStream stream = Main.class.getClassLoader().getResourceAsStream("generics.txt"); + DtsApi.loadGenericsFromStream(stream); + } if(inputGenericsFile != null){ DtsApi.loadGenerics(inputGenericsFile); } diff --git a/libs/generics.txt b/dts-generator/src/main/resources/generics.txt similarity index 100% rename from libs/generics.txt rename to dts-generator/src/main/resources/generics.txt From a2680d28a8a610018c09e8d9a26464a6e791470f Mon Sep 17 00:00:00 2001 From: Martin Guillon Date: Thu, 3 Mar 2022 22:18:41 +0100 Subject: [PATCH 05/17] chore: updated readme for aar support --- README.md | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 4fd7ea2..172aa8b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Android d.ts Generator -A tool that generates TypeScript declaration files (.d.ts) from Jars +A tool that generates TypeScript declaration files (.d.ts) from Jars/Aars > Because there are certain incompatibilities between Java and TypeScript, definitions MAY NOT always be completely accurate. They will however compile and provide auto complete feature inside a modern text editor supporting typings. @@ -24,14 +24,14 @@ cd dts-generator java -jar build/libs/dts-generator.jar -input %ANDROID_HOME%/platforms/android-/android.jar ``` -## Generate definitions for any Jar +## Generate definitions for any Jar/Aar ```shell cd dts-generator ./gradlew jar -java -jar build/libs/dts-generator.jar -input +java -jar build/libs/dts-generator.jar -input ``` -## Pass multiple jars to generator +## Pass multiple jars/aars to generator ```shell cd dts-generator ./gradlew jar @@ -60,14 +60,7 @@ Then check whether the make command will return an error comparing the files. > Note: If the libs folder content is changed, the expected output should also be changed ## Generate definitions for .aar -``` -Open the .aar archive -Extract the classes.jar and any dependencies it may have inside libs/ -Rename classes.jar if necessary -``` -```shell -java -jar build/libs/dts-generator.jar -input classes.jar dependency-of-classes-jar.jar -``` +Pass the aar as input as you would normally do ## Complex typings generation Generating the typings corresponding to the android and android-support jar files is a bit tricky operation, so here's a detailed explanation how to do it. From 078a94063f6bfd562b063770697fe6a755dad057 Mon Sep 17 00:00:00 2001 From: Martin Guillon Date: Thu, 3 Mar 2022 22:18:48 +0100 Subject: [PATCH 06/17] chore: gradle update --- dts-generator/gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dts-generator/gradle/wrapper/gradle-wrapper.properties b/dts-generator/gradle/wrapper/gradle-wrapper.properties index a79f623..e3966cb 100644 --- a/dts-generator/gradle/wrapper/gradle-wrapper.properties +++ b/dts-generator/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip From c4530da2155171cfcb404e23f489a0911ee337c7 Mon Sep 17 00:00:00 2001 From: Martin Guillon Date: Fri, 4 Mar 2022 09:58:52 +0100 Subject: [PATCH 07/17] feat: optional `-ignore-obfuscated 3` parameter to obfuscated classes/members/properties for name < length --- .../java/com/telerik/InputParameters.java | 7 +++++ .../src/main/java/com/telerik/Main.java | 11 +++++++ .../src/main/java/com/telerik/dts/DtsApi.java | 31 +++++++++++++++---- .../main/java/com/telerik/dts/Generator.java | 7 +++-- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/dts-generator/src/main/java/com/telerik/InputParameters.java b/dts-generator/src/main/java/com/telerik/InputParameters.java index afc38c3..4cab5ef 100644 --- a/dts-generator/src/main/java/com/telerik/InputParameters.java +++ b/dts-generator/src/main/java/com/telerik/InputParameters.java @@ -16,6 +16,7 @@ public class InputParameters { private boolean allGenericImplements; private boolean skipDeclarations; private boolean classMode; + private int ignoreObfuscatedNameLength; public InputParameters() { this.outputDir = new File("out"); @@ -24,6 +25,7 @@ public InputParameters() { this.allGenericImplements = false; this.skipDeclarations = false; this.classMode = false; + this.ignoreObfuscatedNameLength = 0; } public File getOutputDir() { @@ -63,4 +65,9 @@ public boolean isAllGenericImplementsEnabled() { public boolean getClassMode() { return this.classMode; } public void setClassMode(boolean classMode) { this.classMode = classMode; } + + public int getIgnoreObfuscatedNameLength() { return this.ignoreObfuscatedNameLength; } + + public void setIgnoreObfuscatedNameLength(int nameLength) { this.ignoreObfuscatedNameLength = nameLength; } + } diff --git a/dts-generator/src/main/java/com/telerik/Main.java b/dts-generator/src/main/java/com/telerik/Main.java index 7ea8ee3..7803571 100644 --- a/dts-generator/src/main/java/com/telerik/Main.java +++ b/dts-generator/src/main/java/com/telerik/Main.java @@ -23,6 +23,10 @@ public class Main { // whether to skip the declarations file generation private static final String SKIP_DECLARATIONS = "-skip-declarations"; + // whether to ignore obfuscated classes/namespaces/methods + // the parameter defines the length of obfuscated names to detect + private static final String IGNORE_OBFUSCATED = "-ignore-obfuscated"; + private static final String HELP = "-help"; public static void main(String[] args) { @@ -70,6 +74,13 @@ public static InputParameters parseCommand(String[] args) throws Exception { inputParameters.setClassMode(true); } + if (commandArg.equals(IGNORE_OBFUSCATED)) { + if (i != (args.length - 1)) { + String nextParam = args[i + 1]; + inputParameters.setIgnoreObfuscatedNameLength(Integer.valueOf(nextParam)); + } + } + if (commandArg.equals(OUT_DIR)) { if (i != (args.length - 1)) { String nextParam = args[i + 1]; diff --git a/dts-generator/src/main/java/com/telerik/dts/DtsApi.java b/dts-generator/src/main/java/com/telerik/dts/DtsApi.java index 5c438cb..c7910ca 100644 --- a/dts-generator/src/main/java/com/telerik/dts/DtsApi.java +++ b/dts-generator/src/main/java/com/telerik/dts/DtsApi.java @@ -1,5 +1,7 @@ package com.telerik.dts; +import com.telerik.InputParameters; + import org.apache.bcel.classfile.Attribute; import org.apache.bcel.classfile.Field; import org.apache.bcel.classfile.FieldOrMethod; @@ -69,9 +71,11 @@ public class DtsApi { private Pattern isWordPattern = Pattern.compile("^[\\w\\d]+$"); private Pattern isVoid = Pattern.compile("V(\\^.*\\;)?"); private HashSet warnedMissing = new HashSet<>(); + private int ignoreObfuscatedNameLength; - public DtsApi(boolean allGenericImplements) { + public DtsApi(boolean allGenericImplements, InputParameters inputParameters) { this.allGenericImplements = allGenericImplements; + this.ignoreObfuscatedNameLength = inputParameters.getIgnoreObfuscatedNameLength(); this.indent = 0; overrideFieldComparator(); @@ -96,8 +100,11 @@ public String generateDtsContent(List javaClasses) { JavaClass currClass = javaClasses.get(i); currentFileClassname = currClass.getClassName(); - String simpleClassName = getSimpleClassname(currClass); + String simpleClassName = getSimpleClassname(currClass); + if (isObfuscated(simpleClassName)) { + continue; + } Signature signature = this.getSignature(currClass); TypeDefinition typeDefinition = null; if (signature != null) { @@ -204,9 +211,9 @@ public String generateDtsContent(List javaClasses) { Arrays.sort(refs); } - String conent = replaceIgnoredNamespaces(sbContent.toString()); + String content = replaceIgnoredNamespaces(sbContent.toString()); - return conent; + return content; } private String replaceIgnoredNamespaces(String content) { @@ -659,7 +666,7 @@ private Set getAllInterfacesFields(List interfaces) { private void processMethod(Method method, JavaClass clazz, TypeDefinition typeDefinition, Set methodsSet) { String name = method.getName(); - if (isPrivateGoogleApiMember(name)) return; + if (shouldIgnoreMember(name)) return; if (method.isSynthetic() || (!method.isPublic() && !method.isProtected())) { return; @@ -940,7 +947,7 @@ private String getMethodParamSignature(JavaClass clazz, TypeDefinition typeDefin private void processField(Field f, JavaClass clazz, TypeDefinition typeDefinition) { String fieldName = f.getName(); - if (isPrivateGoogleApiMember(fieldName)) return; + if (shouldIgnoreMember(fieldName)) return; String tabs = getTabs(this.indent + 1); sbContent.append(tabs + "public "); @@ -1183,6 +1190,18 @@ private String getTabs(int count) { private boolean isPrivateGoogleApiMember(String memberName) { return memberName.startsWith("zz"); } + private boolean isObfuscated(String memberName) { + if (this.ignoreObfuscatedNameLength > 0) { + + // basic test to remove obfuscated classes + return memberName.length() <= this.ignoreObfuscatedNameLength && !memberName.equals("R"); + } + return false; + } + + private boolean shouldIgnoreMember(String memberName) { + return isPrivateGoogleApiMember(memberName) || isObfuscated(memberName); + } private boolean isPrivateGoogleApiClass(String name) { String[] classNameParts = name.replace('$', '.').split("\\."); diff --git a/dts-generator/src/main/java/com/telerik/dts/Generator.java b/dts-generator/src/main/java/com/telerik/dts/Generator.java index 7192bb0..90ded96 100644 --- a/dts-generator/src/main/java/com/telerik/dts/Generator.java +++ b/dts-generator/src/main/java/com/telerik/dts/Generator.java @@ -40,7 +40,7 @@ public void start(InputParameters inputParameters) throws Exception { this.skipDeclarations = inputParameters.getSkipDeclarations(); this.classMode = inputParameters.getClassMode(); this.fileHelper = new FileHelper(inputParameters.getOutputDir()); - this.dtsApi = new DtsApi(allGenericImplements); + this.dtsApi = new DtsApi(allGenericImplements, inputParameters); this.outFileName = FileHelper.DEFAULT_DTS_FILE_NAME; this.declarationsFileName = FileHelper.DEFAULT_DECLARATIONS_FILE_NAME; @@ -67,8 +67,9 @@ private void generateDts() throws Exception { while (ClassRepo.hasNext()) { List classFiles = ClassRepo.getNextClassGroup(); String generatedContent = this.dtsApi.generateDtsContent(classFiles); - - this.fileHelper.writeToFile(generatedContent, this.outFileName, true); + if (generatedContent.length() > 0) { + this.fileHelper.writeToFile(generatedContent, this.outFileName, true); + } } String content = this.fileHelper.readFileContent(this.outFileName); From 50d3046005434e04b6e89a088ddf303d1813830d Mon Sep 17 00:00:00 2001 From: farfromrefuge Date: Wed, 20 Sep 2023 11:07:55 +0200 Subject: [PATCH 08/17] chore: cleanup --- dts-generator/gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dts-generator/gradle/wrapper/gradle-wrapper.properties b/dts-generator/gradle/wrapper/gradle-wrapper.properties index d66f36f..5d05400 100644 --- a/dts-generator/gradle/wrapper/gradle-wrapper.properties +++ b/dts-generator/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip \ No newline at end of file From eedc110863890dca149ea26bceb89c4a2449f308 Mon Sep 17 00:00:00 2001 From: farfromrefuge Date: Wed, 20 Sep 2023 11:33:18 +0200 Subject: [PATCH 09/17] fix: fix eslint warnings --- dts-generator/src/main/java/com/telerik/dts/DtsApi.java | 4 ++-- dts-generator/src/main/java/com/telerik/dts/Generator.java | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/dts-generator/src/main/java/com/telerik/dts/DtsApi.java b/dts-generator/src/main/java/com/telerik/dts/DtsApi.java index 9b7c603..1cc21fa 100644 --- a/dts-generator/src/main/java/com/telerik/dts/DtsApi.java +++ b/dts-generator/src/main/java/com/telerik/dts/DtsApi.java @@ -528,12 +528,12 @@ private int openPackage(JavaClass prevClass, JavaClass currClass) { } else { sbContent.append(tabs + "export "); } - sbContent.appendln("module " + currParts[idx] + " {"); + sbContent.appendln("namespace " + currParts[idx] + " {"); } if (isNested(currClass) && (prevParts.length < currParts.length)) { String tabs = getTabs(prevParts.length - 1); - sbContent.appendln(tabs + "export module " + prevParts[prevParts.length - 1] + " {"); + sbContent.appendln(tabs + "export namespace " + prevParts[prevParts.length - 1] + " {"); } return indent; diff --git a/dts-generator/src/main/java/com/telerik/dts/Generator.java b/dts-generator/src/main/java/com/telerik/dts/Generator.java index 90ded96..851b515 100644 --- a/dts-generator/src/main/java/com/telerik/dts/Generator.java +++ b/dts-generator/src/main/java/com/telerik/dts/Generator.java @@ -59,9 +59,11 @@ private void generateDts() throws Exception { if(inputGenericsFile != null){ DtsApi.loadGenerics(inputGenericsFile); } - + this.fileHelper.writeToFile("/* eslint-disable @typescript-eslint/unified-signatures */\n" + + "/* eslint-disable @typescript-eslint/adjacent-overload-signatures */\n" + + "/* eslint-disable no-redeclare */\n", this.outFileName, false); if(!this.skipDeclarations) { - this.fileHelper.writeToFile(String.format("/// \n", this.declarationsFileName), this.outFileName, false); + this.fileHelper.writeToFile(String.format("/// \n", this.declarationsFileName), this.outFileName, true); } while (ClassRepo.hasNext()) { From 5e3d2841cfb0b4f6a8ae10e2cc532662dd0db4d6 Mon Sep 17 00:00:00 2001 From: farfromrefuge Date: Fri, 13 Oct 2023 14:40:21 +0200 Subject: [PATCH 10/17] feat: const field value is now part of the typings --- dts-generator/src/main/java/com/telerik/dts/DtsApi.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dts-generator/src/main/java/com/telerik/dts/DtsApi.java b/dts-generator/src/main/java/com/telerik/dts/DtsApi.java index 1cc21fa..1a8e2ed 100644 --- a/dts-generator/src/main/java/com/telerik/dts/DtsApi.java +++ b/dts-generator/src/main/java/com/telerik/dts/DtsApi.java @@ -992,7 +992,13 @@ private void processField(Field f, JavaClass clazz, TypeDefinition typeDefinitio name = "\"" + name + "\""; } - sbContent.appendln(name + ": " + getTypeScriptTypeFromJavaType(this.getFieldType(f), typeDefinition) + ";"); + sbContent.append(name + ": " + getTypeScriptTypeFromJavaType(this.getFieldType(f), typeDefinition)); + if (f.getConstantValue() != null) { + sbContent.appendln( " = " + f.getConstantValue() + ";"); + } else { + sbContent.appendln(";"); + + } } private void addClassField(JavaClass clazz) { From a74997178b3cbfea04e44910ae544d6e542e6727 Mon Sep 17 00:00:00 2001 From: farfromrefug Date: Wed, 27 Mar 2024 11:39:19 +0100 Subject: [PATCH 11/17] chore: revert 7659d265 --- dts-generator/src/main/java/com/telerik/dts/DtsApi.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dts-generator/src/main/java/com/telerik/dts/DtsApi.java b/dts-generator/src/main/java/com/telerik/dts/DtsApi.java index 19bb9f3..0bab582 100644 --- a/dts-generator/src/main/java/com/telerik/dts/DtsApi.java +++ b/dts-generator/src/main/java/com/telerik/dts/DtsApi.java @@ -1030,7 +1030,7 @@ private void processField(Field f, JavaClass clazz, TypeDefinition typeDefinitio sbContent.append(name + ": " + getTypeScriptTypeFromJavaType(this.getFieldType(f), typeDefinition)); if (f.getConstantValue() != null) { - sbContent.appendln( " = " + f.getConstantValue() + ";"); + sbContent.appendln( "; // " + f.getConstantValue()); } else { sbContent.appendln(";"); From 437a22c6b4fdead9bda1e44e32a88e2546c69d14 Mon Sep 17 00:00:00 2001 From: farfromrefug Date: Fri, 29 Aug 2025 16:56:08 +0200 Subject: [PATCH 12/17] chore: update --- dts-generator/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dts-generator/build.gradle b/dts-generator/build.gradle index f321e4d..940ff33 100644 --- a/dts-generator/build.gradle +++ b/dts-generator/build.gradle @@ -5,8 +5,8 @@ import java.util.jar.JarFile apply plugin: 'java-library' java { - sourceCompatibility = '17' - targetCompatibility = '17' + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } def version = "4.0.0" From 072d94f146adbff5b5aaafb9dff645984b31c0d3 Mon Sep 17 00:00:00 2001 From: Daniel Grima Date: Sat, 18 Oct 2025 14:29:44 +0200 Subject: [PATCH 13/17] Updates to tackle Kotlin compatibility issues (#84) --- .../src/main/java/com/telerik/dts/DtsApi.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/dts-generator/src/main/java/com/telerik/dts/DtsApi.java b/dts-generator/src/main/java/com/telerik/dts/DtsApi.java index 9177b68..bfd1ff0 100644 --- a/dts-generator/src/main/java/com/telerik/dts/DtsApi.java +++ b/dts-generator/src/main/java/com/telerik/dts/DtsApi.java @@ -139,7 +139,8 @@ public String generateDtsContent(List javaClasses) { currentFileClassname.contains(".debugger.") || currentFileClassname.endsWith("package-info") || currentFileClassname.endsWith("module-info") || - currentFileClassname.endsWith("Kt")) { + currentFileClassname.endsWith("Kt") || + currentFileClassname.contains("$$serializer")) { continue; } @@ -244,7 +245,9 @@ public String generateDtsContent(List javaClasses) { } private String replaceIgnoredNamespaces(String content) { - String regexFormat = "(?%s(?:(?:\\.[a-zA-Z\\d]*)|<[a-zA-Z\\d\\.<>]*>)*)(?[^a-zA-Z\\d]+)"; + // 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 = "(?%s(?:(?:\\.[a-zA-Z\\d]*)|<[a-zA-Z\\d\\.<>, ]*>)*)(?[^a-zA-Z\\d]+|$)"; // these namespaces are not known in some android api levels, so we cannot use them in android-support for instance, so we are replacing them with any for (String ignoredNamespace : this.getIgnoredNamespaces()) { String regexString = String.format(regexFormat, ignoredNamespace.replace(".", "\\.")); @@ -960,6 +963,20 @@ private String getMethodParamSignature(JavaClass clazz, TypeDefinition typeDefin if (localVariable != null) { String name = localVariable.getName(); + + // Sanitize Kotlin synthetic parameter names like + 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 + "_"); From db4703725a52a41bb01659486a869a5660c87ec2 Mon Sep 17 00:00:00 2001 From: Martin Guillon Date: Sat, 25 Oct 2025 21:54:10 +0200 Subject: [PATCH 14/17] fix: many dts generation fixes: * handle kotlin parameters without return paramter names with forbidden chars * ensure replaceGenericsInText does not remove > chars far already any types * better detection of open/close namespace to prevent unclosed namespaces * some inner namespaces where not correctly filled/exported --- .../src/main/java/com/telerik/dts/DtsApi.java | 637 +++++++++++++----- 1 file changed, 484 insertions(+), 153 deletions(-) diff --git a/dts-generator/src/main/java/com/telerik/dts/DtsApi.java b/dts-generator/src/main/java/com/telerik/dts/DtsApi.java index 1cb75d2..19ecf9f 100644 --- a/dts-generator/src/main/java/com/telerik/dts/DtsApi.java +++ b/dts-generator/src/main/java/com/telerik/dts/DtsApi.java @@ -43,9 +43,9 @@ import edu.umd.cs.findbugs.ba.generic.GenericSignatureParser; import edu.umd.cs.findbugs.ba.generic.GenericUtilities; -/** - * Created by plamen5kov on 6/17/16. - */ +import java.util.Deque; +import java.util.ArrayDeque; + public class DtsApi { public static List> externalGenerics = new ArrayList<>(); public static List> generics = new ArrayList<>(); @@ -144,9 +144,7 @@ public String generateDtsContent(List javaClasses) { continue; } - // check if processed class hijacks a namespace - // TODO: optimize - + // compute namespace parts this.namespaceParts = currentFileClassname.split("\\."); if (isIgnoredNamespace()) { System.out.println(String.format("Found ignored namespace. %s", String.join(".", this.namespaceParts))); @@ -161,6 +159,57 @@ public String generateDtsContent(List javaClasses) { String tabs = getTabs(this.indent); + // Special-case: Kotlin Companion inner object — emit as namespace merge instead of class + if ((simpleClassName.equals("Companion") || currClass.getClassName().endsWith("$Companion")) && currClass.getClassName().contains("$")) { + // compute outer simple class name + String parentFull = currClass.getClassName().substring(0, currClass.getClassName().lastIndexOf("$")); + String[] parentParts = parentFull.replace('$', '.').split("\\."); + String outerSimple = parentParts[parentParts.length - 1]; + + // collect members and emit them inside namespace merge: export namespace Outer { export namespace Companion { ... } } + sbContent.appendln(tabs + "export namespace " + outerSimple + " {"); + sbContent.appendln(tabs + "\texport namespace Companion {"); + + List members = getMembers(currClass, getAllInterfaces(currClass)); + for (FieldOrMethod member : members) { + if (member instanceof Method) { + Method m = (Method) member; + if (m.isSynthetic() || (!m.isPublic() && !m.isProtected())) { + continue; + } + if (isConstructor(m)) { + continue; + } + String methodNameRaw = m.getName(); + String methodNameForDecl = jsFieldPattern.matcher(methodNameRaw).matches() ? methodNameRaw : getMethodName(m); + + String paramsSig = getMethodParamSignature(currClass, typeDefinition, m); + String returnType = ""; + if (!isConstructor(m)) { + returnType = ": " + safeGetTypeScriptType(this.getReturnType(m), typeDefinition); + } + sbContent.appendln(tabs + "\t\tfunction " + methodNameForDecl + paramsSig + returnType + ";"); + } else if (member instanceof Field) { + Field f = (Field) member; + if (f.isSynthetic() || (!f.isPublic() && !f.isProtected())) { + continue; + } + String name = f.getName(); + if (!jsFieldPattern.matcher(name).matches()) { + name = "\"" + name + "\""; + } + String fType = safeGetTypeScriptType(this.getFieldType(f), typeDefinition); + sbContent.appendln(tabs + "\t\tconst " + name + ": " + fType + ";"); + } + } + + sbContent.appendln(tabs + "\t}"); + sbContent.appendln(tabs + "}"); + // don't emit the Companion class itself; we've emitted the namespace merge + this.prevClass = currClass; + continue; + } + String extendsLine = getExtendsLine(currClass, typeDefinition); if (simpleClassName.equals("AccessibilityDelegate")) { @@ -239,27 +288,143 @@ public String generateDtsContent(List javaClasses) { Arrays.sort(refs); } - String content = replaceIgnoredNamespaces(sbContent.toString()); + String before = sbContent.toString(); + String content = replaceIgnoredNamespaces(before); + if (!before.equals(content)) { + System.out.println("replaceIgnoredNamespaces changed content. (length before=" + before + " after=" + content + ")"); + // optionally write both to disk / temp files for diffing + } + // String content = replaceIgnoredNamespaces(sbContent.toString()); + + // Ensure braces are balanced (only append missing closing braces) + content = balanceUnclosedBraces(content); return content; } + /** + * Replace known ignored namespaces with 'any' — but do it safely on a per-match basis + * (no greedy regex that can swallow closing '>' characters). For each occurrence of + * the ignored namespace-qualified type we: + * - detect if it's followed by a generic argument list starting with '<' + * - if so, scan forward to find the matching closing '>' (taking nested '<' into account) + * - replace the entire type token (including its generic args) with "any" + * + * This approach never uses a global regex that can accidentally drop characters + * from previously-correct type tokens. + */ 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 = "(?%s(?:(?:\\.[a-zA-Z\\d]*)|<[a-zA-Z\\d\\.<>, ]*>)*)(?[^a-zA-Z\\d]+|$)"; - // these namespaces are not known in some android api levels, so we cannot use them in android-support for instance, so we are replacing them with any + String result = content; + 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"); + result = replaceIgnoredNamespaceOccurrences(result, ignoredNamespace); + String globalAliasedClassName = getGlobalAliasedClassName(ignoredNamespace); + if (!globalAliasedClassName.equals(ignoredNamespace)) { + result = replaceIgnoredNamespaceOccurrences(result, globalAliasedClassName); + } } - // replace "extends any" with "extends java.lang.Object" - content = content.replace(" extends any ", String.format(" extends %s ", DtsApi.JavaLangObject)); + // keep the small extends-any replacement + result = result.replace(" extends any ", String.format(" extends %s ", DtsApi.JavaLangObject)); - return content; + return result; + } + + /** + * Replace occurrences of a particular namespace/prefix like "kotlin.foo.Bar" (or its + * aliased variant) with "any". This is done by finding occurrences of the qualified + * name and — if the next character is '<' — locating the matching '>' and including + * it in the replaced span. The replacement preserves all surrounding characters. + */ + private String replaceIgnoredNamespaceOccurrences(String content, String namespacePrefix) { + if (content == null || content.isEmpty()) { + return content; + } + + StringBuilder out = new StringBuilder(); + int last = 0; + + // pattern matches the namespace prefix followed by one or more ".SimpleName" segments + Pattern p = Pattern.compile(Pattern.quote(namespacePrefix) + "(?:\\.[A-Za-z0-9_]+)*"); + Matcher m = p.matcher(content); + + while (m.find()) { + int s = m.start(); + int e = m.end(); + + // append unchanged region before this match + out.append(content, last, s); + + // Defensive check: ensure the match isn't part of a longer identifier (e.g. preceded by a letter/digit/_) + // if it is, treat it as non-match (copy-through) + boolean isPartOfLongerIdentifier = false; + if (s > 0) { + char before = content.charAt(s - 1); + if (Character.isLetterOrDigit(before) || before == '_' || before == '$') { + isPartOfLongerIdentifier = true; + } + } + if (isPartOfLongerIdentifier) { + // copy the matched text as-is and continue + out.append(content, s, e); + last = e; + // move the matcher's region forward to avoid re-matching the same span + if (e < content.length()) { + m.region(e, content.length()); + } + continue; + } + + // Determine whether a generic argument list follows (starts with '<') + int newEnd = e; + if (e < content.length() && content.charAt(e) == '<') { + int match = findMatchingAngle(content, e); + if (match != -1) { + newEnd = match + 1; // include the closing '>' + } else { + // Unbalanced generics in the source; be conservative and do not consume further chars + newEnd = e; + } + } + + // Replace the full type token (including generics if found) with "any" + out.append("any"); + + // advance last to the end of the consumed span + last = newEnd; + + // advance matcher search region to the new last position + if (last < content.length()) { + m.region(last, content.length()); + } else { + break; + } + } + + // append the rest + out.append(content.substring(last)); + return out.toString(); + } + + /** + * Find matching '>' for a '<' at position 'start' (start points to the '<' char). + * Handles nested '<' / '>' pairs and ignores other characters. + * Returns index of matching '>' or -1 if not found. + */ + private static int findMatchingAngle(String text, int start) { + if (text == null || start < 0 || start >= text.length() || text.charAt(start) != '<') { + return -1; + } + int depth = 0; + for (int i = start; i < text.length(); i++) { + char c = text.charAt(i); + if (c == '<') depth++; + else if (c == '>') { + depth--; + if (depth == 0) return i; + } + } + return -1; // no match found } public static String serializeGenerics() { @@ -303,7 +468,10 @@ public static void loadGenerics(File inputFile) throws Exception { } } - // Adds javalangObject types to all generics which are used without types + /** + * Compatibility API: limited/safe replaceGenericsInText used by other steps/tools. + * Only appends "" for classes we know are generic (from generics + externalGenerics). + */ public static String replaceGenericsInText(String content) { String any = "any"; String result = content; @@ -322,9 +490,9 @@ public static String replaceGenericsInText(String content) { } private static String replaceNonGenericUsage(String content, String className, Integer occurencies, String javalangObject) { - String result = content; - Pattern usedAsNonGenericPattern = Pattern.compile(className.replace(".", "\\.") + "(?[^a-zA-Z\\d^\\.^\\$^\\<])"); - Matcher matcher = usedAsNonGenericPattern.matcher(result); + // AppendReplacement-based approach to avoid regex replacement pitfalls. + Pattern usedAsNonGenericPattern = Pattern.compile(className.replace(".", "\\.") + "(?[^a-zA-Z\\d\\.\\$\\<])"); + Matcher matcher = usedAsNonGenericPattern.matcher(content); if (!matcher.find()) return content; @@ -335,11 +503,16 @@ private static String replaceNonGenericUsage(String content, String className, I } String classSuffix = "<" + String.join(",", arguments) + ">"; - System.out.println(String.format("Appending %s to occurrences of class %s without passed generic types", classSuffix, className)); - - String replaceString = String.format("%s%s$1", className, classSuffix); - result = matcher.replaceAll(replaceString); - return result; + matcher.reset(); + StringBuffer sb = new StringBuffer(); + while (matcher.find()) { + String suffix = matcher.group("Suffix"); + String replacement = className + classSuffix + suffix; + replacement = Matcher.quoteReplacement(replacement); + matcher.appendReplacement(sb, replacement); + } + matcher.appendTail(sb); + return sb.toString(); } private String getExtendsLine(JavaClass currClass, TypeDefinition typeDefinition) { @@ -472,101 +645,95 @@ private String mangleRootClassname(String className) { return className; } - private int closePackage(JavaClass prevClass, JavaClass currClass) { - int indent = 0; - - if (prevClass == null) { - return indent; - } - String prevClassName = prevClass.getClassName(); - int prevDotCount = prevClassName.length() - prevClassName.replace(".", "").length(); - int prevDollarCount = prevClassName.length() - prevClassName.replace("$", "").length(); - int prevCount = prevDotCount + prevDollarCount; + private Deque namespaceStack = new ArrayDeque<>(); - if (currClass == null) { - indent = prevCount; - while (indent > 0) { - String tabs = getTabs(--indent); - sbContent.appendln(tabs + "}"); + /** + * Close namespaces that are not part of currClass's desired namespace path. + * This is stack-based: namespaceStack contains the currently open namespace parts + * (in order). We compute the desired namespace path for currClass (all parts except the final + * class name) and pop/emit closing braces for any stack entries not in that prefix. + * + * Returns the current namespace indentation level (namespaceStack.size()). + */ + private int closePackage(JavaClass prevClass, JavaClass currClass) { + // desired namespace segments for currClass (all parts except last/class name) + String[] desiredParts; + if (currClass != null) { + String curr = currClass.getClassName().replace('$', '.'); + String[] parts = curr.split("\\."); + if (parts.length <= 1) { + desiredParts = new String[0]; + } else { + desiredParts = Arrays.copyOfRange(parts, 0, parts.length - 1); } - return indent; + } else { + desiredParts = new String[0]; } - String currClassName = currClass.getClassName(); - int currDotCount = currClassName.length() - currClassName.replace(".", "").length(); - int currDollarCount = currClassName.length() - currClassName.replace("$", "").length(); - int currCount = currDotCount + currDollarCount; + // compare namespaceStack with desiredParts to find common prefix + List stackList = new ArrayList<>(namespaceStack); + int common = 0; + while (common < stackList.size() && common < desiredParts.length) { + if (stackList.get(common).equals(desiredParts[common])) { + common++; + } else { + break; + } + } - while (prevCount > currCount) { - String tabs = getTabs(--prevCount); + // pop & close namespaces that are beyond the common prefix + for (int i = stackList.size() - 1; i >= common; i--) { + // the tab level for closing should match the index of the namespace being closed + String tabs = getTabs(i); sbContent.appendln(tabs + "}"); + namespaceStack.removeLast(); } - boolean isNested = isNested(currClass); - - if (!isNested) { - throw new UnsupportedOperationException("TODO: implement"); - // String prevClassName = prevClass.getClassName(); - // int dotCount = prevClassName.length() - - // prevClassName.replace(".", "").length(); - // int dollarCount = prevClassName.length() - - // prevClassName.replace("$", "").length(); - // indent = dotCount + dollarCount; - // - // String[] prevParts = prevClassName.replace('$', - // '.').split("\\."); - // String[] currParts = currClass.getClassName().replace('$', - // '.').split("\\."); - // - // int diffIdx = 0; - // while ((diffIdx < prevParts.length) && (diffIdx < - // currParts.length) && - // prevParts[diffIdx].equals(currParts[diffIdx])) { - // ++diffIdx; - // } - // - // int count = prevParts.length - diffIdx - 1; - // while (count-- > 0) { - // String tabs = getTabs(--indent); - // ps.println(tabs + "}"); - // } - } - - return indent; + // indentation is the number of open namespaces remaining + return namespaceStack.size(); } + /** + * Open namespaces needed for currClass. Assumes closePackage(prevClass, currClass) was (or will be) + * called to close non-matching namespaces first; nevertheless this method is defensive and computes + * the common prefix against the current namespaceStack state to open only the missing segments. + * + * Returns the current namespace indentation level (namespaceStack.size()) after opening. + */ private int openPackage(JavaClass prevClass, JavaClass currClass) { - int indent = 0; - - String prevClassName = (prevClass != null) ? prevClass.getClassName() : ""; - String[] prevParts = prevClassName.replace('$', '.').split("\\."); - String[] currParts = currClass.getClassName().replace('$', '.').split("\\."); - - int diffIdx = 0; - while ((diffIdx < prevParts.length) && (diffIdx < currParts.length) - && prevParts[diffIdx].equals(currParts[diffIdx])) { - ++diffIdx; + // desired namespace segments for currClass (all parts except last/class name) + String curr = currClass.getClassName().replace('$', '.'); + String[] parts = curr.split("\\."); + String[] desiredParts = (parts.length <= 1) ? new String[0] : Arrays.copyOfRange(parts, 0, parts.length - 1); + + // current stack as list for prefix comparison + List stackList = new ArrayList<>(namespaceStack); + + int common = 0; + while (common < stackList.size() && common < desiredParts.length) { + if (stackList.get(common).equals(desiredParts[common])) { + common++; + } else { + break; + } } - indent = diffIdx; - for (int idx = diffIdx; idx < currParts.length - 1; idx++) { - ++indent; - String tabs = getTabs(idx); - if (idx == 0) { + // open the remaining desired namespaces (indices common .. desiredParts.length-1) + for (int i = common; i < desiredParts.length; i++) { + String part = desiredParts[i]; + if (part == null || part.isEmpty()) continue; + String tabs = getTabs(i); + if (i == 0) { sbContent.append(tabs + "declare "); } else { sbContent.append(tabs + "export "); } - sbContent.appendln("namespace " + currParts[idx] + " {"); - } - - if (isNested(currClass) && (prevParts.length < currParts.length)) { - String tabs = getTabs(prevParts.length - 1); - sbContent.appendln(tabs + "export namespace " + prevParts[prevParts.length - 1] + " {"); + sbContent.appendln("namespace " + part + " {"); + namespaceStack.addLast(part); } - return indent; + return namespaceStack.size(); } private void processInterfaceConstructor(JavaClass classInterface, TypeDefinition typeDefinition, List allInterfacesMethods) { @@ -584,7 +751,7 @@ private void generateInterfaceConstructorContent(JavaClass classInterface, TypeD sbContent.append(getTabs(this.indent + 2) + getMethodName(m) + getMethodParamSignature(classInterface, typeDefinition, m)); String bmSig = ""; if (!isConstructor(m)) { - bmSig += ": " + getTypeScriptTypeFromJavaType(this.getReturnType(m), typeDefinition); + bmSig += ": " + safeGetTypeScriptType(this.getReturnType(m), typeDefinition); } sbContent.appendln(bmSig + ";"); } @@ -596,8 +763,8 @@ private void generateInterfaceConstructorContent(JavaClass classInterface, TypeD private void generateInterfaceConstructorCommentBlock(JavaClass classInterface, String tabs) { sbContent.appendln(tabs + "/**"); - sbContent.appendln(tabs + " * Constructs a new instance of the " + classInterface.getClassName() + " interface with the provided implementation. An empty constructor exists calling super() when extending the interface class."); - // sbContent.appendln(tabs + " * @param implementation - allows implementor to define their own logic for all public methods."); // <- causes too much noise + sbContent.appendln(tabs + " * Constructs a new instance of the " + classInterface.getClassName() + " interface with the provided implementation."); + sbContent.appendln(tabs + " * An empty constructor exists calling super()."); sbContent.appendln(tabs + " */"); } @@ -650,7 +817,6 @@ private List getInterfaces(JavaClass classInterface) { for (String intface : interfaceNames) { JavaClass clazz1 = ClassRepo.findClass(intface); - // Added guard to prevent NullPointerExceptions in case libs are not provided - the dev can choose to include it and rerun the generator if (clazz1 == null) { if (!warnedMissing.contains(intface)) { warnedMissing.add(intface); @@ -661,7 +827,6 @@ private List getInterfaces(JavaClass classInterface) { String className = clazz1.getClassName(); - // TODO: Pete: Hardcoded until we figure out how to go around the 'type incompatible with Object' issue if (className.equals("java.util.Iterator") || className.equals("android.animation.TypeEvaluator") || className.equals("java.lang.Comparable") || @@ -706,7 +871,6 @@ private void processMethod(Method method, JavaClass clazz, TypeDefinition typeDe return; } - // TODO: Pete: won't generate static initializers as invalid typescript properties if (clazz.isInterface() && name.equals("")) { return; } @@ -755,7 +919,7 @@ private String generateMethodContent(JavaClass clazz, TypeDefinition typeDefinit sbTemp.append(getMethodName(method) + getMethodParamSignature(clazz, typeDefinition, method)); String bmSig = ""; if (!isConstructor(method)) { - bmSig += ": " + getTypeScriptTypeFromJavaType(this.getReturnType(method), typeDefinition); + bmSig += ": " + safeGetTypeScriptType(this.getReturnType(method), typeDefinition); } sbTemp.append(bmSig + ";"); @@ -821,7 +985,6 @@ private static List getTypeParameters(String signature) { return types; } - // gets the full field type including generic types private Type getFieldType(Field f) { Signature signature = this.getSignature(f); if (signature != null) { @@ -838,7 +1001,6 @@ private Type getFieldType(Field f) { return f.getType(); } - // gets the full method return type including generic types private Type getReturnType(Method m) { Signature signature = this.getSignature(m); if (signature != null) { @@ -875,13 +1037,11 @@ private void loadBaseMethods(JavaClass clazz) { if (currClass != null) { - //get all base methods and method names while (true && currClass != null) { boolean isJavaLangObject = currClass.getClassName().equals(DtsApi.JavaLangObject); for (Method m : currClass.getMethods()) { if (!m.isSynthetic() && (m.isPublic() || m.isProtected())) { - // don't write empty constructor typings for java objects if (isJavaLangObject && isConstructor(m)) { continue; } @@ -951,6 +1111,8 @@ private String getMethodParamSignature(JavaClass clazz, TypeDefinition typeDefin StringBuilder sb = new StringBuilder(); sb.append("("); int idx = 0; + // track existing names in this method scope to deduplicate sanitized names + Set existingNames = new HashSet<>(); for (Type type : this.getArgumentTypes(m)) { if (idx > 0) { sb.append(", "); @@ -961,6 +1123,7 @@ private String getMethodParamSignature(JavaClass clazz, TypeDefinition typeDefin ? variables[localVarIndex] : null; + String nameToUse; if (localVariable != null) { String name = localVariable.getName(); @@ -977,23 +1140,25 @@ private String getMethodParamSignature(JavaClass clazz, TypeDefinition typeDefin } } - if (reservedJsKeywords.contains(name)) { - System.out.println(String.format("Appending _ to reserved JS keyword %s", name)); - sb.append(name + "_"); - } else { - sb.append(name); + // sanitize and truncate to max length 10, deduplicate in scope + nameToUse = sanitizeIdentifier(name, existingNames, 10); + + if (reservedJsKeywords.contains(nameToUse)) { + nameToUse = nameToUse + "_"; } + + sb.append(nameToUse); } else { // interface declarations will fallback to paramN since they don't have names in the bytecode - sb.append("param"); - sb.append(idx); + String fallback = "param" + idx; + nameToUse = sanitizeIdentifier(fallback, existingNames, 10); + sb.append(nameToUse); } idx++; sb.append(": "); - String paramTypeName = getTypeScriptTypeFromJavaType(type, typeDefinition); + String paramTypeName = safeGetTypeScriptType(type, typeDefinition); - // TODO: Pete: if (paramTypeName.startsWith("java.util.function")) { sb.append("any /* " + paramTypeName + "*/"); } else { @@ -1014,10 +1179,10 @@ private void processField(Field f, JavaClass clazz, TypeDefinition typeDefinitio // // handle member names that conflict with an inner class. For example: - // + // // class OuterClass { // public static InnerClass: OuterClass.InnerClass; - // + // // class InnerClass {} // } // @@ -1047,7 +1212,7 @@ private void processField(Field f, JavaClass clazz, TypeDefinition typeDefinitio name = "\"" + name + "\""; } - sbContent.append(name + ": " + getTypeScriptTypeFromJavaType(this.getFieldType(f), typeDefinition)); + sbContent.append(name + ": " + safeGetTypeScriptType(this.getFieldType(f), typeDefinition)); if (f.getConstantValue() != null) { sbContent.appendln( "; // " + f.getConstantValue()); } else { @@ -1073,7 +1238,27 @@ private boolean isPrimitiveTSType(String tsType) { } } + /** + * The core conversion: produces balanced TypeScript type strings (including nested generics). + * Also synthesizes "" for raw generic usages if we know class arity. + */ private String getTypeScriptTypeFromJavaType(Type type, TypeDefinition typeDefinition) { + + // early check for object types and ignored namespaces: + if (type instanceof ObjectType) { + String className = ((ObjectType) type).getClassName().replace('$', '.'); + // compute namespaceOnly = everything except the simple class name + String[] parts = className.split("\\."); + if (parts.length > 1) { + String namespaceOnly = String.join(".", Arrays.copyOf(parts, parts.length - 1)); + for (String ignored : getIgnoredNamespaces()) { + if (namespaceOnly.equals(ignored) || namespaceOnly.startsWith(ignored + ".")) { + return "any"; // short-circuit + } + } + } + } + String tsType; String typeSig = type.getSignature(); @@ -1133,25 +1318,31 @@ private void convertToTypeScriptType(Type type, TypeDefinition typeDefinition, S } else if (isArray) { tsType.append("androidNative.Array<"); Type elementType = ((ArrayType) type).getElementType(); - useAnyInsteadOfJavaLangObject(elementType, typeDefinition, tsType); + StringBuilder elemSb = new StringBuilder(); + convertToTypeScriptType(elementType, typeDefinition, elemSb); + tsType.append(elemSb.toString()); tsType.append(">"); + System.out.println(String.format("androidNative.Array. %s", tsType)); } else if (type.equals(Type.STRING)) { tsType.append("string"); } else if (isObjectType) { + // Generic variable handling (type variables like T) if (isGenericObjectType) { GenericObjectType genericObjectType = (GenericObjectType) type; String genericVariable = genericObjectType.getVariable(); - if (genericVariable != null && isWordPattern.matcher(genericVariable).matches()) { + final String genericVarSanitized = (genericVariable == null) ? null : genericVariable.replaceAll("[<>]", ""); + if (genericVarSanitized != null && isWordPattern.matcher(genericVarSanitized).matches()) { if (typeDefinition != null && typeDefinition.getGenericDefinitions() != null && typeDefinition.getGenericDefinitions().stream() - .filter(definition -> definition.getLabel().equals(genericVariable)).count() > 0 + .filter(definition -> definition.getLabel().equals(genericVarSanitized)).count() > 0 && ((ObjectType) typeDefinition.getParent()).getClassName().equals(DtsApi.JavaLangObject)) { - tsType.append(genericObjectType.getVariable()); + tsType.append(genericObjectType.getVariable().replaceAll("[<>]", "")); addReference(type); return; } } } + ObjectType objType = (ObjectType) type; String typeName = objType.getClassName(); if (typeName.contains("$")) { @@ -1162,22 +1353,51 @@ private void convertToTypeScriptType(Type type, TypeDefinition typeDefinition, S typeName = this.typeOverrides.get(typeName); } + String emittedName; if (!typeBelongsInCurrentTopLevelNamespace(typeName) && !typeName.startsWith("java.util.function.") && !isPrivateGoogleApiClass(typeName)) { - tsType.append(getAliasedClassName(typeName)); + emittedName = getAliasedClassName(typeName); } else { - tsType.append(typeName); + emittedName = typeName; } + tsType.append(emittedName); + // Render explicit parameters if present. If none but we have arity info -> synthesize any args. if (type instanceof GenericObjectType) { GenericObjectType genericType = (GenericObjectType) type; if (genericType.getNumParameters() > 0) { - tsType.append("<"); + List paramStrings = new ArrayList<>(); for (ReferenceType refType : genericType.getParameters()) { - useAnyInsteadOfJavaLangObject(refType, typeDefinition, tsType); - tsType.append(','); + StringBuilder paramSb = new StringBuilder(); + convertToTypeScriptType(refType, typeDefinition, paramSb); + paramStrings.add(paramSb.toString()); + } + tsType.append(formatGenericArgs(paramStrings)); + } else { + int arity = getGenericArityForClassName(typeName); + if (arity == 0) { + String globalAliased = getGlobalAliasedClassName(typeName); + if (!globalAliased.equals(typeName)) { + arity = getGenericArityForClassName(globalAliased); + } + } + if (arity > 0) { + List anyArgs = new ArrayList<>(); + for (int i = 0; i < arity; i++) anyArgs.add("any"); + tsType.append(formatGenericArgs(anyArgs)); + } + } + } else { + int arity = getGenericArityForClassName(typeName); + if (arity == 0) { + String globalAliased = getGlobalAliasedClassName(typeName); + if (!globalAliased.equals(typeName)) { + arity = getGenericArityForClassName(globalAliased); } - tsType.deleteCharAt(tsType.lastIndexOf(",")); - tsType.append(">"); + } + if (arity > 0) { + List anyArgs = new ArrayList<>(); + for (int i = 0; i < arity; i++) anyArgs.add("any"); + tsType.append(formatGenericArgs(anyArgs)); } } @@ -1187,17 +1407,6 @@ private void convertToTypeScriptType(Type type, TypeDefinition typeDefinition, S } } - private void useAnyInsteadOfJavaLangObject(Type refType, TypeDefinition typeDefinition, StringBuilder tsType) { -// if (refType instanceof ObjectType) { -// ObjectType currentType = (ObjectType)refType; -// if (currentType.getClassName().equals(DtsApi.JavaLangObject)) { -// tsType.append("any"); -// return; -// } -// } - this.convertToTypeScriptType(refType, typeDefinition, tsType); - } - private void addReference(Type type) { boolean isObjectType = type instanceof ObjectType; if (isObjectType) { @@ -1268,11 +1477,6 @@ private String getTypeSuffix(String fullClassName, TypeDefinition typeDefinition generics.add(new Tuple<>(fullClassName.replace("$", "."), genericDefinitions.size())); for (TypeDefinition.GenericDefinition definition : genericDefinitions) { - ObjectType genericObjectType = (ObjectType) definition.getType(); - String baseClassName = getAliasedClassName(genericObjectType.getClassName()); - String resultType = definition.getType().toString(); - String typeToExtend = resultType.replace(genericObjectType.getClassName(), baseClassName); - //parts.add(String.format("%s extends %s", definition.getLabel(), typeToExtend)); parts.add(definition.getLabel()); } return "<" + String.join(", ", parts) + "> "; @@ -1417,4 +1621,131 @@ private boolean isIgnoredNamespace() { } return false; } -} + + // ---------- Helpers ---------- + private static String formatGenericArgs(List args) { + if (args == null || args.isEmpty()) { + return ""; + } + StringBuilder sb = new StringBuilder(); + sb.append("<"); + for (int i = 0; i < args.size(); i++) { + if (i > 0) sb.append(","); + sb.append(args.get(i)); + } + sb.append(">"); + return sb.toString(); + } + + private static String shortHash(String s) { + int h = 0x811c9dc5; + for (int i = 0; i < s.length(); i++) { + h ^= s.charAt(i); + h *= 16777619; + } + String hex = Integer.toHexString(h); + if (hex.length() <= 3) return hex; + return hex.substring(hex.length() - 3); + } + + private static String sanitizeIdentifier(String orig, Set existing, int maxLen) { + if (orig == null) orig = ""; + String s = orig.replaceAll("[<>]", ""); + s = s.replaceAll("[^A-Za-z0-9_]", "_"); + s = s.replaceAll("_+", "_"); + s = s.replaceAll("^_+|_+$", ""); + if (s.isEmpty()) { + s = "_param"; + } + if (s.matches("^[0-9].*")) { + s = "_" + s; + } + if (s.length() > maxLen) { + String hash = shortHash(orig); + int baseLen = Math.max(1, maxLen - 4); + String base = s.substring(0, baseLen); + s = base + "_" + hash; + } + if (existing != null) { + String candidate = s; + int i = 1; + while (existing.contains(candidate)) { + String suffix = "_" + i; + int allowedBase = Math.max(1, maxLen - suffix.length()); + String base = s.length() > allowedBase ? s.substring(0, allowedBase) : s; + candidate = base + suffix; + i++; + } + s = candidate; + existing.add(s); + } + return s; + } + + private static int getGenericArityForClassName(String className) { + for (Tuple g : generics) { + if (g.x.equals(className)) { + return g.y; + } + } + for (Tuple g : externalGenerics) { + if (g.x.equals(className)) { + return g.y; + } + } + return 0; + } + + + /** + * Balance a single type string by appending missing '>' characters. + * This operates only on the single type string (parameter or return). + */ + private static String balanceAngleBracketsForType(String typeText) { + if (typeText == null || typeText.isEmpty()) return typeText; + int opens = 0; + for (int i = 0; i < typeText.length(); i++) { + char c = typeText.charAt(i); + if (c == '<') opens++; + else if (c == '>') { + opens--; + } + } + if (opens > 0) { + System.out.println(String.format("Found unbalanced. %s", typeText)); + StringBuilder sb = new StringBuilder(typeText); + for (int j = 0; j < opens; j++) sb.append('>'); + return sb.toString(); + } else if (opens < 0) { + StringBuilder sb = new StringBuilder(typeText); + for (int j = 0; j < Math.abs(opens); j++) sb.append('<'); + return sb.toString(); + } + return typeText; + } + + /** + * Wrapper that produces a TypeScript type string for a Java Type and ensures + * the single type token is balanced (each '<' has a matching '>' appended if missing). + * Use this whenever emitting a parameter type, return type or field type. + */ + private String safeGetTypeScriptType(Type type, TypeDefinition typeDefinition) { + String raw = getTypeScriptTypeFromJavaType(type, typeDefinition); + return balanceAngleBracketsForType(raw); + } + + private static String balanceUnclosedBraces(String content) { + long open = content.chars().filter(ch -> ch == '{').count(); + long close = content.chars().filter(ch -> ch == '}').count(); + long diff = open - close; + if (diff > 0) { + StringBuilder sb = new StringBuilder(content); + sb.append("\n"); + for (long i = 0; i < diff; i++) { + sb.append("}\n"); + } + return sb.toString(); + } + return content; + } +} \ No newline at end of file From 888d56a2414208366dda8decd9ce643d67a225ed Mon Sep 17 00:00:00 2001 From: Martin Guillon Date: Sat, 25 Oct 2025 22:03:27 +0200 Subject: [PATCH 15/17] chore: cleanup --- .../src/main/java/com/telerik/dts/DtsApi.java | 65 ++++--------------- 1 file changed, 11 insertions(+), 54 deletions(-) diff --git a/dts-generator/src/main/java/com/telerik/dts/DtsApi.java b/dts-generator/src/main/java/com/telerik/dts/DtsApi.java index 19ecf9f..f3c6a3a 100644 --- a/dts-generator/src/main/java/com/telerik/dts/DtsApi.java +++ b/dts-generator/src/main/java/com/telerik/dts/DtsApi.java @@ -46,6 +46,9 @@ import java.util.Deque; import java.util.ArrayDeque; +/** + * Created by plamen5kov on 6/17/16. + */ public class DtsApi { public static List> externalGenerics = new ArrayList<>(); public static List> generics = new ArrayList<>(); @@ -288,13 +291,7 @@ public String generateDtsContent(List javaClasses) { Arrays.sort(refs); } - String before = sbContent.toString(); - String content = replaceIgnoredNamespaces(before); - if (!before.equals(content)) { - System.out.println("replaceIgnoredNamespaces changed content. (length before=" + before + " after=" + content + ")"); - // optionally write both to disk / temp files for diffing - } - // String content = replaceIgnoredNamespaces(sbContent.toString()); + String content = replaceIgnoredNamespaces(sbContent.toString()); // Ensure braces are balanced (only append missing closing braces) content = balanceUnclosedBraces(content); @@ -468,53 +465,6 @@ public static void loadGenerics(File inputFile) throws Exception { } } - /** - * Compatibility API: limited/safe replaceGenericsInText used by other steps/tools. - * Only appends "" for classes we know are generic (from generics + externalGenerics). - */ - public static String replaceGenericsInText(String content) { - String any = "any"; - String result = content; - - List> allGenerics = Stream.concat(generics.stream(), externalGenerics.stream()).collect(Collectors.toList()); - - for (Tuple generic : allGenerics) { - result = replaceNonGenericUsage(result, generic.x, generic.y, any); - String globalAliasedClassName = getGlobalAliasedClassName(generic.x); - if (!generic.x.equals(globalAliasedClassName)) { - result = replaceNonGenericUsage(result, globalAliasedClassName, generic.y, any); - } - } - - return result; - } - - private static String replaceNonGenericUsage(String content, String className, Integer occurencies, String javalangObject) { - // AppendReplacement-based approach to avoid regex replacement pitfalls. - Pattern usedAsNonGenericPattern = Pattern.compile(className.replace(".", "\\.") + "(?[^a-zA-Z\\d\\.\\$\\<])"); - Matcher matcher = usedAsNonGenericPattern.matcher(content); - - if (!matcher.find()) - return content; - - List arguments = new ArrayList<>(); - for (int i = 0; i < occurencies; i++) { - arguments.add(javalangObject); - } - String classSuffix = "<" + String.join(",", arguments) + ">"; - - matcher.reset(); - StringBuffer sb = new StringBuffer(); - while (matcher.find()) { - String suffix = matcher.group("Suffix"); - String replacement = className + classSuffix + suffix; - replacement = Matcher.quoteReplacement(replacement); - matcher.appendReplacement(sb, replacement); - } - matcher.appendTail(sb); - return sb.toString(); - } - private String getExtendsLine(JavaClass currClass, TypeDefinition typeDefinition) { String override = this.extendsOverrides.get(currClass.getClassName()); if (override != null) { @@ -817,6 +767,7 @@ private List getInterfaces(JavaClass classInterface) { for (String intface : interfaceNames) { JavaClass clazz1 = ClassRepo.findClass(intface); + // Added guard to prevent NullPointerExceptions in case libs are not provided - the dev can choose to include it and rerun the generator if (clazz1 == null) { if (!warnedMissing.contains(intface)) { warnedMissing.add(intface); @@ -827,6 +778,7 @@ private List getInterfaces(JavaClass classInterface) { String className = clazz1.getClassName(); + // TODO: Pete: Hardcoded until we figure out how to go around the 'type incompatible with Object' issue if (className.equals("java.util.Iterator") || className.equals("android.animation.TypeEvaluator") || className.equals("java.lang.Comparable") || @@ -871,6 +823,7 @@ private void processMethod(Method method, JavaClass clazz, TypeDefinition typeDe return; } + // TODO: Pete: won't generate static initializers as invalid typescript properties if (clazz.isInterface() && name.equals("")) { return; } @@ -985,6 +938,7 @@ private static List getTypeParameters(String signature) { return types; } + // gets the full field type including generic types private Type getFieldType(Field f) { Signature signature = this.getSignature(f); if (signature != null) { @@ -1001,6 +955,7 @@ private Type getFieldType(Field f) { return f.getType(); } + // gets the full method return type including generic types private Type getReturnType(Method m) { Signature signature = this.getSignature(m); if (signature != null) { @@ -1037,11 +992,13 @@ private void loadBaseMethods(JavaClass clazz) { if (currClass != null) { + //get all base methods and method names while (true && currClass != null) { boolean isJavaLangObject = currClass.getClassName().equals(DtsApi.JavaLangObject); for (Method m : currClass.getMethods()) { if (!m.isSynthetic() && (m.isPublic() || m.isProtected())) { + // don't write empty constructor typings for java objects if (isJavaLangObject && isConstructor(m)) { continue; } From f94f3b8db6f15dd8decf1087df4b67dc3d991280 Mon Sep 17 00:00:00 2001 From: Martin Guillon Date: Sat, 25 Oct 2025 22:07:42 +0200 Subject: [PATCH 16/17] chore: fixes --- .../src/main/java/com/telerik/dts/DtsApi.java | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/dts-generator/src/main/java/com/telerik/dts/DtsApi.java b/dts-generator/src/main/java/com/telerik/dts/DtsApi.java index f3c6a3a..a6b0b2f 100644 --- a/dts-generator/src/main/java/com/telerik/dts/DtsApi.java +++ b/dts-generator/src/main/java/com/telerik/dts/DtsApi.java @@ -465,6 +465,53 @@ public static void loadGenerics(File inputFile) throws Exception { } } + /** + * Compatibility API: limited/safe replaceGenericsInText used by other steps/tools. + * Only appends "" for classes we know are generic (from generics + externalGenerics). + */ + public static String replaceGenericsInText(String content) { + String any = "any"; + String result = content; + + List> allGenerics = Stream.concat(generics.stream(), externalGenerics.stream()).collect(Collectors.toList()); + + for (Tuple generic : allGenerics) { + result = replaceNonGenericUsage(result, generic.x, generic.y, any); + String globalAliasedClassName = getGlobalAliasedClassName(generic.x); + if (!generic.x.equals(globalAliasedClassName)) { + result = replaceNonGenericUsage(result, globalAliasedClassName, generic.y, any); + } + } + + return result; + } + + private static String replaceNonGenericUsage(String content, String className, Integer occurencies, String javalangObject) { + // AppendReplacement-based approach to avoid regex replacement pitfalls. + Pattern usedAsNonGenericPattern = Pattern.compile(className.replace(".", "\\.") + "(?[^a-zA-Z\\d\\.\\$\\<])"); + Matcher matcher = usedAsNonGenericPattern.matcher(content); + + if (!matcher.find()) + return content; + + List arguments = new ArrayList<>(); + for (int i = 0; i < occurencies; i++) { + arguments.add(javalangObject); + } + String classSuffix = "<" + String.join(",", arguments) + ">"; + + matcher.reset(); + StringBuffer sb = new StringBuffer(); + while (matcher.find()) { + String suffix = matcher.group("Suffix"); + String replacement = className + classSuffix + suffix; + replacement = Matcher.quoteReplacement(replacement); + matcher.appendReplacement(sb, replacement); + } + matcher.appendTail(sb); + return sb.toString(); + } + private String getExtendsLine(JavaClass currClass, TypeDefinition typeDefinition) { String override = this.extendsOverrides.get(currClass.getClassName()); if (override != null) { @@ -1279,7 +1326,6 @@ private void convertToTypeScriptType(Type type, TypeDefinition typeDefinition, S convertToTypeScriptType(elementType, typeDefinition, elemSb); tsType.append(elemSb.toString()); tsType.append(">"); - System.out.println(String.format("androidNative.Array. %s", tsType)); } else if (type.equals(Type.STRING)) { tsType.append("string"); } else if (isObjectType) { From 6635b48e217213e2acb80e6391d07d59ec9a17cf Mon Sep 17 00:00:00 2001 From: Martin Guillon Date: Sat, 25 Oct 2025 22:26:48 +0200 Subject: [PATCH 17/17] fix: constant values real value is now exported as jsdoc --- .../src/main/java/com/telerik/dts/DtsApi.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dts-generator/src/main/java/com/telerik/dts/DtsApi.java b/dts-generator/src/main/java/com/telerik/dts/DtsApi.java index a6b0b2f..39ab0ac 100644 --- a/dts-generator/src/main/java/com/telerik/dts/DtsApi.java +++ b/dts-generator/src/main/java/com/telerik/dts/DtsApi.java @@ -1207,22 +1207,22 @@ private void processField(Field f, JavaClass clazz, TypeDefinition typeDefinitio } String tabs = getTabs(this.indent + 1); + + if (f.getConstantValue() != null) { + sbContent.appendln( tabs + "/**"); + sbContent.appendln( tabs + "* " + f.getConstantValue()); + sbContent.appendln( tabs + "*/"); + } sbContent.append(tabs + "public "); if (f.isStatic()) { sbContent.append("static "); } - + if (!jsFieldPattern.matcher(name).matches()) { name = "\"" + name + "\""; } + sbContent.appendln(name + ": " + safeGetTypeScriptType(this.getFieldType(f), typeDefinition) + ";"); - sbContent.append(name + ": " + safeGetTypeScriptType(this.getFieldType(f), typeDefinition)); - if (f.getConstantValue() != null) { - sbContent.appendln( "; // " + f.getConstantValue()); - } else { - sbContent.appendln(";"); - - } } private void addClassField(JavaClass clazz) {