Added support for record and tuple syntax. (#10865)

* Added support for record and tuple syntax.

This commit adds support for both the hash #{} and bar {||}
syntaxes to babel-parser, as well as adds the supporting
babel-plugin-syntax-record-and-tuple plugin to enable support
for the syntax. Does not include any transform for records and
tuples.

* typo

* added check to ensure recordAndTuple in babel-parser

* switched to syntaxType option instead of explicit entries for each record and tuple type

* switched to using recordAndTupleSyntaxType for generator options instead of adding to node

* added tests for generator option recordAndTupleSyntaxType

* added test for record and tuple bar syntax with flow typings

* added tests for invalid/missing recordAndTuple syntaxType parser option

* fixed flowcheck errors

* fix merge with class privates in tokenizer

* Update packages/babel-parser/src/types.js

Co-Authored-By: Nicolò Ribaudo <nicolo.ribaudo@gmail.com>

* improved recordAndTuple generator error message, added tests for invalid,missing options

* updated error messages for invalid parser syntaxType option

* updated error message

* added better error messages for when the recordAndTuple syntaxType is doesn't match the syntax used

* updated record and tuple support to use new error message templates

* added recordAndTuple to missing plugin helpers

* Fix linting

Co-authored-by: Nicolò Ribaudo <nicolo.ribaudo@gmail.com>
This commit is contained in:
Rick Button
2020-03-16 18:57:44 -04:00
committed by GitHub
parent e06bf8ffdb
commit 3ce7c2e394
69 changed files with 2253 additions and 30 deletions

View File

@@ -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;
@@ -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: {
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;
}
@@ -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);
@@ -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;
@@ -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 {

View File

@@ -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",
@@ -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',

View File

@@ -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;
*/
@@ -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();

View File

@@ -39,6 +39,7 @@ export function getPluginOption(
}
const PIPELINE_PROPOSALS = ["minimal", "smart", "fsharp"];
const RECORD_AND_TUPLE_SYNTAX_TYPES = ["hash", "bar"];
export function validatePlugins(plugins: PluginList) {
if (hasPlugin(plugins, "decorators")) {
@@ -84,6 +85,18 @@ export function validatePlugins(plugins: PluginList) {
PIPELINE_PROPOSALS.map(p => `'${p}'`).join(", "),
);
}
if (
hasPlugin(plugins, "recordAndTuple") &&
!RECORD_AND_TUPLE_SYNTAX_TYPES.includes(
getPluginOption(plugins, "recordAndTuple", "syntaxType"),
)
) {
throw new Error(
"'recordAndTuple' requires 'syntaxType' option whose value should be one of: " +
RECORD_AND_TUPLE_SYNTAX_TYPES.map(p => `'${p}'`).join(", "),
);
}
}
// These plugins are defined using a mixin which extends the parser class.

View File

@@ -405,6 +405,28 @@ export default class Tokenizer extends LocationParser {
}
if (
this.hasPlugin("recordAndTuple") &&
(next === charCodes.leftCurlyBrace ||
next === charCodes.leftSquareBracket)
) {
if (this.getPluginOption("recordAndTuple", "syntaxType") !== "hash") {
throw this.raise(
this.state.pos,
next === charCodes.leftCurlyBrace
? Errors.RecordExpressionHashIncorrectStartSyntaxType
: Errors.TupleExpressionHashIncorrectStartSyntaxType,
);
}
if (next === charCodes.leftCurlyBrace) {
// #{
this.finishToken(tt.braceHashL);
} else {
// #[
this.finishToken(tt.bracketHashL);
}
this.state.pos += 2;
} else if (
this.hasPlugin("classPrivateProperties") ||
this.hasPlugin("classPrivateMethods") ||
this.getPluginOption("pipelineOperator", "proposal") === "smart"
@@ -453,12 +475,12 @@ export default class Tokenizer extends LocationParser {
readToken_interpreter(): boolean {
if (this.state.pos !== 0 || this.length < 2) return false;
let ch = this.input.charCodeAt(this.state.pos + 1);
if (ch !== charCodes.exclamationMark) return false;
const start = this.state.pos;
this.state.pos += 1;
let ch = this.input.charCodeAt(this.state.pos);
if (ch !== charCodes.exclamationMark) return false;
while (!isNewLine(ch) && ++this.state.pos < this.length) {
ch = this.input.charCodeAt(this.state.pos);
}
@@ -514,6 +536,37 @@ export default class Tokenizer extends LocationParser {
this.finishOp(tt.pipeline, 2);
return;
}
// '|}'
if (
this.hasPlugin("recordAndTuple") &&
next === charCodes.rightCurlyBrace
) {
if (this.getPluginOption("recordAndTuple", "syntaxType") !== "bar") {
throw this.raise(
this.state.pos,
Errors.RecordExpressionBarIncorrectEndSyntaxType,
);
}
this.finishOp(tt.braceBarR, 2);
return;
}
// '|]'
if (
this.hasPlugin("recordAndTuple") &&
next === charCodes.rightSquareBracket
) {
if (this.getPluginOption("recordAndTuple", "syntaxType") !== "bar") {
throw this.raise(
this.state.pos,
Errors.TupleExpressionBarIncorrectEndSyntaxType,
);
}
this.finishOp(tt.bracketBarR, 2);
return;
}
}
if (next === charCodes.equalsTo) {
@@ -682,16 +735,48 @@ export default class Tokenizer extends LocationParser {
this.finishToken(tt.comma);
return;
case charCodes.leftSquareBracket:
++this.state.pos;
this.finishToken(tt.bracketL);
if (
this.hasPlugin("recordAndTuple") &&
this.input.charCodeAt(this.state.pos + 1) === charCodes.verticalBar
) {
if (this.getPluginOption("recordAndTuple", "syntaxType") !== "bar") {
throw this.raise(
this.state.pos,
Errors.TupleExpressionBarIncorrectStartSyntaxType,
);
}
// [|
this.finishToken(tt.bracketBarL);
this.state.pos += 2;
} else {
++this.state.pos;
this.finishToken(tt.bracketL);
}
return;
case charCodes.rightSquareBracket:
++this.state.pos;
this.finishToken(tt.bracketR);
return;
case charCodes.leftCurlyBrace:
++this.state.pos;
this.finishToken(tt.braceL);
if (
this.hasPlugin("recordAndTuple") &&
this.input.charCodeAt(this.state.pos + 1) === charCodes.verticalBar
) {
if (this.getPluginOption("recordAndTuple", "syntaxType") !== "bar") {
throw this.raise(
this.state.pos,
Errors.RecordExpressionBarIncorrectStartSyntaxType,
);
}
// {|
this.finishToken(tt.braceBarL);
this.state.pos += 2;
} else {
++this.state.pos;
this.finishToken(tt.braceL);
}
return;
case charCodes.rightCurlyBrace:
++this.state.pos;

View File

@@ -93,9 +93,13 @@ export const types: { [name: string]: TokenType } = {
// Punctuation token types.
bracketL: new TokenType("[", { beforeExpr, startsExpr }),
bracketHashL: new TokenType("#[", { beforeExpr, startsExpr }),
bracketBarL: new TokenType("[|", { beforeExpr, startsExpr }),
bracketR: new TokenType("]"),
bracketBarR: new TokenType("|]"),
braceL: new TokenType("{", { beforeExpr, startsExpr }),
braceBarL: new TokenType("{|", { beforeExpr, startsExpr }),
braceHashL: new TokenType("#{", { beforeExpr, startsExpr }),
braceR: new TokenType("}"),
braceBarR: new TokenType("|}"),
parenL: new TokenType("(", { beforeExpr, startsExpr }),

View File

@@ -389,11 +389,21 @@ export type ArrayExpression = NodeBase & {
elements: $ReadOnlyArray<?(Expression | SpreadElement)>,
};
export type TupleExpression = NodeBase & {
type: "TupleExpression",
elements: $ReadOnlyArray<?(Expression | SpreadElement)>,
};
export type ObjectExpression = NodeBase & {
type: "ObjectExpression",
properties: $ReadOnlyArray<ObjectProperty | ObjectMethod | SpreadElement>,
};
export type RecordExpression = NodeBase & {
type: "RecordExpression",
properties: $ReadOnlyArray<ObjectProperty | ObjectMethod | SpreadElement>,
};
export type ObjectOrClassMember = ClassMethod | ClassProperty | ObjectMember;
export type ObjectMember = ObjectProperty | ObjectMethod;