cst-scalar.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. 'use strict';
  2. var resolveBlockScalar = require('../compose/resolve-block-scalar.js');
  3. var resolveFlowScalar = require('../compose/resolve-flow-scalar.js');
  4. var errors = require('../errors.js');
  5. var stringifyString = require('../stringify/stringifyString.js');
  6. function resolveAsScalar(token, strict = true, onError) {
  7. if (token) {
  8. const _onError = (pos, code, message) => {
  9. const offset = typeof pos === 'number' ? pos : Array.isArray(pos) ? pos[0] : pos.offset;
  10. if (onError)
  11. onError(offset, code, message);
  12. else
  13. throw new errors.YAMLParseError([offset, offset + 1], code, message);
  14. };
  15. switch (token.type) {
  16. case 'scalar':
  17. case 'single-quoted-scalar':
  18. case 'double-quoted-scalar':
  19. return resolveFlowScalar.resolveFlowScalar(token, strict, _onError);
  20. case 'block-scalar':
  21. return resolveBlockScalar.resolveBlockScalar(token, strict, _onError);
  22. }
  23. }
  24. return null;
  25. }
  26. /**
  27. * Create a new scalar token with `value`
  28. *
  29. * Values that represent an actual string but may be parsed as a different type should use a `type` other than `'PLAIN'`,
  30. * as this function does not support any schema operations and won't check for such conflicts.
  31. *
  32. * @param value The string representation of the value, which will have its content properly indented.
  33. * @param context.end Comments and whitespace after the end of the value, or after the block scalar header. If undefined, a newline will be added.
  34. * @param context.implicitKey Being within an implicit key may affect the resolved type of the token's value.
  35. * @param context.indent The indent level of the token.
  36. * @param context.inFlow Is this scalar within a flow collection? This may affect the resolved type of the token's value.
  37. * @param context.offset The offset position of the token.
  38. * @param context.type The preferred type of the scalar token. If undefined, the previous type of the `token` will be used, defaulting to `'PLAIN'`.
  39. */
  40. function createScalarToken(value, context) {
  41. const { implicitKey = false, indent, inFlow = false, offset = -1, type = 'PLAIN' } = context;
  42. const source = stringifyString.stringifyString({ type, value }, {
  43. implicitKey,
  44. indent: indent > 0 ? ' '.repeat(indent) : '',
  45. inFlow,
  46. options: { blockQuote: true, lineWidth: -1 }
  47. });
  48. const end = context.end ?? [
  49. { type: 'newline', offset: -1, indent, source: '\n' }
  50. ];
  51. switch (source[0]) {
  52. case '|':
  53. case '>': {
  54. const he = source.indexOf('\n');
  55. const head = source.substring(0, he);
  56. const body = source.substring(he + 1) + '\n';
  57. const props = [
  58. { type: 'block-scalar-header', offset, indent, source: head }
  59. ];
  60. if (!addEndtoBlockProps(props, end))
  61. props.push({ type: 'newline', offset: -1, indent, source: '\n' });
  62. return { type: 'block-scalar', offset, indent, props, source: body };
  63. }
  64. case '"':
  65. return { type: 'double-quoted-scalar', offset, indent, source, end };
  66. case "'":
  67. return { type: 'single-quoted-scalar', offset, indent, source, end };
  68. default:
  69. return { type: 'scalar', offset, indent, source, end };
  70. }
  71. }
  72. /**
  73. * Set the value of `token` to the given string `value`, overwriting any previous contents and type that it may have.
  74. *
  75. * Best efforts are made to retain any comments previously associated with the `token`,
  76. * though all contents within a collection's `items` will be overwritten.
  77. *
  78. * Values that represent an actual string but may be parsed as a different type should use a `type` other than `'PLAIN'`,
  79. * as this function does not support any schema operations and won't check for such conflicts.
  80. *
  81. * @param token Any token. If it does not include an `indent` value, the value will be stringified as if it were an implicit key.
  82. * @param value The string representation of the value, which will have its content properly indented.
  83. * @param context.afterKey In most cases, values after a key should have an additional level of indentation.
  84. * @param context.implicitKey Being within an implicit key may affect the resolved type of the token's value.
  85. * @param context.inFlow Being within a flow collection may affect the resolved type of the token's value.
  86. * @param context.type The preferred type of the scalar token. If undefined, the previous type of the `token` will be used, defaulting to `'PLAIN'`.
  87. */
  88. function setScalarValue(token, value, context = {}) {
  89. let { afterKey = false, implicitKey = false, inFlow = false, type } = context;
  90. let indent = 'indent' in token ? token.indent : null;
  91. if (afterKey && typeof indent === 'number')
  92. indent += 2;
  93. if (!type)
  94. switch (token.type) {
  95. case 'single-quoted-scalar':
  96. type = 'QUOTE_SINGLE';
  97. break;
  98. case 'double-quoted-scalar':
  99. type = 'QUOTE_DOUBLE';
  100. break;
  101. case 'block-scalar': {
  102. const header = token.props[0];
  103. if (header.type !== 'block-scalar-header')
  104. throw new Error('Invalid block scalar header');
  105. type = header.source[0] === '>' ? 'BLOCK_FOLDED' : 'BLOCK_LITERAL';
  106. break;
  107. }
  108. default:
  109. type = 'PLAIN';
  110. }
  111. const source = stringifyString.stringifyString({ type, value }, {
  112. implicitKey: implicitKey || indent === null,
  113. indent: indent !== null && indent > 0 ? ' '.repeat(indent) : '',
  114. inFlow,
  115. options: { blockQuote: true, lineWidth: -1 }
  116. });
  117. switch (source[0]) {
  118. case '|':
  119. case '>':
  120. setBlockScalarValue(token, source);
  121. break;
  122. case '"':
  123. setFlowScalarValue(token, source, 'double-quoted-scalar');
  124. break;
  125. case "'":
  126. setFlowScalarValue(token, source, 'single-quoted-scalar');
  127. break;
  128. default:
  129. setFlowScalarValue(token, source, 'scalar');
  130. }
  131. }
  132. function setBlockScalarValue(token, source) {
  133. const he = source.indexOf('\n');
  134. const head = source.substring(0, he);
  135. const body = source.substring(he + 1) + '\n';
  136. if (token.type === 'block-scalar') {
  137. const header = token.props[0];
  138. if (header.type !== 'block-scalar-header')
  139. throw new Error('Invalid block scalar header');
  140. header.source = head;
  141. token.source = body;
  142. }
  143. else {
  144. const { offset } = token;
  145. const indent = 'indent' in token ? token.indent : -1;
  146. const props = [
  147. { type: 'block-scalar-header', offset, indent, source: head }
  148. ];
  149. if (!addEndtoBlockProps(props, 'end' in token ? token.end : undefined))
  150. props.push({ type: 'newline', offset: -1, indent, source: '\n' });
  151. for (const key of Object.keys(token))
  152. if (key !== 'type' && key !== 'offset')
  153. delete token[key];
  154. Object.assign(token, { type: 'block-scalar', indent, props, source: body });
  155. }
  156. }
  157. /** @returns `true` if last token is a newline */
  158. function addEndtoBlockProps(props, end) {
  159. if (end)
  160. for (const st of end)
  161. switch (st.type) {
  162. case 'space':
  163. case 'comment':
  164. props.push(st);
  165. break;
  166. case 'newline':
  167. props.push(st);
  168. return true;
  169. }
  170. return false;
  171. }
  172. function setFlowScalarValue(token, source, type) {
  173. switch (token.type) {
  174. case 'scalar':
  175. case 'double-quoted-scalar':
  176. case 'single-quoted-scalar':
  177. token.type = type;
  178. token.source = source;
  179. break;
  180. case 'block-scalar': {
  181. const end = token.props.slice(1);
  182. let oa = source.length;
  183. if (token.props[0].type === 'block-scalar-header')
  184. oa -= token.props[0].source.length;
  185. for (const tok of end)
  186. tok.offset += oa;
  187. delete token.props;
  188. Object.assign(token, { type, source, end });
  189. break;
  190. }
  191. case 'block-map':
  192. case 'block-seq': {
  193. const offset = token.offset + source.length;
  194. const nl = { type: 'newline', offset, indent: token.indent, source: '\n' };
  195. delete token.items;
  196. Object.assign(token, { type, source, end: [nl] });
  197. break;
  198. }
  199. default: {
  200. const indent = 'indent' in token ? token.indent : -1;
  201. const end = 'end' in token && Array.isArray(token.end)
  202. ? token.end.filter(st => st.type === 'space' ||
  203. st.type === 'comment' ||
  204. st.type === 'newline')
  205. : [];
  206. for (const key of Object.keys(token))
  207. if (key !== 'type' && key !== 'offset')
  208. delete token[key];
  209. Object.assign(token, { type, indent, source, end });
  210. }
  211. }
  212. }
  213. exports.createScalarToken = createScalarToken;
  214. exports.resolveAsScalar = resolveAsScalar;
  215. exports.setScalarValue = setScalarValue;