sveltejs / language-tools

The Svelte Language Server, and official extensions which use it
MIT License
1.18k stars 190 forks source link

VSCode Plugin does not handle symlinks correctly #2364

Open sirinsidiator opened 2 months ago

sirinsidiator commented 2 months ago

Describe the bug

The plugin won't initialize when the project folder contains two or more symbolic links that point to the root of the project and as a result no svelte related features like autocompletion work.

This happens for example when using svelte in a tauri project together with the cxx library. When the project is compiled, cxx creates multiple symbolic links that point to the project root inside the target folder, as described here.

Reproduction

The issue can be reproduced with a newly created svelte project (using the default options):

npm create svelte@latest my-app
cd my-app
npm install
code .

Ensure svelte vs code plugin is on, then open e.g. Counter.svelte and try using autocompletion on "display_counter." and notice how it still works as expected.

Svelte plugin output at this point:

Initialize language server at  file:///r%3A/my-app
Initialize new ts service at  r:/my-app/jsconfig.json
Trying to load configs for r:/my-app
Loaded config at  r:\my-app\svelte.config.js
SnapshotManager File Statistics:
Project files: 22
Svelte files: 7
From node_modules: 0
Total: 22
Initialize new ts service at  
Trying to load configs for r:\my-app

Next create two symbolic link to the project root using powershell for example:

New-Item -Path test1 -ItemType SymbolicLink -Value .
New-Item -Path test2 -ItemType SymbolicLink -Value .

Afterwards restart the extension host in vscode and try autocompletion again. It no longer works and only shows "Loading..." forever. The Svelte plugin output looks like this:

Initialize language server at  file:///r%3A/my-app
Initialize new ts service at  r:/my-app/jsconfig.json
Trying to load configs for r:/my-app

After the symbolic links are removed and the extension host is restarted, it works again.

Expected behaviour

The plugin should operate normally, regardless of any symbolic links to the project root.

System Info

Which package is the issue about?

Svelte for VS Code extension

Additional Information, eg. Screenshots

When only one symbolic link exists in the project, The autocompletion works, but the plugin contains some very interesting output, that may explain why it fails to load:

Initialize language server at  file:///r%3A/my-app
Initialize new ts service at  r:/my-app/jsconfig.json
Trying to load configs for r:/my-app
Loaded config at  r:\my-app\svelte.config.js
Loaded config at  r:\my-app\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
Loaded config at  r:\my-app\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\test1\svelte.config.js
SnapshotManager File Statistics:
Project files: 22
Svelte files: 7
From node_modules: 0
Total: 22
Initialize new ts service at  
Trying to load configs for r:\my-app

It seems that it looks for svelte.config.js files inside any folder it can find and runs into some infinite loop when there are multiple symbolic links.

jasonlyu123 commented 2 months ago

The loop is because you create a symlink within the directory itself. Is there any reason why you need this? Or is this an overly simplified reproduction?

sirinsidiator commented 2 months ago

It's a simplified reproduction. The actual project has a lot more dependencies and I'm also running more plugins with it, which makes it hard to see where the problem lies and it took me several hours to track down the cause.

This happens for example when using svelte in a tauri project together with the cxx library. When the project is compiled, cxx creates multiple symbolic links that point to the project root inside the target folder, as described here.

The symlinks it makes are deeply nested inside the output folder for tauri and the paths look something like this: my-app\src-tauri\target\debug\build\my-app-340e74d642c3dd4e\out\cxxbridge\crate\my-app

One other interesting thing to take note of is that npm run dev and npm run build both work without a problem. It seems there is some critical difference in how svelte and the vscode plugin find and load the project configuration, which may actually be an entirely different issue in itself.

Regardless of any bug, I'm also wondering if there is some way to tell the plugin to ignore a folder like src-tauri completely? There is nothing in there it should ever have to look at.

jasonlyu123 commented 2 months ago

Build output does make a lot more sense. Other tools probably only load the top config, or start searching from the svelte file and walk the directory tree upward. We have a unique situation where we need to load all the config in the workspace upfront.

We probably could keep track of visited real paths to eliminate the infinite loop. Or maybe using the gitignore or tsconfig.json include/exclude to exclude searching directories.

sirinsidiator commented 2 months ago

Maybe some new extension settings could work? One to limit the search depth, one to enable using the tsconfig.json include/exclude array and another to specify ignore patterns directly. I doubt there are many projects that have svelte.config.js files more than 3 levels deep.