parseJsonl() was a private function inside json-viewer.mjs, a page entry-point that renders React and reads the JSONView content global at load time, so nothing else could import it. JsonlLineError lived in jsonview/, and the JSON Lines content types were spelled out inline in the sniffer and twice in fixSave(). The Netmonitor needs all three (bug 2059673), so move them to devtools/client/shared/jsonl-utils.mjs and have jsonview import from there. JsonlLineError also gains a Symbol.for() brand and an isInstance() helper, so consumers loading the module through a different global still recognise its instances. The only behaviour change: a line which fails to parse is reparsed preceded by the blank lines it comes after in the document, so that its error message points at the line in the document rather than at line 1. Differential Revision: https://phabricator.services.mozilla.com/D320918
97 lines
3.0 KiB
JavaScript
97 lines
3.0 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
import { parseJsonLossless } from "resource://devtools/client/shared/components/reps/reps/rep-utils.mjs";
|
|
|
|
// Brands JsonlLineError instances so they can be recognized across module
|
|
// instances, where `instanceof` would fail.
|
|
const JSONL_LINE_ERROR = Symbol.for("devtools.jsonl.lineError");
|
|
|
|
/**
|
|
* Represents one line of a JSON Lines document that failed to parse
|
|
* as JSON. `raw` and `message` are non-enumerable so this behaves as
|
|
* a leaf node in the JSON tree view (no expand toggle, no children).
|
|
*/
|
|
export class JsonlLineError {
|
|
/**
|
|
* @param {string} raw
|
|
* The line's original text.
|
|
* @param {string} message
|
|
* The parse error message reported for that line.
|
|
*/
|
|
constructor(raw, message) {
|
|
Object.defineProperties(this, {
|
|
raw: { value: raw, enumerable: false },
|
|
message: { value: message, enumerable: false },
|
|
[JSONL_LINE_ERROR]: { value: true, enumerable: false },
|
|
});
|
|
}
|
|
|
|
static isInstance(value) {
|
|
return !!value?.[JSONL_LINE_ERROR];
|
|
}
|
|
|
|
toJSON() {
|
|
return { error: this.message, raw: this.raw };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parses a JSON Lines document (one JSON value per line) into an
|
|
* array of records, in document order. Blank lines are skipped, so
|
|
* the array is contiguous and displayed like any other JSON array.
|
|
* A line that fails to parse becomes a JsonlLineError instead of
|
|
* blocking the rest of the document.
|
|
*
|
|
* @param {string} jsonlinesText
|
|
* The whole JSON Lines document, as text.
|
|
* @returns {Array<object|JsonlLineError>}
|
|
* One entry per non-blank line, in document order.
|
|
*/
|
|
export function parseJsonl(jsonlinesText) {
|
|
const entries = [];
|
|
const lines = jsonlinesText.split("\n");
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i];
|
|
if (!line.trim()) {
|
|
continue;
|
|
}
|
|
try {
|
|
entries.push(parseJsonLossless(line));
|
|
} catch (err) {
|
|
entries.push(
|
|
new JsonlLineError(line, documentLineErrorMessage(err, line, i))
|
|
);
|
|
}
|
|
}
|
|
return entries;
|
|
}
|
|
|
|
/**
|
|
* Returns the parse error message for a line which failed to parse, pointing at
|
|
* the line's position in the document. The line is reparsed preceded by the
|
|
* blank lines it comes after, since a parser fed a single line can only ever
|
|
* report line 1.
|
|
*
|
|
* @param {Error} err
|
|
* The error thrown when parsing the line on its own.
|
|
* @param {string} line
|
|
* The line's text.
|
|
* @param {number} lineIndex
|
|
* The 0-based position of the line in the document.
|
|
* @returns {string}
|
|
* The parse error message.
|
|
*/
|
|
function documentLineErrorMessage(err, line, lineIndex) {
|
|
if (!lineIndex) {
|
|
return err.message;
|
|
}
|
|
try {
|
|
parseJsonLossless("\n".repeat(lineIndex) + line);
|
|
} catch (paddedErr) {
|
|
return paddedErr.message;
|
|
}
|
|
return err.message;
|
|
}
|