diff --git a/packages/babel-plugin-react-pug/test/unit/transform.test.ts b/packages/babel-plugin-react-pug/test/unit/transform.test.ts
index d4e4371..1a4be32 100644
--- a/packages/babel-plugin-react-pug/test/unit/transform.test.ts
+++ b/packages/babel-plugin-react-pug/test/unit/transform.test.ts
@@ -152,6 +152,275 @@ describe('babel-plugin-react-pug transform', () => {
expect(out).toContain('value');
});
+ it('supports unbuffered code followed by a single if/else-if chain', () => {
+ const out = transform([
+ 'const item = { type: "page", value: 1 };',
+ 'const view = pug`',
+ ' React.Fragment',
+ ' - const { type, value } = item',
+ " if type === 'page'",
+ ' span= value',
+ " else if type === 'status'",
+ ' span Status',
+ ' else',
+ ' span Other',
+ '`;',
+ ].join('\n'));
+ expect(out).toMatchInlineSnapshot(`
+ "const item = {
+ type: "page",
+ value: 1
+ };
+ const view = {(() => {
+ const {
+ type,
+ value
+ } = item;
+ return type === 'page' ? {value} : type === 'status' ? Status : Other;
+ })()};"
+ `);
+ });
+
+ it('supports unbuffered code followed by a single each loop', () => {
+ const out = transform([
+ 'const values = ["a", "b"];',
+ 'const view = pug`',
+ ' React.Fragment',
+ ' - const items = values',
+ ' each item, index in items',
+ ' span(key=index)= item',
+ '`;',
+ ].join('\n'));
+ expect(out).toMatchInlineSnapshot(`
+ "const values = ["a", "b"];
+ const view = {(() => {
+ const items = values;
+ return (() => {
+ const __pugEachResult = [];
+ let __pugEachIndex = 0;
+ for (const item of items) {
+ const index = __pugEachIndex;
+ __pugEachResult.push({item});
+ __pugEachIndex++;
+ }
+ return __pugEachResult;
+ })();
+ })()};"
+ `);
+ });
+
+ it('supports unbuffered code followed by a single each loop with else', () => {
+ const out = transform([
+ 'const values = [];',
+ 'const view = pug`',
+ ' React.Fragment',
+ ' - const items = values',
+ ' each item, index in items',
+ ' span(key=index)= item',
+ ' else',
+ ' span Empty',
+ '`;',
+ ].join('\n'));
+ expect(out).toMatchInlineSnapshot(`
+ "const values = [];
+ const view = {(() => {
+ const items = values;
+ return (() => {
+ const __pugEachResult = [];
+ let __pugEachIndex = 0;
+ for (const item of items) {
+ const index = __pugEachIndex;
+ __pugEachResult.push({item});
+ __pugEachIndex++;
+ }
+ return __pugEachResult.length ? __pugEachResult : Empty;
+ })();
+ })()};"
+ `);
+ });
+
+ it('supports unbuffered code followed by a single while loop', () => {
+ const out = transform([
+ 'const view = pug`',
+ ' React.Fragment',
+ ' - let index = 0',
+ ' while index < 2',
+ ' - index++',
+ ' span= index',
+ '`;',
+ ].join('\n'));
+ expect(out).toMatchInlineSnapshot(`
+ "const view = {(() => {
+ let index = 0;
+ return (() => {
+ const __r = [];
+ while (index < 2) {
+ __r.push((() => {
+ index++;
+ return {index};
+ })());
+ }
+ return __r;
+ })();
+ })()};"
+ `);
+ });
+
+ it('supports unbuffered code followed by a single case chain', () => {
+ const out = transform([
+ 'const inputKind = "page";',
+ 'const view = pug`',
+ ' React.Fragment',
+ ' - const kind = inputKind',
+ ' case kind',
+ " when 'page'",
+ ' span Page',
+ " when 'status'",
+ ' span Status',
+ ' default',
+ ' span Other',
+ '`;',
+ ].join('\n'));
+ expect(out).toMatchInlineSnapshot(`
+ "const inputKind = "page";
+ const view = {(() => {
+ const kind = inputKind;
+ return kind === 'page' ? Page : kind === 'status' ? Status : Other;
+ })()};"
+ `);
+ });
+
+ it('supports sibling JSX around a conditional chain after unbuffered code', () => {
+ const out = transform([
+ 'const item = { type: "page", value: 1 };',
+ 'const view = pug`',
+ ' React.Fragment',
+ ' - const { type, value } = item',
+ ' span Before',
+ " if type === 'page'",
+ ' span= value',
+ ' else',
+ ' span Other',
+ ' span After',
+ '`;',
+ ].join('\n'));
+ expect(out).toMatchInlineSnapshot(`
+ "const item = {
+ type: "page",
+ value: 1
+ };
+ const view = {(() => {
+ const {
+ type,
+ value
+ } = item;
+ return <>Before{type === 'page' ? {value} : Other}After>;
+ })()};"
+ `);
+ });
+
+ it('supports sibling JSX around an each loop after unbuffered code', () => {
+ const out = transform([
+ 'const values = ["a", "b"];',
+ 'const view = pug`',
+ ' React.Fragment',
+ ' - const items = values',
+ ' span Before',
+ ' each item, index in items',
+ ' span(key=index)= item',
+ ' span After',
+ '`;',
+ ].join('\n'));
+ expect(out).toMatchInlineSnapshot(`
+ "const values = ["a", "b"];
+ const view = {(() => {
+ const items = values;
+ return <>Before{(() => {
+ const __pugEachResult = [];
+ let __pugEachIndex = 0;
+ for (const item of items) {
+ const index = __pugEachIndex;
+ __pugEachResult.push({item});
+ __pugEachIndex++;
+ }
+ return __pugEachResult;
+ })()}After>;
+ })()};"
+ `);
+ });
+
+ it('supports nested each inside a conditional chain after unbuffered code', () => {
+ const out = transform([
+ 'const values = ["a", "b"];',
+ 'const visible = true;',
+ 'const view = pug`',
+ ' React.Fragment',
+ ' - const items = values',
+ ' - const show = visible',
+ ' if show',
+ ' each item, index in items',
+ ' span(key=index)= item',
+ ' else',
+ ' span Hidden',
+ '`;',
+ ].join('\n'));
+ expect(out).toMatchInlineSnapshot(`
+ "const values = ["a", "b"];
+ const visible = true;
+ const view = {(() => {
+ const items = values;
+ const show = visible;
+ return show ? (() => {
+ const __pugEachResult = [];
+ let __pugEachIndex = 0;
+ for (const item of items) {
+ const index = __pugEachIndex;
+ __pugEachResult.push({item});
+ __pugEachIndex++;
+ }
+ return __pugEachResult;
+ })() : Hidden;
+ })()};"
+ `);
+ });
+
+ it('supports nested conditionals inside an each loop after unbuffered code', () => {
+ const out = transform([
+ 'const values = [{ visible: true, label: "A" }, { visible: false, label: "B" }];',
+ 'const view = pug`',
+ ' React.Fragment',
+ ' - const items = values',
+ ' each item, index in items',
+ ' if item.visible',
+ ' span(key=index)= item.label',
+ ' else',
+ ' span(key=index) Hidden',
+ '`;',
+ ].join('\n'));
+ expect(out).toMatchInlineSnapshot(`
+ "const values = [{
+ visible: true,
+ label: "A"
+ }, {
+ visible: false,
+ label: "B"
+ }];
+ const view = {(() => {
+ const items = values;
+ return (() => {
+ const __pugEachResult = [];
+ let __pugEachIndex = 0;
+ for (const item of items) {
+ const index = __pugEachIndex;
+ __pugEachResult.push(item.visible ? {item.label} : Hidden);
+ __pugEachIndex++;
+ }
+ return __pugEachResult;
+ })();
+ })()};"
+ `);
+ });
+
it('supports nested pug templates inside ${} interpolation', () => {
const out = transform(COMPILER_NESTED_INTERPOLATION_SOURCE);
expect(out).toMatchInlineSnapshot(`
diff --git a/packages/react-pug-core/src/language/pugToTsx.ts b/packages/react-pug-core/src/language/pugToTsx.ts
index 6cfbb85..1cc73f4 100644
--- a/packages/react-pug-core/src/language/pugToTsx.ts
+++ b/packages/react-pug-core/src/language/pugToTsx.ts
@@ -1398,7 +1398,11 @@ function emitBlockWithCodeSupport(
if (jsxNodes.length === 0) {
emitter.emitSynthetic('null');
} else if (jsxNodes.length === 1) {
- emitNode(jsxNodes[0], emitter, pugText);
+ // This branch is still in a JS-expression position (`return (...)`), not a JSX-children
+ // position. Expression-producing nodes such as if/each/while/case must therefore be
+ // emitted via the expression path, otherwise wrappers like `return ({cond ? ...})`
+ // can become syntactically invalid.
+ emitNodeAsExpression(jsxNodes[0], emitter, pugText);
} else {
emitter.emitSynthetic('<>');
for (const node of jsxNodes) {
diff --git a/packages/react-pug-core/test/unit/pugToTsx.test.ts b/packages/react-pug-core/test/unit/pugToTsx.test.ts
index 3cd4d21..a61dcfd 100644
--- a/packages/react-pug-core/test/unit/pugToTsx.test.ts
+++ b/packages/react-pug-core/test/unit/pugToTsx.test.ts
@@ -1063,6 +1063,163 @@ describe('code blocks', () => {
expect(result.parseError).toBeNull();
});
+ it('keeps a single conditional chain valid when mixed with unbuffered code', () => {
+ const pug = [
+ 'React.Fragment',
+ ' - const { type, value } = item',
+ " if type === 'page'",
+ ' span= value',
+ " else if type === 'status'",
+ ' span Status',
+ ' else',
+ ' span Other',
+ ].join('\n');
+ const result = compilePugToTsx(pug, { mode: 'runtime' });
+ expect(result.parseError).toBeNull();
+ expect(result.transformError).toBeNull();
+ expect(result.tsx).toMatchInlineSnapshot(`"({(() => {const { type, value } = item;return (type === 'page' ? {value} : type === 'status' ? Status : Other);})()})"`);
+ });
+
+ it('keeps a single each loop valid when mixed with unbuffered code', () => {
+ const pug = [
+ 'React.Fragment',
+ ' - const items = values',
+ ' each item, index in items',
+ ' span(key=index)= item',
+ ].join('\n');
+ const result = compilePugToTsx(pug, { mode: 'runtime' });
+ expect(result.parseError).toBeNull();
+ expect(result.transformError).toBeNull();
+ expect(result.tsx).toMatchInlineSnapshot(`"({(() => {const items = values;return ((() => {const __pugEachResult = [];let __pugEachIndex = 0;for (const item of items) {const index = __pugEachIndex;__pugEachResult.push({item});__pugEachIndex++;}return __pugEachResult;})());})()})"`);
+ });
+
+ it('keeps a single each loop with else valid when mixed with unbuffered code', () => {
+ const pug = [
+ 'React.Fragment',
+ ' - const items = values',
+ ' each item, index in items',
+ ' span(key=index)= item',
+ ' else',
+ ' span Empty',
+ ].join('\n');
+ const result = compilePugToTsx(pug, { mode: 'runtime' });
+ expect(result.parseError).toBeNull();
+ expect(result.transformError).toBeNull();
+ expect(result.tsx).toMatchInlineSnapshot(`"({(() => {const items = values;return ((() => {const __pugEachResult = [];let __pugEachIndex = 0;for (const item of items) {const index = __pugEachIndex;__pugEachResult.push({item});__pugEachIndex++;}return __pugEachResult.length ? __pugEachResult : Empty;})());})()})"`);
+ });
+
+ it('keeps a single while loop valid when mixed with unbuffered code', () => {
+ const pug = [
+ 'React.Fragment',
+ ' - let index = 0',
+ ' while index < 2',
+ ' - index++',
+ ' span= index',
+ ].join('\n');
+ const result = compilePugToTsx(pug, { mode: 'runtime' });
+ expect(result.parseError).toBeNull();
+ expect(result.transformError).toBeNull();
+ expect(result.tsx).toMatchInlineSnapshot(`"({(() => {let index = 0;return ((() => {const __r = [];while (index < 2) {__r.push((() => {index++;return {index};})());}return __r;})());})()})"`);
+ });
+
+ it('keeps a single case chain valid when mixed with unbuffered code', () => {
+ const pug = [
+ 'React.Fragment',
+ ' - const kind = inputKind',
+ ' case kind',
+ " when 'page'",
+ ' span Page',
+ " when 'status'",
+ ' span Status',
+ ' default',
+ ' span Other',
+ ].join('\n');
+ const result = compilePugToTsx(pug, { mode: 'runtime' });
+ expect(result.parseError).toBeNull();
+ expect(result.transformError).toBeNull();
+ expect(result.tsx).toMatchInlineSnapshot(`"({(() => {const kind = inputKind;return (kind === 'page' ? Page : kind === 'status' ? Status : Other);})()})"`);
+ });
+
+ it('keeps sibling JSX around a conditional chain valid when mixed with unbuffered code', () => {
+ const pug = [
+ 'React.Fragment',
+ ' - const { type, value } = item',
+ ' span Before',
+ " if type === 'page'",
+ ' span= value',
+ ' else',
+ ' span Other',
+ ' span After',
+ ].join('\n');
+ const result = compilePugToTsx(pug, { mode: 'runtime' });
+ expect(result.parseError).toBeNull();
+ expect(result.transformError).toBeNull();
+ expect(result.tsx).toMatchInlineSnapshot(`"({(() => {const { type, value } = item;return (<>Before{type === 'page' ? {value} : Other}After>);})()})"`);
+ });
+
+ it('keeps sibling JSX around an each loop valid when mixed with unbuffered code', () => {
+ const pug = [
+ 'React.Fragment',
+ ' - const items = values',
+ ' span Before',
+ ' each item, index in items',
+ ' span(key=index)= item',
+ ' span After',
+ ].join('\n');
+ const result = compilePugToTsx(pug, { mode: 'runtime' });
+ expect(result.parseError).toBeNull();
+ expect(result.transformError).toBeNull();
+ expect(result.tsx).toMatchInlineSnapshot(`"({(() => {const items = values;return (<>Before{(() => {const __pugEachResult = [];let __pugEachIndex = 0;for (const item of items) {const index = __pugEachIndex;__pugEachResult.push({item});__pugEachIndex++;}return __pugEachResult;})()}After>);})()})"`);
+ });
+
+ it('keeps nested each inside a conditional chain valid when mixed with unbuffered code', () => {
+ const pug = [
+ 'React.Fragment',
+ ' - const items = values',
+ ' - const show = visible',
+ ' if show',
+ ' each item, index in items',
+ ' span(key=index)= item',
+ ' else',
+ ' span Hidden',
+ ].join('\n');
+ const result = compilePugToTsx(pug, { mode: 'runtime' });
+ expect(result.parseError).toBeNull();
+ expect(result.transformError).toBeNull();
+ expect(result.tsx).toMatchInlineSnapshot(`"({(() => {const items = values;const show = visible;return (show ? (() => {const __pugEachResult = [];let __pugEachIndex = 0;for (const item of items) {const index = __pugEachIndex;__pugEachResult.push({item});__pugEachIndex++;}return __pugEachResult;})() : Hidden);})()})"`);
+ });
+
+ it('keeps nested conditional inside each valid when mixed with unbuffered code', () => {
+ const pug = [
+ 'React.Fragment',
+ ' - const items = values',
+ ' each item, index in items',
+ ' if item.visible',
+ ' span(key=index)= item.label',
+ ' else',
+ ' span(key=index) Hidden',
+ ].join('\n');
+ const result = compilePugToTsx(pug, { mode: 'runtime' });
+ expect(result.parseError).toBeNull();
+ expect(result.transformError).toBeNull();
+ expect(result.tsx).toMatchInlineSnapshot(`"({(() => {const items = values;return ((() => {const __pugEachResult = [];let __pugEachIndex = 0;for (const item of items) {const index = __pugEachIndex;__pugEachResult.push(item.visible ? {item.label} : Hidden);__pugEachIndex++;}return __pugEachResult;})());})()})"`);
+ });
+
+ it('keeps the single-child conditional shape valid in language-service mode too', () => {
+ const pug = [
+ 'React.Fragment',
+ ' - const { type, value } = item',
+ " if type === 'page'",
+ ' span= value',
+ ' else',
+ ' span Other',
+ ].join('\n');
+ const result = compilePugToTsx(pug, { mode: 'shadow' });
+ expect(result.parseError).toBeNull();
+ expect(result.transformError).toBeNull();
+ expect(result.tsx).toMatchInlineSnapshot(`"({(() => {const { type, value } = item;return (type === 'page' ? {value} : Other);})()})"`);
+ });
+
it('code block expression is mapped with FULL_FEATURES', () => {
const pug = '- const x = 10\nspan= x';
const result = compilePugToTsx(pug);