mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-11-15 13:12:22 +01:00
Make template tag scanners quote-aware (#710)
This commit is contained in:
1 parent
23d369d84e
commit
6fc43a60e2
19 files changed
+747
-39
No files matched your search
@@ -252,6 +252,85 @@ export const plugin: PluginDefinition = {
|
||||
],
|
||||
};
|
||||
|
||||
const QUOTE = 0x27; // '
|
||||
const BACKSLASH = 0x5c; // \
|
||||
const CLOSE_BRACKET = 0x5d; // ]
|
||||
const CLOSE_BRACE = 0x7d; // }
|
||||
|
||||
/**
|
||||
* For every index `i`, the index of the `'` that closes a string whose body starts at `i`, or
|
||||
* `-1` when nothing does. One right-to-left pass, so skipping a string costs one lookup.
|
||||
*/
|
||||
function closingQuoteTable(text: string): Int32Array {
|
||||
// Two extra slots so a `\` in the last position can read past the end and find `-1`
|
||||
const table = new Int32Array(text.length + 2).fill(-1);
|
||||
for (let i = text.length - 1; i >= 0; i--) {
|
||||
const c = text.charCodeAt(i);
|
||||
// A backslash escapes whatever follows it, including a quote
|
||||
const next = c === BACKSLASH ? table[i + 2] : table[i + 1];
|
||||
table[i] = c === QUOTE ? i : (next ?? -1);
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrites every template tag in `text` with whatever `replace` returns for its inner content.
|
||||
*
|
||||
* A quoted argument may contain `]}`, so the scan steps over strings instead of stopping at the
|
||||
* first one, and an unterminated quote stays an ordinary character. Kept in sync by hand with
|
||||
* `apps/yaak-client/lib/templateTags.ts`, which carries the full explanation — including why
|
||||
* this is a linear hand-written scan rather than a regex.
|
||||
*/
|
||||
function replaceTemplateTags(text: string, replace: (inner: string) => string): string {
|
||||
let closingQuotes: Int32Array | null = null;
|
||||
let deadEnds: Set<number> | null = null;
|
||||
|
||||
let result = "";
|
||||
let copiedTo = 0;
|
||||
let searchFrom = 0;
|
||||
while (searchFrom < text.length) {
|
||||
const start = text.indexOf("${[", searchFrom);
|
||||
if (start < 0) break;
|
||||
|
||||
const visited: number[] = [];
|
||||
let end = -1;
|
||||
let i = start + 3;
|
||||
while (i < text.length) {
|
||||
if (deadEnds?.has(i)) break;
|
||||
visited.push(i);
|
||||
|
||||
const c = text.charCodeAt(i);
|
||||
if (c === QUOTE) {
|
||||
closingQuotes ??= closingQuoteTable(text);
|
||||
const close = closingQuotes[i + 1] ?? -1;
|
||||
i = close < 0 ? i + 1 : close + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c === CLOSE_BRACKET && text.charCodeAt(i + 1) === CLOSE_BRACE) {
|
||||
end = i + 2;
|
||||
break;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (end < 0) {
|
||||
// Not a tag, but a real one may start inside a string this scan stepped over
|
||||
deadEnds ??= new Set();
|
||||
for (const position of visited) deadEnds.add(position);
|
||||
searchFrom = start + 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
result += text.slice(copiedTo, start) + replace(text.slice(start + 3, end - 2));
|
||||
copiedTo = end;
|
||||
searchFrom = end;
|
||||
}
|
||||
|
||||
return result + text.slice(copiedTo);
|
||||
}
|
||||
|
||||
// TODO: Use a common function for this, but it fails to build on windows during CI if I try importing it here
|
||||
export function resolvedModelName(r: AnyModel | null): string {
|
||||
if (r == null) return "";
|
||||
@@ -266,7 +345,7 @@ export function resolvedModelName(r: AnyModel | null): string {
|
||||
}
|
||||
|
||||
// Replace variable syntax with variable name
|
||||
const withoutVariables = r.url.replace(/\$\{\[\s*([^\]\s]+)\s*]}/g, "$1");
|
||||
const withoutVariables = replaceTemplateTags(r.url, (inner) => inner.trim());
|
||||
if (withoutVariables.trim() === "") {
|
||||
return r.model === "http_request"
|
||||
? r.bodyType && r.bodyType === "graphql"
|
||||
|
||||
Reference in new issue
Block a user