unguard 0.7.1 → 0.9.0

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 CHANGED
@@ -2,7 +2,13 @@
2
2
 
3
3
  Unguard your code. Defend against overdefensive AI-generated code.
4
4
 
5
- Built on [oxc-parser](https://oxc.rs).
5
+ Type-aware static analysis powered by the TypeScript compiler API.
6
+
7
+ If `??` is on a non-nullable type, you don't need it.
8
+
9
+ If `?.` is on a guaranteed object, it's noise.
10
+
11
+ unguard proves it with types.
6
12
 
7
13
  ## Install
8
14
 
@@ -19,40 +25,81 @@ npx unguard
19
25
  ## Usage
20
26
 
21
27
  ```bash
22
- unguard src/
23
- unguard src/ --strict # treat warnings as errors (CI)
24
- unguard src/ --filter no-any-cast # run a single rule
25
- unguard src/ --severity=error # only show errors
26
- unguard src/ --format=flat # one-line-per-diagnostic, grepable
27
- unguard src/ --format=flat | grep error
28
+ unguard src # scan files/directories
29
+ unguard src --config ./unguard.config.json # load config
30
+ unguard src --ignore '**/*.gen.ts' # add ignore globs
31
+ unguard src --filter no-any-cast # run a single rule
32
+ unguard src --rule duplicate-*=warning # override rule severity/policy
33
+ unguard src --rule category:cross-file=warning
34
+ unguard src --rule tag:safety=error
35
+ unguard src --severity=error,warning # show errors+warnings
36
+ unguard src --fail-on=error # fail only on errors
37
+ unguard src --format=flat # one-line-per-diagnostic, grepable
38
+ unguard src --format=flat | grep error
39
+ ```
40
+
41
+ Add `unguard` to your lint check, especially if code is written by AI.
42
+
43
+ ### Config
44
+
45
+ `unguard` automatically loads `./unguard.config.json` (or `./.unguardrc.json`). Use `--config <path>` to specify another file.
46
+
47
+ ```json
48
+ {
49
+ "paths": ["src", "apps/web/src"],
50
+ "ignore": ["**/*.gen.ts", "**/routeTree.gen.ts"],
51
+ "rules": {
52
+ "duplicate-*": "warning",
53
+ "category:cross-file": "warning",
54
+ "tag:safety": "error",
55
+ "no-ts-ignore": "error",
56
+ "prefer-*": "off"
57
+ },
58
+ "failOn": "error"
59
+ }
28
60
  ```
29
61
 
30
- Add `unguard` to your lint check.
62
+ `rules` values can be `off`, `info`, `warning`, or `error`.
63
+ Selectors support:
64
+ - exact rule id: `no-ts-ignore`
65
+ - wildcard: `duplicate-*`
66
+ - category: `category:cross-file`
67
+ - tag: `tag:safety`
68
+
69
+ ### Ignore behavior
70
+
71
+ `unguard` ignores:
72
+ - built-in: `node_modules`, `dist`, `.git`, declaration files (`*.d.ts`, `*.d.cts`, `*.d.mts`)
73
+ - generated files: `*.gen.*`, `*.generated.*`
74
+ - project `.gitignore`
75
+ - anything passed via `--ignore` or config `ignore`
31
76
 
32
77
  ### Exit codes
33
78
 
34
79
  | Code | Meaning |
35
80
  | ---- | ------- |
36
- | 0 | No issues |
37
- | 1 | Warnings or info only |
38
- | 2 | At least one error |
81
+ | 0 | No issues, or issues below `--fail-on` threshold |
82
+ | 1 | Failing diagnostics without errors |
83
+ | 2 | Failing diagnostics with at least one error |
39
84
 
40
- Use `--severity=error` in CI to only fail on errors:
85
+ Use `--fail-on=error` in CI to fail only on errors while still showing all diagnostics:
41
86
 
42
87
  ```bash
43
- unguard src/ --severity=error || exit 1
88
+ unguard src --fail-on=error
44
89
  ```
45
90
 
91
+ `--severity` filters display only. `--fail-on` evaluates all diagnostics after rule policy.
92
+
46
93
  ### Output formats
47
94
 
48
- **Grouped** (default) diagnostics grouped by file:
95
+ **Grouped** (default) -- diagnostics grouped by file:
49
96
 
50
97
  ```txt
51
98
  src/lib/probe.ts
52
99
  37:4 error Empty catch blocks hide failures... no-empty-catch
53
100
  ```
54
101
 
55
- **Flat** (`--format=flat`) one line per diagnostic, grepable:
102
+ **Flat** (`--format=flat`) -- one line per diagnostic, grepable:
56
103
 
57
104
  ```txt
58
105
  src/lib/probe.ts:37:4 error [no-empty-catch] Empty catch blocks hide failures...
@@ -69,26 +116,28 @@ src/lib/probe.ts:37:4 error [no-empty-catch] Empty catch blocks hide failures...
69
116
  | `no-type-assertion` | error | `x as unknown as T` |
70
117
  | `no-ts-ignore` | error | `@ts-ignore` / `@ts-expect-error` |
71
118
 
72
- ### Defensive code
119
+ ### Defensive code (type-aware)
120
+
121
+ These rules use the TypeScript type checker. Non-nullable types suppress the diagnostic; nullable types are flagged.
73
122
 
74
123
  | Rule | Severity | What it catches |
75
124
  | ---- | -------- | --------------- |
76
- | `no-optional-property-access` | info | `obj?.prop` |
77
- | `no-optional-element-access` | info | `obj?.[key]` |
78
- | `no-optional-call` | info | `fn?.()` |
79
- | `no-nullish-coalescing` | info | `x ?? fallback` |
80
- | `no-logical-or-fallback` | warning | `x \|\| fallback` |
125
+ | `no-optional-property-access` | warning | `obj?.prop` on a non-nullable type |
126
+ | `no-optional-element-access` | warning | `obj?.[key]` on a non-nullable type |
127
+ | `no-optional-call` | warning | `fn?.()` on a non-nullable type |
128
+ | `no-nullish-coalescing` | warning | `x ?? fallback` on a non-nullable type |
129
+ | `no-logical-or-fallback` | warning | `map.get(k) \|\| fallback` -- data-structure lookups where `??` is correct; `\|\|` on numeric types that swallow `0` |
81
130
  | `no-null-ternary-normalization` | warning | `x == null ? fallback : x` |
82
- | `no-non-null-assertion` | warning | `x!` |
131
+ | `no-non-null-assertion` | warning | `x!` on a nullable type without a local narrowing guard |
83
132
  | `no-double-negation-coercion` | info | `!!value` |
84
- | `no-redundant-existence-guard` | warning | `obj && obj.prop` |
133
+ | `no-redundant-existence-guard` | warning | `obj && obj.prop` on a non-nullable type |
85
134
 
86
135
  ### Error handling
87
136
 
88
137
  | Rule | Severity | What it catches |
89
138
  | ---- | -------- | --------------- |
90
- | `no-empty-catch` | error | `catch {}` with no body (comments count as annotation) |
91
- | `no-catch-return` | warning | `catch { return fallback }` without rethrowing |
139
+ | `no-empty-catch` | error | `catch {}` with no body and no comment |
140
+ | `no-catch-return` | warning | `catch { return fallback }` with no logging or rethrow |
92
141
  | `no-error-rewrap` | error | `throw new Error(e.message)` without `{ cause: e }` |
93
142
 
94
143
  ### Interface design
@@ -103,10 +152,10 @@ src/lib/probe.ts:37:4 error [no-empty-catch] Empty catch blocks hide failures...
103
152
 
104
153
  | Rule | Severity | What it catches |
105
154
  | ---- | -------- | --------------- |
106
- | `duplicate-type-declaration` | error | Same type shape in multiple files |
107
- | `duplicate-type-name` | error | Same exported type name, different shapes |
108
- | `duplicate-function-declaration` | error | Same function body in multiple files |
109
- | `duplicate-function-name` | error | Same exported function name, different bodies |
155
+ | `duplicate-type-declaration` | warning | Same type shape in multiple files |
156
+ | `duplicate-type-name` | warning | Same exported type name, different shapes |
157
+ | `duplicate-function-declaration` | warning | Same function body in multiple files |
158
+ | `duplicate-function-name` | warning | Same exported function name, different bodies |
110
159
  | `optional-arg-always-used` | warning | Optional param provided at every call site |
111
160
  | `explicit-null-arg` | warning | `fn(null)` / `fn(undefined)` to project functions |
112
161
 
@@ -132,10 +181,24 @@ file.ts:2:31 error Explicit `any` annotation ... (intentional escape hatch for u
132
181
  ## API
133
182
 
134
183
  ```typescript
135
- import { scan } from "unguard";
136
-
137
- const result = await scan({ paths: ["src/"] });
138
- for (const d of result.diagnostics) {
184
+ import { executeScan, scan } from "unguard";
185
+
186
+ const result = await scan({ paths: ["src/"] }); // raw diagnostics
187
+ const execution = await executeScan({
188
+ paths: ["src"],
189
+ ignore: ["**/*.gen.ts"],
190
+ rulePolicy: {
191
+ "duplicate-*": "warning",
192
+ "category:cross-file": "warning",
193
+ "tag:safety": "error",
194
+ "prefer-*": "off",
195
+ },
196
+ showSeverities: ["error", "warning"],
197
+ failOn: "error",
198
+ });
199
+
200
+ console.log(execution.exitCode);
201
+ for (const d of execution.visibleDiagnostics) {
139
202
  console.log(`${d.file}:${d.line} [${d.ruleId}] ${d.message}`);
140
203
  }
141
204
  ```