Skip to content

[BUG] Opening a file inside a version-manager-installed SDK roots dartls in the Flutter SDK itself #534

Description

@janne

Environment

  • flutter-tools.nvim 3.0.1 (0867b34)
  • Neovim 0.12.5
  • Flutter 3.47.1 (stable), installed via mise
  • Linux 7.1.9-arch1-2

Current behavior

Opening a framework source file (e.g. after gd on StatefulWidget) starts a
dartls client rooted at the Flutter SDK itself — a 2.3 GB tree — and the
buffer is left writable.

Reproduced with stock upstream, no user config beyond flutter_lookup_cmd:

-- init.lua
local L = vim.fn.stdpath("data") .. "/lazy/"
vim.opt.rtp:append(L .. "plenary.nvim")
vim.opt.rtp:append(L .. "flutter-tools.nvim")
require("flutter-tools").setup({ flutter_lookup_cmd = "mise where flutter" })
vim.api.nvim_create_autocmd("LspAttach", {
  callback = function(a)
    local c = vim.lsp.get_client_by_id(a.data.client_id)
    print("root=" .. tostring(c.config.root_dir),
          "modifiable=" .. tostring(vim.bo[a.buf].modifiable))
  end,
})

Opening <sdk>/packages/flutter/lib/src/widgets/basic.dart gives:

root       = /home/user/.local/share/mise/http-tarballs/<hash>_strip_1
modifiable = true

Cause

is_flutter_dependency_path() (lua/flutter-tools/utils/path.lua:193-201)
recognises SDK/dependency locations from a hardcoded list:

local path_parts = { [[.pub-cache]], [[Pub\Cache]], [[/fvm/versions/]] }

An SDK installed by mise lives under ~/.local/share/mise/http-tarballs/...,
and one installed by asdf under ~/.asdf/installs/flutter/<version>/. Neither
matches, so a framework source file is treated as an ordinary project file and
get_project_root_dir() (lsp/init.lua:170-182) runs the upward search it was
supposed to skip.

That search uses root_patterns = { ".git", "pubspec.yaml" }, and the Flutter
SDK root contains both:

$ ls -d "$(realpath "$(mise where flutter)")"/{.git,pubspec.yaml}
.../  .../pubspec.yaml
$ du -sh "$(realpath "$(mise where flutter)")"
2.3G

so the whole SDK becomes the root.

There is a second, independent path to the same outcome:
M.attach() (lsp/init.lua:293-296) repeats the upward search whenever
get_project_root_dir() returns nil, which it does whenever no dartls client
has attached yet:

c.root_dir = M.get_project_root_dir()
  or fs.dirname(fs.find(conf.root_patterns, { path = buffer_path, upward = true })[1])

So even with the dependency check corrected, this fallback can still select a
root inside the SDK (in my testing, <sdk>/packages/flutter).

Consequences

  1. The analysis context is the SDK rather than the user's project.
  2. Framework buffers stay writable. ftplugin/dart/init.lua sets
    modifiable = false through the same predicate, so the read-only protection
    is silently lost for these installs (modifiable = true above).
  3. It is expensive. Stepping through four framework files (framework.dart,
    app_bar.dart, scaffold.dart, basic.dart) took the analysis server to
    666 MB RSS at ~32% CPU in a scripted run; an interactive session reached
    1.6 GB. Neovim separately received ~69 000 diagnostics across ~170 files
    while only 4 buffers were loaded.

Point 3 may be inherent to onlyAnalyzeProjectsWithOpenFiles rather than a
consequence of the wrong root — I did not isolate the two, and I mention it as
practical impact rather than as a claim about the cause.

Expected behavior

A buffer inside the Flutter SDK should not produce a root_dir inside the SDK.
It should attach to the project's existing client, as already happens for
.pub-cache and fvm installs.

Suggested fix

Derive the check from the SDK path the plugin already resolves
(executable.get()paths.flutter_sdk, compared after fs_realpath, since
version managers commonly symlink their installs) rather than from a hardcoded
fragment list. That covers every installation method without new configuration.

Guarding the or fallback in M.attach() so it cannot select a root inside the
SDK would close the second path.

Steps to reproduce

  1. Install Flutter via mise (or asdf).
  2. Use the init.lua above.
  3. Open any file under <sdk>/packages/flutter/lib/src/.
  4. Observe root_dir inside the SDK and modifiable = true.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions