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
23 changes: 23 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
root = true

[*]
charset = utf-8
end_of_line = crlf
insert_final_newline = true
trim_trailing_whitespace = true

[*.{cs,csproj,props,targets}]
indent_style = space
indent_size = 4

[*.{yml,yaml,json,md}]
indent_style = space
indent_size = 2

[*.md]
trim_trailing_whitespace = false

[*.cs]
dotnet_analyzer_diagnostic.category-Style.severity = suggestion
dotnet_analyzer_diagnostic.category-Design.severity = warning
csharp_style_namespace_declarations = file_scoped:suggestion
3 changes: 3 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
- [ ] I have added or updated tests covering my change.
- [ ] `dotnet build CaeriusNet.slnx -c Release -p:TreatWarningsAsErrors=true` succeeds locally.
- [ ] `dotnet test CaeriusNet.slnx -c Release --filter "FullyQualifiedName!~IntegrationTests"` is green.
- [ ] `pwsh ./eng/ValidatePackage.ps1 -Configuration Release` succeeds for package/public API changes.
- [ ] `cd Documentations && npm install && npm run docs:build` succeeds for documentation changes (or `npm ci` when a lockfile exists).
- [ ] Docker-backed integration tests were run when storage, transactions, SQL, or TVP behaviour changed.
- [ ] I have updated [`CHANGELOG.md`](../CHANGELOG.md) under `[Unreleased]` if user-visible.
- [ ] I have updated public XML documentation comments for any public API change.
- [ ] No new dependencies introduced (or discussed in an issue first).
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,17 @@ jobs:
-p:TreatWarningsAsErrors=true \
-p:WarningLevel=4

- name: Validate NuGet package
shell: pwsh
run: ./eng/ValidatePackage.ps1 -Configuration Release

- name: Run tests with coverage
run: |
dotnet test CaeriusNet.slnx \
--configuration Release \
--no-restore \
--no-build \
--filter "FullyQualifiedName!~CaeriusNet.IntegrationTests" \
--filter "FullyQualifiedName!~IntegrationTests" \
--collect:"XPlat Code Coverage" \
--results-directory ./coverage \
--logger "trx;LogFileName=test-results.trx" \
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ permissions:
contents: read
security-events: write

concurrency:
group: codeql-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
Expand Down
52 changes: 52 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Docs

on:
pull_request:
branches: [main]
paths:
- 'Documentations/**'
- '.github/workflows/docs.yml'
push:
branches: [main, 'feature/**']
paths:
- 'Documentations/**'
- '.github/workflows/docs.yml'

permissions:
contents: read

concurrency:
group: docs-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'

jobs:
build-docs:
name: Build documentation
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: 24
cache: npm
cache-dependency-path: Documentations/package*.json

- name: Install dependencies
if: hashFiles('Documentations/package-lock.json') == ''
working-directory: Documentations
run: npm install

- name: Install locked dependencies
if: hashFiles('Documentations/package-lock.json') != ''
working-directory: Documentations
run: npm ci

- name: Build docs
working-directory: Documentations
run: npm run docs:build
10 changes: 8 additions & 2 deletions .github/workflows/github-pages-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
branches: [main]
paths:
- 'Documentations/**'
- '.github/workflows/github-pages-deploy.yml'
workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
Expand Down Expand Up @@ -39,12 +40,17 @@ jobs:
with:
node-version: 24
cache: npm
cache-dependency-path: Documentations/package.json
cache-dependency-path: Documentations/package*.json
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Install dependencies
if: hashFiles('Documentations/package-lock.json') == ''
working-directory: Documentations
run: npm install
- name: Install locked dependencies
if: hashFiles('Documentations/package-lock.json') != ''
working-directory: Documentations
run: npm ci
- name: Build with VitePress
working-directory: Documentations
run: npm run docs:build
Expand All @@ -64,4 +70,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v4
8 changes: 6 additions & 2 deletions .github/workflows/nuget-dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ on:
permissions:
contents: read

concurrency:
group: dependabot-version-bump-${{ github.ref }}
cancel-in-progress: false

env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
Expand Down Expand Up @@ -47,5 +51,5 @@ jobs:
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Src/CaeriusNet.csproj
git commit -m "chore: bump version to ${{ steps.bump.outputs.new_version }} [skip ci]"
git push
git commit -m "chore: bump version to ${{ steps.bump.outputs.new_version }}"
git push
52 changes: 51 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ jobs:
with:
dotnet-version: ${{ env.DOTNET_VERSION }}

- name: Setup Node
if: hashFiles('Documentations/package.json') != ''
uses: actions/setup-node@v6
with:
node-version: 24
cache: npm
cache-dependency-path: Documentations/package*.json

- name: Cache NuGet packages
uses: actions/cache@v5
with:
Expand Down Expand Up @@ -90,6 +98,20 @@ jobs:
echo "tag=v${NEW}" >> "$GITHUB_OUTPUT"
echo "πŸ”– Version bump: ${CURRENT} β†’ ${NEW} (${{ github.event.inputs.version_bump }})"

- name: Validate release metadata
run: |
if [ ! -f CHANGELOG.md ]; then
echo "❌ CHANGELOG.md is required before publishing a release."
exit 1
fi
if ! grep -q '^## \[Unreleased\]' CHANGELOG.md; then
echo "❌ CHANGELOG.md must contain an [Unreleased] section before release."
exit 1
fi
if [ -z "${{ github.event.inputs.release_notes }}" ]; then
echo "ℹ️ No manual release notes supplied; GitHub release notes will be generated from commits."
fi

# ── Build & Test (gates on the new version) ────────────────────────────────
- name: Restore
run: dotnet restore CaeriusNet.slnx
Expand All @@ -102,14 +124,38 @@ jobs:
-p:TreatWarningsAsErrors=true \
-p:WarningLevel=4

- name: Test
- name: Test (non-integration)
run: |
dotnet test CaeriusNet.slnx \
--configuration Release \
--no-restore \
--no-build \
--filter "FullyQualifiedName!~IntegrationTests" \
--logger "console;verbosity=normal"

- name: Test (integration, Docker)
run: |
dotnet test Tests/CaeriusNet.IntegrationTests/CaeriusNet.IntegrationTests.csproj \
--configuration Release \
--no-restore \
--no-build \
--logger "console;verbosity=normal"

- name: Install docs dependencies
if: hashFiles('Documentations/package.json') != '' && hashFiles('Documentations/package-lock.json') == ''
working-directory: Documentations
run: npm install

- name: Install locked docs dependencies
if: hashFiles('Documentations/package-lock.json') != ''
working-directory: Documentations
run: npm ci

- name: Build docs
if: hashFiles('Documentations/package.json') != ''
working-directory: Documentations
run: npm run docs:build

# ── Pack ────────────────────────────────────────────────────────────────────
- name: Pack (nupkg + snupkg)
run: |
Expand All @@ -120,6 +166,10 @@ jobs:
-p:IncludeSymbols=true \
-p:SymbolPackageFormat=snupkg

- name: Validate package contents
shell: pwsh
run: ./eng/ValidatePackage.ps1 -Configuration Release -OutputDirectory artifacts/package-validation

- name: List packages
run: ls -la ./packages/

Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ on:
schedule:
- cron: '0 4 * * 1' # Monday 04:00 UTC

permissions:
contents: read

concurrency:
group: security-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
Expand Down
1 change: 1 addition & 0 deletions Analyzer/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
CAERIUS003 | CaeriusNet.Generator | Error | Type must declare a primary constructor with parameters.
CAERIUS004 | CaeriusNet.Generator | Error | [GenerateTvp] requires a non-empty TvpName.
CAERIUS005 | CaeriusNet.Generator | Warning | Parameter type falls back to sql_variant.
CAERIUS006 | CaeriusNet.Generator | Error | Generator target must be a non-generic top-level type.
12 changes: 11 additions & 1 deletion Analyzer/Diagnostics/DiagnosticDescriptors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,14 @@ internal static class DiagnosticDescriptors
true,
"Falling back to sql_variant works but carries performance, indexing, and type-safety penalties.",
HelpLinkBase + "CAERIUS005.md");
}

internal static readonly DiagnosticDescriptor UnsupportedGeneratorTarget = new(
"CAERIUS006",
"Generator target shape is not supported",
"'{0}' is decorated with '{1}' but generator targets must be non-generic top-level types",
Category,
DiagnosticSeverity.Error,
true,
"CaeriusNet generators emit companion partial declarations and currently support only non-generic top-level types.",
HelpLinkBase + "CAERIUS006.md");

Copilot AI Apr 28, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CAERIUS006 is added, but its HelpLink points to Documentations/diagnostics/CAERIUS006.md, which is not present alongside CAERIUS001–CAERIUS005. Add the missing CAERIUS006 documentation page (or adjust the HelpLinkBase/filename) so IDE links don’t 404.

Suggested change
HelpLinkBase + "CAERIUS006.md");
null);

Copilot uses AI. Check for mistakes.
}
36 changes: 14 additions & 22 deletions Analyzer/GeneratorUsageAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ public sealed class GeneratorUsageAnalyzer : DiagnosticAnalyzer
DiagnosticDescriptors.MustBePartial,
DiagnosticDescriptors.MustHavePrimaryConstructor,
DiagnosticDescriptors.TvpNameMustNotBeEmpty,
DiagnosticDescriptors.UnsupportedSqlMapping
DiagnosticDescriptors.UnsupportedSqlMapping,
DiagnosticDescriptors.UnsupportedGeneratorTarget
];

public override void Initialize(AnalysisContext context)
Expand Down Expand Up @@ -44,7 +45,6 @@ private static void AnalyzeTypeDeclaration(
{
if (context.Node is not TypeDeclarationSyntax declaration ||
declaration.AttributeLists.Count == 0 ||
!MayContainRelevantAttribute(declaration) ||
context.SemanticModel.GetDeclaredSymbol(declaration, context.CancellationToken) is not { } typeSymbol)
return;

Expand Down Expand Up @@ -155,6 +155,17 @@ private static bool ReportStructureDiagnostics(
hasFatal = true;
}

if (!validation.IsTopLevel || !validation.IsNonGeneric)
{
context.ReportDiagnostic(
Diagnostic.Create(
DiagnosticDescriptors.UnsupportedGeneratorTarget,
location,
typeSymbol.Name,
attributeDisplayName));
hasFatal = true;
}

if (validation.PrimaryConstructorDeclaration is not null) return hasFatal;
context.ReportDiagnostic(
Diagnostic.Create(
Expand Down Expand Up @@ -207,25 +218,6 @@ private static void ReportUnsupportedSqlMappings(
}
}

private static bool MayContainRelevantAttribute(TypeDeclarationSyntax declaration)
{
foreach (var attribute in declaration.AttributeLists.SelectMany(static list => list.Attributes))
{
var name = attribute.Name switch
{
IdentifierNameSyntax identifierName => identifierName.Identifier.ValueText,
QualifiedNameSyntax qualifiedName => qualifiedName.Right.Identifier.ValueText,
AliasQualifiedNameSyntax aliasQualifiedName => aliasQualifiedName.Name.Identifier.ValueText,
_ => attribute.Name.ToString()
};

if (name is "GenerateDto" or "GenerateDtoAttribute" or "GenerateTvp" or "GenerateTvpAttribute")
return true;
}

return false;
}

private static AttributeData? TryGetDirectAttribute(INamedTypeSymbol typeSymbol, INamedTypeSymbol attributeSymbol)
{
foreach (var attribute in typeSymbol.GetAttributes())
Expand Down Expand Up @@ -255,4 +247,4 @@ private static bool TryGetNamedArgumentSyntax(
argumentSyntax = null!;
return false;
}
}
}
2 changes: 1 addition & 1 deletion Analyzer/GlobalUsings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
global using Microsoft.CodeAnalysis;
global using Microsoft.CodeAnalysis.CSharp;
global using Microsoft.CodeAnalysis.CSharp.Syntax;
global using Microsoft.CodeAnalysis.Diagnostics;
global using Microsoft.CodeAnalysis.Diagnostics;
2 changes: 1 addition & 1 deletion Analyzer/Helpers/SqlTypeDetector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ internal static string GetSqlType(ITypeSymbol type)
}
};
}
}
}
Loading
Loading