]> Repositorios git - classgraph.git/commitdiff
Mark top-level classes (no subclasses) and render them at the top
authorJavier Sagredo <[email protected]>
Sun, 3 May 2026 23:54:00 +0000 (01:54 +0200)
committerJavier Sagredo <[email protected]>
Mon, 4 May 2026 00:02:04 +0000 (02:02 +0200)
data/viewer.css
data/viewer.html
data/viewer.js
src/Classgraph/Render.hs

index 005ce9ba779a20861c7525ab092ff411ff7d0787..62212bbeb9ed2c9f8e139999c37ce1e99ba6cee6 100644 (file)
@@ -136,6 +136,7 @@ body { display: flex; }
   white-space: nowrap;
 }
 .swatch-class    { background: #3b82f6; color: #fff;     border-color: #1d4ed8; }
+.swatch-toplevel { border-color: #f59e0b; border-width: 2px; }
 .swatch-family   { background: #fbbf24; color: #3f2d05; border-color: #b45309; }
 .swatch-instance { background: #ecfdf5; color: #065f46; border-color: #10b981; }
 .swatch-faminst  { background: #f5f3ff; color: #5b21b6; border-color: #a78bfa; font-style: italic; }
index df2ead4064bb80157de4a3f1051a792f51e8a012..09aad9d04576f6239a81dc869b445d7ae1c72df0 100644 (file)
@@ -29,6 +29,7 @@
         <div class="help-content">
           <h4>Nodes</h4>
           <div class="legend-row"><span class="swatch"><span class="swatch-node swatch-class">Cls</span></span><span>Class defined in this program</span></div>
+          <div class="legend-row"><span class="swatch"><span class="swatch-node swatch-class swatch-toplevel">Cls</span></span><span><strong>Top-level</strong> class (no other class extends it; rendered at the top)</span></div>
           <div class="legend-row"><span class="swatch"><span class="swatch-node swatch-family">Fam</span></span><span>Type family (open / closed / associated)</span></div>
           <div class="legend-row"><span class="swatch"><span class="swatch-node swatch-instance">Inst</span></span><span>Class instance</span></div>
           <div class="legend-row"><span class="swatch"><span class="swatch-node swatch-faminst">f a=b</span></span><span><code>type instance F … = …</code> row</span></div>
index 0eb256f22c8ccc9f21c90a2411d55bad24af12f1..ad5868bd13afcb5b40926b21bf11a1d480f7e1e9 100644 (file)
@@ -93,7 +93,7 @@
     state.classId = null;
     state.familyId = null;
     loadGraph(buildClassesView());
-    cy.layout({ name: 'dagre', rankDir: 'BT', nodeSep: 50, rankSep: 90 }).run();
+    cy.layout({ name: 'dagre', rankDir: 'TB', nodeSep: 50, rankSep: 90 }).run();
     setTopbar('classgraph', '');
     setHint('classes');
     setBackVisible(false);
   function relayoutClassesView() {
     if (state.view !== 'classes') return;
     loadGraph(buildClassesView());
-    cy.layout({ name: 'dagre', rankDir: 'BT', nodeSep: 50, rankSep: 90 }).run();
+    cy.layout({ name: 'dagre', rankDir: 'TB', nodeSep: 50, rankSep: 90 }).run();
     setCounts(focusCountsLine());
   }
 
           'font-size': 12,
           'font-weight': 600,
           width: 'label',
-          height: 28,
+          height: 'label',
           padding: '8px',
           shape: 'round-rectangle',
           'border-width': 1,
           'border-color': '#1d4ed8',
+          'text-wrap': 'wrap',
+          'text-justification': 'center',
+          'line-height': 1.2,
           'transition-property': 'opacity, border-width, border-color',
           'transition-duration': '120ms',
         },
       },
+      // Top-level class — no other class has it as a (direct) superclass.
+      // Visually marked with a gold border so they pop against ordinary
+      // classes; their label also carries an extra "★ top" line baked in
+      // by Render.hs.
+      {
+        selector: 'node[kind = "class"][?isTopLevel]',
+        style: {
+          'border-color': '#f59e0b',
+          'border-width': 2.5,
+        },
+      },
       // Type family node (classes view)
       {
         selector: 'node[kind = "family"]',
index 4d5901cd91383cb26632a4c25f0c8913113cbaea..35c83a7bb4b76eb6ec99bf4a6c55d1a940934be8 100644 (file)
@@ -89,19 +89,35 @@ buildGraph pd = CyGraph
   , cyMeta     = Aeson.toJSON pd
   }
   where
+    -- "Top-level" = no other class has this class as a (direct) superclass.
+    -- Computed once over the merged ProgramData; used to mark such classes
+    -- visually so the user can find the entry-point classes at a glance.
+    referencedAsSuper :: Set.Set Text
+    referencedAsSuper = Set.fromList
+      [ renderQualName (seSuperclass se)
+      | c  <- pdClasses pd
+      , se <- ciSuperclasses c
+      ]
+    isTopLevel c = not (Set.member (renderQualName (ciName c)) referencedAsSuper)
+
     classNodes =
       [ CyNode $ Aeson.object
           [ "id"      Aeson..= renderQualName (ciName c)
-          , "label"   Aeson..= qnName (ciName c)
+          , "label"   Aeson..= classNodeLabel c
           , "kind"    Aeson..= ("class" :: Text)
           , "module"  Aeson..= qnModule (ciName c)
           , "package" Aeson..= qnPackage (ciName c)
           , "tyvars"  Aeson..= ciTyVars c
           , "methods" Aeson..= ciMethods c
+          , "isTopLevel" Aeson..= isTopLevel c
           ]
       | c <- pdClasses pd
       ]
 
+    classNodeLabel c
+      | isTopLevel c = qnName (ciName c) <> "\n\9733 top"  -- ★
+      | otherwise    = qnName (ciName c)
+
     familyNodes =
       [ CyNode $ Aeson.object
           [ "id"      Aeson..= renderQualName (tfName f)