Skip to content

Commit

Permalink
feat(parser): WIP! Implements optional chaining
Browse files Browse the repository at this point in the history
This is early draft and can only be finished when the ESTree specs are done.

Added a few tests.
  • Loading branch information
KFlash committed Aug 2, 2019
1 parent 8415be7 commit 09425fc
Show file tree
Hide file tree
Showing 3 changed files with 280 additions and 13 deletions.
76 changes: 64 additions & 12 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3621,7 +3621,8 @@ export function parseMemberOrUpdateExpression(
inGroup: 0 | 1,
start: number,
line: number,
column: number
column: number,
chained: 0 | 1 = 0
): any {
// Update + Member expression
if ((parser.token & Token.IsUpdateOp) === Token.IsUpdateOp && (parser.flags & Flags.NewLine) < 1) {
Expand Down Expand Up @@ -3674,12 +3675,49 @@ export function parseMemberOrUpdateExpression(

parser.assignable = AssignmentKind.Assignable;

expr = finishNode(parser, context, start, line, column, {
type: 'MemberExpression',
object: expr,
computed: true,
property
});
expr = finishNode(
parser,
context,
start,
line,
column,
chained
? {
type: 'OptionalMemberExpression',
object: expr,
computed: true,
optional: true,
property
}
: ({
type: 'MemberExpression',
object: expr,
computed: true,
property
} as any)
);
break;
}

case Token.OptionalChaining: {
nextToken(parser, context); // skips: '?.'

if ((parser.token & Token.IsMemberOrCallExpression) === Token.IsMemberOrCallExpression) {
expr = parseMemberOrUpdateExpression(parser, context, expr, 0, start, line, column, 1);
} else {
parser.assignable = AssignmentKind.Assignable;

const property = parsePropertyOrPrivatePropertyName(parser, context);

expr = finishNode(parser, context, start, line, column, {
type: 'OptionalMemberExpression',
object: expr,
computed: false,
optional: true,
property
} as any);
}

break;
}

Expand All @@ -3689,11 +3727,25 @@ export function parseMemberOrUpdateExpression(

parser.assignable = AssignmentKind.CannotAssign;

expr = finishNode(parser, context, start, line, column, {
type: 'CallExpression',
callee: expr,
arguments: args
});
expr = finishNode(
parser,
context,
start,
line,
column,
chained
? {
type: 'OptionalCallExpression',
callee: expr,
optional: true,
arguments: args
}
: ({
type: 'CallExpression',
callee: expr,
arguments: args
} as any)
);
break;
}

Expand Down
2 changes: 1 addition & 1 deletion src/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ export const enum Token {

// Stage #3 proposals
Coalesce = 123 | IsBinaryOp | IsCoalesc | 1 << PrecStart, // ??,
OptionalChaining = 125, // ?.,
OptionalChaining = 125 | IsMemberOrCallExpression, // ?.,

}

Expand Down
215 changes: 215 additions & 0 deletions test/parser/next/optional-chaining.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import { Context } from '../../../src/common';
import { pass, fail } from '../../test-utils';
import * as t from 'assert';
import { parseSource } from '../../../src/parser';

describe('Next - Optional chaining', () => {
pass('Next - Optional chaining (pass)', [
[
`a?.func?.()`,
Context.OptionsNext,
{
body: [
{
expression: {
arguments: [],
callee: {
computed: false,
object: {
name: 'a',
type: 'Identifier'
},
optional: true,
property: {
name: 'func',
type: 'Identifier'
},
type: 'OptionalMemberExpression'
},
optional: true,
type: 'OptionalCallExpression'
},
type: 'ExpressionStatement'
}
],
sourceType: 'script',
type: 'Program'
}
],
[
`func?.()`,
Context.OptionsNext,
{
body: [
{
expression: {
arguments: [],
callee: {
name: 'func',
type: 'Identifier'
},
optional: true,
type: 'OptionalCallExpression'
},
type: 'ExpressionStatement'
}
],
sourceType: 'script',
type: 'Program'
}
],
[
`obj.a?.[true]`,
Context.OptionsNext,
{
body: [
{
expression: {
computed: true,
object: {
computed: false,
object: {
name: 'obj',
type: 'Identifier'
},
property: {
name: 'a',
type: 'Identifier'
},
type: 'MemberExpression'
},
optional: true,
property: {
type: 'Literal',
value: true
},
type: 'OptionalMemberExpression'
},
type: 'ExpressionStatement'
}
],
sourceType: 'script',
type: 'Program'
}
],
[
`obj?.[expr]?.[other]`,
Context.OptionsNext,
{
body: [
{
expression: {
computed: true,
object: {
computed: true,
object: {
name: 'obj',
type: 'Identifier'
},
optional: true,
property: {
name: 'expr',
type: 'Identifier'
},
type: 'OptionalMemberExpression'
},
optional: true,
property: {
name: 'other',
type: 'Identifier'
},
type: 'OptionalMemberExpression'
},
type: 'ExpressionStatement'
}
],
sourceType: 'script',
type: 'Program'
}
],
[
`a.b.c?.d.e.f`,
Context.OptionsNext,
{
body: [
{
expression: {
computed: false,
object: {
computed: false,
object: {
computed: false,
object: {
computed: false,
object: {
computed: false,
object: {
name: 'a',
type: 'Identifier'
},
property: {
name: 'b',
type: 'Identifier'
},
type: 'MemberExpression'
},
property: {
name: 'c',
type: 'Identifier'
},
type: 'MemberExpression'
},
optional: true,
property: {
name: 'd',
type: 'Identifier'
},
type: 'OptionalMemberExpression'
},
property: {
name: 'e',
type: 'Identifier'
},
type: 'MemberExpression'
},
property: {
name: 'f',
type: 'Identifier'
},
type: 'MemberExpression'
},
type: 'ExpressionStatement'
}
],
sourceType: 'script',
type: 'Program'
}
],
[
`foo?.bar`,
Context.OptionsNext,
{
body: [
{
expression: {
computed: false,
object: {
name: 'foo',
type: 'Identifier'
},
optional: true,
property: {
name: 'bar',
type: 'Identifier'
},
type: 'OptionalMemberExpression'
},
type: 'ExpressionStatement'
}
],
sourceType: 'script',
type: 'Program'
}
]
]);
});

0 comments on commit 09425fc

Please sign in to comment.