scan.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  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.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. while (eos() !== true && (code = advance())) {
  158. if (code === CHAR_BACKWARD_SLASH) {
  159. backslashes = token.backslashes = true;
  160. code = advance();
  161. continue;
  162. }
  163. if (code === CHAR_RIGHT_PARENTHESES) {
  164. isGlob = token.isGlob = true;
  165. finished = true;
  166. break;
  167. }
  168. }
  169. continue;
  170. }
  171. break;
  172. }
  173. }
  174. if (code === CHAR_ASTERISK) {
  175. if (prev === CHAR_ASTERISK) isGlobstar = token.isGlobstar = true;
  176. isGlob = token.isGlob = true;
  177. finished = true;
  178. if (scanToEnd === true) {
  179. continue;
  180. }
  181. break;
  182. }
  183. if (code === CHAR_QUESTION_MARK) {
  184. isGlob = token.isGlob = true;
  185. finished = true;
  186. if (scanToEnd === true) {
  187. continue;
  188. }
  189. break;
  190. }
  191. if (code === CHAR_LEFT_SQUARE_BRACKET) {
  192. while (eos() !== true && (next = advance())) {
  193. if (next === CHAR_BACKWARD_SLASH) {
  194. backslashes = token.backslashes = true;
  195. advance();
  196. continue;
  197. }
  198. if (next === CHAR_RIGHT_SQUARE_BRACKET) {
  199. isBracket = token.isBracket = true;
  200. isGlob = token.isGlob = true;
  201. finished = true;
  202. break;
  203. }
  204. }
  205. if (scanToEnd === true) {
  206. continue;
  207. }
  208. break;
  209. }
  210. if (opts.nonegate !== true && code === CHAR_EXCLAMATION_MARK && index === start) {
  211. negated = token.negated = true;
  212. start++;
  213. continue;
  214. }
  215. if (opts.noparen !== true && code === CHAR_LEFT_PARENTHESES) {
  216. isGlob = token.isGlob = true;
  217. if (scanToEnd === true) {
  218. while (eos() !== true && (code = advance())) {
  219. if (code === CHAR_LEFT_PARENTHESES) {
  220. backslashes = token.backslashes = true;
  221. code = advance();
  222. continue;
  223. }
  224. if (code === CHAR_RIGHT_PARENTHESES) {
  225. finished = true;
  226. break;
  227. }
  228. }
  229. continue;
  230. }
  231. break;
  232. }
  233. if (isGlob === true) {
  234. finished = true;
  235. if (scanToEnd === true) {
  236. continue;
  237. }
  238. break;
  239. }
  240. }
  241. if (opts.noext === true) {
  242. isExtglob = false;
  243. isGlob = false;
  244. }
  245. let base = str;
  246. let prefix = '';
  247. let glob = '';
  248. if (start > 0) {
  249. prefix = str.slice(0, start);
  250. str = str.slice(start);
  251. lastIndex -= start;
  252. }
  253. if (base && isGlob === true && lastIndex > 0) {
  254. base = str.slice(0, lastIndex);
  255. glob = str.slice(lastIndex);
  256. } else if (isGlob === true) {
  257. base = '';
  258. glob = str;
  259. } else {
  260. base = str;
  261. }
  262. if (base && base !== '' && base !== '/' && base !== str) {
  263. if (isPathSeparator(base.charCodeAt(base.length - 1))) {
  264. base = base.slice(0, -1);
  265. }
  266. }
  267. if (opts.unescape === true) {
  268. if (glob) glob = utils.removeBackslashes(glob);
  269. if (base && backslashes === true) {
  270. base = utils.removeBackslashes(base);
  271. }
  272. }
  273. const state = {
  274. prefix,
  275. input,
  276. start,
  277. base,
  278. glob,
  279. isBrace,
  280. isBracket,
  281. isGlob,
  282. isExtglob,
  283. isGlobstar,
  284. negated,
  285. negatedExtglob
  286. };
  287. if (opts.tokens === true) {
  288. state.maxDepth = 0;
  289. if (!isPathSeparator(code)) {
  290. tokens.push(token);
  291. }
  292. state.tokens = tokens;
  293. }
  294. if (opts.parts === true || opts.tokens === true) {
  295. let prevIndex;
  296. for (let idx = 0; idx < slashes.length; idx++) {
  297. const n = prevIndex ? prevIndex + 1 : start;
  298. const i = slashes[idx];
  299. const value = input.slice(n, i);
  300. if (opts.tokens) {
  301. if (idx === 0 && start !== 0) {
  302. tokens[idx].isPrefix = true;
  303. tokens[idx].value = prefix;
  304. } else {
  305. tokens[idx].value = value;
  306. }
  307. depth(tokens[idx]);
  308. state.maxDepth += tokens[idx].depth;
  309. }
  310. if (idx !== 0 || value !== '') {
  311. parts.push(value);
  312. }
  313. prevIndex = i;
  314. }
  315. if (prevIndex && prevIndex + 1 < input.length) {
  316. const value = input.slice(prevIndex + 1);
  317. parts.push(value);
  318. if (opts.tokens) {
  319. tokens[tokens.length - 1].value = value;
  320. depth(tokens[tokens.length - 1]);
  321. state.maxDepth += tokens[tokens.length - 1].depth;
  322. }
  323. }
  324. state.slashes = slashes;
  325. state.parts = parts;
  326. }
  327. return state;
  328. };
  329. module.exports = scan;