Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for record and tuple syntax. #10865

Merged
merged 18 commits into from Mar 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/babel-core/src/parser/util/missing-plugin-helper.js
Expand Up @@ -143,6 +143,12 @@ const pluginNameMap = {
url: "https://git.io/vb4SU",
},
},
recordAndTuple: {
syntax: {
name: "@babel/plugin-syntax-record-and-tuple",
url: "https://git.io/JvKp3",
},
},
throwExpressions: {
syntax: {
name: "@babel/plugin-syntax-throw-expressions",
Expand Down
63 changes: 63 additions & 0 deletions packages/babel-generator/src/generators/types.js
Expand Up @@ -106,6 +106,69 @@ export function ArrayExpression(node: Object) {

export { ArrayExpression as ArrayPattern };

export function RecordExpression(node: Object) {
const props = node.properties;

let startToken;
let endToken;
if (this.format.recordAndTupleSyntaxType === "bar") {
startToken = "{|";
endToken = "|}";
} else if (this.format.recordAndTupleSyntaxType === "hash") {
startToken = "#{";
endToken = "}";
} else {
throw new Error(
`The "recordAndTupleSyntaxType" generator option must be "bar" or "hash" (${JSON.stringify(
this.format.recordAndTupleSyntaxType,
)} received).`,
);
}

this.token(startToken);
this.printInnerComments(node);

if (props.length) {
this.space();
this.printList(props, node, { indent: true, statement: true });
this.space();
}
this.token(endToken);
}

export function TupleExpression(node: Object) {
const elems = node.elements;
const len = elems.length;

let startToken;
let endToken;
if (this.format.recordAndTupleSyntaxType === "bar") {
startToken = "[|";
endToken = "|]";
} else if (this.format.recordAndTupleSyntaxType === "hash") {
startToken = "#[";
endToken = "]";
} else {
throw new Error(
`${this.format.recordAndTupleSyntaxType} is not a valid recordAndTuple syntax type`,
);
}

this.token(startToken);
this.printInnerComments(node);

for (let i = 0; i < elems.length; i++) {
const elem = elems[i];
if (elem) {
if (i > 0) this.space();
this.print(elem, node);
if (i < len - 1) this.token(",");
}
}

this.token(endToken);
}

export function RegExpLiteral(node: Object) {
this.word(`/${node.pattern}/${node.flags}`);
}
Expand Down
1 change: 1 addition & 0 deletions packages/babel-generator/src/index.js
Expand Up @@ -58,6 +58,7 @@ function normalizeOptions(code, opts): Format {
wrap: true,
...opts.jsescOption,
},
recordAndTupleSyntaxType: opts.recordAndTupleSyntaxType,
};

if (format.minified) {
Expand Down
@@ -0,0 +1 @@
{| a: 1 |}
@@ -0,0 +1,4 @@
{
"plugins": [["recordAndTuple", { "syntaxType": "bar" }]],
"recordAndTupleSyntaxType": "bar"
}
@@ -0,0 +1,3 @@
{|
a: 1
|};
@@ -0,0 +1 @@
[| 1 |]
@@ -0,0 +1,4 @@
{
"plugins": [["recordAndTuple", { "syntaxType": "bar" }]],
"recordAndTupleSyntaxType": "bar"
}
@@ -0,0 +1 @@
[|1|];
@@ -0,0 +1 @@
#{ a: 1 }
@@ -0,0 +1,4 @@
{
"plugins": [["recordAndTuple", { "syntaxType": "hash" }]],
"recordAndTupleSyntaxType": "hash"
}
@@ -0,0 +1,3 @@
#{
a: 1
};
@@ -0,0 +1 @@
#[ 1 ]
@@ -0,0 +1,4 @@
{
"plugins": [["recordAndTuple", { "syntaxType": "hash" }]],
"recordAndTupleSyntaxType": "hash"
}
@@ -0,0 +1 @@
#[1];
@@ -0,0 +1 @@
#{ a: 1 }
@@ -0,0 +1,5 @@
{
"plugins": [["recordAndTuple", { "syntaxType": "hash" }]],
"recordAndTupleSyntaxType": "invalid",
"throws": "The \"recordAndTupleSyntaxType\" generator option must be \"bar\" or \"hash\" (\"invalid\" received)"
}
@@ -0,0 +1 @@
#{ a: 1 }
@@ -0,0 +1,4 @@
{
"plugins": [["recordAndTuple", { "syntaxType": "hash" }]],
"throws": "The \"recordAndTupleSyntaxType\" generator option must be \"bar\" or \"hash\" (undefined received)"
}
39 changes: 25 additions & 14 deletions packages/babel-generator/test/index.js
Expand Up @@ -534,22 +534,33 @@ suites.forEach(function(testSuite) {
sourceMaps: task.sourceMap ? true : task.options.sourceMaps,
};

const result = generate(actualAst, options, actualCode);

if (options.sourceMaps) {
expect(result.map).toEqual(task.sourceMap);
}
const run = () => {
return generate(actualAst, options, actualCode);
};

if (
!expected.code &&
result.code &&
fs.statSync(path.dirname(expected.loc)).isDirectory() &&
!process.env.CI
) {
console.log(`New test file created: ${expected.loc}`);
fs.writeFileSync(expected.loc, result.code);
const throwMsg = task.options.throws;
if (throwMsg) {
expect(() => run()).toThrow(
throwMsg === true ? undefined : throwMsg,
);
} else {
expect(result.code).toBe(expected.code);
const result = run();

if (options.sourceMaps) {
expect(result.map).toEqual(task.sourceMap);
}

if (
!expected.code &&
result.code &&
fs.statSync(path.dirname(expected.loc)).isDirectory() &&
!process.env.CI
) {
console.log(`New test file created: ${expected.loc}`);
fs.writeFileSync(expected.loc, result.code);
} else {
expect(result.code).toBe(expected.code);
}
}
}
},
Expand Down
20 changes: 20 additions & 0 deletions packages/babel-parser/ast/spec.md
Expand Up @@ -58,6 +58,8 @@ These are the core @babel/parser (babylon) AST node types.
- [ObjectMember](#objectmember)
- [ObjectProperty](#objectproperty)
- [ObjectMethod](#objectmethod)
- [RecordExpression](#recordexpression)
- [TupleExpression](#tupleexpression)
- [FunctionExpression](#functionexpression)
- [Unary operations](#unary-operations)
- [UnaryExpression](#unaryexpression)
Expand Down Expand Up @@ -719,6 +721,24 @@ interface ObjectMethod <: ObjectMember, Function {
}
```

## RecordExpression

```js
interface RecordExpression <: Expression {
type: "RecordExpression";
properties: [ ObjectProperty | ObjectMethod | SpreadElement ];
JLHwung marked this conversation as resolved.
Show resolved Hide resolved
}
```

## TupleExpression

```js
interface TupleExpression <: Expression {
type: "TupleExpression";
elements: [ Expression | SpreadElement | null ];
}
```

## FunctionExpression

```js
Expand Down
52 changes: 44 additions & 8 deletions packages/babel-parser/src/parser/expression.js
Expand Up @@ -1026,6 +1026,25 @@ export default class ExpressionParser extends LValParser {
case tt.parenL:
return this.parseParenAndDistinguishExpression(canBeArrow);

case tt.bracketBarL:
case tt.bracketHashL: {
this.expectPlugin("recordAndTuple");
const oldInFSharpPipelineDirectBody = this.state
.inFSharpPipelineDirectBody;
const close =
this.state.type === tt.bracketBarL ? tt.bracketBarR : tt.bracketR;
this.state.inFSharpPipelineDirectBody = false;
node = this.startNode();
this.next();
node.elements = this.parseExprList(
close,
true,
refExpressionErrors,
node,
);
this.state.inFSharpPipelineDirectBody = oldInFSharpPipelineDirectBody;
return this.finishNode(node, "TupleExpression");
}
case tt.bracketL: {
const oldInFSharpPipelineDirectBody = this.state
.inFSharpPipelineDirectBody;
Expand All @@ -1049,11 +1068,23 @@ export default class ExpressionParser extends LValParser {
this.state.inFSharpPipelineDirectBody = oldInFSharpPipelineDirectBody;
return this.finishNode(node, "ArrayExpression");
}
case tt.braceBarL:
case tt.braceHashL: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should assert recordAndTuple presents otherwise it is a syntax error.

this.expectPlugin("recordAndTuple");

Can you add a test case when recordAndTuple is not enabled?

this.expectPlugin("recordAndTuple");
const oldInFSharpPipelineDirectBody = this.state
.inFSharpPipelineDirectBody;
const close =
this.state.type === tt.braceBarL ? tt.braceBarR : tt.braceR;
this.state.inFSharpPipelineDirectBody = false;
const ret = this.parseObj(close, false, true, refExpressionErrors);
this.state.inFSharpPipelineDirectBody = oldInFSharpPipelineDirectBody;
return ret;
}
case tt.braceL: {
const oldInFSharpPipelineDirectBody = this.state
.inFSharpPipelineDirectBody;
this.state.inFSharpPipelineDirectBody = false;
const ret = this.parseObj(false, refExpressionErrors);
const ret = this.parseObj(tt.braceR, false, false, refExpressionErrors);
this.state.inFSharpPipelineDirectBody = oldInFSharpPipelineDirectBody;
return ret;
}
Expand Down Expand Up @@ -1465,10 +1496,12 @@ export default class ExpressionParser extends LValParser {
return this.finishNode(node, "TemplateLiteral");
}

// Parse an object literal or binding pattern.
// Parse an object literal, binding pattern, or record.

parseObj<T: N.ObjectPattern | N.ObjectExpression>(
close: TokenType,
isPattern: boolean,
isRecord?: ?boolean,
refExpressionErrors?: ?ExpressionErrors,
): T {
const propHash: any = Object.create(null);
Expand All @@ -1478,12 +1511,12 @@ export default class ExpressionParser extends LValParser {
node.properties = [];
this.next();

while (!this.eat(tt.braceR)) {
while (!this.eat(close)) {
if (first) {
first = false;
} else {
this.expect(tt.comma);
if (this.match(tt.braceR)) {
if (this.match(close)) {
this.addExtra(node, "trailingComma", this.state.lastTokStart);
this.next();
break;
Expand All @@ -1504,10 +1537,13 @@ export default class ExpressionParser extends LValParser {
node.properties.push(prop);
}

return this.finishNode(
node,
isPattern ? "ObjectPattern" : "ObjectExpression",
);
let type = "ObjectExpression";
if (isPattern) {
type = "ObjectPattern";
} else if (isRecord) {
type = "RecordExpression";
}
return this.finishNode(node, type);
}

isAsyncProp(prop: N.ObjectProperty): boolean {
Expand Down
12 changes: 12 additions & 0 deletions packages/babel-parser/src/parser/location.js
Expand Up @@ -127,6 +127,12 @@ export const Errors = Object.freeze({
PrimaryTopicRequiresSmartPipeline:
"Primary Topic Reference found but pipelineOperator not passed 'smart' for 'proposal' option.",
PrivateNameRedeclaration: "Duplicate private name #%0",
RecordExpressionBarIncorrectEndSyntaxType:
"Record expressions ending with '|}' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'bar'",
RecordExpressionBarIncorrectStartSyntaxType:
"Record expressions starting with '{|' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'bar'",
RecordExpressionHashIncorrectStartSyntaxType:
"Record expressions starting with '#{' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'hash'",
RestTrailingComma: "Unexpected trailing comma after rest element",
SloppyFunction:
"In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement",
Expand All @@ -143,6 +149,12 @@ export const Errors = Object.freeze({
SuperPrivateField: "Private fields can't be accessed on super",
//todo: rephrase this error message as it is too subjective
TrailingDecorator: "You have trailing decorators with no method",
TupleExpressionBarIncorrectEndSyntaxType:
"Tuple expressions ending with '|]' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'bar'",
TupleExpressionBarIncorrectStartSyntaxType:
"Tuple expressions starting with '[|' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'bar'",
TupleExpressionHashIncorrectStartSyntaxType:
"Tuple expressions starting with '#[' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'hash'",
UnexpectedArgumentPlaceholder: "Unexpected argument placeholder",
UnexpectedAwaitAfterPipelineBody:
'Unexpected "await" after pipeline body; await must have parentheses in minimal proposal',
Expand Down
4 changes: 3 additions & 1 deletion packages/babel-parser/src/parser/lval.js
Expand Up @@ -41,7 +41,9 @@ export default class LValParser extends NodeUtils {
refNeedsArrowPos?: ?Pos,
) => Expression;
+parseObj: <T: ObjectPattern | ObjectExpression>(
close: TokenType,
isPattern: boolean,
isRecord?: ?boolean,
refExpressionErrors?: ?ExpressionErrors,
) => T;
*/
Expand Down Expand Up @@ -253,7 +255,7 @@ export default class LValParser extends NodeUtils {
}

case tt.braceL:
return this.parseObj(true);
return this.parseObj(tt.braceR, true);
}

return this.parseIdentifier();
Expand Down