Skip to content

Commit 0dd0b5b

Browse files
author
Erikson Kaszubowski
committed
refactor(angularls): better root path resolution
Problem: The current config for Angular LS make strong assumptions when trying to find the root dir, which can lead to unexpected LSP crashes. Solution: By defining the 'cmd' field as a function, the config employs Neovim's LSP root resolution to identify the correct path and find the relevant node_modules folder.
1 parent 107c245 commit 0dd0b5b

File tree

1 file changed

+64
-52
lines changed

1 file changed

+64
-52
lines changed

lsp/angularls.lua

Lines changed: 64 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -14,72 +14,84 @@
1414

1515
-- Angular requires a node_modules directory to probe for @angular/language-service and typescript
1616
-- in order to use your projects configured versions.
17-
local root_dir = vim.fn.getcwd()
18-
local node_modules_dir = vim.fs.find('node_modules', { path = root_dir, upward = true })[1]
19-
local project_root = node_modules_dir and vim.fs.dirname(node_modules_dir) or '?'
17+
local fs, fn, uv = vim.fs, vim.fn, vim.uv
2018

21-
local function get_probe_dir()
22-
return project_root and (project_root .. '/node_modules') or ''
23-
end
19+
local function collect_node_modules(root_dir)
20+
local results = {}
2421

25-
local function get_angular_core_version()
26-
if not project_root then
27-
return ''
22+
local project_node = fs.joinpath(root_dir, 'node_modules')
23+
if uv.fs_stat(project_node) then
24+
table.insert(results, project_node)
2825
end
2926

30-
local package_json = project_root .. '/package.json'
31-
if not vim.uv.fs_stat(package_json) then
32-
return ''
27+
local ngserver_exe = fn.exepath('ngserver')
28+
if ngserver_exe and #ngserver_exe > 0 then
29+
local realpath = uv.fs_realpath(ngserver_exe) or ngserver_exe
30+
local candidate = fs.normalize(fs.joinpath(fs.dirname(realpath), '../../node_modules'))
31+
if uv.fs_stat(candidate) then
32+
table.insert(results, candidate)
33+
end
3334
end
3435

35-
local contents = io.open(package_json):read '*a'
36-
local json = vim.json.decode(contents)
37-
if not json.dependencies then
38-
return ''
36+
local internal_servers = fn.globpath(fn.stdpath('data'), '**/node_modules/.bin/ngserver', true, true)
37+
for _, exe in ipairs(internal_servers) do
38+
local realpath = uv.fs_realpath(exe) or exe
39+
local candidate = fs.normalize(fs.joinpath(fs.dirname(realpath), '../../node_modules'))
40+
if uv.fs_stat(candidate) then
41+
table.insert(results, candidate)
42+
end
3943
end
4044

41-
local angular_core_version = json.dependencies['@angular/core']
42-
43-
angular_core_version = angular_core_version and angular_core_version:match('%d+%.%d+%.%d+')
44-
45-
return angular_core_version
45+
return results
4646
end
4747

48-
local default_probe_dir = get_probe_dir()
49-
local default_angular_core_version = get_angular_core_version()
48+
local function get_angular_core_version(root_dir)
49+
local package_json = fs.joinpath(root_dir, 'package.json')
50+
if not uv.fs_stat(package_json) then
51+
return ''
52+
end
53+
54+
local ok, f = pcall(io.open, package_json, 'r')
55+
if not ok or not f then
56+
return ''
57+
end
5058

51-
-- structure should be like
52-
-- - $EXTENSION_PATH
53-
-- - @angular
54-
-- - language-server
55-
-- - bin
56-
-- - ngserver
57-
-- - typescript
58-
local ngserver_exe = vim.fn.exepath('ngserver')
59-
local ngserver_path = #(ngserver_exe or '') > 0 and vim.fs.dirname(vim.uv.fs_realpath(ngserver_exe)) or '?'
60-
local extension_path = vim.fs.normalize(vim.fs.joinpath(ngserver_path, '../../../'))
59+
local json = vim.json.decode(f:read('*a')) or {}
60+
f:close()
6161

62-
-- angularls will get module by `require.resolve(PROBE_PATH, MODULE_NAME)` of nodejs
63-
local ts_probe_dirs = vim.iter({ extension_path, default_probe_dir }):join(',')
64-
local ng_probe_dirs = vim
65-
.iter({ extension_path, default_probe_dir })
66-
:map(function(p)
67-
return vim.fs.joinpath(p, '/@angular/language-server/node_modules')
68-
end)
69-
:join(',')
62+
local version = (json.dependencies or {})['@angular/core'] or ''
63+
return version:match('%d+%.%d+%.%d+') or ''
64+
end
7065

7166
---@type vim.lsp.Config
7267
return {
73-
cmd = {
74-
'ngserver',
75-
'--stdio',
76-
'--tsProbeLocations',
77-
ts_probe_dirs,
78-
'--ngProbeLocations',
79-
ng_probe_dirs,
80-
'--angularCoreVersion',
81-
default_angular_core_version,
82-
},
68+
cmd = function(dispatchers, config)
69+
local root_dir = config.root or fn.getcwd()
70+
local node_paths = collect_node_modules(root_dir)
71+
72+
local ts_probe = table.concat(node_paths, ',')
73+
local ng_probe = table.concat(
74+
vim
75+
.iter(node_paths)
76+
:map(function(p)
77+
return fs.joinpath(p, '@angular/language-server/node_modules')
78+
end)
79+
:totable(),
80+
','
81+
)
82+
local cmd = {
83+
'ngserver',
84+
'--stdio',
85+
'--tsProbeLocations',
86+
ts_probe,
87+
'--ngProbeLocations',
88+
ng_probe,
89+
'--angularCoreVersion',
90+
get_angular_core_version(root_dir),
91+
}
92+
return vim.lsp.rpc.start(cmd, dispatchers)
93+
end,
94+
8395
filetypes = { 'typescript', 'html', 'typescriptreact', 'typescript.tsx', 'htmlangular' },
84-
root_markers = { 'angular.json', 'nx.json' },
96+
root_markers = { 'angular.json', 'nx.json', 'package.json' },
8597
}

0 commit comments

Comments
 (0)