reference.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. 'use strict';
  2. var normalizeReference = require('../common/utils').normalizeReference;
  3. var isSpace = require('../common/utils').isSpace;
  4. module.exports = function reference(state, startLine, _endLine, silent) {
  5. var ch,
  6. destEndPos,
  7. destEndLineNo,
  8. endLine,
  9. href,
  10. i,
  11. l,
  12. label,
  13. labelEnd,
  14. oldParentType,
  15. res,
  16. start,
  17. str,
  18. terminate,
  19. terminatorRules,
  20. title,
  21. lines = 0,
  22. pos = state.bMarks[startLine] + state.tShift[startLine],
  23. max = state.eMarks[startLine],
  24. nextLine = startLine + 1;
  25. // if it's indented more than 3 spaces, it should be a code block
  26. if (state.sCount[startLine] - state.blkIndent >= 4) { return false; }
  27. if (state.src.charCodeAt(pos) !== 0x5B/* [ */) { return false; }
  28. // Simple check to quickly interrupt scan on [link](url) at the start of line.
  29. // Can be useful on practice: https://github.com/markdown-it/markdown-it/issues/54
  30. while (++pos < max) {
  31. if (state.src.charCodeAt(pos) === 0x5D /* ] */ &&
  32. state.src.charCodeAt(pos - 1) !== 0x5C/* \ */) {
  33. if (pos + 1 === max) { return false; }
  34. if (state.src.charCodeAt(pos + 1) !== 0x3A/* : */) { return false; }
  35. break;
  36. }
  37. }
  38. endLine = state.lineMax;
  39. // jump line-by-line until empty one or EOF
  40. terminatorRules = state.md.block.ruler.getRules('reference');
  41. oldParentType = state.parentType;
  42. state.parentType = 'reference';
  43. for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
  44. // this would be a code block normally, but after paragraph
  45. // it's considered a lazy continuation regardless of what's there
  46. if (state.sCount[nextLine] - state.blkIndent > 3) { continue; }
  47. // quirk for blockquotes, this line should already be checked by that rule
  48. if (state.sCount[nextLine] < 0) { continue; }
  49. // Some tags can terminate paragraph without empty line.
  50. terminate = false;
  51. for (i = 0, l = terminatorRules.length; i < l; i++) {
  52. if (terminatorRules[i](state, nextLine, endLine, true)) {
  53. terminate = true;
  54. break;
  55. }
  56. }
  57. if (terminate) { break; }
  58. }
  59. str = state.getLines(startLine, nextLine, state.blkIndent, false).trim();
  60. max = str.length;
  61. for (pos = 1; pos < max; pos++) {
  62. ch = str.charCodeAt(pos);
  63. if (ch === 0x5B /* [ */) {
  64. return false;
  65. } else if (ch === 0x5D /* ] */) {
  66. labelEnd = pos;
  67. break;
  68. } else if (ch === 0x0A /* \n */) {
  69. lines++;
  70. } else if (ch === 0x5C /* \ */) {
  71. pos++;
  72. if (pos < max && str.charCodeAt(pos) === 0x0A) {
  73. lines++;
  74. }
  75. }
  76. }
  77. if (labelEnd < 0 || str.charCodeAt(labelEnd + 1) !== 0x3A/* : */) { return false; }
  78. // [label]: destination 'title'
  79. // ^^^ skip optional whitespace here
  80. for (pos = labelEnd + 2; pos < max; pos++) {
  81. ch = str.charCodeAt(pos);
  82. if (ch === 0x0A) {
  83. lines++;
  84. } else if (isSpace(ch)) {
  85. /*eslint no-empty:0*/
  86. } else {
  87. break;
  88. }
  89. }
  90. // [label]: destination 'title'
  91. // ^^^^^^^^^^^ parse this
  92. res = state.md.helpers.parseLinkDestination(str, pos, max);
  93. if (!res.ok) { return false; }
  94. href = state.md.normalizeLink(res.str);
  95. if (!state.md.validateLink(href)) { return false; }
  96. pos = res.pos;
  97. lines += res.lines;
  98. // save cursor state, we could require to rollback later
  99. destEndPos = pos;
  100. destEndLineNo = lines;
  101. // [label]: destination 'title'
  102. // ^^^ skipping those spaces
  103. start = pos;
  104. for (; pos < max; pos++) {
  105. ch = str.charCodeAt(pos);
  106. if (ch === 0x0A) {
  107. lines++;
  108. } else if (isSpace(ch)) {
  109. /*eslint no-empty:0*/
  110. } else {
  111. break;
  112. }
  113. }
  114. // [label]: destination 'title'
  115. // ^^^^^^^ parse this
  116. res = state.md.helpers.parseLinkTitle(str, pos, max);
  117. if (pos < max && start !== pos && res.ok) {
  118. title = res.str;
  119. pos = res.pos;
  120. lines += res.lines;
  121. } else {
  122. title = '';
  123. pos = destEndPos;
  124. lines = destEndLineNo;
  125. }
  126. // skip trailing spaces until the rest of the line
  127. while (pos < max) {
  128. ch = str.charCodeAt(pos);
  129. if (!isSpace(ch)) { break; }
  130. pos++;
  131. }
  132. if (pos < max && str.charCodeAt(pos) !== 0x0A) {
  133. if (title) {
  134. // garbage at the end of the line after title,
  135. // but it could still be a valid reference if we roll back
  136. title = '';
  137. pos = destEndPos;
  138. lines = destEndLineNo;
  139. while (pos < max) {
  140. ch = str.charCodeAt(pos);
  141. if (!isSpace(ch)) { break; }
  142. pos++;
  143. }
  144. }
  145. }
  146. if (pos < max && str.charCodeAt(pos) !== 0x0A) {
  147. // garbage at the end of the line
  148. return false;
  149. }
  150. label = normalizeReference(str.slice(1, labelEnd));
  151. if (!label) {
  152. // CommonMark 0.20 disallows empty labels
  153. return false;
  154. }
  155. // Reference can not terminate anything. This check is for safety only.
  156. /*istanbul ignore if*/
  157. if (silent) { return true; }
  158. if (typeof state.env.references === 'undefined') {
  159. state.env.references = {};
  160. }
  161. if (typeof state.env.references[label] === 'undefined') {
  162. state.env.references[label] = { title: title, href: href };
  163. }
  164. state.parentType = oldParentType;
  165. state.line = startLine + lines + 1;
  166. return true;
  167. };