scan.js 9.3 KB

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