import { parse } from '../parser/parser'; import { locate } from '../parser/locate'; import type { Situation } from '../parser/types'; function situationAt(input: string, offset: number): Situation { const { ast, tokens } = parse(input); return locate(offset, ast, tokens); } function situationAtEnd(input: string): Situation { return situationAt(input, input.length); } describe('locate (search segment)', () => { it('returns FIELD_NAME for empty input', () => { expect(situationAt('', 0)).toMatchObject({ section: 'search', kind: 'FIELD_NAME', prefix: '', }); }); it('returns FIELD_NAME with the partial prefix while typing an identifier', () => { expect(situationAtEnd('lev')).toMatchObject({ section: 'search', kind: 'FIELD_NAME', prefix: 'lev', replaceRange: { start: 0, end: 3 }, }); }); it('returns FIELD_VALUE right after the colon', () => { expect(situationAtEnd('level:')).toMatchObject({ section: 'search', kind: 'FIELD_VALUE', fieldName: 'level', prefix: '', }); }); it('returns FIELD_VALUE with the typed value as prefix and field name carried through', () => { expect(situationAtEnd('level:E')).toMatchObject({ section: 'search', kind: 'FIELD_VALUE', fieldName: 'level', prefix: 'E', }); }); it('returns FIELD_VALUE after `=` when the vendor uses `=` as field-assign operator', () => { expect(situationAtEnd('host=')).toMatchObject({ section: 'search', kind: 'FIELD_VALUE', fieldName: 'host', }); }); it('returns VARIABLE right after a lone `$`', () => { const s = situationAtEnd('host:$'); expect(s.kind).toBe('VARIABLE'); expect(s.replaceRange).toEqual({ start: 5, end: 6 }); expect(s.prefix).toBe(''); }); it('returns VARIABLE inside an unterminated `${...`', () => { const s = situationAtEnd('host:${ho'); expect(s.kind).toBe('VARIABLE'); expect(s.prefix).toBe('ho'); expect(s.replaceRange).toEqual({ start: 5, end: 9 }); }); it('returns IN_STRING while the cursor sits inside an unterminated string', () => { expect(situationAtEnd('"foo')).toMatchObject({ section: 'search', kind: 'IN_STRING', }); }); }); describe('locate (analyze segment)', () => { const query = 'level:ERROR | SELECT count(*) AS c FROM log WHERE level=ERROR GROUP BY host ORDER BY c LIMIT 10'; it('switches to analyze section after the pipe', () => { const offset = query.indexOf('|') + 2; // just after `| ` const s = situationAt(query, offset); expect(s.section).toBe('analyze'); }); it('marks the position after SELECT as ANALYZE_FIELD', () => { const offset = query.indexOf('SELECT ') + 'SELECT '.length; expect(situationAt(query, offset).kind).toBe('ANALYZE_FIELD'); }); it('marks the position after FROM as ANALYZE_FIELD (table/field acceptable)', () => { const offset = query.indexOf('FROM ') + 'FROM '.length; expect(situationAt(query, offset).kind).toBe('ANALYZE_FIELD'); }); it('marks the position after GROUP BY as ANALYZE_FIELD', () => { const offset = query.indexOf('GROUP BY ') + 'GROUP BY '.length; expect(situationAt(query, offset).kind).toBe('ANALYZE_FIELD'); }); it('falls back to ANALYZE_KEYWORD for free-form positions', () => { const q = 'a:1 | SELECT count(*) '; expect(situationAt(q, q.length).kind).toBe('ANALYZE_KEYWORD'); }); });