Skip to content

Commit c91d57d

Browse files
aksOpsclaude
andcommitted
fix: Fastify false positive + add E2E ground truth files
- Add framework-specific guard to FastifyRouteDetector (requires 'fastify' import/require before detecting routes, same pattern as Quarkus/Micronaut fix) - Add E2E ground truth JSON files for spring-petclinic and realworld-express (sourced from Context7 and RealWorld API spec) E2E results: petclinic 100% (65/65), realworld-express 96% (24/25) Tests: 1310 pass, 0 failures Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent bcc3df2 commit c91d57d

5 files changed

Lines changed: 115 additions & 2 deletions

File tree

src/main/java/io/github/randomcodespace/iq/detector/typescript/FastifyRouteDetector.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@
3232
@Component
3333
public class FastifyRouteDetector extends AbstractAntlrDetector {
3434

35+
/**
36+
* Guard pattern: file must contain a Fastify import/require to be considered.
37+
* Matches: import fastify from 'fastify', import Fastify from 'fastify',
38+
* require('fastify'), import { ... } from 'fastify', etc.
39+
*/
40+
private static final Pattern FASTIFY_IMPORT_PATTERN = Pattern.compile(
41+
"(?:import\\s+.*?from\\s+['\"]fastify['\"]|require\\s*\\(\\s*['\"]fastify['\"]\\s*\\))",
42+
Pattern.DOTALL
43+
);
44+
3545
private static final Pattern SHORTHAND_PATTERN = Pattern.compile(
3646
"(\\w+)\\.(get|post|put|delete|patch)\\(\\s*['\"`]([^'\"`]+)['\"`]"
3747
);
@@ -72,9 +82,16 @@ public DetectorResult detect(DetectorContext ctx) {
7282

7383
@Override
7484
protected DetectorResult detectWithRegex(DetectorContext ctx) {
85+
String text = ctx.content();
86+
87+
// Guard: only proceed if the file imports/requires 'fastify'.
88+
// Without this, generic patterns like router.get() match Express and other frameworks.
89+
if (!FASTIFY_IMPORT_PATTERN.matcher(text).find()) {
90+
return DetectorResult.empty();
91+
}
92+
7593
List<CodeNode> nodes = new ArrayList<>();
7694
List<CodeEdge> edges = new ArrayList<>();
77-
String text = ctx.content();
7895
String filePath = ctx.filePath();
7996
String moduleName = ctx.moduleName();
8097
Set<String> seenIds = new HashSet<>();

src/test/java/io/github/randomcodespace/iq/detector/typescript/FastifyRouteDetectorTest.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ class FastifyRouteDetectorTest {
1515
@Test
1616
void detectsShorthandRoutes() {
1717
String code = """
18+
import Fastify from 'fastify';
19+
const fastify = Fastify();
1820
fastify.get('/api/users', async (request, reply) => {});
1921
fastify.post('/api/users', async (request, reply) => {});
2022
""";
@@ -30,6 +32,7 @@ void detectsShorthandRoutes() {
3032
@Test
3133
void detectsHooks() {
3234
String code = """
35+
import Fastify from 'fastify';
3336
fastify.addHook('onRequest', async (request, reply) => {});
3437
""";
3538
DetectorContext ctx = DetectorTestUtils.contextFor("src/app.ts", "typescript", code);
@@ -49,9 +52,38 @@ void noMatchOnNonFastifyCode() {
4952
assertTrue(result.edges().isEmpty());
5053
}
5154

55+
@Test
56+
void noMatchOnExpressCode() {
57+
String code = """
58+
const express = require('express');
59+
const router = express.Router();
60+
router.get('/api/users', (req, res) => {});
61+
router.post('/api/users', (req, res) => {});
62+
""";
63+
DetectorContext ctx = DetectorTestUtils.contextFor("src/routes.js", "javascript", code);
64+
DetectorResult result = detector.detect(ctx);
65+
assertTrue(result.nodes().isEmpty(), "Fastify detector should not match Express routes");
66+
assertTrue(result.edges().isEmpty());
67+
}
68+
69+
@Test
70+
void matchesWithRequireFastify() {
71+
String code = """
72+
const fastify = require('fastify')();
73+
fastify.get('/health', async () => ({ status: 'ok' }));
74+
""";
75+
DetectorContext ctx = DetectorTestUtils.contextFor("src/server.js", "javascript", code);
76+
DetectorResult result = detector.detect(ctx);
77+
assertEquals(1, result.nodes().size());
78+
assertEquals("fastify", result.nodes().get(0).getProperties().get("framework"));
79+
}
80+
5281
@Test
5382
void deterministic() {
54-
String code = "fastify.get('/test', handler);";
83+
String code = """
84+
import Fastify from 'fastify';
85+
fastify.get('/test', handler);
86+
""";
5587
DetectorContext ctx = DetectorTestUtils.contextFor("typescript", code);
5688
DetectorTestUtils.assertDeterministic(detector, ctx);
5789
}

src/test/java/io/github/randomcodespace/iq/detector/typescript/TypeScriptDetectorsExtendedTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ class FastifyExtended {
1818
@Test
1919
void detectsFastifyRoutes() {
2020
String code = """
21+
import Fastify from 'fastify';
22+
const fastify = Fastify();
2123
fastify.get('/items', async (request, reply) => {
2224
return db.items.findAll();
2325
});
@@ -38,6 +40,7 @@ void detectsFastifyRoutes() {
3840
@Test
3941
void detectsRouteMethod() {
4042
String code = """
43+
import Fastify from 'fastify';
4144
fastify.route({
4245
method: 'GET',
4346
url: '/api/health',
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"source": "Context7 /spring-projects/spring-petclinic + spring-petclinic source code",
3+
"repo": "https://github.com/spring-projects/spring-petclinic",
4+
"language": "java",
5+
"framework": "spring-boot",
6+
"entities": {
7+
"Owner": {"table": "owners", "layer": "backend"},
8+
"Pet": {"table": "pets", "layer": "backend"},
9+
"Visit": {"table": "visits", "layer": "backend"},
10+
"Vet": {"table": "vets", "layer": "backend"},
11+
"Specialty": {"table": "specialties", "layer": "backend"},
12+
"PetType": {"table": "types", "layer": "backend"}
13+
},
14+
"endpoints": [
15+
{"method": "GET", "path": "/owners/new", "controller": "OwnerController"},
16+
{"method": "POST", "path": "/owners/new", "controller": "OwnerController"},
17+
{"method": "GET", "path": "/owners/find", "controller": "OwnerController"},
18+
{"method": "GET", "path": "/owners", "controller": "OwnerController"},
19+
{"method": "GET", "path": "/owners/{ownerId}/edit", "controller": "OwnerController"},
20+
{"method": "POST", "path": "/owners/{ownerId}/edit", "controller": "OwnerController"},
21+
{"method": "GET", "path": "/owners/{ownerId}", "controller": "OwnerController"},
22+
{"method": "GET", "path": "/owners/{ownerId}/pets/new", "controller": "PetController"},
23+
{"method": "POST", "path": "/owners/{ownerId}/pets/new", "controller": "PetController"},
24+
{"method": "GET", "path": "/owners/{ownerId}/pets/{petId}/edit", "controller": "PetController"},
25+
{"method": "POST", "path": "/owners/{ownerId}/pets/{petId}/edit", "controller": "PetController"},
26+
{"method": "GET", "path": "/owners/{ownerId}/pets/{petId}/visits/new", "controller": "VisitController"},
27+
{"method": "POST", "path": "/owners/{ownerId}/pets/{petId}/visits/new", "controller": "VisitController"},
28+
{"method": "GET", "path": "/vets.html", "controller": "VetController"},
29+
{"method": "GET", "path": "/vets", "controller": "VetController"},
30+
{"method": "GET", "path": "/oups", "controller": "CrashController"}
31+
],
32+
"controllers": ["OwnerController", "PetController", "VisitController", "VetController", "CrashController", "WelcomeController"],
33+
"repositories": ["OwnerRepository"],
34+
"expected_stats": {
35+
"min_nodes": 500,
36+
"min_edges": 1000,
37+
"min_files": 50,
38+
"min_endpoints": 16,
39+
"min_classes": 30,
40+
"min_methods": 30,
41+
"primary_language": "java",
42+
"no_false_frameworks": ["quarkus", "micronaut", "fastify"]
43+
}
44+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"source": "RealWorld API spec + source code inspection",
3+
"repo": "https://github.com/gothinkster/node-express-realworld-example-app",
4+
"language": "typescript",
5+
"framework": "express",
6+
"entities": ["User", "Article", "Comment", "Tag"],
7+
"endpoints_partial": ["/articles", "/users", "/users/login", "/user", "/tags", "/profiles"],
8+
"expected_frameworks": ["express", "prisma"],
9+
"expected_stats": {
10+
"min_nodes": 100,
11+
"min_edges": 50,
12+
"min_endpoints": 15,
13+
"primary_language": "typescript",
14+
"has_auth": true,
15+
"no_false_frameworks": ["quarkus", "micronaut", "spring"]
16+
}
17+
}

0 commit comments

Comments
 (0)