diff --git a/.claude/rules/test-categories.md b/.claude/rules/test-categories.md new file mode 100644 index 00000000..44b98c99 --- /dev/null +++ b/.claude/rules/test-categories.md @@ -0,0 +1,6 @@ +--- +paths: + - "**/TestCategories.fs" +--- + +@../../.github/instructions/test-categories.instructions.md diff --git a/.claude/scripts/github-mcp-headers.ps1 b/.claude/scripts/github-mcp-headers.ps1 new file mode 100644 index 00000000..98534f2f --- /dev/null +++ b/.claude/scripts/github-mcp-headers.ps1 @@ -0,0 +1,22 @@ +# Emits GitHub MCP request headers for Claude Code's `headersHelper`. +# Reuses the GitHub OAuth token stored by Git Credential Manager, so no PAT is needed. +# Prints `{}` when no credential is available, so the server stays unauthenticated instead of failing. + +$ErrorActionPreference = 'Stop' +$env:GCM_INTERACTIVE = 'never' +$env:GIT_TERMINAL_PROMPT = '0' + +$token = $null +try { + $credential = "protocol=https`nhost=github.com`n`n" | git credential fill 2>$null + $token = ($credential | Where-Object { $_ -like 'password=*' }) -replace '^password=', '' +} +catch { +} + +if ($token) { + @{ Authorization = "Bearer $token" } | ConvertTo-Json -Compress +} +else { + '{}' +} diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 3c0225ff..1ed8fff1 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -16,8 +16,15 @@ ], "rollForward": false }, + "fslangmcp": { + "version": "0.18.0", + "commands": [ + "fslangmcp" + ], + "rollForward": true + }, "fantomas": { - "version": "7.0.2", + "version": "8.0.5", "commands": [ "fantomas" ], diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d1253252..09ddbe3f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -19,6 +19,8 @@ }, // https://github.com/devcontainers/features/blob/main/src/github-cli/README.md "ghcr.io/devcontainers/features/github-cli:1": {}, + // https://github.com/devcontainers-extra/features/blob/main/src/starship/README.md + "ghcr.io/devcontainers-extra/features/starship:1": {}, // https://github.com/devcontainers/features/blob/main/src/dotnet/README.md "ghcr.io/devcontainers/features/dotnet:2": { "version": "10.0", @@ -28,6 +30,7 @@ "overrideFeatureInstallOrder": [ "ghcr.io/devcontainers/features/common-utils", "ghcr.io/devcontainers/features/github-cli", + "ghcr.io/devcontainers-extra/features/starship", "ghcr.io/devcontainers/features/dotnet" ], "customizations": { @@ -60,6 +63,9 @@ // They are used for releasing and publishing from the container "GITHUB_TOKEN": "${localEnv:GITHUB_TOKEN}" }, + "onCreateCommand": { + "enable-starship": "echo 'eval \"$(starship init zsh)\"' >> ~/.zshrc" + }, "postAttachCommand": { "restore": "dotnet tool restore && dotnet restore" }, diff --git a/.editorconfig b/.editorconfig index 869005af..c4537e1f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,5 +1,5 @@ -# EditorConfig is awesome: -http://EditorConfig.org +# EditorConfig is awesome: https://EditorConfig.org +# Comments must be on their own lines: the specification does not support inline comments # top-most EditorConfig file root = true @@ -8,7 +8,8 @@ root = true # Core EditorConfig Options # ############################### # All files -[*] # Do not apply to all files not to break something +# Do not apply to all files not to break something +[*] guidelines = 120 dashed, 130 # Either crlf | lf, default is system-dependent (when not specified at all) # end_of_line=crlf @@ -22,8 +23,10 @@ indent_style = tab [*.{cs,csx,fs,fsi,fsx}] trim_trailing_whitespace = true insert_final_newline = true -indent_style = space # default=space -indent_size = 4 # default=4 +# default=space +indent_style = space +# default=4 +indent_size = 4 charset = utf-8 # Project files and app specific XML files @@ -42,7 +45,8 @@ indent_size = 2 # XML files [*.xml] -trim_trailing_whitespace = false # do not trim as it affects CData +# do not trim as it affects CData +trim_trailing_whitespace = false insert_final_newline = true indent_style = space indent_size = 2 @@ -70,12 +74,14 @@ trim_trailing_whitespace = false end_of_line = lf # Batch Files +# keep in sync with .gitattributes [*.{cmd,bat}] end_of_line = crlf # Powershell Files -[*.{ps1, psm1}] -end_of_line = crlf +# keep in sync with .gitattributes +[*.{ps1,psm1}] +end_of_line = lf # Paket files [paket.*] @@ -105,16 +111,12 @@ indent_size=4 max_line_length=130 # Either crlf | lf, default is system-dependent (when not specified at all) -# end_of_line=crlf +# lf matches the eol .gitattributes stores F# files with, so formatting does not touch line endings +end_of_line=lf # Whether end-of-file has a newline, default=true insert_final_newline=true -# false: someLineOfCode -# true: someLineOfCode; -# default false -fsharp_semicolon_at_end_of_line=false - # false: f(1,2) # true: f(1, 2) # default true @@ -135,8 +137,8 @@ fsharp_space_before_uppercase_invocation=true # default false fsharp_space_before_class_constructor=true -# false: __.member Foo(x) = x -# true: __.member Foo (x) = x +# false: member this.Foo(x) = x +# true: member this.Foo (x) = x # default false fsharp_space_before_member=true @@ -160,18 +162,18 @@ fsharp_space_before_semicolon=false # default true fsharp_space_after_semicolon=true -# false: let a = [1;2;3] -# true: let a = [ 1;2;3 ] +# false: let a = [1; 2; 3] +# true: let a = [ 1; 2; 3 ] # default true fsharp_space_around_delimiter=true # breaks an if-then-else in smaller parts if it is on one line -# default 40 +# default 60 fsharp_max_if_then_else_short_width=60 # breaks an infix operator expression if it is on one line # infix: a + b + c -# default 50 +# default 80 fsharp_max_infix_operator_expression=60 # breaks a single-line record declaration @@ -192,7 +194,7 @@ fsharp_max_record_number_of_items=1 fsharp_record_multiline_formatter=character_width # breaks a single line array or list if it exceeds this size -# default 40 +# default 80 fsharp_max_array_or_list_width=100 # breaks an array or list into one item per line if items exceeds this number @@ -207,7 +209,7 @@ fsharp_max_array_or_list_number_of_items=1 # default character_width fsharp_array_or_list_multiline_formatter=character_width -# maximum with of a value binding, does not include keyword "let" +# maximum width of a value binding, does not include keyword "let" # default 80 fsharp_max_value_binding_width=100 @@ -215,10 +217,6 @@ fsharp_max_value_binding_width=100 # default 40 fsharp_max_function_binding_width=80 -# maximum width for expressions like X.DoY().GetZ(10).Help() -# default 50 -fsharp_max_dot_get_expression_width=80 - # whether open/close brackets go on the same column # cramped: type Range = # { From: float @@ -232,10 +230,10 @@ fsharp_max_dot_get_expression_width=80 # From: float # To: float # } -# default cramped +# default aligned fsharp_multiline_bracket_style=stroustrup -# whether to move the beginning of compuitation expression to the new line +# whether to move the beginning of computation expression to the new line # true: let x = # computation { # ... @@ -243,6 +241,7 @@ fsharp_multiline_bracket_style=stroustrup # false: let x = computation { # .. # } +# default true fsharp_newline_before_multiline_computation_expression=false # whether a newline should be placed before members @@ -253,7 +252,7 @@ fsharp_newline_before_multiline_computation_expression=false # { From: float } # # member this.Length = this.To - this.From -# default false +# default true fsharp_newline_between_type_definition_and_members=true # if a function sign exceeds max_line_length, then: @@ -262,7 +261,7 @@ fsharp_newline_between_type_definition_and_members=true # default false fsharp_align_function_signature_to_indentation=false -# see docs: https://github.com/fsprojects/fantomas/blob/master/docs/Documentation.md#fsharp_alternative_long_member_definitions +# see docs: https://fsprojects.github.io/fantomas/docs/end-users/Configuration.html#fsharp_alternative_long_member_definitions # default false fsharp_alternative_long_member_definitions=true @@ -270,7 +269,8 @@ fsharp_alternative_long_member_definitions=true # default false fsharp_multi_line_lambda_closing_newline=true -# allows the 'else'-branch to be aligned at same level as 'else' if the ret type allows it +# keeps the last branch of if/then/else or match at the same indentation as the expression (early-return style) +# see docs: https://fsprojects.github.io/fantomas/docs/end-users/Configuration.html#fsharp_experimental_keep_indent_in_branch # false: match x with # | null -> () # | _ -> () @@ -279,7 +279,7 @@ fsharp_multi_line_lambda_closing_newline=true # | _ -> # () # default false -fsharp_keep_indent_in_branch=true +fsharp_experimental_keep_indent_in_branch=true # whether a bar is placed before DU # false: type MyDU = Short of int @@ -293,4 +293,5 @@ fsharp_blank_lines_around_nested_multiline_expressions=false # set maximal number of consecutive blank lines to keep from original source # it doesn't change number of new blank lines generated by Fantomas +# default 100 fsharp_keep_max_number_of_blank_lines=2 diff --git a/.gitattributes b/.gitattributes index cb6f883d..df5e1a85 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13,6 +13,12 @@ *.fsproj merge=union *.dbproj merge=union *.sh text eol=lf +# cmd.exe misreads labels and goto in LF batch files +*.cmd text eol=crlf +*.bat text eol=crlf +# LF keeps the `#!/usr/bin/env pwsh` shebang working on Linux; PowerShell on Windows reads LF too +*.ps1 text eol=lf +*.psm1 text eol=lf # Standard to msysgit *.doc diff=astextplain diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ab249a8b..1e00d8b1 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,7 +4,7 @@ Describe the big picture of your changes here to communicate to the maintainers ## Types of changes -What types of changes does your code introduce to FSharp.Azure.Cosmos? +What types of changes does your code introduce to FSharp.Collections.Immutable? _Put an `x` in the boxes that apply_ - [ ] Bugfix (non-breaking change which fixes an issue) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 29e17260..9bdccfbf 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -74,24 +74,62 @@ * [`FAKE`](https://fake.build/) – build and release scripting * [`Argu`](https://github.com/fsprojects/Argu) – command-line parsing for build tooling +Use GitHub MCP tools for code search in these repositories when needed. + +## MCP Servers + +Agents discover servers from #file:'.mcp.json'; these are only hints on when to prefer one. + +* For F# work, prefer the `F#` server ([FsLangMCP](https://github.com/Neftedollar/FsLangMCP)) over `rg`/plain text search whenever the task depends on symbol meaning, compile context, cross-project usage, diagnostics, or safe refactoring preview. Its tool package is pinned in #file:'.config/dotnet-tools.json' – run `dotnet tool restore` before first use. +* Use the `GitHub` server for code search in dependency repositories. In Claude Code it authenticates through `.claude/scripts/github-mcp-headers.ps1`, which reuses the GitHub token stored by Git Credential Manager, because the server does not support the OAuth dynamic client registration Claude Code needs. +* Use the `Microsoft Docs` server for official Microsoft and Azure documentation. +* When adding or changing a server, keep `servers` (VS Code / GitHub Copilot) and `mcpServers` (Claude Code) in sync and sorted alphabetically by key. + ## F# Coding Guidelines -### Language Preferences +### Language and Tooling * Always use the latest F# 10 features over old syntax. -* Prefer `voption` over `option`. +* If you are running outside of an IDE, or the IDE does not provide F# semantic tools, use FsLangMCP as the primary tool for F# code navigation, symbol discovery, diagnostics, usage search and refactoring preview. Prefer its semantic tools over plain text search. +* The compiler generates `IsCaseName` instance properties (for example `IsOk`, `IsNotFound`) for each DU case – use them when a single-case check is needed. + +### Asynchrony and Cancellation + * Prefer `task` CE over `async` CE. -* Prefer underscore lambda syntax like `Seq.map _.Name` over `Seq.map (fun x -> x.Name)`, but only when the expression is a simple member access. Complex expressions like `Seq.where (fun x -> x.Name = name)` or `Seq.map (fun x -> x.Field1, x.Field2)` cannot be simplified. -* Simplify `Seq.map (fun x -> someFunction x)` to `Seq.map someFunction`. +* When a method must return non-generic `Task`, annotate the return type explicitly: `member _.MyMethod (...) : Task = task { ... }`. Never cast through pipelines. +* `task` CE can await `ValueTask` APIs directly. +* Curried functions take the `CancellationToken` as their **first** parameter, so that it can be partially applied. +* Never hand-roll wrappers such as `ValueTask (task { ... })`, `.AsTask ()` round-trips or manual `unit -> Task` thunks. Use the matching [`IcedTasks`](https://github.com/TheAngryByrd/IcedTasks) CE instead (`valueTask`, `valueTaskUnit`, `taskUnit`, `coldTask`, `cancellableTask`, `cancellableValueTask`, `backgroundTask`, …) and always use the full CE names, not the short aliases (`vTask`, `pvTask`, …). + +### Values and Collections + +* Prefer `voption` (`ValueSome`/`ValueNone`) over `option`. Fields, members, parameters and values shared between threads included: none of them is a reason to pick `option`. Exception: when an API hands you `'T option` and has no `voption` counterpart, unwrap it with `Option.defaultValue`/`Option.defaultWith` directly – do not insert `ValueOption.ofOption` just to switch modules. +* The mirror case, an API that *takes* `'T option` (an optional argument `?name = …`, a field typed `'T option`): stay in `ValueOption` through the whole chain and convert once, last – `x |> ValueOption.bind _.Value |> ValueOption.toOption`, never `x |> ValueOption.toOption |> Option.bind _.Value`. +* Prefer `struct ('T1 * 'T2)` over reference tuples, and anonymous struct records (`struct {| ... |}`) over tuples for return types of public functions and methods. +* Never group with `Seq.groupBy` – use `ToLookup` from `System.Linq`. It groups once into an `ILookup<'Key, 'T>` instead of re-grouping on every enumeration and does not allocate a tuple per group. Pass a lambda (`xs.ToLookup (fun x -> keyOf x)`), not a bare function value. +* When casting sequence items use `Seq.cast` instead of `Seq.map (fun item -> item :> TargetType)`. +* When concatenating two sequences or lists, prefer `seq { yield! xs; yield! ys }` (or `[ yield! xs; yield! ys ]` for lists) over the `@` operator or `Seq.append`. * When pipe operators are used on a materializable collection multiple times in a row, prefer `Seq` module for the chain and materialize at the end. -* Prefer interpolated strings over `printf` functions for string formatting. -* Use `withNull` for null checks instead of boxing delegates/functions (avoid `isNull (box value)`). +* Keep a pipeline in harmony: when consecutive steps can come from one module, take them from that module, the conversion at the end included – `xs |> Seq.filter isValid |> Seq.sort |> Seq.toArray`, not `… |> Seq.sort |> Array.ofSeq`, and `items |> List.map _.Id |> List.toArray`, not `… |> Array.ofList`. Change the module only where the steps that follow need the other collection. +* Compare `System.Type` values with `Type.(=) (a, b)`, the `Type.op_Equality` operator. The F# `=` goes through generic equality and is several times slower, and comparing names is both slow and wrong: `FullName.StartsWith` is culture-sensitive without a `StringComparison`, and the full name of an array of a generic type, such as ``FSharpOption`1[[System.Int32, ...]][]``, starts with the name of the generic type. To recognize a generic type, check `t.IsGenericType` and compare `t.GetGenericTypeDefinition ()` with its definition, such as `typedefof<_ option>`, through `Type.(=)`. + +### Functions, Lambdas and Strings + +* Prefer underscore lambda syntax like `Seq.map _.Name` over `Seq.map (fun x -> x.Name)`, but only when the expression is a simple member access. Complex expressions like `Seq.where (fun x -> x.Name = name)` or `Seq.map (fun x -> x.Field1, x.Field2)` cannot be simplified. A member chain ending in a method call is not complex: `_.Changed.Subscribe(handler)`. The shorthand needs its input type known, so pipe the value in first – `value |> ValueOption.map _.Id`, not `ValueOption.map _.Id value` (FS0072). Never write a space in `_.MethodCall()` – it breaks parsing. +* Simplify `Seq.map (fun x -> someFunction x)` to `Seq.map someFunction`. +* Prefer interpolated strings over `printf` functions for string formatting. Format specifiers like `$"%s{value}"` are valid in interpolated strings and help type inference. +* Pass an explicit `StringComparison` to every `Equals`, `StartsWith`, `EndsWith`, `IndexOf`, `Contains` and `Compare`, and an explicit comparer to every `HashSet` and `Dictionary`. `Ordinal` by default; culture-sensitive comparison is a decision, never a default. Use `OrdinalIgnoreCase` only where the thing compared really is case-insensitive. +* A slice of a string that is only inspected – compared, trimmed, scanned, matched against a prefix – is a `ReadOnlySpan` (`text.AsSpan (start, length)`), not a `Substring`: the substring allocates a copy per call, the span does not. Materialize with `Substring`/`ToString ()` only for the value that leaves the function or is stored. +* The `StringComparison` rule applies to spans unchanged: `MemoryExtensions` has `StringComparison` overloads of `StartsWith`, `EndsWith`, `Equals`, `CompareTo`, `IndexOf` and `Contains` for `ReadOnlySpan` – `s.AsSpan().TrimStart(' ').StartsWith("//", StringComparison.Ordinal)`. The overloads without one are the generic element-wise `ReadOnlySpan<'T>` ones – ordinal for `char` by accident, not by statement. +* A slice that must be stored – kept in a record or class field, captured by a closure or used across a `let!`/`do!` in a `task` CE, put into a tuple, option or collection – is `ReadOnlyMemory` (`text.AsMemory (...)`), never a `ReadOnlySpan`: a byref-like value cannot be stored. Returning a `ReadOnlySpan` sliced from a string or from a span parameter is fine. +* To compare two slices without building either, use `String.CompareOrdinal (a, aIndex, b, bIndex, length)` or `spanA.Equals (spanB, StringComparison.Ordinal)`. ### Nullable Reference Types * Declare variables non-nullable; check for `null` at entry points only. * Trust the SDK null annotations – do not add null checks when the type system says a value cannot be null. -* Prefer `match` on `null` over `if isNull`: +* Use `withNull` for null checks instead of boxing delegates/functions (avoid `isNull (box value)`). +* Prefer `match` on `null` over `if isNull` – it narrows the type and suppresses nullness warnings: ```fsharp // Preferred @@ -100,6 +138,41 @@ | someObject -> someObject.SomeProperty ``` +* Before suppressing a nullness warning (3261, 3262, …), exhaust these alternatives in order: + 1. `nonNull value` – asserts non-null at runtime and fails fast. + 2. `Unchecked.nonNull value` – skips the runtime check; only when non-null was already verified upstream. + 3. An inline `#nowarn` / `#warnon` pair around the smallest possible scope – last resort for interop boundaries, centralised in a single helper rather than scattered across call sites. +* Never suppress warnings file-wide; `#nowarn` and `#warnon` are valid anywhere in a file. + +### XML Documentation Comments + +* A doc comment is either plain text with no tags at all, or fully explicit XML starting with `` – never a mixture. The compiler adds `` by itself only when the comment has no tags, and silently escapes tags otherwise: + * No tags anywhere → bare `///` lines, no ``. + * Any tag at all (``, ``, ``, ``, ``, …) → the comment must start with an explicit ``, and every `` must be inside it. + + ```fsharp + // ✅ plain text — the compiler supplies + /// Represents the result of a read operation. + + // ❌ a sibling tag without — the is escaped into the summary and lost + /// Reads an item. + /// Item Id + + // ✅ any tag present, so the comment is explicit XML throughout + /// Reads an item. + /// Item Id + ``` + +* Every public API must have XML documentation. +* On an explicit interface implementation (`interface X with member _.M (...) = ...`), write `/// ` alone instead of restating the interface member's documentation, unless this implementation has behavior worth calling out beyond what the interface already documents – write a normal ``/`` there instead. +* Refer to types and members through ``, never through `` or plain text. `` is for literal values only (JSON, SQL, setting names). Refer to language keywords through ``. +* Split multi-paragraph documentation into `` elements inside `` – bare line breaks are collapsed by documentation renderers. +* Write enumerations as `` (or `type="number"`) with `…`, never as Markdown-style bullets. + +### Opens Sorting + +Sort `open` statements alphabetically within groups: `System` first, `Microsoft` second, `FSharp` third; then other external namespaces; then this solution's namespaces. `open type` goes last in each group. Type and module aliases form a separate final group. + ### Class Constructors This is how to define a non-default F# class constructor: @@ -183,16 +256,21 @@ module MyTypeExtensions = * Use camelCase for `let` bindings, functions, private fields, and local variables. * Prefix interface names with `I`. * Do not prefix type parameters with `T` (e.g., use `'Result` instead of `'TResult`). +* Name tests using spaces (e.g., `member this.``Test name with spaces``() : Task = ...`). ## Testing -* Tests use MSTest 4.x. +* Tests use MSTest 4. * Each test must have category applied: * Add a collection-level category on each test class (for example: `[]`, `[]`). * Add operation-level categories on each test method using `nameof` (for example: `[]`). * For specialized areas, add dedicated category attributes together with operation categories (for example: `[]`). * Keep category names centralized in `TestCategories.fs` via custom `TestCategoryBaseAttribute` types; prefer attributes over repeating string literals. * `CollectionAssert` cannot work with F# lists – use F# array syntax (`[| ... |]`) instead. +* `StringAssert` has overloads with `StringComparison`. +* Use `Assert.Contains` instead of `Assert.IsTrue (str.Contains ..., "message")`, and do not put the actual value into the message. +* Check collection size with `Assert.HasCount (expected, collection, "message")` instead of `Assert.AreEqual (expected, collection.Length, "message")` (or `.Count` / `Seq.length`); use `Assert.IsEmpty`, `Assert.IsNotEmpty` and `Assert.ContainsSingle` for the zero, non-zero and single-item cases. On failure they report the actual count and items. +* Try running tests with `--no-build` first, run them individually where possible, and use the trx format for results so failures can be consumed and fixed. * Use Unquote only for complex object/hierarchy assertions; for simple scalar checks prefer standard `Assert.*` APIs. * Every `Assert.*` call **must include a failure message** so output is self-explanatory. * Async tests must return `Task`, not `Async` or `Task` – always declare `) : Task = task {`. diff --git a/.github/instructions/test-categories.instructions.md b/.github/instructions/test-categories.instructions.md new file mode 100644 index 00000000..8516ae64 --- /dev/null +++ b/.github/instructions/test-categories.instructions.md @@ -0,0 +1,7 @@ +--- +applyTo: "**/TestCategories.fs" +--- + +# Test category attributes + +Keep the `TestCategoryBaseAttribute` descendants of this file sorted by type name. Add a new attribute at its place in that order, never at the end of the file. diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 05370ebc..2f332082 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -37,6 +37,5 @@ jobs: 10.x - name: Restore dependencies - run: | - dotnet tool restore - dotnet restore FSharp.Collections.Immutable.slnx + # Restoring the solution restores the local tools first (see Directory.Build.targets) + run: dotnet restore FSharp.Collections.Immutable.slnx diff --git a/.github/workflows/fsdocs-gh-pages.yml b/.github/workflows/fsdocs-gh-pages.yml index 1f2ff013..993a1b48 100644 --- a/.github/workflows/fsdocs-gh-pages.yml +++ b/.github/workflows/fsdocs-gh-pages.yml @@ -29,7 +29,7 @@ jobs: uses: actions/checkout@v6 - name: Setup Pages - uses: actions/configure-pages@v5 + uses: actions/configure-pages@v6 - name: Setup necessary dotnet SDKs uses: actions/setup-dotnet@v5 @@ -44,7 +44,7 @@ jobs: ./build.sh builddocs - name: Upload artifact - uses: actions/upload-pages-artifact@v4 + uses: actions/upload-pages-artifact@v5 with: path: docs/ @@ -58,4 +58,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/publish_ci.yml b/.github/workflows/publish_ci.yml index 34fd6ed3..e464e278 100644 --- a/.github/workflows/publish_ci.yml +++ b/.github/workflows/publish_ci.yml @@ -59,7 +59,7 @@ jobs: NUGET_TOKEN: ${{ secrets.NUGET_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} FAKE_DETAILED_ERRORS: true - ENABLE_COVERAGE: false # AltCover doesn't work with Release builds, reports lower coverage than actual + ENABLE_COVERAGE: false run: | chmod +x ./build.sh ./build.sh "PublishToGitHub" diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 5598491d..6c399004 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -30,7 +30,7 @@ jobs: NUGET_TOKEN: ${{ secrets.NUGET_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} FAKE_DETAILED_ERRORS: true - ENABLE_COVERAGE: false # AltCover doesn't work with Release builds, reports lower coverage than actual + ENABLE_COVERAGE: false run: | chmod +x ./build.sh ./build.sh Publish diff --git a/.github/workflows/repo-assist.md b/.github/workflows/repo-assist.md index 03ca2875..11f07ec8 100644 --- a/.github/workflows/repo-assist.md +++ b/.github/workflows/repo-assist.md @@ -39,7 +39,7 @@ safe-outputs: labels: [automation, repo-assist] max: 3 update_issue: - target: "*" + target: "*" #title-prefix: "[Repo Assist] " add_labels: allowed: [bug, enhancement, "help wanted", "good first issue", "spam", "off topic"] diff --git a/.gitignore b/.gitignore index 99fd3b7f..7fe30ed6 100644 --- a/.gitignore +++ b/.gitignore @@ -405,3 +405,6 @@ tmp/ temp/ .fsdocs docs/ + +# Local junction to .github/skills, created by Directory.Build.targets +/.claude/skills diff --git a/.mcp.json b/.mcp.json index c6181506..2379d355 100644 --- a/.mcp.json +++ b/.mcp.json @@ -1,12 +1,41 @@ { "servers": { + "F#": { + "type": "stdio", + "command": "dotnet", + "args": [ + "tool", + "run", + "fslangmcp" + ] + }, + "GitHub": { + "type": "http", + "url": "https://api.githubcopilot.com/mcp/" + }, "Microsoft Docs": { "type": "http", "url": "https://learn.microsoft.com/api/mcp" + } + }, + "mcpServers": { + "F#": { + "type": "stdio", + "command": "dotnet", + "args": [ + "tool", + "run", + "fslangmcp" + ] }, "GitHub": { "type": "http", - "url": "https://api.githubcopilot.com/mcp/" + "url": "https://api.githubcopilot.com/mcp/", + "headersHelper": "pwsh -NoProfile -File .claude/scripts/github-mcp-headers.ps1" + }, + "Microsoft Docs": { + "type": "http", + "url": "https://learn.microsoft.com/api/mcp" } } } diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..ae0cbfca --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,3 @@ +# Agent Instructions + +Before starting any task, read and follow [.github/copilot-instructions.md](.github/copilot-instructions.md). It holds the full project context and coding standards. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..5dab9eb5 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +# Claude Code Instructions + +The project instructions are shared with GitHub Copilot and maintained in a single place: + +@.github/copilot-instructions.md diff --git a/Directory.Build.targets b/Directory.Build.targets index 484ac5a1..8b9a0783 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -11,36 +11,60 @@ See https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-by-director <_DotnetFantomasOutputFile>$(BaseIntermediateOutputPath)dotnet-fantomas-msbuild-$(NETCoreSdkVersion)-$(OS) - - - - - - - + + + - - - + - - - - + + + <_ClaudeSkillsLink>$(MSBuildThisFileDirectory).claude/skills + <_ClaudeSkillsTarget>$(MSBuildThisFileDirectory).github/skills + + + + + + + + + + + + + + + + + + diff --git a/Directory.Packages.props b/Directory.Packages.props index 5639be71..7c9ec00d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -13,19 +13,19 @@ - - - - - - - - - - - + + + + + + + + + + + - + diff --git a/FSharp.Collections.Immutable.slnx b/FSharp.Collections.Immutable.slnx index 4e8a5d03..b2ce8282 100644 --- a/FSharp.Collections.Immutable.slnx +++ b/FSharp.Collections.Immutable.slnx @@ -9,7 +9,11 @@ - - - + + + + + + + diff --git a/README.md b/README.md index 7819e3fe..33287ccc 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,7 @@ -# FSharp.Collections.Immutable [![NuGet Status](http://img.shields.io/nuget/v/FSharp.Collections.Immutable.svg?style=flat)](https://www.nuget.org/packages/FSharp.Collections.Immutable/) [![GitHub Actions](https://github.com/fsprojects/FSharp.Collections.Immutable/workflows/Build%20main/badge.svg)](https://github.com/fsprojects/FSharp.Collections.Immutable/actions?query=branch%3Amain) +# FSharp.Collections.Immutable [![NuGet Status](https://img.shields.io/nuget/v/FSharp.Collections.Immutable.svg?style=flat)](https://www.nuget.org/packages/FSharp.Collections.Immutable/) [![GitHub Actions](https://github.com/fsprojects/FSharp.Collections.Immutable/workflows/Build%20main/badge.svg)](https://github.com/fsprojects/FSharp.Collections.Immutable/actions?query=branch%3Amain) F# bindings for System.Collections.Immutable -![Build status](https://github.com/fsprojects/FSharp.Collections.Immutable/workflows/.NET%20Core/badge.svg) - **FSharp.Collections.Immutable** is a collection of F# bindings for [System.Collections.Immutable](https://learn.microsoft.com/en-us/dotnet/api/system.collections.immutable). Please contribute to this project. Don't ask for permission, just fork the repository and send pull requests. @@ -44,7 +42,7 @@ FSharp.Collections.Immutable | [![NuGet Badge](https://img.shields.io/nuget/v/FS Make sure the following **requirements** are installed on your system: -- [dotnet SDK](https://www.microsoft.com/net/download/core) 8.0 or higher +- [dotnet SDK](https://www.microsoft.com/net/download/core) 10.0.401 or a later 10.0 SDK (pinned in `global.json`, uses F# 10) or @@ -55,9 +53,9 @@ or ### Environment Variables -- `CONFIGURATION` will set the [configuration](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-build?tabs=netcore2x#options) of the dotnet commands. If not set, it will default to Release. +- `CONFIGURATION` will set the [configuration](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-build?tabs=netcore2x#options) of the dotnet commands. If not set, it will default to Debug, and to Release when the `PublishToNuGet` target runs. - `CONFIGURATION=Debug ./build.sh` will result in `-c` additions to commands such as in `dotnet build -c Debug` -- `ENABLE_COVERAGE` Will enable running code coverage metrics. AltCover can have [severe performance degradation](https://github.com/SteveGilham/altcover/issues/57) so code coverage evaluation are disabled by default to speed up the feedback loop. +- `ENABLE_COVERAGE` Will enable running code coverage metrics with the MSTest runner (`dotnet test --coverage`). Code coverage evaluation is disabled by default to speed up the feedback loop. - `ENABLE_COVERAGE=1 ./build.sh` will enable code coverage evaluation @@ -75,14 +73,14 @@ or - `DotnetRestore` - Runs [dotnet restore](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-restore?tabs=netcore2x) on the [solution file](https://docs.microsoft.com/en-us/visualstudio/extensibility/internals/solution-dot-sln-file?view=vs-2019). - [`DotnetBuild`](#Building) - Runs [dotnet build](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-build?tabs=netcore2x) on the [solution file](https://docs.microsoft.com/en-us/visualstudio/extensibility/internals/solution-dot-sln-file?view=vs-2019). - `FSharpAnalyzers` - Runs [BinaryDefense.FSharp.Analyzers](https://github.com/BinaryDefense/BinaryDefense.FSharp.Analyzers). -- `DotnetTest` - Runs [dotnet test](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-test?tabs=netcore21) on the [solution file](https://docs.microsoft.com/en-us/visualstudio/extensibility/internals/solution-dot-sln-file?view=vs-2019). +- `DotnetTest` - Runs [dotnet test](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-test) on each test project with the MSTest runner. - `GenerateCoverageReport` - Code coverage is run during `DotnetTest` and this generates a report via [ReportGenerator](https://github.com/danielpalme/ReportGenerator). - `ShowCoverageReport` - Shows the report generated in `GenerateCoverageReport`. - `WatchTests` - Runs [dotnet watch](https://docs.microsoft.com/en-us/aspnet/core/tutorials/dotnet-watch?view=aspnetcore-3.0) with the test projects. Useful for rapid feedback loops. - `GenerateAssemblyInfo` - Generates [AssemblyInfo](https://docs.microsoft.com/en-us/dotnet/api/microsoft.visualbasic.applicationservices.assemblyinfo?view=netframework-4.8) for libraries. - `DotnetPack` - Runs [dotnet pack](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-pack). This includes running [Source Link](https://github.com/dotnet/sourcelink). - `SourceLinkTest` - Runs a Source Link test tool to verify Source Links were properly generated. -- `PublishToNuGet` - Publishes the NuGet packages generated in `DotnetPack` to NuGet via [paket push](https://fsprojects.github.io/Paket/paket-push.html). Runs only from `Github Actions`. +- `PublishToNuGet` - Publishes the NuGet packages generated in `DotnetPack` to NuGet via [dotnet nuget push](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-nuget-push). Runs only from `Github Actions`. - `GitRelease` - Creates a commit message with the [Release Notes](https://fake.build/apidocs/v5/fake-core-releasenotes.html) and a git tag via the version in the `Release Notes`. - `GitHubRelease` - Publishes a [GitHub Release](https://help.github.com/en/articles/creating-releases) with the Release Notes and any NuGet packages. Runs only from `Github Actions`. - `FormatCode` - Runs [Fantomas](https://github.com/fsprojects/fantomas) on the solution file. @@ -107,7 +105,7 @@ git push -u origin main - Add your `NUGET_TOKEN` to the [Environment Secrets](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#environment-secrets) of your newly created environment. - Then update the `CHANGELOG.md` with an "Unreleased" section containing release notes for this version, in [KeepAChangelog](https://keepachangelog.com/en/1.1.0/) format. -NOTE: Its highly recommend to add a link to the Pull Request next to the release note that it affects. The reason for this is when the `RELEASE` target is run, it will add these new notes into the body of git commit. GitHub will notice the links and will update the Pull Request with what commit referenced it saying ["added a commit that referenced this pull request"](https://github.com/TheAngryByrd/MiniScaffold/pull/179#ref-commit-837ad59). Since the build script automates the commit message, it will say "Bump Version to x.y.z". The benefit of this is when users goto a Pull Request, it will be clear when and which version those code changes released. Also when reading the `CHANGELOG`, if someone is curious about how or why those changes were made, they can easily discover the work and discussions. +NOTE: Its highly recommend to add a link to the Pull Request next to the release note that it affects. The reason for this is when the `RELEASE` target is run, it will add these new notes into the body of git commit. GitHub will notice the links and will update the Pull Request with what commit referenced it saying ["added a commit that referenced this pull request"](https://github.com/TheAngryByrd/MiniScaffold/pull/179#ref-commit-837ad59). Since the build script automates the commit message, it will say ``release: bump version to `x.y.z` ``. The benefit of this is when users goto a Pull Request, it will be clear when and which version those code changes released. Also when reading the `CHANGELOG`, if someone is curious about how or why those changes were made, they can easily discover the work and discussions. ### Releasing Documentation diff --git a/build/Changelog.fs b/build/Changelog.fs index 308013c5..7026b3de 100644 --- a/build/Changelog.fs +++ b/build/Changelog.fs @@ -64,7 +64,7 @@ let mkReleaseNotes changelog (latestEntry : Changelog.ChangelogEntry) gitHubRepo | Some desc when desc.Contains (linkReference) -> desc | Some desc -> sprintf "%s\n\n%s" (desc.Trim ()) linkReference - { latestEntry with Description = Some description }.ToString () + { latestEntry with Description = Some description }.ToString() let getVersionNumber envVarName ctx = let args = ctx.Context.Arguments diff --git a/build/build.fs b/build/build.fs index 441de3e0..2775bf65 100644 --- a/build/build.fs +++ b/build/build.fs @@ -145,12 +145,12 @@ let failOnWrongBranch () = module dotnet = - let watch cmdParam program args = DotNet.exec cmdParam (sprintf "watch %s" program) args + let watch cmdParam program args = DotNet.exec cmdParam $"watch %s{program}" args let run cmdParam args = DotNet.exec cmdParam "run" args - let tool optionConfig command args = - DotNet.exec optionConfig (sprintf "%s" command) args + let tool optionConfig (command : string) args = + DotNet.exec optionConfig command args |> failOnBadExitAndPrint let reportgenerator optionConfig args = tool optionConfig "reportgenerator" args @@ -175,9 +175,9 @@ module FSharpAnalyzers = member s.Usage = "" -module DocsTool = - let quoted s = $"\"%s{s}\"" +let quoted s = $"\"%s{s}\"" +module DocsTool = let fsDocsDotnetOptions (o : DotNet.Options) = { o with WorkingDirectory = rootDirectory } let fsDocsBuildParams configuration (p : Fsdocs.BuildCommandParams) = { @@ -269,15 +269,6 @@ let dotnetRestore _ = ) |> Seq.iter (retryIfInCI 10) -let dotnetToolRestore _ = - let result = - fun () -> DotNet.exec id "tool" "restore" - |> (retryIfInCI 10) - - - if not result.OK then - failwithf "Failed to restore .NET tools: %A" result.Errors - let updateChangelog ctx = latestEntry <- if not <| isPublishToGitHub ctx then @@ -338,7 +329,7 @@ let dotnetBuild ctx = sln let fsharpAnalyzers _ = - let argParser = ArgumentParser.Create (programName = "fsharp-analyzers") + let argParser = ArgumentParser.Create(programName = "fsharp-analyzers") !!srcGlob |> Seq.iter (fun proj -> @@ -356,37 +347,37 @@ let fsharpAnalyzers _ = ) let dotnetTest ctx = - let excludeCoverage = - !!testsGlob - |> Seq.map (IO.Path.GetFileNameWithoutExtension >> Unchecked.nonNull) - |> String.concat "|" - - let isGenerateCoverageReportTarget = - String.Equals (ctx.Context.FinalTarget, "GenerateCoverageReport", StringComparison.OrdinalIgnoreCase) - || String.Equals (ctx.Context.FinalTarget, "ShowCoverageReport", StringComparison.OrdinalIgnoreCase) - - let shouldCollectCoverage = enableCodeCoverage || isGenerateCoverageReportTarget - - if shouldCollectCoverage then - Directory.create testResultsDir - - let args = - [ - "--no-build" - $"--configuration {configuration (ctx.Context.AllExecutingTargets)}" - if shouldCollectCoverage then - "/p:AltCover=true" - $"/p:AltCoverAssemblyExcludeFilter={excludeCoverage}" - "/p:AltCoverLocalSource=true" - $"--solution \"{sln}\"" - ] - |> String.concat " " + // Create test results directory if it doesn't exist + Directory.create testResultsDir + + let dotnetConfiguration = configuration (ctx.Context.AllExecutingTargets) |> string + + let isGenerateCoverageReport = ctx.Context.TryFindTarget("GenerateCoverageReport").IsSome + + let args = [ + "--no-build" + "--configuration" + dotnetConfiguration + if enableCodeCoverage || isGenerateCoverageReport then + "--coverage" + "--coverage-output-format" + "cobertura" + "--results-directory" + quoted testResultsDir + ] - DotNet.exec (DotNet.Options.withWorkingDirectory rootDirectory) "test" args - |> failOnBadExitAndPrint + !!testsGlob + |> Seq.iter (fun testProject -> + [ "--project"; quoted testProject; yield! args ] + |> String.concat " " + |> DotNet.exec id "test" + |> failOnBadExitAndPrint + ) let generateCoverageReport _ = - let coverageFiles = !!(rootDirectory "tests/**/coverage*.xml") + let coverageFiles = + !!(testResultsDir "*.cobertura.xml") + ++ (testResultsDir "*/coverage.cobertura.xml") let sourceDirs = !!srcGlob |> Seq.map Path.getDirectory |> String.concat ";" @@ -395,9 +386,10 @@ let generateCoverageReport _ = sprintf "-targetdir:\"%s\"" coverageReportDir // Add source dir sprintf "-sourcedirs:\"%s\"" sourceDirs - // Ignore test assemblies and AltCover recorder helpers - sprintf "-assemblyfilters:\"%s\"" "-*.Tests;-AltCover.Recorder.g" - sprintf "-reporttypes:%s" "Html" + // Ignore test assemblies + sprintf "-assemblyfilters:\"%s\"" "-*.Tests" + // Generate HTML and Cobertura reports + sprintf "-reporttypes:%s" "Html;Cobertura" ] let args = independentArgs |> String.concat " " @@ -445,7 +437,7 @@ let generateAssemblyInfo _ = | f when f.EndsWith ("fsproj") -> Fsproj | f when f.EndsWith ("csproj") -> Csproj | f when f.EndsWith ("vbproj") -> Vbproj - | _ -> failwith (sprintf "Project file %s not supported. Unknown project type." projFileName) + | _ -> failwithf "Project file %s not supported. Unknown project type." projFileName let releaseChannel = match latestEntry.SemVer.PreRelease with @@ -456,7 +448,10 @@ let generateAssemblyInfo _ = AssemblyInfo.Title (projectName) AssemblyInfo.Product productName AssemblyInfo.Version latestEntry.AssemblyVersion - AssemblyInfo.Metadata ("ReleaseDate", latestEntry.Date.Value.ToString ("o")) + // The date is optional in a changelog entry + match latestEntry.Date with + | Some releaseDate -> AssemblyInfo.Metadata ("ReleaseDate", releaseDate.ToString ("o")) + | None -> () AssemblyInfo.FileVersion latestEntry.AssemblyVersion AssemblyInfo.InformationalVersion latestEntry.AssemblyVersion AssemblyInfo.Metadata ("ReleaseChannel", releaseChannel) @@ -539,7 +534,7 @@ let gitRelease _ = ++ (rootDirectory "tests/**/AssemblyInfo.fs") |> Seq.iter (Git.Staging.stageFile "" >> ignore) - let msg = $"Bump version to `%s{latestEntry.NuGetVersion}`\n\n%s{releaseNotesGitCommitFormat}" + let msg = $"release: bump version to `%s{latestEntry.NuGetVersion}`\n\n%s{releaseNotesGitCommitFormat}" Git.Commit.exec "" msg @@ -615,10 +610,15 @@ let initTargets (ctx : Context.FakeExecutionContext) = && String.Equals (value, "PublishToGitHub", StringComparison.OrdinalIgnoreCase) ) - /// Defines a dependency - y is dependent on x. Finishes the chain. + /// + /// Defines a dependency - is dependent on . Finishes the chain. + /// let (==>!) x y = x ==> y |> ignore - /// Defines a soft dependency. x must run before y, if it is present, but y does not require x to be run. Finishes the chain. + /// + /// Defines a soft dependency. must run before , if it is present, but + /// does not require to be run. Finishes the chain. + /// let (?=>!) x y = x ?=> y |> ignore //----------------------------------------------------------------------------- // Hide Secrets in Logger @@ -631,7 +631,6 @@ let initTargets (ctx : Context.FakeExecutionContext) = Target.create "Clean" clean Target.create "DotnetRestore" dotnetRestore - Target.create "DotnetToolRestore" dotnetToolRestore Target.create "UpdateChangelog" updateChangelog Target.createBuildFailure "RevertChangelog" revertChangelog // Do NOT put this in the dependency chain Target.createFinal "DeleteChangelogBackupFile" deleteChangelogBackupFile // Do NOT put this in the dependency chain @@ -714,11 +713,6 @@ let initTargets (ctx : Context.FakeExecutionContext) = "DotnetRestore" ==>! "WatchTests" - //"DotnetToolRestore" ?=>! "DotnetRestore" - "DotnetToolRestore" ==>! "BuildDocs" - "DotnetToolRestore" ?=>! "CheckFormatCode" - "DotnetToolRestore" ?=>! "FormatCode" - //----------------------------------------------------------------------------- // Target Start //----------------------------------------------------------------------------- diff --git a/global.json b/global.json index 70c8f035..d3238643 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "10.0.300", + "version": "10.0.401", "rollForward": "latestMinor" }, "test": { diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 00000000..5ea09a4f --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,14 @@ + + + + true + true + false + + + true + true + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + + diff --git a/src/FSharp.Collections.Immutable/AssemblyInfo.fs b/src/FSharp.Collections.Immutable/AssemblyInfo.fs new file mode 100644 index 00000000..db10dac4 --- /dev/null +++ b/src/FSharp.Collections.Immutable/AssemblyInfo.fs @@ -0,0 +1,23 @@ +// Auto-Generated by FAKE; do not edit +namespace System +open System.Reflection + +[] +[] +[] +[] +[] +[] +[] +[] +do () + +module internal AssemblyVersionInformation = + let [] AssemblyTitle = "FSharp.Collections.Immutable" + let [] AssemblyProduct = "FSharp.Collections.Immutable" + let [] AssemblyVersion = "1.0.0" + let [] AssemblyMetadata_ReleaseDate = "2020-02-23T00:00:00.0000000" + let [] AssemblyFileVersion = "1.0.0" + let [] AssemblyInformationalVersion = "1.0.0" + let [] AssemblyMetadata_ReleaseChannel = "release" + let [] AssemblyMetadata_GitHash = "59d59515d1f3a3ecee1bb208b9bc1378035e4694" diff --git a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj index 9f5941f0..6a4bc983 100644 --- a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj +++ b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj @@ -5,14 +5,12 @@ $(AssemblyBaseName) true true - True FSharp.Collections.Immutable FSharp.Collections.Immutable - F# API for using Microsoft Azure Cosmos DB service via NoSQL API - Provides extension methods for the FeedIterator and computation expressions to build operations + F# bindings for System.Collections.Immutable @@ -21,6 +19,7 @@ + diff --git a/src/FSharp.Collections.Immutable/FlatList.fs b/src/FSharp.Collections.Immutable/FlatList.fs index 82bcce74..b6153265 100644 --- a/src/FSharp.Collections.Immutable/FlatList.fs +++ b/src/FSharp.Collections.Immutable/FlatList.fs @@ -23,8 +23,8 @@ module FlatList = ////////// Creating ////////// - let inline empty<'T> : FlatList<_> = FlatListFactory.Create<'T> () - let inline singleton<'T> (item : 'T) : FlatList<'T> = FlatListFactory.Create<'T> (item) + let inline empty<'T> : FlatList<_> = FlatListFactory.Create<'T>() + let inline singleton<'T> (item : 'T) : FlatList<'T> = FlatListFactory.Create<'T>(item) let inline ofSeq source = FlatListFactory.CreateRange source let inline ofArray (source : _ array) = FlatListFactory.CreateRange source @@ -234,7 +234,7 @@ module FlatList = let iter2 action list1 list2 = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<'T, 'U, unit>.Adapt (action) + let f = OptimizedClosures.FSharpFunc<'T, 'U, unit>.Adapt(action) let len = length list1 if len <> length list2 then @@ -245,7 +245,7 @@ module FlatList = let distinctBy projection (list : FlatList<'T>) = let builder : FlatList<'T>.Builder = builderWith <| length list - let set = System.Collections.Generic.HashSet<'Key> (HashIdentity.Structural) + let set = System.Collections.Generic.HashSet<'Key>(HashIdentity.Structural) let mutable outputIndex = 0 for i = 0 to length list - 1 do @@ -260,7 +260,7 @@ module FlatList = let map2 mapping list1 list2 = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (mapping) + let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt(mapping) let len1 = list1.Length if len1 <> list2.Length then @@ -277,7 +277,7 @@ module FlatList = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 checkNotDefault (nameof list3) list3 - let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt (mapping) + let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt(mapping) let len1 = list1.Length if not (len1 = list2.Length) then @@ -296,7 +296,7 @@ module FlatList = let mapi2 mapping list1 list2 = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt (mapping) + let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt(mapping) let len1 = list1.Length if len1 <> list2.Length then @@ -311,7 +311,7 @@ module FlatList = let iteri action list = check list - let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (action) + let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt(action) let len = list.Length for i = 0 to len - 1 do @@ -320,7 +320,7 @@ module FlatList = let iteri2 action list1 list2 = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt (action) + let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt(action) let len1 = list1.Length if len1 <> list2.Length then @@ -331,7 +331,7 @@ module FlatList = let mapi mapping list = check list - let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (mapping) + let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt(mapping) let len = list.Length let res = builderWithLengthOf list @@ -360,7 +360,7 @@ module FlatList = let exists2 predicate list1 list2 = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (predicate) + let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt(predicate) let len1 = list1.Length if len1 <> list2.Length then @@ -381,7 +381,7 @@ module FlatList = let forall2 predicate list1 list2 = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (predicate) + let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt(predicate) let len1 = list1.Length if len1 <> list2.Length then diff --git a/src/FSharp.Collections.Immutable/ImmutableList.fs b/src/FSharp.Collections.Immutable/ImmutableList.fs index 48ced7c7..6683f84a 100644 --- a/src/FSharp.Collections.Immutable/ImmutableList.fs +++ b/src/FSharp.Collections.Immutable/ImmutableList.fs @@ -14,8 +14,8 @@ module ImmutableList = let inline internal check (list : IImmutableList<_>) = checkNotNull (nameof list) list - let inline empty<'T> = ImmutableList.Create<'T> () - let inline singleton<'T> (item : 'T) : ImmutableList<'T> = ImmutableList.Create<'T> (item) + let inline empty<'T> = ImmutableList.Create<'T>() + let inline singleton<'T> (item : 'T) : ImmutableList<'T> = ImmutableList.Create<'T>(item) let inline ofSeq source = checkNotNull (nameof source) source diff --git a/src/FSharp.Collections.Immutable/Maps.fs b/src/FSharp.Collections.Immutable/Maps.fs index 12f5d638..7a73565a 100644 --- a/src/FSharp.Collections.Immutable/Maps.fs +++ b/src/FSharp.Collections.Immutable/Maps.fs @@ -19,7 +19,7 @@ module HashMap = ////////// Creating ////////// - let inline empty<'Key, 'Value when 'Key : not null> = HashMapFactory.Create<'Key, 'Value> () + let inline empty<'Key, 'Value when 'Key : not null> = HashMapFactory.Create<'Key, 'Value>() let inline singleton item = empty.Add (item) let inline ofSeq source = HashMapFactory.CreateRange (source) @@ -57,9 +57,9 @@ module HashMap = check map map.ToBuilder () - let inline ofKeyComparer<'Key, 'Value when 'Key : not null> comparer = HashMapFactory.Create<'Key, 'Value> (comparer) + let inline ofKeyComparer<'Key, 'Value when 'Key : not null> comparer = HashMapFactory.Create<'Key, 'Value>(comparer) let inline ofComparers<'Key, 'Value when 'Key : not null> keyComparer valueComparer = - HashMapFactory.Create<'Key, 'Value> (keyComparer, valueComparer) + HashMapFactory.Create<'Key, 'Value>(keyComparer, valueComparer) let inline isEmpty map = @@ -178,7 +178,7 @@ module SortedMap = ////////// Creating ////////// - let inline empty<'Key, 'Value when 'Key : not null> = SortedMapFactory.Create<'Key, 'Value> () + let inline empty<'Key, 'Value when 'Key : not null> = SortedMapFactory.Create<'Key, 'Value>() let inline singleton item = SortedMapFactory.Create (item) let inline ofSeq source = SortedMapFactory.CreateRange (source) @@ -215,9 +215,9 @@ module SortedMap = check map map.ToBuilder () - let inline ofKeyComparer<'Key, 'Value when 'Key : not null> comparer = SortedMapFactory.Create<'Key, 'Value> (comparer) + let inline ofKeyComparer<'Key, 'Value when 'Key : not null> comparer = SortedMapFactory.Create<'Key, 'Value>(comparer) let inline ofComparers<'Key, 'Value when 'Key : not null> keyComparer valueComparer = - SortedMapFactory.Create<'Key, 'Value> (keyComparer, valueComparer) + SortedMapFactory.Create<'Key, 'Value>(keyComparer, valueComparer) let inline isEmpty map = diff --git a/src/FSharp.Collections.Immutable/Queue.fs b/src/FSharp.Collections.Immutable/Queue.fs index dd8d64fe..ba9d1f39 100644 --- a/src/FSharp.Collections.Immutable/Queue.fs +++ b/src/FSharp.Collections.Immutable/Queue.fs @@ -13,9 +13,9 @@ module Queue = let inline private check (queue : IQueue<_>) = checkNotNull (nameof queue) queue - let inline empty<'T> : Queue<'T> = QueueFactory.Create<'T> () + let inline empty<'T> : Queue<'T> = QueueFactory.Create<'T>() - let inline singleton<'T> (item : 'T) : Queue<'T> = QueueFactory.Create<'T> (item) + let inline singleton<'T> (item : 'T) : Queue<'T> = QueueFactory.Create<'T>(item) let inline ofSeq (source : 'T seq) : Queue<'T> = QueueFactory.CreateRange source diff --git a/src/FSharp.Collections.Immutable/Sets.fs b/src/FSharp.Collections.Immutable/Sets.fs index cbdba229..8d94e56e 100644 --- a/src/FSharp.Collections.Immutable/Sets.fs +++ b/src/FSharp.Collections.Immutable/Sets.fs @@ -16,8 +16,8 @@ module HashSet = ////////// Creating ////////// - let inline empty<'T> = HashSetFactory.Create<'T> () - let inline singleton<'T> (item : 'T) = HashSetFactory.Create<'T> (item) + let inline empty<'T> = HashSetFactory.Create<'T>() + let inline singleton<'T> (item : 'T) = HashSetFactory.Create<'T>(item) let inline ofSeq source = HashSetFactory.CreateRange (source) let inline ofSeqWithComparer comparer source = HashSetFactory.Create (comparer, items = (source |> Array.ofSeq)) let inline ofArray (source : _ array) = HashSetFactory.CreateRange (source) @@ -26,7 +26,7 @@ module HashSet = checkNotNull (nameof hashSetBuilder) hashSetBuilder hashSetBuilder.ToImmutable () - let inline ofComparer<'T> comparer = HashSetFactory.Create<'T> (equalityComparer = comparer) + let inline ofComparer<'T> comparer = HashSetFactory.Create<'T>(equalityComparer = comparer) let inline toSeq (set : HashSet<_>) = set :> seq<_> @@ -149,8 +149,8 @@ module SortedSet = ////////// Creating ////////// - let inline empty<'T> = SortedSetFactory.Create<'T> () - let inline singleton<'T> (item : 'T) = SortedSetFactory.Create<'T> (item) + let inline empty<'T> = SortedSetFactory.Create<'T>() + let inline singleton<'T> (item : 'T) = SortedSetFactory.Create<'T>(item) let inline ofSeq source = SortedSetFactory.CreateRange (source) let inline ofSeqWithComparer comparer source = SortedSetFactory.Create (comparer, items = (source |> Array.ofSeq)) let inline ofArray (source : _ array) = SortedSetFactory.CreateRange (source) @@ -159,7 +159,7 @@ module SortedSet = checkNotNull (nameof sortedSetBuilder) sortedSetBuilder sortedSetBuilder.ToImmutable () - let inline ofComparer<'T> comparer = SortedSetFactory.Create<'T> (comparer = comparer) + let inline ofComparer<'T> comparer = SortedSetFactory.Create<'T>(comparer = comparer) let inline toSeq (set : SortedSet<_>) = set :> seq<_> diff --git a/src/FSharp.Collections.Immutable/Stack.fs b/src/FSharp.Collections.Immutable/Stack.fs index a9502e53..774ad6b5 100644 --- a/src/FSharp.Collections.Immutable/Stack.fs +++ b/src/FSharp.Collections.Immutable/Stack.fs @@ -12,7 +12,7 @@ module Stack = let inline internal check (stack : IStack<_>) = checkNotNull (nameof stack) stack - let inline empty<'T> = StackFactory.Create<'T> () + let inline empty<'T> = StackFactory.Create<'T>() let inline ofSeq source = StackFactory.CreateRange source