oxlint-tui 1.0.15 → 1.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/package.json +1 -1
- package/tui.js +86 -47
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
A lightweight, dependency-free Node.js Terminal User Interface (TUI) for browsing, toggling and visualizing [oxlint](https://github.com/oxc-project/oxc) rules and the number of warnings/errors they produce when toggled ON.
|
|
4
4
|
|
|
5
|
+

|
|
6
|
+
|
|
5
7
|
It automatically loads your local configuration to show you the status of the rules toggled in your project and allows you to toggle them by selecting a rule in the Rules pane and pressing <kbd>1</kbd>, <kbd>2</kbd>, or <kbd>3</kbd>. The config file is modified in real-time.
|
|
6
8
|
|
|
7
9
|
Pressing <kbd>r</kbd> will lint the project using "oxlint". Pressing <kbd>t</kbd> will lint include the oxlint-tsgolint package and lint the project using "oxlint --type-aware". The results are presented directly in the interface.
|
|
@@ -60,8 +62,9 @@ oxlint-tui
|
|
|
60
62
|
| **1** | Set selected rule to "off" |
|
|
61
63
|
| **2** | Set selected rule to "warn" |
|
|
62
64
|
| **3** | Set selected rule to "error" |
|
|
63
|
-
| **
|
|
64
|
-
| **
|
|
65
|
+
| **x** | Run the selected rule |
|
|
66
|
+
| **r** | Run all enabled rules with "oxlint" |
|
|
67
|
+
| **t** | Run all enabled rules with "-p oxlint-tsgolint@latest oxlint --type-aware" |
|
|
65
68
|
| **Enter** | Open Rule Documentation in Browser |
|
|
66
69
|
| **q** / **Esc** | Quit |
|
|
67
70
|
|
|
@@ -78,4 +81,4 @@ If you're willing and able, please feel free to [contribute to this project](htt
|
|
|
78
81
|
|
|
79
82
|
## License
|
|
80
83
|
|
|
81
|
-
MIT
|
|
84
|
+
MIT
|
package/package.json
CHANGED
package/tui.js
CHANGED
|
@@ -38,6 +38,7 @@ const KEY_MAP = {
|
|
|
38
38
|
q: { type: "EXIT" },
|
|
39
39
|
r: { type: "RUN_LINT" },
|
|
40
40
|
t: { type: "RUN_TYPE_AWARE_LINT" },
|
|
41
|
+
x: { type: "RUN_SINGLE_RULE" },
|
|
41
42
|
};
|
|
42
43
|
|
|
43
44
|
let state = {
|
|
@@ -81,19 +82,40 @@ function updateConfig(rule, newStatus) {
|
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
function runLint(
|
|
85
|
+
function runLint({ typeAware = false, rule = null } = {}) {
|
|
85
86
|
if (state.isLinting) return;
|
|
86
87
|
|
|
87
88
|
state.isLinting = true;
|
|
88
|
-
|
|
89
|
+
|
|
90
|
+
let ruleName = rule ? `${rule.scope}/${rule.value}` : null;
|
|
91
|
+
typeAware = typeAware || rule?.type_aware;
|
|
92
|
+
|
|
93
|
+
state.message = "Linting";
|
|
94
|
+
if (ruleName) state.message += ` [${ruleName}]`;
|
|
95
|
+
if (typeAware) state.message += " with --type-aware";
|
|
96
|
+
state.message += "...";
|
|
97
|
+
|
|
89
98
|
state.messageType = "info";
|
|
99
|
+
|
|
90
100
|
render();
|
|
91
101
|
|
|
92
102
|
const npxCmd = platform === "win32" ? "npx.cmd" : "npx";
|
|
93
103
|
|
|
94
|
-
const args =
|
|
95
|
-
|
|
96
|
-
|
|
104
|
+
const args = ["-q", "--yes", "--package", `oxlint@${OXLINT_VERSION}`];
|
|
105
|
+
|
|
106
|
+
if (typeAware) {
|
|
107
|
+
args.push("--package", `oxlint-tsgolint@${TSGOLINT_VERSION}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
args.push("--", "oxlint");
|
|
111
|
+
|
|
112
|
+
if (typeAware) {
|
|
113
|
+
args.push("--type-aware");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (ruleName) {
|
|
117
|
+
args.push("-A", "all", "-D", ruleName);
|
|
118
|
+
}
|
|
97
119
|
|
|
98
120
|
const child = spawn(npxCmd, args);
|
|
99
121
|
|
|
@@ -116,14 +138,17 @@ function runLint(type_aware) {
|
|
|
116
138
|
);
|
|
117
139
|
|
|
118
140
|
if (summaryMatch) {
|
|
119
|
-
state.message = summaryMatch[0];
|
|
120
141
|
const errors = parseInt(summaryMatch[2]);
|
|
142
|
+
state.message = ruleName
|
|
143
|
+
? `[${ruleName}] Found ${errors} issue${errors === 1 ? "" : "s"}`
|
|
144
|
+
: (state.message = summaryMatch[0]);
|
|
121
145
|
state.messageType = errors > 0 ? "error" : "warn";
|
|
122
146
|
} else if (
|
|
123
147
|
stdoutData.toLowerCase().includes("finished") ||
|
|
124
148
|
(code === 0 && stdoutData.length < 200)
|
|
125
149
|
) {
|
|
126
150
|
state.message = "Linting passed! 0 issues found.";
|
|
151
|
+
if (ruleName) state.message = `[${ruleName}] ${state.message}`;
|
|
127
152
|
state.messageType = "success";
|
|
128
153
|
} else {
|
|
129
154
|
const cleanError = stderrData
|
|
@@ -145,7 +170,9 @@ function runLint(type_aware) {
|
|
|
145
170
|
});
|
|
146
171
|
}
|
|
147
172
|
|
|
148
|
-
function
|
|
173
|
+
function execute(action) {
|
|
174
|
+
if (!action) return;
|
|
175
|
+
|
|
149
176
|
const {
|
|
150
177
|
categories,
|
|
151
178
|
rulesByCategory,
|
|
@@ -160,10 +187,33 @@ function reducer(state, action) {
|
|
|
160
187
|
const catViewHeight = viewHeight - statsHeight;
|
|
161
188
|
|
|
162
189
|
switch (action.type) {
|
|
190
|
+
case "EXIT":
|
|
191
|
+
exitAltScreen();
|
|
192
|
+
exit(0);
|
|
193
|
+
return;
|
|
194
|
+
|
|
195
|
+
case "RUN_LINT":
|
|
196
|
+
runLint();
|
|
197
|
+
return;
|
|
198
|
+
|
|
199
|
+
case "RUN_SINGLE_RULE": {
|
|
200
|
+
const rule = currentRules[selectedRuleIdx];
|
|
201
|
+
if (rule) runLint({ rule });
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
case "OPEN_DOCS": {
|
|
206
|
+
if (activePane === 1) {
|
|
207
|
+
const rule = currentRules[selectedRuleIdx];
|
|
208
|
+
if (rule) openUrl(rule.docs_url || rule.url);
|
|
209
|
+
}
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
163
213
|
case "SET_STATUS": {
|
|
164
|
-
if (activePane !== 1) return
|
|
214
|
+
if (activePane !== 1) return;
|
|
165
215
|
const rule = currentRules[selectedRuleIdx];
|
|
166
|
-
if (!rule) return
|
|
216
|
+
if (!rule) return;
|
|
167
217
|
updateConfig(rule, action.value);
|
|
168
218
|
const updatedRules = [...currentRules];
|
|
169
219
|
updatedRules[selectedRuleIdx] = {
|
|
@@ -171,7 +221,7 @@ function reducer(state, action) {
|
|
|
171
221
|
configStatus: action.value,
|
|
172
222
|
isActive: action.value === "error" || action.value === "warn",
|
|
173
223
|
};
|
|
174
|
-
|
|
224
|
+
state = {
|
|
175
225
|
...state,
|
|
176
226
|
message: `Rule '${rule.value}' set to: ${action.value}`,
|
|
177
227
|
messageType: "info",
|
|
@@ -180,19 +230,29 @@ function reducer(state, action) {
|
|
|
180
230
|
[currentCat]: updatedRules,
|
|
181
231
|
},
|
|
182
232
|
};
|
|
233
|
+
render();
|
|
234
|
+
return;
|
|
183
235
|
}
|
|
236
|
+
|
|
184
237
|
case "MOVE_RIGHT":
|
|
185
|
-
if (activePane !== 1)
|
|
186
|
-
|
|
238
|
+
if (activePane !== 1) {
|
|
239
|
+
state = { ...state, activePane: activePane + 1 };
|
|
240
|
+
render();
|
|
241
|
+
}
|
|
242
|
+
return;
|
|
187
243
|
|
|
188
244
|
case "MOVE_LEFT":
|
|
189
|
-
if (activePane !== 0)
|
|
190
|
-
|
|
245
|
+
if (activePane !== 0) {
|
|
246
|
+
state = { ...state, activePane: activePane - 1 };
|
|
247
|
+
render();
|
|
248
|
+
}
|
|
249
|
+
return;
|
|
250
|
+
|
|
191
251
|
case "MOVE_UP":
|
|
192
252
|
if (activePane === 0) {
|
|
193
253
|
const nextIdx =
|
|
194
254
|
selectedCatIdx === 0 ? categories.length - 1 : selectedCatIdx - 1;
|
|
195
|
-
|
|
255
|
+
state = {
|
|
196
256
|
...state,
|
|
197
257
|
selectedCatIdx: nextIdx,
|
|
198
258
|
selectedRuleIdx: 0,
|
|
@@ -202,18 +262,20 @@ function reducer(state, action) {
|
|
|
202
262
|
} else if (activePane === 1) {
|
|
203
263
|
const nextIdx =
|
|
204
264
|
selectedRuleIdx === 0 ? currentRules.length - 1 : selectedRuleIdx - 1;
|
|
205
|
-
|
|
265
|
+
state = {
|
|
206
266
|
...state,
|
|
207
267
|
selectedRuleIdx: nextIdx,
|
|
208
268
|
scrollRule: updateScroll(nextIdx, state.scrollRule, viewHeight),
|
|
209
269
|
};
|
|
210
270
|
}
|
|
211
|
-
|
|
271
|
+
render();
|
|
272
|
+
return;
|
|
273
|
+
|
|
212
274
|
case "MOVE_DOWN":
|
|
213
275
|
if (activePane === 0) {
|
|
214
276
|
const nextIdx =
|
|
215
277
|
selectedCatIdx === categories.length - 1 ? 0 : selectedCatIdx + 1;
|
|
216
|
-
|
|
278
|
+
state = {
|
|
217
279
|
...state,
|
|
218
280
|
selectedCatIdx: nextIdx,
|
|
219
281
|
selectedRuleIdx: 0,
|
|
@@ -223,15 +285,14 @@ function reducer(state, action) {
|
|
|
223
285
|
} else if (activePane === 1) {
|
|
224
286
|
const nextIdx =
|
|
225
287
|
selectedRuleIdx === currentRules.length - 1 ? 0 : selectedRuleIdx + 1;
|
|
226
|
-
|
|
288
|
+
state = {
|
|
227
289
|
...state,
|
|
228
290
|
selectedRuleIdx: nextIdx,
|
|
229
291
|
scrollRule: updateScroll(nextIdx, state.scrollRule, viewHeight),
|
|
230
292
|
};
|
|
231
293
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
return state;
|
|
294
|
+
render();
|
|
295
|
+
return;
|
|
235
296
|
}
|
|
236
297
|
}
|
|
237
298
|
|
|
@@ -292,7 +353,7 @@ function loadRules() {
|
|
|
292
353
|
config = JSON.parse(
|
|
293
354
|
stripJsonComments(fs.readFileSync(configPath, "utf8")),
|
|
294
355
|
);
|
|
295
|
-
} catch (e) {
|
|
356
|
+
} catch (e) {}
|
|
296
357
|
}
|
|
297
358
|
|
|
298
359
|
const map = {};
|
|
@@ -553,7 +614,7 @@ function render() {
|
|
|
553
614
|
? `Config: ${state.configPath}`
|
|
554
615
|
: "No config loaded";
|
|
555
616
|
buffer.push(
|
|
556
|
-
`\x1b[${rows - 1};2H${COLORS.dim}Arrows/HJKL: Nav | 1-3: Status | R: Lint | T: Lint with --type-aware | Enter: Docs | Q: Quit | ${footerConfig}${COLORS.reset}`,
|
|
617
|
+
`\x1b[${rows - 1};2H${COLORS.dim}Arrows/HJKL: Nav | 1-3: Status | R: Lint | T: Lint with --type-aware | X: Run rule | Enter: Docs | Q: Quit | ${footerConfig}${COLORS.reset}`,
|
|
557
618
|
);
|
|
558
619
|
write(buffer.join(""));
|
|
559
620
|
}
|
|
@@ -567,29 +628,7 @@ stdin.on("keypress", (_, key) => {
|
|
|
567
628
|
(key.ctrl && key.name === "c"
|
|
568
629
|
? { type: "EXIT" }
|
|
569
630
|
: KEY_MAP[key.sequence] || null);
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
if (action.type === "EXIT") {
|
|
573
|
-
exitAltScreen();
|
|
574
|
-
exit(0);
|
|
575
|
-
}
|
|
576
|
-
if (action.type === "RUN_LINT") {
|
|
577
|
-
runLint(false);
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
if (action.type === "RUN_TYPE_AWARE_LINT") {
|
|
581
|
-
runLint(true);
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
584
|
-
if (action.type === "OPEN_DOCS" && state.activePane === 1) {
|
|
585
|
-
const currentCat = state.categories[state.selectedCatIdx];
|
|
586
|
-
const rule = state.rulesByCategory[currentCat]?.[state.selectedRuleIdx];
|
|
587
|
-
if (rule) openUrl(rule.docs_url || rule.url);
|
|
588
|
-
return;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
state = reducer(state, action);
|
|
592
|
-
render();
|
|
631
|
+
execute(action);
|
|
593
632
|
});
|
|
594
633
|
|
|
595
634
|
stdout.on("resize", render);
|