Parse the interior of a class/struct body for UPROPERTY and UFUNCTION declarations.
(raw_body: str, cls: ParsedClass)
| 496 | return ''.join(result) |
| 497 | |
| 498 | def _parse_body(raw_body: str, cls: ParsedClass): |
| 499 | """ |
| 500 | Parse the interior of a class/struct body for UPROPERTY and UFUNCTION declarations. |
| 501 | """ |
| 502 | body = _strip_inline_bodies(raw_body) |
| 503 | |
| 504 | # Pre-calculate access specifier regions based on string index in body |
| 505 | access_regions = [] |
| 506 | current_access = 'struct' if cls.kind == 'struct' else 'private' |
| 507 | if current_access == 'struct': current_access = 'public' # structs default to public |
| 508 | access_regions.append((0, current_access)) |
| 509 | |
| 510 | for match in re.finditer(r'\b(public|protected|private)\s*:', body): |
| 511 | access_regions.append((match.end(), match.group(1))) |
| 512 | |
| 513 | def get_access(pos: int) -> str: |
| 514 | acc = access_regions[0][1] |
| 515 | for r_pos, r_acc in access_regions: |
| 516 | if pos >= r_pos: |
| 517 | acc = r_acc |
| 518 | else: |
| 519 | break |
| 520 | return acc |
| 521 | |
| 522 | # ── Properties ─────────────────────────────────────────────────────────── |
| 523 | # Pattern: optional doc, optional UPROPERTY(...), then type, then name; |
| 524 | prop_re = re.compile( |
| 525 | r'(?:/\*\*((?:(?!\*/).)*?)\*/\s*)?' # optional doc |
| 526 | r'(?:UPROPERTY\s*\((.*?)\)\s*)?' # optional UPROPERTY macro |
| 527 | r'((?:TArray\s*<[^>]*>|TMap\s*<[^>]*>|TSet\s*<[^>]*>|TScriptInterface\s*<[^>]*>|' |
| 528 | r'TArray\s*<[^{;]*>|[\w:<>\*&\s,]+?))\s*' # type |
| 529 | r'(\w+)\s*' # name |
| 530 | r'(?:\[[^\]]+\]\s*)?' # optional array bounds e.g. [3] |
| 531 | r'(?:=\s*[^;{]+)?;', # optional default value |
| 532 | re.DOTALL |
| 533 | ) |
| 534 | for m in prop_re.finditer(body): |
| 535 | raw_doc = m.group(1) or '' |
| 536 | spec_str = m.group(2) or '' |
| 537 | type_str = _clean_type(m.group(3)) |
| 538 | prop_name = m.group(4) |
| 539 | |
| 540 | # Skip generated body artifacts |
| 541 | if prop_name in ('GENERATED_BODY', 'GENERATED_USTRUCT_BODY', 'BlueprintSpawnableComponent'): |
| 542 | continue |
| 543 | if not prop_name or (not prop_name[0].isupper() and not prop_name.startswith('b') and not prop_name[0].islower()): |
| 544 | continue |
| 545 | |
| 546 | # Reject false positives matching inside UFUNCTION macros |
| 547 | matched_str = m.group(0) |
| 548 | if matched_str.count(')') > matched_str.count('('): |
| 549 | continue |
| 550 | if type_str.strip().endswith(','): |
| 551 | continue |
| 552 | if 'BlueprintPure' in type_str or 'BlueprintCallable' in type_str or 'CallInEditor' in type_str: |
| 553 | continue |
| 554 | |
| 555 | has_macro = bool(spec_str) |
no test coverage detected