From 3712d882017e16fa3536c6a689af7b007474cebe Mon Sep 17 00:00:00 2001 From: lohit Date: Fri, 25 Jul 2025 14:07:38 +0530 Subject: [PATCH] list block grammar fixes (#5180) --- packages/bruno-lang/v2/src/bruToJson.js | 11 +- packages/bruno-lang/v2/tests/list.spec.js | 798 ++++++++++++++++++++++ 2 files changed, 803 insertions(+), 6 deletions(-) create mode 100644 packages/bruno-lang/v2/tests/list.spec.js diff --git a/packages/bruno-lang/v2/src/bruToJson.js b/packages/bruno-lang/v2/src/bruToJson.js index 3ce411841..46738c86d 100644 --- a/packages/bruno-lang/v2/src/bruToJson.js +++ b/packages/bruno-lang/v2/src/bruToJson.js @@ -67,10 +67,9 @@ const grammar = ohm.grammar(`Bru { textchar = ~nl any // List - listend = stnl* "]" - list = st* "[" listitems? listend - listitems = (~listend stnl)* listitem (~listend stnl* listitem)* (~listend space)* - listitem = st* textchar+ st* + list = st* "[" nl+ listitems? st* nl+ st* "]" + listitems = listitem (nl+ listitem)* + listitem = st+ (alnum | "_" | "-")+ st* meta = "meta" dictionary settings = "settings" dictionary @@ -318,10 +317,10 @@ const sem = grammar.createSemantics().addAttribute('ast', { assertkey(chars) { return chars.sourceString ? chars.sourceString.trim() : ''; }, - list(_1, _2, listitems, _3) { + list(_1, _2, _3, listitems, _4, _5, _6, _7) { return listitems.ast.flat() }, - listitems(_1, listitem, _2, rest, _3) { + listitems(listitem, _1, rest) { return [listitem.ast, ...rest.ast] }, listitem(_1, textchar, _2) { diff --git a/packages/bruno-lang/v2/tests/list.spec.js b/packages/bruno-lang/v2/tests/list.spec.js new file mode 100644 index 000000000..19804779f --- /dev/null +++ b/packages/bruno-lang/v2/tests/list.spec.js @@ -0,0 +1,798 @@ +/** + * This test file is used to test list parsing in various BruFile blocks. + */ +const parser = require('../src/bruToJson'); + +describe('List Support in BruFile Blocks', () => { + + describe('Basic List Functionality', () => { + describe('Valid List Syntax', () => { + it('should parse simple list with proper indentation', () => { + const input = ` +meta { + tags: [ + tag_1 + tag_2 + ] +} +`; + const output = parser(input); + const expected = { + meta: { + seq: 1, + tags: ['tag_1', 'tag_2'], + type: "http" + } + }; + expect(output).toEqual(expected); + }); + + it('should parse list with mixed properties', () => { + const input = ` +meta { + name: request_name + tags: [ + regression + smoke_test + ] + type: http +} +`; + const output = parser(input); + const expected = { + meta: { + seq: 1, + name: "request_name", + tags: ['regression', 'smoke_test'], + type: "http" + } + }; + expect(output).toEqual(expected); + }); + + it('should parse list with varying indentation inside list', () => { + const input = ` +meta { + tags: [ + tag_1 + tag_2 + tag_3 + ] +} +`; + const output = parser(input); + const expected = { + meta: { + seq: 1, + tags: ['tag_1', 'tag_2', 'tag_3'], + type: "http" + } + }; + expect(output).toEqual(expected); + }); + + it('should parse list with alphanumeric, underscore, and hyphen characters', () => { + const input = ` +meta { + tags: [ + tag-with-hyphens + tag_with_underscores + tag123numbers + CamelCaseTag + ] +} +`; + const output = parser(input); + const expected = { + meta: { + seq: 1, + tags: ['tag-with-hyphens', 'tag_with_underscores', 'tag123numbers', 'CamelCaseTag'], + type: "http" + } + }; + expect(output).toEqual(expected); + }); + }); + + describe('Invalid List Syntax', () => { + it('should fail when list items have no indentation', () => { + const input = ` +meta { + tags: [ + tag_1 +tag_2 + ] +} +`; + expect(() => parser(input)).toThrow(); + }); + + it('should fail when list has empty lines between items', () => { + const input = ` +meta { + tags: [ + tag_1 + + tag_2 + ] +} +`; + expect(() => parser(input)).toThrow(); + }); + + it('should fail when list opening bracket is on same line as first item', () => { + const input = ` +meta { + tags: [tag_1 + tag_2 + ] +} +`; + expect(() => parser(input)).toThrow(); + }); + + it('should fail when list closing bracket is on same line as last item', () => { + const input = ` +meta { + tags: [ + tag_1 + tag_2] +} +`; + expect(() => parser(input)).toThrow(); + }); + + it('should fail when list items contain invalid characters - variation 1', () => { + const input = ` +meta { + tags: [ + tag*1 + tag@2 + ] +} +`; + expect(() => parser(input)).toThrow(); + }); + + it('should fail when list items contain spaces', () => { + const input = ` +meta { + tags: [ + tag with spaces + another-tag + ] +} +`; + expect(() => parser(input)).toThrow(); + }); + + it('should fail when list items contain invalid characters - variation 2', () => { + const input = ` +meta { + tags: [ + tag_1, + tag_2 + ] +} +`; + expect(() => parser(input)).toThrow(); + }); + + it('should fail when first list item has no indentation', () => { + const input = ` +meta { + tags: [ tag_1 + tag_2 + ] +} +`; + expect(() => parser(input)).toThrow(); + }); + + it('should fail when list item are not seperated by atleast one newline', () => { + const input = ` +meta { + tags: [ + tag_1 + tag_2 tag_3 + ] +} +`; + expect(() => parser(input)).toThrow(); + }); + + it('should not parse empty list', () => { + const input = ` +meta { + tags: [ + ] +} +`; + + expect(() => parser(input)).toThrow(); + }); + }); + + describe('String Values That Look Like Lists', () => { + it('should parse inline bracketed strings as regular values', () => { + const input = ` +meta { + name: [some name] + tags: [ + actual_list_item + ] +} +`; + const output = parser(input); + const expected = { + meta: { + seq: 1, + name: "[some name]", + tags: ['actual_list_item'], + type: "http" + } + }; + expect(output).toEqual(expected); + }); + + it('should parse bracketed strings with spaces as regular values', () => { + const input = ` +meta { + name: [ this is the name ] + tags: [ + tag_1 + tag_2 + ] +} +`; + const output = parser(input); + const expected = { + meta: { + seq: 1, + name: "[ this is the name ]", + tags: ['tag_1', 'tag_2'], + type: "http" + } + }; + expect(output).toEqual(expected); + }); + + it('should fail when multiline bracketed strings are malformed', () => { + const input = ` +meta { + name: [this spans + multiple lines + ] +} +`; + expect(() => parser(input)).toThrow(); + }); + }); + }); + + describe('Lists in Meta Block', () => { + it('should parse tags in meta block', () => { + const input = ` +meta { + name: API Test + tags: [ + api + integration + v1 + ] +} +`; + const output = parser(input); + const expected = { + meta: { + name: "API Test", + tags: ['api', 'integration', 'v1'], + seq: 1, + type: "http" + } + }; + expect(output).toEqual(expected); + }); + + it('should parse custom list properties in meta block', () => { + const input = ` +meta { + categories: [ + user-management + auth + ] + environments: [ + staging + production + ] +} +`; + const output = parser(input); + const expected = { + meta: { + seq: 1, + categories: ['user-management', 'auth'], + environments: ['staging', 'production'], + type: "http" + } + }; + expect(output).toEqual(expected); + }); + }); + + describe('Lists type content in Body Blocks', () => { + it('should parse bru file with a text body block that has list type values - variation 1', () => { + const input = ` +meta { + name: [name] + tags: [ + tag_1 + tag_2 + ] +} +body:text { + meta { + name: [name] + tags: [ + tag_1 + tag_2 + ] + } +} +`; + const output = parser(input); + + const expected = { + meta: { + name: "[name]", + tags: [ + "tag_1", + "tag_2" + ], + seq: 1, + type: 'http' + }, + body: { + text: `meta { + name: [name] + tags: [ + tag_1 + tag_2 + ] +}` + } + }; + expect(output).toEqual(expected); + }); + + it('should parse bru file with a text body block that has list type values - variation 2', () => { + const input = ` +meta { + name: [name] + tags: [ + tag_1 + tag_2 + ] +} +body:text { + meta { + name: [name] + tags: [ + tag_1 + tag_2 + ] + } +} +`; + const output = parser(input); + + const expected = { + meta: { + name: "[name]", + tags: [ + "tag_1", + "tag_2" + ], + seq: 1, + type: 'http' + }, + body: { + text: `meta { + name: [name] + tags: [ + tag_1 + tag_2 + ] +}` + } + }; + expect(output).toEqual(expected); + }); + + it('should parse bru file with a json body block that has list type values - variation 1', () => { + const input = ` +meta { + name: [name] + tags: [ + tag_1 + tag_2 + ] +} +body:json { + meta { + name: [name] + tags: [ + tag_1 + tag_2 + ] + } +} +`; + const output = parser(input); + + const expected = { + meta: { + name: "[name]", + tags: [ + "tag_1", + "tag_2" + ], + seq: 1, + type: 'http' + }, + body: { + json: `meta { + name: [name] + tags: [ + tag_1 + tag_2 + ] +}` + } + }; + expect(output).toEqual(expected); + }); + + it('should parse bru file with a json body block that has list type values - variation 2', () => { + const input = ` +meta { + name: [name] + tags: [ + tag_1 + tag_2 + ] +} +body:json { + meta { + name: [name] + tags: [ + tag_1 + tag_2 + ] + } +} +`; + const output = parser(input); + + const expected = { + meta: { + name: "[name]", + tags: [ + "tag_1", + "tag_2" + ], + seq: 1, + type: 'http' + }, + body: { + json: `meta { + name: [name] + tags: [ + tag_1 + tag_2 + ] +}` + } + }; + expect(output).toEqual(expected); + }); + + it('should parse bru file with a json body block that has array values', () => { + const input = ` +meta { + name: [name] + tags: [ + tag_1 + tag_2 + ] +} +body:json { + { + array: [ + "1", + "2", + "3" + ] + } +} +`; + const output = parser(input); + + const expected = { + meta: { + name: "[name]", + tags: [ + "tag_1", + "tag_2" + ], + seq: 1, + type: 'http' + }, + body: { + json: `{ + array: [ + "1", + "2", + "3" + ] +}` + } + }; + expect(output).toEqual(expected); + }); + + it('should parse bru file with a json body block that has array of objects - variation 1', () => { + const input = ` +meta { + name: [name] + tags: [ + tag_1 + tag_2 + ] +} +body:json { + { + array: [ + { + "id": 1 + }, + { + "id": 2 + }, + { + "id": 3 + } + ] + } +} +`; + const output = parser(input); + + const expected = { + meta: { + name: "[name]", + tags: [ + "tag_1", + "tag_2" + ], + seq: 1, + type: 'http' + }, + body: { + json: `{ + array: [ + { + "id": 1 + }, + { + "id": 2 + }, + { + "id": 3 + } + ] +}` + } + }; + expect(output).toEqual(expected); + }); + + it('should parse bru file with a json body block that has array of objects - variation 2', () => { + const input = ` +meta { + name: [name] + tags: [ + tag_1 + tag_2 + ] +} +body:json { + [{ + "foo": "bar" + }] +} +`; + const output = parser(input); + + const expected = { + meta: { + name: "[name]", + tags: [ + "tag_1", + "tag_2" + ], + seq: 1, + type: 'http' + }, + body: { + json: `[{ + "foo": "bar" +}]` + } + }; + expect(output).toEqual(expected); + }); + + it('should parse bru file with a json body block that has array of objects - variation 3', () => { + const input = ` +meta { + name: [name] + tags: [ + tag_1 + tag_2 + ] +} +body:json { + [{"foo": "bar"}] +} +`; + const output = parser(input); + + const expected = { + meta: { + name: "[name]", + tags: [ + "tag_1", + "tag_2" + ], + seq: 1, + type: 'http' + }, + body: { + json: `[{"foo": "bar"}]` + } + }; + expect(output).toEqual(expected); + }); + + it('should parse bru file with a json body block that has objects and arrays - variation 1', () => { + const input = ` +meta { + name: [name] + tags: [ + tag_1 + tag_2 + ] +} +body:json { + { + object: { + array: [ + { + "id": 1 + }, + { + "id": 2 + }, + { + "id": 3 + } + ] + } + } +} +`; + const output = parser(input); + + const expected = { + meta: { + name: "[name]", + tags: [ + "tag_1", + "tag_2" + ], + seq: 1, + type: 'http' + }, + body: { + json: `{ + object: { + array: [ + { + "id": 1 + }, + { + "id": 2 + }, + { + "id": 3 + } + ] + } +}` + } + }; + expect(output).toEqual(expected); + }); + + it('should parse bru file with a json body block with complex arrays', () => { + const input = ` +meta { + name: [name] + tags: [ + tag_1 + tag_2 + ] +} +body:json { + [ + "string", + array: [ + "tag_1", + "tag_2" + ], + object: { + array: [ + { + "id": 1 + }, + { + "id": 2 + }, + { + "id": 3 + } + ] + } + ] +} +`; + const output = parser(input); + + const expected = { + meta: { + name: "[name]", + tags: [ + "tag_1", + "tag_2" + ], + seq: 1, + type: 'http' + }, + body: { + json: `[ + "string", + array: [ + "tag_1", + "tag_2" + ], + object: { + array: [ + { + "id": 1 + }, + { + "id": 2 + }, + { + "id": 3 + } + ] + } +]` + } + }; + expect(output).toEqual(expected); + }); + }); +}); \ No newline at end of file