index.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  1. // A simple implementation of make-array
  2. function makeArray (subject) {
  3. return Array.isArray(subject)
  4. ? subject
  5. : [subject]
  6. }
  7. const UNDEFINED = undefined
  8. const EMPTY = ''
  9. const SPACE = ' '
  10. const ESCAPE = '\\'
  11. const REGEX_TEST_BLANK_LINE = /^\s+$/
  12. const REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/
  13. const REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/
  14. const REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/
  15. const REGEX_SPLITALL_CRLF = /\r?\n/g
  16. // Invalid:
  17. // - /foo,
  18. // - ./foo,
  19. // - ../foo,
  20. // - .
  21. // - ..
  22. // Valid:
  23. // - .foo
  24. const REGEX_TEST_INVALID_PATH = /^\.*\/|^\.+$/
  25. const REGEX_TEST_TRAILING_SLASH = /\/$/
  26. const SLASH = '/'
  27. // Do not use ternary expression here, since "istanbul ignore next" is buggy
  28. let TMP_KEY_IGNORE = 'node-ignore'
  29. /* istanbul ignore else */
  30. if (typeof Symbol !== 'undefined') {
  31. TMP_KEY_IGNORE = Symbol.for('node-ignore')
  32. }
  33. const KEY_IGNORE = TMP_KEY_IGNORE
  34. const define = (object, key, value) => {
  35. Object.defineProperty(object, key, {value})
  36. return value
  37. }
  38. const REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g
  39. const RETURN_FALSE = () => false
  40. // Sanitize the range of a regular expression
  41. // The cases are complicated, see test cases for details
  42. const sanitizeRange = range => range.replace(
  43. REGEX_REGEXP_RANGE,
  44. (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0)
  45. ? match
  46. // Invalid range (out of order) which is ok for gitignore rules but
  47. // fatal for JavaScript regular expression, so eliminate it.
  48. : EMPTY
  49. )
  50. // See fixtures #59
  51. const cleanRangeBackSlash = slashes => {
  52. const {length} = slashes
  53. return slashes.slice(0, length - length % 2)
  54. }
  55. // > If the pattern ends with a slash,
  56. // > it is removed for the purpose of the following description,
  57. // > but it would only find a match with a directory.
  58. // > In other words, foo/ will match a directory foo and paths underneath it,
  59. // > but will not match a regular file or a symbolic link foo
  60. // > (this is consistent with the way how pathspec works in general in Git).
  61. // '`foo/`' will not match regular file '`foo`' or symbolic link '`foo`'
  62. // -> ignore-rules will not deal with it, because it costs extra `fs.stat` call
  63. // you could use option `mark: true` with `glob`
  64. // '`foo/`' should not continue with the '`..`'
  65. const REPLACERS = [
  66. [
  67. // Remove BOM
  68. // TODO:
  69. // Other similar zero-width characters?
  70. /^\uFEFF/,
  71. () => EMPTY
  72. ],
  73. // > Trailing spaces are ignored unless they are quoted with backslash ("\")
  74. [
  75. // (a\ ) -> (a )
  76. // (a ) -> (a)
  77. // (a ) -> (a)
  78. // (a \ ) -> (a )
  79. /((?:\\\\)*?)(\\?\s+)$/,
  80. (_, m1, m2) => m1 + (
  81. m2.indexOf('\\') === 0
  82. ? SPACE
  83. : EMPTY
  84. )
  85. ],
  86. // Replace (\ ) with ' '
  87. // (\ ) -> ' '
  88. // (\\ ) -> '\\ '
  89. // (\\\ ) -> '\\ '
  90. [
  91. /(\\+?)\s/g,
  92. (_, m1) => {
  93. const {length} = m1
  94. return m1.slice(0, length - length % 2) + SPACE
  95. }
  96. ],
  97. // Escape metacharacters
  98. // which is written down by users but means special for regular expressions.
  99. // > There are 12 characters with special meanings:
  100. // > - the backslash \,
  101. // > - the caret ^,
  102. // > - the dollar sign $,
  103. // > - the period or dot .,
  104. // > - the vertical bar or pipe symbol |,
  105. // > - the question mark ?,
  106. // > - the asterisk or star *,
  107. // > - the plus sign +,
  108. // > - the opening parenthesis (,
  109. // > - the closing parenthesis ),
  110. // > - and the opening square bracket [,
  111. // > - the opening curly brace {,
  112. // > These special characters are often called "metacharacters".
  113. [
  114. /[\\$.|*+(){^]/g,
  115. match => `\\${match}`
  116. ],
  117. [
  118. // > a question mark (?) matches a single character
  119. /(?!\\)\?/g,
  120. () => '[^/]'
  121. ],
  122. // leading slash
  123. [
  124. // > A leading slash matches the beginning of the pathname.
  125. // > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
  126. // A leading slash matches the beginning of the pathname
  127. /^\//,
  128. () => '^'
  129. ],
  130. // replace special metacharacter slash after the leading slash
  131. [
  132. /\//g,
  133. () => '\\/'
  134. ],
  135. [
  136. // > A leading "**" followed by a slash means match in all directories.
  137. // > For example, "**/foo" matches file or directory "foo" anywhere,
  138. // > the same as pattern "foo".
  139. // > "**/foo/bar" matches file or directory "bar" anywhere that is directly
  140. // > under directory "foo".
  141. // Notice that the '*'s have been replaced as '\\*'
  142. /^\^*\\\*\\\*\\\//,
  143. // '**/foo' <-> 'foo'
  144. () => '^(?:.*\\/)?'
  145. ],
  146. // starting
  147. [
  148. // there will be no leading '/'
  149. // (which has been replaced by section "leading slash")
  150. // If starts with '**', adding a '^' to the regular expression also works
  151. /^(?=[^^])/,
  152. function startingReplacer () {
  153. // If has a slash `/` at the beginning or middle
  154. return !/\/(?!$)/.test(this)
  155. // > Prior to 2.22.1
  156. // > If the pattern does not contain a slash /,
  157. // > Git treats it as a shell glob pattern
  158. // Actually, if there is only a trailing slash,
  159. // git also treats it as a shell glob pattern
  160. // After 2.22.1 (compatible but clearer)
  161. // > If there is a separator at the beginning or middle (or both)
  162. // > of the pattern, then the pattern is relative to the directory
  163. // > level of the particular .gitignore file itself.
  164. // > Otherwise the pattern may also match at any level below
  165. // > the .gitignore level.
  166. ? '(?:^|\\/)'
  167. // > Otherwise, Git treats the pattern as a shell glob suitable for
  168. // > consumption by fnmatch(3)
  169. : '^'
  170. }
  171. ],
  172. // two globstars
  173. [
  174. // Use lookahead assertions so that we could match more than one `'/**'`
  175. /\\\/\\\*\\\*(?=\\\/|$)/g,
  176. // Zero, one or several directories
  177. // should not use '*', or it will be replaced by the next replacer
  178. // Check if it is not the last `'/**'`
  179. (_, index, str) => index + 6 < str.length
  180. // case: /**/
  181. // > A slash followed by two consecutive asterisks then a slash matches
  182. // > zero or more directories.
  183. // > For example, "a/**/b" matches "a/b", "a/x/b", "a/x/y/b" and so on.
  184. // '/**/'
  185. ? '(?:\\/[^\\/]+)*'
  186. // case: /**
  187. // > A trailing `"/**"` matches everything inside.
  188. // #21: everything inside but it should not include the current folder
  189. : '\\/.+'
  190. ],
  191. // normal intermediate wildcards
  192. [
  193. // Never replace escaped '*'
  194. // ignore rule '\*' will match the path '*'
  195. // 'abc.*/' -> go
  196. // 'abc.*' -> skip this rule,
  197. // coz trailing single wildcard will be handed by [trailing wildcard]
  198. /(^|[^\\]+)(\\\*)+(?=.+)/g,
  199. // '*.js' matches '.js'
  200. // '*.js' doesn't match 'abc'
  201. (_, p1, p2) => {
  202. // 1.
  203. // > An asterisk "*" matches anything except a slash.
  204. // 2.
  205. // > Other consecutive asterisks are considered regular asterisks
  206. // > and will match according to the previous rules.
  207. const unescaped = p2.replace(/\\\*/g, '[^\\/]*')
  208. return p1 + unescaped
  209. }
  210. ],
  211. [
  212. // unescape, revert step 3 except for back slash
  213. // For example, if a user escape a '\\*',
  214. // after step 3, the result will be '\\\\\\*'
  215. /\\\\\\(?=[$.|*+(){^])/g,
  216. () => ESCAPE
  217. ],
  218. [
  219. // '\\\\' -> '\\'
  220. /\\\\/g,
  221. () => ESCAPE
  222. ],
  223. [
  224. // > The range notation, e.g. [a-zA-Z],
  225. // > can be used to match one of the characters in a range.
  226. // `\` is escaped by step 3
  227. /(\\)?\[([^\]/]*?)(\\*)($|\])/g,
  228. (match, leadEscape, range, endEscape, close) => leadEscape === ESCAPE
  229. // '\\[bar]' -> '\\\\[bar\\]'
  230. ? `\\[${range}${cleanRangeBackSlash(endEscape)}${close}`
  231. : close === ']'
  232. ? endEscape.length % 2 === 0
  233. // A normal case, and it is a range notation
  234. // '[bar]'
  235. // '[bar\\\\]'
  236. ? `[${sanitizeRange(range)}${endEscape}]`
  237. // Invalid range notaton
  238. // '[bar\\]' -> '[bar\\\\]'
  239. : '[]'
  240. : '[]'
  241. ],
  242. // ending
  243. [
  244. // 'js' will not match 'js.'
  245. // 'ab' will not match 'abc'
  246. /(?:[^*])$/,
  247. // WTF!
  248. // https://git-scm.com/docs/gitignore
  249. // changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)
  250. // which re-fixes #24, #38
  251. // > If there is a separator at the end of the pattern then the pattern
  252. // > will only match directories, otherwise the pattern can match both
  253. // > files and directories.
  254. // 'js*' will not match 'a.js'
  255. // 'js/' will not match 'a.js'
  256. // 'js' will match 'a.js' and 'a.js/'
  257. match => /\/$/.test(match)
  258. // foo/ will not match 'foo'
  259. ? `${match}$`
  260. // foo matches 'foo' and 'foo/'
  261. : `${match}(?=$|\\/$)`
  262. ]
  263. ]
  264. const REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)?\\\*$/
  265. const MODE_IGNORE = 'regex'
  266. const MODE_CHECK_IGNORE = 'checkRegex'
  267. const UNDERSCORE = '_'
  268. const TRAILING_WILD_CARD_REPLACERS = {
  269. [MODE_IGNORE] (_, p1) {
  270. const prefix = p1
  271. // '\^':
  272. // '/*' does not match EMPTY
  273. // '/*' does not match everything
  274. // '\\\/':
  275. // 'abc/*' does not match 'abc/'
  276. ? `${p1}[^/]+`
  277. // 'a*' matches 'a'
  278. // 'a*' matches 'aa'
  279. : '[^/]*'
  280. return `${prefix}(?=$|\\/$)`
  281. },
  282. [MODE_CHECK_IGNORE] (_, p1) {
  283. // When doing `git check-ignore`
  284. const prefix = p1
  285. // '\\\/':
  286. // 'abc/*' DOES match 'abc/' !
  287. ? `${p1}[^/]*`
  288. // 'a*' matches 'a'
  289. // 'a*' matches 'aa'
  290. : '[^/]*'
  291. return `${prefix}(?=$|\\/$)`
  292. }
  293. }
  294. // @param {pattern}
  295. const makeRegexPrefix = pattern => REPLACERS.reduce(
  296. (prev, [matcher, replacer]) =>
  297. prev.replace(matcher, replacer.bind(pattern)),
  298. pattern
  299. )
  300. const isString = subject => typeof subject === 'string'
  301. // > A blank line matches no files, so it can serve as a separator for readability.
  302. const checkPattern = pattern => pattern
  303. && isString(pattern)
  304. && !REGEX_TEST_BLANK_LINE.test(pattern)
  305. && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern)
  306. // > A line starting with # serves as a comment.
  307. && pattern.indexOf('#') !== 0
  308. const splitPattern = pattern => pattern
  309. .split(REGEX_SPLITALL_CRLF)
  310. .filter(Boolean)
  311. class IgnoreRule {
  312. constructor (
  313. pattern,
  314. mark,
  315. body,
  316. ignoreCase,
  317. negative,
  318. prefix
  319. ) {
  320. this.pattern = pattern
  321. this.mark = mark
  322. this.negative = negative
  323. define(this, 'body', body)
  324. define(this, 'ignoreCase', ignoreCase)
  325. define(this, 'regexPrefix', prefix)
  326. }
  327. get regex () {
  328. const key = UNDERSCORE + MODE_IGNORE
  329. if (this[key]) {
  330. return this[key]
  331. }
  332. return this._make(MODE_IGNORE, key)
  333. }
  334. get checkRegex () {
  335. const key = UNDERSCORE + MODE_CHECK_IGNORE
  336. if (this[key]) {
  337. return this[key]
  338. }
  339. return this._make(MODE_CHECK_IGNORE, key)
  340. }
  341. _make (mode, key) {
  342. const str = this.regexPrefix.replace(
  343. REGEX_REPLACE_TRAILING_WILDCARD,
  344. // It does not need to bind pattern
  345. TRAILING_WILD_CARD_REPLACERS[mode]
  346. )
  347. const regex = this.ignoreCase
  348. ? new RegExp(str, 'i')
  349. : new RegExp(str)
  350. return define(this, key, regex)
  351. }
  352. }
  353. const createRule = ({
  354. pattern,
  355. mark
  356. }, ignoreCase) => {
  357. let negative = false
  358. let body = pattern
  359. // > An optional prefix "!" which negates the pattern;
  360. if (body.indexOf('!') === 0) {
  361. negative = true
  362. body = body.substr(1)
  363. }
  364. body = body
  365. // > Put a backslash ("\") in front of the first "!" for patterns that
  366. // > begin with a literal "!", for example, `"\!important!.txt"`.
  367. .replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, '!')
  368. // > Put a backslash ("\") in front of the first hash for patterns that
  369. // > begin with a hash.
  370. .replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, '#')
  371. const regexPrefix = makeRegexPrefix(body)
  372. return new IgnoreRule(
  373. pattern,
  374. mark,
  375. body,
  376. ignoreCase,
  377. negative,
  378. regexPrefix
  379. )
  380. }
  381. class RuleManager {
  382. constructor (ignoreCase) {
  383. this._ignoreCase = ignoreCase
  384. this._rules = []
  385. }
  386. _add (pattern) {
  387. // #32
  388. if (pattern && pattern[KEY_IGNORE]) {
  389. this._rules = this._rules.concat(pattern._rules._rules)
  390. this._added = true
  391. return
  392. }
  393. if (isString(pattern)) {
  394. pattern = {
  395. pattern
  396. }
  397. }
  398. if (checkPattern(pattern.pattern)) {
  399. const rule = createRule(pattern, this._ignoreCase)
  400. this._added = true
  401. this._rules.push(rule)
  402. }
  403. }
  404. // @param {Array<string> | string | Ignore} pattern
  405. add (pattern) {
  406. this._added = false
  407. makeArray(
  408. isString(pattern)
  409. ? splitPattern(pattern)
  410. : pattern
  411. ).forEach(this._add, this)
  412. return this._added
  413. }
  414. // Test one single path without recursively checking parent directories
  415. //
  416. // - checkUnignored `boolean` whether should check if the path is unignored,
  417. // setting `checkUnignored` to `false` could reduce additional
  418. // path matching.
  419. // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
  420. // @returns {TestResult} true if a file is ignored
  421. test (path, checkUnignored, mode) {
  422. let ignored = false
  423. let unignored = false
  424. let matchedRule
  425. this._rules.forEach(rule => {
  426. const {negative} = rule
  427. // | ignored : unignored
  428. // -------- | ---------------------------------------
  429. // negative | 0:0 | 0:1 | 1:0 | 1:1
  430. // -------- | ------- | ------- | ------- | --------
  431. // 0 | TEST | TEST | SKIP | X
  432. // 1 | TESTIF | SKIP | TEST | X
  433. // - SKIP: always skip
  434. // - TEST: always test
  435. // - TESTIF: only test if checkUnignored
  436. // - X: that never happen
  437. if (
  438. unignored === negative && ignored !== unignored
  439. || negative && !ignored && !unignored && !checkUnignored
  440. ) {
  441. return
  442. }
  443. const matched = rule[mode].test(path)
  444. if (!matched) {
  445. return
  446. }
  447. ignored = !negative
  448. unignored = negative
  449. matchedRule = negative
  450. ? UNDEFINED
  451. : rule
  452. })
  453. const ret = {
  454. ignored,
  455. unignored
  456. }
  457. if (matchedRule) {
  458. ret.rule = matchedRule
  459. }
  460. return ret
  461. }
  462. }
  463. const throwError = (message, Ctor) => {
  464. throw new Ctor(message)
  465. }
  466. const checkPath = (path, originalPath, doThrow) => {
  467. if (!isString(path)) {
  468. return doThrow(
  469. `path must be a string, but got \`${originalPath}\``,
  470. TypeError
  471. )
  472. }
  473. // We don't know if we should ignore EMPTY, so throw
  474. if (!path) {
  475. return doThrow(`path must not be empty`, TypeError)
  476. }
  477. // Check if it is a relative path
  478. if (checkPath.isNotRelative(path)) {
  479. const r = '`path.relative()`d'
  480. return doThrow(
  481. `path should be a ${r} string, but got "${originalPath}"`,
  482. RangeError
  483. )
  484. }
  485. return true
  486. }
  487. const isNotRelative = path => REGEX_TEST_INVALID_PATH.test(path)
  488. checkPath.isNotRelative = isNotRelative
  489. // On windows, the following function will be replaced
  490. /* istanbul ignore next */
  491. checkPath.convert = p => p
  492. class Ignore {
  493. constructor ({
  494. ignorecase = true,
  495. ignoreCase = ignorecase,
  496. allowRelativePaths = false
  497. } = {}) {
  498. define(this, KEY_IGNORE, true)
  499. this._rules = new RuleManager(ignoreCase)
  500. this._strictPathCheck = !allowRelativePaths
  501. this._initCache()
  502. }
  503. _initCache () {
  504. // A cache for the result of `.ignores()`
  505. this._ignoreCache = Object.create(null)
  506. // A cache for the result of `.test()`
  507. this._testCache = Object.create(null)
  508. }
  509. add (pattern) {
  510. if (this._rules.add(pattern)) {
  511. // Some rules have just added to the ignore,
  512. // making the behavior changed,
  513. // so we need to re-initialize the result cache
  514. this._initCache()
  515. }
  516. return this
  517. }
  518. // legacy
  519. addPattern (pattern) {
  520. return this.add(pattern)
  521. }
  522. // @returns {TestResult}
  523. _test (originalPath, cache, checkUnignored, slices) {
  524. const path = originalPath
  525. // Supports nullable path
  526. && checkPath.convert(originalPath)
  527. checkPath(
  528. path,
  529. originalPath,
  530. this._strictPathCheck
  531. ? throwError
  532. : RETURN_FALSE
  533. )
  534. return this._t(path, cache, checkUnignored, slices)
  535. }
  536. checkIgnore (path) {
  537. // If the path doest not end with a slash, `.ignores()` is much equivalent
  538. // to `git check-ignore`
  539. if (!REGEX_TEST_TRAILING_SLASH.test(path)) {
  540. return this.test(path)
  541. }
  542. const slices = path.split(SLASH).filter(Boolean)
  543. slices.pop()
  544. if (slices.length) {
  545. const parent = this._t(
  546. slices.join(SLASH) + SLASH,
  547. this._testCache,
  548. true,
  549. slices
  550. )
  551. if (parent.ignored) {
  552. return parent
  553. }
  554. }
  555. return this._rules.test(path, false, MODE_CHECK_IGNORE)
  556. }
  557. _t (
  558. // The path to be tested
  559. path,
  560. // The cache for the result of a certain checking
  561. cache,
  562. // Whether should check if the path is unignored
  563. checkUnignored,
  564. // The path slices
  565. slices
  566. ) {
  567. if (path in cache) {
  568. return cache[path]
  569. }
  570. if (!slices) {
  571. // path/to/a.js
  572. // ['path', 'to', 'a.js']
  573. slices = path.split(SLASH).filter(Boolean)
  574. }
  575. slices.pop()
  576. // If the path has no parent directory, just test it
  577. if (!slices.length) {
  578. return cache[path] = this._rules.test(path, checkUnignored, MODE_IGNORE)
  579. }
  580. const parent = this._t(
  581. slices.join(SLASH) + SLASH,
  582. cache,
  583. checkUnignored,
  584. slices
  585. )
  586. // If the path contains a parent directory, check the parent first
  587. return cache[path] = parent.ignored
  588. // > It is not possible to re-include a file if a parent directory of
  589. // > that file is excluded.
  590. ? parent
  591. : this._rules.test(path, checkUnignored, MODE_IGNORE)
  592. }
  593. ignores (path) {
  594. return this._test(path, this._ignoreCache, false).ignored
  595. }
  596. createFilter () {
  597. return path => !this.ignores(path)
  598. }
  599. filter (paths) {
  600. return makeArray(paths).filter(this.createFilter())
  601. }
  602. // @returns {TestResult}
  603. test (path) {
  604. return this._test(path, this._testCache, true)
  605. }
  606. }
  607. const factory = options => new Ignore(options)
  608. const isPathValid = path =>
  609. checkPath(path && checkPath.convert(path), path, RETURN_FALSE)
  610. // Windows
  611. // --------------------------------------------------------------
  612. /* istanbul ignore next */
  613. if (
  614. // Detect `process` so that it can run in browsers.
  615. typeof process !== 'undefined'
  616. && (
  617. process.env && process.env.IGNORE_TEST_WIN32
  618. || process.platform === 'win32'
  619. )
  620. ) {
  621. /* eslint no-control-regex: "off" */
  622. const makePosix = str => /^\\\\\?\\/.test(str)
  623. || /["<>|\u0000-\u001F]+/u.test(str)
  624. ? str
  625. : str.replace(/\\/g, '/')
  626. checkPath.convert = makePosix
  627. // 'C:\\foo' <- 'C:\\foo' has been converted to 'C:/'
  628. // 'd:\\foo'
  629. const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i
  630. checkPath.isNotRelative = path =>
  631. REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path)
  632. || isNotRelative(path)
  633. }
  634. // COMMONJS_EXPORTS ////////////////////////////////////////////////////////////
  635. module.exports = factory
  636. // Although it is an anti-pattern,
  637. // it is still widely misused by a lot of libraries in github
  638. // Ref: https://github.com/search?q=ignore.default%28%29&type=code
  639. factory.default = factory
  640. module.exports.isPathValid = isPathValid