Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- Heavily-reflected Unreal Engine C++ classes are no longer dropped from the index. Reflection markup that decorates members — `UPROPERTY(...)`, `UFUNCTION(...)`, `UCLASS(...)`, `GENERATED_BODY()`, `UE_DEPRECATED_*(...)`, `DECLARE_DELEGATE_*(...)` — are no-semicolon macro calls that tree-sitter doesn't recognize, so each drops into error recovery; in a big class the errors pile up until the whole `class_specifier` collapses and the class, its base clause, and its members vanish (`UCharacterMovementComponent`, with ~240 such macros, disappeared entirely, breaking every subclass/type-hierarchy and blast-radius query that went through it). These line-leading annotation macros are now blanked (offset-preserving) before parsing so the class survives. Thanks @luoyxy for the report and root-cause analysis. (#1093 follow-up)
- Unreal Engine members and methods prefixed by an export/visibility macro are no longer lost. The `*_API` macro doesn't only sit on the class header — it prefixes almost every exported member of a large UE class (`ENGINE_API virtual void Tick(...)`, `static ENGINE_API void AddReferencedObjects(...)`); the parser read the macro as an extra type token and each such declaration fell into error recovery, so on headers like `Actor.h` and `World.h` hundreds of return types piled up as orphan errors and could still tip the class into collapse. Member/method-level `*_API` / `*_EXPORT` / `*_ABI` macros (Unreal, Qt/Boost, LLVM) are now blanked before parsing, mirroring the existing class-header recovery. (#1093 follow-up)
- Unreal Engine annotation macros that appear mid-line — an enum value's `UMETA(DisplayName=...)`, a parameter's `UPARAM(ref)`, or a deprecation tag wedged into a `using` alias (`using FOnNetTick UE_DEPRECATED(5.5, "...") = ...;`, which alone collapsed `UWorld` in `World.h`) — are now stripped too. These sit in positions the line-leading recovery structurally can't reach, and a single one could take down the surrounding enum or class. They are matched by an Unreal-only name list (`UMETA`, `UPARAM`, `UE_DEPRECATED*`) so no standard-C++ or other-library code is affected. Together these three fixes recover the main class of every large Unreal Engine header tested (`Actor`, `ActorComponent`, `SkeletalMeshComponent`, `World`, `LightComponent`, `CharacterMovementComponent`). (#1093 follow-up)
- A C++ header whose only C++ signal is an export-macro-annotated class is no longer misdetected as C — which had silently dropped the class. A `.h` file defaults to C and is only reclassified as C++ when it shows a C++-specific construct, but that check couldn't see through an export/visibility macro: `class ENGINE_API UFoo : public UObject` didn't match the macro-blind `class Name :` pattern, so a lean Unreal-Engine-style header carrying just `GENERATED_BODY()` and no explicit `public:` / `virtual` / `namespace` / `template` was parsed as C. The C extractor emits no class nodes, so the class — and its inheritance link — vanished from the graph, quietly undoing the macro-class recovery added in #1061. The C++ detection heuristic now recognizes an export-macro-annotated `class`/`struct` declaration (the same shape #1061 blanks before parsing), matching how `class Foo : Bar` was already detected; the two-token `<keyword> <MACRO> <Name>` before a `[:{]` never occurs in valid C, so genuine C headers are unaffected. Thanks @luoyxy for the report and root-cause analysis. (#1133)

## [1.2.0] - 2026-07-02

Expand Down
25 changes: 25 additions & 0 deletions __tests__/extraction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,31 @@ describe('Language Detection', () => {
expect(isSourceFile('default.nix')).toBe(true);
});

it('should detect a .h whose only C++ signal is an export-macro class as cpp', () => {
// Lean Unreal-Engine style header: the class is annotated with an export
// macro and carries no explicit `public:`/`virtual`/`namespace`/`template`,
// so the macro-blind `class\s+\w+\s*[:{]` branch alone can't see it. It must
// still detect as C++ — otherwise the C extractor (classTypes: []) drops the
// class definition entirely. (#1093 follow-up)
const macroClassHeader = `#pragma once
#include "CoreMinimal.h"

UCLASS()
class ENGINE_API UNetConnectionRepControl : public UObject
{
\tGENERATED_BODY()
\tbool IsRepControlEnable() const;
};
`;
expect(detectLanguage('NetConnectionRepControl.h', macroClassHeader)).toBe('cpp');
// Macro class with no base clause, brace on the next line, still C++.
expect(detectLanguage('Foo.h', 'MYMODULE_API_DECL\nclass MYMODULE_API FFoo\n{\n\tint X;\n};\n')).toBe('cpp');
// Export-macro struct with inheritance is likewise C++-only.
expect(detectLanguage('Bar.h', 'struct ENGINE_API FBar : public FBase {};\n')).toBe('cpp');
// Guard: a genuine C header must NOT be dragged to C++ by the new branch.
expect(detectLanguage('cfoo.h', '#ifndef CFOO_H\nstruct Point { int x; int y; };\nvoid f(struct Point p);\n#endif\n')).toBe('c');
});

it('should return unknown for unsupported extensions', () => {
expect(detectLanguage('styles.css')).toBe('unknown');
expect(detectLanguage('data.json')).toBe('unknown');
Expand Down
11 changes: 10 additions & 1 deletion src/extraction/grammars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,16 @@ export function detectLanguage(filePath: string, source?: string, overrides?: Re
*/
function looksLikeCpp(source: string): boolean {
const sample = source.substring(0, 8192);
return /\bnamespace\b|\bclass\s+\w+\s*[:{]|\btemplate\s*<|\b(?:public|private|protected)\s*:|\bvirtual\b|\busing\s+(?:namespace\b|\w+\s*=)/.test(sample);
// The `class MACRO Name : Base` / `class MACRO Name { … }` branch mirrors what
// `blankCppExportMacros` recovers: an ALL-CAPS export/visibility macro
// (`ENGINE_API`, `MYMODULE_API`, `*_EXPORT`, …) sitting between `class`/`struct`
// and the type name. Without it, a header whose ONLY C++ signal is such a
// macro-annotated class — common for lean Unreal-Engine types that carry just
// `GENERATED_BODY()` and no explicit `public:`/`virtual` — is misdetected as C,
// routed through the C extractor (which extracts no classes), and its class
// definition silently vanishes. The two-token shape (`<KW> <MACRO> <Name>`
// before a `[:{]`) never occurs in valid C, so this can't misclassify C headers.
return /\bnamespace\b|\bclass\s+\w+\s*[:{]|\b(?:class|struct)\s+[A-Z][A-Z0-9_]+\s+\w+\s*(?:final\s*)?[:{]|\btemplate\s*<|\b(?:public|private|protected)\s*:|\bvirtual\b|\busing\s+(?:namespace\b|\w+\s*=)/.test(sample);
}

/**
Expand Down