diff --git a/README.md b/README.md index 390a8e3..6478f2e 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,41 @@ The following options may be passed to the `highlight` function. } ``` +**Get segments for custom highlighter** +```js +const { getSegments } = require('sql-highlight') + +const sqlString = "SELECT `id`, `username` FROM `users` WHERE `email` = 'test@example.com'" + +const segments = getSegments(sqlString) + +console.log(segments) +``` + +**Output:** +```js +[ + { name: 'keyword', content: 'SELECT' }, + { name: 'default', content: ' ' }, + { name: 'string', content: '`id`' }, + { name: 'special', content: ',' }, + { name: 'default', content: ' ' }, + { name: 'string', content: '`username`' }, + { name: 'default', content: ' ' }, + { name: 'keyword', content: 'FROM' }, + { name: 'default', content: ' ' }, + { name: 'string', content: '`users`' }, + { name: 'default', content: ' ' }, + { name: 'keyword', content: 'WHERE' }, + { name: 'default', content: ' ' }, + { name: 'string', content: '`email`' }, + { name: 'default', content: ' ' }, + { name: 'special', content: '=' }, + { name: 'default', content: ' ' }, + { name: 'string', content: "'test@example.com'" } +] +``` + ## Contributing See the [contribution guidelines](CONTRIBUTING.md). diff --git a/index.test.js b/index.test.js index 39ebb41..caf7909 100644 --- a/index.test.js +++ b/index.test.js @@ -1,4 +1,4 @@ -const { highlight } = require('./lib') +const { highlight, getSegments } = require('./lib') const OPTIONS = { colors: { @@ -182,3 +182,49 @@ describe('html', () => { .toBe('SELECT id FROM users') }) }) + +describe('getSegments', () => { + it('complex query', () => { + expect(getSegments("SELECT COUNT(id), `id`, `username` FROM `users` WHERE `email` = 'test@example.com' AND `foo` = 'BAR' OR 1=1")) + .toStrictEqual([ + { name: 'keyword', content: 'SELECT' }, + { name: 'default', content: ' ' }, + { name: 'function', content: 'COUNT' }, + { name: 'bracket', content: '(' }, + { name: 'default', content: 'id' }, + { name: 'bracket', content: ')' }, + { name: 'special', content: ',' }, + { name: 'default', content: ' ' }, + { name: 'string', content: '`id`' }, + { name: 'special', content: ',' }, + { name: 'default', content: ' ' }, + { name: 'string', content: '`username`' }, + { name: 'default', content: ' ' }, + { name: 'keyword', content: 'FROM' }, + { name: 'default', content: ' ' }, + { name: 'string', content: '`users`' }, + { name: 'default', content: ' ' }, + { name: 'keyword', content: 'WHERE' }, + { name: 'default', content: ' ' }, + { name: 'string', content: '`email`' }, + { name: 'default', content: ' ' }, + { name: 'special', content: '=' }, + { name: 'default', content: ' ' }, + { name: 'string', content: "'test@example.com'" }, + { name: 'default', content: ' ' }, + { name: 'keyword', content: 'AND' }, + { name: 'default', content: ' ' }, + { name: 'string', content: '`foo`' }, + { name: 'default', content: ' ' }, + { name: 'special', content: '=' }, + { name: 'default', content: ' ' }, + { name: 'string', content: "'BAR'" }, + { name: 'default', content: ' ' }, + { name: 'keyword', content: 'OR' }, + { name: 'default', content: ' ' }, + { name: 'number', content: '1' }, + { name: 'special', content: '=' }, + { name: 'number', content: '1' } + ]) + }) +}) diff --git a/lib/index.d.ts b/lib/index.d.ts index b5d8ada..c2da18e 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -12,6 +12,12 @@ declare module 'sql-highlight' { clear: string; }; } + + export interface Segment { + name: string; + content: string; + } + export function getSegments(sqlString: string): Array; export function highlight(sqlString: string, options?: HighlightOptions): string; } diff --git a/lib/index.js b/lib/index.js index 245e97a..a62cd93 100644 --- a/lib/index.js +++ b/lib/index.js @@ -18,6 +18,8 @@ const DEFAULT_OPTIONS = { const SPLIT_CHARS = '[^a-zA-Z_]' +const DEFAULT_KEYWORD = 'default' + const highlighters = [ { name: 'keyword', @@ -47,9 +49,7 @@ const highlighters = [ } ] -function highlight (sqlString, options) { - options = Object.assign({}, DEFAULT_OPTIONS, options) - +function getSegments (sqlString) { const matches = [] for (const hl of highlighters) { @@ -57,7 +57,7 @@ function highlight (sqlString, options) { // This is probably the one time when an assignment inside a condition makes sense // eslint-disable-next-line no-cond-assign - while (match = hl.regex.exec(sqlString)) { + while ((match = hl.regex.exec(sqlString))) { let text = match[0] let boringLength = 0 @@ -73,7 +73,10 @@ function highlight (sqlString, options) { matches.push({ name: hl.name, start: match.index + boringLength, - length: (hl.trimEnd ? text.substr(0, text.length - hl.trimEnd) : text).length + length: (hl.trimEnd + ? text.substring(0, text.length - hl.trimEnd) + : text + ).length }) } } @@ -81,42 +84,57 @@ function highlight (sqlString, options) { const sortedMatches = matches.slice().sort((a, b) => a.start - b.start) // filter/exclude nested matches (matches within the last match) - const filteredMatches = [] + const segments = [] let upperBound = 0 for (let i = 0; i < sortedMatches.length; i++) { if (sortedMatches[i].start >= upperBound) { - filteredMatches.push(sortedMatches[i]) + if (sortedMatches[i].start > upperBound) { + segments.push({ + name: DEFAULT_KEYWORD, + content: sqlString.substring(upperBound, sortedMatches[i].start) + }) + } + + segments.push({ + name: sortedMatches[i].name, + content: sqlString.substring( + sortedMatches[i].start, + sortedMatches[i].start + sortedMatches[i].length + ) + }) upperBound = sortedMatches[i].start + sortedMatches[i].length } } - let highlighted = '' - - for (let i = 0; i < filteredMatches.length; i++) { - const match = filteredMatches[i] - const nextMatch = filteredMatches[i + 1] - - const stringMatch = sqlString.substr(match.start, match.length) - - if (options.html) { - highlighted += `` - highlighted += stringMatch - highlighted += '' - } else { - highlighted += options.colors[match.name] - highlighted += stringMatch - highlighted += options.colors.clear - } - if (nextMatch) { - highlighted += sqlString.substr(match.start + match.length, nextMatch.start - (match.start + match.length)) - } else if (sqlString.length > (match.start + match.length)) { - highlighted += sqlString.substr(match.start + match.length) - } + if (upperBound < sqlString.length - 1) { + segments.push({ + name: DEFAULT_KEYWORD, + content: sqlString.substring( + upperBound, + upperBound + sqlString.length + 1 + ) + }) } + return segments +} - return highlighted +function highlight (sqlString, options) { + options = Object.assign({}, DEFAULT_OPTIONS, options) + + return getSegments(sqlString) + .map(({ name, content }) => { + if (name === DEFAULT_KEYWORD) { + return content + } + if (options.html) { + return `${content}` + } + return options.colors[name] + content + options.colors.clear + }) + .join('') } module.exports = { + getSegments, highlight }