links-notation 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -0
- package/README.ru.md +33 -0
- package/dist/index.js +55 -1
- package/index.d.ts +52 -1
- package/package.json +1 -1
- package/src/ParseError.js +104 -0
- package/src/Parser.js +6 -2
- package/src/index.js +1 -0
- package/tests/ParseErrorPosition.test.js +109 -0
package/README.md
CHANGED
|
@@ -279,11 +279,44 @@ Container for grouping related links.
|
|
|
279
279
|
- `constructor(links)` - Create a new group
|
|
280
280
|
- `format()` - Format the group as a string
|
|
281
281
|
|
|
282
|
+
#### `ParseError`
|
|
283
|
+
|
|
284
|
+
Thrown by `parse(input)` when the document does not parse. The message says
|
|
285
|
+
where the document stopped making sense and quotes the offending line:
|
|
286
|
+
|
|
287
|
+
```js
|
|
288
|
+
import { Parser, ParseError } from 'links-notation';
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
new Parser().parse('# ok line\n# break: two\nci_gate x\n');
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.error(error.message);
|
|
294
|
+
if (error instanceof ParseError) {
|
|
295
|
+
console.error(`${error.line}:${error.column} (offset ${error.offset})`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
```text
|
|
301
|
+
Syntax error at line 2, column 8: Expected "(", [ \t], [\r\n], or [^ \t\n\r(:)] but ":" found.
|
|
302
|
+
2 | # break: two
|
|
303
|
+
| ^
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
- `offset` - Offset of the offending position from the start of the document
|
|
307
|
+
- `line`, `column` - Where the document stopped parsing, counted from 1
|
|
308
|
+
- `found` - The character found instead, or `null` at the end of the document
|
|
309
|
+
- `lineText` - The offending line, as written
|
|
310
|
+
- `snippet` - The offending line with a caret under the offending column
|
|
311
|
+
- `location` - The position as the generated parser reports it
|
|
312
|
+
- `cause` - The error the generated parser threw
|
|
313
|
+
|
|
282
314
|
## Project Structure
|
|
283
315
|
|
|
284
316
|
- `src/grammar.pegjs` - Peggy.js grammar definition
|
|
285
317
|
- `src/Link.js` - Link data structure
|
|
286
318
|
- `src/LinksGroup.js` - Links group container
|
|
319
|
+
- `src/ParseError.js` - Parse error with the position of the defect
|
|
287
320
|
- `src/Parser.js` - Parser wrapper
|
|
288
321
|
- `src/index.js` - Main entry point
|
|
289
322
|
- `tests/` - Test files
|
package/README.ru.md
CHANGED
|
@@ -198,11 +198,44 @@ console.log(formatLinks(links)); // (value ((id 1) (label one)))
|
|
|
198
198
|
- `constructor(links)` - Создание новой группы
|
|
199
199
|
- `format()` - Форматирование группы в строку
|
|
200
200
|
|
|
201
|
+
#### `ParseError`
|
|
202
|
+
|
|
203
|
+
Выбрасывается методом `parse(input)`, когда документ не разбирается. Сообщение
|
|
204
|
+
говорит, где документ перестал быть понятным, и цитирует сломанную строку:
|
|
205
|
+
|
|
206
|
+
```js
|
|
207
|
+
import { Parser, ParseError } from 'links-notation';
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
new Parser().parse('# ok line\n# break: two\nci_gate x\n');
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error(error.message);
|
|
213
|
+
if (error instanceof ParseError) {
|
|
214
|
+
console.error(`${error.line}:${error.column} (смещение ${error.offset})`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
```text
|
|
220
|
+
Syntax error at line 2, column 8: Expected "(", [ \t], [\r\n], or [^ \t\n\r(:)] but ":" found.
|
|
221
|
+
2 | # break: two
|
|
222
|
+
| ^
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
- `offset` - Смещение сломанной позиции от начала документа
|
|
226
|
+
- `line`, `column` - Где документ перестал разбираться, счет с 1
|
|
227
|
+
- `found` - Найденный символ или `null` в конце документа
|
|
228
|
+
- `lineText` - Сломанная строка, как она написана
|
|
229
|
+
- `snippet` - Сломанная строка с указателем под нужным столбцом
|
|
230
|
+
- `location` - Позиция в том виде, в каком ее сообщает сгенерированный парсер
|
|
231
|
+
- `cause` - Ошибка, которую выбросил сгенерированный парсер
|
|
232
|
+
|
|
201
233
|
## Структура проекта
|
|
202
234
|
|
|
203
235
|
- `src/grammar.pegjs` - Определение грамматики Peggy.js
|
|
204
236
|
- `src/Link.js` - Структура данных связи
|
|
205
237
|
- `src/LinksGroup.js` - Контейнер групп связей
|
|
238
|
+
- `src/ParseError.js` - Ошибка разбора с позицией дефекта
|
|
206
239
|
- `src/Parser.js` - Обертка парсера
|
|
207
240
|
- `src/index.js` - Главная точка входа
|
|
208
241
|
- `tests/` - Файлы тестов
|
package/dist/index.js
CHANGED
|
@@ -269,6 +269,57 @@ class LinksGroup {
|
|
|
269
269
|
return list.map((item) => `(${item.id || item})`).join(" ");
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
|
+
// src/ParseError.js
|
|
273
|
+
var QUOTED_LINE_WIDTH = 80;
|
|
274
|
+
var ELLIPSIS = "...";
|
|
275
|
+
|
|
276
|
+
class ParseError extends Error {
|
|
277
|
+
constructor(input, error) {
|
|
278
|
+
const start = error?.location?.start ?? { offset: 0, line: 1, column: 1 };
|
|
279
|
+
const lineText = lineAt(input, start.offset);
|
|
280
|
+
const summary = `line ${start.line}, column ${start.column}: ${error.message}`;
|
|
281
|
+
const snippet = quote(start.line, lineText, start.column);
|
|
282
|
+
super(`Syntax error at ${summary}
|
|
283
|
+
${snippet}`);
|
|
284
|
+
this.name = "ParseError";
|
|
285
|
+
this.cause = error;
|
|
286
|
+
this.location = error.location;
|
|
287
|
+
this.offset = start.offset;
|
|
288
|
+
this.line = start.line;
|
|
289
|
+
this.column = start.column;
|
|
290
|
+
this.found = error.found ?? null;
|
|
291
|
+
this.lineText = lineText;
|
|
292
|
+
this.snippet = snippet;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function lineAt(input, offset) {
|
|
296
|
+
const at = Math.max(0, Math.min(offset, input.length));
|
|
297
|
+
const start = input.lastIndexOf(`
|
|
298
|
+
`, at - 1) + 1;
|
|
299
|
+
const end = input.indexOf(`
|
|
300
|
+
`, start);
|
|
301
|
+
const line = input.slice(start, end === -1 ? input.length : end);
|
|
302
|
+
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
303
|
+
}
|
|
304
|
+
function quote(number, lineText, column) {
|
|
305
|
+
const [quoted, at] = windowAround(lineText, column);
|
|
306
|
+
const gutter = " ".repeat(String(number).length);
|
|
307
|
+
return `${number} | ${quoted}
|
|
308
|
+
${gutter} | ${" ".repeat(at - 1)}^`;
|
|
309
|
+
}
|
|
310
|
+
function windowAround(lineText, column) {
|
|
311
|
+
if (lineText.length <= QUOTED_LINE_WIDTH) {
|
|
312
|
+
return [lineText, column];
|
|
313
|
+
}
|
|
314
|
+
const target = column - 1;
|
|
315
|
+
const lastStart = lineText.length - QUOTED_LINE_WIDTH;
|
|
316
|
+
const start = Math.min(Math.max(target - Math.floor(QUOTED_LINE_WIDTH / 2), 0), lastStart);
|
|
317
|
+
const end = start + QUOTED_LINE_WIDTH;
|
|
318
|
+
const quoted = (start > 0 ? ELLIPSIS : "") + lineText.slice(start, end) + (end < lineText.length ? ELLIPSIS : "");
|
|
319
|
+
const shift = start > 0 ? ELLIPSIS.length : 0;
|
|
320
|
+
return [quoted, target - start + shift + 1];
|
|
321
|
+
}
|
|
322
|
+
|
|
272
323
|
// src/parser-generated.js
|
|
273
324
|
class peg$SyntaxError extends SyntaxError {
|
|
274
325
|
constructor(message, expected, found, location) {
|
|
@@ -2024,9 +2075,11 @@ class Parser {
|
|
|
2024
2075
|
const rawResult = peg$parse(input);
|
|
2025
2076
|
return this.transformResult(rawResult);
|
|
2026
2077
|
} catch (error) {
|
|
2078
|
+
if (error && error.location) {
|
|
2079
|
+
throw new ParseError(input, error);
|
|
2080
|
+
}
|
|
2027
2081
|
const parseError = new Error(`Parse error: ${error.message}`);
|
|
2028
2082
|
parseError.cause = error;
|
|
2029
|
-
parseError.location = error.location;
|
|
2030
2083
|
throw parseError;
|
|
2031
2084
|
}
|
|
2032
2085
|
}
|
|
@@ -2163,6 +2216,7 @@ export {
|
|
|
2163
2216
|
FormatOptions,
|
|
2164
2217
|
Link,
|
|
2165
2218
|
LinksGroup,
|
|
2219
|
+
ParseError,
|
|
2166
2220
|
Parser,
|
|
2167
2221
|
formatLinks
|
|
2168
2222
|
};
|
package/index.d.ts
CHANGED
|
@@ -144,11 +144,62 @@ export class Parser {
|
|
|
144
144
|
* Parse Lino notation text into Link objects
|
|
145
145
|
* @param input - The Lino notation text to parse
|
|
146
146
|
* @returns Array of parsed Link objects
|
|
147
|
-
* @throws {
|
|
147
|
+
* @throws {ParseError} If the text does not parse
|
|
148
148
|
*/
|
|
149
149
|
parse(input: string): Link[];
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
/**
|
|
153
|
+
* The position a parse stopped at, as the generated parser reports it
|
|
154
|
+
*/
|
|
155
|
+
export interface ParseErrorLocation {
|
|
156
|
+
start: { offset: number; line: number; column: number };
|
|
157
|
+
end: { offset: number; line: number; column: number };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The error thrown when a document does not parse
|
|
162
|
+
*
|
|
163
|
+
* The message says where the document stopped making sense and quotes the
|
|
164
|
+
* offending line with a caret under it.
|
|
165
|
+
*/
|
|
166
|
+
export class ParseError extends Error {
|
|
167
|
+
/**
|
|
168
|
+
* Offset of the offending position from the start of the document
|
|
169
|
+
*/
|
|
170
|
+
offset: number;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Line the offending position is on, counted from 1
|
|
174
|
+
*/
|
|
175
|
+
line: number;
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Column the offending position is at, counted from 1
|
|
179
|
+
*/
|
|
180
|
+
column: number;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The character found instead, or null at the end of the document
|
|
184
|
+
*/
|
|
185
|
+
found: string | null;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* The offending line, as written, without its line ending
|
|
189
|
+
*/
|
|
190
|
+
lineText: string;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* The offending line with a caret under the offending column
|
|
194
|
+
*/
|
|
195
|
+
snippet: string;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* The position the generated parser reported
|
|
199
|
+
*/
|
|
200
|
+
location: ParseErrorLocation;
|
|
201
|
+
}
|
|
202
|
+
|
|
152
203
|
/**
|
|
153
204
|
* Options for formatting links
|
|
154
205
|
*/
|
package/package.json
CHANGED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/** The number of characters a quoted line is cut down to. */
|
|
2
|
+
const QUOTED_LINE_WIDTH = 80;
|
|
3
|
+
|
|
4
|
+
/** What a message writes in place of the part of a long line it left out. */
|
|
5
|
+
const ELLIPSIS = '...';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The error thrown when a document does not parse, carrying the position the
|
|
9
|
+
* parser stopped at.
|
|
10
|
+
*
|
|
11
|
+
* The generated parser reports that position, but only on the error object; the
|
|
12
|
+
* message it writes says what it expected without saying where. This error puts
|
|
13
|
+
* the line and the column in the message as well, and quotes the offending line
|
|
14
|
+
* with a caret under it, the way the Rust and C# ports do.
|
|
15
|
+
*/
|
|
16
|
+
export class ParseError extends Error {
|
|
17
|
+
/**
|
|
18
|
+
* @param {string} input - The document that failed to parse
|
|
19
|
+
* @param {Error} error - The error the generated parser threw
|
|
20
|
+
*/
|
|
21
|
+
constructor(input, error) {
|
|
22
|
+
const start = error?.location?.start ?? { offset: 0, line: 1, column: 1 };
|
|
23
|
+
const lineText = lineAt(input, start.offset);
|
|
24
|
+
const summary = `line ${start.line}, column ${start.column}: ${error.message}`;
|
|
25
|
+
const snippet = quote(start.line, lineText, start.column);
|
|
26
|
+
super(`Syntax error at ${summary}\n${snippet}`);
|
|
27
|
+
|
|
28
|
+
this.name = 'ParseError';
|
|
29
|
+
/** @type {Error} The error the generated parser threw */
|
|
30
|
+
this.cause = error;
|
|
31
|
+
/** @type {{start: {offset: number, line: number, column: number}}} */
|
|
32
|
+
this.location = error.location;
|
|
33
|
+
/** @type {number} Offset of the offending position from the start of the document */
|
|
34
|
+
this.offset = start.offset;
|
|
35
|
+
/** @type {number} Line the offending position is on, counted from 1 */
|
|
36
|
+
this.line = start.line;
|
|
37
|
+
/** @type {number} Column the offending position is at, counted from 1 */
|
|
38
|
+
this.column = start.column;
|
|
39
|
+
/** @type {string|null} The character found instead, or null at the end of the document */
|
|
40
|
+
this.found = error.found ?? null;
|
|
41
|
+
/** @type {string} The offending line, as written, without its line ending */
|
|
42
|
+
this.lineText = lineText;
|
|
43
|
+
/** @type {string} The offending line with a caret under the offending column */
|
|
44
|
+
this.snippet = snippet;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The line the given offset falls on, without its line ending.
|
|
50
|
+
* @param {string} input - The document being parsed
|
|
51
|
+
* @param {number} offset - Offset of the offending position
|
|
52
|
+
* @returns {string} The offending line
|
|
53
|
+
*/
|
|
54
|
+
function lineAt(input, offset) {
|
|
55
|
+
const at = Math.max(0, Math.min(offset, input.length));
|
|
56
|
+
const start = input.lastIndexOf('\n', at - 1) + 1;
|
|
57
|
+
const end = input.indexOf('\n', start);
|
|
58
|
+
const line = input.slice(start, end === -1 ? input.length : end);
|
|
59
|
+
return line.endsWith('\r') ? line.slice(0, -1) : line;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The offending line with a caret under the offending column, quoted the way a
|
|
64
|
+
* compiler quotes source. A long line is shown as a window around the caret, so
|
|
65
|
+
* the message stays the same size whether the document has ten lines or fifteen
|
|
66
|
+
* hundred.
|
|
67
|
+
* @param {number} number - The line number, counted from 1
|
|
68
|
+
* @param {string} lineText - The offending line
|
|
69
|
+
* @param {number} column - The offending column, counted from 1
|
|
70
|
+
* @returns {string} The quoted line and the caret under it
|
|
71
|
+
*/
|
|
72
|
+
function quote(number, lineText, column) {
|
|
73
|
+
const [quoted, at] = windowAround(lineText, column);
|
|
74
|
+
const gutter = ' '.repeat(String(number).length);
|
|
75
|
+
return `${number} | ${quoted}\n${gutter} | ${' '.repeat(at - 1)}^`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Cuts a line down to a window around the given column, and says which column
|
|
80
|
+
* the offending character sits at in that window. Both columns count from 1.
|
|
81
|
+
* @param {string} lineText - The offending line
|
|
82
|
+
* @param {number} column - The offending column, counted from 1
|
|
83
|
+
* @returns {[string, number]} The window and the column within it
|
|
84
|
+
*/
|
|
85
|
+
function windowAround(lineText, column) {
|
|
86
|
+
if (lineText.length <= QUOTED_LINE_WIDTH) {
|
|
87
|
+
return [lineText, column];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const target = column - 1;
|
|
91
|
+
const lastStart = lineText.length - QUOTED_LINE_WIDTH;
|
|
92
|
+
const start = Math.min(
|
|
93
|
+
Math.max(target - Math.floor(QUOTED_LINE_WIDTH / 2), 0),
|
|
94
|
+
lastStart
|
|
95
|
+
);
|
|
96
|
+
const end = start + QUOTED_LINE_WIDTH;
|
|
97
|
+
|
|
98
|
+
const quoted =
|
|
99
|
+
(start > 0 ? ELLIPSIS : '') +
|
|
100
|
+
lineText.slice(start, end) +
|
|
101
|
+
(end < lineText.length ? ELLIPSIS : '');
|
|
102
|
+
const shift = start > 0 ? ELLIPSIS.length : 0;
|
|
103
|
+
return [quoted, target - start + shift + 1];
|
|
104
|
+
}
|
package/src/Parser.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Link } from './Link.js';
|
|
2
|
+
import { ParseError } from './ParseError.js';
|
|
2
3
|
import * as parserModule from './parser-generated.js';
|
|
3
4
|
|
|
4
5
|
export class Parser {
|
|
@@ -35,10 +36,13 @@ export class Parser {
|
|
|
35
36
|
const rawResult = parserModule.parse(input);
|
|
36
37
|
return this.transformResult(rawResult);
|
|
37
38
|
} catch (error) {
|
|
38
|
-
//
|
|
39
|
+
// A syntax error knows where it stopped; anything else is passed on with
|
|
40
|
+
// the original error kept as the cause.
|
|
41
|
+
if (error && error.location) {
|
|
42
|
+
throw new ParseError(input, error);
|
|
43
|
+
}
|
|
39
44
|
const parseError = new Error(`Parse error: ${error.message}`);
|
|
40
45
|
parseError.cause = error;
|
|
41
|
-
parseError.location = error.location;
|
|
42
46
|
throw parseError;
|
|
43
47
|
}
|
|
44
48
|
}
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { Link, formatLinks } from './Link.js';
|
|
2
2
|
export { LinksGroup } from './LinksGroup.js';
|
|
3
3
|
export { Parser } from './Parser.js';
|
|
4
|
+
export { ParseError } from './ParseError.js';
|
|
4
5
|
export { FormatConfig } from './FormatConfig.js';
|
|
5
6
|
export { FormatOptions } from './FormatOptions.js';
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// A parse error has to say where the document stopped making sense.
|
|
2
|
+
//
|
|
3
|
+
// The positions asserted here are the ones the Rust and C# ports report for the
|
|
4
|
+
// same input, so the implementations can be held to the same contract
|
|
5
|
+
// (https://github.com/link-foundation/links-notation/issues/302).
|
|
6
|
+
|
|
7
|
+
import { test, expect } from 'bun:test';
|
|
8
|
+
import { Parser, ParseError } from '../src/index.js';
|
|
9
|
+
|
|
10
|
+
const parser = new Parser();
|
|
11
|
+
|
|
12
|
+
function syntaxError(document) {
|
|
13
|
+
try {
|
|
14
|
+
const links = parser.parse(document);
|
|
15
|
+
throw new Error(
|
|
16
|
+
`expected ${JSON.stringify(document)} not to parse, got ${links.length} links`
|
|
17
|
+
);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
expect(error).toBeInstanceOf(ParseError);
|
|
20
|
+
return error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
test('reports the line and column of the defect', () => {
|
|
25
|
+
// The example from the issue: the defect is the colon on line 2, and the
|
|
26
|
+
// two lines after it are fine.
|
|
27
|
+
const error = syntaxError('# ok line\n# break: two\nci_gate x\n stage rust');
|
|
28
|
+
|
|
29
|
+
expect(error.line).toBe(2);
|
|
30
|
+
expect(error.column).toBe(8);
|
|
31
|
+
expect(error.found).toBe(':');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('offset agrees with the other implementations', () => {
|
|
35
|
+
// Rust and C# report offset 17, line 2, column 8 for this document.
|
|
36
|
+
const error = syntaxError('# ok line\n# break: two\n');
|
|
37
|
+
|
|
38
|
+
expect(error.offset).toBe(17);
|
|
39
|
+
expect(error.line).toBe(2);
|
|
40
|
+
expect(error.column).toBe(8);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('reports the line a late defect is on', () => {
|
|
44
|
+
const error = syntaxError('a\nb\nc\nd\ne: f: g\nh\n');
|
|
45
|
+
|
|
46
|
+
expect(error.line).toBe(5);
|
|
47
|
+
expect(error.column).toBe(5);
|
|
48
|
+
expect(error.lineText).toBe('e: f: g');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('reports the end of the document when a group is never closed', () => {
|
|
52
|
+
const error = syntaxError('a (b\n');
|
|
53
|
+
|
|
54
|
+
expect(error.offset).toBe(5);
|
|
55
|
+
expect(error.line).toBe(2);
|
|
56
|
+
expect(error.column).toBe(1);
|
|
57
|
+
expect(error.found).toBeNull();
|
|
58
|
+
expect(error.message).toContain('end of input');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('reports an unmatched closing parenthesis', () => {
|
|
62
|
+
const error = syntaxError('a b)\n');
|
|
63
|
+
|
|
64
|
+
expect(error.offset).toBe(3);
|
|
65
|
+
expect(error.line).toBe(1);
|
|
66
|
+
expect(error.column).toBe(4);
|
|
67
|
+
expect(error.found).toBe(')');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('message says where the document broke', () => {
|
|
71
|
+
const error = syntaxError('# ok line\n# break: two\n');
|
|
72
|
+
|
|
73
|
+
expect(error.message.startsWith('Syntax error at line 2, column 8:')).toBe(
|
|
74
|
+
true
|
|
75
|
+
);
|
|
76
|
+
expect(error.snippet).toBe('2 | # break: two\n | ^');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('message quotes one line rather than the rest of the document', () => {
|
|
80
|
+
const document = `# ok line\n# break: two\n${'trailing line\n'.repeat(500)}`;
|
|
81
|
+
|
|
82
|
+
const error = syntaxError(document);
|
|
83
|
+
|
|
84
|
+
expect(error.message).toContain('line 2, column 8');
|
|
85
|
+
expect(error.message).not.toContain('trailing line');
|
|
86
|
+
expect(error.message.length).toBeLessThan(200);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('message of a long line stays a message', () => {
|
|
90
|
+
const document = `${'a'.repeat(400)}: ${'b'.repeat(400)}: c`;
|
|
91
|
+
|
|
92
|
+
const error = syntaxError(document);
|
|
93
|
+
|
|
94
|
+
expect(error.line).toBe(1);
|
|
95
|
+
expect(error.column).toBe(803);
|
|
96
|
+
expect(error.message).toContain('...');
|
|
97
|
+
expect(error.message.length).toBeLessThan(300);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('the location the parser used to report is still there', () => {
|
|
101
|
+
const error = syntaxError('a: b: c');
|
|
102
|
+
|
|
103
|
+
expect(error.location.start).toEqual({ offset: 4, line: 1, column: 5 });
|
|
104
|
+
expect(error.cause).toBeDefined();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('a document that parses reports nothing', () => {
|
|
108
|
+
expect(parser.parse('a: b\n c: d\n').length).toBe(2);
|
|
109
|
+
});
|