|
@@ -2811,6 +2811,59 @@ class MYGAME_API UMyComponent : public UActorComponent { };
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+ describe('C++ forward declarations do not mint phantom class nodes (#1093)', () => {
|
|
|
|
|
+ // `class Foo;` parses as a bodiless class_specifier. Repeated across headers,
|
|
|
|
|
+ // each forward decl minted a phantom bodiless `class` node that crowded out —
|
|
|
|
|
+ // and could be picked as the blast-radius representative over — the single
|
|
|
|
|
+ // real definition. Bodiless struct/enum specifiers were already skipped;
|
|
|
|
|
+ // classes now are too, but ONLY for C/C++ (opt-in flag), never for languages
|
|
|
|
|
+ // where a bodiless class is a complete definition.
|
|
|
|
|
+ it('keeps only the real definition, dropping repeated forward decls', () => {
|
|
|
|
|
+ const code = `
|
|
|
|
|
+class APXCharacter; // forward decl (header 1)
|
|
|
|
|
+class APXCharacter; // forward decl (header 2)
|
|
|
|
|
+friend class APXCharacter; // elaborated / friend forward reference
|
|
|
|
|
+
|
|
|
|
|
+class APXCharacter { // the one real definition
|
|
|
|
|
+ int hp;
|
|
|
|
|
+ void takeDamage(int amount) { hp -= amount; }
|
|
|
|
|
+};
|
|
|
|
|
+`;
|
|
|
|
|
+ const result = extractFromSource('character.cpp', code);
|
|
|
|
|
+ const classes = result.nodes.filter(
|
|
|
|
|
+ (n) => n.kind === 'class' && n.name === 'APXCharacter'
|
|
|
|
|
+ );
|
|
|
|
|
+ // Exactly one class node, and it's the definition (carries the member).
|
|
|
|
|
+ expect(classes).toHaveLength(1);
|
|
|
|
|
+ expect(classes[0].startLine).toBe(6);
|
|
|
|
|
+ expect(
|
|
|
|
|
+ result.nodes.some((n) => n.kind === 'method' && n.name === 'takeDamage')
|
|
|
|
|
+ ).toBe(true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('elaborated type references in declarations create no phantom class', () => {
|
|
|
|
|
+ // `class Foo obj;` is a variable declaration using an elaborated type, not
|
|
|
|
|
+ // a class definition — it must not mint a `Foo` class node.
|
|
|
|
|
+ const result = extractFromSource('use.cpp', 'class Foo;\nvoid f() { class Foo *p = nullptr; (void)p; }\n');
|
|
|
|
|
+ expect(result.nodes.filter((n) => n.kind === 'class' && n.name === 'Foo')).toHaveLength(0);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('does NOT affect languages where a bodiless class is complete', () => {
|
|
|
|
|
+ // Kotlin `class Empty` and Scala `trait`/`case object`/`class` with no body
|
|
|
|
|
+ // are complete definitions — the C/C++-only skip must leave them indexed.
|
|
|
|
|
+ const kt = extractFromSource('Empty.kt', 'class Empty\nclass Full { val x = 1 }\n');
|
|
|
|
|
+ const ktClasses = kt.nodes.filter((n) => n.kind === 'class').map((n) => n.name);
|
|
|
|
|
+ expect(ktClasses).toContain('Empty');
|
|
|
|
|
+ expect(ktClasses).toContain('Full');
|
|
|
|
|
+
|
|
|
|
|
+ const scala = extractFromSource('M.scala', 'trait Marker\ncase object Red\nclass Foo\n');
|
|
|
|
|
+ const scalaNames = scala.nodes
|
|
|
|
|
+ .filter((n) => ['class', 'trait', 'interface'].includes(n.kind))
|
|
|
|
|
+ .map((n) => n.name);
|
|
|
|
|
+ expect(scalaNames).toEqual(expect.arrayContaining(['Marker', 'Red', 'Foo']));
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
describe('C++ templated base-class inheritance (#1043)', () => {
|
|
describe('C++ templated base-class inheritance (#1043)', () => {
|
|
|
// Inheriting from a template (`class D : public Base<int>`) recorded the base
|
|
// Inheriting from a template (`class D : public Base<int>`) recorded the base
|
|
|
// ref as the full instantiation `Base<int>`, which never name-matched the
|
|
// ref as the full instantiation `Base<int>`, which never name-matched the
|