Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
4ea5786
[ts-frontend] M1: scaffold, EtsIR DTO mirrors, serializer, invariant …
CaelmBleidd Jul 2, 2026
6beec1a
[ts-frontend] M2: TypeScript type -> TypeDto conversion
CaelmBleidd Jul 2, 2026
f3c8fd6
[ts-frontend] M3: straight-line lowering to three-address code
CaelmBleidd Jul 2, 2026
b6d05d7
[ts-frontend] M4: control-flow lowering (CFG)
CaelmBleidd Jul 2, 2026
a251ff6
[ts-frontend] M5: classes, interfaces, enums, namespaces
CaelmBleidd Jul 2, 2026
f82c1f5
[ts-frontend] M6: try/catch/finally and import/export infos
CaelmBleidd Jul 2, 2026
162fe37
[ts-frontend] M7: closures, object literals, destructuring, optional …
CaelmBleidd Jul 2, 2026
77d61fd
[ts-frontend] M8: provider selection, project/multi CLI modes, Gradle…
CaelmBleidd Jul 2, 2026
deb6c07
[ts-frontend] M9: CI integration and documentation
CaelmBleidd Jul 2, 2026
14f7665
[ts-frontend] Expand README with full usage documentation
CaelmBleidd Jul 2, 2026
b356509
[ts-frontend] Fix ci-ets: unbreak ArkAnalyzer setup and make it best-…
CaelmBleidd Jul 3, 2026
ab85ff1
[ts-frontend] Fix ArkAnalyzer setup: pin @types/node via the manifest
CaelmBleidd Jul 3, 2026
cbc8e5e
[ts-frontend] Review fixes: PtrCallExpr.ptr validation, inc/dec seman…
CaelmBleidd Jul 3, 2026
6758a43
[ts-frontend] Hoist raw fallback values into temps
CaelmBleidd Jul 3, 2026
9b6ca2a
[ts-frontend] Duplicate finally blocks on abrupt exits
CaelmBleidd Jul 3, 2026
525637f
[ts-frontend] Review fixes: cast-stripped LHS in raw-value check, ci.…
CaelmBleidd Jul 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
with:
# Only write to the cache for builds on the specific branches. (Default is 'main' only.)
# Builds on other branches will only read existing entries from the cache.
cache-read-only: ${{ github.ref != 'refs/heads/develop' && github.ref != 'ref/heads/neo' }}
cache-read-only: ${{ github.ref != 'refs/heads/develop' && github.ref != 'refs/heads/neo' }}

- name: Build and run tests
run: ./gradlew --scan build -x :jacodb-ets:build
Expand Down Expand Up @@ -108,9 +108,31 @@ jobs:
with:
# Only write to the cache for builds on the specific branches. (Default is 'main' only.)
# Builds on other branches will only read existing entries from the cache.
cache-read-only: ${{ github.ref != 'refs/heads/develop' && github.ref != 'ref/heads/neo' }}
cache-read-only: ${{ github.ref != 'refs/heads/develop' && github.ref != 'refs/heads/neo' }}

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: jacodb-ets/ts-frontend/package-lock.json

- name: Set up ArkAnalyzer
- name: Build and test ts-frontend
working-directory: jacodb-ets/ts-frontend
run: |
npm ci
npm run build
npm test

# The legacy ArkAnalyzer provider stays supported (selectable via
# ETS_IR_PROVIDER=arkanalyzer); setting ARKANALYZER_DIR here enables
# the provider-parity test in EtsTsFrontendTest.
#
# Best-effort: the default provider is the bundled ts-frontend, so a
# broken upstream ArkAnalyzer must not fail the job — without
# ARKANALYZER_DIR the parity test skips itself.
- name: Set up ArkAnalyzer (legacy provider, best-effort)
continue-on-error: true
run: |
REPO_URL="https://gitcode.com/Lipen/arkanalyzer"
DEST_DIR="arkanalyzer"
Expand All @@ -131,7 +153,6 @@ jobs:
echo "Repository cloned successfully."
fi

echo "ARKANALYZER_DIR=$(realpath $DEST_DIR)" >> $GITHUB_ENV
cd $DEST_DIR

# ArkAnalyzer's bundled TypeScript (ohos-typescript 4.9.5) cannot parse
Expand All @@ -145,6 +166,9 @@ jobs:
npm install
npm run build

# Export only after a successful build so a broken checkout is never used.
echo "ARKANALYZER_DIR=$(realpath .)" >> $GITHUB_ENV

- name: Run ETS tests
run: ./gradlew --scan :jacodb-ets:generateTestResources :jacodb-ets:test

Expand Down
6 changes: 6 additions & 0 deletions jacodb-ets/ARKANALYZER.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# ArkAnalyzer

> **Note:** ArkAnalyzer is now the LEGACY EtsIR provider. The default provider
> is the native TypeScript frontend in [`ts-frontend`](ts-frontend/README.md),
> which requires no external checkout. ArkAnalyzer remains fully supported:
> select it with `ETS_IR_PROVIDER=arkanalyzer` (plus `ARKANALYZER_DIR`) or by
> passing `EtsIrProvider.ARKANALYZER` to the `loadEts*AutoConvert` functions.

## Installation

Clone and install the ArkAnalyzer via NPM:
Expand Down
135 changes: 106 additions & 29 deletions jacodb-ets/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import org.apache.tools.ant.taskdefs.condition.Os
import java.io.FileNotFoundException

plugins {
Expand All @@ -21,44 +22,117 @@ dependencies {
testFixturesImplementation(Libs.junit_jupiter_api)
}

// ----------------------------------------------------------------------------
// Native TypeScript frontend (ts-frontend)
// ----------------------------------------------------------------------------

val tsFrontendDir: File = projectDir.resolve("ts-frontend")
val npmExecutable: String = if (Os.isFamily(Os.FAMILY_WINDOWS)) "npm.cmd" else "npm"

fun isNpmAvailable(): Boolean = try {
ProcessBuilder(npmExecutable, "--version").start().waitFor() == 0
} catch (_: Exception) {
false
}

val installTsFrontend = tasks.register<Exec>("installTsFrontend") {
group = "build"
description = "Installs npm dependencies of the ts-frontend."
workingDir = tsFrontendDir
commandLine(npmExecutable, "ci")
inputs.files(tsFrontendDir.resolve("package.json"), tsFrontendDir.resolve("package-lock.json"))
outputs.dir(tsFrontendDir.resolve("node_modules"))
onlyIf {
isNpmAvailable().also { available ->
if (!available) logger.warn("npm is not available; skipping ts-frontend install")
}
}
}

val buildTsFrontend = tasks.register<Exec>("buildTsFrontend") {
group = "build"
description = "Compiles the ts-frontend (TypeScript -> dist)."
dependsOn(installTsFrontend)
workingDir = tsFrontendDir
commandLine(npmExecutable, "run", "build")
inputs.dir(tsFrontendDir.resolve("src"))
inputs.files(tsFrontendDir.resolve("package.json"), tsFrontendDir.resolve("tsconfig.json"))
outputs.dir(tsFrontendDir.resolve("dist"))
onlyIf {
isNpmAvailable().also { available ->
if (!available) logger.warn("npm is not available; skipping ts-frontend build")
}
}
}

tasks.register<Exec>("testTsFrontend") {
group = "verification"
description = "Runs the ts-frontend unit tests (vitest)."
dependsOn(installTsFrontend)
workingDir = tsFrontendDir
commandLine(npmExecutable, "test")
onlyIf {
isNpmAvailable().also { available ->
if (!available) logger.warn("npm is not available; skipping ts-frontend tests")
}
}
}

tasks.test {
dependsOn(buildTsFrontend)
systemProperty("ets.frontend.dir", tsFrontendDir.absolutePath)
}

// ----------------------------------------------------------------------------
// Test resource generation
// ----------------------------------------------------------------------------

// Example usage:
// ```
// export ARKANALYZER_DIR=~/dev/arkanalyzer
// ./gradlew generateTestResources
// # or with the legacy ArkAnalyzer provider:
// export ARKANALYZER_DIR=~/dev/arkanalyzer
// ETS_IR_PROVIDER=arkanalyzer ./gradlew generateTestResources
// ```
tasks.register("generateTestResources") {
group = "build"
description = "Generates test resources from TypeScript files using ArkAnalyzer."
description = "Generates test resources (EtsIR JSON) from TypeScript sample files."
dependsOn(buildTsFrontend)
doLast {
println("Generating test resources using ArkAnalyzer...")
val provider = (System.getenv("ETS_IR_PROVIDER") ?: "ts-frontend").lowercase()
println("Generating test resources using provider: $provider")
val startTime = System.currentTimeMillis()

val envVarName = "ARKANALYZER_DIR"
val defaultArkAnalyzerDir = "arkanalyzer"

val arkAnalyzerDir = rootDir.resolve(System.getenv(envVarName) ?: run {
println("Please, set $envVarName environment variable. Using default value: '$defaultArkAnalyzerDir'")
defaultArkAnalyzerDir
})
if (!arkAnalyzerDir.exists()) {
throw FileNotFoundException(
"ArkAnalyzer directory does not exist: '${arkAnalyzerDir.absolutePath}'. " +
"Did you forget to set the '$envVarName' environment variable? " +
"Current value is '${System.getenv(envVarName)}', " +
"current dir is '${File("").absolutePath}'."
)
}
println("Using ArkAnalyzer directory: '${arkAnalyzerDir.relativeTo(rootDir)}'")

val scriptSubPath = "src/save/serializeArkIR"
val script = arkAnalyzerDir.resolve("out").resolve("$scriptSubPath.js")
if (!script.exists()) {
throw FileNotFoundException(
"Script file not found: '$script'. " +
"Did you forget to execute 'npm run build' in the arkanalyzer project?"
)
val script: File = when (provider) {
"arkanalyzer" -> {
val envVarName = "ARKANALYZER_DIR"
val arkAnalyzerDir = rootDir.resolve(System.getenv(envVarName) ?: "arkanalyzer")
if (!arkAnalyzerDir.exists()) {
throw FileNotFoundException(
"ArkAnalyzer directory does not exist: '${arkAnalyzerDir.absolutePath}'. " +
"Did you forget to set the '$envVarName' environment variable?"
)
}
arkAnalyzerDir.resolve("out/src/save/serializeArkIR.js").also {
if (!it.exists()) {
throw FileNotFoundException(
"Script file not found: '$it'. " +
"Did you forget to execute 'npm run build' in the arkanalyzer project?"
)
}
}
}

else -> tsFrontendDir.resolve("dist/index.js").also {
if (!it.exists()) {
throw FileNotFoundException(
"Script file not found: '$it'. " +
"Did you forget to execute 'npm run build' in ts-frontend?"
)
}
}
}
println("Using script: '${script.relativeTo(arkAnalyzerDir)}'")
println("Using script: '$script'")

val resources = projectDir.resolve("src/test/resources")
val inputDir = resources.resolve("samples/source")
Expand Down Expand Up @@ -87,8 +161,11 @@ tasks.register("generateTestResources") {
}

if (!ok) {
println("Timeout!")
process.destroy()
throw GradleException("Test resource generation timed out")
}
if (process.exitValue() != 0) {
throw GradleException("Test resource generation failed with exit code ${process.exitValue()}")
}

println("Done generating test resources in %.1fs".format((System.currentTimeMillis() - startTime) / 1000.0))
Expand Down
Loading
Loading