@@ -18,106 +18,66 @@ const DEFAULT_OPTIONS = {
18
18
}
19
19
}
20
20
21
- const SPLIT_CHARS = '[^a-zA-Z_]'
22
-
23
21
const DEFAULT_KEYWORD = 'default'
24
22
25
23
const highlighters = [
26
- {
27
- name : 'keyword' ,
28
- group : 1 ,
29
- regex : new RegExp ( `(^|${ SPLIT_CHARS } )(${ keywords . join ( '|' ) } )(?=${ SPLIT_CHARS } |$)` , 'gi' )
30
- } ,
31
- {
32
- name : 'special' ,
33
- regex : / ( = | ! = | % | \/ | \* | - | , | ; | : | \+ | < | > ) / g
34
- } ,
35
- {
36
- name : 'function' ,
37
- regex : / ( \w + ?) \( / g,
38
- trimEnd : 1
39
- } ,
40
- {
41
- name : 'number' ,
42
- regex : / ( \b \d + (?: \. \d + ) ? ) / g
43
- } ,
44
- {
45
- name : 'string' ,
46
- regex : / ( [ ' ] (?: \\ ' | .) * ?[ ' ] | [ " ] (?: \\ " | .) * ?[ " ] | [ ` ] (?: \\ ` | .) * ?[ ` ] ) / g
47
- } ,
48
- {
49
- name : 'bracket' ,
50
- regex : / ( [ ( ) ] ) / g
51
- }
52
- ]
24
+ / \b (?< number > \d + (?: \. \d + ) ? ) \b / ,
53
25
54
- function getSegments ( sqlString ) {
55
- const matches = [ ]
56
-
57
- for ( const hl of highlighters ) {
58
- let match
59
-
60
- // This is probably the one time when an assignment inside a condition makes sense
61
- // eslint-disable-next-line no-cond-assign
62
- while ( match = hl . regex . exec ( sqlString ) ) {
63
- let text = match [ 0 ]
64
- let boringLength = 0
65
-
66
- // If a specific group is requested, use that group instead, and make sure
67
- // we offset the index by the length of the preceding groups
68
- if ( hl . group ) {
69
- text = match [ hl . group + 1 ]
70
- for ( let i = 1 ; i <= hl . group ; i ++ ) {
71
- boringLength += match [ i ] . length
72
- }
73
- }
26
+ // Note: Repeating string escapes like 'sql''server' will also work as they are just repeating strings
27
+ / (?< string > ' (?: [ ^ ' \\ ] | \\ .) * ' | " (?: [ ^ " \\ ] | \\ .) * " | ` (?: [ ^ ` \\ ] | \\ .) * ` ) / ,
74
28
75
- const trimmedText = hl . trimEnd
76
- ? text . substring ( 0 , text . length - hl . trimEnd )
77
- : text
78
- matches . push ( {
79
- name : hl . name ,
80
- start : match . index + boringLength ,
81
- length : trimmedText . length
82
- } )
83
- }
84
- }
29
+ / \b (?< function > \w + ) (? = \s * \( ) / ,
30
+
31
+ / (?< bracket > [ ( ) ] ) / ,
85
32
86
- const sortedMatches = matches . slice ( ) . sort ( ( a , b ) => a . start - b . start )
33
+ / (?< special > ! = | [ = % * / \- + , ; : < > ] ) /
34
+ ]
35
+
36
+ function getRegexString ( regex ) {
37
+ const str = regex . toString ( )
38
+ return str . replace ( / ^ \/ | \/ \w * $ / g, '' )
39
+ }
87
40
88
- // filter/exclude nested matches (matches within the last match)
41
+ // Regex of the shape /(.*?)|((?<token1>...)|(?<token2>...)|...|$)/y
42
+ const tokenizer = new RegExp (
43
+ '(.*?)(' +
44
+ '\\b(?<keyword>' + keywords . join ( '|' ) + ')\\b|' +
45
+ highlighters . map ( getRegexString ) . join ( '|' ) +
46
+ '|$)' , // $ needed to to match "default" till the end of string
47
+ 'isy'
48
+ )
49
+
50
+ function getSegments ( sqlString ) {
89
51
const segments = [ ]
90
- let upperBound = 0
91
- for ( let i = 0 ; i < sortedMatches . length ; i ++ ) {
92
- if ( sortedMatches [ i ] . start < upperBound ) { continue }
52
+ let match
53
+
54
+ // Reset the starting position
55
+ tokenizer . lastIndex = 0
93
56
94
- // If no match, add a default segment
95
- if ( sortedMatches [ i ] . start > upperBound ) {
57
+ // This is probably the one time when an assignment inside a condition makes sense
58
+ // eslint-disable-next-line no-cond-assign
59
+ while ( match = tokenizer . exec ( sqlString ) ) {
60
+ if ( match [ 1 ] ) {
96
61
segments . push ( {
97
62
name : DEFAULT_KEYWORD ,
98
- content : sqlString . substring ( upperBound , sortedMatches [ i ] . start )
63
+ content : match [ 1 ]
99
64
} )
100
65
}
101
66
102
- segments . push ( {
103
- name : sortedMatches [ i ] . name ,
104
- content : sqlString . substring (
105
- sortedMatches [ i ] . start ,
106
- sortedMatches [ i ] . start + sortedMatches [ i ] . length
107
- )
108
- } )
109
- upperBound = sortedMatches [ i ] . start + sortedMatches [ i ] . length
110
- }
67
+ if ( match [ 2 ] ) {
68
+ const name = Object . keys ( match . groups ) . find ( key => match . groups [ key ] )
69
+ segments . push ( {
70
+ name,
71
+ content : match . groups [ name ]
72
+ } )
73
+ }
111
74
112
- if ( upperBound < sqlString . length - 1 ) {
113
- segments . push ( {
114
- name : DEFAULT_KEYWORD ,
115
- content : sqlString . substring (
116
- upperBound ,
117
- upperBound + sqlString . length + 1
118
- )
119
- } )
75
+ // Stop at the end of string
76
+ if ( match . index + match [ 0 ] . length >= sqlString . length ) {
77
+ break
78
+ }
120
79
}
80
+
121
81
return segments
122
82
}
123
83
@@ -140,5 +100,6 @@ function highlight (sqlString, options) {
140
100
141
101
module . exports = {
142
102
getSegments,
143
- highlight
103
+ highlight,
104
+ DEFAULT_OPTIONS
144
105
}
0 commit comments