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
47 changes: 47 additions & 0 deletions .github/workflows/dead-code.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Dead Code

on:
pull_request:
branches: [ main, v3-beta ]
paths:
- 'Packages/src/**/*.cs'
- 'tools/UnityCliLoop.DeadCodeScanner/**'
- 'tests/UnityCliLoop.DeadCodeScanner.Tests/**'
- 'scripts/check-dead-code.sh'
- '.github/workflows/dead-code.yml'
workflow_dispatch:

permissions:
contents: read

jobs:
dead-code:
name: Dead Code Gate
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
Comment thread
coderabbitai[bot] marked this conversation as resolved.
with:
persist-credentials: false

- name: Setup .NET
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7
with:
dotnet-version: 10.0.x

- name: Test dead code scanner
run: dotnet test tests/UnityCliLoop.DeadCodeScanner.Tests/UnityCliLoop.DeadCodeScanner.Tests.csproj --configuration Release

- name: Scan for dead code
run: >
scripts/check-dead-code.sh
--root .
--scope public
--include-types true
--include-members true
--include-locals true
--include-test-only true
--include-kept false
--format table
--fail-on high-confidence
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,6 @@ internal static async Task<bool> RunForegroundSequenceAsync(
return true;
}

internal static async Task<bool> TryRunBackgroundSequenceAsync(
IDynamicCodeExecutionRuntime runtime,
bool yieldToForegroundRequests,
CancellationToken ct)
{
System.Diagnostics.Debug.Assert(runtime != null, "runtime must not be null");

// Why: background probes must match the foreground sequence so whichever path succeeds
// first marks the same execution shape as ready.
foreach (string warmupCode in ExecuteDynamicCodeReadinessProbe.CreateReturnStringProbeCodes())
{
DynamicCodeExecutionRequest request = CreateRequest(
warmupCode,
yieldToForegroundRequests);
(bool entered, ExecutionResult result) = await runtime.TryExecuteIfIdleAsync(request, ct).ConfigureAwait(false);
if (!entered || !result.Success)
{
return false;
}
}

return true;
}

private static DynamicCodeExecutionRequest CreateRequest(
string code,
bool yieldToForegroundRequests)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@ namespace io.github.hatayama.UnityCliLoop.FirstPartyTools
// otherwise one path can look ready while the user's first return-string execution is still cold.
public static class ExecuteDynamicCodeReadinessProbe
{
public static string CreatePrimaryReturnStringProbeCode()
{
return DynamicCodeForegroundWarmupSnippets.ReturnStringShapes[0];
}

public static string[] CreateReturnStringProbeCodes()
{
string[] source = DynamicCodeForegroundWarmupSnippets.ReturnStringShapes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,5 @@ public static void ResetServerScopedServicesBeforeDomainReload()
{
ExecuteDynamicCodeEditorStartup.ResetServerScopedServicesBeforeDomainReload();
}

public static string CreateExecuteDynamicCodeReadinessProbeCode()
{
// Why: composition root can only depend on the bundled-tool facade assembly,
// so the dynamic-code assembly keeps ownership of the actual probe source shape.
return ExecuteDynamicCodeReadinessProbe.CreatePrimaryReturnStringProbeCode();
}
}
}
12 changes: 12 additions & 0 deletions docs/dead-code-scanner.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ For a broader member/local-variable pass, run:
dotnet run --project tools/UnityCliLoop.DeadCodeScanner -- --scope public --include-types true --include-members true --include-locals true --include-test-only true --include-kept false --format table
```

## CI gate

`.github/workflows/dead-code.yml` runs automatically on pull requests that
target `main` or `v3-beta` and touch `Packages/src/**/*.cs`, the scanner
itself, its tests, `scripts/check-dead-code.sh`, or the workflow file.

The gate uses `--fail-on high-confidence`, so CI fails only for
`Unused`, `UnusedPrivateMember`, and `UnusedLocal`.

`PublicCandidate` and `TestOnly` do not fail CI. Those findings need
manual review of non-C# references and cannot be decided mechanically.

## Interpreting the output

Interpret scanner output conservatively:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using NUnit.Framework;
using UnityCliLoop.DeadCodeScanner;

namespace UnityCliLoop.DeadCodeScanner.Tests
{
[TestFixture]
public sealed class CommandLineOptionsTests
{
// Verifies that --fail-on high-confidence enables the CI fail flag and --fail-on none leaves it off.
[Test]
public void Parse_WhenFailOnIsHighConfidenceOrNone_ShouldSetFailOnHighConfidenceFlag()
{
ScanOptions highConfidenceOptions = CommandLineOptions.Parse(new[]
{
"--fail-on",
"high-confidence"
});
ScanOptions noneOptions = CommandLineOptions.Parse(new[]
{
"--fail-on",
"none"
});

Assert.That(highConfidenceOptions.FailOnHighConfidence, Is.True);
Assert.That(noneOptions.FailOnHighConfidence, Is.False);
}
}
}
13 changes: 13 additions & 0 deletions tests/UnityCliLoop.DeadCodeScanner.Tests/DeadCodeScannerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,19 @@ public async Task ScanAsync_WhenProductionSymbolIsOnlyUsedByAssets_ShouldReportT
&& issue.FullName.Contains("TestOnlyFactory", StringComparison.Ordinal)), Is.True);
}

// Verifies that default-scope unused private findings are treated as high-confidence deletion candidates for the CI gate.
[Test]
public async Task ScanAsync_WhenUsingDefaultPrivateScope_ShouldReportHighConfidenceDeletionCandidates()
{
DeadCodeScanner scanner = new();
ScanOptions options = ScanOptions.Default(_rootPath);

System.Collections.Generic.IReadOnlyList<DeadCodeIssue> issues =
await scanner.ScanAsync(options, CancellationToken.None);

Assert.That(issues.Any(issue => issue.IsHighConfidenceDeletionCandidate()), Is.True);
}

// Verifies that Unity or reflection entry points can be reported separately when requested.
[Test]
public async Task ScanAsync_WhenIncludingKeptSymbols_ShouldReportUnityToolAsKept()
Expand Down
Loading