Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove JS comments between JSXAttributes #828

Merged
merged 1 commit into from
Sep 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/weak-coats-peel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'wmr': patch
---

Fix missing props when there were comments between attributes in JSX
109 changes: 109 additions & 0 deletions packages/wmr/src/lib/transform-jsx-to-htm-lite.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,41 @@ export default function transformJsxToHtmLite({ types: t }, options = {}) {
name.appendString('}');
}

// Remove all JS-style comments in between JSXAttributes.
// This includes both single line comments and multi line ones.
// <A /* comment */ foo="a" /* other comment */ />
// <div
// // comment
// id="foo"
// // other comment
// />
//
// Result:
// <A foo="a" />
// <div
// id="foo"
// />
const attrs = path.get('attributes');
if (Array.isArray(attrs.node) && attrs.node.length > 0) {
let ms = path.ctx.out;

let i = name.end;
for (const attr of attrs.node) {
const comments = parseMaybeComments(ms.original, i);
for (const comment of comments) {
ms.remove(comment.start, comment.end);
}

i = attr.end;
}

// Remove potential comment before closing the opening tag
const comments = parseMaybeComments(ms.original, i);
for (const comment of comments) {
ms.remove(comment.start, comment.end);
}
}

if (isRootElement(path)) {
path.prependString(tagString + '`');

Expand Down Expand Up @@ -149,3 +184,77 @@ export default function transformJsxToHtmLite({ types: t }, options = {}) {
}
};
}

/**
* Parse comments and return the start and end offset of all comments
* that were found. This includes sibling comments.
* @param {string} code
* @param {number} start
*/
function parseMaybeComments(code, start) {
/** @type {Array<{start: number, end: number}>} */
const out = [];

const NONE = 0;
const SINGLE = 1;
const MULTI = 2;

let commentStart = 0;
let lineStart = start;

let type = NONE;
for (let i = start; i < code.length; i++) {
let char = code.charAt(i);

if (type === SINGLE) {
if (char === '\n' || char === '\r') {
if (lineStart < commentStart) {
const leading = code.slice(lineStart, commentStart).match(/^\s+/);
if (leading) {
commentStart = lineStart;
}
}

out.push({ start: commentStart, end: i });
lineStart = i;
type = NONE;
} else {
continue;
}
} else if (type === MULTI) {
if (char === '*' && code.charAt(i + 1) === '/') {
if (lineStart < commentStart) {
const leading = code.slice(lineStart, commentStart).match(/^\n\s+/);
if (leading) {
commentStart = lineStart;
}
}

let end = i + 2;
out.push({ start: commentStart, end });
type = NONE;
i = end;
} else {
continue;
}
} else if (char === '/') {
if (code.charAt(i + 1) === '/') {
type = SINGLE;
commentStart = i;
i++;
} else if (code.charAt(i + 1) === '*') {
commentStart = i;
type = MULTI;
i++;
}
} else if (char === '\n') {
lineStart = i;
} else if (/\s/.test(char)) {
commentStart++;
} else {
break;
}
}

return out;
}
19 changes: 19 additions & 0 deletions packages/wmr/test/fixtures/transformations/jsx-comment.expected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { html as $$html } from '/@npm/htm/preact';
// prettier-ignore
export const a = $$html`<div
id="foo"
disabled
/>`

// prettier-ignore
export const b = $$html`<div
id="foo"
/>`

const Foo = () => null;
// prettier-ignore
export const c = $$html`<div
id="foo"
>
<${Foo} foo=${2} />
</div>`
28 changes: 28 additions & 0 deletions packages/wmr/test/fixtures/transformations/jsx-comment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// prettier-ignore
export const a = <div
// comment 1
id="foo"
// comment 2
disabled
// comment 3
/>

// prettier-ignore
export const b = <div
// comment 1
// comment 2
id="foo"
// comment 3
// comment 3
/>

const Foo = () => null;
// prettier-ignore
export const c = <div
/* a */
id="foo"
/* b */
/* c */
>
<Foo /*asd*/ foo={2} /*asd*//>
</div>
9 changes: 8 additions & 1 deletion packages/wmr/test/transformations.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from 'path';
import { promises as fs } from 'fs';
import { setupTest, teardown, runWmr, loadFixture, get } from './test-helpers.js';
import { setupTest, teardown, runWmr, loadFixture, get, withLog } from './test-helpers.js';
import { modularizeCss } from '../src/plugins/wmr/styles/css-modules.js';

const runWmrFast = (cwd, ...args) => runWmr(cwd, '--no-optimize', '--no-compress', ...args);
Expand Down Expand Up @@ -49,6 +49,13 @@ describe('transformations', () => {
const expected = await readFile(env, 'jsx-self-closed.expected.js');
expect((await get(instance, 'jsx-self-closed.js')).body).toEqual(expected);
});

it('should remove single line comments between props', async () => {
const expected = await readFile(env, 'jsx-comment.expected.js');
await withLog(instance.output, async () => {
expect((await get(instance, 'jsx-comment.js')).body).toEqual(expected);
});
});
});

describe('css', () => {
Expand Down