diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..70ab4c5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,128 @@ +name: ๐Ÿ› Bug Report +description: Report a bug or unexpected behavior +title: "[Bug] " +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thanks for reporting a bug! Please fill out the form below. + + - type: textarea + id: description + attributes: + label: Description + description: A clear and concise description of what the bug is + placeholder: Describe the bug... + validations: + required: true + + - type: textarea + id: reproduction + attributes: + label: Steps to Reproduce + description: Steps to reproduce the behavior + placeholder: | + 1. Create SwcNative instance + 2. Call parseSync with... + 3. See error + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected Behavior + description: What you expected to happen + placeholder: The code should... + validations: + required: true + + - type: textarea + id: actual + attributes: + label: Actual Behavior + description: What actually happened + placeholder: Instead, I got... + validations: + required: true + + - type: textarea + id: code + attributes: + label: Code Example + description: A minimal code example that reproduces the issue + render: kotlin + placeholder: | + ```kotlin + val swc = SwcNative() + // Your code here + ``` + validations: + required: true + + - type: textarea + id: error + attributes: + label: Error Message / Stack Trace + description: If applicable, paste the error message or stack trace + render: text + placeholder: | + Exception in thread "main" java.lang.RuntimeException: ... + + - type: dropdown + id: module + attributes: + label: Affected Module + description: Which module is affected? + options: + - swc-binding + - swc-generator + - swc-jni + - Documentation + - Other + validations: + required: true + + - type: input + id: version + attributes: + label: Version + description: swc-binding version (e.g., 0.7.0) + placeholder: 0.7.0 + validations: + required: true + + - type: input + id: kotlin_version + attributes: + label: Kotlin Version + description: Kotlin version (e.g., 2.2.10) + placeholder: 2.2.10 + + - type: input + id: jvm_version + attributes: + label: JVM Version + description: Java version (e.g., 17) + placeholder: 17 + + - type: dropdown + id: os + attributes: + label: Operating System + options: + - Linux + - macOS + - Windows + - Other + validations: + required: true + + - type: textarea + id: additional + attributes: + label: Additional Context + description: Any other context, screenshots, or information about the problem + placeholder: Add any other context about the problem here... + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..617668d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,9 @@ +blank_issues_enabled: false +contact_links: + - name: ๐Ÿ’ฌ Discussions + url: https://github.com/yidafu/swc-binding/discussions + about: Ask questions and discuss ideas with the community + - name: ๐Ÿ“– Documentation + url: https://yidafu.github.io/swc-binding/docs/ + about: Check out our documentation + diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml new file mode 100644 index 0000000..f47a6f7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.yml @@ -0,0 +1,66 @@ +name: ๐Ÿ“š Documentation +description: Report documentation issues or suggest improvements +title: "[Docs] " +labels: ["documentation"] +body: + - type: markdown + attributes: + value: | + Thanks for helping improve our documentation! + + - type: dropdown + id: type + attributes: + label: Type + description: What type of documentation issue is this? + options: + - Typo or grammar error + - Missing information + - Unclear or confusing + - Outdated information + - Broken link + - Other + validations: + required: true + + - type: textarea + id: location + attributes: + label: Location + description: Where is the documentation issue? (file path, URL, section name) + placeholder: README.md, swc-binding/README.md, API docs, etc. + validations: + required: true + + - type: textarea + id: description + attributes: + label: Description + description: Describe the documentation issue or improvement + placeholder: The documentation says... but it should say... + validations: + required: true + + - type: textarea + id: suggestion + attributes: + label: Suggested Change + description: If you have a suggestion for how to fix it, please provide it here + placeholder: | + Change: + ``` + Old text + ``` + + To: + ``` + New text + ``` + + - type: textarea + id: additional + attributes: + label: Additional Context + description: Any other context about the documentation issue + placeholder: Add any other context here... + diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..94c1ba5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,81 @@ +name: โœจ Feature Request +description: Suggest a new feature or enhancement +title: "[Feature] " +labels: ["enhancement"] +body: + - type: markdown + attributes: + value: | + Thanks for suggesting a feature! Please fill out the form below. + + - type: textarea + id: problem + attributes: + label: Problem Statement + description: What problem does this feature solve? + placeholder: I'm always frustrated when... + validations: + required: true + + - type: textarea + id: solution + attributes: + label: Proposed Solution + description: Describe the solution you'd like + placeholder: I would like to see... + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives Considered + description: Describe any alternative solutions or features you've considered + placeholder: Other approaches I've thought about... + + - type: dropdown + id: module + attributes: + label: Affected Module + description: Which module would this feature affect? + options: + - swc-binding + - swc-generator + - swc-jni + - Documentation + - Other + validations: + required: true + + - type: textarea + id: api + attributes: + label: Proposed API + description: If applicable, describe the proposed API or interface + render: kotlin + placeholder: | + ```kotlin + // Proposed API example + swc.newMethod(...) + ``` + + - type: dropdown + id: priority + attributes: + label: Priority + description: How important is this feature to you? + options: + - Low - Nice to have + - Medium - Would be helpful + - High - Important for my use case + - Critical - Blocking my project + validations: + required: true + + - type: textarea + id: additional + attributes: + label: Additional Context + description: Any other context, examples, or mockups about the feature request + placeholder: Add any other context or screenshots about the feature request here... + diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml new file mode 100644 index 0000000..9999d06 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -0,0 +1,59 @@ +name: โ“ Question +description: Ask a question about the project +title: "[Question] " +labels: ["question"] +body: + - type: markdown + attributes: + value: | + Have a question? We're here to help! + + - type: textarea + id: question + attributes: + label: Question + description: What would you like to know? + placeholder: Your question here... + validations: + required: true + + - type: textarea + id: context + attributes: + label: Context + description: Provide any relevant context about your question + placeholder: | + I'm trying to... + I've looked at... + I'm using version... + + - type: textarea + id: code + attributes: + label: Code Example (if applicable) + description: If your question involves code, please share a minimal example + render: kotlin + placeholder: | + ```kotlin + // Your code here + ``` + + - type: dropdown + id: module + attributes: + label: Related Module + description: Which module is your question about? + options: + - swc-binding + - swc-generator + - swc-jni + - General + - Other + + - type: textarea + id: additional + attributes: + label: Additional Information + description: Any other information that might help answer your question + placeholder: Add any other context here... + diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..b70b0e6 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,79 @@ +## Description + + + +## Type of Change + + + +- [ ] ๐Ÿ› Bug fix (non-breaking change which fixes an issue) +- [ ] โœจ New feature (non-breaking change which adds functionality) +- [ ] ๐Ÿ’ฅ Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] ๐Ÿ“š Documentation update +- [ ] ๐Ÿ”ง Refactoring (no functional changes) +- [ ] โšก Performance improvement +- [ ] โœ… Test update +- [ ] ๐Ÿ”จ Build/CI changes +- [ ] ๐ŸŽจ Style/formatting changes +- [ ] ๐Ÿงน Chore (other changes that don't modify src or test files) + +## Related Issues + + + +Closes # +Related to # + +## Changes Made + + + +- +- +- + +## Module(s) Affected + + + +- [ ] swc-binding +- [ ] swc-generator +- [ ] swc-jni +- [ ] Documentation +- [ ] Build/CI +- [ ] Other: + +## Testing + + + +- [ ] Unit tests added/updated +- [ ] Integration tests added/updated +- [ ] Manual testing performed + +**Test Steps:** +1. +2. +3. + +## Checklist + + + +- [ ] My code follows the project's style guidelines +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have updated the documentation accordingly +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Screenshots (if applicable) + + + +## Additional Notes + + + diff --git a/.vscode/settings.json b/.vscode/settings.json index be97557..da8ab11 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "java.compile.nullAnalysis.mode": "disabled" + "java.compile.nullAnalysis.mode": "disabled", + "java.configuration.updateBuildConfiguration": "disabled" } \ No newline at end of file diff --git a/README.md b/README.md index 07171ed..15fb1c2 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Maven Central](https://img.shields.io/maven-central/v/dev.yidafu.swc/swc-binding.svg)](https://search.maven.org/artifact/dev.yidafu.swc/swc-binding) -[![Kotlin](https://img.shields.io/badge/kotlin-1.9.21-blue.svg)](https://kotlinlang.org/) -[![SWC](https://img.shields.io/badge/SWC-43.0.0-green.svg)](https://github.com/swc-project/swc) -[![Rust](https://img.shields.io/badge/rust-1.70+-orange.svg)](https://www.rust-lang.org/) +[![Kotlin](https://img.shields.io/badge/kotlin-2.2.10-blue.svg)](https://kotlinlang.org/) +[![SWC](https://img.shields.io/badge/SWC-43.0.0-98D982?logo=swc&logoColor=white)](https://github.com/swc-project/swc) +[![Rust](https://img.shields.io/badge/rust-1.70+-CE412B?logo=rust&logoColor=white)](https://www.rust-lang.org/) [![JVM](https://img.shields.io/badge/JVM-17+-red.svg)](https://www.oracle.com/java/) [SWC](https://github.com/swc-project/swc) JVM binding in Kotlin. @@ -40,7 +40,6 @@ - [Article](#article) - [License](#license) - ## Installation ```kotlin @@ -167,6 +166,7 @@ swc.parseAsync( #### Available Async Methods All synchronous methods have async counterparts: + - `parseAsync()` - Asynchronously parse code - `parseFileAsync()` - Asynchronously parse file - `transformAsync()` - Asynchronously transform code @@ -207,7 +207,6 @@ Native method fun parseSync(code: String, options: String, filename: String?): String ``` - ### parseFileSync [see more](https://yidafu.github.io/swc-binding/docs/swc-binding/dev.yidafu.swc/-swc-native/parse-file-sync.html) diff --git a/swc-binding/README.md b/swc-binding/README.md index 52dad7e..8fa6769 100644 --- a/swc-binding/README.md +++ b/swc-binding/README.md @@ -4,8 +4,9 @@ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](../LICENSE) [![Maven Central](https://img.shields.io/maven-central/v/dev.yidafu.swc/swc-binding.svg)](https://search.maven.org/artifact/dev.yidafu.swc/swc-binding) -[![Kotlin](https://img.shields.io/badge/kotlin-1.9.21-blue.svg)](https://kotlinlang.org/) -[![SWC](https://img.shields.io/badge/SWC-43.0.0-green.svg)](https://github.com/swc-project/swc) +[![Kotlin](https://img.shields.io/badge/kotlin-2.2.10-blue.svg)](https://kotlinlang.org/) +[![SWC](https://img.shields.io/badge/SWC-43.0.0-98D982?logo=swc&logoColor=white)](https://github.com/swc-project/swc) +[![Rust](https://img.shields.io/badge/rust-1.70+-CE412B?logo=rust&logoColor=white)](https://www.rust-lang.org/) [![JVM](https://img.shields.io/badge/JVM-17+-red.svg)](https://www.oracle.com/java/) Kotlin/JVM library that provides idiomatic bindings to SWC via JNI. This is the published artifact used by applications. @@ -32,18 +33,372 @@ val output = swc.transformSync( println(output.code) ``` +## SwcNative Usage Guide + +`SwcNative` is the main entry point for all SWC operations. It provides both synchronous and asynchronous methods for parsing, transforming, printing, and minifying JavaScript/TypeScript code. + +### Creating an Instance + +```kotlin +val swc = SwcNative() +``` + +The `SwcNative` instance automatically loads the native library (`swc_jni`) when created. The library is loaded from the JAR resources if not found in the system library path. + +### Synchronous Methods + +All synchronous methods block the calling thread until the operation completes. Use them for simple scripts or when blocking is acceptable. + +#### Parsing Code + +Parse JavaScript/TypeScript code into an AST: + +```kotlin +val swc = SwcNative() +val ast = swc.parseSync( + code = "const x = 42;", + options = esParseOptions { }, + filename = "test.js" // Optional +) +``` + +**TypeScript parsing:** + +```kotlin +val ast = swc.parseSync( + code = "const x: number = 42;", + options = tsParseOptions { + tsx = true + decorators = true + }, + filename = "test.ts" +) +``` + +**JSX parsing:** + +```kotlin +val ast = swc.parseSync( + code = """ + function App() { + return
Hello
; + } + """.trimIndent(), + options = esParseOptions { + jsx = true + }, + filename = "App.jsx" +) +``` + +#### Parsing Files + +Parse a file directly: + +```kotlin +val ast = swc.parseFileSync( + filepath = "src/index.ts", + options = tsParseOptions { + tsx = true + } +) +``` + +#### Transforming Code + +Transform code (transpile, minify, apply plugins, etc.): + +```kotlin +val output = swc.transformSync( + code = "const arrow = () => 42;", + isModule = false, + options = options { + jsc { + target = JscTarget.ES5 + parser { + syntax = Syntax.ECMASCRIPT + } + } + } +) +println(output.code) // Transformed code +``` + +**Transforming TypeScript to JavaScript:** + +```kotlin +val output = swc.transformSync( + code = "const x: number = 42;", + isModule = true, + options = options { + jsc { + target = JscTarget.ES2020 + parser { + syntax = Syntax.TYPESCRIPT + tsx = true + } + } + } +) +``` + +**Transforming files:** + +```kotlin +val output = swc.transformFileSync( + filepath = "src/index.ts", + isModule = true, + options = options { + jsc { + target = JscTarget.ES2020 + parser { + syntax = Syntax.TYPESCRIPT + } + } + } +) +``` + +#### Printing AST + +Convert an AST back to code: + +```kotlin +val ast = swc.parseSync("const x = 42;", esParseOptions { }, "test.js") +val output = swc.printSync(ast, options { }) +println(output.code) // "const x = 42;" +``` + +#### Minifying Code + +Minify JavaScript/TypeScript code: + +```kotlin +val code = """ + function add(a, b) { + return a + b; + } +""".trimIndent() + +val output = swc.minifySync( + src = code, + options = JsMinifyOptions() +) +println(output.code) // Minified code +``` + +### Asynchronous Methods + +Asynchronous methods execute in background threads and don't block the calling thread. They are available in two forms: callback-based (Java-friendly) and coroutine-based (Kotlin-idiomatic). + +#### Using Coroutines (Recommended for Kotlin) + +All async methods have suspend function variants that work seamlessly with Kotlin coroutines: + +```kotlin +import kotlinx.coroutines.* + +suspend fun parseCode() { + val swc = SwcNative() + val ast = swc.parseAsync( + code = "const x = 42;", + options = esParseOptions { }, + filename = "test.js" + ) + println("Parsed: ${ast.type}") +} + +// Usage +runBlocking { + parseCode() +} +``` + +**Parallel parsing:** + +```kotlin +suspend fun parseMultipleFiles() = coroutineScope { + val swc = SwcNative() + val options = esParseOptions { } + + val results = listOf("file1.js", "file2.js", "file3.js") + .map { file -> + async { swc.parseFileAsync(file, options) } + } + .awaitAll() + + results.forEach { ast -> + println("Parsed: ${ast.type}") + } +} +``` + +**Async transform:** + +```kotlin +suspend fun transformCode() { + val swc = SwcNative() + val output = swc.transformAsync( + code = "const arrow = () => 42;", + isModule = false, + options = options { + jsc { + target = JscTarget.ES5 + parser { + syntax = Syntax.ECMASCRIPT + } + } + } + ) + println(output.code) +} +``` + +**Async minify:** + +```kotlin +suspend fun minifyCode() { + val swc = SwcNative() + val output = swc.minifyAsync( + src = "function add(a, b) { return a + b; }", + options = JsMinifyOptions() + ) + println(output.code) +} +``` + +#### Using Callbacks (Java-friendly) + +Callback-based methods work in both Kotlin and Java: + +```kotlin +val swc = SwcNative() +swc.parseAsync( + code = "const x = 42;", + options = esParseOptions { }, + filename = "test.js", + onSuccess = { ast -> + println("Success: ${ast.type}") + }, + onError = { error -> + println("Error: $error") + } +) +``` + +**Java usage:** + +```java +SwcNative swc = new SwcNative(); +swc.parseAsync("const x = 42;", options, null, + (Program ast) -> System.out.println("Success: " + ast.getType()), + (String error) -> System.err.println("Error: " + error) +); +``` + +### Available Methods Summary + +#### Synchronous Method List + +- `parseSync(code, options, filename?)` - Parse code to AST +- `parseFileSync(filepath, options)` - Parse file to AST +- `transformSync(code, isModule, options)` - Transform code +- `transformFileSync(filepath, isModule, options)` - Transform file +- `printSync(program, options)` - Print AST to code +- `minifySync(src, options)` - Minify code + +#### Asynchronous Method List (Coroutines) + +- `parseAsync(code, options, filename?)` - Parse code to AST (suspend) +- `parseFileAsync(filepath, options)` - Parse file to AST (suspend) +- `transformAsync(code, isModule, options)` - Transform code (suspend) +- `transformFileAsync(filepath, isModule, options)` - Transform file (suspend) +- `printAsync(program, options)` - Print AST to code (suspend) +- `minifyAsync(src, options)` - Minify code (suspend) + +#### Asynchronous Method List (Callbacks) + +- `parseAsync(code, options, filename?, onSuccess, onError)` - Parse code with callbacks +- `parseFileAsync(filepath, options, onSuccess, onError)` - Parse file with callbacks +- `transformAsync(code, isModule, options, onSuccess, onError)` - Transform with callbacks +- `transformFileAsync(filepath, isModule, options, onSuccess, onError)` - Transform file with callbacks +- `printAsync(program, options, onSuccess, onError)` - Print AST with callbacks +- `minifyAsync(src, options, onSuccess, onError)` - Minify with callbacks + +### Error Handling + +All methods throw `RuntimeException` on failure. For async methods using coroutines, exceptions are propagated: + +```kotlin +try { + val ast = swc.parseSync("invalid syntax", esParseOptions { }, "test.js") +} catch (e: RuntimeException) { + println("Parse failed: ${e.message}") +} +``` + +**With coroutines:** + +```kotlin +try { + val ast = swc.parseAsync("invalid syntax", esParseOptions { }, "test.js") +} catch (e: RuntimeException) { + println("Parse failed: ${e.message}") +} +``` + +**With callbacks:** + +```kotlin +swc.parseAsync( + code = "invalid syntax", + options = esParseOptions { }, + filename = "test.js", + onSuccess = { /* ... */ }, + onError = { error -> + println("Parse failed: $error") + } +) +``` + +### Threading Model + +- **Synchronous methods**: Execute on the calling thread and block until completion. +- **Asynchronous methods**: Execute in background threads managed by the Rust runtime. Callbacks are invoked from Rust background threads. Coroutines suspend the calling coroutine and resume when the operation completes. + +### SwcNative Best Practices + +- **Use async methods for production**: Async methods don't block threads and provide better performance for concurrent operations. + +- **Prefer coroutines in Kotlin**: Use suspend functions for cleaner, more idiomatic Kotlin code. + +- **Reuse SwcNative instances**: Creating a new `SwcNative()` instance is lightweight, but reusing instances is fine for multiple operations. + +- **Handle errors appropriately**: Always wrap operations in try-catch blocks or handle errors in callbacks. + +- **Use appropriate parser options**: Choose `esParseOptions` for JavaScript/JSX and `tsParseOptions` for TypeScript/TSX. + ## Documentation -- API Docs: https://yidafu.github.io/swc-binding/docs/ +- API Docs: [https://yidafu.github.io/swc-binding/docs/](https://yidafu.github.io/swc-binding/docs/) - Samples: `swc-binding/src/test` -## Important Notes +## DSL Usage Guide -### Manual AST Construction +The swc-binding library provides a type-safe DSL (Domain-Specific Language) for building AST nodes and configuration objects. The DSL uses Kotlin's builder pattern to create SWC AST nodes in an idiomatic way. -When manually constructing AST nodes using the DSL, you **must** explicitly set all boolean fields to avoid serialization errors. The Rust backend expects non-null boolean values, but Kotlin's nullable boolean fields (`Boolean?`) will serialize as `null` if not set. +### Importing DSL Functions + +```kotlin +import dev.yidafu.swc.generated.dsl.* // ktlint-disable no-wildcard-imports +``` + +### Creating AST Nodes + +#### Module + +A `Module` represents an ES module with a body containing statements and declarations: -**Example - Correct:** ```kotlin val mod = module { span = emptySpan() @@ -51,14 +406,20 @@ val mod = module { variableDeclaration { span = emptySpan() kind = VariableDeclarationKind.CONST - declare = false // โœ… Explicitly set + declare = false declarations = arrayOf( variableDeclarator { + span = emptySpan() id = identifier { + span = emptySpan() value = "x" - optional = false // โœ… Explicitly set + optional = false + } + init = numericLiteral { + span = emptySpan() + value = 42.0 + raw = "42" } - init = numericLiteral { ... } } ) } @@ -66,6 +427,308 @@ val mod = module { } ``` +### Important Notes + +#### Manual AST Construction + +When manually constructing AST nodes using the DSL, it's **recommended** to explicitly set boolean fields for clarity and compatibility. While many fields have default values and `@EncodeDefault` annotations that handle serialization, explicitly setting boolean fields makes your code more maintainable and avoids potential edge cases. + +**Example - Correct:** +```kotlin +variableDeclaration { + span = emptySpan() + kind = VariableDeclarationKind.CONST + declare = false // โœ… Explicitly set + declarations = arrayOf( + variableDeclarator { + span = emptySpan() + id = identifier { + span = emptySpan() + value = "x" + optional = false // โœ… Explicitly set + } + init = numericLiteral { + span = emptySpan() + value = 42.0 + raw = "42" + } + } + ) +} +``` + +#### Variable Declaration + +```kotlin +variableDeclaration { + span = emptySpan() + kind = VariableDeclarationKind.CONST // or LET, VAR + declare = false + declarations = arrayOf( + variableDeclarator { + span = emptySpan() + id = identifier { + span = emptySpan() + value = "myVar" + optional = false + } + init = stringLiteral { + span = emptySpan() + value = "Hello" + raw = "\"Hello\"" + } + } + ) +} +``` + +#### Function Declaration + +```kotlin +functionDeclaration { + span = emptySpan() + identifier = identifier { + span = emptySpan() + value = "add" + optional = false + } + declare = false + async = false + generator = false + params = arrayOf( + param { + span = emptySpan() + pat = identifier { + span = emptySpan() + value = "a" + optional = false + } + }, + param { + span = emptySpan() + pat = identifier { + span = emptySpan() + value = "b" + optional = false + } + } + ) + body = blockStatement { + span = emptySpan() + stmts = arrayOf( + returnStatement { + span = emptySpan() + arg = binaryExpression { + span = emptySpan() + operator = BinaryOperator.PLUS + left = identifier { + span = emptySpan() + value = "a" + optional = false + } + right = identifier { + span = emptySpan() + value = "b" + optional = false + } + } + } + ) + } +} +``` + +#### Class Declaration + +```kotlin +classDeclaration { + span = emptySpan() + identifier = identifier { + span = emptySpan() + value = "MyClass" + optional = false + } + declare = false + abstract = false + body = arrayOf( + classProperty { + span = emptySpan() + key = identifier { + span = emptySpan() + value = "name" + optional = false + } + value = stringLiteral { + span = emptySpan() + value = "Example" + raw = "\"Example\"" + } + typeAnnotation = tsTypeAnnotation { + span = emptySpan() + typeAnnotation = tsKeywordType { + span = emptySpan() + kind = TsKeywordTypeKind.STRING + } + } + } + ) + superClass = null + superTypeParams = null + implements = arrayOf() + decorators = arrayOf() +} +``` + +#### Import Declaration + +```kotlin +importDeclaration { + span = emptySpan() + specifiers = arrayOf( + importDefaultSpecifier { + local = identifier { + span = emptySpan() + value = "React" + optional = false + } + } + ) + source = stringLiteral { + span = emptySpan() + value = "react" + raw = "\"react\"" + } + typeOnly = false +} +``` + +### Creating Configuration Objects + +#### Parser Options + +**TypeScript Parser Options:** + +```kotlin +val tsOptions = tsParseOptions { + tsx = true + decorators = true + dynamicImport = true + target = JscTarget.ES2020 +} +``` + +**ECMAScript Parser Options:** + +```kotlin +val esOptions = esParseOptions { + jsx = true + allowReturnOutsideFunction = true + allowAwaitOutsideFunction = false + allowSuperOutsideMethod = false + allowImportExportEverywhere = false + allowUndeclaredExports = false +} +``` + +#### Transform Options + +```kotlin +val transformOptions = options { + jsc { + target = JscTarget.ES2020 + parser { + syntax = Syntax.TYPESCRIPT + tsx = true + decorators = true + } + transform { + react { + runtime = ReactConfigRuntime.CLASSIC + pragma = "React.createElement" + pragmaFrag = "React.Fragment" + } + } + minify { + compress = JsMinifyCompressOptions() + mangle = JsMinifyMangleOptions() + } + } + module { + type = ModuleConfigType.ES6 + } + minify = false + isModule = true +} +``` + +### Common Patterns + +#### Creating Literals + +**String Literal:** + +```kotlin +stringLiteral { + span = emptySpan() + value = "Hello, World!" + raw = "\"Hello, World!\"" +} +``` + +**Numeric Literal:** + +```kotlin +numericLiteral { + span = emptySpan() + value = 42.0 + raw = "42" +} +``` + +**Boolean Literal:** + +```kotlin +booleanLiteral { + span = emptySpan() + value = true +} +``` + +#### Creating Expressions + +**Binary Expression:** + +```kotlin +binaryExpression { + span = emptySpan() + operator = BinaryOperator.PLUS // or MINUS, MULTIPLY, etc. + left = identifier { value = "a"; span = emptySpan(); optional = false } + right = identifier { value = "b"; span = emptySpan(); optional = false } +} +``` + +**Call Expression:** + +```kotlin +callExpression { + span = emptySpan() + callee = identifier { + span = emptySpan() + value = "console" + optional = false + } + arguments = arrayOf( + argument { + expr = stringLiteral { + span = emptySpan() + value = "Hello" + raw = "\"Hello\"" + } + } + ) +} +``` + **Example - Incorrect:** ```kotlin val mod = module { @@ -79,12 +742,52 @@ val mod = module { } ``` -**Common boolean fields that must be set:** -- `Identifier.optional: Boolean?` โ†’ set to `false` -- `VariableDeclaration.declare: Boolean?` โ†’ set to `false` -- `ArrowFunctionExpression.async: Boolean?` โ†’ set to `false` -- `ArrowFunctionExpression.generator: Boolean?` โ†’ set to `false` -- `TemplateElement.tail: Boolean?` โ†’ set to `false` or `true` (depending on position) +**Member Expression:** + +```kotlin +memberExpression { + span = emptySpan() + obj = identifier { + span = emptySpan() + value = "console" + optional = false + } + prop = identifier { + span = emptySpan() + value = "log" + optional = false + } + computed = false +} +``` + +### Important Notes + +#### Span Fields + +All AST nodes require a `span` field. Use `emptySpan()` to create a default span: + +```kotlin +span = emptySpan() // Creates a span with start=0, end=0, ctxt=0 +``` + +#### Boolean Fields + +When manually constructing AST nodes, it's **recommended** to explicitly set boolean fields for clarity and to ensure compatibility. While many boolean fields have `@EncodeDefault` annotations and may work without explicit values, setting them explicitly makes your code more maintainable and avoids potential serialization issues. + +**Common boolean fields you should set:** + +- `Identifier.optional: Boolean?` โ†’ typically set to `false` +- `VariableDeclaration.declare: Boolean?` โ†’ typically set to `false` +- `FunctionDeclaration.async: Boolean?` โ†’ set to `false` for non-async functions +- `FunctionDeclaration.generator: Boolean?` โ†’ set to `false` for non-generator functions +- `ArrowFunctionExpression.async: Boolean?` โ†’ set to `false` for non-async arrow functions +- `ArrowFunctionExpression.generator: Boolean?` โ†’ set to `false` for non-generator arrow functions +- `TemplateElement.tail: Boolean?` โ†’ set to `false` or `true` (depending on position in template) + +#### Best Practices + +- **Prefer parsing over manual construction**: Use `parseSync` to parse code into AST instead of manually constructing it, as parsed ASTs have all fields properly initialized. **Recommended approach:** Use `parseSync` to parse code into AST instead of manually constructing it, as parsed ASTs have all fields properly initialized. @@ -94,8 +797,14 @@ val mod = swcNative.parseSync("const x = 42;", esParseOptions { }, "test.js") as val output = swcNative.printSync(mod, options { }) ``` -## License +- **Use DSL for modifications**: The DSL is most useful when you need to modify or create specific AST nodes programmatically. -MIT +- **Always set span fields**: Every AST node requires a `span` field. Use `emptySpan()` for default values. +- **Set boolean fields explicitly**: While not always required, explicitly setting boolean fields improves code clarity and ensures compatibility. +For more examples, see the test files in `swc-binding/src/test`. + +## License + +MIT diff --git a/swc-generator/README.md b/swc-generator/README.md index 8d7e1b9..74cb401 100644 --- a/swc-generator/README.md +++ b/swc-generator/README.md @@ -3,8 +3,8 @@ [Read this in Chinese: README.zh-CN.md](README.zh-CN.md) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](../LICENSE) -[![Kotlin](https://img.shields.io/badge/kotlin-1.9.21-blue.svg)](https://kotlinlang.org/) -[![SWC](https://img.shields.io/badge/SWC-43.0.0-green.svg)](https://github.com/swc-project/swc) +[![Kotlin](https://img.shields.io/badge/kotlin-2.2.10-blue.svg)](https://kotlinlang.org/) +[![SWC](https://img.shields.io/badge/SWC-43.0.0-98D982?logo=swc&logoColor=white)](https://github.com/swc-project/swc) [![JVM](https://img.shields.io/badge/JVM-17+-red.svg)](https://www.oracle.com/java/) Kotlin-based code generator for SWC Kotlin/JVM bindings. It reads SWC type definitions and generates strongly-typed Kotlin APIs and DSL helpers. @@ -46,6 +46,7 @@ Kotlin-based code generator for SWC Kotlin/JVM bindings. It reads SWC type defin Some Rust structs contain independent `ctxt: SyntaxContext` fields (not within `span`), and these fields don't have `serde(default)`, making them required during deserialization. The code generator automatically adds `ctxt: Int = 0` fields with `@EncodeDefault` annotations to the following classes: **Statement-related:** + - `BlockStatement`: corresponds to Rust `BlockStmt` struct **Expression-related:** @@ -55,21 +56,100 @@ Some Rust structs contain independent `ctxt: SyntaxContext` fields (not within ` - `TaggedTemplateExpression`: corresponds to Rust `TaggedTpl` struct **Declaration-related:** + - `FunctionDeclaration`: corresponds to Rust `Function` struct (expanded through `FnDecl`'s `#[serde(flatten)]`) - `VariableDeclaration`: corresponds to Rust `VarDecl` struct **Class-related:** + - `Class`: corresponds to Rust `Class` struct - `PrivateProperty`: corresponds to Rust `PrivateProp` struct - `Constructor`: corresponds to Rust `Constructor` struct **Identifier-related:** + - `Identifier`: corresponds to Rust `Ident` struct This special handling ensures the generated Kotlin code is compatible with Rust's deserialization requirements. **Note**: Although the Rust `Span` struct itself does not contain a `ctxt` field (only `start` and `end`), the Kotlin `Span` class retains the `ctxt` field for compatibility with TypeScript definitions and existing code. These node-level independent `ctxt` fields are additional fields needed to match Rust's deserialization requirements. +### Explicit Type Field Handling + +Some classes require explicit `type` fields even though they have `@JsonClassDiscriminator` and `@SerialName` annotations. This is because when these classes are serialized as concrete types (e.g., `Array`), the discriminator doesn't automatically add the `type` field. The following classes have explicit `type` fields added: + +- `VariableDeclarator` +- `Param` (Kotlin class name, serialized as "Parameter") +- `BlockStatement` +- `JSXOpeningElement` +- `JSXClosingElement` + +### Property Type Overrides + +Certain properties require special type mappings that differ from their TypeScript definitions: + +**Global Type Overrides:** + +Properties that are declared as `any` in TypeScript but are mapped to `String?` in Kotlin: + +- `global_defs` / `globalDefs` +- `targets` +- `sequences` +- `pure_getters` / `pureGetters` +- `top_retain` / `topRetain` +- `toplevel` + +**Interface-Specific Overrides:** + +- `ForOfStatement.await`: Declared as `Span?` in TypeScript but actually should be `Boolean?` in Kotlin. This is a SWC-specific case that requires special handling. + +### Kotlin Reserved Word Mapping + +To avoid conflicts with Kotlin keywords, certain property names are automatically mapped: + +- `object` โ†’ `jsObject` +- `inline` โ†’ `jsInline` +- `in` โ†’ `jsIn` +- `super` โ†’ `jsSuper` +- `class` โ†’ `jsClass` +- `interface` โ†’ `jsInterface` +- `fun` โ†’ `jsFun` +- `val` โ†’ `jsVal` +- `var` โ†’ `jsVar` +- `when` โ†’ `jsWhen` +- `is` โ†’ `jsIs` +- `as` โ†’ `jsAs` +- `import` โ†’ `jsImport` +- `package` โ†’ `jsPackage` + +### Type Name Overrides + +To avoid conflicts with Kotlin built-in types or reserved names, certain type names are mapped: + +- `Class` โ†’ `JsClass` +- `Super` โ†’ `JsSuper` +- `Import` โ†’ `JsImport` +- `ExprOrSpread` โ†’ `Argument` (unified mapping to avoid dual-type decoding/DSL conflicts) + +### Span Property Handling + +Special handling for `span` and span coordinate properties: + +- **`span` property**: Uses `emptySpan()` function call as default value, ensuring the `ctxt` field is included +- **Span coordinate properties** (`start`, `end`, `ctxt` in `Span` class): Use `Int` type with default value `0` +- **`start` and `end`**: Annotated with `@EncodeDefault` to ensure serialization +- **`ctxt`**: Not annotated with `@EncodeDefault` because `@swc/core` doesn't output default `ctxt` values of 0 + +### Serialization Annotations + +The generator automatically adds appropriate serialization annotations: + +- **`@Serializable`**: Added to sealed interfaces and final classes +- **`@JsonClassDiscriminator`**: Added to base interfaces for polymorphic serialization +- **`@SerialName`**: Added to properties to ensure correct serialization names +- **`@SwcDslMarker`**: Added to interfaces and implementation classes for DSL type safety +- **`@OptIn(ExperimentalSerializationApi)`**: Added to implementation classes that use experimental serialization features + ## Development - See `swc-generator/ARCHITECTURE.md` and `ARCHITECTURE_OVERVIEW.md` @@ -79,5 +159,3 @@ This special handling ensures the generated Kotlin code is compatible with Rust' ## License MIT - -