@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.
Files changed (195) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +143 -0
  3. package/SKILL.md +149 -0
  4. package/dist/analyzer.d.ts +5 -0
  5. package/dist/analyzer.js +13 -0
  6. package/dist/analyzer.js.map +1 -0
  7. package/dist/analyzers/ast.d.ts +12 -0
  8. package/dist/analyzers/ast.js +73 -0
  9. package/dist/analyzers/ast.js.map +1 -0
  10. package/dist/analyzers/judge-chunks.d.ts +13 -0
  11. package/dist/analyzers/judge-chunks.js +87 -0
  12. package/dist/analyzers/judge-chunks.js.map +1 -0
  13. package/dist/analyzers/judge-validate.d.ts +19 -0
  14. package/dist/analyzers/judge-validate.js +81 -0
  15. package/dist/analyzers/judge-validate.js.map +1 -0
  16. package/dist/analyzers/judge.d.ts +44 -0
  17. package/dist/analyzers/judge.js +177 -0
  18. package/dist/analyzers/judge.js.map +1 -0
  19. package/dist/change.d.ts +19 -0
  20. package/dist/change.js +87 -0
  21. package/dist/change.js.map +1 -0
  22. package/dist/cli.d.ts +2 -0
  23. package/dist/cli.js +125 -0
  24. package/dist/cli.js.map +1 -0
  25. package/dist/config.d.ts +91 -0
  26. package/dist/config.js +61 -0
  27. package/dist/config.js.map +1 -0
  28. package/dist/confirm.d.ts +11 -0
  29. package/dist/confirm.js +100 -0
  30. package/dist/confirm.js.map +1 -0
  31. package/dist/finding.d.ts +13 -0
  32. package/dist/finding.js +18 -0
  33. package/dist/finding.js.map +1 -0
  34. package/dist/lang.d.ts +16 -0
  35. package/dist/lang.js +37 -0
  36. package/dist/lang.js.map +1 -0
  37. package/dist/model.d.ts +2 -0
  38. package/dist/model.js +34 -0
  39. package/dist/model.js.map +1 -0
  40. package/dist/report.d.ts +124 -0
  41. package/dist/report.js +112 -0
  42. package/dist/report.js.map +1 -0
  43. package/dist/rule.d.ts +66 -0
  44. package/dist/rule.js +259 -0
  45. package/dist/rule.js.map +1 -0
  46. package/dist/scan.d.ts +14 -0
  47. package/dist/scan.js +33 -0
  48. package/dist/scan.js.map +1 -0
  49. package/dist/score.d.ts +147 -0
  50. package/dist/score.js +142 -0
  51. package/dist/score.js.map +1 -0
  52. package/dist/shared/concurrency.d.ts +5 -0
  53. package/dist/shared/concurrency.js +17 -0
  54. package/dist/shared/concurrency.js.map +1 -0
  55. package/dist/shared/env.d.ts +27 -0
  56. package/dist/shared/env.js +29 -0
  57. package/dist/shared/env.js.map +1 -0
  58. package/dist/shared/errors.d.ts +18 -0
  59. package/dist/shared/errors.js +26 -0
  60. package/dist/shared/errors.js.map +1 -0
  61. package/dist/shared/log.d.ts +2 -0
  62. package/dist/shared/log.js +18 -0
  63. package/dist/shared/log.js.map +1 -0
  64. package/dist/shared/paths.d.ts +3 -0
  65. package/dist/shared/paths.js +6 -0
  66. package/dist/shared/paths.js.map +1 -0
  67. package/dist/shared/prompts.d.ts +3 -0
  68. package/dist/shared/prompts.js +34 -0
  69. package/dist/shared/prompts.js.map +1 -0
  70. package/package.json +79 -0
  71. package/prompts/judge.md +37 -0
  72. package/rules/any/futureproof/abstraction/pass-through-wrapper.md +70 -0
  73. package/rules/any/futureproof/abstraction/single-caller-helper.md +38 -0
  74. package/rules/any/futureproof/abstraction/single-impl-interface.md +83 -0
  75. package/rules/any/futureproof/exports/dead-export.md +62 -0
  76. package/rules/any/futureproof/layering/framework-type-in-domain.md +83 -0
  77. package/rules/any/futureproof/layering/logic-in-handler.md +94 -0
  78. package/rules/any/futureproof/params/boolean-positional-param.md +128 -0
  79. package/rules/any/futureproof/params/positional-config-args.md +135 -0
  80. package/rules/any/futureproof/structure/wide-function.md +138 -0
  81. package/rules/any/futureproof/testing/test-asserts-implementation.md +70 -0
  82. package/rules/any/hacky/comments/comment-restates-code.md +50 -0
  83. package/rules/any/hacky/comments/shipped-todo-comment.md +55 -0
  84. package/rules/any/hacky/concurrency/sleep-based-sync.md +95 -0
  85. package/rules/any/hacky/config/hardcoded-url.md +88 -0
  86. package/rules/any/hacky/constants/magic-number.md +94 -0
  87. package/rules/any/hacky/debugging/debug-print.md +73 -0
  88. package/rules/any/hacky/duplication/copy-paste-block.md +117 -0
  89. package/rules/any/hacky/types/stringly-typed-enum.md +86 -0
  90. package/rules/any/idiom/comments/no-section-banners.md +47 -0
  91. package/rules/go/futureproof/constants/string-enums-when-serialized.md +47 -0
  92. package/rules/go/futureproof/constants/typed-constants-for-enums.md +45 -0
  93. package/rules/go/futureproof/interfaces/compile-time-impl-assertion.md +43 -0
  94. package/rules/go/futureproof/interfaces/define-interfaces-at-consumer.md +47 -0
  95. package/rules/go/futureproof/interfaces/keep-interfaces-small.md +53 -0
  96. package/rules/go/futureproof/naming/no-utils-helpers-common-package.md +34 -0
  97. package/rules/go/futureproof/structs/zero-value-usable.md +50 -0
  98. package/rules/go/futureproof/types/generics-over-any.md +55 -0
  99. package/rules/go/hacky/concurrency/no-goroutine-without-wait.md +44 -0
  100. package/rules/go/hacky/context/never-pass-nil-context.md +64 -0
  101. package/rules/go/hacky/context/no-context-in-struct.md +44 -0
  102. package/rules/go/hacky/errors/check-close-error-on-writable.md +53 -0
  103. package/rules/go/hacky/errors/custom-error-type-errors-as.md +54 -0
  104. package/rules/go/hacky/errors/ignored-error-blank.md +84 -0
  105. package/rules/go/hacky/errors/panic-only-unrecoverable.md +77 -0
  106. package/rules/go/hacky/errors/sentinel-errors-with-errors-is.md +63 -0
  107. package/rules/go/hacky/errors/wrap-errors-with-w.md +71 -0
  108. package/rules/go/hacky/state/package-level-mutable-var.md +95 -0
  109. package/rules/go/hacky/structure/os-exit-only-in-main.md +61 -0
  110. package/rules/go/hacky/type-safety/no-map-string-any.md +49 -0
  111. package/rules/go/hacky/types/json-into-struct-not-map.md +48 -0
  112. package/rules/go/hacky/types/no-any.md +71 -0
  113. package/rules/go/hacky/types/no-map-any-any.md +35 -0
  114. package/rules/go/idiom/comments/doc-comment-on-exported.md +81 -0
  115. package/rules/go/idiom/comments/doc-comment-starts-with-name.md +36 -0
  116. package/rules/go/idiom/concurrency/mutex-over-channels-for-state.md +47 -0
  117. package/rules/go/idiom/constructors/constructor-named-new.md +37 -0
  118. package/rules/go/idiom/context/context-is-first-param.md +47 -0
  119. package/rules/go/idiom/context/io-takes-context.md +50 -0
  120. package/rules/go/idiom/errors/capitalized-error-message.md +52 -0
  121. package/rules/go/idiom/errors/check-error-immediately.md +45 -0
  122. package/rules/go/idiom/errors/defer-close-explicit-discard.md +65 -0
  123. package/rules/go/idiom/errors/error-is-last-return-value.md +53 -0
  124. package/rules/go/idiom/errors/no-log-and-return.md +45 -0
  125. package/rules/go/idiom/formatting/blank-line-before-return.md +48 -0
  126. package/rules/go/idiom/formatting/multiline-struct-literals.md +56 -0
  127. package/rules/go/idiom/imports/import-order-groups.md +42 -0
  128. package/rules/go/idiom/imports/side-effect-imports-own-group.md +51 -0
  129. package/rules/go/idiom/interfaces/accept-interfaces-return-concrete.md +39 -0
  130. package/rules/go/idiom/iterators/prefer-iter-seq.md +47 -0
  131. package/rules/go/idiom/logging/lowercase-log-messages.md +80 -0
  132. package/rules/go/idiom/logging/use-slog.md +46 -0
  133. package/rules/go/idiom/naming/acronyms-consistent-case.md +97 -0
  134. package/rules/go/idiom/naming/interface-er-suffix.md +33 -0
  135. package/rules/go/idiom/naming/no-package-name-stutter.md +37 -0
  136. package/rules/go/idiom/naming/package-name-single-lowercase-word.md +40 -0
  137. package/rules/go/idiom/receivers/consistent-receiver-kind-per-type.md +40 -0
  138. package/rules/go/idiom/receivers/no-this-receiver.md +37 -0
  139. package/rules/go/idiom/receivers/pointer-vs-value-receiver-choice.md +49 -0
  140. package/rules/go/idiom/receivers/short-receiver-names.md +43 -0
  141. package/rules/go/idiom/structs/new-expr-for-pointer-fields.md +40 -0
  142. package/rules/go/idiom/structure/main-delegates-to-run.md +54 -0
  143. package/rules/ts/futureproof/classes/composition-over-abstract-base.md +73 -0
  144. package/rules/ts/futureproof/functions/explicit-return-type-on-exports.md +50 -0
  145. package/rules/ts/futureproof/structure/colocate-zod-schemas.md +46 -0
  146. package/rules/ts/futureproof/types/exhaustive-switch-never-check.md +52 -0
  147. package/rules/ts/futureproof/types/readonly-for-immutable-data.md +46 -0
  148. package/rules/ts/futureproof/zod/enum-values-from-const-object.md +37 -0
  149. package/rules/ts/futureproof/zod/no-z-native-enum.md +36 -0
  150. package/rules/ts/futureproof/zod/schema-first-infer-type.md +46 -0
  151. package/rules/ts/hacky/async/no-floating-promises.md +52 -0
  152. package/rules/ts/hacky/config/env-read-outside-config.md +56 -0
  153. package/rules/ts/hacky/errors/catch-param-typed-unknown.md +67 -0
  154. package/rules/ts/hacky/errors/custom-error-class-instanceof.md +66 -0
  155. package/rules/ts/hacky/errors/empty-catch.md +52 -0
  156. package/rules/ts/hacky/errors/log-and-swallow.md +65 -0
  157. package/rules/ts/hacky/errors/throw-typed-error-with-context.md +64 -0
  158. package/rules/ts/hacky/operators/nullish-coalescing-over-or.md +50 -0
  159. package/rules/ts/hacky/state/exported-let.md +60 -0
  160. package/rules/ts/hacky/type-safety/no-double-assertion.md +34 -0
  161. package/rules/ts/hacky/type-safety/no-explicit-any.md +48 -0
  162. package/rules/ts/hacky/type-safety/no-non-null-assertion.md +43 -0
  163. package/rules/ts/hacky/type-safety/no-unchecked-type-assertion.md +54 -0
  164. package/rules/ts/hacky/type-safety/validate-parsed-json.md +48 -0
  165. package/rules/ts/idiom/async/no-promise-chains.md +45 -0
  166. package/rules/ts/idiom/async/parallelize-independent-awaits.md +46 -0
  167. package/rules/ts/idiom/comments/doc-comment-above-declaration.md +43 -0
  168. package/rules/ts/idiom/constants/as-const-for-literal-config.md +65 -0
  169. package/rules/ts/idiom/constants/no-enum.md +37 -0
  170. package/rules/ts/idiom/control-flow/always-brace-if.md +56 -0
  171. package/rules/ts/idiom/control-flow/guard-clauses-early-return.md +52 -0
  172. package/rules/ts/idiom/control-flow/no-nested-ternary.md +54 -0
  173. package/rules/ts/idiom/errors/error-message-lowercase.md +56 -0
  174. package/rules/ts/idiom/errors/log-at-boundary-not-every-layer.md +66 -0
  175. package/rules/ts/idiom/exports/inline-export-at-declaration.md +49 -0
  176. package/rules/ts/idiom/exports/no-default-export.md +37 -0
  177. package/rules/ts/idiom/formatting/multiline-object-literals.md +46 -0
  178. package/rules/ts/idiom/functions/arrow-for-callbacks.md +44 -0
  179. package/rules/ts/idiom/functions/function-declaration-for-top-level.md +56 -0
  180. package/rules/ts/idiom/imports/export-type-for-types.md +34 -0
  181. package/rules/ts/idiom/imports/import-order.md +66 -0
  182. package/rules/ts/idiom/imports/import-type-for-types.md +50 -0
  183. package/rules/ts/idiom/imports/side-effect-imports-last.md +42 -0
  184. package/rules/ts/idiom/logging/structured-logging-lowercase.md +45 -0
  185. package/rules/ts/idiom/naming/boolean-name-question-prefix.md +42 -0
  186. package/rules/ts/idiom/naming/descriptive-identifier-quality.md +48 -0
  187. package/rules/ts/idiom/naming/screaming-snake-module-constants.md +58 -0
  188. package/rules/ts/idiom/strings/template-literals-over-concat.md +51 -0
  189. package/rules/ts/idiom/types/interface-extends-over-intersection.md +44 -0
  190. package/rules/ts/idiom/types/null-vs-undefined-convention.md +43 -0
  191. package/rules/ts/idiom/types/prefer-type-over-interface.md +64 -0
  192. package/rules/ts/idiom/types/satisfies-over-annotation.md +43 -0
  193. package/rules/ts/idiom/types/type-predicates-for-narrowing.md +53 -0
  194. package/rules/ts/idiom/variables/const-by-default.md +41 -0
  195. package/rules/ts/idiom/variables/no-var.md +36 -0
@@ -0,0 +1,83 @@
1
+ ---
2
+ severity: minor
3
+ detect: judge
4
+ confirm: implCount
5
+ falsePositives:
6
+ - a port at an architecture boundary whose second implementation is a fake or mock in tests
7
+ - a small consumer-side interface in Go, one or two methods declared next to the code that uses them, which narrows a dependency rather than mirroring it
8
+ - an interface that is part of a published library's contract, where implementations live outside this repository
9
+ - an interface with several implementations, or one whose second implementation arrives in the same change
10
+ ---
11
+
12
+ ## Why
13
+
14
+ An interface with one implementation and no test double is a copy of a type's method list that has to be edited every time the type changes, and it forces readers through an extra jump to find the code that runs. It does not decouple anything, because nothing else plugs in. Use the concrete type until a second implementation or a test fake exists; extracting an interface then is a mechanical change.
15
+
16
+ ## Message
17
+
18
+ interface has a single implementation and no test double; use the concrete type until a second one exists
19
+
20
+ ## Bad
21
+
22
+ ```ts
23
+ // BAD: the interface mirrors the only class that implements it
24
+ export interface UserRepository {
25
+ findById(id: string): Promise<User | undefined>;
26
+ insert(input: CreateUserInput): Promise<User>;
27
+ delete(id: string): Promise<void>;
28
+ }
29
+
30
+ export class PostgresUserRepository implements UserRepository {
31
+ async findById(id: string): Promise<User | undefined> {
32
+ return this.db.selectOne(usersTable, { id });
33
+ }
34
+
35
+ async insert(input: CreateUserInput): Promise<User> {
36
+ return this.db.insert(usersTable, input);
37
+ }
38
+
39
+ async delete(id: string): Promise<void> {
40
+ await this.db.delete(usersTable, { id });
41
+ }
42
+ }
43
+ ```
44
+
45
+ ```go
46
+ // BAD: the interface mirrors the whole method set of the only type that satisfies it
47
+ type UserStore interface {
48
+ FindByID(ctx context.Context, id string) (User, error)
49
+ Insert(ctx context.Context, in CreateUserInput) (User, error)
50
+ Delete(ctx context.Context, id string) error
51
+ }
52
+
53
+ var _ UserStore = (*PostgresUserStore)(nil)
54
+
55
+ type PostgresUserStore struct {
56
+ db *sql.DB
57
+ }
58
+ ```
59
+
60
+ ## Good
61
+
62
+ ```ts
63
+ export class PostgresUserRepository {
64
+ async findById(id: string): Promise<User | undefined> {
65
+ return this.db.selectOne(usersTable, { id });
66
+ }
67
+
68
+ async insert(input: CreateUserInput): Promise<User> {
69
+ return this.db.insert(usersTable, input);
70
+ }
71
+ }
72
+ ```
73
+
74
+ ```go
75
+ // UserFinder is the one method the notifier needs; any store that can find users satisfies it.
76
+ type UserFinder interface {
77
+ FindByID(ctx context.Context, id string) (User, error)
78
+ }
79
+
80
+ type Notifier struct {
81
+ users UserFinder
82
+ }
83
+ ```
@@ -0,0 +1,62 @@
1
+ ---
2
+ severity: minor
3
+ detect: judge
4
+ confirm: refCount
5
+ ignore:
6
+ - '**/index.ts'
7
+ - '**/*.test.ts'
8
+ - '**/*.spec.ts'
9
+ - '**/*_test.go'
10
+ falsePositives:
11
+ - the public API of a library package or a package entry point, where callers live outside this repository
12
+ - CLI binaries, `main`, `init`, and framework entry points such as route or plugin registrations
13
+ - symbols reached by reflection, templates, serialization tags, or generated code
14
+ - methods that exist to satisfy an interface, such as `String()` or `MarshalJSON()`
15
+ - types and constants that describe a wire format or database schema, which are referenced by name only in data
16
+ ---
17
+
18
+ ## Why
19
+
20
+ An exported symbol that nothing references is code that must still compile, be reviewed, and be kept consistent with every refactor, while offering nothing in return. Exporting it also signals to readers that something depends on it, so they leave it alone. Delete it; version control remembers it if it is ever needed.
21
+
22
+ ## Message
23
+
24
+ exported symbol is referenced nowhere; delete it or make it private
25
+
26
+ ## Bad
27
+
28
+ ```ts
29
+ // BAD: exported but no caller in the repository
30
+ export function formatLegacyDate(date: Date): string {
31
+ return `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()}`;
32
+ }
33
+
34
+ export function formatDate(date: Date): string {
35
+ return date.toISOString().slice(0, 10);
36
+ }
37
+ ```
38
+
39
+ ```go
40
+ // BAD: exported but no caller in the repository
41
+ func FormatLegacyDate(t time.Time) string {
42
+ return t.Format("2/1/2006")
43
+ }
44
+
45
+ func FormatDate(t time.Time) string {
46
+ return t.Format(time.DateOnly)
47
+ }
48
+ ```
49
+
50
+ ## Good
51
+
52
+ ```ts
53
+ export function formatDate(date: Date): string {
54
+ return date.toISOString().slice(0, 10);
55
+ }
56
+ ```
57
+
58
+ ```go
59
+ func FormatDate(t time.Time) string {
60
+ return t.Format(time.DateOnly)
61
+ }
62
+ ```
@@ -0,0 +1,83 @@
1
+ ---
2
+ severity: major
3
+ detect: judge
4
+ ignore:
5
+ - '**/handlers/**'
6
+ - '**/handler/**'
7
+ - '**/api/**'
8
+ - '**/routes/**'
9
+ - '**/router/**'
10
+ - '**/controllers/**'
11
+ - '**/middleware/**'
12
+ - '**/transport/**'
13
+ - '**/http/**'
14
+ - '**/*.handler.ts'
15
+ - '**/*.controller.ts'
16
+ - '**/*.route.ts'
17
+ - '**/main.go'
18
+ - '**/cmd/**'
19
+ - '**/*.test.ts'
20
+ - '**/*.spec.ts'
21
+ - '**/*_test.go'
22
+ ---
23
+
24
+ ## Why
25
+
26
+ A service function that takes the framework's request or response type can only be called by that framework: not from a job, a CLI, another transport, or a unit test without a fake request. It also pulls every HTTP concern, from headers to status codes, into the layer that should only know the domain. Give the service plain typed inputs and return plain typed results; the handler translates at the edge.
27
+
28
+ ## Message
29
+
30
+ http framework type in a domain signature; take plain input and return a plain result
31
+
32
+ ## Bad
33
+
34
+ ```ts
35
+ export class OrderService {
36
+ // BAD: the service can only be driven by express
37
+ async create(req: Request, res: Response): Promise<void> {
38
+ const input = createOrderSchema.parse(req.body);
39
+ const order = await this.repo.insert(input);
40
+ res.status(201).json(order);
41
+ }
42
+ }
43
+ ```
44
+
45
+ ```go
46
+ // BAD: the service can only be driven by net/http
47
+ func (s *OrderService) Create(w http.ResponseWriter, r *http.Request) {
48
+ var in CreateOrderInput
49
+ if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
50
+ http.Error(w, err.Error(), http.StatusBadRequest)
51
+ return
52
+ }
53
+
54
+ order, err := s.repo.Insert(r.Context(), in)
55
+ if err != nil {
56
+ http.Error(w, err.Error(), http.StatusInternalServerError)
57
+ return
58
+ }
59
+
60
+ writeJSON(w, http.StatusCreated, order)
61
+ }
62
+ ```
63
+
64
+ ## Good
65
+
66
+ ```ts
67
+ export class OrderService {
68
+ async create(input: CreateOrderInput): Promise<Order> {
69
+ return this.repo.insert(input);
70
+ }
71
+ }
72
+ ```
73
+
74
+ ```go
75
+ func (s *OrderService) Create(ctx context.Context, in CreateOrderInput) (Order, error) {
76
+ order, err := s.repo.Insert(ctx, in)
77
+ if err != nil {
78
+ return Order{}, fmt.Errorf("inserting order: %w", err)
79
+ }
80
+
81
+ return order, nil
82
+ }
83
+ ```
@@ -0,0 +1,94 @@
1
+ ---
2
+ severity: major
3
+ detect: judge
4
+ falsePositives:
5
+ - parsing and validating the request, and mapping a service result or error to a status code, which is the handler's job
6
+ - a single guard clause such as checking authentication before delegating
7
+ - a small script, example, or prototype with no service layer at all
8
+ - middleware, which operates on the request itself rather than on domain state
9
+ ---
10
+
11
+ ## Why
12
+
13
+ Business rules written inside an HTTP handler can only be reached through HTTP, so they cannot be reused by a CLI, a queue consumer, or a scheduled job, and they can only be tested by spinning up requests. The handler grows with every rule until nobody can see the request handling for the logic. Keep the handler to parsing input, calling one service function, and mapping the result to a response; the rules live in the service where they can be called from anywhere.
14
+
15
+ ## Message
16
+
17
+ business logic inside an http handler; move it to a service the handler calls
18
+
19
+ ## Bad
20
+
21
+ ```ts
22
+ router.post('/checkout', async (req, res) => {
23
+ const cart = await carts.find(req.session.cartId);
24
+ // BAD: pricing, stock, and persistence rules live in the route
25
+ let total = 0;
26
+ for (const line of cart.lines) {
27
+ const product = await products.find(line.productId);
28
+ if (product.stock < line.quantity) {
29
+ return res.status(409).json({ error: 'out of stock' });
30
+ }
31
+ total += product.price * line.quantity;
32
+ }
33
+ if (cart.coupon) {
34
+ total = total * (1 - cart.coupon.percent / 100);
35
+ }
36
+ const order = await orders.insert({ cartId: cart.id, total });
37
+ res.status(201).json(order);
38
+ });
39
+ ```
40
+
41
+ ```go
42
+ func (h *Handler) Checkout(w http.ResponseWriter, r *http.Request) {
43
+ cart, err := h.carts.Find(r.Context(), sessionCartID(r))
44
+ if err != nil {
45
+ http.Error(w, "cart not found", http.StatusNotFound)
46
+ return
47
+ }
48
+ // BAD: pricing, stock, and persistence rules live in the handler
49
+ var total int
50
+ for _, line := range cart.Lines {
51
+ product, err := h.products.Find(r.Context(), line.ProductID)
52
+ if err != nil || product.Stock < line.Quantity {
53
+ http.Error(w, "out of stock", http.StatusConflict)
54
+ return
55
+ }
56
+ total += product.Price * line.Quantity
57
+ }
58
+ if cart.Coupon != nil {
59
+ total = total * (100 - cart.Coupon.Percent) / 100
60
+ }
61
+ order, err := h.orders.Insert(r.Context(), Order{CartID: cart.ID, Total: total})
62
+ if err != nil {
63
+ http.Error(w, "internal error", http.StatusInternalServerError)
64
+ return
65
+ }
66
+ writeJSON(w, http.StatusCreated, order)
67
+ }
68
+ ```
69
+
70
+ ## Good
71
+
72
+ ```ts
73
+ router.post('/checkout', async (req, res) => {
74
+ const result = await checkout.run({ cartId: req.session.cartId });
75
+
76
+ if (!result.ok) {
77
+ return res.status(statusFor(result.error)).json({ error: result.error.message });
78
+ }
79
+
80
+ res.status(201).json(result.order);
81
+ });
82
+ ```
83
+
84
+ ```go
85
+ func (h *Handler) Checkout(w http.ResponseWriter, r *http.Request) {
86
+ order, err := h.checkout.Run(r.Context(), checkout.Input{CartID: sessionCartID(r)})
87
+ if err != nil {
88
+ writeError(w, err)
89
+ return
90
+ }
91
+
92
+ writeJSON(w, http.StatusCreated, order)
93
+ }
94
+ ```
@@ -0,0 +1,128 @@
1
+ ---
2
+ severity: minor
3
+ detect: ast
4
+ ast:
5
+ ts:
6
+ utils:
7
+ param:
8
+ any:
9
+ - kind: required_parameter
10
+ - kind: optional_parameter
11
+ rule:
12
+ matches: param
13
+ any:
14
+ - has:
15
+ kind: type_annotation
16
+ has:
17
+ kind: predefined_type
18
+ regex: '^boolean$'
19
+ - all:
20
+ - not:
21
+ has:
22
+ kind: type_annotation
23
+ - has:
24
+ field: value
25
+ any:
26
+ - kind: 'true'
27
+ - kind: 'false'
28
+ all:
29
+ - any:
30
+ - follows:
31
+ matches: param
32
+ stopBy: end
33
+ - precedes:
34
+ matches: param
35
+ stopBy: end
36
+ go:
37
+ rule:
38
+ kind: parameter_declaration
39
+ has:
40
+ field: type
41
+ regex: '^bool$'
42
+ inside:
43
+ kind: parameter_list
44
+ not:
45
+ follows:
46
+ kind: parameter_list
47
+ any:
48
+ - follows:
49
+ kind: parameter_declaration
50
+ stopBy: end
51
+ - precedes:
52
+ kind: parameter_declaration
53
+ stopBy: end
54
+ ignore:
55
+ - '**/*.test.ts'
56
+ - '**/*.spec.ts'
57
+ - '**/*_test.go'
58
+ ---
59
+
60
+ ## Why
61
+
62
+ A boolean argument reads as `render(user, true)` at the call site, so every reader has to open the definition to learn what `true` means, and the second flag turns the function into four functions in a trench coat. Callers cannot add a third mode without touching every existing call. Take an options object or struct with named fields, or split the function into two named behaviors.
63
+
64
+ ## Message
65
+
66
+ boolean positional parameter; use an options object or two functions
67
+
68
+ ## Bad
69
+
70
+ ```ts
71
+ // BAD: render(user, true) says nothing at the call site
72
+ export function render(user: User, compact: boolean): string {
73
+ return compact ? user.name : `${user.name} <${user.email}>`;
74
+ }
75
+ ```
76
+
77
+ ```go
78
+ // BAD: Render(user, true) says nothing at the call site
79
+ func Render(user User, compact bool) string {
80
+ if compact {
81
+ return user.Name
82
+ }
83
+
84
+ return user.Name + " <" + user.Email + ">"
85
+ }
86
+ ```
87
+
88
+ ## Good
89
+
90
+ ```ts
91
+ type RenderOptions = {
92
+ compact?: boolean;
93
+ };
94
+
95
+ export function render(user: User, options: RenderOptions = {}): string {
96
+ return options.compact ? user.name : `${user.name} <${user.email}>`;
97
+ }
98
+ ```
99
+
100
+ ```ts
101
+ export function renderCompact(user: User): string {
102
+ return user.name;
103
+ }
104
+
105
+ export function renderFull(user: User): string {
106
+ return `${user.name} <${user.email}>`;
107
+ }
108
+ ```
109
+
110
+ ```go
111
+ type RenderOptions struct {
112
+ Compact bool
113
+ }
114
+
115
+ func Render(user User, opts RenderOptions) string {
116
+ if opts.Compact {
117
+ return user.Name
118
+ }
119
+
120
+ return user.Name + " <" + user.Email + ">"
121
+ }
122
+ ```
123
+
124
+ ```go
125
+ func IsAdmin(user User) bool {
126
+ return user.Role == RoleAdmin
127
+ }
128
+ ```
@@ -0,0 +1,135 @@
1
+ ---
2
+ severity: minor
3
+ detect: ast
4
+ ast:
5
+ ts:
6
+ utils:
7
+ param:
8
+ any:
9
+ - kind: required_parameter
10
+ - kind: optional_parameter
11
+ rule:
12
+ kind: formal_parameters
13
+ inside:
14
+ kind: method_definition
15
+ has:
16
+ field: name
17
+ regex: '^constructor$'
18
+ has:
19
+ matches: param
20
+ follows:
21
+ matches: param
22
+ stopBy: end
23
+ follows:
24
+ matches: param
25
+ stopBy: end
26
+ follows:
27
+ matches: param
28
+ stopBy: end
29
+ go:
30
+ utils:
31
+ param:
32
+ kind: parameter_declaration
33
+ rule:
34
+ kind: parameter_list
35
+ not:
36
+ follows:
37
+ kind: parameter_list
38
+ inside:
39
+ kind: function_declaration
40
+ has:
41
+ field: name
42
+ regex: '^(New|Must)[A-Z]?'
43
+ has:
44
+ matches: param
45
+ follows:
46
+ matches: param
47
+ stopBy: end
48
+ follows:
49
+ matches: param
50
+ stopBy: end
51
+ follows:
52
+ matches: param
53
+ stopBy: end
54
+ ignore:
55
+ - '**/*.test.ts'
56
+ - '**/*.spec.ts'
57
+ - '**/*_test.go'
58
+ ---
59
+
60
+ ## Why
61
+
62
+ A constructor with four positional arguments is called as `new Server('0.0.0.0', 8080, true, 30)`, which no reader can decode and no caller can extend without breaking every other caller. Constructors grow more often than any other signature, because every new dependency and setting lands there. Take one options object or config struct with named, defaultable fields, or use functional options in Go.
63
+
64
+ ## Message
65
+
66
+ constructor takes 4+ positional arguments; take an options object or config struct
67
+
68
+ ## Bad
69
+
70
+ ```ts
71
+ export class Server {
72
+ // BAD: new Server('0.0.0.0', 8080, true, 30) is unreadable and cannot grow
73
+ constructor(host: string, port: number, tls: boolean, timeoutSeconds: number) {
74
+ this.address = `${host}:${port}`;
75
+ this.tls = tls;
76
+ this.timeoutSeconds = timeoutSeconds;
77
+ }
78
+ }
79
+ ```
80
+
81
+ ```go
82
+ // BAD: NewServer("0.0.0.0", 8080, true, 30*time.Second) is unreadable and cannot grow
83
+ func NewServer(host string, port int, tls bool, timeout time.Duration) *Server {
84
+ return &Server{addr: fmt.Sprintf("%s:%d", host, port), tls: tls, timeout: timeout}
85
+ }
86
+ ```
87
+
88
+ ## Good
89
+
90
+ ```ts
91
+ type ServerOptions = {
92
+ host: string;
93
+ port: number;
94
+ tls?: boolean;
95
+ timeoutSeconds?: number;
96
+ };
97
+
98
+ export class Server {
99
+ constructor(options: ServerOptions) {
100
+ this.address = `${options.host}:${options.port}`;
101
+ this.tls = options.tls ?? false;
102
+ this.timeoutSeconds = options.timeoutSeconds ?? 30;
103
+ }
104
+ }
105
+ ```
106
+
107
+ ```go
108
+ type Config struct {
109
+ Host string
110
+ Port int
111
+ TLS bool
112
+ Timeout time.Duration
113
+ }
114
+
115
+ func NewServer(cfg Config) *Server {
116
+ return &Server{addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), tls: cfg.TLS, timeout: cfg.Timeout}
117
+ }
118
+ ```
119
+
120
+ ```go
121
+ type Option func(*Server)
122
+
123
+ func WithTimeout(d time.Duration) Option {
124
+ return func(s *Server) { s.timeout = d }
125
+ }
126
+
127
+ func NewServer(addr string, opts ...Option) *Server {
128
+ s := &Server{addr: addr, timeout: 30 * time.Second}
129
+ for _, opt := range opts {
130
+ opt(s)
131
+ }
132
+
133
+ return s
134
+ }
135
+ ```