From 9cb200267a472173bf5381a3a8c90b9bf55cf525 Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Thu, 20 Aug 2026 11:58:41 +0200 Subject: [PATCH 01/24] refactor: import from shared packages --- packages/esql/src/parser/core/constants.ts | 5 +++-- packages/esql/src/parser/core/tokens.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/esql/src/parser/core/constants.ts b/packages/esql/src/parser/core/constants.ts index e4e08c23..5f43adc3 100644 --- a/packages/esql/src/parser/core/constants.ts +++ b/packages/esql/src/parser/core/constants.ts @@ -6,9 +6,10 @@ */ import { Token } from 'antlr4'; +import { headerCommandNames, sourceCommandNames } from '@elastic/esql-definitions/commandNames'; export const DEFAULT_CHANNEL: number = +Token.DEFAULT_CHANNEL; export const HIDDEN_CHANNEL: number = +Token.HIDDEN_CHANNEL; -export const HEADER_COMMANDS = new Set(['SET']); -export const SOURCE_COMMANDS = new Set(['FROM', 'ROW', 'SHOW', 'TS', 'EXPLAIN', 'PROMQL']); +export const HEADER_COMMANDS = new Set(headerCommandNames); +export const SOURCE_COMMANDS = new Set(sourceCommandNames); diff --git a/packages/esql/src/parser/core/tokens.ts b/packages/esql/src/parser/core/tokens.ts index 995c25d4..31d15ca2 100644 --- a/packages/esql/src/parser/core/tokens.ts +++ b/packages/esql/src/parser/core/tokens.ts @@ -7,7 +7,7 @@ import type { Token } from 'antlr4'; import { DEFAULT_CHANNEL } from './constants'; -import type { ESQLLocation } from '../../types'; +import type { ESQLLocation } from '@elastic/esql-types'; /** * Finds all tokens in the given range using binary search. Allows to further From 576d072e32ae92b57285766600d88437d7e13e76 Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Thu, 20 Aug 2026 12:41:05 +0200 Subject: [PATCH 02/24] feat: move `tokens` module to the parser package --- packages/esql-parser/package.json | 6 ++++++ packages/esql-parser/src/constants.ts | 15 +++++++++++++++ packages/esql-parser/src/index.ts | 2 ++ .../src/parser/core => esql-parser/src}/tokens.ts | 0 packages/esql/package.json | 1 + .../promql/parser/cst_to_ast_converter.ts | 2 +- .../promql/parser/promql_error_listener.ts | 2 +- .../esql/src/parser/core/cst_to_ast_converter.ts | 2 +- packages/esql/src/parser/core/decorations.ts | 2 +- .../esql/src/parser/core/esql_error_listener.ts | 2 +- yarn.lock | 7 ++++++- 11 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 packages/esql-parser/src/constants.ts rename packages/{esql/src/parser/core => esql-parser/src}/tokens.ts (100%) diff --git a/packages/esql-parser/package.json b/packages/esql-parser/package.json index dde00e7d..15c9fb9e 100644 --- a/packages/esql-parser/package.json +++ b/packages/esql-parser/package.json @@ -31,6 +31,12 @@ "typecheck": "tsc --noEmit", "test": "jest" }, + "dependencies": { + "@elastic/esql-definitions": "workspace:^", + "@elastic/esql-types": "workspace:^", + "antlr4": "4.13.2", + "tslib": "2.8.1" + }, "devDependencies": { "@types/jest": "30.0.0", "jest": "30.4.2", diff --git a/packages/esql-parser/src/constants.ts b/packages/esql-parser/src/constants.ts new file mode 100644 index 00000000..5f43adc3 --- /dev/null +++ b/packages/esql-parser/src/constants.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Token } from 'antlr4'; +import { headerCommandNames, sourceCommandNames } from '@elastic/esql-definitions/commandNames'; + +export const DEFAULT_CHANNEL: number = +Token.DEFAULT_CHANNEL; +export const HIDDEN_CHANNEL: number = +Token.HIDDEN_CHANNEL; + +export const HEADER_COMMANDS = new Set(headerCommandNames); +export const SOURCE_COMMANDS = new Set(sourceCommandNames); diff --git a/packages/esql-parser/src/index.ts b/packages/esql-parser/src/index.ts index 1fec1c76..d6f85f36 100644 --- a/packages/esql-parser/src/index.ts +++ b/packages/esql-parser/src/index.ts @@ -4,3 +4,5 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + +export * from './tokens'; diff --git a/packages/esql/src/parser/core/tokens.ts b/packages/esql-parser/src/tokens.ts similarity index 100% rename from packages/esql/src/parser/core/tokens.ts rename to packages/esql-parser/src/tokens.ts diff --git a/packages/esql/package.json b/packages/esql/package.json index ce680c43..ca70c2df 100644 --- a/packages/esql/package.json +++ b/packages/esql/package.json @@ -48,6 +48,7 @@ "@elastic/esql-ast": "workspace:^", "@elastic/esql-definitions": "workspace:^", "@elastic/esql-grammar": "workspace:^", + "@elastic/esql-parser": "workspace:^", "@elastic/esql-promql-grammar": "workspace:^", "@elastic/esql-traversal": "workspace:^", "@elastic/esql-types": "workspace:^", diff --git a/packages/esql/src/embedded_languages/promql/parser/cst_to_ast_converter.ts b/packages/esql/src/embedded_languages/promql/parser/cst_to_ast_converter.ts index f3d6a4c6..6e7cfd95 100644 --- a/packages/esql/src/embedded_languages/promql/parser/cst_to_ast_converter.ts +++ b/packages/esql/src/embedded_languages/promql/parser/cst_to_ast_converter.ts @@ -7,7 +7,7 @@ import type * as antlr from 'antlr4'; import * as cst from '@elastic/esql-promql-grammar'; -import { getPosition } from '../../../parser/core/tokens'; +import { getPosition } from '@elastic/esql-parser'; import { PromQLBuilder } from '@elastic/esql-ast'; import type { AstNodeParserFields } from '@elastic/esql-types'; import type { ESQLParamKinds } from '../../../types'; diff --git a/packages/esql/src/embedded_languages/promql/parser/promql_error_listener.ts b/packages/esql/src/embedded_languages/promql/parser/promql_error_listener.ts index debfa111..4557090b 100644 --- a/packages/esql/src/embedded_languages/promql/parser/promql_error_listener.ts +++ b/packages/esql/src/embedded_languages/promql/parser/promql_error_listener.ts @@ -6,7 +6,7 @@ */ import * as antlr4 from 'antlr4'; -import { getPosition } from '../../../parser/core/tokens'; +import { getPosition } from '@elastic/esql-parser'; import type { EditorError } from '../../../types'; /** diff --git a/packages/esql/src/parser/core/cst_to_ast_converter.ts b/packages/esql/src/parser/core/cst_to_ast_converter.ts index 487bc3dd..bb1464cb 100644 --- a/packages/esql/src/parser/core/cst_to_ast_converter.ts +++ b/packages/esql/src/parser/core/cst_to_ast_converter.ts @@ -16,7 +16,7 @@ import { type AstNodeParserFields, type AstNodeTemplate, } from '@elastic/esql-ast'; -import { getPosition } from './tokens'; +import { getPosition } from '@elastic/esql-parser'; import { nonNullable, unescapeColumn } from './helpers'; import { firstItem, lastItem, resolveItem, singleItems } from '../../ast/visitor/utils'; import { type ArithmeticUnaryContext } from '@elastic/esql-grammar'; diff --git a/packages/esql/src/parser/core/decorations.ts b/packages/esql/src/parser/core/decorations.ts index 5a2ed5a1..1e922fe3 100644 --- a/packages/esql/src/parser/core/decorations.ts +++ b/packages/esql/src/parser/core/decorations.ts @@ -29,7 +29,7 @@ import type { ParsedFormattingDecorationLines, } from './types'; import { HIDDEN_CHANNEL } from './constants'; -import { findVisibleToken, isLikelyPunctuation } from './tokens'; +import { findVisibleToken, isLikelyPunctuation } from '@elastic/esql-parser'; const commentSubtype = (text: string): ESQLAstComment['subtype'] | undefined => { if (text[0] === '/') { diff --git a/packages/esql/src/parser/core/esql_error_listener.ts b/packages/esql/src/parser/core/esql_error_listener.ts index 9fdd3f83..4cf8bb62 100644 --- a/packages/esql/src/parser/core/esql_error_listener.ts +++ b/packages/esql/src/parser/core/esql_error_listener.ts @@ -6,7 +6,7 @@ */ import * as antlr4 from 'antlr4'; -import { getPosition } from './tokens'; +import { getPosition } from '@elastic/esql-parser'; import { TOKEN_RECOGNITION_ERROR_PREFIX, isUnquotedIdentifierError, diff --git a/yarn.lock b/yarn.lock index 013ed1af..32be8341 100644 --- a/yarn.lock +++ b/yarn.lock @@ -960,13 +960,17 @@ __metadata: languageName: unknown linkType: soft -"@elastic/esql-parser@workspace:packages/esql-parser": +"@elastic/esql-parser@workspace:^, @elastic/esql-parser@workspace:packages/esql-parser": version: 0.0.0-use.local resolution: "@elastic/esql-parser@workspace:packages/esql-parser" dependencies: + "@elastic/esql-definitions": "workspace:^" + "@elastic/esql-types": "workspace:^" "@types/jest": "npm:30.0.0" + antlr4: "npm:4.13.2" jest: "npm:30.4.2" ts-jest: "npm:29.4.11" + tslib: "npm:2.8.1" tsup: "npm:8.5.1" typescript: "npm:6.0.3" languageName: unknown @@ -1017,6 +1021,7 @@ __metadata: "@elastic/esql-ast": "workspace:^" "@elastic/esql-definitions": "workspace:^" "@elastic/esql-grammar": "workspace:^" + "@elastic/esql-parser": "workspace:^" "@elastic/esql-promql-grammar": "workspace:^" "@elastic/esql-traversal": "workspace:^" "@elastic/esql-types": "workspace:^" From cbfc9f8d42f16b33f628a193bf5aac60352e0ec4 Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Thu, 20 Aug 2026 13:46:37 +0200 Subject: [PATCH 03/24] refactor: move AST printer to the traversal package --- packages/esql-traversal/package.json | 1 + packages/esql-traversal/src/debug/index.ts | 8 ++++++++ packages/{esql => esql-traversal}/src/debug/print_ast.ts | 5 ++--- packages/esql-traversal/src/index.ts | 1 + packages/esql/src/debug/index.ts | 2 +- 5 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 packages/esql-traversal/src/debug/index.ts rename packages/{esql => esql-traversal}/src/debug/print_ast.ts (95%) diff --git a/packages/esql-traversal/package.json b/packages/esql-traversal/package.json index ab8082e4..0f1dd736 100644 --- a/packages/esql-traversal/package.json +++ b/packages/esql-traversal/package.json @@ -34,6 +34,7 @@ "dependencies": { "@elastic/esql-ast": "workspace:^", "@elastic/esql-types": "workspace:^", + "tree-dump": "1.1.0", "tslib": "2.8.1" }, "devDependencies": { diff --git a/packages/esql-traversal/src/debug/index.ts b/packages/esql-traversal/src/debug/index.ts new file mode 100644 index 00000000..8fb8180c --- /dev/null +++ b/packages/esql-traversal/src/debug/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { printAst, type PrintAstOptions } from './print_ast'; diff --git a/packages/esql/src/debug/print_ast.ts b/packages/esql-traversal/src/debug/print_ast.ts similarity index 95% rename from packages/esql/src/debug/print_ast.ts rename to packages/esql-traversal/src/debug/print_ast.ts index 660054af..2a04595b 100644 --- a/packages/esql/src/debug/print_ast.ts +++ b/packages/esql-traversal/src/debug/print_ast.ts @@ -6,10 +6,9 @@ */ import { printTree } from 'tree-dump'; -import { childrenOfAnyNode } from '../ast/visitor/utils'; +import { childrenOfAnyNode } from '../esql'; import { isParamLiteral } from '@elastic/esql-ast'; -import type { ESQLProperNode } from '../types'; -import type { PromQLAstNode } from '@elastic/esql-types'; +import type { ESQLProperNode, PromQLAstNode } from '@elastic/esql-types'; /** * Options for printing an AST. diff --git a/packages/esql-traversal/src/index.ts b/packages/esql-traversal/src/index.ts index b032b4db..12cc5459 100644 --- a/packages/esql-traversal/src/index.ts +++ b/packages/esql-traversal/src/index.ts @@ -7,3 +7,4 @@ export * from './promql'; export * from './esql'; +export * from './debug'; diff --git a/packages/esql/src/debug/index.ts b/packages/esql/src/debug/index.ts index 8fb8180c..3e2a9e63 100644 --- a/packages/esql/src/debug/index.ts +++ b/packages/esql/src/debug/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { printAst, type PrintAstOptions } from './print_ast'; +export { printAst, type PrintAstOptions } from '@elastic/esql-traversal'; From b3bc37ee9f2fb8a586c950bc8b8d128a49bd3930 Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Thu, 20 Aug 2026 14:39:44 +0200 Subject: [PATCH 04/24] refactor: move PromQL parser to the parser package --- packages/esql-parser/package.json | 3 +++ packages/esql-parser/src/index.ts | 1 + .../promql/parser => esql-parser/src/promql}/README.md | 0 .../src/promql}/__tests__/decorations.test.ts | 0 .../src/promql}/__tests__/function.test.ts | 2 +- .../src/promql}/__tests__/parser.test.ts | 0 .../src/promql}/__tests__/selector.test.ts | 2 +- .../src/promql}/cst_to_ast_converter.ts | 5 ++--- .../promql/parser => esql-parser/src/promql}/decorations.ts | 0 .../promql/parser => esql-parser/src/promql}/index.ts | 0 .../promql/parser => esql-parser/src/promql}/parser.ts | 3 +-- .../src/promql}/promql_error_listener.ts | 4 ++-- yarn.lock | 4 ++++ 13 files changed, 15 insertions(+), 9 deletions(-) rename packages/{esql/src/embedded_languages/promql/parser => esql-parser/src/promql}/README.md (100%) rename packages/{esql/src/embedded_languages/promql/parser => esql-parser/src/promql}/__tests__/decorations.test.ts (100%) rename packages/{esql/src/embedded_languages/promql/parser => esql-parser/src/promql}/__tests__/function.test.ts (99%) rename packages/{esql/src/embedded_languages/promql/parser => esql-parser/src/promql}/__tests__/parser.test.ts (100%) rename packages/{esql/src/embedded_languages/promql/parser => esql-parser/src/promql}/__tests__/selector.test.ts (99%) rename packages/{esql/src/embedded_languages/promql/parser => esql-parser/src/promql}/cst_to_ast_converter.ts (99%) rename packages/{esql/src/embedded_languages/promql/parser => esql-parser/src/promql}/decorations.ts (100%) rename packages/{esql/src/embedded_languages/promql/parser => esql-parser/src/promql}/index.ts (100%) rename packages/{esql/src/embedded_languages/promql/parser => esql-parser/src/promql}/parser.ts (96%) rename packages/{esql/src/embedded_languages/promql/parser => esql-parser/src/promql}/promql_error_listener.ts (92%) diff --git a/packages/esql-parser/package.json b/packages/esql-parser/package.json index 15c9fb9e..ec228f14 100644 --- a/packages/esql-parser/package.json +++ b/packages/esql-parser/package.json @@ -32,7 +32,10 @@ "test": "jest" }, "dependencies": { + "@elastic/esql-ast": "workspace:^", "@elastic/esql-definitions": "workspace:^", + "@elastic/esql-promql-grammar": "workspace:^", + "@elastic/esql-traversal": "workspace:^", "@elastic/esql-types": "workspace:^", "antlr4": "4.13.2", "tslib": "2.8.1" diff --git a/packages/esql-parser/src/index.ts b/packages/esql-parser/src/index.ts index d6f85f36..ce85c18e 100644 --- a/packages/esql-parser/src/index.ts +++ b/packages/esql-parser/src/index.ts @@ -6,3 +6,4 @@ */ export * from './tokens'; +export * from './promql'; diff --git a/packages/esql/src/embedded_languages/promql/parser/README.md b/packages/esql-parser/src/promql/README.md similarity index 100% rename from packages/esql/src/embedded_languages/promql/parser/README.md rename to packages/esql-parser/src/promql/README.md diff --git a/packages/esql/src/embedded_languages/promql/parser/__tests__/decorations.test.ts b/packages/esql-parser/src/promql/__tests__/decorations.test.ts similarity index 100% rename from packages/esql/src/embedded_languages/promql/parser/__tests__/decorations.test.ts rename to packages/esql-parser/src/promql/__tests__/decorations.test.ts diff --git a/packages/esql/src/embedded_languages/promql/parser/__tests__/function.test.ts b/packages/esql-parser/src/promql/__tests__/function.test.ts similarity index 99% rename from packages/esql/src/embedded_languages/promql/parser/__tests__/function.test.ts rename to packages/esql-parser/src/promql/__tests__/function.test.ts index b1e7a728..30ac8bca 100644 --- a/packages/esql/src/embedded_languages/promql/parser/__tests__/function.test.ts +++ b/packages/esql-parser/src/promql/__tests__/function.test.ts @@ -6,7 +6,7 @@ */ import { PromQLParser } from '../parser'; -import { printAst } from '../../../../debug'; +import { printAst } from '@elastic/esql-traversal'; import type { PromQLFunction, PromQLSelector } from '@elastic/esql-types'; describe('PromQL "function" node parsing', () => { diff --git a/packages/esql/src/embedded_languages/promql/parser/__tests__/parser.test.ts b/packages/esql-parser/src/promql/__tests__/parser.test.ts similarity index 100% rename from packages/esql/src/embedded_languages/promql/parser/__tests__/parser.test.ts rename to packages/esql-parser/src/promql/__tests__/parser.test.ts diff --git a/packages/esql/src/embedded_languages/promql/parser/__tests__/selector.test.ts b/packages/esql-parser/src/promql/__tests__/selector.test.ts similarity index 99% rename from packages/esql/src/embedded_languages/promql/parser/__tests__/selector.test.ts rename to packages/esql-parser/src/promql/__tests__/selector.test.ts index c52f6b98..9fe5a9b0 100644 --- a/packages/esql/src/embedded_languages/promql/parser/__tests__/selector.test.ts +++ b/packages/esql-parser/src/promql/__tests__/selector.test.ts @@ -6,7 +6,7 @@ */ import { PromQLParser } from '../parser'; -import { printAst } from '../../../../debug'; +import { printAst } from '@elastic/esql-traversal'; import type { PromQLSelector } from '@elastic/esql-types'; describe('PromQL "selector" node parsing', () => { diff --git a/packages/esql/src/embedded_languages/promql/parser/cst_to_ast_converter.ts b/packages/esql-parser/src/promql/cst_to_ast_converter.ts similarity index 99% rename from packages/esql/src/embedded_languages/promql/parser/cst_to_ast_converter.ts rename to packages/esql-parser/src/promql/cst_to_ast_converter.ts index 6e7cfd95..f10f8eff 100644 --- a/packages/esql/src/embedded_languages/promql/parser/cst_to_ast_converter.ts +++ b/packages/esql-parser/src/promql/cst_to_ast_converter.ts @@ -7,10 +7,9 @@ import type * as antlr from 'antlr4'; import * as cst from '@elastic/esql-promql-grammar'; -import { getPosition } from '@elastic/esql-parser'; +import { getPosition } from '../tokens'; import { PromQLBuilder } from '@elastic/esql-ast'; -import type { AstNodeParserFields } from '@elastic/esql-types'; -import type { ESQLParamKinds } from '../../../types'; +import type { AstNodeParserFields, ESQLParamKinds } from '@elastic/esql-types'; import type * as ast from '@elastic/esql-types'; import type { PromQLParser } from './parser'; diff --git a/packages/esql/src/embedded_languages/promql/parser/decorations.ts b/packages/esql-parser/src/promql/decorations.ts similarity index 100% rename from packages/esql/src/embedded_languages/promql/parser/decorations.ts rename to packages/esql-parser/src/promql/decorations.ts diff --git a/packages/esql/src/embedded_languages/promql/parser/index.ts b/packages/esql-parser/src/promql/index.ts similarity index 100% rename from packages/esql/src/embedded_languages/promql/parser/index.ts rename to packages/esql-parser/src/promql/index.ts diff --git a/packages/esql/src/embedded_languages/promql/parser/parser.ts b/packages/esql-parser/src/promql/parser.ts similarity index 96% rename from packages/esql/src/embedded_languages/promql/parser/parser.ts rename to packages/esql-parser/src/promql/parser.ts index 9253c2a5..0efc2fc0 100644 --- a/packages/esql/src/embedded_languages/promql/parser/parser.ts +++ b/packages/esql-parser/src/promql/parser.ts @@ -14,8 +14,7 @@ import { PromQLErrorListener } from './promql_error_listener'; import { PromQLCstToAstConverter } from './cst_to_ast_converter'; import { PromQLBuilder } from '@elastic/esql-ast'; import { attachPromQLDecorations, collectPromQLDecorations } from './decorations'; -import type { PromQLAstQueryExpression, PromQLParseResult } from '@elastic/esql-types'; -import type { EditorError } from '../../../types'; +import type { EditorError, PromQLAstQueryExpression, PromQLParseResult } from '@elastic/esql-types'; export interface PromQLParseOptions { /** diff --git a/packages/esql/src/embedded_languages/promql/parser/promql_error_listener.ts b/packages/esql-parser/src/promql/promql_error_listener.ts similarity index 92% rename from packages/esql/src/embedded_languages/promql/parser/promql_error_listener.ts rename to packages/esql-parser/src/promql/promql_error_listener.ts index 4557090b..694c7fde 100644 --- a/packages/esql/src/embedded_languages/promql/parser/promql_error_listener.ts +++ b/packages/esql-parser/src/promql/promql_error_listener.ts @@ -6,8 +6,8 @@ */ import * as antlr4 from 'antlr4'; -import { getPosition } from '@elastic/esql-parser'; -import type { EditorError } from '../../../types'; +import { getPosition } from '../tokens'; +import type { EditorError } from '@elastic/esql-types'; /** * Error listener for PromQL parsing that collects syntax errors. diff --git a/yarn.lock b/yarn.lock index 32be8341..a5de447f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -964,7 +964,10 @@ __metadata: version: 0.0.0-use.local resolution: "@elastic/esql-parser@workspace:packages/esql-parser" dependencies: + "@elastic/esql-ast": "workspace:^" "@elastic/esql-definitions": "workspace:^" + "@elastic/esql-promql-grammar": "workspace:^" + "@elastic/esql-traversal": "workspace:^" "@elastic/esql-types": "workspace:^" "@types/jest": "npm:30.0.0" antlr4: "npm:4.13.2" @@ -998,6 +1001,7 @@ __metadata: "@elastic/esql-types": "workspace:^" "@types/jest": "npm:30.0.0" jest: "npm:30.4.2" + tree-dump: "npm:1.1.0" ts-jest: "npm:29.4.11" tslib: "npm:2.8.1" tsup: "npm:8.5.1" From e8c5a09fddfccdf92647fe993cc47810414cb6a8 Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Thu, 20 Aug 2026 14:47:38 +0200 Subject: [PATCH 05/24] chore: use the PromQL parser from the new location --- packages/esql/src/composer/synth/promql.ts | 2 +- packages/esql/src/debug/__tests__/print_ast.test.ts | 2 +- packages/esql/src/embedded_languages/promql/index.ts | 9 ++++++--- .../pretty_print/__tests__/basic_pretty_printer.test.ts | 2 +- .../__tests__/basic_pretty_printer_precedence.test.ts | 2 +- .../__tests__/wrapping_pretty_printer.comments.test.ts | 2 +- .../__tests__/wrapping_pretty_printer.test.ts | 2 +- packages/esql/src/parser/core/cst_to_ast_converter.ts | 3 +-- packages/esql/src/stories/FormatPromQL.tsx | 2 +- 9 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/esql/src/composer/synth/promql.ts b/packages/esql/src/composer/synth/promql.ts index 86b6c910..0eabe2fe 100644 --- a/packages/esql/src/composer/synth/promql.ts +++ b/packages/esql/src/composer/synth/promql.ts @@ -6,7 +6,7 @@ */ import type { ParseOptions } from '../../parser'; -import { PromQLParser } from '../../embedded_languages/promql/parser'; +import { PromQLParser } from '@elastic/esql-parser'; import type { PromQLAstQueryExpression } from '@elastic/esql-types'; import { createTag } from './tag'; import type { SynthGenerator } from './types'; diff --git a/packages/esql/src/debug/__tests__/print_ast.test.ts b/packages/esql/src/debug/__tests__/print_ast.test.ts index bfa3512c..b1ff2e0e 100644 --- a/packages/esql/src/debug/__tests__/print_ast.test.ts +++ b/packages/esql/src/debug/__tests__/print_ast.test.ts @@ -6,7 +6,7 @@ */ import { Parser } from '../../parser'; -import { printAst } from '../print_ast'; +import { printAst } from '@elastic/esql-traversal'; test('can print a basic "FROM index" query AST', () => { const { root } = Parser.parse('FROM index'); diff --git a/packages/esql/src/embedded_languages/promql/index.ts b/packages/esql/src/embedded_languages/promql/index.ts index d6834e4c..7a7cfb99 100644 --- a/packages/esql/src/embedded_languages/promql/index.ts +++ b/packages/esql/src/embedded_languages/promql/index.ts @@ -6,9 +6,12 @@ */ // Parser -export { PromQLParser, type PromQLParseOptions } from './parser'; -export { PromQLErrorListener } from './parser/promql_error_listener'; -export { PromQLCstToAstConverter } from './parser/cst_to_ast_converter'; +export { + PromQLParser, + type PromQLParseOptions, + PromQLErrorListener, + PromQLCstToAstConverter, +} from '@elastic/esql-parser'; // Pretty Printer export { PromQLBasicPrettyPrinter, type PromQLBasicPrettyPrinterOptions } from './pretty_print'; diff --git a/packages/esql/src/embedded_languages/promql/pretty_print/__tests__/basic_pretty_printer.test.ts b/packages/esql/src/embedded_languages/promql/pretty_print/__tests__/basic_pretty_printer.test.ts index 76ddc84c..5f8071cf 100644 --- a/packages/esql/src/embedded_languages/promql/pretty_print/__tests__/basic_pretty_printer.test.ts +++ b/packages/esql/src/embedded_languages/promql/pretty_print/__tests__/basic_pretty_printer.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { PromQLParser } from '../../parser'; +import { PromQLParser } from '@elastic/esql-parser'; import { PromQLBasicPrettyPrinter, type PromQLBasicPrettyPrinterOptions, diff --git a/packages/esql/src/embedded_languages/promql/pretty_print/__tests__/basic_pretty_printer_precedence.test.ts b/packages/esql/src/embedded_languages/promql/pretty_print/__tests__/basic_pretty_printer_precedence.test.ts index 2637612b..a3ead01a 100644 --- a/packages/esql/src/embedded_languages/promql/pretty_print/__tests__/basic_pretty_printer_precedence.test.ts +++ b/packages/esql/src/embedded_languages/promql/pretty_print/__tests__/basic_pretty_printer_precedence.test.ts @@ -6,7 +6,7 @@ */ import { PromQLBuilder } from '@elastic/esql-ast'; -import { PromQLParser } from '../../parser'; +import { PromQLParser } from '@elastic/esql-parser'; import { PromQLBasicPrettyPrinter } from '../basic_pretty_printer'; import type { PromQLAstExpression } from '@elastic/esql-types'; diff --git a/packages/esql/src/embedded_languages/promql/pretty_print/__tests__/wrapping_pretty_printer.comments.test.ts b/packages/esql/src/embedded_languages/promql/pretty_print/__tests__/wrapping_pretty_printer.comments.test.ts index 14c1a2bb..38eebdd2 100644 --- a/packages/esql/src/embedded_languages/promql/pretty_print/__tests__/wrapping_pretty_printer.comments.test.ts +++ b/packages/esql/src/embedded_languages/promql/pretty_print/__tests__/wrapping_pretty_printer.comments.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { PromQLParser } from '../../parser'; +import { PromQLParser } from '@elastic/esql-parser'; import { PromQLWrappingPrettyPrinter, type PromQLWrappingPrettyPrinterOptions, diff --git a/packages/esql/src/embedded_languages/promql/pretty_print/__tests__/wrapping_pretty_printer.test.ts b/packages/esql/src/embedded_languages/promql/pretty_print/__tests__/wrapping_pretty_printer.test.ts index a790f1a4..5867b775 100644 --- a/packages/esql/src/embedded_languages/promql/pretty_print/__tests__/wrapping_pretty_printer.test.ts +++ b/packages/esql/src/embedded_languages/promql/pretty_print/__tests__/wrapping_pretty_printer.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { PromQLParser } from '../../parser'; +import { PromQLParser } from '@elastic/esql-parser'; import { PromQLWrappingPrettyPrinter, type PromQLWrappingPrettyPrinterOptions, diff --git a/packages/esql/src/parser/core/cst_to_ast_converter.ts b/packages/esql/src/parser/core/cst_to_ast_converter.ts index bb1464cb..df24f2ff 100644 --- a/packages/esql/src/parser/core/cst_to_ast_converter.ts +++ b/packages/esql/src/parser/core/cst_to_ast_converter.ts @@ -16,11 +16,10 @@ import { type AstNodeParserFields, type AstNodeTemplate, } from '@elastic/esql-ast'; -import { getPosition } from '@elastic/esql-parser'; +import { getPosition, PromQLParser } from '@elastic/esql-parser'; import { nonNullable, unescapeColumn } from './helpers'; import { firstItem, lastItem, resolveItem, singleItems } from '../../ast/visitor/utils'; import { type ArithmeticUnaryContext } from '@elastic/esql-grammar'; -import { PromQLParser } from '../../embedded_languages/promql/parser/parser'; import type { Parser } from './parser'; import type { PromQLAstQueryExpression } from '@elastic/esql-types'; diff --git a/packages/esql/src/stories/FormatPromQL.tsx b/packages/esql/src/stories/FormatPromQL.tsx index 67a068e6..6df53cbb 100644 --- a/packages/esql/src/stories/FormatPromQL.tsx +++ b/packages/esql/src/stories/FormatPromQL.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { PromQLParser } from '../embedded_languages/promql/parser'; +import { PromQLParser } from '@elastic/esql-parser'; import { PromQLWrappingPrettyPrinter, type PromQLWrappingPrettyPrinterOptions, From c37dd08d491d9a2037839b342368a4b02cad7a29 Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Thu, 20 Aug 2026 15:24:33 +0200 Subject: [PATCH 06/24] refactor: move `Visitor` to the `-traversal` package --- packages/esql-traversal/src/esql/index.ts | 1 + .../src/esql}/visitor/README.md | 0 .../src/esql}/visitor/contexts.ts | 7 ++++--- .../src/esql}/visitor/global_visitor_context.ts | 4 ++-- .../src/esql}/visitor/index.ts | 2 +- .../src/esql}/visitor/types.ts | 2 +- .../src/esql}/visitor/visitor.ts | 4 ++-- .../src/ast/visitor/__tests__/commands.test.ts | 2 +- .../src/ast/visitor/__tests__/expressions.test.ts | 2 +- .../src/ast/visitor/__tests__/scenarios.test.ts | 2 +- .../src/ast/visitor/__tests__/visitor.test.ts | 7 +++++-- packages/esql/src/ast/visitor/utils.ts | 15 --------------- 12 files changed, 19 insertions(+), 29 deletions(-) rename packages/{esql/src/ast => esql-traversal/src/esql}/visitor/README.md (100%) rename packages/{esql/src/ast => esql-traversal/src/esql}/visitor/contexts.ts (99%) rename packages/{esql/src/ast => esql-traversal/src/esql}/visitor/global_visitor_context.ts (99%) rename packages/{esql/src/ast => esql-traversal/src/esql}/visitor/index.ts (82%) rename packages/{esql/src/ast => esql-traversal/src/esql}/visitor/types.ts (99%) rename packages/{esql/src/ast => esql-traversal/src/esql}/visitor/visitor.ts (99%) delete mode 100644 packages/esql/src/ast/visitor/utils.ts diff --git a/packages/esql-traversal/src/esql/index.ts b/packages/esql-traversal/src/esql/index.ts index 47d133d3..f825461a 100644 --- a/packages/esql-traversal/src/esql/index.ts +++ b/packages/esql-traversal/src/esql/index.ts @@ -8,3 +8,4 @@ export * from './utils'; export * from './children'; export * from './walker'; +export * from './visitor'; diff --git a/packages/esql/src/ast/visitor/README.md b/packages/esql-traversal/src/esql/visitor/README.md similarity index 100% rename from packages/esql/src/ast/visitor/README.md rename to packages/esql-traversal/src/esql/visitor/README.md diff --git a/packages/esql/src/ast/visitor/contexts.ts b/packages/esql-traversal/src/esql/visitor/contexts.ts similarity index 99% rename from packages/esql/src/ast/visitor/contexts.ts rename to packages/esql-traversal/src/esql/visitor/contexts.ts index f9a70062..f688c094 100644 --- a/packages/esql/src/ast/visitor/contexts.ts +++ b/packages/esql-traversal/src/esql/visitor/contexts.ts @@ -10,7 +10,8 @@ import type { SharedData } from './global_visitor_context'; import { type GlobalVisitorContext } from './global_visitor_context'; -import { children, firstItem, singleItems } from './utils'; +import { children } from '../children'; +import { firstItem, singleItems } from '../utils'; import type { ESQLAstChangePointCommand, ESQLAstCommand, @@ -41,7 +42,8 @@ import type { ESQLOrderExpression, ESQLParens, ESQLSource, -} from '../../types'; + PromQLAstQueryExpression, +} from '@elastic/esql-types'; import type { CommandVisitorInput, ESQLAstExpressionNode, @@ -54,7 +56,6 @@ import type { VisitorOutput, } from './types'; import { Builder, isProperNode } from '@elastic/esql-ast'; -import { PromQLAstQueryExpression } from '@elastic/esql-types'; export class VisitorContext< Methods extends VisitorMethods = VisitorMethods, diff --git a/packages/esql/src/ast/visitor/global_visitor_context.ts b/packages/esql-traversal/src/esql/visitor/global_visitor_context.ts similarity index 99% rename from packages/esql/src/ast/visitor/global_visitor_context.ts rename to packages/esql-traversal/src/esql/visitor/global_visitor_context.ts index 58e5c780..c789d950 100644 --- a/packages/esql/src/ast/visitor/global_visitor_context.ts +++ b/packages/esql-traversal/src/esql/visitor/global_visitor_context.ts @@ -32,9 +32,9 @@ import type { ESQLOrderExpression, ESQLParens, ESQLSource, -} from '../../types'; + PromQLAstQueryExpression, +} from '@elastic/esql-types'; import type * as types from './types'; -import type { PromQLAstQueryExpression } from '@elastic/esql-types'; export type SharedData = Record; diff --git a/packages/esql/src/ast/visitor/index.ts b/packages/esql-traversal/src/esql/visitor/index.ts similarity index 82% rename from packages/esql/src/ast/visitor/index.ts rename to packages/esql-traversal/src/esql/visitor/index.ts index 5016f5f2..8ed832d4 100644 --- a/packages/esql/src/ast/visitor/index.ts +++ b/packages/esql-traversal/src/esql/visitor/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -export type { VisitorMethods } from './types'; +export type { VisitorMethods, ESQLAstExpressionNode, ESQLAstQueryNode } from './types'; export { Visitor, type VisitorOptions } from './visitor'; export { GlobalVisitorContext, type SharedData } from './global_visitor_context'; export * from './contexts'; diff --git a/packages/esql/src/ast/visitor/types.ts b/packages/esql-traversal/src/esql/visitor/types.ts similarity index 99% rename from packages/esql/src/ast/visitor/types.ts rename to packages/esql-traversal/src/esql/visitor/types.ts index ab3c8ad6..2db682b2 100644 --- a/packages/esql/src/ast/visitor/types.ts +++ b/packages/esql-traversal/src/esql/visitor/types.ts @@ -6,7 +6,7 @@ */ import type { SharedData } from './global_visitor_context'; -import type * as ast from '../../types'; +import type * as ast from '@elastic/esql-types'; import type * as contexts from './contexts'; /** diff --git a/packages/esql/src/ast/visitor/visitor.ts b/packages/esql-traversal/src/esql/visitor/visitor.ts similarity index 99% rename from packages/esql/src/ast/visitor/visitor.ts rename to packages/esql-traversal/src/esql/visitor/visitor.ts index 1c7ea46a..75624311 100644 --- a/packages/esql/src/ast/visitor/visitor.ts +++ b/packages/esql-traversal/src/esql/visitor/visitor.ts @@ -20,7 +20,7 @@ import type { UndefinedToVoid, VisitorMethods, } from './types'; -import type { ESQLAstQueryExpression, ESQLCommand, ESQLProperNode } from '../../types'; +import type { ESQLAstQueryExpression, ESQLCommand, ESQLProperNode } from '@elastic/esql-types'; import { Builder } from '@elastic/esql-ast'; export interface VisitorOptions< @@ -358,7 +358,7 @@ export class Visitor< * @returns The output of the visitor. */ public visitHeaderCommand( - node: import('../../types').ESQLAstHeaderCommand, + node: import('@elastic/esql-types').ESQLAstHeaderCommand, input: UndefinedToVoid>[1]> ) { this.ctx.assertMethodExists('visitHeaderCommand'); diff --git a/packages/esql/src/ast/visitor/__tests__/commands.test.ts b/packages/esql/src/ast/visitor/__tests__/commands.test.ts index 49a6804b..c07cd39a 100644 --- a/packages/esql/src/ast/visitor/__tests__/commands.test.ts +++ b/packages/esql/src/ast/visitor/__tests__/commands.test.ts @@ -6,7 +6,7 @@ */ import { EsqlQuery } from '../../../composer/query'; -import { Visitor } from '../visitor'; +import { Visitor } from '@elastic/esql-traversal'; test('"visitCommand" captures all non-captured commands', () => { const { ast } = EsqlQuery.fromSrc(` diff --git a/packages/esql/src/ast/visitor/__tests__/expressions.test.ts b/packages/esql/src/ast/visitor/__tests__/expressions.test.ts index 67aacc57..1572e335 100644 --- a/packages/esql/src/ast/visitor/__tests__/expressions.test.ts +++ b/packages/esql/src/ast/visitor/__tests__/expressions.test.ts @@ -6,7 +6,7 @@ */ import { parse } from '../../../parser'; -import { Visitor } from '../visitor'; +import { Visitor } from '@elastic/esql-traversal'; test('"visitExpression" captures all non-captured expressions', () => { const { ast } = parse(` diff --git a/packages/esql/src/ast/visitor/__tests__/scenarios.test.ts b/packages/esql/src/ast/visitor/__tests__/scenarios.test.ts index 73138265..bc706dcd 100644 --- a/packages/esql/src/ast/visitor/__tests__/scenarios.test.ts +++ b/packages/esql/src/ast/visitor/__tests__/scenarios.test.ts @@ -15,7 +15,7 @@ import { parse } from '../../../parser'; import { BasicPrettyPrinter } from '../../../pretty_print'; import type { ESQLAstItem, ESQLAstQueryExpression } from '@elastic/esql-types'; -import { Visitor } from '../visitor'; +import { Visitor } from '@elastic/esql-traversal'; test('change LIMIT from 24 to 42', () => { const { root } = parse(` diff --git a/packages/esql/src/ast/visitor/__tests__/visitor.test.ts b/packages/esql/src/ast/visitor/__tests__/visitor.test.ts index 9c6dda1f..b6444821 100644 --- a/packages/esql/src/ast/visitor/__tests__/visitor.test.ts +++ b/packages/esql/src/ast/visitor/__tests__/visitor.test.ts @@ -7,8 +7,11 @@ import { parse } from '../../../parser'; import type { ESQLAstForkCommand } from '@elastic/esql-types'; -import { CommandVisitorContext, WhereCommandVisitorContext } from '../contexts'; -import { Visitor } from '../visitor'; +import { + CommandVisitorContext, + WhereCommandVisitorContext, + Visitor, +} from '@elastic/esql-traversal'; test('can collect all command names in type safe way', () => { const visitor = new Visitor() diff --git a/packages/esql/src/ast/visitor/utils.ts b/packages/esql/src/ast/visitor/utils.ts deleted file mode 100644 index 6e161b23..00000000 --- a/packages/esql/src/ast/visitor/utils.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { - singleItems, - firstItem, - resolveItem, - lastItem, - children, - childrenOfAnyNode, -} from '@elastic/esql-traversal'; From 5693a9a67bc9dd6b9280726b886776210c593d0a Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Fri, 21 Aug 2026 16:39:26 +0200 Subject: [PATCH 07/24] test: create shared test helpers --- .../esql-traversal/src/__tests__/builders.ts | 105 ++++++++++++++++++ .../esql}/visitor/__tests__/commands.test.ts | 0 .../visitor/__tests__/expressions.test.ts | 0 .../esql}/visitor/__tests__/visitor.test.ts | 0 .../src/esql/walker/__tests__/walker.test.ts | 12 +- .../walker/__tests__/walker_abort.test.ts | 63 ++--------- .../walker/__tests__/walker_order.test.ts | 12 +- .../walker/__tests__/walker_promql.test.ts | 23 +--- .../__tests__/walker_skip_children.test.ts | 55 ++------- .../walker/__tests__/walker_statics.test.ts | 5 +- 10 files changed, 123 insertions(+), 152 deletions(-) create mode 100644 packages/esql-traversal/src/__tests__/builders.ts rename packages/{esql/src/ast => esql-traversal/src/esql}/visitor/__tests__/commands.test.ts (100%) rename packages/{esql/src/ast => esql-traversal/src/esql}/visitor/__tests__/expressions.test.ts (100%) rename packages/{esql/src/ast => esql-traversal/src/esql}/visitor/__tests__/visitor.test.ts (100%) diff --git a/packages/esql-traversal/src/__tests__/builders.ts b/packages/esql-traversal/src/__tests__/builders.ts new file mode 100644 index 00000000..191c321c --- /dev/null +++ b/packages/esql-traversal/src/__tests__/builders.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Builder, PromQLBuilder } from '@elastic/esql-ast'; +import type { + ESQLAstItem, + ESQLFunction, + ESQLUnknownItem, + PromQLAstExpression, + PromQLEvaluation, + PromQLLabel, + PromQLLabelMap, + PromQLLabelMatchOperator, + PromQLLabelValue, + PromQLSelector, + PromQLStringLiteral, +} from '@elastic/esql-types'; + +export const expr = Builder.expression; +export const promqlExpr = PromQLBuilder.expression; + +export const unary = (name: string, arg: ESQLAstItem): ESQLFunction => + expr.func.node({ name, subtype: 'unary-expression', args: [arg] }); + +export const unknown = (): ESQLUnknownItem => ({ + ...Builder.parserFields({ incomplete: true }), + type: 'unknown', + name: 'unknown', +}); + +export const fromSources = () => + Builder.expression.query([ + Builder.command({ + name: 'from', + args: ['a', 'b', 'c'].map((name) => expr.source.index(name)), + }), + ]); + +export const fromSourceComponents = () => + Builder.expression.query([ + Builder.command({ + name: 'from', + args: [expr.source.index('b', 'a'), expr.source.index('c', undefined, 'd')], + }), + ]); + +export const rowNestedCall = () => + Builder.expression.query([ + Builder.command({ + name: 'row', + args: [ + expr.func.call('fn', [ + expr.literal.integer(1), + expr.literal.integer(2), + expr.literal.integer(3), + expr.func.call('gg', [expr.literal.integer(4), expr.literal.integer(5)]), + ]), + ], + }), + ]); + +export const rowWithMap = () => + Builder.expression.query([ + Builder.command({ + name: 'row', + args: [ + expr.func.call('fn', [ + expr.literal.boolean(true), + expr.map({ + entries: [ + expr.entry('foo', expr.literal.integer(1)), + expr.entry('bar', expr.literal.integer(2)), + expr.entry('baz', expr.literal.integer(3)), + ], + }), + ]), + ], + }), + ]); + +export const id = PromQLBuilder.identifier; + +export const time = promqlExpr.literal.time; + +export const str = (value: string): PromQLStringLiteral => + promqlExpr.literal.string(value, `"${value}"`); + +export const sel = ( + metric: string, + options: { + labelMap?: PromQLLabelMap; + duration?: PromQLAstExpression; + evaluation?: PromQLEvaluation; + } = {} +): PromQLSelector => promqlExpr.selector.node({ metric: id(metric), ...options }); + +export const label = ( + name: string, + operator: PromQLLabelMatchOperator, + value?: PromQLLabelValue +): PromQLLabel => PromQLBuilder.label(id(name), operator, value); diff --git a/packages/esql/src/ast/visitor/__tests__/commands.test.ts b/packages/esql-traversal/src/esql/visitor/__tests__/commands.test.ts similarity index 100% rename from packages/esql/src/ast/visitor/__tests__/commands.test.ts rename to packages/esql-traversal/src/esql/visitor/__tests__/commands.test.ts diff --git a/packages/esql/src/ast/visitor/__tests__/expressions.test.ts b/packages/esql-traversal/src/esql/visitor/__tests__/expressions.test.ts similarity index 100% rename from packages/esql/src/ast/visitor/__tests__/expressions.test.ts rename to packages/esql-traversal/src/esql/visitor/__tests__/expressions.test.ts diff --git a/packages/esql/src/ast/visitor/__tests__/visitor.test.ts b/packages/esql-traversal/src/esql/visitor/__tests__/visitor.test.ts similarity index 100% rename from packages/esql/src/ast/visitor/__tests__/visitor.test.ts rename to packages/esql-traversal/src/esql/visitor/__tests__/visitor.test.ts diff --git a/packages/esql-traversal/src/esql/walker/__tests__/walker.test.ts b/packages/esql-traversal/src/esql/walker/__tests__/walker.test.ts index ae5988bc..3268cf53 100644 --- a/packages/esql-traversal/src/esql/walker/__tests__/walker.test.ts +++ b/packages/esql-traversal/src/esql/walker/__tests__/walker.test.ts @@ -26,8 +26,7 @@ import type { ESQLStringLiteral, } from '@elastic/esql-types'; import { walk, Walker } from '../walker'; - -const { expression: expr } = Builder; +import { expr, unary, unknown } from '../../../__tests__/builders'; /** * The parser does not mint an `operator` identifier node for binary expressions @@ -38,15 +37,6 @@ const { expression: expr } = Builder; const binary = (name: string, args: ESQLAstItem[]): ESQLFunction => expr.func.node({ name, subtype: 'binary-expression', args }); -const unary = (name: string, arg: ESQLAstItem): ESQLFunction => - expr.func.node({ name, subtype: 'unary-expression', args: [arg] }); - -const unknown = (): ESQLUnknownItem => ({ - ...Builder.parserFields({ incomplete: true }), - type: 'unknown', - name: 'unknown', -}); - /** `TS index | EVAL a(b(c(foo)))` */ const tsEvalNestedCalls = () => expr.query([ diff --git a/packages/esql-traversal/src/esql/walker/__tests__/walker_abort.test.ts b/packages/esql-traversal/src/esql/walker/__tests__/walker_abort.test.ts index 85698ecb..3cbc7762 100644 --- a/packages/esql-traversal/src/esql/walker/__tests__/walker_abort.test.ts +++ b/packages/esql-traversal/src/esql/walker/__tests__/walker_abort.test.ts @@ -8,33 +8,13 @@ import { Builder } from '@elastic/esql-ast'; import type { ESQLIntegerLiteral } from '@elastic/esql-types'; import { Walker } from '../walker'; - -const { expression: expr } = Builder; - -/** `FROM a, b, c` */ -const fromSources = () => - Builder.expression.query([ - Builder.command({ - name: 'from', - args: ['a', 'b', 'c'].map((name) => expr.source.index(name)), - }), - ]); - -/** `ROW fn(1, 2, 3, gg(4, 5))` */ -const rowNestedCall = () => - Builder.expression.query([ - Builder.command({ - name: 'row', - args: [ - expr.func.call('fn', [ - expr.literal.integer(1), - expr.literal.integer(2), - expr.literal.integer(3), - expr.func.call('gg', [expr.literal.integer(4), expr.literal.integer(5)]), - ]), - ], - }), - ]); +import { + expr, + fromSourceComponents, + fromSources, + rowNestedCall, + rowWithMap, +} from '../../../__tests__/builders'; /** `FROM index METADATA a, b, c` */ const fromWithMetadata = () => @@ -53,26 +33,6 @@ const fromWithMetadata = () => }), ]); -/** `ROW fn(TRUE, { "foo": 1, "bar": 2, "baz": 3 })` */ -const rowWithMap = () => - Builder.expression.query([ - Builder.command({ - name: 'row', - args: [ - expr.func.call('fn', [ - expr.literal.boolean(true), - expr.map({ - entries: [ - expr.entry('foo', expr.literal.integer(1)), - expr.entry('bar', expr.literal.integer(2)), - expr.entry('baz', expr.literal.integer(3)), - ], - }), - ]), - ], - }), - ]); - /** `FROM index | LIMIT 123` */ const fromLimit = () => Builder.expression.query([ @@ -80,15 +40,6 @@ const fromLimit = () => Builder.command({ name: 'limit', args: [expr.literal.integer(123)] }), ]); -/** `FROM a:b, c::d` */ -const fromSourceComponents = () => - Builder.expression.query([ - Builder.command({ - name: 'from', - args: [expr.source.index('b', 'a'), expr.source.index('c', undefined, 'd')], - }), - ]); - describe('aborting traversal', () => { test('can abort traversal after second comand argument', () => { const ast = fromSources(); diff --git a/packages/esql-traversal/src/esql/walker/__tests__/walker_order.test.ts b/packages/esql-traversal/src/esql/walker/__tests__/walker_order.test.ts index 6f2125bc..62bc2afe 100644 --- a/packages/esql-traversal/src/esql/walker/__tests__/walker_order.test.ts +++ b/packages/esql-traversal/src/esql/walker/__tests__/walker_order.test.ts @@ -13,17 +13,7 @@ import type { ESQLStringLiteral, } from '@elastic/esql-types'; import { walk, Walker } from '../walker'; - -const { expression: expr } = Builder; - -/** `FROM a, b, c` */ -const fromSources = () => - Builder.expression.query([ - Builder.command({ - name: 'from', - args: ['a', 'b', 'c'].map((name) => expr.source.index(name)), - }), - ]); +import { expr, fromSources } from '../../../__tests__/builders'; /** `FROM index METADATA a, b, c` */ const fromWithMetadata = () => diff --git a/packages/esql-traversal/src/esql/walker/__tests__/walker_promql.test.ts b/packages/esql-traversal/src/esql/walker/__tests__/walker_promql.test.ts index e242ec26..f9311b6e 100644 --- a/packages/esql-traversal/src/esql/walker/__tests__/walker_promql.test.ts +++ b/packages/esql-traversal/src/esql/walker/__tests__/walker_promql.test.ts @@ -21,12 +21,9 @@ import type { PromQLBinaryExpression, PromQLLabelMap, PromQLLabel, - PromQLLabelMatchOperator, - PromQLLabelValue, PromQLIdentifier, PromQLLiteral, PromQLGrouping, - PromQLStringLiteral, PromQLSubquery, PromQLParens, PromQLUnaryExpression, @@ -37,25 +34,7 @@ import type { PromQLGroupModifier, } from '@elastic/esql-types'; import { Walker } from '../walker'; - -const { expression: expr } = PromQLBuilder; - -const id = PromQLBuilder.identifier; -const time = expr.literal.time; -const str = (value: string): PromQLStringLiteral => expr.literal.string(value, `"${value}"`); -const sel = ( - metric: string, - options: { - labelMap?: PromQLLabelMap; - duration?: PromQLAstExpression; - evaluation?: PromQLEvaluation; - } = {} -): PromQLSelector => expr.selector.node({ metric: id(metric), ...options }); -const label = ( - name: string, - operator: PromQLLabelMatchOperator, - value?: PromQLLabelValue -): PromQLLabel => PromQLBuilder.label(id(name), operator, value); +import { promqlExpr as expr, id, label, sel, str, time } from '../../../__tests__/builders'; /** Wraps an ES|QL `PROMQL` command query into a single-command ES|QL query. */ const promqlCommand = ( diff --git a/packages/esql-traversal/src/esql/walker/__tests__/walker_skip_children.test.ts b/packages/esql-traversal/src/esql/walker/__tests__/walker_skip_children.test.ts index ef8e91dd..a0d26275 100644 --- a/packages/esql-traversal/src/esql/walker/__tests__/walker_skip_children.test.ts +++ b/packages/esql-traversal/src/esql/walker/__tests__/walker_skip_children.test.ts @@ -8,33 +8,17 @@ import { Builder } from '@elastic/esql-ast'; import type { ESQLIntegerLiteral } from '@elastic/esql-types'; import { Walker } from '../walker'; - -const { expression: expr } = Builder; +import { + expr, + fromSourceComponents, + fromSources, + rowNestedCall, + rowWithMap, +} from '../../../__tests__/builders'; /** `FROM a, b, c` */ -const fromSources = () => - Builder.expression.query([ - Builder.command({ - name: 'from', - args: ['a', 'b', 'c'].map((name) => expr.source.index(name)), - }), - ]); /** `ROW fn(1, 2, 3, gg(4, 5))` */ -const rowNestedCall = () => - Builder.expression.query([ - Builder.command({ - name: 'row', - args: [ - expr.func.call('fn', [ - expr.literal.integer(1), - expr.literal.integer(2), - expr.literal.integer(3), - expr.func.call('gg', [expr.literal.integer(4), expr.literal.integer(5)]), - ]), - ], - }), - ]); /** `FROM index METADATA a, b, c` */ const fromWithMetadata = () => @@ -54,24 +38,6 @@ const fromWithMetadata = () => ]); /** `ROW fn(TRUE, { "foo": 1, "bar": 2, "baz": 3 })` */ -const rowWithMap = () => - Builder.expression.query([ - Builder.command({ - name: 'row', - args: [ - expr.func.call('fn', [ - expr.literal.boolean(true), - expr.map({ - entries: [ - expr.entry('foo', expr.literal.integer(1)), - expr.entry('bar', expr.literal.integer(2)), - expr.entry('baz', expr.literal.integer(3)), - ], - }), - ]), - ], - }), - ]); /** `FROM index | LIMIT 123` */ const fromLimit = () => @@ -81,13 +47,6 @@ const fromLimit = () => ]); /** `FROM a:b, c::d` */ -const fromSourceComponents = () => - Builder.expression.query([ - Builder.command({ - name: 'from', - args: [expr.source.index('b', 'a'), expr.source.index('c', undefined, 'd')], - }), - ]); describe('skipping children', () => { test('can skip command arguments', () => { diff --git a/packages/esql-traversal/src/esql/walker/__tests__/walker_statics.test.ts b/packages/esql-traversal/src/esql/walker/__tests__/walker_statics.test.ts index 9f5b8116..36ea72f9 100644 --- a/packages/esql-traversal/src/esql/walker/__tests__/walker_statics.test.ts +++ b/packages/esql-traversal/src/esql/walker/__tests__/walker_statics.test.ts @@ -28,8 +28,8 @@ import type { PromQLSelector, } from '@elastic/esql-types'; import { Walker } from '../walker'; +import { expr, unary } from '../../../__tests__/builders'; -const { expression: expr } = Builder; const { expression: pexpr } = PromQLBuilder; /** @@ -39,9 +39,6 @@ const { expression: pexpr } = PromQLBuilder; const binary = (name: string, left: ESQLAstItem, right: ESQLAstItem): ESQLFunction => expr.func.node({ name, subtype: 'binary-expression', args: [left, right] }); -const unary = (name: string, arg: ESQLAstItem): ESQLFunction => - expr.func.node({ name, subtype: 'unary-expression', args: [arg] }); - const promqlCommand = (query: PromQLAstQueryExpression, params?: ESQLMap): ESQLAstPromqlCommand => { const command = Builder.command({ name: 'promql' }) as ESQLAstPromqlCommand; From 43c00141c8412f50e95dcabfb0b8087340e3a038 Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Fri, 21 Aug 2026 16:53:32 +0200 Subject: [PATCH 08/24] test: migrate `Visitor` tests --- .../esql/visitor/__tests__/commands.test.ts | 95 +---- .../visitor/__tests__/expressions.test.ts | 72 +--- .../visitor/__tests__/fixtures_commands.ts | 342 ++++++++++++++++++ .../visitor/__tests__/fixtures_expressions.ts | 202 +++++++++++ .../visitor/__tests__/fixtures_visitor.ts | 89 +++++ .../esql/visitor/__tests__/visitor.test.ts | 38 +- 6 files changed, 679 insertions(+), 159 deletions(-) create mode 100644 packages/esql-traversal/src/esql/visitor/__tests__/fixtures_commands.ts create mode 100644 packages/esql-traversal/src/esql/visitor/__tests__/fixtures_expressions.ts create mode 100644 packages/esql-traversal/src/esql/visitor/__tests__/fixtures_visitor.ts diff --git a/packages/esql-traversal/src/esql/visitor/__tests__/commands.test.ts b/packages/esql-traversal/src/esql/visitor/__tests__/commands.test.ts index c07cd39a..caf1007e 100644 --- a/packages/esql-traversal/src/esql/visitor/__tests__/commands.test.ts +++ b/packages/esql-traversal/src/esql/visitor/__tests__/commands.test.ts @@ -5,15 +5,11 @@ * 2.0. */ -import { EsqlQuery } from '../../../composer/query'; -import { Visitor } from '@elastic/esql-traversal'; +import * as fixtures from './fixtures_commands'; +import { Visitor } from '../index'; test('"visitCommand" captures all non-captured commands', () => { - const { ast } = EsqlQuery.fromSrc(` - FROM index - | STATS 1, "str", [true], a = b BY field - | LIMIT 123 - `); + const ast = fixtures.fromStatsLimit().commands; const visitor = new Visitor() .on('visitStatsCommand', (ctx) => { return ''; @@ -30,12 +26,7 @@ test('"visitCommand" captures all non-captured commands', () => { }); test('can visit JOIN command', () => { - const { ast } = EsqlQuery.fromSrc(` - FROM index - | STATS 1, "str", [true], a = b BY field - | RIGHT JOIN abc ON xyz - | LIMIT 123 - `); + const ast = fixtures.fromStatsJoinLimit().commands; const visitor = new Visitor() .on('visitJoinCommand', (ctx) => { return `JOIN[type = ${ctx.node.commandType}]`; @@ -52,12 +43,7 @@ test('can visit JOIN command', () => { }); test('can visit JOIN command arguments', () => { - const { ast } = EsqlQuery.fromSrc(` - FROM index - | STATS 1, "str", [true], a = b BY field - | RIGHT JOIN abc ON xyz - | LIMIT 123 - `); + const ast = fixtures.fromStatsJoinLimit().commands; const visitor = new Visitor() .on('visitFunctionCallExpression', (ctx) => { if (ctx.node.subtype === 'binary-expression') { @@ -84,12 +70,7 @@ test('can visit JOIN command arguments', () => { }); test('can visit JOIN ON option', () => { - const { ast } = EsqlQuery.fromSrc(` - FROM index - | STATS 1, "str", [true], a = b BY field - | RIGHT JOIN abc ON xyz - | LIMIT 123 - `); + const ast = fixtures.fromStatsJoinLimit().commands; const visitor = new Visitor() .on('visitColumnExpression', (ctx) => { return ctx.node.name; @@ -115,12 +96,7 @@ test('can visit JOIN ON option', () => { }); test('can visit CHANGE_POINT command', () => { - const { ast } = EsqlQuery.fromSrc(` - FROM k8s - | STATS count=COUNT() BY @timestamp=BUCKET(@timestamp, 1 MINUTE) - | CHANGE_POINT count ON @timestamp AS type, pvalue - | LIMIT 123 - `); + const ast = fixtures.fromChangePoint().commands; const visitor = new Visitor() .on('visitExpression', (ctx) => { return null; @@ -140,10 +116,7 @@ test('can visit CHANGE_POINT command', () => { }); test('can visit REGISTERED_DOMAIN command', () => { - const { ast } = EsqlQuery.fromSrc(` - FROM index - | REGISTERED_DOMAIN parts = host - `); + const ast = fixtures.fromRegisteredDomain().commands; const visitor = new Visitor() .on('visitExpression', () => { return null; @@ -163,10 +136,7 @@ test('can visit REGISTERED_DOMAIN command', () => { }); test('can visit IP_LOCATION command', () => { - const { ast } = EsqlQuery.fromSrc(` - FROM index - | IP_LOCATION geo = client_ip - `); + const ast = fixtures.fromIpLocation().commands; const visitor = new Visitor() .on('visitExpression', () => { return null; @@ -186,11 +156,7 @@ test('can visit IP_LOCATION command', () => { }); test('can visit RERANK command', () => { - const { ast } = EsqlQuery.fromSrc(` - FROM movies - | RERANK "star wars" ON title=X(title, 2), description=X(description, 1.5) WITH {"inferenceId":"rerankerInferenceId", "scoreColumn":"rerank_score"} - | LIMIT 123 - `); + const ast = fixtures.fromRerankLimit().commands; const visitor = new Visitor() .on('visitLiteralExpression', (ctx) => { @@ -253,10 +219,7 @@ test('can visit RERANK command', () => { }); test('can visit COMPLETION command', () => { - const { ast } = EsqlQuery.fromSrc(` - FROM index - | COMPLETION "test" WITH inferenceId - `); + const ast = fixtures.fromCompletion().commands; const visitor = new Visitor() .on('visitExpression', (ctx) => { return null; @@ -276,10 +239,7 @@ test('can visit COMPLETION command', () => { }); test('can visit URI_PARTS command', () => { - const { ast } = EsqlQuery.fromSrc(` -FROM index -| URI_PARTS parts = url -`); + const ast = fixtures.fromUriParts().commands; const visitor = new Visitor() .on('visitExpression', (ctx) => { return null; @@ -299,10 +259,7 @@ FROM index }); test('can visit MMR command', () => { - const { ast } = EsqlQuery.fromSrc(` - FROM movies - | MMR [0.5, 0.4, 0.3, 0.2]::dense_vector ON genre LIMIT 10 WITH { "lambda": 0.5 } - `); + const ast = fixtures.fromMmr().commands; const visitor = new Visitor() .on('visitLiteralExpression', (ctx) => { return ctx.node.value; @@ -351,20 +308,7 @@ test('can visit MMR command', () => { }); test('can visit FROM command with complex subqueries', () => { - const { ast } = EsqlQuery.fromSrc(` - FROM index1, - (FROM index2 - | WHERE a > 10 - | EVAL b = a * 2 - | STATS cnt = COUNT(*) BY c - | SORT cnt desc - | LIMIT 10), - index3, - (FROM index4 | STATS count(*)) - | WHERE d > 10 - | STATS max = max(*) BY e - | SORT max desc - `); + const ast = fixtures.fromSubqueries().commands; const visitor = new Visitor() .on('visitParensExpression', () => { return 'SUBQUERY'; @@ -387,11 +331,7 @@ test('can visit FROM command with complex subqueries', () => { }); test('can visit DEDUP command via specific visitor', () => { - const { ast } = EsqlQuery.fromSrc(` - FROM index - | DEDUP - | LIMIT 10 - `); + const ast = fixtures.fromDedupLimit().commands; const visitor = new Visitor() .on('visitDedupCommand', () => { return 'DEDUP'; @@ -408,10 +348,7 @@ test('can visit DEDUP command via specific visitor', () => { }); test('"visitCommand" captures DEDUP when visitDedupCommand is not registered', () => { - const { ast } = EsqlQuery.fromSrc(` - FROM index - | DEDUP - `); + const ast = fixtures.fromDedup().commands; const visitor = new Visitor() .on('visitCommand', (ctx) => { return ctx.name(); diff --git a/packages/esql-traversal/src/esql/visitor/__tests__/expressions.test.ts b/packages/esql-traversal/src/esql/visitor/__tests__/expressions.test.ts index 1572e335..de1431f4 100644 --- a/packages/esql-traversal/src/esql/visitor/__tests__/expressions.test.ts +++ b/packages/esql-traversal/src/esql/visitor/__tests__/expressions.test.ts @@ -5,15 +5,11 @@ * 2.0. */ -import { parse } from '../../../parser'; -import { Visitor } from '@elastic/esql-traversal'; +import { Visitor } from '../index'; +import * as fixtures from './fixtures_expressions'; test('"visitExpression" captures all non-captured expressions', () => { - const { ast } = parse(` - FROM index - | STATS 1, "str", [true], a = b BY field - | LIMIT 123 - `); + const ast = fixtures.fromStatsMixedByLimit().commands; const visitor = new Visitor() .on('visitExpression', (ctx) => { return ''; @@ -34,11 +30,7 @@ test('"visitExpression" captures all non-captured expressions', () => { test('can terminate walk early, does not visit all literals', () => { const numbers: number[] = []; - const { ast } = parse(` - FROM index - | STATS 0, 1, 2, 3 - | LIMIT 123 - `); + const ast = fixtures.fromStatsNumbersLimit().commands; const result = new Visitor() .on('visitExpression', (ctx) => { return 0; @@ -60,9 +52,7 @@ test('can terminate walk early, does not visit all literals', () => { }); test('"visitColumnExpression" takes over all column visits', () => { - const { ast } = parse(` - FROM index | STATS a - `); + const ast = fixtures.fromStatsColumn().commands; const visitor = new Visitor() .on('visitColumnExpression', (ctx) => { return ''; @@ -83,11 +73,7 @@ test('"visitColumnExpression" takes over all column visits', () => { }); test('"visitSourceExpression" takes over all source visits', () => { - const { ast } = parse(` - FROM index - | STATS 1, "str", [true], a = b BY field - | LIMIT 123 - `); + const ast = fixtures.fromStatsMixedByLimit().commands; const visitor = new Visitor() .on('visitSourceExpression', (ctx) => { return ''; @@ -108,11 +94,7 @@ test('"visitSourceExpression" takes over all source visits', () => { }); test('"visitFunctionCallExpression" takes over all literal visits', () => { - const { ast } = parse(` - FROM index - | STATS 1, "str", [true], a = b BY field - | LIMIT 123 - `); + const ast = fixtures.fromStatsMixedByLimit().commands; const visitor = new Visitor() .on('visitFunctionCallExpression', (ctx) => { return ''; @@ -133,9 +115,7 @@ test('"visitFunctionCallExpression" takes over all literal visits', () => { }); test('"visitMapExpression" takes over expression visiting', () => { - const { ast } = parse(` - ROW fn(1, {"a": 2}) - `); + const ast = fixtures.rowFnWithMap().commands; const visitor = new Visitor() .on('visitMapExpression', (ctx) => { return ''; @@ -159,9 +139,7 @@ test('"visitMapExpression" takes over expression visiting', () => { }); test('"visitMapEntryExpression" takes over expression visiting', () => { - const { ast } = parse(` - ROW fn(1, {"a": 2, "b": "3"}) - `); + const ast = fixtures.rowFnWithTwoEntryMap().commands; const visitor = new Visitor() .on('visitMapEntryExpression', (ctx) => { return ''; @@ -188,11 +166,7 @@ test('"visitMapEntryExpression" takes over expression visiting', () => { }); test('"visitLiteral" takes over all literal visits', () => { - const { ast } = parse(` - FROM index - | STATS 1, "str", [true], a = b BY field - | LIMIT 123 - `); + const ast = fixtures.fromStatsMixedByLimit().commands; const visitor = new Visitor() .on('visitLiteralExpression', (ctx) => { return ''; @@ -213,11 +187,7 @@ test('"visitLiteral" takes over all literal visits', () => { }); test('"visitExpression" does visit WHERE clause args', () => { - const { ast } = parse(` - FROM index - | STATS 1 WHERE 2 - | LIMIT 123 - `); + const ast = fixtures.fromStatsWhereLimit().commands; const visitor = new Visitor() .on('visitLiteralExpression', (ctx) => { return ''; @@ -241,10 +211,7 @@ test('"visitExpression" does visit WHERE clause args', () => { }); test('"visitExpression" does visit identifier nodes', () => { - const { ast } = parse(` - FROM index - | RIGHT JOIN a ON c - `); + const ast = fixtures.fromRightJoin().commands; const expressions: string[] = []; new Visitor() .on('visitExpression', (ctx) => { @@ -263,20 +230,7 @@ test('"visitExpression" does visit identifier nodes', () => { }); test('"visitParensExpression" can traverse complex subqueries with processing', () => { - const { ast } = parse(` - FROM index1, - (FROM index2 - | WHERE a > 10 - | EVAL b = a * 2 - | STATS cnt = COUNT(*) BY c - | SORT cnt desc - | LIMIT 10), - index3, - (FROM index4 | STATS count(*)) - | WHERE d > 10 - | STATS max = max(*) BY e - | SORT max desc - `); + const ast = fixtures.fromSubqueriesWhereStatsSort().commands; const visitor = new Visitor() .on('visitParensExpression', (ctx) => { const child = ctx.visitChild(undefined); diff --git a/packages/esql-traversal/src/esql/visitor/__tests__/fixtures_commands.ts b/packages/esql-traversal/src/esql/visitor/__tests__/fixtures_commands.ts new file mode 100644 index 00000000..f60c2551 --- /dev/null +++ b/packages/esql-traversal/src/esql/visitor/__tests__/fixtures_commands.ts @@ -0,0 +1,342 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Builder } from '@elastic/esql-ast'; +import type { + ESQLAstChangePointCommand, + ESQLAstCompletionCommand, + ESQLAstIpLocationCommand, + ESQLAstItem, + ESQLAstJoinCommand, + ESQLAstMmrCommand, + ESQLAstQueryExpression, + ESQLAstRegisteredDomainCommand, + ESQLAstRerankCommand, + ESQLAstUriPartsCommand, + ESQLColumn, + ESQLFunction, +} from '@elastic/esql-types'; +import { expr } from '../../../__tests__/builders'; + +const from = (index = 'index') => + Builder.command({ name: 'from', args: [expr.source.index(index)] }); + +const limit = (value: number) => + Builder.command({ name: 'limit', args: [expr.literal.integer(value)] }); + +const call = (name: string, spelling: string, args: ESQLAstItem[]): ESQLFunction => + expr.func.node({ + name, + operator: Builder.identifier(spelling), + args, + subtype: 'variadic-call', + }); + +const binary = (name: string, args: [ESQLAstItem, ESQLAstItem]): ESQLFunction => + expr.func.node({ name, args, subtype: 'binary-expression' }); + +const assign = (target: ESQLColumn, value: ESQLAstItem): ESQLFunction => + binary('=', [target, value]); + +// STATS 1, "str", [true], a = b BY field +const statsCommand = () => + Builder.command({ + name: 'stats', + args: [ + expr.literal.integer(1), + expr.literal.string('str'), + expr.list.literal({ values: [expr.literal.boolean(true)] }), + assign(expr.column('a'), [expr.column('b')]), + Builder.option({ name: 'by', args: [expr.column('field')] }), + ], + }); + +// FROM index | STATS 1, "str", [true], a = b BY field | LIMIT 123 +export const fromStatsLimit = (): ESQLAstQueryExpression => + Builder.expression.query([from(), statsCommand(), limit(123)]); + +/** + * `FROM index | STATS 1, "str", [true], a = b BY field | RIGHT JOIN abc ON xyz + * | LIMIT 123` + */ +export const fromStatsJoinLimit = (): ESQLAstQueryExpression => { + const join: ESQLAstJoinCommand = { + ...Builder.command({ + name: 'join', + args: [expr.source.index('abc'), Builder.option({ name: 'on', args: [expr.column('xyz')] })], + }), + commandType: 'right', + }; + + return Builder.expression.query([from(), statsCommand(), join, limit(123)]); +}; + +/** + * `FROM k8s | STATS count=COUNT() BY @timestamp=BUCKET(@timestamp, 1 MINUTE) + * | CHANGE_POINT count ON @timestamp AS type, pvalue | LIMIT 123` + */ +export const fromChangePoint = (): ESQLAstQueryExpression => { + const stats = Builder.command({ + name: 'stats', + args: [ + assign(expr.column('count'), [call('count', 'COUNT', [])]), + Builder.option({ + name: 'by', + args: [ + assign(expr.column('@timestamp'), [ + call('bucket', 'BUCKET', [ + expr.column('@timestamp'), + expr.literal.timespan(1, 'MINUTE'), + ]), + ]), + ], + }), + ], + }); + + const value = expr.column('count'); + const key = expr.column('@timestamp'); + const type = expr.column('type'); + const pvalue = expr.column('pvalue'); + + const changePoint: ESQLAstChangePointCommand = { + ...Builder.command({ + name: 'change_point', + args: [ + value, + Builder.option({ name: 'on', args: [key] }), + Builder.option({ name: 'as', args: [type, pvalue] }), + ], + }), + value, + key, + target: { type, pvalue }, + }; + + return Builder.expression.query([from('k8s'), stats, changePoint, limit(123)]); +}; + +const assignmentCommand = ( + name: Name, + targetName: string, + expressionName: string +) => { + const targetField = expr.column(targetName); + const expression = expr.column(expressionName); + + return { + ...Builder.command({ + name, + args: [binary('=', [targetField, expression])], + }), + targetField, + expression, + }; +}; + +// FROM index | REGISTERED_DOMAIN parts = host +export const fromRegisteredDomain = (): ESQLAstQueryExpression => { + const command: ESQLAstRegisteredDomainCommand = assignmentCommand( + 'registered_domain', + 'parts', + 'host' + ); + + return Builder.expression.query([from(), command]); +}; + +// FROM index | IP_LOCATION geo = client_ip +export const fromIpLocation = (): ESQLAstQueryExpression => { + const command: ESQLAstIpLocationCommand = assignmentCommand('ip_location', 'geo', 'client_ip'); + + return Builder.expression.query([from(), command]); +}; + +// FROM index | URI_PARTS parts = url +export const fromUriParts = (): ESQLAstQueryExpression => { + const command: ESQLAstUriPartsCommand = assignmentCommand('uri_parts', 'parts', 'url'); + + return Builder.expression.query([from(), command]); +}; + +const missingInferenceId = () => + expr.literal.string('', { name: 'inferenceId' }, { incomplete: true }); + +/** + * `FROM movies | RERANK "star wars" ON title=X(title, 2), + * description=X(description, 1.5) WITH {"inferenceId":"rerankerInferenceId", + * "scoreColumn":"rerank_score"} | LIMIT 123` + */ +export const fromRerankLimit = (): ESQLAstQueryExpression => { + const query = expr.literal.string('star wars'); + const fields = [ + assign(expr.column('title'), [call('x', 'X', [expr.column('title'), expr.literal.integer(2)])]), + assign(expr.column('description'), [ + call('x', 'X', [expr.column('description'), expr.literal.decimal(1.5)]), + ]), + ]; + + const rerank: ESQLAstRerankCommand = { + ...Builder.command({ + name: 'rerank', + args: [ + query, + Builder.option({ name: 'on', args: [...fields] }), + Builder.option({ + name: 'with', + args: [ + expr.map({ + entries: [ + expr.entry('inferenceId', expr.literal.string('rerankerInferenceId')), + expr.entry('scoreColumn', expr.literal.string('rerank_score')), + ], + }), + ], + }), + ], + }), + query, + fields, + inferenceId: missingInferenceId(), + }; + + return Builder.expression.query([from('movies'), rerank, limit(123)]); +}; + +// FROM index | COMPLETION "test" WITH inferenceId +export const fromCompletion = (): ESQLAstQueryExpression => { + const prompt = expr.literal.string('test'); + const completion: ESQLAstCompletionCommand = { + ...Builder.command({ + name: 'completion', + args: [ + prompt, + Builder.option( + { name: 'with', args: [expr.map({}, { incomplete: true })] }, + { incomplete: true } + ), + ], + }), + prompt, + inferenceId: missingInferenceId(), + }; + + return Builder.expression.query([from(), completion]); +}; + +// FROM movies | MMR [0.5, 0.4, 0.3, 0.2]::dense_vector ON genre LIMIT 10 WITH { "lambda": 0.5 } +export const fromMmr = (): ESQLAstQueryExpression => { + const queryVector = expr.inlineCast({ + castType: 'dense_vector', + value: expr.list.literal({ + values: [0.5, 0.4, 0.3, 0.2].map((value) => expr.literal.decimal(value)), + }), + }); + const diversifyField = Builder.identifier('genre'); + const genre = expr.column({ args: [diversifyField] }); + const limitValue = expr.literal.integer(10); + const namedParameters = expr.map({ + entries: [expr.entry('lambda', expr.literal.decimal(0.5))], + }); + + const mmr: ESQLAstMmrCommand = { + ...Builder.command({ + name: 'mmr', + args: [ + queryVector, + Builder.option({ name: 'on', args: [genre] }), + Builder.option({ name: 'limit', args: [limitValue] }), + Builder.option({ name: 'with', args: [namedParameters] }), + ], + }), + queryVector, + diversifyField, + limit: limitValue, + namedParameters, + }; + + return Builder.expression.query([from('movies'), mmr]); +}; + +/** + * `FROM index1, + * (FROM index2 | WHERE a > 10 | EVAL b = a * 2 | STATS cnt = COUNT(*) BY c | SORT cnt desc | LIMIT 10), + * index3, + * (FROM index4 | STATS count(*)) + * | WHERE d > 10 + * | STATS max = max(*) BY e + * | SORT max desc` + */ +export const fromSubqueries = (): ESQLAstQueryExpression => { + const subquery1 = expr.parens( + Builder.expression.query([ + from('index2'), + Builder.command({ + name: 'where', + args: [binary('>', [expr.column('a'), expr.literal.integer(10)])], + }), + Builder.command({ + name: 'eval', + args: [ + assign(expr.column('b'), [binary('*', [expr.column('a'), expr.literal.integer(2)])]), + ], + }), + Builder.command({ + name: 'stats', + args: [ + assign(expr.column('cnt'), [call('count', 'COUNT', [expr.column('*')])]), + Builder.option({ name: 'by', args: [expr.column('c')] }), + ], + }), + Builder.command({ + name: 'sort', + args: [expr.order(expr.column('cnt'), { order: 'DESC', nulls: '' })], + }), + limit(10), + ]) + ); + + const subquery2 = expr.parens( + Builder.expression.query([ + from('index4'), + Builder.command({ + name: 'stats', + args: [call('count', 'count', [expr.column('*')])], + }), + ]) + ); + + return Builder.expression.query([ + Builder.command({ + name: 'from', + args: [expr.source.index('index1'), subquery1, expr.source.index('index3'), subquery2], + }), + Builder.command({ + name: 'where', + args: [binary('>', [expr.column('d'), expr.literal.integer(10)])], + }), + Builder.command({ + name: 'stats', + args: [ + assign(expr.column('max'), [call('max', 'max', [expr.column('*')])]), + Builder.option({ name: 'by', args: [expr.column('e')] }), + ], + }), + Builder.command({ + name: 'sort', + args: [expr.order(expr.column('max'), { order: 'DESC', nulls: '' })], + }), + ]); +}; + +// FROM index | DEDUP | LIMIT 10 +export const fromDedupLimit = (): ESQLAstQueryExpression => + Builder.expression.query([from(), Builder.command({ name: 'dedup' }), limit(10)]); + +// FROM index | DEDUP +export const fromDedup = (): ESQLAstQueryExpression => + Builder.expression.query([from(), Builder.command({ name: 'dedup' })]); diff --git a/packages/esql-traversal/src/esql/visitor/__tests__/fixtures_expressions.ts b/packages/esql-traversal/src/esql/visitor/__tests__/fixtures_expressions.ts new file mode 100644 index 00000000..aabc253d --- /dev/null +++ b/packages/esql-traversal/src/esql/visitor/__tests__/fixtures_expressions.ts @@ -0,0 +1,202 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * Query fixtures for the expression visitor tests, built with {@link Builder} + * rather than parsed, since the parser sits above this package in the + * dependency graph. The comment above each fixture is the ES|QL text it stands + * in for. + */ + +import { Builder } from '@elastic/esql-ast'; +import type { ESQLAstItem, ESQLAstQueryExpression, ESQLFunction } from '@elastic/esql-types'; +import { expr } from '../../../__tests__/builders'; + +const binary = (name: string, args: [left: ESQLAstItem, right: ESQLAstItem]): ESQLFunction => + expr.func.node({ name, subtype: 'binary-expression', args }); + +const assign = (target: string, value: ESQLAstItem): ESQLFunction => + binary('=', [expr.column(target), [value]]); + +const from = (...indices: string[]) => + Builder.command({ name: 'from', args: indices.map((index) => expr.source.index(index)) }); + +const limit = (value: number) => + Builder.command({ name: 'limit', args: [expr.literal.integer(value)] }); + +const by = (column: string) => Builder.option({ name: 'by', args: [expr.column(column)] }); + +const sortDesc = (column: string) => + Builder.command({ + name: 'sort', + args: [expr.order(expr.column(column), { order: 'DESC', nulls: '' })], + }); + +// FROM index | STATS 1, "str", [true], a = b BY field | LIMIT 123 +export const fromStatsMixedByLimit = (): ESQLAstQueryExpression => + Builder.expression.query([ + from('index'), + Builder.command({ + name: 'stats', + args: [ + expr.literal.integer(1), + expr.literal.string('str'), + expr.list.literal({ values: [expr.literal.boolean(true)] }), + assign('a', expr.column('b')), + by('field'), + ], + }), + limit(123), + ]); + +// FROM index | STATS 0, 1, 2, 3 | LIMIT 123 +export const fromStatsNumbersLimit = (): ESQLAstQueryExpression => + Builder.expression.query([ + from('index'), + Builder.command({ + name: 'stats', + args: [0, 1, 2, 3].map((value) => expr.literal.integer(value)), + }), + limit(123), + ]); + +// FROM index | STATS a +export const fromStatsColumn = (): ESQLAstQueryExpression => + Builder.expression.query([ + from('index'), + Builder.command({ name: 'stats', args: [expr.column('a')] }), + ]); + +// ROW fn(1, {"a": 2}) +export const rowFnWithMap = (): ESQLAstQueryExpression => + Builder.expression.query([ + Builder.command({ + name: 'row', + args: [ + expr.func.call('fn', [ + expr.literal.integer(1), + expr.map({ entries: [expr.entry('a', expr.literal.integer(2))] }), + ]), + ], + }), + ]); + +// ROW fn(1, {"a": 2, "b": "3"}) +export const rowFnWithTwoEntryMap = (): ESQLAstQueryExpression => + Builder.expression.query([ + Builder.command({ + name: 'row', + args: [ + expr.func.call('fn', [ + expr.literal.integer(1), + expr.map({ + entries: [ + expr.entry('a', expr.literal.integer(2)), + expr.entry('b', expr.literal.string('3')), + ], + }), + ]), + ], + }), + ]); + +// FROM index | STATS 1 WHERE 2 | LIMIT 123 +export const fromStatsWhereLimit = (): ESQLAstQueryExpression => + Builder.expression.query([ + from('index'), + Builder.command({ + name: 'stats', + args: [expr.where([expr.literal.integer(1), expr.literal.integer(2)])], + }), + limit(123), + ]); + +// FROM index | RIGHT JOIN a ON c +export const fromRightJoin = (): ESQLAstQueryExpression => + Builder.expression.query([ + from('index'), + Builder.command({ + name: 'join', + commandType: 'right', + args: [expr.source.index('a'), Builder.option({ name: 'on', args: [expr.column('c')] })], + }), + ]); + +/** + * ``` + * FROM index1, + * (FROM index2 + * | WHERE a > 10 + * | EVAL b = a * 2 + * | STATS cnt = COUNT(*) BY c + * | SORT cnt desc + * | LIMIT 10), + * index3, + * (FROM index4 | STATS count(*)) + * | WHERE d > 10 + * | STATS max = max(*) BY e + * | SORT max desc + * ``` + */ +export const fromSubqueriesWhereStatsSort = (): ESQLAstQueryExpression => + Builder.expression.query([ + Builder.command({ + name: 'from', + args: [ + expr.source.index('index1'), + expr.parens( + Builder.expression.query([ + from('index2'), + Builder.command({ + name: 'where', + args: [binary('>', [expr.column('a'), expr.literal.integer(10)])], + }), + Builder.command({ + name: 'eval', + args: [assign('b', binary('*', [expr.column('a'), expr.literal.integer(2)]))], + }), + Builder.command({ + name: 'stats', + args: [ + assign( + 'cnt', + expr.func.node({ + name: 'count', + subtype: 'variadic-call', + operator: Builder.identifier('COUNT'), + args: [expr.column('*')], + }) + ), + by('c'), + ], + }), + sortDesc('cnt'), + limit(10), + ]) + ), + expr.source.index('index3'), + expr.parens( + Builder.expression.query([ + from('index4'), + Builder.command({ + name: 'stats', + args: [expr.func.call('count', [expr.column('*')])], + }), + ]) + ), + ], + }), + Builder.command({ + name: 'where', + args: [binary('>', [expr.column('d'), expr.literal.integer(10)])], + }), + Builder.command({ + name: 'stats', + args: [assign('max', expr.func.call('max', [expr.column('*')])), by('e')], + }), + sortDesc('max'), + ]); diff --git a/packages/esql-traversal/src/esql/visitor/__tests__/fixtures_visitor.ts b/packages/esql-traversal/src/esql/visitor/__tests__/fixtures_visitor.ts new file mode 100644 index 00000000..5032a98f --- /dev/null +++ b/packages/esql-traversal/src/esql/visitor/__tests__/fixtures_visitor.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Builder } from '@elastic/esql-ast'; +import type { ESQLAstExpression, ESQLAstQueryExpression } from '@elastic/esql-types'; +import { expr } from '../../../__tests__/builders'; + +const set = (name: string, value: ESQLAstExpression) => + Builder.header.command.set([expr.func.binary('=', [Builder.identifier({ name }), value])]); + +const from = (index = 'index') => + Builder.command({ name: 'from', args: [expr.source.index(index)] }); + +const limit = (value: number) => + Builder.command({ name: 'limit', args: [expr.literal.integer(value)] }); + +// FROM index | LIMIT 123 +export const fromLimit = (): ESQLAstQueryExpression => + Builder.expression.query([from(), limit(123)]); + +// FROM index | FORK (WHERE 1) (WHERE 2) +export const fromFork = (): ESQLAstQueryExpression => + Builder.expression.query([ + from(), + Builder.command({ + name: 'fork', + args: [1, 2].map((n) => + expr.parens( + Builder.expression.query([ + Builder.command({ name: 'where', args: [expr.literal.integer(n)] }), + ]) + ) + ), + }), + ]); + +// FROM index | SORT asfd | WHERE 1 | ENRICH adsf | LIMIT 123 +export const fromSortWhereEnrichLimit = (): ESQLAstQueryExpression => + Builder.expression.query([ + from(), + Builder.command({ name: 'sort', args: [expr.column('asfd')] }), + Builder.command({ name: 'where', args: [expr.literal.integer(1)] }), + Builder.command({ + name: 'enrich', + args: [expr.source.node({ sourceType: 'policy', index: 'adsf' })], + }), + limit(123), + ]); + +// SET timeout = "30s"; FROM index | LIMIT 10 +export const setTimeoutFromLimit = (): ESQLAstQueryExpression => + Builder.expression.query([from(), limit(10)], undefined, [ + set('timeout', expr.literal.string('30s')), + ]); + +// SET timeout = "30s"; FROM index +export const setTimeoutFrom = (): ESQLAstQueryExpression => + Builder.expression.query([from()], undefined, [set('timeout', expr.literal.string('30s'))]); + +// SET a = 1; FROM index | LIMIT 10 +export const setOneFromLimit = (): ESQLAstQueryExpression => + Builder.expression.query([from(), limit(10)], undefined, [set('a', expr.literal.integer(1))]); + +// SET a = 1; SET b = 2; FROM index +export const setTwoFrom = (): ESQLAstQueryExpression => + Builder.expression.query([from()], undefined, [ + set('a', expr.literal.integer(1)), + set('b', expr.literal.integer(2)), + ]); + +// SET a = 1; SET b = 2; SET c = 3; FROM index +export const setThreeFrom = (): ESQLAstQueryExpression => + Builder.expression.query([from()], undefined, [ + set('a', expr.literal.integer(1)), + set('b', expr.literal.integer(2)), + set('c', expr.literal.integer(3)), + ]); + +// SET a = 1; SET b = "value"; SET c = true; FROM index +export const setMixedFrom = (): ESQLAstQueryExpression => + Builder.expression.query([from()], undefined, [ + set('a', expr.literal.integer(1)), + set('b', expr.literal.string('value')), + set('c', expr.literal.boolean(true)), + ]); diff --git a/packages/esql-traversal/src/esql/visitor/__tests__/visitor.test.ts b/packages/esql-traversal/src/esql/visitor/__tests__/visitor.test.ts index b6444821..ae062aed 100644 --- a/packages/esql-traversal/src/esql/visitor/__tests__/visitor.test.ts +++ b/packages/esql-traversal/src/esql/visitor/__tests__/visitor.test.ts @@ -5,13 +5,9 @@ * 2.0. */ -import { parse } from '../../../parser'; +import * as fixtures from './fixtures_visitor'; import type { ESQLAstForkCommand } from '@elastic/esql-types'; -import { - CommandVisitorContext, - WhereCommandVisitorContext, - Visitor, -} from '@elastic/esql-traversal'; +import { CommandVisitorContext, WhereCommandVisitorContext, Visitor } from '../index'; test('can collect all command names in type safe way', () => { const visitor = new Visitor() @@ -26,7 +22,7 @@ test('can collect all command names in type safe way', () => { return cmds; }); - const { ast } = parse('FROM index | LIMIT 123'); + const ast = fixtures.fromLimit().commands; const res = visitor.visitQuery(ast); expect(res).toEqual(['from', 'limit']); @@ -45,14 +41,14 @@ test('can pass inputs to visitors', () => { return cmds; }); - const { ast } = parse('FROM index | LIMIT 123'); + const ast = fixtures.fromLimit().commands; const res = visitor.visitQuery(ast); expect(res).toEqual(['pfx:from', 'pfx:limit']); }); test('a query can have a parent fork command', () => { - const { ast } = parse('FROM index | FORK (WHERE 1) (WHERE 2)'); + const ast = fixtures.fromFork().commands; let parentCount = 0; new Visitor() @@ -78,7 +74,7 @@ test('a query can have a parent fork command', () => { }); test('can specify specific visitors for commands', () => { - const { ast } = parse('FROM index | SORT asfd | WHERE 1 | ENRICH adsf | LIMIT 123'); + const ast = fixtures.fromSortWhereEnrichLimit().commands; const res = new Visitor() .on('visitWhereCommand', () => 'where') .on('visitSortCommand', () => 'sort') @@ -91,7 +87,7 @@ test('can specify specific visitors for commands', () => { }); test('a command can access parent query node', () => { - const { root } = parse('FROM index | SORT asfd | WHERE 1 | ENRICH adsf | LIMIT 123'); + const root = fixtures.fromSortWhereEnrichLimit(); new Visitor() .on('visitWhereCommand', (ctx) => { if (ctx.parent!.node !== root) { @@ -108,7 +104,7 @@ test('a command can access parent query node', () => { }); test('specific commands receive specific visitor contexts', () => { - const { root } = parse('FROM index | SORT asfd | WHERE 1 | ENRICH adsf | LIMIT 123'); + const root = fixtures.fromSortWhereEnrichLimit(); new Visitor() .on('visitWhereCommand', (ctx) => { @@ -142,7 +138,7 @@ test('specific commands receive specific visitor contexts', () => { describe('header commands', () => { test('can visit header commands', () => { - const { root } = parse('SET timeout = "30s"; FROM index | LIMIT 10'); + const root = fixtures.setTimeoutFromLimit(); const headerNames: string[] = []; new Visitor() @@ -160,7 +156,7 @@ describe('header commands', () => { }); test('can visit multiple header commands', () => { - const { root } = parse('SET a = 1; SET b = 2; SET c = 3; FROM index'); + const root = fixtures.setThreeFrom(); const headerNames: string[] = []; new Visitor() @@ -178,7 +174,7 @@ describe('header commands', () => { }); test('can visit header command arguments', () => { - const { root } = parse('SET timeout = "30s"; FROM index'); + const root = fixtures.setTimeoutFrom(); const identifiers: string[] = []; const literals: string[] = []; @@ -219,7 +215,7 @@ describe('header commands', () => { }); test('header commands are visited before regular commands', () => { - const { root } = parse('SET a = 1; FROM index | LIMIT 10'); + const root = fixtures.setOneFromLimit(); const visitOrder: string[] = []; new Visitor() @@ -243,7 +239,7 @@ describe('header commands', () => { }); test('can iterate through header commands', () => { - const { root } = parse('SET a = 1; SET b = 2; FROM index'); + const root = fixtures.setTwoFrom(); const headerCommandCount = [ ...new Visitor().on('visitQuery', (ctx) => ctx.headerCommands()).visitQuery(root), ].length; @@ -252,7 +248,7 @@ describe('header commands', () => { }); test('header command context has correct parent', () => { - const { root } = parse('SET timeout = "30s"; FROM index'); + const root = fixtures.setTimeoutFrom(); new Visitor() .on('visitHeaderCommand', (ctx) => { @@ -269,7 +265,7 @@ describe('header commands', () => { }); test('can visit header command directly', () => { - const { root } = parse('SET timeout = "30s"; FROM index'); + const root = fixtures.setTimeoutFrom(); const headerCommand = root.header![0]; const result = new Visitor() @@ -282,7 +278,7 @@ describe('header commands', () => { }); test('header commands with various value types', () => { - const { root } = parse('SET a = 1; SET b = "value"; SET c = true; FROM index'); + const root = fixtures.setMixedFrom(); const literals: (string | number | boolean)[] = []; new Visitor() @@ -316,7 +312,7 @@ describe('header commands', () => { }); test('can return values from header command visitor', () => { - const { root } = parse('SET a = 1; SET b = 2; FROM index'); + const root = fixtures.setTwoFrom(); const results = new Visitor() .on('visitHeaderCommand', (ctx) => { From 3fa2277b9ac5e5faa7be64c1d35348e7eea54ee2 Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Fri, 21 Aug 2026 16:55:19 +0200 Subject: [PATCH 09/24] chore: import `Visitor` from the `*-traversal` package --- packages/esql/src/ast/index.ts | 1 - packages/esql/src/ast/mutate/commands/from/metadata.ts | 3 +-- packages/esql/src/ast/mutate/commands/from/sources.ts | 2 +- packages/esql/src/ast/mutate/commands/sort/index.ts | 2 +- packages/esql/src/ast/mutate/generic/commands/args/index.ts | 2 +- packages/esql/src/ast/mutate/generic/commands/index.ts | 2 +- .../esql/src/ast/mutate/generic/commands/options/index.ts | 2 +- packages/esql/src/composer/composer_query.ts | 3 +-- packages/esql/src/pretty_print/basic_pretty_printer.ts | 4 +--- .../esql/src/pretty_print/wrapping_pretty_printer/printer.ts | 2 +- 10 files changed, 9 insertions(+), 14 deletions(-) diff --git a/packages/esql/src/ast/index.ts b/packages/esql/src/ast/index.ts index db05fe8e..71ae56ee 100644 --- a/packages/esql/src/ast/index.ts +++ b/packages/esql/src/ast/index.ts @@ -12,4 +12,3 @@ export * from './grouping'; // AST manipulation tools export * as mutate from './mutate'; -export * from './visitor'; diff --git a/packages/esql/src/ast/mutate/commands/from/metadata.ts b/packages/esql/src/ast/mutate/commands/from/metadata.ts index 6afed1c2..1cfd89f7 100644 --- a/packages/esql/src/ast/mutate/commands/from/metadata.ts +++ b/packages/esql/src/ast/mutate/commands/from/metadata.ts @@ -5,14 +5,13 @@ * 2.0. */ -import { Walker } from '@elastic/esql-traversal'; +import { Walker, Visitor } from '@elastic/esql-traversal'; import type { ESQLAstQueryExpression, ESQLColumn, ESQLCommand, ESQLCommandOption, } from '../../../../types'; -import { Visitor } from '../../../visitor'; import { cmpArr, findByPredicate } from '../../util'; import * as generic from '../../generic'; import { Builder } from '@elastic/esql-ast'; diff --git a/packages/esql/src/ast/mutate/commands/from/sources.ts b/packages/esql/src/ast/mutate/commands/from/sources.ts index 3df5be77..a3e33022 100644 --- a/packages/esql/src/ast/mutate/commands/from/sources.ts +++ b/packages/esql/src/ast/mutate/commands/from/sources.ts @@ -7,7 +7,7 @@ import { Builder, isSubQuery } from '@elastic/esql-ast'; import type { ESQLAstQueryExpression, ESQLCommand, ESQLSource } from '../../../../types'; -import { Visitor } from '../../../visitor'; +import { Visitor } from '@elastic/esql-traversal'; import * as generic from '../../generic'; import * as util from '../../util'; import type { Predicate } from '../../types'; diff --git a/packages/esql/src/ast/mutate/commands/sort/index.ts b/packages/esql/src/ast/mutate/commands/sort/index.ts index 189277a8..d14cc543 100644 --- a/packages/esql/src/ast/mutate/commands/sort/index.ts +++ b/packages/esql/src/ast/mutate/commands/sort/index.ts @@ -12,7 +12,7 @@ import type { ESQLCommand, ESQLOrderExpression, } from '../../../../types'; -import { Visitor } from '../../../visitor'; +import { Visitor } from '@elastic/esql-traversal'; import type { Predicate } from '../../types'; import * as util from '../../util'; import * as generic from '../../generic'; diff --git a/packages/esql/src/ast/mutate/generic/commands/args/index.ts b/packages/esql/src/ast/mutate/generic/commands/args/index.ts index 8358a388..095e498a 100644 --- a/packages/esql/src/ast/mutate/generic/commands/args/index.ts +++ b/packages/esql/src/ast/mutate/generic/commands/args/index.ts @@ -12,7 +12,7 @@ import type { ESQLProperNode, ESQLSingleAstItem, } from '@elastic/esql-types'; -import { Visitor } from '../../../../visitor'; +import { Visitor } from '@elastic/esql-traversal'; export const insert = ( command: ESQLCommand, diff --git a/packages/esql/src/ast/mutate/generic/commands/index.ts b/packages/esql/src/ast/mutate/generic/commands/index.ts index 0451ab8d..1a586bdd 100644 --- a/packages/esql/src/ast/mutate/generic/commands/index.ts +++ b/packages/esql/src/ast/mutate/generic/commands/index.ts @@ -6,7 +6,7 @@ */ import type { ESQLAstQueryExpression, ESQLCommand } from '../../../../types'; -import { Visitor } from '../../../visitor'; +import { Visitor } from '@elastic/esql-traversal'; import type { Predicate } from '../../types'; export * as args from './args'; diff --git a/packages/esql/src/ast/mutate/generic/commands/options/index.ts b/packages/esql/src/ast/mutate/generic/commands/options/index.ts index de82e312..b4eee4f9 100644 --- a/packages/esql/src/ast/mutate/generic/commands/options/index.ts +++ b/packages/esql/src/ast/mutate/generic/commands/options/index.ts @@ -12,7 +12,7 @@ import type { ESQLCommand, ESQLCommandOption, } from '../../../../../types'; -import { Visitor } from '../../../../visitor'; +import { Visitor } from '@elastic/esql-traversal'; import type { Predicate } from '../../../types'; import * as commands from '..'; diff --git a/packages/esql/src/composer/composer_query.ts b/packages/esql/src/composer/composer_query.ts index 2aba5d97..81cc5bfa 100644 --- a/packages/esql/src/composer/composer_query.ts +++ b/packages/esql/src/composer/composer_query.ts @@ -42,8 +42,7 @@ import type { QueryCommandTag, QueryCommandTagParametrized, } from './types'; -import { Walker, replaceProperties } from '@elastic/esql-traversal'; -import { resolveItem } from '../ast/visitor/utils'; +import { Walker, replaceProperties, resolveItem } from '@elastic/esql-traversal'; import { printAst } from '../debug'; export class ComposerQuery { diff --git a/packages/esql/src/pretty_print/basic_pretty_printer.ts b/packages/esql/src/pretty_print/basic_pretty_printer.ts index 0877e6d9..e82afba4 100644 --- a/packages/esql/src/pretty_print/basic_pretty_printer.ts +++ b/packages/esql/src/pretty_print/basic_pretty_printer.ts @@ -23,9 +23,7 @@ import { binaryExpressionGroup, unaryExpressionGroup, } from '../ast/grouping'; -import type { ESQLAstExpressionNode } from '../ast/visitor/types'; -import { Visitor } from '../ast/visitor'; -import { resolveItem } from '../ast/visitor/utils'; +import { Visitor, resolveItem, type ESQLAstExpressionNode } from '@elastic/esql-traversal'; import { commandOptionsWithEqualsSeparator, commandsWithNoCommaArgSeparator, diff --git a/packages/esql/src/pretty_print/wrapping_pretty_printer/printer.ts b/packages/esql/src/pretty_print/wrapping_pretty_printer/printer.ts index e9d2f124..d8032918 100644 --- a/packages/esql/src/pretty_print/wrapping_pretty_printer/printer.ts +++ b/packages/esql/src/pretty_print/wrapping_pretty_printer/printer.ts @@ -65,7 +65,7 @@ import { import { getPrettyPrintStats } from '../helpers'; import { PromQLWrappingPrettyPrinter } from '../../embedded_languages/promql/pretty_print'; import type { PromQLAstQueryExpression } from '@elastic/esql-types'; -import { singleItems, resolveItem } from '../../ast/visitor/utils'; +import { singleItems, resolveItem } from '@elastic/esql-traversal'; import { BinaryExpressionGroup, binaryExpressionGroup, From 1b219df1056cf975f2f4bea4862778f694b56ded Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Fri, 21 Aug 2026 17:04:28 +0200 Subject: [PATCH 10/24] refactor: move ES|QL main parser to the `*-parser` package --- .../parser => esql-parser/src/esql}/README.md | 0 .../esql}/__tests__/ast_parser.source.test.ts | 2 +- .../binary_expression_grouping.test.ts | 7 ++--- .../__tests__/boolean_expressions.test.ts | 2 +- .../src/esql}/__tests__/change_point.test.ts | 4 +-- .../src/esql}/__tests__/columns.test.ts | 0 .../__tests__/columns_with_brackets.test.ts | 2 +- .../esql}/__tests__/command_options.test.ts | 0 .../src/esql}/__tests__/commands.test.ts | 0 .../src/esql}/__tests__/comments.test.ts | 9 ++++-- .../src/esql}/__tests__/completion.test.ts | 4 +-- .../src/esql}/__tests__/dedup.test.ts | 4 +-- .../src/esql}/__tests__/dissect.test.ts | 2 +- .../src/esql}/__tests__/drop.test.ts | 2 +- .../src/esql}/__tests__/enrich.test.ts | 2 +- .../src/esql}/__tests__/eval.test.ts | 2 +- .../src/esql}/__tests__/explain.test.ts | 2 +- .../esql}/__tests__/expressions/regex.test.ts | 9 ++++-- .../src/esql}/__tests__/fixtures.ts | 0 .../src/esql}/__tests__/fork.test.ts | 4 +-- .../src/esql}/__tests__/from.test.ts | 2 +- .../src/esql}/__tests__/function.test.ts | 2 +- .../src/esql}/__tests__/fuse.test.ts | 2 +- .../src/esql}/__tests__/grok.test.ts | 2 +- .../src/esql}/__tests__/header.set.test.ts | 4 +-- .../src/esql}/__tests__/inlinecast.test.ts | 2 +- .../src/esql}/__tests__/inlinestats.test.ts | 2 +- .../src/esql}/__tests__/ip_location.test.ts | 4 +-- .../src/esql}/__tests__/join.test.ts | 2 +- .../src/esql}/__tests__/keep.test.ts | 2 +- .../src/esql}/__tests__/limit.test.ts | 0 .../src/esql}/__tests__/map.test.ts | 0 .../src/esql}/__tests__/metrics_info.test.ts | 4 +-- .../src/esql}/__tests__/mmr.test.ts | 4 +-- .../src/esql}/__tests__/mv_expand.test.ts | 2 +- .../src/esql}/__tests__/params.test.ts | 0 .../src/esql}/__tests__/parser.test.ts | 0 .../src/esql}/__tests__/parser_fields.test.ts | 6 ++-- .../src/esql}/__tests__/promql.test.ts | 6 ++-- .../src/esql}/__tests__/promql_query.test.ts | 6 ++-- .../esql-parser/src/esql/__tests__/query.ts | 31 +++++++++++++++++++ .../esql}/__tests__/registered_domain.test.ts | 4 +-- .../src/esql}/__tests__/rename.test.ts | 2 +- .../src/esql}/__tests__/rerank.test.ts | 4 +-- .../src/esql}/__tests__/row.test.ts | 2 +- .../src/esql}/__tests__/sample.test.ts | 2 +- .../src/esql}/__tests__/show.test.ts | 2 +- .../src/esql}/__tests__/sort.test.ts | 0 .../src/esql}/__tests__/stats.test.ts | 2 +- .../src/esql}/__tests__/timeseries.test.ts | 0 .../src/esql}/__tests__/ts_info.test.ts | 2 +- .../src/esql}/__tests__/uri_parts.test.ts | 8 +++-- .../src/esql}/__tests__/user_agent.test.ts | 4 +-- .../src/esql}/cst_to_ast_converter.ts | 7 +++-- .../src/esql}/decorations.ts | 8 ++--- .../src/esql}/esql_error_listener.ts | 4 +-- .../core => esql-parser/src/esql}/helpers.ts | 0 packages/esql-parser/src/esql/index.ts | 9 ++++++ .../core => esql-parser/src/esql}/parser.ts | 6 ++-- .../core => esql-parser/src/esql}/types.ts | 2 +- packages/esql-parser/src/index.ts | 2 ++ .../src/parser/__tests__/highlight.test.ts | 2 +- .../esql/src/parser/__tests__/literal.test.ts | 2 +- .../esql/src/parser/__tests__/parens.test.ts | 2 +- .../esql/src/parser/__tests__/where.test.ts | 2 +- packages/esql/src/parser/core/constants.ts | 15 --------- packages/esql/src/parser/index.ts | 15 +++++---- 67 files changed, 146 insertions(+), 102 deletions(-) rename packages/{esql/src/parser => esql-parser/src/esql}/README.md (100%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/ast_parser.source.test.ts (99%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/binary_expression_grouping.test.ts (95%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/boolean_expressions.test.ts (99%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/change_point.test.ts (98%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/columns.test.ts (100%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/columns_with_brackets.test.ts (99%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/command_options.test.ts (100%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/commands.test.ts (100%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/comments.test.ts (99%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/completion.test.ts (99%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/dedup.test.ts (95%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/dissect.test.ts (94%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/drop.test.ts (99%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/enrich.test.ts (98%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/eval.test.ts (93%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/explain.test.ts (97%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/expressions/regex.test.ts (99%) rename packages/{esql/src => esql-parser/src/esql}/__tests__/fixtures.ts (100%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/fork.test.ts (97%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/from.test.ts (99%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/function.test.ts (99%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/fuse.test.ts (99%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/grok.test.ts (96%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/header.set.test.ts (99%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/inlinecast.test.ts (98%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/inlinestats.test.ts (94%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/ip_location.test.ts (97%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/join.test.ts (99%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/keep.test.ts (99%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/limit.test.ts (100%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/map.test.ts (100%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/metrics_info.test.ts (93%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/mmr.test.ts (98%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/mv_expand.test.ts (94%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/params.test.ts (100%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/parser.test.ts (100%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/parser_fields.test.ts (91%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/promql.test.ts (99%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/promql_query.test.ts (96%) create mode 100644 packages/esql-parser/src/esql/__tests__/query.ts rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/registered_domain.test.ts (98%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/rename.test.ts (98%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/rerank.test.ts (99%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/row.test.ts (93%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/sample.test.ts (96%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/show.test.ts (95%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/sort.test.ts (100%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/stats.test.ts (99%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/timeseries.test.ts (100%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/ts_info.test.ts (93%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/uri_parts.test.ts (97%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/user_agent.test.ts (99%) rename packages/{esql/src/parser/core => esql-parser/src/esql}/cst_to_ast_converter.ts (99%) rename packages/{esql/src/parser/core => esql-parser/src/esql}/decorations.ts (97%) rename packages/{esql/src/parser/core => esql-parser/src/esql}/esql_error_listener.ts (96%) rename packages/{esql/src/parser/core => esql-parser/src/esql}/helpers.ts (100%) create mode 100644 packages/esql-parser/src/esql/index.ts rename packages/{esql/src/parser/core => esql-parser/src/esql}/parser.ts (98%) rename packages/{esql/src/parser/core => esql-parser/src/esql}/types.ts (97%) delete mode 100644 packages/esql/src/parser/core/constants.ts diff --git a/packages/esql/src/parser/README.md b/packages/esql-parser/src/esql/README.md similarity index 100% rename from packages/esql/src/parser/README.md rename to packages/esql-parser/src/esql/README.md diff --git a/packages/esql/src/parser/__tests__/ast_parser.source.test.ts b/packages/esql-parser/src/esql/__tests__/ast_parser.source.test.ts similarity index 99% rename from packages/esql/src/parser/__tests__/ast_parser.source.test.ts rename to packages/esql-parser/src/esql/__tests__/ast_parser.source.test.ts index a1e9ef50..1049b890 100644 --- a/packages/esql/src/parser/__tests__/ast_parser.source.test.ts +++ b/packages/esql-parser/src/esql/__tests__/ast_parser.source.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { parse } from '../core/parser'; +import { parse } from '../parser'; describe('source nodes', () => { it('cluster vs quoted source', () => { diff --git a/packages/esql/src/parser/__tests__/binary_expression_grouping.test.ts b/packages/esql-parser/src/esql/__tests__/binary_expression_grouping.test.ts similarity index 95% rename from packages/esql/src/parser/__tests__/binary_expression_grouping.test.ts rename to packages/esql-parser/src/esql/__tests__/binary_expression_grouping.test.ts index 6432d49a..30adabfe 100644 --- a/packages/esql/src/parser/__tests__/binary_expression_grouping.test.ts +++ b/packages/esql-parser/src/esql/__tests__/binary_expression_grouping.test.ts @@ -5,10 +5,9 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; -import type { ESQLAstItem, ESQLAstQueryExpression, ESQLProperNode } from '../../types'; -import { singleItems } from '../../ast/visitor/utils'; -import { Walker } from '@elastic/esql-traversal'; +import { EsqlQuery } from './query'; +import type { ESQLAstItem, ESQLAstQueryExpression, ESQLProperNode } from '@elastic/esql-types'; +import { singleItems, Walker } from '@elastic/esql-traversal'; const removeParserFields = (tree: ESQLAstQueryExpression): void => { Walker.walk(tree, { diff --git a/packages/esql/src/parser/__tests__/boolean_expressions.test.ts b/packages/esql-parser/src/esql/__tests__/boolean_expressions.test.ts similarity index 99% rename from packages/esql/src/parser/__tests__/boolean_expressions.test.ts rename to packages/esql-parser/src/esql/__tests__/boolean_expressions.test.ts index 9d1dbeb3..2beaa425 100644 --- a/packages/esql/src/parser/__tests__/boolean_expressions.test.ts +++ b/packages/esql-parser/src/esql/__tests__/boolean_expressions.test.ts @@ -6,7 +6,7 @@ */ import { Parser } from '..'; -import type { ESQLAstExpression, ESQLFunction } from '../../types'; +import type { ESQLAstExpression, ESQLFunction } from '@elastic/esql-types'; describe('Column Identifier Expressions', () => { it('a literal/constant', () => { diff --git a/packages/esql/src/parser/__tests__/change_point.test.ts b/packages/esql-parser/src/esql/__tests__/change_point.test.ts similarity index 98% rename from packages/esql/src/parser/__tests__/change_point.test.ts rename to packages/esql-parser/src/esql/__tests__/change_point.test.ts index 715ec7c7..f3594801 100644 --- a/packages/esql/src/parser/__tests__/change_point.test.ts +++ b/packages/esql-parser/src/esql/__tests__/change_point.test.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; -import type { ESQLCommandOption } from '../../types'; +import { EsqlQuery } from './query'; +import type { ESQLCommandOption } from '@elastic/esql-types'; import { Walker } from '@elastic/esql-traversal'; describe('CHANGE_POINT command', () => { diff --git a/packages/esql/src/parser/__tests__/columns.test.ts b/packages/esql-parser/src/esql/__tests__/columns.test.ts similarity index 100% rename from packages/esql/src/parser/__tests__/columns.test.ts rename to packages/esql-parser/src/esql/__tests__/columns.test.ts diff --git a/packages/esql/src/parser/__tests__/columns_with_brackets.test.ts b/packages/esql-parser/src/esql/__tests__/columns_with_brackets.test.ts similarity index 99% rename from packages/esql/src/parser/__tests__/columns_with_brackets.test.ts rename to packages/esql-parser/src/esql/__tests__/columns_with_brackets.test.ts index e9b5b2aa..71d726ce 100644 --- a/packages/esql/src/parser/__tests__/columns_with_brackets.test.ts +++ b/packages/esql-parser/src/esql/__tests__/columns_with_brackets.test.ts @@ -6,7 +6,7 @@ */ import { Parser } from '..'; -import { printAst } from '../../debug'; +import { printAst } from '@elastic/esql-traversal'; describe('Column Bracketed Syntax: [qualifier].[name]', () => { describe('STATS', () => { diff --git a/packages/esql/src/parser/__tests__/command_options.test.ts b/packages/esql-parser/src/esql/__tests__/command_options.test.ts similarity index 100% rename from packages/esql/src/parser/__tests__/command_options.test.ts rename to packages/esql-parser/src/esql/__tests__/command_options.test.ts diff --git a/packages/esql/src/parser/__tests__/commands.test.ts b/packages/esql-parser/src/esql/__tests__/commands.test.ts similarity index 100% rename from packages/esql/src/parser/__tests__/commands.test.ts rename to packages/esql-parser/src/esql/__tests__/commands.test.ts diff --git a/packages/esql/src/parser/__tests__/comments.test.ts b/packages/esql-parser/src/esql/__tests__/comments.test.ts similarity index 99% rename from packages/esql/src/parser/__tests__/comments.test.ts rename to packages/esql-parser/src/esql/__tests__/comments.test.ts index 9bd64742..64a1b398 100644 --- a/packages/esql/src/parser/__tests__/comments.test.ts +++ b/packages/esql-parser/src/esql/__tests__/comments.test.ts @@ -6,8 +6,13 @@ */ import { parse } from '..'; -import { EsqlQuery } from '../../composer/query'; -import type { ESQLAstItem, ESQLAstRerankCommand, ESQLCommandOption, ESQLMap } from '../../types'; +import { EsqlQuery } from './query'; +import type { + ESQLAstItem, + ESQLAstRerankCommand, + ESQLCommandOption, + ESQLMap, +} from '@elastic/esql-types'; import { Walker } from '@elastic/esql-traversal'; describe('Comments', () => { diff --git a/packages/esql/src/parser/__tests__/completion.test.ts b/packages/esql-parser/src/esql/__tests__/completion.test.ts similarity index 99% rename from packages/esql/src/parser/__tests__/completion.test.ts rename to packages/esql-parser/src/esql/__tests__/completion.test.ts index 7420c2de..5406d812 100644 --- a/packages/esql/src/parser/__tests__/completion.test.ts +++ b/packages/esql-parser/src/esql/__tests__/completion.test.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; -import type { ESQLAstCompletionCommand, ESQLAstItem, ESQLFunction } from '../../types'; +import { EsqlQuery } from './query'; +import type { ESQLAstCompletionCommand, ESQLAstItem, ESQLFunction } from '@elastic/esql-types'; describe('COMPLETION command', () => { describe('correctly formatted', () => { diff --git a/packages/esql/src/parser/__tests__/dedup.test.ts b/packages/esql-parser/src/esql/__tests__/dedup.test.ts similarity index 95% rename from packages/esql/src/parser/__tests__/dedup.test.ts rename to packages/esql-parser/src/esql/__tests__/dedup.test.ts index c434e326..e9d4b991 100644 --- a/packages/esql/src/parser/__tests__/dedup.test.ts +++ b/packages/esql-parser/src/esql/__tests__/dedup.test.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; +import { EsqlQuery } from './query'; import { Walker } from '@elastic/esql-traversal'; -import type { ESQLAstQueryExpression } from '../../types'; +import type { ESQLAstQueryExpression } from '@elastic/esql-types'; describe('DEDUP', () => { const getDedup = (ast: ESQLAstQueryExpression) => diff --git a/packages/esql/src/parser/__tests__/dissect.test.ts b/packages/esql-parser/src/esql/__tests__/dissect.test.ts similarity index 94% rename from packages/esql/src/parser/__tests__/dissect.test.ts rename to packages/esql-parser/src/esql/__tests__/dissect.test.ts index a43dafee..6e774f2c 100644 --- a/packages/esql/src/parser/__tests__/dissect.test.ts +++ b/packages/esql-parser/src/esql/__tests__/dissect.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; +import { EsqlQuery } from './query'; import { Walker } from '@elastic/esql-traversal'; describe('DISSECT', () => { diff --git a/packages/esql/src/parser/__tests__/drop.test.ts b/packages/esql-parser/src/esql/__tests__/drop.test.ts similarity index 99% rename from packages/esql/src/parser/__tests__/drop.test.ts rename to packages/esql-parser/src/esql/__tests__/drop.test.ts index e0e4403c..67d553d0 100644 --- a/packages/esql/src/parser/__tests__/drop.test.ts +++ b/packages/esql-parser/src/esql/__tests__/drop.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; +import { EsqlQuery } from './query'; import { Walker } from '@elastic/esql-traversal'; describe('DROP', () => { diff --git a/packages/esql/src/parser/__tests__/enrich.test.ts b/packages/esql-parser/src/esql/__tests__/enrich.test.ts similarity index 98% rename from packages/esql/src/parser/__tests__/enrich.test.ts rename to packages/esql-parser/src/esql/__tests__/enrich.test.ts index 9586404d..43731444 100644 --- a/packages/esql/src/parser/__tests__/enrich.test.ts +++ b/packages/esql-parser/src/esql/__tests__/enrich.test.ts @@ -6,7 +6,7 @@ */ import { parse } from '..'; -import type { ESQLCommand, ESQLSource } from '../../types'; +import type { ESQLCommand, ESQLSource } from '@elastic/esql-types'; import { Walker } from '@elastic/esql-traversal'; describe('ENRICH', () => { diff --git a/packages/esql/src/parser/__tests__/eval.test.ts b/packages/esql-parser/src/esql/__tests__/eval.test.ts similarity index 93% rename from packages/esql/src/parser/__tests__/eval.test.ts rename to packages/esql-parser/src/esql/__tests__/eval.test.ts index 239193c1..0919304a 100644 --- a/packages/esql/src/parser/__tests__/eval.test.ts +++ b/packages/esql-parser/src/esql/__tests__/eval.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; +import { EsqlQuery } from './query'; describe('EVAL', () => { describe('correctly formatted', () => { diff --git a/packages/esql/src/parser/__tests__/explain.test.ts b/packages/esql-parser/src/esql/__tests__/explain.test.ts similarity index 97% rename from packages/esql/src/parser/__tests__/explain.test.ts rename to packages/esql-parser/src/esql/__tests__/explain.test.ts index fb705e3b..05172f3b 100644 --- a/packages/esql/src/parser/__tests__/explain.test.ts +++ b/packages/esql-parser/src/esql/__tests__/explain.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; +import { EsqlQuery } from './query'; describe('EXPLAIN', () => { describe('correctly formatted', () => { diff --git a/packages/esql/src/parser/__tests__/expressions/regex.test.ts b/packages/esql-parser/src/esql/__tests__/expressions/regex.test.ts similarity index 99% rename from packages/esql/src/parser/__tests__/expressions/regex.test.ts rename to packages/esql-parser/src/esql/__tests__/expressions/regex.test.ts index ea171902..ac5a1a20 100644 --- a/packages/esql/src/parser/__tests__/expressions/regex.test.ts +++ b/packages/esql-parser/src/esql/__tests__/expressions/regex.test.ts @@ -5,8 +5,13 @@ * 2.0. */ -import { Parser } from '../..'; -import type { ESQLFunction, ESQLList, ESQLSingleAstItem, ESQLStringLiteral } from '../../../types'; +import { Parser } from '../../parser'; +import type { + ESQLFunction, + ESQLList, + ESQLSingleAstItem, + ESQLStringLiteral, +} from '@elastic/esql-types'; describe('regular expressions', () => { it('LIKE operator', () => { diff --git a/packages/esql/src/__tests__/fixtures.ts b/packages/esql-parser/src/esql/__tests__/fixtures.ts similarity index 100% rename from packages/esql/src/__tests__/fixtures.ts rename to packages/esql-parser/src/esql/__tests__/fixtures.ts diff --git a/packages/esql/src/parser/__tests__/fork.test.ts b/packages/esql-parser/src/esql/__tests__/fork.test.ts similarity index 97% rename from packages/esql/src/parser/__tests__/fork.test.ts rename to packages/esql-parser/src/esql/__tests__/fork.test.ts index 55cf4ba4..f2f4a896 100644 --- a/packages/esql/src/parser/__tests__/fork.test.ts +++ b/packages/esql-parser/src/esql/__tests__/fork.test.ts @@ -6,8 +6,8 @@ */ import { parse } from '..'; -import { EsqlQuery } from '../../composer/query'; -import type { ESQLForkParens } from '../../types'; +import { EsqlQuery } from './query'; +import type { ESQLForkParens } from '@elastic/esql-types'; import { Walker } from '@elastic/esql-traversal'; describe('FORK', () => { diff --git a/packages/esql/src/parser/__tests__/from.test.ts b/packages/esql-parser/src/esql/__tests__/from.test.ts similarity index 99% rename from packages/esql/src/parser/__tests__/from.test.ts rename to packages/esql-parser/src/esql/__tests__/from.test.ts index 2d70c32b..a5e3c878 100644 --- a/packages/esql/src/parser/__tests__/from.test.ts +++ b/packages/esql-parser/src/esql/__tests__/from.test.ts @@ -6,7 +6,7 @@ */ import { parse } from '..'; -import type { ESQLAstQueryExpression, ESQLParens } from '../../types'; +import type { ESQLAstQueryExpression, ESQLParens } from '@elastic/esql-types'; import { isParens, isSubQuery } from '@elastic/esql-ast'; describe('FROM', () => { diff --git a/packages/esql/src/parser/__tests__/function.test.ts b/packages/esql-parser/src/esql/__tests__/function.test.ts similarity index 99% rename from packages/esql/src/parser/__tests__/function.test.ts rename to packages/esql-parser/src/esql/__tests__/function.test.ts index 3b8caa26..6714d964 100644 --- a/packages/esql/src/parser/__tests__/function.test.ts +++ b/packages/esql-parser/src/esql/__tests__/function.test.ts @@ -6,7 +6,7 @@ */ import { Parser, parse } from '..'; -import { EsqlQuery } from '../../composer/query'; +import { EsqlQuery } from './query'; import { Walker } from '@elastic/esql-traversal'; describe('function AST nodes', () => { diff --git a/packages/esql/src/parser/__tests__/fuse.test.ts b/packages/esql-parser/src/esql/__tests__/fuse.test.ts similarity index 99% rename from packages/esql/src/parser/__tests__/fuse.test.ts rename to packages/esql-parser/src/esql/__tests__/fuse.test.ts index 9e24f390..6bf1cc71 100644 --- a/packages/esql/src/parser/__tests__/fuse.test.ts +++ b/packages/esql-parser/src/esql/__tests__/fuse.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { parse } from '../core/parser'; +import { parse } from '../parser'; describe('FUSE', () => { describe('correctly formatted', () => { diff --git a/packages/esql/src/parser/__tests__/grok.test.ts b/packages/esql-parser/src/esql/__tests__/grok.test.ts similarity index 96% rename from packages/esql/src/parser/__tests__/grok.test.ts rename to packages/esql-parser/src/esql/__tests__/grok.test.ts index 8143bc6c..d18fe6fc 100644 --- a/packages/esql/src/parser/__tests__/grok.test.ts +++ b/packages/esql-parser/src/esql/__tests__/grok.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; +import { EsqlQuery } from './query'; import { Walker } from '@elastic/esql-traversal'; describe('GROK', () => { diff --git a/packages/esql/src/parser/__tests__/header.set.test.ts b/packages/esql-parser/src/esql/__tests__/header.set.test.ts similarity index 99% rename from packages/esql/src/parser/__tests__/header.set.test.ts rename to packages/esql-parser/src/esql/__tests__/header.set.test.ts index 8fd2add2..ee8e5292 100644 --- a/packages/esql/src/parser/__tests__/header.set.test.ts +++ b/packages/esql-parser/src/esql/__tests__/header.set.test.ts @@ -12,8 +12,8 @@ import type { ESQLIntegerLiteral, ESQLSingleAstItem, ESQLStringLiteral, -} from '../../types'; -import { Parser } from '../core/parser'; +} from '@elastic/esql-types'; +import { Parser } from '../parser'; const getSetAssignment = (setInstruction: { args?: unknown[] }): ESQLBinaryExpression => { const [firstArg] = setInstruction.args ?? []; diff --git a/packages/esql/src/parser/__tests__/inlinecast.test.ts b/packages/esql-parser/src/esql/__tests__/inlinecast.test.ts similarity index 98% rename from packages/esql/src/parser/__tests__/inlinecast.test.ts rename to packages/esql-parser/src/esql/__tests__/inlinecast.test.ts index c0da47db..6724e386 100644 --- a/packages/esql/src/parser/__tests__/inlinecast.test.ts +++ b/packages/esql-parser/src/esql/__tests__/inlinecast.test.ts @@ -6,7 +6,7 @@ */ import { parse } from '..'; -import type { ESQLFunction, ESQLInlineCast, ESQLSingleAstItem } from '../../types'; +import type { ESQLFunction, ESQLInlineCast, ESQLSingleAstItem } from '@elastic/esql-types'; describe('Inline cast (::)', () => { describe('correctly formatted', () => { diff --git a/packages/esql/src/parser/__tests__/inlinestats.test.ts b/packages/esql-parser/src/esql/__tests__/inlinestats.test.ts similarity index 94% rename from packages/esql/src/parser/__tests__/inlinestats.test.ts rename to packages/esql-parser/src/esql/__tests__/inlinestats.test.ts index a0490bc7..62248ad0 100644 --- a/packages/esql/src/parser/__tests__/inlinestats.test.ts +++ b/packages/esql-parser/src/esql/__tests__/inlinestats.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; +import { EsqlQuery } from './query'; import { Walker } from '@elastic/esql-traversal'; describe('INLINE STATS', () => { diff --git a/packages/esql/src/parser/__tests__/ip_location.test.ts b/packages/esql-parser/src/esql/__tests__/ip_location.test.ts similarity index 97% rename from packages/esql/src/parser/__tests__/ip_location.test.ts rename to packages/esql-parser/src/esql/__tests__/ip_location.test.ts index 495b7181..854aa666 100644 --- a/packages/esql/src/parser/__tests__/ip_location.test.ts +++ b/packages/esql-parser/src/esql/__tests__/ip_location.test.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; +import { EsqlQuery } from './query'; import { Walker } from '@elastic/esql-traversal'; -import type { ESQLAstIpLocationCommand, ESQLAstQueryExpression } from '../../types'; +import type { ESQLAstIpLocationCommand, ESQLAstQueryExpression } from '@elastic/esql-types'; describe('IP_LOCATION', () => { const getIpLocation = (ast: ESQLAstQueryExpression): ESQLAstIpLocationCommand => diff --git a/packages/esql/src/parser/__tests__/join.test.ts b/packages/esql-parser/src/esql/__tests__/join.test.ts similarity index 99% rename from packages/esql/src/parser/__tests__/join.test.ts rename to packages/esql-parser/src/esql/__tests__/join.test.ts index d6e5ffa4..79b60a02 100644 --- a/packages/esql/src/parser/__tests__/join.test.ts +++ b/packages/esql-parser/src/esql/__tests__/join.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; +import { EsqlQuery } from './query'; import { Walker } from '@elastic/esql-traversal'; describe(' JOIN command', () => { diff --git a/packages/esql/src/parser/__tests__/keep.test.ts b/packages/esql-parser/src/esql/__tests__/keep.test.ts similarity index 99% rename from packages/esql/src/parser/__tests__/keep.test.ts rename to packages/esql-parser/src/esql/__tests__/keep.test.ts index abc179b7..0dd3f91d 100644 --- a/packages/esql/src/parser/__tests__/keep.test.ts +++ b/packages/esql-parser/src/esql/__tests__/keep.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; +import { EsqlQuery } from './query'; import { Walker } from '@elastic/esql-traversal'; describe('KEEP', () => { diff --git a/packages/esql/src/parser/__tests__/limit.test.ts b/packages/esql-parser/src/esql/__tests__/limit.test.ts similarity index 100% rename from packages/esql/src/parser/__tests__/limit.test.ts rename to packages/esql-parser/src/esql/__tests__/limit.test.ts diff --git a/packages/esql/src/parser/__tests__/map.test.ts b/packages/esql-parser/src/esql/__tests__/map.test.ts similarity index 100% rename from packages/esql/src/parser/__tests__/map.test.ts rename to packages/esql-parser/src/esql/__tests__/map.test.ts diff --git a/packages/esql/src/parser/__tests__/metrics_info.test.ts b/packages/esql-parser/src/esql/__tests__/metrics_info.test.ts similarity index 93% rename from packages/esql/src/parser/__tests__/metrics_info.test.ts rename to packages/esql-parser/src/esql/__tests__/metrics_info.test.ts index e442ba3e..f205a3aa 100644 --- a/packages/esql/src/parser/__tests__/metrics_info.test.ts +++ b/packages/esql-parser/src/esql/__tests__/metrics_info.test.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; +import { EsqlQuery } from './query'; import { Walker } from '@elastic/esql-traversal'; -import type { ESQLAstQueryExpression, ESQLAstMetricsInfoCommand } from '../../types'; +import type { ESQLAstQueryExpression, ESQLAstMetricsInfoCommand } from '@elastic/esql-types'; describe('METRICS_INFO', () => { const getMetricsInfo = (ast: ESQLAstQueryExpression): ESQLAstMetricsInfoCommand => diff --git a/packages/esql/src/parser/__tests__/mmr.test.ts b/packages/esql-parser/src/esql/__tests__/mmr.test.ts similarity index 98% rename from packages/esql/src/parser/__tests__/mmr.test.ts rename to packages/esql-parser/src/esql/__tests__/mmr.test.ts index 2400634b..e8614a13 100644 --- a/packages/esql/src/parser/__tests__/mmr.test.ts +++ b/packages/esql-parser/src/esql/__tests__/mmr.test.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; -import type { ESQLAstMmrCommand, ESQLCommandOption, ESQLMap } from '../../types'; +import { EsqlQuery } from './query'; +import type { ESQLAstMmrCommand, ESQLCommandOption, ESQLMap } from '@elastic/esql-types'; describe('MMR command', () => { describe('basic parsing', () => { diff --git a/packages/esql/src/parser/__tests__/mv_expand.test.ts b/packages/esql-parser/src/esql/__tests__/mv_expand.test.ts similarity index 94% rename from packages/esql/src/parser/__tests__/mv_expand.test.ts rename to packages/esql-parser/src/esql/__tests__/mv_expand.test.ts index c18bc03c..fb0b3997 100644 --- a/packages/esql/src/parser/__tests__/mv_expand.test.ts +++ b/packages/esql-parser/src/esql/__tests__/mv_expand.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; +import { EsqlQuery } from './query'; import { Walker } from '@elastic/esql-traversal'; describe('MV_EXPAND', () => { diff --git a/packages/esql/src/parser/__tests__/params.test.ts b/packages/esql-parser/src/esql/__tests__/params.test.ts similarity index 100% rename from packages/esql/src/parser/__tests__/params.test.ts rename to packages/esql-parser/src/esql/__tests__/params.test.ts diff --git a/packages/esql/src/parser/__tests__/parser.test.ts b/packages/esql-parser/src/esql/__tests__/parser.test.ts similarity index 100% rename from packages/esql/src/parser/__tests__/parser.test.ts rename to packages/esql-parser/src/esql/__tests__/parser.test.ts diff --git a/packages/esql/src/parser/__tests__/parser_fields.test.ts b/packages/esql-parser/src/esql/__tests__/parser_fields.test.ts similarity index 91% rename from packages/esql/src/parser/__tests__/parser_fields.test.ts rename to packages/esql-parser/src/esql/__tests__/parser_fields.test.ts index 76db553c..27bec839 100644 --- a/packages/esql/src/parser/__tests__/parser_fields.test.ts +++ b/packages/esql-parser/src/esql/__tests__/parser_fields.test.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; -import * as fixtures from '../../__tests__/fixtures'; -import type { ESQLProperNode } from '../../types'; +import { EsqlQuery } from './query'; +import * as fixtures from './fixtures'; +import type { ESQLProperNode } from '@elastic/esql-types'; import { Walker } from '@elastic/esql-traversal'; /** diff --git a/packages/esql/src/parser/__tests__/promql.test.ts b/packages/esql-parser/src/esql/__tests__/promql.test.ts similarity index 99% rename from packages/esql/src/parser/__tests__/promql.test.ts rename to packages/esql-parser/src/esql/__tests__/promql.test.ts index e4328066..90550081 100644 --- a/packages/esql/src/parser/__tests__/promql.test.ts +++ b/packages/esql-parser/src/esql/__tests__/promql.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; +import { EsqlQuery } from './query'; import type { ESQLCommand, ESQLMap, @@ -13,8 +13,8 @@ import type { ESQLParens, ESQLBinaryExpression, ESQLAstPromqlCommand, -} from '../../types'; -import { printAst } from '../../debug'; +} from '@elastic/esql-types'; +import { printAst } from '@elastic/esql-traversal'; /** * ``` diff --git a/packages/esql/src/parser/__tests__/promql_query.test.ts b/packages/esql-parser/src/esql/__tests__/promql_query.test.ts similarity index 96% rename from packages/esql/src/parser/__tests__/promql_query.test.ts rename to packages/esql-parser/src/esql/__tests__/promql_query.test.ts index 0711254c..5e81b4de 100644 --- a/packages/esql/src/parser/__tests__/promql_query.test.ts +++ b/packages/esql-parser/src/esql/__tests__/promql_query.test.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; -import { printAst } from '../../debug'; -import type { ESQLAstPromqlCommand } from '../../types'; +import { EsqlQuery } from './query'; +import { printAst } from '@elastic/esql-traversal'; +import type { ESQLAstPromqlCommand } from '@elastic/esql-types'; /** * ``` diff --git a/packages/esql-parser/src/esql/__tests__/query.ts b/packages/esql-parser/src/esql/__tests__/query.ts new file mode 100644 index 00000000..68cb07cf --- /dev/null +++ b/packages/esql-parser/src/esql/__tests__/query.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Token } from 'antlr4'; +import type { ESQLAstQueryExpression, EditorError } from '@elastic/esql-types'; +import { parse, type ParseOptions } from '../parser'; + +/** + * Test-only stand-in for the `EsqlQuery` composer class, which cannot be used + * here because it also pulls in the pretty-printer. Exposes the same `ast` / + * `src` / `tokens` / `errors` shape — note that `ast` is the root + * *QueryExpression* node, unlike `ParseResult.ast`, which is the command list. + */ +export class EsqlQuery { + public static readonly fromSrc = (src: string, opts?: ParseOptions): EsqlQuery => { + const { root, tokens, errors } = parse(src, opts); + + return new EsqlQuery(root, src, tokens, errors); + }; + + constructor( + public readonly ast: ESQLAstQueryExpression, + public readonly src: string = '', + public readonly tokens: Token[] = [], + public readonly errors: EditorError[] = [] + ) {} +} diff --git a/packages/esql/src/parser/__tests__/registered_domain.test.ts b/packages/esql-parser/src/esql/__tests__/registered_domain.test.ts similarity index 98% rename from packages/esql/src/parser/__tests__/registered_domain.test.ts rename to packages/esql-parser/src/esql/__tests__/registered_domain.test.ts index b40ed3c0..f775d8b9 100644 --- a/packages/esql/src/parser/__tests__/registered_domain.test.ts +++ b/packages/esql-parser/src/esql/__tests__/registered_domain.test.ts @@ -5,13 +5,13 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; +import { EsqlQuery } from './query'; import { Walker } from '@elastic/esql-traversal'; import type { ESQLAstQueryExpression, ESQLAstRegisteredDomainCommand, ESQLFunction, -} from '../../types'; +} from '@elastic/esql-types'; describe('REGISTERED_DOMAIN', () => { const getRegisteredDomain = (ast: ESQLAstQueryExpression): ESQLAstRegisteredDomainCommand => diff --git a/packages/esql/src/parser/__tests__/rename.test.ts b/packages/esql-parser/src/esql/__tests__/rename.test.ts similarity index 98% rename from packages/esql/src/parser/__tests__/rename.test.ts rename to packages/esql-parser/src/esql/__tests__/rename.test.ts index 43de3a0c..b0c2a7dd 100644 --- a/packages/esql/src/parser/__tests__/rename.test.ts +++ b/packages/esql-parser/src/esql/__tests__/rename.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; +import { EsqlQuery } from './query'; import { Walker } from '@elastic/esql-traversal'; describe('RENAME', () => { diff --git a/packages/esql/src/parser/__tests__/rerank.test.ts b/packages/esql-parser/src/esql/__tests__/rerank.test.ts similarity index 99% rename from packages/esql/src/parser/__tests__/rerank.test.ts rename to packages/esql-parser/src/esql/__tests__/rerank.test.ts index d4bb4ce6..adf9936c 100644 --- a/packages/esql/src/parser/__tests__/rerank.test.ts +++ b/packages/esql-parser/src/esql/__tests__/rerank.test.ts @@ -5,14 +5,14 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; +import { EsqlQuery } from './query'; import type { ESQLAstRerankCommand, ESQLCommandOption, ESQLLiteral, ESQLMap, ESQLMapEntry, -} from '../../types'; +} from '@elastic/esql-types'; describe('RERANK', () => { describe('basic parsing', () => { diff --git a/packages/esql/src/parser/__tests__/row.test.ts b/packages/esql-parser/src/esql/__tests__/row.test.ts similarity index 93% rename from packages/esql/src/parser/__tests__/row.test.ts rename to packages/esql-parser/src/esql/__tests__/row.test.ts index b99b78de..63a4e299 100644 --- a/packages/esql/src/parser/__tests__/row.test.ts +++ b/packages/esql-parser/src/esql/__tests__/row.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; +import { EsqlQuery } from './query'; describe('ROW', () => { describe('correctly formatted', () => { diff --git a/packages/esql/src/parser/__tests__/sample.test.ts b/packages/esql-parser/src/esql/__tests__/sample.test.ts similarity index 96% rename from packages/esql/src/parser/__tests__/sample.test.ts rename to packages/esql-parser/src/esql/__tests__/sample.test.ts index 4922ba05..2dd30848 100644 --- a/packages/esql/src/parser/__tests__/sample.test.ts +++ b/packages/esql-parser/src/esql/__tests__/sample.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Parser } from '../core/parser'; +import { Parser } from '../parser'; describe('SAMPLE', () => { test('correctly formatted', () => { diff --git a/packages/esql/src/parser/__tests__/show.test.ts b/packages/esql-parser/src/esql/__tests__/show.test.ts similarity index 95% rename from packages/esql/src/parser/__tests__/show.test.ts rename to packages/esql-parser/src/esql/__tests__/show.test.ts index 812235eb..7ee32125 100644 --- a/packages/esql/src/parser/__tests__/show.test.ts +++ b/packages/esql-parser/src/esql/__tests__/show.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; +import { EsqlQuery } from './query'; describe('SHOW', () => { describe('correctly formatted', () => { diff --git a/packages/esql/src/parser/__tests__/sort.test.ts b/packages/esql-parser/src/esql/__tests__/sort.test.ts similarity index 100% rename from packages/esql/src/parser/__tests__/sort.test.ts rename to packages/esql-parser/src/esql/__tests__/sort.test.ts diff --git a/packages/esql/src/parser/__tests__/stats.test.ts b/packages/esql-parser/src/esql/__tests__/stats.test.ts similarity index 99% rename from packages/esql/src/parser/__tests__/stats.test.ts rename to packages/esql-parser/src/esql/__tests__/stats.test.ts index 95e4e313..b44ae954 100644 --- a/packages/esql/src/parser/__tests__/stats.test.ts +++ b/packages/esql-parser/src/esql/__tests__/stats.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; +import { EsqlQuery } from './query'; import { Walker } from '@elastic/esql-traversal'; describe('STATS', () => { diff --git a/packages/esql/src/parser/__tests__/timeseries.test.ts b/packages/esql-parser/src/esql/__tests__/timeseries.test.ts similarity index 100% rename from packages/esql/src/parser/__tests__/timeseries.test.ts rename to packages/esql-parser/src/esql/__tests__/timeseries.test.ts diff --git a/packages/esql/src/parser/__tests__/ts_info.test.ts b/packages/esql-parser/src/esql/__tests__/ts_info.test.ts similarity index 93% rename from packages/esql/src/parser/__tests__/ts_info.test.ts rename to packages/esql-parser/src/esql/__tests__/ts_info.test.ts index 93ece79d..23ad4ac9 100644 --- a/packages/esql/src/parser/__tests__/ts_info.test.ts +++ b/packages/esql-parser/src/esql/__tests__/ts_info.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { parse } from '../core/parser'; +import { parse } from '../parser'; describe('TS_INFO', () => { it('parses ts_info command', () => { diff --git a/packages/esql/src/parser/__tests__/uri_parts.test.ts b/packages/esql-parser/src/esql/__tests__/uri_parts.test.ts similarity index 97% rename from packages/esql/src/parser/__tests__/uri_parts.test.ts rename to packages/esql-parser/src/esql/__tests__/uri_parts.test.ts index c11730c7..78b15a05 100644 --- a/packages/esql/src/parser/__tests__/uri_parts.test.ts +++ b/packages/esql-parser/src/esql/__tests__/uri_parts.test.ts @@ -5,9 +5,13 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; +import { EsqlQuery } from './query'; import { Walker } from '@elastic/esql-traversal'; -import type { ESQLAstQueryExpression, ESQLAstUriPartsCommand, ESQLFunction } from '../../types'; +import type { + ESQLAstQueryExpression, + ESQLAstUriPartsCommand, + ESQLFunction, +} from '@elastic/esql-types'; describe('URI_PARTS', () => { const getUriParts = (ast: ESQLAstQueryExpression): ESQLAstUriPartsCommand => diff --git a/packages/esql/src/parser/__tests__/user_agent.test.ts b/packages/esql-parser/src/esql/__tests__/user_agent.test.ts similarity index 99% rename from packages/esql/src/parser/__tests__/user_agent.test.ts rename to packages/esql-parser/src/esql/__tests__/user_agent.test.ts index 2b60f6b1..1d812832 100644 --- a/packages/esql/src/parser/__tests__/user_agent.test.ts +++ b/packages/esql-parser/src/esql/__tests__/user_agent.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; +import { EsqlQuery } from './query'; import { Walker } from '@elastic/esql-traversal'; import type { ESQLAstQueryExpression, @@ -13,7 +13,7 @@ import type { ESQLCommandOption, ESQLFunction, ESQLMap, -} from '../../types'; +} from '@elastic/esql-types'; /** * Examples follow the ES|QL USER_AGENT command syntax described in Elastic’s docs: diff --git a/packages/esql/src/parser/core/cst_to_ast_converter.ts b/packages/esql-parser/src/esql/cst_to_ast_converter.ts similarity index 99% rename from packages/esql/src/parser/core/cst_to_ast_converter.ts rename to packages/esql-parser/src/esql/cst_to_ast_converter.ts index df24f2ff..f7d1f715 100644 --- a/packages/esql/src/parser/core/cst_to_ast_converter.ts +++ b/packages/esql-parser/src/esql/cst_to_ast_converter.ts @@ -7,7 +7,7 @@ import type * as antlr from 'antlr4'; import * as cst from '@elastic/esql-grammar'; -import type * as ast from '../../types'; +import type * as ast from '@elastic/esql-types'; import { isCommand, isStringLiteral, @@ -16,9 +16,10 @@ import { type AstNodeParserFields, type AstNodeTemplate, } from '@elastic/esql-ast'; -import { getPosition, PromQLParser } from '@elastic/esql-parser'; +import { getPosition } from '../tokens'; +import { PromQLParser } from '../promql'; import { nonNullable, unescapeColumn } from './helpers'; -import { firstItem, lastItem, resolveItem, singleItems } from '../../ast/visitor/utils'; +import { firstItem, lastItem, resolveItem, singleItems } from '@elastic/esql-traversal'; import { type ArithmeticUnaryContext } from '@elastic/esql-grammar'; import type { Parser } from './parser'; import type { PromQLAstQueryExpression } from '@elastic/esql-types'; diff --git a/packages/esql/src/parser/core/decorations.ts b/packages/esql-parser/src/esql/decorations.ts similarity index 97% rename from packages/esql/src/parser/core/decorations.ts rename to packages/esql-parser/src/esql/decorations.ts index 1e922fe3..cd819093 100644 --- a/packages/esql/src/parser/core/decorations.ts +++ b/packages/esql-parser/src/esql/decorations.ts @@ -14,7 +14,7 @@ import type { Token } from 'antlr4'; import { type CommonTokenStream } from 'antlr4'; import { Builder } from '@elastic/esql-ast'; -import { Visitor } from '../../ast/visitor'; +import { Visitor } from '@elastic/esql-traversal'; import type { ESQLAstComment, ESQLAstCommentMultiLine, @@ -22,14 +22,14 @@ import type { ESQLAstNodeFormatting, ESQLAstQueryExpression, ESQLProperNode, -} from '../../types'; +} from '@elastic/esql-types'; import type { ParsedFormattingCommentDecoration, ParsedFormattingDecoration, ParsedFormattingDecorationLines, } from './types'; -import { HIDDEN_CHANNEL } from './constants'; -import { findVisibleToken, isLikelyPunctuation } from '@elastic/esql-parser'; +import { HIDDEN_CHANNEL } from '../constants'; +import { findVisibleToken, isLikelyPunctuation } from '../tokens'; const commentSubtype = (text: string): ESQLAstComment['subtype'] | undefined => { if (text[0] === '/') { diff --git a/packages/esql/src/parser/core/esql_error_listener.ts b/packages/esql-parser/src/esql/esql_error_listener.ts similarity index 96% rename from packages/esql/src/parser/core/esql_error_listener.ts rename to packages/esql-parser/src/esql/esql_error_listener.ts index 4cf8bb62..bd0427f7 100644 --- a/packages/esql/src/parser/core/esql_error_listener.ts +++ b/packages/esql-parser/src/esql/esql_error_listener.ts @@ -6,13 +6,13 @@ */ import * as antlr4 from 'antlr4'; -import { getPosition } from '@elastic/esql-parser'; +import { getPosition } from '../tokens'; import { TOKEN_RECOGNITION_ERROR_PREFIX, isUnquotedIdentifierError, expandSpanToIdentifier, } from './helpers'; -import type { EditorError } from '../../types'; +import type { EditorError } from '@elastic/esql-types'; // These will need to be manually updated whenever the relevant grammar changes. const SYNTAX_ERRORS_TO_IGNORE = [ diff --git a/packages/esql/src/parser/core/helpers.ts b/packages/esql-parser/src/esql/helpers.ts similarity index 100% rename from packages/esql/src/parser/core/helpers.ts rename to packages/esql-parser/src/esql/helpers.ts diff --git a/packages/esql-parser/src/esql/index.ts b/packages/esql-parser/src/esql/index.ts new file mode 100644 index 00000000..5f6ba629 --- /dev/null +++ b/packages/esql-parser/src/esql/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { parse, Parser, type ParseOptions, type ParseResult } from './parser'; +export { ESQLErrorListener } from './esql_error_listener'; diff --git a/packages/esql/src/parser/core/parser.ts b/packages/esql-parser/src/esql/parser.ts similarity index 98% rename from packages/esql/src/parser/core/parser.ts rename to packages/esql-parser/src/esql/parser.ts index c71057a5..35ea37f5 100644 --- a/packages/esql/src/parser/core/parser.ts +++ b/packages/esql-parser/src/esql/parser.ts @@ -28,9 +28,9 @@ import type { ESQLMap, ESQLProperNode, EditorError, -} from '../../types'; -import { singleItems } from '../../ast/visitor/utils'; -import { DEFAULT_CHANNEL, SOURCE_COMMANDS } from './constants'; +} from '@elastic/esql-types'; +import { singleItems } from '@elastic/esql-traversal'; +import { DEFAULT_CHANNEL, SOURCE_COMMANDS } from '../constants'; import type { EsqlParsingTarget } from './types'; export interface ParseOptions { diff --git a/packages/esql/src/parser/core/types.ts b/packages/esql-parser/src/esql/types.ts similarity index 97% rename from packages/esql/src/parser/core/types.ts rename to packages/esql-parser/src/esql/types.ts index 8559fd6a..6f75852a 100644 --- a/packages/esql/src/parser/core/types.ts +++ b/packages/esql-parser/src/esql/types.ts @@ -7,7 +7,7 @@ import type * as esql_parser from '@elastic/esql-grammar'; import type { CstToAstConverter } from './cst_to_ast_converter'; -import type { ESQLAstComment } from '../../types'; +import type { ESQLAstComment } from '@elastic/esql-types'; /** * Lines of decorations per *whitespace line*. A *whitespace line* is a line diff --git a/packages/esql-parser/src/index.ts b/packages/esql-parser/src/index.ts index ce85c18e..d7e03db7 100644 --- a/packages/esql-parser/src/index.ts +++ b/packages/esql-parser/src/index.ts @@ -5,5 +5,7 @@ * 2.0. */ +export * from './constants'; export * from './tokens'; +export * from './esql'; export * from './promql'; diff --git a/packages/esql/src/parser/__tests__/highlight.test.ts b/packages/esql/src/parser/__tests__/highlight.test.ts index cb19c34f..ef0da7fb 100644 --- a/packages/esql/src/parser/__tests__/highlight.test.ts +++ b/packages/esql/src/parser/__tests__/highlight.test.ts @@ -12,7 +12,7 @@ import type { ESQLAstHighlightCommand, ESQLAstQueryExpression, ESQLSingleAstItem, -} from '../../types'; +} from '@elastic/esql-types'; describe('HIGHLIGHT', () => { const getHighlight = (ast: ESQLAstQueryExpression): ESQLAstHighlightCommand => diff --git a/packages/esql/src/parser/__tests__/literal.test.ts b/packages/esql/src/parser/__tests__/literal.test.ts index 080adf08..08e5d00e 100644 --- a/packages/esql/src/parser/__tests__/literal.test.ts +++ b/packages/esql/src/parser/__tests__/literal.test.ts @@ -7,7 +7,7 @@ import { parse, Parser } from '..'; import { BasicPrettyPrinter } from '../../pretty_print'; -import type { ESQLLiteral, ESQLStringLiteral } from '../../types'; +import type { ESQLLiteral, ESQLStringLiteral } from '@elastic/esql-types'; describe('literal expression', () => { it('NULL', () => { diff --git a/packages/esql/src/parser/__tests__/parens.test.ts b/packages/esql/src/parser/__tests__/parens.test.ts index a8f463af..b2149077 100644 --- a/packages/esql/src/parser/__tests__/parens.test.ts +++ b/packages/esql/src/parser/__tests__/parens.test.ts @@ -6,7 +6,7 @@ */ import { Parser } from '..'; -import type { ESQLParens } from '../../types'; +import type { ESQLParens } from '@elastic/esql-types'; import { BasicPrettyPrinter } from '../../pretty_print'; const parse = (src: string) => Parser.parse(src, { withParens: true }).root; diff --git a/packages/esql/src/parser/__tests__/where.test.ts b/packages/esql/src/parser/__tests__/where.test.ts index bb7b7d3c..207449e7 100644 --- a/packages/esql/src/parser/__tests__/where.test.ts +++ b/packages/esql/src/parser/__tests__/where.test.ts @@ -7,7 +7,7 @@ import { parse } from '..'; import { EsqlQuery } from '../../composer/query'; -import type { ESQLColumn, ESQLCommand, ESQLFunction, ESQLInlineCast } from '../../types'; +import type { ESQLColumn, ESQLCommand, ESQLFunction, ESQLInlineCast } from '@elastic/esql-types'; import { Walker } from '@elastic/esql-traversal'; import { BasicPrettyPrinter } from '../../pretty_print'; diff --git a/packages/esql/src/parser/core/constants.ts b/packages/esql/src/parser/core/constants.ts deleted file mode 100644 index 5f43adc3..00000000 --- a/packages/esql/src/parser/core/constants.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { Token } from 'antlr4'; -import { headerCommandNames, sourceCommandNames } from '@elastic/esql-definitions/commandNames'; - -export const DEFAULT_CHANNEL: number = +Token.DEFAULT_CHANNEL; -export const HIDDEN_CHANNEL: number = +Token.HIDDEN_CHANNEL; - -export const HEADER_COMMANDS = new Set(headerCommandNames); -export const SOURCE_COMMANDS = new Set(sourceCommandNames); diff --git a/packages/esql/src/parser/index.ts b/packages/esql/src/parser/index.ts index 016f7cbc..fdb7b333 100644 --- a/packages/esql/src/parser/index.ts +++ b/packages/esql/src/parser/index.ts @@ -5,9 +5,12 @@ * 2.0. */ -// Main parser API -export { parse, Parser, type ParseOptions, type ParseResult } from './core/parser'; - -export { HEADER_COMMANDS, SOURCE_COMMANDS } from './core/constants'; - -export { ESQLErrorListener } from './core/esql_error_listener'; +export { + parse, + Parser, + type ParseOptions, + type ParseResult, + ESQLErrorListener, + HEADER_COMMANDS, + SOURCE_COMMANDS, +} from '@elastic/esql-parser'; From 76c8b6d870998867440cd0d23cd80316b92fed0a Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Mon, 24 Aug 2026 11:02:51 +0200 Subject: [PATCH 11/24] test: move tests to correct locations --- .../src/esql}/__tests__/highlight.test.ts | 31 +------- .../src/esql}/__tests__/literal.test.ts | 61 +--------------- .../src/esql}/__tests__/parens.test.ts | 36 +--------- .../src/esql}/__tests__/where.test.ts | 21 ++++-- .../__tests__/round_trip.highlight.test.ts | 41 +++++++++++ .../__tests__/round_trip.literal.test.ts | 71 +++++++++++++++++++ .../__tests__/round_trip.parens.test.ts | 44 ++++++++++++ 7 files changed, 175 insertions(+), 130 deletions(-) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/highlight.test.ts (89%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/literal.test.ts (81%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/parens.test.ts (76%) rename packages/{esql/src/parser => esql-parser/src/esql}/__tests__/where.test.ts (90%) create mode 100644 packages/esql/src/pretty_print/__tests__/round_trip.highlight.test.ts create mode 100644 packages/esql/src/pretty_print/__tests__/round_trip.literal.test.ts create mode 100644 packages/esql/src/pretty_print/__tests__/round_trip.parens.test.ts diff --git a/packages/esql/src/parser/__tests__/highlight.test.ts b/packages/esql-parser/src/esql/__tests__/highlight.test.ts similarity index 89% rename from packages/esql/src/parser/__tests__/highlight.test.ts rename to packages/esql-parser/src/esql/__tests__/highlight.test.ts index ef0da7fb..277a9e21 100644 --- a/packages/esql/src/parser/__tests__/highlight.test.ts +++ b/packages/esql-parser/src/esql/__tests__/highlight.test.ts @@ -5,9 +5,8 @@ * 2.0. */ -import { EsqlQuery } from '../../composer/query'; +import { EsqlQuery } from './query'; import { Walker } from '@elastic/esql-traversal'; -import { BasicPrettyPrinter } from '../../pretty_print'; import type { ESQLAstHighlightCommand, ESQLAstQueryExpression, @@ -200,20 +199,6 @@ describe('HIGHLIGHT', () => { ]); }); - it('round-trips single-field syntax through the pretty-printer', () => { - const src = 'FROM logs | HIGHLIGHT "fox" ON content'; - const { ast } = EsqlQuery.fromSrc(src); - - expect(BasicPrettyPrinter.query(ast)).toBe(src); - }); - - it('round-trips multi-field syntax through the pretty-printer', () => { - const src = 'FROM logs | HIGHLIGHT "ring sauron" ON title, body'; - const { ast } = EsqlQuery.fromSrc(src); - - expect(BasicPrettyPrinter.query(ast)).toBe(src); - }); - describe('prefix clause', () => { it('leaves prefix undefined when no prefix clause is present', () => { const src = 'FROM logs | HIGHLIGHT "fox" ON content'; @@ -287,19 +272,5 @@ describe('HIGHLIGHT', () => { expect(cmd.incomplete).toBe(true); expect(cmd.prefix).toBeUndefined(); }); - - it('round-trips prefix = "hl_" through the pretty-printer', () => { - const src = 'FROM logs | HIGHLIGHT prefix = "hl_" "fox" ON content'; - const { ast } = EsqlQuery.fromSrc(src); - - expect(BasicPrettyPrinter.query(ast)).toBe(src); - }); - - it('round-trips empty prefix through the pretty-printer', () => { - const src = 'FROM logs | HIGHLIGHT prefix = "" "fox" ON content'; - const { ast } = EsqlQuery.fromSrc(src); - - expect(BasicPrettyPrinter.query(ast)).toBe(src); - }); }); }); diff --git a/packages/esql/src/parser/__tests__/literal.test.ts b/packages/esql-parser/src/esql/__tests__/literal.test.ts similarity index 81% rename from packages/esql/src/parser/__tests__/literal.test.ts rename to packages/esql-parser/src/esql/__tests__/literal.test.ts index 08e5d00e..64645953 100644 --- a/packages/esql/src/parser/__tests__/literal.test.ts +++ b/packages/esql-parser/src/esql/__tests__/literal.test.ts @@ -5,8 +5,7 @@ * 2.0. */ -import { parse, Parser } from '..'; -import { BasicPrettyPrinter } from '../../pretty_print'; +import { parse, Parser } from '../parser'; import type { ESQLLiteral, ESQLStringLiteral } from '@elastic/esql-types'; describe('literal expression', () => { @@ -218,64 +217,6 @@ describe('literal expression', () => { expect((root.commands[0].args[0] as ESQLStringLiteral).valueUnquoted).toBe('path\\'); }); }); - - describe('round-trip: parse to BasicPrettyPrinter.print', () => { - const reprint = (src: string) => BasicPrettyPrinter.print(parse(src).root); - - it(String.raw`handlers\\run.cs (issue #212 exact repro)`, () => { - const src = String.raw`FROM a | WHERE x == "handlers\\run.cs"`; - - expect(reprint(src)).toBe(src); - }); - - it(String.raw`a\\new.cs (backslash + n)`, () => { - const src = String.raw`FROM a | WHERE x == "a\\new.cs"`; - - expect(reprint(src)).toBe(src); - }); - - it(String.raw`a\\temp (backslash + t)`, () => { - const src = String.raw`FROM a | WHERE x == "a\\temp"`; - - expect(reprint(src)).toBe(src); - }); - - it(String.raw`actual newline escape \n`, () => { - const src = 'FROM a | WHERE x == "a\\nb"'; - - expect(reprint(src)).toBe(src); - }); - - it(String.raw`actual tab escape \t`, () => { - const src = 'FROM a | WHERE x == "a\\tb"'; - - expect(reprint(src)).toBe(src); - }); - - it(String.raw`actual carriage-return escape \r`, () => { - const src = String.raw`FROM a | WHERE x == "a\rb"`; - - expect(reprint(src)).toBe(src); - }); - - it('escaped double-quote', () => { - const src = String.raw`FROM a | WHERE x == "say \"hi\""`; - - expect(reprint(src)).toBe(src); - }); - - it('two consecutive escaped backslashes', () => { - const src = String.raw`FROM a | WHERE x == "a\\\\b"`; - - expect(reprint(src)).toBe(src); - }); - - it('escaped backslash before non-special letter is unaffected', () => { - const src = String.raw`FROM a | WHERE x == "a\\slash"`; - - expect(reprint(src)).toBe(src); - }); - }); }); describe('triple quoted', () => { diff --git a/packages/esql/src/parser/__tests__/parens.test.ts b/packages/esql-parser/src/esql/__tests__/parens.test.ts similarity index 76% rename from packages/esql/src/parser/__tests__/parens.test.ts rename to packages/esql-parser/src/esql/__tests__/parens.test.ts index b2149077..0fa14024 100644 --- a/packages/esql/src/parser/__tests__/parens.test.ts +++ b/packages/esql-parser/src/esql/__tests__/parens.test.ts @@ -5,13 +5,11 @@ * 2.0. */ -import { Parser } from '..'; +import { Parser } from '../parser'; import type { ESQLParens } from '@elastic/esql-types'; -import { BasicPrettyPrinter } from '../../pretty_print'; const parse = (src: string) => Parser.parse(src, { withParens: true }).root; const firstArg = (src: string) => parse(src).commands[0].args[0]; -const reprint = (src: string) => BasicPrettyPrinter.print(parse(src)); describe('expression parens are dropped by default', () => { const firstArgDefault = (src: string) => Parser.parse(src).root.commands[0].args[0]; @@ -167,38 +165,6 @@ describe('expression parens preserved with { withParens: true }', () => { }); }); - describe('round-trip: printer preserves source parens', () => { - it('redundant same-precedence right-operand parens are preserved', () => { - expect(reprint('FROM a | WHERE a + (b + c)')).toBe('FROM a | WHERE a + (b + c)'); - expect(reprint('FROM a | WHERE a * (b * c)')).toBe('FROM a | WHERE a * (b * c)'); - expect(reprint('FROM a | WHERE a - (b + c)')).toBe('FROM a | WHERE a - (b + c)'); - }); - - it('redundant left-operand parens are preserved', () => { - expect(reprint('FROM a | WHERE (b / c) * 10')).toBe('FROM a | WHERE (b / c) * 10'); - expect(reprint('FROM a | WHERE (b + c) AND d')).toBe('FROM a | WHERE (b + c) AND d'); - }); - - it('semantically significant parens are preserved', () => { - expect(reprint('FROM a | WHERE (a + b) * c')).toBe('FROM a | WHERE (a + b) * c'); - expect(reprint('FROM a | WHERE a / (b * c)')).toBe('FROM a | WHERE a / (b * c)'); - expect(reprint('FROM a | WHERE b AND (c OR d)')).toBe('FROM a | WHERE b AND (c OR d)'); - }); - - it('nested parens are preserved', () => { - expect(reprint('FROM a | WHERE (1 + (2 + 3))')).toBe('FROM a | WHERE (1 + (2 + 3))'); - }); - - it('NOT (expr) parens are preserved', () => { - expect(reprint('FROM a | WHERE NOT (a OR b)')).toBe('FROM a | WHERE NOT (a OR b)'); - expect(reprint('FROM a | WHERE NOT (a > b)')).toBe('FROM a | WHERE NOT (a > b)'); - }); - - it('inline cast with parenthesized inner expression', () => { - expect(reprint('ROW (1 + 2)::string')).toBe('ROW (1 + 2)::STRING'); - }); - }); - describe('parens produce structurally distinct ASTs', () => { const strip = (src: string) => { const { root } = Parser.parse(src, { withParens: true }); diff --git a/packages/esql/src/parser/__tests__/where.test.ts b/packages/esql-parser/src/esql/__tests__/where.test.ts similarity index 90% rename from packages/esql/src/parser/__tests__/where.test.ts rename to packages/esql-parser/src/esql/__tests__/where.test.ts index 207449e7..7f213c98 100644 --- a/packages/esql/src/parser/__tests__/where.test.ts +++ b/packages/esql-parser/src/esql/__tests__/where.test.ts @@ -5,11 +5,10 @@ * 2.0. */ -import { parse } from '..'; -import { EsqlQuery } from '../../composer/query'; +import { parse } from '../parser'; +import { EsqlQuery } from './query'; import type { ESQLColumn, ESQLCommand, ESQLFunction, ESQLInlineCast } from '@elastic/esql-types'; -import { Walker } from '@elastic/esql-traversal'; -import { BasicPrettyPrinter } from '../../pretty_print'; +import { Walker, printAst } from '@elastic/esql-traversal'; describe('WHERE', () => { describe('correctly formatted', () => { @@ -174,7 +173,19 @@ describe('WHERE', () => { }, ], }); - expect(BasicPrettyPrinter.print(root)).toBe(text); + expect('\n' + printAst(root)).toBe(` +query 0-9 +├─ command 0-9 "from" +│ └─ source 5-9 "index" +│ └─ literal 5-9 ""index"" +└─ command 13-40 "where" + └─ function 19-40 ":" + ├─ function 19-30 "concat" + │ ├─ column 26-26 "a" + │ │ └─ identifier 26-26 "a" + │ └─ column 29-29 "b" + │ └─ identifier 29-29 "b" + └─ literal 34-40 ""query""`); }); }); }); diff --git a/packages/esql/src/pretty_print/__tests__/round_trip.highlight.test.ts b/packages/esql/src/pretty_print/__tests__/round_trip.highlight.test.ts new file mode 100644 index 00000000..9bdd4145 --- /dev/null +++ b/packages/esql/src/pretty_print/__tests__/round_trip.highlight.test.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EsqlQuery } from '../../composer/query'; +import { BasicPrettyPrinter } from '..'; + +describe('HIGHLIGHT round-trips through the pretty-printer', () => { + it('round-trips single-field syntax through the pretty-printer', () => { + const src = 'FROM logs | HIGHLIGHT "fox" ON content'; + const { ast } = EsqlQuery.fromSrc(src); + + expect(BasicPrettyPrinter.query(ast)).toBe(src); + }); + + it('round-trips multi-field syntax through the pretty-printer', () => { + const src = 'FROM logs | HIGHLIGHT "ring sauron" ON title, body'; + const { ast } = EsqlQuery.fromSrc(src); + + expect(BasicPrettyPrinter.query(ast)).toBe(src); + }); + + describe('prefix clause', () => { + it('round-trips prefix = "hl_" through the pretty-printer', () => { + const src = 'FROM logs | HIGHLIGHT prefix = "hl_" "fox" ON content'; + const { ast } = EsqlQuery.fromSrc(src); + + expect(BasicPrettyPrinter.query(ast)).toBe(src); + }); + + it('round-trips empty prefix through the pretty-printer', () => { + const src = 'FROM logs | HIGHLIGHT prefix = "" "fox" ON content'; + const { ast } = EsqlQuery.fromSrc(src); + + expect(BasicPrettyPrinter.query(ast)).toBe(src); + }); + }); +}); diff --git a/packages/esql/src/pretty_print/__tests__/round_trip.literal.test.ts b/packages/esql/src/pretty_print/__tests__/round_trip.literal.test.ts new file mode 100644 index 00000000..ca6e0522 --- /dev/null +++ b/packages/esql/src/pretty_print/__tests__/round_trip.literal.test.ts @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { parse } from '../../parser'; +import { BasicPrettyPrinter } from '..'; + +describe('literal expression', () => { + describe('string', () => { + describe('round-trip: parse to BasicPrettyPrinter.print', () => { + const reprint = (src: string) => BasicPrettyPrinter.print(parse(src).root); + + it(String.raw`handlers\\run.cs (issue #212 exact repro)`, () => { + const src = String.raw`FROM a | WHERE x == "handlers\\run.cs"`; + + expect(reprint(src)).toBe(src); + }); + + it(String.raw`a\\new.cs (backslash + n)`, () => { + const src = String.raw`FROM a | WHERE x == "a\\new.cs"`; + + expect(reprint(src)).toBe(src); + }); + + it(String.raw`a\\temp (backslash + t)`, () => { + const src = String.raw`FROM a | WHERE x == "a\\temp"`; + + expect(reprint(src)).toBe(src); + }); + + it(String.raw`actual newline escape \n`, () => { + const src = 'FROM a | WHERE x == "a\\nb"'; + + expect(reprint(src)).toBe(src); + }); + + it(String.raw`actual tab escape \t`, () => { + const src = 'FROM a | WHERE x == "a\\tb"'; + + expect(reprint(src)).toBe(src); + }); + + it(String.raw`actual carriage-return escape \r`, () => { + const src = String.raw`FROM a | WHERE x == "a\rb"`; + + expect(reprint(src)).toBe(src); + }); + + it('escaped double-quote', () => { + const src = String.raw`FROM a | WHERE x == "say \"hi\""`; + + expect(reprint(src)).toBe(src); + }); + + it('two consecutive escaped backslashes', () => { + const src = String.raw`FROM a | WHERE x == "a\\\\b"`; + + expect(reprint(src)).toBe(src); + }); + + it('escaped backslash before non-special letter is unaffected', () => { + const src = String.raw`FROM a | WHERE x == "a\\slash"`; + + expect(reprint(src)).toBe(src); + }); + }); + }); +}); diff --git a/packages/esql/src/pretty_print/__tests__/round_trip.parens.test.ts b/packages/esql/src/pretty_print/__tests__/round_trip.parens.test.ts new file mode 100644 index 00000000..89fa62a8 --- /dev/null +++ b/packages/esql/src/pretty_print/__tests__/round_trip.parens.test.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Parser } from '../../parser'; +import { BasicPrettyPrinter } from '..'; + +const parse = (src: string) => Parser.parse(src, { withParens: true }).root; +const reprint = (src: string) => BasicPrettyPrinter.print(parse(src)); + +describe('round-trip: printer preserves source parens', () => { + it('redundant same-precedence right-operand parens are preserved', () => { + expect(reprint('FROM a | WHERE a + (b + c)')).toBe('FROM a | WHERE a + (b + c)'); + expect(reprint('FROM a | WHERE a * (b * c)')).toBe('FROM a | WHERE a * (b * c)'); + expect(reprint('FROM a | WHERE a - (b + c)')).toBe('FROM a | WHERE a - (b + c)'); + }); + + it('redundant left-operand parens are preserved', () => { + expect(reprint('FROM a | WHERE (b / c) * 10')).toBe('FROM a | WHERE (b / c) * 10'); + expect(reprint('FROM a | WHERE (b + c) AND d')).toBe('FROM a | WHERE (b + c) AND d'); + }); + + it('semantically significant parens are preserved', () => { + expect(reprint('FROM a | WHERE (a + b) * c')).toBe('FROM a | WHERE (a + b) * c'); + expect(reprint('FROM a | WHERE a / (b * c)')).toBe('FROM a | WHERE a / (b * c)'); + expect(reprint('FROM a | WHERE b AND (c OR d)')).toBe('FROM a | WHERE b AND (c OR d)'); + }); + + it('nested parens are preserved', () => { + expect(reprint('FROM a | WHERE (1 + (2 + 3))')).toBe('FROM a | WHERE (1 + (2 + 3))'); + }); + + it('NOT (expr) parens are preserved', () => { + expect(reprint('FROM a | WHERE NOT (a OR b)')).toBe('FROM a | WHERE NOT (a OR b)'); + expect(reprint('FROM a | WHERE NOT (a > b)')).toBe('FROM a | WHERE NOT (a > b)'); + }); + + it('inline cast with parenthesized inner expression', () => { + expect(reprint('ROW (1 + 2)::string')).toBe('ROW (1 + 2)::STRING'); + }); +}); From 3b6e5f170e70d6384278ac8c97c8fa444e9bb55a Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Mon, 24 Aug 2026 11:27:54 +0200 Subject: [PATCH 12/24] chore: cleanup codebase, move files to right locations --- .../src/debug/__tests__/fixtures.ts | 225 ++++++++++++++++++ .../src/debug/__tests__/print_ast.test.ts | 20 +- .../esql/src/{parser/index.ts => parser.ts} | 0 3 files changed, 233 insertions(+), 12 deletions(-) create mode 100644 packages/esql-traversal/src/debug/__tests__/fixtures.ts rename packages/{esql => esql-traversal}/src/debug/__tests__/print_ast.test.ts (79%) rename packages/esql/src/{parser/index.ts => parser.ts} (100%) diff --git a/packages/esql-traversal/src/debug/__tests__/fixtures.ts b/packages/esql-traversal/src/debug/__tests__/fixtures.ts new file mode 100644 index 00000000..1ead4c89 --- /dev/null +++ b/packages/esql-traversal/src/debug/__tests__/fixtures.ts @@ -0,0 +1,225 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * Query fixtures for the `printAst` tests, built with {@link Builder} rather + * than parsed, since the parser sits above this package in the dependency + * graph. `printAst` renders each node's location and `.text`, so unlike the + * other fixtures in this package these carry the exact values the parser + * produces; they were generated from real parser output and verified against + * it node by node. + */ + +import { Builder } from '@elastic/esql-ast'; +import { expr } from '../../__tests__/builders'; +import type { ESQLAstQueryExpression } from '@elastic/esql-types'; + +// FROM index +export const fromIndex = (): ESQLAstQueryExpression => + Builder.expression.query( + [ + Builder.command( + { + name: 'from', + args: [ + expr.source.node( + { + sourceType: 'index', + index: expr.literal.string( + 'index', + { name: '"index"', unquoted: true }, + { location: { min: 5, max: 9 }, text: 'index' } + ), + }, + { location: { min: 5, max: 9 }, text: 'index' } + ), + ], + }, + { location: { min: 0, max: 9 }, text: 'FROMindex' } + ), + ], + { location: { min: 0, max: 9 }, text: 'FROMindex' } + ); + +// ROW 123, "foo" +export const rowNumberAndString = (): ESQLAstQueryExpression => + Builder.expression.query( + [ + Builder.command( + { + name: 'row', + args: [ + expr.literal.integer(123, undefined, { location: { min: 4, max: 6 }, text: '123' }), + expr.literal.string( + 'foo', + { name: '"foo"' }, + { location: { min: 9, max: 13 }, text: '"foo"' } + ), + ], + }, + { location: { min: 0, max: 13 }, text: 'ROW123,"foo"' } + ), + ], + { location: { min: 0, max: 13 }, text: 'ROW123,"foo"' } + ); + +// FROM a | STATS fn = count(a * (1 + 3), {"adf": 123}) BY b | LIMIT 123 +export const fromStatsByLimit = (): ESQLAstQueryExpression => + Builder.expression.query( + [ + Builder.command( + { + name: 'from', + args: [ + expr.source.node( + { + sourceType: 'index', + index: expr.literal.string( + 'a', + { name: '"a"', unquoted: true }, + { location: { min: 5, max: 5 }, text: 'a' } + ), + }, + { location: { min: 5, max: 5 }, text: 'a' } + ), + ], + }, + { location: { min: 0, max: 5 }, text: 'FROMa' } + ), + Builder.command( + { + name: 'stats', + args: [ + expr.func.node( + { + name: '=', + subtype: 'binary-expression', + args: [ + expr.column( + { + args: [ + Builder.identifier( + { name: 'fn' }, + { location: { min: 15, max: 16 }, text: 'fn' } + ), + ], + }, + undefined, + { location: { min: 15, max: 16 }, text: 'fn' } + ), + [ + expr.func.node( + { + name: 'count', + subtype: 'variadic-call', + operator: Builder.identifier( + { name: 'count' }, + { location: { min: 20, max: 24 }, text: 'count' } + ), + args: [ + expr.func.node( + { + name: '*', + subtype: 'binary-expression', + args: [ + expr.column( + { + args: [ + Builder.identifier( + { name: 'a' }, + { location: { min: 26, max: 26 }, text: 'a' } + ), + ], + }, + undefined, + { location: { min: 26, max: 26 }, text: 'a' } + ), + expr.func.node( + { + name: '+', + subtype: 'binary-expression', + args: [ + expr.literal.integer(1, undefined, { + location: { min: 31, max: 31 }, + text: '1', + }), + expr.literal.integer(3, undefined, { + location: { min: 35, max: 35 }, + text: '3', + }), + ], + }, + { location: { min: 31, max: 35 }, text: '1+3' } + ), + ], + }, + { location: { min: 26, max: 36 }, text: 'a*(1+3)' } + ), + expr.map( + { + entries: [ + expr.entry( + expr.literal.string( + 'adf', + { name: '"adf"' }, + { location: { min: 40, max: 44 }, text: '"adf"' } + ), + expr.literal.integer(123, undefined, { + location: { min: 47, max: 49 }, + text: '123', + }), + { location: { min: 40, max: 49 }, text: '"adf": 123' } + ), + ], + }, + { location: { min: 39, max: 50 }, text: '{"adf": 123}' } + ), + ], + }, + { location: { min: 20, max: 51 }, text: 'count(a*(1+3),{"adf":123})' } + ), + ], + ], + }, + { location: { min: 15, max: 51 }, text: 'fn=count(a*(1+3),{"adf":123})' } + ), + Builder.option( + { + name: 'by', + args: [ + expr.column( + { + args: [ + Builder.identifier( + { name: 'b' }, + { location: { min: 56, max: 56 }, text: 'b' } + ), + ], + }, + undefined, + { location: { min: 56, max: 56 }, text: 'b' } + ), + ], + }, + { location: { min: 53, max: 56 }, text: 'STATSfn=count(a*(1+3),{"adf":123})BYb' } + ), + ], + }, + { location: { min: 9, max: 56 }, text: 'STATSfn=count(a*(1+3),{"adf":123})BYb' } + ), + Builder.command( + { + name: 'limit', + args: [ + expr.literal.integer(123, undefined, { location: { min: 66, max: 68 }, text: '123' }), + ], + }, + { location: { min: 60, max: 68 }, text: 'LIMIT123' } + ), + ], + { location: { min: 0, max: 5 }, text: 'FROMa' } + ); diff --git a/packages/esql/src/debug/__tests__/print_ast.test.ts b/packages/esql-traversal/src/debug/__tests__/print_ast.test.ts similarity index 79% rename from packages/esql/src/debug/__tests__/print_ast.test.ts rename to packages/esql-traversal/src/debug/__tests__/print_ast.test.ts index b1ff2e0e..370012fa 100644 --- a/packages/esql/src/debug/__tests__/print_ast.test.ts +++ b/packages/esql-traversal/src/debug/__tests__/print_ast.test.ts @@ -5,11 +5,11 @@ * 2.0. */ -import { Parser } from '../../parser'; -import { printAst } from '@elastic/esql-traversal'; +import { printAst } from '../print_ast'; +import * as fixtures from './fixtures'; test('can print a basic "FROM index" query AST', () => { - const { root } = Parser.parse('FROM index'); + const root = fixtures.fromIndex(); const text = printAst(root); expect('\n' + text).toBe(` @@ -20,7 +20,7 @@ query 0-9 }); test('can print a basic "FROM index" with .text field contents', () => { - const { root } = Parser.parse('FROM index'); + const root = fixtures.fromIndex(); const text = printAst(root, { text: true }); expect('\n' + text).toBe(` @@ -31,7 +31,7 @@ query 0-9, text = "FROMindex" }); test('can print a basic \'ROW 123, "foo"\' query AST', () => { - const { root } = Parser.parse('ROW 123, "foo"'); + const root = fixtures.rowNumberAndString(); const text = printAst(root); expect('\n' + text).toBe(` @@ -42,7 +42,7 @@ query 0-13 }); test('can print only node types in "compact" mode', () => { - const { root } = Parser.parse('ROW 123, "foo"'); + const root = fixtures.rowNumberAndString(); const text = printAst(root, { compact: true }); expect('\n' + text).toBe(` @@ -53,9 +53,7 @@ query }); test('can limit tree depth with "depth" option', () => { - const { root } = Parser.parse( - 'FROM a | STATS fn = count(a * (1 + 3), {"adf": 123}) BY b | LIMIT 123' - ); + const root = fixtures.fromStatsByLimit(); const text = printAst(root, { depth: 3 }); expect('\n' + text).toBe(` @@ -74,9 +72,7 @@ query 0-5 }); test('can limit total number of nodes printed with "limit" option', () => { - const { root } = Parser.parse( - 'FROM a | STATS fn = count(a * (1 + 3), {"adf": 123}) BY b | LIMIT 123' - ); + const root = fixtures.fromStatsByLimit(); const text = printAst(root, { depth: 3, limit: 5 }); expect('\n' + text).toBe(` diff --git a/packages/esql/src/parser/index.ts b/packages/esql/src/parser.ts similarity index 100% rename from packages/esql/src/parser/index.ts rename to packages/esql/src/parser.ts From 92f886ce33da6f7fc1a34256ff462ed5568206ec Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Mon, 24 Aug 2026 11:51:05 +0200 Subject: [PATCH 13/24] refactor: remove unnecessary node wrapping --- packages/esql/src/parser/core/cst_to_ast_converter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/esql/src/parser/core/cst_to_ast_converter.ts b/packages/esql/src/parser/core/cst_to_ast_converter.ts index 487bc3dd..cb0ec62b 100644 --- a/packages/esql/src/parser/core/cst_to_ast_converter.ts +++ b/packages/esql/src/parser/core/cst_to_ast_converter.ts @@ -883,8 +883,8 @@ export class CstToAstConverter { {}, { location: { - min: firstItem([resolveItem(field)])?.location?.min ?? 0, - max: firstItem([resolveItem(condition)])?.location?.max ?? 0, + min: field.location.min, + max: condition.location.max, }, } ); From fa452383bf72d7e2b2fd7e4289861c1789152c86 Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Mon, 24 Aug 2026 11:59:18 +0200 Subject: [PATCH 14/24] refactor: improve return type of `.visitValueExpression()` --- .../src/esql/cst_to_ast_converter.ts | 33 ++++--------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/packages/esql-parser/src/esql/cst_to_ast_converter.ts b/packages/esql-parser/src/esql/cst_to_ast_converter.ts index 64eb9803..d9da60ad 100644 --- a/packages/esql-parser/src/esql/cst_to_ast_converter.ts +++ b/packages/esql-parser/src/esql/cst_to_ast_converter.ts @@ -2560,14 +2560,7 @@ export class CstToAstConverter { ); } - /** - * @todo Make it return a single value, not an array. - */ - private visitValueExpression(ctx: cst.ValueExpressionContext) { - if (!ctx.getText()) { - return []; - } - + private visitValueExpression(ctx: cst.ValueExpressionContext): ast.ESQLAstExpression | undefined { if (ctx instanceof cst.ValueExpressionDefaultContext) { return this.fromOperatorExpression(ctx.operatorExpression()); } @@ -2895,9 +2888,7 @@ export class CstToAstConverter { } private fromLogicalInLeft(leftCtx: cst.ValueExpressionContext): ast.ESQLAstExpression { - return resolveItem( - this.visitValueExpression(leftCtx) ?? this.fromParserRuleToUnknown(leftCtx) - ) as ast.ESQLAstExpression; + return this.visitValueExpression(leftCtx) ?? this.fromParserRuleToUnknown(leftCtx); } private toLogicalInFunction( @@ -2943,9 +2934,7 @@ export class CstToAstConverter { private toRegexBinaryExpression( ctx: cst.LikeExpressionContext | cst.RlikeExpressionContext ): ast.ESQLBinaryExpression | undefined { - const left = resolveItem(this.visitValueExpression(ctx.valueExpression()) ?? []) as - | ast.ESQLAstExpression - | undefined; + const left = this.visitValueExpression(ctx.valueExpression()); if (!left) { return undefined; @@ -2975,9 +2964,7 @@ export class CstToAstConverter { private toRegexListExpression( ctx: cst.LikeListExpressionContext | cst.RlikeListExpressionContext ): ast.ESQLBinaryExpression | undefined { - const left = resolveItem(this.visitValueExpression(ctx.valueExpression()) ?? []) as - | ast.ESQLAstExpression - | undefined; + const left = this.visitValueExpression(ctx.valueExpression()); if (!left) { return undefined; @@ -3032,7 +3019,7 @@ export class CstToAstConverter { const arg = this.visitValueExpression(ctx.valueExpression()); if (arg) { - fn.args.push(Array.isArray(arg) ? resolveItem(arg) : arg); + fn.args.push(arg); } return fn; @@ -3876,15 +3863,9 @@ export class CstToAstConverter { continue; } - const resolved = resolveItem(element) as ast.ESQLAstExpression; - - if (!resolved) { - continue; - } - - values.push(resolved); + values.push(element); - if (resolved.incomplete) { + if (element.incomplete) { incomplete = true; } } From eff7a841ca60fb9637bd82085647f340cbee82a3 Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Mon, 24 Aug 2026 12:27:19 +0200 Subject: [PATCH 15/24] fix: remove `[right]` boxing in assignment expression --- .../src/esql/__tests__/rerank.test.ts | 10 ++--- .../src/esql/__tests__/stats.test.ts | 44 +++++++++---------- .../src/esql/cst_to_ast_converter.ts | 7 +-- .../src/pretty_print/basic_pretty_printer.ts | 1 + .../wrapping_pretty_printer/printer.ts | 5 ++- 5 files changed, 30 insertions(+), 37 deletions(-) diff --git a/packages/esql-parser/src/esql/__tests__/rerank.test.ts b/packages/esql-parser/src/esql/__tests__/rerank.test.ts index adf9936c..b88ee2c7 100644 --- a/packages/esql-parser/src/esql/__tests__/rerank.test.ts +++ b/packages/esql-parser/src/esql/__tests__/rerank.test.ts @@ -136,12 +136,10 @@ describe('RERANK', () => { name: '=', args: [ {}, - [ - { - type: 'function', - name: 'substring', - }, - ], + { + type: 'function', + name: 'substring', + }, ], }, ], diff --git a/packages/esql-parser/src/esql/__tests__/stats.test.ts b/packages/esql-parser/src/esql/__tests__/stats.test.ts index b44ae954..01e119c9 100644 --- a/packages/esql-parser/src/esql/__tests__/stats.test.ts +++ b/packages/esql-parser/src/esql/__tests__/stats.test.ts @@ -102,18 +102,16 @@ describe('STATS', () => { }, ], }, - [ - { - type: 'function', - name: 'agg', - args: [ - { - type: 'literal', - valueUnquoted: 'salary', - }, - ], - }, - ], + { + type: 'function', + name: 'agg', + args: [ + { + type: 'literal', + valueUnquoted: 'salary', + }, + ], + }, ], }, ], @@ -293,18 +291,16 @@ describe('STATS', () => { }, ], }, - [ - { - type: 'function', - name: 'agg', - args: [ - { - type: 'literal', - valueUnquoted: 'salary', - }, - ], - }, - ], + { + type: 'function', + name: 'agg', + args: [ + { + type: 'literal', + valueUnquoted: 'salary', + }, + ], + }, ], }, { diff --git a/packages/esql-parser/src/esql/cst_to_ast_converter.ts b/packages/esql-parser/src/esql/cst_to_ast_converter.ts index d9da60ad..541b396e 100644 --- a/packages/esql-parser/src/esql/cst_to_ast_converter.ts +++ b/packages/esql-parser/src/esql/cst_to_ast_converter.ts @@ -3392,12 +3392,7 @@ export class CstToAstConverter { if (qualifiedNameCtx && ctx.ASSIGN()) { const left = this.fromQualifiedName(qualifiedNameCtx); const right = this.fromBooleanExpressionToExpressionOrUnknown(ctx.booleanExpression()); - const args = [ - left, - // TODO: Remove array boxing here. This fails many autocomplete tests, - // should be probably fixed in a standalone PR. - [right], - ] as ast.ESQLBinaryExpression['args']; + const args = [left, right] as ast.ESQLBinaryExpression['args']; const assignment = this.toFunction( '=', diff --git a/packages/esql/src/pretty_print/basic_pretty_printer.ts b/packages/esql/src/pretty_print/basic_pretty_printer.ts index e82afba4..2443410b 100644 --- a/packages/esql/src/pretty_print/basic_pretty_printer.ts +++ b/packages/esql/src/pretty_print/basic_pretty_printer.ts @@ -455,6 +455,7 @@ export class BasicPrettyPrinter { const shouldGroup = operandGroup && + group !== BinaryExpressionGroup.assignment && (operandGroup === BinaryExpressionGroup.unknown || operandGroup < group || // Right operand at same precedence needs parens for /, -, % diff --git a/packages/esql/src/pretty_print/wrapping_pretty_printer/printer.ts b/packages/esql/src/pretty_print/wrapping_pretty_printer/printer.ts index d8032918..dfe22d49 100644 --- a/packages/esql/src/pretty_print/wrapping_pretty_printer/printer.ts +++ b/packages/esql/src/pretty_print/wrapping_pretty_printer/printer.ts @@ -687,8 +687,11 @@ export class WrappingPrettyPrinter { const group_of = binaryExpressionGroup(node); const leftGroup = binaryExpressionGroup(leftItem); const rightGroup = binaryExpressionGroup(rightItem); - const wrapLeft = leftGroup !== BinaryExpressionGroup.none && leftGroup < group_of; + const isAssignment = group_of === BinaryExpressionGroup.assignment; + const wrapLeft = + !isAssignment && leftGroup !== BinaryExpressionGroup.none && leftGroup < group_of; const wrapRight = + !isAssignment && rightGroup !== BinaryExpressionGroup.none && (rightGroup < group_of || (rightGroup === group_of && (node.name === '/' || node.name === '-' || node.name === '%'))); From d2a613bcf9f43ae7fb75b3c5effe052fb9e380c9 Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Thu, 27 Aug 2026 10:51:12 +0200 Subject: [PATCH 16/24] feat: remove `.fromConstantToArray()` and make use of `.fromConstantStrict()` --- .../src/esql/__tests__/limit.test.ts | 28 +++++------ .../src/esql/cst_to_ast_converter.ts | 49 ++++++------------- 2 files changed, 28 insertions(+), 49 deletions(-) diff --git a/packages/esql-parser/src/esql/__tests__/limit.test.ts b/packages/esql-parser/src/esql/__tests__/limit.test.ts index 5498641f..db4dfa9c 100644 --- a/packages/esql-parser/src/esql/__tests__/limit.test.ts +++ b/packages/esql-parser/src/esql/__tests__/limit.test.ts @@ -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, }, - ], + }, ], }, ]); diff --git a/packages/esql-parser/src/esql/cst_to_ast_converter.ts b/packages/esql-parser/src/esql/cst_to_ast_converter.ts index 541b396e..419a284c 100644 --- a/packages/esql-parser/src/esql/cst_to_ast_converter.ts +++ b/packages/esql-parser/src/esql/cst_to_ast_converter.ts @@ -303,7 +303,7 @@ export class CstToAstConverter { // Handle constant value if (constantCtx) { - const right = this.fromConstantToArray(constantCtx) as ast.ESQLLiteral; + const right = this.fromConstantStrict(constantCtx); const expression = this.toBinaryExpression('=', ctx, [left, right]); if (left.incomplete || right.incomplete) { @@ -764,7 +764,7 @@ export class CstToAstConverter { private fromLimitCommand(ctx: cst.LimitCommandContext): ast.ESQLCommand<'limit'> { const command = this.createCommand('limit', ctx); if (ctx.constant()) { - const limitValue = this.fromConstantToArray(ctx.constant()); + const limitValue = this.fromConstant(ctx.constant()); if (limitValue != null) { command.args.push(limitValue); } @@ -1062,7 +1062,7 @@ export class CstToAstConverter { options.push(option); // it can throw while accessing constant for incomplete commands, so try catch it try { - const optionValue = this.fromConstantToArray(optionCtx.constant()); + const optionValue = this.fromConstant(optionCtx.constant()); if (optionValue != null) { option.args.push(optionValue); } @@ -1531,7 +1531,7 @@ export class CstToAstConverter { const command = this.createCommand('sample', ctx); if (ctx.constant()) { - const probability = this.fromConstantToArray(ctx.constant()); + const probability = this.fromConstant(ctx.constant()); if (probability != null) { command.args.push(probability); } @@ -1574,7 +1574,7 @@ export class CstToAstConverter { return; } - const queryText = this.fromConstantToArray(ctx._queryText); + const queryText = this.fromConstant(ctx._queryText); if (!queryText) { return; } @@ -2317,7 +2317,7 @@ export class CstToAstConverter { const limitOption = this.toOption(limitToken.getText().toLowerCase(), limitValueCtx); - limitOption.args.push(this.fromConstantToArray(limitValueCtx)); + limitOption.args.push(this.fromConstantStrict(limitValueCtx)); limitOption.location.min = limitToken.symbol.start; limitOption.location.max = limitValueCtx.stop?.stop ?? limitToken.symbol.stop; @@ -3040,7 +3040,7 @@ export class CstToAstConverter { const constantCtx = ctx.constant(); if (constantCtx) { - const constantExpression = this.fromConstantToArray(constantCtx); + const constantExpression = this.fromConstantStrict(constantCtx); return this.toBinaryExpression(':', ctx, [expression, constantExpression]); } @@ -3561,7 +3561,7 @@ export class CstToAstConverter { const constantCtx = valueCtx.constant(); if (constantCtx) { - value = this.fromConstantToArray(constantCtx) as ast.ESQLAstExpression; + value = this.fromConstant(constantCtx); } const mapExpressionCtx = valueCtx.mapExpression(); @@ -3591,25 +3591,13 @@ export class CstToAstConverter { // ----------------------------------------------------- constant expressions - private fromConstant(ctx: cst.ConstantContext): ast.ESQLAstExpression | undefined { - const node = this.fromConstantToArray(ctx); - - if (Array.isArray(node)) { - return resolveItem(node); - } - - return node; - } - private fromConstantStrict(ctx: cst.ConstantContext): ast.ESQLAstExpression { return this.fromConstant(ctx) ?? this.fromParserRuleToUnknown(ctx); } - /** - * @todo Make return type more specific. - * @todo Make it not return arrays. - */ - private fromConstantToArray(ctx: cst.ConstantContext): ast.ESQLAstItem { + private fromConstant( + ctx: cst.ConstantContext + ): ast.ESQLLiteral | ast.ESQLList | ast.ESQLUnknownItem | undefined { if (ctx instanceof cst.NullLiteralContext) { return this.toLiteral('null', ctx.NULL()); } else if (ctx instanceof cst.QualifiedIntegerLiteralContext) { @@ -3766,19 +3754,12 @@ export class CstToAstConverter { return this.toParam(ctx); } - private fromInputParameter(ctx: cst.InputParameterContext): ast.ESQLLiteral[] { - const values: ast.ESQLLiteral[] = []; - const children = ctx.children; - - if (children) { - for (const child of children) { - const param = this.toParam(child); + private fromInputParameter(ctx: cst.InputParameterContext): ast.ESQLParam | undefined { + for (const child of ctx.children ?? []) { + const param = this.toParam(child); - if (param) values.push(param); - } + if (param) return param; } - - return values; } private toParam(ctx: antlr.ParseTree): ast.ESQLParam | undefined { From bdf2b8844f35aba403d5589e1f09a97698e233eb Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Thu, 27 Aug 2026 11:04:10 +0200 Subject: [PATCH 17/24] feat: use `unknown` for missing right side node --- .../src/esql/__tests__/header.set.test.ts | 5 +++- .../src/esql/cst_to_ast_converter.ts | 25 ++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/esql-parser/src/esql/__tests__/header.set.test.ts b/packages/esql-parser/src/esql/__tests__/header.set.test.ts index ee8e5292..6280d87c 100644 --- a/packages/esql-parser/src/esql/__tests__/header.set.test.ts +++ b/packages/esql-parser/src/esql/__tests__/header.set.test.ts @@ -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, }, ], diff --git a/packages/esql-parser/src/esql/cst_to_ast_converter.ts b/packages/esql-parser/src/esql/cst_to_ast_converter.ts index 419a284c..e6ac3546 100644 --- a/packages/esql-parser/src/esql/cst_to_ast_converter.ts +++ b/packages/esql-parser/src/esql/cst_to_ast_converter.ts @@ -96,6 +96,16 @@ export class CstToAstConverter { }; } + private toUnknownMissingNode(anchor: antlr.Token): ast.ESQLUnknownItem { + return { + type: 'unknown', + name: 'unknown', + text: '', + location: { min: anchor.stop, max: anchor.stop }, + incomplete: true, + }; + } + /** * Extends `fn.location` to cover all its arguments. * @@ -326,7 +336,8 @@ export class CstToAstConverter { } // Handle missing value (incomplete assignment) if (assignToken) { - const expression = this.toBinaryExpression('=', ctx, [left, []]); + const right = this.toUnknownMissingNode(assignToken.symbol); + const expression = this.toBinaryExpression('=', ctx, [left, right]); expression.incomplete = true; expression.location = { min: left.location.min, @@ -1258,8 +1269,9 @@ export class CstToAstConverter { for (const clause of clauses) { if (clause._enrichField) { const args: ast.ESQLColumn[] = []; + const assignCtx = clause.ASSIGN(); - if (clause.ASSIGN()) { + if (assignCtx) { args.push(this.toColumn(clause._newName)); if (textExistsAndIsValid(clause._enrichField?.getText())) { args.push(this.toColumn(clause._enrichField)); @@ -1273,7 +1285,14 @@ export class CstToAstConverter { } if (args.length) { const fn = this.toFunction('=', clause, undefined, 'binary-expression'); - fn.args.push(args[0], args[1] ? [args[1]] : []); + let right: ast.ESQLAstExpression | undefined = args[1]; + + if (!right) { + right = this.toUnknownMissingNode(assignCtx!.symbol); + fn.incomplete = true; + } + + fn.args.push(args[0], right); option.args.push(fn); } } From 6587e714dff06fc0a3c9d9576ceeec431388968c Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Thu, 27 Aug 2026 11:29:38 +0200 Subject: [PATCH 18/24] feat: cleanup types --- packages/esql-types/src/types.ts | 2 +- .../esql/src/pretty_print/wrapping_pretty_printer/printer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/esql-types/src/types.ts b/packages/esql-types/src/types.ts index 739bb1ff..db6c0d92 100644 --- a/packages/esql-types/src/types.ts +++ b/packages/esql-types/src/types.ts @@ -401,7 +401,7 @@ export type BinaryExpressionMatchOperator = ':'; export type BinaryExpressionIn = 'in' | 'not in'; export type BinaryExpressionLogical = 'and' | 'or'; -export interface ESQLInlineCast extends ESQLAstBaseItem { +export interface ESQLInlineCast extends ESQLAstBaseItem { type: 'inlineCast'; value: ValueType; castType: string; diff --git a/packages/esql/src/pretty_print/wrapping_pretty_printer/printer.ts b/packages/esql/src/pretty_print/wrapping_pretty_printer/printer.ts index dfe22d49..12299523 100644 --- a/packages/esql/src/pretty_print/wrapping_pretty_printer/printer.ts +++ b/packages/esql/src/pretty_print/wrapping_pretty_printer/printer.ts @@ -887,7 +887,7 @@ export class WrappingPrettyPrinter { // --------------------------------------------------- Inline cast expression protected docInlineCast(node: ESQLInlineCast): Doc { - const value = node.value as ESQLAstExpression; + const value = node.value; const wrapInBrackets = value.type !== 'literal' && value.type !== 'column' && From bbbf7b5fa73e8831040d0ea21ae6cf660c3ee14f Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Thu, 27 Aug 2026 11:30:02 +0200 Subject: [PATCH 19/24] refactor: remove unnecessary `firstItem()` --- packages/esql-traversal/src/esql/visitor/contexts.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/esql-traversal/src/esql/visitor/contexts.ts b/packages/esql-traversal/src/esql/visitor/contexts.ts index f688c094..f9d116ec 100644 --- a/packages/esql-traversal/src/esql/visitor/contexts.ts +++ b/packages/esql-traversal/src/esql/visitor/contexts.ts @@ -711,9 +711,7 @@ export class InlineCastExpressionVisitorContext< public value(): ESQLAstExpression { this.ctx.assertMethodExists('visitExpression'); - const value = firstItem([this.node.value])!; - - return value; + return this.node.value; } public visitValue( From ee1abebc350cf8b417a6356261f834ed0bb8f792 Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Thu, 27 Aug 2026 11:52:19 +0200 Subject: [PATCH 20/24] refactor: remove `resolveItem()` usage --- .../src/esql/cst_to_ast_converter.ts | 18 +++++------------- .../esql-traversal/src/esql/walker/walker.ts | 9 ++++----- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/packages/esql-parser/src/esql/cst_to_ast_converter.ts b/packages/esql-parser/src/esql/cst_to_ast_converter.ts index e6ac3546..58fbd169 100644 --- a/packages/esql-parser/src/esql/cst_to_ast_converter.ts +++ b/packages/esql-parser/src/esql/cst_to_ast_converter.ts @@ -19,7 +19,7 @@ import { import { getPosition } from '../tokens'; import { PromQLParser } from '../promql'; import { nonNullable, unescapeColumn } from './helpers'; -import { firstItem, lastItem, resolveItem, singleItems } from '@elastic/esql-traversal'; +import { firstItem, lastItem, singleItems } from '@elastic/esql-traversal'; import { type ArithmeticUnaryContext } from '@elastic/esql-grammar'; import type { Parser } from './parser'; import type { PromQLAstQueryExpression } from '@elastic/esql-types'; @@ -1339,12 +1339,10 @@ export class CstToAstConverter { for (const joinPredicateCtx of joinCondition.booleanExpression_list()) { const expression = this.fromBooleanExpressionToExpressionOrUnknown(joinPredicateCtx); - if (expression) { - joinPredicates.push(expression); + joinPredicates.push(expression); - if (resolveItem(expression).incomplete) { - onOption.incomplete = true; - } + if (expression.incomplete) { + onOption.incomplete = true; } } @@ -2832,13 +2830,7 @@ export class CstToAstConverter { } if (ctx instanceof cst.BooleanDefaultContext) { - const node = this.fromBooleanDefault(ctx); - - if (Array.isArray(node)) { - return resolveItem(node); - } - - return node; + return this.fromBooleanDefault(ctx); } return undefined; diff --git a/packages/esql-traversal/src/esql/walker/walker.ts b/packages/esql-traversal/src/esql/walker/walker.ts index 9cf89f65..73e086d3 100644 --- a/packages/esql-traversal/src/esql/walker/walker.ts +++ b/packages/esql-traversal/src/esql/walker/walker.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { resolveItem } from '../utils'; import { isPromqlNode, replaceProperties, templateToPredicate } from './helpers'; import { PromqlWalker, type PromqlWalkerOptions } from '../../promql/walker'; import type * as types from '@elastic/esql-types'; @@ -858,11 +857,11 @@ export class Walker { if (this.readAndResetSkippedChildren()) return; if (options.order === 'backward') { - this.walkSingleAstItem(resolveItem(node.value), node); - this.walkSingleAstItem(resolveItem(node.key), node); + this.walkSingleAstItem(node.value, node); + this.walkSingleAstItem(node.key, node); } else { - this.walkSingleAstItem(resolveItem(node.key), node); - this.walkSingleAstItem(resolveItem(node.value), node); + this.walkSingleAstItem(node.key, node); + this.walkSingleAstItem(node.value, node); } } From 9763e9ed4638b7583d883cde6953d4eec7118aa5 Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Thu, 27 Aug 2026 12:21:49 +0200 Subject: [PATCH 21/24] refactor: narrow down types from `ESQLAstItem` to `ESQLAstExpression` --- .../src/esql/__tests__/completion.test.ts | 4 +- .../src/esql/cst_to_ast_converter.ts | 28 +--- .../src/debug/__tests__/fixtures.ts | 138 +++++++++--------- .../visitor/__tests__/fixtures_commands.ts | 28 ++-- .../visitor/__tests__/fixtures_expressions.ts | 2 +- .../src/esql/walker/__tests__/walker.test.ts | 7 +- .../esql-traversal/src/esql/walker/walker.ts | 8 +- packages/esql-types/src/types.ts | 32 ++-- 8 files changed, 115 insertions(+), 132 deletions(-) diff --git a/packages/esql-parser/src/esql/__tests__/completion.test.ts b/packages/esql-parser/src/esql/__tests__/completion.test.ts index 5406d812..d61511b6 100644 --- a/packages/esql-parser/src/esql/__tests__/completion.test.ts +++ b/packages/esql-parser/src/esql/__tests__/completion.test.ts @@ -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', () => { @@ -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', diff --git a/packages/esql-parser/src/esql/cst_to_ast_converter.ts b/packages/esql-parser/src/esql/cst_to_ast_converter.ts index 58fbd169..e2dd7008 100644 --- a/packages/esql-parser/src/esql/cst_to_ast_converter.ts +++ b/packages/esql-parser/src/esql/cst_to_ast_converter.ts @@ -124,13 +124,6 @@ export class CstToAstConverter { 'max', (args) => args.length - 1 ); - // in case of empty array as last arg, bump the max location by 3 chars (empty brackets) - if ( - Array.isArray(fn.args[fn.args.length - 1]) && - !(fn.args[fn.args.length - 1] as ast.ESQLAstItem[]).length - ) { - location.max += 3; - } } return location; } @@ -140,24 +133,17 @@ export class CstToAstConverter { * `extendLocationToArgs` is removed. */ private walkFunctionStructure( - args: ast.ESQLAstItem[], + args: ast.ESQLAstExpression[], initialLocation: ast.ESQLLocation, prop: 'min' | 'max', - getNextItemIndex: (arg: ast.ESQLAstItem[]) => number + getNextItemIndex: (arg: ast.ESQLAstExpression[]) => number ) { - let nextArg: ast.ESQLAstItem | undefined = args[getNextItemIndex(args)]; + let nextArg: ast.ESQLAstExpression | undefined = args[getNextItemIndex(args)]; const location = { ...initialLocation }; - while (Array.isArray(nextArg) || nextArg) { - if (Array.isArray(nextArg)) { - nextArg = nextArg[getNextItemIndex(nextArg)]; - } else { - location[prop] = Math[prop](location[prop], nextArg.location[prop]); - if (nextArg.type === 'function') { - nextArg = nextArg.args[getNextItemIndex(nextArg.args)]; - } else { - nextArg = undefined; - } - } + while (nextArg) { + location[prop] = Math[prop](location[prop], nextArg.location[prop]); + nextArg = + nextArg.type === 'function' ? nextArg.args[getNextItemIndex(nextArg.args)] : undefined; } return location[prop]; } diff --git a/packages/esql-traversal/src/debug/__tests__/fixtures.ts b/packages/esql-traversal/src/debug/__tests__/fixtures.ts index 1ead4c89..547b1059 100644 --- a/packages/esql-traversal/src/debug/__tests__/fixtures.ts +++ b/packages/esql-traversal/src/debug/__tests__/fixtures.ts @@ -111,78 +111,76 @@ export const fromStatsByLimit = (): ESQLAstQueryExpression => undefined, { location: { min: 15, max: 16 }, text: 'fn' } ), - [ - expr.func.node( - { - name: 'count', - subtype: 'variadic-call', - operator: Builder.identifier( - { name: 'count' }, - { location: { min: 20, max: 24 }, text: 'count' } + expr.func.node( + { + name: 'count', + subtype: 'variadic-call', + operator: Builder.identifier( + { name: 'count' }, + { location: { min: 20, max: 24 }, text: 'count' } + ), + args: [ + expr.func.node( + { + name: '*', + subtype: 'binary-expression', + args: [ + expr.column( + { + args: [ + Builder.identifier( + { name: 'a' }, + { location: { min: 26, max: 26 }, text: 'a' } + ), + ], + }, + undefined, + { location: { min: 26, max: 26 }, text: 'a' } + ), + expr.func.node( + { + name: '+', + subtype: 'binary-expression', + args: [ + expr.literal.integer(1, undefined, { + location: { min: 31, max: 31 }, + text: '1', + }), + expr.literal.integer(3, undefined, { + location: { min: 35, max: 35 }, + text: '3', + }), + ], + }, + { location: { min: 31, max: 35 }, text: '1+3' } + ), + ], + }, + { location: { min: 26, max: 36 }, text: 'a*(1+3)' } ), - args: [ - expr.func.node( - { - name: '*', - subtype: 'binary-expression', - args: [ - expr.column( - { - args: [ - Builder.identifier( - { name: 'a' }, - { location: { min: 26, max: 26 }, text: 'a' } - ), - ], - }, - undefined, - { location: { min: 26, max: 26 }, text: 'a' } - ), - expr.func.node( - { - name: '+', - subtype: 'binary-expression', - args: [ - expr.literal.integer(1, undefined, { - location: { min: 31, max: 31 }, - text: '1', - }), - expr.literal.integer(3, undefined, { - location: { min: 35, max: 35 }, - text: '3', - }), - ], - }, - { location: { min: 31, max: 35 }, text: '1+3' } - ), - ], - }, - { location: { min: 26, max: 36 }, text: 'a*(1+3)' } - ), - expr.map( - { - entries: [ - expr.entry( - expr.literal.string( - 'adf', - { name: '"adf"' }, - { location: { min: 40, max: 44 }, text: '"adf"' } - ), - expr.literal.integer(123, undefined, { - location: { min: 47, max: 49 }, - text: '123', - }), - { location: { min: 40, max: 49 }, text: '"adf": 123' } + expr.map( + { + entries: [ + expr.entry( + expr.literal.string( + 'adf', + { name: '"adf"' }, + { location: { min: 40, max: 44 }, text: '"adf"' } ), - ], - }, - { location: { min: 39, max: 50 }, text: '{"adf": 123}' } - ), - ], - }, - { location: { min: 20, max: 51 }, text: 'count(a*(1+3),{"adf":123})' } - ), - ], + expr.literal.integer(123, undefined, { + location: { min: 47, max: 49 }, + text: '123', + }), + { location: { min: 40, max: 49 }, text: '"adf": 123' } + ), + ], + }, + { location: { min: 39, max: 50 }, text: '{"adf": 123}' } + ), + ], + }, + { location: { min: 20, max: 51 }, text: 'count(a*(1+3),{"adf":123})' } + ), ], }, { location: { min: 15, max: 51 }, text: 'fn=count(a*(1+3),{"adf":123})' } diff --git a/packages/esql-traversal/src/esql/visitor/__tests__/fixtures_commands.ts b/packages/esql-traversal/src/esql/visitor/__tests__/fixtures_commands.ts index f60c2551..bca41982 100644 --- a/packages/esql-traversal/src/esql/visitor/__tests__/fixtures_commands.ts +++ b/packages/esql-traversal/src/esql/visitor/__tests__/fixtures_commands.ts @@ -50,7 +50,7 @@ const statsCommand = () => expr.literal.integer(1), expr.literal.string('str'), expr.list.literal({ values: [expr.literal.boolean(true)] }), - assign(expr.column('a'), [expr.column('b')]), + assign(expr.column('a'), expr.column('b')), Builder.option({ name: 'by', args: [expr.column('field')] }), ], }); @@ -83,16 +83,17 @@ export const fromChangePoint = (): ESQLAstQueryExpression => { const stats = Builder.command({ name: 'stats', args: [ - assign(expr.column('count'), [call('count', 'COUNT', [])]), + assign(expr.column('count'), call('count', 'COUNT', [])), Builder.option({ name: 'by', args: [ - assign(expr.column('@timestamp'), [ + assign( + expr.column('@timestamp'), call('bucket', 'BUCKET', [ expr.column('@timestamp'), expr.literal.timespan(1, 'MINUTE'), - ]), - ]), + ]) + ), ], }), ], @@ -174,10 +175,11 @@ const missingInferenceId = () => export const fromRerankLimit = (): ESQLAstQueryExpression => { const query = expr.literal.string('star wars'); const fields = [ - assign(expr.column('title'), [call('x', 'X', [expr.column('title'), expr.literal.integer(2)])]), - assign(expr.column('description'), [ - call('x', 'X', [expr.column('description'), expr.literal.decimal(1.5)]), - ]), + assign(expr.column('title'), call('x', 'X', [expr.column('title'), expr.literal.integer(2)])), + assign( + expr.column('description'), + call('x', 'X', [expr.column('description'), expr.literal.decimal(1.5)]) + ), ]; const rerank: ESQLAstRerankCommand = { @@ -281,14 +283,12 @@ export const fromSubqueries = (): ESQLAstQueryExpression => { }), Builder.command({ name: 'eval', - args: [ - assign(expr.column('b'), [binary('*', [expr.column('a'), expr.literal.integer(2)])]), - ], + args: [assign(expr.column('b'), binary('*', [expr.column('a'), expr.literal.integer(2)]))], }), Builder.command({ name: 'stats', args: [ - assign(expr.column('cnt'), [call('count', 'COUNT', [expr.column('*')])]), + assign(expr.column('cnt'), call('count', 'COUNT', [expr.column('*')])), Builder.option({ name: 'by', args: [expr.column('c')] }), ], }), @@ -322,7 +322,7 @@ export const fromSubqueries = (): ESQLAstQueryExpression => { Builder.command({ name: 'stats', args: [ - assign(expr.column('max'), [call('max', 'max', [expr.column('*')])]), + assign(expr.column('max'), call('max', 'max', [expr.column('*')])), Builder.option({ name: 'by', args: [expr.column('e')] }), ], }), diff --git a/packages/esql-traversal/src/esql/visitor/__tests__/fixtures_expressions.ts b/packages/esql-traversal/src/esql/visitor/__tests__/fixtures_expressions.ts index aabc253d..4625b1a9 100644 --- a/packages/esql-traversal/src/esql/visitor/__tests__/fixtures_expressions.ts +++ b/packages/esql-traversal/src/esql/visitor/__tests__/fixtures_expressions.ts @@ -20,7 +20,7 @@ const binary = (name: string, args: [left: ESQLAstItem, right: ESQLAstItem]): ES expr.func.node({ name, subtype: 'binary-expression', args }); const assign = (target: string, value: ESQLAstItem): ESQLFunction => - binary('=', [expr.column(target), [value]]); + binary('=', [expr.column(target), value]); const from = (...indices: string[]) => Builder.command({ name: 'from', args: indices.map((index) => expr.source.index(index)) }); diff --git a/packages/esql-traversal/src/esql/walker/__tests__/walker.test.ts b/packages/esql-traversal/src/esql/walker/__tests__/walker.test.ts index 3268cf53..1c5ed827 100644 --- a/packages/esql-traversal/src/esql/walker/__tests__/walker.test.ts +++ b/packages/esql-traversal/src/esql/walker/__tests__/walker.test.ts @@ -475,15 +475,16 @@ const fromWhereDeepInlineCast = () => binary('+', [ expr.literal.integer(1), expr.func.call('fn', [ - unary('not', [ + unary( + 'not', binary('*', [ expr.literal.integer(-1), expr.inlineCast({ castType: 'integer', value: expr.column(['a', 'b', 'c']), }), - ]), - ]), + ]) + ), ]), ]), ]), diff --git a/packages/esql-traversal/src/esql/walker/walker.ts b/packages/esql-traversal/src/esql/walker/walker.ts index 73e086d3..dd698eb2 100644 --- a/packages/esql-traversal/src/esql/walker/walker.ts +++ b/packages/esql-traversal/src/esql/walker/walker.ts @@ -753,15 +753,13 @@ export class Walker { } public walkExpression( - node: types.ESQLAstItem | types.ESQLAstExpression, + node: types.ESQLAstExpression | types.ESQLAstExpression[], parent: types.ESQLProperNode | undefined = undefined ): void { if (Array.isArray(node)) { - const list = node as types.ESQLAstItem[]; - this.walkList(list, parent); + this.walkList(node, parent); } else { - const item = node as types.ESQLSingleAstItem; - this.walkSingleAstItem(item, parent); + this.walkSingleAstItem(node, parent); } } diff --git a/packages/esql-types/src/types.ts b/packages/esql-types/src/types.ts index db6c0d92..c2eda027 100644 --- a/packages/esql-types/src/types.ts +++ b/packages/esql-types/src/types.ts @@ -30,7 +30,7 @@ export type ESQLAstCommand = export type ESQLAstAllCommands = ESQLAstCommand | ESQLAstHeaderCommand; -export type ESQLAstNode = ESQLAstCommand | ESQLAstHeaderCommand | ESQLAstExpression | ESQLAstItem; +export type ESQLAstNode = ESQLAstCommand | ESQLAstHeaderCommand | ESQLAstExpression; /** * Represents an *expression* in the AST. @@ -72,20 +72,20 @@ export type ESQLSingleAstItem = export type ESQLAstField = ESQLColumn | ESQLBinaryExpression | ESQLAstExpression | ESQLParam; /** - * An array of AST nodes represents different things in different contexts. - * For example, in command top level arguments it is treated as an "assignment expression". + * @deprecated The AST no longer contains array-boxed nodes, use + * {@link ESQLAstExpression} (or {@link ESQLSingleAstItem}) directly. */ -export type ESQLAstItem = ESQLSingleAstItem | ESQLAstItem[]; +export type ESQLAstItem = ESQLSingleAstItem; export type ESQLAstNodeWithArgs = ESQLCommand | ESQLCommandOption | ESQLFunction; export type ESQLAstNodeWithChildren = ESQLAstNodeWithArgs | ESQLList; /** - * *Proper* are nodes which are objects with `type` property, once we get rid - * of the nodes which are plain arrays, all nodes will be *proper* and we can - * remove this type. + * @deprecated Historically, *proper* nodes were nodes with a `type` property, + * as opposed to nodes represented by plain arrays. Plain-array nodes no + * longer exist, hence all nodes are now *proper* — use {@link ESQLAstNode} instead. */ -export type ESQLProperNode = ESQLAstExpression | ESQLAstCommand | ESQLAstHeaderCommand; +export type ESQLProperNode = ESQLAstNode; export interface ESQLLocation { min: number; @@ -127,7 +127,7 @@ export interface ESQLCommand extends ESQLAstBaseItem { */ commandType?: string; - args: ESQLAstItem[]; + args: ESQLAstExpression[]; } export interface ESQLAstJoinCommand extends ESQLCommand<'join'> { @@ -295,7 +295,7 @@ export type ESQLIdentifierOrParam = ESQLIdentifier | ESQLParamLiteral; export interface ESQLCommandOption extends ESQLAstBaseItem { type: 'option'; - args: ESQLAstItem[]; + args: ESQLAstExpression[]; } export interface ESQLAstQueryExpression extends ESQLAstBaseItem<''> { @@ -335,12 +335,12 @@ export interface ESQLFunction< */ operator?: ESQLIdentifier | ESQLParamLiteral; - args: ESQLAstItem[]; + args: ESQLAstExpression[]; } export interface ESQLFunctionCallExpression extends ESQLFunction<'variadic-call'> { subtype: 'variadic-call'; - args: ESQLAstItem[]; + args: ESQLAstExpression[]; } export interface ESQLUnaryExpression extends ESQLFunction< @@ -348,7 +348,7 @@ export interface ESQLUnaryExpression extends ESQLF Name > { subtype: 'unary-expression'; - args: [ESQLAstItem]; + args: [ESQLAstExpression]; } export interface ESQLPostfixUnaryExpression extends ESQLFunction< @@ -356,7 +356,7 @@ export interface ESQLPostfixUnaryExpression extend Name > { subtype: 'postfix-unary-expression'; - args: [ESQLAstItem]; + args: [ESQLAstExpression]; } /** @@ -370,14 +370,14 @@ export interface ESQLOrderExpression extends ESQLAstBaseItem { type: 'order'; order: '' | 'ASC' | 'DESC'; nulls: '' | 'NULLS FIRST' | 'NULLS LAST'; - args: [field: ESQLAstItem]; + args: [field: ESQLAstExpression]; } export interface ESQLBinaryExpression< Name extends BinaryExpressionOperator = BinaryExpressionOperator, > extends ESQLFunction<'binary-expression', Name> { subtype: 'binary-expression'; - args: [ESQLAstItem, ESQLAstItem]; + args: [ESQLAstExpression, ESQLAstExpression]; } export type BinaryExpressionOperator = From e0171db99811d7d162ddcb4aaa1841bead1aae9e Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Thu, 27 Aug 2026 12:47:00 +0200 Subject: [PATCH 22/24] refactor: remove more `resolveItem()` and `singleItem()` usages --- packages/esql-ast/src/esql/builder/builder.ts | 11 ++--- packages/esql-ast/src/esql/is.ts | 2 +- .../src/esql/cst_to_ast_converter.ts | 49 ++++++++----------- packages/esql-parser/src/esql/parser.ts | 5 +- packages/esql-traversal/src/esql/children.ts | 13 ++--- .../src/esql/visitor/contexts.ts | 29 +++-------- .../esql/visitor/global_visitor_context.ts | 4 -- .../esql-traversal/src/esql/visitor/types.ts | 1 - .../src/ast/mutate/commands/limit/index.ts | 2 +- .../src/ast/mutate/commands/rerank/index.ts | 22 +++------ .../ast/mutate/generic/commands/args/index.ts | 2 +- .../mutate/generic/commands/options/index.ts | 4 +- packages/esql/src/composer/composer_query.ts | 6 +-- .../src/pretty_print/basic_pretty_printer.ts | 23 +++------ .../wrapping_pretty_printer/printer.ts | 23 +++------ 15 files changed, 69 insertions(+), 127 deletions(-) diff --git a/packages/esql-ast/src/esql/builder/builder.ts b/packages/esql-ast/src/esql/builder/builder.ts index 511a966d..5e63db9c 100644 --- a/packages/esql-ast/src/esql/builder/builder.ts +++ b/packages/esql-ast/src/esql/builder/builder.ts @@ -34,7 +34,6 @@ import type { ESQLSource, ESQLParamLiteral, ESQLFunction, - ESQLAstItem, ESQLStringLiteral, ESQLBinaryExpression, ESQLUnaryExpression, @@ -349,7 +348,7 @@ export namespace Builder { export const call = ( nameOrOperator: string | ESQLIdentifier | ESQLParamLiteral, - args: ESQLAstItem[], + args: ESQLAstExpression[], template?: Omit, 'subtype' | 'name' | 'operator' | 'args'>, fromParser?: Partial ): ESQLFunction => { @@ -370,7 +369,7 @@ export namespace Builder { export const unary = ( name: string, - arg: ESQLAstItem, + arg: ESQLAstExpression, template?: Omit, 'subtype' | 'name' | 'operator' | 'args'>, fromParser?: Partial ): ESQLUnaryExpression => { @@ -383,7 +382,7 @@ export namespace Builder { export const postfix = ( name: string, - arg: ESQLAstItem, + arg: ESQLAstExpression, template?: Omit, 'subtype' | 'name' | 'operator' | 'args'>, fromParser?: Partial ): ESQLUnaryExpression => { @@ -396,7 +395,7 @@ export namespace Builder { export const binary = ( name: Name, - args: [left: ESQLAstItem, right: ESQLAstItem], + args: [left: ESQLAstExpression, right: ESQLAstExpression], template?: Omit>, 'subtype' | 'name' | 'args'>, fromParser?: Partial ): ESQLBinaryExpression => { @@ -409,7 +408,7 @@ export namespace Builder { } export const where = ( - args: [left: ESQLAstItem, right: ESQLAstItem], + args: [left: ESQLAstExpression, right: ESQLAstExpression], template?: Omit, 'subtype' | 'name' | 'operator' | 'args'>, fromParser?: Partial ) => Builder.expression.func.binary('where', args, template, fromParser); diff --git a/packages/esql-ast/src/esql/is.ts b/packages/esql-ast/src/esql/is.ts index aa8b3dee..a5ae3ce0 100644 --- a/packages/esql-ast/src/esql/is.ts +++ b/packages/esql-ast/src/esql/is.ts @@ -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' && diff --git a/packages/esql-parser/src/esql/cst_to_ast_converter.ts b/packages/esql-parser/src/esql/cst_to_ast_converter.ts index e2dd7008..0525889e 100644 --- a/packages/esql-parser/src/esql/cst_to_ast_converter.ts +++ b/packages/esql-parser/src/esql/cst_to_ast_converter.ts @@ -19,7 +19,6 @@ import { import { getPosition } from '../tokens'; import { PromQLParser } from '../promql'; import { nonNullable, unescapeColumn } from './helpers'; -import { firstItem, lastItem, singleItems } from '@elastic/esql-traversal'; import { type ArithmeticUnaryContext } from '@elastic/esql-grammar'; import type { Parser } from './parser'; import type { PromQLAstQueryExpression } from '@elastic/esql-types'; @@ -582,7 +581,7 @@ export class CstToAstConverter { private toOption( name: string, ctx: antlr.ParserRuleContext, - args: ast.ESQLAstItem[] = [], + args: ast.ESQLAstExpression[] = [], incomplete?: boolean ): ast.ESQLCommandOption { return { @@ -591,9 +590,7 @@ export class CstToAstConverter { text: ctx.getText(), location: getPosition(ctx.start, ctx.stop), args, - incomplete: - incomplete ?? - (Boolean(ctx.exception) || [...singleItems(args)].some((arg) => arg.incomplete)), + incomplete: incomplete ?? (Boolean(ctx.exception) || args.some((arg) => arg.incomplete)), }; } @@ -891,7 +888,7 @@ export class CstToAstConverter { private toByOption( ctx: antlr.ParserRuleContext & Pick, - args: ast.ESQLAstItem[] + args: ast.ESQLAstExpression[] ): ast.ESQLCommandOption | undefined { const byCtx = ctx.BY(); @@ -902,7 +899,7 @@ export class CstToAstConverter { const option = this.toOption(byCtx.getText().toLowerCase(), ctx, args, !args.length); option.location.min = byCtx.symbol.start; - const lastArg = lastItem(option.args); + const lastArg = option.args.at(-1); option.location.max = lastArg?.location.max ?? byCtx.symbol.stop; return option; @@ -918,10 +915,8 @@ export class CstToAstConverter { return command; } - private fromOrderExpressions( - ctx: cst.OrderExpressionContext[] - ): Array { - const expressions: Array = []; + private fromOrderExpressions(ctx: cst.OrderExpressionContext[]): ast.ESQLAstExpression[] { + const expressions: ast.ESQLAstExpression[] = []; for (const orderCtx of ctx) { expressions.push(this.fromOrderExpression(orderCtx)); @@ -930,9 +925,7 @@ export class CstToAstConverter { return expressions; } - private fromOrderExpression( - ctx: cst.OrderExpressionContext - ): ast.ESQLOrderExpression | ast.ESQLAstItem { + private fromOrderExpression(ctx: cst.OrderExpressionContext): ast.ESQLAstExpression { const arg = this.fromBooleanExpressionToExpressionOrUnknown(ctx.booleanExpression()); let order: ast.ESQLOrderExpression['order'] = ''; @@ -984,7 +977,7 @@ export class CstToAstConverter { return command; } - private fromRenameClauses(clausesCtx: cst.RenameClauseContext[]): ast.ESQLAstItem[] { + private fromRenameClauses(clausesCtx: cst.RenameClauseContext[]): ast.ESQLAstExpression[] { return clausesCtx .map((clause) => { const asToken = clause.getToken(cst.EsqlParser.AS, 0); @@ -1009,8 +1002,8 @@ export class CstToAstConverter { renameFunction.args.push(this.toColumn(arg)); } } - const firstArg = firstItem(renameFunction.args); - const lastArg = lastItem(renameFunction.args); + const firstArg = renameFunction.args.at(0); + const lastArg = renameFunction.args.at(-1); const location = renameFunction.location; if (firstArg) location.min = firstArg.location.min; if (lastArg) location.max = lastArg.location.max; @@ -1284,7 +1277,7 @@ export class CstToAstConverter { } const location = option.location; - const lastArg = lastItem(option.args); + const lastArg = option.args.at(-1); location.min = withCtx.symbol.start; location.max = lastArg?.location?.max ?? withCtx.symbol.stop; @@ -1320,7 +1313,7 @@ export class CstToAstConverter { const joinTarget = this.fromJoinTarget(ctx.joinTarget()); const joinCondition = ctx.joinCondition(); const onOption = this.toOption('on', joinCondition); - const joinPredicates: ast.ESQLAstItem[] = onOption.args; + const joinPredicates: ast.ESQLAstExpression[] = onOption.args; for (const joinPredicateCtx of joinCondition.booleanExpression_list()) { const expression = this.fromBooleanExpressionToExpressionOrUnknown(joinPredicateCtx); @@ -1625,7 +1618,7 @@ export class CstToAstConverter { onOption.args.push(...fields); onOption.location.min = onToken.symbol.start; - const lastArg = lastItem(onOption.args); + const lastArg = onOption.args.at(-1); if (lastArg) { onOption.location.max = lastArg.location.max; } @@ -1686,7 +1679,7 @@ export class CstToAstConverter { private fromFuseCommand(ctx: cst.FuseCommandContext): ast.ESQLAstFuseCommand { const fuseTypeCtx = ctx.identifier(); - const args: ast.ESQLAstItem[] = []; + const args: ast.ESQLAstExpression[] = []; let incomplete = false; const fuseType = fuseTypeCtx ? this.fromIdentifier(fuseTypeCtx) : undefined; @@ -1725,7 +1718,7 @@ export class CstToAstConverter { // SCORE BY const scoreCtx = configCtx.SCORE(); if (scoreCtx && byContext) { - const args: ast.ESQLAstItem[] = []; + const args: ast.ESQLAstExpression[] = []; const scoreColumnCtx = configCtx.qualifiedName(); if (textExistsAndIsValid(scoreColumnCtx.getText())) { @@ -1747,7 +1740,7 @@ export class CstToAstConverter { // GROUP BY const groupCtx = configCtx.GROUP(); if (groupCtx && byContext) { - const args: ast.ESQLAstItem[] = []; + const args: ast.ESQLAstExpression[] = []; const groupColumnCtx = configCtx.qualifiedName(); if (textExistsAndIsValid(groupColumnCtx.getText())) { @@ -1761,7 +1754,7 @@ export class CstToAstConverter { // WITH const withCtx = configCtx.WITH(); if (withCtx) { - const args: ast.ESQLAstItem[] = []; + const args: ast.ESQLAstExpression[] = []; const mapExpressionCtx = configCtx.mapExpression(); const map = this.fromMapExpression(mapExpressionCtx); @@ -2235,7 +2228,7 @@ export class CstToAstConverter { // --------------------------------------------------------------------- MMR private fromMmrCommand(ctx: cst.MmrCommandContext): ast.ESQLCommand<'mmr'> { - const args: ast.ESQLAstItem[] = []; + const args: ast.ESQLAstExpression[] = []; const queryVector = this.fromMmrQueryVectorParam(ctx.mmrQueryVectorParams()); if (queryVector) args.push(queryVector); @@ -2759,8 +2752,8 @@ export class CstToAstConverter { private fromBooleanExpressions( ctx: cst.BooleanExpressionContext[] | undefined - ): ast.ESQLAstItem[] { - const list: ast.ESQLAstItem[] = []; + ): ast.ESQLAstExpression[] { + const list: ast.ESQLAstExpression[] = []; if (!ctx) { return list; @@ -3475,7 +3468,7 @@ export class CstToAstConverter { ctx: antlr.ParserRuleContext, customPosition?: ast.ESQLLocation, subtype?: Subtype, - args: ast.ESQLAstItem[] = [], + args: ast.ESQLAstExpression[] = [], incomplete?: boolean ): ast.ESQLFunction { const node: ast.ESQLFunction = { diff --git a/packages/esql-parser/src/esql/parser.ts b/packages/esql-parser/src/esql/parser.ts index 20e632b9..de13184a 100644 --- a/packages/esql-parser/src/esql/parser.ts +++ b/packages/esql-parser/src/esql/parser.ts @@ -29,7 +29,6 @@ import type { ESQLProperNode, EditorError, } from '@elastic/esql-types'; -import { singleItems } from '@elastic/esql-traversal'; import { DEFAULT_CHANNEL, SOURCE_COMMANDS } from '../constants'; import type { EsqlParsingTarget } from './types'; import { assertQueryNesting, QUERY_NESTING_ERROR_CODE, QueryNestingError } from './query_nesting'; @@ -268,7 +267,7 @@ export class Parser { } const { root, ast, errors, ...result } = Parser.parseCommand('EVAL ' + src, options); - const expressions = [...singleItems(root.args)]; + const expressions = root.args; if (expressions.length !== 1) { throw new Error( @@ -294,7 +293,7 @@ export class Parser { public static readonly parseMap = (src: string, options?: ParseOptions): ParseResult => { const { root, ast, errors, ...result } = Parser.parseCommand('ROW f(1,' + src + ')', options); - const expressions = [...singleItems(root.args)]; + const expressions = root.args; if (expressions.length !== 1) { throw new Error( diff --git a/packages/esql-traversal/src/esql/children.ts b/packages/esql-traversal/src/esql/children.ts index 4f35678b..c47730ce 100644 --- a/packages/esql-traversal/src/esql/children.ts +++ b/packages/esql-traversal/src/esql/children.ts @@ -13,7 +13,6 @@ import type { ESQLCommand, ESQLProperNode, } from '@elastic/esql-types'; -import { singleItems } from './utils'; import { childrenOfPromqlNode } from '../promql/children'; export function* children( @@ -25,11 +24,11 @@ export function* children( case 'header-command': case 'order': case 'option': { - yield* singleItems(node.args); + yield* node.args; break; } case 'list': { - yield* singleItems(node.values); + yield* node.values; break; } case 'map': { @@ -42,11 +41,7 @@ export function* children( break; } case 'inlineCast': { - if (Array.isArray(node.value)) { - yield* singleItems(node.value); - } else { - yield node.value; - } + yield node.value; break; } case 'parens': { @@ -65,7 +60,7 @@ export function* childrenOfAnyNode( } if ('args' in node && Array.isArray(node.args)) { - yield* singleItems(node.args); + yield* node.args; return; } diff --git a/packages/esql-traversal/src/esql/visitor/contexts.ts b/packages/esql-traversal/src/esql/visitor/contexts.ts index f9d116ec..a282e2b3 100644 --- a/packages/esql-traversal/src/esql/visitor/contexts.ts +++ b/packages/esql-traversal/src/esql/visitor/contexts.ts @@ -11,13 +11,11 @@ import type { SharedData } from './global_visitor_context'; import { type GlobalVisitorContext } from './global_visitor_context'; import { children } from '../children'; -import { firstItem, singleItems } from '../utils'; import type { ESQLAstChangePointCommand, ESQLAstCommand, ESQLAstExpression, ESQLAstHeaderCommand, - ESQLAstItem, ESQLAstJoinCommand, ESQLAstMetricsInfoCommand, ESQLAstQueryExpression, @@ -215,9 +213,6 @@ export class CommandVisitorContext< public *options(): Iterable { for (const arg of this.node.args) { - if (!arg || Array.isArray(arg)) { - continue; - } if (arg.type === 'option') { yield arg; } @@ -237,7 +232,7 @@ export class CommandVisitorContext< } } - public *args(option: '' | string = ''): Iterable { + public *args(option: '' | string = ''): Iterable { option = option.toLowerCase(); if (!option) { @@ -245,10 +240,6 @@ export class CommandVisitorContext< if (!arg) { continue; } - if (Array.isArray(arg)) { - yield arg; - continue; - } if (arg.type !== 'option') { yield arg; } @@ -256,7 +247,7 @@ export class CommandVisitorContext< } const optionNode = this.node.args.find( - (arg) => !Array.isArray(arg) && arg && arg.type === 'option' && arg.name === option + (arg) => arg && arg.type === 'option' && arg.name === option ); if (optionNode) { @@ -272,7 +263,7 @@ export class CommandVisitorContext< ): Iterable> { this.ctx.assertMethodExists('visitExpression'); - for (const arg of singleItems(this.args(option))) { + for (const arg of this.args(option)) { yield this.visitExpression( arg, typeof input === 'function' @@ -287,7 +278,7 @@ export class CommandVisitorContext< ): Iterable>> { this.ctx.assertMethodExists('visitSourceExpression'); - for (const arg of singleItems(this.node.args)) { + for (const arg of this.node.args) { if (arg.type === 'source') { const sourceContext = new SourceExpressionVisitorContext(this.ctx, arg, this); const result = this.ctx.methods.visitSourceExpression!(sourceContext, input); @@ -305,10 +296,6 @@ export class CommandVisitorContext< public *visitSubQueries() { this.ctx.assertMethodExists('visitQuery'); for (const arg of this.node.args) { - if (!arg || Array.isArray(arg)) { - continue; - } - if (arg.type === 'query' && 'commands' in arg) { const result = this.visitSubQuery(arg); yield result; @@ -373,7 +360,7 @@ export class FromCommandVisitorContext< let metadataOption: ESQLCommandOption | undefined; - for (const arg of singleItems(this.node.args)) { + for (const arg of this.node.args) { if (arg.type === 'option' && arg.name === 'metadata') { metadataOption = arg; break; @@ -384,7 +371,7 @@ export class FromCommandVisitorContext< return; } - for (const arg of singleItems(metadataOption.args)) { + for (const arg of metadataOption.args) { if (arg.type === 'column') { const columnContext = new ColumnExpressionVisitorContext(this.ctx, arg, this); const result = this.ctx.methods.visitColumnExpression!(columnContext, input); @@ -404,7 +391,7 @@ export class LimitCommandVisitorContext< * @returns The first numeric literal argument of the command. */ public numericLiteral(): ESQLIntegerLiteral | ESQLDecimalLiteral | undefined { - const arg = firstItem(this.node.args); + const arg = this.node.args[0]; if ( arg && @@ -426,7 +413,7 @@ export class LimitCommandVisitorContext< public setLimit(value: number): void { const literalNode = Builder.expression.literal.numeric({ value, literalType: 'integer' }); - const options = this.node.args.filter((arg) => !Array.isArray(arg) && arg.type === 'option'); + const options = this.node.args.filter((arg) => arg.type === 'option'); this.node.args = [literalNode, ...options]; } diff --git a/packages/esql-traversal/src/esql/visitor/global_visitor_context.ts b/packages/esql-traversal/src/esql/visitor/global_visitor_context.ts index c789d950..2a8b0fdb 100644 --- a/packages/esql-traversal/src/esql/visitor/global_visitor_context.ts +++ b/packages/esql-traversal/src/esql/visitor/global_visitor_context.ts @@ -760,10 +760,6 @@ export class GlobalVisitorContext< expressionNode: types.ESQLAstExpressionNode, input: types.ExpressionVisitorInput ): types.ExpressionVisitorOutput { - if (Array.isArray(expressionNode)) { - throw new Error('should not happen'); - } - switch (expressionNode.type) { case 'column': { if (!this.methods.visitColumnExpression) break; diff --git a/packages/esql-traversal/src/esql/visitor/types.ts b/packages/esql-traversal/src/esql/visitor/types.ts index 2db682b2..25fe5bba 100644 --- a/packages/esql-traversal/src/esql/visitor/types.ts +++ b/packages/esql-traversal/src/esql/visitor/types.ts @@ -17,7 +17,6 @@ export type ESQLAstQueryNode = ast.ESQLAstQueryExpression; /** * Represents an "expression" node in the AST. */ -// export type ESQLAstExpressionNode = ESQLAstItem; export type ESQLAstExpressionNode = ast.ESQLAstExpression; /** diff --git a/packages/esql/src/ast/mutate/commands/limit/index.ts b/packages/esql/src/ast/mutate/commands/limit/index.ts index fde3d7fb..c868d515 100644 --- a/packages/esql/src/ast/mutate/commands/limit/index.ts +++ b/packages/esql/src/ast/mutate/commands/limit/index.ts @@ -93,7 +93,7 @@ export const set = ( } const literal = Builder.expression.literal.numeric({ literalType: 'integer', value }); - const options = node.args.filter((arg) => !Array.isArray(arg) && arg.type === 'option'); + const options = node.args.filter((arg) => arg.type === 'option'); node.args = [literal, ...options]; diff --git a/packages/esql/src/ast/mutate/commands/rerank/index.ts b/packages/esql/src/ast/mutate/commands/rerank/index.ts index 25d3de87..a939c4b8 100644 --- a/packages/esql/src/ast/mutate/commands/rerank/index.ts +++ b/packages/esql/src/ast/mutate/commands/rerank/index.ts @@ -13,7 +13,7 @@ import type { ESQLStringLiteral, ESQLParamLiteral, ESQLMap, - ESQLAstItem, + ESQLAstExpression, ESQLMapEntry, ESQLLiteral, } from '../../../../types'; @@ -44,12 +44,7 @@ export const setQuery = (cmd: ESQLAstRerankCommand, query: string | ESQLStringLi cmd.query = queryLiteral; - if ( - firstArg && - !Array.isArray(firstArg) && - firstArg.type === 'function' && - firstArg.name === '=' - ) { + if (firstArg && firstArg.type === 'function' && firstArg.name === '=') { // It's an assignment, update the right side of the expression firstArg.args[1] = queryLiteral; } else { @@ -68,8 +63,7 @@ export const setQuery = (cmd: ESQLAstRerankCommand, query: string | ESQLStringLi */ export const setTargetField = (cmd: ESQLAstRerankCommand, target: string | null) => { const firstArg = cmd.args[0]; - const isAssignment = - firstArg && !Array.isArray(firstArg) && firstArg.type === 'function' && firstArg.name === '='; + const isAssignment = firstArg && firstArg.type === 'function' && firstArg.name === '='; // Case 1: Set a new target field if (target !== null) { @@ -117,8 +111,8 @@ export const setFields = ( }); } - const isOnOption = (arg: ESQLAstItem): arg is ESQLCommandOption => - !!arg && !Array.isArray(arg) && arg.type === 'option' && arg.name === 'on'; + const isOnOption = (arg: ESQLAstExpression): arg is ESQLCommandOption => + !!arg && arg.type === 'option' && arg.name === 'on'; const onOption = cmd.args.find(isOnOption); @@ -156,14 +150,14 @@ export const setWithParameter = ( return val; }; - const isWithOption = (arg: ESQLAstItem): arg is ESQLCommandOption => - !!arg && !Array.isArray(arg) && arg.type === 'option' && arg.name === 'with'; + const isWithOption = (arg: ESQLAstExpression): arg is ESQLCommandOption => + !!arg && arg.type === 'option' && arg.name === 'with'; // Validates and retrieves the map from a WITH option const getWithOptionMap = (withOption: ESQLCommandOption): ESQLMap => { const mapArg = withOption.args[0]; - if (!mapArg || typeof mapArg === 'string' || Array.isArray(mapArg) || mapArg.type !== 'map') { + if (!mapArg || mapArg.type !== 'map') { throw new Error('WITH option must contain a map'); } diff --git a/packages/esql/src/ast/mutate/generic/commands/args/index.ts b/packages/esql/src/ast/mutate/generic/commands/args/index.ts index 095e498a..34eedf4e 100644 --- a/packages/esql/src/ast/mutate/generic/commands/args/index.ts +++ b/packages/esql/src/ast/mutate/generic/commands/args/index.ts @@ -71,7 +71,7 @@ export const remove = ( return ctx.node; } - if (!Array.isArray(arg) && isSubQuery(arg)) { + if (isSubQuery(arg)) { const found = remove(arg.child, node); if (found) { diff --git a/packages/esql/src/ast/mutate/generic/commands/options/index.ts b/packages/esql/src/ast/mutate/generic/commands/options/index.ts index b4eee4f9..44b886d0 100644 --- a/packages/esql/src/ast/mutate/generic/commands/options/index.ts +++ b/packages/esql/src/ast/mutate/generic/commands/options/index.ts @@ -7,7 +7,7 @@ import { Builder } from '@elastic/esql-ast'; import type { - ESQLAstItem, + ESQLAstExpression, ESQLAstQueryExpression, ESQLCommand, ESQLCommandOption, @@ -110,7 +110,7 @@ export const remove = (ast: ESQLAstQueryExpression, option: ESQLCommandOption): return false; } - const index = (ctx.node.args as ESQLAstItem[]).indexOf(target); + const index = (ctx.node.args as ESQLAstExpression[]).indexOf(target); if (index === -1) { return false; diff --git a/packages/esql/src/composer/composer_query.ts b/packages/esql/src/composer/composer_query.ts index 81cc5bfa..03a67002 100644 --- a/packages/esql/src/composer/composer_query.ts +++ b/packages/esql/src/composer/composer_query.ts @@ -42,7 +42,7 @@ import type { QueryCommandTag, QueryCommandTagParametrized, } from './types'; -import { Walker, replaceProperties, resolveItem } from '@elastic/esql-traversal'; +import { Walker, replaceProperties } from '@elastic/esql-traversal'; import { printAst } from '../debug'; export class ComposerQuery { @@ -1182,8 +1182,8 @@ export class ComposerQuery { const left = arg.args[0]; const right = arg.args[1]; - if (isIdentifier(left) && (isProperNode(right) || Array.isArray(right))) { - sets.push([left.name as string, resolveItem(right)]); + if (isIdentifier(left) && isProperNode(right)) { + sets.push([left.name as string, right]); } } } diff --git a/packages/esql/src/pretty_print/basic_pretty_printer.ts b/packages/esql/src/pretty_print/basic_pretty_printer.ts index 2443410b..1bfcaa1d 100644 --- a/packages/esql/src/pretty_print/basic_pretty_printer.ts +++ b/packages/esql/src/pretty_print/basic_pretty_printer.ts @@ -23,7 +23,7 @@ import { binaryExpressionGroup, unaryExpressionGroup, } from '../ast/grouping'; -import { Visitor, resolveItem, type ESQLAstExpressionNode } from '@elastic/esql-traversal'; +import { Visitor, type ESQLAstExpressionNode } from '@elastic/esql-traversal'; import { commandOptionsWithEqualsSeparator, commandsWithNoCommaArgSeparator, @@ -32,7 +32,7 @@ import { import type { ESQLAstBaseItem, ESQLAstCommand, - ESQLAstItem, + ESQLAstExpression, ESQLAstQueryExpression, ESQLMap, ESQLProperNode, @@ -211,9 +211,7 @@ export class BasicPrettyPrinter { minusCount: number = 0 ): string | undefined { if (isBinaryExpression(node) && node.name === '*') { - let [left, right] = node.args; - left = resolveItem(left); - right = resolveItem(right); + const [left, right] = node.args; if (isProperNode(left) && isProperNode(right)) { if (!!left.formatting || !!right.formatting) { @@ -449,7 +447,7 @@ export class BasicPrettyPrinter { // For assignments (=), left is the target name, right is the expression to assign. const [left, right] = ctx.arguments(); - const formatOperand = (operand: ESQLAstItem, index: number): string => { + const formatOperand = (operand: ESQLAstExpression, index: number): string => { const operandGroup = binaryExpressionGroup(operand); let formatted = ctx.visitArgument(index); @@ -556,10 +554,6 @@ export class BasicPrettyPrinter { if (cmd === 'FORK') { const branches = node.args .map((branch) => { - if (Array.isArray(branch)) { - return undefined; - } - // Check for ESQLAstQueryExpression specifically (has 'commands' property) if ( branch.type === 'parens' && @@ -615,13 +609,8 @@ export class BasicPrettyPrinter { .on('visitQuery', (ctx) => { const opts = this.opts; - let parentNode; - if (ctx.parent?.node && !Array.isArray(ctx.parent.node)) { - parentNode = ctx.parent.node; - } - - const useMultiLine = - opts.multiline && !Array.isArray(parentNode) && parentNode?.name !== 'fork'; + const parentNode = ctx.parent?.node; + const useMultiLine = opts.multiline && parentNode?.name !== 'fork'; const cmdSeparator = useMultiLine ? `\n${opts.pipeTab ?? ' '}| ` : ' | '; let text = ''; diff --git a/packages/esql/src/pretty_print/wrapping_pretty_printer/printer.ts b/packages/esql/src/pretty_print/wrapping_pretty_printer/printer.ts index 12299523..8225c1c4 100644 --- a/packages/esql/src/pretty_print/wrapping_pretty_printer/printer.ts +++ b/packages/esql/src/pretty_print/wrapping_pretty_printer/printer.ts @@ -53,7 +53,6 @@ import type { ESQLOrderExpression, ESQLParens, ESQLSource, - ESQLAstItem, ESQLAstExpression, } from '@elastic/esql-types'; import type { BasicPrettyPrinterOptions } from '../basic_pretty_printer'; @@ -65,7 +64,6 @@ import { import { getPrettyPrintStats } from '../helpers'; import { PromQLWrappingPrettyPrinter } from '../../embedded_languages/promql/pretty_print'; import type { PromQLAstQueryExpression } from '@elastic/esql-types'; -import { singleItems, resolveItem } from '@elastic/esql-traversal'; import { BinaryExpressionGroup, binaryExpressionGroup, @@ -261,7 +259,7 @@ export class WrappingPrettyPrinter { protected docHeaderCommand(cmd: ESQLAstHeaderCommand): Doc { const lower = this.opts.lowercaseCommands; const name = lower ? cmd.name.toLowerCase() : cmd.name.toUpperCase(); - const argItems = [...singleItems(cmd.args)]; + const argItems = cmd.args; const argDocs = argItems.map((a) => this.docExpression(a)); const hasLBD = argItems.some((a) => getPrettyPrintStats(a).hasLineBreakingDecorations); const argsDoc: Doc = argDocs.length @@ -294,7 +292,7 @@ export class WrappingPrettyPrinter { const cmdName = this.formatCmdName(cmd); // Separate args from options - const allArgs = [...singleItems(cmd.args)]; + const allArgs = cmd.args; const args = allArgs.filter((a) => a.type !== 'option'); const opts = allArgs.filter((a) => a.type === 'option') as ESQLCommandOption[]; @@ -486,7 +484,7 @@ export class WrappingPrettyPrinter { protected docCommandOption(opt: ESQLCommandOption): Doc { const lower = this.opts.lowercaseOptions; const name = lower ? opt.name.toLowerCase() : opt.name.toUpperCase(); - const args = [...singleItems(opt.args)]; + const args = opt.args; const argDocs = args.map((a) => this.docExpression(a)); if (argDocs.length === 0) return name; @@ -574,13 +572,7 @@ export class WrappingPrettyPrinter { // -------------------------------------------------------------- Expressions - protected docExpression(node: ESQLAstItem): Doc { - if (Array.isArray(node)) { - const items = [...singleItems([node])]; - if (items.length === 1) return this.docExpression(items[0]); - return items.map((item) => this.docExpression(item)); - } - + protected docExpression(node: ESQLAstExpression): Doc { if (isPromqlNode(node)) { const promqlDoc = this.docPromql(node as PromQLAstQueryExpression); @@ -683,7 +675,7 @@ export class WrappingPrettyPrinter { } const operator = this.opts.lowercaseKeywords ? op.toLowerCase() : op.toUpperCase(); - const [leftItem, rightItem] = node.args as [ESQLAstItem, ESQLAstItem]; + const [leftItem, rightItem] = node.args; const group_of = binaryExpressionGroup(node); const leftGroup = binaryExpressionGroup(leftItem); const rightGroup = binaryExpressionGroup(rightItem); @@ -711,8 +703,7 @@ export class WrappingPrettyPrinter { */ private docSimplifyMultiplicationByOne(node: ESQLAstExpression, minusCount = 0): Doc | undefined { if (isBinaryExpression(node) && node.name === '*') { - const left = resolveItem(node.args[0]); - const right = resolveItem(node.args[1]); + const [left, right] = node.args; if (isProperNode(left) && isProperNode(right)) { if (!!left.formatting || !!right.formatting) return undefined; @@ -755,7 +746,7 @@ export class WrappingPrettyPrinter { : this.opts.lowercaseFunctions ? op.toLowerCase() : op.toUpperCase(); - const args = [...singleItems(node.args)]; + const args = node.args; if (args.length === 0) return `${name}()`; From cea43f88d845e0c3db6b3c08208b4f6349a89b8e Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Thu, 27 Aug 2026 13:15:15 +0200 Subject: [PATCH 23/24] feat: deprecate array unboxing helper methods --- .../__tests__/binary_expression_grouping.test.ts | 8 ++------ packages/esql-traversal/src/esql/utils.ts | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/esql-parser/src/esql/__tests__/binary_expression_grouping.test.ts b/packages/esql-parser/src/esql/__tests__/binary_expression_grouping.test.ts index 30adabfe..96324580 100644 --- a/packages/esql-parser/src/esql/__tests__/binary_expression_grouping.test.ts +++ b/packages/esql-parser/src/esql/__tests__/binary_expression_grouping.test.ts @@ -6,8 +6,8 @@ */ 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, { @@ -15,10 +15,6 @@ const removeParserFields = (tree: ESQLAstQueryExpression): void => { 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)]; - } }, }); }; diff --git a/packages/esql-traversal/src/esql/utils.ts b/packages/esql-traversal/src/esql/utils.ts index 98f04d98..8651e82e 100644 --- a/packages/esql-traversal/src/esql/utils.ts +++ b/packages/esql-traversal/src/esql/utils.ts @@ -10,6 +10,9 @@ import type { ESQLAstExpression, ESQLAstItem, ESQLSingleAstItem } from '@elastic /** * Normalizes AST "item" list to only contain *single* items. * + * @deprecated The AST no longer contains array-boxed nodes — iterate the list + * directly. Kept (runtime-tolerant of legacy boxed input) for one major + * release cycle. * @param items A list of single or nested items. */ export function* singleItems( @@ -27,6 +30,9 @@ export function* singleItems( /** * Returns the first normalized "single item" from the "item" list. * + * @deprecated The AST no longer contains array-boxed nodes — use `items[0]` + * directly. Kept (runtime-tolerant of legacy boxed input) for one major + * release cycle. * @param items Returns the first "single item" from the "item" list. * @returns A "single item", if any. */ @@ -36,6 +42,11 @@ export const firstItem = (items: ESQLAstItem[]): ESQLAstExpression | undefined = } }; +/** + * @deprecated The AST no longer contains array-boxed nodes — use the item + * directly. Kept (runtime-tolerant of legacy boxed input) for one major + * release cycle. + */ export const resolveItem = (items: ESQLAstItem | ESQLAstItem[]): ESQLSingleAstItem => { return Array.isArray(items) ? resolveItem(items[0]) : items; }; @@ -43,6 +54,9 @@ export const resolveItem = (items: ESQLAstItem | ESQLAstItem[]): ESQLSingleAstIt /** * Returns the last normalized "single item" from the "item" list. * + * @deprecated The AST no longer contains array-boxed nodes — use + * `items.at(-1)` directly. Kept (runtime-tolerant of legacy boxed input) + * for one major release cycle. * @param items Returns the last "single item" from the "item" list. * @returns A "single item", if any. */ From db0a5232aaa272ed46f267d9de20f62d6fa9c77c Mon Sep 17 00:00:00 2001 From: vadimkibana Date: Fri, 28 Aug 2026 10:55:10 +0200 Subject: [PATCH 24/24] chore: add Changesets entry --- .changeset/ast-unboxing-major.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .changeset/ast-unboxing-major.md diff --git a/.changeset/ast-unboxing-major.md b/.changeset/ast-unboxing-major.md new file mode 100644 index 00000000..012f854f --- /dev/null +++ b/.changeset/ast-unboxing-major.md @@ -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`.