]> Repositorios git - classgraph.git/commitdiff
Let drill-in work on external classes that have local instances
authorJavier Sagredo <[email protected]>
Wed, 6 May 2026 23:59:19 +0000 (01:59 +0200)
committerJavier Sagredo <[email protected]>
Wed, 6 May 2026 23:59:19 +0000 (01:59 +0200)
drillInto used to gate on classById.has(n.id()) and silently fall
through to highlightOnly when the double-clicked class wasn't
locally defined. That's the wrong call when the class is external
but instances of it *are* in our dumps (orphan-style, or because
the package defining the instances was extracted while the package
defining the class wasn't) — there's still a useful instance view
to render.

Two changes:

* drillInto now drills in whenever the class is locally defined OR
  has at least one entry in instancesByClass. When neither is true
  (truly nothing to show), it console.warns with the qid so you can
  diagnose why your double-click did nothing.

* buildInstanceView synthesises a minimal ClassInfo from the first
  matching instance's iiClass when classById misses, so the rest of
  the build pipeline (which needs ciSuperclasses, ciAssocTypes,
  ciTyVars …) keeps working with empty defaults. The focused-class
  node ends up as an external-styled stub but the per-instance
  rendering — contexts, superclass requirements, fam-instances — is
  all there.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
data/viewer.js

index 4208694bd4e04e8d0d9d0d1976177801b1e25a7e..31adcd04d496acb3c3c6cc0517fdcb93d4fc5882 100644 (file)
   }
 
   function buildInstanceView(classId) {
-    const cls = classById.get(classId);
-    if (!cls) return null;
+    let cls = classById.get(classId);
     const all = instancesByClass.get(classId) || [];
+    if (!cls) {
+      // External class — its defining package wasn't extracted, but
+      // we may still have instances of it (orphan-style, or because
+      // the package that *defines instances* was extracted while the
+      // package that *defines the class* wasn't). Synthesise a
+      // minimal ClassInfo from the first instance's iiClass so the
+      // rest of the code path keeps working. Superclasses/methods/
+      // associated families are unknown; we leave them empty.
+      if (all.length === 0) return null;
+      const first = all[0];
+      cls = {
+        ciName: first.iiClass,
+        ciTyVars: [],
+        ciSuperclasses: [],
+        ciAssocTypes: [],
+        ciMethods: [],
+        ciSrc: null,
+        ciDoc: null,
+        _externalShim: true,
+      };
+    }
     const hidden = getHidden(classId);
     const insts = all.filter(i => !hidden.has(i._idx));
 
       pinClass(n.id());
       return;
     }
-    // Class node → drill into its instance view.
-    if (data.kind === 'class' && !data.focused && classById.has(n.id())) {
-      switchToInstance(n.id());
-      return;
+    // Class node → drill into its instance view. We *don't* gate on
+    // classById.has(n.id()) here — buildInstanceView falls back to a
+    // synthesised ClassInfo when the class is external but local
+    // instances exist, so external classes that only show up via
+    // someone else's superclass theta still drill in usefully.
+    if (data.kind === 'class' && !data.focused) {
+      const haveLocalDef  = classById.has(n.id());
+      const localInstCount = (instancesByClass.get(n.id()) || []).length;
+      if (haveLocalDef || localInstCount > 0) {
+        switchToInstance(n.id());
+        return;
+      }
+      // No instances and no local definition either — nothing to show.
+      // Warn so the user can diagnose why their double-click did
+      // nothing instead of being silently confused.
+      console.warn('classgraph: nothing to drill into for', n.id(),
+        '— class is external and no instances of it appear in the dumps.');
     }
     // Family node → drill into its family view.
     if (data.kind === 'family' && !data.focused && familyById.has(n.id())) {