Skip to content

Commit

Permalink
feat: allow functions in whitelist / blacklist
Browse files Browse the repository at this point in the history
refs #212
  • Loading branch information
fczbkk committed Feb 21, 2022
1 parent 17685aa commit 9ea3eee
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 8 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,11 @@ getCssSelector(targetElement, {root: document.querySelector('.myRootElement')});
### Blacklist
If you want to ignore some selectors, you can put them on the blacklist. Blacklist is an array that can contain either regular expressions, or strings. In strings, you can use an asterisk (`*`) as a wildcard that will match any number of any characters.
If you want to ignore some selectors, you can put them on the blacklist. Blacklist is an array that can contain either regular expressions, strings and/or functions.
In **strings**, you can use an asterisk (`*`) as a wildcard that will match any number of any characters.
**Functions** will receive a selector as a parameter. They should always return boolean, `true` if it is a match, `false` if it is not. Any other type of return value will be ignored.
```html
<body>
Expand All @@ -203,6 +207,10 @@ getCssSelector(targetElement, {blacklist: ['.first*']});
// ".secondClass"
getCssSelector(targetElement, {blacklist: [/first/]});
// ".secondClass"
getCssSelector(targetElement, {blacklist: [
(input) => input.startsWith()

This comment has been minimized.

Copy link
@fregante

fregante Feb 21, 2022

Contributor

This is missing a string inside the startsWith method

This comment has been minimized.

Copy link
@fczbkk

fczbkk Feb 21, 2022

Author Owner
]});
// ".secondClass"
```
You can target selectors of any types using the blacklist.
Expand Down
3 changes: 2 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ export interface ResultData {
export type CssSelector = string
export type CssSelectors = Array<CssSelector>

export type CssSelectorMatch = RegExp | string
type CssSelectorMatchFn = (input: string) => boolean
export type CssSelectorMatch = RegExp | string | CssSelectorMatchFn

export enum CssSelectorType {
id = 'id',
Expand Down
35 changes: 30 additions & 5 deletions src/utilities-data.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {CssSelectorMatch, PatternMatcher} from './types'
import {isRegExp} from './utilities-options'
import {showWarning} from './utilities-messages'

/**
* Creates array containing only items included in all input arrays.
Expand Down Expand Up @@ -39,10 +40,34 @@ export function wildcardToRegExp (input: string): string {
export function createPatternMatcher (
list: CssSelectorMatch[]
): PatternMatcher {
const patterns = list.map(
(item) => (isRegExp(item)
? item
: new RegExp('^' + wildcardToRegExp(item) + '$'))
const matchFunctions = list.map((item) => {
if (isRegExp(item)) {
return (input: string) => item.test(input)
}

if (typeof item === 'function') {
return (input: string) => {
const result = item(input)
if (typeof result !== 'boolean') {
// eslint-disable-next-line max-len
showWarning('pattern matcher function invalid', 'Provided pattern matching function does not return boolean. It\'s result will be ignored.', item)
return false
}
return result
}
}

if (typeof item === 'string') {
const re = new RegExp('^' + wildcardToRegExp(item) + '$')
return (input: string) => re.test(input)
}

// eslint-disable-next-line max-len
showWarning('pattern matcher invalid', 'Pattern matching only accepts strings, regular expressions and/or functions. This item is invalid and will be ignored.', item)
return () => false
})

return (input: string) => matchFunctions.some(
(matchFunction) => matchFunction(input)
)
return (input: string) => patterns.some((re) => re.test(input))
}
2 changes: 1 addition & 1 deletion src/utilities-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function isRegExp (input: unknown): input is RegExp {
* @param input
*/
export function isCssSelectorMatch (input: unknown): input is CssSelectorMatch {
return (typeof input === 'string') || isRegExp(input)
return ['string', 'function'].includes(typeof input) || isRegExp(input)
}

/**
Expand Down
10 changes: 10 additions & 0 deletions test/options-blacklist.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ describe('options: blacklist', function () {
assert.equal(result, '.bbb')
})

it('should work with function', () => {
root.innerHTML = '<div class="aaa bbb"></div>'
const result = getCssSelector(root.firstElementChild, {
blacklist: [
(input) => input.endsWith('aaa')
]
})
assert.equal(result, '.bbb')
})

it('should work with multiple items', function () {
root.innerHTML = '<div class="aaa bbb"></div>'
const result = getCssSelector(
Expand Down
10 changes: 10 additions & 0 deletions test/options-whitelist.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ describe('options: whitelist', function () {
assert.equal(result, '.bbb')
})

it('should work with function', () => {
root.innerHTML = '<div class="aaa bbb"></div>'
const result = getCssSelector(root.firstElementChild, {
whitelist: [
(input) => input.endsWith('bbb')
]
})
assert.equal(result, '.bbb')
})

it('should work with multiple items', function () {
root.innerHTML = '<div class="aaa bbb"></div>'
const result = getCssSelector(
Expand Down
26 changes: 26 additions & 0 deletions test/utilities-pattern-matcher.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,30 @@ describe('utilities - pattern matcher', () => {
assert.equal(matchPattern('strict'), true)
assert.equal(matchPattern('STRICT'), false)
})

it('should accept function', () => {
const matchFunction = (input) => input === 'aaa'
const matchPattern = createPatternMatcher([matchFunction])
assert.equal(matchPattern('aaa'), true)
assert.equal(matchPattern('xxx'), false)
})

it('should always return `false` if function returns non-boolean', () => {
const matchFunction = () => 'non-boolean return value'
const matchPattern = createPatternMatcher([matchFunction])
assert.equal(matchPattern('aaa'), false)
})

it('should ignore invalid inputs', () => {
const matchPattern = createPatternMatcher([
true,
false,
undefined,
null,
123,
[],
{}
])
assert.equal(matchPattern('aaa'), false)
})
})

0 comments on commit 9ea3eee

Please sign in to comment.