slop-police 0.3.0 → 0.3.2

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
@@ -6,17 +6,19 @@
6
6
  A type-aware TypeScript linter for the dead code and needless indirection AI agents leave behind.
7
7
  </p>
8
8
 
9
- | | pnpm | npm |
10
- | --- | --- | --- |
11
- | Add to the project | `pnpm add -D slop-police` | `npm i -D slop-police` |
12
- | Run | `pnpm exec slop-police` | `npx slop-police` |
13
- | Run once, no install | `pnpm dlx slop-police` | `npx slop-police` |
9
+ ```sh
10
+ pnpm add slop-police
11
+ # or
12
+ npm i slop-police
13
+ ```
14
+
15
+ Run it with `pnpm exec slop-police` or `npx slop-police`.
14
16
 
15
17
  Example output:
16
18
 
17
19
  ```
18
- packages/frontend/src/store/deck-store.ts:42:3 error unused-methods Method DeckStore.reset is never used
19
- packages/api/src/shared/links/links.ts:30:3 error unused-object-keys links.privacyPolicy is never read
20
+ deck-store.ts:42:3 error unused-methods Method DeckStore.reset is never used
21
+ links.ts:30:3 error unused-object-keys links.privacyPolicy is never read
20
22
 
21
23
  2 errors, 0 warnings
22
24
  ```
@@ -40,7 +42,6 @@ slop-police reads `slop-police.json` from the current directory:
40
42
  "no-pass-through-wrapper": "error",
41
43
  "no-duplicate-function": "warn",
42
44
  "no-placeholder-comment": "error",
43
- "no-promise-default-fallback": "error",
44
45
  "no-stringified-unknown-error": "warn"
45
46
  }
46
47
  }
@@ -165,19 +166,6 @@ export async function loadUsers(id: string) {
165
166
  // Function loadUsers has the same body shape as loadPosts (posts.ts:1), loadTeams (teams.ts:1)
166
167
  ```
167
168
 
168
- ### no-promise-default-fallback
169
-
170
- 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.
171
-
172
- ```ts
173
- const config = await fetchConfig().catch(() => null); // Promise rejection is replaced with null
174
- navigator.clipboard.writeText(text).catch(() => {}); // Promise rejection is swallowed by an empty catch handler
175
- const enabled = await fetchFlag().catch((error) => { // Promise rejection is logged and replaced with false
176
- console.error("flag load failed", error);
177
- return false;
178
- });
179
- ```
180
-
181
169
  ### no-stringified-unknown-error
182
170
 
183
171
  `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. A function that does nothing but this conversion, like `getErrorMessage(error)`, is where it belongs and isn't reported; use it instead of repeating the expression.
@@ -270,7 +258,7 @@ export const noRepeatedUrl = defineRule({
270
258
  });
271
259
  ```
272
260
 
273
- Test rules with `lint` from `slop-police/testing`, which runs a rule alone on a source string (or on files, given an object of paths to contents) in a strict TypeScript project:
261
+ Test rules with `lint` from `slop-police/testing`, which runs a rule alone on a source string (as `index.ts`) or on files (an object of paths to contents) in a strict TypeScript project:
274
262
 
275
263
  ```ts
276
264
  import { lint } from "slop-police/testing";
@@ -278,12 +266,12 @@ import { noDirectFetch } from "./slop-rules.ts";
278
266
 
279
267
  test("no-direct-fetch", () => {
280
268
  expect(lint(noDirectFetch, `fetch("/users");`)).toEqual([
281
- { line: 1, column: 1, message: "Use the API client instead of fetch()" },
269
+ { file: "index.ts", line: 1, column: 1, message: "Use the API client instead of fetch()" },
282
270
  ]);
283
271
  });
284
272
  ```
285
273
 
286
- Plugins need the npm launcher (`npx slop-police` or `pnpm exec slop-police`), which runs them in Node while the linter serves ASTs and types. TypeScript plugins need Node.js 22.18 or later, and published plugin packages must ship JavaScript.
274
+ Plugins need the npm launcher (`npx slop-police` or `pnpm exec slop-police`), which runs them in Node while the linter serves ASTs and types. Published plugin packages must ship JavaScript, since Node doesn't run TypeScript from `node_modules`.
287
275
 
288
276
  ## Ignoring findings
289
277
 
@@ -303,7 +291,7 @@ For `no-placeholder-comment`, put the ignore comment on the line directly above
303
291
 
304
292
  ## Requirements
305
293
 
306
- 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.
294
+ macOS or Linux on arm64 or x64 (on Windows, use WSL), and Node.js 22.18 or later. The linter bundles its own TypeScript 7 compiler, so your tsconfigs need to work with TypeScript 7.
307
295
 
308
296
  ## License
309
297
 
package/lib/runner.js CHANGED
@@ -38,7 +38,7 @@ async function loadPlugins(root, plugins) {
38
38
  try {
39
39
  exports = await import(resolvePlugin(root, plugin));
40
40
  } catch (error) {
41
- throw new LintError(`Unable to load plugin ${plugin}: ${error.message}${loadHint(error, plugin)}`, 2);
41
+ throw new LintError(`Unable to load plugin ${plugin}: ${error.message}`, 2);
42
42
  }
43
43
  const found = Object.values(exports).flat().filter(isRule);
44
44
  if (found.length === 0) {
@@ -62,13 +62,6 @@ function resolvePlugin(root, plugin) {
62
62
  return pathToFileURL(createRequire(path.join(root, "package.json")).resolve(plugin)).href;
63
63
  }
64
64
 
65
- function loadHint(error, plugin) {
66
- if (error.code === "ERR_UNKNOWN_FILE_EXTENSION" && /\.[cm]?tsx?$/.test(plugin)) {
67
- return " (TypeScript plugins need Node.js 22.18 or later)";
68
- }
69
- return "";
70
- }
71
-
72
65
  // lint runs the built-in rules enabled in root's config in the binary and the
73
66
  // enabled ones among rules here, and returns the binary's report: {results,
74
67
  // output, exitCode}. It throws a LintError when the run fails.
package/lib/testing.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import type { Rule, RuleOptions } from "./index.js";
2
2
 
3
3
  export interface Finding {
4
+ /** The path relative to the project: index.ts for a source string. */
5
+ file: string;
4
6
  /** 1-based. */
5
7
  line: number;
6
8
  /** 1-based, in UTF-16 code units. */
@@ -8,14 +10,8 @@ export interface Finding {
8
10
  message: string;
9
11
  }
10
12
 
11
- /** Runs rule alone on source, as index.ts in a strict TypeScript project. */
12
- export declare function lint(rule: Rule<any>, source: string, options?: RuleOptions): Finding[];
13
13
  /**
14
- * Runs rule alone on files (paths to contents), in a strict TypeScript project
15
- * unless files has a tsconfig.json.
14
+ * Runs rule alone on source (as index.ts) or on files (paths to contents), in
15
+ * a strict TypeScript project unless files has a tsconfig.json.
16
16
  */
17
- export declare function lint(
18
- rule: Rule<any>,
19
- files: Record<string, string>,
20
- options?: RuleOptions,
21
- ): (Finding & { file: string })[];
17
+ export declare function lint(rule: Rule<any>, source: string | Record<string, string>, options?: RuleOptions): Finding[];
package/lib/testing.js CHANGED
@@ -17,9 +17,9 @@ const tsconfig = {
17
17
  include: ["**/*.ts", "**/*.tsx"],
18
18
  };
19
19
 
20
- // lint runs rule alone on source (index.ts) or on files (paths to contents,
21
- // with a default strict tsconfig.json unless one is given) and returns the
22
- // findings, with the file of each when given files.
20
+ // lint runs rule alone on source (as index.ts) or on files (paths to
21
+ // contents, with a default strict tsconfig.json unless one is given) and
22
+ // returns its findings.
23
23
  export function lint(rule, source, options) {
24
24
  const files = typeof source === "string" ? { "index.ts": source } : source;
25
25
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "slop-police-test-"));
@@ -31,9 +31,7 @@ export function lint(rule, source, options) {
31
31
  fs.mkdirSync(path.dirname(file), { recursive: true });
32
32
  fs.writeFileSync(file, content);
33
33
  }
34
- return lintWithRules(findBinary(), root, [rule]).results.map(({ file, line, column, message }) =>
35
- typeof source === "string" ? { line, column, message } : { file, line, column, message },
36
- );
34
+ return lintWithRules(findBinary(), root, [rule]).results.map(({ file, line, column, message }) => ({ file, line, column, message }));
37
35
  } finally {
38
36
  fs.rmSync(root, { recursive: true, force: true });
39
37
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slop-police",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Type-aware TypeScript linter for the dead code and needless indirection AI agents leave behind",
5
5
  "keywords": [
6
6
  "typescript",
@@ -38,12 +38,12 @@
38
38
  "LICENSE"
39
39
  ],
40
40
  "engines": {
41
- "node": ">=18"
41
+ "node": ">=22.18"
42
42
  },
43
43
  "optionalDependencies": {
44
- "slop-police-darwin-arm64": "0.3.0",
45
- "slop-police-darwin-x64": "0.3.0",
46
- "slop-police-linux-arm64": "0.3.0",
47
- "slop-police-linux-x64": "0.3.0"
44
+ "slop-police-darwin-arm64": "0.3.2",
45
+ "slop-police-darwin-x64": "0.3.2",
46
+ "slop-police-linux-arm64": "0.3.2",
47
+ "slop-police-linux-x64": "0.3.2"
48
48
  }
49
49
  }