@bfirestone45/opencode-slop-review 0.5.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/README.md +23 -0
- package/commands/slop-review-review.md +9 -0
- package/index.js +57 -0
- package/package.json +42 -0
- package/skills/ai-slop-review/SKILL.md +762 -0
- package/skills/ai-slop-review/references/go.md +401 -0
- package/skills/ai-slop-review/references/python.md +263 -0
- package/skills/ai-slop-review/references/rust.md +296 -0
- package/skills/ai-slop-review/references/svelte-ts.md +384 -0
- package/version.txt +1 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# Rust AI Slop Signals
|
|
2
|
+
|
|
3
|
+
Language-specific signals for Rust codebases. These supplement the universal signals
|
|
4
|
+
in SKILL.md -- apply both.
|
|
5
|
+
|
|
6
|
+
## What idiomatic Rust looks like
|
|
7
|
+
|
|
8
|
+
### At version 1.94+ (Edition 2024)
|
|
9
|
+
- `async fn` in traits -- no more `async-trait` crate for simple cases
|
|
10
|
+
- `impl Trait` in more return positions
|
|
11
|
+
- `LazyLock`/`LazyCell` from stdlib over `once_cell`/`lazy_static` crates
|
|
12
|
+
- Let chains for cleaner pattern matching
|
|
13
|
+
- `#[must_use]` on fallible functions and important return values
|
|
14
|
+
- `#[diagnostic::on_unimplemented]` for better error messages on traits
|
|
15
|
+
|
|
16
|
+
### Stdlib preferences
|
|
17
|
+
- Lazy init: `std::sync::LazyLock` over `once_cell::sync::Lazy`
|
|
18
|
+
- Error types: `thiserror` for library errors, `anyhow`/`eyre` for application errors
|
|
19
|
+
- Async runtime: `tokio` (check project convention)
|
|
20
|
+
- Serialization: `serde` with derive macros
|
|
21
|
+
- CLI: `clap` with derive macros
|
|
22
|
+
|
|
23
|
+
### Error handling convention
|
|
24
|
+
- `?` propagation, never manual `match Ok/Err` for simple forwarding
|
|
25
|
+
- Typed error enums with `thiserror` in library code
|
|
26
|
+
- `anyhow::Result` acceptable in binary/CLI code, not libraries
|
|
27
|
+
- `expect()` only in `main()` or provably unreachable paths
|
|
28
|
+
- `unwrap()` only in tests
|
|
29
|
+
|
|
30
|
+
### Type system usage
|
|
31
|
+
- Enums over bools for state/mode parameters
|
|
32
|
+
- Newtype pattern for domain values (UserId, OrderId)
|
|
33
|
+
- `From`/`Into` implementations for error conversion
|
|
34
|
+
- `Default` derive when obvious default exists
|
|
35
|
+
- Private fields with public constructors for invariant enforcement
|
|
36
|
+
|
|
37
|
+
### Project adaptation
|
|
38
|
+
Before flagging any idiom violation, check if the project's baseline uses different conventions.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Priority order for Rust
|
|
43
|
+
|
|
44
|
+
1. Error handling -- unwrap/expect abuse, missing ? propagation
|
|
45
|
+
2. Ownership and borrowing -- clone storms, RefCell overuse
|
|
46
|
+
3. Type system usage -- enums vs. bools, newtypes, trait implementations
|
|
47
|
+
4. Unsafe and panic safety -- library code that panics or uses unsafe carelessly
|
|
48
|
+
5. Clippy and tooling -- suppressed warnings, unnecessary pub visibility
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Error handling (highest priority)
|
|
53
|
+
|
|
54
|
+
**unwrap() outside of tests:**
|
|
55
|
+
```rust
|
|
56
|
+
// SLOP -- panics in production on any failure
|
|
57
|
+
let config = read_config().unwrap();
|
|
58
|
+
let conn = db.connect().unwrap();
|
|
59
|
+
|
|
60
|
+
// IDIOMATIC -- propagate with ?
|
|
61
|
+
let config = read_config()?;
|
|
62
|
+
let conn = db.connect().context("connecting to database")?;
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**expect() as a substitute for error propagation:**
|
|
66
|
+
```rust
|
|
67
|
+
// SLOP -- slightly better than unwrap but still panics
|
|
68
|
+
let user = find_user(id).expect("user should exist");
|
|
69
|
+
|
|
70
|
+
// expect() is OK for genuinely unrecoverable cases in main() or bootstrap
|
|
71
|
+
// but not in library code or request handlers
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Box<dyn Error> in library code:**
|
|
75
|
+
```rust
|
|
76
|
+
// SLOP -- erases the error type, consumers cannot match on it
|
|
77
|
+
fn process(data: &[u8]) -> Result<Output, Box<dyn Error>> {
|
|
78
|
+
|
|
79
|
+
// IDIOMATIC -- typed error enum (thiserror)
|
|
80
|
+
#[derive(Debug, thiserror::Error)]
|
|
81
|
+
enum ProcessError {
|
|
82
|
+
#[error("invalid format: {0}")]
|
|
83
|
+
InvalidFormat(String),
|
|
84
|
+
#[error("io error")]
|
|
85
|
+
Io(#[from] std::io::Error),
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
fn process(data: &[u8]) -> Result<Output, ProcessError> {
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Not using ? propagation:**
|
|
92
|
+
```rust
|
|
93
|
+
// SLOP -- manual match on every Result
|
|
94
|
+
let data = match read_file(path) {
|
|
95
|
+
Ok(d) => d,
|
|
96
|
+
Err(e) => return Err(e.into()),
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// IDIOMATIC
|
|
100
|
+
let data = read_file(path)?;
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Borrow checker fights
|
|
106
|
+
|
|
107
|
+
**Excessive .clone() -- the #1 Rust slop signal:**
|
|
108
|
+
```rust
|
|
109
|
+
// SLOP -- cloning to avoid borrow checker errors
|
|
110
|
+
fn process(items: &[Item]) -> Vec<Output> {
|
|
111
|
+
let cloned = items.to_vec(); // unnecessary clone
|
|
112
|
+
cloned.iter().map(|item| {
|
|
113
|
+
let name = item.name.clone(); // another unnecessary clone
|
|
114
|
+
transform(name)
|
|
115
|
+
}).collect()
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
When you see `.clone()` more than once or twice in a function, check whether
|
|
120
|
+
ownership could be restructured instead. Some clones are necessary and correct --
|
|
121
|
+
the signal is *pervasive* cloning as a pattern.
|
|
122
|
+
|
|
123
|
+
**Rc<RefCell<T>> when ownership could be restructured:**
|
|
124
|
+
```rust
|
|
125
|
+
// SLOP -- interior mutability as a crutch
|
|
126
|
+
let shared_state = Rc::new(RefCell::new(State::new()));
|
|
127
|
+
|
|
128
|
+
// Often the real fix is restructuring so one owner holds the state
|
|
129
|
+
// and others get references or communicate via channels
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Note: with Edition 2024, `async fn` in traits eliminates some cases where
|
|
133
|
+
`Rc<RefCell<T>>` was used as a workaround for trait object limitations in
|
|
134
|
+
async code. If you see this pattern in async trait implementations, check
|
|
135
|
+
whether native `async fn` in traits makes it unnecessary.
|
|
136
|
+
|
|
137
|
+
**Arc<Mutex<T>> overuse:**
|
|
138
|
+
Same pattern as Rc<RefCell<T>> but in async or threaded code. If every piece of
|
|
139
|
+
shared state is behind Arc<Mutex<T>>, the author likely fought the borrow checker
|
|
140
|
+
rather than designing for ownership.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Type system underuse
|
|
145
|
+
|
|
146
|
+
**Bool flags instead of enums:**
|
|
147
|
+
```rust
|
|
148
|
+
// SLOP -- what does (true, false) mean?
|
|
149
|
+
fn create_user(name: &str, is_admin: bool, is_active: bool) -> User {
|
|
150
|
+
|
|
151
|
+
// IDIOMATIC -- illegal states unrepresentable
|
|
152
|
+
enum Role { Admin, User }
|
|
153
|
+
enum Status { Active, Inactive }
|
|
154
|
+
fn create_user(name: &str, role: Role, status: Status) -> User {
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Stringly-typed state:**
|
|
158
|
+
```rust
|
|
159
|
+
// SLOP
|
|
160
|
+
fn set_status(status: &str) {
|
|
161
|
+
match status {
|
|
162
|
+
"active" | "inactive" | "pending" => { ... }
|
|
163
|
+
_ => panic!("invalid status"), // runtime error for a compile-time problem
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// IDIOMATIC
|
|
168
|
+
enum Status { Active, Inactive, Pending }
|
|
169
|
+
fn set_status(status: Status) { ... }
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Missing standard trait implementations:**
|
|
173
|
+
- `Debug` derived but `Display` not implemented (users see `MyError { kind: ... }` instead of a message)
|
|
174
|
+
- No `From`/`Into` implementations for error types (forces manual conversion everywhere)
|
|
175
|
+
- No `Default` when there is an obvious default configuration
|
|
176
|
+
- Missing `Clone`, `PartialEq`, `Hash` on types that clearly need them
|
|
177
|
+
|
|
178
|
+
**Raw primitives for domain values:**
|
|
179
|
+
```rust
|
|
180
|
+
// SLOP -- easy to pass user_id where order_id is expected
|
|
181
|
+
fn get_order(user_id: u64, order_id: u64) -> Order {
|
|
182
|
+
|
|
183
|
+
// IDIOMATIC -- newtype pattern
|
|
184
|
+
struct UserId(u64);
|
|
185
|
+
struct OrderId(u64);
|
|
186
|
+
fn get_order(user_id: UserId, order_id: OrderId) -> Order {
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Unsafe and panic safety
|
|
192
|
+
|
|
193
|
+
**Unsafe without SAFETY comment:**
|
|
194
|
+
```rust
|
|
195
|
+
// SLOP
|
|
196
|
+
unsafe {
|
|
197
|
+
ptr::write(dest, value);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// REQUIRED
|
|
201
|
+
// SAFETY: dest is guaranteed to be valid and aligned because
|
|
202
|
+
// it was allocated by Vec::with_capacity and index < len.
|
|
203
|
+
unsafe {
|
|
204
|
+
ptr::write(dest, value);
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Panicking in library code:**
|
|
209
|
+
- `panic!()`, `unreachable!()`, `todo!()` in non-test code
|
|
210
|
+
- `.unwrap()` on user-influenced data paths
|
|
211
|
+
- `assert!()` for input validation (use `Result` instead)
|
|
212
|
+
- Array indexing without bounds checks where the index comes from external input
|
|
213
|
+
|
|
214
|
+
**transmute without justification:**
|
|
215
|
+
`std::mem::transmute` should be extremely rare and always documented. If AI generated
|
|
216
|
+
a transmute, it almost certainly found a Stack Overflow answer from 2016 and
|
|
217
|
+
copy-pasted it.
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Build and tooling signals
|
|
222
|
+
|
|
223
|
+
**`lazy_static!` or `once_cell` for lazy initialization (Edition 2024+):**
|
|
224
|
+
```rust
|
|
225
|
+
// SLOP -- unnecessary third-party crate since Rust 1.80+
|
|
226
|
+
use lazy_static::lazy_static;
|
|
227
|
+
lazy_static! {
|
|
228
|
+
static ref CONFIG: Config = load_config();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// SLOP -- same issue with once_cell
|
|
232
|
+
use once_cell::sync::Lazy;
|
|
233
|
+
static CONFIG: Lazy<Config> = Lazy::new(|| load_config());
|
|
234
|
+
|
|
235
|
+
// IDIOMATIC -- stdlib LazyLock (stable since 1.80, preferred in Edition 2024)
|
|
236
|
+
use std::sync::LazyLock;
|
|
237
|
+
static CONFIG: LazyLock<Config> = LazyLock::new(|| load_config());
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
If you see `lazy_static` or `once_cell::sync::Lazy` in new code targeting
|
|
241
|
+
Rust 1.80+, flag it. For `once_cell::sync::OnceCell`, the stdlib equivalent
|
|
242
|
+
is `std::sync::OnceLock`. Existing projects may still use the crates for MSRV
|
|
243
|
+
reasons -- check `Cargo.toml` for `rust-version` before flagging.
|
|
244
|
+
|
|
245
|
+
**`async-trait` crate in Edition 2024 code:**
|
|
246
|
+
```rust
|
|
247
|
+
// SLOP -- unnecessary with native async fn in traits (Edition 2024)
|
|
248
|
+
#[async_trait]
|
|
249
|
+
trait MyService {
|
|
250
|
+
async fn handle(&self) -> Result<()>;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// IDIOMATIC -- native async fn in traits
|
|
254
|
+
trait MyService {
|
|
255
|
+
async fn handle(&self) -> Result<()>;
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
The `async-trait` crate is still needed for `dyn Trait` usage or when trait
|
|
260
|
+
objects require `Send` bounds that native async traits do not yet handle well.
|
|
261
|
+
Flag it only when the trait is used with static dispatch (generics/`impl Trait`).
|
|
262
|
+
|
|
263
|
+
**Suppressed warnings:**
|
|
264
|
+
```rust
|
|
265
|
+
// SLOP -- hiding problems instead of fixing them
|
|
266
|
+
#[allow(unused_variables)]
|
|
267
|
+
#[allow(dead_code)]
|
|
268
|
+
#[allow(unused_imports)]
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
A few `#[allow]` annotations are normal. Dozens scattered across a file means the
|
|
272
|
+
code was generated and then warnings were suppressed to make it compile.
|
|
273
|
+
|
|
274
|
+
**Over-broad pub visibility:**
|
|
275
|
+
```rust
|
|
276
|
+
// SLOP -- everything public for no reason
|
|
277
|
+
pub struct Config {
|
|
278
|
+
pub db_url: String,
|
|
279
|
+
pub db_pool_size: u32,
|
|
280
|
+
pub secret_key: String, // this should NOT be pub
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
In idiomatic Rust, items are private by default and only made `pub` when needed
|
|
285
|
+
by the module's public API. If every struct, field, function, and module is `pub`,
|
|
286
|
+
it is likely AI-generated with no thought about encapsulation.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Rust-specific test signals
|
|
291
|
+
|
|
292
|
+
- No `#[should_panic]` or explicit error matching on tests for error paths
|
|
293
|
+
- `assert!(result.is_ok())` instead of `let value = result.unwrap()` + assertions on value
|
|
294
|
+
- Tests that do not use `#[test]` helper patterns (common setup extracted to functions)
|
|
295
|
+
- Integration tests in `src/` instead of `tests/` directory
|
|
296
|
+
- No `proptest` or `quickcheck` where the function has clear algebraic properties
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
# Svelte / TypeScript AI Slop Signals
|
|
2
|
+
|
|
3
|
+
Language-specific signals for Svelte and TypeScript codebases. These supplement the
|
|
4
|
+
universal signals in SKILL.md -- apply both.
|
|
5
|
+
|
|
6
|
+
## What idiomatic Svelte/TypeScript looks like
|
|
7
|
+
|
|
8
|
+
### Svelte 24.x LTS (Svelte 5)
|
|
9
|
+
- Runes: `$state`, `$derived`, `$effect` -- no more `$:` reactive declarations
|
|
10
|
+
- `$props()` with typed interface -- no more `export let`
|
|
11
|
+
- `$bindable()` for two-way binding props
|
|
12
|
+
- Snippets over slots for content composition
|
|
13
|
+
- `$inspect()` for debugging (dev-only)
|
|
14
|
+
- Fine-grained reactivity -- no more immutable assignment patterns needed for arrays/objects
|
|
15
|
+
|
|
16
|
+
### TypeScript 5.9+
|
|
17
|
+
- `satisfies` operator for type narrowing without widening
|
|
18
|
+
- `using` declarations for resource management (Explicit Resource Management)
|
|
19
|
+
- `const` type parameters for literal inference
|
|
20
|
+
- `NoInfer<T>` utility type
|
|
21
|
+
- Discriminated unions over optional fields
|
|
22
|
+
- `readonly` on config/prop interfaces
|
|
23
|
+
- Template literal types for string validation
|
|
24
|
+
|
|
25
|
+
### SvelteKit patterns
|
|
26
|
+
- `+page.server.ts` load functions over `onMount` fetching
|
|
27
|
+
- Form actions over manual fetch POST
|
|
28
|
+
- `$page.data` for layout-shared data
|
|
29
|
+
- SvelteKit's `fetch` (not axios/node-fetch) -- handles cookies, SSR, relative URLs
|
|
30
|
+
- `$env/static/private` for secrets (never in `+page.ts`)
|
|
31
|
+
|
|
32
|
+
### Project adaptation
|
|
33
|
+
Before flagging any idiom violation, check the project's baseline conventions.
|
|
34
|
+
|
|
35
|
+
## Priority order for Svelte/TypeScript
|
|
36
|
+
|
|
37
|
+
1. TypeScript type safety -- any abuse, type assertions, missing discriminated unions
|
|
38
|
+
2. Svelte reactivity model -- correct reactive updates, cleanup, binding usage
|
|
39
|
+
3. SvelteKit patterns -- load functions, form actions, data flow
|
|
40
|
+
4. Component design -- prop drilling, store usage, composition
|
|
41
|
+
5. Security -- XSS via {@html}, client-side secrets
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## TypeScript misuse
|
|
46
|
+
|
|
47
|
+
**`any` as an escape hatch:**
|
|
48
|
+
```typescript
|
|
49
|
+
// SLOP -- gives up type safety entirely
|
|
50
|
+
const response: any = await fetch('/api/users');
|
|
51
|
+
const data: any = await response.json();
|
|
52
|
+
|
|
53
|
+
// IDIOMATIC -- define the shape
|
|
54
|
+
interface User {
|
|
55
|
+
id: string;
|
|
56
|
+
name: string;
|
|
57
|
+
email: string;
|
|
58
|
+
}
|
|
59
|
+
const response = await fetch('/api/users');
|
|
60
|
+
const data: User[] = await response.json();
|
|
61
|
+
|
|
62
|
+
// EVEN BETTER -- runtime validation
|
|
63
|
+
const data = UserArraySchema.parse(await response.json());
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**@ts-ignore without explanation:**
|
|
67
|
+
```typescript
|
|
68
|
+
// SLOP
|
|
69
|
+
// @ts-ignore
|
|
70
|
+
const result = thing.method();
|
|
71
|
+
|
|
72
|
+
// IF NECESSARY -- explain why
|
|
73
|
+
// @ts-expect-error: library types are wrong, see https://github.com/lib/issues/123
|
|
74
|
+
const result = thing.method();
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Type assertions without runtime validation:**
|
|
78
|
+
```typescript
|
|
79
|
+
// SLOP -- trusts external data blindly
|
|
80
|
+
const user = JSON.parse(body) as User;
|
|
81
|
+
|
|
82
|
+
// IDIOMATIC -- validate at the boundary
|
|
83
|
+
const user = userSchema.parse(JSON.parse(body));
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Optionals everywhere instead of discriminated unions:**
|
|
87
|
+
```typescript
|
|
88
|
+
// SLOP -- which combinations are valid? Who knows
|
|
89
|
+
interface ApiResponse {
|
|
90
|
+
data?: User[];
|
|
91
|
+
error?: string;
|
|
92
|
+
loading?: boolean;
|
|
93
|
+
total?: number;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// IDIOMATIC -- each state is explicit
|
|
97
|
+
type ApiResponse =
|
|
98
|
+
| { status: 'loading' }
|
|
99
|
+
| { status: 'error'; error: string }
|
|
100
|
+
| { status: 'success'; data: User[]; total: number };
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Missing readonly:**
|
|
104
|
+
```typescript
|
|
105
|
+
// SLOP -- props and config objects should be immutable
|
|
106
|
+
interface Config {
|
|
107
|
+
apiUrl: string;
|
|
108
|
+
timeout: number;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// IDIOMATIC
|
|
112
|
+
interface Config {
|
|
113
|
+
readonly apiUrl: string;
|
|
114
|
+
readonly timeout: number;
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Svelte reactivity model violations
|
|
121
|
+
|
|
122
|
+
**Using Svelte 4 reactive declarations instead of runes:**
|
|
123
|
+
```svelte
|
|
124
|
+
<script lang="ts">
|
|
125
|
+
// SLOP -- Svelte 4 syntax, no longer idiomatic
|
|
126
|
+
export let count = 0;
|
|
127
|
+
$: doubled = count * 2;
|
|
128
|
+
$: if (count > 10) {
|
|
129
|
+
console.log('count is big');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// IDIOMATIC -- Svelte 5 runes
|
|
133
|
+
interface Props {
|
|
134
|
+
count: number;
|
|
135
|
+
}
|
|
136
|
+
let { count }: Props = $props();
|
|
137
|
+
let doubled = $derived(count * 2);
|
|
138
|
+
$effect(() => {
|
|
139
|
+
if (count > 10) {
|
|
140
|
+
console.log('count is big');
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
</script>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Imperative mutation instead of reactive state:**
|
|
147
|
+
```svelte
|
|
148
|
+
<script lang="ts">
|
|
149
|
+
// SLOP -- using $state but mutating without Svelte detecting it
|
|
150
|
+
let items: string[] = $state([]);
|
|
151
|
+
function addItem(item: string) {
|
|
152
|
+
// In Svelte 5, $state makes arrays/objects deeply reactive,
|
|
153
|
+
// so .push() DOES work. But creating new references is still
|
|
154
|
+
// clearer for complex transformations.
|
|
155
|
+
items = [...items, item];
|
|
156
|
+
}
|
|
157
|
+
</script>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Missing $effect cleanup:**
|
|
161
|
+
```svelte
|
|
162
|
+
<script lang="ts">
|
|
163
|
+
// SLOP -- interval never cleared, memory leak
|
|
164
|
+
$effect(() => {
|
|
165
|
+
setInterval(pollData, 5000);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// IDIOMATIC -- $effect return value is the cleanup function
|
|
169
|
+
$effect(() => {
|
|
170
|
+
const interval = setInterval(pollData, 5000);
|
|
171
|
+
return () => clearInterval(interval);
|
|
172
|
+
});
|
|
173
|
+
</script>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Direct DOM manipulation bypassing Svelte:**
|
|
177
|
+
```svelte
|
|
178
|
+
<script lang="ts">
|
|
179
|
+
// SLOP -- fighting the framework
|
|
180
|
+
import { onMount } from 'svelte';
|
|
181
|
+
onMount(() => {
|
|
182
|
+
document.querySelector('.header')?.classList.add('active');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// IDIOMATIC -- use Svelte bindings and class directives
|
|
186
|
+
let isActive = $state(false);
|
|
187
|
+
</script>
|
|
188
|
+
<div class="header" class:active={isActive}>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Missing UI states -- the classic AI tell:**
|
|
192
|
+
```svelte
|
|
193
|
+
<!-- SLOP -- only shows data, no loading/error/empty states -->
|
|
194
|
+
{#if data}
|
|
195
|
+
{#each data as item}
|
|
196
|
+
<Card {item} />
|
|
197
|
+
{/each}
|
|
198
|
+
{/if}
|
|
199
|
+
|
|
200
|
+
<!-- IDIOMATIC -- handles all states -->
|
|
201
|
+
{#if loading}
|
|
202
|
+
<Spinner />
|
|
203
|
+
{:else if error}
|
|
204
|
+
<ErrorMessage {error} />
|
|
205
|
+
{:else if data.length === 0}
|
|
206
|
+
<EmptyState />
|
|
207
|
+
{:else}
|
|
208
|
+
{#each data as item}
|
|
209
|
+
<Card {item} />
|
|
210
|
+
{/each}
|
|
211
|
+
{/if}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## SvelteKit patterns
|
|
217
|
+
|
|
218
|
+
**Data fetching in onMount instead of load function:**
|
|
219
|
+
```svelte
|
|
220
|
+
<script lang="ts">
|
|
221
|
+
// SLOP -- data fetched client-side only, no SSR, no loading state from SvelteKit
|
|
222
|
+
import { onMount } from 'svelte';
|
|
223
|
+
let users: User[] = $state([]);
|
|
224
|
+
onMount(async () => {
|
|
225
|
+
const res = await fetch('/api/users');
|
|
226
|
+
users = await res.json();
|
|
227
|
+
});
|
|
228
|
+
</script>
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
// IDIOMATIC -- in +page.ts or +page.server.ts
|
|
233
|
+
export const load = async ({ fetch }) => {
|
|
234
|
+
const res = await fetch('/api/users');
|
|
235
|
+
return { users: await res.json() };
|
|
236
|
+
};
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Not using SvelteKit's fetch:**
|
|
240
|
+
```typescript
|
|
241
|
+
// SLOP -- loses cookie forwarding, SSR compatibility
|
|
242
|
+
import axios from 'axios';
|
|
243
|
+
const data = await axios.get('/api/data');
|
|
244
|
+
|
|
245
|
+
// IDIOMATIC -- SvelteKit's fetch handles cookies, SSR, and relative URLs
|
|
246
|
+
export const load = async ({ fetch }) => {
|
|
247
|
+
const res = await fetch('/api/data');
|
|
248
|
+
return { data: await res.json() };
|
|
249
|
+
};
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**Manual form handling instead of form actions:**
|
|
253
|
+
```svelte
|
|
254
|
+
<!-- SLOP -- reinventing what SvelteKit provides, using Svelte 4 event syntax -->
|
|
255
|
+
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
|
|
256
|
+
<input bind:value={name} />
|
|
257
|
+
<button>Submit</button>
|
|
258
|
+
</form>
|
|
259
|
+
|
|
260
|
+
<script lang="ts">
|
|
261
|
+
let name = $state('');
|
|
262
|
+
async function handleSubmit() {
|
|
263
|
+
await fetch('/api/users', {
|
|
264
|
+
method: 'POST',
|
|
265
|
+
body: JSON.stringify({ name }),
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
</script>
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
// IDIOMATIC -- in +page.server.ts
|
|
273
|
+
export const actions = {
|
|
274
|
+
default: async ({ request }) => {
|
|
275
|
+
const data = await request.formData();
|
|
276
|
+
const name = data.get('name');
|
|
277
|
+
// validate and save
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Ignoring layout data and page stores:**
|
|
283
|
+
- Not using `$page.data` for shared data from layout load functions
|
|
284
|
+
- Not using `$navigating` for navigation state
|
|
285
|
+
- Duplicating data fetching that a parent layout already provides
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Component design
|
|
290
|
+
|
|
291
|
+
**Prop drilling (4+ levels):**
|
|
292
|
+
```svelte
|
|
293
|
+
<!-- SLOP -- passing theme through every intermediate component -->
|
|
294
|
+
<App {theme}>
|
|
295
|
+
<Layout {theme}>
|
|
296
|
+
<Sidebar {theme}>
|
|
297
|
+
<NavItem {theme} />
|
|
298
|
+
|
|
299
|
+
<!-- IDIOMATIC -- use Svelte context -->
|
|
300
|
+
<!-- App.svelte -->
|
|
301
|
+
<script lang="ts">
|
|
302
|
+
import { setContext } from 'svelte';
|
|
303
|
+
setContext('theme', theme);
|
|
304
|
+
</script>
|
|
305
|
+
|
|
306
|
+
<!-- NavItem.svelte -->
|
|
307
|
+
<script lang="ts">
|
|
308
|
+
import { getContext } from 'svelte';
|
|
309
|
+
const theme = getContext<Theme>('theme');
|
|
310
|
+
</script>
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**One-off component duplication:**
|
|
314
|
+
If a component exists that does 90% of what is needed, and AI created a new component
|
|
315
|
+
with 90% identical code plus a small variation -- that is slop. The existing component
|
|
316
|
+
should be extended with a prop or snippet.
|
|
317
|
+
|
|
318
|
+
**Untyped props and events:**
|
|
319
|
+
```svelte
|
|
320
|
+
<script lang="ts">
|
|
321
|
+
// SLOP -- no types on props
|
|
322
|
+
let { data, onSubmit } = $props();
|
|
323
|
+
|
|
324
|
+
// IDIOMATIC -- typed interface with $props()
|
|
325
|
+
interface Props {
|
|
326
|
+
readonly data: User;
|
|
327
|
+
onSubmit: (user: User) => void;
|
|
328
|
+
}
|
|
329
|
+
let { data, onSubmit }: Props = $props();
|
|
330
|
+
</script>
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
**Using `export let` (Svelte 4 legacy):**
|
|
334
|
+
```svelte
|
|
335
|
+
<script lang="ts">
|
|
336
|
+
// SLOP -- Svelte 4 prop declaration, no longer idiomatic
|
|
337
|
+
export let user: User;
|
|
338
|
+
export let onSave: (user: User) => void;
|
|
339
|
+
|
|
340
|
+
// IDIOMATIC -- Svelte 5 $props() with typed interface
|
|
341
|
+
interface Props {
|
|
342
|
+
readonly user: User;
|
|
343
|
+
onSave: (user: User) => void;
|
|
344
|
+
}
|
|
345
|
+
let { user, onSave }: Props = $props();
|
|
346
|
+
</script>
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Security signals
|
|
352
|
+
|
|
353
|
+
**XSS via {@html}:**
|
|
354
|
+
```svelte
|
|
355
|
+
<!-- CRITICAL -- user content rendered as raw HTML -->
|
|
356
|
+
{@html userComment}
|
|
357
|
+
|
|
358
|
+
<!-- SAFE -- Svelte auto-escapes by default in text content -->
|
|
359
|
+
{userComment}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**Client-side secrets:**
|
|
363
|
+
```typescript
|
|
364
|
+
// SLOP -- exposed to the browser
|
|
365
|
+
const API_KEY = 'sk-1234567890';
|
|
366
|
+
|
|
367
|
+
// IDIOMATIC -- server-side only, in +page.server.ts or via $env/static/private
|
|
368
|
+
import { API_KEY } from '$env/static/private';
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
- Any `import` from `$env/static/private` in a `+page.ts` (non-server) file
|
|
372
|
+
- API keys in `.env` without the `PUBLIC_` prefix that are used client-side
|
|
373
|
+
- Sensitive data in `+page.ts` load functions instead of `+page.server.ts`
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## TypeScript-specific test signals
|
|
378
|
+
|
|
379
|
+
- Tests that use `as any` to bypass type errors instead of providing proper test data
|
|
380
|
+
- No `vitest` or `playwright` in a SvelteKit project (the default test runners)
|
|
381
|
+
- Component tests that only check rendering, not interaction or state changes
|
|
382
|
+
- Missing `@testing-library/svelte` patterns -- testing implementation details instead of behavior
|
|
383
|
+
- No type-level tests for discriminated unions and conditional types
|
|
384
|
+
- E2E tests that use `data-testid` on everything instead of accessible selectors
|
package/version.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.5.0
|