module

Noir::TreeSitterJavaRouteExtractor

Tree-sitter-backed Java route extractor.

Scope for this first cut: Spring's annotation-based routing — class-level @RequestMapping-family annotations composing with method-level mapping annotations. This is the most common Java route shape by a wide margin and what src/analyzer/analyzers/java/ spring.cr exists to surface. Covers:

  • @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping — each fixes the HTTP verb
  • @RequestMapping — generic; verb comes from a method = RequestMethod.X element-value pair, or is reported as ANY when none is given because Spring matches all HTTP methods
  • Class-level mapping annotations contribute a prefix that's concatenated onto the per-method path
  • Annotation value supplied positionally (@GetMapping("/x")) or as a keyword element (value = "/x" / path = "/x"), including arrays
  • Same-file meta-annotations that compose Spring mapping annotations of their own
  • Multi-line annotations (the Java grammar eats whitespace for us)

Deliberately not covered yet (handled by the legacy analyzer while this PoC stabilises):

  • Cross-file meta-annotation resolution

Other JVM frameworks (Armeria, Play, JSP, Vert.x) can layer on top of this once the Spring port is validated.

Constants

ANNOTATION_VERBS = {"GetMapping" => "GET", "PostMapping" => "POST", "PutMapping" => "PUT", "DeleteMapping" => "DELETE", "PatchMapping" => "PATCH", "RequestMapping" => nil, "GetExchange" => "GET", "PostExchange" => "POST", "PutExchange" => "PUT", "DeleteExchange" => "DELETE", "PatchExchange" => "PATCH", "HttpExchange" => nil}

Spring mapping annotation names mapped to the HTTP verb they imply. nil means "look at the annotation's method = argument".

DOTTED_NAME_RE = /(?<![\w$])[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+/
SINGLE_IMPORT_RE = /^\s*import\s+([\w$.]+)\.([\w$]+)\s*;/m
STATIC_IMPORT_NAME_RE = /\bimport\s+static\s+([\w$.]+)\.([\w$]+|\*)\s*;/
VERB_IDENTIFIERS = {"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "TRACE"}

Known HTTP-verb identifiers we consider valid method = ... values. Used when reconstructing verbs from ERROR nodes around the invalid [...] bracket syntax.

Instance methods

extract_controller_interface_implementations_from(root : LibTreeSitter::TSNode, source : String, external_meta_mappings = Hash(String, ClassMapping).new, constants : Hash(String, String) | Nil = nil) : Array(ControllerInterfaceImplementation)
Source
extract_interface_routes_from(root : LibTreeSitter::TSNode, source : String, external_meta_mappings = Hash(String, ClassMapping).new, constants : Hash(String, String) | Nil = nil) : Hash(String, Array(Route))
Source
extract_meta_mappings_from(root : LibTreeSitter::TSNode, source : String, constants : Hash(String, String) | Nil = nil) : Hash(String, ClassMapping)
Source
extract_routes(source : String) : Array(Route)

Parses source and returns every Spring-style route it can resolve. Top-level classes are scanned in order; nested classes inherit their parent class's mapping prefix.

Source
extract_routes_from(root : LibTreeSitter::TSNode, source : String, external_meta_mappings = Hash(String, ClassMapping).new, constants : Hash(String, String) | Nil = nil) : Array(Route)

_from variant — accept a pre-parsed root so the Spring analyzer can amortise tree-sitter parses across multiple extractions on the same file. Tree lifetime is the caller's responsibility.

Source
extract_string_and_array_constants(source : String, project_constants : Hash(String, String) | Nil = nil) : Tuple(Hash(String, String), Hash(String, Array(String)))

The file's String constants plus its String[] constants (String[] OPEN = {"/a/**", BASE + "/b"} / new String[]{...}), both keyed bare, Class.NAME and pkg.Class.NAME like extract_string_constants. project_constants seeds other classes' qualified names (see extract_string_constants_from). An element that does not resolve to a String is left out; the rest of the array stays.

Source
extract_string_constants(source : String) : Hash(String, String)
Source
extract_string_constants_from(root : LibTreeSitter::TSNode, source : String, project_constants : Hash(String, String) | Nil = nil) : Hash(String, String)

project_constants seeds the table with other files' qualified constants (Paths.API, com.ex.Paths.API, see JavaEngine#java_project_constants) so @RequestMapping(Paths.API) and BASE = Paths.API + "/x" resolve across files. This file's own qualified keys overwrite the seed. Only the names this file actually spells are copied — the project table can be thousands of entries.

Source

Nested types