Skip to content

Commit f66ffcc

Browse files
authored
Merge pull request #1 from NHSISL/users/cjdutoit/code-refactor-sdk
CODERUB: Code refactor sdk
2 parents 5a7c5b4 + 158f158 commit f66ffcc

157 files changed

Lines changed: 12635 additions & 3 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/copilot-instructions.md

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
2+
# Copilot Instructions
3+
4+
## Code Style Rules
5+
6+
### Line Length
7+
8+
- All `.cs` source files must adhere to the following rule:
9+
- No line of code should exceed **120 characters** in length.
10+
- This includes comments, string literals, and code.
11+
- Exception: automatically generated files may be ignored if they cannot be reformatted safely.
12+
- **How to measure (raw file characters, per-line only):**
13+
- Count based on **raw file characters**, not editor rendering.
14+
- **Tabs count as 4 characters** for measurement.
15+
- Trailing whitespace must be removed.
16+
- **Per-physical-line measurement ONLY.** The unit of measurement is a **single newline-delimited line**.
17+
- **Never** add or aggregate the lengths of multiple lines.
18+
- A wrapped invocation is compliant if **each** physical line is ≤ 120 characters.
19+
- Ignore soft wrapping (on-screen wrapping that doesn't insert a newline).
20+
21+
### Code Formatting
22+
23+
- Single-line instructions must follow each other with **no blank lines** in between.
24+
- **New rule (clarified):** A multi-line instruction must be preceded by **exactly one blank line _only when it begins a new statement_**.
25+
Do **not** require a blank line before a multi-line **continuation** of an existing statement.
26+
- If a multi-line instruction is followed by further instructions, it must also be followed by **exactly one blank line**.
27+
- **Exception (block first statement):** If a statement is the **first statement inside a block**—i.e., directly after `{`**no preceding blank line** is required.
28+
- Any C# `return` statement must be preceded by **exactly one blank line** unless it is the first statement in a block.
29+
- If a constructor/method name would push a line past 120 characters, move `new`, the call, or the arguments to the next line.
30+
- Always format so that **no single physical line exceeds 120 characters**, even when calls span multiple lines.
31+
- **Definition of a blank line (updated):**
32+
A blank line is any physical line that contains **no visible characters**. After trimming whitespace, the line must be empty.
33+
Lines containing only spaces or tabs **are valid blank lines**.
34+
- **Method separation:** Method declarations must be preceded by **exactly one blank line** after the closing brace of the previous member.
35+
- **Argument indentation:**
36+
- For multi-line method or constructor calls, the first line ends before the first argument.
37+
- Each wrapped argument line must be indented **one additional indentation level** (usually 4 spaces).
38+
- Do **not** use extra indentation levels.
39+
- The closing `)` must align with the start of the call.
40+
- **Continuation clarification (applies across all checks):**
41+
- A line is considered a **continuation of the same statement** and must **not** be flagged for a missing blank line when **both** are true:
42+
1) The previous non-empty trimmed line **does not** end with `;` or `}`, **and**
43+
2) The current line, after trimming leading whitespace, **starts with a continuation indicator**, such as:
44+
`.`, `??`, `?`, `:`, `+`, `-`, `*`, `/`, `%`, `&&`, `||`, `=>`, `,`, `)`, `]`,
45+
or any identifier/keyword when the previous line ends with an incomplete construct (e.g., open `(`, interpolated start `$"`, method/constructor call, LINQ chain).
46+
- Only when a **new statement** begins (i.e., the previous trimmed line **ends** with `;` or `}`) and the **next statement is multi-line** should an **exactly one** blank line be required before it.
47+
48+
### Enforcement
49+
50+
- Copilot should **not generate code** that exceeds the 120-character line limit.
51+
- When writing new C# code, Copilot should:
52+
- Break up long method/constructor calls across multiple lines.
53+
- Use string interpolation or verbatim strings with proper line breaks where needed.
54+
- Format long LINQ queries across multiple lines.
55+
- Wrap parameters and arguments for readability.
56+
- Insert a blank line before any `return` following other statements.
57+
- Prefer moving `new` or the method invocation to the next line when appropriate.
58+
59+
### Review Guidelines (strict)
60+
61+
- Copilot must:
62+
- Evaluate **each physical line independently**.
63+
- Flag a violation only when a **single physical line** exceeds 120 characters.
64+
- When flagging, include line number and measured character count.
65+
- Suggest multiline formatting only when the offending line exceeds 120.
66+
- **Not** flag whitespace-only lines; they are valid blank lines.
67+
- **Continuation Detection (unambiguous):** Do **not** require a blank line before a line that is a continuation of the same statement.
68+
Treat a line as a continuation when **both** of the following hold:
69+
1) The previous non-empty trimmed line **does not** end with `;` or `}`, **and**
70+
2) The current line (after trimming leading whitespace) **begins with** a continuation indicator:
71+
`.`, `??`, `?`, `:`, `+`, `-`, `*`, `/`, `%`, `&&`, `||`, `=>`, `,`, `)`, `]`,
72+
or any identifier/keyword when the previous line ends with an incomplete construct (e.g., open `(`, start of `$"`, method/constructor call, LINQ chain).
73+
- Flag missing blank lines before `return` **only** when `return` is the first token on the line **and** the previous non-empty trimmed line ended with `;` or `}`.
74+
- **Operator lines:**
75+
- Measure compliance per physical line.
76+
- Do not combine operator lines with continuations.
77+
- Operator-at-end style is preferred.
78+
- **Block-first statement exemption:**
79+
- Do not require a preceding blank line if the previous meaningful line ends with `{`.
80+
81+
### Examples
82+
83+
#### ✅ Correct (first statement inside a block; no blank line required)
84+
85+
```csharp
86+
public void Foo()
87+
{
88+
DoSomething(
89+
x,
90+
y);
91+
}
92+
```
93+
94+
#### ❌ Incorrect (blank line required between two statements)
95+
96+
```csharp
97+
DoSomething();
98+
DoSomethingElse(
99+
x,
100+
y);
101+
```
102+
103+
#### ✅ Correct (wrapped invocation; each line ≤ 120)
104+
105+
```csharp
106+
Validate(
107+
createException: () => new InvalidDecisionPollException(
108+
message: "Invalid decisionPoll. Please correct the errors and try again."),
109+
(Rule: IsInvalid(decisionPoll.Id), Parameter: nameof(DecisionPoll.Id)));
110+
```
111+
112+
#### ❌ Incorrect (single line > 120)
113+
114+
```csharp
115+
Validate(createException: () => new InvalidDecisionPollException(message: "Invalid decisionPoll. Please correct the errors and try again."));
116+
```
117+
118+
---
119+
120+
### Code Formatting Rule Examples
121+
122+
#### ✅ Correct (return with blank line)
123+
124+
```csharp
125+
var user = users.FirstOrDefault(u => u.Id == id);
126+
127+
return user;
128+
```
129+
130+
#### ❌ Incorrect
131+
132+
```csharp
133+
var user = users.FirstOrDefault(u => u.Id == id);
134+
return user;
135+
```
136+
137+
---
138+
139+
### Argument Indentation Examples
140+
141+
#### ✅ Correct
142+
143+
```csharp
144+
DoSomething(
145+
firstArgument: "value1",
146+
secondArgument: "value2",
147+
thirdArgument: "value3");
148+
```
149+
150+
#### ❌ Incorrect (extra indentation)
151+
152+
```csharp
153+
DoSomething(
154+
firstArgument: "value1",
155+
secondArgument: "value2",
156+
thirdArgument: "value3");
157+
```
158+
159+
#### ❌ Incorrect (misaligned closing parenthesis)
160+
161+
```csharp
162+
DoSomething(
163+
firstArgument: "value1",
164+
secondArgument: "value2",
165+
thirdArgument: "value3"
166+
);
167+
```
168+
169+
---
170+
171+
### More Formatting Examples
172+
173+
#### ✅ Correct
174+
175+
```csharp
176+
var filteredUsers = users
177+
.Where(u => u.IsActive && u.LastLoginDate >= DateTime.UtcNow.AddDays(-30))
178+
.OrderByDescending(u => u.LastLoginDate)
179+
.Select(u => new
180+
{
181+
u.Id,
182+
u.Name,
183+
u.Email,
184+
LastSeen = u.LastLoginDate.ToString("yyyy-MM-dd HH:mm:ss")
185+
})
186+
.ToList();
187+
```
188+
189+
#### ❌ Incorrect
190+
191+
```csharp
192+
var filteredUsers = users.Where(u => u.IsActive && u.LastLoginDate >= DateTime.UtcNow.AddDays(-30)).OrderByDescending(u => u.LastLoginDate).Select(u => new { u.Id, u.Name, u.Email, LastSeen = u.LastLoginDate.ToString("yyyy-MM-dd HH:mm:ss") }).ToList();
193+
```
194+
195+
---
196+
197+
### Rationale
198+
199+
- **Per-line measurement** prevents false positives in wrapped calls.
200+
- **Tabs count as 4 characters** ensures consistent line-length calculation.
201+
- **Whitespace-only blank lines count as blank** and match VS behaviour.
202+
- **Return visibility** improves readability.
203+
- **Argument indentation** improves consistency.
204+
- **Block-first patterns** avoid unnecessary whitespace noise.
205+
206+
---
207+
208+
## Supporting .editorconfig Settings
209+
210+
```ini
211+
[*.{cs,vb,ts,tsx}]
212+
guidelines = 120
213+
indent_style = space
214+
indent_size = 4
215+
trim_trailing_whitespace = true
216+
end_of_line = crlf
217+
218+
dotnet_sort_system_directives_first = true
219+
```

.github/workflows/build.yml

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
name: Build
2+
on:
3+
push:
4+
branches:
5+
- main
6+
pull_request:
7+
types:
8+
- opened
9+
- synchronize
10+
- reopened
11+
- closed
12+
branches:
13+
- main
14+
jobs:
15+
build:
16+
name: Build
17+
runs-on: windows-latest
18+
steps:
19+
- name: Enable long paths for Git
20+
run: git config --system core.longpaths true
21+
- name: Check out
22+
uses: actions/checkout@v3
23+
- name: Setup .Net
24+
uses: actions/setup-dotnet@v3
25+
with:
26+
dotnet-version: 10.0.100
27+
- name: Restore
28+
run: dotnet restore
29+
- name: Build
30+
run: dotnet build --no-restore
31+
- name: Run Unit Tests
32+
run: >-
33+
$projects = Get-ChildItem -Path . -Filter "*Tests.Unit*.csproj" -Recurse
34+
35+
foreach ($project in $projects) {
36+
Write-Host "Running tests for: $($project.FullName)"
37+
dotnet test $project.FullName --no-build --verbosity normal
38+
}
39+
shell: pwsh
40+
- name: Run Acceptance Tests
41+
run: >-
42+
$projects = Get-ChildItem -Path . -Filter "*Tests.Acceptance*.csproj" -Recurse
43+
44+
foreach ($project in $projects) {
45+
Write-Host "Running tests for: $($project.FullName)"
46+
dotnet test $project.FullName --no-build --verbosity normal
47+
}
48+
add_tag:
49+
name: Tag and Release
50+
runs-on: ubuntu-latest
51+
needs:
52+
- build
53+
if: >-
54+
needs.build.result == 'success' &&
55+
56+
github.event.pull_request.merged &&
57+
58+
github.event.pull_request.base.ref == 'main' &&
59+
60+
startsWith(github.event.pull_request.title, 'RELEASES:') &&
61+
62+
contains(github.event.pull_request.labels.*.name, 'RELEASES')
63+
steps:
64+
- name: Checkout code
65+
uses: actions/checkout@v3
66+
with:
67+
token: ${{ secrets.PAT_FOR_TAGGING }}
68+
- name: Configure Git
69+
run: >-
70+
git config user.name "GitHub Action"
71+
72+
git config user.email "action@github.com"
73+
- name: Extract Version
74+
id: extract_version
75+
run: >
76+
# Running on Linux/Unix
77+
78+
sudo apt-get install xmlstarlet
79+
80+
version_number=$(xmlstarlet sel -t -v "//Version" -n NHS.Digital.ApiPlatform.Sdk/NHS.Digital.ApiPlatform.Sdk.csproj)
81+
82+
echo "$version_number"
83+
84+
echo "version_number<<EOF" >> $GITHUB_OUTPUT
85+
86+
echo "$version_number" >> $GITHUB_OUTPUT
87+
88+
echo "EOF" >> $GITHUB_OUTPUT
89+
shell: bash
90+
- name: Display Version
91+
run: 'echo "Version number: ${{ steps.extract_version.outputs.version_number }}"'
92+
- name: Extract Package Release Notes
93+
id: extract_package_release_notes
94+
run: >
95+
# Running on Linux/Unix
96+
97+
sudo apt-get install xmlstarlet
98+
99+
package_release_notes=$(xmlstarlet sel -t -v "//PackageReleaseNotes" -n NHS.Digital.ApiPlatform.Sdk/NHS.Digital.ApiPlatform.Sdk.csproj)
100+
101+
echo "$package_release_notes"
102+
103+
echo "package_release_notes<<EOF" >> $GITHUB_OUTPUT
104+
105+
echo "$package_release_notes" >> $GITHUB_OUTPUT
106+
107+
echo "EOF" >> $GITHUB_OUTPUT
108+
shell: bash
109+
- name: Display Package Release Notes
110+
run: 'echo "Package Release Notes: ${{ steps.extract_package_release_notes.outputs.package_release_notes }}"'
111+
- name: Create GitHub Tag
112+
run: >-
113+
git tag -a "v${{ steps.extract_version.outputs.version_number }}" -m "Release - v${{ steps.extract_version.outputs.version_number }}"
114+
115+
git push origin --tags
116+
- name: Create GitHub Release
117+
uses: actions/create-release@v1
118+
with:
119+
tag_name: v${{ steps.extract_version.outputs.version_number }}
120+
release_name: Release - v${{ steps.extract_version.outputs.version_number }}
121+
body: >-
122+
## Release - v${{ steps.extract_version.outputs.version_number }}
123+
124+
125+
### Release Notes
126+
127+
${{ steps.extract_package_release_notes.outputs.package_release_notes }}
128+
env:
129+
GITHUB_TOKEN: ${{ secrets.PAT_FOR_TAGGING }}
130+
publish:
131+
name: Publish to NuGet
132+
runs-on: ubuntu-latest
133+
needs:
134+
- add_tag
135+
if: needs.add_tag.result == 'success'
136+
steps:
137+
- name: Check out
138+
uses: actions/checkout@v3
139+
- name: Setup .Net
140+
uses: actions/setup-dotnet@v3
141+
with:
142+
dotnet-version: 10.0.100
143+
- name: Restore
144+
run: dotnet restore
145+
- name: Build
146+
run: dotnet build --no-restore --configuration Release
147+
- name: Pack NuGet Package
148+
run: dotnet pack --configuration Release --include-symbols
149+
- name: Push NuGet Package
150+
run: dotnet nuget push **/bin/Release/**/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_ACCESS }} --skip-duplicate

0 commit comments

Comments
 (0)