@aarock1234/slopscan 0.1.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/LICENSE +21 -0
- package/README.md +143 -0
- package/SKILL.md +149 -0
- package/dist/analyzer.d.ts +5 -0
- package/dist/analyzer.js +13 -0
- package/dist/analyzer.js.map +1 -0
- package/dist/analyzers/ast.d.ts +12 -0
- package/dist/analyzers/ast.js +73 -0
- package/dist/analyzers/ast.js.map +1 -0
- package/dist/analyzers/judge-chunks.d.ts +13 -0
- package/dist/analyzers/judge-chunks.js +87 -0
- package/dist/analyzers/judge-chunks.js.map +1 -0
- package/dist/analyzers/judge-validate.d.ts +19 -0
- package/dist/analyzers/judge-validate.js +81 -0
- package/dist/analyzers/judge-validate.js.map +1 -0
- package/dist/analyzers/judge.d.ts +44 -0
- package/dist/analyzers/judge.js +177 -0
- package/dist/analyzers/judge.js.map +1 -0
- package/dist/change.d.ts +19 -0
- package/dist/change.js +87 -0
- package/dist/change.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +125 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +91 -0
- package/dist/config.js +61 -0
- package/dist/config.js.map +1 -0
- package/dist/confirm.d.ts +11 -0
- package/dist/confirm.js +100 -0
- package/dist/confirm.js.map +1 -0
- package/dist/finding.d.ts +13 -0
- package/dist/finding.js +18 -0
- package/dist/finding.js.map +1 -0
- package/dist/lang.d.ts +16 -0
- package/dist/lang.js +37 -0
- package/dist/lang.js.map +1 -0
- package/dist/model.d.ts +2 -0
- package/dist/model.js +34 -0
- package/dist/model.js.map +1 -0
- package/dist/report.d.ts +124 -0
- package/dist/report.js +112 -0
- package/dist/report.js.map +1 -0
- package/dist/rule.d.ts +66 -0
- package/dist/rule.js +259 -0
- package/dist/rule.js.map +1 -0
- package/dist/scan.d.ts +14 -0
- package/dist/scan.js +33 -0
- package/dist/scan.js.map +1 -0
- package/dist/score.d.ts +147 -0
- package/dist/score.js +142 -0
- package/dist/score.js.map +1 -0
- package/dist/shared/concurrency.d.ts +5 -0
- package/dist/shared/concurrency.js +17 -0
- package/dist/shared/concurrency.js.map +1 -0
- package/dist/shared/env.d.ts +27 -0
- package/dist/shared/env.js +29 -0
- package/dist/shared/env.js.map +1 -0
- package/dist/shared/errors.d.ts +18 -0
- package/dist/shared/errors.js +26 -0
- package/dist/shared/errors.js.map +1 -0
- package/dist/shared/log.d.ts +2 -0
- package/dist/shared/log.js +18 -0
- package/dist/shared/log.js.map +1 -0
- package/dist/shared/paths.d.ts +3 -0
- package/dist/shared/paths.js +6 -0
- package/dist/shared/paths.js.map +1 -0
- package/dist/shared/prompts.d.ts +3 -0
- package/dist/shared/prompts.js +34 -0
- package/dist/shared/prompts.js.map +1 -0
- package/package.json +79 -0
- package/prompts/judge.md +37 -0
- package/rules/any/futureproof/abstraction/pass-through-wrapper.md +70 -0
- package/rules/any/futureproof/abstraction/single-caller-helper.md +38 -0
- package/rules/any/futureproof/abstraction/single-impl-interface.md +83 -0
- package/rules/any/futureproof/exports/dead-export.md +62 -0
- package/rules/any/futureproof/layering/framework-type-in-domain.md +83 -0
- package/rules/any/futureproof/layering/logic-in-handler.md +94 -0
- package/rules/any/futureproof/params/boolean-positional-param.md +128 -0
- package/rules/any/futureproof/params/positional-config-args.md +135 -0
- package/rules/any/futureproof/structure/wide-function.md +138 -0
- package/rules/any/futureproof/testing/test-asserts-implementation.md +70 -0
- package/rules/any/hacky/comments/comment-restates-code.md +50 -0
- package/rules/any/hacky/comments/shipped-todo-comment.md +55 -0
- package/rules/any/hacky/concurrency/sleep-based-sync.md +95 -0
- package/rules/any/hacky/config/hardcoded-url.md +88 -0
- package/rules/any/hacky/constants/magic-number.md +94 -0
- package/rules/any/hacky/debugging/debug-print.md +73 -0
- package/rules/any/hacky/duplication/copy-paste-block.md +117 -0
- package/rules/any/hacky/types/stringly-typed-enum.md +86 -0
- package/rules/any/idiom/comments/no-section-banners.md +47 -0
- package/rules/go/futureproof/constants/string-enums-when-serialized.md +47 -0
- package/rules/go/futureproof/constants/typed-constants-for-enums.md +45 -0
- package/rules/go/futureproof/interfaces/compile-time-impl-assertion.md +43 -0
- package/rules/go/futureproof/interfaces/define-interfaces-at-consumer.md +47 -0
- package/rules/go/futureproof/interfaces/keep-interfaces-small.md +53 -0
- package/rules/go/futureproof/naming/no-utils-helpers-common-package.md +34 -0
- package/rules/go/futureproof/structs/zero-value-usable.md +50 -0
- package/rules/go/futureproof/types/generics-over-any.md +55 -0
- package/rules/go/hacky/concurrency/no-goroutine-without-wait.md +44 -0
- package/rules/go/hacky/context/never-pass-nil-context.md +64 -0
- package/rules/go/hacky/context/no-context-in-struct.md +44 -0
- package/rules/go/hacky/errors/check-close-error-on-writable.md +53 -0
- package/rules/go/hacky/errors/custom-error-type-errors-as.md +54 -0
- package/rules/go/hacky/errors/ignored-error-blank.md +84 -0
- package/rules/go/hacky/errors/panic-only-unrecoverable.md +77 -0
- package/rules/go/hacky/errors/sentinel-errors-with-errors-is.md +63 -0
- package/rules/go/hacky/errors/wrap-errors-with-w.md +71 -0
- package/rules/go/hacky/state/package-level-mutable-var.md +95 -0
- package/rules/go/hacky/structure/os-exit-only-in-main.md +61 -0
- package/rules/go/hacky/type-safety/no-map-string-any.md +49 -0
- package/rules/go/hacky/types/json-into-struct-not-map.md +48 -0
- package/rules/go/hacky/types/no-any.md +71 -0
- package/rules/go/hacky/types/no-map-any-any.md +35 -0
- package/rules/go/idiom/comments/doc-comment-on-exported.md +81 -0
- package/rules/go/idiom/comments/doc-comment-starts-with-name.md +36 -0
- package/rules/go/idiom/concurrency/mutex-over-channels-for-state.md +47 -0
- package/rules/go/idiom/constructors/constructor-named-new.md +37 -0
- package/rules/go/idiom/context/context-is-first-param.md +47 -0
- package/rules/go/idiom/context/io-takes-context.md +50 -0
- package/rules/go/idiom/errors/capitalized-error-message.md +52 -0
- package/rules/go/idiom/errors/check-error-immediately.md +45 -0
- package/rules/go/idiom/errors/defer-close-explicit-discard.md +65 -0
- package/rules/go/idiom/errors/error-is-last-return-value.md +53 -0
- package/rules/go/idiom/errors/no-log-and-return.md +45 -0
- package/rules/go/idiom/formatting/blank-line-before-return.md +48 -0
- package/rules/go/idiom/formatting/multiline-struct-literals.md +56 -0
- package/rules/go/idiom/imports/import-order-groups.md +42 -0
- package/rules/go/idiom/imports/side-effect-imports-own-group.md +51 -0
- package/rules/go/idiom/interfaces/accept-interfaces-return-concrete.md +39 -0
- package/rules/go/idiom/iterators/prefer-iter-seq.md +47 -0
- package/rules/go/idiom/logging/lowercase-log-messages.md +80 -0
- package/rules/go/idiom/logging/use-slog.md +46 -0
- package/rules/go/idiom/naming/acronyms-consistent-case.md +97 -0
- package/rules/go/idiom/naming/interface-er-suffix.md +33 -0
- package/rules/go/idiom/naming/no-package-name-stutter.md +37 -0
- package/rules/go/idiom/naming/package-name-single-lowercase-word.md +40 -0
- package/rules/go/idiom/receivers/consistent-receiver-kind-per-type.md +40 -0
- package/rules/go/idiom/receivers/no-this-receiver.md +37 -0
- package/rules/go/idiom/receivers/pointer-vs-value-receiver-choice.md +49 -0
- package/rules/go/idiom/receivers/short-receiver-names.md +43 -0
- package/rules/go/idiom/structs/new-expr-for-pointer-fields.md +40 -0
- package/rules/go/idiom/structure/main-delegates-to-run.md +54 -0
- package/rules/ts/futureproof/classes/composition-over-abstract-base.md +73 -0
- package/rules/ts/futureproof/functions/explicit-return-type-on-exports.md +50 -0
- package/rules/ts/futureproof/structure/colocate-zod-schemas.md +46 -0
- package/rules/ts/futureproof/types/exhaustive-switch-never-check.md +52 -0
- package/rules/ts/futureproof/types/readonly-for-immutable-data.md +46 -0
- package/rules/ts/futureproof/zod/enum-values-from-const-object.md +37 -0
- package/rules/ts/futureproof/zod/no-z-native-enum.md +36 -0
- package/rules/ts/futureproof/zod/schema-first-infer-type.md +46 -0
- package/rules/ts/hacky/async/no-floating-promises.md +52 -0
- package/rules/ts/hacky/config/env-read-outside-config.md +56 -0
- package/rules/ts/hacky/errors/catch-param-typed-unknown.md +67 -0
- package/rules/ts/hacky/errors/custom-error-class-instanceof.md +66 -0
- package/rules/ts/hacky/errors/empty-catch.md +52 -0
- package/rules/ts/hacky/errors/log-and-swallow.md +65 -0
- package/rules/ts/hacky/errors/throw-typed-error-with-context.md +64 -0
- package/rules/ts/hacky/operators/nullish-coalescing-over-or.md +50 -0
- package/rules/ts/hacky/state/exported-let.md +60 -0
- package/rules/ts/hacky/type-safety/no-double-assertion.md +34 -0
- package/rules/ts/hacky/type-safety/no-explicit-any.md +48 -0
- package/rules/ts/hacky/type-safety/no-non-null-assertion.md +43 -0
- package/rules/ts/hacky/type-safety/no-unchecked-type-assertion.md +54 -0
- package/rules/ts/hacky/type-safety/validate-parsed-json.md +48 -0
- package/rules/ts/idiom/async/no-promise-chains.md +45 -0
- package/rules/ts/idiom/async/parallelize-independent-awaits.md +46 -0
- package/rules/ts/idiom/comments/doc-comment-above-declaration.md +43 -0
- package/rules/ts/idiom/constants/as-const-for-literal-config.md +65 -0
- package/rules/ts/idiom/constants/no-enum.md +37 -0
- package/rules/ts/idiom/control-flow/always-brace-if.md +56 -0
- package/rules/ts/idiom/control-flow/guard-clauses-early-return.md +52 -0
- package/rules/ts/idiom/control-flow/no-nested-ternary.md +54 -0
- package/rules/ts/idiom/errors/error-message-lowercase.md +56 -0
- package/rules/ts/idiom/errors/log-at-boundary-not-every-layer.md +66 -0
- package/rules/ts/idiom/exports/inline-export-at-declaration.md +49 -0
- package/rules/ts/idiom/exports/no-default-export.md +37 -0
- package/rules/ts/idiom/formatting/multiline-object-literals.md +46 -0
- package/rules/ts/idiom/functions/arrow-for-callbacks.md +44 -0
- package/rules/ts/idiom/functions/function-declaration-for-top-level.md +56 -0
- package/rules/ts/idiom/imports/export-type-for-types.md +34 -0
- package/rules/ts/idiom/imports/import-order.md +66 -0
- package/rules/ts/idiom/imports/import-type-for-types.md +50 -0
- package/rules/ts/idiom/imports/side-effect-imports-last.md +42 -0
- package/rules/ts/idiom/logging/structured-logging-lowercase.md +45 -0
- package/rules/ts/idiom/naming/boolean-name-question-prefix.md +42 -0
- package/rules/ts/idiom/naming/descriptive-identifier-quality.md +48 -0
- package/rules/ts/idiom/naming/screaming-snake-module-constants.md +58 -0
- package/rules/ts/idiom/strings/template-literals-over-concat.md +51 -0
- package/rules/ts/idiom/types/interface-extends-over-intersection.md +44 -0
- package/rules/ts/idiom/types/null-vs-undefined-convention.md +43 -0
- package/rules/ts/idiom/types/prefer-type-over-interface.md +64 -0
- package/rules/ts/idiom/types/satisfies-over-annotation.md +43 -0
- package/rules/ts/idiom/types/type-predicates-for-narrowing.md +53 -0
- package/rules/ts/idiom/variables/const-by-default.md +41 -0
- package/rules/ts/idiom/variables/no-var.md +36 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Abdel Seaf
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# slopscan
|
|
2
|
+
|
|
3
|
+
Scores a git diff for slop: code that is hacky, non-idiomatic, or hard to change later, whoever wrote it.
|
|
4
|
+
TypeScript and Go.
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
slop score 63 (D)
|
|
8
|
+
idiom 92 · hacky 100 · futureproof 0
|
|
9
|
+
2 files, 14 scored lines
|
|
10
|
+
|
|
11
|
+
src/users.ts:11 [critical] ts.hacky.empty-catch
|
|
12
|
+
empty catch swallows the error; handle it, rethrow with context, or let it propagate
|
|
13
|
+
> catch {}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## How it works
|
|
17
|
+
|
|
18
|
+
Five nouns, one pipeline.
|
|
19
|
+
|
|
20
|
+
| noun | what it is |
|
|
21
|
+
| ---------- | ---------------------------------------------------------------------------- |
|
|
22
|
+
| `Rule` | one markdown file under `rules/` with a why, bad examples, and good examples |
|
|
23
|
+
| `Change` | a changed file at head with the set of lines this diff touched |
|
|
24
|
+
| `Analyzer` | `(change, rules) => findings`; two of them, syntax and judge |
|
|
25
|
+
| `Finding` | a rule, a location, a verbatim quote, a confidence |
|
|
26
|
+
| `Report` | the score, its axes, and the ranked findings |
|
|
27
|
+
|
|
28
|
+
`scan` reads what changed, runs every analyzer over it, confirms the findings that need a repo-wide check, and
|
|
29
|
+
scores what survives. The syntax analyzer parses each file once with tree-sitter and runs every `ast` rule against
|
|
30
|
+
the tree. The judge analyzer makes one model call per changed file with the `judge` rules as its rubric, and drops
|
|
31
|
+
any finding whose quote is not in the file, is not on a changed line, or is under the confidence floor.
|
|
32
|
+
|
|
33
|
+
## Rules
|
|
34
|
+
|
|
35
|
+
A rule's identity is its path: `rules/ts/hacky/type-safety/no-explicit-any.md` is `ts.hacky.no-explicit-any`.
|
|
36
|
+
The frontmatter carries only what the path cannot:
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
severity: major # info | minor | major | critical
|
|
40
|
+
detect: ast # ast | judge
|
|
41
|
+
ast:
|
|
42
|
+
rule:
|
|
43
|
+
kind: predefined_type
|
|
44
|
+
regex: ^any$
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Then `## Why`, `## Message`, `## Bad`, and `## Good`. The examples are the tests: `rules.test.ts` runs every `ast`
|
|
48
|
+
rule's bad blocks (must match) and good blocks (must not). A `// BAD:` marker line above the offending code records
|
|
49
|
+
which line the match has to land on.
|
|
50
|
+
|
|
51
|
+
Judge rules may name a `confirm` predicate (`callCount`, `implCount`, `refCount`). The judge nominates a `symbol`
|
|
52
|
+
and the finding only counts if the repo-wide count agrees.
|
|
53
|
+
|
|
54
|
+
Three axes, weighted into one score:
|
|
55
|
+
|
|
56
|
+
- **idiom**: does it read like the language.
|
|
57
|
+
- **hacky**: shortcuts that work today and bite later.
|
|
58
|
+
- **futureproof**: how many places have to move for the next plausible change.
|
|
59
|
+
|
|
60
|
+
## Scoring
|
|
61
|
+
|
|
62
|
+
Points per finding by severity (1 / 3 / 8 / 20), damped geometrically within one rule so a hundred `any` casts
|
|
63
|
+
score about twice one. Points per hundred changed lines feed `100 * (1 - e^(-density / 12))` per axis, and the
|
|
64
|
+
axes combine `0.25 idiom + 0.40 hacky + 0.35 futureproof`. Grades A through F, exit code 1 above the fail
|
|
65
|
+
threshold. Every constant lives in `.slopscan.yml`.
|
|
66
|
+
|
|
67
|
+
## GitHub Action
|
|
68
|
+
|
|
69
|
+
```yaml
|
|
70
|
+
name: slopscan
|
|
71
|
+
on:
|
|
72
|
+
pull_request:
|
|
73
|
+
|
|
74
|
+
permissions:
|
|
75
|
+
contents: read
|
|
76
|
+
pull-requests: write
|
|
77
|
+
|
|
78
|
+
jobs:
|
|
79
|
+
slop:
|
|
80
|
+
runs-on: ubuntu-latest
|
|
81
|
+
steps:
|
|
82
|
+
- uses: actions/checkout@v5
|
|
83
|
+
with:
|
|
84
|
+
fetch-depth: 0
|
|
85
|
+
- uses: aarock1234/slopscan@v1
|
|
86
|
+
with:
|
|
87
|
+
openrouter-api-key: ${{ secrets.OPENROUTER_API_KEY }}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The Action writes the report to the job summary, uploads the full JSON as an artifact, keeps one sticky comment on
|
|
91
|
+
the pull request up to date, and fails the check when the score is over `failThreshold`. Without a key it runs the
|
|
92
|
+
syntax rules only. Inputs: `base`, `judge`, `openrouter-api-key`, `openai-api-key`, `comment`, `fail-on-threshold`,
|
|
93
|
+
`version`. Outputs: `score`, `grade`.
|
|
94
|
+
|
|
95
|
+
To run it on demand, comment `@slopscan` or `/slopscan` on a pull request. That needs an `issue_comment` trigger next to
|
|
96
|
+
`pull_request` and a guard so only people with write access can start a run that uses the repository's secrets:
|
|
97
|
+
|
|
98
|
+
```yaml
|
|
99
|
+
on:
|
|
100
|
+
pull_request:
|
|
101
|
+
issue_comment:
|
|
102
|
+
types: [created]
|
|
103
|
+
|
|
104
|
+
jobs:
|
|
105
|
+
slop:
|
|
106
|
+
if: >-
|
|
107
|
+
github.event_name == 'pull_request' ||
|
|
108
|
+
(github.event.issue.pull_request &&
|
|
109
|
+
(contains(github.event.comment.body, '@slopscan') || contains(github.event.comment.body, '/slopscan')) &&
|
|
110
|
+
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association))
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The Action reacts to the comment with eyes, checks out that pull request's head, and posts the report as usual.
|
|
114
|
+
`@slopscan` also notifies whoever owns that GitHub handle; `/slopscan` does not.
|
|
115
|
+
|
|
116
|
+
Judge results are cached by content under `.slopscan-cache`, so re-running the same commit costs nothing.
|
|
117
|
+
|
|
118
|
+
## What leaves your machine
|
|
119
|
+
|
|
120
|
+
The syntax rules run locally. The judge sends each changed file's changed regions, with fifteen lines of context
|
|
121
|
+
and the file's imports, to the model provider named in `judge.model`. Nothing else is sent. If that is not
|
|
122
|
+
acceptable for a repository, run with `--no-judge` or leave the key out of the Action.
|
|
123
|
+
|
|
124
|
+
## Usage
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
pnpm install
|
|
128
|
+
pnpm dev scan --base main # judge on; needs OPENAI_API_KEY in .env
|
|
129
|
+
pnpm dev scan --base main --no-judge # syntax rules only, no key needed
|
|
130
|
+
pnpm dev scan --base main --format json # or markdown
|
|
131
|
+
pnpm dev rules # list rules
|
|
132
|
+
pnpm dev scan --rules ./my-rules # bring your own rule directory
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Authoring:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
pnpm script scripts/check-rule.ts rules/ts/hacky/type-safety/no-explicit-any.md # one rule, fast
|
|
139
|
+
pnpm test # everything
|
|
140
|
+
pnpm script scripts/eval-judge.ts # judge precision/recall, costs calls
|
|
141
|
+
pnpm script scripts/write-skill.ts # regenerate SKILL.md
|
|
142
|
+
pnpm script scripts/import-guide.ts --lang ts --guide path/to/STYLE.md # drafts from a prose guide
|
|
143
|
+
```
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: slop-rules
|
|
3
|
+
description: Coding rules for TypeScript and Go that slopscan enforces. Use when writing, editing, or reviewing .ts, .tsx, or .go code. Each rule links to its full explanation with good and bad examples.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Slop rules
|
|
7
|
+
|
|
8
|
+
Code is slop when it is hacky, non-idiomatic, or hard to change later, regardless of who wrote it. Each rule below
|
|
9
|
+
names a one-line message; the linked file holds the reasoning and examples. Read a rule file when a message is not
|
|
10
|
+
self-explanatory.
|
|
11
|
+
|
|
12
|
+
## Idiom: write the language the way it is written
|
|
13
|
+
|
|
14
|
+
| rule | lang | severity | check | message |
|
|
15
|
+
| ------------------------------------------------------------------------------------------------------------- | ---- | -------- | -------- | ------------------------------------------------------------------------------------------------- |
|
|
16
|
+
| [any.idiom.no-section-banners](rules/any/idiom/comments/no-section-banners.md) | any | minor | syntax | banner comments signal a file doing too much; split it instead of decorating it |
|
|
17
|
+
| [go.idiom.accept-interfaces-return-concrete](rules/go/idiom/interfaces/accept-interfaces-return-concrete.md) | go | minor | judgment | accept interfaces and return concrete types |
|
|
18
|
+
| [go.idiom.acronyms-consistent-case](rules/go/idiom/naming/acronyms-consistent-case.md) | go | minor | syntax | acronyms keep one case in identifiers: userID, HTTPClient, parseURL |
|
|
19
|
+
| [go.idiom.blank-line-before-return](rules/go/idiom/formatting/blank-line-before-return.md) | go | info | judgment | return has a blank line before it unless it is the only statement in the block |
|
|
20
|
+
| [go.idiom.capitalized-error-message](rules/go/idiom/errors/capitalized-error-message.md) | go | minor | syntax | error strings are lowercase fragments so they read well when wrapped |
|
|
21
|
+
| [go.idiom.check-error-immediately](rules/go/idiom/errors/check-error-immediately.md) | go | minor | judgment | check the error on the line after the call that returned it |
|
|
22
|
+
| [go.idiom.consistent-receiver-kind-per-type](rules/go/idiom/receivers/consistent-receiver-kind-per-type.md) | go | minor | judgment | type mixes pointer and value receivers; use one kind for every method |
|
|
23
|
+
| [go.idiom.constructor-named-new](rules/go/idiom/constructors/constructor-named-new.md) | go | minor | judgment | constructor for the package's primary type is named New, not NewX |
|
|
24
|
+
| [go.idiom.context-is-first-param](rules/go/idiom/context/context-is-first-param.md) | go | minor | syntax | context.Context is the first parameter |
|
|
25
|
+
| [go.idiom.defer-close-explicit-discard](rules/go/idiom/errors/defer-close-explicit-discard.md) | go | minor | syntax | bare defer Close ignores its error silently; discard it with _ = or check it |
|
|
26
|
+
| [go.idiom.doc-comment-on-exported](rules/go/idiom/comments/doc-comment-on-exported.md) | go | minor | syntax | exported declaration has no doc comment |
|
|
27
|
+
| [go.idiom.doc-comment-starts-with-name](rules/go/idiom/comments/doc-comment-starts-with-name.md) | go | info | judgment | doc comment does not start with the name it documents |
|
|
28
|
+
| [go.idiom.error-is-last-return-value](rules/go/idiom/errors/error-is-last-return-value.md) | go | minor | syntax | error is the last return value |
|
|
29
|
+
| [go.idiom.import-order-groups](rules/go/idiom/imports/import-order-groups.md) | go | minor | judgment | imports are grouped stdlib, external, internal, side-effect with a blank line between groups |
|
|
30
|
+
| [go.idiom.interface-er-suffix](rules/go/idiom/naming/interface-er-suffix.md) | go | info | judgment | single-method interface is named for its action with an er suffix |
|
|
31
|
+
| [go.idiom.io-takes-context](rules/go/idiom/context/io-takes-context.md) | go | minor | judgment | function does I/O without a context; take ctx and pass it to the call |
|
|
32
|
+
| [go.idiom.lowercase-log-messages](rules/go/idiom/logging/lowercase-log-messages.md) | go | minor | syntax | log messages are lowercase fragments |
|
|
33
|
+
| [go.idiom.main-delegates-to-run](rules/go/idiom/structure/main-delegates-to-run.md) | go | minor | judgment | main does the wiring itself; move it into run() error so defers run on every exit |
|
|
34
|
+
| [go.idiom.multiline-struct-literals](rules/go/idiom/formatting/multiline-struct-literals.md) | go | info | syntax | struct literal with several fields goes one field per line |
|
|
35
|
+
| [go.idiom.mutex-over-channels-for-state](rules/go/idiom/concurrency/mutex-over-channels-for-state.md) | go | minor | judgment | channel used as a lock around plain state; use a sync.Mutex next to the field |
|
|
36
|
+
| [go.idiom.new-expr-for-pointer-fields](rules/go/idiom/structs/new-expr-for-pointer-fields.md) | go | info | judgment | temporary declared only to take its address; use new(expr) |
|
|
37
|
+
| [go.idiom.no-log-and-return](rules/go/idiom/errors/no-log-and-return.md) | go | minor | judgment | error is logged and returned; handle it or propagate it, not both |
|
|
38
|
+
| [go.idiom.no-package-name-stutter](rules/go/idiom/naming/no-package-name-stutter.md) | go | minor | judgment | exported name repeats the package name; let the package carry the context |
|
|
39
|
+
| [go.idiom.no-this-receiver](rules/go/idiom/receivers/no-this-receiver.md) | go | minor | syntax | receiver named this/self; use a short abbreviation of the type |
|
|
40
|
+
| [go.idiom.package-name-single-lowercase-word](rules/go/idiom/naming/package-name-single-lowercase-word.md) | go | minor | syntax | package names are one short lowercase word |
|
|
41
|
+
| [go.idiom.pointer-vs-value-receiver-choice](rules/go/idiom/receivers/pointer-vs-value-receiver-choice.md) | go | minor | judgment | receiver kind does not fit the method: mutation or a large struct needs a pointer receiver |
|
|
42
|
+
| [go.idiom.prefer-iter-seq](rules/go/idiom/iterators/prefer-iter-seq.md) | go | minor | judgment | channel or callback generator; return an iter.Seq so callers can range and break |
|
|
43
|
+
| [go.idiom.short-receiver-names](rules/go/idiom/receivers/short-receiver-names.md) | go | minor | syntax | receiver name longer than two letters; use a short abbreviation of the type |
|
|
44
|
+
| [go.idiom.side-effect-imports-own-group](rules/go/idiom/imports/side-effect-imports-own-group.md) | go | minor | syntax | side-effect imports go in their own group after the internal imports |
|
|
45
|
+
| [go.idiom.use-slog](rules/go/idiom/logging/use-slog.md) | go | minor | syntax | unstructured log call; use log/slog with a level and key-value attributes |
|
|
46
|
+
| [ts.idiom.always-brace-if](rules/ts/idiom/control-flow/always-brace-if.md) | ts | minor | syntax | braceless `if` body; wrap it in a block |
|
|
47
|
+
| [ts.idiom.arrow-for-callbacks](rules/ts/idiom/functions/arrow-for-callbacks.md) | ts | minor | syntax | `function` expression as a callback; use an arrow function |
|
|
48
|
+
| [ts.idiom.as-const-for-literal-config](rules/ts/idiom/constants/as-const-for-literal-config.md) | ts | minor | syntax | literal const object without `as const`; its values widen to `string` |
|
|
49
|
+
| [ts.idiom.boolean-name-question-prefix](rules/ts/idiom/naming/boolean-name-question-prefix.md) | ts | minor | judgment | boolean name does not read as a question; prefix with is, has, should, or can |
|
|
50
|
+
| [ts.idiom.const-by-default](rules/ts/idiom/variables/const-by-default.md) | ts | minor | judgment | `let` is never reassigned; declare it with `const` |
|
|
51
|
+
| [ts.idiom.descriptive-identifier-quality](rules/ts/idiom/naming/descriptive-identifier-quality.md) | ts | minor | judgment | identifier does not describe what it holds or does; use a descriptive name |
|
|
52
|
+
| [ts.idiom.doc-comment-above-declaration](rules/ts/idiom/comments/doc-comment-above-declaration.md) | ts | info | judgment | declaration documented in a trailing comment; put the comment on the line above |
|
|
53
|
+
| [ts.idiom.error-message-lowercase](rules/ts/idiom/errors/error-message-lowercase.md) | ts | minor | syntax | error message starts with a capital letter; write it as a lowercase fragment |
|
|
54
|
+
| [ts.idiom.export-type-for-types](rules/ts/idiom/imports/export-type-for-types.md) | ts | minor | judgment | re-export is a type; use `export type` |
|
|
55
|
+
| [ts.idiom.function-declaration-for-top-level](rules/ts/idiom/functions/function-declaration-for-top-level.md) | ts | minor | syntax | top-level arrow function; use a `function` declaration |
|
|
56
|
+
| [ts.idiom.guard-clauses-early-return](rules/ts/idiom/control-flow/guard-clauses-early-return.md) | ts | minor | judgment | nested conditionals wrap the happy path; check preconditions first and return early |
|
|
57
|
+
| [ts.idiom.import-order](rules/ts/idiom/imports/import-order.md) | ts | minor | syntax | imports out of order; external packages, then `@/` modules, then relative paths |
|
|
58
|
+
| [ts.idiom.import-type-for-types](rules/ts/idiom/imports/import-type-for-types.md) | ts | minor | judgment | import is only used as a type; use `import type` |
|
|
59
|
+
| [ts.idiom.inline-export-at-declaration](rules/ts/idiom/exports/inline-export-at-declaration.md) | ts | minor | syntax | export list detached from its declarations; export at the declaration site |
|
|
60
|
+
| [ts.idiom.interface-extends-over-intersection](rules/ts/idiom/types/interface-extends-over-intersection.md) | ts | info | judgment | object shape extended with an intersection; use `interface extends` |
|
|
61
|
+
| [ts.idiom.log-at-boundary-not-every-layer](rules/ts/idiom/errors/log-at-boundary-not-every-layer.md) | ts | minor | judgment | catch logs and rethrows in an inner layer; let it propagate and log once at the boundary |
|
|
62
|
+
| [ts.idiom.multiline-object-literals](rules/ts/idiom/formatting/multiline-object-literals.md) | ts | info | syntax | object with several properties on one line; put one property per line |
|
|
63
|
+
| [ts.idiom.no-default-export](rules/ts/idiom/exports/no-default-export.md) | ts | minor | syntax | default export has no name of its own; use a named export |
|
|
64
|
+
| [ts.idiom.no-enum](rules/ts/idiom/constants/no-enum.md) | ts | minor | syntax | enum is a runtime construct; use a const object with `as const` and a derived union |
|
|
65
|
+
| [ts.idiom.no-nested-ternary](rules/ts/idiom/control-flow/no-nested-ternary.md) | ts | minor | syntax | nested ternary; use an if chain, a switch, or a lookup |
|
|
66
|
+
| [ts.idiom.no-promise-chains](rules/ts/idiom/async/no-promise-chains.md) | ts | minor | syntax | promise chain; use async/await |
|
|
67
|
+
| [ts.idiom.no-var](rules/ts/idiom/variables/no-var.md) | ts | minor | syntax | `var` is function-scoped and hoisted; use `const` or `let` |
|
|
68
|
+
| [ts.idiom.null-vs-undefined-convention](rules/ts/idiom/types/null-vs-undefined-convention.md) | ts | info | judgment | null and undefined used interchangeably; undefined for absence, null for a deliberate empty value |
|
|
69
|
+
| [ts.idiom.parallelize-independent-awaits](rules/ts/idiom/async/parallelize-independent-awaits.md) | ts | minor | judgment | independent awaits run one after another; run them together with `Promise.all` |
|
|
70
|
+
| [ts.idiom.prefer-type-over-interface](rules/ts/idiom/types/prefer-type-over-interface.md) | ts | minor | syntax | plain data shape declared as `interface`; use `type` |
|
|
71
|
+
| [ts.idiom.satisfies-over-annotation](rules/ts/idiom/types/satisfies-over-annotation.md) | ts | minor | syntax | object literal annotated with `Record` loses its keys; use `satisfies` |
|
|
72
|
+
| [ts.idiom.screaming-snake-module-constants](rules/ts/idiom/naming/screaming-snake-module-constants.md) | ts | minor | syntax | module-level literal constant is camelCase; use SCREAMING_SNAKE_CASE |
|
|
73
|
+
| [ts.idiom.side-effect-imports-last](rules/ts/idiom/imports/side-effect-imports-last.md) | ts | minor | syntax | side-effect import belongs after all named imports |
|
|
74
|
+
| [ts.idiom.structured-logging-lowercase](rules/ts/idiom/logging/structured-logging-lowercase.md) | ts | minor | syntax | log message starts with a capital letter; write it as a lowercase fragment |
|
|
75
|
+
| [ts.idiom.template-literals-over-concat](rules/ts/idiom/strings/template-literals-over-concat.md) | ts | minor | syntax | string built with `+`; use a template literal |
|
|
76
|
+
| [ts.idiom.type-predicates-for-narrowing](rules/ts/idiom/types/type-predicates-for-narrowing.md) | ts | minor | judgment | type check returns a plain boolean; declare a type predicate so callers narrow without casting |
|
|
77
|
+
|
|
78
|
+
## Hacky: shortcuts that work today and bite later
|
|
79
|
+
|
|
80
|
+
| rule | lang | severity | check | message |
|
|
81
|
+
| -------------------------------------------------------------------------------------------------- | ---- | -------- | -------- | -------------------------------------------------------------------------------------------------- |
|
|
82
|
+
| [any.hacky.comment-restates-code](rules/any/hacky/comments/comment-restates-code.md) | any | minor | judgment | comment restates the code; say why or delete it |
|
|
83
|
+
| [any.hacky.copy-paste-block](rules/any/hacky/duplication/copy-paste-block.md) | any | minor | judgment | copy-pasted block differs in one identifier; extract a function or loop over the values |
|
|
84
|
+
| [any.hacky.debug-print](rules/any/hacky/debugging/debug-print.md) | any | minor | syntax | debug print in library code; use the structured logger or remove it |
|
|
85
|
+
| [any.hacky.hardcoded-url](rules/any/hacky/config/hardcoded-url.md) | any | minor | syntax | hardcoded url in logic; read the base url from config and build the path on it |
|
|
86
|
+
| [any.hacky.magic-number](rules/any/hacky/constants/magic-number.md) | any | minor | judgment | magic number in logic; name it as a constant that says what it means |
|
|
87
|
+
| [any.hacky.shipped-todo-comment](rules/any/hacky/comments/shipped-todo-comment.md) | any | minor | syntax | todo marker shipped in code; do the work or link the tracking issue |
|
|
88
|
+
| [any.hacky.sleep-based-sync](rules/any/hacky/concurrency/sleep-based-sync.md) | any | major | syntax | fixed sleep used as synchronization; wait on a promise, channel, or readiness signal instead |
|
|
89
|
+
| [any.hacky.stringly-typed-enum](rules/any/hacky/types/stringly-typed-enum.md) | any | minor | judgment | string literals used as an enum; declare the set once as typed constants |
|
|
90
|
+
| [go.hacky.check-close-error-on-writable](rules/go/hacky/errors/check-close-error-on-writable.md) | go | critical | judgment | close error on a written resource is discarded; a failed close can mean lost data |
|
|
91
|
+
| [go.hacky.custom-error-type-errors-as](rules/go/hacky/errors/custom-error-type-errors-as.md) | go | minor | syntax | type assertion on err misses wrapped errors; use errors.AsType or errors.As |
|
|
92
|
+
| [go.hacky.ignored-error-blank](rules/go/hacky/errors/ignored-error-blank.md) | go | major | syntax | returned error discarded with the blank identifier; check it or comment why it cannot matter |
|
|
93
|
+
| [go.hacky.json-into-struct-not-map](rules/go/hacky/types/json-into-struct-not-map.md) | go | major | judgment | json decoded into a map; declare a struct with tags so fields are checked at compile time |
|
|
94
|
+
| [go.hacky.never-pass-nil-context](rules/go/hacky/context/never-pass-nil-context.md) | go | major | syntax | nil passed as a context; use context.Background() or context.TODO() |
|
|
95
|
+
| [go.hacky.no-any](rules/go/hacky/types/no-any.md) | go | major | syntax | any erases the type; use a concrete type, a type parameter, or a small interface |
|
|
96
|
+
| [go.hacky.no-context-in-struct](rules/go/hacky/context/no-context-in-struct.md) | go | major | syntax | context stored in a struct outlives the request it belongs to; pass ctx per call |
|
|
97
|
+
| [go.hacky.no-goroutine-without-wait](rules/go/hacky/concurrency/no-goroutine-without-wait.md) | go | major | judgment | goroutine started with nothing waiting for it or collecting its error |
|
|
98
|
+
| [go.hacky.no-map-any-any](rules/go/hacky/types/no-map-any-any.md) | go | major | syntax | map[any]any is fully untyped; give the map concrete key and value types |
|
|
99
|
+
| [go.hacky.no-map-string-any](rules/go/hacky/type-safety/no-map-string-any.md) | go | major | syntax | map[string]any defers every field to runtime; decode into a struct |
|
|
100
|
+
| [go.hacky.os-exit-only-in-main](rules/go/hacky/structure/os-exit-only-in-main.md) | go | major | syntax | os.Exit outside main skips every defer; return an error and exit from main |
|
|
101
|
+
| [go.hacky.package-level-mutable-var](rules/go/hacky/state/package-level-mutable-var.md) | go | minor | syntax | package-level mutable collection is shared global state; own it in a struct and pass it in |
|
|
102
|
+
| [go.hacky.panic-only-unrecoverable](rules/go/hacky/errors/panic-only-unrecoverable.md) | go | major | syntax | panic in library code; return an error and let the caller decide |
|
|
103
|
+
| [go.hacky.sentinel-errors-with-errors-is](rules/go/hacky/errors/sentinel-errors-with-errors-is.md) | go | minor | syntax | error compared with == misses wrapped errors; use errors.Is |
|
|
104
|
+
| [go.hacky.wrap-errors-with-w](rules/go/hacky/errors/wrap-errors-with-w.md) | go | major | syntax | fmt.Errorf with %v drops the error chain; wrap with %w |
|
|
105
|
+
| [ts.hacky.catch-param-typed-unknown](rules/ts/hacky/errors/catch-param-typed-unknown.md) | ts | major | syntax | catch parameter treated as `Error` without a check; keep it `unknown` and narrow with `instanceof` |
|
|
106
|
+
| [ts.hacky.custom-error-class-instanceof](rules/ts/hacky/errors/custom-error-class-instanceof.md) | ts | minor | syntax | branching on error message text; throw a custom error class and check `instanceof` |
|
|
107
|
+
| [ts.hacky.empty-catch](rules/ts/hacky/errors/empty-catch.md) | ts | critical | syntax | empty catch swallows the error; handle it, rethrow with context, or let it propagate |
|
|
108
|
+
| [ts.hacky.env-read-outside-config](rules/ts/hacky/config/env-read-outside-config.md) | ts | minor | syntax | environment read outside the config module; validate env once and pass typed config |
|
|
109
|
+
| [ts.hacky.exported-let](rules/ts/hacky/state/exported-let.md) | ts | minor | syntax | exported let is global mutable state; expose functions over the state or export a const |
|
|
110
|
+
| [ts.hacky.log-and-swallow](rules/ts/hacky/errors/log-and-swallow.md) | ts | major | syntax | catch only logs and continues; rethrow with context or return an explicit failure |
|
|
111
|
+
| [ts.hacky.no-double-assertion](rules/ts/hacky/type-safety/no-double-assertion.md) | ts | major | syntax | double assertion through `unknown` bypasses all checking; validate or narrow instead |
|
|
112
|
+
| [ts.hacky.no-explicit-any](rules/ts/hacky/type-safety/no-explicit-any.md) | ts | major | syntax | `any` disables type checking; use a real type, a generic, or `unknown` |
|
|
113
|
+
| [ts.hacky.no-floating-promises](rules/ts/hacky/async/no-floating-promises.md) | ts | major | judgment | promise is neither awaited nor returned; its rejection is lost |
|
|
114
|
+
| [ts.hacky.no-non-null-assertion](rules/ts/hacky/type-safety/no-non-null-assertion.md) | ts | major | syntax | non-null assertion trades a compile-time check for a runtime crash; narrow or throw |
|
|
115
|
+
| [ts.hacky.no-unchecked-type-assertion](rules/ts/hacky/type-safety/no-unchecked-type-assertion.md) | ts | major | judgment | type assertion on unvalidated data; validate with a schema or a type guard instead |
|
|
116
|
+
| [ts.hacky.nullish-coalescing-over-or](rules/ts/hacky/operators/nullish-coalescing-over-or.md) | ts | minor | syntax | ` | | `with a default replaces 0, empty string, and false; use`??` |
|
|
117
|
+
| [ts.hacky.throw-typed-error-with-context](rules/ts/hacky/errors/throw-typed-error-with-context.md) | ts | minor | syntax | bare `Error` with a fixed message; throw a typed error that carries context |
|
|
118
|
+
| [ts.hacky.validate-parsed-json](rules/ts/hacky/type-safety/validate-parsed-json.md) | ts | major | syntax | `JSON.parse` result cast to a type without validation; parse it through a schema |
|
|
119
|
+
|
|
120
|
+
## Future-proof: keep the next change small
|
|
121
|
+
|
|
122
|
+
| rule | lang | severity | check | message |
|
|
123
|
+
| ------------------------------------------------------------------------------------------------------------------- | ---- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
124
|
+
| [any.futureproof.boolean-positional-param](rules/any/futureproof/params/boolean-positional-param.md) | any | minor | syntax | boolean positional parameter; use an options object or two functions |
|
|
125
|
+
| [any.futureproof.dead-export](rules/any/futureproof/exports/dead-export.md) | any | minor | judgment | exported symbol is referenced nowhere; delete it or make it private |
|
|
126
|
+
| [any.futureproof.framework-type-in-domain](rules/any/futureproof/layering/framework-type-in-domain.md) | any | major | judgment | http framework type in a domain signature; take plain input and return a plain result |
|
|
127
|
+
| [any.futureproof.logic-in-handler](rules/any/futureproof/layering/logic-in-handler.md) | any | major | judgment | business logic inside an http handler; move it to a service the handler calls |
|
|
128
|
+
| [any.futureproof.pass-through-wrapper](rules/any/futureproof/abstraction/pass-through-wrapper.md) | any | minor | judgment | pass-through wrapper forwards its arguments unchanged; call the target directly |
|
|
129
|
+
| [any.futureproof.positional-config-args](rules/any/futureproof/params/positional-config-args.md) | any | minor | syntax | constructor takes 4+ positional arguments; take an options object or config struct |
|
|
130
|
+
| [any.futureproof.single-caller-helper](rules/any/futureproof/abstraction/single-caller-helper.md) | any | minor | judgment | single-use helper adds indirection without removing duplication |
|
|
131
|
+
| [any.futureproof.single-impl-interface](rules/any/futureproof/abstraction/single-impl-interface.md) | any | minor | judgment | interface has a single implementation and no test double; use the concrete type until a second one exists |
|
|
132
|
+
| [any.futureproof.test-asserts-implementation](rules/any/futureproof/testing/test-asserts-implementation.md) | any | minor | judgment | test asserts on internals; assert on observable behavior instead |
|
|
133
|
+
| [any.futureproof.wide-function](rules/any/futureproof/structure/wide-function.md) | any | minor | syntax | function is too wide: more than 5 parameters or over 60 lines; group parameters into a struct and split the body |
|
|
134
|
+
| [go.futureproof.compile-time-impl-assertion](rules/go/futureproof/interfaces/compile-time-impl-assertion.md) | go | info | judgment | type meant to satisfy an interface is not checked; add var _ Iface = (*T)(nil) |
|
|
135
|
+
| [go.futureproof.define-interfaces-at-consumer](rules/go/futureproof/interfaces/define-interfaces-at-consumer.md) | go | minor | judgment | interface declared beside its implementation; define it where it is consumed |
|
|
136
|
+
| [go.futureproof.generics-over-any](rules/go/futureproof/types/generics-over-any.md) | go | minor | judgment | interface plus assertions where a type parameter would keep the caller's type |
|
|
137
|
+
| [go.futureproof.keep-interfaces-small](rules/go/futureproof/interfaces/keep-interfaces-small.md) | go | minor | syntax | interface with five or more methods; split it and compose smaller interfaces |
|
|
138
|
+
| [go.futureproof.no-utils-helpers-common-package](rules/go/futureproof/naming/no-utils-helpers-common-package.md) | go | minor | syntax | package named as a grab-bag; name it after the concept it owns |
|
|
139
|
+
| [go.futureproof.string-enums-when-serialized](rules/go/futureproof/constants/string-enums-when-serialized.md) | go | minor | judgment | iota enum is serialized as a bare integer; back it with a string |
|
|
140
|
+
| [go.futureproof.typed-constants-for-enums](rules/go/futureproof/constants/typed-constants-for-enums.md) | go | minor | judgment | related constants form an enum but have no named type; declare one |
|
|
141
|
+
| [go.futureproof.zero-value-usable](rules/go/futureproof/structs/zero-value-usable.md) | go | minor | judgment | zero value panics on first use; make the type usable without a constructor |
|
|
142
|
+
| [ts.futureproof.colocate-zod-schemas](rules/ts/futureproof/structure/colocate-zod-schemas.md) | ts | info | judgment | schema lives away from the code that uses it; colocate it with its consumer |
|
|
143
|
+
| [ts.futureproof.composition-over-abstract-base](rules/ts/futureproof/classes/composition-over-abstract-base.md) | ts | minor | judgment | abstract base class used for code sharing; prefer a shared function or injected dependency |
|
|
144
|
+
| [ts.futureproof.enum-values-from-const-object](rules/ts/futureproof/zod/enum-values-from-const-object.md) | ts | minor | syntax | `z.enum` with inline string literals; derive the values from a const object |
|
|
145
|
+
| [ts.futureproof.exhaustive-switch-never-check](rules/ts/futureproof/types/exhaustive-switch-never-check.md) | ts | minor | judgment | switch over a union without a `never` check; new variants will fall through silently |
|
|
146
|
+
| [ts.futureproof.explicit-return-type-on-exports](rules/ts/futureproof/functions/explicit-return-type-on-exports.md) | ts | minor | syntax | exported function has no return type; declare it so the contract cannot drift |
|
|
147
|
+
| [ts.futureproof.no-z-native-enum](rules/ts/futureproof/zod/no-z-native-enum.md) | ts | minor | syntax | `z.nativeEnum` is deprecated; use `z.enum` over the values of a const object |
|
|
148
|
+
| [ts.futureproof.readonly-for-immutable-data](rules/ts/futureproof/types/readonly-for-immutable-data.md) | ts | info | judgment | data that is never mutated is typed as mutable; mark it `readonly` |
|
|
149
|
+
| [ts.futureproof.schema-first-infer-type](rules/ts/futureproof/zod/schema-first-infer-type.md) | ts | minor | judgment | hand-written type duplicates a schema; derive it with `z.infer` |
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Change } from './change.js';
|
|
2
|
+
import type { Finding } from './finding.js';
|
|
3
|
+
import type { Rule } from './rule.js';
|
|
4
|
+
export type Analyzer = (change: Change, rules: readonly Rule[]) => Promise<Finding[]>;
|
|
5
|
+
export declare function collectFindings(changes: readonly Change[], rules: readonly Rule[], analyzers: readonly Analyzer[]): Promise<Finding[]>;
|
package/dist/analyzer.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { isIgnored } from './change.js';
|
|
2
|
+
import { compareFindings } from './finding.js';
|
|
3
|
+
import { ruleAppliesTo } from './lang.js';
|
|
4
|
+
import { mapConcurrent } from './shared/concurrency.js';
|
|
5
|
+
export async function collectFindings(changes, rules, analyzers) {
|
|
6
|
+
const perChange = await mapConcurrent(changes, async (change) => {
|
|
7
|
+
const applicable = rules.filter(rule => ruleAppliesTo(rule.lang, change.lang) && !isIgnored(change.path, rule.ignore));
|
|
8
|
+
const results = await Promise.all(analyzers.map(analyzer => analyzer(change, applicable)));
|
|
9
|
+
return results.flat();
|
|
10
|
+
});
|
|
11
|
+
return perChange.flat().sort(compareFindings);
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=analyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer.js","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAMxD,MAAM,CAAC,KAAK,UAAU,eAAe,CACpC,OAA0B,EAC1B,KAAsB,EACtB,SAA8B;IAE9B,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,KAAK,EAAC,MAAM,EAAC,EAAE;QAC7D,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAC9B,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CACrF,CAAC;QACF,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;QAE3F,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,OAAO,SAAS,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;AAC/C,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SgNode } from '@ast-grep/napi';
|
|
2
|
+
import type { Analyzer } from '../analyzer.js';
|
|
3
|
+
import { Lang } from '../lang.js';
|
|
4
|
+
import type { AstMatcher } from '../rule.js';
|
|
5
|
+
export type AstMatch = {
|
|
6
|
+
line: number;
|
|
7
|
+
endLine: number;
|
|
8
|
+
text: string;
|
|
9
|
+
};
|
|
10
|
+
export declare const astAnalyzer: Analyzer;
|
|
11
|
+
export declare function parseSource(lang: Lang, source: string, path?: string): SgNode;
|
|
12
|
+
export declare function matchAst(root: SgNode, matcher: AstMatcher): AstMatch[];
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import goLang from '@ast-grep/lang-go';
|
|
2
|
+
import { Lang as Parser, parse, registerDynamicLanguage } from '@ast-grep/napi';
|
|
3
|
+
import { Lang } from '../lang.js';
|
|
4
|
+
import { Detect } from '../rule.js';
|
|
5
|
+
const GO_PARSER = 'go';
|
|
6
|
+
const TSX_EXTENSION = '.tsx';
|
|
7
|
+
// tree-sitter grammars: TypeScript ships with the bindings, Go is loaded from its prebuilt library.
|
|
8
|
+
// registration must happen exactly once per process.
|
|
9
|
+
registerDynamicLanguage({ [GO_PARSER]: goLang });
|
|
10
|
+
const PARSERS = {
|
|
11
|
+
[Lang.TS]: Parser.TypeScript,
|
|
12
|
+
[Lang.GO]: GO_PARSER,
|
|
13
|
+
};
|
|
14
|
+
// runs every ast rule for the file's language against one parsed tree and keeps the matches that start on
|
|
15
|
+
// a changed line. a match that begins on an untouched line belongs to whoever wrote it, not to this diff.
|
|
16
|
+
export const astAnalyzer = (change, rules) => {
|
|
17
|
+
const matchers = rules.flatMap(rule => {
|
|
18
|
+
if (rule.detect !== Detect.AST) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
const matcher = rule.ast[change.lang];
|
|
22
|
+
return matcher === undefined ? [] : [{ rule, matcher }];
|
|
23
|
+
});
|
|
24
|
+
if (matchers.length === 0) {
|
|
25
|
+
return Promise.resolve([]);
|
|
26
|
+
}
|
|
27
|
+
const root = parseSource(change.lang, change.source, change.path);
|
|
28
|
+
const findings = matchers.flatMap(({ rule, matcher }) => onePerLine(matchAst(root, matcher).filter(match => change.changedLines.has(match.line))).map(match => toFinding(rule, change.path, match)));
|
|
29
|
+
return Promise.resolve(findings);
|
|
30
|
+
};
|
|
31
|
+
// a chain like `p.then().catch().finally()` is three matches starting on one line; the reader wants one finding
|
|
32
|
+
function onePerLine(matches) {
|
|
33
|
+
const seen = new Set();
|
|
34
|
+
return matches.filter(match => {
|
|
35
|
+
if (seen.has(match.line)) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
seen.add(match.line);
|
|
39
|
+
return true;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
// parses once so every rule can run against the same tree. tsx has its own grammar.
|
|
43
|
+
export function parseSource(lang, source, path = '') {
|
|
44
|
+
const parser = lang === Lang.TS && path.endsWith(TSX_EXTENSION) ? Parser.Tsx : PARSERS[lang];
|
|
45
|
+
return parse(parser, source).root();
|
|
46
|
+
}
|
|
47
|
+
// the matcher is authored as free-form YAML and validated by ast-grep when it compiles it,
|
|
48
|
+
// which is why the envelope type is loose and the bindings accept it as-is.
|
|
49
|
+
export function matchAst(root, matcher) {
|
|
50
|
+
return root.findAll(matcher).map(node => {
|
|
51
|
+
const { start, end } = node.range();
|
|
52
|
+
return {
|
|
53
|
+
line: start.line + 1,
|
|
54
|
+
endLine: end.line + 1,
|
|
55
|
+
text: node.text(),
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function toFinding(rule, path, match) {
|
|
60
|
+
return {
|
|
61
|
+
ruleId: rule.id,
|
|
62
|
+
path,
|
|
63
|
+
line: match.line,
|
|
64
|
+
endLine: match.endLine,
|
|
65
|
+
quote: firstLine(match.text),
|
|
66
|
+
message: rule.message,
|
|
67
|
+
confidence: 1,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function firstLine(text) {
|
|
71
|
+
return text.split('\n', 1)[0]?.trim() ?? '';
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=ast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast.js","sourceRoot":"","sources":["../../src/analyzers/ast.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Change } from '../change.js';
|
|
2
|
+
export type Region = {
|
|
3
|
+
start: number;
|
|
4
|
+
end: number;
|
|
5
|
+
};
|
|
6
|
+
export type Chunk = {
|
|
7
|
+
path: string;
|
|
8
|
+
regions: readonly Region[];
|
|
9
|
+
text: string;
|
|
10
|
+
estimatedTokens: number;
|
|
11
|
+
};
|
|
12
|
+
export declare function chunkChange(change: Change, budgetTokens: number): Chunk[];
|
|
13
|
+
export declare function estimateTokens(text: string): number;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// how many untouched lines to show around each changed line, and how close two regions must be to merge
|
|
2
|
+
const CONTEXT_LINES = 15;
|
|
3
|
+
const MERGE_GAP = 40;
|
|
4
|
+
const GUTTER_WIDTH = 5;
|
|
5
|
+
const CHARS_PER_TOKEN = 4;
|
|
6
|
+
// splits a change into chunks that each fit the token budget. regions are changed lines widened by context and
|
|
7
|
+
// merged when close; the file's leading import block rides along so symbols have a home.
|
|
8
|
+
export function chunkChange(change, budgetTokens) {
|
|
9
|
+
const lineCount = change.source.split('\n').length;
|
|
10
|
+
const regions = mergeRegions([...importRegion(change.source), ...changedRegions(change, lineCount)]);
|
|
11
|
+
const budgetChars = budgetTokens * CHARS_PER_TOKEN;
|
|
12
|
+
const chunks = [];
|
|
13
|
+
let pending = [];
|
|
14
|
+
let pendingChars = 0;
|
|
15
|
+
for (const region of regions) {
|
|
16
|
+
const chars = renderRegion(change, region).length;
|
|
17
|
+
if (pending.length > 0 && pendingChars + chars > budgetChars) {
|
|
18
|
+
chunks.push(toChunk(change, pending));
|
|
19
|
+
pending = [];
|
|
20
|
+
pendingChars = 0;
|
|
21
|
+
}
|
|
22
|
+
pending.push(region);
|
|
23
|
+
pendingChars += chars;
|
|
24
|
+
}
|
|
25
|
+
if (pending.length > 0) {
|
|
26
|
+
chunks.push(toChunk(change, pending));
|
|
27
|
+
}
|
|
28
|
+
return chunks;
|
|
29
|
+
}
|
|
30
|
+
export function estimateTokens(text) {
|
|
31
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
32
|
+
}
|
|
33
|
+
function changedRegions(change, lineCount) {
|
|
34
|
+
return [...change.changedLines]
|
|
35
|
+
.sort((a, b) => a - b)
|
|
36
|
+
.map(line => ({
|
|
37
|
+
start: Math.max(1, line - CONTEXT_LINES),
|
|
38
|
+
end: Math.min(lineCount, line + CONTEXT_LINES),
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
const IMPORT_LINE = /^(import\b|\)$|\t"|\s+"[^"]+"$)/;
|
|
42
|
+
const IMPORT_SCAN_LIMIT = 80;
|
|
43
|
+
// lines 1..N covering the file's import statements, or nothing when the file has none
|
|
44
|
+
function importRegion(source) {
|
|
45
|
+
const lines = source.split('\n', IMPORT_SCAN_LIMIT);
|
|
46
|
+
let end = 0;
|
|
47
|
+
lines.forEach((line, index) => {
|
|
48
|
+
if (IMPORT_LINE.test(line)) {
|
|
49
|
+
end = index + 1;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
return end === 0 ? [] : [{ start: 1, end }];
|
|
53
|
+
}
|
|
54
|
+
function mergeRegions(regions) {
|
|
55
|
+
const sorted = [...regions].sort((a, b) => a.start - b.start);
|
|
56
|
+
const merged = [];
|
|
57
|
+
for (const region of sorted) {
|
|
58
|
+
const last = merged.at(-1);
|
|
59
|
+
if (last !== undefined && region.start - last.end <= MERGE_GAP) {
|
|
60
|
+
last.end = Math.max(last.end, region.end);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
merged.push({ ...region });
|
|
64
|
+
}
|
|
65
|
+
return merged;
|
|
66
|
+
}
|
|
67
|
+
function toChunk(change, regions) {
|
|
68
|
+
const header = `file: ${change.path} (${change.lang})\n\n`;
|
|
69
|
+
const body = regions.map(region => renderRegion(change, region)).join(`\n${' '.repeat(GUTTER_WIDTH + 1)}...\n\n`);
|
|
70
|
+
const text = `${header}${body}`;
|
|
71
|
+
return {
|
|
72
|
+
path: change.path,
|
|
73
|
+
regions,
|
|
74
|
+
text,
|
|
75
|
+
estimatedTokens: estimateTokens(text),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function renderRegion(change, region) {
|
|
79
|
+
const lines = change.source.split('\n');
|
|
80
|
+
const rendered = [];
|
|
81
|
+
for (let line = region.start; line <= region.end; line += 1) {
|
|
82
|
+
const marker = change.changedLines.has(line) ? '+' : ' ';
|
|
83
|
+
rendered.push(`${marker}${String(line).padStart(GUTTER_WIDTH)} ${lines[line - 1] ?? ''}`);
|
|
84
|
+
}
|
|
85
|
+
return rendered.join('\n');
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=judge-chunks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"judge-chunks.js","sourceRoot":"","sources":["../../src/analyzers/judge-chunks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { Change } from '../change.js';
|
|
3
|
+
import type { Finding } from '../finding.js';
|
|
4
|
+
import type { JudgeRule } from '../rule.js';
|
|
5
|
+
export declare function judgeOutputSchema(ruleIds: readonly [string, ...string[]]): z.ZodObject<{
|
|
6
|
+
findings: z.ZodArray<z.ZodObject<{
|
|
7
|
+
ruleId: z.ZodEnum<{
|
|
8
|
+
[x: string]: string;
|
|
9
|
+
}>;
|
|
10
|
+
line: z.ZodNumber;
|
|
11
|
+
quote: z.ZodString;
|
|
12
|
+
message: z.ZodString;
|
|
13
|
+
confidence: z.ZodNumber;
|
|
14
|
+
symbol: z.ZodNullable<z.ZodString>;
|
|
15
|
+
}, z.core.$strip>>;
|
|
16
|
+
}, z.core.$strip>;
|
|
17
|
+
export type JudgeOutput = z.infer<ReturnType<typeof judgeOutputSchema>>;
|
|
18
|
+
export type RawJudgeFinding = JudgeOutput['findings'][number];
|
|
19
|
+
export declare function validateFindings(raw: readonly RawJudgeFinding[], change: Change, rules: ReadonlyMap<string, JudgeRule>, minConfidence: number): Finding[];
|