Support parsing Flow's Optional Indexed Access Types (#13224)

This commit is contained in:
George Zahariev 2021-04-27 10:27:48 -07:00 committed by Nicolò Ribaudo
parent 57daba85aa
commit 8433cd0c05
18 changed files with 327 additions and 12 deletions

View File

@ -745,3 +745,16 @@ export function IndexedAccessType(this: Printer, node: t.IndexedAccessType) {
this.print(node.indexType, node); this.print(node.indexType, node);
this.token("]"); this.token("]");
} }
export function OptionalIndexedAccessType(
this: Printer,
node: t.OptionalIndexedAccessType,
) {
this.print(node.objectType, node);
if (node.optional) {
this.token("?.");
}
this.token("[");
this.print(node.indexType, node);
this.token("]");
}

View File

@ -135,6 +135,10 @@ export function UnionTypeAnnotation(node: any, parent: any): boolean {
export { UnionTypeAnnotation as IntersectionTypeAnnotation }; export { UnionTypeAnnotation as IntersectionTypeAnnotation };
export function OptionalIndexedAccessType(node: any, parent: any): boolean {
return t.isIndexedAccessType(parent, { objectType: node });
}
export function TSAsExpression() { export function TSAsExpression() {
return true; return true;
} }

View File

@ -0,0 +1,9 @@
type A = Obj?.['a'];
type B = Array<string>?.[number];
type C = Obj?.['bar']['baz'];
type D = (Obj?.['bar'])['baz'];
type E = Obj?.['bar'][];

View File

@ -0,0 +1,5 @@
type A = Obj?.['a'];
type B = Array<string>?.[number];
type C = Obj?.['bar']['baz'];
type D = (Obj?.['bar'])['baz'];
type E = Obj?.['bar'][];

View File

@ -1619,13 +1619,19 @@ export default (superClass: Class<Parser>): Class<Parser> =>
} }
flowParsePostfixType(): N.FlowTypeAnnotation { flowParsePostfixType(): N.FlowTypeAnnotation {
const startPos = this.state.start, const startPos = this.state.start;
startLoc = this.state.startLoc; const startLoc = this.state.startLoc;
let type = this.flowParsePrimaryType(); let type = this.flowParsePrimaryType();
while (this.match(tt.bracketL) && !this.canInsertSemicolon()) { let seenOptionalIndexedAccess = false;
while (
(this.match(tt.bracketL) || this.match(tt.questionDot)) &&
!this.canInsertSemicolon()
) {
const node = this.startNodeAt(startPos, startLoc); const node = this.startNodeAt(startPos, startLoc);
const optional = this.eat(tt.questionDot);
seenOptionalIndexedAccess = seenOptionalIndexedAccess || optional;
this.expect(tt.bracketL); this.expect(tt.bracketL);
if (this.match(tt.bracketR)) { if (!optional && this.match(tt.bracketR)) {
node.elementType = type; node.elementType = type;
this.next(); // eat `]` this.next(); // eat `]`
type = this.finishNode(node, "ArrayTypeAnnotation"); type = this.finishNode(node, "ArrayTypeAnnotation");
@ -1633,10 +1639,18 @@ export default (superClass: Class<Parser>): Class<Parser> =>
node.objectType = type; node.objectType = type;
node.indexType = this.flowParseType(); node.indexType = this.flowParseType();
this.expect(tt.bracketR); this.expect(tt.bracketR);
type = this.finishNode<N.FlowIndexedAccessType>( if (seenOptionalIndexedAccess) {
node, node.optional = optional;
"IndexedAccessType", type = this.finishNode<N.FlowOptionalIndexedAccessType>(
); node,
"OptionalIndexedAccessType",
);
} else {
type = this.finishNode<N.FlowIndexedAccessType>(
node,
"IndexedAccessType",
);
}
} }
} }
return type; return type;
@ -2216,6 +2230,9 @@ export default (superClass: Class<Parser>): Class<Parser> =>
) { ) {
return this.finishOp(tt.relational, 1); return this.finishOp(tt.relational, 1);
} else if (this.state.inType && code === charCodes.questionMark) { } else if (this.state.inType && code === charCodes.questionMark) {
if (next === charCodes.dot) {
return this.finishOp(tt.questionDot, 2);
}
// allow double nullable types in Flow: ??string // allow double nullable types in Flow: ??string
return this.finishOp(tt.question, 1); return this.finishOp(tt.question, 1);
} else if (isIteratorStart(code, next)) { } else if (isIteratorStart(code, next)) {

View File

@ -1058,6 +1058,13 @@ export type FlowIndexedAccessType = Node & {
indexType: FlowType, indexType: FlowType,
}; };
export type FlowOptionalIndexedAccessType = Node & {
type: "OptionalIndexedAccessType",
objectType: FlowType,
indexType: FlowType,
optional: boolean,
};
// ESTree // ESTree
export type EstreeProperty = NodeBase & { export type EstreeProperty = NodeBase & {

View File

@ -0,0 +1,7 @@
type A = Obj?.['a'];
type B = Obj['a']?.['b'];
type C = Obj?.['a']['b'];
type D = Obj?.['a']?.['b'];

View File

@ -0,0 +1,190 @@
{
"type": "File",
"start":0,"end":103,"loc":{"start":{"line":1,"column":0},"end":{"line":7,"column":27}},
"program": {
"type": "Program",
"start":0,"end":103,"loc":{"start":{"line":1,"column":0},"end":{"line":7,"column":27}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "TypeAlias",
"start":0,"end":20,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":20}},
"id": {
"type": "Identifier",
"start":5,"end":6,"loc":{"start":{"line":1,"column":5},"end":{"line":1,"column":6},"identifierName":"A"},
"name": "A"
},
"typeParameters": null,
"right": {
"type": "OptionalIndexedAccessType",
"start":9,"end":19,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":19}},
"objectType": {
"type": "GenericTypeAnnotation",
"start":9,"end":12,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":12}},
"typeParameters": null,
"id": {
"type": "Identifier",
"start":9,"end":12,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":12},"identifierName":"Obj"},
"name": "Obj"
}
},
"indexType": {
"type": "StringLiteralTypeAnnotation",
"start":15,"end":18,"loc":{"start":{"line":1,"column":15},"end":{"line":1,"column":18}},
"extra": {
"rawValue": "a",
"raw": "'a'"
},
"value": "a"
},
"optional": true
}
},
{
"type": "TypeAlias",
"start":22,"end":47,"loc":{"start":{"line":3,"column":0},"end":{"line":3,"column":25}},
"id": {
"type": "Identifier",
"start":27,"end":28,"loc":{"start":{"line":3,"column":5},"end":{"line":3,"column":6},"identifierName":"B"},
"name": "B"
},
"typeParameters": null,
"right": {
"type": "OptionalIndexedAccessType",
"start":31,"end":46,"loc":{"start":{"line":3,"column":9},"end":{"line":3,"column":24}},
"objectType": {
"type": "IndexedAccessType",
"start":31,"end":39,"loc":{"start":{"line":3,"column":9},"end":{"line":3,"column":17}},
"objectType": {
"type": "GenericTypeAnnotation",
"start":31,"end":34,"loc":{"start":{"line":3,"column":9},"end":{"line":3,"column":12}},
"typeParameters": null,
"id": {
"type": "Identifier",
"start":31,"end":34,"loc":{"start":{"line":3,"column":9},"end":{"line":3,"column":12},"identifierName":"Obj"},
"name": "Obj"
}
},
"indexType": {
"type": "StringLiteralTypeAnnotation",
"start":35,"end":38,"loc":{"start":{"line":3,"column":13},"end":{"line":3,"column":16}},
"extra": {
"rawValue": "a",
"raw": "'a'"
},
"value": "a"
}
},
"indexType": {
"type": "StringLiteralTypeAnnotation",
"start":42,"end":45,"loc":{"start":{"line":3,"column":20},"end":{"line":3,"column":23}},
"extra": {
"rawValue": "b",
"raw": "'b'"
},
"value": "b"
},
"optional": true
}
},
{
"type": "TypeAlias",
"start":49,"end":74,"loc":{"start":{"line":5,"column":0},"end":{"line":5,"column":25}},
"id": {
"type": "Identifier",
"start":54,"end":55,"loc":{"start":{"line":5,"column":5},"end":{"line":5,"column":6},"identifierName":"C"},
"name": "C"
},
"typeParameters": null,
"right": {
"type": "OptionalIndexedAccessType",
"start":58,"end":73,"loc":{"start":{"line":5,"column":9},"end":{"line":5,"column":24}},
"objectType": {
"type": "OptionalIndexedAccessType",
"start":58,"end":68,"loc":{"start":{"line":5,"column":9},"end":{"line":5,"column":19}},
"objectType": {
"type": "GenericTypeAnnotation",
"start":58,"end":61,"loc":{"start":{"line":5,"column":9},"end":{"line":5,"column":12}},
"typeParameters": null,
"id": {
"type": "Identifier",
"start":58,"end":61,"loc":{"start":{"line":5,"column":9},"end":{"line":5,"column":12},"identifierName":"Obj"},
"name": "Obj"
}
},
"indexType": {
"type": "StringLiteralTypeAnnotation",
"start":64,"end":67,"loc":{"start":{"line":5,"column":15},"end":{"line":5,"column":18}},
"extra": {
"rawValue": "a",
"raw": "'a'"
},
"value": "a"
},
"optional": true
},
"indexType": {
"type": "StringLiteralTypeAnnotation",
"start":69,"end":72,"loc":{"start":{"line":5,"column":20},"end":{"line":5,"column":23}},
"extra": {
"rawValue": "b",
"raw": "'b'"
},
"value": "b"
},
"optional": false
}
},
{
"type": "TypeAlias",
"start":76,"end":103,"loc":{"start":{"line":7,"column":0},"end":{"line":7,"column":27}},
"id": {
"type": "Identifier",
"start":81,"end":82,"loc":{"start":{"line":7,"column":5},"end":{"line":7,"column":6},"identifierName":"D"},
"name": "D"
},
"typeParameters": null,
"right": {
"type": "OptionalIndexedAccessType",
"start":85,"end":102,"loc":{"start":{"line":7,"column":9},"end":{"line":7,"column":26}},
"objectType": {
"type": "OptionalIndexedAccessType",
"start":85,"end":95,"loc":{"start":{"line":7,"column":9},"end":{"line":7,"column":19}},
"objectType": {
"type": "GenericTypeAnnotation",
"start":85,"end":88,"loc":{"start":{"line":7,"column":9},"end":{"line":7,"column":12}},
"typeParameters": null,
"id": {
"type": "Identifier",
"start":85,"end":88,"loc":{"start":{"line":7,"column":9},"end":{"line":7,"column":12},"identifierName":"Obj"},
"name": "Obj"
}
},
"indexType": {
"type": "StringLiteralTypeAnnotation",
"start":91,"end":94,"loc":{"start":{"line":7,"column":15},"end":{"line":7,"column":18}},
"extra": {
"rawValue": "a",
"raw": "'a'"
},
"value": "a"
},
"optional": true
},
"indexType": {
"type": "StringLiteralTypeAnnotation",
"start":98,"end":101,"loc":{"start":{"line":7,"column":22},"end":{"line":7,"column":25}},
"extra": {
"rawValue": "b",
"raw": "'b'"
},
"value": "b"
},
"optional": true
}
}
],
"directives": []
}
}

View File

@ -390,6 +390,9 @@ export interface NodePathAssetions {
assertOptionalCallExpression( assertOptionalCallExpression(
opts?: object, opts?: object,
): asserts this is NodePath<t.OptionalCallExpression>; ): asserts this is NodePath<t.OptionalCallExpression>;
assertOptionalIndexedAccessType(
opts?: object,
): asserts this is NodePath<t.OptionalIndexedAccessType>;
assertOptionalMemberExpression( assertOptionalMemberExpression(
opts?: object, opts?: object,
): asserts this is NodePath<t.OptionalMemberExpression>; ): asserts this is NodePath<t.OptionalMemberExpression>;

View File

@ -234,6 +234,9 @@ export interface NodePathValidators {
isOptionalCallExpression( isOptionalCallExpression(
opts?: object, opts?: object,
): this is NodePath<t.OptionalCallExpression>; ): this is NodePath<t.OptionalCallExpression>;
isOptionalIndexedAccessType(
opts?: object,
): this is NodePath<t.OptionalIndexedAccessType>;
isOptionalMemberExpression( isOptionalMemberExpression(
opts?: object, opts?: object,
): this is NodePath<t.OptionalMemberExpression>; ): this is NodePath<t.OptionalMemberExpression>;

View File

@ -884,6 +884,12 @@ export function assertIndexedAccessType(
): asserts node is t.IndexedAccessType { ): asserts node is t.IndexedAccessType {
assert("IndexedAccessType", node, opts); assert("IndexedAccessType", node, opts);
} }
export function assertOptionalIndexedAccessType(
node: object | null | undefined,
opts?: object | null,
): asserts node is t.OptionalIndexedAccessType {
assert("OptionalIndexedAccessType", node, opts);
}
export function assertJSXAttribute( export function assertJSXAttribute(
node: object | null | undefined, node: object | null | undefined,
opts?: object | null, opts?: object | null,

View File

@ -208,6 +208,7 @@ export type Node =
| ObjectTypeSpreadProperty | ObjectTypeSpreadProperty
| OpaqueType | OpaqueType
| OptionalCallExpression | OptionalCallExpression
| OptionalIndexedAccessType
| OptionalMemberExpression | OptionalMemberExpression
| ParenthesizedExpression | ParenthesizedExpression
| Pattern | Pattern
@ -1395,6 +1396,13 @@ export interface IndexedAccessType extends BaseNode {
indexType: FlowType; indexType: FlowType;
} }
export interface OptionalIndexedAccessType extends BaseNode {
type: "OptionalIndexedAccessType";
objectType: FlowType;
indexType: FlowType;
optional: boolean;
}
export interface JSXAttribute extends BaseNode { export interface JSXAttribute extends BaseNode {
type: "JSXAttribute"; type: "JSXAttribute";
name: JSXIdentifier | JSXNamespacedName; name: JSXIdentifier | JSXNamespacedName;
@ -2361,7 +2369,9 @@ export type Flow =
| TypeParameterInstantiation | TypeParameterInstantiation
| UnionTypeAnnotation | UnionTypeAnnotation
| Variance | Variance
| VoidTypeAnnotation; | VoidTypeAnnotation
| IndexedAccessType
| OptionalIndexedAccessType;
export type FlowType = export type FlowType =
| AnyTypeAnnotation | AnyTypeAnnotation
| ArrayTypeAnnotation | ArrayTypeAnnotation
@ -2386,7 +2396,9 @@ export type FlowType =
| TupleTypeAnnotation | TupleTypeAnnotation
| TypeofTypeAnnotation | TypeofTypeAnnotation
| UnionTypeAnnotation | UnionTypeAnnotation
| VoidTypeAnnotation; | VoidTypeAnnotation
| IndexedAccessType
| OptionalIndexedAccessType;
export type FlowBaseAnnotation = export type FlowBaseAnnotation =
| AnyTypeAnnotation | AnyTypeAnnotation
| BooleanTypeAnnotation | BooleanTypeAnnotation

View File

@ -860,6 +860,12 @@ export function indexedAccessType(
): t.IndexedAccessType { ): t.IndexedAccessType {
return builder("IndexedAccessType", ...arguments); return builder("IndexedAccessType", ...arguments);
} }
export function optionalIndexedAccessType(
objectType: t.FlowType,
indexType: t.FlowType,
): t.OptionalIndexedAccessType {
return builder("OptionalIndexedAccessType", ...arguments);
}
export function jsxAttribute( export function jsxAttribute(
name: t.JSXIdentifier | t.JSXNamespacedName, name: t.JSXIdentifier | t.JSXNamespacedName,
value?: value?:

View File

@ -154,6 +154,7 @@ export {
enumStringMember as EnumStringMember, enumStringMember as EnumStringMember,
enumDefaultedMember as EnumDefaultedMember, enumDefaultedMember as EnumDefaultedMember,
indexedAccessType as IndexedAccessType, indexedAccessType as IndexedAccessType,
optionalIndexedAccessType as OptionalIndexedAccessType,
jsxAttribute as JSXAttribute, jsxAttribute as JSXAttribute,
jsxClosingElement as JSXClosingElement, jsxClosingElement as JSXClosingElement,
jsxElement as JSXElement, jsxElement as JSXElement,

View File

@ -562,8 +562,19 @@ defineType("EnumDefaultedMember", {
defineType("IndexedAccessType", { defineType("IndexedAccessType", {
visitor: ["objectType", "indexType"], visitor: ["objectType", "indexType"],
aliases: ["Flow", "FlowType"],
fields: { fields: {
objectType: validateType("FlowType"), objectType: validateType("FlowType"),
indexType: validateType("FlowType"), indexType: validateType("FlowType"),
}, },
}); });
defineType("OptionalIndexedAccessType", {
visitor: ["objectType", "indexType"],
aliases: ["Flow", "FlowType"],
fields: {
objectType: validateType("FlowType"),
indexType: validateType("FlowType"),
optional: validate(assertValueType("boolean")),
},
});

View File

@ -2470,6 +2470,23 @@ export function isIndexedAccessType(
return false; return false;
} }
export function isOptionalIndexedAccessType(
node: object | null | undefined,
opts?: object | null,
): node is t.OptionalIndexedAccessType {
if (!node) return false;
const nodeType = (node as t.Node).type;
if (nodeType === "OptionalIndexedAccessType") {
if (typeof opts === "undefined") {
return true;
} else {
return shallowEqual(node, opts);
}
}
return false;
}
export function isJSXAttribute( export function isJSXAttribute(
node: object | null | undefined, node: object | null | undefined,
opts?: object | null, opts?: object | null,
@ -5101,7 +5118,9 @@ export function isFlow(
"TypeParameterInstantiation" === nodeType || "TypeParameterInstantiation" === nodeType ||
"UnionTypeAnnotation" === nodeType || "UnionTypeAnnotation" === nodeType ||
"Variance" === nodeType || "Variance" === nodeType ||
"VoidTypeAnnotation" === nodeType "VoidTypeAnnotation" === nodeType ||
"IndexedAccessType" === nodeType ||
"OptionalIndexedAccessType" === nodeType
) { ) {
if (typeof opts === "undefined") { if (typeof opts === "undefined") {
return true; return true;
@ -5143,7 +5162,9 @@ export function isFlowType(
"TupleTypeAnnotation" === nodeType || "TupleTypeAnnotation" === nodeType ||
"TypeofTypeAnnotation" === nodeType || "TypeofTypeAnnotation" === nodeType ||
"UnionTypeAnnotation" === nodeType || "UnionTypeAnnotation" === nodeType ||
"VoidTypeAnnotation" === nodeType "VoidTypeAnnotation" === nodeType ||
"IndexedAccessType" === nodeType ||
"OptionalIndexedAccessType" === nodeType
) { ) {
if (typeof opts === "undefined") { if (typeof opts === "undefined") {
return true; return true;