From 034c6ae66f23a873c0a9e3e81119db83f1ab9845 Mon Sep 17 00:00:00 2001 From: buduoqiu Date: Sat, 19 Sep 2026 12:53:39 +0800 Subject: [PATCH 1/6] feat(rules): add F# review support --- internal/config/allowlist/allowed_ext_test.go | 6 +++ .../allowlist/supported_file_types.json | 2 + internal/config/rules/rule_docs/fsharp.md | 37 +++++++++++++++++++ internal/config/rules/system_rules.json | 1 + internal/config/rules/system_rules_test.go | 3 ++ 5 files changed, 49 insertions(+) create mode 100644 internal/config/rules/rule_docs/fsharp.md diff --git a/internal/config/allowlist/allowed_ext_test.go b/internal/config/allowlist/allowed_ext_test.go index 6181463d4..a009cf412 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}, 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..953c544e7 --- /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`, `Option.Value`, `Result.get`, or equivalent unwraps where `None` 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 an `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 +- Unsafe casts (`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 that promises a different type, exception behavior, mutability contract, or visibility from the implementation it exposes +- 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"}, From f11c5fa8f5a363d5a5ecdf20e82907115263f8f9 Mon Sep 17 00:00:00 2001 From: buduoqiu Date: Sat, 19 Sep 2026 13:26:14 +0800 Subject: [PATCH 2/6] fix(rules): correct F# review guidance --- internal/config/rules/rule_docs/fsharp.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/config/rules/rule_docs/fsharp.md b/internal/config/rules/rule_docs/fsharp.md index 953c544e7..f0175e8c7 100644 --- a/internal/config/rules/rule_docs/fsharp.md +++ b/internal/config/rules/rule_docs/fsharp.md @@ -4,7 +4,7 @@ - `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`, `Option.Value`, `Result.get`, or equivalent unwraps where `None` or `Error` can occur for runtime, external, or untrusted input +- `Option.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 @@ -28,10 +28,10 @@ #### .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 -- Unsafe casts (`unbox`, `:?`, `Unchecked.defaultof`, or reflection-based invocation) without a locally established runtime type/nullability invariant +- 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 that promises a different type, exception behavior, mutability contract, or visibility from the implementation it exposes +- 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 From 979b829059d2c4f94ac2521ac128e47d81cb20a1 Mon Sep 17 00:00:00 2001 From: buduoqiu Date: Sat, 19 Sep 2026 18:54:20 +0800 Subject: [PATCH 3/6] docs(site): list the F# review rule --- pages/src/content/docs/en/review-rules.md | 1 + pages/src/content/docs/ja/review-rules.md | 1 + pages/src/content/docs/ko/review-rules.md | 1 + pages/src/content/docs/ru/review-rules.md | 1 + pages/src/content/docs/zh/review-rules.md | 1 + 5 files changed, 5 insertions(+) diff --git a/pages/src/content/docs/en/review-rules.md b/pages/src/content/docs/en/review-rules.md index ae0fdf5a6..477173c17 100644 --- a/pages/src/content/docs/en/review-rules.md +++ b/pages/src/content/docs/en/review-rules.md @@ -174,6 +174,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 ad23a80a8..b262b26c4 100644 --- a/pages/src/content/docs/ja/review-rules.md +++ b/pages/src/content/docs/ja/review-rules.md @@ -132,6 +132,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 13b16fba8..31f035df7 100644 --- a/pages/src/content/docs/ko/review-rules.md +++ b/pages/src/content/docs/ko/review-rules.md @@ -163,6 +163,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 3cbd6e211..19acaa2d6 100644 --- a/pages/src/content/docs/ru/review-rules.md +++ b/pages/src/content/docs/ru/review-rules.md @@ -175,6 +175,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 9652bf907..7c1beb12b 100644 --- a/pages/src/content/docs/zh/review-rules.md +++ b/pages/src/content/docs/zh/review-rules.md @@ -155,6 +155,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` | From 0f53eae9be8f917a01d82d9a5238b5291b0518f7 Mon Sep 17 00:00:00 2001 From: Kite <254839944+lizhengfeng101@users.noreply.github.com> Date: Sat, 19 Sep 2026 20:55:18 +0800 Subject: [PATCH 4/6] feat: Apply suggestion from @lizhengfeng101 --- internal/config/rules/rule_docs/fsharp.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/config/rules/rule_docs/fsharp.md b/internal/config/rules/rule_docs/fsharp.md index f0175e8c7..7c32139b0 100644 --- a/internal/config/rules/rule_docs/fsharp.md +++ b/internal/config/rules/rule_docs/fsharp.md @@ -20,7 +20,7 @@ - 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 an `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 +- 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 From 8364f31ef861b77de861316e122d90c3a73a2635 Mon Sep 17 00:00:00 2001 From: buduoqiu Date: Sat, 19 Sep 2026 23:42:30 +0800 Subject: [PATCH 5/6] fix(allowlist): exclude F# test files --- internal/config/allowlist/allowed_ext_test.go | 5 +++++ internal/config/allowlist/default_exclude_patterns.json | 1 + pages/src/content/docs/en/review-rules.md | 1 + pages/src/content/docs/ja/review-rules.md | 1 + pages/src/content/docs/ko/review-rules.md | 1 + pages/src/content/docs/ru/review-rules.md | 1 + pages/src/content/docs/zh/review-rules.md | 1 + 7 files changed, 11 insertions(+) diff --git a/internal/config/allowlist/allowed_ext_test.go b/internal/config/allowlist/allowed_ext_test.go index a009cf412..2350bae45 100644 --- a/internal/config/allowlist/allowed_ext_test.go +++ b/internal/config/allowlist/allowed_ext_test.go @@ -169,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/pages/src/content/docs/en/review-rules.md b/pages/src/content/docs/en/review-rules.md index 477173c17..62152cefd 100644 --- a/pages/src/content/docs/en/review-rules.md +++ b/pages/src/content/docs/en/review-rules.md @@ -117,6 +117,7 @@ matches test-file patterns: - `**/*_test.go` - `**/src/test/java/**/*.java` - `**/src/test/**/*.kt` +- `**/*Test.fs` - `**/*.test.{js,jsx,ts,tsx}` - `**/*.spec.{js,jsx,ts,tsx}` - `**/__tests__/**` diff --git a/pages/src/content/docs/ja/review-rules.md b/pages/src/content/docs/ja/review-rules.md index b262b26c4..114d76379 100644 --- a/pages/src/content/docs/ja/review-rules.md +++ b/pages/src/content/docs/ja/review-rules.md @@ -81,6 +81,7 @@ OCR は [`bmatcuk/doublestar/v4`](https://pkg.go.dev/github.com/bmatcuk/doublest - `**/*_test.go` - `**/src/test/java/**/*.java` - `**/src/test/**/*.kt` +- `**/*Test.fs` - `**/*.test.{js,jsx,ts,tsx}` - `**/*.spec.{js,jsx,ts,tsx}` - `**/__tests__/**` diff --git a/pages/src/content/docs/ko/review-rules.md b/pages/src/content/docs/ko/review-rules.md index 31f035df7..84a9c7181 100644 --- a/pages/src/content/docs/ko/review-rules.md +++ b/pages/src/content/docs/ko/review-rules.md @@ -108,6 +108,7 @@ OCR은 [`bmatcuk/doublestar/v4`](https://pkg.go.dev/github.com/bmatcuk/doublesta - `**/*_test.go` - `**/src/test/java/**/*.java` - `**/src/test/**/*.kt` +- `**/*Test.fs` - `**/*.test.{js,jsx,ts,tsx}` - `**/*.spec.{js,jsx,ts,tsx}` - `**/__tests__/**` diff --git a/pages/src/content/docs/ru/review-rules.md b/pages/src/content/docs/ru/review-rules.md index 19acaa2d6..2d1f9f3ca 100644 --- a/pages/src/content/docs/ru/review-rules.md +++ b/pages/src/content/docs/ru/review-rules.md @@ -117,6 +117,7 @@ OCR использует [`bmatcuk/doublestar/v4`](https://pkg.go.dev/github.com - `**/*_test.go` - `**/src/test/java/**/*.java` - `**/src/test/**/*.kt` +- `**/*Test.fs` - `**/*.test.{js,jsx,ts,tsx}` - `**/*.spec.{js,jsx,ts,tsx}` - `**/__tests__/**` diff --git a/pages/src/content/docs/zh/review-rules.md b/pages/src/content/docs/zh/review-rules.md index 7c1beb12b..0cabf794d 100644 --- a/pages/src/content/docs/zh/review-rules.md +++ b/pages/src/content/docs/zh/review-rules.md @@ -101,6 +101,7 @@ OCR 用 [`bmatcuk/doublestar/v4`](https://pkg.go.dev/github.com/bmatcuk/doublest - `**/*_test.go` - `**/src/test/java/**/*.java` - `**/src/test/**/*.kt` +- `**/*Test.fs` - `**/*.test.{js,jsx,ts,tsx}` - `**/*.spec.{js,jsx,ts,tsx}` - `**/__tests__/**` From a8a04887eac99c4da6bf53392d3bad040189ed04 Mon Sep 17 00:00:00 2001 From: buduoqiu Date: Sun, 20 Sep 2026 01:42:39 +0800 Subject: [PATCH 6/6] docs(rules): cover ValueOption.get --- internal/config/rules/rule_docs/fsharp.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/config/rules/rule_docs/fsharp.md b/internal/config/rules/rule_docs/fsharp.md index 7c32139b0..f2c8a1d7a 100644 --- a/internal/config/rules/rule_docs/fsharp.md +++ b/internal/config/rules/rule_docs/fsharp.md @@ -4,7 +4,7 @@ - `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`, 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 +- `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