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 +13 -25
- package/lib/runner.js +1 -8
- package/lib/testing.d.ts +5 -9
- package/lib/testing.js +4 -6
- package/package.json +6 -6
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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
|
|
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.
|
|
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}
|
|
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
|
|
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
|
|
21
|
-
// with a default strict tsconfig.json unless one is given) and
|
|
22
|
-
//
|
|
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.
|
|
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.
|
|
45
|
-
"slop-police-darwin-x64": "0.3.
|
|
46
|
-
"slop-police-linux-arm64": "0.3.
|
|
47
|
-
"slop-police-linux-x64": "0.3.
|
|
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
|
}
|