chore: updated grammer for value, added parsetree for finding current context

This commit is contained in:
ahrefabhi 2025-06-24 19:07:26 +05:30
parent e2498863e7
commit 3da7ce5f03
11 changed files with 3742 additions and 384 deletions

View File

@ -14,10 +14,10 @@ import { Color } from '@signozhq/design-tokens';
import { copilot } from '@uiw/codemirror-theme-copilot';
import CodeMirror, {
EditorView,
Extension,
keymap,
Extension,
} from '@uiw/react-codemirror';
import { Button, Card, Collapse, Popover, Tag } from 'antd';
import { Button, Card, Collapse, Popover, Space, Tag, Typography } from 'antd';
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion';
import cx from 'classnames';
@ -196,7 +196,6 @@ function QuerySearch({
return op.toUpperCase() === 'IN' || op.toUpperCase() === 'NOT IN';
};
// Helper function to format value based on operator type and value type
const formatValueForOperator = (
value: string,
operatorToken: string | undefined,
@ -215,7 +214,10 @@ function QuerySearch({
// If we're already inside bracket list for IN operator and it's a string value
// just wrap in quotes but not brackets (we're already in brackets)
if (type === 'value' || type === 'keyword') {
if (
(type === 'value' || type === 'keyword') &&
!/^[a-zA-Z0-9_][a-zA-Z0-9_.\[\]]*$/.test(value)
) {
return wrapStringValueInQuotes(value);
}
@ -1054,7 +1056,7 @@ function QuerySearch({
</Collapse>
</Card>
)}
{/*
{queryContext && (
<Card size="small" title="Current Context" className="query-context">
<div className="context-details">
@ -1097,7 +1099,7 @@ function QuerySearch({
</Space>
</div>
</Card>
)} */}
)}
</div>
);
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -2,25 +2,23 @@
import { ParseTreeListener } from 'antlr4';
import {
AndExpressionContext,
ArrayContext,
ComparisonContext,
ExpressionContext,
FullTextContext,
FunctionCallContext,
FunctionParamContext,
FunctionParamListContext,
InClauseContext,
KeyContext,
NotInClauseContext,
OrExpressionContext,
PrimaryContext,
QueryContext,
UnaryExpressionContext,
ValueContext,
ValueListContext,
} from './FilterQueryParser';
import { QueryContext } from './FilterQueryParser';
import { ExpressionContext } from './FilterQueryParser';
import { OrExpressionContext } from './FilterQueryParser';
import { AndExpressionContext } from './FilterQueryParser';
import { UnaryExpressionContext } from './FilterQueryParser';
import { PrimaryContext } from './FilterQueryParser';
import { ComparisonContext } from './FilterQueryParser';
import { InClauseContext } from './FilterQueryParser';
import { NotInClauseContext } from './FilterQueryParser';
import { ValueListContext } from './FilterQueryParser';
import { FullTextContext } from './FilterQueryParser';
import { FunctionCallContext } from './FilterQueryParser';
import { FunctionParamListContext } from './FilterQueryParser';
import { FunctionParamContext } from './FilterQueryParser';
import { ArrayContext } from './FilterQueryParser';
import { ValueContext } from './FilterQueryParser';
import { KeyContext } from './FilterQueryParser';
/**
* This interface defines a complete listener for a parse tree produced by
@ -32,199 +30,166 @@ export default class FilterQueryListener extends ParseTreeListener {
* @param ctx the parse tree
*/
enterQuery?: (ctx: QueryContext) => void;
/**
* Exit a parse tree produced by `FilterQueryParser.query`.
* @param ctx the parse tree
*/
exitQuery?: (ctx: QueryContext) => void;
/**
* Enter a parse tree produced by `FilterQueryParser.expression`.
* @param ctx the parse tree
*/
enterExpression?: (ctx: ExpressionContext) => void;
/**
* Exit a parse tree produced by `FilterQueryParser.expression`.
* @param ctx the parse tree
*/
exitExpression?: (ctx: ExpressionContext) => void;
/**
* Enter a parse tree produced by `FilterQueryParser.orExpression`.
* @param ctx the parse tree
*/
enterOrExpression?: (ctx: OrExpressionContext) => void;
/**
* Exit a parse tree produced by `FilterQueryParser.orExpression`.
* @param ctx the parse tree
*/
exitOrExpression?: (ctx: OrExpressionContext) => void;
/**
* Enter a parse tree produced by `FilterQueryParser.andExpression`.
* @param ctx the parse tree
*/
enterAndExpression?: (ctx: AndExpressionContext) => void;
/**
* Exit a parse tree produced by `FilterQueryParser.andExpression`.
* @param ctx the parse tree
*/
exitAndExpression?: (ctx: AndExpressionContext) => void;
/**
* Enter a parse tree produced by `FilterQueryParser.unaryExpression`.
* @param ctx the parse tree
*/
enterUnaryExpression?: (ctx: UnaryExpressionContext) => void;
/**
* Exit a parse tree produced by `FilterQueryParser.unaryExpression`.
* @param ctx the parse tree
*/
exitUnaryExpression?: (ctx: UnaryExpressionContext) => void;
/**
* Enter a parse tree produced by `FilterQueryParser.primary`.
* @param ctx the parse tree
*/
enterPrimary?: (ctx: PrimaryContext) => void;
/**
* Exit a parse tree produced by `FilterQueryParser.primary`.
* @param ctx the parse tree
*/
exitPrimary?: (ctx: PrimaryContext) => void;
/**
* Enter a parse tree produced by `FilterQueryParser.comparison`.
* @param ctx the parse tree
*/
enterComparison?: (ctx: ComparisonContext) => void;
/**
* Exit a parse tree produced by `FilterQueryParser.comparison`.
* @param ctx the parse tree
*/
exitComparison?: (ctx: ComparisonContext) => void;
/**
* Enter a parse tree produced by `FilterQueryParser.inClause`.
* @param ctx the parse tree
*/
enterInClause?: (ctx: InClauseContext) => void;
/**
* Exit a parse tree produced by `FilterQueryParser.inClause`.
* @param ctx the parse tree
*/
exitInClause?: (ctx: InClauseContext) => void;
/**
* Enter a parse tree produced by `FilterQueryParser.notInClause`.
* @param ctx the parse tree
*/
enterNotInClause?: (ctx: NotInClauseContext) => void;
/**
* Exit a parse tree produced by `FilterQueryParser.notInClause`.
* @param ctx the parse tree
*/
exitNotInClause?: (ctx: NotInClauseContext) => void;
/**
* Enter a parse tree produced by `FilterQueryParser.valueList`.
* @param ctx the parse tree
*/
enterValueList?: (ctx: ValueListContext) => void;
/**
* Exit a parse tree produced by `FilterQueryParser.valueList`.
* @param ctx the parse tree
*/
exitValueList?: (ctx: ValueListContext) => void;
/**
* Enter a parse tree produced by `FilterQueryParser.fullText`.
* @param ctx the parse tree
*/
enterFullText?: (ctx: FullTextContext) => void;
/**
* Exit a parse tree produced by `FilterQueryParser.fullText`.
* @param ctx the parse tree
*/
exitFullText?: (ctx: FullTextContext) => void;
/**
* Enter a parse tree produced by `FilterQueryParser.functionCall`.
* @param ctx the parse tree
*/
enterFunctionCall?: (ctx: FunctionCallContext) => void;
/**
* Exit a parse tree produced by `FilterQueryParser.functionCall`.
* @param ctx the parse tree
*/
exitFunctionCall?: (ctx: FunctionCallContext) => void;
/**
* Enter a parse tree produced by `FilterQueryParser.functionParamList`.
* @param ctx the parse tree
*/
enterFunctionParamList?: (ctx: FunctionParamListContext) => void;
/**
* Exit a parse tree produced by `FilterQueryParser.functionParamList`.
* @param ctx the parse tree
*/
exitFunctionParamList?: (ctx: FunctionParamListContext) => void;
/**
* Enter a parse tree produced by `FilterQueryParser.functionParam`.
* @param ctx the parse tree
*/
enterFunctionParam?: (ctx: FunctionParamContext) => void;
/**
* Exit a parse tree produced by `FilterQueryParser.functionParam`.
* @param ctx the parse tree
*/
exitFunctionParam?: (ctx: FunctionParamContext) => void;
/**
* Enter a parse tree produced by `FilterQueryParser.array`.
* @param ctx the parse tree
*/
enterArray?: (ctx: ArrayContext) => void;
/**
* Exit a parse tree produced by `FilterQueryParser.array`.
* @param ctx the parse tree
*/
exitArray?: (ctx: ArrayContext) => void;
/**
* Enter a parse tree produced by `FilterQueryParser.value`.
* @param ctx the parse tree
*/
enterValue?: (ctx: ValueContext) => void;
/**
* Exit a parse tree produced by `FilterQueryParser.value`.
* @param ctx the parse tree
*/
exitValue?: (ctx: ValueContext) => void;
/**
* Enter a parse tree produced by `FilterQueryParser.key`.
* @param ctx the parse tree
*/
enterKey?: (ctx: KeyContext) => void;
/**
* Exit a parse tree produced by `FilterQueryParser.key`.
* @param ctx the parse tree

View File

@ -1022,17 +1022,15 @@ export default class FilterQueryParser extends Parser {
try {
this.state = 202;
this._errHandler.sync(this);
switch (this._input.LA(1)) {
case 37:
switch (this._interp.adaptivePredict(this._input, 12, this._ctx)) {
case 1:
this.enterOuterAlt(localctx, 1);
{
this.state = 199;
this.key();
}
break;
case 34:
case 35:
case 36:
case 2:
this.enterOuterAlt(localctx, 2);
{
this.state = 200;
@ -1046,8 +1044,6 @@ export default class FilterQueryParser extends Parser {
this.array();
}
break;
default:
throw new NoViableAltException(this);
}
} catch (re) {
if (re instanceof RecognitionException) {
@ -1099,7 +1095,7 @@ export default class FilterQueryParser extends Parser {
{
this.state = 208;
_la = this._input.LA(1);
if (!(((_la - 34) & ~0x1f) === 0 && ((1 << (_la - 34)) & 7) !== 0)) {
if (!(((_la - 34) & ~0x1f) === 0 && ((1 << (_la - 34)) & 15) !== 0)) {
this._errHandler.recoverInline(this);
} else {
this._errHandler.reportMatch(this);
@ -1639,7 +1635,7 @@ export default class FilterQueryParser extends Parser {
1,
0,
34,
36,
37,
227,
0,
34,
@ -3809,6 +3805,9 @@ export class ValueContext extends ParserRuleContext {
public BOOL(): TerminalNode {
return this.getToken(FilterQueryParser.BOOL, 0);
}
public KEY(): TerminalNode {
return this.getToken(FilterQueryParser.KEY, 0);
}
public get ruleIndex(): number {
return FilterQueryParser.RULE_value;
}

View File

@ -2,25 +2,23 @@
import { ParseTreeVisitor } from 'antlr4';
import {
AndExpressionContext,
ArrayContext,
ComparisonContext,
ExpressionContext,
FullTextContext,
FunctionCallContext,
FunctionParamContext,
FunctionParamListContext,
InClauseContext,
KeyContext,
NotInClauseContext,
OrExpressionContext,
PrimaryContext,
QueryContext,
UnaryExpressionContext,
ValueContext,
ValueListContext,
} from './FilterQueryParser';
import { QueryContext } from './FilterQueryParser';
import { ExpressionContext } from './FilterQueryParser';
import { OrExpressionContext } from './FilterQueryParser';
import { AndExpressionContext } from './FilterQueryParser';
import { UnaryExpressionContext } from './FilterQueryParser';
import { PrimaryContext } from './FilterQueryParser';
import { ComparisonContext } from './FilterQueryParser';
import { InClauseContext } from './FilterQueryParser';
import { NotInClauseContext } from './FilterQueryParser';
import { ValueListContext } from './FilterQueryParser';
import { FullTextContext } from './FilterQueryParser';
import { FunctionCallContext } from './FilterQueryParser';
import { FunctionParamListContext } from './FilterQueryParser';
import { FunctionParamContext } from './FilterQueryParser';
import { ArrayContext } from './FilterQueryParser';
import { ValueContext } from './FilterQueryParser';
import { KeyContext } from './FilterQueryParser';
/**
* This interface defines a complete generic visitor for a parse tree produced
@ -38,112 +36,96 @@ export default class FilterQueryVisitor<
* @return the visitor result
*/
visitQuery?: (ctx: QueryContext) => Result;
/**
* Visit a parse tree produced by `FilterQueryParser.expression`.
* @param ctx the parse tree
* @return the visitor result
*/
visitExpression?: (ctx: ExpressionContext) => Result;
/**
* Visit a parse tree produced by `FilterQueryParser.orExpression`.
* @param ctx the parse tree
* @return the visitor result
*/
visitOrExpression?: (ctx: OrExpressionContext) => Result;
/**
* Visit a parse tree produced by `FilterQueryParser.andExpression`.
* @param ctx the parse tree
* @return the visitor result
*/
visitAndExpression?: (ctx: AndExpressionContext) => Result;
/**
* Visit a parse tree produced by `FilterQueryParser.unaryExpression`.
* @param ctx the parse tree
* @return the visitor result
*/
visitUnaryExpression?: (ctx: UnaryExpressionContext) => Result;
/**
* Visit a parse tree produced by `FilterQueryParser.primary`.
* @param ctx the parse tree
* @return the visitor result
*/
visitPrimary?: (ctx: PrimaryContext) => Result;
/**
* Visit a parse tree produced by `FilterQueryParser.comparison`.
* @param ctx the parse tree
* @return the visitor result
*/
visitComparison?: (ctx: ComparisonContext) => Result;
/**
* Visit a parse tree produced by `FilterQueryParser.inClause`.
* @param ctx the parse tree
* @return the visitor result
*/
visitInClause?: (ctx: InClauseContext) => Result;
/**
* Visit a parse tree produced by `FilterQueryParser.notInClause`.
* @param ctx the parse tree
* @return the visitor result
*/
visitNotInClause?: (ctx: NotInClauseContext) => Result;
/**
* Visit a parse tree produced by `FilterQueryParser.valueList`.
* @param ctx the parse tree
* @return the visitor result
*/
visitValueList?: (ctx: ValueListContext) => Result;
/**
* Visit a parse tree produced by `FilterQueryParser.fullText`.
* @param ctx the parse tree
* @return the visitor result
*/
visitFullText?: (ctx: FullTextContext) => Result;
/**
* Visit a parse tree produced by `FilterQueryParser.functionCall`.
* @param ctx the parse tree
* @return the visitor result
*/
visitFunctionCall?: (ctx: FunctionCallContext) => Result;
/**
* Visit a parse tree produced by `FilterQueryParser.functionParamList`.
* @param ctx the parse tree
* @return the visitor result
*/
visitFunctionParamList?: (ctx: FunctionParamListContext) => Result;
/**
* Visit a parse tree produced by `FilterQueryParser.functionParam`.
* @param ctx the parse tree
* @return the visitor result
*/
visitFunctionParam?: (ctx: FunctionParamContext) => Result;
/**
* Visit a parse tree produced by `FilterQueryParser.array`.
* @param ctx the parse tree
* @return the visitor result
*/
visitArray?: (ctx: ArrayContext) => Result;
/**
* Visit a parse tree produced by `FilterQueryParser.value`.
* @param ctx the parse tree
* @return the visitor result
*/
visitValue?: (ctx: ValueContext) => Result;
/**
* Visit a parse tree produced by `FilterQueryParser.key`.
* @param ctx the parse tree

View File

@ -0,0 +1,94 @@
import FilterQueryLexer from './FilterQueryLexer';
import FilterQueryParser from './FilterQueryParser';
import { ParseTreeWalker, CharStreams, CommonTokenStream, Token } from 'antlr4';
import { isOperatorToken } from 'utils/tokenUtils';
import FilterQueryListener from './FilterQueryListener';
import {
KeyContext,
ValueContext,
ComparisonContext,
} from './FilterQueryParser';
import { IToken } from 'types/antlrQueryTypes';
// 👇 Define the token classification
type TokenClassification = 'Key' | 'Value' | 'Operator';
interface TokenInfo {
text: string;
startIndex: number;
stopIndex: number;
type: TokenClassification;
}
// 👇 Custom listener to walk the parse tree
class TypeTrackingListener implements FilterQueryListener {
public tokens: TokenInfo[] = [];
enterKey(ctx: KeyContext) {
const token = ctx.KEY().symbol;
this.tokens.push({
text: token.text!,
startIndex: token.start,
stopIndex: token.stop,
type: 'Key',
});
}
enterValue(ctx: ValueContext) {
const token = ctx.start;
this.tokens.push({
text: token.text!,
startIndex: token.start,
stopIndex: token.stop,
type: 'Value',
});
}
enterComparison(ctx: ComparisonContext) {
const children = ctx.children || [];
for (const child of children) {
const token = (child as any).symbol;
if (token && isOperatorToken(token.type)) {
this.tokens.push({
text: token.text!,
startIndex: token.start,
stopIndex: token.stop,
type: 'Operator',
});
}
}
}
// Required no-op stubs
enterEveryRule() {}
exitEveryRule() {}
exitKey() {}
exitValue() {}
exitComparison() {}
visitTerminal() {}
visitErrorNode() {}
}
// 👇 Analyze function
export function analyzeQuery(input: string, lastToken: IToken) {
input = input.trim();
const chars = CharStreams.fromString(input);
const lexer = new FilterQueryLexer(chars);
const tokens = new CommonTokenStream(lexer);
const parser = new FilterQueryParser(tokens);
const tree = parser.query();
const listener = new TypeTrackingListener();
ParseTreeWalker.DEFAULT.walk(listener, tree);
const currentToken = listener.tokens.find(
(token) =>
token.text === lastToken.text &&
token.startIndex === lastToken.start &&
token.stopIndex === lastToken.stop,
);
return currentToken;
}

File diff suppressed because one or more lines are too long

View File

@ -93,6 +93,7 @@ value
: QUOTED_TEXT
| NUMBER
| BOOL
| KEY
;
key

View File

@ -3,6 +3,16 @@
import { CharStreams, CommonTokenStream, Token } from 'antlr4';
import FilterQueryLexer from 'parser/FilterQueryLexer';
import { IQueryContext, IQueryPair, IToken } from 'types/antlrQueryTypes';
import { analyzeQuery } from 'parser/analyzeQuery';
import {
isBracketToken,
isConjunctionToken,
isFunctionToken,
isKeyToken,
isMultiValueOperator,
isOperatorToken,
isValueToken,
} from './tokenUtils';
// Function to normalize multiple spaces to single spaces when not in quotes
function normalizeSpaces(query: string): string {
@ -69,7 +79,8 @@ export function createContext(
// Helper to determine token type for context
function determineTokenContext(
tokenType: number,
token: IToken,
query: string,
): {
isInKey: boolean;
isInOperator: boolean;
@ -78,57 +89,49 @@ function determineTokenContext(
isInConjunction: boolean;
isInParenthesis: boolean;
} {
// Key context
const isInKey = tokenType === FilterQueryLexer.KEY;
let isInKey: boolean = false;
let isInOperator: boolean = false;
let isInValue: boolean = false;
let isInFunction: boolean = false;
let isInConjunction: boolean = false;
let isInParenthesis: boolean = false;
// Operator context
const isInOperator = [
FilterQueryLexer.EQUALS,
FilterQueryLexer.NOT_EQUALS,
FilterQueryLexer.NEQ,
FilterQueryLexer.LT,
FilterQueryLexer.LE,
FilterQueryLexer.GT,
FilterQueryLexer.GE,
FilterQueryLexer.LIKE,
FilterQueryLexer.NOT_LIKE,
FilterQueryLexer.ILIKE,
FilterQueryLexer.NOT_ILIKE,
FilterQueryLexer.BETWEEN,
FilterQueryLexer.EXISTS,
FilterQueryLexer.REGEXP,
FilterQueryLexer.CONTAINS,
FilterQueryLexer.IN,
FilterQueryLexer.NOT,
].includes(tokenType);
const tokenType = token.type;
const currentTokenContext = analyzeQuery(query, token);
// Value context
const isInValue = [
FilterQueryLexer.QUOTED_TEXT,
FilterQueryLexer.NUMBER,
FilterQueryLexer.BOOL,
].includes(tokenType);
if (!currentTokenContext) {
// Key context
isInKey = isKeyToken(tokenType);
// Operator context
isInOperator = isOperatorToken(tokenType);
// Value context
isInValue = isValueToken(tokenType);
} else {
switch (currentTokenContext.type) {
case 'Operator':
isInOperator = true;
break;
case 'Value':
isInValue = true;
break;
case 'Key':
isInKey = true;
break;
default:
break;
}
}
// Function context
const isInFunction = [
FilterQueryLexer.HAS,
FilterQueryLexer.HASANY,
FilterQueryLexer.HASALL,
FilterQueryLexer.HASNONE,
].includes(tokenType);
isInFunction = isFunctionToken(tokenType);
// Conjunction context
const isInConjunction = [FilterQueryLexer.AND, FilterQueryLexer.OR].includes(
tokenType,
);
isInConjunction = isConjunctionToken(tokenType);
// Parenthesis context
const isInParenthesis = [
FilterQueryLexer.LPAREN,
FilterQueryLexer.RPAREN,
FilterQueryLexer.LBRACK,
FilterQueryLexer.RBRACK,
].includes(tokenType);
isInParenthesis = isBracketToken(tokenType);
return {
isInKey,
@ -140,61 +143,6 @@ function determineTokenContext(
};
}
// Helper function to check if a token is an operator
function isOperatorToken(tokenType: number): boolean {
return [
FilterQueryLexer.EQUALS,
FilterQueryLexer.NOT_EQUALS,
FilterQueryLexer.NEQ,
FilterQueryLexer.LT,
FilterQueryLexer.LE,
FilterQueryLexer.GT,
FilterQueryLexer.GE,
FilterQueryLexer.LIKE,
FilterQueryLexer.NOT_LIKE,
FilterQueryLexer.ILIKE,
FilterQueryLexer.NOT_ILIKE,
FilterQueryLexer.BETWEEN,
FilterQueryLexer.EXISTS,
FilterQueryLexer.REGEXP,
FilterQueryLexer.CONTAINS,
FilterQueryLexer.IN,
FilterQueryLexer.NOT,
].includes(tokenType);
}
// Helper function to check if a token is a value
function isValueToken(tokenType: number): boolean {
return [
FilterQueryLexer.QUOTED_TEXT,
FilterQueryLexer.NUMBER,
FilterQueryLexer.BOOL,
].includes(tokenType);
}
// Helper function to check if a token is a conjunction
function isConjunctionToken(tokenType: number): boolean {
return [FilterQueryLexer.AND, FilterQueryLexer.OR].includes(tokenType);
}
// Helper function to check if a token is a bracket
function isBracketToken(tokenType: number): boolean {
return [
FilterQueryLexer.LPAREN,
FilterQueryLexer.RPAREN,
FilterQueryLexer.LBRACK,
FilterQueryLexer.RBRACK,
].includes(tokenType);
}
// Helper function to check if an operator typically uses bracket values (multi-value operators)
function isMultiValueOperator(operatorToken?: string): boolean {
if (!operatorToken) return false;
const upperOp = operatorToken.toUpperCase();
return upperOp === 'IN' || upperOp === 'NOT IN';
}
// Function to determine token context boundaries more precisely
function determineContextBoundaries(
query: string,
@ -888,7 +836,7 @@ export function getQueryContextAtCursor(
lastTokenBeforeCursor &&
(isAtSpace || isAfterSpace || isTransitionPoint)
) {
const lastTokenContext = determineTokenContext(lastTokenBeforeCursor.type);
const lastTokenContext = determineTokenContext(lastTokenBeforeCursor, input);
// Apply the context progression logic: key → operator → value → conjunction → key
if (lastTokenContext.isInKey) {
@ -984,7 +932,7 @@ export function getQueryContextAtCursor(
// FIXED: Consider the case where the cursor is at the end of a token
// with no space yet (user is actively typing)
if (exactToken && adjustedCursorIndex === exactToken.stop + 1) {
const tokenContext = determineTokenContext(exactToken.type);
const tokenContext = determineTokenContext(exactToken, input);
// When the cursor is at the end of a token, return the current token context
return {
@ -1011,7 +959,7 @@ export function getQueryContextAtCursor(
// Regular token-based context detection (when cursor is directly on a token)
if (exactToken?.channel === 0) {
const tokenContext = determineTokenContext(exactToken.type);
const tokenContext = determineTokenContext(exactToken, input);
// Get relevant tokens based on current pair
const keyFromPair = currentPair?.key || '';
@ -1044,7 +992,7 @@ export function getQueryContextAtCursor(
// If we're between tokens but not after a space, use previous token to determine context
if (previousToken?.channel === 0) {
const prevContext = determineTokenContext(previousToken.type);
const prevContext = determineTokenContext(previousToken, input);
// Get relevant tokens based on current pair
const keyFromPair = currentPair?.key || '';
@ -1221,7 +1169,10 @@ export function extractQueryPairs(query: string): IQueryPair[] {
}
// If token is a KEY, start a new pair
if (token.type === FilterQueryLexer.KEY) {
if (
token.type === FilterQueryLexer.KEY &&
!(currentPair && currentPair.key)
) {
// If we have an existing incomplete pair, add it to the result
if (currentPair && currentPair.key) {
queryPairs.push({

View File

@ -0,0 +1,70 @@
import FilterQueryLexer from 'parser/FilterQueryLexer';
export function isKeyToken(tokenType: number): boolean {
return tokenType === FilterQueryLexer.KEY;
}
// Helper function to check if a token is an operator
export function isOperatorToken(tokenType: number): boolean {
return [
FilterQueryLexer.EQUALS,
FilterQueryLexer.NOT_EQUALS,
FilterQueryLexer.NEQ,
FilterQueryLexer.LT,
FilterQueryLexer.LE,
FilterQueryLexer.GT,
FilterQueryLexer.GE,
FilterQueryLexer.LIKE,
FilterQueryLexer.NOT_LIKE,
FilterQueryLexer.ILIKE,
FilterQueryLexer.NOT_ILIKE,
FilterQueryLexer.BETWEEN,
FilterQueryLexer.EXISTS,
FilterQueryLexer.REGEXP,
FilterQueryLexer.CONTAINS,
FilterQueryLexer.IN,
FilterQueryLexer.NOT,
].includes(tokenType);
}
// Helper function to check if a token is a value
export function isValueToken(tokenType: number): boolean {
return [
FilterQueryLexer.QUOTED_TEXT,
FilterQueryLexer.NUMBER,
FilterQueryLexer.BOOL,
FilterQueryLexer.KEY,
].includes(tokenType);
}
// Helper function to check if a token is a conjunction
export function isConjunctionToken(tokenType: number): boolean {
return [FilterQueryLexer.AND, FilterQueryLexer.OR].includes(tokenType);
}
// Helper function to check if a token is a bracket
export function isBracketToken(tokenType: number): boolean {
return [
FilterQueryLexer.LPAREN,
FilterQueryLexer.RPAREN,
FilterQueryLexer.LBRACK,
FilterQueryLexer.RBRACK,
].includes(tokenType);
}
// Helper function to check if an operator typically uses bracket values (multi-value operators)
export function isMultiValueOperator(operatorToken?: string): boolean {
if (!operatorToken) return false;
const upperOp = operatorToken.toUpperCase();
return upperOp === 'IN' || upperOp === 'NOT IN';
}
export function isFunctionToken(tokenType: number): boolean {
return [
FilterQueryLexer.HAS,
FilterQueryLexer.HASANY,
FilterQueryLexer.HASALL,
FilterQueryLexer.HASNONE,
].includes(tokenType);
}