Skip to content

Commit e6c582e

Browse files
authored
bug: Fix qflist and monorepo support (dmmulroy#20)
* bug: fix file path reference in qflist to prevent opening to empty buffers * bug: fix monorepo support by defaulting to using neared tsconfig to open buffer * feat: support flags as a table that accepts flag names as keys and values as strings, booleans, or functions
1 parent 16bd30e commit e6c582e

File tree

3 files changed

+118
-53
lines changed

3 files changed

+118
-53
lines changed

README.md

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,18 +71,29 @@ To run TypeScript type-checking, execute the `:TSC` command in Neovim. The plugi
7171

7272
## Configuration
7373

74-
By default it uses the default `tsc` command with the `--noEmit` flag to avoid generating output files during type-checking. Here's the default configuration:
74+
By default, the plugin uses the default `tsc` command with the `--noEmit` flag to avoid generating output files during type-checking. It also emulates the default tsc behavior of performing a backward search from the current directory for a `tsconfig` file. The flags option can accept both a string and a table. Here's the default configuration:
7575

7676
```lua
7777
{
7878
auto_open_qflist = true,
7979
enable_progress_notifications = true,
80-
flags = "--noEmit",
80+
flags = {
81+
noEmit = true,
82+
project = function()
83+
return utils.find_nearest_tsconfig()
84+
end,
85+
},
8186
hide_progress_notifications_from_history = true,
8287
spinner = { "", "", "", "", "", "", "", "" },
8388
}
8489
```
8590

91+
With this configuration, you can use keys for flag names and their corresponding values to enable/disable the flag (in the case of `noEmit = true`) or provide a function (as in the case of the `project`). This makes the configuration more explicit and easier to read. Additionally, the flags option is backwards compatible and can accept a string value if you prefer a simpler configuration:
92+
93+
```lua
94+
flags = "--noEmit",
95+
```
96+
8697
## FAQs
8798

8899
### I'm using `nvim-notify` and being spammed by progress notifcations, what's going on?
@@ -98,16 +109,20 @@ end
98109

99110
```
100111

101-
### Why doesn't `tsc.nvim` typecheck my entire monorepo?
112+
### Why doesn't tsc.nvim typecheck my entire monorepo?
102113

103-
In a monorepo setup, tsc.nvim only typechecks the current project by default because it uses the tsconfig.json of the current directory. If you need to typecheck across all projects in the monorepo, you must change the flags configuration option in the setup function from --noEmit to --build --composite. The --build flag instructs TypeScript to typecheck all referenced projects and the --composite flag enables project references and incremental builds for better management of dependencies and build performance. Your adjusted setup function should look like this:
114+
In a monorepo setup, tsc.nvim only typechecks the project associated with the nearest `tsconfig.json` by default. If you need to typecheck across all projects in the monorepo, you must change the flags configuration option in the setup function to include `--build`. The `--build` flag instructs TypeScript to typecheck all referenced projects, taking into account project references and incremental builds for better management of dependencies and build performance. Your adjusted setup function should look like this:
104115

105116
```lua
106117
require('tsc').setup({
107-
flags = "--build --composite",
118+
flags = {
119+
build = true,
120+
},
108121
})
109122
```
110123

124+
With this configuration, tsc.nvim will typecheck all projects in the monorepo, taking into account project references and incremental builds.
125+
111126
## Contributing
112127

113128
Feel free to open issues or submit pull requests if you encounter any bugs or have suggestions for improvements. Your contributions are welcome!

lua/tsc/init.lua

Lines changed: 13 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@ if success then
99
nvim_notify = pcall_result
1010
end
1111

12-
local is_running = false
13-
1412
local DEFAULT_CONFIG = {
1513
auto_open_qflist = true,
1614
enable_progress_notifications = true,
17-
flags = "--noEmit",
15+
flags = {
16+
noEmit = true,
17+
project = function()
18+
return utils.find_nearest_tsconfig()
19+
end,
20+
},
1821
hide_progress_notifications_from_history = true,
1922
spinner = { "", "", "", "", "", "", "", "" },
2023
}
@@ -25,6 +28,7 @@ local DEFAULT_NOTIFY_OPTIONS = {
2528
}
2629

2730
local config = {}
31+
local is_running = false
2832

2933
local function get_notify_options(...)
3034
local overrides = {}
@@ -38,42 +42,6 @@ local function get_notify_options(...)
3842
return vim.tbl_deep_extend("force", {}, DEFAULT_NOTIFY_OPTIONS, overrides)
3943
end
4044

41-
local function set_qflist(errors)
42-
vim.fn.setqflist({}, "r", { title = "TSC", items = errors })
43-
44-
if #errors > 0 and config.auto_open_qflist then
45-
vim.cmd("copen")
46-
end
47-
end
48-
49-
local function parse_tsc_output(output)
50-
local errors = {}
51-
local files = {}
52-
53-
if output == nil then
54-
return { errors = errors, files = files }
55-
end
56-
57-
for _, line in ipairs(output) do
58-
local filename, lineno, colno, message = line:match("^(.+)%((%d+),(%d+)%)%s*:%s*(.+)$")
59-
if filename ~= nil then
60-
table.insert(errors, {
61-
filename = filename,
62-
lnum = tonumber(lineno),
63-
col = tonumber(colno),
64-
text = message,
65-
type = "E",
66-
})
67-
68-
if vim.tbl_contains(files, filename) == false then
69-
table.insert(files, filename)
70-
end
71-
end
72-
end
73-
74-
return { errors = errors, files = files }
75-
end
76-
7745
local function format_notification_msg(msg, spinner_idx)
7846
if spinner_idx == 0 or spinner_idx == nil then
7947
return string.format(" %s ", msg)
@@ -84,14 +52,14 @@ end
8452

8553
M.run = function()
8654
-- Closed over state
87-
local cmd = utils.get_tsc_cmd()
55+
local tsc = utils.find_tsc_bin()
8856
local errors = {}
8957
local files_with_errors = {}
9058
local notify_record
9159
local notify_called = false
9260
local spinner_idx = 0
9361

94-
if not utils.is_executable(cmd) then
62+
if not utils.is_executable(tsc) then
9563
vim.notify(
9664
format_notification_msg(
9765
"tsc was not available or found in your node_modules or $PATH. Please run install and try again."
@@ -140,12 +108,12 @@ M.run = function()
140108
end
141109

142110
local function on_stdout(_, output)
143-
local result = parse_tsc_output(output)
111+
local result = utils.parse_tsc_output(output)
144112

145113
errors = result.errors
146114
files_with_errors = result.files
147115

148-
set_qflist(errors)
116+
utils.set_qflist(errors, config.auto_open_qflist)
149117
end
150118

151119
local on_exit = function()
@@ -184,20 +152,18 @@ M.run = function()
184152
stdout_buffered = true,
185153
}
186154

187-
vim.fn.jobstart(cmd .. " " .. config.flags, opts)
155+
vim.fn.jobstart(tsc .. " " .. utils.parse_flags(config.flags), opts)
188156
end
189157

190158
function M.is_running()
191159
return is_running
192160
end
193161

194162
function M.setup(opts)
195-
config = vim.tbl_deep_extend("force", config, DEFAULT_CONFIG, opts or {})
163+
config = vim.tbl_extend("force", config, DEFAULT_CONFIG, opts or {})
196164

197165
vim.api.nvim_create_user_command("TSC", function()
198-
vim.cmd("silent cd %:h")
199166
M.run()
200-
vim.cmd("silent cd-")
201167
end, { desc = "Run `tsc` asynchronously and load the results into a qflist", force = true })
202168
end
203169

lua/tsc/utils.lua

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ M.is_executable = function(cmd)
44
return cmd and vim.fn.executable(cmd) == 1 or false
55
end
66

7-
M.get_tsc_cmd = function()
7+
M.find_tsc_bin = function()
88
local node_modules_tsc_binary = vim.fn.findfile("node_modules/.bin/tsc", ".;")
99

1010
if node_modules_tsc_binary ~= "" then
@@ -14,4 +14,88 @@ M.get_tsc_cmd = function()
1414
return "tsc"
1515
end
1616

17+
M.find_nearest_tsconfig = function()
18+
local tsconfig = vim.fn.findfile("tsconfig.json", ".;")
19+
20+
if tsconfig ~= "" then
21+
return tsconfig
22+
end
23+
24+
return nil
25+
end
26+
27+
M.parse_flags = function(flags)
28+
if type(flags) == "string" then
29+
return flags
30+
end
31+
32+
local parsed_flags = ""
33+
34+
for key, value in pairs(flags) do
35+
key = string.gsub(key, "%-%-", "")
36+
37+
if type(value) == "function" then
38+
value = value()
39+
end
40+
41+
if type(value) ~= "string" and type(value) ~= "boolean" then
42+
vim.notify(
43+
string.format(
44+
"Skipping flag '%s' because of an invalid value. Valid values include strings, booleans, or a function that returns a string or boolean.",
45+
key
46+
),
47+
vim.log.levels.ERROR
48+
)
49+
else
50+
if type(value) == "boolean" then
51+
if value == true then
52+
parsed_flags = parsed_flags .. string.format("--%s ", key)
53+
end
54+
else
55+
parsed_flags = parsed_flags .. string.format("--%s %s ", key, value)
56+
end
57+
end
58+
end
59+
60+
return parsed_flags
61+
end
62+
63+
M.parse_tsc_output = function(output)
64+
local errors = {}
65+
local files = {}
66+
67+
if output == nil then
68+
return { errors = errors, files = files }
69+
end
70+
71+
for _, line in ipairs(output) do
72+
local filename, lineno, colno, message = line:match("^(.+)%((%d+),(%d+)%)%s*:%s*(.+)$")
73+
if filename ~= nil then
74+
table.insert(errors, {
75+
filename = filename,
76+
lnum = tonumber(lineno),
77+
col = tonumber(colno),
78+
text = message,
79+
type = "E",
80+
})
81+
82+
if vim.tbl_contains(files, filename) == false then
83+
table.insert(files, filename)
84+
end
85+
end
86+
end
87+
88+
return { errors = errors, files = files }
89+
end
90+
91+
M.set_qflist = function(errors, auto_open)
92+
auto_open = auto_open or true
93+
94+
vim.fn.setqflist({}, "r", { title = "TSC", items = errors })
95+
96+
if #errors > 0 and auto_open then
97+
vim.cmd("copen")
98+
end
99+
end
100+
17101
return M

0 commit comments

Comments
 (0)