diff --git a/source/parser.hera b/source/parser.hera
index 34e42a33..ba767af5 100644
--- a/source/parser.hera
+++ b/source/parser.hera
@@ -1735,6 +1735,10 @@ PropertyAccess
}
}
+# Property glob that starts with "." or "?.", not a brace
+ExplicitPropertyGlob
+ &ExplicitAccessStart PropertyGlob -> $2
+
PropertyGlob
# NOTE: Added shorthand obj.{a,b:c} -> {a: obj.a, c: obj.b}
( PropertyAccessModifier? OptionalDot ):dot InlineComment* BracedObjectLiteral:object ->
@@ -3740,7 +3744,7 @@ MethodDefinition
}
# NOTE: Not adding extra validation using PropertySetParameterList
# NOTE: If this node layout changes, be sure to update `convertMethodTOFunction`
- MethodSignature:signature !(PropertyAccess / UnaryPostfix / NonNullAssertion) BracedBlock?:block ->
+ MethodSignature:signature !(PropertyAccess / ExplicitPropertyGlob / UnaryPostfix / NonNullAssertion) BracedBlock?:block ->
let children = $0
let generatorPos = 0
let { modifier } = signature
diff --git a/source/parser/lib.civet b/source/parser/lib.civet
index 180a3e80..1430bef6 100644
--- a/source/parser/lib.civet
+++ b/source/parser/lib.civet
@@ -31,6 +31,7 @@ import type {
MemberExpression
MethodDefinition
NormalCatchParameter
+ ObjectExpression
ParenthesizedExpression
Placeholder
StatementNode
@@ -558,13 +559,14 @@ function processCallMemberExpression(node: CallExpression | MemberExpression): A
if glob?.type is "PropertyGlob"
prefix .= children[...i]
parts := []
- let refAssignmentComma
+ let ref
// add ref to ensure object base evaluated only once
- if prefix.length > 1
- ref := makeRef()
- { refAssignmentComma } = makeRefAssignment ref, prefix
- prefix = [ref]
- prefix = prefix.concat(glob.dot)
+ if prefix.length > 1 and glob.object.properties# > 1
+ ref = makeRef()
+ { refAssignment } := makeRefAssignment ref, prefix
+ // First use of prefix assigns ref
+ prefix = [ makeLeftHandSideExpression refAssignment ]
+ prefix = prefix.concat glob.dot
for part of glob.object.properties
if part.type is "Error"
@@ -575,7 +577,7 @@ function processCallMemberExpression(node: CallExpression | MemberExpression): A
type: "Error"
message: "Glob pattern cannot have method definition"
continue
- if part.value and !["CallExpression", "MemberExpression", "Identifier"].includes(part.value.type)
+ if part.value and part.value.type is not in ["CallExpression", "MemberExpression", "Identifier"] as (string?)[]
parts.push
type: "Error"
message: `Glob pattern must have call or member expression value, found ${JSON.stringify(part.value)}`
@@ -591,8 +593,10 @@ function processCallMemberExpression(node: CallExpression | MemberExpression): A
// Not yet needed:
[name, value] = [value, name] if glob.reversed
- if !suppressPrefix // Don't prefix @ shorthand
+ unless suppressPrefix // Don't prefix @ shorthand
value = prefix.concat trimFirstSpace value
+ // Switch from refAssignment to ref
+ prefix = [ ref ] ++ glob.dot if ref?
if (wValue) value.unshift(wValue)
if part.type is "SpreadProperty"
parts.push {
@@ -603,6 +607,7 @@ function processCallMemberExpression(node: CallExpression | MemberExpression): A
names: part.names
children: part.children.slice(0, 2) // whitespace, ...
.concat(value, part.delim)
+ usesRef: Boolean ref
}
else
parts.push {
@@ -619,21 +624,16 @@ function processCallMemberExpression(node: CallExpression | MemberExpression): A
value
part.delim // comma delimiter
]
+ usesRef: Boolean ref
}
- object: ASTNodeObject .= {
+ object: ObjectExpression :=
type: "ObjectExpression"
children: [
glob.object.children.0 // {
...parts
glob.object.children.-1 // whitespace and }
- ],
+ ]
properties: parts
- }
- if refAssignmentComma
- object = makeNode
- type: "ParenthesizedExpression"
- children: ["(", ...refAssignmentComma, object, ")"]
- expression: object
if (i is children.length - 1) return object
return processCallMemberExpression({ // in case there are more
...node
@@ -842,44 +842,36 @@ function convertNamedImportsToObject(node, pattern?: boolean)
// {foo} is equivalent to foo={foo}, and
// {foo, bar: baz} is equivalent to foo={foo} and bar={baz}.
// {...foo} is a special case.
-function convertObjectToJSXAttributes(obj) {
- const { properties } = obj
- const parts = [] // JSX attributes
- const rest = [] // parts that need to be in {...rest} form
- for (let i = 0; i < properties.length; i++) {
+function convertObjectToJSXAttributes(obj: ObjectExpression)
+ parts := [] // JSX attributes
+ rest := [] // parts that need to be in {...rest} form
+ for part, i of obj.properties
+ if part.usesRef
+ rest.push part
+ continue
if (i > 0) parts.push(' ')
- const part = properties[i]
- switch (part.type) {
- case 'Identifier':
+ switch part.type
+ when "Identifier"
parts.push([part.name, '={', part.name, '}'])
- break
- case 'Property':
- if (part.name.type is 'ComputedPropertyName') {
+ when "Property"
+ if part.name.type is "ComputedPropertyName"
rest.push(part)
- } else {
+ else
parts.push([part.name, '={', trimFirstSpace(part.value), '}'])
- }
- break
- case 'SpreadProperty':
+ when "SpreadProperty"
parts.push(['{', part.dots, part.value, '}'])
- break
- case 'MethodDefinition':
+ when "MethodDefinition"
const func = convertMethodToFunction(part)
- if (func) {
+ if func
parts.push([part.name, '={', convertMethodToFunction(part), '}'])
- } else {
+ else
rest.push(part)
- }
- break
- default:
- throw new Error(`invalid object literal type in JSX attribute: ${part.type}`)
- }
- }
- if (rest.length) {
- parts.push(['{...{', ...rest, '}}'])
- }
+ else
+ throw new Error `invalid object literal type in JSX attribute: ${part.type}`
+ if rest#
+ parts.push " " if parts# and parts.-1 is not " "
+ parts.push(["{...{", ...rest, "}}"])
return parts
-}
/**
* Returns a new MethodDefinition node.
diff --git a/source/parser/types.civet b/source/parser/types.civet
index 257dfdea..ac498bf4 100644
--- a/source/parser/types.civet
+++ b/source/parser/types.civet
@@ -111,6 +111,7 @@ export type OtherNode =
| Placeholder
| PropertyAccess
| PropertyBind
+ | PropertyGlob
| RangeExpression
| ReturnTypeAnnotation
| ReturnValue
@@ -497,6 +498,13 @@ export type PropertyBind
name: string
args: ASTNode[]
+export type PropertyGlob
+ type: "PropertyGlob"
+ children: Children
+ parent?: Parent
+ dot: ASTNode
+ object: ObjectExpression
+
export type Call
type: "Call"
children: Children
@@ -848,7 +856,7 @@ export type ObjectExpression
type: "ObjectExpression"
children: Children
names: string[]
- properties: Property[]
+ properties: (Property | SpreadProperty | MethodDefinition | ASTError)[]
parent?: Parent
export type Property
@@ -858,6 +866,7 @@ export type Property
name: string
names: string[]
value: ASTNode
+ usesRef?: boolean
export type ArrayExpression
type: "ArrayExpression"
diff --git a/test/import.civet b/test/import.civet
index 18a06bd5..7821242a 100644
--- a/test/import.civet
+++ b/test/import.civet
@@ -333,12 +333,20 @@ describe "import", ->
}
"""
+ testCase """
+ single dynamic import declaration expression
+ ---
+ fs := import { readFileSync } from fs
+ ---
+ const fs = {readFileSync:await import("fs").readFileSync}
+ """
+
testCase """
dynamic import declaration expression
---
fs := import { readFileSync, writeFile as wf, writeFileSync: wfs } from fs
---
- let ref;const fs = (ref = await import("fs"),{readFileSync:ref.readFileSync,wf:ref.writeFile,wfs:ref.writeFileSync})
+ let ref;const fs = {readFileSync:(ref = await import("fs")).readFileSync,wf:ref.writeFile,wfs:ref.writeFileSync}
"""
throws """
@@ -354,7 +362,7 @@ describe "import", ->
---
data := import { version } from package.json with type: 'json'
---
- let ref;const data = (ref = await import("package.json", {with:{type: 'json'}}),{version:ref.version})
+ const data = {version:await import("package.json", {with:{type: 'json'}}).version}
"""
// #1307
diff --git a/test/jsx/attr.civet b/test/jsx/attr.civet
index fe0f93ee..3e786314 100644
--- a/test/jsx/attr.civet
+++ b/test/jsx/attr.civet
@@ -107,6 +107,22 @@ describe "braced JSX attributes", ->
"""
+ testCase """
+ glob with complex left-hand side
+ ---
+
+ ---
+ let ref;
+ """
+
+ testCase """
+ glob with complex left-hand side and more
+ ---
+
+ ---
+ let ref;
+ """
+
testCase """
bind shorthand
---
diff --git a/test/object.civet b/test/object.civet
index bf58ed51..b5f0f584 100644
--- a/test/object.civet
+++ b/test/object.civet
@@ -1285,7 +1285,15 @@ describe "object", ->
---
x.y()?.z.{a,b}
---
- let ref;(ref = x.y()?.z,{a:ref.a,b:ref.b})
+ let ref;({a:(ref = x.y()?.z).a,b:ref.b})
+ """
+
+ testCase """
+ no ref if single right-hand side
+ ---
+ a.b.{x}
+ ---
+ ({x:a.b.x})
"""
testCase """
@@ -1356,7 +1364,7 @@ describe "object", ->
---
f a.b.{x,y}
---
- let ref;f((ref = a.b,{x:ref.x,y:ref.y}))
+ let ref;f({x:(ref = a.b).x,y:ref.y})
"""
testCase """
@@ -1364,7 +1372,7 @@ describe "object", ->
---
f first, a.b.{x,y}, last
---
- let ref;f(first, (ref = a.b,{x:ref.x,y:ref.y}), last)
+ let ref;f(first, {x:(ref = a.b).x,y:ref.y}, last)
"""
throws """
@@ -1421,6 +1429,14 @@ describe "object", ->
({a:x.a,b:x.b, c:y.c,d:y.d})
"""
+ testCase """
+ two inside braced object literals with complex base
+ ---
+ {x().{a,b}, y()?.{c,d}}
+ ---
+ let ref;let ref1;({a:(ref = x()).a,b:ref.b, c:(ref1 = y())?.c,d:ref1?.d})
+ """
+
testCase """
with reserved word keys
---