|
1 | | -/** |
2 | | - * Build a regex that matches the start of a strip-target call. |
3 | | - * Matches at line start with optional whitespace and optional label prefix (e.g. `default:`). |
4 | | - * The actual parenthesis matching is done procedurally to avoid catastrophic backtracking. |
5 | | - * |
6 | | - * @param {string[]} functions - Function names to strip. |
7 | | - * @returns {RegExp} Pattern that matches `FuncName(` in strippable positions. |
8 | | - */ |
9 | | -export function buildStripPattern(functions) { |
10 | | - const escaped = functions.map(f => f.replace(/\./g, '\\.')).join('|'); |
11 | | - return new RegExp(`^([ \\t]*(?:\\w+:[ \\t]*)?)(${escaped})\\(`, 'gm'); |
12 | | -} |
| 1 | +import { parse } from 'acorn'; |
| 2 | +import { unpluginFactory } from 'unplugin-strip'; |
13 | 3 |
|
14 | 4 | /** |
15 | | - * Strip specified function calls from source text. |
16 | | - * Uses parenthesis counting to find the end of the call, avoiding regex backtracking. |
| 5 | + * Create a strip transform function for the given function names. |
| 6 | + * Uses unplugin-strip (AST-based) to reliably remove function calls |
| 7 | + * in all positions — statement-level, inline, inside template literals, etc. |
17 | 8 | * |
18 | | - * @param {string} source - Source code. |
19 | | - * @param {RegExp} pattern - Compiled strip pattern from buildStripPattern. |
20 | | - * @returns {string} Processed source. |
| 9 | + * @param {string[]} functions - Function names to strip (e.g. 'Debug.assert'). |
| 10 | + * @returns {(source: string) => string} Transform function. |
21 | 11 | */ |
22 | | -export function applyStrip(source, pattern) { |
23 | | - pattern.lastIndex = 0; |
24 | | - |
25 | | - const removals = []; // [startIndex, endIndex] pairs to remove |
26 | | - let match; |
27 | | - |
28 | | - while ((match = pattern.exec(source)) !== null) { |
29 | | - const linePrefix = match[1]; // leading whitespace + optional label |
30 | | - const funcNameStart = match.index + linePrefix.length; |
31 | | - const parenStart = match.index + match[0].length - 1; |
32 | | - let depth = 1; |
33 | | - let i = parenStart + 1; |
34 | | - let inString = false; |
35 | | - let stringChar = ''; |
36 | | - let inTemplate = false; |
37 | | - let templateDepth = 0; |
38 | | - |
39 | | - while (i < source.length && depth > 0) { |
40 | | - const ch = source[i]; |
41 | | - |
42 | | - // Skip single-line comments |
43 | | - if (ch === '/' && i + 1 < source.length && source[i + 1] === '/') { |
44 | | - while (i < source.length && source[i] !== '\n') i++; |
45 | | - continue; |
46 | | - } |
47 | | - |
48 | | - // Skip multi-line comments |
49 | | - if (ch === '/' && i + 1 < source.length && source[i + 1] === '*') { |
50 | | - i += 2; |
51 | | - while (i < source.length - 1 && !(source[i] === '*' && source[i + 1] === '/')) i++; |
52 | | - i += 2; |
53 | | - continue; |
54 | | - } |
55 | | - |
56 | | - if (inString) { |
57 | | - if (ch === stringChar && source[i - 1] !== '\\') { |
58 | | - inString = false; |
59 | | - } |
60 | | - i++; |
61 | | - continue; |
62 | | - } |
63 | | - |
64 | | - if (inTemplate) { |
65 | | - if (ch === '`' && source[i - 1] !== '\\') { |
66 | | - inTemplate = false; |
67 | | - } else if (ch === '$' && i + 1 < source.length && source[i + 1] === '{') { |
68 | | - templateDepth++; |
69 | | - i += 2; |
70 | | - continue; |
71 | | - } else if (ch === '}' && templateDepth > 0) { |
72 | | - templateDepth--; |
73 | | - } |
74 | | - i++; |
75 | | - continue; |
76 | | - } |
77 | | - |
78 | | - if (ch === '\'' || ch === '"') { |
79 | | - inString = true; |
80 | | - stringChar = ch; |
81 | | - } else if (ch === '`') { |
82 | | - inTemplate = true; |
83 | | - templateDepth = 0; |
84 | | - } else if (ch === '(') { |
85 | | - depth++; |
86 | | - } else if (ch === ')') { |
87 | | - depth--; |
88 | | - } |
89 | | - |
90 | | - i++; |
| 12 | +export function createStripTransform(functions) { |
| 13 | + const plugin = unpluginFactory({ |
| 14 | + functions, |
| 15 | + sourceMap: false, |
| 16 | + debugger: false, |
| 17 | + include: '**/*.js' |
| 18 | + }); |
| 19 | + |
| 20 | + const ctx = { |
| 21 | + parse(code) { |
| 22 | + return parse(code, { |
| 23 | + ecmaVersion: 'latest', |
| 24 | + sourceType: 'module' |
| 25 | + }); |
91 | 26 | } |
| 27 | + }; |
92 | 28 |
|
93 | | - if (depth === 0) { |
94 | | - // i is now right after the closing paren |
95 | | - let end = i; |
96 | | - // Skip optional semicolon and trailing whitespace/newline |
97 | | - while (end < source.length && (source[end] === ' ' || source[end] === '\t')) end++; |
98 | | - if (end < source.length && source[end] === ';') end++; |
99 | | - while (end < source.length && (source[end] === ' ' || source[end] === '\t')) end++; |
100 | | - if (end < source.length && source[end] === '\n') end++; |
101 | | - else if (end < source.length && source[end] === '\r') { |
102 | | - end++; |
103 | | - if (end < source.length && source[end] === '\n') end++; |
104 | | - } |
105 | | - |
106 | | - // The match starts at the beginning of the line (anchored with ^). |
107 | | - // If the prefix is only whitespace, remove the entire line. |
108 | | - // If the prefix includes a label (e.g. "default: "), remove only the call. |
109 | | - const start = match.index; |
110 | | - if (/^[ \t]*$/.test(linePrefix)) { |
111 | | - removals.push([start, end]); |
112 | | - } else { |
113 | | - removals.push([funcNameStart, end]); |
114 | | - } |
115 | | - } |
116 | | - } |
117 | | - |
118 | | - if (removals.length === 0) return source; |
119 | | - |
120 | | - // Build result by skipping removed ranges, handling overlapping/nested ranges |
121 | | - let result = ''; |
122 | | - let pos = 0; |
123 | | - for (const [start, end] of removals) { |
124 | | - if (start < pos) continue; // skip ranges nested inside an already-removed range |
125 | | - result += source.slice(pos, start); |
126 | | - pos = end; |
127 | | - } |
128 | | - result += source.slice(pos); |
129 | | - return result; |
| 29 | + return function applyStrip(source) { |
| 30 | + const result = plugin.transform.call(ctx, source, 'file.js'); |
| 31 | + return result ? result.code : source; |
| 32 | + }; |
130 | 33 | } |
0 commit comments