basant307/AI_Governance_Project
045
1// A simple implementation of make-array2function makeArray (subject) {3 return Array.isArray(subject)4 ? subject5 : [subject]6}7 8const EMPTY = ''9const SPACE = ' '10const ESCAPE = '\\'11const REGEX_TEST_BLANK_LINE = /^\s+$/12const REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/13const REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/14const REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/15const REGEX_SPLITALL_CRLF = /\r?\n/g16// /foo,17// ./foo,18// ../foo,19// .20// ..21const REGEX_TEST_INVALID_PATH = /^\.*\/|^\.+$/22 23const SLASH = '/'24 25// Do not use ternary expression here, since "istanbul ignore next" is buggy26let TMP_KEY_IGNORE = 'node-ignore'27/* istanbul ignore else */28if (typeof Symbol !== 'undefined') {29 TMP_KEY_IGNORE = Symbol.for('node-ignore')30}31const KEY_IGNORE = TMP_KEY_IGNORE32 33const define = (object, key, value) =>34 Object.defineProperty(object, key, {value})35 36const REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g37 38const RETURN_FALSE = () => false39 40// Sanitize the range of a regular expression41// The cases are complicated, see test cases for details42const sanitizeRange = range => range.replace(43 REGEX_REGEXP_RANGE,44 (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0)45 ? match46 // Invalid range (out of order) which is ok for gitignore rules but47 // fatal for JavaScript regular expression, so eliminate it.48 : EMPTY49)50 51// See fixtures #5952const cleanRangeBackSlash = slashes => {53 const {length} = slashes54 return slashes.slice(0, length - length % 2)55}56 57// > If the pattern ends with a slash,58// > it is removed for the purpose of the following description,59// > but it would only find a match with a directory.60// > In other words, foo/ will match a directory foo and paths underneath it,61// > but will not match a regular file or a symbolic link foo62// > (this is consistent with the way how pathspec works in general in Git).63// '`foo/`' will not match regular file '`foo`' or symbolic link '`foo`'64// -> ignore-rules will not deal with it, because it costs extra `fs.stat` call65// you could use option `mark: true` with `glob`66 67// '`foo/`' should not continue with the '`..`'68const REPLACERS = [69 70 [71 // remove BOM72 // TODO:73 // Other similar zero-width characters?74 /^\uFEFF/,75 () => EMPTY76 ],77 78 // > Trailing spaces are ignored unless they are quoted with backslash ("\")79 [80 // (a\ ) -> (a )81 // (a ) -> (a)82 // (a ) -> (a)83 // (a \ ) -> (a )84 /((?:\\\\)*?)(\\?\s+)$/,85 (_, m1, m2) => m1 + (86 m2.indexOf('\\') === 087 ? SPACE88 : EMPTY89 )90 ],91 92 // replace (\ ) with ' '93 // (\ ) -> ' '94 // (\\ ) -> '\\ '95 // (\\\ ) -> '\\ '96 [97 /(\\+?)\s/g,98 (_, m1) => {99 const {length} = m1100 return m1.slice(0, length - length % 2) + SPACE101 }102 ],103 104 // Escape metacharacters105 // which is written down by users but means special for regular expressions.106 107 // > There are 12 characters with special meanings:108 // > - the backslash \,109 // > - the caret ^,110 // > - the dollar sign $,111 // > - the period or dot .,112 // > - the vertical bar or pipe symbol |,113 // > - the question mark ?,114 // > - the asterisk or star *,115 // > - the plus sign +,116 // > - the opening parenthesis (,117 // > - the closing parenthesis ),118 // > - and the opening square bracket [,119 // > - the opening curly brace {,120 // > These special characters are often called "metacharacters".121 [122 /[\\$.|*+(){^]/g,123 match => `\\${match}`124 ],125 126 [127 // > a question mark (?) matches a single character128 /(?!\\)\?/g,129 () => '[^/]'130 ],131 132 // leading slash133 [134 135 // > A leading slash matches the beginning of the pathname.136 // > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".137 // A leading slash matches the beginning of the pathname138 /^\//,139 () => '^'140 ],141 142 // replace special metacharacter slash after the leading slash143 [144 /\//g,145 () => '\\/'146 ],147 148 [149 // > A leading "**" followed by a slash means match in all directories.150 // > For example, "**/foo" matches file or directory "foo" anywhere,151 // > the same as pattern "foo".152 // > "**/foo/bar" matches file or directory "bar" anywhere that is directly153 // > under directory "foo".154 // Notice that the '*'s have been replaced as '\\*'155 /^\^*\\\*\\\*\\\//,156 157 // '**/foo' <-> 'foo'158 () => '^(?:.*\\/)?'159 ],160 161 // starting162 [163 // there will be no leading '/'164 // (which has been replaced by section "leading slash")165 // If starts with '**', adding a '^' to the regular expression also works166 /^(?=[^^])/,167 function startingReplacer () {168 // If has a slash `/` at the beginning or middle169 return !/\/(?!$)/.test(this)170 // > Prior to 2.22.1171 // > If the pattern does not contain a slash /,172 // > Git treats it as a shell glob pattern173 // Actually, if there is only a trailing slash,174 // git also treats it as a shell glob pattern175 176 // After 2.22.1 (compatible but clearer)177 // > If there is a separator at the beginning or middle (or both)178 // > of the pattern, then the pattern is relative to the directory179 // > level of the particular .gitignore file itself.180 // > Otherwise the pattern may also match at any level below181 // > the .gitignore level.182 ? '(?:^|\\/)'183 184 // > Otherwise, Git treats the pattern as a shell glob suitable for185 // > consumption by fnmatch(3)186 : '^'187 }188 ],189 190 // two globstars191 [192 // Use lookahead assertions so that we could match more than one `'/**'`193 /\\\/\\\*\\\*(?=\\\/|$)/g,194 195 // Zero, one or several directories196 // should not use '*', or it will be replaced by the next replacer197 198 // Check if it is not the last `'/**'`199 (_, index, str) => index + 6 < str.length200 201 // case: /**/202 // > A slash followed by two consecutive asterisks then a slash matches203 // > zero or more directories.204 // > For example, "a/**/b" matches "a/b", "a/x/b", "a/x/y/b" and so on.205 // '/**/'206 ? '(?:\\/[^\\/]+)*'207 208 // case: /**209 // > A trailing `"/**"` matches everything inside.210 211 // #21: everything inside but it should not include the current folder212 : '\\/.+'213 ],214 215 // normal intermediate wildcards216 [217 // Never replace escaped '*'218 // ignore rule '\*' will match the path '*'219 220 // 'abc.*/' -> go221 // 'abc.*' -> skip this rule,222 // coz trailing single wildcard will be handed by [trailing wildcard]223 /(^|[^\\]+)(\\\*)+(?=.+)/g,224 225 // '*.js' matches '.js'226 // '*.js' doesn't match 'abc'227 (_, p1, p2) => {228 // 1.229 // > An asterisk "*" matches anything except a slash.230 // 2.231 // > Other consecutive asterisks are considered regular asterisks232 // > and will match according to the previous rules.233 const unescaped = p2.replace(/\\\*/g, '[^\\/]*')234 return p1 + unescaped235 }236 ],237 238 [239 // unescape, revert step 3 except for back slash240 // For example, if a user escape a '\\*',241 // after step 3, the result will be '\\\\\\*'242 /\\\\\\(?=[$.|*+(){^])/g,243 () => ESCAPE244 ],245 246 [247 // '\\\\' -> '\\'248 /\\\\/g,249 () => ESCAPE250 ],251 252 [253 // > The range notation, e.g. [a-zA-Z],254 // > can be used to match one of the characters in a range.255 256 // `\` is escaped by step 3257 /(\\)?\[([^\]/]*?)(\\*)($|\])/g,258 (match, leadEscape, range, endEscape, close) => leadEscape === ESCAPE259 // '\\[bar]' -> '\\\\[bar\\]'260 ? `\\[${range}${cleanRangeBackSlash(endEscape)}${close}`261 : close === ']'262 ? endEscape.length % 2 === 0263 // A normal case, and it is a range notation264 // '[bar]'265 // '[bar\\\\]'266 ? `[${sanitizeRange(range)}${endEscape}]`267 // Invalid range notaton268 // '[bar\\]' -> '[bar\\\\]'269 : '[]'270 : '[]'271 ],272 273 // ending274 [275 // 'js' will not match 'js.'276 // 'ab' will not match 'abc'277 /(?:[^*])$/,278 279 // WTF!280 // https://git-scm.com/docs/gitignore281 // changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)282 // which re-fixes #24, #38283 284 // > If there is a separator at the end of the pattern then the pattern285 // > will only match directories, otherwise the pattern can match both286 // > files and directories.287 288 // 'js*' will not match 'a.js'289 // 'js/' will not match 'a.js'290 // 'js' will match 'a.js' and 'a.js/'291 match => /\/$/.test(match)292 // foo/ will not match 'foo'293 ? `${match}$`294 // foo matches 'foo' and 'foo/'295 : `${match}(?=$|\\/$)`296 ],297 298 // trailing wildcard299 [300 /(\^|\\\/)?\\\*$/,301 (_, p1) => {302 const prefix = p1303 // '\^':304 // '/*' does not match EMPTY305 // '/*' does not match everything306 307 // '\\\/':308 // 'abc/*' does not match 'abc/'309 ? `${p1}[^/]+`310 311 // 'a*' matches 'a'312 // 'a*' matches 'aa'313 : '[^/]*'314 315 return `${prefix}(?=$|\\/$)`316 }317 ],318]319 320// A simple cache, because an ignore rule only has only one certain meaning321const regexCache = Object.create(null)322 323// @param {pattern}324const makeRegex = (pattern, ignoreCase) => {325 let source = regexCache[pattern]326 327 if (!source) {328 source = REPLACERS.reduce(329 (prev, [matcher, replacer]) =>330 prev.replace(matcher, replacer.bind(pattern)),331 pattern332 )333 regexCache[pattern] = source334 }335 336 return ignoreCase337 ? new RegExp(source, 'i')338 : new RegExp(source)339}340 341const isString = subject => typeof subject === 'string'342 343// > A blank line matches no files, so it can serve as a separator for readability.344const checkPattern = pattern => pattern345 && isString(pattern)346 && !REGEX_TEST_BLANK_LINE.test(pattern)347 && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern)348 349 // > A line starting with # serves as a comment.350 && pattern.indexOf('#') !== 0351 352const splitPattern = pattern => pattern.split(REGEX_SPLITALL_CRLF)353 354class IgnoreRule {355 constructor (356 origin,357 pattern,358 negative,359 regex360 ) {361 this.origin = origin362 this.pattern = pattern363 this.negative = negative364 this.regex = regex365 }366}367 368const createRule = (pattern, ignoreCase) => {369 const origin = pattern370 let negative = false371 372 // > An optional prefix "!" which negates the pattern;373 if (pattern.indexOf('!') === 0) {374 negative = true375 pattern = pattern.substr(1)376 }377 378 pattern = pattern379 // > Put a backslash ("\") in front of the first "!" for patterns that380 // > begin with a literal "!", for example, `"\!important!.txt"`.381 .replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, '!')382 // > Put a backslash ("\") in front of the first hash for patterns that383 // > begin with a hash.384 .replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, '#')385 386 const regex = makeRegex(pattern, ignoreCase)387 388 return new IgnoreRule(389 origin,390 pattern,391 negative,392 regex393 )394}395 396const throwError = (message, Ctor) => {397 throw new Ctor(message)398}399 400const checkPath = (path, originalPath, doThrow) => {401 if (!isString(path)) {402 return doThrow(403 `path must be a string, but got \`${originalPath}\``,404 TypeError405 )406 }407 408 // We don't know if we should ignore EMPTY, so throw409 if (!path) {410 return doThrow(`path must not be empty`, TypeError)411 }412 413 // Check if it is a relative path414 if (checkPath.isNotRelative(path)) {415 const r = '`path.relative()`d'416 return doThrow(417 `path should be a ${r} string, but got "${originalPath}"`,418 RangeError419 )420 }421 422 return true423}424 425const isNotRelative = path => REGEX_TEST_INVALID_PATH.test(path)426 427checkPath.isNotRelative = isNotRelative428checkPath.convert = p => p429 430class Ignore {431 constructor ({432 ignorecase = true,433 ignoreCase = ignorecase,434 allowRelativePaths = false435 } = {}) {436 define(this, KEY_IGNORE, true)437 438 this._rules = []439 this._ignoreCase = ignoreCase440 this._allowRelativePaths = allowRelativePaths441 this._initCache()442 }443 444 _initCache () {445 this._ignoreCache = Object.create(null)446 this._testCache = Object.create(null)447 }448 449 _addPattern (pattern) {450 // #32451 if (pattern && pattern[KEY_IGNORE]) {452 this._rules = this._rules.concat(pattern._rules)453 this._added = true454 return455 }456 457 if (checkPattern(pattern)) {458 const rule = createRule(pattern, this._ignoreCase)459 this._added = true460 this._rules.push(rule)461 }462 }463 464 // @param {Array<string> | string | Ignore} pattern465 add (pattern) {466 this._added = false467 468 makeArray(469 isString(pattern)470 ? splitPattern(pattern)471 : pattern472 ).forEach(this._addPattern, this)473 474 // Some rules have just added to the ignore,475 // making the behavior changed.476 if (this._added) {477 this._initCache()478 }479 480 return this481 }482 483 // legacy484 addPattern (pattern) {485 return this.add(pattern)486 }487 488 // | ignored : unignored489 // negative | 0:0 | 0:1 | 1:0 | 1:1490 // -------- | ------- | ------- | ------- | --------491 // 0 | TEST | TEST | SKIP | X492 // 1 | TESTIF | SKIP | TEST | X493 494 // - SKIP: always skip495 // - TEST: always test496 // - TESTIF: only test if checkUnignored497 // - X: that never happen498 499 // @param {boolean} whether should check if the path is unignored,500 // setting `checkUnignored` to `false` could reduce additional501 // path matching.502 503 // @returns {TestResult} true if a file is ignored504 _testOne (path, checkUnignored) {505 let ignored = false506 let unignored = false507 508 this._rules.forEach(rule => {509 const {negative} = rule510 if (511 unignored === negative && ignored !== unignored512 || negative && !ignored && !unignored && !checkUnignored513 ) {514 return515 }516 517 const matched = rule.regex.test(path)518 519 if (matched) {520 ignored = !negative521 unignored = negative522 }523 })524 525 return {526 ignored,527 unignored528 }529 }530 531 // @returns {TestResult}532 _test (originalPath, cache, checkUnignored, slices) {533 const path = originalPath534 // Supports nullable path535 && checkPath.convert(originalPath)536 537 checkPath(538 path,539 originalPath,540 this._allowRelativePaths541 ? RETURN_FALSE542 : throwError543 )544 545 return this._t(path, cache, checkUnignored, slices)546 }547 548 _t (path, cache, checkUnignored, slices) {549 if (path in cache) {550 return cache[path]551 }552 553 if (!slices) {554 // path/to/a.js555 // ['path', 'to', 'a.js']556 slices = path.split(SLASH)557 }558 559 slices.pop()560 561 // If the path has no parent directory, just test it562 if (!slices.length) {563 return cache[path] = this._testOne(path, checkUnignored)564 }565 566 const parent = this._t(567 slices.join(SLASH) + SLASH,568 cache,569 checkUnignored,570 slices571 )572 573 // If the path contains a parent directory, check the parent first574 return cache[path] = parent.ignored575 // > It is not possible to re-include a file if a parent directory of576 // > that file is excluded.577 ? parent578 : this._testOne(path, checkUnignored)579 }580 581 ignores (path) {582 return this._test(path, this._ignoreCache, false).ignored583 }584 585 createFilter () {586 return path => !this.ignores(path)587 }588 589 filter (paths) {590 return makeArray(paths).filter(this.createFilter())591 }592 593 // @returns {TestResult}594 test (path) {595 return this._test(path, this._testCache, true)596 }597}598 599const factory = options => new Ignore(options)600 601const isPathValid = path =>602 checkPath(path && checkPath.convert(path), path, RETURN_FALSE)603 604factory.isPathValid = isPathValid605 606// Fixes typescript607factory.default = factory608 609module.exports = factory610 611// Windows612// --------------------------------------------------------------613/* istanbul ignore if */614if (615 // Detect `process` so that it can run in browsers.616 typeof process !== 'undefined'617 && (618 process.env && process.env.IGNORE_TEST_WIN32619 || process.platform === 'win32'620 )621) {622 /* eslint no-control-regex: "off" */623 const makePosix = str => /^\\\\\?\\/.test(str)624 || /["<>|\u0000-\u001F]+/u.test(str)625 ? str626 : str.replace(/\\/g, '/')627 628 checkPath.convert = makePosix629 630 // 'C:\\foo' <- 'C:\\foo' has been converted to 'C:/'631 // 'd:\\foo'632 const REGIX_IS_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i633 checkPath.isNotRelative = path =>634 REGIX_IS_WINDOWS_PATH_ABSOLUTE.test(path)635 || isNotRelative(path)636}637 