Skip to content
Open
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
22 changes: 12 additions & 10 deletions cmd/build_leyline_coverage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import (
// seedCoverageFixture builds a synthetic leyline-parse db containing _ast
// rows ONLY for Go, plus a source dir holding both a .go and a .sql file —
// the exact hollow-projection shape the review of mache-73b885 reproduced
// live (leyline v0.7.5 has no sql grammar, so sql schema dirs project
// live (as of ley-line-open v0.8.0 only CUE lacks a grammar; sql/java/etc.
// all parse now, so cue is the sole hollow-projection language)
// empty with zero warnings).
func seedCoverageFixture(t *testing.T) (*sql.DB, string) {
t.Helper()
Expand Down Expand Up @@ -107,28 +108,29 @@ func TestRunBuildViaLeylineSchema_UnparseableLanguageErrors(t *testing.T) {
defer saved.restore()

src := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(src, "util.sql"),
[]byte("CREATE TABLE users (id INTEGER PRIMARY KEY);\n"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(src, "conf.cue"),
[]byte("package conf\n\nx: 1\n"), 0o644))
output := filepath.Join(t.TempDir(), "out.db")

// The ORIGINAL #524 repro: the sql PRESET ref. Preset schemas carry
// no Language hints, so this exercises the preset-derived language
// stamp, not the hint collector.
schemaPath = "sql"
// cue is the SOLE language ley-line-open v0.8.0 has no grammar for
// (no tree-sitter-0.26 cue grammar exists) — the last hollow-projection
// case after sql/java/etc. gained grammars. Preset ref (no Language
// hints) exercises the preset-derived language stamp.
schemaPath = "cue"
err := runBuildViaLeyline(src, output, true /* explicit backend */)
require.Error(t, err, "preset-ref hollow projection must not build silently on the explicit backend")
assert.Contains(t, err.Error(), "sql", "error must name the unparseable language")
assert.Contains(t, err.Error(), "cue", "error must name the unparseable language")
assert.Contains(t, err.Error(), "--backend=tree-sitter", "error must point at the working escape hatch")

// Same via an explicit Language-hinted schema FILE (the hint collector).
work := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(work, "schema.json"),
[]byte(`{"version":"v1alpha1","nodes":[{"name":"tables","selector":"(create_table_statement) @t","language":"sql"}]}`), 0o644))
[]byte(`{"version":"v1alpha1","nodes":[{"name":"fields","selector":"(field) @f","language":"cue"}]}`), 0o644))
t.Chdir(work)
schemaPath = "schema.json"
err = runBuildViaLeyline(src, output, true /* explicit backend */)
require.Error(t, err, "hint-based hollow projection must not build silently on the explicit backend")
assert.Contains(t, err.Error(), "sql")
assert.Contains(t, err.Error(), "cue")

// Auto path: warns and builds (advisory), does not error.
require.NoError(t, runBuildViaLeyline(src, output, false /* auto */),
Expand Down
56 changes: 56 additions & 0 deletions cmd/serve_find_smells_views_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,62 @@ func TestEnsureCanonicalViews_ReferrerFromContainerNodeID(t *testing.T) {
assert.Equal(t, "f.go/call_expression_top", topReferrer, "empty container_node_id falls back to node_id")
}

// TestEnsureCanonicalViews_QualifierFromNodeRefs pins the node_refs.qualifier
// passthrough (ley-line-open v0.8.0, mache-dcb808): the mention arm surfaces
// the ref's qualifier (receiver/selector text) instead of the pre-v0.8.0
// hardcoded ”. Legacy dbs without the column degrade to ”.
func TestEnsureCanonicalViews_QualifierFromNodeRefs(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "qual.db")
db, err := sql.Open("sqlite", dbPath)
require.NoError(t, err)
defer func() { _ = db.Close() }()

_, err = db.Exec(`
CREATE TABLE node_defs (token TEXT, node_id TEXT);
CREATE TABLE node_refs (token TEXT, node_id TEXT, qualifier TEXT);
INSERT INTO node_refs VALUES
('Println', 'f.go/call_0', 'fmt'),
('Method', 'f.go/call_1', 'obj'),
('bareCall','f.go/call_2', NULL);
`)
require.NoError(t, err)

qg := &sqlDBQuerier{db: db}
require.NoError(t, ensureCanonicalViews(qg))

var q string
require.NoError(t, db.QueryRow(`SELECT qualifier FROM v_refs WHERE token='Println'`).Scan(&q))
assert.Equal(t, "fmt", q, "mention arm must surface node_refs.qualifier")
require.NoError(t, db.QueryRow(`SELECT qualifier FROM v_refs WHERE token='Method'`).Scan(&q))
assert.Equal(t, "obj", q)
require.NoError(t, db.QueryRow(`SELECT qualifier FROM v_refs WHERE token='bareCall'`).Scan(&q))
assert.Equal(t, "", q, "NULL qualifier degrades to empty string")
}

// TestEnsureCanonicalViews_QualifierAbsentDegradesToEmpty pins the legacy
// path: a node_refs WITHOUT a qualifier column must still build v_refs with
// an empty qualifier (no error).
func TestEnsureCanonicalViews_QualifierAbsentDegradesToEmpty(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "noqual.db")
db, err := sql.Open("sqlite", dbPath)
require.NoError(t, err)
defer func() { _ = db.Close() }()

_, err = db.Exec(`
CREATE TABLE node_defs (token TEXT, node_id TEXT);
CREATE TABLE node_refs (token TEXT, node_id TEXT);
INSERT INTO node_refs VALUES ('x', 'f.go/call_0');
`)
require.NoError(t, err)

qg := &sqlDBQuerier{db: db}
require.NoError(t, ensureCanonicalViews(qg), "missing qualifier column must not error")

var q string
require.NoError(t, db.QueryRow(`SELECT qualifier FROM v_refs WHERE token='x'`).Scan(&q))
assert.Equal(t, "", q)
}

// TestEnsureCanonicalViews_ReferrerFallsBackWithoutContainer pins the
// legacy/tree-sitter shape: node_refs WITHOUT a container_node_id column →
// referrer_node_id is node_id (the mache-schema caller construct), unchanged.
Expand Down
21 changes: 20 additions & 1 deletion cmd/smell_refs_views.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,21 @@ func ensureCanonicalViews(qg refsQuerier) error {
return fmt.Errorf("probe node_refs.container_node_id: %w", err)
}

// node_refs.qualifier (ley-line-open v0.8.0, bead ley-line-open-4dde42):
// the receiver/selector text of a call site — the bare-token row of a
// dual-emit pair carries its qualifier (the `Println` row carries `fmt`;
// `obj.Method()` carries `obj`; '' for bare calls). Before v0.8.0 the
// mention arm had no qualifier source, so it emitted a constant '' and
// only capnp binding rows carried one. Surfacing it lets package-scoped
// rules GROUP BY structurally (fan_out_skew) and resolve stdlib-vs-local
// callees through _imports (fatal_call rung-1, mache-4dc205). Probe and
// prefer it; degrades to '' on legacy dbs — tree-sitter/mache-schema
// behavior is unchanged.
hasRefsQualifier, err := tableHasColumn(qg, "node_refs", "qualifier")
if err != nil {
return fmt.Errorf("probe node_refs.qualifier: %w", err)
}

defsHashExpr := "NULL"
if hasDefsNodeHash {
defsHashExpr = "node_hash"
Expand Down Expand Up @@ -142,13 +157,17 @@ func ensureCanonicalViews(qg refsQuerier) error {
if hasRefsContainer {
refsReferrerExpr = "COALESCE(NULLIF(container_node_id, ''), node_id)"
}
refsQualExpr := "''"
if hasRefsQualifier {
refsQualExpr = "COALESCE(qualifier, '')"
}
refsBody := `SELECT ` + refsReferrerExpr + ` AS referrer_node_id,
token,
NULL AS target_node_id,
NULL AS ref_uri,
NULL AS ref_line,
'mention' AS fidelity,
'' AS qualifier,
` + refsQualExpr + ` AS qualifier,
` + refsHashExpr + ` AS node_hash
FROM node_refs`
// Binding-fidelity rows come from the per-connection
Expand Down
Loading