From 1a6ba78ced65d85c100f8d16c4ab8fc1717f665c Mon Sep 17 00:00:00 2001 From: David Cameron Date: Wed, 13 May 2026 10:35:34 -0400 Subject: [PATCH] Fix parsing method member forms --- feature/accessor.js | 8 +++++--- feature/class.js | 18 ++++++++++++++++-- test/feature/async-class.js | 8 ++++++++ 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/feature/accessor.js b/feature/accessor.js index 817b7ea..ad119d5 100644 --- a/feature/accessor.js +++ b/feature/accessor.js @@ -40,11 +40,13 @@ const accessor = (kind) => a => { token('get', ASSIGN - 1, accessor('get')); token('set', ASSIGN - 1, accessor('set')); -// Method shorthand: { foo() {} } → [':', 'foo', ['=>', ['()', params], body]] +// Method shorthand: { foo() {} } / { "foo"() {} } → [':', 'foo', ['=>', ['()', params], body]] // Uses token() infix handler - returns undefined to fall through to function call token('(', ASSIGN - 1, a => { - // Only handle infix position with plain identifier in low-precedence context (object literal) - if (!a || typeof a !== 'string') return; + // Only handle infix position with a static property key in low-precedence context (object literal) + if (!a) return; + if (Array.isArray(a) && a[0] === undefined) a = a[1]; + if (typeof a !== 'string') return; const params = expr(0, CPAREN) || null; space(); // Not followed by { - not method shorthand, fall through diff --git a/feature/class.js b/feature/class.js index a7abee0..96cf515 100644 --- a/feature/class.js +++ b/feature/class.js @@ -1,12 +1,26 @@ // Class declarations and expressions - parse half // class A extends B { ... } -import { binary, unary, token, expr, space, next, parse, keyword, word, skip } from '../parse.js'; +import { binary, token, expr, space, next, parse, keyword, word, skip, cur, idx } from '../parse.js'; import { block } from './if.js'; const TOKEN = 200, PREFIX = 140, COMP = 90; +const OPAREN = 40, CPAREN = 41, OBRACE = 123, CBRACE = 125; // static member → ['static', member] -unary('static', PREFIX); +token('static', PREFIX, a => { + if (a) return; + space(); + const name = next(parse.id); + if (!name) return; + space(); + if (cur.charCodeAt(idx) !== OPAREN) return ['static', name]; + skip(); + const params = expr(0, CPAREN) || null; + space(); + if (cur.charCodeAt(idx) !== OBRACE) return ['static', ['()', name, params]]; + skip(); + return ['static', [':', name, ['=>', ['()', params], expr(0, CBRACE) || null]]]; +}); // instanceof: object instanceof Constructor binary('instanceof', COMP); diff --git a/test/feature/async-class.js b/test/feature/async-class.js index 2f7d812..1c1862f 100644 --- a/test/feature/async-class.js +++ b/test/feature/async-class.js @@ -81,6 +81,10 @@ test('async/class: anonymous class', () => { test('async/class: static', () => { is(parse('static x'), ['static', 'x']); is(parse('static x = 1'), ['=', ['static', 'x'], [, 1]]); + is(parse('class A { static m(a) { return a } }'), [ + 'class', 'A', null, + ['static', [':', 'm', ['=>', ['()', 'a'], ['return', 'a']]]] + ]); }); test('async/class: super', () => { @@ -130,6 +134,10 @@ test('meta: new.target', () => { test('object: method shorthand', () => { is(parse('{ foo() {} }'), ['{}', [':', 'foo', ['=>', ['()', null], null]]]); is(parse('{ add(a, b) { a + b } }'), ['{}', [':', 'add', ['=>', ['()', [',', 'a', 'b']], ['+', 'a', 'b']]]]); + is(parse('{ "x/y.js"(exports, module) { module.exports = {} } }'), [ + '{}', + [':', 'x/y.js', ['=>', ['()', [',', 'exports', 'module']], ['=', ['.', 'module', 'exports'], ['{}', null]]]] + ]); // Evaluation const obj = compile(parse('{ double(x) { x * 2 } }'))(); is(obj.double(5), 10);