Closes three gaps that broke `trace(controller, mapper-xml)` on real Spring +
MyBatis projects:
1. **Field-injected concrete-bean trace.** Java `this.<field>.method()` is
unwrapped at extraction (was surfaced as `this.<field>.method` and dropped
through every name-matcher strategy). The receiver name is then looked up
in the enclosing class's field declarations to get the declared type and
resolve the method on it. Closes the controller→bean hop when the field
name doesn't capitalize to the type (`userbo` → `UserBO`). General Java
fix, not Spring-specific.
2. **MyBatis XML mapper as a first-class language.** New extractor parses
`<mapper namespace="..."><select|insert|update|delete|sql id="X">` and
emits method-shaped nodes qualified as `<namespace>::<id>`, plus
`<include refid="X"/>` references to `<sql>` fragments. Non-mapper XML
(pom, log4j, web.xml) → file node only. A new synthesizer
(`mybatisJavaXmlEdges`) joins Java mapper methods to XML statements by
suffix-matching qualified names. Ambiguous simple-name collisions dropped
for precision.
3. **Spring `@Value`/`@ConfigurationProperties` → application config.**
`application.{yml,yaml,properties}` + profile variants parse on the
framework path; each leaf key becomes a `constant` node qualified by its
dotted path. `@Value("${k}")` / `@Value("${k:default}")` and
`@ConfigurationProperties(prefix="X")` emit binding nodes that resolve
with Spring's relaxed binding (kebab↔camel↔snake).
Validated on macrozheng/mall-tiny: full chain
`UmsRoleController.listResource → UmsRoleService.listResource → impl →
UmsResourceMapper.getResourceListByRoleId → XML <select>` connects across 5
hops via static + synthesized edges. 11/11 @Value annotations resolved
(incl. `@ConfigurationProperties(prefix="secure.ignored")`); 6/6 custom-SQL
mapper methods bridge to XML.
Tests: 4 new integration tests in frameworks-integration.test.ts. Full
suite: 1005 passed.
Docs: CHANGELOG `[Unreleased]` entry + dynamic-dispatch-coverage-playbook
narrative + matrix row.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
55839edd8f
commit
2543ae565a
@@ -23,6 +23,7 @@ import { LiquidExtractor } from './liquid-extractor';
|
||||
import { SvelteExtractor } from './svelte-extractor';
|
||||
import { DfmExtractor } from './dfm-extractor';
|
||||
import { VueExtractor } from './vue-extractor';
|
||||
import { MyBatisExtractor } from './mybatis-extractor';
|
||||
import {
|
||||
getAllFrameworkResolvers,
|
||||
getApplicableFrameworks,
|
||||
@@ -1453,7 +1454,23 @@ export class TreeSitterExtractor {
|
||||
if (nameField && objectField && (node.type === 'method_invocation' || node.type === 'member_call_expression' || node.type === 'scoped_call_expression')) {
|
||||
// Method call with explicit receiver: receiver.method() / $receiver->method() / ClassName::method()
|
||||
const methodName = getNodeText(nameField, this.source);
|
||||
let receiverName = getNodeText(objectField, this.source);
|
||||
// Java `this.userbo.toLogin2()` parses as method_invocation(object=field_access(this, userbo)).
|
||||
// Without unwrapping, receiverName is `this.userbo` and the name-matcher's
|
||||
// single-dot receiver regex fails. Pull out the immediate field after `this.`
|
||||
// so the receiver is the field name (`userbo`), which the resolver can then
|
||||
// look up in the enclosing class's field declarations.
|
||||
let receiverName: string;
|
||||
if (objectField.type === 'field_access') {
|
||||
const inner = getChildByField(objectField, 'object');
|
||||
const fld = getChildByField(objectField, 'field');
|
||||
if (inner && fld && (inner.type === 'this' || inner.type === 'this_expression')) {
|
||||
receiverName = getNodeText(fld, this.source);
|
||||
} else {
|
||||
receiverName = getNodeText(objectField, this.source);
|
||||
}
|
||||
} else {
|
||||
receiverName = getNodeText(objectField, this.source);
|
||||
}
|
||||
// Strip PHP $ prefix from variable names
|
||||
receiverName = receiverName.replace(/^\$/, '');
|
||||
|
||||
@@ -2687,10 +2704,16 @@ export function extractFromSource(
|
||||
// Use custom extractor for Liquid
|
||||
const extractor = new LiquidExtractor(filePath, source);
|
||||
result = extractor.extract();
|
||||
} else if (detectedLanguage === 'yaml' || detectedLanguage === 'twig') {
|
||||
// No symbol extraction — file is tracked at the file-record level only.
|
||||
// Framework extractors (e.g. Drupal routing resolver) run below and may
|
||||
// add route nodes / references for yaml files such as *.routing.yml.
|
||||
} else if (detectedLanguage === 'xml') {
|
||||
// Custom extractor for MyBatis mapper XML. Non-mapper XML returns just a
|
||||
// file node so the watcher tracks it without emitting symbols.
|
||||
const extractor = new MyBatisExtractor(filePath, source);
|
||||
result = extractor.extract();
|
||||
} else if (detectedLanguage === 'yaml' || detectedLanguage === 'twig' || detectedLanguage === 'properties') {
|
||||
// No symbol extraction at this stage — files are tracked at the file-record
|
||||
// level only. Framework extractors (Drupal routing yml, Spring `@Value`
|
||||
// resolution against application.yml/application.properties) run later and
|
||||
// add per-file nodes/references when they apply.
|
||||
result = { nodes: [], edges: [], unresolvedReferences: [], errors: [], durationMs: 0 };
|
||||
} else if (
|
||||
detectedLanguage === 'pascal' &&
|
||||
|
||||
Reference in New Issue
Block a user