Skip to content

Commit

Permalink
fix(parser): improved optional chaing implementation
Browse files Browse the repository at this point in the history
Should work the same way as in Babylon parser now.
  • Loading branch information
KFlash committed Aug 2, 2019
1 parent 09425fc commit c8532d9
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 5 deletions.
6 changes: 4 additions & 2 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@ export const enum Errors {
CatchWithoutTry,
FinallyWithoutTry,
UnCorrespondingFragmentTag,
InvalidCoalescing
InvalidCoalescing,
InvalidChaining
}

/*@internal*/
Expand Down Expand Up @@ -352,7 +353,8 @@ export const errorMessages: {
[Errors.FinallyWithoutTry]: 'Finally without try',
[Errors.UnCorrespondingFragmentTag]: 'Expected corresponding closing tag for JSX fragment',
[Errors.InvalidCoalescing]:
'Coalescing and logical operators used together in the same expression must be disambiguated with parentheses'
'Coalescing and logical operators used together in the same expression must be disambiguated with parentheses',
[Errors.InvalidChaining]: 'Constructors in/after an Optional Chain are not allowed'
};

export class ParseError extends SyntaxError {
Expand Down
13 changes: 10 additions & 3 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7191,17 +7191,24 @@ export function parseNewExpression(

parser.assignable = AssignmentKind.CannotAssign;

// NewExpression without arguments.
const callee = parseMembeExpressionNoCall(
const expr = parsePrimaryExpressionExtended(
parser,
context,
parsePrimaryExpressionExtended(parser, context, BindingKind.Empty, 1, 0, 0, inGroup, tokenPos, linePos, colPos),
BindingKind.Empty,
1,
0,
0,
inGroup,
tokenPos,
linePos,
colPos
);

if (parser.token === Token.OptionalChaining) report(parser, Errors.InvalidChaining);

// NewExpression without arguments.
const callee = parseMembeExpressionNoCall(parser, context, expr, inGroup, tokenPos, linePos, colPos);

parser.assignable = AssignmentKind.CannotAssign;

return finishNode(parser, context, start, line, column, {
Expand Down
158 changes: 158 additions & 0 deletions test/parser/next/optional-chaining.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,165 @@ import * as t from 'assert';
import { parseSource } from '../../../src/parser';

describe('Next - Optional chaining', () => {
for (const arg of [
'func?.()',
'func?.(a, b)',
'a?.func?.()',
'a?.func?.(a, b)',
'a.func?.()',
'obj?.[expr]',
'obj?.[expr]?.[other]',
`obj?.[true]`,
'obj?.[true]?.[true]',
'obj.a?.[expr]',
`obj.a?.[true]`,
`foo.bar?.baz`,
`foo?.bar?.baz`,
`foo?.bar`,
'a.b?.c()',
'(a?.b).c;',
'(a?.b).c();',
'(a?.b)?.c.d?.e;',
`a?.b.c.d.e?.f`,
`a.b.c?.d.e.f`,
`if (a?.b?.c) {
console.log(a?.b?.c);
} else if (a?.b.c?.d?.e.f) {
console.log(a?.b.c?.d?.e.f);
}`,
`if (a?.b?.c === 'foobar') {}
if (a?.b()?.c) {}
if (a?.b?.()?.c) {}`,
`a?.b(...args);`,
`a?.b(...args).c;`,
`a?.b(...args).c(...args);`
]) {
it(`${arg}`, () => {
t.doesNotThrow(() => {
parseSource(`${arg}`, undefined, Context.OptionsNext);
});
});
it(`${arg}`, () => {
t.doesNotThrow(() => {
parseSource(`${arg}`, undefined, Context.OptionsNext | Context.OptionsWebCompat);
});
});
}

fail('Expressions - Optional chaining (fail)', [
['const a = { b(){ return super?.c; } }', Context.OptionsNext],
['class A{ b(){ return super?.b; } }', Context.OptionsWebCompat],
['new a?.();', Context.OptionsNext | Context.Module | Context.Strict],
['new C?.b.d()', Context.OptionsNext | Context.OptionsWebCompat],
['a.?b.?()', Context.OptionsNext | Context.OptionsWebCompat],
['a.?()', Context.OptionsNext | Context.OptionsWebCompat],
['a.?()', Context.None]
]);

pass('Next - Optional chaining (pass)', [
[
`a?.(...args);`,
Context.OptionsNext,
{
body: [
{
expression: {
arguments: [
{
argument: {
name: 'args',
type: 'Identifier'
},
type: 'SpreadElement'
}
],
callee: {
name: 'a',
type: 'Identifier'
},
optional: true,
type: 'OptionalCallExpression'
},
type: 'ExpressionStatement'
}
],
sourceType: 'script',
type: 'Program'
}
],
[
`class A extends B {
constructor(){
super()?.b;
}
}`,
Context.OptionsNext,
{
body: [
{
body: {
body: [
{
computed: false,
decorators: [],
key: {
name: 'constructor',
type: 'Identifier'
},
kind: 'constructor',
static: false,
type: 'MethodDefinition',
value: {
async: false,
body: {
body: [
{
expression: {
computed: false,
object: {
arguments: [],
callee: {
type: 'Super'
},
type: 'CallExpression'
},
optional: true,
property: {
name: 'b',
type: 'Identifier'
},
type: 'OptionalMemberExpression'
},
type: 'ExpressionStatement'
}
],
type: 'BlockStatement'
},
generator: false,
id: null,
params: [],
type: 'FunctionExpression'
}
}
],
type: 'ClassBody'
},
decorators: [],
id: {
name: 'A',
type: 'Identifier'
},
superClass: {
name: 'B',
type: 'Identifier'
},
type: 'ClassDeclaration'
}
],
sourceType: 'script',
type: 'Program'
}
],
[
`a?.func?.()`,
Context.OptionsNext,
Expand Down

0 comments on commit c8532d9

Please sign in to comment.