Skip to content

Improved Markdown generation in make_md.py:#1200

Merged
Arctis-Fireblight merged 2 commits intoRedot-Engine:masterfrom
Arctis-Fireblight:Fix-Markdown-Script
Feb 19, 2026
Merged

Improved Markdown generation in make_md.py:#1200
Arctis-Fireblight merged 2 commits intoRedot-Engine:masterfrom
Arctis-Fireblight:Fix-Markdown-Script

Conversation

@Arctis-Fireblight
Copy link
Contributor

@Arctis-Fireblight Arctis-Fireblight commented Feb 18, 2026

  • Updated anchor creation for consistency.
  • Added _category_.json generation for better doc indexing.
  • Introduced escaping functionality for special characters in descriptions and MD files.
  • Enhanced sanitization logic for class/file/operator names.
  • Streamlined Markdown reference links and tag escaping logic.

Summary by CodeRabbit

  • Documentation
    • Standardized documentation link/anchor format for more consistent navigation.
    • Improved escaping and markup handling to prevent rendering issues with inline HTML, braces, and markdown-sensitive characters.
    • Better handling of type and cross-reference links for clearer, correct references.
    • Added automatic category index generation to improve discovery and organization.
    • Fixed formatting quirks to ensure accurate display of code examples and separators.

- Updated anchor creation for consistency.
- Added `_category_.json` generation for better doc indexing.
- Introduced escaping functionality for special characters in descriptions and MD files.
- Enhanced sanitization logic for class/file/operator names.
- Streamlined Markdown reference links and tag escaping logic.
@coderabbitai
Copy link

coderabbitai bot commented Feb 18, 2026

Walkthrough

Minor doc formatting fix in doc/classes/Engine.xml and a large refactor of doc/tools/make_md.py that changes anchor/link generation, adds escaping helpers, and alters markdown output and type-linking behavior.

Changes

Cohort / File(s) Summary
Documentation Format Fix
doc/classes/Engine.xml
Fixed get_donor_info method description formatting (adjusted inline code markup).
Markdown Generation Refactor
doc/tools/make_md.py
Added generation of _category_.json; replaced class_<name> anchors/links with sanitized <name>.md targets; updated type/link rendering to point to .md (including Array/Dictionary handling); introduced escape_tags(input: str) and escape_braces(input: str) and integrated stronger escaping (HTML-like content, braces, markdown-sensitive chars) across signature/description rendering; adjusted horizontal rule HTML output; added operator-name sanitization improvements.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.65% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Improved Markdown generation in make_md.py' directly matches the main changes in the PR, which focus on enhancements to the make_md.py script for better markdown generation, anchor consistency, escaping, and sanitization.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
doc/tools/make_md.py (2)

1731-1731: <hr> is a void element — the </hr> closing tag is unnecessary.

While not technically broken (browsers tolerate it), <hr> is a void element per the HTML spec and should not have a closing tag. Using <hr class="..."> or <hr class="..." /> is more conventional.

♻️ Suggested tweak
-    return f'<hr class="classref-{separator_class}-separator"></hr>\n\n'
+    return f'<hr class="classref-{separator_class}-separator" />\n\n'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@doc/tools/make_md.py` at line 1731, The returned HTML uses a forbidden
closing tag for a void element: remove the unnecessary "</hr>" and return a
proper void <hr> (e.g., '<hr class="classref-{separator_class}-separator">' or
self-closing) so the string constructed where separator_class is interpolated no
longer includes the "</hr>" closing tag.

1307-1307: Mutating shared definition objects with escape_tags is fragile.

Assigning back to property_def.text (and similarly m.description at lines 1245, 1356, 1404, 1450) permanently alters the shared definition object. If the object were ever rendered again (e.g., referenced from another context, or if the script logic changes), the escaping would double. Prefer escaping into a local variable instead of mutating the source.

♻️ Example for the property_def.text case
-                property_def.text = escape_tags(property_def.text)
                 f.write(make_deprecated_experimental(property_def, state))

-                if property_def.text is not None and property_def.text.strip() != "":
-                    f.write(f"{format_text_block(property_def.text.strip(), property_def, state)}\n\n")
+                prop_text = escape_tags(property_def.text) if property_def.text is not None else None
+                if prop_text is not None and prop_text.strip() != "":
+                    f.write(f"{format_text_block(prop_text.strip(), property_def, state)}\n\n")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@doc/tools/make_md.py` at line 1307, Do not mutate shared definition objects
by reassigning escaped strings back into them; instead compute an escaped local
variable and use that for rendering. For the property case, replace
"property_def.text = escape_tags(property_def.text)" with something like
"escaped_text = escape_tags(property_def.text)" and use escaped_text wherever
the template/render needs the escaped value; apply the same pattern for the
other places that reassign (the m.description occurrences) so you never
overwrite property_def.text or m.description in-place.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@doc/tools/make_md.py`:
- Line 1036: The link construction for override properties incorrectly prefixes
the anchor with "class_", causing broken anchors; in the ref assembly where
property_def, override_file and sanitize_class_name are used (the f-string
building ref), remove the "class_" prefix so the fragment becomes
`{sanitize_class_name(property_def.overrides)}_property_{property_def.name}` to
match the other anchor generation convention and ensure the link points to the
correct anchor in the target file.
- Around line 1516-1517: The escape_tags function returns malformed HTML
entities; update escape_tags (the function defined as escape_tags) to use
semicolon-terminated entities by replacing "<" with "&lt;" and ">" with "&gt;"
(matching the escape_md behavior) so the returned strings produce valid escaped
HTML.
- Around line 1245-1246: escape_tags is being called on optional strings (e.g.,
m.description) and will raise AttributeError when the value is None; update each
site (the shown call with m.description and the other four occurrences at the
same pattern) to only call escape_tags when the value is not None (or use a
conditional expression to set m.description = escape_tags(m.description) if
m.description is not None else None). Locate the call sites referencing
escape_tags and the optional fields coming from MethodDef.__init__ /
PropertyDef.__init__ and wrap the call in a None-check so the code preserves
None values instead of invoking escape_tags on them.

---

Nitpick comments:
In `@doc/tools/make_md.py`:
- Line 1731: The returned HTML uses a forbidden closing tag for a void element:
remove the unnecessary "</hr>" and return a proper void <hr> (e.g., '<hr
class="classref-{separator_class}-separator">' or self-closing) so the string
constructed where separator_class is interpolated no longer includes the "</hr>"
closing tag.
- Line 1307: Do not mutate shared definition objects by reassigning escaped
strings back into them; instead compute an escaped local variable and use that
for rendering. For the property case, replace "property_def.text =
escape_tags(property_def.text)" with something like "escaped_text =
escape_tags(property_def.text)" and use escaped_text wherever the
template/render needs the escaped value; apply the same pattern for the other
places that reassign (the m.description occurrences) so you never overwrite
property_def.text or m.description in-place.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
doc/tools/make_md.py (2)

1516-1525: None-to-"" fallback in escape_tags/escape_braces is a latent footgun.

Both functions accept Optional[str] but return str, silently converting None to "". All current callers guard against None before calling, so the internal None branch is never executed. If a future call site drops the guard (or calls the function directly on an Optional field), the None-to-"" conversion would silently transform a "no description" sentinel into an empty string, changing downstream is not None checks.

Prefer making the type contract explicit:

♻️ Proposed fix
-def escape_tags(input: Optional[str]) -> str:
-    if input is None:
-        return ""
-    return input.replace("<", "&lt;").replace(">", "&gt;")
+def escape_tags(input: str) -> str:
+    return input.replace("<", "&lt;").replace(">", "&gt;")


-def escape_braces(input: Optional[str]) -> str:
-    if input is None:
-        return ""
-    return input.replace("{", "\\{").replace("}", "\\}")
+def escape_braces(input: str) -> str:
+    return input.replace("{", "\\{").replace("}", "\\}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@doc/tools/make_md.py` around lines 1516 - 1525, The functions escape_tags and
escape_braces currently accept Optional[str] but silently convert None to "",
which can mask "no description" sentinels; change their type contract to require
a plain str (remove Optional from the parameter type) and delete the None
fallback branch so they no longer silently coerce None. Update all call sites
that previously passed Optional values to explicitly guard/handle None before
calling (or propagate None as appropriate) so callers preserve the original
None-vs-empty-string semantics.

2484-2486: HTML_TAG_RE only matches tags with at most one attribute — multi-attribute tags will have their </> incorrectly escaped.

The attribute sub-pattern (?:\s+[a-zA-Z0-9_/-]+(?:=...)?)? uses a single ?, so only zero or one attribute is accepted. A tag like <a href="..." id="..."> would not match, and its </> would be escaped by the even-index branch.

For the current content (qualifier <span class="...">, <hr class="...">, <div class="...">) one attribute is always sufficient. This becomes fragile if generated HTML ever uses multiple attributes.

♻️ Proposed more robust regex (repeating attribute group)
-HTML_TAG_RE = re.compile(
-    r"(<(?:[a-zA-Z0-9_/]+(?:\s+[a-zA-Z0-9_/-]+(?:=\"[^\"]*\"|='[^']*'|[^>]*))?|!--.*?--|https?://.*?)>)"
-)
+HTML_TAG_RE = re.compile(
+    r"(<(?:[a-zA-Z0-9_/]+(?:\s+[a-zA-Z0-9_:/-]+(?:=\"[^\"]*\"|='[^']*')?)*|!--.*?--|https?://[^>]*)>)"
+)

Additionally, the double-check on line 2533 (if not parts[i].startswith("<") or not parts[i].endswith(">")) is dead code: the regex's capturing group always starts with < and ends with >, so this branch can never execute.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@doc/tools/make_md.py` around lines 2484 - 2486, The HTML_TAG_RE currently
allows at most one attribute, so change its attribute sub-pattern in HTML_TAG_RE
to allow repeating attributes (make the attribute group quantified to * instead
of ? so tags like <a href="..." id="..."> match correctly) and keep the other
alternatives (comments/URLs) intact; also remove the dead check that verifies
parts[i].startswith("<") or parts[i].endswith(">") because the regex capturing
group always yields strings beginning with '<' and ending with '>' (delete that
conditional branch where it appears).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@doc/tools/make_md.py`:
- Around line 1245-1246: The pre-escaping call escape_tags on member description
fields (e.g., the assignment to m.description before format_text_block) corrupts
code-block content; remove the escape_tags pre-processing assignments entirely
(the one for m.description and the four analogous calls for
annotations/properties/constructors/methods/operators) so descriptions flow
unchanged into format_text_block which uses escape_md/HTML_TAG_RE and respects
inside_code; ensure no other code-paths call escape_tags on the full description
before format_text_block.
- Around line 1446-1447: The operator-detail output currently calls escape_tags
on the signature (variable signature produced by make_method_signature) which is
always a str and turns qualifier badge HTML (badge = '<span
class="const">const</span>') into escaped text; remove the dead None-guard and
stop calling escape_tags for the operator signature so the signature is written
raw like the constructor/method sections (i.e., write f.write(f"{ret_type}
{signature} {self_link}\n\n") using the unescaped signature), ensuring
escape_tags is still used where appropriate for other fields but not for the
operator signature.
- Around line 782-795: The hardcoded magic number position: 6 and the literal
label "Class Reference" should be made configurable and localizable: add a new
CLI option on args (e.g., args.class_ref_position or args.position_class_ref) or
declare a named constant near the top of the module for
DEFAULT_CLASS_REF_POSITION and use that instead of 6 when constructing
category_data, and replace the literal label with a call to translate("Class
Reference") to match other headings; ensure the new CLI arg is parsed and
documented so callers can override the position without editing source, and
update the code paths that reference
args.dry_run/category_json_path/category_data to use the new param/constant and
translate() for the label.
- Line 1735: The returned HTML string currently emits a non-self-closing hr tag
which breaks MDX/JSX parsing; update the return in make_md.py (the line that
builds the separator using separator_class) to use a self-closing hr void
element with the same class name and preserve the trailing newlines so the
output becomes a valid MDX-compatible JSX void element instead of "<hr
...></hr>".

---

Nitpick comments:
In `@doc/tools/make_md.py`:
- Around line 1516-1525: The functions escape_tags and escape_braces currently
accept Optional[str] but silently convert None to "", which can mask "no
description" sentinels; change their type contract to require a plain str
(remove Optional from the parameter type) and delete the None fallback branch so
they no longer silently coerce None. Update all call sites that previously
passed Optional values to explicitly guard/handle None before calling (or
propagate None as appropriate) so callers preserve the original
None-vs-empty-string semantics.
- Around line 2484-2486: The HTML_TAG_RE currently allows at most one attribute,
so change its attribute sub-pattern in HTML_TAG_RE to allow repeating attributes
(make the attribute group quantified to * instead of ? so tags like <a
href="..." id="..."> match correctly) and keep the other alternatives
(comments/URLs) intact; also remove the dead check that verifies
parts[i].startswith("<") or parts[i].endswith(">") because the regex capturing
group always yields strings beginning with '<' and ending with '>' (delete that
conditional branch where it appears).

- Updated anchor creation for consistency.
- Added `_category_.json` generation for better doc indexing.
- Introduced escaping functionality for special characters in descriptions and MD files.
- Enhanced sanitization logic for class/file/operator names.
- Streamlined Markdown reference links and tag escaping logic.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@doc/tools/make_md.py`:
- Around line 1445-1447: The operator signatures are being passed through
escape_tags which turns the HTML qualifier badges (e.g., <span
class="const">const</span>) into literal text; update the operator signature
emission to match constructors/methods by removing the escape_tags call and
write the signature raw (i.e., use the returned signature from
make_method_signature directly in the f.write call), and also remove the
unnecessary "if signature is not None else None" guard since
make_method_signature always returns a str; touch the code paths that call
escape_tags for operators (look for the lines using make_method_signature,
escape_tags, and the variable signature in this module) to make these changes.

@Arctis-Fireblight Arctis-Fireblight merged commit 82ff6fe into Redot-Engine:master Feb 19, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant