scan.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. 'use strict';
  2. const utils = require('./utils');
  3. const {
  4. CHAR_ASTERISK, /* * */
  5. CHAR_AT, /* @ */
  6. CHAR_BACKWARD_SLASH, /* \ */
  7. CHAR_COMMA, /* , */
  8. CHAR_DOT, /* . */
  9. CHAR_EXCLAMATION_MARK, /* ! */
  10. CHAR_FORWARD_SLASH, /* / */
  11. CHAR_LEFT_CURLY_BRACE, /* { */
  12. CHAR_LEFT_PARENTHESES, /* ( */
  13. CHAR_LEFT_SQUARE_BRACKET, /* [ */
  14. CHAR_PLUS, /* + */
  15. CHAR_QUESTION_MARK, /* ? */
  16. CHAR_RIGHT_CURLY_BRACE, /* } */
  17. CHAR_RIGHT_PARENTHESES, /* ) */
  18. CHAR_RIGHT_SQUARE_BRACKET /* ] */
  19. } = require('./constants');
  20. const isPathSeparator = code => {
  21. return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;
  22. };
  23. const depth = token => {
  24. if (token.isPrefix !== true) {
  25. token.depth = token.isGlobstar ? Infinity : 1;
  26. }
  27. };
  28. /**
  29. * Quickly scans a glob pattern and returns an object with a handful of
  30. * useful properties, like `isGlob`, `path` (the leading non-glob, if it exists),
  31. * `glob` (the actual pattern), and `negated` (true if the path starts with `!`).
  32. *
  33. * ```js
  34. * const pm = require('picomatch');
  35. * console.log(pm.scan('foo/bar/*.js'));
  36. * { isGlob: true, input: 'foo/bar/*.js', base: 'foo/bar', glob: '*.js' }
  37. * ```
  38. * @param {String} `str`
  39. * @param {Object} `options`
  40. * @return {Object} Returns an object with tokens and regex source string.
  41. * @api public
  42. */
  43. const scan = (input, options) => {
  44. const opts = options || {};
  45. const length = input.length - 1;
  46. const scanToEnd = opts.parts === true || opts.scanToEnd === true;
  47. const slashes = [];
  48. const tokens = [];
  49. const parts = [];
  50. let str = input;
  51. let index = -1;
  52. let start = 0;
  53. let lastIndex = 0;
  54. let isBrace = false;
  55. let isBracket = false;
  56. let isGlob = false;
  57. let isExtglob = false;
  58. let isGlobstar = false;
  59. let braceEscaped = false;
  60. let backslashes = false;
  61. let negated = false;
  62. let finished = false;
  63. let braces = 0;
  64. let prev;
  65. let code;
  66. let token = { value: '', depth: 0, isGlob: false };
  67. const eos = () => index >= length;
  68. const peek = () => str.charCodeAt(index + 1);
  69. const advance = () => {
  70. prev = code;
  71. return str.charCodeAt(++index);
  72. };
  73. while (index < length) {
  74. code = advance();
  75. let next;
  76. if (code === CHAR_BACKWARD_SLASH) {
  77. backslashes = token.backslashes = true;
  78. code = advance();
  79. if (code === CHAR_LEFT_CURLY_BRACE) {
  80. braceEscaped = true;
  81. }
  82. continue;
  83. }
  84. if (braceEscaped === true || code === CHAR_LEFT_CURLY_BRACE) {
  85. braces++;
  86. while (eos() !== true && (code = advance())) {
  87. if (code === CHAR_BACKWARD_SLASH) {
  88. backslashes = token.backslashes = true;
  89. advance();
  90. continue;
  91. }
  92. if (code === CHAR_LEFT_CURLY_BRACE) {
  93. braces++;
  94. continue;
  95. }
  96. if (braceEscaped !== true && code === CHAR_DOT && (code = advance()) === CHAR_DOT) {
  97. isBrace = token.isBrace = true;
  98. isGlob = token.isGlob = true;
  99. finished = true;
  100. if (scanToEnd === true) {
  101. continue;
  102. }
  103. break;
  104. }
  105. if (braceEscaped !== true && code === CHAR_COMMA) {
  106. isBrace = token.isBrace = true;
  107. isGlob = token.isGlob = true;
  108. finished = true;
  109. if (scanToEnd === true) {
  110. continue;
  111. }
  112. break;
  113. }
  114. if (code === CHAR_RIGHT_CURLY_BRACE) {
  115. braces--;
  116. if (braces === 0) {
  117. braceEscaped = false;
  118. isBrace = token.isBrace = true;
  119. finished = true;
  120. break;
  121. }
  122. }
  123. }
  124. if (scanToEnd === true) {
  125. continue;
  126. }
  127. break;
  128. }
  129. if (code === CHAR_FORWARD_SLASH) {
  130. slashes.push(index);
  131. tokens.push(token);
  132. token = { value: '', depth: 0, isGlob: false };
  133. if (finished === true) continue;
  134. if (prev === CHAR_DOT && index === (start + 1)) {
  135. start += 2;
  136. continue;
  137. }
  138. lastIndex = index + 1;
  139. continue;
  140. }
  141. if (opts.noext !== true) {
  142. const isExtglobChar = code === CHAR_PLUS
  143. || code === CHAR_AT
  144. || code === CHAR_ASTERISK
  145. || code === CHAR_QUESTION_MARK
  146. || code === CHAR_EXCLAMATION_MARK;
  147. if (isExtglobChar === true && peek() === CHAR_LEFT_PARENTHESES) {
  148. isGlob = token.isGlob = true;
  149. isExtglob = token.isExtglob = true;
  150. finished = true;
  151. if (scanToEnd === true) {
  152. while (eos() !== true && (code = advance())) {
  153. if (code === CHAR_BACKWARD_SLASH) {
  154. backslashes = token.backslashes = true;
  155. code = advance();
  156. continue;
  157. }
  158. if (code === CHAR_RIGHT_PARENTHESES) {
  159. isGlob = token.isGlob = true;
  160. finished = true;
  161. break;
  162. }
  163. }
  164. continue;
  165. }
  166. break;
  167. }
  168. }
  169. if (code === CHAR_ASTERISK) {
  170. if (prev === CHAR_ASTERISK) isGlobstar = token.isGlobstar = true;
  171. isGlob = token.isGlob = true;
  172. finished = true;
  173. if (scanToEnd === true) {
  174. continue;
  175. }
  176. break;
  177. }
  178. if (code === CHAR_QUESTION_MARK) {
  179. isGlob = token.isGlob = true;
  180. finished = true;
  181. if (scanToEnd === true) {
  182. continue;
  183. }
  184. break;
  185. }
  186. if (code === CHAR_LEFT_SQUARE_BRACKET) {
  187. while (eos() !== true && (next = advance())) {
  188. if (next === CHAR_BACKWARD_SLASH) {
  189. backslashes = token.backslashes = true;
  190. advance();
  191. continue;
  192. }
  193. if (next === CHAR_RIGHT_SQUARE_BRACKET) {
  194. isBracket = token.isBracket = true;
  195. isGlob = token.isGlob = true;
  196. finished = true;
  197. break;
  198. }
  199. }
  200. if (scanToEnd === true) {
  201. continue;
  202. }
  203. break;
  204. }
  205. if (opts.nonegate !== true && code === CHAR_EXCLAMATION_MARK && index === start) {
  206. negated = token.negated = true;
  207. start++;
  208. continue;
  209. }
  210. if (opts.noparen !== true && code === CHAR_LEFT_PARENTHESES) {
  211. isGlob = token.isGlob = true;
  212. if (scanToEnd === true) {
  213. while (eos() !== true && (code = advance())) {
  214. if (code === CHAR_LEFT_PARENTHESES) {
  215. backslashes = token.backslashes = true;
  216. code = advance();
  217. continue;
  218. }
  219. if (code === CHAR_RIGHT_PARENTHESES) {
  220. finished = true;
  221. break;
  222. }
  223. }
  224. continue;
  225. }
  226. break;
  227. }
  228. if (isGlob === true) {
  229. finished = true;
  230. if (scanToEnd === true) {
  231. continue;
  232. }
  233. break;
  234. }
  235. }
  236. if (opts.noext === true) {
  237. isExtglob = false;
  238. isGlob = false;
  239. }
  240. let base = str;
  241. let prefix = '';
  242. let glob = '';
  243. if (start > 0) {
  244. prefix = str.slice(0, start);
  245. str = str.slice(start);
  246. lastIndex -= start;
  247. }
  248. if (base && isGlob === true && lastIndex > 0) {
  249. base = str.slice(0, lastIndex);
  250. glob = str.slice(lastIndex);
  251. } else if (isGlob === true) {
  252. base = '';
  253. glob = str;
  254. } else {
  255. base = str;
  256. }
  257. if (base && base !== '' && base !== '/' && base !== str) {
  258. if (isPathSeparator(base.charCodeAt(base.length - 1))) {
  259. base = base.slice(0, -1);
  260. }
  261. }
  262. if (opts.unescape === true) {
  263. if (glob) glob = utils.removeBackslashes(glob);
  264. if (base && backslashes === true) {
  265. base = utils.removeBackslashes(base);
  266. }
  267. }
  268. const state = {
  269. prefix,
  270. input,
  271. start,
  272. base,
  273. glob,
  274. isBrace,
  275. isBracket,
  276. isGlob,
  277. isExtglob,
  278. isGlobstar,
  279. negated
  280. };
  281. if (opts.tokens === true) {
  282. state.maxDepth = 0;
  283. if (!isPathSeparator(code)) {
  284. tokens.push(token);
  285. }
  286. state.tokens = tokens;
  287. }
  288. if (opts.parts === true || opts.tokens === true) {
  289. let prevIndex;
  290. for (let idx = 0; idx < slashes.length; idx++) {
  291. const n = prevIndex ? prevIndex + 1 : start;
  292. const i = slashes[idx];
  293. const value = input.slice(n, i);
  294. if (opts.tokens) {
  295. if (idx === 0 && start !== 0) {
  296. tokens[idx].isPrefix = true;
  297. tokens[idx].value = prefix;
  298. } else {
  299. tokens[idx].value = value;
  300. }
  301. depth(tokens[idx]);
  302. state.maxDepth += tokens[idx].depth;
  303. }
  304. if (idx !== 0 || value !== '') {
  305. parts.push(value);
  306. }
  307. prevIndex = i;
  308. }
  309. if (prevIndex && prevIndex + 1 < input.length) {
  310. const value = input.slice(prevIndex + 1);
  311. parts.push(value);
  312. if (opts.tokens) {
  313. tokens[tokens.length - 1].value = value;
  314. depth(tokens[tokens.length - 1]);
  315. state.maxDepth += tokens[tokens.length - 1].depth;
  316. }
  317. }
  318. state.slashes = slashes;
  319. state.parts = parts;
  320. }
  321. return state;
  322. };
  323. module.exports = scan;