Skip to content

Commit 2ce7b0e

Browse files
committed
fix(ast): postfix operator of epsilon
When a postfix operator (e.g. `?`, `*`, `+`, ...) is applied to epsilon (aka. the empty string) then we should not render the operator. Otherwise we can create invalid expressions. For example, - /aε*/ would become /a*/ which changes the meaning of the expression. - /ε*/ would become /*/ which is not a valid expression at all and throws an error on construction.
1 parent 25f5d69 commit 2ce7b0e

File tree

1 file changed

+33
-8
lines changed

1 file changed

+33
-8
lines changed

src/ast.ts

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -194,14 +194,39 @@ export function toString(ast: RegExpAST, options: RenderOptions): string {
194194
return maybeWithParens(ast.left, ast, options) + maybeWithParens(ast.right, ast, options)
195195
case 'union':
196196
return maybeWithParens(ast.left, ast, options) + '|' + maybeWithParens(ast.right, ast, options)
197-
case 'star':
198-
return maybeWithParens(ast.inner, ast, options) + '*'
199-
case 'plus':
200-
return maybeWithParens(ast.inner, ast, options) + '+'
201-
case 'optional':
202-
return maybeWithParens(ast.inner, ast, options) + '?'
203-
case 'repeat':
204-
return maybeWithParens(ast.inner, ast, options) + repeatBoundsToString(ast.bounds)
197+
198+
// For postfix operators if we have to check whether `ast.inner` is not effectively epsilon.
199+
// In that case we shouldn't append the operator, otherwise can generate invalid expressions.
200+
// For example, `aε*` would become `a*`.
201+
case 'star': {
202+
const innerStr = maybeWithParens(ast.inner, ast, options)
203+
if (innerStr === '')
204+
return ''
205+
else
206+
return innerStr + '*'
207+
}
208+
case 'plus': {
209+
const innerStr = maybeWithParens(ast.inner, ast, options)
210+
if (innerStr === '')
211+
return ''
212+
else
213+
return innerStr + '+'
214+
}
215+
case 'optional': {
216+
const innerStr = maybeWithParens(ast.inner, ast, options)
217+
if (innerStr === '')
218+
return ''
219+
else
220+
return innerStr + '?'
221+
}
222+
case 'repeat': {
223+
const innerStr = maybeWithParens(ast.inner, ast, options)
224+
if (innerStr === '')
225+
return ''
226+
else
227+
return innerStr + repeatBoundsToString(ast.bounds)
228+
}
229+
205230
case 'capture-group':
206231
return captureGroupToString(ast.name, ast.inner, options)
207232
case 'positive-lookahead':

0 commit comments

Comments
 (0)