refactor(situation): use node types instead of names

This commit is contained in:
Matias Chomicki
2022-10-06 11:20:07 +02:00
parent 6119c3c727
commit 7adf94609c
@@ -1,27 +1,28 @@
import type { Tree, SyntaxNode } from '@lezer/common';
import { parser } from '@grafana/lezer-logql';
import {
parser,
VectorAggregationExpr,
String,
Selector,
RangeAggregationExpr,
Range,
PipelineExpr,
PipelineStage,
Matchers,
Matcher,
LogQL,
LogRangeExpr,
LogExpr,
Identifier,
Grouping,
Expr,
} from '@grafana/lezer-logql';
type Direction = 'parent' | 'firstChild' | 'lastChild' | 'nextSibling';
type NodeTypeName =
| '⚠' // this is used as error-name
| 'Expr'
| 'Grouping'
| 'Identifier'
| 'LogExpr'
| 'LogRangeExpr'
| 'LogQL'
| 'Matcher'
| 'Matchers'
| 'PipelineStage'
| 'PipelineExpr'
| 'Range'
| 'RangeAggregationExpr'
| 'Selector'
| 'String'
| 'VectorAggregationExpr';
type NodeType = number;
type Path = Array<[Direction, NodeTypeName]>;
type Path = Array<[Direction, NodeType]>;
function move(node: SyntaxNode, direction: Direction): SyntaxNode | null {
return node[direction];
@@ -29,13 +30,13 @@ function move(node: SyntaxNode, direction: Direction): SyntaxNode | null {
function walk(node: SyntaxNode, path: Path): SyntaxNode | null {
let current: SyntaxNode | null = node;
for (const [direction, expectedType] of path) {
for (const [direction, expectedNode] of path) {
current = move(current, direction);
if (current === null) {
// we could not move in the direction, we stop
return null;
}
if (current.type.name !== expectedType) {
if (current.type.id !== expectedNode) {
// the reached node has wrong type, we stop
return null;
}
@@ -117,55 +118,55 @@ export type Situation =
};
type Resolver = {
path: NodeTypeName[];
path: NodeType[];
fun: (node: SyntaxNode, text: string, pos: number) => Situation | null;
};
function isPathMatch(resolverPath: string[], cursorPath: string[]): boolean {
function isPathMatch(resolverPath: NodeType[], cursorPath: number[]): boolean {
return resolverPath.every((item, index) => item === cursorPath[index]);
}
const ERROR_NODE_NAME: NodeTypeName = '⚠'; // this is used as error-name
const ERROR_NODE_ID = 0;
const RESOLVERS: Resolver[] = [
{
path: ['Selector'],
path: [Selector],
fun: resolveSelector,
},
{
path: ['LogQL'],
path: [LogQL],
fun: resolveTopLevel,
},
{
path: ['String', 'Matcher'],
path: [String, Matcher],
fun: resolveMatcher,
},
{
path: ['Grouping'],
path: [Grouping],
fun: resolveLabelsForGrouping,
},
{
path: ['LogRangeExpr'],
path: [LogRangeExpr],
fun: resolveLogRange,
},
{
path: [ERROR_NODE_NAME, 'Matcher'],
path: [ERROR_NODE_ID, Matcher],
fun: resolveMatcher,
},
{
path: [ERROR_NODE_NAME, 'Range'],
path: [ERROR_NODE_ID, Range],
fun: resolveDurations,
},
{
path: [ERROR_NODE_NAME, 'LogRangeExpr'],
path: [ERROR_NODE_ID, LogRangeExpr],
fun: resolveLogRangeFromError,
},
{
path: [ERROR_NODE_NAME, 'VectorAggregationExpr'],
path: [ERROR_NODE_ID, VectorAggregationExpr],
fun: () => ({ type: 'IN_AGGREGATION' }),
},
{
path: [ERROR_NODE_NAME, 'PipelineStage', 'PipelineExpr'],
path: [ERROR_NODE_ID, PipelineStage, PipelineExpr],
fun: resolvePipeError,
},
];
@@ -182,11 +183,11 @@ function getLabelOp(opNode: SyntaxNode): LabelOperator | null {
}
function getLabel(matcherNode: SyntaxNode, text: string): Label | null {
if (matcherNode.type.name !== 'Matcher') {
if (matcherNode.type.id !== Matcher) {
return null;
}
const nameNode = walk(matcherNode, [['firstChild', 'Identifier']]);
const nameNode = walk(matcherNode, [['firstChild', Identifier]]);
if (nameNode === null) {
return null;
@@ -202,7 +203,7 @@ function getLabel(matcherNode: SyntaxNode, text: string): Label | null {
return null;
}
const valueNode = walk(matcherNode, [['lastChild', 'String']]);
const valueNode = walk(matcherNode, [['lastChild', String]]);
if (valueNode === null) {
return null;
@@ -215,16 +216,16 @@ function getLabel(matcherNode: SyntaxNode, text: string): Label | null {
}
function getLabels(selectorNode: SyntaxNode, text: string): Label[] {
if (selectorNode.type.name !== 'Selector') {
if (selectorNode.type.id !== Selector) {
return [];
}
let listNode: SyntaxNode | null = walk(selectorNode, [['firstChild', 'Matchers']]);
let listNode: SyntaxNode | null = walk(selectorNode, [['firstChild', Matchers]]);
const labels: Label[] = [];
while (listNode !== null) {
const matcherNode = walk(listNode, [['lastChild', 'Matcher']]);
const matcherNode = walk(listNode, [['lastChild', Matcher]]);
if (matcherNode === null) {
// unexpected, we stop
return [];
@@ -236,7 +237,7 @@ function getLabels(selectorNode: SyntaxNode, text: string): Label[] {
}
// there might be more labels
listNode = walk(listNode, [['firstChild', 'Matchers']]);
listNode = walk(listNode, [['firstChild', Matchers]]);
}
// our labels-list is last-first, so we reverse it
@@ -248,8 +249,8 @@ function getLabels(selectorNode: SyntaxNode, text: string): Label[] {
function resolvePipeError(node: SyntaxNode, text: string, pos: number): Situation | null {
// for example `{level="info"} |`
const exprNode = walk(node, [
['parent', 'PipelineStage'],
['parent', 'PipelineExpr'],
['parent', PipelineStage],
['parent', PipelineExpr],
]);
if (exprNode === null) {
@@ -262,9 +263,7 @@ function resolvePipeError(node: SyntaxNode, text: string, pos: number): Situatio
return null;
}
const parentName = parent.type.name;
if (parentName === 'LogExpr' || parentName === 'LogRangeExpr') {
if (parent.type.id === LogExpr || parent.type.id === LogRangeExpr) {
return resolveLogOrLogRange(parent, text, pos, true);
}
@@ -272,7 +271,7 @@ function resolvePipeError(node: SyntaxNode, text: string, pos: number): Situatio
}
function resolveLabelsForGrouping(node: SyntaxNode, text: string, pos: number): Situation | null {
const aggrExpNode = walk(node, [['parent', 'VectorAggregationExpr']]);
const aggrExpNode = walk(node, [['parent', VectorAggregationExpr]]);
if (aggrExpNode === null) {
return null;
}
@@ -282,9 +281,9 @@ function resolveLabelsForGrouping(node: SyntaxNode, text: string, pos: number):
}
const selectorNode = walk(bodyNode, [
['firstChild', 'RangeAggregationExpr'],
['lastChild', 'LogRangeExpr'],
['firstChild', 'Selector'],
['firstChild', RangeAggregationExpr],
['lastChild', LogRangeExpr],
['firstChild', Selector],
]);
if (selectorNode === null) {
@@ -305,12 +304,12 @@ function resolveMatcher(node: SyntaxNode, text: string, pos: number): Situation
// - or an error node (like in `{job=^}`)
const inStringNode = !node.type.isError;
const parent = walk(node, [['parent', 'Matcher']]);
const parent = walk(node, [['parent', Matcher]]);
if (parent === null) {
return null;
}
const labelNameNode = walk(parent, [['firstChild', 'Identifier']]);
const labelNameNode = walk(parent, [['firstChild', Identifier]]);
if (labelNameNode === null) {
return null;
}
@@ -321,7 +320,7 @@ function resolveMatcher(node: SyntaxNode, text: string, pos: number): Situation
// there can be one or many `Matchers` parents, we have
// to go through all of them
const firstListNode = walk(parent, [['parent', 'Matchers']]);
const firstListNode = walk(parent, [['parent', Matchers]]);
if (firstListNode === null) {
return null;
}
@@ -332,21 +331,19 @@ function resolveMatcher(node: SyntaxNode, text: string, pos: number): Situation
// as soon as we reach Selector, we stop
let selectorNode: SyntaxNode | null = null;
while (selectorNode === null) {
const p = listNode.parent;
if (p === null) {
const parent = listNode.parent;
if (parent === null) {
return null;
}
const { name } = p.type;
switch (name) {
case 'Matchers':
switch (parent.type.id) {
case Matchers:
//we keep looping
listNode = p;
listNode = parent;
continue;
case 'Selector':
case Selector:
// we reached the end, we can stop the loop
selectorNode = p;
selectorNode = parent;
continue;
default:
// we reached some other node, we stop
@@ -373,8 +370,8 @@ function resolveTopLevel(node: SyntaxNode, text: string, pos: number): Situation
// `{x="y"}` situation, with the cursor at the end
const logExprNode = walk(node, [
['lastChild', 'Expr'],
['lastChild', 'LogExpr'],
['lastChild', Expr],
['lastChild', LogExpr],
]);
if (logExprNode != null) {
@@ -385,8 +382,8 @@ function resolveTopLevel(node: SyntaxNode, text: string, pos: number): Situation
// (basically, user enters a non-special characters as first
// character in query field)
const idNode = walk(node, [
['firstChild', ERROR_NODE_NAME],
['firstChild', 'Identifier'],
['firstChild', ERROR_NODE_ID],
['firstChild', Identifier],
]);
if (idNode != null) {
@@ -410,7 +407,7 @@ function resolveLogRange(node: SyntaxNode, text: string, pos: number): Situation
}
function resolveLogRangeFromError(node: SyntaxNode, text: string, pos: number): Situation | null {
const parent = walk(node, [['parent', 'LogRangeExpr']]);
const parent = walk(node, [['parent', LogRangeExpr]]);
if (parent === null) {
return null;
}
@@ -421,7 +418,7 @@ function resolveLogRangeFromError(node: SyntaxNode, text: string, pos: number):
function resolveLogOrLogRange(node: SyntaxNode, text: string, pos: number, afterPipe: boolean): Situation | null {
// here the `node` is either a LogExpr or a LogRangeExpr
// we want to handle the case where we are next to a selector
const selectorNode = walk(node, [['firstChild', 'Selector']]);
const selectorNode = walk(node, [['firstChild', Selector]]);
// we check that the selector is before the cursor, not after it
if (selectorNode != null && selectorNode.to <= pos) {
@@ -441,7 +438,7 @@ function resolveSelector(node: SyntaxNode, text: string, pos: number): Situation
// false positive:
// `{a="1"^}`
const child = walk(node, [['firstChild', 'Matchers']]);
const child = walk(node, [['firstChild', Matchers]]);
if (child !== null) {
// means the label-matching part contains at least one label already.
//
@@ -511,13 +508,13 @@ export function getSituation(text: string, pos: number): Situation | null {
const currentNode = cur.node;
const names = [cur.name];
const ids = [cur.type.id];
while (cur.parent()) {
names.push(cur.name);
ids.push(cur.type.id);
}
for (let resolver of RESOLVERS) {
if (isPathMatch(resolver.path, names)) {
if (isPathMatch(resolver.path, ids)) {
return resolver.fun(currentNode, text, pos);
}
}