adapt the lezer grammar and codemirror autocompletion with duration and number that are equivalent (#14417)

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
This commit is contained in:
Augustin Husson 2024-07-31 15:31:42 +02:00 committed by GitHub
parent bffe0f80dc
commit 29b62762db
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 67 additions and 57 deletions

View file

@ -775,7 +775,7 @@ describe('computeStartCompletePosition test', () => {
it(value.title, () => { it(value.title, () => {
const state = createEditorState(value.expr); const state = createEditorState(value.expr);
const node = syntaxTree(state).resolve(value.pos, -1); const node = syntaxTree(state).resolve(value.pos, -1);
const result = computeStartCompletePosition(node, value.pos); const result = computeStartCompletePosition(state, node, value.pos);
expect(value.expectedStart).toEqual(result); expect(value.expectedStart).toEqual(result);
}); });
}); });

View file

@ -21,7 +21,6 @@ import {
BinaryExpr, BinaryExpr,
BoolModifier, BoolModifier,
Div, Div,
Duration,
Eql, Eql,
EqlRegex, EqlRegex,
EqlSingle, EqlSingle,
@ -40,7 +39,6 @@ import {
Mul, Mul,
Neq, Neq,
NeqRegex, NeqRegex,
NumberLiteral,
OffsetExpr, OffsetExpr,
Or, Or,
Pow, Pow,
@ -54,6 +52,8 @@ import {
UnquotedLabelMatcher, UnquotedLabelMatcher,
QuotedLabelMatcher, QuotedLabelMatcher,
QuotedLabelName, QuotedLabelName,
NumberDurationLiteralInDurationContext,
NumberDurationLiteral,
} from '@prometheus-io/lezer-promql'; } from '@prometheus-io/lezer-promql';
import { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete'; import { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
import { EditorState } from '@codemirror/state'; import { EditorState } from '@codemirror/state';
@ -179,7 +179,8 @@ function computeStartCompleteLabelPositionInLabelMatcherOrInGroupingLabel(node:
// It is an important step because the start position will be used by CMN to find the string and then to use it to filter the CompletionResult. // It is an important step because the start position will be used by CMN to find the string and then to use it to filter the CompletionResult.
// A wrong `start` position will lead to have the completion not working. // A wrong `start` position will lead to have the completion not working.
// Note: this method is exported only for testing purpose. // Note: this method is exported only for testing purpose.
export function computeStartCompletePosition(node: SyntaxNode, pos: number): number { export function computeStartCompletePosition(state: EditorState, node: SyntaxNode, pos: number): number {
const currentText = state.doc.slice(node.from, pos).toString();
let start = node.from; let start = node.from;
if (node.type.id === LabelMatchers || node.type.id === GroupingLabels) { if (node.type.id === LabelMatchers || node.type.id === GroupingLabels) {
start = computeStartCompleteLabelPositionInLabelMatcherOrInGroupingLabel(node, pos); start = computeStartCompleteLabelPositionInLabelMatcherOrInGroupingLabel(node, pos);
@ -191,11 +192,16 @@ export function computeStartCompletePosition(node: SyntaxNode, pos: number): num
start++; start++;
} else if ( } else if (
node.type.id === OffsetExpr || node.type.id === OffsetExpr ||
(node.type.id === NumberLiteral && node.parent?.type.id === 0 && node.parent.parent?.type.id === SubqueryExpr) || // Since duration and number are equivalent, writing go[5] or go[5d] is syntactically accurate.
// Before we were able to guess when we had to autocomplete the duration later based on the error node,
// which is not possible anymore.
// So we have to analyze the string about the current node to see if the duration unit is already present or not.
(node.type.id === NumberDurationLiteralInDurationContext && !durationTerms.map((v) => v.label).includes(currentText[currentText.length - 1])) ||
(node.type.id === NumberDurationLiteral && node.parent?.type.id === 0 && node.parent.parent?.type.id === SubqueryExpr) ||
(node.type.id === 0 && (node.type.id === 0 &&
(node.parent?.type.id === OffsetExpr || (node.parent?.type.id === OffsetExpr ||
node.parent?.type.id === MatrixSelector || node.parent?.type.id === MatrixSelector ||
(node.parent?.type.id === SubqueryExpr && containsAtLeastOneChild(node.parent, Duration)))) (node.parent?.type.id === SubqueryExpr && containsAtLeastOneChild(node.parent, NumberDurationLiteralInDurationContext))))
) { ) {
start = pos; start = pos;
} }
@ -230,7 +236,7 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode): Context
result.push({ kind: ContextKind.Duration }); result.push({ kind: ContextKind.Duration });
break; break;
} }
if (node.parent?.type.id === SubqueryExpr && containsAtLeastOneChild(node.parent, Duration)) { if (node.parent?.type.id === SubqueryExpr && containsAtLeastOneChild(node.parent, NumberDurationLiteralInDurationContext)) {
// we are likely in the given situation: // we are likely in the given situation:
// `rate(foo[5d:5])` // `rate(foo[5d:5])`
// so we should autocomplete a duration // so we should autocomplete a duration
@ -434,7 +440,7 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode): Context
result.push({ kind: ContextKind.MetricName, metricName: state.sliceDoc(node.from, node.to).slice(1, -1) }); result.push({ kind: ContextKind.MetricName, metricName: state.sliceDoc(node.from, node.to).slice(1, -1) });
} }
break; break;
case NumberLiteral: case NumberDurationLiteral:
if (node.parent?.type.id === 0 && node.parent.parent?.type.id === SubqueryExpr) { if (node.parent?.type.id === 0 && node.parent.parent?.type.id === SubqueryExpr) {
// Here we are likely in this situation: // Here we are likely in this situation:
// `go[5d:4]` // `go[5d:4]`
@ -449,7 +455,7 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode): Context
result.push({ kind: ContextKind.Number }); result.push({ kind: ContextKind.Number });
} }
break; break;
case Duration: case NumberDurationLiteralInDurationContext:
case OffsetExpr: case OffsetExpr:
result.push({ kind: ContextKind.Duration }); result.push({ kind: ContextKind.Duration });
break; break;
@ -591,7 +597,7 @@ export class HybridComplete implements CompleteStrategy {
} }
} }
return asyncResult.then((result) => { return asyncResult.then((result) => {
return arrayToCompletionResult(result, computeStartCompletePosition(tree, pos), pos, completeSnippet, span); return arrayToCompletionResult(result, computeStartCompletePosition(state, tree, pos), pos, completeSnippet, span);
}); });
} }

View file

@ -17,7 +17,7 @@ import {
BinaryExpr, BinaryExpr,
FunctionCall, FunctionCall,
MatrixSelector, MatrixSelector,
NumberLiteral, NumberDurationLiteral,
OffsetExpr, OffsetExpr,
ParenExpr, ParenExpr,
StepInvariantExpr, StepInvariantExpr,
@ -42,7 +42,7 @@ export function getType(node: SyntaxNode | null): ValueType {
return getType(node.firstChild); return getType(node.firstChild);
case StringLiteral: case StringLiteral:
return ValueType.string; return ValueType.string;
case NumberLiteral: case NumberDurationLiteral:
return ValueType.scalar; return ValueType.scalar;
case MatrixSelector: case MatrixSelector:
return ValueType.matrix; return ValueType.matrix;

View file

@ -17,8 +17,8 @@ export const promQLHighLight = styleTags({
LineComment: tags.comment, LineComment: tags.comment,
LabelName: tags.labelName, LabelName: tags.labelName,
StringLiteral: tags.string, StringLiteral: tags.string,
NumberLiteral: tags.number, NumberDurationLiteral: tags.number,
Duration: tags.number, NumberDurationLiteralInDurationContext: tags.number,
Identifier: tags.variableName, Identifier: tags.variableName,
'Abs Absent AbsentOverTime Acos Acosh Asin Asinh Atan Atanh AvgOverTime Ceil Changes Clamp ClampMax ClampMin Cos Cosh CountOverTime DaysInMonth DayOfMonth DayOfWeek DayOfYear Deg Delta Deriv Exp Floor HistogramAvg HistogramCount HistogramFraction HistogramQuantile HistogramSum HoltWinters Hour Idelta Increase Irate LabelReplace LabelJoin LastOverTime Ln Log10 Log2 MaxOverTime MinOverTime Minute Month Pi PredictLinear PresentOverTime QuantileOverTime Rad Rate Resets Round Scalar Sgn Sin Sinh Sort SortDesc SortByLabel SortByLabelDesc Sqrt StddevOverTime StdvarOverTime SumOverTime Tan Tanh Time Timestamp Vector Year': 'Abs Absent AbsentOverTime Acos Acosh Asin Asinh Atan Atanh AvgOverTime Ceil Changes Clamp ClampMax ClampMin Cos Cosh CountOverTime DaysInMonth DayOfMonth DayOfWeek DayOfYear Deg Delta Deriv Exp Floor HistogramAvg HistogramCount HistogramFraction HistogramQuantile HistogramSum HoltWinters Hour Idelta Increase Irate LabelReplace LabelJoin LastOverTime Ln Log10 Log2 MaxOverTime MinOverTime Minute Month Pi PredictLinear PresentOverTime QuantileOverTime Rad Rate Resets Round Scalar Sgn Sin Sinh Sort SortDesc SortByLabel SortByLabelDesc Sqrt StddevOverTime StdvarOverTime SumOverTime Tan Tanh Time Timestamp Vector Year':
tags.function(tags.variableName), tags.function(tags.variableName),

View file

@ -29,7 +29,7 @@ expr[@isGroup=Expr] {
BinaryExpr | BinaryExpr |
FunctionCall | FunctionCall |
MatrixSelector | MatrixSelector |
NumberLiteral | NumberDurationLiteral |
OffsetExpr | OffsetExpr |
ParenExpr | ParenExpr |
StringLiteral | StringLiteral |
@ -194,16 +194,16 @@ ParenExpr {
} }
OffsetExpr { OffsetExpr {
expr Offset Sub? Duration expr Offset NumberDurationLiteralInDurationContext
} }
MatrixSelector { MatrixSelector {
// TODO: Can this not be more specific than "expr"? // TODO: Can this not be more specific than "expr"?
expr "[" Duration "]" expr "[" NumberDurationLiteralInDurationContext "]"
} }
SubqueryExpr { SubqueryExpr {
expr "[" Duration ":" ("" | Duration) "]" expr "[" NumberDurationLiteralInDurationContext ":" ("" | NumberDurationLiteralInDurationContext) "]"
} }
UnaryExpr { UnaryExpr {
@ -245,14 +245,18 @@ QuotedLabelName {
} }
StepInvariantExpr { StepInvariantExpr {
expr At ( NumberLiteral | AtModifierPreprocessors "(" ")" ) expr At ( NumberDurationLiteral | AtModifierPreprocessors "(" ")" )
} }
AtModifierPreprocessors { AtModifierPreprocessors {
Start | End Start | End
} }
NumberLiteral { NumberDurationLiteral {
("-"|"+")?~signed (number | inf | nan)
}
NumberDurationLiteralInDurationContext {
("-"|"+")?~signed (number | inf | nan) ("-"|"+")?~signed (number | inf | nan)
} }
@ -264,7 +268,7 @@ NumberLiteral {
number { number {
(std.digit+ (("_")? std.digit)* ("." std.digit+ (("_")? std.digit)*)? | "." std.digit+ (("_")? std.digit)*) (("e" | "E") ("+" | "-")? std.digit+ (("_")? std.digit)*)? | (std.digit+ (("_")? std.digit)* ("." std.digit+ (("_")? std.digit)*)? | "." std.digit+ (("_")? std.digit)*) (("e" | "E") ("+" | "-")? std.digit+ (("_")? std.digit)*)? |
"0x" (std.digit | $[a-fA-F])+ "0x" (std.digit | $[a-fA-F])+ | duration
} }
StringLiteral { // TODO: This is for JS, make this work for PromQL. StringLiteral { // TODO: This is for JS, make this work for PromQL.
'"' (![\\\n"] | "\\" _)* '"'? | '"' (![\\\n"] | "\\" _)* '"'? |
@ -272,7 +276,7 @@ NumberLiteral {
"`" ![`]* "`" "`" ![`]* "`"
} }
Duration { duration {
// Each line below is just the same regex repeated over and over, but each time with one of the units made non-optional, // Each line below is just the same regex repeated over and over, but each time with one of the units made non-optional,
// to ensure that at least one <number>+<unit> pair is provided and an empty string is not recognized as a valid duration. // to ensure that at least one <number>+<unit> pair is provided and an empty string is not recognized as a valid duration.
( ( std.digit+ "y" ) ( std.digit+ "w" )? ( std.digit+ "d" )? ( std.digit+ "h" )? ( std.digit+ "m" )? ( std.digit+ "s" )? ( std.digit+ "ms" )? ) | ( ( std.digit+ "y" ) ( std.digit+ "w" )? ( std.digit+ "d" )? ( std.digit+ "h" )? ( std.digit+ "m" )? ( std.digit+ "s" )? ( std.digit+ "ms" )? ) |

View file

@ -4,7 +4,7 @@
==> ==>
PromQL(NumberLiteral) PromQL(NumberDurationLiteral)
# Double-quoted string literal # Double-quoted string literal
@ -46,7 +46,7 @@ PromQL(StringLiteral)
==> ==>
PromQL(BinaryExpr(NumberLiteral, Add, NumberLiteral)) PromQL(BinaryExpr(NumberDurationLiteral, Add, NumberDurationLiteral))
# Complex expression # Complex expression
@ -73,7 +73,7 @@ PromQL(
VectorSelector( VectorSelector(
Identifier Identifier
), ),
Duration NumberDurationLiteralInDurationContext
) )
) )
) )
@ -103,7 +103,7 @@ PromQL(
VectorSelector( VectorSelector(
Identifier Identifier
), ),
Duration NumberDurationLiteralInDurationContext
) )
) )
) )
@ -240,21 +240,21 @@ PromQL(
) )
) )
# Duration units # NumberDurationLiteralInDurationContext units
foo[1y2w3d4h5m6s7ms] foo[1y2w3d4h5m6s7ms]
==> ==>
PromQL(MatrixSelector(VectorSelector(Identifier),Duration)) PromQL(MatrixSelector(VectorSelector(Identifier),NumberDurationLiteralInDurationContext))
# Incorrectly ordered duration units # Incorrectly ordered NumberDurationLiteralInDurationContext units
foo[1m2h] foo[1m2h]
==> ==>
PromQL(SubqueryExpr(VectorSelector(Identifier),Duration,⚠,Duration)) PromQL(MatrixSelector(VectorSelector(Identifier),NumberDurationLiteralInDurationContext,⚠))
# Using a function name as a metric name # Using a function name as a metric name
@ -311,7 +311,7 @@ PromQL(
), ),
Gtr, Gtr,
BoolModifier(Bool), BoolModifier(Bool),
NumberLiteral NumberDurationLiteral
) )
) )
@ -357,7 +357,7 @@ PromQL(
VectorSelector( VectorSelector(
Identifier Identifier
), ),
Duration NumberDurationLiteralInDurationContext
) )
) )
) )
@ -389,8 +389,8 @@ PromQL(
FunctionIdentifier(Clamp), FunctionIdentifier(Clamp),
FunctionCallBody( FunctionCallBody(
VectorSelector(Identifier), VectorSelector(Identifier),
NumberLiteral, NumberDurationLiteral,
NumberLiteral NumberDurationLiteral
) )
) )
) )
@ -450,7 +450,7 @@ PromQL(
Identifier Identifier
), ),
At, At,
NumberLiteral NumberDurationLiteral
) )
) )
@ -483,7 +483,7 @@ PromQL(
FunctionCallBody( FunctionCallBody(
MatrixSelector( MatrixSelector(
VectorSelector(Identifier), VectorSelector(Identifier),
Duration NumberDurationLiteralInDurationContext
) )
) )
), ),
@ -491,14 +491,14 @@ PromQL(
AggregateExpr( AggregateExpr(
AggregateOp(Topk), AggregateOp(Topk),
FunctionCallBody( FunctionCallBody(
NumberLiteral, NumberDurationLiteral,
FunctionCall( FunctionCall(
FunctionIdentifier(Rate), FunctionIdentifier(Rate),
FunctionCallBody( FunctionCallBody(
StepInvariantExpr( StepInvariantExpr(
MatrixSelector(VectorSelector(Identifier), Duration), MatrixSelector(VectorSelector(Identifier), NumberDurationLiteralInDurationContext),
At, At,
NumberLiteral NumberDurationLiteral
) )
) )
) )
@ -518,7 +518,7 @@ PromQL(
Identifier Identifier
), ),
At, At,
NumberLiteral NumberDurationLiteral
) )
) )
@ -533,7 +533,7 @@ PromQL(
Identifier Identifier
), ),
At, At,
NumberLiteral NumberDurationLiteral
) )
) )
@ -556,98 +556,98 @@ PromQL(VectorSelector(Identifier))
NaN NaN
==> ==>
PromQL(NumberLiteral) PromQL(NumberDurationLiteral)
# Lower-cased NaN. # Lower-cased NaN.
nan nan
==> ==>
PromQL(NumberLiteral) PromQL(NumberDurationLiteral)
# Inf. # Inf.
Inf Inf
==> ==>
PromQL(NumberLiteral) PromQL(NumberDurationLiteral)
# Negative Inf. # Negative Inf.
-Inf -Inf
==> ==>
PromQL(NumberLiteral) PromQL(NumberDurationLiteral)
# Positive Inf. # Positive Inf.
+Inf +Inf
==> ==>
PromQL(NumberLiteral) PromQL(NumberDurationLiteral)
# Lower-cased Inf. # Lower-cased Inf.
inf inf
==> ==>
PromQL(NumberLiteral) PromQL(NumberDurationLiteral)
# Upper-cased Inf. # Upper-cased Inf.
INF INF
==> ==>
PromQL(NumberLiteral) PromQL(NumberDurationLiteral)
# Negative number literal. # Negative number literal.
-42 -42
==> ==>
PromQL(NumberLiteral) PromQL(NumberDurationLiteral)
# Explicitly positive number literal. # Explicitly positive number literal.
+42 +42
==> ==>
PromQL(NumberLiteral) PromQL(NumberDurationLiteral)
# Trying to illegally use NaN as a metric name. # Trying to illegally use NaN as a metric name.
NaN{foo="bar"} NaN{foo="bar"}
==> ==>
PromQL(BinaryExpr(NumberLiteral,⚠,VectorSelector(LabelMatchers(UnquotedLabelMatcher(LabelName,MatchOp(EqlSingle),StringLiteral))))) PromQL(BinaryExpr(NumberDurationLiteral,⚠,VectorSelector(LabelMatchers(UnquotedLabelMatcher(LabelName,MatchOp(EqlSingle),StringLiteral)))))
# Trying to illegally use Inf as a metric name. # Trying to illegally use Inf as a metric name.
Inf{foo="bar"} Inf{foo="bar"}
==> ==>
PromQL(BinaryExpr(NumberLiteral,⚠,VectorSelector(LabelMatchers(UnquotedLabelMatcher(LabelName,MatchOp(EqlSingle),StringLiteral))))) PromQL(BinaryExpr(NumberDurationLiteral,⚠,VectorSelector(LabelMatchers(UnquotedLabelMatcher(LabelName,MatchOp(EqlSingle),StringLiteral)))))
# Negative offset # Negative offset
foo offset -5d foo offset -5d
==> ==>
PromQL(OffsetExpr(VectorSelector(Identifier), Offset, Sub, Duration)) PromQL(OffsetExpr(VectorSelector(Identifier), Offset, NumberDurationLiteralInDurationContext))
# Negative offset with space # Negative offset with space
foo offset - 5d foo offset - 5d
==> ==>
PromQL(OffsetExpr(VectorSelector(Identifier), Offset, Sub, Duration)) PromQL(OffsetExpr(VectorSelector(Identifier), Offset, NumberDurationLiteralInDurationContext))
# Positive offset # Positive offset
foo offset 5d foo offset 5d
==> ==>
PromQL(OffsetExpr(VectorSelector(Identifier), Offset, Duration)) PromQL(OffsetExpr(VectorSelector(Identifier), Offset, NumberDurationLiteralInDurationContext))
# Parsing only metric names with alternative @top { "top": "MetricName" } # Parsing only metric names with alternative @top { "top": "MetricName" }
@ -661,7 +661,7 @@ MetricName(Identifier)
1 + foo atan2 bar 1 + foo atan2 bar
==> ==>
PromQL(BinaryExpr(NumberLiteral,Add,BinaryExpr(VectorSelector(Identifier),Atan2,VectorSelector(Identifier)))) PromQL(BinaryExpr(NumberDurationLiteral,Add,BinaryExpr(VectorSelector(Identifier),Atan2,VectorSelector(Identifier))))
# Testing quoted metric name # Testing quoted metric name