mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-29 06:32:28 +00:00
fix(ingestion): skip File->Member DEFINES edges for class members (#1949)
* fix(ingestion): skip File->Member DEFINES edges for class members Previously every symbol — including Methods and Properties that belong to a class — received a direct File->Symbol DEFINES edge. This caused the radial-layout view to show File linking directly to all Properties and Methods, bypassing their enclosing class node and flattening the OOP hierarchy. Fix: gate the DEFINES emission on the symbol having no owner. Class members are already reachable through the File->Class DEFINES edge and the Class->Member HAS_METHOD / HAS_PROPERTY edges, so the redundant direct edge is unnecessary and misleading in the graph. The same guard is applied in all four emission sites: - parsing-processor.ts (sequential fallback path) - parse-worker.ts (main symbol loop + routed-property branch) - call-processor.ts (routed-property branch) Pipeline-graph golden updated: DEFINES 21→20, relationships 74→73. Closes #1944 Co-authored-by: Claude <noreply@anthropic.com> AI-model: claude-sonnet-4-6 * fix(wiki): extend getFilesWithExports to include exported class members The PR that removed File→Member DEFINES edges left getFilesWithExports() under-reporting: its one-hop MATCH only reaches top-level symbols. Add a UNION leg that follows File→DEFINES→Class→HAS_METHOD/HAS_PROPERTY→Member so exported class methods and properties appear in wiki/cluster export summaries again. --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
This commit is contained in:
parent
60355a0370
commit
a5a890439f
5 changed files with 71 additions and 48 deletions
|
|
@ -1036,15 +1036,18 @@ export const processCalls = async (
|
|||
? { declaredType: routedFieldInfo.type }
|
||||
: {}),
|
||||
});
|
||||
const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
|
||||
graph.addRelationship({
|
||||
id: relId,
|
||||
sourceId: fileId,
|
||||
targetId: nodeId,
|
||||
type: 'DEFINES',
|
||||
confidence: 1.0,
|
||||
reason: '',
|
||||
});
|
||||
// Only emit File -> Property DEFINES for top-level properties (issue #1944).
|
||||
if (!propEnclosingClassId) {
|
||||
const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
|
||||
graph.addRelationship({
|
||||
id: relId,
|
||||
sourceId: fileId,
|
||||
targetId: nodeId,
|
||||
type: 'DEFINES',
|
||||
confidence: 1.0,
|
||||
reason: '',
|
||||
});
|
||||
}
|
||||
if (propEnclosingClassId) {
|
||||
graph.addRelationship({
|
||||
id: generateId('HAS_PROPERTY', `${propEnclosingClassId}->${nodeId}`),
|
||||
|
|
|
|||
|
|
@ -879,23 +879,28 @@ const processParsingSequential = async (
|
|||
qualifiedName: qualifiedTypeName,
|
||||
});
|
||||
|
||||
const fileId = generateId('File', file.path);
|
||||
|
||||
const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
|
||||
|
||||
const relationship: GraphRelationship = {
|
||||
id: relId,
|
||||
sourceId: fileId,
|
||||
targetId: nodeId,
|
||||
type: 'DEFINES',
|
||||
confidence: 1.0,
|
||||
reason: '',
|
||||
};
|
||||
|
||||
graph.addRelationship(relationship);
|
||||
|
||||
// ── HAS_METHOD / HAS_PROPERTY: link member to enclosing class ──
|
||||
const ownerIdForMemberEdge = enclosingClassId ?? objectLiteralOwnerInfo?.ownerId ?? null;
|
||||
|
||||
// Only emit File -> Symbol DEFINES for top-level symbols. Class members
|
||||
// are reachable via Class -> Member (HAS_METHOD / HAS_PROPERTY), so a
|
||||
// direct File -> Member edge would bypass the class in the graph and
|
||||
// produce a flat radial layout instead of the correct File->Class->Member
|
||||
// hierarchy (issue #1944).
|
||||
if (!ownerIdForMemberEdge) {
|
||||
const fileId = generateId('File', file.path);
|
||||
const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
|
||||
const relationship: GraphRelationship = {
|
||||
id: relId,
|
||||
sourceId: fileId,
|
||||
targetId: nodeId,
|
||||
type: 'DEFINES',
|
||||
confidence: 1.0,
|
||||
reason: '',
|
||||
};
|
||||
graph.addRelationship(relationship);
|
||||
}
|
||||
|
||||
if (ownerIdForMemberEdge) {
|
||||
const memberEdgeType = nodeLabel === 'Property' ? 'HAS_PROPERTY' : 'HAS_METHOD';
|
||||
graph.addRelationship({
|
||||
|
|
|
|||
|
|
@ -1590,16 +1590,20 @@ const processFileGroup = (
|
|||
? { isReadonly: routedFieldInfo.isReadonly }
|
||||
: {}),
|
||||
});
|
||||
const fileId = generateId('File', file.path);
|
||||
const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
|
||||
result.relationships.push({
|
||||
id: relId,
|
||||
sourceId: fileId,
|
||||
targetId: nodeId,
|
||||
type: 'DEFINES',
|
||||
confidence: 1.0,
|
||||
reason: '',
|
||||
});
|
||||
// Only emit File -> Property DEFINES for top-level properties
|
||||
// (issue #1944); class members are reached via HAS_PROPERTY.
|
||||
if (!propEnclosingClassId) {
|
||||
const fileId = generateId('File', file.path);
|
||||
const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
|
||||
result.relationships.push({
|
||||
id: relId,
|
||||
sourceId: fileId,
|
||||
targetId: nodeId,
|
||||
type: 'DEFINES',
|
||||
confidence: 1.0,
|
||||
reason: '',
|
||||
});
|
||||
}
|
||||
if (propEnclosingClassId) {
|
||||
result.relationships.push({
|
||||
id: generateId('HAS_PROPERTY', `${propEnclosingClassId}->${nodeId}`),
|
||||
|
|
@ -2094,16 +2098,19 @@ const processFileGroup = (
|
|||
: {}),
|
||||
});
|
||||
|
||||
const fileId = generateId('File', file.path);
|
||||
const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
|
||||
result.relationships.push({
|
||||
id: relId,
|
||||
sourceId: fileId,
|
||||
targetId: nodeId,
|
||||
type: 'DEFINES',
|
||||
confidence: 1.0,
|
||||
reason: '',
|
||||
});
|
||||
// Only emit File -> Symbol DEFINES for top-level symbols (issue #1944).
|
||||
if (ownerId === undefined) {
|
||||
const fileId = generateId('File', file.path);
|
||||
const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
|
||||
result.relationships.push({
|
||||
id: relId,
|
||||
sourceId: fileId,
|
||||
targetId: nodeId,
|
||||
type: 'DEFINES',
|
||||
confidence: 1.0,
|
||||
reason: '',
|
||||
});
|
||||
}
|
||||
|
||||
// ── HAS_METHOD / HAS_PROPERTY: link member to enclosing class ──
|
||||
if (ownerId !== undefined) {
|
||||
|
|
|
|||
|
|
@ -57,6 +57,9 @@ export async function closeWikiDb(): Promise<void> {
|
|||
|
||||
/**
|
||||
* Get all source files with their exported symbol names and types.
|
||||
* Includes top-level exports (File→DEFINES→n) and exported class members
|
||||
* (File→DEFINES→Class→HAS_METHOD/HAS_PROPERTY→n) since class members no
|
||||
* longer have a direct File→DEFINES edge.
|
||||
*/
|
||||
export async function getFilesWithExports(): Promise<FileWithExports[]> {
|
||||
const rows = await executeQuery(
|
||||
|
|
@ -65,7 +68,12 @@ export async function getFilesWithExports(): Promise<FileWithExports[]> {
|
|||
MATCH (f:File)-[:CodeRelation {type: 'DEFINES'}]->(n)
|
||||
WHERE n.isExported = true
|
||||
RETURN f.filePath AS filePath, n.name AS name, labels(n)[0] AS type
|
||||
ORDER BY f.filePath
|
||||
UNION
|
||||
MATCH (f:File)-[:CodeRelation {type: 'DEFINES'}]->(c)
|
||||
-[mr:CodeRelation]->(n)
|
||||
WHERE mr.type IN ['HAS_METHOD', 'HAS_PROPERTY'] AND n.isExported = true
|
||||
RETURN f.filePath AS filePath, n.name AS name, labels(n)[0] AS type
|
||||
ORDER BY filePath
|
||||
`,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"fixture": "mini-repo",
|
||||
"totalFileCount": 7,
|
||||
"symbols": 37,
|
||||
"relationships": 74,
|
||||
"relationships": 73,
|
||||
"processes": 4,
|
||||
"byType": {
|
||||
"Class": 1,
|
||||
|
|
@ -19,11 +19,11 @@
|
|||
"byRelType": {
|
||||
"CALLS": 9,
|
||||
"CONTAINS": 7,
|
||||
"DEFINES": 21,
|
||||
"DEFINES": 20,
|
||||
"HAS_METHOD": 1,
|
||||
"IMPORTS": 12,
|
||||
"MEMBER_OF": 12,
|
||||
"STEP_IN_PROCESS": 12
|
||||
},
|
||||
"edgeDigest": "6f414427a20c037df3e336f055c83f987e7d381c9bfa73b4d2be690cb8103302"
|
||||
"edgeDigest": "196198eb9a900721aec892400d141189aa1e874722917843c7f1206805d817f1"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue