BREAKING(csv): remove ParseError (#5405)

* initial commit

* update

---------

Co-authored-by: Asher Gomez <ashersaupingomez@gmail.com>
This commit is contained in:
Tim Reichen 2024-07-16 09:35:40 +02:00 committed by GitHub
parent 13e23ab0e3
commit edd21d5ae5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 63 additions and 164 deletions

View File

@ -101,7 +101,9 @@ export async function parseRecord(
const col = codePointLength(
fullLine.slice(0, fullLine.length - line.slice(j).length),
);
throw new ParseError(startLine + 1, lineIndex, col, ERR_BARE_QUOTE);
throw new SyntaxError(
createBareQuoteErrorMessage(startLine + 1, lineIndex, col),
);
}
}
recordBuffer += field;
@ -141,7 +143,9 @@ export async function parseRecord(
const col = codePointLength(
fullLine.slice(0, fullLine.length - line.length - quoteLen),
);
throw new ParseError(startLine + 1, lineIndex, col, ERR_QUOTE);
throw new SyntaxError(
createQuoteErrorMessage(startLine + 1, lineIndex, col),
);
}
} else if (line.length > 0 || !reader.isEOF()) {
// Hit end of line (copy all data so far).
@ -154,7 +158,9 @@ export async function parseRecord(
// Abrupt end of file (EOF or error).
if (!options.lazyQuotes) {
const col = codePointLength(fullLine);
throw new ParseError(startLine + 1, lineIndex, col, ERR_QUOTE);
throw new SyntaxError(
createQuoteErrorMessage(startLine + 1, lineIndex, col),
);
}
fieldIndexes.push(recordBuffer.length);
break parseField;
@ -164,7 +170,9 @@ export async function parseRecord(
// Abrupt end of file (EOF on error).
if (!options.lazyQuotes) {
const col = codePointLength(fullLine);
throw new ParseError(startLine + 1, lineIndex, col, ERR_QUOTE);
throw new SyntaxError(
createQuoteErrorMessage(startLine + 1, lineIndex, col),
);
}
fieldIndexes.push(recordBuffer.length);
break parseField;
@ -181,129 +189,20 @@ export async function parseRecord(
return result;
}
/**
* A ParseError is returned for parsing errors.
* Line numbers are 1-indexed and columns are 0-indexed.
*
* @example Usage
* ```ts
* import { parse, ParseError } from "@std/csv/parse";
* import { assertEquals } from "@std/assert";
*
* try {
* parse(`a "word","b"`);
* } catch (error) {
* if (error instanceof ParseError) {
* assertEquals(error.message, `parse error on line 1, column 2: bare " in non-quoted-field`);
* }
* }
* ```
*/
export class ParseError extends SyntaxError {
/**
* Line where the record starts.
*
* @example Usage
* ```ts
* import { parse, ParseError } from "@std/csv/parse";
* import { assertEquals } from "@std/assert";
*
* try {
* parse(`a "word","b"`);
* } catch (error) {
* if (error instanceof ParseError) {
* assertEquals(error.startLine, 1);
* }
* }
* ```
*/
startLine: number;
/**
* Line where the error occurred.
*
* @example Usage
* ```ts
* import { parse, ParseError } from "@std/csv/parse";
* import { assertEquals } from "@std/assert";
*
* try {
* parse(`a "word","b"`);
* } catch (error) {
* if (error instanceof ParseError) {
* assertEquals(error.line, 1);
* }
* }
* ```
*/
line: number;
/**
* Column (rune index) where the error occurred.
*
* @example Usage
* ```ts
* import { parse, ParseError } from "@std/csv/parse";
* import { assertEquals } from "@std/assert";
*
* try {
* parse(`a "word","b"`);
* } catch (error) {
* if (error instanceof ParseError) {
* assertEquals(error.column, 2);
* }
* }
* ```
*/
column: number | null;
/**
* Constructs a new instance.
*
* @example Usage
* ```ts
* import { parse, ParseError } from "@std/csv/parse";
* import { assertEquals } from "@std/assert";
*
* try {
* parse(`a "word","b"`);
* } catch (error) {
* if (error instanceof ParseError) {
* assertEquals(error.message, `parse error on line 1, column 2: bare " in non-quoted-field`);
* }
* }
* ```
*
* @param start Line where the record starts
* @param line Line where the error occurred
* @param column Column The index where the error occurred
* @param message Error message
*/
constructor(
start: number,
line: number,
column: number | null,
message: string,
) {
super();
this.startLine = start;
this.column = column;
this.line = line;
if (message === ERR_FIELD_COUNT) {
this.message = `record on line ${line}: ${message}`;
} else if (start !== line) {
this.message =
`record on line ${start}; parse error on line ${line}, column ${column}: ${message}`;
} else {
this.message =
`parse error on line ${line}, column ${column}: ${message}`;
}
}
export function createBareQuoteErrorMessage(
start: number,
line: number,
column: number,
) {
return `record on line ${start}; parse error on line ${line}, column ${column}: bare " in non-quoted-field`;
}
export function createQuoteErrorMessage(
start: number,
line: number,
column: number,
) {
return `record on line ${start}; parse error on line ${line}, column ${column}: extraneous or missing " in quoted-field`;
}
export const ERR_BARE_QUOTE = 'bare " in non-quoted-field';
export const ERR_QUOTE = 'extraneous or missing " in quoted-field';
export const ERR_INVALID_DELIM = "Invalid Delimiter";
export const ERR_FIELD_COUNT = "wrong number of fields";
export function convertRowToObject(
row: string[],

View File

@ -3,18 +3,15 @@
import {
convertRowToObject,
ERR_BARE_QUOTE,
ERR_FIELD_COUNT,
ERR_INVALID_DELIM,
ERR_QUOTE,
ParseError,
createBareQuoteErrorMessage,
createQuoteErrorMessage,
type ParseResult,
type ReadOptions,
type RecordWithColumn,
} from "./_io.ts";
import { codePointLength } from "./_shared.ts";
export { ParseError, type ParseResult, type RecordWithColumn };
export type { ParseResult, RecordWithColumn };
const BYTE_ORDER_MARK = "\ufeff";
@ -112,7 +109,9 @@ class Parser {
const col = codePointLength(
fullLine.slice(0, fullLine.length - line.slice(j).length),
);
throw new ParseError(startLine + 1, lineIndex, col, ERR_BARE_QUOTE);
throw new SyntaxError(
createBareQuoteErrorMessage(startLine + 1, lineIndex, col),
);
}
}
recordBuffer += field;
@ -152,7 +151,9 @@ class Parser {
const col = codePointLength(
fullLine.slice(0, fullLine.length - line.length - quoteLen),
);
throw new ParseError(startLine + 1, lineIndex, col, ERR_QUOTE);
throw new SyntaxError(
createQuoteErrorMessage(startLine + 1, lineIndex, col),
);
}
} else if (line.length > 0 || !(this.#isEOF())) {
// Hit end of line (copy all data so far).
@ -165,7 +166,9 @@ class Parser {
// Abrupt end of file (EOF or error).
if (!this.#options.lazyQuotes) {
const col = codePointLength(fullLine);
throw new ParseError(startLine + 1, lineIndex, col, ERR_QUOTE);
throw new SyntaxError(
createQuoteErrorMessage(startLine + 1, lineIndex, col),
);
}
fieldIndexes.push(recordBuffer.length);
break parseField;
@ -175,7 +178,9 @@ class Parser {
// Abrupt end of file (EOF on error).
if (!this.#options.lazyQuotes) {
const col = codePointLength(fullLine);
throw new ParseError(startLine + 1, lineIndex, col, ERR_QUOTE);
throw new SyntaxError(
createQuoteErrorMessage(startLine + 1, lineIndex, col),
);
}
fieldIndexes.push(recordBuffer.length);
break parseField;
@ -209,7 +214,7 @@ class Parser {
INVALID_RUNE.includes(options.comment)) ||
options.separator === options.comment
) {
throw new Error(ERR_INVALID_DELIM);
throw new Error("Invalid Delimiter");
}
while (true) {
@ -232,7 +237,9 @@ class Parser {
if (lineResult.length > 0) {
if (_nbFields && _nbFields !== lineResult.length) {
throw new ParseError(lineIndex, lineIndex, null, ERR_FIELD_COUNT);
throw new SyntaxError(
`record on line ${lineIndex}: wrong number of fields`,
);
}
result.push(lineResult);
}

View File

@ -1,13 +1,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { CsvParseStream } from "./parse_stream.ts";
import type { CsvParseStreamOptions } from "./parse_stream.ts";
import { ERR_QUOTE, ParseError } from "./_io.ts";
import {
assert,
assertEquals,
assertRejects,
assertStringIncludes,
} from "@std/assert";
import { assertEquals, assertRejects } from "@std/assert";
import type { AssertTrue, IsExact } from "@std/testing/types";
import { fromFileUrl, join } from "@std/path";
import { delay } from "@std/async/delay";
@ -48,12 +42,11 @@ Deno.test({
const reader = readable.getReader();
assertEquals(await reader.read(), { done: false, value: ["id", "name"] });
assertEquals(await reader.read(), { done: false, value: ["1", "foo"] });
const error = await assertRejects(() => reader.read());
assert(error instanceof ParseError);
assertEquals(error.startLine, 4);
assertEquals(error.line, 5);
assertEquals(error.column, 0);
assertStringIncludes(error.message, ERR_QUOTE);
await assertRejects(
() => reader.read(),
SyntaxError,
`record on line 4; parse error on line 5, column 0: extraneous or missing " in quoted-field`,
);
},
});

View File

@ -5,7 +5,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { assert, assertEquals, assertThrows } from "@std/assert";
import { parse, ParseError, type ParseOptions } from "./parse.ts";
import { parse, type ParseOptions } from "./parse.ts";
import type { AssertTrue, IsExact } from "@std/testing/types";
const BYTE_ORDER_MARK = "\ufeff";
@ -193,7 +193,7 @@ Deno.test({
const input = `a""b,c`;
assertThrows(
() => parse(input),
ParseError,
SyntaxError,
'parse error on line 1, column 1: bare " in non-quoted-field',
);
},
@ -204,7 +204,7 @@ Deno.test({
const input = `a,b,🐱"`;
assertThrows(
() => parse(input),
ParseError,
SyntaxError,
'parse error on line 1, column 5: bare " in non-quoted-field',
);
},
@ -223,7 +223,7 @@ Deno.test({
const input = `a "word","b"`;
assertThrows(
() => parse(input),
ParseError,
SyntaxError,
'parse error on line 1, column 2: bare " in non-quoted-field',
);
},
@ -234,7 +234,7 @@ Deno.test({
const input = `"a word",b"`;
assertThrows(
() => parse(input),
ParseError,
SyntaxError,
'parse error on line 1, column 10: bare " in non-quoted-field',
);
},
@ -245,7 +245,7 @@ Deno.test({
const input = `"a "word","b"`;
assertThrows(
() => parse(input),
ParseError,
SyntaxError,
`parse error on line 1, column 3: extraneous or missing " in quoted-field`,
);
},
@ -256,7 +256,7 @@ Deno.test({
const input = "a,b,c\nd,e";
assertThrows(
() => parse(input, { fieldsPerRecord: 0 }),
ParseError,
SyntaxError,
"record on line 2: wrong number of fields",
);
},
@ -267,7 +267,7 @@ Deno.test({
const input = `a,b,c`;
assertThrows(
() => parse(input, { fieldsPerRecord: 2 }),
ParseError,
SyntaxError,
"record on line 1: wrong number of fields",
);
},
@ -384,7 +384,7 @@ Deno.test({
const input = 'a,"b\nc"d,e';
assertThrows(
() => parse(input, { fieldsPerRecord: 2 }),
ParseError,
SyntaxError,
'record on line 1; parse error on line 2, column 1: extraneous or missing " in quoted-field',
);
},
@ -395,7 +395,7 @@ Deno.test({
const input = 'a,b\n"d\n\n,e';
assertThrows(
() => parse(input, { fieldsPerRecord: 2 }),
ParseError,
SyntaxError,
'record on line 2; parse error on line 5, column 0: extraneous or missing " in quoted-field',
);
},
@ -438,7 +438,7 @@ Deno.test({
const input = '"field"\r\r';
assertThrows(
() => parse(input, { fieldsPerRecord: 2 }),
ParseError,
SyntaxError,
'parse error on line 1, column 6: extraneous or missing " in quoted-field',
);
},
@ -581,7 +581,7 @@ Deno.test({
const input = '"foo"bar"\r\n';
assertThrows(
() => parse(input),
ParseError,
SyntaxError,
`parse error on line 1, column 4: extraneous or missing " in quoted-field`,
);
},
@ -616,7 +616,7 @@ Deno.test({
const input = `"""""""`;
assertThrows(
() => parse(input),
ParseError,
SyntaxError,
`parse error on line 1, column 7: extraneous or missing " in quoted-field`,
);
},