Skip to content
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
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"@metamask/snaps-utils": "^11.7.1",
"@metamask/utils": "^11.9.0",
"@types/node": "^22.13.1",
"acorn": "^8.15.0",
"chokidar": "^4.0.1",
"glob": "^11.0.0",
"libp2p": "2.10.0",
Expand Down
51 changes: 51 additions & 0 deletions packages/cli/src/vite/strip-comments-plugin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { describe, it, expect } from 'vitest';

import { stripCommentsPlugin } from './strip-comments-plugin.ts';

describe('stripCommentsPlugin', () => {
const plugin = stripCommentsPlugin();
const renderChunk = plugin.renderChunk as (code: string) => string | null;

it.each([
[
'single-line comment',
'const x = 1; // comment\nconst y = 2;',
'const x = 1; \nconst y = 2;',
],
[
'multi-line comment',
'const x = 1; /* comment */ const y = 2;',
'const x = 1; const y = 2;',
],
[
'multiple comments',
'/* a */ const x = 1; // b\n/* c */',
' const x = 1; \n',
],
[
'comment containing import()',
'const x = 1; // import("module")\nconst y = 2;',
'const x = 1; \nconst y = 2;',
],
[
'comment with string content preserved',
'const x = "// in string"; // real comment',
'const x = "// in string"; ',
],
['code that is only a comment', '// just a comment', ''],
])('removes %s', (_name, code, expected) => {
expect(renderChunk(code)).toBe(expected);
});

it.each([
['string with // pattern', 'const x = "// not a comment";'],
['string with /* */ pattern', 'const x = "/* not a comment */";'],
['regex literal like //', 'const re = /\\/\\//;'],
['template literal with // pattern', 'const x = `// not a comment`;'],
['nested quotes in string', 'const x = "a \\"// not comment\\" b";'],
['no comments', 'const x = 1;'],
['empty code', ''],
])('returns null for %s', (_name, code) => {
expect(renderChunk(code)).toBeNull();
});
});
77 changes: 25 additions & 52 deletions packages/cli/src/vite/strip-comments-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import type { Comment } from 'acorn';
import { parse } from 'acorn';
import type { Plugin } from 'rollup';

/**
* Rollup plugin that strips comments from bundled code.
* Rollup plugin that strips comments from bundled code using AST parsing.
*
* SES rejects code containing `import(` patterns, even when they appear
* in comments. This plugin removes all comments to avoid triggering
* that detection.
* in comments. This plugin uses Acorn to definitively identify comment nodes
* and removes them to avoid triggering that detection.
*
* Uses the `renderChunk` hook to process the final output.
*
Expand All @@ -15,58 +17,29 @@ export function stripCommentsPlugin(): Plugin {
return {
name: 'strip-comments',
renderChunk(code) {
// Remove single-line comments (// ...)
// Remove multi-line comments (/* ... */)
// Be careful not to remove comments inside strings
const comments: Comment[] = [];

parse(code, {
ecmaVersion: 'latest',
sourceType: 'module',
onComment: comments,
});

if (comments.length === 0) {
return null;
}

// Build result by copying non-comment ranges.
// Comments are sorted by position since acorn parses linearly.
let result = '';
let i = 0;
while (i < code.length) {
const char = code[i] as string;
const nextChar = code[i + 1];
let position = 0;

// Check for string literals
if (char === '"' || char === "'" || char === '`') {
const quote = char;
result += quote;
i += 1;
// Copy string content including escape sequences
while (i < code.length) {
const strChar = code[i] as string;
if (strChar === '\\' && i + 1 < code.length) {
result += strChar + (code[i + 1] as string);
i += 2;
} else if (strChar === quote) {
result += quote;
i += 1;
break;
} else {
result += strChar;
i += 1;
}
}
}
// Check for single-line comment
else if (char === '/' && nextChar === '/') {
// Skip until end of line
while (i < code.length && code[i] !== '\n') {
i += 1;
}
}
// Check for multi-line comment
else if (char === '/' && nextChar === '*') {
i += 2;
// Skip until */
while (i < code.length && !(code[i - 1] === '*' && code[i] === '/')) {
i += 1;
}
i += 1; // Skip the closing /
}
// Regular character
else {
result += char;
i += 1;
}
for (const comment of comments) {
result += code.slice(position, comment.start);
position = comment.end;
}

result += code.slice(position);
return result;
},
};
Expand Down
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3369,6 +3369,7 @@ __metadata:
"@typescript-eslint/parser": "npm:^8.29.0"
"@typescript-eslint/utils": "npm:^8.29.0"
"@vitest/eslint-plugin": "npm:^1.6.5"
acorn: "npm:^8.15.0"
chokidar: "npm:^4.0.1"
depcheck: "npm:^1.4.7"
eslint: "npm:^9.23.0"
Expand Down
Loading