Skip to content

Commit a0f599e

Browse files
committed
feat: Add Python class inheritance extraction for superclass relationships
Addresses Python's class definition syntax where parent classes are specified in argument_list nodes (e.g. `class Child(Parent, Mixin):`). Extracts identifier and attribute children from argument_list as 'extends' references to properly model Python's inheritance patterns in the code graph.
1 parent 392c146 commit a0f599e

4 files changed

Lines changed: 104 additions & 0 deletions

File tree

debug_python_ast.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const { getParser, initGrammars, loadAllGrammars } = require('./dist/extraction/grammars');
2+
3+
(async () => {
4+
await initGrammars();
5+
await loadAllGrammars();
6+
7+
const parser = getParser('python');
8+
9+
const code = `class Child(Parent):
10+
pass`;
11+
12+
const tree = parser.parse(code);
13+
14+
function walk(node, depth = 0) {
15+
const indent = ' '.repeat(depth);
16+
const preview = node.text.substring(0, 30).replace(/\n/g, '\\n');
17+
console.log(`${indent}${node.type} [${node.startPosition.row}:${node.startPosition.column}] "${preview}"`);
18+
19+
for (let i = 0; i < node.namedChildCount; i++) {
20+
const child = node.namedChild(i);
21+
if (child) walk(child, depth + 1);
22+
}
23+
}
24+
25+
walk(tree.rootNode);
26+
})();

debug_python_ast2.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const { getParser, initGrammars, loadAllGrammars } = require('./dist/extraction/grammars');
2+
3+
(async () => {
4+
await initGrammars();
5+
await loadAllGrammars();
6+
7+
const parser = getParser('python');
8+
9+
const code = `class Child(Parent, Mixin, Base):
10+
pass`;
11+
12+
const tree = parser.parse(code);
13+
14+
function walk(node, depth = 0) {
15+
const indent = ' '.repeat(depth);
16+
const preview = node.text.substring(0, 40).replace(/\n/g, '\\n');
17+
console.log(`${indent}${node.type} "${preview}"`);
18+
19+
for (let i = 0; i < node.namedChildCount; i++) {
20+
const child = node.namedChild(i);
21+
if (child) walk(child, depth + 1);
22+
}
23+
}
24+
25+
walk(tree.rootNode);
26+
})();

src/extraction/tree-sitter.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,6 +1241,23 @@ export class TreeSitterExtractor {
12411241
}
12421242
}
12431243

1244+
// Python superclass list: `class Flask(Scaffold, Mixin):`
1245+
// argument_list contains identifier children for each parent class
1246+
if (child.type === 'argument_list' && node.type === 'class_definition') {
1247+
for (const arg of child.namedChildren) {
1248+
if (arg.type === 'identifier' || arg.type === 'attribute') {
1249+
const name = getNodeText(arg, this.source);
1250+
this.unresolvedReferences.push({
1251+
fromNodeId: classId,
1252+
referenceName: name,
1253+
referenceKind: 'extends',
1254+
line: arg.startPosition.row + 1,
1255+
column: arg.startPosition.column,
1256+
});
1257+
}
1258+
}
1259+
}
1260+
12441261
// Go interface embedding: `type Querier interface { LabelQuerier; ... }`
12451262
// constraint_elem wraps the embedded interface type identifier
12461263
if (child.type === 'constraint_elem') {

test_python_inheritance.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const { extractFromSource } = require('./dist/extraction');
2+
const { initGrammars, loadAllGrammars } = require('./dist/extraction/grammars');
3+
4+
(async () => {
5+
await initGrammars();
6+
await loadAllGrammars();
7+
8+
const code = `
9+
class Parent:
10+
pass
11+
12+
class Child(Parent):
13+
pass
14+
15+
class Multiple(Parent, Mixin):
16+
pass
17+
`;
18+
19+
const result = extractFromSource('test.py', code);
20+
21+
console.log('=== NODES ===');
22+
result.nodes.forEach(n => {
23+
console.log(`${n.kind}: ${n.name} (line ${n.startLine})`);
24+
});
25+
26+
console.log('\n=== UNRESOLVED REFERENCES ===');
27+
result.unresolvedReferences.forEach(r => {
28+
console.log(`${r.referenceKind}: ${r.referenceName} (from ${r.fromNodeId})`);
29+
});
30+
31+
console.log('\n=== EDGES ===');
32+
result.edges.forEach(e => {
33+
console.log(`${e.kind}: ${e.source} -> ${e.target}`);
34+
});
35+
})();

0 commit comments

Comments
 (0)