Skip to content

fix: escape JavaScript line terminators in generated docs - #489

Open
stbenjam wants to merge 4 commits into
mainfrom
agent/escape-js-attr-line-terminators
Open

fix: escape JavaScript line terminators in generated docs#489
stbenjam wants to merge 4 commits into
mainfrom
agent/escape-js-attr-line-terminators

Conversation

@stbenjam

@stbenjam stbenjam commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Summary

Generated documentation builds inline event handlers with JavaScript string values nested inside HTML attributes. The old helper manually escaped a single-quoted JavaScript literal and omitted ECMAScript line terminators, which could leave invalid handlers in generated pages.

Use JSON.stringify as the JavaScript string-literal serializer, escape U+2028 and U+2029 for compatibility, then HTML-escape the resulting double-quoted literal. Update all inline-handler call sites to use the complete serialized literal.

Fixes #488

Validation

  • .venv/bin/pytest tests/codex/test_docs_output_safety.py -q — 65 passed
  • make test — 3903 passed, 22 warnings
  • make lint — passed
  • make update — passed with no generated changes
  • git diff --check — passed

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@stbenjam, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 11 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2017839f-4c24-4e8e-96b1-6fa1637ddc9a

📥 Commits

Reviewing files that changed from the base of the PR and between c325108 and 03ea660.

📒 Files selected for processing (2)
  • src/skillsaw/docs/html_renderer.py
  • tests/codex/test_docs_output_safety.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@stbenjam
stbenjam marked this pull request as ready for review August 7, 2026 01:06
@qodo-code-review

Copy link
Copy Markdown

ⓘ Your Qodo trial ends soon. Ask your workspace admin to set up billing to keep reviews running after the trial. Manage billing

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Escape JS line terminators in escJsAttr for generated HTML docs

🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Escape CR/LF and Unicode line separators in escJsAttr to keep inline JS string literals valid.
• Preserve existing backslash/quote escaping order for attribute-embedded JavaScript.
• Add a Node-backed regression test covering mixed escaping and all JS line terminators.
Diagram

graph TD
  A["Python docs renderer"] --> B["_get_js() template"] --> C["escJsAttr()"] --> D["HTML onclick attribute"] --> E["Browser JS parser"]
  F["Pytest safety suite"] --> G{{"Node --check / -e"}} --> B
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use JSON.stringify-based escaping for JS literals
  • ➕ Avoids hand-maintained escape lists; JSON escaping naturally handles line terminators via \uXXXX escapes
  • ➕ Reduces risk of missing other JS-invalid characters over time
  • ➖ Must still ensure correct behavior for single-quoted attribute-embedded strings and preserve current escaping order expectations
  • ➖ Would be a larger refactor of the embedded template and tests
2. Avoid inline JS attributes (use data-* + addEventListener)
  • ➕ Eliminates the nested escaping problem (HTML attribute + JS literal) entirely
  • ➕ Improves maintainability by separating data from behavior
  • ➖ Substantially larger change to generated HTML structure and client-side JS
  • ➖ Higher risk of regressions in docs UI behavior

Recommendation: Current approach is appropriate for a small, targeted fix: escaping CR/LF/U+2028/U+2029 after existing backslash/quote escapes directly addresses the JS-literal validity issue without refactoring the docs UI. The added Node-backed regression test provides strong protection against regressions.

Files changed (2) +43 / -5

Bug fix (1) +11 / -1
html_renderer.pyEscape JS line terminators in escJsAttr for attribute-embedded handlers +11/-1

Escape JS line terminators in escJsAttr for attribute-embedded handlers

• Updates the embedded JavaScript helper escJsAttr to escape CR, LF, U+2028, and U+2029 in addition to existing backslash and single-quote escaping. Keeps the escape ordering aligned with HTML entity decoding behavior in attributes.

src/skillsaw/docs/html_renderer.py

Tests (1) +32 / -4
test_docs_output_safety.pyAdd Node regression test for escJsAttr line terminator escaping +32/-4

Add Node regression test for escJsAttr line terminator escaping

• Strengthens the existing JS-template safety test to assert the presence of all escape steps. Adds a Node-executed regression test that feeds a string containing CR/LF/U+2028/U+2029 plus backslash/quote sequences through escJsAttr and validates the exact escaped output.

tests/codex/test_docs_output_safety.py

@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.88%. Comparing base (c325108) to head (03ea660).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #489   +/-   ##
=======================================
  Coverage   93.88%   93.88%           
=======================================
  Files         173      173           
  Lines       14715    14715           
=======================================
  Hits        13815    13815           
  Misses        900      900           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@qodo-code-review

qodo-code-review Bot commented Aug 7, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Docstring references shipped bug ✓ Resolved 📜 Skill insight ⚙ Maintainability
Description
The modified test docstring includes bug-history phrasing (once shipped ...) rather than
describing the behavior/invariant under test. This violates the requirement that docstrings/comments
describe the shipped code, not historical/review context, and can become misleading over time.
Code

tests/codex/test_docs_output_safety.py[R478-480]

        """The JS template is a non-raw Python string — backslash halving
-        once shipped an unparseable script and a blank page. Pin the
-        emitted (post-halving) escJsAttr line, and parse every script
-        block with node when it is available."""
+        once shipped an unparseable script and a blank page. Pin the emitted
+        (post-halving) escJsAttr escapes, and parse every script block with
Relevance

●●● Strong

Team previously accepted rewording docstrings to remove “before the fix”/history framing.

PR-#485

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2344941 requires comments/docstrings to describe the shipped code rather than
historical/review-process residue. The modified docstring explicitly describes a past incident
(once shipped an unparseable script and a blank page) instead of stating the invariant/behavior
the test enforces.

tests/codex/test_docs_output_safety.py[478-481]
Skill: skillsaw-review-panel

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The updated docstring for `test_emitted_page_script_survives_backslash_escaping` includes review/bug-history phrasing (`once shipped ...`) instead of a timeless description of the behavior/invariant the test enforces.

## Issue Context
Compliance requires docstrings/comments to describe the shipped code and the behavior under test, not historical narratives that can become stale.

## Fix Focus Areas
- tests/codex/test_docs_output_safety.py[478-481]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Brittle JS helper extraction ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
test_escjsattr_escapes_javascript_line_terminators slices helper functions out of _get_js()
using the exact string "\n\n  init();" as a boundary. Any formatting-only change around init();
(extra/removed newline, indentation change) will raise ValueError before assertions run, failing
CI unrelated to escaping behavior.
Code

tests/codex/test_docs_output_safety.py[R526-528]

+        helpers = js[
+            js.index("function escAttr") : js.index("\n\n  init();", js.index("function escAttr"))
+        ]
Relevance

●●● Strong

Repo has accepted making tests/workflows less brittle to formatting/drift; likely to accept reducing
whitespace-sensitive slicing.

PR-#137
PR-#479

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The test’s slicing boundary is the literal substring "\n\n  init();", which is tied to the current
formatting of the _get_js() template just before the init(); call. This couples the test to
template whitespace rather than behavior, so whitespace-only edits can break the test even if
escaping remains correct.

tests/codex/test_docs_output_safety.py[515-535]
src/skillsaw/docs/html_renderer.py[1204-1224]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new regression test extracts JavaScript helpers by slicing between `js.index("function escAttr")` and `js.index("\n\n  init();", ...)`, which hard-codes specific whitespace/formatting in the JS template. This makes the test fail with `ValueError` on harmless formatting changes.

## Issue Context
The test only needs the `escAttr`/`escJsAttr` function bodies; it shouldn't depend on the exact blank lines/indentation before `init()`.

## Fix Focus Areas
- tests/codex/test_docs_output_safety.py[525-532]

### Suggested fix
Replace the `"\n\n  init();"` boundary with a more stable extraction:
- Use a regex that captures from `function escAttr` through the end of `function escJsAttr` (e.g., up to the matching closing brace), or
- Add explicit stable markers in `_get_js()` around the helper section (e.g., `// BEGIN_ESC_HELPERS` / `// END_ESC_HELPERS`) and slice based on those markers.

Ensure the test fails with a clear assertion message if extraction fails, rather than an uncaught `ValueError`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context used
✅ Compliance rules (platform): 171 rules
✅ Skills: 6 invoked
  skillsaw-pr-review
  skillsaw-issue-solver
  skillsaw-pr-followup
  skillsaw-create-plugin
  skillsaw-review-panel
  skillsaw-maintenance

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread tests/codex/test_docs_output_safety.py Outdated
Comment thread tests/codex/test_docs_output_safety.py Outdated
Comment thread src/skillsaw/docs/html_renderer.py Outdated
Comment on lines +1205 to +1221
function escJs(str) {
// JSON.stringify is the JavaScript string-literal serializer. It handles
// quotes, backslashes, and ordinary line terminators; escape the two
// legacy Unicode line separators that it leaves literal.
if (!str) return '""';
return JSON.stringify(String(str))
.replace(/\\u2028/g, '\\\\u2028')
.replace(/\\u2029/g, '\\\\u2029');
}

function escJsAttr(str) {
// A JS string literal nested inside an HTML attribute — two contexts, so
// two escapes in that order. innerHTML decodes the entities before the
// handler compiles, so the JS escapes must survive that decode: \' stays
// \', while " arrives as a plain quote which cannot close a
// single-quoted JS string.
return escAttr(String(str).replace(/\\\\/g, '\\\\\\\\').replace(/'/g, "\\\\'"));
// escJs returns a complete double-quoted JS string literal. HTML-escape
// that literal so it can be placed inside a double-quoted attribute;
// innerHTML decodes the entities before the inline handler compiles.
return escAttr(escJs(str));
}
// END_ESCAPERS

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this extra layer of indirection lol

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 03ea660df2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// quotes, backslashes, and ordinary line terminators; escape the two
// legacy Unicode line separators that it leaves literal. Then HTML-escape
// the complete literal for the double-quoted attribute.
var literal = !str ? '""' : JSON.stringify(String(str));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve falsy values in the serializer

When an inline-handler value is numeric 0 or boolean false, this truthiness check serializes it as "" instead of preserving String(str) as the previous helper did. The docs model deliberately supports falsy manifest-derived names such as PluginDoc(name=0), so the generated plugin card now calls navigateTo("") and returns home rather than opening the plugin named 0; serialize all non-null values directly instead of using a truthiness test.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs: escape line terminators in generated escJsAttr

1 participant