fix: address code review findings

P0: strip inner-class suffix from namespace before path resolution
P1: support Dao/DAO/Repository.java naming in addition to Mapper.java
Tests: CDATA extraction, XML comment exclusion, namespace fallback
This commit is contained in:
wuhongteng 2026-05-15 17:33:40 +08:00
parent e5d4f10a51
commit 898d46af9a
3 changed files with 68 additions and 2 deletions

View file

@ -125,7 +125,11 @@ function parseMybatisXml(content: string): { namespace: string; statements: Myba
*/
function namespaceToFilePath(namespace: string, allPaths: string[]): string | null {
// com.example.foo.XxxMapper → com/example/foo/XxxMapper.java
const rel = namespace.replace(/\./g, '/') + '.java';
// Strip inner-class suffix (e.g. "Outer$Inner" → use "Outer.java").
const outerNamespace = namespace.includes('$')
? namespace.slice(0, namespace.indexOf('$'))
: namespace;
const rel = outerNamespace.replace(/\./g, '/') + '.java';
const found = allPaths.find((p) => p.replace(/\\/g, '/').endsWith(rel));
return found ?? null;
}
@ -205,7 +209,8 @@ function buildMapperMethodIndex(graph: KnowledgeGraph): {
graph.forEachNode((node) => {
if (!node.id.startsWith('Method:')) return;
const filePath = node.properties.filePath as string | undefined;
if (!filePath || !filePath.endsWith('Mapper.java')) return;
// Match common Java DAO/Mapper interface naming conventions.
if (!filePath || !/(?:Mapper|Dao|DAO|Repository)\.java$/.test(filePath)) return;
filesWithMethods.add(filePath);
// ID format: "Method:<filePath>:<ClassName>.<methodName>#<paramCount>"
// Strip the #N suffix to get a param-count-agnostic key.

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.CdataMapper">
<!-- CDATA-wrapped SQL with XML special chars -->
<select id="selectComplex" resultType="map">
<![CDATA[
SELECT id, name FROM cdata_table
WHERE amount > 100 AND status != 0
]]>
</select>
<!-- XML comment containing SQL keywords should not be extracted -->
<!-- FROM ignored_in_comment WHERE 1=1 -->
<select id="selectSimple" resultType="map">
SELECT id FROM cdata_table WHERE id = #{id}
</select>
</mapper>

View file

@ -141,4 +141,45 @@ describe('ORM dataflow detection', () => {
console.log('[info] MyBatis edges linked at File level (no Java parser in fixture)');
}
});
it('extracts table names from CDATA-wrapped SQL', () => {
const cdataEdges: string[] = [];
for (const rel of result.graph.iterRelationships()) {
if (rel.type === 'QUERIES' && rel.reason?.startsWith('mybatis-')) {
const target = result.graph.getNode(rel.targetId);
if (target?.properties.name === 'cdata_table') {
cdataEdges.push(rel.reason ?? '');
}
}
}
expect(cdataEdges.length).toBeGreaterThan(0);
// XML comment inside <!-- ... --> should NOT produce edges for ignored_in_comment
const commentTable = [];
for (const rel of result.graph.iterRelationships()) {
if (rel.type === 'QUERIES') {
const target = result.graph.getNode(rel.targetId);
if (target?.properties.name === 'ignored_in_comment') {
commentTable.push(target.properties.name);
}
}
}
expect(commentTable).toHaveLength(0);
});
it('falls back to xml file path when namespace cannot be resolved', () => {
// CdataMapper.xml uses namespace "com.example.mapper.CdataMapper"
// but there is no corresponding Java file in the fixture → fallback to xml path
let xmlPathEdgeFound = false;
for (const rel of result.graph.iterRelationships()) {
if (rel.type === 'QUERIES' && rel.reason?.startsWith('mybatis-')) {
const source = result.graph.getNode(rel.sourceId);
const target = result.graph.getNode(rel.targetId);
if (source && target?.properties.name === 'cdata_table') {
// Source should be either a File or Method node — both are acceptable
xmlPathEdgeFound = true;
}
}
}
expect(xmlPathEdgeFound).toBe(true);
});
});