slop-police 0.1.1 → 0.2.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.
Files changed (2) hide show
  1. package/README.md +77 -2
  2. package/package.json +5 -5
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # slop-police
2
2
 
3
- A fast, type-aware linter for TypeScript that catches the dead code and needless indirection AI coding agents tend to leave behind: methods nobody calls, object keys nobody reads, getters and aliases that only rename something.
3
+ A fast, type-aware linter for TypeScript that catches the dead code and needless indirection AI coding agents tend to leave behind: methods nobody calls, object keys nobody reads, getters, aliases and wrappers that only rename or forward something, helpers copied instead of reused, comments that only promise future work, and errors swallowed or flattened into strings.
4
4
 
5
5
  It runs the TypeScript 7 compiler in-process, so rules resolve symbols the same way `tsc` does, across all of your tsconfig projects at once.
6
6
 
@@ -31,7 +31,12 @@ slop-police reads `slop-police.json` from the current directory:
31
31
  "no-identity-type-alias": "warn",
32
32
  "no-pass-through-getter": "error",
33
33
  "no-redundant-value-alias": "error",
34
- "no-single-string-cn": "off"
34
+ "no-single-string-cn": "off",
35
+ "no-pass-through-wrapper": "error",
36
+ "no-duplicate-function": "warn",
37
+ "no-placeholder-comment": "error",
38
+ "no-promise-default-fallback": "error",
39
+ "no-stringified-unknown-error": "warn"
35
40
  }
36
41
  }
37
42
  ```
@@ -91,6 +96,24 @@ class Store {
91
96
  }
92
97
  ```
93
98
 
99
+ ### no-pass-through-wrapper
100
+
101
+ A function or method whose body only returns a call (awaited or not) to a named function, passing its own parameters on as they are.
102
+
103
+ ```ts
104
+ function loadUser(id: string) { // Function loadUser only forwards to api.loadUser
105
+ return api.loadUser(id);
106
+ }
107
+ ```
108
+
109
+ It uses types to skip wrappers that do something:
110
+
111
+ - the shape is required: a method of a typed object literal (a `Proxy` handler, a Worker's `fetch`), a method that implements or overrides an inherited member, or a function assigned to a variable with a declared type. An override that only calls `super`'s method of the same name is still reported.
112
+ - the wrapper adds types: type parameters, overloads, or parameter and return types declared differently from the callee's, like `t(key: TranslationKey)` around `translate(key: string)`.
113
+ - it delegates to a private or protected member, like `get(key) { return this.items.get(key); }`.
114
+
115
+ Calls on computed values (`getDay(date).getTime()`, `/^\s*$/.test(text)`) aren't forwarding, so they don't count.
116
+
94
117
  ### no-redundant-value-alias
95
118
 
96
119
  A local `const` that only renames another `const`, when using the original directly means the same thing.
@@ -111,6 +134,56 @@ function render() {
111
134
  <div className={cn("flex gap-2")} /> // Use the class string directly instead of wrapping it in cn()
112
135
  ```
113
136
 
137
+ ### no-placeholder-comment
138
+
139
+ A comment that only promises future work instead of describing the code: "add more validation", "handle more cases", "can be extended in the future", "extend this logic", "customize this behavior", "future enhancement", "implement … here". Ordinary TODOs don't match.
140
+
141
+ ```ts
142
+ function parse(input: string) {
143
+ // Add more validation here
144
+ return JSON.parse(input);
145
+ }
146
+ ```
147
+
148
+ ### no-duplicate-function
149
+
150
+ A function whose body has the same shape as functions in at least two other files: a helper that was rewritten instead of reused. Shapes ignore the names of parameters and locals and the values of literals, but not the names of what the body calls or reads. Bodies with fewer than 2 statements or 16 syntax nodes, and functions in test files, don't count. Option: `minFiles` (default `3`, at least `2`), the number of files a shape has to appear in.
151
+
152
+ ```ts
153
+ // users.ts, posts.ts and teams.ts each have:
154
+ export async function loadUsers(id: string) {
155
+ const response = await fetchJson("/users/" + id);
156
+ if (!response.ok) throw new Error("failed to load users");
157
+ return response.data;
158
+ }
159
+ // Function loadUsers has the same body shape as loadPosts (posts.ts:1), loadTeams (teams.ts:1)
160
+ ```
161
+
162
+ ### no-promise-default-fallback
163
+
164
+ A promise `.catch()` handler that hides the rejection: an empty handler, or one that only returns `null`, `undefined`, `false`, `0`, `""`, `[]` or `{}`, optionally after `console.*` or `logger.*` calls. Only `.catch` on promises and other thenables counts.
165
+
166
+ ```ts
167
+ const config = await fetchConfig().catch(() => null); // Promise rejection is replaced with null
168
+ navigator.clipboard.writeText(text).catch(() => {}); // Promise rejection is swallowed by an empty catch handler
169
+ const enabled = await fetchFlag().catch((error) => { // Promise rejection is logged and replaced with false
170
+ console.error("flag load failed", error);
171
+ return false;
172
+ });
173
+ ```
174
+
175
+ ### no-stringified-unknown-error
176
+
177
+ `error instanceof Error ? error.message : String(error)` (in either order), which flattens a caught value into a string and drops its type, stack and cause. A plain `instanceof Error` check counts when the variable is named `error`, `err`, `e` or `cause`; an `||` of `instanceof` checks counts for any name.
178
+
179
+ ```ts
180
+ try {
181
+ await sendMessage(chatId, text);
182
+ } catch (error) {
183
+ return { ok: false, error: error instanceof Error ? error.message : String(error) }; // Caught value error is flattened into a string
184
+ }
185
+ ```
186
+
114
187
  ## Ignoring findings
115
188
 
116
189
  A `// slop-police-ignore` comment directly above a statement, class member or object property silences findings in it, including everything nested inside. List rule names to silence only those, and add a reason after `--`:
@@ -125,6 +198,8 @@ class Store {
125
198
  export const legacyLinks = { a: "/a", b: "/b" };
126
199
  ```
127
200
 
201
+ For `no-placeholder-comment`, put the ignore comment on the line directly above the reported comment.
202
+
128
203
  ## Requirements
129
204
 
130
205
  macOS or Linux on arm64 or x64 (on Windows, use WSL). The linter bundles its own TypeScript 7 compiler, so your tsconfigs need to work with TypeScript 7.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slop-police",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Type-aware TypeScript linter for the dead code and needless indirection AI agents leave behind",
5
5
  "keywords": [
6
6
  "typescript",
@@ -28,9 +28,9 @@
28
28
  "node": ">=18"
29
29
  },
30
30
  "optionalDependencies": {
31
- "slop-police-darwin-arm64": "0.1.1",
32
- "slop-police-darwin-x64": "0.1.1",
33
- "slop-police-linux-arm64": "0.1.1",
34
- "slop-police-linux-x64": "0.1.1"
31
+ "slop-police-darwin-arm64": "0.2.0",
32
+ "slop-police-darwin-x64": "0.2.0",
33
+ "slop-police-linux-arm64": "0.2.0",
34
+ "slop-police-linux-x64": "0.2.0"
35
35
  }
36
36
  }