Skip to content

Commit 5a08fa0

Browse files
committed
last cleanup
1 parent 85b7875 commit 5a08fa0

8 files changed

Lines changed: 85 additions & 43 deletions

File tree

src/platform/packages/shared/kbn-esql-language/src/commands/definitions/utils/ast.test.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10-
import { PromQLParser } from '@elastic/esql';
10+
import { Parser, PromQLParser, Walker } from '@elastic/esql';
11+
import type { ESQLAstItem, ESQLAstQueryExpression } from '@elastic/esql/types';
1112
import { EDITOR_MARKER } from '../constants';
1213
import {
1314
addAutocompleteMarker,
1415
correctPromqlQuerySyntax,
1516
correctQuerySyntax,
1617
getBracketsToClose,
18+
isMarkerNode,
1719
removeAutocompleteMarkers,
1820
} from './ast';
1921

@@ -134,3 +136,37 @@ describe('correctPromqlQuerySyntax', () => {
134136
expect(JSON.stringify(normalizedRoot)).not.toContain(EDITOR_MARKER);
135137
});
136138
});
139+
140+
describe('removeAutocompleteMarkers', () => {
141+
const parseAutocomplete = (innerText: string): ESQLAstQueryExpression => {
142+
const corrected = correctQuerySyntax(addAutocompleteMarker(innerText));
143+
return Parser.parse(corrected, { withFormatting: true }).root;
144+
};
145+
146+
const countMarkers = (node: ESQLAstQueryExpression): number => {
147+
let count = 0;
148+
Walker.walk(node, {
149+
visitAny: (current) => {
150+
if (isMarkerNode(current as ESQLAstItem)) {
151+
count++;
152+
}
153+
},
154+
});
155+
return count;
156+
};
157+
158+
it('drops marker-only nodes nested in args arrays', () => {
159+
const root = parseAutocomplete('FROM index | EVAL result = ROUND(doubleField, ');
160+
161+
expect(countMarkers(root)).toBeGreaterThan(0);
162+
expect(countMarkers(removeAutocompleteMarkers(root))).toBe(0);
163+
});
164+
165+
it('strips the marker from the inline cast type, not only from text', () => {
166+
const root = parseAutocomplete('FROM index | EVAL casted = keywordField::');
167+
168+
// Before cleaning, the marker leaks into inlineCast.castType (a plain string property).
169+
expect(JSON.stringify(root)).toContain(EDITOR_MARKER);
170+
expect(JSON.stringify(removeAutocompleteMarkers(root))).not.toContain(EDITOR_MARKER);
171+
});
172+
});

src/platform/packages/shared/kbn-esql-language/src/commands/definitions/utils/ast.ts

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10-
import { isList, isOptionNode, PromQLParser, Walker } from '@elastic/esql';
11-
import type { PromQLAstNode, PromQLAstQueryExpression } from '@elastic/esql';
10+
import { isList, isOptionNode, Walker } from '@elastic/esql';
11+
import type { PromQLAstNode } from '@elastic/esql';
1212
import type {
1313
ESQLFunction,
1414
ESQLSingleAstItem,
@@ -68,6 +68,11 @@ export function removeAutocompleteMarkers<T>(value: T): T {
6868
.map((item) => removeAutocompleteMarkers(item)) as T;
6969
}
7070

71+
// Strip the marker from any string value, not only `text` (e.g. inline cast `castType`).
72+
if (typeof value === 'string') {
73+
return (value.includes(EDITOR_MARKER) ? value.replace(EDITOR_MARKER, '') : value) as T;
74+
}
75+
7176
if (!value || typeof value !== 'object') {
7277
return value;
7378
}
@@ -82,14 +87,6 @@ export function removeAutocompleteMarkers<T>(value: T): T {
8287
cleanedValue[key as keyof typeof cleanedValue] = removeAutocompleteMarkers(child);
8388
}
8489

85-
if (
86-
'text' in cleanedValue &&
87-
typeof cleanedValue.text === 'string' &&
88-
cleanedValue.text.includes(EDITOR_MARKER)
89-
) {
90-
cleanedValue.text = cleanedValue.text.replace(EDITOR_MARKER, '');
91-
}
92-
9390
return cleanedValue;
9491
}
9592

@@ -275,19 +272,6 @@ export function correctPromqlQuerySyntax(input: string): string {
275272
return query + getPromqlBracketsToClose(query);
276273
}
277274

278-
export function parsePromqlAutocompleteQuery(query: string): {
279-
correctedQuery: string;
280-
root: PromQLAstQueryExpression;
281-
} {
282-
const correctedQuery = correctPromqlQuerySyntax(query);
283-
const { root } = PromQLParser.parse(correctedQuery);
284-
285-
return {
286-
correctedQuery,
287-
root: removeAutocompleteMarkers(root),
288-
};
289-
}
290-
291275
export function getPromqlBracketsToClose(text: string): string {
292276
const esqlBrackets = getBracketsToClose(text).join('');
293277
const promqlBraces = getPromqlBracesToClose(text);

src/platform/packages/shared/kbn-esql-language/src/commands/definitions/utils/autocomplete/promql/suggestion_query_engine.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type { ESQLControlVariable } from '@kbn/esql-types';
1111
import type { ICommandContext, ISuggestionItem } from '../../../../registry/types';
1212
import { getQueryPosition } from './query_position';
1313
import { getPreGroupedAggregationName } from '../../promql';
14-
import { parsePromqlAutocompleteQuery } from '../../ast';
14+
import { parsePromqlAutocompleteQuery } from '../../../../../language/shared/parse_for_autocomplete_query';
1515
import { buildVectorSuggestions } from './query_positions/suggestion_helpers';
1616
import { positionHandlers } from './query_positions/dispatcher';
1717

src/platform/packages/shared/kbn-esql-language/src/language/autocomplete/autocomplete.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ import { getColumnsByTypeRetriever } from '../shared/columns_retrieval_helpers';
3737
import { getUnmappedFieldsStrategy } from '../../commands/definitions/utils/settings';
3838
import { isTimeseriesSourceCommand } from '../../commands/definitions/utils/timeseries_check';
3939
import { attachReplacementRanges, ReplacementRangeStrategyKind } from './utils/prefix_range';
40-
import { correctQuerySyntax } from '../../commands/definitions/utils/ast';
4140

4241
function isSourceCommandSuggestion({ label }: { label: string }) {
4342
const sourceCommands = esqlCommandRegistry
@@ -73,10 +72,9 @@ export async function suggest(
7372
// Use the appropriate AST context for field retrieval
7473
// When in a subquery, use the subquery's AST to get only its fields
7574
const astForFields = astContext.astForContext;
76-
const correctedQuery = correctQuerySyntax(innerText);
7775

7876
const { getColumnsByType, getColumnMap } = getColumnsByTypeRetriever(
79-
getQueryForFields(correctedQuery, astForFields),
77+
getQueryForFields(innerText, astForFields),
8078
innerText,
8179
resourceRetriever
8280
);

src/platform/packages/shared/kbn-esql-language/src/language/shared/get_query_for_fields.test.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { BasicPrettyPrinter } from '@elastic/esql';
1111
import { getQueryForFields } from './get_query_for_fields';
1212
import { EDITOR_MARKER } from '../../commands/definitions/constants';
1313
import { parseAutocompleteQuery } from './parse_for_autocomplete_query';
14-
import { correctQuerySyntax } from '../../commands/definitions/utils/ast';
1514

1615
describe('getQueryForFields', () => {
1716
const assert = (queryBeforeCursor: string, expected: string) => {
@@ -56,13 +55,6 @@ describe('getQueryForFields', () => {
5655
});
5756

5857
it('should not treat a comma inside an incomplete function call as an EVAL separator', () => {
59-
const query = 'FROM index | EVAL result = ROUND(field, ';
60-
const { root } = parseAutocompleteQuery(query, query.length);
61-
62-
const result = getQueryForFields(correctQuerySyntax(query), root);
63-
const printedResult = BasicPrettyPrinter.print(result);
64-
65-
expect(printedResult).toEqual('FROM index');
66-
expect(printedResult).not.toContain(EDITOR_MARKER);
58+
assert('FROM index | EVAL result = ROUND(field, ', 'FROM index');
6759
});
6860
});

src/platform/packages/shared/kbn-esql-language/src/language/shared/get_query_for_fields.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
import type { ESQLAstQueryExpression, ESQLAstForkCommand } from '@elastic/esql/types';
11+
import { getBracketsToClose } from '../../commands/definitions/utils/ast';
1112
import { expandEvals } from './expand_evals';
1213

1314
/**
@@ -21,7 +22,7 @@ import { expandEvals } from './expand_evals';
2122
* IMPORTANT: the AST nodes in the new query still reference locations in the original query text
2223
*
2324
* @param queryString The original query string
24-
* @param commands
25+
* @param root
2526
* @returns
2627
*/
2728
export function getQueryForFields(
@@ -49,8 +50,10 @@ export function getQueryForFields(
4950
}
5051

5152
if (lastCommand && lastCommand.name === 'eval') {
52-
const endsWithComma = queryString.trim().endsWith(',');
53-
if (lastCommand.args.length > 1 || endsWithComma) {
53+
const hasTrailingSeparatorComma =
54+
getBracketsToClose(queryString).length === 0 && queryString.trimEnd().endsWith(',');
55+
56+
if (lastCommand.args.length > 1 || hasTrailingSeparatorComma) {
5457
/**
5558
* If we get here, we know that we have a multi-expression EVAL statement.
5659
*
@@ -64,7 +67,7 @@ export function getQueryForFields(
6467
* Simplified: FROM lolz | EVAL foo = 1 | EVAL bar = foo + 1 | EVAL baz = bar + 1
6568
*/
6669
const expanded = expandEvals(commands);
67-
const newCommands = expanded.slice(0, endsWithComma ? undefined : -1);
70+
const newCommands = expanded.slice(0, hasTrailingSeparatorComma ? undefined : -1);
6871
return { ...root, commands: newCommands };
6972
}
7073
}

src/platform/packages/shared/kbn-esql-language/src/language/shared/parse_for_autocomplete_query.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ describe('getAutocompleteCursorContext', () => {
4747
['timeseries source command after comma', 'TS timeseries_index, '],
4848
['row assignment', 'ROW total = '],
4949
['eval assignment', 'FROM employees | EVAL total = '],
50+
['inline cast type', 'FROM employees | EVAL casted = keywordField::'],
5051
['function argument', 'FROM employees | EVAL total = ROUND(salary, '],
5152
['where binary expression', 'FROM employees | WHERE age > '],
5253
['where list value', 'FROM employees | WHERE age IN (1, '],
@@ -87,4 +88,15 @@ describe('getAutocompleteCursorContext', () => {
8788
assertNoMarker(astContext.containingFunction, query, 'containingFunction');
8889
}
8990
);
91+
92+
it.each([
93+
'FROM employees | EVAL total = ',
94+
'ROW total = ',
95+
'FROM employees | EVAL total = ROUND(salary, ',
96+
'FROM employees | WHERE age IN (1, ',
97+
])('resolves an expression context (not just marker-free) for %s', (query) => {
98+
const { astContext } = getAutocompleteCursorContext(query, query.length);
99+
100+
expect(astContext.type).toBe('expression');
101+
});
90102
});

src/platform/packages/shared/kbn-esql-language/src/language/shared/parse_for_autocomplete_query.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10-
import { Parser } from '@elastic/esql';
10+
import { Parser, PromQLParser } from '@elastic/esql';
11+
import type { PromQLAstQueryExpression } from '@elastic/esql';
1112
import type { ESQLAstQueryExpression } from '@elastic/esql/types';
1213
import {
1314
addAutocompleteMarker,
15+
correctPromqlQuerySyntax,
1416
correctQuerySyntax,
1517
findAstPosition,
1618
removeAutocompleteMarkers,
@@ -19,7 +21,6 @@ import { getCursorContext } from './get_cursor_context';
1921

2022
interface ParsedAutocompleteQuery {
2123
innerText: string;
22-
correctedQuery: string;
2324
root: ESQLAstQueryExpression;
2425
}
2526

@@ -34,6 +35,22 @@ export function parseAutocompleteQuery(fullText: string, offset: number): Parsed
3435

3536
return {
3637
innerText,
38+
root: removeAutocompleteMarkers(root),
39+
};
40+
}
41+
42+
/**
43+
* PromQL counterpart of {@link parseAutocompleteQuery}: corrects partial PromQL syntax,
44+
* parses it, and strips autocomplete markers from the resulting AST.
45+
*/
46+
export function parsePromqlAutocompleteQuery(query: string): {
47+
correctedQuery: string;
48+
root: PromQLAstQueryExpression;
49+
} {
50+
const correctedQuery = correctPromqlQuerySyntax(query);
51+
const { root } = PromQLParser.parse(correctedQuery);
52+
53+
return {
3754
correctedQuery,
3855
root: removeAutocompleteMarkers(root),
3956
};

0 commit comments

Comments
 (0)