fix(csv-loader): escape quotes in schema rows
Implement loader-side escaping for the schema row to prevent `csv-parse` from misinterpreting double-quoted string literals (e.g., `"active" | "inactive"`) as field delimiters.
This commit is contained in:
@@ -33,6 +33,53 @@ import {
|
||||
parseReferenceValue,
|
||||
} from "./reference-resolver.js";
|
||||
import { generateTypeDefinition } from "./type-gen.js";
|
||||
|
||||
/**
|
||||
* Escape double-quote characters in the schema row so csv-parse doesn't
|
||||
* misinterpret them as CSV field delimiters.
|
||||
*
|
||||
* The schema row is the 2nd non-empty line. Cells that contain the quote
|
||||
* character (e.g. `"active" | "inactive"`) have their inner quotes escaped
|
||||
* and are wrapped in quotes, so csv-parse treats each as a single field.
|
||||
*/
|
||||
function escapeSchemaRowQuotes(
|
||||
content: string,
|
||||
delimiter: string,
|
||||
quote: string,
|
||||
escape: string,
|
||||
): string {
|
||||
if (!quote || !content.includes(quote)) {
|
||||
return content;
|
||||
}
|
||||
|
||||
const lines = content.split(/\r?\n/);
|
||||
let schemaRowIndex = -1;
|
||||
let nonEmptyCount = 0;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (lines[i].trim() === "") continue;
|
||||
nonEmptyCount++;
|
||||
if (nonEmptyCount === 2) {
|
||||
schemaRowIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (schemaRowIndex === -1) {
|
||||
return content;
|
||||
}
|
||||
|
||||
const cells = lines[schemaRowIndex].split(delimiter);
|
||||
const transformed = cells
|
||||
.map((cell) => {
|
||||
if (!cell.includes(quote)) {
|
||||
return cell;
|
||||
}
|
||||
const escaped = cell.split(quote).join(escape + quote);
|
||||
return quote + escaped + quote;
|
||||
})
|
||||
.join(delimiter);
|
||||
lines[schemaRowIndex] = transformed;
|
||||
return lines.join("\n");
|
||||
}
|
||||
import { csvToModule } from "./module-gen.js";
|
||||
import {
|
||||
parseTypeDeclaration,
|
||||
@@ -96,6 +143,15 @@ export function parseCsv(
|
||||
filteredContent = nonCommentLines.join("\n");
|
||||
}
|
||||
|
||||
// Escape double-quotes in the schema row so csv-parse doesn't treat them
|
||||
// as CSV field delimiters (e.g. `"active" | "inactive"` in a schema cell).
|
||||
filteredContent = escapeSchemaRowQuotes(
|
||||
filteredContent,
|
||||
delimiter,
|
||||
quote,
|
||||
escape,
|
||||
);
|
||||
|
||||
const records = parse(filteredContent, {
|
||||
delimiter,
|
||||
quote,
|
||||
|
||||
@@ -65,6 +65,21 @@ describe("parseCsv - basic parsing", () => {
|
||||
expect(result.data[1]).toEqual({ name: "Bob", status: "off" });
|
||||
});
|
||||
|
||||
it("should parse CSV with double-quoted string literal columns", () => {
|
||||
const csv = [
|
||||
"name,status",
|
||||
'string,"active" | "inactive"',
|
||||
"Alice,active",
|
||||
"Bob,inactive",
|
||||
].join("\n");
|
||||
|
||||
const result = parseCsv(csv, { emitTypes: false });
|
||||
|
||||
expect(result.data).toHaveLength(2);
|
||||
expect(result.data[0]).toEqual({ name: "Alice", status: "active" });
|
||||
expect(result.data[1]).toEqual({ name: "Bob", status: "inactive" });
|
||||
});
|
||||
|
||||
it("should parse CSV with array columns", () => {
|
||||
const csv = [
|
||||
"name,tags",
|
||||
|
||||
Reference in New Issue
Block a user