diff --git a/README.md b/README.md
index 88dc1ab..d15cde6 100644
--- a/README.md
+++ b/README.md
@@ -59,13 +59,13 @@ document.body.innerHTML += highlighted
**Output:**
```html
SELECT
-`id`
+`id`
,
-`username`
+`username`
FROM
-`users`
+`users`
WHERE
-`email`
+`email`
=
'test@example.com'
```
@@ -112,22 +112,22 @@ console.log(segments)
```js
[
{ name: 'keyword', content: 'SELECT' },
- { name: 'default', content: ' ' },
- { name: 'string', content: '`id`' },
+ { name: 'whitespace', content: ' ' },
+ { name: 'identifier', content: '`id`' },
{ name: 'special', content: ',' },
- { name: 'default', content: ' ' },
- { name: 'string', content: '`username`' },
- { name: 'default', content: ' ' },
+ { name: 'whitespace', content: ' ' },
+ { name: 'identifier', content: '`username`' },
+ { name: 'whitespace', content: ' ' },
{ name: 'keyword', content: 'FROM' },
- { name: 'default', content: ' ' },
- { name: 'string', content: '`users`' },
- { name: 'default', content: ' ' },
+ { name: 'whitespace', content: ' ' },
+ { name: 'identifier', content: '`users`' },
+ { name: 'whitespace', content: ' ' },
{ name: 'keyword', content: 'WHERE' },
- { name: 'default', content: ' ' },
- { name: 'string', content: '`email`' },
- { name: 'default', content: ' ' },
+ { name: 'whitespace', content: ' ' },
+ { name: 'identifier', content: '`email`' },
+ { name: 'whitespace', content: ' ' },
{ name: 'special', content: '=' },
- { name: 'default', content: ' ' },
+ { name: 'whitespace', content: ' ' },
{ name: 'string', content: "'test@example.com'" }
]
```
diff --git a/lib/index.js b/lib/index.js
index ba2d539..5824a79 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -19,13 +19,11 @@ const DEFAULT_OPTIONS = {
}
}
-const DEFAULT_KEYWORD = 'default'
-
const highlighters = [
/\b(?\d+(?:\.\d+)?)\b/,
// Note: Repeating string escapes like 'sql''server' will also work as they are just repeating strings
- /(?'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`)/,
+ /(?'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*")/,
/(?--[^\n\r]*|#[^\n\r]*|\/\*(?:[^*]|\*(?!\/))*\*\/)/,
@@ -34,54 +32,29 @@ const highlighters = [
/(?[()])/,
- /(?!=|[=%*/\-+,;:<>])/
-]
+ /(?!=|[=%*/\-+,;:<>.])/,
-function getRegexString (regex) {
- const str = regex.toString()
- return str.replace(/^\/|\/\w*$/g, '')
-}
+ /(?\b\w+\b|`(?:[^`\\]|\\.)*`)/,
+
+ /(?\s+)/,
+
+ /(?\.+?)/
+]
-// Regex of the shape /(.*?)|((?...)|(?...)|...|$)/y
+// Regex of the shape /(?...)|(?...)|.../g
const tokenizer = new RegExp(
- '(.*?)(' +
- '\\b(?' + keywords.join('|') + ')\\b|' +
- highlighters.map(getRegexString).join('|') +
- '|$)', // $ needed to to match "default" till the end of string
- 'isy'
+ [
+ '\\b(?' + keywords.join('|') + ')\\b',
+ ...highlighters.map(regex => regex.source)
+ ].join('|'),
+ 'gis'
)
function getSegments (sqlString) {
- const segments = []
- let match
-
- // Reset the starting position
- tokenizer.lastIndex = 0
-
- // This is probably the one time when an assignment inside a condition makes sense
- // eslint-disable-next-line no-cond-assign
- while (match = tokenizer.exec(sqlString)) {
- if (match[1]) {
- segments.push({
- name: DEFAULT_KEYWORD,
- content: match[1]
- })
- }
-
- if (match[2]) {
- const name = Object.keys(match.groups).find(key => match.groups[key])
- segments.push({
- name,
- content: match.groups[name]
- })
- }
-
- // Stop at the end of string
- if (match.index + match[0].length >= sqlString.length) {
- break
- }
- }
-
+ const segments = Array.from(sqlString.matchAll(tokenizer), match => ({
+ name: Object.keys(match.groups).find(key => match.groups[key]),
+ content: match[0]
+ }))
return segments
}
@@ -90,14 +63,14 @@ function highlight (sqlString, options) {
return getSegments(sqlString)
.map(({ name, content }) => {
- if (name === DEFAULT_KEYWORD) {
- return content
- }
if (options.html) {
const escapedContent = options.htmlEscaper(content)
- return `${escapedContent}`
+ return name === 'whitespace' ? escapedContent : `${escapedContent}`
+ }
+ if (options.colors[name]) {
+ return options.colors[name] + content + options.colors.clear
}
- return options.colors[name] + content + options.colors.clear
+ return content
})
.join('')
}
diff --git a/test/index.test.js b/test/index.test.js
index a208a3a..ab04fe3 100644
--- a/test/index.test.js
+++ b/test/index.test.js
@@ -6,6 +6,7 @@ const OPTIONS = {
function: '[function]',
number: '[number]',
string: '[string]',
+ identifier: '[identifier]',
special: '[special]',
bracket: '[bracket]',
comment: '[comment]',
@@ -33,12 +34,12 @@ describe('unicode', () => {
it('strings (mixing quotes)', () => {
expect(hlUni('\'"`\' "\'`" `"\'`'))
- .toBe('[string]\'"`\'[clear] [string]"\'`"[clear] [string]`"\'`[clear]')
+ .toBe('[string]\'"`\'[clear] [string]"\'`"[clear] [identifier]`"\'`[clear]')
})
- it('strings (scaping quotes)', () => {
+ it('strings (escaping quotes)', () => {
expect(hlUni('\'\\\'\' "\\"" `\\``'))
- .toBe('[string]\'\\\'\'[clear] [string]"\\""[clear] [string]`\\``[clear]')
+ .toBe('[string]\'\\\'\'[clear] [string]"\\""[clear] [identifier]`\\``[clear]')
})
it('integers', () => {
@@ -83,47 +84,47 @@ describe('unicode', () => {
it('alphanumeric', () => {
expect(hlUni('(f1)'))
- .toBe('[bracket]([clear]f1[bracket])[clear]')
+ .toBe('[bracket]([clear][identifier]f1[clear][bracket])[clear]')
})
it('functions', () => {
expect(hlUni('COUNT(`id`)'))
- .toBe('[function]COUNT[clear][bracket]([clear][string]`id`[clear][bracket])[clear]')
+ .toBe('[function]COUNT[clear][bracket]([clear][identifier]`id`[clear][bracket])[clear]')
})
it('basic query', () => {
expect(hlUni("SELECT * FROM `users` WHERE `email` = 'test@example.com'"))
- .toBe("[keyword]SELECT[clear] [special]*[clear] [keyword]FROM[clear] [string]`users`[clear] [keyword]WHERE[clear] [string]`email`[clear] [special]=[clear] [string]'test@example.com'[clear]")
+ .toBe("[keyword]SELECT[clear] [special]*[clear] [keyword]FROM[clear] [identifier]`users`[clear] [keyword]WHERE[clear] [identifier]`email`[clear] [special]=[clear] [string]'test@example.com'[clear]")
})
it('complex query', () => {
expect(hlUni("SELECT COUNT(id), `id`, `username` FROM `users` WHERE `email` = 'test@example.com' AND `foo` = 'BAR' OR 1=1"))
- .toBe("[keyword]SELECT[clear] [function]COUNT[clear][bracket]([clear]id[bracket])[clear][special],[clear] [string]`id`[clear][special],[clear] [string]`username`[clear] [keyword]FROM[clear] [string]`users`[clear] [keyword]WHERE[clear] [string]`email`[clear] [special]=[clear] [string]'test@example.com'[clear] [keyword]AND[clear] [string]`foo`[clear] [special]=[clear] [string]'BAR'[clear] [keyword]OR[clear] [number]1[clear][special]=[clear][number]1[clear]")
+ .toBe("[keyword]SELECT[clear] [function]COUNT[clear][bracket]([clear][identifier]id[clear][bracket])[clear][special],[clear] [identifier]`id`[clear][special],[clear] [identifier]`username`[clear] [keyword]FROM[clear] [identifier]`users`[clear] [keyword]WHERE[clear] [identifier]`email`[clear] [special]=[clear] [string]'test@example.com'[clear] [keyword]AND[clear] [identifier]`foo`[clear] [special]=[clear] [string]'BAR'[clear] [keyword]OR[clear] [number]1[clear][special]=[clear][number]1[clear]")
})
it('query with identifiers without apostrophes', () => {
expect(hlUni('SELECT id FROM users'))
- .toBe('[keyword]SELECT[clear] id [keyword]FROM[clear] users')
+ .toBe('[keyword]SELECT[clear] [identifier]id[clear] [keyword]FROM[clear] [identifier]users[clear]')
})
it('query with nested segments (minus in string)', () => {
expect(hlUni('DROP PROCEDURE IF EXISTS `some-database`.`some-table`;'))
- .toBe('[keyword]DROP[clear] [keyword]PROCEDURE[clear] [keyword]IF[clear] [keyword]EXISTS[clear] [string]`some-database`[clear].[string]`some-table`[clear][special];[clear]')
+ .toBe('[keyword]DROP[clear] [keyword]PROCEDURE[clear] [keyword]IF[clear] [keyword]EXISTS[clear] [identifier]`some-database`[clear][special].[clear][identifier]`some-table`[clear][special];[clear]')
})
it('multiple queries', () => {
expect(hlUni('SELECT * FROM a;SELECT * FROM b;'))
- .toBe('[keyword]SELECT[clear] [special]*[clear] [keyword]FROM[clear] a[special];[clear][keyword]SELECT[clear] [special]*[clear] [keyword]FROM[clear] b[special];[clear]')
+ .toBe('[keyword]SELECT[clear] [special]*[clear] [keyword]FROM[clear] [identifier]a[clear][special];[clear][keyword]SELECT[clear] [special]*[clear] [keyword]FROM[clear] [identifier]b[clear][special];[clear]')
})
it('comment single line', () => {
expect(hlUni('-- comment 1 "comment" /* still */ comment 2\nSELECT `not comment`; -- comment 3'))
- .toBe('[comment]-- comment 1 "comment" /* still */ comment 2[clear]\n[keyword]SELECT[clear] [string]`not comment`[clear][special];[clear] [comment]-- comment 3[clear]')
+ .toBe('[comment]-- comment 1 "comment" /* still */ comment 2[clear]\n[keyword]SELECT[clear] [identifier]`not comment`[clear][special];[clear] [comment]-- comment 3[clear]')
})
it('comment mysql', () => {
expect(hlUni('# comment 1 "comment" /* still */ comment 2\nSELECT `not comment`; # comment 3'))
- .toBe('[comment]# comment 1 "comment" /* still */ comment 2[clear]\n[keyword]SELECT[clear] [string]`not comment`[clear][special];[clear] [comment]# comment 3[clear]')
+ .toBe('[comment]# comment 1 "comment" /* still */ comment 2[clear]\n[keyword]SELECT[clear] [identifier]`not comment`[clear][special];[clear] [comment]# comment 3[clear]')
})
it('comment multiline', () => {
@@ -150,12 +151,12 @@ describe('html', () => {
it('strings (mixing quotes)', () => {
expect(hlHtml('\'"`\' "\'`" `"\'`'))
- .toBe(''"`' "'`" `"'`')
+ .toBe(''"`' "'`" `"'`')
})
it('strings (scaping quotes)', () => {
expect(hlHtml('\'\\\'\' "\\"" `\\``'))
- .toBe(''\\'' "\\"" `\\``')
+ .toBe(''\\'' "\\"" `\\``')
})
it('integers', () => {
@@ -195,52 +196,52 @@ describe('html', () => {
it('alphanumeric', () => {
expect(hlHtml('(f1)'))
- .toBe('(f1)')
+ .toBe('(f1)')
})
it('functions', () => {
expect(hlHtml('COUNT(`id`)'))
- .toBe('COUNT(`id`)')
+ .toBe('COUNT(`id`)')
})
it('basic query', () => {
expect(hlHtml("SELECT * FROM `users` WHERE `email` = 'test@example.com'"))
- .toBe('SELECT * FROM `users` WHERE `email` = 'test@example.com'')
+ .toBe('SELECT * FROM `users` WHERE `email` = 'test@example.com'')
})
it('complex query', () => {
expect(hlHtml("SELECT COUNT(id), `id`, `username` FROM `users` WHERE `email` = 'test@example.com' AND `foo` = 'BAR' OR 1=1"))
- .toBe('SELECT COUNT(id), `id`, `username` FROM `users` WHERE `email` = 'test@example.com' AND `foo` = 'BAR' OR 1=1')
+ .toBe('SELECT COUNT(id), `id`, `username` FROM `users` WHERE `email` = 'test@example.com' AND `foo` = 'BAR' OR 1=1')
})
it('query with identifiers without apostrophes', () => {
expect(hlHtml('SELECT id FROM users'))
- .toBe('SELECT id FROM users')
+ .toBe('SELECT id FROM users')
})
it('query with nested segments (minus in string)', () => {
expect(hlHtml('DROP PROCEDURE IF EXISTS `some-database`.`some-table`;'))
- .toBe('DROP PROCEDURE IF EXISTS `some-database`.`some-table`;')
+ .toBe('DROP PROCEDURE IF EXISTS `some-database`.`some-table`;')
})
it('multiple queries', () => {
expect(hlHtml('SELECT * FROM a;SELECT * FROM b;'))
- .toBe('SELECT * FROM a;SELECT * FROM b;')
+ .toBe('SELECT * FROM a;SELECT * FROM b;')
})
it('escapes HTML entities', () => {
expect(hlHtml("select * from a where b = 'array