resolve-props.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. 'use strict';
  2. function resolveProps(tokens, { flow, indicator, next, offset, onError, parentIndent, startOnNewline }) {
  3. let spaceBefore = false;
  4. let atNewline = startOnNewline;
  5. let hasSpace = startOnNewline;
  6. let comment = '';
  7. let commentSep = '';
  8. let hasNewline = false;
  9. let reqSpace = false;
  10. let tab = null;
  11. let anchor = null;
  12. let tag = null;
  13. let newlineAfterProp = null;
  14. let comma = null;
  15. let found = null;
  16. let start = null;
  17. for (const token of tokens) {
  18. if (reqSpace) {
  19. if (token.type !== 'space' &&
  20. token.type !== 'newline' &&
  21. token.type !== 'comma')
  22. onError(token.offset, 'MISSING_CHAR', 'Tags and anchors must be separated from the next token by white space');
  23. reqSpace = false;
  24. }
  25. if (tab) {
  26. if (atNewline && token.type !== 'comment' && token.type !== 'newline') {
  27. onError(tab, 'TAB_AS_INDENT', 'Tabs are not allowed as indentation');
  28. }
  29. tab = null;
  30. }
  31. switch (token.type) {
  32. case 'space':
  33. // At the doc level, tabs at line start may be parsed
  34. // as leading white space rather than indentation.
  35. // In a flow collection, only the parser handles indent.
  36. if (!flow &&
  37. (indicator !== 'doc-start' || next?.type !== 'flow-collection') &&
  38. token.source.includes('\t')) {
  39. tab = token;
  40. }
  41. hasSpace = true;
  42. break;
  43. case 'comment': {
  44. if (!hasSpace)
  45. onError(token, 'MISSING_CHAR', 'Comments must be separated from other tokens by white space characters');
  46. const cb = token.source.substring(1) || ' ';
  47. if (!comment)
  48. comment = cb;
  49. else
  50. comment += commentSep + cb;
  51. commentSep = '';
  52. atNewline = false;
  53. break;
  54. }
  55. case 'newline':
  56. if (atNewline) {
  57. if (comment)
  58. comment += token.source;
  59. else
  60. spaceBefore = true;
  61. }
  62. else
  63. commentSep += token.source;
  64. atNewline = true;
  65. hasNewline = true;
  66. if (anchor || tag)
  67. newlineAfterProp = token;
  68. hasSpace = true;
  69. break;
  70. case 'anchor':
  71. if (anchor)
  72. onError(token, 'MULTIPLE_ANCHORS', 'A node can have at most one anchor');
  73. if (token.source.endsWith(':'))
  74. onError(token.offset + token.source.length - 1, 'BAD_ALIAS', 'Anchor ending in : is ambiguous', true);
  75. anchor = token;
  76. if (start === null)
  77. start = token.offset;
  78. atNewline = false;
  79. hasSpace = false;
  80. reqSpace = true;
  81. break;
  82. case 'tag': {
  83. if (tag)
  84. onError(token, 'MULTIPLE_TAGS', 'A node can have at most one tag');
  85. tag = token;
  86. if (start === null)
  87. start = token.offset;
  88. atNewline = false;
  89. hasSpace = false;
  90. reqSpace = true;
  91. break;
  92. }
  93. case indicator:
  94. // Could here handle preceding comments differently
  95. if (anchor || tag)
  96. onError(token, 'BAD_PROP_ORDER', `Anchors and tags must be after the ${token.source} indicator`);
  97. if (found)
  98. onError(token, 'UNEXPECTED_TOKEN', `Unexpected ${token.source} in ${flow ?? 'collection'}`);
  99. found = token;
  100. atNewline =
  101. indicator === 'seq-item-ind' || indicator === 'explicit-key-ind';
  102. hasSpace = false;
  103. break;
  104. case 'comma':
  105. if (flow) {
  106. if (comma)
  107. onError(token, 'UNEXPECTED_TOKEN', `Unexpected , in ${flow}`);
  108. comma = token;
  109. atNewline = false;
  110. hasSpace = false;
  111. break;
  112. }
  113. // else fallthrough
  114. default:
  115. onError(token, 'UNEXPECTED_TOKEN', `Unexpected ${token.type} token`);
  116. atNewline = false;
  117. hasSpace = false;
  118. }
  119. }
  120. const last = tokens[tokens.length - 1];
  121. const end = last ? last.offset + last.source.length : offset;
  122. if (reqSpace &&
  123. next &&
  124. next.type !== 'space' &&
  125. next.type !== 'newline' &&
  126. next.type !== 'comma' &&
  127. (next.type !== 'scalar' || next.source !== '')) {
  128. onError(next.offset, 'MISSING_CHAR', 'Tags and anchors must be separated from the next token by white space');
  129. }
  130. if (tab &&
  131. ((atNewline && tab.indent <= parentIndent) ||
  132. next?.type === 'block-map' ||
  133. next?.type === 'block-seq'))
  134. onError(tab, 'TAB_AS_INDENT', 'Tabs are not allowed as indentation');
  135. return {
  136. comma,
  137. found,
  138. spaceBefore,
  139. comment,
  140. hasNewline,
  141. anchor,
  142. tag,
  143. newlineAfterProp,
  144. end,
  145. start: start ?? end
  146. };
  147. }
  148. exports.resolveProps = resolveProps;