pattern.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. // this is just a very light wrapper around 2 arrays with an offset index
  2. import { GLOBSTAR } from 'minimatch';
  3. const isPatternList = (pl) => pl.length >= 1;
  4. const isGlobList = (gl) => gl.length >= 1;
  5. /**
  6. * An immutable-ish view on an array of glob parts and their parsed
  7. * results
  8. */
  9. export class Pattern {
  10. #patternList;
  11. #globList;
  12. #index;
  13. length;
  14. #platform;
  15. #rest;
  16. #globString;
  17. #isDrive;
  18. #isUNC;
  19. #isAbsolute;
  20. #followGlobstar = true;
  21. constructor(patternList, globList, index, platform) {
  22. if (!isPatternList(patternList)) {
  23. throw new TypeError('empty pattern list');
  24. }
  25. if (!isGlobList(globList)) {
  26. throw new TypeError('empty glob list');
  27. }
  28. if (globList.length !== patternList.length) {
  29. throw new TypeError('mismatched pattern list and glob list lengths');
  30. }
  31. this.length = patternList.length;
  32. if (index < 0 || index >= this.length) {
  33. throw new TypeError('index out of range');
  34. }
  35. this.#patternList = patternList;
  36. this.#globList = globList;
  37. this.#index = index;
  38. this.#platform = platform;
  39. // normalize root entries of absolute patterns on initial creation.
  40. if (this.#index === 0) {
  41. // c: => ['c:/']
  42. // C:/ => ['C:/']
  43. // C:/x => ['C:/', 'x']
  44. // //host/share => ['//host/share/']
  45. // //host/share/ => ['//host/share/']
  46. // //host/share/x => ['//host/share/', 'x']
  47. // /etc => ['/', 'etc']
  48. // / => ['/']
  49. if (this.isUNC()) {
  50. // '' / '' / 'host' / 'share'
  51. const [p0, p1, p2, p3, ...prest] = this.#patternList;
  52. const [g0, g1, g2, g3, ...grest] = this.#globList;
  53. if (prest[0] === '') {
  54. // ends in /
  55. prest.shift();
  56. grest.shift();
  57. }
  58. const p = [p0, p1, p2, p3, ''].join('/');
  59. const g = [g0, g1, g2, g3, ''].join('/');
  60. this.#patternList = [p, ...prest];
  61. this.#globList = [g, ...grest];
  62. this.length = this.#patternList.length;
  63. }
  64. else if (this.isDrive() || this.isAbsolute()) {
  65. const [p1, ...prest] = this.#patternList;
  66. const [g1, ...grest] = this.#globList;
  67. if (prest[0] === '') {
  68. // ends in /
  69. prest.shift();
  70. grest.shift();
  71. }
  72. const p = p1 + '/';
  73. const g = g1 + '/';
  74. this.#patternList = [p, ...prest];
  75. this.#globList = [g, ...grest];
  76. this.length = this.#patternList.length;
  77. }
  78. }
  79. }
  80. /**
  81. * The first entry in the parsed list of patterns
  82. */
  83. pattern() {
  84. return this.#patternList[this.#index];
  85. }
  86. /**
  87. * true of if pattern() returns a string
  88. */
  89. isString() {
  90. return typeof this.#patternList[this.#index] === 'string';
  91. }
  92. /**
  93. * true of if pattern() returns GLOBSTAR
  94. */
  95. isGlobstar() {
  96. return this.#patternList[this.#index] === GLOBSTAR;
  97. }
  98. /**
  99. * true if pattern() returns a regexp
  100. */
  101. isRegExp() {
  102. return this.#patternList[this.#index] instanceof RegExp;
  103. }
  104. /**
  105. * The /-joined set of glob parts that make up this pattern
  106. */
  107. globString() {
  108. return (this.#globString =
  109. this.#globString ||
  110. (this.#index === 0
  111. ? this.isAbsolute()
  112. ? this.#globList[0] + this.#globList.slice(1).join('/')
  113. : this.#globList.join('/')
  114. : this.#globList.slice(this.#index).join('/')));
  115. }
  116. /**
  117. * true if there are more pattern parts after this one
  118. */
  119. hasMore() {
  120. return this.length > this.#index + 1;
  121. }
  122. /**
  123. * The rest of the pattern after this part, or null if this is the end
  124. */
  125. rest() {
  126. if (this.#rest !== undefined)
  127. return this.#rest;
  128. if (!this.hasMore())
  129. return (this.#rest = null);
  130. this.#rest = new Pattern(this.#patternList, this.#globList, this.#index + 1, this.#platform);
  131. this.#rest.#isAbsolute = this.#isAbsolute;
  132. this.#rest.#isUNC = this.#isUNC;
  133. this.#rest.#isDrive = this.#isDrive;
  134. return this.#rest;
  135. }
  136. /**
  137. * true if the pattern represents a //unc/path/ on windows
  138. */
  139. isUNC() {
  140. const pl = this.#patternList;
  141. return this.#isUNC !== undefined
  142. ? this.#isUNC
  143. : (this.#isUNC =
  144. this.#platform === 'win32' &&
  145. this.#index === 0 &&
  146. pl[0] === '' &&
  147. pl[1] === '' &&
  148. typeof pl[2] === 'string' &&
  149. !!pl[2] &&
  150. typeof pl[3] === 'string' &&
  151. !!pl[3]);
  152. }
  153. // pattern like C:/...
  154. // split = ['C:', ...]
  155. // XXX: would be nice to handle patterns like `c:*` to test the cwd
  156. // in c: for *, but I don't know of a way to even figure out what that
  157. // cwd is without actually chdir'ing into it?
  158. /**
  159. * True if the pattern starts with a drive letter on Windows
  160. */
  161. isDrive() {
  162. const pl = this.#patternList;
  163. return this.#isDrive !== undefined
  164. ? this.#isDrive
  165. : (this.#isDrive =
  166. this.#platform === 'win32' &&
  167. this.#index === 0 &&
  168. this.length > 1 &&
  169. typeof pl[0] === 'string' &&
  170. /^[a-z]:$/i.test(pl[0]));
  171. }
  172. // pattern = '/' or '/...' or '/x/...'
  173. // split = ['', ''] or ['', ...] or ['', 'x', ...]
  174. // Drive and UNC both considered absolute on windows
  175. /**
  176. * True if the pattern is rooted on an absolute path
  177. */
  178. isAbsolute() {
  179. const pl = this.#patternList;
  180. return this.#isAbsolute !== undefined
  181. ? this.#isAbsolute
  182. : (this.#isAbsolute =
  183. (pl[0] === '' && pl.length > 1) ||
  184. this.isDrive() ||
  185. this.isUNC());
  186. }
  187. /**
  188. * consume the root of the pattern, and return it
  189. */
  190. root() {
  191. const p = this.#patternList[0];
  192. return typeof p === 'string' && this.isAbsolute() && this.#index === 0
  193. ? p
  194. : '';
  195. }
  196. /**
  197. * True if the pattern has any non-string components
  198. */
  199. hasMagic() {
  200. for (let i = 0; i < this.length; i++) {
  201. if (typeof this.#patternList[i] !== 'string') {
  202. return true;
  203. }
  204. }
  205. return false;
  206. }
  207. /**
  208. * Check to see if the current globstar pattern is allowed to follow
  209. * a symbolic link.
  210. */
  211. checkFollowGlobstar() {
  212. return !(this.#index === 0 ||
  213. !this.isGlobstar() ||
  214. !this.#followGlobstar);
  215. }
  216. /**
  217. * Mark that the current globstar pattern is following a symbolic link
  218. */
  219. markFollowGlobstar() {
  220. if (this.#index === 0 || !this.isGlobstar() || !this.#followGlobstar)
  221. return false;
  222. this.#followGlobstar = false;
  223. return true;
  224. }
  225. }
  226. //# sourceMappingURL=pattern.js.map