You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

339 lines
8.3 KiB

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