resolve-props.js 5.4 KB

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