Allow multiple JSX children appear in Mustache tag (#125)

* fix(www): link styles (#100)

Co-authored-by: Nate Moore <nate@skypack.dev>

* Add `assets/` (#102)

* chore: add assets

* docs: update readme

Co-authored-by: Nate Moore <nate@skypack.dev>

* docs: fix readme

* docs: fix readme

* chore: remove github banner

* Allow multiple JSX in mustache

* Manually discard package-lock update (due to local use of npm v7)

* Tidy up

* Revert mode ts-ignore

Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
Co-authored-by: Nate Moore <nate@skypack.dev>
This commit is contained in:
Kevin (Kun) "Kassimo" Qian 2021-04-22 12:10:06 -07:00 committed by GitHub
parent f5384b139d
commit 5eb232501f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 72 additions and 43 deletions

View file

@ -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 `<script $ lang="ts">${codeStart}${codeEnd}</script>`
const codeStart = codeChunks[0]; // If no children, there should only exist a single chunk.
if (codeStart && [`'`, `"`].includes(codeStart[0])) {
return `<script $ lang="ts">${codeChunks.join('')}</script>`
}
return `{${codeStart}${codeEnd}}`;
return `{${codeChunks.join('')}}`;
}
return `<script $ lang="ts">${text}</script>`;

View file

@ -75,7 +75,8 @@ function getAttributes(attrs: Attribute[]): Record<string, string> {
}
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})`;

View file

@ -63,8 +63,7 @@ export default function (module: Script): Transformer {
type: 'MustacheTag',
expression: {
type: 'Expression',
codeStart: '`' + escape(code) + '`',
codeEnd: '',
codeChunks: ['`' + escape(code) + '`'],
children: [],
},
},

View file

@ -222,9 +222,10 @@ export default function transformStyles({ compileOptions, filename, fileID }: Tr
}
} else if (attr.value[k].type === 'MustacheTag' && attr.value[k]) {
// dont 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}'`;
}
}
}

View file

@ -53,8 +53,7 @@ export interface Expression {
type: 'Expression';
start: number;
end: number;
codeStart: string;
codeEnd: string;
codeChunks: string[];
children: BaseNode[];
}

View file

@ -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;

View file

@ -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();

View file

@ -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);
});

View file

@ -0,0 +1,14 @@
---
let title = 'My Site';
---
<html lang="en">
<head>
<title>My site</title>
</head>
<body>
<h1>{title}</h1>
{false ? <h1>#t</h1> : <h1>#f</h1>}
</body>
</html>

View file

@ -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 () => {

View file

@ -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))],
});
}