compilers.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. 'use strict';
  2. /**
  3. * Nanomatch compilers
  4. */
  5. module.exports = function(nanomatch, options) {
  6. var star = '[^/]*?';
  7. var ast = nanomatch.ast = nanomatch.parser.ast;
  8. ast.state = nanomatch.parser.state;
  9. nanomatch.compiler.state = ast.state;
  10. nanomatch.compiler
  11. /**
  12. * Negation / escaping
  13. */
  14. .set('not', function(node) {
  15. var prev = this.prev();
  16. if (this.options.nonegate === true || prev.type !== 'bos') {
  17. return this.emit('\\' + node.val, node);
  18. }
  19. return this.emit(node.val, node);
  20. })
  21. .set('escape', function(node) {
  22. if (this.options.unescape && /^[\w_.-]/.test(node.val)) {
  23. return this.emit(node.val, node);
  24. }
  25. return this.emit('\\' + node.val, node);
  26. })
  27. .set('quoted', function(node) {
  28. return this.emit(node.val, node);
  29. })
  30. /**
  31. * Regex
  32. */
  33. .set('dollar', function(node) {
  34. if (node.parent.type === 'bracket') {
  35. return this.emit(node.val, node);
  36. }
  37. return this.emit('\\' + node.val, node);
  38. })
  39. /**
  40. * Dot: "."
  41. */
  42. .set('dot', function(node) {
  43. if (node.dotfiles === true) this.dotfiles = true;
  44. return this.emit('\\' + node.val, node);
  45. })
  46. /**
  47. * Slashes: "/" and "\"
  48. */
  49. .set('backslash', function(node) {
  50. return this.emit(node.val, node);
  51. })
  52. .set('slash', function(node, nodes, i) {
  53. var val = '\\' + node.val;
  54. var parent = node.parent;
  55. var prev = this.prev();
  56. // set "node.hasSlash" to true on all ancestor parens nodes
  57. while (parent.type === 'paren' && !parent.hasSlash) {
  58. parent.hasSlash = true;
  59. parent = parent.parent;
  60. }
  61. if (prev.addQmark) {
  62. val += '?';
  63. }
  64. // word boundary
  65. if (node.rest.slice(0, 2) === '\\b') {
  66. return this.emit(val, node);
  67. }
  68. // globstars
  69. if (node.parsed === '**' || node.parsed === './**') {
  70. this.output = '(?:' + this.output;
  71. return this.emit(val + ')?', node);
  72. }
  73. // negation
  74. if (node.parsed === '!**' && this.options.nonegate !== true) {
  75. return this.emit(val + '?\\b', node);
  76. }
  77. return this.emit(val, node);
  78. })
  79. /**
  80. * Square brackets
  81. */
  82. .set('bracket', function(node) {
  83. var close = node.close;
  84. var open = !node.escaped ? '[' : '\\[';
  85. var negated = node.negated;
  86. var inner = node.inner;
  87. var val = node.val;
  88. if (node.escaped === true) {
  89. inner = inner.replace(/\\?(\W)/g, '\\$1');
  90. negated = '';
  91. }
  92. if (inner === ']-') {
  93. inner = '\\]\\-';
  94. }
  95. if (negated && inner.indexOf('.') === -1) {
  96. inner += '.';
  97. }
  98. if (negated && inner.indexOf('/') === -1) {
  99. inner += '/';
  100. }
  101. val = open + negated + inner + close;
  102. return this.emit(val, node);
  103. })
  104. /**
  105. * Square: "[.]" (only matches a single character in brackets)
  106. */
  107. .set('square', function(node) {
  108. var val = !/^\w/.test(node.val) ? '\\' + node.val : node.val;
  109. return this.emit(val, node);
  110. })
  111. /**
  112. * Question mark: "?"
  113. */
  114. .set('qmark', function(node) {
  115. var prev = this.prev();
  116. var val = '[^.\\\\/]';
  117. if (this.options.dot || (prev.type !== 'bos' && prev.type !== 'slash')) {
  118. val = '[^\\\\/]';
  119. }
  120. if (node.parsed.slice(-1) === '(') {
  121. var ch = node.rest.charAt(0);
  122. if (ch === '!' || ch === '=' || ch === ':') {
  123. return this.emit(node.val, node);
  124. }
  125. }
  126. if (node.val.length > 1) {
  127. val += '{' + node.val.length + '}';
  128. }
  129. return this.emit(val, node);
  130. })
  131. /**
  132. * Plus
  133. */
  134. .set('plus', function(node) {
  135. var prev = node.parsed.slice(-1);
  136. if (prev === ']' || prev === ')') {
  137. return this.emit(node.val, node);
  138. }
  139. if (!this.output || (/[?*+]/.test(ch) && node.parent.type !== 'bracket')) {
  140. return this.emit('\\+', node);
  141. }
  142. var ch = this.output.slice(-1);
  143. if (/\w/.test(ch) && !node.inside) {
  144. return this.emit('+\\+?', node);
  145. }
  146. return this.emit('+', node);
  147. })
  148. /**
  149. * globstar: '**'
  150. */
  151. .set('globstar', function(node, nodes, i) {
  152. if (!this.output) {
  153. this.state.leadingGlobstar = true;
  154. }
  155. var next = this.next();
  156. var prev = this.prev();
  157. var next2 = this.next(2);
  158. var prev2 = this.prev(2);
  159. var type = prev.type;
  160. var val = node.val;
  161. if (prev.type === 'slash' && next.type === 'slash') {
  162. if (prev2.type === 'text') {
  163. this.output += '?';
  164. if (next2.type !== 'text') {
  165. this.output += '\\b';
  166. }
  167. }
  168. }
  169. var parsed = node.parsed;
  170. if (parsed.charAt(0) === '!') {
  171. parsed = parsed.slice(1);
  172. }
  173. if (parsed && type !== 'slash' && type !== 'bos') {
  174. val = star;
  175. } else {
  176. val = this.options.dot !== true
  177. ? '(?:(?!(?:\\/|^)\\.).)*?'
  178. : '(?:(?!(?:\\/|^)(?:\\.{1,2})($|\\/))(?!\\.{2}).)*?';
  179. }
  180. if ((type === 'slash' || type === 'bos') && this.options.dot !== true) {
  181. val = '(?!\\.)' + val;
  182. }
  183. if (prev.type === 'slash' && next.type === 'slash' && prev2.type !== 'text') {
  184. if (next2.type === 'text' || next2.type === 'star') {
  185. node.addQmark = true;
  186. }
  187. }
  188. if (this.options.capture) {
  189. val = '(' + val + ')';
  190. }
  191. return this.emit(val, node);
  192. })
  193. /**
  194. * Star: "*"
  195. */
  196. .set('star', function(node, nodes, i) {
  197. var prior = nodes[i - 2] || {};
  198. var prev = this.prev();
  199. var next = this.next();
  200. var type = prev.type;
  201. function isStart(n) {
  202. return n.type === 'bos' || n.type === 'slash';
  203. }
  204. if (this.output === '' && this.options.contains !== true) {
  205. this.output = '(?!\\/)';
  206. }
  207. if (type === 'bracket' && this.options.bash === false) {
  208. var str = next && next.type === 'bracket' ? star : '*?';
  209. if (!prev.nodes || prev.nodes[1].type !== 'posix') {
  210. return this.emit(str, node);
  211. }
  212. }
  213. var prefix = !this.dotfiles && type !== 'text' && type !== 'escape'
  214. ? (this.options.dot ? '(?!(?:^|\\/)\\.{1,2}(?:$|\\/))' : '(?!\\.)')
  215. : '';
  216. if (isStart(prev) || (isStart(prior) && type === 'not')) {
  217. if (prefix !== '(?!\\.)') {
  218. prefix += '(?!(\\.{2}|\\.\\/))(?=.)';
  219. } else {
  220. prefix += '(?=.)';
  221. }
  222. } else if (prefix === '(?!\\.)') {
  223. prefix = '';
  224. }
  225. if (prev.type === 'not' && prior.type === 'bos' && this.options.dot === true) {
  226. this.output = '(?!\\.)' + this.output;
  227. }
  228. var output = prefix + star;
  229. if (this.options.capture) {
  230. output = '(' + output + ')';
  231. }
  232. return this.emit(output, node);
  233. })
  234. /**
  235. * Text
  236. */
  237. .set('text', function(node) {
  238. return this.emit(node.val, node);
  239. })
  240. /**
  241. * End-of-string
  242. */
  243. .set('eos', function(node) {
  244. var prev = this.prev();
  245. var val = node.val;
  246. this.output = '(?:(?:\\.(?:\\/|\\\\))(?=.))?' + this.output;
  247. if (this.state.metachar && prev.type !== 'qmark' && prev.type !== 'slash') {
  248. val += (this.options.contains ? '(?:\\/|\\\\)?' : '(?:(?:\\/|\\\\)|$)');
  249. }
  250. return this.emit(val, node);
  251. });
  252. /**
  253. * Allow custom compilers to be passed on options
  254. */
  255. if (options && typeof options.compilers === 'function') {
  256. options.compilers(nanomatch.compiler);
  257. }
  258. };