@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,138 @@
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
+ any:
13
+ - all:
14
+ - any:
15
+ - kind: function_declaration
16
+ - kind: method_definition
17
+ - kind: function_expression
18
+ - kind: arrow_function
19
+ - regex: '^(?:[^\n]*\n){60}'
20
+ - kind: formal_parameters
21
+ has:
22
+ matches: param
23
+ follows:
24
+ matches: param
25
+ stopBy: end
26
+ follows:
27
+ matches: param
28
+ stopBy: end
29
+ follows:
30
+ matches: param
31
+ stopBy: end
32
+ follows:
33
+ matches: param
34
+ stopBy: end
35
+ follows:
36
+ matches: param
37
+ stopBy: end
38
+ go:
39
+ utils:
40
+ param:
41
+ kind: parameter_declaration
42
+ rule:
43
+ any:
44
+ - all:
45
+ - any:
46
+ - kind: function_declaration
47
+ - kind: method_declaration
48
+ - kind: func_literal
49
+ - regex: '^(?:[^\n]*\n){60}'
50
+ - kind: parameter_list
51
+ not:
52
+ follows:
53
+ kind: parameter_list
54
+ has:
55
+ matches: param
56
+ follows:
57
+ matches: param
58
+ stopBy: end
59
+ follows:
60
+ matches: param
61
+ stopBy: end
62
+ follows:
63
+ matches: param
64
+ stopBy: end
65
+ follows:
66
+ matches: param
67
+ stopBy: end
68
+ follows:
69
+ matches: param
70
+ stopBy: end
71
+ ignore:
72
+ - '**/*.test.ts'
73
+ - '**/*.spec.ts'
74
+ - '**/*_test.go'
75
+ ---
76
+
77
+ ## Why
78
+
79
+ A function with six or more parameters or more than sixty lines is doing several jobs, and each new requirement makes it longer and its call sites more fragile, since positional arguments are easy to swap and impossible to read. Group related parameters into a typed options object or struct, and split the body along the steps it already performs. The pieces get names, tests, and a chance of being reused.
80
+
81
+ ## Message
82
+
83
+ function is too wide: more than 5 parameters or over 60 lines; group parameters into a struct and split the body
84
+
85
+ ## Bad
86
+
87
+ ```ts
88
+ // BAD: six positional arguments nobody can order from memory
89
+ export function createInvoice(
90
+ customerId: string,
91
+ items: LineItem[],
92
+ currency: string,
93
+ dueDate: Date,
94
+ notes: string,
95
+ sendEmail: boolean
96
+ ): Invoice {
97
+ return build(customerId, items, currency, dueDate, notes, sendEmail);
98
+ }
99
+ ```
100
+
101
+ ```go
102
+ // BAD: six positional arguments nobody can order from memory
103
+ func CreateInvoice(customerID string, items []LineItem, currency string, dueDate time.Time, notes string, sendEmail bool) Invoice {
104
+ return build(customerID, items, currency, dueDate, notes, sendEmail)
105
+ }
106
+ ```
107
+
108
+ ## Good
109
+
110
+ ```ts
111
+ type CreateInvoiceInput = {
112
+ customerId: string;
113
+ items: LineItem[];
114
+ currency: string;
115
+ dueDate: Date;
116
+ notes?: string;
117
+ sendEmail?: boolean;
118
+ };
119
+
120
+ export function createInvoice(input: CreateInvoiceInput): Invoice {
121
+ return build(input);
122
+ }
123
+ ```
124
+
125
+ ```go
126
+ type CreateInvoiceInput struct {
127
+ CustomerID string
128
+ Items []LineItem
129
+ Currency string
130
+ DueDate time.Time
131
+ Notes string
132
+ SendEmail bool
133
+ }
134
+
135
+ func CreateInvoice(in CreateInvoiceInput) Invoice {
136
+ return build(in)
137
+ }
138
+ ```
@@ -0,0 +1,70 @@
1
+ ---
2
+ severity: minor
3
+ detect: judge
4
+ falsePositives:
5
+ - asserting that a collaborator at a boundary received the right call, such as the mailer being sent the right message, when that call is the observable outcome
6
+ - calling an unexported or private pure function directly and asserting on its return value
7
+ - reaching into unexported fields to set up state before exercising the public behavior
8
+ - asserting call counts for behavior the contract promises, such as a cache hitting the loader exactly once
9
+ ---
10
+
11
+ ## Why
12
+
13
+ A test that spies on private methods or inspects unexported fields passes only while the code is written one particular way, so a refactor that keeps every behavior still turns the suite red and teaches people to delete tests. It also documents nothing a caller can rely on. Assert on what the caller sees: return values, thrown errors, persisted state, and calls to external systems.
14
+
15
+ ## Message
16
+
17
+ test asserts on internals; assert on observable behavior instead
18
+
19
+ ## Bad
20
+
21
+ ```ts
22
+ it('creates a user', async () => {
23
+ const service = new UserService(repo);
24
+ // BAD: the test is coupled to a private helper's name and arguments
25
+ const spy = vi.spyOn(service as never, 'normalizeEmail');
26
+
27
+ await service.create({ email: 'Ada@Example.com' });
28
+
29
+ expect(spy).toHaveBeenCalledWith('Ada@Example.com');
30
+ });
31
+ ```
32
+
33
+ ```go
34
+ func TestPutStoresEntry(t *testing.T) {
35
+ c := cache.New()
36
+
37
+ c.Put("k", []byte("v"))
38
+
39
+ // BAD: the test knows the map is called entries and breaks when storage changes
40
+ if len(c.entries) != 1 {
41
+ t.Fatalf("expected 1 entry, got %d", len(c.entries))
42
+ }
43
+ }
44
+ ```
45
+
46
+ ## Good
47
+
48
+ ```ts
49
+ it('creates a user with a normalized email', async () => {
50
+ const service = new UserService(repo);
51
+
52
+ const user = await service.create({ email: 'Ada@Example.com' });
53
+
54
+ expect(user.email).toBe('ada@example.com');
55
+ expect(await repo.findById(user.id)).toEqual(user);
56
+ });
57
+ ```
58
+
59
+ ```go
60
+ func TestPutThenGet(t *testing.T) {
61
+ c := cache.New()
62
+
63
+ c.Put("k", []byte("v"))
64
+
65
+ got, ok := c.Get("k")
66
+ if !ok || string(got) != "v" {
67
+ t.Fatalf("Get(k) = %q, %v; want v, true", got, ok)
68
+ }
69
+ }
70
+ ```
@@ -0,0 +1,50 @@
1
+ ---
2
+ severity: minor
3
+ detect: judge
4
+ falsePositives:
5
+ - doc comments on exported symbols that state the contract, even when short
6
+ - comments explaining why, a constraint, or a non-obvious consequence
7
+ ---
8
+
9
+ ## Why
10
+
11
+ A comment that says what the next line already says is noise the reader has to check against the code, and it goes stale the first time the code changes. Comments earn their place by saying why, naming a constraint, or warning about a consequence the code cannot show.
12
+
13
+ ## Message
14
+
15
+ comment restates the code; say why or delete it
16
+
17
+ ## Bad
18
+
19
+ ```ts
20
+ // BAD: the comment is the line, in English
21
+ // increment the counter
22
+ counter += 1;
23
+ ```
24
+
25
+ ```go
26
+ func total(orders []Order) int {
27
+ sum := 0
28
+ // BAD: the comment is the loop, in English
29
+ // loop over the orders and add up the amounts
30
+ for _, order := range orders {
31
+ sum += order.Amount
32
+ }
33
+
34
+ return sum
35
+ }
36
+ ```
37
+
38
+ ## Good
39
+
40
+ ```ts
41
+ // the API rejects batches over 100 items, so chunk before sending
42
+ const batches = chunk(items, 100);
43
+ ```
44
+
45
+ ```go
46
+ // Find returns ErrNotFound rather than a nil user so callers cannot forget the missing case.
47
+ func Find(id string) (*User, error) {
48
+ return repo.Find(id)
49
+ }
50
+ ```
@@ -0,0 +1,55 @@
1
+ ---
2
+ severity: minor
3
+ detect: ast
4
+ ast:
5
+ ts:
6
+ rule:
7
+ kind: comment
8
+ regex: '^(//|/\*+)[\s*]*(TODO|FIXME|HACK|XXX)\b'
9
+ go:
10
+ rule:
11
+ kind: comment
12
+ regex: '^(//|/\*+)[\s*]*(TODO|FIXME|HACK|XXX)\b'
13
+ ---
14
+
15
+ ## Why
16
+
17
+ A TODO in merged code is a known defect with no owner, no deadline, and no ticket, and it stays there until someone rediscovers the problem the hard way. Either do the work before merging, or open an issue and reference it so the gap is tracked where people actually look. A comment that explains a deliberate limitation and points at the issue is fine; a bare marker is not.
18
+
19
+ ## Message
20
+
21
+ todo marker shipped in code; do the work or link the tracking issue
22
+
23
+ ## Bad
24
+
25
+ ```ts
26
+ export function parsePrice(raw: string): number {
27
+ // BAD: known gap with no owner
28
+ // TODO: handle currencies other than USD
29
+ return Number(raw.replace('$', ''));
30
+ }
31
+ ```
32
+
33
+ ```go
34
+ func ParsePrice(raw string) (int, error) {
35
+ // BAD: known shortcut with no owner
36
+ // FIXME: this breaks on prices over 1000
37
+ return strconv.Atoi(strings.TrimPrefix(raw, "$"))
38
+ }
39
+ ```
40
+
41
+ ## Good
42
+
43
+ ```ts
44
+ // only USD is supported; other currencies are rejected upstream by the validator (see #482)
45
+ export function parsePrice(raw: string): number {
46
+ return Number(raw.replace('$', ''));
47
+ }
48
+ ```
49
+
50
+ ```go
51
+ // ParsePrice accepts USD only; other currencies are rejected by the validator (see #482).
52
+ func ParsePrice(raw string) (int, error) {
53
+ return strconv.Atoi(strings.TrimPrefix(raw, "$"))
54
+ }
55
+ ```
@@ -0,0 +1,95 @@
1
+ ---
2
+ severity: major
3
+ detect: ast
4
+ ast:
5
+ ts:
6
+ rule:
7
+ kind: new_expression
8
+ pattern: new Promise($$$ARGS)
9
+ has:
10
+ pattern: setTimeout($$$TIMER)
11
+ stopBy: end
12
+ go:
13
+ rule:
14
+ pattern:
15
+ context: 'func f() { time.Sleep($D) }'
16
+ selector: call_expression
17
+ ignore:
18
+ - '**/*.test.ts'
19
+ - '**/*.spec.ts'
20
+ - '**/*_test.go'
21
+ - '**/main.go'
22
+ - '**/cmd/**'
23
+ - '**/e2e/**'
24
+ ---
25
+
26
+ ## Why
27
+
28
+ A fixed delay is a guess about how long something else takes, and the guess is wrong on a slow CI runner, a loaded server, or a faster machine that now waits for nothing. The code passes locally and fails intermittently elsewhere, which is the most expensive kind of bug to chase. Wait on the actual signal: a promise, a channel, a readiness check, or a context-aware timer for a real backoff.
29
+
30
+ ## Message
31
+
32
+ fixed sleep used as synchronization; wait on a promise, channel, or readiness signal instead
33
+
34
+ ## Bad
35
+
36
+ ```ts
37
+ async function startWorker(worker: Worker): Promise<void> {
38
+ worker.start();
39
+ // BAD: hoping startup is done after two seconds
40
+ await new Promise(resolve => setTimeout(resolve, 2000));
41
+ worker.send(job);
42
+ }
43
+ ```
44
+
45
+ ```go
46
+ func startWorker(w *Worker) {
47
+ go w.Run()
48
+ // BAD: hoping the goroutine is ready after a fixed delay
49
+ time.Sleep(500 * time.Millisecond)
50
+ w.Send(job)
51
+ }
52
+ ```
53
+
54
+ ## Good
55
+
56
+ ```ts
57
+ async function startWorker(worker: Worker): Promise<void> {
58
+ await worker.start();
59
+ worker.send(job);
60
+ }
61
+ ```
62
+
63
+ ```ts
64
+ import { once } from 'node:events';
65
+
66
+ async function startWorker(worker: Worker): Promise<void> {
67
+ worker.start();
68
+ await once(worker, 'ready');
69
+ worker.send(job);
70
+ }
71
+ ```
72
+
73
+ ```go
74
+ func startWorker(w *Worker) {
75
+ go w.Run()
76
+ <-w.Ready()
77
+ w.Send(job)
78
+ }
79
+ ```
80
+
81
+ ```go
82
+ func retry(ctx context.Context, delay time.Duration, attempt func() error) error {
83
+ for {
84
+ if err := attempt(); err == nil {
85
+ return nil
86
+ }
87
+
88
+ select {
89
+ case <-ctx.Done():
90
+ return ctx.Err()
91
+ case <-time.After(delay):
92
+ }
93
+ }
94
+ }
95
+ ```
@@ -0,0 +1,88 @@
1
+ ---
2
+ severity: minor
3
+ detect: ast
4
+ ast:
5
+ ts:
6
+ rule:
7
+ any:
8
+ - kind: string
9
+ regex: '^["'']https?://'
10
+ - kind: template_string
11
+ regex: '^`https?://'
12
+ not:
13
+ regex: 'https?://(www\.)?w3\.org|https?://schemas\.|https?://json-schema\.org'
14
+ go:
15
+ rule:
16
+ any:
17
+ - kind: interpreted_string_literal
18
+ regex: '^"https?://'
19
+ - kind: raw_string_literal
20
+ regex: '^`https?://'
21
+ not:
22
+ regex: 'https?://(www\.)?w3\.org|https?://schemas\.|https?://json-schema\.org'
23
+ ignore:
24
+ - '**/config/**'
25
+ - '**/config.ts'
26
+ - '**/config.go'
27
+ - '**/constants.ts'
28
+ - '**/constants.go'
29
+ - '**/*.config.ts'
30
+ - '**/*.test.ts'
31
+ - '**/*.spec.ts'
32
+ - '**/*_test.go'
33
+ - '**/testdata/**'
34
+ - '**/fixtures/**'
35
+ - '**/__mocks__/**'
36
+ ---
37
+
38
+ ## Why
39
+
40
+ A URL written inline is an environment decision buried in logic: it points at production from a test run, at staging after a deploy, and at nothing once the host moves. Every environment change becomes a code change and a search across the repo. Read the base URL from configuration and build paths on top of it.
41
+
42
+ ## Message
43
+
44
+ hardcoded url in logic; read the base url from config and build the path on it
45
+
46
+ ## Bad
47
+
48
+ ```ts
49
+ export async function fetchUser(id: string): Promise<User> {
50
+ // BAD: the host is fixed in code
51
+ const response = await fetch(`https://api.example.com/users/${id}`);
52
+
53
+ return userSchema.parse(await response.json());
54
+ }
55
+ ```
56
+
57
+ ```go
58
+ func FetchUser(ctx context.Context, id string) (User, error) {
59
+ // BAD: the host is fixed in code
60
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.example.com/users/"+id, nil)
61
+ if err != nil {
62
+ return User{}, fmt.Errorf("building request: %w", err)
63
+ }
64
+
65
+ return doUser(req)
66
+ }
67
+ ```
68
+
69
+ ## Good
70
+
71
+ ```ts
72
+ export async function fetchUser(config: ApiConfig, id: string): Promise<User> {
73
+ const response = await fetch(new URL(`/users/${id}`, config.baseUrl));
74
+
75
+ return userSchema.parse(await response.json());
76
+ }
77
+ ```
78
+
79
+ ```go
80
+ func (c *Client) FetchUser(ctx context.Context, id string) (User, error) {
81
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL.JoinPath("users", id).String(), nil)
82
+ if err != nil {
83
+ return User{}, fmt.Errorf("building request: %w", err)
84
+ }
85
+
86
+ return c.doUser(req)
87
+ }
88
+ ```
@@ -0,0 +1,94 @@
1
+ ---
2
+ severity: minor
3
+ detect: judge
4
+ ignore:
5
+ - '**/*.test.ts'
6
+ - '**/*.spec.ts'
7
+ - '**/*_test.go'
8
+ - '**/constants.ts'
9
+ - '**/constants.go'
10
+ - '**/testdata/**'
11
+ - '**/fixtures/**'
12
+ falsePositives:
13
+ - the literals 0, 1, -1, 2, and 100, and small loop bounds or step sizes
14
+ - array, slice, or tuple indexes and offsets
15
+ - the right-hand side of a named constant declaration, which is where the number belongs
16
+ - a unit conversion factor next to a named constant, such as `const TIMEOUT_MS = 30 * 1000`
17
+ - well-known protocol values where the call name makes the meaning obvious, such as `res.status(404)` or `os.Exit(1)`
18
+ - numbers in a plain arithmetic formula whose surrounding function name explains them, such as a percentage or area calculation
19
+ ---
20
+
21
+ ## Why
22
+
23
+ A bare `30000` in a call or a comparison forces every reader to guess what it means and whether the `30000` forty lines down is the same thing. When the value has to change, the search finds every unrelated occurrence too. Give the number a name that says what it is, and reuse that name so the two places cannot drift.
24
+
25
+ ## Message
26
+
27
+ magic number in logic; name it as a constant that says what it means
28
+
29
+ ## Bad
30
+
31
+ ```ts
32
+ async function pollJob(id: string): Promise<Job> {
33
+ let attempts = 0;
34
+
35
+ while (true) {
36
+ const job = await fetchJob(id);
37
+ // BAD: nobody knows why five, or whether the five below is the same five
38
+ if (job.done || attempts > 5) {
39
+ return job;
40
+ }
41
+
42
+ attempts += 1;
43
+ // BAD: a duration with no name
44
+ await delay(1500);
45
+ }
46
+ }
47
+ ```
48
+
49
+ ```go
50
+ func (s *Service) ListRecent(ctx context.Context) ([]Item, error) {
51
+ // BAD: nobody knows whether 250 is a page size, an API cap, or a guess
52
+ items, err := s.repo.List(ctx, 250)
53
+ if err != nil {
54
+ return nil, fmt.Errorf("listing items: %w", err)
55
+ }
56
+
57
+ return items, nil
58
+ }
59
+ ```
60
+
61
+ ## Good
62
+
63
+ ```ts
64
+ const MAX_POLL_ATTEMPTS = 5;
65
+ const POLL_INTERVAL_MS = 1500;
66
+
67
+ async function pollJob(id: string): Promise<Job> {
68
+ let attempts = 0;
69
+
70
+ while (true) {
71
+ const job = await fetchJob(id);
72
+ if (job.done || attempts > MAX_POLL_ATTEMPTS) {
73
+ return job;
74
+ }
75
+
76
+ attempts += 1;
77
+ await delay(POLL_INTERVAL_MS);
78
+ }
79
+ }
80
+ ```
81
+
82
+ ```go
83
+ // recentPageSize matches the upstream API's maximum page size.
84
+ const recentPageSize = 250
85
+
86
+ func (s *Service) ListRecent(ctx context.Context) ([]Item, error) {
87
+ items, err := s.repo.List(ctx, recentPageSize)
88
+ if err != nil {
89
+ return nil, fmt.Errorf("listing items: %w", err)
90
+ }
91
+
92
+ return items, nil
93
+ }
94
+ ```
@@ -0,0 +1,73 @@
1
+ ---
2
+ severity: minor
3
+ detect: ast
4
+ ast:
5
+ ts:
6
+ rule:
7
+ any:
8
+ - pattern: console.log($$$ARGS)
9
+ - pattern: console.debug($$$ARGS)
10
+ - pattern: console.dir($$$ARGS)
11
+ - pattern: console.table($$$ARGS)
12
+ go:
13
+ rule:
14
+ kind: call_expression
15
+ has:
16
+ field: function
17
+ regex: '^(fmt\.Print(ln|f)?|println|print)$'
18
+ ignore:
19
+ - '**/*.test.ts'
20
+ - '**/*.spec.ts'
21
+ - '**/cli.ts'
22
+ - '**/scripts/**'
23
+ - '**/bin/**'
24
+ - '**/main.go'
25
+ - '**/cmd/**'
26
+ - '**/*_test.go'
27
+ ---
28
+
29
+ ## Why
30
+
31
+ A print statement left in library code writes unstructured text to stdout with no level, no context, and no way to turn it off, and it pollutes the output of any program that embeds this code. Use the project's logger so the line carries a level and structured fields and can be filtered or shipped. If the line was for debugging, delete it.
32
+
33
+ ## Message
34
+
35
+ debug print in library code; use the structured logger or remove it
36
+
37
+ ## Bad
38
+
39
+ ```ts
40
+ export async function createUser(input: CreateUserInput): Promise<User> {
41
+ // BAD: unstructured stdout with no level
42
+ console.log('creating user', input.email);
43
+
44
+ return repo.insert(input);
45
+ }
46
+ ```
47
+
48
+ ```go
49
+ func CreateUser(ctx context.Context, in CreateUserInput) (User, error) {
50
+ // BAD: unstructured stdout with no level
51
+ fmt.Println("creating user", in.Email)
52
+
53
+ return repo.Insert(ctx, in)
54
+ }
55
+ ```
56
+
57
+ ## Good
58
+
59
+ ```ts
60
+ export async function createUser(input: CreateUserInput): Promise<User> {
61
+ logger.info({ email: input.email }, 'creating user');
62
+
63
+ return repo.insert(input);
64
+ }
65
+ ```
66
+
67
+ ```go
68
+ func CreateUser(ctx context.Context, in CreateUserInput) (User, error) {
69
+ slog.InfoContext(ctx, "creating user", "email", in.Email)
70
+
71
+ return repo.Insert(ctx, in)
72
+ }
73
+ ```