Skip to content

Commit 05bd48d

Browse files
Copilotnomeguy
andcommitted
Fix escapeAssertion to respect string literals and add tests
Co-authored-by: nomeguy <85475922+nomeguy@users.noreply.github.com>
1 parent a051dd7 commit 05bd48d

2 files changed

Lines changed: 86 additions & 4 deletions

File tree

src/util/util.ts

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,62 @@ import { mustGetDefaultFileSystem } from '../persist';
2020
const escapeAssertionReg = new RegExp(/(^|[^A-Za-z0-9_])([rp])[0-9]*\./g);
2121

2222
function escapeAssertion(s: string): string {
23-
s = s.replace(escapeAssertionReg, (match, p1, p2) => {
24-
return p1 + p2 + match.substring(p1.length + p2.length).replace('.', '_');
25-
});
26-
return s;
23+
// Track whether we're inside a string literal
24+
let result = '';
25+
let inSingleQuote = false;
26+
let inDoubleQuote = false;
27+
let i = 0;
28+
29+
while (i < s.length) {
30+
const char = s[i];
31+
32+
// Toggle quote tracking
33+
if (char === "'" && !inDoubleQuote) {
34+
inSingleQuote = !inSingleQuote;
35+
result += char;
36+
i++;
37+
continue;
38+
}
39+
if (char === '"' && !inSingleQuote) {
40+
inDoubleQuote = !inDoubleQuote;
41+
result += char;
42+
i++;
43+
continue;
44+
}
45+
46+
// If inside a string literal, just append the character
47+
if (inSingleQuote || inDoubleQuote) {
48+
result += char;
49+
i++;
50+
continue;
51+
}
52+
53+
// Check if current position is the start of an r/p pattern that should be escaped
54+
// Pattern: (^|[^A-Za-z0-9_])([rp])[0-9]*\.
55+
const remaining = s.substring(i);
56+
const match = remaining.match(/^([rp])([0-9]*)\./);
57+
58+
if (match) {
59+
// Check if this is a valid boundary (either start of string or preceded by non-alphanumeric)
60+
const prevChar = i > 0 ? s[i - 1] : '';
61+
const isValidBoundary = i === 0 || /[^A-Za-z0-9_]/.test(prevChar);
62+
63+
if (isValidBoundary) {
64+
// Replace the dot with underscore
65+
result += match[1] + match[2] + '_';
66+
i += match[0].length;
67+
} else {
68+
// Not a valid boundary, just add the character
69+
result += char;
70+
i++;
71+
}
72+
} else {
73+
result += char;
74+
i++;
75+
}
76+
}
77+
78+
return result;
2779
}
2880

2981
// removeComments removes the comments starting with # in the text.

test/util.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,33 @@ test('bracketCompatible', () => {
228228
)
229229
).toEqual("g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act || r.obj in ['data2', 'data3'] || r.obj in ['data4', 'data5']");
230230
});
231+
232+
test('test escapeAssertion', () => {
233+
// Basic escaping
234+
expect(util.escapeAssertion('r.obj == p.obj')).toEqual('r_obj == p_obj');
235+
expect(util.escapeAssertion('r.act == p.act')).toEqual('r_act == p_act');
236+
237+
// With numbers
238+
expect(util.escapeAssertion('r2.obj == p3.sub')).toEqual('r2_obj == p3_sub');
239+
240+
// With parentheses
241+
expect(util.escapeAssertion('(r.obj == p.obj)')).toEqual('(r_obj == p_obj)');
242+
243+
// Nested property access should not escape inner dots
244+
expect(util.escapeAssertion('r.obj.owner.id')).toEqual('r_obj.owner.id');
245+
expect(util.escapeAssertion('!!r.sub.id && r.sub.id == r.obj.owner.id')).toEqual('!!r_sub.id && r_sub.id == r_obj.owner.id');
246+
247+
// String literals with double quotes - should NOT escape inside strings
248+
expect(util.escapeAssertion('p.obj == "r.my_resource"')).toEqual('p_obj == "r.my_resource"');
249+
expect(util.escapeAssertion('p.obj == "r.something" && r.act == p.act')).toEqual('p_obj == "r.something" && r_act == p_act');
250+
251+
// String literals with single quotes - should NOT escape inside strings
252+
expect(util.escapeAssertion("p.obj == 'r.my_resource'")).toEqual("p_obj == 'r.my_resource'");
253+
expect(util.escapeAssertion("p.obj == 'p.something' && r.act == p.act")).toEqual("p_obj == 'p.something' && r_act == p_act");
254+
255+
// Mixed quotes
256+
expect(util.escapeAssertion('r.obj == "test" && p.sub == \'value\'')).toEqual('r_obj == "test" && p_sub == \'value\'');
257+
258+
// Complex string literal cases
259+
expect(util.escapeAssertion('p.rule == "r.obj == \\"value\\""')).toEqual('p_rule == "r.obj == \\"value\\""');
260+
});

0 commit comments

Comments
 (0)