Skip to content

Phase 0 provider abstraction#1

Merged
mrpbennett merged 8 commits intomainfrom
phase-0-provider-abstraction
Mar 18, 2026
Merged

Phase 0 provider abstraction#1
mrpbennett merged 8 commits intomainfrom
phase-0-provider-abstraction

Conversation

@mrpbennett
Copy link
Copy Markdown
Owner

@mrpbennett mrpbennett commented Mar 18, 2026

Summary by Sourcery

Introduce a framework-agnostic NimbleAPI plugin with a pluggable provider system and migrate existing FastAPI-specific functionality to support both FastAPI (Python) and Spring Boot (Java) projects.

New Features:

  • Add a provider registry and detection system to support multiple web frameworks with a common route exploration UI.
  • Implement a Spring / Spring Boot provider for Java, including route and test-client extraction via Tree-sitter queries.
  • Expose a new :NimbleAPI info command to show provider status, detection results, and diagnostics.
  • Provide a caching layer for route trees and flat route lists, keyed by project and file mtimes.

Enhancements:

  • Rename and rebrand the plugin from fastapi.nvim to nimbleapi.nvim, updating commands, keymaps, and highlight groups to be framework-agnostic.
  • Refine the explorer sidebar to be context-aware, show provider-specific labels, and surface diagnostics when no routes are found.
  • Generalize file utilities, globbing, import resolution, and Tree-sitter parsing to work across multiple languages.
  • Update picker backends (builtin, Telescope, Snacks) to use the new NimbleAPI naming and highlight groups.
  • Add internal documentation (CLAUDE.md) describing the provider architecture, data flow, and guidelines for adding new frameworks.

Tests:

  • Add a headless provider detection test that verifies Spring project detection and route extraction based on a synthetic Maven project fixture.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Mar 18, 2026

Reviewer's Guide

Introduces a new framework-agnostic provider architecture and renames the plugin from fastapi.nvim to nimbleapi.nvim, adds a Spring/Spring Boot Java provider with Tree-sitter queries and diagnostics, and refactors caches, explorer, pickers, codelens, and entrypoints to be provider-aware while updating documentation and adding a small headless test.

Sequence diagram for :NimbleAPI info and provider detection

sequenceDiagram
  actor User
  participant Neovim as NeovimCommand
  participant API as nimbleapi_init
  participant Providers as providers_init
  participant RouteProvider

  User->>Neovim: :NimbleAPI info
  Neovim->>API: info(ctx)
  API->>Providers: info(ctx)
  activate Providers
  Providers->>Providers: resolve_root(ctx)
  Providers-->>Providers: root
  Providers->>Providers: get_provider(ctx)
  alt provider cached
    Providers-->>Providers: return cached provider
  else not cached
    Providers->>Providers: detect(root)
    loop for each registered provider
      Providers->>RouteProvider: check_prerequisites()
      alt prerequisites ok
        Providers->>RouteProvider: detect(root)
        alt project matches
          Providers-->>Providers: set active provider
        else no match
          Providers-->>Providers: record diagnostics
        end
      else prerequisites failed
        Providers-->>Providers: record diagnostics
      end
    end
  end
  Providers-->>API: lines[] diagnostics and status
  deactivate Providers
  API-->>Neovim: lines[]
  Neovim-->>User: show notification with provider info
Loading

File-Level Changes

Change Details Files
Rename and rebrand the plugin from fastapi.nvim to nimbleapi.nvim, updating public API, commands, and README to reflect multi-framework support.
  • Update README with new branding, logo, plugin name, supported frameworks table, configuration options, commands, and contributing/provider documentation reference.
  • Change Lua module and runtime plugin names from fastapi.* to nimbleapi.*, including user commands (FastAPI -> NimbleAPI), default keymaps, highlight groups, and filetypes.
  • Adjust explorer and buffer UI labels from FastAPI-specific wording to generic route explorer terminology.
README.md
plugin/nimbleapi.lua
lua/nimbleapi/init.lua
lua/nimbleapi/explorer.lua
lua/nimbleapi/config.lua
lua/nimbleapi/pickers/*.lua
lua/telescope/_extensions/nimbleapi.lua
Introduce a provider abstraction layer and refactor routing logic, discovery, and cache to be framework-agnostic.
  • Add nimbleapi.providers registry that resolves project roots, detects the appropriate provider (FastAPI, Spring), caches provider selection, and exposes diagnostics via :NimbleAPI info.
  • Refactor cache module to delegate route tree and flat route extraction to the active provider, including provider-aware invalidation and route lookup building.
  • Update explorer, picker, and codelens modules to request routes and test-call extraction via cache/providers, adding context-aware behavior (e.g., per-buffer context in explorer and codelens).
lua/nimbleapi/providers/init.lua
lua/nimbleapi/cache.lua
lua/nimbleapi/explorer.lua
lua/nimbleapi/picker.lua
lua/nimbleapi/codelens.lua
lua/nimbleapi/utils.lua
lua/nimbleapi/import_resolver.lua
Add a Spring / Spring Boot provider using Tree-sitter Java, with project detection, route extraction, and test client support.
  • Implement springboot provider with detection via pom.xml/Gradle dependencies, project root resolution, entry-point discovery (@SpringBootApplication or controller), route extraction from controller annotations, and test call extraction from MockMvc/WebTestClient.
  • Create Java Tree-sitter queries for Spring Boot apps, controllers, routes, and test clients to support provider parsing.
  • Extend plugin autocmds and watcher patterns to include Java files and route updates when the active provider handles the saved/entered buffer.
lua/nimbleapi/providers/springboot.lua
queries/java/springboot-apps.scm
queries/java/springboot-controllers.scm
queries/java/springboot-routes.scm
queries/java/springboot-testclient.scm
plugin/nimbleapi.lua
Refine FastAPI-specific modules into a FastAPI provider and generalize project utilities.
  • Wrap FastAPI behavior in a provider module that implements the RouteProvider interface and delegates to existing parser, app_finder, router_resolver, and import_resolver logic.
  • Simplify FastAPI app discovery by removing the direct entry_point config override and relying on pyproject.toml plus heuristic scans, while reusing generalized project-root finding utilities.
  • Generalize utils helpers for path existence, start-dir resolution, project-root discovery, and file globbing across languages and providers.
lua/nimbleapi/providers/fastapi.lua
lua/nimbleapi/app_finder.lua
lua/nimbleapi/router_resolver.lua
lua/nimbleapi/import_resolver.lua
lua/nimbleapi/utils.lua
Enhance diagnostics, highlighting, and testing to support multi-provider workflows.
  • Implement :NimbleAPI info command and providers.info() output to summarize registered providers, prerequisite status, detection results, and active provider for the current context.
  • Rename and define new NimbleApi* highlight groups and apply them throughout explorer, pickers, and codelens for methods, paths, titles, and separators.
  • Add a headless spec (provider_detection_spec) and a small test runner script to validate provider detection using a temporary Spring fixture project.
plugin/nimbleapi.lua
lua/nimbleapi/init.lua
lua/nimbleapi/providers/init.lua
README.md
CLAUDE.md
spec/provider_detection_spec.lua
spec/run_headless.lua

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@mrpbennett mrpbennett merged commit 3fe6394 into main Mar 18, 2026
2 checks passed
@mrpbennett mrpbennett deleted the phase-0-provider-abstraction branch March 18, 2026 17:09
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • In plugin/nimbleapi.lua the BufWritePost/BufEnter autocmd patterns are hard-coded to *.py and *.java; consider deriving these from require("nimbleapi.providers").get_all_extensions() so new providers automatically participate in file watching and codelens without touching the plugin layer.
  • The RouteProvider type in providers/init.lua documents find_project_root as fun(): string, but it is always called with a startpath and the concrete implementations accept an argument; aligning the type annotation and call sites (e.g., consistently find_project_root(startpath: string|nil)) would make the interface clearer.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `plugin/nimbleapi.lua` the BufWritePost/BufEnter autocmd patterns are hard-coded to `*.py` and `*.java`; consider deriving these from `require("nimbleapi.providers").get_all_extensions()` so new providers automatically participate in file watching and codelens without touching the plugin layer.
- The `RouteProvider` type in `providers/init.lua` documents `find_project_root` as `fun(): string`, but it is always called with a `startpath` and the concrete implementations accept an argument; aligning the type annotation and call sites (e.g., consistently `find_project_root(startpath: string|nil)`) would make the interface clearer.

## Individual Comments

### Comment 1
<location path="lua/nimbleapi/cache.lua" line_range="100-109" />
<code_context>
+function M.get_route_tree(ctx)
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Avoid repeated notifications when no provider is detected by caching the empty result per root

When `get_route_tree` sees `get_provider` return `nil`, it warns but never sets `tree_cache[root]`. With `provider_cache[root] = false`, later calls still hit this branch and warn every time. Consider caching the empty result as `tree_cache[root] = false` before returning so callers can skip this path without repeating the warning.
</issue_to_address>

### Comment 2
<location path="lua/nimbleapi/cache.lua" line_range="146-150" />
<code_context>
+--- Get all routes as a flat list (with full paths).
+---@param ctx string|table|nil
+---@return table[]
+function M.get_all_routes(ctx)
+  local providers = require("nimbleapi.providers")
+  local root = providers.resolve_root(ctx)
+  if flat_cache[root] ~= nil then
+    return flat_cache[root] or {}
+  end
+
+  local provider = providers.get_provider(ctx)
+
+  if not provider then
+    -- Trigger get_route_tree so the user sees the diagnostic message
+    M.get_route_tree(ctx)
+    return {}
+  end
+
+  local project_root = provider.find_project_root(root)
+  local routes = provider.get_all_routes(project_root)
+  if not routes or #routes == 0 then
+    -- Try via route tree for providers that build trees
+    local tree = M.get_route_tree(ctx)
+    if tree then
+      routes = require("nimbleapi.router_resolver").flatten_routes(tree)
+    end
+  end
+  flat_cache[root] = routes or false
+  return routes or {}
+end
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Similarly cache the absence of a provider in flat route cache to prevent repeated diagnostics

When no provider is found, you call `get_route_tree(ctx)` to emit diagnostics but don’t update `flat_cache[root]`, so subsequent calls keep recomputing and re-emitting diagnostics. To keep behavior idempotent and consistent with `get_route_tree`, set `flat_cache[root] = false` before returning `{}` in this branch.

```suggestion
  if not provider then
    -- Trigger get_route_tree so the user sees the diagnostic message
    M.get_route_tree(ctx)
    flat_cache[root] = false
    return {}
  end
```
</issue_to_address>

### Comment 3
<location path="lua/nimbleapi/providers/init.lua" line_range="18" />
<code_context>
+---@field extract_routes fun(filepath: string): table[]
+---@field extract_includes fun(filepath: string): table[]
+---@field extract_test_calls_buf fun(bufnr: integer): table[]
+---@field find_project_root fun(): string
+```
+
</code_context>
<issue_to_address>
**nitpick:** Update the RouteProvider type annotation for find_project_root to reflect the startpath parameter

`find_project_root` is documented as taking no parameters, but both `fastapi` and `springboot` providers implement it as `find_project_root(startpath)` and it’s called as `provider.find_project_root(root)`. Please update the annotation to `---@field find_project_root fun(startpath: string|nil): string` so it matches actual usage and supports static tooling.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread lua/nimbleapi/cache.lua
Comment on lines +100 to +109
function M.get_route_tree(ctx)
local providers = require("nimbleapi.providers")
local root = providers.resolve_root(ctx)
if tree_cache[root] ~= nil then
return tree_cache[root] or nil
end

local provider = providers.get_provider(ctx)

if not provider then
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Avoid repeated notifications when no provider is detected by caching the empty result per root

When get_route_tree sees get_provider return nil, it warns but never sets tree_cache[root]. With provider_cache[root] = false, later calls still hit this branch and warn every time. Consider caching the empty result as tree_cache[root] = false before returning so callers can skip this path without repeating the warning.

Comment thread lua/nimbleapi/cache.lua
Comment on lines +146 to +150
if not provider then
-- Trigger get_route_tree so the user sees the diagnostic message
M.get_route_tree(ctx)
return {}
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Similarly cache the absence of a provider in flat route cache to prevent repeated diagnostics

When no provider is found, you call get_route_tree(ctx) to emit diagnostics but don’t update flat_cache[root], so subsequent calls keep recomputing and re-emitting diagnostics. To keep behavior idempotent and consistent with get_route_tree, set flat_cache[root] = false before returning {} in this branch.

Suggested change
if not provider then
-- Trigger get_route_tree so the user sees the diagnostic message
M.get_route_tree(ctx)
return {}
end
if not provider then
-- Trigger get_route_tree so the user sees the diagnostic message
M.get_route_tree(ctx)
flat_cache[root] = false
return {}
end

---@field extract_routes fun(filepath: string): table[]
---@field extract_includes fun(filepath: string): table[]
---@field extract_test_calls_buf fun(bufnr: integer): table[]
---@field find_project_root fun(): string
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Update the RouteProvider type annotation for find_project_root to reflect the startpath parameter

find_project_root is documented as taking no parameters, but both fastapi and springboot providers implement it as find_project_root(startpath) and it’s called as provider.find_project_root(root). Please update the annotation to ---@field find_project_root fun(startpath: string|nil): string so it matches actual usage and supports static tooling.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant