mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-26 10:26:26 +01:00
fix(runner): require a standalone await token in the wait guard
The regex-literal-start check treated an await-named property, such
as obj.await, as the await keyword. Code that divides after such a
property, like ${obj.await / divisor + (await vi.waitFor(...))},
could then mask the real vi.waitFor( call as part of a false regex
literal and hide it from the guard.
The check now looks at the character before the matched keyword. A
dot there means the word is a property name, not the keyword, so the
slash after it divides.
Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2 files changed
+30
-1
No files matched your search
@@ -216,6 +216,18 @@ test("ignores a vi.waitFor( written inside a regex literal's own text, inside a
|
||||
assert.deepEqual(findBareRealProcessWaits(source), []);
|
||||
});
|
||||
|
||||
test("rejects a vi.waitFor( that follows division after an await-named property, inside a template literal interpolation", () => {
|
||||
const source = [
|
||||
"async function example() {",
|
||||
" const message = `status: ${obj.await / divisor + (await vi.waitFor(() => expect(x).toBe(1)))}`;",
|
||||
"}",
|
||||
].join("\n");
|
||||
|
||||
assert.deepEqual(findBareRealProcessWaits(source), [
|
||||
{ line: 2, pattern: "vi.waitFor(" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("still flags a real bare vi.waitFor( on the line after a comment mentioning the pattern", () => {
|
||||
const source = [
|
||||
"async function example() {",
|
||||
|
||||
@@ -62,6 +62,20 @@ const REGEX_PRECEDING_KEYWORDS = new Set([
|
||||
"yield", "case", "throw", "do", "else", "await",
|
||||
]);
|
||||
|
||||
// A word that reads as one of `REGEX_PRECEDING_KEYWORDS` is a property
|
||||
// name, not the keyword, when a `.` (a plain member access or the last
|
||||
// character of an optional-chain `?.`) sits directly before it, with only
|
||||
// space allowed in between. `obj.await` is a value, so the `/` right after
|
||||
// it divides; only a standalone `await` puts the `/` in an expression
|
||||
// position.
|
||||
function isKeywordShapedPropertyName(source, wordStartIndex) {
|
||||
let beforeWordIndex = wordStartIndex - 1;
|
||||
while (beforeWordIndex >= 0 && /\s/.test(source[beforeWordIndex])) {
|
||||
beforeWordIndex -= 1;
|
||||
}
|
||||
return beforeWordIndex >= 0 && source[beforeWordIndex] === ".";
|
||||
}
|
||||
|
||||
function isRegexLiteralStart(source, index) {
|
||||
let previousIndex = index - 1;
|
||||
while (previousIndex >= 0 && /\s/.test(source[previousIndex])) {
|
||||
@@ -72,7 +86,10 @@ function isRegexLiteralStart(source, index) {
|
||||
if (!/[\w$)\]]/.test(previousCharacter)) return true;
|
||||
const wordMatch = source.slice(0, previousIndex + 1).match(/[A-Za-z_$][\w$]*$/);
|
||||
const word = wordMatch?.[0] ?? "";
|
||||
return REGEX_PRECEDING_KEYWORDS.has(word);
|
||||
if (!REGEX_PRECEDING_KEYWORDS.has(word)) return false;
|
||||
const wordStartIndex = previousIndex + 1 - word.length;
|
||||
if (isKeywordShapedPropertyName(source, wordStartIndex)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Mask a `/pattern/flags` regex literal, starting at the opening `/` in
|
||||
|
||||
Reference in new issue
Block a user