From a40c1e227ca5a30f93fd6dbd2d0925b1ea23c144 Mon Sep 17 00:00:00 2001 From: ismail simsek Date: Mon, 8 Aug 2022 10:14:12 +0200 Subject: [PATCH] Prometheus: Use lezer-promql types instead of hardcoded strings (#53287) * Prometheus: Use lezer-promql types instead of hardcoded strings * Update comment line * Removing more hardcoded values --- .../monaco-completion-provider/situation.ts | 179 ++++++++++-------- .../prometheus/querybuilder/parsing.ts | 6 +- 2 files changed, 105 insertions(+), 80 deletions(-) diff --git a/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/situation.ts b/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/situation.ts index 4205a0b5945..77563964b6d 100644 --- a/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/situation.ts +++ b/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/situation.ts @@ -1,32 +1,54 @@ -import type { Tree, SyntaxNode } from '@lezer/common'; -import { parser } from 'lezer-promql'; +import type { SyntaxNode, Tree } from '@lezer/common'; +import { + AggregateExpr, + AggregateModifier, + EqlRegex, + EqlSingle, + FunctionCallBody, + GroupingLabels, + Identifier, + LabelMatcher, + LabelMatchers, + LabelMatchList, + LabelName, + MatchOp, + MatrixSelector, + MetricIdentifier, + Neq, + NeqRegex, + parser, + PromQL, + StringLiteral, + VectorSelector, +} from 'lezer-promql'; import { NeverCaseError } from './util'; type Direction = 'parent' | 'firstChild' | 'lastChild' | 'nextSibling'; -type NodeTypeName = - | '⚠' // this is used as error-name - | 'AggregateExpr' - | 'AggregateModifier' - | 'FunctionCallBody' - | 'GroupingLabels' - | 'Identifier' - | 'LabelMatcher' - | 'LabelMatchers' - | 'LabelMatchList' - | 'LabelName' - | 'MetricIdentifier' - | 'PromQL' - | 'StringLiteral' - | 'VectorSelector' - | 'MatrixSelector' - | 'MatchOp' - | 'EqlSingle' - | 'Neq' - | 'EqlRegex' - | 'NeqRegex'; -type Path = Array<[Direction, NodeTypeName]>; +type NodeTypeId = + | 0 // this is used as error-id + | typeof AggregateExpr + | typeof AggregateModifier + | typeof FunctionCallBody + | typeof GroupingLabels + | typeof Identifier + | typeof LabelMatcher + | typeof LabelMatchers + | typeof LabelMatchList + | typeof LabelName + | typeof MetricIdentifier + | typeof PromQL + | typeof StringLiteral + | typeof VectorSelector + | typeof MatrixSelector + | typeof MatchOp + | typeof EqlSingle + | typeof Neq + | typeof EqlRegex + | typeof NeqRegex; + +type Path = Array<[Direction, NodeTypeId]>; function move(node: SyntaxNode, direction: Direction): SyntaxNode | null { switch (direction) { @@ -51,7 +73,7 @@ function walk(node: SyntaxNode, path: Path): SyntaxNode | null { // we could not move in the direction, we stop return null; } - if (current.type.name !== expectedType) { + if (current.type.id !== expectedType) { // the reached node has wrong type, we stop return null; } @@ -134,52 +156,52 @@ export type Situation = }; type Resolver = { - path: NodeTypeName[]; + path: NodeTypeId[]; fun: (node: SyntaxNode, text: string, pos: number) => Situation | null; }; -function isPathMatch(resolverPath: string[], cursorPath: string[]): boolean { +function isPathMatch(resolverPath: NodeTypeId[], 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_NAME: NodeTypeId = 0; // this is used as error-id const RESOLVERS: Resolver[] = [ { - path: ['LabelMatchers', 'VectorSelector'], + path: [LabelMatchers, VectorSelector], fun: resolveLabelKeysWithEquals, }, { - path: ['PromQL'], + path: [PromQL], fun: resolveTopLevel, }, { - path: ['FunctionCallBody'], + path: [FunctionCallBody], fun: resolveInFunction, }, { - path: ['StringLiteral', 'LabelMatcher'], + path: [StringLiteral, LabelMatcher], fun: resolveLabelMatcher, }, { - path: [ERROR_NODE_NAME, 'LabelMatcher'], + path: [ERROR_NODE_NAME, LabelMatcher], fun: resolveLabelMatcher, }, { - path: [ERROR_NODE_NAME, 'MatrixSelector'], + path: [ERROR_NODE_NAME, MatrixSelector], fun: resolveDurations, }, { - path: ['GroupingLabels'], + path: [GroupingLabels], fun: resolveLabelsForGrouping, }, ]; -const LABEL_OP_MAP = new Map([ - ['EqlSingle', '='], - ['EqlRegex', '=~'], - ['Neq', '!='], - ['NeqRegex', '!~'], +const LABEL_OP_MAP = new Map([ + [EqlSingle, '='], + [EqlRegex, '=~'], + [Neq, '!='], + [NeqRegex, '!~'], ]); function getLabelOp(opNode: SyntaxNode): LabelOperator | null { @@ -188,21 +210,21 @@ function getLabelOp(opNode: SyntaxNode): LabelOperator | null { return null; } - return LABEL_OP_MAP.get(opChild.name) ?? null; + return LABEL_OP_MAP.get(opChild.type.id) ?? null; } function getLabel(labelMatcherNode: SyntaxNode, text: string): Label | null { - if (labelMatcherNode.type.name !== 'LabelMatcher') { + if (labelMatcherNode.type.id !== LabelMatcher) { return null; } - const nameNode = walk(labelMatcherNode, [['firstChild', 'LabelName']]); + const nameNode = walk(labelMatcherNode, [['firstChild', LabelName]]); if (nameNode === null) { return null; } - const opNode = walk(nameNode, [['nextSibling', 'MatchOp']]); + const opNode = walk(nameNode, [['nextSibling', MatchOp]]); if (opNode === null) { return null; } @@ -212,7 +234,7 @@ function getLabel(labelMatcherNode: SyntaxNode, text: string): Label | null { return null; } - const valueNode = walk(labelMatcherNode, [['lastChild', 'StringLiteral']]); + const valueNode = walk(labelMatcherNode, [['lastChild', StringLiteral]]); if (valueNode === null) { return null; @@ -223,17 +245,18 @@ function getLabel(labelMatcherNode: SyntaxNode, text: string): Label | null { return { name, value, op }; } + function getLabels(labelMatchersNode: SyntaxNode, text: string): Label[] { - if (labelMatchersNode.type.name !== 'LabelMatchers') { + if (labelMatchersNode.type.id !== LabelMatchers) { return []; } - let listNode: SyntaxNode | null = walk(labelMatchersNode, [['firstChild', 'LabelMatchList']]); + let listNode: SyntaxNode | null = walk(labelMatchersNode, [['firstChild', LabelMatchList]]); const labels: Label[] = []; while (listNode !== null) { - const matcherNode = walk(listNode, [['lastChild', 'LabelMatcher']]); + const matcherNode = walk(listNode, [['lastChild', LabelMatcher]]); if (matcherNode === null) { // unexpected, we stop return []; @@ -245,7 +268,7 @@ function getLabels(labelMatchersNode: SyntaxNode, text: string): Label[] { } // there might be more labels - listNode = walk(listNode, [['firstChild', 'LabelMatchList']]); + listNode = walk(listNode, [['firstChild', LabelMatchList]]); } // our labels-list is last-first, so we reverse it @@ -264,16 +287,16 @@ function getNodeChildren(node: SyntaxNode): SyntaxNode[] { return children; } -function getNodeInSubtree(node: SyntaxNode, typeName: NodeTypeName): SyntaxNode | null { +function getNodeInSubtree(node: SyntaxNode, typeId: NodeTypeId): SyntaxNode | null { // first we try the current node - if (node.type.name === typeName) { + if (node.type.id === typeId) { return node; } // then we try the children const children = getNodeChildren(node); for (const child of children) { - const n = getNodeInSubtree(child, typeName); + const n = getNodeInSubtree(child, typeId); if (n !== null) { return n; } @@ -284,23 +307,23 @@ function getNodeInSubtree(node: SyntaxNode, typeName: NodeTypeName): SyntaxNode function resolveLabelsForGrouping(node: SyntaxNode, text: string, pos: number): Situation | null { const aggrExpNode = walk(node, [ - ['parent', 'AggregateModifier'], - ['parent', 'AggregateExpr'], + ['parent', AggregateModifier], + ['parent', AggregateExpr], ]); if (aggrExpNode === null) { return null; } - const bodyNode = aggrExpNode.getChild('FunctionCallBody'); + const bodyNode = aggrExpNode.getChild(FunctionCallBody); if (bodyNode === null) { return null; } - const metricIdNode = getNodeInSubtree(bodyNode, 'MetricIdentifier'); + const metricIdNode = getNodeInSubtree(bodyNode, MetricIdentifier); if (metricIdNode === null) { return null; } - const idNode = walk(metricIdNode, [['firstChild', 'Identifier']]); + const idNode = walk(metricIdNode, [['firstChild', Identifier]]); if (idNode === null) { return null; } @@ -319,12 +342,12 @@ function resolveLabelMatcher(node: SyntaxNode, text: string, pos: number): Situa // - or an error node (like in `{job=^}`) const inStringNode = !node.type.isError; - const parent = walk(node, [['parent', 'LabelMatcher']]); + const parent = walk(node, [['parent', LabelMatcher]]); if (parent === null) { return null; } - const labelNameNode = walk(parent, [['firstChild', 'LabelName']]); + const labelNameNode = walk(parent, [['firstChild', LabelName]]); if (labelNameNode === null) { return null; } @@ -335,7 +358,7 @@ function resolveLabelMatcher(node: SyntaxNode, text: string, pos: number): Situa // there can be one or many `LabelMatchList` parents, we have // to go through all of them - const firstListNode = walk(parent, [['parent', 'LabelMatchList']]); + const firstListNode = walk(parent, [['parent', LabelMatchList]]); if (firstListNode === null) { return null; } @@ -352,14 +375,14 @@ function resolveLabelMatcher(node: SyntaxNode, text: string, pos: number): Situa return null; } - const { name } = p.type; + const { id } = p.type; - switch (name) { - case 'LabelMatchList': + switch (id) { + case LabelMatchList: //we keep looping listNode = p; continue; - case 'LabelMatchers': + case LabelMatchers: // we reached the end, we can stop the loop labelMatchersNode = p; continue; @@ -376,9 +399,9 @@ function resolveLabelMatcher(node: SyntaxNode, text: string, pos: number): Situa const otherLabels = allLabels.filter((label) => label.name !== labelName); const metricNameNode = walk(labelMatchersNode, [ - ['parent', 'VectorSelector'], - ['firstChild', 'MetricIdentifier'], - ['firstChild', 'Identifier'], + ['parent', VectorSelector], + ['firstChild', MetricIdentifier], + ['firstChild', Identifier], ]); if (metricNameNode === null) { @@ -436,7 +459,7 @@ function resolveLabelKeysWithEquals(node: SyntaxNode, text: string, pos: number) // next false positive: // `something{a="1"^}` - const child = walk(node, [['firstChild', 'LabelMatchList']]); + const child = walk(node, [['firstChild', LabelMatchList]]); if (child !== null) { // means the label-matching part contains at least one label already. // @@ -452,9 +475,9 @@ function resolveLabelKeysWithEquals(node: SyntaxNode, text: string, pos: number) } const metricNameNode = walk(node, [ - ['parent', 'VectorSelector'], - ['firstChild', 'MetricIdentifier'], - ['firstChild', 'Identifier'], + ['parent', VectorSelector], + ['firstChild', MetricIdentifier], + ['firstChild', Identifier], ]); const otherLabels = getLabels(node, text); @@ -511,10 +534,10 @@ export function getSituation(text: string, pos: number): Situation | null { /* PromQL -Expr -VectorSelector -LabelMatchers -*/ + Expr + VectorSelector + LabelMatchers + */ const tree = parser.parse(text); // if the tree contains error, it is very probable that @@ -527,15 +550,15 @@ LabelMatchers const cur = maybeErrorNode != null ? maybeErrorNode.cursor : tree.cursor(pos); 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) { // i do not use a foreach because i want to stop as soon // as i find something - if (isPathMatch(resolver.path, names)) { + if (isPathMatch(resolver.path, ids)) { return resolver.fun(currentNode, text, pos); } } diff --git a/public/app/plugins/datasource/prometheus/querybuilder/parsing.ts b/public/app/plugins/datasource/prometheus/querybuilder/parsing.ts index 10140f57077..c348ef708c9 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/parsing.ts +++ b/public/app/plugins/datasource/prometheus/querybuilder/parsing.ts @@ -23,6 +23,7 @@ import { ParenExpr, parser, StringLiteral, + VectorSelector, Without, } from 'lezer-promql'; @@ -167,7 +168,7 @@ export function handleExpression(expr: string, node: SyntaxNode, context: Contex } function isIntervalVariableError(node: SyntaxNode) { - return node.prevSibling?.name === 'Expr' && node.prevSibling?.firstChild?.name === 'VectorSelector'; + return node.prevSibling?.type.id === Expr && node.prevSibling?.firstChild?.type.id === VectorSelector; } function getLabel(expr: string, node: SyntaxNode): QueryBuilderLabelFilter { @@ -182,6 +183,7 @@ function getLabel(expr: string, node: SyntaxNode): QueryBuilderLabelFilter { } const rangeFunctions = ['changes', 'rate', 'irate', 'increase', 'delta']; + /** * Handle function call which is usually and identifier and its body > arguments. * @param expr @@ -346,7 +348,7 @@ function handleBinary(expr: string, node: SyntaxNode, context: Context) { // Due to the way binary ops are parsed we can get a binary operation on the right that starts with a number which // is a factor for a current binary operation. So we have to add it as an operation now. const leftMostChild = getLeftMostChild(right); - if (leftMostChild?.name === 'NumberLiteral') { + if (leftMostChild?.type.id === NumberLiteral) { visQuery.operations.push(makeBinOp(opDef, expr, leftMostChild, !!binModifier?.isBool)); }