Skip to content

Commit 7cf3ae5

Browse files
feat(generator): allow inheritance over [NinoType]
merged #158
2 parents b38bf17 + 4fc1433 commit 7cf3ae5

File tree

9 files changed

+440
-118
lines changed

9 files changed

+440
-118
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -164,55 +164,18 @@ jobs:
164164
echo "Running benchmarks for PR #${{ github.event.pull_request.number }}..."
165165
dotnet run -c Release --no-build
166166
167-
# Verify benchmark results exist
168-
if [[ -f "BenchmarkDotNet.Artifacts/results/Nino.Benchmark.SimpleTest-report-github.md" ]]; then
169-
echo "✅ Benchmark completed successfully"
167+
# Verify benchmark results exist (check for any github markdown file)
168+
MARKDOWN_COUNT=$(find BenchmarkDotNet.Artifacts/results -name "*-report-github.md" -type f 2>/dev/null | wc -l)
169+
if [[ $MARKDOWN_COUNT -gt 0 ]]; then
170+
echo "✅ Benchmark completed successfully - found $MARKDOWN_COUNT report file(s)"
171+
find BenchmarkDotNet.Artifacts/results -name "*-report-github.md" -type f
170172
else
171173
echo "❌ Benchmark results not found"
172174
echo "Looking for markdown files in results directory..."
173175
find BenchmarkDotNet.Artifacts -name "*.md" -type f || echo "No markdown files found"
174176
exit 1
175177
fi
176178
177-
- name: Post benchmark results to PR
178-
env:
179-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
180-
run: |
181-
set -euo pipefail
182-
183-
# Find the generated markdown file
184-
echo "🔍 Looking for benchmark markdown files..."
185-
BENCHMARK_FILE=$(find Nino.Benchmark -path "*/BenchmarkDotNet.Artifacts/results/*.md" -type f | head -n1)
186-
187-
if [[ -z "$BENCHMARK_FILE" || ! -f "$BENCHMARK_FILE" ]]; then
188-
echo "❌ No markdown benchmark report found"
189-
exit 1
190-
fi
191-
192-
echo "✅ Found benchmark report: $BENCHMARK_FILE"
193-
194-
# Read benchmark content
195-
PERF_CONTENT=$(cat "$BENCHMARK_FILE")
196-
197-
# Create PR comment with benchmark results
198-
{
199-
echo "## 📊 Benchmark Results"
200-
echo ""
201-
echo "<details>"
202-
echo "<summary>Click to expand benchmark results</summary>"
203-
echo ""
204-
echo "$PERF_CONTENT"
205-
echo ""
206-
echo "</details>"
207-
echo ""
208-
echo "---"
209-
echo "*Benchmark generated automatically on $(date -u '+%Y-%m-%d %H:%M:%S UTC')*"
210-
} > benchmark_comment.md
211-
212-
# Post comment to PR
213-
gh pr comment ${{ github.event.pull_request.number }} --body-file benchmark_comment.md
214-
echo "✅ Benchmark results posted to PR #${{ github.event.pull_request.number }}"
215-
216179
- name: Upload benchmark results
217180
uses: actions/upload-artifact@v4
218181
if: always()
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
name: Claude Code Review - Secure Analysis
2+
3+
on:
4+
workflow_run:
5+
workflows: ["Claude Code Review - Collect PR Data"]
6+
types: [completed]
7+
8+
jobs:
9+
secure-review:
10+
if: ${{ github.event.workflow_run.conclusion == 'success' }}
11+
runs-on: ubuntu-latest
12+
permissions:
13+
contents: read
14+
pull-requests: write
15+
issues: read
16+
actions: read
17+
18+
steps:
19+
- name: Download and extract PR artifacts
20+
id: download
21+
run: |
22+
# Get artifacts from the workflow run
23+
ARTIFACTS=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}/artifacts)
24+
ARTIFACT_ID=$(echo "$ARTIFACTS" | jq -r '.artifacts[0].id')
25+
ARTIFACT_NAME=$(echo "$ARTIFACTS" | jq -r '.artifacts[0].name')
26+
27+
echo "Downloading artifact: $ARTIFACT_NAME (ID: $ARTIFACT_ID)"
28+
29+
# Download the artifact
30+
gh api repos/${{ github.repository }}/actions/artifacts/$ARTIFACT_ID/zip > artifact.zip
31+
32+
# Extract it
33+
unzip -q artifact.zip
34+
35+
# Read PR info
36+
if [ -f pr-info.txt ]; then
37+
echo "=== PR Info ==="
38+
cat pr-info.txt
39+
PR_NUM=$(grep "PR Number:" pr-info.txt | cut -d' ' -f3)
40+
echo "pr_number=$PR_NUM" >> $GITHUB_OUTPUT
41+
echo "Found PR number: $PR_NUM"
42+
else
43+
echo "ERROR: pr-info.txt not found"
44+
exit 1
45+
fi
46+
47+
# Check diff file
48+
if [ -f pr.diff ]; then
49+
echo "Diff file size: $(wc -l < pr.diff) lines"
50+
else
51+
echo "ERROR: pr.diff not found"
52+
exit 1
53+
fi
54+
env:
55+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
56+
57+
- name: Checkout repository
58+
uses: actions/checkout@v4
59+
with:
60+
fetch-depth: 1
61+
62+
- name: Run Claude Code Review
63+
id: claude-review
64+
uses: anthropics/claude-code-action@v1
65+
with:
66+
github_token: ${{ secrets.GITHUB_TOKEN }}
67+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
68+
prompt: |
69+
REPO: ${{ github.repository }}
70+
PR NUMBER: ${{ steps.download.outputs.pr_number }}
71+
72+
A PR diff file has been provided at: pr.diff
73+
74+
Please review this pull request diff and provide feedback on:
75+
- Code quality and best practices
76+
- Potential bugs or issues
77+
- Performance considerations
78+
- Security concerns
79+
- Test coverage
80+
81+
Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.
82+
83+
IMPORTANT: Do not execute any code from the PR. Only analyze the diff file.
84+
Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.
85+
86+
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
87+
# or https://docs.claude.com/en/docs/claude-code/cli-reference for available options
88+
claude_args: '--allowed-tools "Bash(gh pr comment:*),Bash(gh pr view:*),Bash(cat:pr.diff),Bash(cat:pr-info.txt)"'
89+
90+
- name: Check for benchmark results
91+
id: benchmark
92+
continue-on-error: true
93+
run: |
94+
PR_NUM="${{ steps.download.outputs.pr_number }}"
95+
96+
# Look for benchmark artifacts from the CI workflow
97+
echo "Looking for benchmark artifacts for PR #$PR_NUM..."
98+
99+
# Get all workflow runs for this PR
100+
RUNS=$(gh api "repos/${{ github.repository }}/actions/runs?event=pull_request&status=completed" \
101+
--jq ".workflow_runs[] | select(.head_sha == \"${{ github.event.workflow_run.head_sha }}\") | .id")
102+
103+
BENCHMARK_FOUND=false
104+
for RUN_ID in $RUNS; do
105+
echo "Checking run $RUN_ID for benchmark artifacts..."
106+
107+
# Check if this run has benchmark artifacts
108+
ARTIFACTS=$(gh api "repos/${{ github.repository }}/actions/runs/$RUN_ID/artifacts" \
109+
--jq ".artifacts[] | select(.name | startswith(\"benchmark-results-pr-\"))")
110+
111+
if [ -n "$ARTIFACTS" ]; then
112+
echo "Found benchmark artifacts in run $RUN_ID"
113+
ARTIFACT_ID=$(echo "$ARTIFACTS" | jq -r '.id' | head -1)
114+
115+
# Download benchmark artifact
116+
gh api "repos/${{ github.repository }}/actions/artifacts/$ARTIFACT_ID/zip" > benchmark.zip
117+
unzip -q benchmark.zip -d benchmark-results
118+
119+
BENCHMARK_FOUND=true
120+
break
121+
fi
122+
done
123+
124+
if [ "$BENCHMARK_FOUND" = true ]; then
125+
echo "has_benchmark=true" >> $GITHUB_OUTPUT
126+
echo "✅ Benchmark results found and downloaded"
127+
else
128+
echo "has_benchmark=false" >> $GITHUB_OUTPUT
129+
echo "ℹ️ No benchmark results found for this PR"
130+
fi
131+
env:
132+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
133+
134+
- name: Post benchmark results to PR
135+
if: steps.benchmark.outputs.has_benchmark == 'true'
136+
run: |
137+
PR_NUM="${{ steps.download.outputs.pr_number }}"
138+
139+
# Find the benchmark markdown file
140+
BENCHMARK_FILE=$(find benchmark-results -name "*-report-github.md" -type f | head -1)
141+
142+
if [ -n "$BENCHMARK_FILE" ] && [ -f "$BENCHMARK_FILE" ]; then
143+
echo "Found benchmark report: $BENCHMARK_FILE"
144+
145+
# Create formatted comment
146+
{
147+
echo "## 📊 Benchmark Results"
148+
echo ""
149+
echo "<details>"
150+
echo "<summary>Click to expand benchmark results</summary>"
151+
echo ""
152+
cat "$BENCHMARK_FILE"
153+
echo ""
154+
echo "</details>"
155+
echo ""
156+
echo "---"
157+
echo "*Benchmark generated automatically on $(date -u '+%Y-%m-%d %H:%M:%S UTC')*"
158+
} > benchmark_comment.md
159+
160+
# Post to PR
161+
gh pr comment "$PR_NUM" --body-file benchmark_comment.md
162+
echo "✅ Benchmark results posted to PR #$PR_NUM"
163+
else
164+
echo "⚠️ Benchmark markdown file not found in downloaded artifacts"
165+
fi
166+
env:
167+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Lines changed: 21 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,36 @@
1-
name: Claude Code Review
1+
name: Claude Code Review - Collect PR Data
22

33
on:
44
pull_request:
5-
types: [opened, synchronize]
6-
# Optional: Only run on specific file changes
7-
# paths:
8-
# - "src/**/*.ts"
9-
# - "src/**/*.tsx"
10-
# - "src/**/*.js"
11-
# - "src/**/*.jsx"
5+
types: [opened, synchronize, reopened]
126

137
jobs:
14-
claude-review:
15-
# Optional: Filter by PR author
16-
# if: |
17-
# github.event.pull_request.user.login == 'external-contributor' ||
18-
# github.event.pull_request.user.login == 'new-developer' ||
19-
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
20-
8+
collect-pr-data:
219
runs-on: ubuntu-latest
2210
permissions:
2311
contents: read
2412
pull-requests: read
25-
issues: read
26-
id-token: write
2713

2814
steps:
2915
- name: Checkout repository
3016
uses: actions/checkout@v4
3117
with:
32-
fetch-depth: 1
33-
34-
- name: Run Claude Code Review
35-
id: claude-review
36-
uses: anthropics/claude-code-action@v1
18+
fetch-depth: 0
19+
20+
- name: Save PR diff
21+
run: |
22+
git diff -U3 origin/${{ github.base_ref }}... > pr.diff || true
23+
echo "PR Number: ${{ github.event.pull_request.number }}" > pr-info.txt
24+
echo "PR Title: ${{ github.event.pull_request.title }}" >> pr-info.txt
25+
echo "PR Author: ${{ github.event.pull_request.user.login }}" >> pr-info.txt
26+
echo "Base Branch: ${{ github.base_ref }}" >> pr-info.txt
27+
echo "Head Branch: ${{ github.head_ref }}" >> pr-info.txt
28+
29+
- name: Upload PR artifacts
30+
uses: actions/upload-artifact@v4
3731
with:
38-
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
39-
prompt: |
40-
REPO: ${{ github.repository }}
41-
PR NUMBER: ${{ github.event.pull_request.number }}
42-
43-
Please review this pull request and provide feedback on:
44-
- Code quality and best practices
45-
- Potential bugs or issues
46-
- Performance considerations
47-
- Security concerns
48-
- Test coverage
49-
50-
Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.
51-
52-
Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.
53-
54-
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
55-
# or https://docs.claude.com/en/docs/claude-code/cli-reference for available options
56-
claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'
57-
32+
name: pr-data-${{ github.event.pull_request.number }}
33+
path: |
34+
pr.diff
35+
pr-info.txt
36+
retention-days: 1

src/Nino.Core/NinoTypeAttribute.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace Nino.Core
1111
, Inherited = false)]
1212
public class NinoTypeAttribute : Attribute
1313
{
14-
public NinoTypeAttribute(bool autoCollect = true, bool containNonPublicMembers = false)
14+
public NinoTypeAttribute(bool autoCollect = true, bool containNonPublicMembers = false, bool allowInheritance = true)
1515
{
1616
}
1717
}

src/Nino.Generator/GlobalGenerator.cs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.CSharp;
56
using Microsoft.CodeAnalysis.CSharp.Syntax;
67
using Nino.Generator.BuiltInType;
78
using Nino.Generator.Common;
@@ -16,10 +17,20 @@ public class GlobalGenerator : IIncrementalGenerator
1617
{
1718
public void Initialize(IncrementalGeneratorInitializationContext context)
1819
{
20+
// Scan types directly marked with [NinoType]
1921
var ninoTypeModels = context.GetTypeSyntaxes()
2022
.Where(static syntax => syntax != null)
2123
.Collect();
2224

25+
// Scan all type declarations (including those that may inherit from NinoType)
26+
var allTypeDeclarations = context.SyntaxProvider
27+
.CreateSyntaxProvider<CSharpSyntaxNode>(
28+
static (node, _) => node is TypeDeclarationSyntax,
29+
static (context, _) => (CSharpSyntaxNode)context.Node
30+
)
31+
.Where(static syntax => syntax != null)
32+
.Collect();
33+
2334
var typeDeclarations = context.SyntaxProvider
2435
.CreateSyntaxProvider(
2536
static (node, _) =>
@@ -32,14 +43,16 @@ node is GenericNameSyntax or ArrayTypeSyntax
3243
.Collect();
3344

3445
var merged = context.CompilationProvider.Combine(typeDeclarations)
35-
.Combine(ninoTypeModels);
46+
.Combine(ninoTypeModels)
47+
.Combine(allTypeDeclarations);
3648

3749
// Add explicit caching and error boundaries
3850
context.RegisterSourceOutput(merged, static (spc, source) =>
3951
{
40-
var compilation = source.Left.Left;
41-
var typeSyntaxes = source.Left.Right;
42-
var ninoTypeSyntaxes = source.Right;
52+
var compilation = source.Left.Left.Left;
53+
var typeSyntaxes = source.Left.Left.Right;
54+
var ninoTypeSyntaxes = source.Left.Right;
55+
var allTypeDeclarations = source.Right;
4356

4457
// Add stability check
4558
if (compilation == null) return;
@@ -122,6 +135,20 @@ node is GenericNameSyntax or ArrayTypeSyntax
122135
}
123136
}
124137

138+
// process all type declarations (including those inheriting from NinoType)
139+
foreach (var typeSyntax in allTypeDeclarations)
140+
{
141+
var typeSymbol = typeSyntax.GetTypeSymbol(compilation);
142+
if (typeSymbol != null
143+
&& typeSymbol.DeclaredAccessibility == Accessibility.Public
144+
&& typeSymbol.CheckGenericValidity()
145+
&& typeSymbol.IsNinoType()) // IsNinoType now checks inheritance
146+
{
147+
var type = typeSymbol.GetNormalizedTypeSymbol().GetPureType();
148+
allTypes.Add(type);
149+
}
150+
}
151+
125152
// parametrized nino types + concrete nino types
126153
HashSet<ITypeSymbol> ninoTypeSymbols = new(TupleSanitizedEqualityComparer.Default);
127154
// all recognizable potential types that might be serialized/deserialized

0 commit comments

Comments
 (0)