function makeArray (subject) {
return Array.isArray(subject)
? subject
: [subject]
}
const EMPTY = ''
const SPACE = ' '
const ESCAPE = '\\'
const REGEX_TEST_BLANK_LINE = /^\s+$/
const REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/
const REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/
const REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/
const REGEX_SPLITALL_CRLF = /\r?\n/g
const REGEX_TEST_INVALID_PATH = /^\.*\/|^\.+$/
const SLASH = '/'
let TMP_KEY_IGNORE = 'node-ignore'
if (typeof Symbol !== 'undefined') {
TMP_KEY_IGNORE = Symbol.for('node-ignore')
}
const KEY_IGNORE = TMP_KEY_IGNORE
const define = (object, key, value) =>
Object.defineProperty(object, key, {value})
const REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g
const RETURN_FALSE = () => false
const sanitizeRange = range => range.replace(
REGEX_REGEXP_RANGE,
(match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0)
? match
: EMPTY
)
const cleanRangeBackSlash = slashes => {
const {length} = slashes
return slashes.slice(0, length - length % 2)
}
const REPLACERS = [
[
/^\uFEFF/,
() => EMPTY
],
[
/\\?\s+$/,
match => match.indexOf('\\') === 0
? SPACE
: EMPTY
],
[
/\\\s/g,
() => SPACE
],
[
/[\\$.|*+(){^]/g,
match => `\\${match}`
],
[
/(?!\\)\?/g,
() => '[^/]'
],
[
/^\//,
() => '^'
],
[
/\//g,
() => '\\/'
],
[
/^\^*\\\*\\\*\\\//,
() => '^(?:.*\\/)?'
],
[
/^(?=[^^])/,
function startingReplacer () {
return !/\/(?!$)/.test(this)
? '(?:^|\\/)'
: '^'
}
],
[
/\\\/\\\*\\\*(?=\\\/|$)/g,
(_, index, str) => index + 6 < str.length
? '(?:\\/[^\\/]+)*'
: '\\/.+'
],
[
/(^|[^\\]+)(\\\*)+(?=.+)/g,
(_, p1, p2) => {
const unescaped = p2.replace(/\\\*/g, '[^\\/]*')
return p1 + unescaped
}
],
[
/\\\\\\(?=[$.|*+(){^])/g,
() => ESCAPE
],
[
/\\\\/g,
() => ESCAPE
],
[
/(\\)?\[([^\]/]*?)(\\*)($|\])/g,
(match, leadEscape, range, endEscape, close) => leadEscape === ESCAPE
? `\\[${range}${cleanRangeBackSlash(endEscape)}${close}`
: close === ']'
? endEscape.length % 2 === 0
? `[${sanitizeRange(range)}${endEscape}]`
: '[]'
: '[]'
],
[
/(?:[^*])$/,
match => /\/$/.test(match)
? `${match}$`
: `${match}(?=$|\\/$)`
],
[
/(\^|\\\/)?\\\*$/,
(_, p1) => {
const prefix = p1
? `${p1}[^/]+`
: '[^/]*'
return `${prefix}(?=$|\\/$)`
}
],
]
const regexCache = Object.create(null)
const makeRegex = (pattern, ignoreCase) => {
let source = regexCache[pattern]
if (!source) {
source = REPLACERS.reduce(
(prev, current) => prev.replace(current[0], current[1].bind(pattern)),
pattern
)
regexCache[pattern] = source
}
return ignoreCase
? new RegExp(source, 'i')
: new RegExp(source)
}
const isString = subject => typeof subject === 'string'
const checkPattern = pattern => pattern
&& isString(pattern)
&& !REGEX_TEST_BLANK_LINE.test(pattern)
&& !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern)
&& pattern.indexOf('#') !== 0
const splitPattern = pattern => pattern.split(REGEX_SPLITALL_CRLF)
class IgnoreRule {
constructor (
origin,
pattern,
negative,
regex
) {
this.origin = origin
this.pattern = pattern
this.negative = negative
this.regex = regex
}
}
const createRule = (pattern, ignoreCase) => {
const origin = pattern
let negative = false
if (pattern.indexOf('!') === 0) {
negative = true
pattern = pattern.substr(1)
}
pattern = pattern
.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, '!')
.replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, '#')
const regex = makeRegex(pattern, ignoreCase)
return new IgnoreRule(
origin,
pattern,
negative,
regex
)
}
const throwError = (message, Ctor) => {
throw new Ctor(message)
}
const checkPath = (path, originalPath, doThrow) => {
if (!isString(path)) {
return doThrow(
`path must be a string, but got \`${originalPath}\``,
TypeError
)
}
if (!path) {
return doThrow(`path must not be empty`, TypeError)
}
if (checkPath.isNotRelative(path)) {
const r = '`path.relative()`d'
return doThrow(
`path should be a ${r} string, but got "${originalPath}"`,
RangeError
)
}
return true
}
const isNotRelative = path => REGEX_TEST_INVALID_PATH.test(path)
checkPath.isNotRelative = isNotRelative
checkPath.convert = p => p
class Ignore {
constructor ({
ignorecase = true,
ignoreCase = ignorecase,
allowRelativePaths = false
} = {}) {
define(this, KEY_IGNORE, true)
this._rules = []
this._ignoreCase = ignoreCase
this._allowRelativePaths = allowRelativePaths
this._initCache()
}
_initCache () {
this._ignoreCache = Object.create(null)
this._testCache = Object.create(null)
}
_addPattern (pattern) {
if (pattern && pattern[KEY_IGNORE]) {
this._rules = this._rules.concat(pattern._rules)
this._added = true
return
}
if (checkPattern(pattern)) {
const rule = createRule(pattern, this._ignoreCase)
this._added = true
this._rules.push(rule)
}
}
add (pattern) {
this._added = false
makeArray(
isString(pattern)
? splitPattern(pattern)
: pattern
).forEach(this._addPattern, this)
if (this._added) {
this._initCache()
}
return this
}
addPattern (pattern) {
return this.add(pattern)
}
_testOne (path, checkUnignored) {
let ignored = false
let unignored = false
this._rules.forEach(rule => {
const {negative} = rule
if (
unignored === negative && ignored !== unignored
|| negative && !ignored && !unignored && !checkUnignored
) {
return
}
const matched = rule.regex.test(path)
if (matched) {
ignored = !negative
unignored = negative
}
})
return {
ignored,
unignored
}
}
_test (originalPath, cache, checkUnignored, slices) {
const path = originalPath
&& checkPath.convert(originalPath)
checkPath(
path,
originalPath,
this._allowRelativePaths
? RETURN_FALSE
: throwError
)
return this._t(path, cache, checkUnignored, slices)
}
_t (path, cache, checkUnignored, slices) {
if (path in cache) {
return cache[path]
}
if (!slices) {
slices = path.split(SLASH)
}
slices.pop()
if (!slices.length) {
return cache[path] = this._testOne(path, checkUnignored)
}
const parent = this._t(
slices.join(SLASH) + SLASH,
cache,
checkUnignored,
slices
)
return cache[path] = parent.ignored
? parent
: this._testOne(path, checkUnignored)
}
ignores (path) {
return this._test(path, this._ignoreCache, false).ignored
}
createFilter () {
return path => !this.ignores(path)
}
filter (paths) {
return makeArray(paths).filter(this.createFilter())
}
test (path) {
return this._test(path, this._testCache, true)
}
}
const factory = options => new Ignore(options)
const isPathValid = path =>
checkPath(path && checkPath.convert(path), path, RETURN_FALSE)
factory.isPathValid = isPathValid
factory.default = factory
module.exports = factory
if (
typeof process !== 'undefined'
&& (
process.env && process.env.IGNORE_TEST_WIN32
|| process.platform === 'win32'
)
) {
const makePosix = str => /^\\\\\?\\/.test(str)
|| /["<>|\u0000-\u001F]+/u.test(str)
? str
: str.replace(/\\/g, '/')
checkPath.convert = makePosix
const REGIX_IS_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i
checkPath.isNotRelative = path =>
REGIX_IS_WINDOWS_PATH_ABSOLUTE.test(path)
|| isNotRelative(path)
}