diff --git a/internal/config/allowlist/allowed_ext_test.go b/internal/config/allowlist/allowed_ext_test.go index 6181463d4..2350bae45 100644 --- a/internal/config/allowlist/allowed_ext_test.go +++ b/internal/config/allowlist/allowed_ext_test.go @@ -35,6 +35,12 @@ func TestIsAllowedExt(t *testing.T) { {".KT", true}, {".kts", true}, {".KTS", true}, + {".fs", true}, + {".FS", true}, + {".fsi", true}, + {".FSI", true}, + {".fsx", true}, + {".FSX", true}, {".ftl", true}, {".FTL", true}, {".ftlh", true}, @@ -163,6 +169,11 @@ func TestIsExcludedPath(t *testing.T) { {"kotlin scripts test dir", "src/test/kotlin/scripts/FooTest.kts", true}, {"kotlin scripts main dir", "src/main/kotlin/scripts/Foo.kts", false}, + // F# test files + {"fsharp Test suffix", "src/Domain/OrderTest.fs", true}, + {"fsharp Test suffix at root", "OrderTest.fs", true}, + {"fsharp non-test", "src/Domain/TestSupport.fs", false}, + // JS/TS test files {"js test file", "src/utils.test.js", true}, {"tsx test file", "src/Component.test.tsx", true}, diff --git a/internal/config/allowlist/default_exclude_patterns.json b/internal/config/allowlist/default_exclude_patterns.json index c9bc63463..443938428 100644 --- a/internal/config/allowlist/default_exclude_patterns.json +++ b/internal/config/allowlist/default_exclude_patterns.json @@ -2,6 +2,7 @@ "**/*_test.go", "**/src/test/java/**/*.java", "**/src/test/**/*.{kt,kts}", + "**/*Test.fs", "**/*.test.{js,jsx,ts,tsx}", "**/*.spec.{js,jsx,ts,tsx}", "**/__tests__/**", diff --git a/internal/config/allowlist/supported_file_types.json b/internal/config/allowlist/supported_file_types.json index 3902a2e00..62b5931a8 100644 --- a/internal/config/allowlist/supported_file_types.json +++ b/internal/config/allowlist/supported_file_types.json @@ -22,6 +22,8 @@ ".cs", ".vb", ".fs", + ".fsi", + ".fsx", ".go", ".rs", ".rb", diff --git a/internal/config/rules/rule_docs/fsharp.md b/internal/config/rules/rule_docs/fsharp.md new file mode 100644 index 000000000..f2c8a1d7a --- /dev/null +++ b/internal/config/rules/rule_docs/fsharp.md @@ -0,0 +1,37 @@ +> Favor precision over recall: report only defects that are likely to cause incorrect behavior, security vulnerabilities, resource leaks, or material performance problems. Do not report formatting that Fantomas can fix, or replace a project's established F# style with a personal preference. This rule covers F# implementation (`.fs`), signature (`.fsi`), and script (`.fsx`) files; account for the target framework and the surrounding .NET APIs before raising compatibility findings. + +#### Discriminated Unions and Pattern Matching +- `match` expressions that omit a reachable discriminated-union or `option` case, especially after a union gains a new case; do not report a match that the compiler can prove exhaustive +- Catch-all `_` branches used only to suppress an incomplete-pattern warning when an omitted case needs distinct behavior or error handling +- Active patterns or guards whose ordering shadows a later reachable case, silently selecting the wrong branch +- `Option.get`, `ValueOption.get`, accessing `.Value` on an option/value option, or project-specific partial `Result` unwraps where `None`, `ValueNone`, or `Error` can occur for runtime, external, or untrusted input +- Treating a domain failure as an exception while callers are otherwise required to handle it through `Result`; do not flag an intentionally documented exception boundary + +#### Resource Lifetime and Mutable State +- `IDisposable`, streams, database connections, locks, or cancellation registrations acquired without `use`, `use!`, `try/finally`, or an equivalent cleanup path that covers exceptions and early returns +- A resource returned from a `use` scope, captured by a closure that outlives the scope, or disposed before an asynchronous workflow that still consumes it completes +- Mutable state shared between agents, tasks, event callbacks, or parallel collection operations without synchronization or a clear single-owner protocol +- Reusing a mutable buffer, array, or record across asynchronous operations where a later mutation can race with a consumer; ordinary local mutation with no escaping reference is not a finding + +#### Async, Tasks, and Cancellation +- `Task` or `Async` workflows that drop a reachable exception, cancellation, or result instead of propagating or intentionally handling it at the boundary +- Calling `.Result`, `.Wait()`, or `Async.RunSynchronously` on a context that can require asynchronous progress, producing a deadlock or thread-pool starvation risk +- Accepting a `CancellationToken` but failing to pass it into a cancellable I/O, delay, HTTP, database, or child task operation when the API supports it +- Starting background work without retaining, awaiting, supervising, or observing the task's exception; do not flag a deliberately detached process when its lifetime and error reporting are explicit + +#### Sequences, Collections, and Performance +- Enumerating a `seq` with side effects or an expensive source multiple times when the result is expected to be stable or when repeated execution changes behavior; materialize once only when the code needs repeated traversal +- Calling `Seq.head`, `Seq.reduce`, indexed access, or a map lookup without establishing that the collection/key is present for non-constant input +- Accidentally forcing a lazy sequence in a hot path, or composing a sequence pipeline whose deferred exceptions escape a boundary that promises eager validation +- Quadratic list append, repeated immutable-map updates, or repeated string concatenation in a loop where input size can make the behavior materially expensive; do not replace concise collection code merely for micro-optimizations + +#### .NET Interop and Type Boundaries +- Passing F# `option` values through a .NET API as though they were `null`, or treating a nullable/reference return from .NET as non-null without a local invariant +- P/Invoke, reflection, serialization, or JSON bindings whose declared types, field names, nullability, ownership, or enum values do not match the external contract +- Runtime downcasts or unchecked/default-producing operations (`:?>`, `unbox`, `Unchecked.defaultof`, or reflection-based invocation) without a locally established runtime type/nullability invariant +- Untrusted values flowing into SQL, shell commands, file paths, URLs, deserialization, or HTML without validation or parameterization, and secrets written to source, logs, or error messages + +#### Signatures and Module Boundaries +- An `.fsi` signature whose exposed types, arity, generic constraints, mutability, or visibility do not match the implementation or intended public API +- Public functions that leak mutable implementation state or an internal representation where callers can violate the module's invariants +- Module initialization with observable I/O, non-deterministic global state, or exceptions that make importing the module fail unexpectedly; do not report explicit, documented application bootstrap code diff --git a/internal/config/rules/system_rules.json b/internal/config/rules/system_rules.json index f14164595..e66f14a58 100644 --- a/internal/config/rules/system_rules.json +++ b/internal/config/rules/system_rules.json @@ -21,6 +21,7 @@ "**/*.astro": "astro.md", "**/*.{ts,js,tsx,jsx,mjs,cjs}": "ts_js_tsx_jsx.md", "**/*.{kt,kts}": "kotlin.md", + "**/*.{fs,fsi,fsx}": "fsharp.md", "**/*.rs": "rust.md", "**/*.{cpp,cc,cxx,hpp,hxx}": "cpp.md", "**/*.c": "c.md", diff --git a/internal/config/rules/system_rules_test.go b/internal/config/rules/system_rules_test.go index 3bb96e2c4..3a6bbddd6 100644 --- a/internal/config/rules/system_rules_test.go +++ b/internal/config/rules/system_rules_test.go @@ -95,6 +95,9 @@ func TestResolve_DefaultRules(t *testing.T) { {"server/bootstrap.cjs", "TypeScript"}, {"app.kt", "Null Safety"}, {"scripts/setup.kts", "Null Safety"}, + {"src/domain/Order.fs", "Discriminated Unions and Pattern Matching"}, + {"src/domain/Order.fsi", "Discriminated Unions and Pattern Matching"}, + {"scripts/migrate.fsx", "Discriminated Unions and Pattern Matching"}, {"src/main/handler.cpp", "Smart Pointer"}, {"src/main/handler.cxx", "Smart Pointer"}, {"include/handler.hxx", "Smart Pointer"}, diff --git a/pages/src/content/docs/en/review-rules.md b/pages/src/content/docs/en/review-rules.md index 917e194c0..67b99e218 100644 --- a/pages/src/content/docs/en/review-rules.md +++ b/pages/src/content/docs/en/review-rules.md @@ -134,6 +134,7 @@ generated code, and vendored dependencies: - `**/*_test.go` - `**/src/test/java/**/*.java` - `**/src/test/**/*.{kt,kts}` +- `**/*Test.fs` - `**/*.test.{js,jsx,ts,tsx}` - `**/*.spec.{js,jsx,ts,tsx}` - `**/__tests__/**` @@ -230,6 +231,7 @@ matching order: | `**/*.astro` | `astro.md` — Astro components and islands. | | `**/*.{ts,js,tsx,jsx,mjs,cjs}` | `ts_js_tsx_jsx.md` | | `**/*.{kt,kts}` | `kotlin.md` | +| `**/*.{fs,fsi,fsx}` | `fsharp.md` — F# implementation, signature, and script files. | | `**/*.rs` | `rust.md` | | `**/*.R` | `r.md` | | `**/*.{cpp,cc,cxx,hpp,hxx}` | `cpp.md` | diff --git a/pages/src/content/docs/ja/review-rules.md b/pages/src/content/docs/ja/review-rules.md index 7f2a3d15d..feca32574 100644 --- a/pages/src/content/docs/ja/review-rules.md +++ b/pages/src/content/docs/ja/review-rules.md @@ -98,6 +98,7 @@ OCR は [`bmatcuk/doublestar/v4`](https://pkg.go.dev/github.com/bmatcuk/doublest - `**/*_test.go` - `**/src/test/java/**/*.java` - `**/src/test/**/*.{kt,kts}` +- `**/*Test.fs` - `**/*.test.{js,jsx,ts,tsx}` - `**/*.spec.{js,jsx,ts,tsx}` - `**/__tests__/**` @@ -188,6 +189,7 @@ OCR は [`bmatcuk/doublestar/v4`](https://pkg.go.dev/github.com/bmatcuk/doublest | `**/*.astro` | `astro.md`: Astro コンポーネントと islands。 | | `**/*.{ts,js,tsx,jsx,mjs,cjs}` | `ts_js_tsx_jsx.md` | | `**/*.{kt,kts}` | `kotlin.md` | +| `**/*.{fs,fsi,fsx}` | `fsharp.md`: F# の実装、シグネチャ、スクリプトファイル。 | | `**/*.rs` | `rust.md` | | `**/*.R` | `r.md` | | `**/*.{cpp,cc,cxx,hpp,hxx}` | `cpp.md` | diff --git a/pages/src/content/docs/ko/review-rules.md b/pages/src/content/docs/ko/review-rules.md index 981708faf..27437224c 100644 --- a/pages/src/content/docs/ko/review-rules.md +++ b/pages/src/content/docs/ko/review-rules.md @@ -124,6 +124,7 @@ OCR은 [`bmatcuk/doublestar/v4`](https://pkg.go.dev/github.com/bmatcuk/doublesta - `**/*_test.go` - `**/src/test/java/**/*.java` - `**/src/test/**/*.{kt,kts}` +- `**/*Test.fs` - `**/*.test.{js,jsx,ts,tsx}` - `**/*.spec.{js,jsx,ts,tsx}` - `**/__tests__/**` @@ -218,6 +219,7 @@ diff 단계에서 일어납니다. | `**/*.astro` | `astro.md` — Astro 컴포넌트와 아일랜드. | | `**/*.{ts,js,tsx,jsx,mjs,cjs}` | `ts_js_tsx_jsx.md` | | `**/*.{kt,kts}` | `kotlin.md` | +| `**/*.{fs,fsi,fsx}` | `fsharp.md` — F# 구현, 시그니처, 스크립트 파일. | | `**/*.rs` | `rust.md` | | `**/*.R` | `r.md` | | `**/*.{cpp,cc,cxx,hpp,hxx}` | `cpp.md` | diff --git a/pages/src/content/docs/ru/review-rules.md b/pages/src/content/docs/ru/review-rules.md index 151b61d28..8729ea3ad 100644 --- a/pages/src/content/docs/ru/review-rules.md +++ b/pages/src/content/docs/ru/review-rules.md @@ -133,6 +133,7 @@ OCR использует [`bmatcuk/doublestar/v4`](https://pkg.go.dev/github.com - `**/*_test.go` - `**/src/test/java/**/*.java` - `**/src/test/**/*.{kt,kts}` +- `**/*Test.fs` - `**/*.test.{js,jsx,ts,tsx}` - `**/*.spec.{js,jsx,ts,tsx}` - `**/__tests__/**` @@ -230,6 +231,7 @@ OCR использует [`bmatcuk/doublestar/v4`](https://pkg.go.dev/github.com | `**/*.astro` | `astro.md` — компоненты и islands Astro. | | `**/*.{ts,js,tsx,jsx,mjs,cjs}` | `ts_js_tsx_jsx.md` | | `**/*.{kt,kts}` | `kotlin.md` | +| `**/*.{fs,fsi,fsx}` | `fsharp.md` — файлы реализации, сигнатур и скриптов F#. | | `**/*.rs` | `rust.md` | | `**/*.R` | `r.md` | | `**/*.{cpp,cc,cxx,hpp,hxx}` | `cpp.md` | diff --git a/pages/src/content/docs/zh/review-rules.md b/pages/src/content/docs/zh/review-rules.md index c9e06d7ea..d104a326d 100644 --- a/pages/src/content/docs/zh/review-rules.md +++ b/pages/src/content/docs/zh/review-rules.md @@ -116,6 +116,7 @@ OCR 用 [`bmatcuk/doublestar/v4`](https://pkg.go.dev/github.com/bmatcuk/doublest - `**/*_test.go` - `**/src/test/java/**/*.java` - `**/src/test/**/*.{kt,kts}` +- `**/*Test.fs` - `**/*.test.{js,jsx,ts,tsx}` - `**/*.spec.{js,jsx,ts,tsx}` - `**/__tests__/**` @@ -209,6 +210,7 @@ OCR 用 [`bmatcuk/doublestar/v4`](https://pkg.go.dev/github.com/bmatcuk/doublest | `**/*.astro` | `astro.md`——Astro 组件与 islands。 | | `**/*.{ts,js,tsx,jsx,mjs,cjs}` | `ts_js_tsx_jsx.md` | | `**/*.{kt,kts}` | `kotlin.md` | +| `**/*.{fs,fsi,fsx}` | `fsharp.md`——F# 实现、签名和脚本文件。 | | `**/*.rs` | `rust.md` | | `**/*.R` | `r.md` | | `**/*.{cpp,cc,cxx,hpp,hxx}` | `cpp.md` |