init
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0add-migration.ps1" %*
|
||||
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0add-migration.ps1" %*
|
||||
@@ -0,0 +1,92 @@
|
||||
param(
|
||||
[string]$Name,
|
||||
[ValidateSet("SQLite", "SqlServer", "PostgreSQL", "MySQL", "All")]
|
||||
[string]$Provider = "All",
|
||||
[string]$Project = "Avalonia-EFCore/Avalonia-EFCore.csproj",
|
||||
[string]$StartupProject = "Avalonia-API/Avalonia-API.csproj",
|
||||
[string]$OutputDir = "Migrations"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
|
||||
Set-Location $repoRoot
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Name)) {
|
||||
$Name = "AutoMigration_{0}" -f (Get-Date -Format "yyyyMMddHHmmss")
|
||||
}
|
||||
|
||||
Write-Host "Restoring local dotnet tools..."
|
||||
dotnet tool restore
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "dotnet tool restore failed."
|
||||
}
|
||||
|
||||
function Get-ContextName([string]$providerName) {
|
||||
switch ($providerName) {
|
||||
"SQLite" { return "SqliteAppDataContext" }
|
||||
"SqlServer" { return "SqlServerAppDataContext" }
|
||||
"PostgreSQL" { return "PostgreSqlAppDataContext" }
|
||||
"MySQL" { return "MySqlAppDataContext" }
|
||||
default { throw "Unsupported provider '$providerName'." }
|
||||
}
|
||||
}
|
||||
|
||||
function Add-ProviderMigration([string]$providerName) {
|
||||
$context = Get-ContextName $providerName
|
||||
$providerOutputDir = Join-Path $OutputDir $providerName
|
||||
|
||||
Write-Host "Generating migration '$Name' for $providerName..."
|
||||
dotnet tool run dotnet-ef migrations add $Name `
|
||||
--project $Project `
|
||||
--startup-project $StartupProject `
|
||||
--context $context `
|
||||
--output-dir $providerOutputDir
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "dotnet ef migrations add failed for $providerName."
|
||||
}
|
||||
|
||||
$migrationDir = Join-Path (Split-Path $Project -Parent) $providerOutputDir
|
||||
$migrationFile = Get-ChildItem $migrationDir -Filter "*_$Name.cs" |
|
||||
Where-Object { $_.Name -notlike "*.Designer.cs" } |
|
||||
Sort-Object LastWriteTime -Descending |
|
||||
Select-Object -First 1
|
||||
|
||||
if ($null -eq $migrationFile) {
|
||||
throw "Migration file was not found for '$Name' ($providerName)."
|
||||
}
|
||||
|
||||
$content = Get-Content $migrationFile.FullName -Raw
|
||||
$upMatch = [regex]::Match($content, "protected override void Up\(MigrationBuilder migrationBuilder\)\s*\{(?<body>.*?)\n\s*\}", "Singleline")
|
||||
$downMatch = [regex]::Match($content, "protected override void Down\(MigrationBuilder migrationBuilder\)\s*\{(?<body>.*?)\n\s*\}", "Singleline")
|
||||
|
||||
$upBody = if ($upMatch.Success) { $upMatch.Groups["body"].Value.Trim() } else { "" }
|
||||
$downBody = if ($downMatch.Success) { $downMatch.Groups["body"].Value.Trim() } else { "" }
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($upBody) -and [string]::IsNullOrWhiteSpace($downBody)) {
|
||||
Write-Host "No model changes were detected for $providerName. Removing empty migration '$Name'..."
|
||||
dotnet tool run dotnet-ef migrations remove --force `
|
||||
--project $Project `
|
||||
--startup-project $StartupProject `
|
||||
--context $context
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "dotnet ef migrations remove failed for $providerName."
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "Migration generated for ${providerName}:"
|
||||
Write-Host " $($migrationFile.FullName)"
|
||||
}
|
||||
|
||||
$providers = if ($Provider -eq "All") {
|
||||
@("SQLite", "SqlServer", "PostgreSQL", "MySQL")
|
||||
} else {
|
||||
@($Provider)
|
||||
}
|
||||
|
||||
foreach ($providerName in $providers) {
|
||||
Add-ProviderMigration $providerName
|
||||
}
|
||||
|
||||
Write-Host "Review the migration files, then start the app. Startup will apply the migration set matching DatabaseConfiguration.Provider."
|
||||
@@ -0,0 +1,6 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
set SCRIPT_DIR=%~dp0
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%find-missing-csharp-docs.ps1" %*
|
||||
exit /b %ERRORLEVEL%
|
||||
@@ -0,0 +1,358 @@
|
||||
param(
|
||||
[string]$Path = ".",
|
||||
[string]$OutputPath = "scripts/missing-csharp-docs.txt",
|
||||
[switch]$IncludeMigrations,
|
||||
[switch]$IncludeGenerated,
|
||||
[switch]$Json
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
|
||||
$scanRoot = Resolve-Path (Join-Path $repoRoot $Path)
|
||||
|
||||
$excludedDirectories = @(
|
||||
"\bin\",
|
||||
"\obj\",
|
||||
"\.git\",
|
||||
"\.vs\",
|
||||
"\node_modules\",
|
||||
"\dist\",
|
||||
"\logs\"
|
||||
)
|
||||
|
||||
if (-not $IncludeMigrations) {
|
||||
$excludedDirectories += "\Migrations\"
|
||||
}
|
||||
|
||||
$memberRegexes = @(
|
||||
@{
|
||||
Kind = "Type"
|
||||
Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:(?:public|private|protected|internal|static|abstract|sealed|partial|readonly|unsafe|file)\s+)*(?:class|interface|struct|enum|record(?:\s+(?:class|struct))?)\s+[A-Za-z_][A-Za-z0-9_]*'
|
||||
},
|
||||
@{
|
||||
Kind = "Delegate"
|
||||
Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:(?:public|private|protected|internal|static|virtual|abstract|sealed|override|new|unsafe|partial)\s+)*delegate\s+'
|
||||
},
|
||||
@{
|
||||
Kind = "Event"
|
||||
Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:(?:public|private|protected|internal|static|virtual|abstract|sealed|override|new|unsafe)\s+)*event\s+'
|
||||
},
|
||||
@{
|
||||
Kind = "Property"
|
||||
Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:(?:public|private|protected|internal|static|virtual|abstract|sealed|override|new|readonly|required|unsafe)\s+)+(?:[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s\(\)]*|\([^\)]*\)\??)\s+[A-Za-z_][A-Za-z0-9_]*\s*\{\s*(?:get|set|init)\b'
|
||||
},
|
||||
@{
|
||||
Kind = "InterfaceProperty"
|
||||
Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s\(\)]*|\([^\)]*\)\??)\s+[A-Za-z_][A-Za-z0-9_]*\s*\{\s*(?:get|set|init)\b'
|
||||
},
|
||||
@{
|
||||
Kind = "Constructor"
|
||||
Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:(?:public|private|protected|internal|static|unsafe)\s+)+[A-Za-z_][A-Za-z0-9_]*\s*\('
|
||||
},
|
||||
@{
|
||||
Kind = "Method"
|
||||
Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:(?:public|private|protected|internal|static|virtual|abstract|sealed|override|async|extern|new|unsafe|partial)\s+)+(?:[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s\(\)]*|\([^\)]*\)\??)\s+(?:operator\s*[^\s\(]+|[A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>]+>)?\s*\('
|
||||
},
|
||||
@{
|
||||
Kind = "InterfaceMethod"
|
||||
Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s\(\)]*|\([^\)]*\)\??)\s+[A-Za-z_][A-Za-z0-9_]*(?:<[^>]+>)?\s*\([^;{}]*\)\s*;'
|
||||
},
|
||||
@{
|
||||
Kind = "Field"
|
||||
Pattern = '^\s*(?:\[(?:[^\]]+)\]\s*)*(?:(?:public|private|protected|internal|static|readonly|const|volatile|new|unsafe)\s+)+(?:[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s\(\)]*|\([^\)]*\)\??)\s+[A-Za-z_][A-Za-z0-9_]*(?:\s*=\s*[^;]+)?\s*;'
|
||||
}
|
||||
)
|
||||
|
||||
function Test-IsExcludedFile {
|
||||
param([System.IO.FileInfo]$File)
|
||||
|
||||
$fullName = $File.FullName
|
||||
foreach ($directory in $excludedDirectories) {
|
||||
if ($fullName.Contains($directory)) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $IncludeGenerated) {
|
||||
if ($File.Name -like "*.g.cs" -or
|
||||
$File.Name -like "*.g.i.cs" -or
|
||||
$File.Name -like "*.Designer.cs" -or
|
||||
$File.Name -like "*.AssemblyInfo.cs") {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
function Get-RelativePath {
|
||||
param(
|
||||
[string]$BasePath,
|
||||
[string]$TargetPath
|
||||
)
|
||||
|
||||
$baseFullPath = [System.IO.Path]::GetFullPath($BasePath)
|
||||
if (-not $baseFullPath.EndsWith([System.IO.Path]::DirectorySeparatorChar)) {
|
||||
$baseFullPath += [System.IO.Path]::DirectorySeparatorChar
|
||||
}
|
||||
|
||||
$targetFullPath = [System.IO.Path]::GetFullPath($TargetPath)
|
||||
$baseUri = New-Object System.Uri($baseFullPath)
|
||||
$targetUri = New-Object System.Uri($targetFullPath)
|
||||
$relativeUri = $baseUri.MakeRelativeUri($targetUri)
|
||||
return [System.Uri]::UnescapeDataString($relativeUri.ToString()).Replace("/", [System.IO.Path]::DirectorySeparatorChar)
|
||||
}
|
||||
|
||||
function Remove-LineNoise {
|
||||
param([string]$Line)
|
||||
|
||||
$lineWithoutStrings = [regex]::Replace($Line, '@?"(?:[^"\\]|\\.|"")*"', '""')
|
||||
return [regex]::Replace($lineWithoutStrings, '//.*$', '')
|
||||
}
|
||||
|
||||
function Get-PreviousCodeLineIndex {
|
||||
param(
|
||||
[string[]]$Lines,
|
||||
[int]$StartIndex
|
||||
)
|
||||
|
||||
for ($i = $StartIndex; $i -ge 0; $i--) {
|
||||
$trimmed = $Lines[$i].Trim()
|
||||
if ([string]::IsNullOrWhiteSpace($trimmed)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if ($trimmed.StartsWith("[") -and $trimmed.EndsWith("]")) {
|
||||
continue
|
||||
}
|
||||
|
||||
return $i
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
function Test-HasXmlDoc {
|
||||
param(
|
||||
[string[]]$Lines,
|
||||
[int]$DeclarationIndex
|
||||
)
|
||||
|
||||
$previousIndex = Get-PreviousCodeLineIndex -Lines $Lines -StartIndex ($DeclarationIndex - 1)
|
||||
return $previousIndex -ge 0 -and $Lines[$previousIndex].TrimStart().StartsWith("///")
|
||||
}
|
||||
|
||||
function Get-DeclarationText {
|
||||
param(
|
||||
[string[]]$Lines,
|
||||
[int]$StartIndex
|
||||
)
|
||||
|
||||
$parts = New-Object System.Collections.Generic.List[string]
|
||||
$maxIndex = [Math]::Min($Lines.Length - 1, $StartIndex + 8)
|
||||
|
||||
for ($i = $StartIndex; $i -le $maxIndex; $i++) {
|
||||
$clean = Remove-LineNoise $Lines[$i]
|
||||
if ([string]::IsNullOrWhiteSpace($clean)) {
|
||||
continue
|
||||
}
|
||||
|
||||
$parts.Add($clean.Trim())
|
||||
$joined = ($parts -join " ")
|
||||
if ($joined -match '[\{;\}=]\s*$' -or $joined.Contains("=>")) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return ($parts -join " ")
|
||||
}
|
||||
|
||||
function Test-IsInsideInterface {
|
||||
param(
|
||||
[string[]]$Lines,
|
||||
[int]$Index
|
||||
)
|
||||
|
||||
$scopeStack = New-Object System.Collections.Generic.List[string]
|
||||
$pendingInterface = $false
|
||||
|
||||
for ($i = 0; $i -lt $Index; $i++) {
|
||||
$line = Remove-LineNoise $Lines[$i]
|
||||
if ($line -match '\binterface\s+[A-Za-z_][A-Za-z0-9_]*') {
|
||||
$pendingInterface = $true
|
||||
}
|
||||
|
||||
foreach ($char in $line.ToCharArray()) {
|
||||
if ($char -eq "{") {
|
||||
if ($pendingInterface) {
|
||||
$scopeStack.Add("interface")
|
||||
$pendingInterface = $false
|
||||
} else {
|
||||
$scopeStack.Add("block")
|
||||
}
|
||||
} elseif ($char -eq "}") {
|
||||
if ($scopeStack.Count -gt 0) {
|
||||
$scopeStack.RemoveAt($scopeStack.Count - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $scopeStack.Contains("interface")
|
||||
}
|
||||
|
||||
function Get-MemberName {
|
||||
param(
|
||||
[string]$Kind,
|
||||
[string]$Declaration
|
||||
)
|
||||
|
||||
switch ($Kind) {
|
||||
"Type" {
|
||||
if ($Declaration -match '\b(?:class|interface|struct|enum|record(?:\s+(?:class|struct))?)\s+(?<name>[A-Za-z_][A-Za-z0-9_]*)') {
|
||||
return $Matches["name"]
|
||||
}
|
||||
}
|
||||
"Delegate" {
|
||||
if ($Declaration -match '\b(?<name>[A-Za-z_][A-Za-z0-9_]*)\s*\(') {
|
||||
return $Matches["name"]
|
||||
}
|
||||
}
|
||||
"Event" {
|
||||
if ($Declaration -match '\bevent\s+[A-Za-z_][A-Za-z0-9_<>,\[\]\?\.\s]*\s+(?<name>[A-Za-z_][A-Za-z0-9_]*)') {
|
||||
return $Matches["name"]
|
||||
}
|
||||
}
|
||||
"Constructor" {
|
||||
if ($Declaration -match '\b(?<name>[A-Za-z_][A-Za-z0-9_]*)\s*\(') {
|
||||
return $Matches["name"]
|
||||
}
|
||||
}
|
||||
"Method" {
|
||||
$matches = [regex]::Matches($Declaration, '\s(?<name>operator\s*[^\s\(]+|[A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>]+>)?\s*\(')
|
||||
if ($matches.Count -gt 0) {
|
||||
return $matches[$matches.Count - 1].Groups["name"].Value
|
||||
}
|
||||
}
|
||||
"InterfaceMethod" {
|
||||
$matches = [regex]::Matches($Declaration, '\s(?<name>[A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>]+>)?\s*\(')
|
||||
if ($matches.Count -gt 0) {
|
||||
return $matches[$matches.Count - 1].Groups["name"].Value
|
||||
}
|
||||
}
|
||||
default {
|
||||
if ($Declaration -match '\b(?<name>[A-Za-z_][A-Za-z0-9_]*)\s*(?:[=;\{])') {
|
||||
return $Matches["name"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
function Test-IsEnumMember {
|
||||
param(
|
||||
[string[]]$Lines,
|
||||
[int]$Index
|
||||
)
|
||||
|
||||
$line = Remove-LineNoise $Lines[$Index]
|
||||
if ($line -notmatch '^\s*[A-Za-z_][A-Za-z0-9_]*(?:\s*=\s*[^,]+)?\s*,?\s*$') {
|
||||
return $false
|
||||
}
|
||||
|
||||
for ($i = $Index - 1; $i -ge 0; $i--) {
|
||||
$previous = Remove-LineNoise $Lines[$i]
|
||||
if ($previous -match '\benum\s+[A-Za-z_][A-Za-z0-9_]*') {
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($previous.Contains("{") -or $previous.Contains("}")) {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
$files = Get-ChildItem -Path $scanRoot -Recurse -File -Filter "*.cs" |
|
||||
Where-Object { -not (Test-IsExcludedFile $_) } |
|
||||
Sort-Object FullName
|
||||
|
||||
$results = New-Object System.Collections.Generic.List[object]
|
||||
|
||||
foreach ($file in $files) {
|
||||
$lines = Get-Content $file.FullName -Encoding UTF8
|
||||
$relativePath = Get-RelativePath -BasePath $repoRoot -TargetPath $file.FullName
|
||||
|
||||
for ($i = 0; $i -lt $lines.Length; $i++) {
|
||||
$line = $lines[$i]
|
||||
$trimmed = $line.Trim()
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($trimmed) -or
|
||||
$trimmed.StartsWith("///") -or
|
||||
$trimmed.StartsWith("//") -or
|
||||
$trimmed.StartsWith("#") -or
|
||||
$trimmed.StartsWith("[") -or
|
||||
$trimmed -in @("{", "}", "};")) {
|
||||
continue
|
||||
}
|
||||
|
||||
$declaration = Get-DeclarationText -Lines $lines -StartIndex $i
|
||||
$matchedKind = $null
|
||||
|
||||
foreach ($entry in $memberRegexes) {
|
||||
if ($declaration -cmatch $entry.Pattern) {
|
||||
if (($entry.Kind -eq "InterfaceMethod" -or $entry.Kind -eq "InterfaceProperty") -and
|
||||
-not (Test-IsInsideInterface -Lines $lines -Index $i)) {
|
||||
continue
|
||||
}
|
||||
|
||||
$matchedKind = $entry.Kind
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ($null -eq $matchedKind -and (Test-IsEnumMember -Lines $lines -Index $i)) {
|
||||
$matchedKind = "EnumMember"
|
||||
}
|
||||
|
||||
if ($null -eq $matchedKind) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (Test-HasXmlDoc -Lines $lines -DeclarationIndex $i) {
|
||||
continue
|
||||
}
|
||||
|
||||
$results.Add([pscustomobject]@{
|
||||
File = $relativePath
|
||||
Line = $i + 1
|
||||
Kind = $matchedKind
|
||||
Name = Get-MemberName -Kind $matchedKind -Declaration $declaration
|
||||
Declaration = $declaration
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if ($Json) {
|
||||
$output = $results | ConvertTo-Json -Depth 4
|
||||
} else {
|
||||
$output = $results | Format-Table File, Line, Kind, Name, Declaration -AutoSize | Out-String -Width 240
|
||||
}
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($OutputPath)) {
|
||||
$resolvedOutputPath = Join-Path $repoRoot $OutputPath
|
||||
$outputDirectory = Split-Path $resolvedOutputPath -Parent
|
||||
if (-not [string]::IsNullOrWhiteSpace($outputDirectory)) {
|
||||
New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null
|
||||
}
|
||||
|
||||
Set-Content -Path $resolvedOutputPath -Value $output -Encoding UTF8
|
||||
Write-Host "Missing XML documentation report written to $resolvedOutputPath"
|
||||
Write-Host "Total missing items: $($results.Count)"
|
||||
} else {
|
||||
$output
|
||||
Write-Host "Total missing items: $($results.Count)"
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
[
|
||||
{
|
||||
"File": "Avalonia-API\\Authentication\\JwtTokenService.cs",
|
||||
"Line": 20,
|
||||
"Kind": "Method",
|
||||
"Name": "CreateAccessToken",
|
||||
"Declaration": "public (string Token, DateTime ExpiresAt) CreateAccessToken(UserEntity user, IReadOnlyCollection\u003cstring\u003e roles) {"
|
||||
},
|
||||
{
|
||||
"File": "Avalonia-API\\Authentication\\RefreshTokenService.cs",
|
||||
"Line": 21,
|
||||
"Kind": "Method",
|
||||
"Name": "CreateAsync",
|
||||
"Declaration": "public async Task\u003c(string Token, ApiRefreshTokenEntity Entity)\u003e CreateAsync( int userId, string? device, string? ipAddress, CancellationToken cancellationToken = default) {"
|
||||
},
|
||||
{
|
||||
"File": "Avalonia-API\\Authentication\\RefreshTokenService.cs",
|
||||
"Line": 78,
|
||||
"Kind": "Method",
|
||||
"Name": "RotateAsync",
|
||||
"Declaration": "public async Task\u003c(string Token, ApiRefreshTokenEntity Entity)?\u003e RotateAsync( string? token, string? device, string? ipAddress, CancellationToken cancellationToken = default) {"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
你是一个资深 C# 工程师。现在我会给你一个 missing-csharp-docs.txt 文件,里面列出了项目中缺少 XML 文档注释的 C# 类型、方法、属性、字段、构造函数、接口成员等。
|
||||
|
||||
请根据这个 txt 文件逐项读取对应源码文件,并直接修改源码,为缺少注释的成员补全中文 XML 文档注释。
|
||||
|
||||
要求如下:
|
||||
|
||||
1. 注释必须是中文。
|
||||
2. 使用标准 C# XML 文档注释格式。
|
||||
3. 类、接口、record、struct、enum 使用:
|
||||
/// <summary>
|
||||
/// ...
|
||||
/// </summary>
|
||||
4. 方法、构造函数必须尽量补全:
|
||||
/// <summary>
|
||||
/// ...
|
||||
/// </summary>
|
||||
/// <param name="xxx">...</param>
|
||||
/// <returns>...</returns>
|
||||
/// <exception cref="...">...</exception>
|
||||
5. 如果方法没有参数,不要生成 <param>。
|
||||
6. 如果方法返回 void、Task 或构造函数,不要生成无意义的 <returns>。
|
||||
7. 如果方法返回 Task<T>、ValueTask<T>、T、IEnumerable<T> 等有实际返回值的类型,需要生成 <returns>,说明返回内容。
|
||||
8. 如果方法体中明确 throw 了异常,或声明逻辑明显可能抛出特定异常,可以补充 <exception>;不确定时不要乱写。
|
||||
9. 属性使用:
|
||||
/// <summary>
|
||||
/// 获取或设置...
|
||||
/// </summary>
|
||||
如果是只读属性,写“获取...”;如果是计算属性,说明它计算或表示的含义。
|
||||
10. 字段使用:
|
||||
/// <summary>
|
||||
/// 保存/定义/指示...
|
||||
/// </summary>
|
||||
11. 枚举成员也要加中文 summary,说明每个枚举值的含义。
|
||||
12. 接口方法必须在 interface 中写完整注释,包括 summary、param、returns。
|
||||
13. 具体实现类如果实现了已有注释的接口方法,优先使用:
|
||||
/// <inheritdoc />
|
||||
不要在实现类重复写一大段相同注释。
|
||||
14. 如果实现类方法不是接口实现,或者接口中没有对应注释,则在实现类中写完整注释。
|
||||
15. 不要只根据方法名机械生成注释,要结合方法体、参数、返回值、调用逻辑和业务语义来写。
|
||||
16. 不要改业务逻辑,不要改方法签名,不要改格式以外的代码。
|
||||
17. XML 注释放在 attribute 之前,例如:
|
||||
/// <summary>
|
||||
/// 用户 ID。
|
||||
/// </summary>
|
||||
[Column("user-id")]
|
||||
public int UserId { get; set; }
|
||||
18. 如果成员前已经有 XML 注释,不要重复添加。
|
||||
19. 如果 txt 中的行号因为代码变化不准确,要通过成员名称和声明内容定位实际源码位置。
|
||||
20. 修改完成后,重新运行已有的注释扫描脚本确认缺失项为 0。
|
||||
21. 最后运行相关 dotnet build 验证没有语法错误。
|
||||
22. 最后给我总结:修改了哪些文件、补了多少处注释、扫描结果、构建结果。
|
||||
|
||||
执行方式:
|
||||
- 直接读取 missing-csharp-docs.txt。
|
||||
- 按 txt 中列出的 File、Line、Kind、Name、Declaration 定位源码成员。
|
||||
- 逐文件修改。
|
||||
- 不要新建额外的注释生成脚本。
|
||||
- 不要生成新的工具脚本。
|
||||
- 可以使用现有脚本重新扫描验证。
|
||||
- 最终直接完成代码修改。
|
||||
Reference in New Issue
Block a user