import { describe, it, expect } from 'vitest'; import { extractFromSource } from '../src/extraction/tree-sitter'; // Robustness of the MyBatis / iBatis mapper extractor. Four shapes the regex // scanner previously mishandled, all reported and diagnosed by @ESPINS in #1182: // 1. single-quoted attribute values, // 2. tags that live inside XML comments, // 3. iBatis 2 `` files (zero statement coverage before), // 4. two statements that share a qualifiedName *and* a start line colliding // on the node id (silent statement loss at the DB layer). // The quoting and comment suites below follow @ESPINS's fix-mybatis-quotes-comments // branch; the iBatis and collision suites cover the regex-only path taken here // (no parser dependency). const methodNodes = (xml: string, file = 'FooMapper.xml') => extractFromSource(file, xml).nodes.filter((n) => n.kind === 'method'); const methodNames = (xml: string, file = 'FooMapper.xml') => methodNodes(xml, file).map((n) => n.qualifiedName); describe('MyBatis extractor — attribute quoting', () => { it('accepts a single-quoted namespace', () => { const xml = "" + ''; expect(methodNames(xml)).toContain('com.example.FooMapper::getById'); }); it('accepts a single-quoted statement id', () => { const xml = '' + ""; expect(methodNames(xml)).toContain('com.example.FooMapper::getById'); }); it('accepts a single-quoted ', () => { const xml = '' + 'id, name' + "" + ''; const refs = extractFromSource('FooMapper.xml', xml).unresolvedReferences.map( (r) => r.referenceName ); expect(refs).toContain('com.example.FooMapper::cols'); }); it('reads single-quoted resultType / parameterType into the signature', () => { const xml = "" + "" + ''; const sig = methodNodes(xml).find((n) => n.name === 'getById')?.signature; expect(sig).toContain('result=User'); expect(sig).toContain('param=int'); }); it('handles mixed single- and double-quoted attributes in one file', () => { const xml = "" + "" + 'UPDATE t SET x=1' + ''; expect(methodNames(xml)).toEqual([ 'com.example.FooMapper::getById', 'com.example.FooMapper::touch', ]); }); it('still accepts double-quoted attributes (regression guard)', () => { const xml = '' + ''; expect(methodNames(xml)).toContain('com.example.FooMapper::getById'); }); }); describe('MyBatis extractor — XML comments', () => { const result = (xml: string) => extractFromSource('FooMapper.xml', xml); it('does not emit a node for a statement inside a comment', () => { const xml = '' + '' + ''; const names = result(xml) .nodes.filter((n) => n.kind === 'method') .map((n) => n.name); expect(names).toContain('live'); expect(names).not.toContain('dead'); }); it('does not follow an inside a comment', () => { const xml = '' + '' + ''; const refs = result(xml).unresolvedReferences.map((r) => r.referenceName); expect(refs).not.toContain('com.example.FooMapper::cols'); }); it('keeps the correct startLine for a statement after a multi-line comment', () => { const xml = '\n' + '\n' + '\n' + '\n'; const stmt = result(xml).nodes.find((n) => n.name === 'getById'); expect(stmt).toBeDefined(); // The SELECT 1' + ']]>' + ''; const names = result(xml) .nodes.filter((n) => n.kind === 'method') .map((n) => n.name); expect(names).toContain('live'); }); it('does not crash on an unterminated comment (blanks to end of file)', () => { const xml = '' + '' + '