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:
@@ -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 {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user