Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions plugins/dotnet-msbuild/skills/msbuild-antipatterns/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ Use this catalog when scanning project files for improvements.

**Exception**: Non-SDK-style (legacy) projects require explicit file includes. If migrating, see `msbuild-modernization` skill.

**Exception (F# / `.fsproj`)**: F# compilation is order-dependent — the compiler processes `<Compile Include>` items sequentially and a file can only reference types/modules declared in files listed above it. `.fsproj` files must therefore list every source file explicitly, in dependency order (utility/leaf modules at the top, the entry point such as `Program.fs` at the bottom). If a `.fsi` signature file is used, it must appear **immediately before** its companion `.fs` implementation file.

---

## AP-06: Using `<Reference>` with HintPath for NuGet Packages
Expand Down
62 changes: 62 additions & 0 deletions tests/dotnet-msbuild/msbuild-antipatterns/eval.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,65 @@ scenarios:
- "Identified DefineConstants overwrites existing constants instead of appending"
- "Identified analyzer PackageReference missing PrivateAssets, unquoted MSBuild condition, mixed Include/Update in same ItemGroup, or Exec instead of Message task"
timeout: 160

- name: "Add a module to an F# project"
prompt: |
Add a Validation.fs module to the F# project at fsharp/OrderService/ that
validates orders before processing. It should check that the order has at
least one item and that the customer name is not empty. Return a
Result<Order, string> with a descriptive error message on failure. Then
update Program.fs to validate the order before processing it, printing the
error if validation fails.
setup:
copy_test_files: true
assertions:
- type: "exit_success"
- type: "file_contains"
path: "fsharp/OrderService/OrderService.fsproj"
value: "Validation.fs"
rubric:
- "Created Validation.fs with a validation function returning Result<Order, string>"
- "Inserted Compile Include=\"Validation.fs\" AFTER Domain.fs and BEFORE Program.fs in the .fsproj"
- "Did NOT place Validation.fs after Program.fs in the Compile item list"
- "Updated Program.fs to call the validation function before processing"
Comment thread
T-Gro marked this conversation as resolved.
- "Ran dotnet build and it succeeded"
timeout: 120
Comment thread
T-Gro marked this conversation as resolved.

- name: "Fix broken file order causing FS0039"
prompt: |
The F# project at fsharp/BrokenOrder/ fails to build. Diagnose and fix the issue.
setup:
copy_test_files: true
assertions:
- type: "exit_success"
- type: "file_contains"
path: "fsharp/BrokenOrder/BrokenOrder.fsproj"
value: "Domain.fs"
rubric:
- "Ran dotnet build and observed FS0039 or similar 'not defined' errors"
- "Identified that the failure is caused by wrong file order in the .fsproj — Services.fs is listed before Domain.fs"
- "Reordered Compile items so Domain.fs appears before Services.fs in the .fsproj"
- "Did NOT modify any .fs source files to work around the ordering issue"
Comment thread
T-Gro marked this conversation as resolved.
- "Ran dotnet build after the fix and confirmed it succeeds"
timeout: 120

- name: "Add a signature file to define public API"
prompt: |
Add a signature file (Domain.fsi) for the Domain module in the F# project at
fsharp/OrderService/ to explicitly define its public API surface. The
signature should expose all types and keep the module's public contract
clear. Make sure the project still builds after adding the signature file.
setup:
copy_test_files: true
assertions:
- type: "exit_success"
- type: "file_contains"
path: "fsharp/OrderService/OrderService.fsproj"
value: "Domain.fsi"
rubric:
- "Created Domain.fsi with type signatures matching the types in Domain.fs"
- "Inserted Compile Include=\"Domain.fsi\" immediately BEFORE Domain.fs in the .fsproj"
- "Did NOT place Domain.fsi after Domain.fs in the Compile item list"
- "The .fsi file contains type declarations (not implementation code)"
- "The project builds successfully with the signature file present"
timeout: 120
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Services.fs" />
<Compile Include="Domain.fs" />
<Compile Include="Program.fs" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module BrokenOrder.Domain

type OrderId = OrderId of int

type OrderItem = {
Name: string
Quantity: int
Price: decimal
}

type Order = {
Id: OrderId
CustomerName: string
Items: OrderItem list
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
open BrokenOrder.Domain
open BrokenOrder.Services

let order = {
Id = OrderId 1
CustomerName = "Alice"
Items = [
{ Name = "Widget"; Quantity = 2; Price = 9.99m }
{ Name = "Gadget"; Quantity = 1; Price = 24.99m }
]
}

let total = processOrder order
printfn "Order total: $%M" total
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module BrokenOrder.Services

open BrokenOrder.Domain

let calculateTotal (order: Order) =
order.Items
|> List.sumBy (fun item -> item.Price * decimal item.Quantity)

let processOrder (order: Order) =
let total = calculateTotal order
printfn "Processing order for %s: $%M" order.CustomerName total
total
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Project>
<!-- Empty: intentionally short-circuits the parent Directory.Build.props
which exists only as a C# antipattern fixture and would otherwise
leak C#-specific settings (analyzers, TreatWarningsAsErrors) into
these F# fixtures. -->
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Project>
<!-- Empty: see Directory.Build.props in this folder for rationale. -->
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module OrderService.Domain

type OrderId = OrderId of int

type OrderItem = {
Name: string
Quantity: int
Price: decimal
}

type Order = {
Id: OrderId
CustomerName: string
Items: OrderItem list
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Domain.fs" />
<Compile Include="Services.fs" />
<Compile Include="Program.fs" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
open OrderService.Domain
open OrderService.Services

let order = {
Id = OrderId 1
CustomerName = "Alice"
Items = [
{ Name = "Widget"; Quantity = 2; Price = 9.99m }
{ Name = "Gadget"; Quantity = 1; Price = 24.99m }
]
}

let total = processOrder order
printfn "Order total: $%M" total
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module OrderService.Services

open OrderService.Domain

let calculateTotal (order: Order) =
order.Items
|> List.sumBy (fun item -> item.Price * decimal item.Quantity)

let processOrder (order: Order) =
let total = calculateTotal order
printfn "Processing order for %s: $%M" order.CustomerName total
total
Loading