diff --git a/README.md b/README.md index a53af59..adca7ea 100644 --- a/README.md +++ b/README.md @@ -71,10 +71,14 @@ Every donation, no matter the size, makes a difference and is deeply appreciated ## Changelog +### Version 1.9.4 + +1. Adding repair using AI(You can disable it in settings) + ### Version 1.9.3 1. Improved the Analytics by adding session -2. Imorived dart code generation +2. Improved dart code generation 3. Tracking JSON when repair failed(This will be removed in next versions) ### Version 1.9.2 diff --git a/gradle.properties b/gradle.properties index c65cf01..e689d3d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ pluginGroup=com.godwin.jsonparser pluginName=Json Parser # SemVer format -> https://semver.org -pluginVersion=1.9.3 +pluginVersion=1.9.4 # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html pluginSinceBuild=241 pluginUntilBuild=261.* diff --git a/src/main/kotlin/com/godwin/jsonparser/services/JsonPersistence.kt b/src/main/kotlin/com/godwin/jsonparser/services/JsonPersistence.kt index 58729ed..82d34d0 100644 --- a/src/main/kotlin/com/godwin/jsonparser/services/JsonPersistence.kt +++ b/src/main/kotlin/com/godwin/jsonparser/services/JsonPersistence.kt @@ -21,6 +21,8 @@ class JsonPersistence : PersistentStateComponent { var starClicked: Int = 0 var analyticsEnabled: Boolean = true var analyticsErrorJsonEnabled: Boolean = true + var repairUsingAi: Boolean = true + override fun getState(): JsonPersistence { return this } diff --git a/src/main/kotlin/com/godwin/jsonparser/ui/ParserWidget.kt b/src/main/kotlin/com/godwin/jsonparser/ui/ParserWidget.kt index d9773e1..80287be 100644 --- a/src/main/kotlin/com/godwin/jsonparser/ui/ParserWidget.kt +++ b/src/main/kotlin/com/godwin/jsonparser/ui/ParserWidget.kt @@ -4,6 +4,7 @@ import com.godwin.jsonparser.generator.common.ui.* import com.godwin.jsonparser.generator.common.util.FileGenerationUtil import com.godwin.jsonparser.rx.Publisher import com.godwin.jsonparser.rx.Subscriber +import com.godwin.jsonparser.ui.components.CircularProgress import com.godwin.jsonparser.ui.dialog.OptionDialog import com.godwin.jsonparser.util.JsonDownloader import com.godwin.jsonparser.util.JsonUtils @@ -44,6 +45,7 @@ class ParserWidget( private val inputEditor: Editor = createEditor() private val bodyWidget = ParserBodyWidget(project) private var repairButton: JButton? = null + private var repairLoadingIndicator: CircularProgress? = null private var generateCodeButton: JButton? = null private var jsonException: Exception? = null val container: JPanel = jBorderLayout { @@ -80,6 +82,17 @@ class ParserWidget( addActionListener { handleRepair() } } add(repairButton) + repairLoadingIndicator = CircularProgress().apply { + isVisible = false + preferredSize = java.awt.Dimension(18, 18) + toolTipText = "Repairing JSON..." + } + add(repairLoadingIndicator) + fillSpace() + jButton("Options", { handleOptions() }) + } + jHorizontalLinearLayout { + jButton("Support ❤️", { handleDonate() }) fillSpace() if (isAndroidStudioOrIntelliJ()) { generateCodeButton = JButton("Generate Code").apply { @@ -88,11 +101,6 @@ class ParserWidget( } add(generateCodeButton) } - jButton("Options", { handleOptions() }) - } - jHorizontalLinearLayout { - jButton("Support ❤️", { handleDonate() }) - fillSpace() } } } @@ -165,7 +173,6 @@ class ParserWidget( val jsonString = JsonUtils.cleanUpJsonString(inputEditor.document.text) try { showBody(jsonString) - showHideGenerateCodeButton(true) NotificationUtil.showDonateNotification() } catch (_: Exception) { showHideGenerateCodeButton(false) @@ -225,6 +232,7 @@ class ParserWidget( showHideGenerateCodeButton(false) AnalyticsService.track(AnalyticsConstant.PARSE_FAILED) }) + showHideGenerateCodeButton(true) bodyWidget.showRaw(jsonString) bodyWidget.showTree(jsonString) } @@ -232,21 +240,44 @@ class ParserWidget( private fun handleRepair() { AnalyticsService.track(AnalyticsConstant.ACTION_REPAIR) val input = inputEditor.document.text - try { - val repairedJson = JsonRepairEngine.repair(project, input) - - showHideRepairButton(false) -// showDiff(input, repairedJson) - if (repairedJson == null) { - AnalyticsService.track(AnalyticsConstant.REPAIR_FAILED, jsonData = input) - NotificationUtil.showJsonRepairFailed() - return + + setRepairLoading(true) + + JsonRepairEngine.repair(input).thenAccept { repairedJson -> + SwingUtilities.invokeLater { + setRepairLoading(false) + if (repairedJson == null) { + AnalyticsService.track(AnalyticsConstant.REPAIR_FAILED, jsonData = input) + NotificationUtil.showJsonRepairFailed() + return@invokeLater + } + showHideRepairButton(false) + showMergeRepair(input, repairedJson) + handleParse() + } + }.exceptionally { e -> + SwingUtilities.invokeLater { + setRepairLoading(false) + Messages.showErrorDialog(project, "Failed to repair JSON: ${e.message}", "Repair Failed") + AnalyticsService.track( + AnalyticsConstant.REPAIR_FAILED, jsonData = input, additionaldata = e.message + ) + } + null + } + } + + private fun setRepairLoading(loading: Boolean) { + SwingUtilities.invokeLater { + repairButton?.isVisible = !loading + repairLoadingIndicator?.isVisible = loading + repairLoadingIndicator?.parent?.revalidate() + repairLoadingIndicator?.parent?.repaint() + if (loading) { + repairLoadingIndicator?.start() + } else { + repairLoadingIndicator?.stop() } - showMergeRepair(input, repairedJson) - handleParse() - } catch (e: Exception) { - Messages.showErrorDialog(project, "Failed to repair JSON: ${e.message}", "Repair Failed") - AnalyticsService.track(AnalyticsConstant.REPAIR_FAILED, jsonData = input) } } @@ -315,7 +346,16 @@ class ParserWidget( private fun actionChooseFile() { AnalyticsService.track(AnalyticsConstant.ACTION_LOAD_FILE) FileChooser.chooseFile( - FileChooserDescriptor(true, false, false, false, false, false), project, null + FileChooserDescriptor( + true, + false, + false, + false, + false, + false + ), + project, + null ) { file -> try { val content = String(file.contentsToByteArray()).replace("\r\n", "\n") diff --git a/src/main/kotlin/com/godwin/jsonparser/ui/components/CircularProgress.kt b/src/main/kotlin/com/godwin/jsonparser/ui/components/CircularProgress.kt new file mode 100644 index 0000000..66a7c19 --- /dev/null +++ b/src/main/kotlin/com/godwin/jsonparser/ui/components/CircularProgress.kt @@ -0,0 +1,84 @@ +package com.godwin.jsonparser.ui.components + +import java.awt.* +import java.awt.geom.Arc2D +import javax.swing.JComponent +import javax.swing.Timer + +class CircularProgress( + private val strokeWidth: Int = 2, + private val trackColor: Color = Color(220, 220, 220), + private val progressColor: Color = Color(30, 144, 255), + private val cometTail: Boolean = true +) : JComponent() { + + private var angle = 0 + private val timer: Timer + + init { + preferredSize = Dimension(100, 100) + + // Rotate 10° every 20ms → smooth spin + timer = Timer(20) { + angle = (angle - 10) % 360 + repaint() + } + timer.start() + } + + fun stop() = timer.stop() + fun start() { + if (!timer.isRunning) + timer.start() + } + + override fun paintComponent(g: Graphics) { + super.paintComponent(g) + val g2 = g.create() as Graphics2D + + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) + + val size = minOf(width, height) - strokeWidth * 2 + val x = (width - size) / 2 + val y = (height - size) / 2 + + // Background track + g2.stroke = BasicStroke(strokeWidth.toFloat(), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND) + g2.color = trackColor + g2.drawOval(x, y, size, size) + + if (cometTail) { + // Comet-tail effect: multiple arcs with increasing opacity + val arcLength = 90 + val segments = 10 + + for (i in 0 until segments) { + val alpha = ((i + 1).toFloat() / segments * 255).toInt() + g2.color = Color( + progressColor.red, + progressColor.green, + progressColor.blue, + alpha + ) + val segAngle = angle + (i * arcLength / segments) + g2.draw( + Arc2D.Float( + x.toFloat(), y.toFloat(), size.toFloat(), size.toFloat(), + segAngle.toFloat(), (arcLength / segments).toFloat(), Arc2D.OPEN + ) + ) + } + } else { + // Simple solid spinning arc + g2.color = progressColor + g2.draw( + Arc2D.Float( + x.toFloat(), y.toFloat(), size.toFloat(), size.toFloat(), + angle.toFloat(), 90f, Arc2D.OPEN + ) + ) + } + + g2.dispose() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/godwin/jsonparser/ui/settings/JsonParserSettingsConfigurable.kt b/src/main/kotlin/com/godwin/jsonparser/ui/settings/JsonParserSettingsConfigurable.kt index 6dd83ce..fcb4d63 100644 --- a/src/main/kotlin/com/godwin/jsonparser/ui/settings/JsonParserSettingsConfigurable.kt +++ b/src/main/kotlin/com/godwin/jsonparser/ui/settings/JsonParserSettingsConfigurable.kt @@ -3,7 +3,6 @@ package com.godwin.jsonparser.ui.settings import com.godwin.jsonparser.services.JsonPersistence import com.intellij.openapi.options.Configurable import com.intellij.ui.components.JBLabel -import java.awt.BorderLayout import javax.swing.BoxLayout import javax.swing.JCheckBox import javax.swing.JComponent @@ -12,6 +11,7 @@ import javax.swing.JPanel class JsonParserSettingsConfigurable : Configurable { private var analyticsCheckBox: JCheckBox? = null private var errorJsonTracking: JCheckBox? = null + private var aiRepairEnabled: JCheckBox? = null override fun getDisplayName() = "Json Parser" @@ -24,26 +24,38 @@ class JsonParserSettingsConfigurable : Configurable { "Enable logging of json string when repair doesn't worked", JsonPersistence.getInstance().analyticsErrorJsonEnabled ) + aiRepairEnabled = JCheckBox( + "Enable fixing your json using AI (We are not storing any of your json anywhere)", + JsonPersistence.getInstance().repairUsingAi + ) + + val noteLabel = + JBLabel("Note: Your string will be logged when repair doesn't worked. You can disabled this anytime.") - val noteLabel = JBLabel("Note: Your string will be logged when repair doesn't worked. You can disabled this anytime.") - return JPanel().apply { layout = BoxLayout(this, BoxLayout.Y_AXIS) add(analyticsCheckBox) add(errorJsonTracking) + add(aiRepairEnabled) add(noteLabel) } } override fun isModified(): Boolean { return analyticsCheckBox?.isSelected != JsonPersistence.getInstance().analyticsEnabled + || errorJsonTracking?.isSelected != JsonPersistence.getInstance().analyticsErrorJsonEnabled + || aiRepairEnabled?.isSelected != JsonPersistence.getInstance().repairUsingAi } override fun apply() { JsonPersistence.getInstance().analyticsEnabled = analyticsCheckBox?.isSelected ?: true + JsonPersistence.getInstance().analyticsErrorJsonEnabled = errorJsonTracking?.isSelected ?: true + JsonPersistence.getInstance().repairUsingAi = aiRepairEnabled?.isSelected ?: true } override fun reset() { analyticsCheckBox?.isSelected = JsonPersistence.getInstance().analyticsEnabled + errorJsonTracking?.isSelected = JsonPersistence.getInstance().analyticsErrorJsonEnabled + aiRepairEnabled?.isSelected = JsonPersistence.getInstance().repairUsingAi } } \ No newline at end of file diff --git a/src/main/kotlin/com/godwin/jsonparser/util/JsonUtils.kt b/src/main/kotlin/com/godwin/jsonparser/util/JsonUtils.kt index 6052c6f..a23fbe8 100644 --- a/src/main/kotlin/com/godwin/jsonparser/util/JsonUtils.kt +++ b/src/main/kotlin/com/godwin/jsonparser/util/JsonUtils.kt @@ -1,6 +1,7 @@ package com.godwin.jsonparser.util import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.core.JsonProcessingException import com.fasterxml.jackson.core.util.DefaultIndenter import com.fasterxml.jackson.core.util.DefaultPrettyPrinter import com.fasterxml.jackson.databind.DeserializationFeature @@ -30,6 +31,20 @@ object JsonUtils { } } + fun isValidJsonError(jsonStr: String): String? { + return try { + ObjectMapper().readTree(jsonStr) + null + } catch (e: Exception) { + return if (e is JsonProcessingException) { + e.originalMessage + } else { + e.message + } + } + } + + fun getMap(jsonStr: String): Any { val node = mapper.readTree(jsonStr) diff --git a/src/main/kotlin/com/godwin/jsonparser/util/analytics/Analytics.kt b/src/main/kotlin/com/godwin/jsonparser/util/analytics/Analytics.kt deleted file mode 100644 index 67ca293..0000000 --- a/src/main/kotlin/com/godwin/jsonparser/util/analytics/Analytics.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.godwin.jsonparser.util.analytics - -import com.godwin.jsonparser.services.JsonPersistence -import com.intellij.openapi.application.ApplicationInfo -import com.intellij.openapi.application.PermanentInstallationID -import java.net.URI -import java.net.http.HttpClient -import java.net.http.HttpRequest -import java.net.http.HttpResponse - -object Analytics { - private const val WORKER_URL = "https://jsonparser.godwinjoseph-k.workers.dev/" - - private val client = HttpClient.newBuilder() - .followRedirects(HttpClient.Redirect.NORMAL) - .build() - - fun track(eventName: String) { - if (!JsonPersistence.getInstance().analyticsEnabled) return - // Get the unique IntelliJ ID for this installation (Anonymized) - val userId = PermanentInstallationID.get() - val ideVersion = ApplicationInfo.getInstance().versionName - - // Create the JSON payload - val json = """ - { - "event_name": "$eventName", - "ide_version": "$ideVersion", - "user_id": "$userId" - } - """.trimIndent() - - val request = HttpRequest.newBuilder() - .uri(URI.create(WORKER_URL)) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(json)) - .build() - - // Send asynchronously to avoid blocking the UI thread - client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenAccept { response -> - if (response.statusCode() != 202) { - println("Analytics Error: ${response.statusCode()}") - } - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/godwin/jsonparser/util/repair/JsonRepairEngine.kt b/src/main/kotlin/com/godwin/jsonparser/util/repair/JsonRepairEngine.kt index 643be6c..40cd5fc 100644 --- a/src/main/kotlin/com/godwin/jsonparser/util/repair/JsonRepairEngine.kt +++ b/src/main/kotlin/com/godwin/jsonparser/util/repair/JsonRepairEngine.kt @@ -3,32 +3,40 @@ package com.godwin.jsonparser.util.repair import com.godwin.jsonparser.common.exception.InputTooLargeException import com.godwin.jsonparser.util.JsonUtils import com.godwin.jsonparser.util.Log +import com.godwin.jsonparser.util.repair.strategy.AiRepairStrategy import com.godwin.jsonparser.util.repair.strategy.JsonAutoRepairStrategy -import com.godwin.jsonparser.util.repair.strategy.PsiRepairStrategy -import com.intellij.openapi.project.Project +import com.godwin.jsonparser.util.repair.strategy.RepairStrategy +import java.util.concurrent.CompletableFuture object JsonRepairEngine { - private val strategies = listOf( - JsonAutoRepairStrategy(), - PsiRepairStrategy() - ) - - fun repair(project: Project, input: String): String? { + fun repair( + input: String, strategies: List = listOf( + JsonAutoRepairStrategy(), + AiRepairStrategy() + ) + ): CompletableFuture { if (input.length > 1_000_000) { - throw InputTooLargeException("Input is too large") + return CompletableFuture.failedFuture(InputTooLargeException("Input is too large")) } - var text = input - strategies.forEach { strategy -> - try { - text = strategy.repair(project, text) - } catch (e: Exception) { - Log.e("${strategy.javaClass} Failed: ${e.message}") + // Chain strategies: run next only if current result is still invalid JSON + return strategies.fold(CompletableFuture.completedFuture(input)) { chain, strategy -> + chain.thenCompose { current -> + val error = JsonUtils.isValidJsonError(current); + if (error == null) { + // Already valid — skip remaining strategies + CompletableFuture.completedFuture(current) + } else { + strategy.repair(current, error) + .exceptionally { e -> + Log.e("${strategy.javaClass.simpleName} failed: ${e.message}") + current // fall through with unchanged text on error + } + } } + }.thenApply { result -> + if (JsonUtils.isValidJson(result)) result else null } - val result = JsonUtils.isValidJson(text) - - return if (result) text else null } } diff --git a/src/main/kotlin/com/godwin/jsonparser/util/repair/strategy/AiRepairStrategy.kt b/src/main/kotlin/com/godwin/jsonparser/util/repair/strategy/AiRepairStrategy.kt new file mode 100644 index 0000000..37a0f12 --- /dev/null +++ b/src/main/kotlin/com/godwin/jsonparser/util/repair/strategy/AiRepairStrategy.kt @@ -0,0 +1,65 @@ +package com.godwin.jsonparser.util.repair.strategy + +import com.godwin.jsonparser.services.JsonPersistence +import java.net.URI +import java.net.http.HttpClient +import java.net.http.HttpRequest +import java.net.http.HttpResponse +import java.time.Duration +import java.util.concurrent.CompletableFuture + +class AiRepairStrategy : RepairStrategy() { + private val workerUrl = "https://jsonrepair.godwinjoseph-k.workers.dev/" + + override fun repair(input: String, error: String?): CompletableFuture { + if (!JsonPersistence.getInstance().repairUsingAi) { + return CompletableFuture.completedFuture(input) + } + return sendRequest(input, error) + } + + private fun sendRequest(input: String, error: String?): CompletableFuture { + val client = HttpClient.newBuilder() + .followRedirects(HttpClient.Redirect.NORMAL) + .connectTimeout(Duration.ofSeconds(5)) + .build() + + val requestBody = com.google.gson.JsonObject().apply { + addProperty("json", input) + addProperty("error", error ?: "") + }.toString() + + val request = HttpRequest.newBuilder() + .uri(URI.create(workerUrl)) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(requestBody)) + .build() + + val result = CompletableFuture() + client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .whenComplete { response, error -> + when { + error != null -> result.complete(input) + response.statusCode() == 200 -> { + // Response body: {"success": true, "repaired": "..."} + val repaired = extractRepaired(response.body()) ?: input + result.complete(repaired) + } + + else -> result.complete(input) + } + } + return result + } + + private fun extractRepaired(body: String): String? { + return try { + com.google.gson.JsonParser.parseString(body) + .asJsonObject + .get("repaired") + ?.asString + } catch (e: Exception) { + null + } + } +} diff --git a/src/main/kotlin/com/godwin/jsonparser/util/repair/strategy/JsonAutoRepairStrategy.kt b/src/main/kotlin/com/godwin/jsonparser/util/repair/strategy/JsonAutoRepairStrategy.kt index 5cccd08..e6c54cd 100644 --- a/src/main/kotlin/com/godwin/jsonparser/util/repair/strategy/JsonAutoRepairStrategy.kt +++ b/src/main/kotlin/com/godwin/jsonparser/util/repair/strategy/JsonAutoRepairStrategy.kt @@ -1,14 +1,16 @@ package com.godwin.jsonparser.util.repair.strategy import com.godwin.jsonautorepair.JsonAutoRepair -import com.intellij.openapi.project.Project +import java.util.concurrent.CompletableFuture class JsonAutoRepairStrategy : RepairStrategy() { private companion object { const val MAX_ITERATIONS = 5 } - override fun repair(project: Project, input: String): String { - return JsonAutoRepair(MAX_ITERATIONS).repair(input).output + override fun repair(input: String, error: String?): CompletableFuture { + return CompletableFuture.supplyAsync { + JsonAutoRepair(MAX_ITERATIONS).repair(input).output + } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/godwin/jsonparser/util/repair/strategy/PsiRepairStrategy.kt b/src/main/kotlin/com/godwin/jsonparser/util/repair/strategy/PsiRepairStrategy.kt deleted file mode 100644 index 6607f22..0000000 --- a/src/main/kotlin/com/godwin/jsonparser/util/repair/strategy/PsiRepairStrategy.kt +++ /dev/null @@ -1,164 +0,0 @@ -package com.godwin.jsonparser.util.repair.strategy - -import com.intellij.json.JsonFileType -import com.intellij.openapi.command.WriteCommandAction -import com.intellij.openapi.editor.Document -import com.intellij.openapi.project.Project -import com.intellij.psi.* -import com.intellij.psi.codeStyle.CodeStyleManager -import com.intellij.testFramework.LightVirtualFile - -class PsiRepairStrategy : RepairStrategy() { - - private companion object { - const val MAX_ITERATIONS = 5 - } - - override fun repair(project: Project, input: String): String { - var text = input.trim() - text = preProcessJson(text) - - val virtualFile = LightVirtualFile("repair.json", JsonFileType.INSTANCE, text) - val psiFile = PsiManager.getInstance(project).findFile(virtualFile) ?: return text - val document = PsiDocumentManager.getInstance(project).getDocument(psiFile) ?: return text - - var iteration = 0 - var previousErrorCount = Int.MAX_VALUE - - while (iteration < MAX_ITERATIONS) { - PsiDocumentManager.getInstance(project).commitDocument(document) - - val errors = collectErrors(psiFile) - if (errors.isEmpty()) break - if (errors.size >= previousErrorCount) break - - previousErrorCount = errors.size - - WriteCommandAction.runWriteCommandAction(project) { - errors.sortedByDescending { it.textRange.startOffset }.forEach { error -> - fixError(error, document) - } - } - - iteration++ - } - - PsiDocumentManager.getInstance(project).commitDocument(document) - - WriteCommandAction.runWriteCommandAction(project) { - CodeStyleManager.getInstance(project).reformat(psiFile) - } - - return document.text - } - - private fun preProcessJson(text: String): String { - var result = text - result = result.replace("'", "\"") - result = result.replace("True", "true") - result = result.replace("False", "false") - result = result.replace("NULL", "null") - result = result.replace("None", "null") - result = Regex("//.*?$", RegexOption.MULTILINE).replace(result, "") - result = Regex("/\\*.*?\\*/", RegexOption.DOT_MATCHES_ALL).replace(result, "") - result = Regex("([{,])\\s*([a-zA-Z_][a-zA-Z0-9_]*)\\s*:").replace(result, "$1\"$2\":") - return result - } - - private fun collectErrors(file: PsiFile): List { - val list = mutableListOf() - file.accept(object : PsiRecursiveElementVisitor() { - override fun visitErrorElement(element: PsiErrorElement) { - list.add(element) - } - }) - return list - } - - private fun fixError(error: PsiErrorElement, document: Document) { - val msg = error.errorDescription.lowercase() - val offset = error.textRange.startOffset - - try { - when { - msg.contains("',' expected") || msg.contains("comma expected") -> { - if (offset <= document.textLength) { - document.insertString(offset, ",") - } - } - - msg.contains("':' expected") || msg.contains("colon expected") -> { - if (offset <= document.textLength) { - document.insertString(offset, ":") - } - } - - msg.contains("trailing comma") -> { - removeTrailingComma(error, document) - } - - msg.contains("double-quoted") || msg.contains("property key") -> { - fixUnquotedKey(error, document) - } - - msg.contains("value expected") -> { - if (offset <= document.textLength) { - document.insertString(offset, "null") - } - } - - msg.contains("'}' expected") -> { - document.insertString(document.textLength, "}") - } - - msg.contains("']' expected") -> { - document.insertString(document.textLength, "]") - } - - msg.contains("invalid") || msg.contains("unexpected") -> { - if (error.textRange.endOffset <= document.textLength) { - document.deleteString(offset, error.textRange.endOffset) - } - } - } - } catch (e: Exception) { - } - } - - private fun removeTrailingComma(error: PsiErrorElement, document: Document) { - try { - var prev = error.prevSibling - while (prev != null && prev.text.isBlank()) { - prev = prev.prevSibling - } - if (prev?.text?.trim() == ",") { - val start = prev.textRange.startOffset - val end = prev.textRange.endOffset - if (end <= document.textLength) { - document.deleteString(start, end) - } - } - } catch (e: Exception) { - } - } - - private fun fixUnquotedKey(error: PsiErrorElement, document: Document) { - try { - val parent = error.parent ?: return - val text = parent.text - val colon = text.indexOf(":") - if (colon > 0) { - val key = text.substring(0, colon).trim() - if (key.isNotEmpty() && !key.startsWith("\"") && key.matches(Regex("[a-zA-Z_][a-zA-Z0-9_]*"))) { - val fixed = "\"$key\"${text.substring(colon)}" - val start = parent.textRange.startOffset - val end = parent.textRange.endOffset - if (end <= document.textLength) { - document.replaceString(start, end, fixed) - } - } - } - } catch (e: Exception) { - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/godwin/jsonparser/util/repair/strategy/RepairStrategy.kt b/src/main/kotlin/com/godwin/jsonparser/util/repair/strategy/RepairStrategy.kt index 2d057e3..ace4607 100644 --- a/src/main/kotlin/com/godwin/jsonparser/util/repair/strategy/RepairStrategy.kt +++ b/src/main/kotlin/com/godwin/jsonparser/util/repair/strategy/RepairStrategy.kt @@ -1,7 +1,7 @@ package com.godwin.jsonparser.util.repair.strategy -import com.intellij.openapi.project.Project +import java.util.concurrent.CompletableFuture abstract class RepairStrategy { - abstract fun repair(project: Project, input: String): String -} \ No newline at end of file + abstract fun repair( input: String,error: String?): CompletableFuture +} diff --git a/src/test/kotlin/com/godwin/jsonparser/util/repair/JsonRepairEngineTest.kt b/src/test/kotlin/com/godwin/jsonparser/util/repair/JsonRepairEngineTest.kt index 51fa7ba..c34b666 100644 --- a/src/test/kotlin/com/godwin/jsonparser/util/repair/JsonRepairEngineTest.kt +++ b/src/test/kotlin/com/godwin/jsonparser/util/repair/JsonRepairEngineTest.kt @@ -1,80 +1,78 @@ package com.godwin.jsonparser.util.repair +import com.godwin.jsonparser.util.repair.strategy.JsonAutoRepairStrategy import com.intellij.testFramework.fixtures.BasePlatformTestCase -import org.junit.Test class JsonRepairEngineTest : BasePlatformTestCase() { - @Test fun testSingleQuotesToDoubleQuotes() { val input = "{'name': 'John', 'age': 30}" - val result = JsonRepairEngine.repair(project, input) + val result = JsonRepairEngine.repair(input, strategies = listOf(JsonAutoRepairStrategy())).join() assertNotNull(result) assertTrue(result!!.contains("\"name\"")) assertTrue(result.contains("\"John\"")) } - @Test fun testUnquotedKeys() { val input = "{name: \"John\", age: 30}" - val result = JsonRepairEngine.repair(project, input) + val result = JsonRepairEngine.repair(input, strategies = listOf(JsonAutoRepairStrategy())).join() assertNotNull(result) assertTrue(result!!.contains("\"name\"")) assertTrue(result.contains("\"age\"")) } - @Test + fun testTrailingComma() { val input = "{\"name\": \"John\", \"age\": 30,}" - val result = JsonRepairEngine.repair(project, input) + val result = JsonRepairEngine.repair(input, strategies = listOf(JsonAutoRepairStrategy())).join() assertNotNull(result) assertFalse(result!!.contains(",}")) } - @Test + fun testBooleanCase() { val input = "{\"active\": True, \"verified\": False}" - val result = JsonRepairEngine.repair(project, input) + val result = JsonRepairEngine.repair(input, strategies = listOf(JsonAutoRepairStrategy())).join() assertNotNull(result) assertTrue(result!!.contains("true")) assertTrue(result.contains("false")) } - @Test + fun testNullCase() { val input = "{\"value\": NULL}" - val result = JsonRepairEngine.repair(project, input) + val result = JsonRepairEngine.repair(input, strategies = listOf(JsonAutoRepairStrategy())).join() assertNotNull(result) assertTrue(result!!.contains("null")) } - @Test + fun testSingleLineComments() { val input = """{ "name": "John", // This is a comment "age": 30 }""" - val result = JsonRepairEngine.repair(project, input) + val result = JsonRepairEngine.repair(input, strategies = listOf(JsonAutoRepairStrategy())).join() assertNotNull(result) assertFalse(result!!.contains("//")) } - @Test + fun testMultiLineComments() { val input = """{ "name": "John", /* This is a multi-line comment */ "age": 30 }""" - val result = JsonRepairEngine.repair(project, input) + val result = JsonRepairEngine.repair(input, strategies = listOf(JsonAutoRepairStrategy())).join() assertNotNull(result) assertFalse(result!!.contains("/*")) } - @Test + fun testMixedIssues() { val input = "{name: 'John', age: 30, active: True,}" - val result = JsonRepairEngine.repair(project, input) + val result = JsonRepairEngine.repair(input, strategies = listOf(JsonAutoRepairStrategy())).join() assertNotNull(result) assertTrue(result!!.contains("\"name\"")) assertTrue(result.contains("\"John\"")) @@ -82,59 +80,59 @@ class JsonRepairEngineTest : BasePlatformTestCase() { assertFalse(result.contains(",}")) } - @Test + fun testNestedObjects() { val input = "{name: 'John', address: {city: 'NYC', zip: 10001}}" - val result = JsonRepairEngine.repair(project, input) + val result = JsonRepairEngine.repair(input, strategies = listOf(JsonAutoRepairStrategy())).join() assertNotNull(result) assertTrue(result!!.contains("\"name\"")) assertTrue(result.contains("\"address\"")) assertTrue(result.contains("\"city\"")) } - @Test + fun testArrays() { val input = "{items: ['apple', 'banana', 'orange']}" - val result = JsonRepairEngine.repair(project, input) + val result = JsonRepairEngine.repair(input, strategies = listOf(JsonAutoRepairStrategy())).join() assertNotNull(result) assertTrue(result!!.contains("\"items\"")) assertTrue(result.contains("\"apple\"")) } - @Test + fun testValidJson() { val input = "{\"name\": \"John\", \"age\": 30}" - val result = JsonRepairEngine.repair(project, input) + val result = JsonRepairEngine.repair(input, strategies = listOf(JsonAutoRepairStrategy())).join() assertNotNull(result) assertTrue(result!!.contains("\"name\"")) assertTrue(result.contains("\"age\"")) } - @Test + fun testEmptyObject() { val input = "{}" - val result = JsonRepairEngine.repair(project, input) + val result = JsonRepairEngine.repair(input, strategies = listOf(JsonAutoRepairStrategy())).join() assertNotNull(result) assertEquals("{}", result!!.trim()) } - @Test + fun testEmptyArray() { val input = "[]" - val result = JsonRepairEngine.repair(project, input) + val result = JsonRepairEngine.repair(input, strategies = listOf(JsonAutoRepairStrategy())).join() assertNotNull(result) assertEquals("[]", result!!.trim()) } - @Test + fun testPythonNone() { val input = "{\"value\": None}" - val result = JsonRepairEngine.repair(project, input) + val result = JsonRepairEngine.repair(input, strategies = listOf(JsonAutoRepairStrategy())).join() assertNotNull(result) assertTrue(result!!.contains("null")) } - @Test + fun testComplexNestedStructure() { val input = """{ name: 'Company', @@ -147,7 +145,7 @@ class JsonRepairEngineTest : BasePlatformTestCase() { version: 1 } }""" - val result = JsonRepairEngine.repair(project, input) + val result = JsonRepairEngine.repair(input, strategies = listOf(JsonAutoRepairStrategy())).join() assertNotNull(result) assertTrue(result!!.contains("\"name\"")) assertTrue(result.contains("\"employees\"")) @@ -156,22 +154,20 @@ class JsonRepairEngineTest : BasePlatformTestCase() { assertTrue(result.contains("false")) } -// @Test -// fun testSimpleJsonWithoutDoubleQuotes() { -// val input = "{\"name\":\"god}" -// val result = JsonRepairEngine.repair(project, input) -// assertNotNull(result) -// assertTrue(result!!.contains("\"name\"")) -// assertTrue(result.contains("\"god\"")) -// } + fun testSimpleJsonWithoutDoubleQuotes() { + val input = "{\"name\":\"god}" + val result = JsonRepairEngine.repair(input, strategies = listOf(JsonAutoRepairStrategy())).join() + assertNotNull(result) + assertTrue(result!!.contains("\"name\"")) + assertTrue(result.contains("\"god}\"")) + } - @Test fun testSimpleJsonWithoutDoubleQuotesNewLine() { val input = """{ "name":"god } """.trimIndent() - val result = JsonRepairEngine.repair(project, input) + val result = JsonRepairEngine.repair(input, strategies = listOf(JsonAutoRepairStrategy())).join() assertNotNull(result) assertTrue(result!!.contains("\"name\"")) assertTrue(result.contains("\"god\""))