Нет описания

resolve-flow-collection.js 8.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. 'use strict';
  2. var Node = require('../nodes/Node.js');
  3. var Pair = require('../nodes/Pair.js');
  4. var YAMLMap = require('../nodes/YAMLMap.js');
  5. var YAMLSeq = require('../nodes/YAMLSeq.js');
  6. var resolveEnd = require('./resolve-end.js');
  7. var resolveProps = require('./resolve-props.js');
  8. var utilContainsNewline = require('./util-contains-newline.js');
  9. var utilMapIncludes = require('./util-map-includes.js');
  10. const blockMsg = 'Block collections are not allowed within flow collections';
  11. const isBlock = (token) => token && (token.type === 'block-map' || token.type === 'block-seq');
  12. function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onError) {
  13. const isMap = fc.start.source === '{';
  14. const fcName = isMap ? 'flow map' : 'flow sequence';
  15. const coll = isMap
  16. ? new YAMLMap.YAMLMap(ctx.schema)
  17. : new YAMLSeq.YAMLSeq(ctx.schema);
  18. coll.flow = true;
  19. const atRoot = ctx.atRoot;
  20. if (atRoot)
  21. ctx.atRoot = false;
  22. let offset = fc.offset + fc.start.source.length;
  23. for (let i = 0; i < fc.items.length; ++i) {
  24. const collItem = fc.items[i];
  25. const { start, key, sep, value } = collItem;
  26. const props = resolveProps.resolveProps(start, {
  27. flow: fcName,
  28. indicator: 'explicit-key-ind',
  29. next: key ?? sep?.[0],
  30. offset,
  31. onError,
  32. startOnNewline: false
  33. });
  34. if (!props.found) {
  35. if (!props.anchor && !props.tag && !sep && !value) {
  36. if (i === 0 && props.comma)
  37. onError(props.comma, 'UNEXPECTED_TOKEN', `Unexpected , in ${fcName}`);
  38. else if (i < fc.items.length - 1)
  39. onError(props.start, 'UNEXPECTED_TOKEN', `Unexpected empty item in ${fcName}`);
  40. if (props.comment) {
  41. if (coll.comment)
  42. coll.comment += '\n' + props.comment;
  43. else
  44. coll.comment = props.comment;
  45. }
  46. offset = props.end;
  47. continue;
  48. }
  49. if (!isMap && ctx.options.strict && utilContainsNewline.containsNewline(key))
  50. onError(key, // checked by containsNewline()
  51. 'MULTILINE_IMPLICIT_KEY', 'Implicit keys of flow sequence pairs need to be on a single line');
  52. }
  53. if (i === 0) {
  54. if (props.comma)
  55. onError(props.comma, 'UNEXPECTED_TOKEN', `Unexpected , in ${fcName}`);
  56. }
  57. else {
  58. if (!props.comma)
  59. onError(props.start, 'MISSING_CHAR', `Missing , between ${fcName} items`);
  60. if (props.comment) {
  61. let prevItemComment = '';
  62. loop: for (const st of start) {
  63. switch (st.type) {
  64. case 'comma':
  65. case 'space':
  66. break;
  67. case 'comment':
  68. prevItemComment = st.source.substring(1);
  69. break loop;
  70. default:
  71. break loop;
  72. }
  73. }
  74. if (prevItemComment) {
  75. let prev = coll.items[coll.items.length - 1];
  76. if (Node.isPair(prev))
  77. prev = prev.value ?? prev.key;
  78. if (prev.comment)
  79. prev.comment += '\n' + prevItemComment;
  80. else
  81. prev.comment = prevItemComment;
  82. props.comment = props.comment.substring(prevItemComment.length + 1);
  83. }
  84. }
  85. }
  86. if (!isMap && !sep && !props.found) {
  87. // item is a value in a seq
  88. // → key & sep are empty, start does not include ? or :
  89. const valueNode = value
  90. ? composeNode(ctx, value, props, onError)
  91. : composeEmptyNode(ctx, props.end, sep, null, props, onError);
  92. coll.items.push(valueNode);
  93. offset = valueNode.range[2];
  94. if (isBlock(value))
  95. onError(valueNode.range, 'BLOCK_IN_FLOW', blockMsg);
  96. }
  97. else {
  98. // item is a key+value pair
  99. // key value
  100. const keyStart = props.end;
  101. const keyNode = key
  102. ? composeNode(ctx, key, props, onError)
  103. : composeEmptyNode(ctx, keyStart, start, null, props, onError);
  104. if (isBlock(key))
  105. onError(keyNode.range, 'BLOCK_IN_FLOW', blockMsg);
  106. // value properties
  107. const valueProps = resolveProps.resolveProps(sep ?? [], {
  108. flow: fcName,
  109. indicator: 'map-value-ind',
  110. next: value,
  111. offset: keyNode.range[2],
  112. onError,
  113. startOnNewline: false
  114. });
  115. if (valueProps.found) {
  116. if (!isMap && !props.found && ctx.options.strict) {
  117. if (sep)
  118. for (const st of sep) {
  119. if (st === valueProps.found)
  120. break;
  121. if (st.type === 'newline') {
  122. onError(st, 'MULTILINE_IMPLICIT_KEY', 'Implicit keys of flow sequence pairs need to be on a single line');
  123. break;
  124. }
  125. }
  126. if (props.start < valueProps.found.offset - 1024)
  127. onError(valueProps.found, 'KEY_OVER_1024_CHARS', 'The : indicator must be at most 1024 chars after the start of an implicit flow sequence key');
  128. }
  129. }
  130. else if (value) {
  131. if ('source' in value && value.source && value.source[0] === ':')
  132. onError(value, 'MISSING_CHAR', `Missing space after : in ${fcName}`);
  133. else
  134. onError(valueProps.start, 'MISSING_CHAR', `Missing , or : between ${fcName} items`);
  135. }
  136. // value value
  137. const valueNode = value
  138. ? composeNode(ctx, value, valueProps, onError)
  139. : valueProps.found
  140. ? composeEmptyNode(ctx, valueProps.end, sep, null, valueProps, onError)
  141. : null;
  142. if (valueNode) {
  143. if (isBlock(value))
  144. onError(valueNode.range, 'BLOCK_IN_FLOW', blockMsg);
  145. }
  146. else if (valueProps.comment) {
  147. if (keyNode.comment)
  148. keyNode.comment += '\n' + valueProps.comment;
  149. else
  150. keyNode.comment = valueProps.comment;
  151. }
  152. const pair = new Pair.Pair(keyNode, valueNode);
  153. if (ctx.options.keepSourceTokens)
  154. pair.srcToken = collItem;
  155. if (isMap) {
  156. const map = coll;
  157. if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode))
  158. onError(keyStart, 'DUPLICATE_KEY', 'Map keys must be unique');
  159. map.items.push(pair);
  160. }
  161. else {
  162. const map = new YAMLMap.YAMLMap(ctx.schema);
  163. map.flow = true;
  164. map.items.push(pair);
  165. coll.items.push(map);
  166. }
  167. offset = valueNode ? valueNode.range[2] : valueProps.end;
  168. }
  169. }
  170. const expectedEnd = isMap ? '}' : ']';
  171. const [ce, ...ee] = fc.end;
  172. let cePos = offset;
  173. if (ce && ce.source === expectedEnd)
  174. cePos = ce.offset + ce.source.length;
  175. else {
  176. const name = fcName[0].toUpperCase() + fcName.substring(1);
  177. const msg = atRoot
  178. ? `${name} must end with a ${expectedEnd}`
  179. : `${name} in block collection must be sufficiently indented and end with a ${expectedEnd}`;
  180. onError(offset, atRoot ? 'MISSING_CHAR' : 'BAD_INDENT', msg);
  181. if (ce && ce.source.length !== 1)
  182. ee.unshift(ce);
  183. }
  184. if (ee.length > 0) {
  185. const end = resolveEnd.resolveEnd(ee, cePos, ctx.options.strict, onError);
  186. if (end.comment) {
  187. if (coll.comment)
  188. coll.comment += '\n' + end.comment;
  189. else
  190. coll.comment = end.comment;
  191. }
  192. coll.range = [fc.offset, cePos, end.offset];
  193. }
  194. else {
  195. coll.range = [fc.offset, cePos, cePos];
  196. }
  197. return coll;
  198. }
  199. exports.resolveFlowCollection = resolveFlowCollection;