UNPKG

7.36 kBJavaScriptView Raw
1'use strict';
2
3var Scalar = require('../nodes/Scalar.js');
4var resolveEnd = require('./resolve-end.js');
5
6function resolveFlowScalar(scalar, strict, onError) {
7 const { offset, type, source, end } = scalar;
8 let _type;
9 let value;
10 const _onError = (rel, code, msg) => onError(offset + rel, code, msg);
11 switch (type) {
12 case 'scalar':
13 _type = Scalar.Scalar.PLAIN;
14 value = plainValue(source, _onError);
15 break;
16 case 'single-quoted-scalar':
17 _type = Scalar.Scalar.QUOTE_SINGLE;
18 value = singleQuotedValue(source, _onError);
19 break;
20 case 'double-quoted-scalar':
21 _type = Scalar.Scalar.QUOTE_DOUBLE;
22 value = doubleQuotedValue(source, _onError);
23 break;
24 /* istanbul ignore next should not happen */
25 default:
26 onError(scalar, 'UNEXPECTED_TOKEN', `Expected a flow scalar value, but found: ${type}`);
27 return {
28 value: '',
29 type: null,
30 comment: '',
31 range: [offset, offset + source.length, offset + source.length]
32 };
33 }
34 const valueEnd = offset + source.length;
35 const re = resolveEnd.resolveEnd(end, valueEnd, strict, onError);
36 return {
37 value,
38 type: _type,
39 comment: re.comment,
40 range: [offset, valueEnd, re.offset]
41 };
42}
43function plainValue(source, onError) {
44 let badChar = '';
45 switch (source[0]) {
46 /* istanbul ignore next should not happen */
47 case '\t':
48 badChar = 'a tab character';
49 break;
50 case ',':
51 badChar = 'flow indicator character ,';
52 break;
53 case '%':
54 badChar = 'directive indicator character %';
55 break;
56 case '|':
57 case '>': {
58 badChar = `block scalar indicator ${source[0]}`;
59 break;
60 }
61 case '@':
62 case '`': {
63 badChar = `reserved character ${source[0]}`;
64 break;
65 }
66 }
67 if (badChar)
68 onError(0, 'BAD_SCALAR_START', `Plain value cannot start with ${badChar}`);
69 return foldLines(source);
70}
71function singleQuotedValue(source, onError) {
72 if (source[source.length - 1] !== "'" || source.length === 1)
73 onError(source.length, 'MISSING_CHAR', "Missing closing 'quote");
74 return foldLines(source.slice(1, -1)).replace(/''/g, "'");
75}
76function foldLines(source) {
77 /**
78 * The negative lookbehind here and in the `re` RegExp is to
79 * prevent causing a polynomial search time in certain cases.
80 *
81 * The try-catch is for Safari, which doesn't support this yet:
82 * https://caniuse.com/js-regexp-lookbehind
83 */
84 let first, line;
85 try {
86 first = new RegExp('(.*?)(?<![ \t])[ \t]*\r?\n', 'sy');
87 line = new RegExp('[ \t]*(.*?)(?:(?<![ \t])[ \t]*)?\r?\n', 'sy');
88 }
89 catch {
90 first = /(.*?)[ \t]*\r?\n/sy;
91 line = /[ \t]*(.*?)[ \t]*\r?\n/sy;
92 }
93 let match = first.exec(source);
94 if (!match)
95 return source;
96 let res = match[1];
97 let sep = ' ';
98 let pos = first.lastIndex;
99 line.lastIndex = pos;
100 while ((match = line.exec(source))) {
101 if (match[1] === '') {
102 if (sep === '\n')
103 res += sep;
104 else
105 sep = '\n';
106 }
107 else {
108 res += sep + match[1];
109 sep = ' ';
110 }
111 pos = line.lastIndex;
112 }
113 const last = /[ \t]*(.*)/sy;
114 last.lastIndex = pos;
115 match = last.exec(source);
116 return res + sep + (match?.[1] ?? '');
117}
118function doubleQuotedValue(source, onError) {
119 let res = '';
120 for (let i = 1; i < source.length - 1; ++i) {
121 const ch = source[i];
122 if (ch === '\r' && source[i + 1] === '\n')
123 continue;
124 if (ch === '\n') {
125 const { fold, offset } = foldNewline(source, i);
126 res += fold;
127 i = offset;
128 }
129 else if (ch === '\\') {
130 let next = source[++i];
131 const cc = escapeCodes[next];
132 if (cc)
133 res += cc;
134 else if (next === '\n') {
135 // skip escaped newlines, but still trim the following line
136 next = source[i + 1];
137 while (next === ' ' || next === '\t')
138 next = source[++i + 1];
139 }
140 else if (next === '\r' && source[i + 1] === '\n') {
141 // skip escaped CRLF newlines, but still trim the following line
142 next = source[++i + 1];
143 while (next === ' ' || next === '\t')
144 next = source[++i + 1];
145 }
146 else if (next === 'x' || next === 'u' || next === 'U') {
147 const length = { x: 2, u: 4, U: 8 }[next];
148 res += parseCharCode(source, i + 1, length, onError);
149 i += length;
150 }
151 else {
152 const raw = source.substr(i - 1, 2);
153 onError(i - 1, 'BAD_DQ_ESCAPE', `Invalid escape sequence ${raw}`);
154 res += raw;
155 }
156 }
157 else if (ch === ' ' || ch === '\t') {
158 // trim trailing whitespace
159 const wsStart = i;
160 let next = source[i + 1];
161 while (next === ' ' || next === '\t')
162 next = source[++i + 1];
163 if (next !== '\n' && !(next === '\r' && source[i + 2] === '\n'))
164 res += i > wsStart ? source.slice(wsStart, i + 1) : ch;
165 }
166 else {
167 res += ch;
168 }
169 }
170 if (source[source.length - 1] !== '"' || source.length === 1)
171 onError(source.length, 'MISSING_CHAR', 'Missing closing "quote');
172 return res;
173}
174/**
175 * Fold a single newline into a space, multiple newlines to N - 1 newlines.
176 * Presumes `source[offset] === '\n'`
177 */
178function foldNewline(source, offset) {
179 let fold = '';
180 let ch = source[offset + 1];
181 while (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
182 if (ch === '\r' && source[offset + 2] !== '\n')
183 break;
184 if (ch === '\n')
185 fold += '\n';
186 offset += 1;
187 ch = source[offset + 1];
188 }
189 if (!fold)
190 fold = ' ';
191 return { fold, offset };
192}
193const escapeCodes = {
194 '0': '\0', // null character
195 a: '\x07', // bell character
196 b: '\b', // backspace
197 e: '\x1b', // escape character
198 f: '\f', // form feed
199 n: '\n', // line feed
200 r: '\r', // carriage return
201 t: '\t', // horizontal tab
202 v: '\v', // vertical tab
203 N: '\u0085', // Unicode next line
204 _: '\u00a0', // Unicode non-breaking space
205 L: '\u2028', // Unicode line separator
206 P: '\u2029', // Unicode paragraph separator
207 ' ': ' ',
208 '"': '"',
209 '/': '/',
210 '\\': '\\',
211 '\t': '\t'
212};
213function parseCharCode(source, offset, length, onError) {
214 const cc = source.substr(offset, length);
215 const ok = cc.length === length && /^[0-9a-fA-F]+$/.test(cc);
216 const code = ok ? parseInt(cc, 16) : NaN;
217 if (isNaN(code)) {
218 const raw = source.substr(offset - 2, length + 2);
219 onError(offset - 2, 'BAD_DQ_ESCAPE', `Invalid escape sequence ${raw}`);
220 return raw;
221 }
222 return String.fromCodePoint(code);
223}
224
225exports.resolveFlowScalar = resolveFlowScalar;