GraalJS Compatibility #156 - #157
Conversation
Nashorn mapped a property write on a Java host object to its setter (bean.foo = x -> setFoo(x)); GraalJS does not, so every such write now throws "TypeError: writeMember ... Unknown identifier" at runtime. Replace all 38 property writes in lib/text-encoding.js with the matching setter calls, each verified against its handler class. Nashorn also coerced number/boolean values to String when writing a String-typed property; the plain setter call does not, which broke the existing urlEscape(33)/urlEscape(true) tests. Feed the text setters a null-safe String(...) so that coercion is preserved on both engines. Add a testGraalJs task (org.graalvm.polyglot:js) wired into check that runs the existing suite under the GraalJS engine, mirroring the platform's gradle/js-tests.gradle. Requires xpVersion=8.1.0-SNAPSHOT from the dev repo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
CI ran on
That 82/82 is the pre-#12198 signature: the |
com.enonic.xp 4.2.0 generates the per-engine test tasks from xp.scriptEngines, and testing:8.1.0-SNAPSHOT brings the GraalJS engine transitively, so the hand-rolled testGraalJs task and the graalvm.js dependency are no longer needed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PinKDCEKkM3QeGTnSYQ5zg
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #157 +/- ##
=========================================
Coverage 96.57% 96.57%
Complexity 75 75
=========================================
Files 11 11
Lines 146 146
Branches 4 4
=========================================
Hits 141 141
Partials 5 5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Addresses review: the explicit String(text) coercion is unnecessary and the simpler __.nullOrValue(text) mirrors how charset is already passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PinKDCEKkM3QeGTnSYQ5zg
__.nullOrValue(text) breaks testUrlEscapeBool/testUrlEscapeNumber under GraalJS: unlike Nashorn, GraalJS will not coerce a JS boolean/number into the Java String parameter of setText, so the explicit String(text) is required. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PinKDCEKkM3QeGTnSYQ5zg
The *Decode functions take an encoded string by contract, so the explicit String() coercion is unnecessary there (per review). The escape/unescape functions and charsetEncode keep it: they accept arbitrary values, and GraalJS will not coerce a JS number/boolean into the Java String setter (see testUrlEscapeBool/testUrlEscapeNumber). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PinKDCEKkM3QeGTnSYQ5zg
The released 3.0.0 uses Nashorn-style bean property assignment (bean.stream = stream) which GraalJS rejects, failing the two-step login tests under testGraalJS. The 3.0.0-SNAPSHOT build carries the setter-based fix (enonic/lib-text-encoding#157). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PinKDCEKkM3QeGTnSYQ5zg
* GraalJS Compatibility #102 Nashorn maps `bean.email = x` to `setEmail(x)`; GraalJS does not (the nashorn-compat shorthand was removed in enonic/xp#9236), so the property write in lib/gravatar.js throws `TypeError: writeMember (email) ... Unknown identifier: email` at runtime. Call the setter instead. Add the dual-engine test setup (testRuntimeOnly org.graalvm.polyglot:js and a testGraalJs task wired into check), a script-level test that exercises gravatar.hash on both engines, and bump xpVersion to 8.1.0-SNAPSHOT (the ScriptRunnerSupport fix from enonic/xp#12198 that testGraalJs needs). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Use plugin script-engine matrix instead of custom GraalJS harness com.enonic.xp 4.2.0 generates the per-engine test tasks from xp.scriptEngines, and testing:8.1.0-SNAPSHOT brings the GraalJS engine transitively, so the hand-rolled testGraalJs task and the graalvm.js dependency are no longer needed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PinKDCEKkM3QeGTnSYQ5zg * Use lib-text-encoding 3.0.0-SNAPSHOT for GraalJS compatibility The released 3.0.0 uses Nashorn-style bean property assignment (bean.stream = stream) which GraalJS rejects, failing the two-step login tests under testGraalJS. The 3.0.0-SNAPSHOT build carries the setter-based fix (enonic/lib-text-encoding#157). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PinKDCEKkM3QeGTnSYQ5zg --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes #156.
What
Nashorn maps a property write on a Java host object to its setter (
bean.foo = x→setFoo(x)); GraalJS does not — that shorthand went away with nashorn-compat mode (enonic/xp#9236). Every such write throwsTypeError: writeMember (foo) ... Unknown identifier: fooat runtime.This replaces all 38 property writes listed in the issue with the matching setter calls in
src/main/resources/lib/text-encoding.js, each verified against its handler class:bean.stream = …bean.setStream(…)bean.text = …bean.setText(…)bean.charset = …bean.setCharset(…)charsetName, setter issetCharset)bean.key = …bean.setKey(…)setStreamtakesObject(the Java side converts strings/numbers/booleans), so the stream sites pass the value straight through.setKeyis guarded by an explicitkey === undefinedthrow, so it needs no null wrap.String coercion for
setTextOne nuance beyond the mechanical setter conversion: Nashorn also coerced number/boolean values to
Stringwhen writing aString-typed property, sourlEscape(33)→"33"andurlEscape(true)→"true". The plainsetText(33)call does not — GraalJS rejects it with "Cannot convert '33' (…) to Java type 'java.lang.String': Invalid or lossy primitive coercion" — which broke the pre-existingtestUrlEscapeNumber/testUrlEscapeBooltests.To preserve that behaviour on both engines, the
textsetters receive a null-safeString(...):This is a superset of the
__.nullOrValuethe issue prescribes — sameundefined/null→nullhandling, plus the coercion Nashorn's property write used to perform. Thecharsetsetter keeps__.nullOrValue(charset)(a charset is a name, never a non-string).Test on both engines
Adds a
testGraalJstask (mirroring the platform'sgradle/js-tests.gradle) that runs the existing suite under the GraalJS engine, wired intocheck. It needs theScriptRunnerSupportfix from enonic/xp#12198, soxpVersionis bumped to8.1.0-SNAPSHOTand the repo is switched toxp.enonicRepo('dev').Local verification
Built the platform branch
enonic/xp@claude/graaljs-support-limitations-pzu6z8(which carries #12198) tomavenLocal, addedmavenLocal()locally, then ran./gradlew test testGraalJs:testGraalJsfails, e.g.PolyglotException at .../text-encoding.js:18test(Nashorn): 82 tests, 0 failedtestGraalJs(GraalJS): 82 tests, 0 failedCI status
testGraalJswill stay red in CI until enonic/xp#12198 is merged and a fresh8.1.0-SNAPSHOTis published. The snapshot currently onrepo.enonic.com/devstill carries the oldScriptRunnerSupport, which closes the script executor during test discovery. This PR is kept draft until then; the Nashorntesttask stays green.