@@ -16,17 +16,17 @@ abstract class RegExp extends Expr {
16
16
/**
17
17
* Holds if this `RegExp` has the `s` flag for multi-line matching.
18
18
*/
19
- predicate isDotAll ( ) { none ( ) }
19
+ predicate isDotAll ( ) { this . getAMode ( ) = "DOTALL" }
20
20
21
21
/**
22
22
* Holds if this `RegExp` has the `i` flag for case-insensitive matching.
23
23
*/
24
- predicate isIgnoreCase ( ) { none ( ) }
24
+ predicate isIgnoreCase ( ) { this . getAMode ( ) = "IGNORECASE" }
25
25
26
26
/**
27
- * Gets the flags for this `RegExp`, or the empty string if it has no flags.
27
+ * Gets a string representing the flags for this `RegExp`, or the empty string if it has no flags.
28
28
*/
29
- string getFlags ( ) { result = "" }
29
+ string getFlags ( ) { result = concat ( string mode | mode = this . getAMode ( ) | mode , " | " ) }
30
30
31
31
/**
32
32
* Helper predicate for `charSetStart(int start, int end)`.
@@ -274,6 +274,58 @@ abstract class RegExp extends Expr {
274
274
275
275
private predicate isGroupStart ( int i ) { this .nonEscapedCharAt ( i ) = "(" and not this .inCharSet ( i ) }
276
276
277
+ /**
278
+ * Holds if a parse mode starts between `start` and `end`.
279
+ */
280
+ private predicate flagGroupStart ( int start , int end ) {
281
+ this .isGroupStart ( start ) and
282
+ this .getChar ( start + 1 ) = "?" and
283
+ this .getChar ( start + 2 ) in [ "i" , "x" , "s" , "m" , "w" ] and
284
+ end = start + 2
285
+ }
286
+
287
+ /**
288
+ * Holds if a parse mode group is between `start` and `end`, and includes the
289
+ * mode flag `c`. For example the following span, with mode flag `i`:
290
+ * ```
291
+ * (?i)
292
+ * ```
293
+ */
294
+ private predicate flagGroup ( int start , int end , string c ) {
295
+ exists ( int inStart , int inEnd |
296
+ this .flagGroupStart ( start , inStart ) and
297
+ this .groupContents ( start , end , inStart , inEnd ) and
298
+ this .getChar ( [ inStart .. inEnd - 1 ] ) = c
299
+ )
300
+ }
301
+
302
+ /**
303
+ * Gets a mode of this regular expression string if it is defined by a mode prefix.
304
+ */
305
+ string getModeFromPrefix ( ) {
306
+ exists ( string c | this .flagGroup ( _, _, c ) |
307
+ c = "i" and result = "IGNORECASE" // case insensitive
308
+ or
309
+ c = "x" and result = "VERBOSE" // ignores whitespace and `#` comments within patterns
310
+ or
311
+ c = "s" and result = "DOTALL" // dot matches all characters, including line terminators
312
+ or
313
+ c = "m" and result = "MULTILINE" // `^` and `$` also match beginning and end of lines
314
+ or
315
+ c = "w" and result = "UNICODE" // Unicode UAX 29 word boundary mode
316
+ )
317
+ }
318
+
319
+ /**
320
+ * Gets a mode (if any) of this regular expression. Can be any of:
321
+ * IGNORECASE
322
+ * VERBOSE
323
+ * DOTALL
324
+ * MULTILINE
325
+ * UNICODE
326
+ */
327
+ string getAMode ( ) { result = this .getModeFromPrefix ( ) }
328
+
277
329
/**
278
330
* Holds if the `i`th character could not be parsed.
279
331
*/
@@ -653,6 +705,8 @@ abstract class RegExp extends Expr {
653
705
this .commentGroupStart ( start , end )
654
706
or
655
707
this .simpleGroupStart ( start , end )
708
+ or
709
+ this .flagGroupStart ( start , end )
656
710
}
657
711
658
712
/** Matches the start of a non-capturing group, e.g. `(?:` */
0 commit comments