From 2d2693e04ea7f8a699ca20b151292ce05f1bc2bb Mon Sep 17 00:00:00 2001 From: Ronald Brill Date: Mon, 26 Feb 2024 11:16:01 +0100 Subject: [PATCH] initial implementation of function rest parameter support (arrow functions are not supported as of now) --- src/org/mozilla/javascript/CodeGenerator.java | 1 + src/org/mozilla/javascript/Decompiler.java | 4 + src/org/mozilla/javascript/IRFactory.java | 10 +- .../javascript/InterpretedFunction.java | 3 + src/org/mozilla/javascript/Interpreter.java | 20 ++ .../mozilla/javascript/InterpreterData.java | 1 + src/org/mozilla/javascript/Parser.java | 27 ++ src/org/mozilla/javascript/ScriptRuntime.java | 27 ++ src/org/mozilla/javascript/Token.java | 3 +- src/org/mozilla/javascript/TokenStream.java | 4 + .../mozilla/javascript/ast/FunctionNode.java | 10 + .../mozilla/javascript/ast/ScriptNode.java | 4 + .../javascript/optimizer/BodyCodegen.java | 40 ++- .../mozilla/javascript/optimizer/Codegen.java | 19 +- .../javascript/resources/Messages.properties | 3 + .../resources/Messages_fr.properties | 3 + .../es6/FunctionsRestParametersTest.java | 234 ++++++++++++++++++ testsrc/test262.properties | 9 +- 18 files changed, 397 insertions(+), 25 deletions(-) create mode 100644 testsrc/org/mozilla/javascript/tests/es6/FunctionsRestParametersTest.java diff --git a/src/org/mozilla/javascript/CodeGenerator.java b/src/org/mozilla/javascript/CodeGenerator.java index 9093ab0af2..095406b970 100644 --- a/src/org/mozilla/javascript/CodeGenerator.java +++ b/src/org/mozilla/javascript/CodeGenerator.java @@ -186,6 +186,7 @@ private void generateICodeFromTree(Node tree) { itsData.argNames = scriptOrFn.getParamAndVarNames(); itsData.argIsConst = scriptOrFn.getParamAndVarConst(); itsData.argCount = scriptOrFn.getParamCount(); + itsData.argsHasRest = scriptOrFn.hasRestParameter(); itsData.encodedSourceStart = scriptOrFn.getEncodedSourceStart(); itsData.encodedSourceEnd = scriptOrFn.getEncodedSourceEnd(); diff --git a/src/org/mozilla/javascript/Decompiler.java b/src/org/mozilla/javascript/Decompiler.java index cbd5ab63df..709e49cb78 100644 --- a/src/org/mozilla/javascript/Decompiler.java +++ b/src/org/mozilla/javascript/Decompiler.java @@ -782,6 +782,10 @@ else if (nextToken == Token.NAME) { i = printSourceString(source, i + 1, false, result); continue; + case Token.DOTDOTDOT: + result.append("..."); + break; + default: // If we don't know how to decompile it, raise an exception. throw new RuntimeException("Token: " + Token.name(source.charAt(i))); diff --git a/src/org/mozilla/javascript/IRFactory.java b/src/org/mozilla/javascript/IRFactory.java index b0ff9559eb..ce77106b2f 100644 --- a/src/org/mozilla/javascript/IRFactory.java +++ b/src/org/mozilla/javascript/IRFactory.java @@ -2450,11 +2450,15 @@ Node decompileFunctionHeader(FunctionNode fn) { decompiler.addToken(Token.LP); } List params = fn.getParams(); - for (int i = 0; i < params.size(); i++) { - decompile(params.get(i)); - if (i < params.size() - 1) { + int last = params.size() - 1; + for (int i = 0; i <= last; i++) { + if (i > 0) { decompiler.addToken(Token.COMMA); } + if (fn.hasRestParameter() && i == last) { + decompiler.addToken(Token.DOTDOTDOT); + } + decompile(params.get(i)); } if (!noParen) { decompiler.addToken(Token.RP); diff --git a/src/org/mozilla/javascript/InterpretedFunction.java b/src/org/mozilla/javascript/InterpretedFunction.java index aeb660ba01..f72608f337 100644 --- a/src/org/mozilla/javascript/InterpretedFunction.java +++ b/src/org/mozilla/javascript/InterpretedFunction.java @@ -133,6 +133,9 @@ protected int getLanguageVersion() { @Override protected int getParamCount() { + if (idata.argsHasRest) { + return idata.argCount - 1; + } return idata.argCount; } diff --git a/src/org/mozilla/javascript/Interpreter.java b/src/org/mozilla/javascript/Interpreter.java index af904e2686..96e75d0fda 100644 --- a/src/org/mozilla/javascript/Interpreter.java +++ b/src/org/mozilla/javascript/Interpreter.java @@ -188,6 +188,26 @@ void initializeArgs( for (int i = definedArgs; i != idata.itsMaxVars; ++i) { stack[i] = Undefined.instance; } + + if (idata.argsHasRest) { + Object[] vals; + int offset = idata.argCount - 1; + if (argCount >= idata.argCount) { + vals = new Object[argCount - offset]; + + argShift = argShift + offset; + for (int valsIdx = 0; valsIdx != vals.length; ++argShift, ++valsIdx) { + Object val = args[argShift]; + if (val == UniqueTag.DOUBLE_MARK) { + val = ScriptRuntime.wrapNumber(argsDbl[argShift]); + } + vals[valsIdx] = val; + } + } else { + vals = ScriptRuntime.emptyArgs; + } + stack[offset] = cx.newArray(scope, vals); + } } CallFrame cloneFrozen() { diff --git a/src/org/mozilla/javascript/InterpreterData.java b/src/org/mozilla/javascript/InterpreterData.java index 8e95210d0f..9551dabf9d 100644 --- a/src/org/mozilla/javascript/InterpreterData.java +++ b/src/org/mozilla/javascript/InterpreterData.java @@ -68,6 +68,7 @@ private void init() { String[] argNames; boolean[] argIsConst; int argCount; + boolean argsHasRest; int itsMaxCalleeArgs; diff --git a/src/org/mozilla/javascript/Parser.java b/src/org/mozilla/javascript/Parser.java index 9cc30fe574..8a31a212a4 100644 --- a/src/org/mozilla/javascript/Parser.java +++ b/src/org/mozilla/javascript/Parser.java @@ -787,10 +787,20 @@ private void parseFunctionParams(FunctionNode fnNode) throws IOException { do { int tt = peekToken(); if (tt == Token.RP) { + if (fnNode.hasRestParameter()) { + // Error: parameter after rest parameter + reportError("msg.parm.after.rest", ts.tokenBeg, ts.tokenEnd - ts.tokenBeg); + } + fnNode.putIntProp(Node.TRAILING_COMMA, 1); break; } if (tt == Token.LB || tt == Token.LC) { + if (fnNode.hasRestParameter()) { + // Error: parameter after rest parameter + reportError("msg.parm.after.rest", ts.tokenBeg, ts.tokenEnd - ts.tokenBeg); + } + AstNode expr = destructuringPrimaryExpr(); markDestructuring(expr); fnNode.addParam(expr); @@ -804,7 +814,24 @@ private void parseFunctionParams(FunctionNode fnNode) throws IOException { defineSymbol(Token.LP, pname, false); destructuring.put(pname, expr); } else { + boolean wasRest = false; + if (tt == Token.DOTDOTDOT) { + if (fnNode.hasRestParameter()) { + // Error: parameter after rest parameter + reportError("msg.parm.after.rest", ts.tokenBeg, ts.tokenEnd - ts.tokenBeg); + } + + fnNode.setHasRestParameter(true); + wasRest = true; + consumeToken(); + } + if (mustMatchToken(Token.NAME, "msg.no.parm", true)) { + if (!wasRest && fnNode.hasRestParameter()) { + // Error: parameter after rest parameter + reportError("msg.parm.after.rest", ts.tokenBeg, ts.tokenEnd - ts.tokenBeg); + } + Name paramNameNode = createNameNode(); Comment jsdocNodeForName = getAndResetJsDoc(); if (jsdocNodeForName != null) { diff --git a/src/org/mozilla/javascript/ScriptRuntime.java b/src/org/mozilla/javascript/ScriptRuntime.java index 2a02469156..95da2fbb64 100644 --- a/src/org/mozilla/javascript/ScriptRuntime.java +++ b/src/org/mozilla/javascript/ScriptRuntime.java @@ -876,6 +876,33 @@ public static Object[] padArguments(Object[] args, int count) { return result; } + /** + * Helper function for builtin objects that use the varargs form. ECMA function formal arguments + * are undefined if not supplied; this function pads the argument array out to the expected + * length, if necessary. Also the rest parameter array construction is done here. + */ + public static Object[] padAndRestArguments( + Context cx, Scriptable scope, Object[] args, int argCount) { + Object[] result = new Object[argCount]; + int paramCount = argCount - 1; + if (args.length < paramCount) { + System.arraycopy(args, 0, result, 0, args.length); + Arrays.fill(result, args.length, paramCount, Undefined.instance); + } else { + System.arraycopy(args, 0, result, 0, paramCount); + } + + Object[] restValues; + if (args.length > paramCount) { + restValues = new Object[args.length - paramCount]; + System.arraycopy(args, paramCount, restValues, 0, restValues.length); + } else { + restValues = ScriptRuntime.emptyArgs; + } + result[paramCount] = cx.newArray(scope, restValues); + return result; + } + public static String escapeString(String s) { return escapeString(s, '"'); } diff --git a/src/org/mozilla/javascript/Token.java b/src/org/mozilla/javascript/Token.java index ec424c4950..de0961d938 100644 --- a/src/org/mozilla/javascript/Token.java +++ b/src/org/mozilla/javascript/Token.java @@ -225,7 +225,8 @@ public static enum CommentType { TEMPLATE_CHARS = 171, // template literal - literal section TEMPLATE_LITERAL_SUBST = 172, // template literal - substitution TAGGED_TEMPLATE_LITERAL = 173, // template literal - tagged/handler - LAST_TOKEN = 173; + DOTDOTDOT = 174, // spread/rest ... + LAST_TOKEN = 174; /** * Returns a name for the token. If Rhino is compiled with certain hardcoded debugging flags in diff --git a/src/org/mozilla/javascript/TokenStream.java b/src/org/mozilla/javascript/TokenStream.java index e84d1c9a07..503ad01fc7 100644 --- a/src/org/mozilla/javascript/TokenStream.java +++ b/src/org/mozilla/javascript/TokenStream.java @@ -1152,6 +1152,10 @@ && peekChar() == '!' return Token.COLON; case '.': if (matchChar('.')) { + if (parser.compilerEnv.getLanguageVersion() >= Context.VERSION_1_8 + && matchChar('.')) { + return Token.DOTDOTDOT; + } return Token.DOTDOT; } else if (matchChar('(')) { return Token.DOTQUERY; diff --git a/src/org/mozilla/javascript/ast/FunctionNode.java b/src/org/mozilla/javascript/ast/FunctionNode.java index a19f7ce4f6..7c5d07d6e8 100644 --- a/src/org/mozilla/javascript/ast/FunctionNode.java +++ b/src/org/mozilla/javascript/ast/FunctionNode.java @@ -80,6 +80,7 @@ public static enum Form { private Form functionForm = Form.FUNCTION; private int lp = -1; private int rp = -1; + private boolean hasRestParameter; // codegen variables private int functionType; @@ -284,6 +285,15 @@ public void setIsES6Generator() { isGenerator = true; } + @Override + public boolean hasRestParameter() { + return hasRestParameter; + } + + public void setHasRestParameter(boolean hasRestParameter) { + this.hasRestParameter = hasRestParameter; + } + public void addResumptionPoint(Node target) { if (generatorResumePoints == null) generatorResumePoints = new ArrayList<>(); generatorResumePoints.add(target); diff --git a/src/org/mozilla/javascript/ast/ScriptNode.java b/src/org/mozilla/javascript/ast/ScriptNode.java index f27023fcb5..bfe31daf96 100644 --- a/src/org/mozilla/javascript/ast/ScriptNode.java +++ b/src/org/mozilla/javascript/ast/ScriptNode.java @@ -251,6 +251,10 @@ public boolean[] getParamAndVarConst() { return isConsts; } + public boolean hasRestParameter() { + return false; + } + void addSymbol(Symbol symbol) { if (variableNames != null) codeBug(); if (symbol.getDeclType() == Token.LP) { diff --git a/src/org/mozilla/javascript/optimizer/BodyCodegen.java b/src/org/mozilla/javascript/optimizer/BodyCodegen.java index 4ad3a167da..209fd208ec 100644 --- a/src/org/mozilla/javascript/optimizer/BodyCodegen.java +++ b/src/org/mozilla/javascript/optimizer/BodyCodegen.java @@ -322,18 +322,34 @@ private void generatePrologue() { int parmCount = scriptOrFn.getParamCount(); if (parmCount > 0 && !inDirectCallFunction) { // Set up args array - // check length of arguments, pad if need be - cfw.addALoad(argsLocal); - cfw.add(ByteCode.ARRAYLENGTH); - cfw.addPush(parmCount); - int label = cfw.acquireLabel(); - cfw.add(ByteCode.IF_ICMPGE, label); - cfw.addALoad(argsLocal); - cfw.addPush(parmCount); - addScriptRuntimeInvoke( - "padArguments", "([Ljava/lang/Object;I" + ")[Ljava/lang/Object;"); - cfw.addAStore(argsLocal); - cfw.markLabel(label); + if (scriptOrFn.hasRestParameter()) { + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + cfw.addALoad(argsLocal); + cfw.addPush(parmCount); + addScriptRuntimeInvoke( + "padAndRestArguments", + "(" + + "Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "[Ljava/lang/Object;" + + "I" + + ")[Ljava/lang/Object;"); + cfw.addAStore(argsLocal); + } else { + // check length of arguments, pad if need be + cfw.addALoad(argsLocal); + cfw.add(ByteCode.ARRAYLENGTH); + cfw.addPush(parmCount); + int label = cfw.acquireLabel(); + cfw.add(ByteCode.IF_ICMPGE, label); + cfw.addALoad(argsLocal); + cfw.addPush(parmCount); + addScriptRuntimeInvoke( + "padArguments", "([Ljava/lang/Object;I)[Ljava/lang/Object;"); + cfw.addAStore(argsLocal); + cfw.markLabel(label); + } } int paramCount = fnCurrent.fnode.getParamCount(); diff --git a/src/org/mozilla/javascript/optimizer/Codegen.java b/src/org/mozilla/javascript/optimizer/Codegen.java index 997530db6d..f6257766b0 100644 --- a/src/org/mozilla/javascript/optimizer/Codegen.java +++ b/src/org/mozilla/javascript/optimizer/Codegen.java @@ -743,7 +743,8 @@ private void generateNativeFunctionOverrides(ClassFileWriter cfw, String encoded final int Do_getEncodedSource = 4; final int Do_getParamOrVarConst = 5; final int Do_isGeneratorFunction = 6; - final int SWITCH_COUNT = 7; + final int Do_hasRestParameter = 7; + final int SWITCH_COUNT = 8; for (int methodIndex = 0; methodIndex != SWITCH_COUNT; ++methodIndex) { if (methodIndex == Do_getEncodedSource && encodedSource == null) { @@ -786,6 +787,10 @@ private void generateNativeFunctionOverrides(ClassFileWriter cfw, String encoded methodLocals = 1; // Only this cfw.startMethod("isGeneratorFunction", "()Z", ACC_PROTECTED); break; + case Do_hasRestParameter: + methodLocals = 1; // Only this + cfw.startMethod("hasRestParameter", "()Z", ACC_PUBLIC); + break; default: throw Kit.codeBug(); } @@ -830,7 +835,11 @@ private void generateNativeFunctionOverrides(ClassFileWriter cfw, String encoded case Do_getParamCount: // Push number of defined parameters - cfw.addPush(n.getParamCount()); + if (n.hasRestParameter()) { + cfw.addPush(n.getParamCount() - 1); + } else { + cfw.addPush(n.getParamCount()); + } cfw.add(ByteCode.IRETURN); break; @@ -920,6 +929,12 @@ private void generateNativeFunctionOverrides(ClassFileWriter cfw, String encoded cfw.add(ByteCode.IRETURN); break; + case Do_hasRestParameter: + // Push boolean of defined hasRestParameter + cfw.addPush(n.hasRestParameter()); + cfw.add(ByteCode.IRETURN); + break; + case Do_getEncodedSource: // Push number encoded source start and end // to prepare for encodedSource.substring(start, end) diff --git a/src/org/mozilla/javascript/resources/Messages.properties b/src/org/mozilla/javascript/resources/Messages.properties index d1d161939e..96d2dd50b1 100644 --- a/src/org/mozilla/javascript/resources/Messages.properties +++ b/src/org/mozilla/javascript/resources/Messages.properties @@ -312,6 +312,9 @@ msg.no.parm =\ msg.no.paren.after.parms =\ missing ) after formal parameters +msg.parm.after.rest =\ + parameter after rest parameter + msg.no.brace.body =\ missing '{' before function body diff --git a/src/org/mozilla/javascript/resources/Messages_fr.properties b/src/org/mozilla/javascript/resources/Messages_fr.properties index 2082b17c72..c464ab1a82 100644 --- a/src/org/mozilla/javascript/resources/Messages_fr.properties +++ b/src/org/mozilla/javascript/resources/Messages_fr.properties @@ -276,6 +276,9 @@ msg.no.parm =\ msg.no.paren.after.parms =\ il manque '')'' apr\u00E8s les param\u00E8tres formels +msg.parm.after.rest =\ + parameter after rest parameter + msg.no.brace.body =\ il manque ''{'' avant le corps d''une fonction diff --git a/testsrc/org/mozilla/javascript/tests/es6/FunctionsRestParametersTest.java b/testsrc/org/mozilla/javascript/tests/es6/FunctionsRestParametersTest.java new file mode 100644 index 0000000000..dfda2a3fc4 --- /dev/null +++ b/testsrc/org/mozilla/javascript/tests/es6/FunctionsRestParametersTest.java @@ -0,0 +1,234 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.javascript.tests.es6; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.EvaluatorException; +import org.mozilla.javascript.ScriptableObject; +import org.mozilla.javascript.tests.Utils; + +/** + * Tests for Functions Rest Parameters. + * + * @author Ronald Brill + */ +public class FunctionsRestParametersTest { + + private Context cx; + private ScriptableObject scope; + + @Before + public void setUp() { + cx = Context.enter(); + cx.setLanguageVersion(Context.VERSION_ES6); + scope = cx.initStandardObjects(); + } + + @After + public void tearDown() { + Context.exit(); + } + + @Test + public void oneRestArg() { + String code = + "function rest(...theArgs) {\n" + + " return theArgs;\n" + + "}\n" + + "try {\n" + + " rest(1, 'abc', 2, '##').toString();\n" + + "} catch (e) { e.message }"; + + test("1,abc,2,##", code); + } + + @Test + public void oneRestArgNothingProvided() { + String code = + "function rest(...theArgs) {\n" + + " return theArgs;\n" + + "}\n" + + "try {\n" + + " var r = rest();\n" + + " '' + Array.isArray(r) + '-' + r.length;\n" + + "} catch (e) { e.message }"; + + test("true-0", code); + } + + @Test + public void oneRestArgOneProvided() { + String code = + "function rest(...theArgs) {\n" + + " return theArgs;\n" + + "}\n" + + "try {\n" + + " var r = rest('xy');\n" + + " '' + Array.isArray(r) + '-' + r.length;\n" + + "} catch (e) { e.message }"; + + test("true-1", code); + } + + @Test + public void twoRestArg() { + String code = + "function rest(v, ...theArgs) {\n" + + " return theArgs;\n" + + "}\n" + + "try {\n" + + " rest(1, 'abc', 2, '##').toString();\n" + + "} catch (e) { e.message }"; + + test("abc,2,##", code); + } + + @Test + public void twoRestArgNothingProvided() { + String code = + "function rest(v, ...theArgs) {\n" + + " return '' + typeof v + ' - ' + Array.isArray(theArgs) + '-' + theArgs.length;\n" + + "}\n" + + "try {\n" + + " rest();\n" + + "} catch (e) { e.message }"; + + test("undefined - true-0", code); + } + + @Test + public void twoRestArgOneProvided() { + String code = + "function rest(v, ...theArgs) {\n" + + " return v + ' - ' + Array.isArray(theArgs) + '-' + theArgs.length;\n" + + "}\n" + + "try {\n" + + " rest('77');\n" + + "} catch (e) { e.message }"; + + test("77 - true-0", code); + } + + @Test + public void arguments() { + String code = + "function rest(arg, ...restArgs) {\n" + + " return arguments.length;\n" + + "}\n" + + "try {\n" + + " '' + rest('77') + '-' + rest(1, 2, 3, 4);\n" + + "} catch (e) { e.message }"; + + test("1-4", code); + } + + @Test + public void length() { + String code = + "function foo1(...theArgs) {}\n" + + "function foo2(arg, ...theArgs) {}\n" + + "try {\n" + + " foo1.length + '-' + foo2.length;\n" + + "} catch (e) { e.message }"; + + test("0-1", code); + } + + @Test + public void argLength() { + String code = + "function rest(...theArgs) {\n" + + " return theArgs.length;\n" + + "}\n" + + "try {\n" + + " rest(1,2) + '-' + rest(1) + '-' + rest();\n" + + "} catch (e) { e.message }"; + + test("2-1-0", code); + } + + @Test + public void string1() { + String code = + "function rest(...theArgs) {\n" + + " return theArgs.length;\n" + + "}\n" + + "try {\n" + + " rest.toString();\n" + + "} catch (e) { e.message }"; + + test("\nfunction rest(...theArgs) {\n return theArgs.length;\n}\n", code); + } + + @Test + public void string2() { + String code = + "function rest( arg , ...theArgs ) {\n" + + " return theArgs.length;\n" + + "}\n" + + "try {\n" + + " rest.toString();\n" + + "} catch (e) { e.message }"; + + test("\nfunction rest(arg, ...theArgs) {\n return theArgs.length;\n}\n", code); + } + + @Test + public void trailingComma() { + String code = "function rest(...theArgs,) {\n" + " return theArgs;\n" + "}\n"; + + assertEvaluatorException("parameter after rest parameter (test#1)", code); + } + + @Test + public void twoRestParams() { + String code = "function rest(...rest1, ...rest2) {\n" + " return theArgs;\n" + "}\n"; + + assertEvaluatorException("parameter after rest parameter (test#1)", code); + } + + @Test + public void paramAfterRestParam() { + String code = "function rest(...rest1, param) {\n" + " return theArgs;\n" + "}\n"; + + assertEvaluatorException("parameter after rest parameter (test#1)", code); + } + + private static void test(Object expected, String js) { + Utils.runWithAllOptimizationLevels( + cx -> { + cx.setLanguageVersion(Context.VERSION_ES6); + ScriptableObject scope = cx.initStandardObjects(); + + Object result = cx.evaluateString(scope, js, "test", 1, null); + assertEquals(expected, result); + + return null; + }); + } + + private static void assertEvaluatorException(String expected, String js) { + Utils.runWithAllOptimizationLevels( + cx -> { + cx.setLanguageVersion(Context.VERSION_ES6); + ScriptableObject scope = cx.initStandardObjects(); + + try { + cx.evaluateString(scope, js, "test", 1, null); + fail("EvaluatorException expected"); + } catch (EvaluatorException e) { + assertEquals(expected, e.getMessage()); + } + + return null; + }); + } +} diff --git a/testsrc/test262.properties b/testsrc/test262.properties index 332b9c6711..322e1ab255 100644 --- a/testsrc/test262.properties +++ b/testsrc/test262.properties @@ -2502,7 +2502,7 @@ language/arguments-object 189/260 (72.69%) mapped/nonwritable-nonenumerable-nonconfigurable-descriptors-set-by-param.js non-strict unmapped/via-params-dflt.js unmapped/via-params-dstr.js non-strict - unmapped/via-params-rest.js + unmapped/via-params-rest.js non-strict arguments-caller.js async-gen-meth-args-trailing-comma-multiple.js {unsupported: [async-iteration, async]} async-gen-meth-args-trailing-comma-null.js {unsupported: [async-iteration, async]} @@ -5171,16 +5171,11 @@ language/reserved-words 2/27 (7.41%) await-module.js {unsupported: [module]} await-script.js -language/rest-parameters 10/11 (90.91%) +language/rest-parameters 5/11 (45.45%) array-pattern.js arrow-function.js - expected-argument-count.js no-alias-arguments.js object-pattern.js - rest-index.js - rest-parameters-apply.js - rest-parameters-call.js - rest-parameters-produce-an-array.js with-new-target.js language/source-text 0/1 (0.0%)