Skip to content

Commit e0b036f

Browse files
fix: filter trimmed module references (#18)
* fix: filter trimmed module references * chore: gofmt engine after validation
1 parent bb4149a commit e0b036f

2 files changed

Lines changed: 130 additions & 5 deletions

File tree

internal/engine/engine.go

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ var purposeMap = map[string]string{
5454
"lib": "Shared library code",
5555
"utils": "Utility functions",
5656
"services": "Business logic services",
57-
"middleware": "HTTP middleware",
57+
"middleware": "HTTP middleware",
5858
"cli": "Command-line interface",
5959
"schema": "Data schema definitions",
6060
"renderer": "Output renderers",
@@ -108,6 +108,46 @@ func generateAddFeatureHint(modules map[string]schema.ModuleInfo, entrypoints []
108108
return ""
109109
}
110110

111+
func filterRetainedModules(names []string, retained map[string]bool) []string {
112+
if len(names) == 0 {
113+
return nil
114+
}
115+
out := make([]string, 0, len(names))
116+
for _, name := range names {
117+
if retained[name] {
118+
out = append(out, name)
119+
}
120+
}
121+
return out
122+
}
123+
124+
func rankMostDepended(modules map[string]schema.ModuleInfo) []string {
125+
names := make([]string, 0, len(modules))
126+
for name := range modules {
127+
names = append(names, name)
128+
}
129+
sort.SliceStable(names, func(i, j int) bool {
130+
ci := len(modules[names[i]].DependedBy)
131+
cj := len(modules[names[j]].DependedBy)
132+
if ci != cj {
133+
return ci > cj
134+
}
135+
return names[i] < names[j]
136+
})
137+
return names
138+
}
139+
140+
func findIsolatedModules(modules map[string]schema.ModuleInfo) []string {
141+
var isolated []string
142+
for name, mod := range modules {
143+
if len(mod.DependsOn) == 0 && len(mod.DependedBy) == 0 {
144+
isolated = append(isolated, name)
145+
}
146+
}
147+
sort.Strings(isolated)
148+
return isolated
149+
}
150+
111151
// detectDoNotTouch returns paths that should generally not be modified by hand.
112152
func detectDoNotTouch(root string) []string {
113153
candidates := []string{"migrations", "vendor", "generated", "proto", ".github"}
@@ -439,6 +479,14 @@ func assembleIndex(
439479
allMods = allMods[:maxModules]
440480
}
441481

482+
retainedModules := make(map[string]bool, len(allMods))
483+
for _, mod := range allMods {
484+
if strings.HasPrefix(mod.Name, "testdata") {
485+
continue
486+
}
487+
retainedModules[mod.Name] = true
488+
}
489+
442490
modules := map[string]schema.ModuleInfo{}
443491
for _, mod := range allMods {
444492
if strings.HasPrefix(mod.Name, "testdata") {
@@ -491,8 +539,8 @@ func assembleIndex(
491539
FileList: fileList,
492540
Exports: exports,
493541
TypeDefs: typeDefs,
494-
DependsOn: mod.DependsOn,
495-
DependedBy: mod.DependedBy,
542+
DependsOn: filterRetainedModules(mod.DependsOn, retainedModules),
543+
DependedBy: filterRetainedModules(mod.DependedBy, retainedModules),
496544
Activity: activityLevel,
497545
}
498546
}
@@ -504,14 +552,17 @@ func assembleIndex(
504552
if strings.HasPrefix(e.From, "testdata") || strings.HasPrefix(e.To, "testdata") {
505553
continue
506554
}
555+
if !retainedModules[e.From] || !retainedModules[e.To] {
556+
continue
557+
}
507558
schemaEdges = append(schemaEdges, [2]string{e.From, e.To})
508559
}
509-
mostDepended := g.MostDepended()
560+
mostDepended := rankMostDepended(modules)
510561
// Cap most-depended list.
511562
if len(mostDepended) > 10 {
512563
mostDepended = mostDepended[:10]
513564
}
514-
isolated := g.Isolated()
565+
isolated := findIsolatedModules(modules)
515566

516567
// --- Git ---
517568
hotFiles := make([]schema.HotFile, len(activity.HotFiles))

internal/engine/engine_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package engine
2+
3+
import (
4+
"testing"
5+
6+
"github.com/glincker/stacklit/internal/config"
7+
"github.com/glincker/stacklit/internal/git"
8+
"github.com/glincker/stacklit/internal/graph"
9+
"github.com/glincker/stacklit/internal/monorepo"
10+
"github.com/glincker/stacklit/internal/parser"
11+
)
12+
13+
func TestAssembleIndexFiltersTrimmedModuleReferences(t *testing.T) {
14+
files := []*parser.FileInfo{
15+
{Path: "src/api/index.ts", Language: "TypeScript", Imports: []string{"src/auth", "src/db"}, LineCount: 50},
16+
{Path: "src/auth/service.ts", Language: "TypeScript", Imports: []string{"src/db"}, LineCount: 40},
17+
{Path: "src/db/pool.ts", Language: "TypeScript", Exports: []string{"Pool"}, LineCount: 30},
18+
}
19+
20+
g := graph.Build(files, graph.BuildOptions{MaxDepth: 4})
21+
cfg := config.DefaultConfig()
22+
cfg.MaxModules = 2
23+
24+
idx := assembleIndex(
25+
".",
26+
&monorepo.Result{Type: "single"},
27+
[]string{"src/api/index.ts", "src/auth/service.ts", "src/db/pool.ts"},
28+
files,
29+
g,
30+
&git.Activity{},
31+
map[string][]byte{},
32+
cfg,
33+
)
34+
35+
if len(idx.Modules) != 2 {
36+
t.Fatalf("expected 2 retained modules, got %d: %v", len(idx.Modules), idx.Modules)
37+
}
38+
if _, ok := idx.Modules["src/db"]; ok {
39+
t.Fatalf("expected trimmed module src/db to be omitted from modules, got %v", idx.Modules)
40+
}
41+
42+
api := idx.Modules["src/api"]
43+
if len(api.DependsOn) != 1 || api.DependsOn[0] != "src/auth" {
44+
t.Fatalf("expected src/api depends_on to retain only src/auth, got %v", api.DependsOn)
45+
}
46+
47+
auth := idx.Modules["src/auth"]
48+
if len(auth.DependsOn) != 0 {
49+
t.Fatalf("expected src/auth depends_on to drop trimmed src/db reference, got %v", auth.DependsOn)
50+
}
51+
if len(auth.DependedBy) != 1 || auth.DependedBy[0] != "src/api" {
52+
t.Fatalf("expected src/auth depended_by to contain only src/api, got %v", auth.DependedBy)
53+
}
54+
55+
if len(idx.Dependencies.Edges) != 1 || idx.Dependencies.Edges[0] != ([2]string{"src/api", "src/auth"}) {
56+
t.Fatalf("expected only retained edge src/api -> src/auth, got %v", idx.Dependencies.Edges)
57+
}
58+
59+
if len(idx.Dependencies.MostDepended) < 2 {
60+
t.Fatalf("expected ranked retained modules, got %v", idx.Dependencies.MostDepended)
61+
}
62+
if idx.Dependencies.MostDepended[0] != "src/auth" {
63+
t.Fatalf("expected src/auth to be most depended after trimming, got %v", idx.Dependencies.MostDepended)
64+
}
65+
for _, name := range idx.Dependencies.MostDepended {
66+
if name == "src/db" {
67+
t.Fatalf("expected trimmed module src/db to be absent from most_depended, got %v", idx.Dependencies.MostDepended)
68+
}
69+
}
70+
71+
if len(idx.Dependencies.Isolated) != 0 {
72+
t.Fatalf("expected no isolated retained modules, got %v", idx.Dependencies.Isolated)
73+
}
74+
}

0 commit comments

Comments
 (0)