oxlint-tui 1.0.15 → 1.0.17

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.
Files changed (3) hide show
  1. package/README.md +5 -5
  2. package/package.json +1 -1
  3. package/tui.js +91 -47
package/README.md CHANGED
@@ -2,9 +2,9 @@
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
- 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.
5
+ ![oxlint-tui-demo](https://github.com/user-attachments/assets/cefd05a6-8bda-4622-8adf-008402f856b4)
6
6
 
7
- 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.
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.
8
8
 
9
9
  **NOTE**: At the moment, comments will be erased from your configuration file when adding or toggling rules.
10
10
 
@@ -60,8 +60,8 @@ oxlint-tui
60
60
  | **1** | Set selected rule to "off" |
61
61
  | **2** | Set selected rule to "warn" |
62
62
  | **3** | Set selected rule to "error" |
63
- | **r** | Run the linter with "oxlint" |
64
- | **t** | Run the linter with "-p oxlint-tsgolint@latest oxlint --type-aware" |
63
+ | **x** | Run "oxlint" with the selected rule |
64
+ | **r** | Run "oxlint with all enabled rules |
65
65
  | **Enter** | Open Rule Documentation in Browser |
66
66
  | **q** / **Esc** | Quit |
67
67
 
@@ -78,4 +78,4 @@ If you're willing and able, please feel free to [contribute to this project](htt
78
78
 
79
79
  ## License
80
80
 
81
- MIT
81
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-tui",
3
- "version": "1.0.15",
3
+ "version": "1.0.17",
4
4
  "description": "A Node TUI Oxlint rules and configuration browser",
5
5
  "type": "module",
6
6
  "bin": {
package/tui.js CHANGED
@@ -37,7 +37,7 @@ const KEY_MAP = {
37
37
  3: { type: "SET_STATUS", value: "error" },
38
38
  q: { type: "EXIT" },
39
39
  r: { type: "RUN_LINT" },
40
- t: { type: "RUN_TYPE_AWARE_LINT" },
40
+ x: { type: "RUN_SINGLE_RULE" },
41
41
  };
42
42
 
43
43
  let state = {
@@ -81,19 +81,40 @@ function updateConfig(rule, newStatus) {
81
81
  }
82
82
  }
83
83
 
84
- function runLint(type_aware) {
84
+ function runLint({ typeAware = false, rule = null } = {}) {
85
85
  if (state.isLinting) return;
86
86
 
87
87
  state.isLinting = true;
88
- state.message = type_aware ? "Linting with --type-aware..." : "Linting...";
88
+
89
+ let ruleName = rule ? `${rule.scope}/${rule.value}` : null;
90
+ typeAware = typeAware || rule?.type_aware;
91
+
92
+ state.message = "Linting";
93
+ if (ruleName) state.message += ` [${ruleName}]`;
94
+ if (typeAware) state.message += " with --type-aware";
95
+ state.message += "...";
96
+
89
97
  state.messageType = "info";
98
+
90
99
  render();
91
100
 
92
101
  const npxCmd = platform === "win32" ? "npx.cmd" : "npx";
93
102
 
94
- const args = type_aware
95
- ? ["-q", "--yes", "--package", `oxlint@${OXLINT_VERSION}`, "--package", `oxlint-tsgolint@${TSGOLINT_VERSION}`, "--", "oxlint", "--type-aware",]
96
- : ["-q", "--yes", "--package", `oxlint@${OXLINT_VERSION}`, "--", "oxlint"];
103
+ const args = ["-q", "--yes", "--package", `oxlint@${OXLINT_VERSION}`];
104
+
105
+ if (typeAware) {
106
+ args.push("--package", `oxlint-tsgolint@${TSGOLINT_VERSION}`);
107
+ }
108
+
109
+ args.push("--", "oxlint");
110
+
111
+ if (typeAware) {
112
+ args.push("--type-aware");
113
+ }
114
+
115
+ if (ruleName) {
116
+ args.push("-A", "all", "-D", ruleName);
117
+ }
97
118
 
98
119
  const child = spawn(npxCmd, args);
99
120
 
@@ -116,14 +137,17 @@ function runLint(type_aware) {
116
137
  );
117
138
 
118
139
  if (summaryMatch) {
119
- state.message = summaryMatch[0];
120
140
  const errors = parseInt(summaryMatch[2]);
141
+ state.message = ruleName
142
+ ? `[${ruleName}] Found ${errors} issue${errors === 1 ? "" : "s"}`
143
+ : (state.message = summaryMatch[0]);
121
144
  state.messageType = errors > 0 ? "error" : "warn";
122
145
  } else if (
123
146
  stdoutData.toLowerCase().includes("finished") ||
124
147
  (code === 0 && stdoutData.length < 200)
125
148
  ) {
126
149
  state.message = "Linting passed! 0 issues found.";
150
+ if (ruleName) state.message = `[${ruleName}] ${state.message}`;
127
151
  state.messageType = "success";
128
152
  } else {
129
153
  const cleanError = stderrData
@@ -145,7 +169,9 @@ function runLint(type_aware) {
145
169
  });
146
170
  }
147
171
 
148
- function reducer(state, action) {
172
+ function execute(action) {
173
+ if (!action) return;
174
+
149
175
  const {
150
176
  categories,
151
177
  rulesByCategory,
@@ -160,10 +186,39 @@ function reducer(state, action) {
160
186
  const catViewHeight = viewHeight - statsHeight;
161
187
 
162
188
  switch (action.type) {
189
+ case "EXIT":
190
+ exitAltScreen();
191
+ exit(0);
192
+ return;
193
+
194
+ case "RUN_LINT":
195
+ const allRules = Object.values(state.rulesByCategory).flat();
196
+ const hasActiveTypeAwareRule = allRules.some(
197
+ (rule) => rule.isActive && rule.type_aware === true
198
+ );
199
+
200
+ runLint({ typeAware: hasActiveTypeAwareRule });
201
+ runLint();
202
+ return;
203
+
204
+ case "RUN_SINGLE_RULE": {
205
+ const rule = currentRules[selectedRuleIdx];
206
+ if (rule) runLint({ rule });
207
+ return;
208
+ }
209
+
210
+ case "OPEN_DOCS": {
211
+ if (activePane === 1) {
212
+ const rule = currentRules[selectedRuleIdx];
213
+ if (rule) openUrl(rule.docs_url || rule.url);
214
+ }
215
+ return;
216
+ }
217
+
163
218
  case "SET_STATUS": {
164
- if (activePane !== 1) return state;
219
+ if (activePane !== 1) return;
165
220
  const rule = currentRules[selectedRuleIdx];
166
- if (!rule) return state;
221
+ if (!rule) return;
167
222
  updateConfig(rule, action.value);
168
223
  const updatedRules = [...currentRules];
169
224
  updatedRules[selectedRuleIdx] = {
@@ -171,7 +226,7 @@ function reducer(state, action) {
171
226
  configStatus: action.value,
172
227
  isActive: action.value === "error" || action.value === "warn",
173
228
  };
174
- return {
229
+ state = {
175
230
  ...state,
176
231
  message: `Rule '${rule.value}' set to: ${action.value}`,
177
232
  messageType: "info",
@@ -180,19 +235,29 @@ function reducer(state, action) {
180
235
  [currentCat]: updatedRules,
181
236
  },
182
237
  };
238
+ render();
239
+ return;
183
240
  }
241
+
184
242
  case "MOVE_RIGHT":
185
- if (activePane !== 1) return { ...state, activePane: activePane + 1 };
186
- return state;
243
+ if (activePane !== 1) {
244
+ state = { ...state, activePane: activePane + 1 };
245
+ render();
246
+ }
247
+ return;
187
248
 
188
249
  case "MOVE_LEFT":
189
- if (activePane !== 0) return { ...state, activePane: activePane - 1 };
190
- return state;
250
+ if (activePane !== 0) {
251
+ state = { ...state, activePane: activePane - 1 };
252
+ render();
253
+ }
254
+ return;
255
+
191
256
  case "MOVE_UP":
192
257
  if (activePane === 0) {
193
258
  const nextIdx =
194
259
  selectedCatIdx === 0 ? categories.length - 1 : selectedCatIdx - 1;
195
- return {
260
+ state = {
196
261
  ...state,
197
262
  selectedCatIdx: nextIdx,
198
263
  selectedRuleIdx: 0,
@@ -202,18 +267,20 @@ function reducer(state, action) {
202
267
  } else if (activePane === 1) {
203
268
  const nextIdx =
204
269
  selectedRuleIdx === 0 ? currentRules.length - 1 : selectedRuleIdx - 1;
205
- return {
270
+ state = {
206
271
  ...state,
207
272
  selectedRuleIdx: nextIdx,
208
273
  scrollRule: updateScroll(nextIdx, state.scrollRule, viewHeight),
209
274
  };
210
275
  }
211
- return state;
276
+ render();
277
+ return;
278
+
212
279
  case "MOVE_DOWN":
213
280
  if (activePane === 0) {
214
281
  const nextIdx =
215
282
  selectedCatIdx === categories.length - 1 ? 0 : selectedCatIdx + 1;
216
- return {
283
+ state = {
217
284
  ...state,
218
285
  selectedCatIdx: nextIdx,
219
286
  selectedRuleIdx: 0,
@@ -223,15 +290,14 @@ function reducer(state, action) {
223
290
  } else if (activePane === 1) {
224
291
  const nextIdx =
225
292
  selectedRuleIdx === currentRules.length - 1 ? 0 : selectedRuleIdx + 1;
226
- return {
293
+ state = {
227
294
  ...state,
228
295
  selectedRuleIdx: nextIdx,
229
296
  scrollRule: updateScroll(nextIdx, state.scrollRule, viewHeight),
230
297
  };
231
298
  }
232
- return state;
233
- default:
234
- return state;
299
+ render();
300
+ return;
235
301
  }
236
302
  }
237
303
 
@@ -553,7 +619,7 @@ function render() {
553
619
  ? `Config: ${state.configPath}`
554
620
  : "No config loaded";
555
621
  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}`,
622
+ `\x1b[${rows - 1};2H${COLORS.dim}Arrows/HJKL: Nav | 1-3: Status | R: Lint | X: Run rule | Enter: Docs | Q: Quit | ${footerConfig}${COLORS.reset}`,
557
623
  );
558
624
  write(buffer.join(""));
559
625
  }
@@ -567,29 +633,7 @@ stdin.on("keypress", (_, key) => {
567
633
  (key.ctrl && key.name === "c"
568
634
  ? { type: "EXIT" }
569
635
  : KEY_MAP[key.sequence] || null);
570
- if (!action) return;
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();
636
+ execute(action);
593
637
  });
594
638
 
595
639
  stdout.on("resize", render);