diff --git a/prettier-plugin-astro/index.js b/prettier-plugin-astro/index.js
index a1194de9d0..ef7977351c 100644
--- a/prettier-plugin-astro/index.js
+++ b/prettier-plugin-astro/index.js
@@ -55,12 +55,13 @@ const findExpressionsInAST = (node, collect = []) => {
return collect;
}
-const formatExpression = ({ expression: { codeStart, codeEnd, children }}, text, options) => {
+const formatExpression = ({ expression: { codeChunks, children }}, text, options) => {
if (children.length === 0) {
- if ([`'`, `"`].includes(codeStart[0])) {
- return ``
+ const codeStart = codeChunks[0]; // If no children, there should only exist a single chunk.
+ if (codeStart && [`'`, `"`].includes(codeStart[0])) {
+ return ``
}
- return `{${codeStart}${codeEnd}}`;
+ return `{${codeChunks.join('')}}`;
}
return ``;
diff --git a/src/compiler/codegen/index.ts b/src/compiler/codegen/index.ts
index 8d968e1ea5..a10268883c 100644
--- a/src/compiler/codegen/index.ts
+++ b/src/compiler/codegen/index.ts
@@ -75,7 +75,8 @@ function getAttributes(attrs: Attribute[]): Record {
}
switch (val.type) {
case 'MustacheTag': {
- result[attr.name] = '(' + val.expression.codeStart + ')';
+ // FIXME: this won't work when JSX element can appear in attributes (rare but possible).
+ result[attr.name] = '(' + val.expression.codeChunks[0] + ')';
continue;
}
case 'Text':
@@ -101,7 +102,8 @@ function getTextFromAttribute(attr: any): string {
break;
}
case 'MustacheTag': {
- return attr.expression.codeStart;
+ // FIXME: this won't work when JSX element can appear in attributes (rare but possible).
+ return attr.expression.codeChunks[0];
}
}
throw new Error(`Unknown attribute type ${attr.type}`);
@@ -520,13 +522,20 @@ function compileHtml(enterNode: TemplateNode, state: CodegenState, compileOption
enter(node: TemplateNode) {
switch (node.type) {
case 'Expression': {
- let child = '';
+ let children: string[] = [];
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- if (node.children!.length) {
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- child = compileHtml(node.children![0], state, compileOptions);
+ for (const child of node.children!) {
+ children.push(compileHtml(child, state, compileOptions));
+ }
+ let raw = '';
+ let nextChildIndex = 0;
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ for (const chunk of node.codeChunks!) {
+ raw += chunk;
+ if (nextChildIndex < children.length) {
+ raw += children[nextChildIndex++];
+ }
}
- let raw = node.codeStart + child + node.codeEnd;
// TODO Do we need to compile this now, or should we compile the entire module at the end?
let code = compileExpressionSafe(raw).trim().replace(/\;$/, '');
outSource += `,(${code})`;
diff --git a/src/compiler/transform/prism.ts b/src/compiler/transform/prism.ts
index 7848e16721..1bb024a845 100644
--- a/src/compiler/transform/prism.ts
+++ b/src/compiler/transform/prism.ts
@@ -63,8 +63,7 @@ export default function (module: Script): Transformer {
type: 'MustacheTag',
expression: {
type: 'Expression',
- codeStart: '`' + escape(code) + '`',
- codeEnd: '',
+ codeChunks: ['`' + escape(code) + '`'],
children: [],
},
},
diff --git a/src/compiler/transform/styles.ts b/src/compiler/transform/styles.ts
index 77eedfa896..53585651f1 100644
--- a/src/compiler/transform/styles.ts
+++ b/src/compiler/transform/styles.ts
@@ -222,9 +222,10 @@ export default function transformStyles({ compileOptions, filename, fileID }: Tr
}
} else if (attr.value[k].type === 'MustacheTag' && attr.value[k]) {
// don‘t add same scopedClass twice (this check is a little more basic, but should suffice)
- if (!attr.value[k].expression.codeStart.includes(`' ${scopedClass}'`)) {
+ if (!attr.value[k].expression.codeChunks[0].includes(`' ${scopedClass}'`)) {
// MustacheTag
- attr.value[k].expression.codeStart = `(${attr.value[k].expression.codeStart}) + ' ${scopedClass}'`;
+ // FIXME: this won't work when JSX element can appear in attributes (rare but possible).
+ attr.value[k].expression.codeChunks[0] = `(${attr.value[k].expression.codeChunks[0]}) + ' ${scopedClass}'`;
}
}
}
diff --git a/src/parser/interfaces.ts b/src/parser/interfaces.ts
index 4a4d43f718..3273b8be15 100644
--- a/src/parser/interfaces.ts
+++ b/src/parser/interfaces.ts
@@ -53,8 +53,7 @@ export interface Expression {
type: 'Expression';
start: number;
end: number;
- codeStart: string;
- codeEnd: string;
+ codeChunks: string[];
children: BaseNode[];
}
diff --git a/src/parser/parse/read/expression.ts b/src/parser/parse/read/expression.ts
index f0033354d0..9d0d091756 100644
--- a/src/parser/parse/read/expression.ts
+++ b/src/parser/parse/read/expression.ts
@@ -168,12 +168,12 @@ function consume_expression(source: string, start: number): Expression {
type: 'Expression',
start,
end: Number.NaN,
- codeStart: '',
- codeEnd: '',
+ codeChunks: [],
children: [],
};
- let codeEndStart: number = 0;
+ let codeStart: number = start;
+
const state: ParseState = {
source,
start,
@@ -196,10 +196,11 @@ function consume_expression(source: string, start: number): Expression {
break;
}
case '<': {
- expr.codeStart = source.substring(start, state.index - 1);
+ const chunk = source.substring(codeStart, state.index - 1);
+ expr.codeChunks.push(chunk);
const tag = consume_tag(state);
expr.children.push(tag);
- codeEndStart = state.index;
+ codeStart = state.index;
break;
}
case "'":
@@ -225,10 +226,8 @@ function consume_expression(source: string, start: number): Expression {
expr.end = state.index - 1;
- if (codeEndStart) {
- expr.codeEnd = source.substring(codeEndStart, expr.end);
- } else {
- expr.codeStart = source.substring(start, expr.end);
+ if (expr.children.length || !expr.codeChunks.length) {
+ expr.codeChunks.push(source.substring(codeStart, expr.end));
}
return expr;
diff --git a/test/astro-expr.test.js b/test/astro-expr.test.js
index ea461af4bf..c3c985712f 100644
--- a/test/astro-expr.test.js
+++ b/test/astro-expr.test.js
@@ -53,4 +53,11 @@ Expressions('Ignores characters inside of multiline comments', async ({ runtime
}
});
+Expressions('Allows multiple JSX children in mustache', async ({ runtime }) => {
+ const result = await runtime.load('/multiple-children');
+ assert.equal(result.statusCode, 200);
+
+ assert.ok(result.contents.includes('#f') && !result.contents.includes('#t'));
+});
+
Expressions.run();
diff --git a/test/astro-prettier.test.js b/test/astro-prettier.test.js
index 1dd1887f4f..f3d1626c5a 100644
--- a/test/astro-prettier.test.js
+++ b/test/astro-prettier.test.js
@@ -7,15 +7,15 @@ const Prettier = suite('Prettier formatting');
setup(Prettier, './fixtures/astro-prettier');
-/**
- * Utility to get `[src, out]` files
- * @param name {string}
- * @param ctx {any}
- */
+/**
+ * Utility to get `[src, out]` files
+ * @param name {string}
+ * @param ctx {any}
+ */
const getFiles = async (name, { readFile }) => {
const [src, out] = await Promise.all([readFile(`/in/${name}.astro`), readFile(`/out/${name}.astro`)]);
return [src, out];
-}
+};
Prettier('can format a basic Astro file', async (ctx) => {
const [src, out] = await getFiles('basic', ctx);
@@ -28,7 +28,7 @@ Prettier('can format a basic Astro file', async (ctx) => {
Prettier('can format an Astro file with frontmatter', async (ctx) => {
const [src, out] = await getFiles('frontmatter', ctx);
assert.not.equal(src, out);
-
+
const formatted = format(src);
assert.equal(formatted, out);
});
@@ -36,7 +36,7 @@ Prettier('can format an Astro file with frontmatter', async (ctx) => {
Prettier('can format an Astro file with embedded JSX expressions', async (ctx) => {
const [src, out] = await getFiles('embedded-expr', ctx);
assert.not.equal(src, out);
-
+
const formatted = format(src);
assert.equal(formatted, out);
});
diff --git a/test/fixtures/astro-expr/astro/pages/multiple-children.astro b/test/fixtures/astro-expr/astro/pages/multiple-children.astro
new file mode 100644
index 0000000000..fb0fafd4a8
--- /dev/null
+++ b/test/fixtures/astro-expr/astro/pages/multiple-children.astro
@@ -0,0 +1,14 @@
+---
+let title = 'My Site';
+---
+
+
+
+ My site
+
+
+ {title}
+
+ {false ? #t
: #f
}
+
+
\ No newline at end of file
diff --git a/test/helpers.js b/test/helpers.js
index eb7cabb0bf..b140055637 100644
--- a/test/helpers.js
+++ b/test/helpers.js
@@ -28,8 +28,8 @@ export function setup(Suite, fixturePath) {
context.runtime = runtime;
context.readFile = async (path) => {
const resolved = fileURLToPath(new URL(`${fixturePath}${path}`, import.meta.url));
- return readFile(resolved).then(r => r.toString('utf-8'));
- }
+ return readFile(resolved).then((r) => r.toString('utf-8'));
+ };
});
Suite.after(async () => {
diff --git a/test/test-utils.js b/test/test-utils.js
index 5d51826363..6a71d834ac 100644
--- a/test/test-utils.js
+++ b/test/test-utils.js
@@ -6,13 +6,13 @@ export function doc(html) {
return cheerio.load(html);
}
-/**
- * format the contents of an astro file
- * @param contents {string}
- */
+/**
+ * format the contents of an astro file
+ * @param contents {string}
+ */
export function format(contents) {
return prettier.format(contents, {
- parser: 'astro',
- plugins: [fileURLToPath(new URL('../prettier-plugin-astro', import.meta.url))]
- })
+ parser: 'astro',
+ plugins: [fileURLToPath(new URL('../prettier-plugin-astro', import.meta.url))],
+ });
}