simpleqt opened a new pull request, #788:
URL: https://github.com/apache/commons-text/pull/788

   ## Summary
   
   `StringSubstitutor` ignores the escape character when the variable's escape 
character is the first character of the range passed to the offset/length 
overloads, so an escaped variable is substituted anyway.
   
   ## Root cause
   
   `StringSubstitutor.substitute(TextStringBuilder, int offset, int length, 
List)` decides whether a variable prefix is escaped with:
   
   ```java
   if (pos > offset && builder.charAt(pos - 1) == escapeCh) {
   ```
   
   `offset` is the start of the range being processed, not the start of the 
builder. When a caller substitutes only part of a builder and the escape 
character is immediately before that range, `pos == offset` for the prefix, so 
the test fails and the escape is not recognised:
   
   - `replaceIn(builder, 0, length)` on `~${user}` → `${user}` (correct)
   - `replaceIn(builder, 1, length - 1)` on the same text → `~fred` (escape 
ignored, variable substituted)
   
   The whole-string overloads hide the bug because they pass `offset = 0`.
   
   ## Reproduction
   
   With `new StringSubstitutor(map, "${", "}", '~')` and `map.put("user", 
"fred")`:
   
   ```java
   TextStringBuilder b = new TextStringBuilder("~${user}");
   sub.replaceIn(b, 1, b.length() - 1);
   // before: "~fred"   expected: "${user}"
   ```
   
   `~` here is the escape character; the caller's intent is that `${user}` is 
left literal, exactly as it is when the whole builder is processed.
   
   ## Fix
   
   The escape character is now honoured when it lies in the range being 
substituted **or immediately before it**, because the character directly before 
the range is the one that prefixes the range's first position:
   
   ```java
   final int escStart = offset > 0 ? offset - 1 : offset;
   ...
   if (pos > escStart && builder.charAt(pos - 1) == escapeCh) {
   ```
   
   This keeps the existing behaviour for `offset == 0` (and for ranges shorter 
than one character), and makes substitution of a sub-range agree with 
substitution of the whole builder. An escape character that is not the 
variable's own escape is still left untouched, and an escape character inside 
the range behaves as before.
   
   ## Tests
   
   Added `StringSubstitutorTest.testSubstituteEscapeAtStartOfRange`, which 
covers: the whole builder, the escape character immediately before the 
substituted range, the same followed by an unescaped variable, an escape 
character inside the range, and an unrelated escape character before the range 
that must be left alone.
   
   Evidence on the new test:
   
   - Without the production change: `expected: <${user}> but was: <~fred>` — 80 
tests run, 1 failure.
   - With the change: 80 tests run, 0 failures.
   
   Full verification with the default `mvn` goal: 2045 tests, 0 failures; 
checkstyle, PMD and SpotBugs all pass.
   
   Note: 6 tests in `PathFenceResolverTest` and `FileStringLookupTest` error on 
this Windows machine with `IllegalArgumentException: 'other' has different 
root`, because their `write(Path, String)` helper does 
`CURRENT_PATH.toAbsolutePath().relativize(path.toAbsolutePath())` and the 
`@TempDir` is on `C:\` while the working directory is on `G:\`. Those 6 errors 
are present on unmodified `master` and are unrelated to this change.
   
   ## Jira
   
   `CONTRIBUTING.md` asks for a Jira ticket, and `RELEASE-NOTES.txt` is 
generated from Jira. I do not have an Apache Jira account, so there is no 
ticket key to reference here; the commit message has no Jira key for that 
reason. Happy to update the commit message and add a `RELEASE-NOTES.txt` entry 
if a ticket is created and a key is provided.
   
   ## AI disclosure
   
   An AI agent assisted with investigating, implementing and testing this 
change, and with drafting this description. The change was reviewed and 
verified by the submitter.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to