diff --git a/src/class-elements/grammar-get-followed-by-name-on-next-line-without-parameters-error.case b/src/class-elements/grammar-get-followed-by-name-on-next-line-without-parameters-error.case new file mode 100644 index 00000000000..4df4f6825e3 --- /dev/null +++ b/src/class-elements/grammar-get-followed-by-name-on-next-line-without-parameters-error.case @@ -0,0 +1,28 @@ +// Copyright (C) 2026 hexbinoct. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +desc: SyntaxError for a getter whose name is on the next line and that has no parameter list +info: | + ClassElement : + MethodDefinition + static MethodDefinition + FieldDefinition ; + ; + + MethodDefinition : + get ClassElementName ( ) { FunctionBody } + + FieldDefinition : + ClassElementName Initializer _opt + + The name after "get" is allowed by the getter production, so no semicolon is + inserted after "get", and the getter is missing its parameter list. + +template: syntax/invalid +features: [class-fields-public] +---*/ + +//- elements +get +x diff --git a/test/language/expressions/class/elements/syntax/early-errors/grammar-get-followed-by-name-on-next-line-without-parameters-error.js b/test/language/expressions/class/elements/syntax/early-errors/grammar-get-followed-by-name-on-next-line-without-parameters-error.js new file mode 100644 index 00000000000..9d58f0c004f --- /dev/null +++ b/test/language/expressions/class/elements/syntax/early-errors/grammar-get-followed-by-name-on-next-line-without-parameters-error.js @@ -0,0 +1,36 @@ +// This file was procedurally generated from the following sources: +// - src/class-elements/grammar-get-followed-by-name-on-next-line-without-parameters-error.case +// - src/class-elements/syntax/invalid/cls-expr-elements-invalid-syntax.template +/*--- +description: SyntaxError for a getter whose name is on the next line and that has no parameter list (class expression) +esid: prod-ClassElement +features: [class-fields-public, class] +flags: [generated] +negative: + phase: parse + type: SyntaxError +info: | + ClassElement : + MethodDefinition + static MethodDefinition + FieldDefinition ; + ; + + MethodDefinition : + get ClassElementName ( ) { FunctionBody } + + FieldDefinition : + ClassElementName Initializer _opt + + The name after "get" is allowed by the getter production, so no semicolon is + inserted after "get", and the getter is missing its parameter list. + +---*/ + + +$DONOTEVALUATE(); + +var C = class { + get + x +}; diff --git a/test/language/statements/class/elements/syntax/early-errors/grammar-get-followed-by-name-on-next-line-without-parameters-error.js b/test/language/statements/class/elements/syntax/early-errors/grammar-get-followed-by-name-on-next-line-without-parameters-error.js new file mode 100644 index 00000000000..7aabf00f4e6 --- /dev/null +++ b/test/language/statements/class/elements/syntax/early-errors/grammar-get-followed-by-name-on-next-line-without-parameters-error.js @@ -0,0 +1,36 @@ +// This file was procedurally generated from the following sources: +// - src/class-elements/grammar-get-followed-by-name-on-next-line-without-parameters-error.case +// - src/class-elements/syntax/invalid/cls-decl-elements-invalid-syntax.template +/*--- +description: SyntaxError for a getter whose name is on the next line and that has no parameter list (class declaration) +esid: prod-ClassElement +features: [class-fields-public, class] +flags: [generated] +negative: + phase: parse + type: SyntaxError +info: | + ClassElement : + MethodDefinition + static MethodDefinition + FieldDefinition ; + ; + + MethodDefinition : + get ClassElementName ( ) { FunctionBody } + + FieldDefinition : + ClassElementName Initializer _opt + + The name after "get" is allowed by the getter production, so no semicolon is + inserted after "get", and the getter is missing its parameter list. + +---*/ + + +$DONOTEVALUATE(); + +class C { + get + x +} diff --git a/test/language/statements/class/elements/syntax/valid/grammar-get-followed-by-name-on-next-line.js b/test/language/statements/class/elements/syntax/valid/grammar-get-followed-by-name-on-next-line.js new file mode 100644 index 00000000000..09f379a2340 --- /dev/null +++ b/test/language/statements/class/elements/syntax/valid/grammar-get-followed-by-name-on-next-line.js @@ -0,0 +1,62 @@ +// Copyright (C) 2026 hexbinoct. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +description: A line terminator between "get" and the name does not end a field named "get" +esid: prod-ClassElement +features: [class, class-methods-private] +info: | + ClassElement : + MethodDefinition + static MethodDefinition + FieldDefinition ; + static FieldDefinition ; + ; + + MethodDefinition : + get ClassElementName ( ) { FunctionBody } + + FieldDefinition : + ClassElementName Initializer _opt + + The getter production has no [no LineTerminator here] restriction, so the + name on the next line is allowed and no semicolon is inserted after "get". +---*/ + +class A { + get + a() { return "a"; } +} + +class B { + static get + b() { return "b"; } +} + +class C { + get + ["c"]() { return "c"; } +} + +class D { + get + #d() { return "d"; } + read() { return this.#d; } +} + +var a = new A(); +assert.sameValue(typeof Object.getOwnPropertyDescriptor(A.prototype, "a").get, "function", "(A) a is a getter on the prototype"); +assert.sameValue(a.a, "a", "(A) the getter returns its value"); +assert.sameValue(Object.prototype.hasOwnProperty.call(a, "get"), false, "(A) there is no field named get"); + +assert.sameValue(typeof Object.getOwnPropertyDescriptor(B, "b").get, "function", "(B) b is a static getter"); +assert.sameValue(B.b, "b", "(B) the static getter returns its value"); +assert.sameValue(Object.prototype.hasOwnProperty.call(B, "get"), false, "(B) there is no static field named get"); + +var c = new C(); +assert.sameValue(typeof Object.getOwnPropertyDescriptor(C.prototype, "c").get, "function", "(C) c is a getter on the prototype"); +assert.sameValue(c.c, "c", "(C) the getter returns its value"); +assert.sameValue(Object.prototype.hasOwnProperty.call(c, "get"), false, "(C) there is no field named get"); + +var d = new D(); +assert.sameValue(d.read(), "d", "(D) #d is a private getter"); +assert.sameValue(Object.prototype.hasOwnProperty.call(d, "get"), false, "(D) there is no field named get"); diff --git a/test/language/statements/class/elements/syntax/valid/grammar-method-name-followed-by-parameters-on-next-line.js b/test/language/statements/class/elements/syntax/valid/grammar-method-name-followed-by-parameters-on-next-line.js new file mode 100644 index 00000000000..658c206657a --- /dev/null +++ b/test/language/statements/class/elements/syntax/valid/grammar-method-name-followed-by-parameters-on-next-line.js @@ -0,0 +1,40 @@ +// Copyright (C) 2026 hexbinoct. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +description: A line terminator between a method name and its parameters does not end a field +esid: prod-ClassElement +features: [class] +info: | + ClassElement : + MethodDefinition + FieldDefinition ; + ; + + MethodDefinition : + ClassElementName ( UniqueFormalParameters ) { FunctionBody } + + FieldDefinition : + ClassElementName Initializer _opt + + A "(" after a ClassElementName is allowed by MethodDefinition, so no + semicolon is inserted before it, even after a line terminator. +---*/ + +class A { + m + () { return "m"; } + + get + () { return "get"; } + + set + (v) { return "set" + v; } +} + +var a = new A(); +assert.sameValue(a.m(), "m", "m is a method on the prototype"); +assert.sameValue(a.get(), "get", "get is a method on the prototype"); +assert.sameValue(a.set(1), "set1", "set is a method on the prototype"); +assert.sameValue(Object.prototype.hasOwnProperty.call(a, "m"), false, "there is no field named m"); +assert.sameValue(Object.prototype.hasOwnProperty.call(a, "get"), false, "there is no field named get"); +assert.sameValue(Object.prototype.hasOwnProperty.call(a, "set"), false, "there is no field named set"); diff --git a/test/language/statements/class/elements/syntax/valid/grammar-method-named-static.js b/test/language/statements/class/elements/syntax/valid/grammar-method-named-static.js new file mode 100644 index 00000000000..607b8179405 --- /dev/null +++ b/test/language/statements/class/elements/syntax/valid/grammar-method-named-static.js @@ -0,0 +1,39 @@ +// Copyright (C) 2026 hexbinoct. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +description: A method can be named "static", with or without the static modifier +esid: prod-ClassElement +features: [class] +info: | + ClassElement : + MethodDefinition + static MethodDefinition + FieldDefinition ; + static FieldDefinition ; + ; + + MethodDefinition : + ClassElementName ( UniqueFormalParameters ) { FunctionBody } + + ClassElementName : + PropertyName + + "static" is an IdentifierName, so it can be the name of a method. A "(" + after it, on the same line or the next, continues that method definition. +---*/ + +class A { + static() { return "prototype"; } + static static() { return "constructor"; } +} + +class B { + static + () { return "next line"; } +} + +assert.sameValue(A.prototype.static(), "prototype", "(A) static is a method on the prototype"); +assert.sameValue(A.static(), "constructor", "(A) static is also a static method"); +assert.sameValue(new B().static(), "next line", "(B) static is a method on the prototype"); +assert.sameValue(Object.prototype.hasOwnProperty.call(B, "static"), false, "(B) static is not a static method"); +assert.sameValue(Object.prototype.hasOwnProperty.call(new B(), "static"), false, "(B) there is no field named static"); diff --git a/test/language/statements/class/elements/syntax/valid/grammar-set-followed-by-name-on-next-line.js b/test/language/statements/class/elements/syntax/valid/grammar-set-followed-by-name-on-next-line.js new file mode 100644 index 00000000000..7c1e92a850f --- /dev/null +++ b/test/language/statements/class/elements/syntax/valid/grammar-set-followed-by-name-on-next-line.js @@ -0,0 +1,68 @@ +// Copyright (C) 2026 hexbinoct. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +description: A line terminator between "set" and the name does not end a field named "set" +esid: prod-ClassElement +features: [class, class-methods-private] +info: | + ClassElement : + MethodDefinition + static MethodDefinition + FieldDefinition ; + static FieldDefinition ; + ; + + MethodDefinition : + set ClassElementName ( PropertySetParameterList ) { FunctionBody } + + FieldDefinition : + ClassElementName Initializer _opt + + The setter production has no [no LineTerminator here] restriction, so the + name on the next line is allowed and no semicolon is inserted after "set". +---*/ + +var stored; + +class A { + set + a(v) { stored = "a" + v; } +} + +class B { + static set + b(v) { stored = "b" + v; } +} + +class C { + set + ["c"](v) { stored = "c" + v; } +} + +class D { + set + #d(v) { stored = "d" + v; } + write(v) { this.#d = v; } +} + +var a = new A(); +assert.sameValue(typeof Object.getOwnPropertyDescriptor(A.prototype, "a").set, "function", "(A) a is a setter on the prototype"); +a.a = 1; +assert.sameValue(stored, "a1", "(A) the setter is called"); +assert.sameValue(Object.prototype.hasOwnProperty.call(a, "set"), false, "(A) there is no field named set"); + +assert.sameValue(typeof Object.getOwnPropertyDescriptor(B, "b").set, "function", "(B) b is a static setter"); +B.b = 2; +assert.sameValue(stored, "b2", "(B) the static setter is called"); +assert.sameValue(Object.prototype.hasOwnProperty.call(B, "set"), false, "(B) there is no static field named set"); + +var c = new C(); +assert.sameValue(typeof Object.getOwnPropertyDescriptor(C.prototype, "c").set, "function", "(C) c is a setter on the prototype"); +c.c = 3; +assert.sameValue(stored, "c3", "(C) the setter is called"); +assert.sameValue(Object.prototype.hasOwnProperty.call(c, "set"), false, "(C) there is no field named set"); + +var d = new D(); +d.write(4); +assert.sameValue(stored, "d4", "(D) #d is a private setter"); +assert.sameValue(Object.prototype.hasOwnProperty.call(d, "set"), false, "(D) there is no field named set"); diff --git a/test/language/statements/class/elements/syntax/valid/grammar-static-followed-by-field-on-next-line.js b/test/language/statements/class/elements/syntax/valid/grammar-static-followed-by-field-on-next-line.js new file mode 100644 index 00000000000..2523ddb34b0 --- /dev/null +++ b/test/language/statements/class/elements/syntax/valid/grammar-static-followed-by-field-on-next-line.js @@ -0,0 +1,38 @@ +// Copyright (C) 2026 hexbinoct. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +description: A line terminator after "static" does not end a field named "static" when a field name follows +esid: prod-ClassElement +features: [class, class-static-fields-public] +info: | + ClassElement : + MethodDefinition + static MethodDefinition + FieldDefinition ; + static FieldDefinition ; + ; + + FieldDefinition : + ClassElementName Initializer _opt + + The "static" FieldDefinition production has no [no LineTerminator here] + restriction, so a field name on the next line is allowed and no semicolon is + inserted after "static". +---*/ + +class A { + static + x = 1; + + static + y +} + +assert.sameValue(A.x, 1, "x is a static field"); +assert.sameValue(Object.prototype.hasOwnProperty.call(A, "y"), true, "y is a static field"); +assert.sameValue(A.y, undefined, "y has no initializer"); + +var a = new A(); +assert.sameValue(Object.prototype.hasOwnProperty.call(a, "static"), false, "there is no field named static"); +assert.sameValue(Object.prototype.hasOwnProperty.call(a, "x"), false, "x is not an instance field"); +assert.sameValue(Object.prototype.hasOwnProperty.call(a, "y"), false, "y is not an instance field"); diff --git a/test/language/statements/class/elements/syntax/valid/grammar-static-followed-by-method-on-next-line.js b/test/language/statements/class/elements/syntax/valid/grammar-static-followed-by-method-on-next-line.js new file mode 100644 index 00000000000..246f54a4712 --- /dev/null +++ b/test/language/statements/class/elements/syntax/valid/grammar-static-followed-by-method-on-next-line.js @@ -0,0 +1,51 @@ +// Copyright (C) 2026 hexbinoct. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +description: A line terminator after "static" does not end a field named "static" when a method follows +esid: prod-ClassElement +features: [class, generators, async-functions] +info: | + ClassElement : + MethodDefinition + static MethodDefinition + FieldDefinition ; + static FieldDefinition ; + ; + + MethodDefinition : + ClassElementName ( UniqueFormalParameters ) { FunctionBody } + GeneratorMethod + AsyncMethod + get ClassElementName ( ) { FunctionBody } + + The "static" MethodDefinition production has no [no LineTerminator here] + restriction, so a method on the next line is allowed and no semicolon is + inserted after "static". +---*/ + +class A { + static + m() { return "m"; } + + static + *g() {} + + static + async am() {} + + static + get + x() { return "x"; } +} + +assert.sameValue(A.m(), "m", "m is a static method"); +assert.sameValue(Object.prototype.hasOwnProperty.call(A.prototype, "m"), false, "m is not on the prototype"); +assert.sameValue(Object.prototype.hasOwnProperty.call(A, "g"), true, "g is a static generator method"); +assert.sameValue(Object.prototype.hasOwnProperty.call(A, "am"), true, "am is a static async method"); +assert.sameValue(typeof Object.getOwnPropertyDescriptor(A, "x").get, "function", "x is a static getter"); +assert.sameValue(A.x, "x", "the static getter returns its value"); + +var a = new A(); +assert.sameValue(Object.prototype.hasOwnProperty.call(a, "static"), false, "there is no field named static"); +assert.sameValue(Object.prototype.hasOwnProperty.call(A, "static"), false, "there is no static field named static"); +assert.sameValue(Object.prototype.hasOwnProperty.call(a, "get"), false, "there is no field named get");