From e21bc5547d919296f1393540a1da8fcf81864e9d Mon Sep 17 00:00:00 2001 From: LuisFigueroaG Date: Thu, 29 Oct 2026 00:54:15 -0300 Subject: [PATCH] fix(csv-parse): keep parsing after skipped max_record_size and rtrim errors With `skip_records_with_error`, the `CSV_MAX_RECORD_SIZE` and `CSV_NON_TRIMABLE_CHAR_AFTER_CLOSING_QUOTE` errors returned straight out of the parse loop. The record was reported as skipped, but the rest of the current chunk was dropped, and the next chunk was parsed together with the stale buffer of the previous one. Handle them like the other skippable errors and only report them once per record. --- packages/csv-parse/lib/api/index.js | 31 +++++---- .../test/option.skip_records_with_error.ts | 66 +++++++++++++++++++ 2 files changed, 84 insertions(+), 13 deletions(-) diff --git a/packages/csv-parse/lib/api/index.js b/packages/csv-parse/lib/api/index.js index ca97838cd..38e8fa759 100644 --- a/packages/csv-parse/lib/api/index.js +++ b/packages/csv-parse/lib/api/index.js @@ -415,9 +415,10 @@ const transform = function (original_options = {}) { if (this.state.commenting === false) { if ( max_record_size !== 0 && + this.state.recordHasError === false && this.state.record_length + this.state.field.length > max_record_size ) { - return this.__error( + const err = this.__error( new CsvError( "CSV_MAX_RECORD_SIZE", [ @@ -430,6 +431,7 @@ const transform = function (original_options = {}) { this.__infoField(), ), ); + if (err !== undefined) return err; } } const lappend = @@ -442,18 +444,21 @@ const transform = function (original_options = {}) { if (lappend === true && rappend === true) { this.state.field.append(chr); } else if (rtrim === true && !this.__isCharTrimable(buf, pos)) { - return this.__error( - new CsvError( - "CSV_NON_TRIMABLE_CHAR_AFTER_CLOSING_QUOTE", - [ - "Invalid Closing Quote:", - "found non trimable byte after quote", - `at line ${this.info.lines}`, - ], - this.options, - this.__infoField(), - ), - ); + if (this.state.recordHasError === false) { + const err = this.__error( + new CsvError( + "CSV_NON_TRIMABLE_CHAR_AFTER_CLOSING_QUOTE", + [ + "Invalid Closing Quote:", + "found non trimable byte after quote", + `at line ${this.info.lines}`, + ], + this.options, + this.__infoField(), + ), + ); + if (err !== undefined) return err; + } } else { if (lappend === false) { pos += this.__isCharTrimable(buf, pos) - 1; diff --git a/packages/csv-parse/test/option.skip_records_with_error.ts b/packages/csv-parse/test/option.skip_records_with_error.ts index 1389ff7e7..aecf99eab 100644 --- a/packages/csv-parse/test/option.skip_records_with_error.ts +++ b/packages/csv-parse/test/option.skip_records_with_error.ts @@ -159,6 +159,72 @@ describe("Option `skip_records_with_error`", function () { parser.end(); }); + it('handle "CSV_MAX_RECORD_SIZE"', function (next) { + const errors: CsvError[] = []; + const parser = parse( + { max_record_size: 5, skip_records_with_error: true }, + (err, records) => { + if (err) return next(err); + records.should.eql([ + ["a", "b"], + ["c", "d"], + ["e", "f"], + ]); + assert_error(errors, [ + { + message: + "Max Record Size: record exceed the maximum number of tolerated bytes of 5 at line 2", + code: "CSV_MAX_RECORD_SIZE", + }, + ]); + errors.length.should.eql(1); + next(); + }, + ); + parser.on("skip", (err) => { + errors.push(err); + }); + // Split the oversized field across writes + parser.write("a,b\n12"); + parser.write("3456789,0\nc,d\n"); + parser.write("e,f\n"); + parser.end(); + }); + + it('handle "CSV_NON_TRIMABLE_CHAR_AFTER_CLOSING_QUOTE"', function (next) { + const errors: CsvError[] = []; + const parser = parse( + { rtrim: true, skip_records_with_error: true }, + (err, records) => { + if (err) return next(err); + records.should.eql([ + ["a", "b"], + ["c", "d"], + ["e", "f"], + ]); + assert_error(errors, [ + { + message: + "Invalid Closing Quote: found non trimable byte after quote at line 2", + code: "CSV_NON_TRIMABLE_CHAR_AFTER_CLOSING_QUOTE", + }, + ]); + errors.length.should.eql(1); + next(); + }, + ); + parser.on("skip", (err) => { + errors.push(err); + }); + parser.write(dedent` + a,b + "1" xx,2 + c,d + e,f + `); + parser.end(); + }); + describe("with `bom` option", function () { it('handle "CSV_RECORD_INCONSISTENT_FIELDS_LENGTH" with bom (fix #411)', function (next) { let errors = 0;