Skip to content

Commit

Permalink
Merge e874e3b into e321ac4
Browse files Browse the repository at this point in the history
  • Loading branch information
mediabakery committed Aug 7, 2022
2 parents e321ac4 + e874e3b commit ce5ddf4
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 31 deletions.
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
48 changes: 47 additions & 1 deletion index.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { highlight } = require('./lib')
const { highlight, getSegments } = require('./lib')

const OPTIONS = {
colors: {
Expand Down Expand Up @@ -182,3 +182,49 @@ describe('html', () => {
.toBe('<span class="sql-hl-keyword">SELECT</span> id <span class="sql-hl-keyword">FROM</span> 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' }
])
})
})
6 changes: 6 additions & 0 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ declare module 'sql-highlight' {
clear: string;
};
}

export interface Segment {
name: string;
content: string;
}

export function getSegments(sqlString: string): Array<Segment>;
export function highlight(sqlString: string, options?: HighlightOptions): string;
}
78 changes: 48 additions & 30 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const DEFAULT_OPTIONS = {

const SPLIT_CHARS = '[^a-zA-Z_]'

const DEFAULT_KEYWORD = 'default'

const highlighters = [
{
name: 'keyword',
Expand Down Expand Up @@ -47,17 +49,15 @@ const highlighters = [
}
]

function highlight (sqlString, options) {
options = Object.assign({}, DEFAULT_OPTIONS, options)

function getSegments (sqlString) {
const matches = []

for (const hl of highlighters) {
let match

// 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

Expand All @@ -73,50 +73,68 @@ 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
})
}
}

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 += `<span class="${options.classPrefix}${match.name}">`
highlighted += stringMatch
highlighted += '</span>'
} 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 `<span class="${options.classPrefix}${name}">${content}</span>`
}
return options.colors[name] + content + options.colors.clear
})
.join('')
}

module.exports = {
getSegments,
highlight
}

0 comments on commit ce5ddf4

Please sign in to comment.