[ES|QL] Support argument precedence in BasicPrettyPrinter (#191727)

## Summary

Inserts brackets where necessary, if binary expressions have different
precedence. For example:

```
FROM a | WHERE (1 + 2) * (3 - 4)
```


### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios


### For maintainers

- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
Vadim Kibana 2024-08-29 15:15:54 +02:00 committed by GitHub
parent 3de8133a5a
commit a156e6726f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 60 additions and 2 deletions

View file

@ -211,7 +211,7 @@ describe('single line query', () => {
});
});
describe('binary expression expression', () => {
describe('binary expression', () => {
test('arithmetic expression', () => {
const { text } = reprint('ROW 1 + 2');
@ -235,6 +235,36 @@ describe('single line query', () => {
expect(text).toBe('FROM a | WHERE a LIKE "b"');
});
test('inserts brackets where necessary due precedence', () => {
const { text } = reprint('FROM a | WHERE (1 + 2) * 3');
expect(text).toBe('FROM a | WHERE (1 + 2) * 3');
});
test('inserts brackets where necessary due precedence - 2', () => {
const { text } = reprint('FROM a | WHERE (1 + 2) * (3 - 4)');
expect(text).toBe('FROM a | WHERE (1 + 2) * (3 - 4)');
});
test('inserts brackets where necessary due precedence - 3', () => {
const { text } = reprint('FROM a | WHERE (1 + 2) * (3 - 4) / (5 + 6 + 7)');
expect(text).toBe('FROM a | WHERE (1 + 2) * (3 - 4) / (5 + 6 + 7)');
});
test('inserts brackets where necessary due precedence - 4', () => {
const { text } = reprint('FROM a | WHERE (1 + (1 + 2)) * ((3 - 4) / (5 + 6 + 7))');
expect(text).toBe('FROM a | WHERE (1 + 1 + 2) * (3 - 4) / (5 + 6 + 7)');
});
test('inserts brackets where necessary due precedence - 5', () => {
const { text } = reprint('FROM a | WHERE (1 + (1 + 2)) * (((3 - 4) / (5 + 6 + 7)) + 1)');
expect(text).toBe('FROM a | WHERE (1 + 1 + 2) * ((3 - 4) / (5 + 6 + 7) + 1)');
});
});
});

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
import { binaryExpressionGroup } from '../ast/helpers';
import { ESQLAstCommand } from '../types';
import { ESQLAstExpressionNode, ESQLAstQueryNode, Visitor } from '../visitor';
import { LeafPrinter } from './leaf_printer';
@ -135,10 +136,12 @@ export class BasicPrettyPrinter {
.on('visitExpression', (ctx) => {
return '<EXPRESSION>';
})
.on('visitSourceExpression', (ctx) => LeafPrinter.source(ctx.node))
.on('visitColumnExpression', (ctx) => LeafPrinter.column(ctx.node))
.on('visitLiteralExpression', (ctx) => LeafPrinter.literal(ctx.node))
.on('visitTimeIntervalLiteralExpression', (ctx) => LeafPrinter.timeInterval(ctx.node))
.on('visitInlineCastExpression', (ctx) => {
const value = ctx.value();
const wrapInBrackets =
@ -154,6 +157,7 @@ export class BasicPrettyPrinter {
return `${valueFormatted}::${ctx.node.castType}`;
})
.on('visitListLiteralExpression', (ctx) => {
let elements = '';
@ -163,6 +167,7 @@ export class BasicPrettyPrinter {
return `[${elements}]`;
})
.on('visitFunctionCallExpression', (ctx) => {
const opts = this.opts;
const node = ctx.node;
@ -183,7 +188,25 @@ export class BasicPrettyPrinter {
case 'binary-expression': {
operator = this.keyword(operator);
return `${ctx.visitArgument(0)} ${operator} ${ctx.visitArgument(1)}`;
const group = binaryExpressionGroup(ctx.node);
const [left, right] = ctx.arguments();
const groupLeft = binaryExpressionGroup(left);
const groupRight = binaryExpressionGroup(right);
let leftFormatted = ctx.visitArgument(0);
let rightFormatted = ctx.visitArgument(1);
if (groupLeft && groupLeft < group) {
leftFormatted = `(${leftFormatted})`;
}
if (groupRight && groupRight < group) {
rightFormatted = `(${rightFormatted})`;
}
const formatted = `${leftFormatted} ${operator} ${rightFormatted}`;
return formatted;
}
default: {
if (opts.lowercaseFunctions) {
@ -200,9 +223,11 @@ export class BasicPrettyPrinter {
}
}
})
.on('visitRenameExpression', (ctx) => {
return `${ctx.visitArgument(0)} ${this.keyword('AS')} ${ctx.visitArgument(1)}`;
})
.on('visitCommandOption', (ctx) => {
const opts = this.opts;
const option = opts.lowercaseOptions ? ctx.node.name : ctx.node.name.toUpperCase();
@ -218,6 +243,7 @@ export class BasicPrettyPrinter {
return optionFormatted;
})
.on('visitCommand', (ctx) => {
const opts = this.opts;
const cmd = opts.lowercaseCommands ? ctx.node.name : ctx.node.name.toUpperCase();
@ -239,6 +265,7 @@ export class BasicPrettyPrinter {
return cmdFormatted;
})
.on('visitQuery', (ctx) => {
const opts = this.opts;
const cmdSeparator = opts.multiline ? `\n${opts.pipeTab ?? ' '}| ` : ' | ';

View file

@ -332,6 +332,7 @@ export class WrappingPrettyPrinter {
.on('visitRenameExpression', (ctx, inp: Input): Output => {
const operator = this.keyword('AS');
return this.visitBinaryExpression(ctx, operator, inp);
})