diff --git a/package.json b/package.json index fb4c1a69425..0f2aac4a28e 100644 --- a/package.json +++ b/package.json @@ -251,7 +251,7 @@ "@grafana/flamegraph": "workspace:*", "@grafana/google-sdk": "0.1.1", "@grafana/lezer-logql": "0.2.1", - "@grafana/lezer-traceql": "0.0.10", + "@grafana/lezer-traceql": "0.0.11", "@grafana/monaco-logql": "^0.0.7", "@grafana/runtime": "workspace:*", "@grafana/scenes": "1.24.1", diff --git a/public/app/plugins/datasource/tempo/traceql/TraceQLEditor.test.tsx b/public/app/plugins/datasource/tempo/traceql/TraceQLEditor.test.tsx index 86f382184d2..f8e21f40bd0 100644 --- a/public/app/plugins/datasource/tempo/traceql/TraceQLEditor.test.tsx +++ b/public/app/plugins/datasource/tempo/traceql/TraceQLEditor.test.tsx @@ -3,9 +3,9 @@ import { computeErrorMessage, getErrorNodes } from './errorHighlighting'; describe('Check for syntax errors in query', () => { it.each([ ['{span.http.status_code = }', 'Invalid value after comparison or aritmetic operator.'], - ['{span.http.status_code 200}', 'Invalid comparison operator after field expression.'], - ['{span.http.status_code ""}', 'Invalid comparison operator after field expression.'], - ['{span.http.status_code @ 200}', 'Invalid comparison operator after field expression.'], + ['{span.http.status_code 200}', 'Invalid operator after field expression.'], + ['{span.http.status_code ""}', 'Invalid operator after field expression.'], + ['{span.http.status_code @ 200}', 'Invalid operator after field expression.'], ['{span.http.status_code span.http.status_code}', 'Invalid operator after field expression.'], [ '{span.http.status_code = 200} {span.http.status_code = 200}', @@ -28,8 +28,8 @@ describe('Check for syntax errors in query', () => { ['{ .a && }', 'Invalid value after logical operator.'], ['{ .a || }', 'Invalid value after logical operator.'], ['{ .a + }', 'Invalid value after comparison or aritmetic operator.'], - ['{ 200 = 200 200 }', 'Invalid comparison operator after field expression.'], - ['{.foo 300}', 'Invalid comparison operator after field expression.'], + ['{ 200 = 200 200 }', 'Invalid operator after field expression.'], + ['{.foo 300}', 'Invalid operator after field expression.'], ['{.foo 300 && .bar = 200}', 'Invalid operator after field expression.'], ['{.foo 300 && .bar 200}', 'Invalid operator after field expression.'], ['{.foo=1} {.bar=2}', 'Invalid spanset combining operator after spanset expression.'], @@ -59,6 +59,7 @@ describe('Check for syntax errors in query', () => { ['{.foo=300} && {.foo=300} | avg(.value) =', 'Invalid value after comparison operator.'], ['{.foo=300} | max(duration) > 1hs', 'Invalid value after comparison operator.'], ['{ span.http.status_code', 'Invalid comparison operator after field expression.'], + ['{ .foo = "bar"', 'Invalid comparison operator after field expression.'], ['abcxyz', 'Invalid query.'], ])('error message for invalid query - %s, %s', (query: string, expectedErrorMessage: string) => { const errorNode = getErrorNodes(query)[0]; @@ -80,6 +81,9 @@ describe('Check for syntax errors in query', () => { `{ true } /* && { false } && */ && { true } // && { false } && { true }`, ], + ['{span.s"t\\"at"us}'], + ['{span.s"t\\\\at"us}'], + ['{ span.s"tat"us" = "GET123 }'], // weird query, but technically valid ])('valid query - %s', (query: string) => { expect(getErrorNodes(query)).toStrictEqual([]); }); diff --git a/public/app/plugins/datasource/tempo/traceql/traceql.ts b/public/app/plugins/datasource/tempo/traceql/traceql.ts index c077ebcd32d..e93dda447bf 100644 --- a/public/app/plugins/datasource/tempo/traceql/traceql.ts +++ b/public/app/plugins/datasource/tempo/traceql/traceql.ts @@ -73,15 +73,32 @@ export const language: languages.IMonarchLanguage = { // trace ID [/^\s*[0-9A-Fa-f]+\s*$/, 'tag'], - // functions, keywords, predefined values + // keywords [ - /[a-zA-Z_.]\w*/, + // match only predefined keywords + `(?:${keywords.join('|')})`, + { + cases: { + '@keywords': 'keyword', + '@default': 'tag', // fallback, but should never happen + }, + }, + ], + + // functions and predefined values + [ + // If not inside quotes, namely outside of open and closed `"`, + // allow only word characters (those matching `\w`) and full stop (`.`). + // + // If inside quotes, e.g. `"here"`, allow for any character, except for `"` and `\` which must be + // escaped with a backslash (`\"` and `\\` respectively). + // Quotes can be used to support special tag names, such as those with spaces (e.g., `my tag`). + /(?:\w|[.]|"(?:\\"|\\\\|[^\\"])*")+/, { cases: { '@functions': 'predefined', - '@keywords': 'keyword', '@statusValues': 'type', - '@default': 'tag', + '@default': 'tag', // fallback, used for tag names }, }, ], @@ -89,8 +106,8 @@ export const language: languages.IMonarchLanguage = { // strings [/"([^"\\]|\\.)*$/, 'string.invalid'], // non-teminated string [/'([^'\\]|\\.)*$/, 'string.invalid'], // non-teminated string - [/"/, 'string', '@string_double'], - [/'/, 'string', '@string_single'], + [/([^\w])(")/, [{ token: '' }, { token: 'string', next: '@string_double' }]], + [/([^\w])(')/, [{ token: '' }, { token: 'string', next: '@string_single' }]], // delimiters and operators [/[{}()\[\]]/, 'delimiter.bracket'], diff --git a/yarn.lock b/yarn.lock index 11e1256a044..9c0caf3dc47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3224,12 +3224,12 @@ __metadata: languageName: node linkType: hard -"@grafana/lezer-traceql@npm:0.0.10": - version: 0.0.10 - resolution: "@grafana/lezer-traceql@npm:0.0.10" +"@grafana/lezer-traceql@npm:0.0.11": + version: 0.0.11 + resolution: "@grafana/lezer-traceql@npm:0.0.11" peerDependencies: "@lezer/lr": ^1.3.0 - checksum: 9335458543a5155a4ece6a0d526dd78d8dba087f197b19ca80546529a39a107d62d4d0faa3fabc3f4d5caefc173cb1a6b93650a471101d34a0ee1c8f7c348de0 + checksum: cb312cf8f0cf70af6eb307aa0b6478cd8bde9233f281d6f4bb385f3c846a1e9fe0caf8addef5cbc0ccadb57b968fbd10e26ad5df6f898fb2b174536dca924926 languageName: node linkType: hard @@ -17311,7 +17311,7 @@ __metadata: "@grafana/flamegraph": "workspace:*" "@grafana/google-sdk": "npm:0.1.1" "@grafana/lezer-logql": "npm:0.2.1" - "@grafana/lezer-traceql": "npm:0.0.10" + "@grafana/lezer-traceql": "npm:0.0.11" "@grafana/monaco-logql": "npm:^0.0.7" "@grafana/runtime": "workspace:*" "@grafana/scenes": "npm:1.24.1"