Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
9cb2002
refactor: import from shared packages
vadimkibana Aug 20, 2026
576d072
feat: move `tokens` module to the parser package
vadimkibana Aug 20, 2026
cbfc9f8
refactor: move AST printer to the traversal package
vadimkibana Aug 20, 2026
b3bc37e
refactor: move PromQL parser to the parser package
vadimkibana Aug 20, 2026
e8c5a09
chore: use the PromQL parser from the new location
vadimkibana Aug 20, 2026
c37dd08
refactor: move `Visitor` to the `-traversal` package
vadimkibana Aug 20, 2026
5693a9a
test: create shared test helpers
vadimkibana Aug 21, 2026
43c0014
test: migrate `Visitor` tests
vadimkibana Aug 21, 2026
3fa2277
chore: import `Visitor` from the `*-traversal` package
vadimkibana Aug 21, 2026
1b219df
refactor: move ES|QL main parser to the `*-parser` package
vadimkibana Aug 21, 2026
c0ff517
Merge remote-tracking branch 'origin/main' into parser-package-3
vadimkibana Aug 24, 2026
76c8b6d
test: move tests to correct locations
vadimkibana Aug 24, 2026
3b6e5f1
chore: cleanup codebase, move files to right locations
vadimkibana Aug 24, 2026
92f886c
refactor: remove unnecessary node wrapping
vadimkibana Aug 24, 2026
60f8006
chore: catch up with latest
vadimkibana Aug 24, 2026
fa45238
refactor: improve return type of `.visitValueExpression()`
vadimkibana Aug 24, 2026
eff7a84
fix: remove `[right]` boxing in assignment expression
vadimkibana Aug 24, 2026
d2a613b
feat: remove `.fromConstantToArray()` and make use of `.fromConstantS…
vadimkibana Aug 27, 2026
bdf2b88
feat: use `unknown` for missing right side node
vadimkibana Aug 27, 2026
6587e71
feat: cleanup types
vadimkibana Aug 27, 2026
bbbf7b5
refactor: remove unnecessary `firstItem()`
vadimkibana Aug 27, 2026
ee1abeb
refactor: remove `resolveItem()` usage
vadimkibana Aug 27, 2026
9763e9e
refactor: narrow down types from `ESQLAstItem` to `ESQLAstExpression`
vadimkibana Aug 27, 2026
e0171db
refactor: remove more `resolveItem()` and `singleItem()` usages
vadimkibana Aug 27, 2026
cea43f8
feat: deprecate array unboxing helper methods
vadimkibana Aug 27, 2026
db0a523
chore: add Changesets entry
vadimkibana Aug 28, 2026
c7e467e
chore: catch up with origin
vadimkibana Aug 28, 2026
1537160
Merge branch 'main' into parser-node-unboxing
vadimkibana Aug 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .changeset/ast-unboxing-major.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
'@elastic/esql-types': major
'@elastic/esql-traversal': major
'@elastic/esql-parser': minor
'@elastic/esql': minor
---

Remove array-boxed nodes (`[node]`) from the ES|QL AST — in parser output and in the type system.

**Breaking changes in `@elastic/esql-types`:**

- `ESQLAstItem` no longer has an array arm: it is now a deprecated alias of `ESQLSingleAstItem`. Code that hand-builds boxed args (`args: [left, [right]]`) no longer compiles — write `args: [left, right]` instead.
- `args` is narrowed from `ESQLAstItem[]` to `ESQLAstExpression[]` on `ESQLCommand`, `ESQLCommandOption`, `ESQLFunction`, and `ESQLFunctionCallExpression`, and the `ESQLUnaryExpression`, `ESQLPostfixUnaryExpression`, `ESQLOrderExpression`, and `ESQLBinaryExpression` tuples are narrowed accordingly.
- `ESQLProperNode` is now a deprecated alias of `ESQLAstNode` — all nodes are *proper* nodes now.

**Breaking changes in `@elastic/esql-traversal`:**

- `Walker.walkExpression()` is typed `ESQLAstExpression | ESQLAstExpression[]` (previously relied on the `ESQLAstItem` array arm).
- `VisitorContext.args()` yields `ESQLAstExpression` and no longer yields raw arrays.
- `firstItem`, `lastItem`, `resolveItem`, and `singleItems` are deprecated: the AST no longer contains array-boxed nodes, so access args directly (`args[0]`, `args.at(-1)`, plain iteration). They remain exported and runtime-tolerant of legacy boxed input for one major cycle.

**Parser output changes (`@elastic/esql-parser`):**

- No AST node is ever wrapped in an array anymore. Notable shapes that changed: `LIMIT ?` / `SAMPLE ?` (`args: [[param]]` to `args: [param]`), `DISSECT ... append_separator=?`, `RERANK ?` (`query` was an array in violation of its declared type), `WHERE x : ?`, and field assignments (`args: [column, [expression]]` to `args: [column, expression]`).
- A missing assignment right-hand side (`SET x =`, `ENRICH p WITH x =`) is now an explicit `{ type: 'unknown', incomplete: true }` placeholder node instead of an empty array.

**Migration:** replace `[node]` boxing with `node` when building ASTs; replace `firstItem(args)`/`resolveItem(arg)`/`lastItem(args)`/`singleItems(args)` with `args[0]`/`arg`/`args.at(-1)`/`args`.
11 changes: 5 additions & 6 deletions packages/esql-ast/src/esql/builder/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import type {
ESQLSource,
ESQLParamLiteral,
ESQLFunction,
ESQLAstItem,
ESQLStringLiteral,
ESQLBinaryExpression,
ESQLUnaryExpression,
Expand Down Expand Up @@ -349,7 +348,7 @@ export namespace Builder {

export const call = (
nameOrOperator: string | ESQLIdentifier | ESQLParamLiteral,
args: ESQLAstItem[],
args: ESQLAstExpression[],
template?: Omit<AstNodeTemplate<ESQLFunction>, 'subtype' | 'name' | 'operator' | 'args'>,
fromParser?: Partial<AstNodeParserFields>
): ESQLFunction => {
Expand All @@ -370,7 +369,7 @@ export namespace Builder {

export const unary = (
name: string,
arg: ESQLAstItem,
arg: ESQLAstExpression,
template?: Omit<AstNodeTemplate<ESQLFunction>, 'subtype' | 'name' | 'operator' | 'args'>,
fromParser?: Partial<AstNodeParserFields>
): ESQLUnaryExpression => {
Expand All @@ -383,7 +382,7 @@ export namespace Builder {

export const postfix = (
name: string,
arg: ESQLAstItem,
arg: ESQLAstExpression,
template?: Omit<AstNodeTemplate<ESQLFunction>, 'subtype' | 'name' | 'operator' | 'args'>,
fromParser?: Partial<AstNodeParserFields>
): ESQLUnaryExpression => {
Expand All @@ -396,7 +395,7 @@ export namespace Builder {

export const binary = <Name extends BinaryExpressionOperator = BinaryExpressionOperator>(
name: Name,
args: [left: ESQLAstItem, right: ESQLAstItem],
args: [left: ESQLAstExpression, right: ESQLAstExpression],
template?: Omit<AstNodeTemplate<ESQLBinaryExpression<Name>>, 'subtype' | 'name' | 'args'>,
fromParser?: Partial<AstNodeParserFields>
): ESQLBinaryExpression<Name> => {
Expand All @@ -409,7 +408,7 @@ export namespace Builder {
}

export const where = (
args: [left: ESQLAstItem, right: ESQLAstItem],
args: [left: ESQLAstExpression, right: ESQLAstExpression],
template?: Omit<AstNodeTemplate<ESQLFunction>, 'subtype' | 'name' | 'operator' | 'args'>,
fromParser?: Partial<AstNodeParserFields>
) => Builder.expression.func.binary('where', args, template, fromParser);
Expand Down
2 changes: 1 addition & 1 deletion packages/esql-ast/src/esql/is.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export const isESQLFunction = (node: unknown): node is types.ESQLFunction =>
(node as types.ESQLFunction).type === 'function';

export const isESQLNamedParamLiteral = (
node: types.ESQLAstItem
node: types.ESQLAstExpression
): node is types.ESQLNamedParamLiteral =>
isESQLAstBaseItem(node) &&
(node as types.ESQLNamedParamLiteral).literalType === 'param' &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,15 @@
*/

import { EsqlQuery } from './query';
import type { ESQLAstItem, ESQLAstQueryExpression, ESQLProperNode } from '@elastic/esql-types';
import { singleItems, Walker } from '@elastic/esql-traversal';
import type { ESQLAstQueryExpression, ESQLProperNode } from '@elastic/esql-types';
import { Walker } from '@elastic/esql-traversal';

const removeParserFields = (tree: ESQLAstQueryExpression): void => {
Walker.walk(tree, {
visitAny: (node: Partial<ESQLProperNode>) => {
delete node.text;
delete node.location;
delete node.incomplete;
const args = (node as { args?: ESQLAstItem[] }).args;
if (Array.isArray(args)) {
(node as { args?: ESQLAstItem[] }).args = [...singleItems(args)];
}
},
});
};
Expand Down
4 changes: 2 additions & 2 deletions packages/esql-parser/src/esql/__tests__/completion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import { EsqlQuery } from './query';
import type { ESQLAstCompletionCommand, ESQLAstItem, ESQLFunction } from '@elastic/esql-types';
import type { ESQLAstCompletionCommand, ESQLFunction } from '@elastic/esql-types';

describe('COMPLETION command', () => {
describe('correctly formatted', () => {
Expand Down Expand Up @@ -35,7 +35,7 @@ describe('COMPLETION command', () => {
it('parses prompt when it is a param', () => {
const text = `FROM index | COMPLETION ? WITH { "inference_id": "my_inference_endpoint" }`;
const query = EsqlQuery.fromSrc(text);
const promptArg = query.ast.commands[1].args[0] as ESQLAstItem[];
const promptArg = query.ast.commands[1].args[0];

expect(promptArg).toMatchObject({
type: 'literal',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,10 @@ describe('SET instruction parsing', () => {
type: 'function',
subtype: 'binary-expression',
name: '=',
args: [{ type: 'identifier', name: 'timezone' }, []],
args: [
{ type: 'identifier', name: 'timezone' },
{ type: 'unknown', incomplete: true },
],
incomplete: true,
},
],
Expand Down
28 changes: 13 additions & 15 deletions packages/esql-parser/src/esql/__tests__/limit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,20 @@ describe('LIMIT', () => {
type: 'command',
name: 'limit',
args: [
[
{
incomplete: false,
name: '',
paramKind: '?',
paramType: 'named',
text: '?param',
type: 'literal',
literalType: 'param',
value: 'param',
location: {
max: 24,
min: 19,
},
{
incomplete: false,
name: '',
paramKind: '?',
paramType: 'named',
text: '?param',
type: 'literal',
literalType: 'param',
value: 'param',
location: {
max: 24,
min: 19,
},
],
},
],
},
]);
Expand Down
10 changes: 4 additions & 6 deletions packages/esql-parser/src/esql/__tests__/rerank.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,10 @@ describe('RERANK', () => {
name: '=',
args: [
{},
[
{
type: 'function',
name: 'substring',
},
],
{
type: 'function',
name: 'substring',
},
],
},
],
Expand Down
44 changes: 20 additions & 24 deletions packages/esql-parser/src/esql/__tests__/stats.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,18 +102,16 @@ describe('STATS', () => {
},
],
},
[
{
type: 'function',
name: 'agg',
args: [
{
type: 'literal',
valueUnquoted: 'salary',
},
],
},
],
{
type: 'function',
name: 'agg',
args: [
{
type: 'literal',
valueUnquoted: 'salary',
},
],
},
],
},
],
Expand Down Expand Up @@ -293,18 +291,16 @@ describe('STATS', () => {
},
],
},
[
{
type: 'function',
name: 'agg',
args: [
{
type: 'literal',
valueUnquoted: 'salary',
},
],
},
],
{
type: 'function',
name: 'agg',
args: [
{
type: 'literal',
valueUnquoted: 'salary',
},
],
},
],
},
{
Expand Down
Loading