fix(core): avoid crashing UI in case of multiline function autocomplete (#11684)

This commit is contained in:
brian-mulier-p
2025-10-03 09:36:09 +02:00
committed by brian.mulier
parent 54482e1d06
commit 95133ebc40
2 changed files with 74 additions and 11 deletions

View File

@@ -1,17 +1,23 @@
const pebbleStart = "\\{\\{ *";
const fieldWithoutDotCapture = "([^\\(\\)}:~. ]*)(?![^\\(\\)}\\s])";
const dotAccessedFieldWithParentCapture = "([^\\(\\)},:~ ]+)\\." + fieldWithoutDotCapture;
const maybeTextFollowedBySeparator = "(?:[^~},: ]*[~ ]+)*";
const maybeParams = "((?:[^\\n\\(\\)~},:= ]+=[^\\n~},:= ]+(?: *, *)?)+)?['\"]?([^\\n\\(\\)~},:= ]*)?";
const functionWithMaybeParams = "([^\\n\\(\\)},:~ ]+)\\(" + maybeParams
const maybeText = (allowSeparators: boolean) => "(?:\"[^\"]*\")|(?:'[^']*')|(?:(?:(?!\\}\\})" + (allowSeparators ? "[\\S\\n ]" : "[^~+,:\\n ]") + ")*)";
const maybeAnotherPebbleExpression = "(?:[\\n ]*\\{\\{[\\n ]*" + maybeText(true) + "[\\n ]*\\}\\}[\\n ]*)*";
const pebbleStart = "\\{\\{[\\n ]*";
const fieldWithoutDotCapture = "([^\\(\\)\\}:~+.\\n '\"]*)(?![^\\(\\)\\}\\n ])";
const dotAccessedFieldWithParentCapture = "([^\\(\\)\\}:~+\\n '\"]*)\\." + fieldWithoutDotCapture;
const maybeTextFollowedBySeparator = "(?:" + maybeText(false) + "[~+ ]+)*";
const paramKey = "[^\\n \\(\\)~+\\},:=]+";
const paramValue = "(?:(?:(?:\"[^\"]*\"?)|(?:'[^']*'?)|[^,)]))*";
const maybeParams = "(" +
"(?:[\\n ]*" + paramKey + "[\\n ]*=[\\n ]*" + paramValue + "(?:[\\n ]*,[\\n ]*)?)+)?" +
"([^\\n \\(\\)~+\\},:=]*)?";
const functionWithMaybeParams = "([^\\n\\(\\)\\},:~ ]+)\\(" + maybeParams
export default {
beforeSeparator: (additionalSeparators: string[] = []) => `([^}:\\s${additionalSeparators.join("")}]*)`,
beforeSeparator: (additionalSeparators: string[] = []) => `([^\\}:\\n ${additionalSeparators.join("")}]*)`,
/** [fullMatch, dotForbiddenField] */
capturePebbleVarRoot: `${pebbleStart}${maybeTextFollowedBySeparator}${fieldWithoutDotCapture}`,
capturePebbleVarRoot: `${maybeAnotherPebbleExpression}${pebbleStart}${maybeTextFollowedBySeparator}${fieldWithoutDotCapture}`,
/** [fullMatch, parentFieldMaybeIncludingDots, childField] */
capturePebbleVarParent: `${pebbleStart}${maybeTextFollowedBySeparator}${dotAccessedFieldWithParentCapture}`,
capturePebbleVarParent: `${maybeAnotherPebbleExpression}${pebbleStart}${maybeTextFollowedBySeparator}${dotAccessedFieldWithParentCapture}`,
/** [fullMatch, functionName, textBetweenParenthesis, maybeTypedWordStart] */
capturePebbleFunction: `${pebbleStart}${maybeTextFollowedBySeparator}${functionWithMaybeParams}`,
capturePebbleFunction: `${maybeAnotherPebbleExpression}${pebbleStart}${maybeTextFollowedBySeparator}${functionWithMaybeParams}`,
captureStringValue: "^[\"']([^\"']+)[\"']$"
}
}

View File

@@ -59,6 +59,9 @@ describe("Regex", () => {
functionMatcher = new RegExp(RegexProvider.capturePebbleFunction + "$").exec("{{myFunc(my-param_1='value1', myK");
expect([...functionMatcher]).toEqual(["{{myFunc(my-param_1='value1', myK", "myFunc", "my-param_1='value1', ", "myK"]);
functionMatcher = new RegExp(RegexProvider.capturePebbleFunction + "$").exec("{{myFunc(my-param_1='value1')}} {{mySecondFunc(second-func-param_1='secondFuncValue1', 'to") ?? [];
expect([...functionMatcher]).toEqual(["{{myFunc(my-param_1='value1')}} {{mySecondFunc(second-func-param_1='secondFuncValue1', 'to", "mySecondFunc", "second-func-param_1='secondFuncValue1', ", "'to"]);
})
it("capture string value", () => {
@@ -71,4 +74,58 @@ describe("Regex", () => {
stringMatcher = new RegExp(RegexProvider.captureStringValue).exec("a");
expect(stringMatcher).toBeNull();
})
it("multiline function, avoid crashing", () => {
const complexMultilineFunctionButClosedPebbleExpression = `id: breaking-ui
namespace: io.kestra.blx
description: "Upload multiple files to s3 sequentially"
tasks:
- id: placeholder
type: io.kestra.plugin.core.log.Log
message: |-
{{
"to_entries[] | select(.key | startswith(\\"" +
inputs.selector +
"\\")) | (.key + \\"->\\" + .value)"
}}
`
const regex = new RegExp(RegexProvider.capturePebbleFunction + "$");
expect(regex.exec(complexMultilineFunctionButClosedPebbleExpression)).eq(null);
const shouldMatchLastFunction = `id: breaking-ui
namespace: io.kestra.blx
description: "Upload multiple files to s3 sequentially"
tasks:
- id: placeholder
type: io.kestra.plugin.core.log.Log
message: |-
{{
"to_entries[] | select(.key | startswith(\\"" +
inputs.selector +
"\\")) | (.key + \\"->\\" + .value)"
}} {{myFunc(my-param_1='value1', my-param_2="value2", myK`
expect([...(regex.exec(shouldMatchLastFunction) ?? [])]).toEqual([
`id: breaking-ui
namespace: io.kestra.blx
description: "Upload multiple files to s3 sequentially"
tasks:
- id: placeholder
type: io.kestra.plugin.core.log.Log
message: |-
{{
"to_entries[] | select(.key | startswith(\\"" +
inputs.selector +
"\\")) | (.key + \\"->\\" + .value)"
}} {{myFunc(my-param_1='value1', my-param_2="value2", myK`,
"myFunc",
"my-param_1='value1', my-param_2=\"value2\", ",
"myK",
]);
})
})