@@ -179,6 +179,170 @@ for (const sample of jsoncSamples) {
179179
180180console . log ( `Validated ${ jsoncSamples . length } JSONC parser fixtures.` ) ;
181181
182+ // Test JSONL Parser
183+ const { parser : jsonlParser } = await bundledRequire (
184+ "src/languages/jsonl/parser.js" ,
185+ "acode-jsonl-parser-test.cjs" ,
186+ ) ;
187+
188+ const jsonlSamples = [
189+ {
190+ name : "JSON Lines multiple objects and comments" ,
191+ source : `{"id": 1, "name": "Acode", "active": true}
192+ {"id": 2, "name": "CodeMirror", "meta": null, "count": -3.14e+2}
193+ // Line comment between records
194+ /* Block comment */
195+ {"items": [1, "two", false]}
196+ "plain string line"
197+ 12345
198+ true
199+ null
200+ ` ,
201+ nodes : [
202+ "Object" ,
203+ "Property" ,
204+ "PropertyName" ,
205+ "Number" ,
206+ "String" ,
207+ "True" ,
208+ "False" ,
209+ "Null" ,
210+ "Array" ,
211+ "LineComment" ,
212+ "BlockComment" ,
213+ ] ,
214+ } ,
215+ ] ;
216+
217+ for ( const sample of jsonlSamples ) {
218+ const tree = jsonlParser . parse ( sample . source ) ;
219+ const names = new Set ( ) ;
220+ const errors = [ ] ;
221+
222+ tree . iterate ( {
223+ enter ( node ) {
224+ names . add ( node . name ) ;
225+ if ( node . type . isError ) {
226+ errors . push ( [ node . from , node . to ] ) ;
227+ }
228+ } ,
229+ } ) ;
230+
231+ assert . equal ( errors . length , 0 , `${ sample . name } produced parse errors` ) ;
232+ assert . equal ( tree . length , sample . source . length , `${ sample . name } was not fully parsed` ) ;
233+ for ( const node of sample . nodes ) {
234+ assert ( names . has ( node ) , `${ sample . name } did not produce ${ node } ` ) ;
235+ }
236+ }
237+
238+ console . log ( `Validated ${ jsonlSamples . length } JSONL parser fixtures.` ) ;
239+
240+ const jsonlBehaviorBundle = path . join ( os . tmpdir ( ) , "acode-jsonl-behavior-test-bundle.mjs" ) ;
241+ const jsonlBehaviorSource = String . raw `
242+ import assert from "node:assert/strict";
243+ import { CompletionContext } from "@codemirror/autocomplete";
244+ import { EditorState } from "@codemirror/state";
245+ import { foldable, getIndentation, indentUnit, syntaxTree } from "@codemirror/language";
246+ import { classHighlighter, highlightTree } from "@lezer/highlight";
247+ import { jsonl, jsonlLanguage } from "./src/languages/jsonl/index.js";
248+
249+ const source = [
250+ '{"id": 1, "name": "Acode", "valid": true}',
251+ '// comment line',
252+ '{"tags": ["editor", "mobile"], "meta": null, "score": -4.5e1}',
253+ '/* block comment */',
254+ '{"nested": {',
255+ ' "key": "value"',
256+ '}}',
257+ '',
258+ ].join("\n");
259+
260+ const tree = jsonlLanguage.parser.parse(source);
261+ const names = new Set();
262+ const errors = [];
263+ tree.iterate({
264+ enter(node) {
265+ names.add(node.name);
266+ if (node.type.isError) errors.push([node.from, node.to]);
267+ },
268+ });
269+
270+ assert.equal(errors.length, 0, "JSONL fixture produced parse errors");
271+ for (const name of [
272+ "Object",
273+ "Property",
274+ "PropertyName",
275+ "Number",
276+ "String",
277+ "True",
278+ "Null",
279+ "Array",
280+ "LineComment",
281+ "BlockComment",
282+ ]) {
283+ assert(names.has(name), "JSONL fixture did not produce " + name);
284+ }
285+
286+ const spans = [];
287+ highlightTree(tree, classHighlighter, (from, to, cls) => {
288+ spans.push({ text: source.slice(from, to), cls });
289+ });
290+
291+ function isHighlighted(text, tokenClass) {
292+ return spans.some((span) => span.text === text && span.cls.includes(tokenClass));
293+ }
294+
295+ assert(isHighlighted('"name"', "tok-propertyName"));
296+ assert(isHighlighted('"Acode"', "tok-string"));
297+ assert(isHighlighted("1", "tok-number"));
298+ assert(isHighlighted("-4.5e1", "tok-number"));
299+ assert(isHighlighted("true", "tok-bool"));
300+ assert(isHighlighted("null", "tok-keyword"));
301+ assert(isHighlighted('// comment line', "tok-comment"));
302+ assert(isHighlighted('/* block comment */', "tok-comment"));
303+
304+ const indentSource = '{\n "nested": {\n "key": "val"\n }\n}\n';
305+ const indentState = EditorState.create({
306+ doc: indentSource,
307+ extensions: [jsonl(), indentUnit.of(" ")],
308+ });
309+ syntaxTree(indentState);
310+ assert.equal(getIndentation(indentState, indentState.doc.line(2).from), 2);
311+ assert.equal(getIndentation(indentState, indentState.doc.line(3).from), 4);
312+ assert.equal(getIndentation(indentState, indentState.doc.line(4).from), 2);
313+ assert.equal(getIndentation(indentState, indentState.doc.line(5).from), 0);
314+ assert(foldable(indentState, indentState.doc.line(1).from, indentState.doc.line(1).to));
315+
316+ const compState = EditorState.create({ doc: "tr", extensions: [jsonl()] });
317+ const compSource = compState.languageDataAt("autocomplete", 2)[0];
318+ const compResult = compSource(new CompletionContext(compState, 2, true));
319+ assert(compResult.options.some((opt) => opt.label === "true"));
320+ assert(compResult.options.some((opt) => opt.label === "false"));
321+ assert(compResult.options.some((opt) => opt.label === "null"));
322+
323+ console.log("Validated JSONL parsing, highlighting, indentation, folding, and completion fixtures.");
324+ ` ;
325+
326+ await build ( {
327+ stdin : {
328+ contents : jsonlBehaviorSource ,
329+ resolveDir : process . cwd ( ) ,
330+ sourcefile : "jsonl-behavior-test.mjs" ,
331+ loader : "js" ,
332+ } ,
333+ bundle : true ,
334+ format : "esm" ,
335+ platform : "node" ,
336+ outfile : jsonlBehaviorBundle ,
337+ logLevel : "silent" ,
338+ } ) ;
339+
340+ try {
341+ await import ( pathToFileURL ( jsonlBehaviorBundle ) . href + `?t=${ Date . now ( ) } ` ) ;
342+ } finally {
343+ fs . rmSync ( jsonlBehaviorBundle , { force : true } ) ;
344+ }
345+
182346const yamlBehaviorBundle = path . join ( os . tmpdir ( ) , "acode-yaml-behavior-test-bundle.mjs" ) ;
183347const yamlBehaviorSource = String . raw `
184348import assert from "node:assert/strict";
0 commit comments