This is an automated email from the ASF dual-hosted git repository.
hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new fc022c6009 issue #2618 : allow selecting ECMAScript language level in
JavaScript transform (#7733)
fc022c6009 is described below
commit fc022c60093ac2ea500b8bcf19f36dbcf7a74441
Author: Matt Casters <[email protected]>
AuthorDate: Mon Aug 3 08:56:26 2026 +0200
issue #2618 : allow selecting ECMAScript language level in JavaScript
transform (#7733)
Expose Rhino language levels (DEFAULT, 1.5-1.8, ES6, ECMASCRIPT) in the
transform UI and metadata so users can use modern syntax such as let/const
while keeping older scripts on legacy levels. Default remains ES6 for
compatibility with existing pipelines.
---
.../ROOT/pages/pipeline/transforms/javascript.adoc | 47 +++++-
.../transforms/javascript/ScriptValues.java | 16 ++-
.../transforms/javascript/ScriptValuesDialog.java | 53 ++++++-
.../javascript/ScriptValuesEcmaVersion.java | 158 +++++++++++++++++++++
.../transforms/javascript/ScriptValuesMeta.java | 21 +++
.../javascript/messages/messages_en_US.properties | 12 ++
.../javascript/ScriptValuesEcmaVersionTest.java | 101 +++++++++++++
.../javascript/ScriptValuesMetaTest.java | 1 +
.../javascript/src/test/resources/javascript.xml | 1 +
9 files changed, 407 insertions(+), 3 deletions(-)
diff --git
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/javascript.adoc
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/javascript.adoc
index 738d3d8c30..30a9cfd6d5 100644
---
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/javascript.adoc
+++
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/javascript.adoc
@@ -127,9 +127,54 @@ The Javascript pane is the editing area for writing your
code. You can insert co
The position at the bottom of the Javascript panel displays the line number
and position of the cursor.
+The `ECMAScript level` selects the Mozilla Rhino language level used to
compile and run your scripts. The default is *ES6*, which matches the engine
default in recent Rhino releases and supports modern syntax such as `let`,
`const`, arrow functions, and template literals.
+
+Existing pipelines that do not store a language level continue to run at
*ES6*, so current behavior is preserved. Choose a legacy level only when a
script depends on older JavaScript semantics.
+
+NOTE: Support is limited to what the embedded Rhino engine implements. Some
modern features (for example `class` syntax or `async`/`await`) may still be
unavailable even at ES6 or ECMASCRIPT.
+
+=== A short history of the language levels
+
+JavaScript grew up in the browser under Netscape and later Mozilla.
+Before the language was primarily described by yearly *ECMAScript* editions
(ES5, ES6/ES2015, ES2016, …), Mozilla shipped numbered *JavaScript* releases
(1.5, 1.6, 1.7, 1.8) in SpiderMonkey—the engine that powered Firefox.
+
+*Rhino* is Mozilla’s Java implementation of that same language family.
+It was built so server-side and embedded applications (like Hop pipelines)
could run JavaScript on the JVM with tight Java interoperability.
+Rhino kept those historic language version switches so embedders could opt
into newer syntax without silently breaking older scripts.
+Hop exposes that choice as *ECMAScript level*.
+
+Rough timeline:
+
+* **JavaScript 1.0–1.4** — Early Netscape era (`var`, functions, prototypes,
basic objects). Hop lists levels starting at the more complete **1.5** baseline.
+* **JavaScript 1.5** (around 2000, Firefox 1.0 era) — Aligns with *ECMAScript
3*: exception-based error handling, richer regular expressions, `Number`
formatting helpers (`toFixed`, `toPrecision`, …), getters/setters, and other
ES3-era language work. This is the classic “pre-modern” scripting baseline many
long-lived scripts still assume.
+* **JavaScript 1.6** (around 2005, Firefox 1.5) — Functional-style *Array*
helpers: `indexOf`, `lastIndexOf`, `every`, `filter`, `forEach`, `map`, `some`,
plus Array/String generics. Also introduced *E4X* (ECMAScript for XML) and
related XML-oriented syntax that some older Hop samples still demonstrate.
+* **JavaScript 1.7** (around 2006, Firefox 2) — Block scoping with `let`,
iterators and generators (`yield`), early `for…of`-style iteration ideas, and
destructuring assignment. Several ideas here later became standard ECMAScript;
a few Mozilla-only experiments (such as array comprehensions) did not.
+* **JavaScript 1.8** (around 2008, Firefox 3) — Array reduction (`reduce`,
`reduceRight`) and more generator/expression-oriented features. Still the last
of the old “JavaScript 1.x” marketing line before the industry moved fully to
ES5/ES6 versioning.
+* **ES5** (2009) — Strict mode, `JSON`, `Object.create` / property
descriptors, and other library/language cleanups. Rhino’s numbered 1.x levels
predate calling this out as its own Hop combo entry; much of ES5 is available
when you move to modern levels.
+* **ES6 / ES2015 and later** — The big modern leap: proper `let`/`const`
semantics, arrow functions, template literals, classes (where implemented),
promises, modules, and a long tail of yearly ECMAScript features. Rhino models
this mainly as *VERSION_ES6* and, for the newest engine behavior,
*VERSION_ECMASCRIPT*.
+
+Why offer the old levels at all? Because language upgrades are not always
additive in practice.
+A script written for a looser 1.5-era engine can break under ES6 rules
(reserved words, stricter scoping, different edge-case semantics).
+Keeping **DEFAULT** and **1.5–1.8** available means you can modernize new
transforms with **ES6** while leaving battle-tested legacy scripts on the level
they were written for.
+
+=== Available levels
+
+[%header]
+|===
+|Level|When it appeared (approx.)|Notable capabilities
+|DEFAULT|Rhino legacy default|Odd historical “default” mode kept for
compatibility. Limited modern syntax (for example `let` is not available).
Prefer an explicit 1.x or ES6 level for new work.
+|1.5|~2000 (ES3 / JS 1.5)|`var`-centric scripts, ES3 exceptions and regex
features, classic `Number` formatting methods, getters/setters.
+|1.6|~2005|Array extras (`map`, `filter`, `forEach`, `some`, `every`,
`indexOf`, …), Array/String generics, E4X XML features where enabled.
+|1.7|~2006|`let`, generators/`yield`, iterator-style looping, destructuring
assignment (Mozilla-era forms).
+|1.8|~2008|`Array.reduce` / `reduceRight`, expression closures and further
generator-oriented features from that era.
+|ES6 (default)|ES2015+ (engine default since Rhino 1.8.0)|Modern syntax Rhino
implements: `let`/`const`, arrow functions, template literals, spread, and many
later library features. Default for new transforms and for pipelines that omit
the setting.
+|ECMASCRIPT|Ongoing|Latest ECMAScript level supported by the embedded Rhino
build. Tracks the engine’s newest work and may enable stricter checks than ES6.
+|===
+
+
The `Optimization level` selects the level of JavaScript optimization. The
values are:
-* 1: JavaScript runs in interpreted mode.
+* -1: JavaScript runs in interpreted mode.
* 0: No optimizations are performed.
* 1-9: All optimizations are performed. 9 performs the most optimization with
faster script execution, but compiles slower. The default is 9.
diff --git
a/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValues.java
b/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValues.java
index 8588ceba96..c9674a3204 100644
---
a/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValues.java
+++
b/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValues.java
@@ -183,9 +183,23 @@ public class ScriptValues extends
BaseTransform<ScriptValuesMeta, ScriptValuesDa
}
}
- // set the optimization level
+ // set language version and optimization level
data.context = ContextFactory.getGlobal().enterContext();
+ try {
+ String languageVersionAsString = resolve(meta.getLanguageVersion());
+ ScriptValuesEcmaVersion languageVersion =
+ ScriptValuesEcmaVersion.fromCode(languageVersionAsString);
+ languageVersion.applyTo(data.context);
+ if (isBasic()) {
+ logBasic(
+ BaseMessages.getString(
+ PKG, "ScriptValuesMod.LanguageVersion.Level",
languageVersion.getCode()));
+ }
+ } catch (HopException e) {
+ throw new HopTransformException(e.getMessage(), e);
+ }
+
try {
String optimizationLevelAsString =
resolve(meta.getOptimizationLevel());
if (!Utils.isEmpty(Const.trim(optimizationLevelAsString))) {
diff --git
a/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesDialog.java
b/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesDialog.java
index 7106a3c557..9da945a3e6 100644
---
a/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesDialog.java
+++
b/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesDialog.java
@@ -50,6 +50,7 @@ import org.apache.hop.ui.core.dialog.MessageDialogWithToggle;
import org.apache.hop.ui.core.dialog.PreviewRowsDialog;
import org.apache.hop.ui.core.gui.GuiResource;
import org.apache.hop.ui.core.widget.ColumnInfo;
+import org.apache.hop.ui.core.widget.ComboVar;
import org.apache.hop.ui.core.widget.HopTree;
import org.apache.hop.ui.core.widget.JavaScriptStyledTextComp;
import org.apache.hop.ui.core.widget.StyledTextComp;
@@ -162,6 +163,7 @@ public class ScriptValuesDialog extends BaseTransformDialog
{
private final ScriptValuesMeta input;
private ScriptValuesHelp scVHelp;
+ private ComboVar wLanguageVersion;
private TextVar wOptimizationLevel;
private TreeItem iteminput;
@@ -292,6 +294,30 @@ public class ScriptValuesDialog extends
BaseTransformDialog {
wOptimizationLevel.setLayoutData(fdOptimizationLevel);
wOptimizationLevel.addModifyListener(lsMod);
+ // ECMAScript language level (Rhino)
+ //
+ Label wlLanguageVersion = new Label(wTop, SWT.NONE);
+ wlLanguageVersion.setText(
+ BaseMessages.getString(PKG,
"ScriptValuesDialogMod.LanguageVersion.Label"));
+ PropsUi.setLook(wlLanguageVersion);
+ FormData fdlLanguageVersion = new FormData();
+ fdlLanguageVersion.left = new FormAttachment(wTree, margin);
+ fdlLanguageVersion.bottom = new FormAttachment(wOptimizationLevel,
-margin);
+ wlLanguageVersion.setLayoutData(fdlLanguageVersion);
+
+ wLanguageVersion = new ComboVar(variables, wTop, SWT.BORDER |
SWT.READ_ONLY);
+ wLanguageVersion.setEditable(true);
+ wLanguageVersion.setItems(ScriptValuesEcmaVersion.getDescriptions());
+ wLanguageVersion.setToolTipText(
+ BaseMessages.getString(PKG,
"ScriptValuesDialogMod.LanguageVersion.Tooltip"));
+ PropsUi.setLook(wLanguageVersion);
+ FormData fdLanguageVersion = new FormData();
+ fdLanguageVersion.left = new FormAttachment(wlLanguageVersion, margin);
+ fdLanguageVersion.top = new FormAttachment(wlLanguageVersion, 0,
SWT.CENTER);
+ fdLanguageVersion.right = new FormAttachment(100, margin);
+ wLanguageVersion.setLayoutData(fdLanguageVersion);
+ wLanguageVersion.addModifyListener(lsMod);
+
// The position just above that and below the script...
//
wlPosition = new Label(wTop, SWT.LEFT);
@@ -300,7 +326,7 @@ public class ScriptValuesDialog extends BaseTransformDialog
{
FormData fdlPosition = new FormData();
fdlPosition.left = new FormAttachment(wTree, margin);
fdlPosition.right = new FormAttachment(100, 0);
- fdlPosition.bottom = new FormAttachment(wOptimizationLevel, -margin);
+ fdlPosition.bottom = new FormAttachment(wLanguageVersion, -margin);
wlPosition.setLayoutData(fdlPosition);
folder = new CTabFolder(wTop, SWT.BORDER | SWT.RESIZE);
@@ -764,6 +790,19 @@ public class ScriptValuesDialog extends
BaseTransformDialog {
wOptimizationLevel.setText(ScriptValuesMeta.OPTIMIZATION_LEVEL_DEFAULT);
}
+ try {
+ ScriptValuesEcmaVersion languageVersion =
+ ScriptValuesEcmaVersion.fromCode(input.getLanguageVersion());
+ wLanguageVersion.setText(languageVersion.getDescription());
+ } catch (HopException e) {
+ // Keep the raw value so the user can correct it (or use a variable)
+ if (!Utils.isEmpty(input.getLanguageVersion())) {
+ wLanguageVersion.setText(input.getLanguageVersion().trim());
+ } else {
+ wLanguageVersion.setText(ScriptValuesEcmaVersion.ES6.getDescription());
+ }
+ }
+
for (int i = 0; i < input.getScriptFields().size(); i++) {
ScriptValuesMeta.ScriptField field = input.getScriptFields().get(i);
if (!Utils.isEmpty(field.getName())) {
@@ -866,6 +905,8 @@ public class ScriptValuesDialog extends BaseTransformDialog
{
private void getInfo(ScriptValuesMeta meta) {
meta.setOptimizationLevel(wOptimizationLevel.getText());
+ meta.setLanguageVersion(
+
ScriptValuesEcmaVersion.codeFromDescription(wLanguageVersion.getText()));
meta.getScriptFields().clear();
for (TableItem item : wFields.getNonEmptyItems()) {
@@ -1146,6 +1187,15 @@ public class ScriptValuesDialog extends
BaseTransformDialog {
jscx = ContextFactory.getGlobal().enterContext();
jscx.setOptimizationLevel(-1);
+ try {
+ ScriptValuesEcmaVersion.apply(
+ jscx,
+ variables.resolve(
+
ScriptValuesEcmaVersion.codeFromDescription(wLanguageVersion.getText())));
+ } catch (HopException e) {
+ testException = e;
+ retval = false;
+ }
jsscope = jscx.initStandardObjects(null, false);
// Adding the existing Scripts to the Context
@@ -1851,6 +1901,7 @@ public class ScriptValuesDialog extends
BaseTransformDialog {
int lineno,
Object securityDomain) {
CompilerEnvirons evn = new CompilerEnvirons();
+ evn.initFromContext(cx);
evn.setOptimizationLevel(-1);
evn.setGeneratingSource(true);
evn.setGenerateDebugInfo(true);
diff --git
a/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesEcmaVersion.java
b/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesEcmaVersion.java
new file mode 100644
index 0000000000..823da6a6de
--- /dev/null
+++
b/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesEcmaVersion.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.pipeline.transforms.javascript;
+
+import java.util.Arrays;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.util.Utils;
+import org.apache.hop.i18n.BaseMessages;
+import org.mozilla.javascript.Context;
+
+/**
+ * Supported Mozilla Rhino ECMAScript / JavaScript language levels for the
JavaScript transform.
+ *
+ * <p>Stable codes are stored in pipeline XML. The engine default used by
recent Rhino releases (and
+ * by this transform when the field is empty) is {@link #ES6}.
+ */
+public enum ScriptValuesEcmaVersion {
+ /** Legacy Rhino default; limited modern syntax (for example no {@code
let}). */
+ DEFAULT("DEFAULT", Context.VERSION_DEFAULT),
+ JS_1_5("1.5", Context.VERSION_1_5),
+ JS_1_6("1.6", Context.VERSION_1_6),
+ JS_1_7("1.7", Context.VERSION_1_7),
+ JS_1_8("1.8", Context.VERSION_1_8),
+ /**
+ * ECMAScript 6 and later features implemented by Rhino. Default for new
transforms and for Rhino
+ * itself since 1.8.
+ */
+ ES6("ES6", Context.VERSION_ES6),
+ /**
+ * Latest ECMAScript level supported by the embedded Rhino engine (may
enable stricter checks than
+ * {@link #ES6}).
+ */
+ ECMASCRIPT("ECMASCRIPT", Context.VERSION_ECMASCRIPT);
+
+ private static final Class<?> PKG = ScriptValuesMeta.class;
+
+ /** Stable code stored in metadata / XML for new transforms (matches Rhino's
default). */
+ public static final String DEFAULT_CODE = ES6.code;
+
+ private final String code;
+ private final int rhinoVersion;
+
+ ScriptValuesEcmaVersion(String code, int rhinoVersion) {
+ this.code = code;
+ this.rhinoVersion = rhinoVersion;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public int getRhinoVersion() {
+ return rhinoVersion;
+ }
+
+ /** Localized UI label for this level. */
+ public String getDescription() {
+ return BaseMessages.getString(PKG, "ScriptValuesMod.LanguageVersion." +
code);
+ }
+
+ /** All localized descriptions in enum order (for combo boxes). */
+ public static String[] getDescriptions() {
+ return Arrays.stream(values())
+ .map(ScriptValuesEcmaVersion::getDescription)
+ .toArray(String[]::new);
+ }
+
+ /** Codes only, for tests and non-UI lists. */
+ public static String[] getCodes() {
+ return Arrays.stream(values()).map(v -> v.code).toArray(String[]::new);
+ }
+
+ /**
+ * Resolve a stored code, description, or numeric Rhino version to a known
level. Empty input maps
+ * to {@link #ES6} (engine default / compatible with existing pipelines on
recent Rhino).
+ */
+ public static ScriptValuesEcmaVersion fromCode(String value) throws
HopException {
+ if (Utils.isEmpty(value) || Utils.isEmpty(value.trim())) {
+ return ES6;
+ }
+ String trimmed = value.trim();
+
+ for (ScriptValuesEcmaVersion version : values()) {
+ if (version.code.equalsIgnoreCase(trimmed)) {
+ return version;
+ }
+ }
+
+ for (ScriptValuesEcmaVersion version : values()) {
+ if (version.getDescription().equals(trimmed)) {
+ return version;
+ }
+ }
+
+ // Numeric Rhino language version (e.g. 200, 250)
+ try {
+ int numeric = Integer.parseInt(trimmed);
+ for (ScriptValuesEcmaVersion version : values()) {
+ if (version.rhinoVersion == numeric) {
+ return version;
+ }
+ }
+ } catch (NumberFormatException ignored) {
+ // fall through to error
+ }
+
+ throw new HopException(
+ BaseMessages.getString(
+ PKG, "ScriptValuesMetaMod.Exception.UnsupportedLanguageVersion",
trimmed));
+ }
+
+ /**
+ * Map a UI description back to the stable storage code. Falls back to the
text itself when it is
+ * already a code or a variable expression.
+ */
+ public static String codeFromDescription(String description) {
+ if (Utils.isEmpty(description)) {
+ return DEFAULT_CODE;
+ }
+ String trimmed = description.trim();
+ for (ScriptValuesEcmaVersion version : values()) {
+ if (version.getDescription().equals(trimmed) ||
version.code.equalsIgnoreCase(trimmed)) {
+ return version.code;
+ }
+ }
+ return trimmed;
+ }
+
+ /** Apply this language level to a Rhino {@link Context}. */
+ public void applyTo(Context context) {
+ context.setLanguageVersion(rhinoVersion);
+ }
+
+ /**
+ * Resolve {@code languageVersion} (code or description) and set it on the
context.
+ *
+ * @param context Rhino context
+ * @param languageVersion language version code/description (already
variable-resolved)
+ */
+ public static void apply(Context context, String languageVersion) throws
HopException {
+ fromCode(languageVersion).applyTo(context);
+ }
+}
diff --git
a/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesMeta.java
b/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesMeta.java
index 2b0ee74ef6..5618b4f50e 100644
---
a/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesMeta.java
+++
b/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesMeta.java
@@ -63,6 +63,9 @@ public class ScriptValuesMeta extends
BaseTransformMeta<ScriptValues, ScriptValu
public static final String OPTIMIZATION_LEVEL_DEFAULT = "9";
+ /** Default ECMAScript language level code ({@link
ScriptValuesEcmaVersion#DEFAULT_CODE}). */
+ public static final String LANGUAGE_VERSION_DEFAULT =
ScriptValuesEcmaVersion.DEFAULT_CODE;
+
@Getter
@Setter
public static class ScriptField {
@@ -135,11 +138,22 @@ public class ScriptValuesMeta extends
BaseTransformMeta<ScriptValues, ScriptValu
injectionKeyDescription = "ScriptValuesMod.Injection.OPTIMIZATION_LEVEL")
private String optimizationLevel;
+ /**
+ * Rhino ECMAScript language level code (see {@link
ScriptValuesEcmaVersion}). Empty or missing
+ * values use {@link #LANGUAGE_VERSION_DEFAULT} (ES6), matching Rhino's
current engine default.
+ */
+ @HopMetadataProperty(
+ key = "languageVersion",
+ injectionKey = "LANGUAGE_VERSION",
+ injectionKeyDescription = "ScriptValuesMod.Injection.LANGUAGE_VERSION")
+ private String languageVersion;
+
public ScriptValuesMeta() {
super();
jsScripts = new ArrayList<>();
scriptFields = new ArrayList<>();
optimizationLevel = OPTIMIZATION_LEVEL_DEFAULT;
+ languageVersion = LANGUAGE_VERSION_DEFAULT;
ScriptValuesScript script = new ScriptValuesScript();
script.setType(ScriptValuesScript.TRANSFORM_SCRIPT);
@@ -152,6 +166,7 @@ public class ScriptValuesMeta extends
BaseTransformMeta<ScriptValues, ScriptValu
public ScriptValuesMeta(ScriptValuesMeta m) {
this();
this.optimizationLevel = m.optimizationLevel;
+ this.languageVersion = m.languageVersion;
m.jsScripts.forEach(s -> this.jsScripts.add(new ScriptValuesScript(s)));
m.scriptFields.forEach(f -> scriptFields.add(new ScriptField(f)));
}
@@ -248,6 +263,12 @@ public class ScriptValuesMeta extends
BaseTransformMeta<ScriptValues, ScriptValu
jsContext = ContextFactory.getGlobal().enterContext();
jsScope = jsContext.initStandardObjects(null, false);
+ try {
+ ScriptValuesEcmaVersion.apply(jsContext,
variables.resolve(languageVersion));
+ } catch (HopException e) {
+ cr = new CheckResult(ICheckResult.TYPE_RESULT_ERROR, e.getMessage(),
transformMeta);
+ remarks.add(cr);
+ }
try {
jsContext.setOptimizationLevel(Integer.parseInt(variables.resolve(optimizationLevel)));
} catch (NumberFormatException nfe) {
diff --git
a/plugins/transforms/javascript/src/main/resources/org/apache/hop/pipeline/transforms/javascript/messages/messages_en_US.properties
b/plugins/transforms/javascript/src/main/resources/org/apache/hop/pipeline/transforms/javascript/messages/messages_en_US.properties
index c1c06d5c3f..80984b56f7 100644
---
a/plugins/transforms/javascript/src/main/resources/org/apache/hop/pipeline/transforms/javascript/messages/messages_en_US.properties
+++
b/plugins/transforms/javascript/src/main/resources/org/apache/hop/pipeline/transforms/javascript/messages/messages_en_US.properties
@@ -46,6 +46,8 @@ ScriptValuesDialogMod.JavascriptFunctions.Label=Java script
functions \:
ScriptValuesDialogMod.LogicFunctions.Label=Logic Functions
ScriptValuesDialogMod.NoActiveScriptSet=No active Script has been set\! Should
the first tab set as active Script?
ScriptValuesDialogMod.NumericFunctions.Label=Numeric Functions
+ScriptValuesDialogMod.LanguageVersion.Label=ECMAScript level
+ScriptValuesDialogMod.LanguageVersion.Tooltip=Rhino JavaScript language level.
ES6 (default) supports let, const, arrow functions, template literals and more.
Choose a legacy level (DEFAULT, 1.5-1.8) for older scripts that rely on pre-ES6
behavior. ECMASCRIPT tracks the latest features supported by the embedded Rhino
engine.
ScriptValuesDialogMod.OptimizationLevel.Label=Optimization level
ScriptValuesDialogMod.OptimizationLevel.Tooltip=Valid values are -1 for
interpretive mode, 0 for no optimizations, and a value from 1 - 9 where 9
specifies maximal optimization
ScriptValuesDialogMod.OutputFields.Label=Output fields
@@ -82,6 +84,7 @@ ScriptValuesMetaMod.CheckResult.ScriptCompiledOK=Script
compiled without a probl
ScriptValuesMetaMod.CheckResult.ScriptCompiledOK2=Script executed without a
problem
ScriptValuesMetaMod.Exception.FieldToReplaceNotFound=Field to replace [{0}]
was not found in the transform input fields list.
ScriptValuesMetaMod.Exception.NumberFormatException=Optimization level: Could
not convert "{0}" to a number.
+ScriptValuesMetaMod.Exception.UnsupportedLanguageVersion=Unsupported
ECMAScript language level "{0}". Valid values are DEFAULT, 1.5, 1.6, 1.7, 1.8,
ES6, ECMASCRIPT (or the corresponding Rhino version numbers 0, 150, 160, 170,
180, 200, 250).
ScriptValuesMetaMod.Exception.UnableToLoadAdditionalClass=Unable to load
additional classes
ScriptValuesMetaMod.Exception.UnableToLoadTransformMetaFromXML=Unable to load
transform info from XML
ScriptValuesMetaMod.Exception.UnableToParseXMLforAdditionalClasses=Unable to
parse XML for additional classes (future feature)
@@ -94,11 +97,20 @@ ScriptValuesMod.Injection.FIELD_RENAME_TO=The name that the
output field will be
ScriptValuesMod.Injection.FIELD_REPLACE=This setting controls the behavior of
adding or replacing fields.
ScriptValuesMod.Injection.FIELD_TYPE=The field type.
ScriptValuesMod.Injection.FIELDS=The output fields to write to.
+ScriptValuesMod.Injection.LANGUAGE_VERSION=Rhino ECMAScript language level
(DEFAULT, 1.5, 1.6, 1.7, 1.8, ES6, ECMASCRIPT).
ScriptValuesMod.Injection.OPTIMIZATION_LEVEL=Specify the level of optimization.
ScriptValuesMod.Injection.SCRIPT=The java script statement.
ScriptValuesMod.Injection.SCRIPT_TYPE=The type of script: -1=normal,
0=transform, 1=start, 2=end
ScriptValuesMod.Injection.SCRIPT_NAME=The name of the script.
ScriptValuesMod.Injection.SCRIPTS=The java scripts.
+ScriptValuesMod.LanguageVersion.DEFAULT=DEFAULT - Legacy Rhino (limited modern
syntax)
+ScriptValuesMod.LanguageVersion.1.5=1.5 - JavaScript 1.5
+ScriptValuesMod.LanguageVersion.1.6=1.6 - JavaScript 1.6
+ScriptValuesMod.LanguageVersion.1.7=1.7 - JavaScript 1.7
+ScriptValuesMod.LanguageVersion.1.8=1.8 - JavaScript 1.8
+ScriptValuesMod.LanguageVersion.ES6=ES6 - ECMAScript 6+ (let, const, arrows,
...) [default]
+ScriptValuesMod.LanguageVersion.ECMASCRIPT=ECMASCRIPT - Latest supported by
Rhino
+ScriptValuesMod.LanguageVersion.Level=ECMAScript language level set to {0}.
ScriptValuesMod.Log.CouldNotAddDefaultConstants=Could not add default
constants to the project.
ScriptValuesMod.Log.CouldNotAddDefaultFunctions=Could not add default
functions to the project.
ScriptValuesMod.Log.CouldNotAttachAdditionalScripts=Could not attach
additional scripts to the project.
diff --git
a/plugins/transforms/javascript/src/test/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesEcmaVersionTest.java
b/plugins/transforms/javascript/src/test/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesEcmaVersionTest.java
new file mode 100644
index 0000000000..b0090b0820
--- /dev/null
+++
b/plugins/transforms/javascript/src/test/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesEcmaVersionTest.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hop.pipeline.transforms.javascript;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.hop.core.exception.HopException;
+import org.junit.jupiter.api.Test;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.ContextFactory;
+import org.mozilla.javascript.Scriptable;
+
+class ScriptValuesEcmaVersionTest {
+
+ @Test
+ void emptyDefaultsToEs6() throws Exception {
+ assertEquals(ScriptValuesEcmaVersion.ES6,
ScriptValuesEcmaVersion.fromCode(null));
+ assertEquals(ScriptValuesEcmaVersion.ES6,
ScriptValuesEcmaVersion.fromCode(""));
+ assertEquals(ScriptValuesEcmaVersion.ES6,
ScriptValuesEcmaVersion.fromCode(" "));
+ }
+
+ @Test
+ void resolvesCodesCaseInsensitively() throws Exception {
+ assertEquals(ScriptValuesEcmaVersion.ES6,
ScriptValuesEcmaVersion.fromCode("es6"));
+ assertEquals(
+ ScriptValuesEcmaVersion.ECMASCRIPT,
ScriptValuesEcmaVersion.fromCode("ECMASCRIPT"));
+ assertEquals(ScriptValuesEcmaVersion.DEFAULT,
ScriptValuesEcmaVersion.fromCode("DEFAULT"));
+ assertEquals(ScriptValuesEcmaVersion.JS_1_7,
ScriptValuesEcmaVersion.fromCode("1.7"));
+ }
+
+ @Test
+ void resolvesNumericRhinoVersions() throws Exception {
+ assertEquals(ScriptValuesEcmaVersion.ES6,
ScriptValuesEcmaVersion.fromCode("200"));
+ assertEquals(ScriptValuesEcmaVersion.ECMASCRIPT,
ScriptValuesEcmaVersion.fromCode("250"));
+ assertEquals(ScriptValuesEcmaVersion.DEFAULT,
ScriptValuesEcmaVersion.fromCode("0"));
+ }
+
+ @Test
+ void rejectsUnknownValues() {
+ assertThrows(HopException.class, () ->
ScriptValuesEcmaVersion.fromCode("ES2024"));
+ assertThrows(HopException.class, () ->
ScriptValuesEcmaVersion.fromCode("999"));
+ }
+
+ @Test
+ void codeFromDescriptionRoundTrips() {
+ for (ScriptValuesEcmaVersion version : ScriptValuesEcmaVersion.values()) {
+ assertEquals(
+ version.getCode(),
ScriptValuesEcmaVersion.codeFromDescription(version.getCode()));
+ }
+ }
+
+ @Test
+ void es6AllowsLetAndConst() throws Exception {
+ Context cx = ContextFactory.getGlobal().enterContext();
+ try {
+ ScriptValuesEcmaVersion.ES6.applyTo(cx);
+ Scriptable scope = cx.initStandardObjects();
+ Object result = cx.evaluateString(scope, "let a = 1; const b = 2; a +
b", "t", 1, null);
+ assertEquals(3.0, Context.toNumber(result), 0.0001);
+ } finally {
+ Context.exit();
+ }
+ }
+
+ @Test
+ void legacyDefaultRejectsLet() throws Exception {
+ Context cx = ContextFactory.getGlobal().enterContext();
+ try {
+ ScriptValuesEcmaVersion.DEFAULT.applyTo(cx);
+ Scriptable scope = cx.initStandardObjects();
+ assertThrows(Exception.class, () -> cx.evaluateString(scope, "let a = 1;
a", "t", 1, null));
+ } finally {
+ Context.exit();
+ }
+ }
+
+ @Test
+ void descriptionsAreNonEmpty() {
+ String[] descriptions = ScriptValuesEcmaVersion.getDescriptions();
+ assertEquals(ScriptValuesEcmaVersion.values().length, descriptions.length);
+ for (String description : descriptions) {
+ assertTrue(description != null && !description.isBlank());
+ }
+ }
+}
diff --git
a/plugins/transforms/javascript/src/test/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesMetaTest.java
b/plugins/transforms/javascript/src/test/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesMetaTest.java
index 018d10a9ec..57a8c78090 100644
---
a/plugins/transforms/javascript/src/test/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesMetaTest.java
+++
b/plugins/transforms/javascript/src/test/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesMetaTest.java
@@ -54,6 +54,7 @@ class ScriptValuesMetaTest {
// See if after all that we still have the correct data
//
assertEquals("2", meta.getOptimizationLevel());
+ assertEquals("ES6", meta.getLanguageVersion());
// The scripts
assertEquals(3, meta.getJsScripts().size());
diff --git a/plugins/transforms/javascript/src/test/resources/javascript.xml
b/plugins/transforms/javascript/src/test/resources/javascript.xml
index 964c6d5e31..2b0fe35b2f 100644
--- a/plugins/transforms/javascript/src/test/resources/javascript.xml
+++ b/plugins/transforms/javascript/src/test/resources/javascript.xml
@@ -19,6 +19,7 @@
<transform>
<optimizationLevel>2</optimizationLevel>
+ <languageVersion>ES6</languageVersion>
<jsScripts>
<jsScript>
<jsScript_type>1</jsScript_type>