From 461ba2531a51099b75c3fe22e28622741ab7d5d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Wed, 19 May 2021 16:00:24 -0400 Subject: [PATCH] refactor: add parse*Literal parser routines (#13333) * refactor: simplify parseLiteral interface * refactor: extract specific methods on parsing literals * fix: avoid StringLiteral type comparison * add test cases * fix: remove redundant node * Update packages/babel-parser/src/plugins/flow/index.js Co-authored-by: Federico Ciardi * update test fixtures * fix: refine parseLiteral typings Co-authored-by: Federico Ciardi --- .../babel-parser/src/parser/expression.js | 87 +++++++++++------ packages/babel-parser/src/parser/statement.js | 19 ++-- packages/babel-parser/src/plugins/estree.js | 70 +++++--------- .../babel-parser/src/plugins/flow/index.js | 31 +++--- packages/babel-parser/src/types.js | 45 ++++++++- .../valid-syntax-with-attributes/input.js | 1 + .../valid-syntax-with-attributes/options.json | 4 + .../valid-syntax-with-attributes/output.json | 51 ++++++++++ .../estree/module-string-names/mixed/input.js | 2 + .../module-string-names/mixed/options.json | 3 + .../module-string-names/mixed/output.json | 96 +++++++++++++++++++ 11 files changed, 308 insertions(+), 101 deletions(-) create mode 100644 packages/babel-parser/test/fixtures/estree/import-assertions/valid-syntax-with-attributes/input.js create mode 100644 packages/babel-parser/test/fixtures/estree/import-assertions/valid-syntax-with-attributes/options.json create mode 100644 packages/babel-parser/test/fixtures/estree/import-assertions/valid-syntax-with-attributes/output.json create mode 100644 packages/babel-parser/test/fixtures/estree/module-string-names/mixed/input.js create mode 100644 packages/babel-parser/test/fixtures/estree/module-string-names/mixed/options.json create mode 100644 packages/babel-parser/test/fixtures/estree/module-string-names/mixed/output.json diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index 43616202ee5f..eb2ddaa9e14e 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -1068,33 +1068,28 @@ export default class ExpressionParser extends LValParser { } case tt.regexp: { - const value = this.state.value; - node = this.parseLiteral(value.value, "RegExpLiteral"); - node.pattern = value.pattern; - node.flags = value.flags; - return node; + return this.parseRegExpLiteral(this.state.value); } case tt.num: - return this.parseLiteral(this.state.value, "NumericLiteral"); + return this.parseNumericLiteral(this.state.value); case tt.bigint: - return this.parseLiteral(this.state.value, "BigIntLiteral"); + return this.parseBigIntLiteral(this.state.value); case tt.decimal: - return this.parseLiteral(this.state.value, "DecimalLiteral"); + return this.parseDecimalLiteral(this.state.value); case tt.string: - return this.parseLiteral(this.state.value, "StringLiteral"); + return this.parseStringLiteral(this.state.value); case tt._null: - node = this.startNode(); - this.next(); - return this.finishNode(node, "NullLiteral"); + return this.parseNullLiteral(); case tt._true: + return this.parseBooleanLiteral(true); case tt._false: - return this.parseBooleanLiteral(); + return this.parseBooleanLiteral(false); case tt.parenL: return this.parseParenAndDistinguishExpression(canBeArrow); @@ -1290,13 +1285,6 @@ export default class ExpressionParser extends LValParser { return this.finishNode(node, "Super"); } - parseBooleanLiteral(): N.BooleanLiteral { - const node = this.startNode(); - node.value = this.match(tt._true); - this.next(); - return this.finishNode(node, "BooleanLiteral"); - } - parseMaybePrivateName( isPrivateNameAllowed: boolean, ): N.PrivateName | N.Identifier { @@ -1398,21 +1386,60 @@ export default class ExpressionParser extends LValParser { return this.parseMetaProperty(node, id, "meta"); } - parseLiteral( + parseLiteralAtNode( value: any, - type: /*T["kind"]*/ string, - startPos?: number, - startLoc?: Position, + type: $ElementType, + node: any, ): T { - startPos = startPos || this.state.start; - startLoc = startLoc || this.state.startLoc; - - const node = this.startNodeAt(startPos, startLoc); this.addExtra(node, "rawValue", value); - this.addExtra(node, "raw", this.input.slice(startPos, this.state.end)); + this.addExtra(node, "raw", this.input.slice(node.start, this.state.end)); node.value = value; this.next(); - return this.finishNode(node, type); + return this.finishNode(node, type); + } + + parseLiteral(value: any, type: $ElementType): T { + const node = this.startNode(); + return this.parseLiteralAtNode(value, type, node); + } + + parseStringLiteral(value: any) { + return this.parseLiteral(value, "StringLiteral"); + } + + parseNumericLiteral(value: any) { + return this.parseLiteral(value, "NumericLiteral"); + } + + parseBigIntLiteral(value: any) { + return this.parseLiteral(value, "BigIntLiteral"); + } + + parseDecimalLiteral(value: any) { + return this.parseLiteral(value, "DecimalLiteral"); + } + + parseRegExpLiteral(value: { value: any, pattern: string, flags: string }) { + const node = this.parseLiteral( + value.value, + "RegExpLiteral", + ); + node.pattern = value.pattern; + node.flags = value.flags; + return node; + } + + parseBooleanLiteral(value: boolean) { + const node = this.startNode(); + node.value = value; + this.next(); + return this.finishNode(node, "BooleanLiteral"); + } + + parseNullLiteral() { + const node = this.startNode(); + this.next(); + return this.finishNode(node, "NullLiteral"); } // https://tc39.es/ecma262/#prod-CoverParenthesizedExpressionAndArrowParameterList diff --git a/packages/babel-parser/src/parser/statement.js b/packages/babel-parser/src/parser/statement.js index 7aefb14a4f49..f3c9772c3023 100644 --- a/packages/babel-parser/src/parser/statement.js +++ b/packages/babel-parser/src/parser/statement.js @@ -2041,7 +2041,7 @@ export default class StatementParser extends ExpressionParser { // $FlowIgnore if (!isFrom && specifier.local) { const { local } = specifier; - if (local.type === "StringLiteral") { + if (local.type !== "Identifier") { this.raise( specifier.start, Errors.ExportBindingIsString, @@ -2157,10 +2157,7 @@ export default class StatementParser extends ExpressionParser { // https://tc39.es/ecma262/#prod-ModuleExportName parseModuleExportName(): N.StringLiteral | N.Identifier { if (this.match(tt.string)) { - const result = this.parseLiteral( - this.state.value, - "StringLiteral", - ); + const result = this.parseStringLiteral(this.state.value); const surrogate = result.value.match(loneSurrogate); if (surrogate) { this.raise( @@ -2259,7 +2256,7 @@ export default class StatementParser extends ExpressionParser { // parse AssertionKey : IdentifierName, StringLiteral const keyName = this.state.value; if (this.match(tt.string)) { - node.key = this.parseLiteral(keyName, "StringLiteral"); + node.key = this.parseStringLiteral(keyName); } else { node.key = this.parseIdentifier(true); } @@ -2291,10 +2288,7 @@ export default class StatementParser extends ExpressionParser { Errors.ModuleAttributeInvalidValue, ); } - node.value = this.parseLiteral( - this.state.value, - "StringLiteral", - ); + node.value = this.parseStringLiteral(this.state.value); this.finishNode(node, "ImportAttribute"); attrs.push(node); } while (this.eat(tt.comma)); @@ -2345,7 +2339,7 @@ export default class StatementParser extends ExpressionParser { Errors.ModuleAttributeInvalidValue, ); } - node.value = this.parseLiteral(this.state.value, "StringLiteral"); + node.value = this.parseStringLiteral(this.state.value); this.finishNode(node, "ImportAttribute"); attrs.push(node); } while (this.eat(tt.comma)); @@ -2424,12 +2418,13 @@ export default class StatementParser extends ExpressionParser { // https://tc39.es/ecma262/#prod-ImportSpecifier parseImportSpecifier(node: N.ImportDeclaration): void { const specifier = this.startNode(); + const importedIsString = this.match(tt.string); specifier.imported = this.parseModuleExportName(); if (this.eatContextual("as")) { specifier.local = this.parseIdentifier(); } else { const { imported } = specifier; - if (imported.type === "StringLiteral") { + if (importedIsString) { throw this.raise( specifier.start, Errors.ImportBindingIsString, diff --git a/packages/babel-parser/src/plugins/estree.js b/packages/babel-parser/src/plugins/estree.js index 9a592c18b6df..8d566a1ddd3a 100644 --- a/packages/babel-parser/src/plugins/estree.js +++ b/packages/babel-parser/src/plugins/estree.js @@ -1,6 +1,6 @@ // @flow -import { types as tt, TokenType } from "../tokenizer/types"; +import { TokenType } from "../tokenizer/types"; import type Parser from "../parser"; import type { ExpressionErrors } from "../parser/util"; import * as N from "../types"; @@ -9,7 +9,7 @@ import { Errors } from "../parser/error"; export default (superClass: Class): Class => class extends superClass { - estreeParseRegExpLiteral({ pattern, flags }: N.RegExpLiteral): N.Node { + parseRegExpLiteral({ pattern, flags }): N.Node { let regex = null; try { regex = new RegExp(pattern, flags); @@ -17,13 +17,13 @@ export default (superClass: Class): Class => // In environments that don't support these flags value will // be null as the regex can't be represented natively. } - const node = this.estreeParseLiteral(regex); + const node = this.estreeParseLiteral(regex); node.regex = { pattern, flags }; return node; } - estreeParseBigIntLiteral(value: any): N.Node { + parseBigIntLiteral(value: any): N.Node { // https://github.com/estree/estree/blob/master/es2020.md#bigintliteral let bigInt; try { @@ -32,13 +32,13 @@ export default (superClass: Class): Class => } catch { bigInt = null; } - const node = this.estreeParseLiteral(bigInt); + const node = this.estreeParseLiteral(bigInt); node.bigint = String(node.value || value); return node; } - estreeParseDecimalLiteral(value: any): N.Node { + parseDecimalLiteral(value: any): N.Node { // https://github.com/estree/estree/blob/master/experimental/decimal.md // todo: use BigDecimal when node supports it. const decimal = null; @@ -48,8 +48,24 @@ export default (superClass: Class): Class => return node; } - estreeParseLiteral(value: any): N.Node { - return this.parseLiteral(value, "Literal"); + estreeParseLiteral(value: any) { + return this.parseLiteral(value, "Literal"); + } + + parseStringLiteral(value: any): N.Node { + return this.estreeParseLiteral(value); + } + + parseNumericLiteral(value: any): any { + return this.estreeParseLiteral(value); + } + + parseNullLiteral(): N.Node { + return this.estreeParseLiteral(null); + } + + parseBooleanLiteral(value: boolean): N.BooleanLiteral { + return this.estreeParseLiteral(value); } directiveToStmt(directive: N.Directive): N.ExpressionStatement { @@ -165,35 +181,6 @@ export default (superClass: Class): Class => classBody.body.push(method); } - parseExprAtom(refExpressionErrors?: ?ExpressionErrors): N.Expression { - switch (this.state.type) { - case tt.num: - case tt.string: - return this.estreeParseLiteral(this.state.value); - - case tt.regexp: - return this.estreeParseRegExpLiteral(this.state.value); - - case tt.bigint: - return this.estreeParseBigIntLiteral(this.state.value); - - case tt.decimal: - return this.estreeParseDecimalLiteral(this.state.value); - - case tt._null: - return this.estreeParseLiteral(null); - - case tt._true: - return this.estreeParseLiteral(true); - - case tt._false: - return this.estreeParseLiteral(false); - - default: - return super.parseExprAtom(refExpressionErrors); - } - } - parseMaybePrivateName(...args: [boolean]): any { const node = super.parseMaybePrivateName(...args); if ( @@ -230,13 +217,8 @@ export default (superClass: Class): Class => return node.name; } - parseLiteral( - value: any, - type: /*T["kind"]*/ string, - startPos?: number, - startLoc?: Position, - ): T { - const node = super.parseLiteral(value, type, startPos, startLoc); + parseLiteral(value: any, type: $ElementType): T { + const node = super.parseLiteral(value, type); node.raw = node.extra.raw; delete node.extra; diff --git a/packages/babel-parser/src/plugins/flow/index.js b/packages/babel-parser/src/plugins/flow/index.js index 36eb19a7368b..d9c3c33b94db 100644 --- a/packages/babel-parser/src/plugins/flow/index.js +++ b/packages/babel-parser/src/plugins/flow/index.js @@ -1536,7 +1536,7 @@ export default (superClass: Class): Class => return this.finishNode(node, "FunctionTypeAnnotation"); case tt.string: - return this.parseLiteral( + return this.parseLiteral( this.state.value, "StringLiteralTypeAnnotation", ); @@ -1545,26 +1545,27 @@ export default (superClass: Class): Class => case tt._false: node.value = this.match(tt._true); this.next(); - return this.finishNode(node, "BooleanLiteralTypeAnnotation"); + return this.finishNode( + node, + "BooleanLiteralTypeAnnotation", + ); case tt.plusMin: if (this.state.value === "-") { this.next(); if (this.match(tt.num)) { - return this.parseLiteral( + return this.parseLiteralAtNode( -this.state.value, "NumberLiteralTypeAnnotation", - node.start, - node.loc.start, + node, ); } if (this.match(tt.bigint)) { - return this.parseLiteral( + return this.parseLiteralAtNode( -this.state.value, "BigIntLiteralTypeAnnotation", - node.start, - node.loc.start, + node, ); } @@ -2669,7 +2670,7 @@ export default (superClass: Class): Class => // parse import-type/typeof shorthand parseImportSpecifier(node: N.ImportDeclaration): void { const specifier = this.startNode(); - const firstIdentLoc = this.state.start; + const firstIdentIsString = this.match(tt.string); const firstIdent = this.parseModuleExportName(); let specifierTypeKind = null; @@ -2713,13 +2714,15 @@ export default (superClass: Class): Class => specifier.local = specifier.imported.__clone(); } } else { - if (firstIdent.type === "StringLiteral") { + if (firstIdentIsString) { + /*:: invariant(firstIdent instanceof N.StringLiteral) */ throw this.raise( specifier.start, Errors.ImportBindingIsString, firstIdent.value, ); } + /*:: invariant(firstIdent instanceof N.Node) */ isBinding = true; specifier.imported = firstIdent; specifier.importKind = null; @@ -2731,7 +2734,7 @@ export default (superClass: Class): Class => if (nodeIsTypeImport && specifierIsTypeImport) { this.raise( - firstIdentLoc, + specifier.start, FlowErrors.ImportTypeShorthandOnlyInPureImport, ); } @@ -3388,14 +3391,14 @@ export default (superClass: Class): Class => const endOfInit = () => this.match(tt.comma) || this.match(tt.braceR); switch (this.state.type) { case tt.num: { - const literal = this.parseLiteral(this.state.value, "NumericLiteral"); + const literal = this.parseNumericLiteral(this.state.value); if (endOfInit()) { return { type: "number", pos: literal.start, value: literal }; } return { type: "invalid", pos: startPos }; } case tt.string: { - const literal = this.parseLiteral(this.state.value, "StringLiteral"); + const literal = this.parseStringLiteral(this.state.value); if (endOfInit()) { return { type: "string", pos: literal.start, value: literal }; } @@ -3403,7 +3406,7 @@ export default (superClass: Class): Class => } case tt._true: case tt._false: { - const literal = this.parseBooleanLiteral(); + const literal = this.parseBooleanLiteral(this.match(tt._true)); if (endOfInit()) { return { type: "boolean", diff --git a/packages/babel-parser/src/types.js b/packages/babel-parser/src/types.js index c3af5a3fd226..4fd8649ed00a 100644 --- a/packages/babel-parser/src/types.js +++ b/packages/babel-parser/src/types.js @@ -98,7 +98,8 @@ export type Literal = | StringLiteral | BooleanLiteral | NumericLiteral - | BigIntLiteral; + | BigIntLiteral + | DecimalLiteral; export type RegExpLiteral = NodeBase & { type: "RegExpLiteral", @@ -130,6 +131,11 @@ export type BigIntLiteral = NodeBase & { value: number, }; +export type DecimalLiteral = NodeBase & { + type: "DecimalLiteral", + value: number, +}; + export type ParserOutput = { comments: $ReadOnlyArray, errors: Array, @@ -1066,7 +1072,44 @@ export type FlowOptionalIndexedAccessType = Node & { optional: boolean, }; +export type StringLiteralTypeAnnotation = NodeBase & { + type: "StringLiteralTypeAnnotation", + value: string, +}; + +export type BooleanLiteralTypeAnnotation = NodeBase & { + type: "BooleanLiteralTypeAnnotation", + value: boolean, +}; +export type NumberLiteralTypeAnnotation = NodeBase & { + type: "NumberLiteralTypeAnnotation", + value: number, +}; + +export type BigIntLiteralTypeAnnotation = NodeBase & { + type: "BigIntLiteralTypeAnnotation", + //todo(flow): use bigint when Flow supports BigInt + value: number, +}; + // ESTree +export type EstreeLiteral = NodeBase & { + type: "Literal", + value: any, +}; + +type EstreeRegExpLiteralRegex = { + pattern: string, + flags: string, +}; +export type EstreeRegExpLiteral = EstreeLiteral & { + regex: EstreeRegExpLiteralRegex, +}; + +export type EstreeBigIntLiteral = EstreeLiteral & { + value: number | null, + bigint: string, +}; export type EstreeProperty = NodeBase & { type: "Property", diff --git a/packages/babel-parser/test/fixtures/estree/import-assertions/valid-syntax-with-attributes/input.js b/packages/babel-parser/test/fixtures/estree/import-assertions/valid-syntax-with-attributes/input.js new file mode 100644 index 000000000000..7b43091236b9 --- /dev/null +++ b/packages/babel-parser/test/fixtures/estree/import-assertions/valid-syntax-with-attributes/input.js @@ -0,0 +1 @@ +import foo from "foo.json" assert { type: "json" }; diff --git a/packages/babel-parser/test/fixtures/estree/import-assertions/valid-syntax-with-attributes/options.json b/packages/babel-parser/test/fixtures/estree/import-assertions/valid-syntax-with-attributes/options.json new file mode 100644 index 000000000000..bc028a2d77d0 --- /dev/null +++ b/packages/babel-parser/test/fixtures/estree/import-assertions/valid-syntax-with-attributes/options.json @@ -0,0 +1,4 @@ +{ + "plugins": ["flow", "jsx", "estree", "importAssertions"], + "sourceType": "module" +} diff --git a/packages/babel-parser/test/fixtures/estree/import-assertions/valid-syntax-with-attributes/output.json b/packages/babel-parser/test/fixtures/estree/import-assertions/valid-syntax-with-attributes/output.json new file mode 100644 index 000000000000..e08a9f151de8 --- /dev/null +++ b/packages/babel-parser/test/fixtures/estree/import-assertions/valid-syntax-with-attributes/output.json @@ -0,0 +1,51 @@ +{ + "type": "File", + "start":0,"end":51,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":51}}, + "program": { + "type": "Program", + "start":0,"end":51,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":51}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "ImportDeclaration", + "start":0,"end":51,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":51}}, + "specifiers": [ + { + "type": "ImportDefaultSpecifier", + "start":7,"end":10,"loc":{"start":{"line":1,"column":7},"end":{"line":1,"column":10}}, + "local": { + "type": "Identifier", + "start":7,"end":10,"loc":{"start":{"line":1,"column":7},"end":{"line":1,"column":10},"identifierName":"foo"}, + "name": "foo" + } + } + ], + "importKind": "value", + "source": { + "type": "Literal", + "start":16,"end":26,"loc":{"start":{"line":1,"column":16},"end":{"line":1,"column":26}}, + "value": "foo.json", + "raw": "\"foo.json\"" + }, + "assertions": [ + { + "type": "ImportAttribute", + "start":36,"end":48,"loc":{"start":{"line":1,"column":36},"end":{"line":1,"column":48}}, + "key": { + "type": "Identifier", + "start":36,"end":40,"loc":{"start":{"line":1,"column":36},"end":{"line":1,"column":40},"identifierName":"type"}, + "name": "type" + }, + "value": { + "type": "Literal", + "start":42,"end":48,"loc":{"start":{"line":1,"column":42},"end":{"line":1,"column":48}}, + "value": "json", + "raw": "\"json\"" + } + } + ] + } + ] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/estree/module-string-names/mixed/input.js b/packages/babel-parser/test/fixtures/estree/module-string-names/mixed/input.js new file mode 100644 index 000000000000..a6cfd5e177b6 --- /dev/null +++ b/packages/babel-parser/test/fixtures/estree/module-string-names/mixed/input.js @@ -0,0 +1,2 @@ +import { "foo" as bar, "default" as qux } from "module-a"; +export * as "foo", { default as "quux" } from "module-b"; diff --git a/packages/babel-parser/test/fixtures/estree/module-string-names/mixed/options.json b/packages/babel-parser/test/fixtures/estree/module-string-names/mixed/options.json new file mode 100644 index 000000000000..2104ca43283f --- /dev/null +++ b/packages/babel-parser/test/fixtures/estree/module-string-names/mixed/options.json @@ -0,0 +1,3 @@ +{ + "sourceType": "module" +} diff --git a/packages/babel-parser/test/fixtures/estree/module-string-names/mixed/output.json b/packages/babel-parser/test/fixtures/estree/module-string-names/mixed/output.json new file mode 100644 index 000000000000..1005de8ecc72 --- /dev/null +++ b/packages/babel-parser/test/fixtures/estree/module-string-names/mixed/output.json @@ -0,0 +1,96 @@ +{ + "type": "File", + "start":0,"end":116,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":57}}, + "program": { + "type": "Program", + "start":0,"end":116,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":57}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "ImportDeclaration", + "start":0,"end":58,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":58}}, + "specifiers": [ + { + "type": "ImportSpecifier", + "start":9,"end":21,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":21}}, + "imported": { + "type": "Literal", + "start":9,"end":14,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":14}}, + "value": "foo", + "raw": "\"foo\"" + }, + "importKind": null, + "local": { + "type": "Identifier", + "start":18,"end":21,"loc":{"start":{"line":1,"column":18},"end":{"line":1,"column":21},"identifierName":"bar"}, + "name": "bar" + } + }, + { + "type": "ImportSpecifier", + "start":23,"end":39,"loc":{"start":{"line":1,"column":23},"end":{"line":1,"column":39}}, + "imported": { + "type": "Literal", + "start":23,"end":32,"loc":{"start":{"line":1,"column":23},"end":{"line":1,"column":32}}, + "value": "default", + "raw": "\"default\"" + }, + "importKind": null, + "local": { + "type": "Identifier", + "start":36,"end":39,"loc":{"start":{"line":1,"column":36},"end":{"line":1,"column":39},"identifierName":"qux"}, + "name": "qux" + } + } + ], + "importKind": "value", + "source": { + "type": "Literal", + "start":47,"end":57,"loc":{"start":{"line":1,"column":47},"end":{"line":1,"column":57}}, + "value": "module-a", + "raw": "\"module-a\"" + } + }, + { + "type": "ExportNamedDeclaration", + "start":59,"end":116,"loc":{"start":{"line":2,"column":0},"end":{"line":2,"column":57}}, + "specifiers": [ + { + "type": "ExportNamespaceSpecifier", + "start":66,"end":76,"loc":{"start":{"line":2,"column":7},"end":{"line":2,"column":17}}, + "exported": { + "type": "Literal", + "start":71,"end":76,"loc":{"start":{"line":2,"column":12},"end":{"line":2,"column":17}}, + "value": "foo", + "raw": "\"foo\"" + } + }, + { + "type": "ExportSpecifier", + "start":80,"end":97,"loc":{"start":{"line":2,"column":21},"end":{"line":2,"column":38}}, + "local": { + "type": "Identifier", + "start":80,"end":87,"loc":{"start":{"line":2,"column":21},"end":{"line":2,"column":28},"identifierName":"default"}, + "name": "default" + }, + "exported": { + "type": "Literal", + "start":91,"end":97,"loc":{"start":{"line":2,"column":32},"end":{"line":2,"column":38}}, + "value": "quux", + "raw": "\"quux\"" + } + } + ], + "source": { + "type": "Literal", + "start":105,"end":115,"loc":{"start":{"line":2,"column":46},"end":{"line":2,"column":56}}, + "value": "module-b", + "raw": "\"module-b\"" + }, + "declaration": null, + "exportKind": "value" + } + ] + } +} \ No newline at end of file