Skip to content

Commit ca9a7e8

Browse files
authored
fix(core): core library audit fixes (#811)
2 parents edc5616 + 136cf42 commit ca9a7e8

56 files changed

Lines changed: 853 additions & 219 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.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
uid: vs_analyzer_dcs_1102
3+
title: Visual Studio Tools
4+
author: DisCatSharp Team
5+
---
6+
7+
# DisCatSharp Analyzer Rule DCS1102
8+
9+
This rule identifies calls to `BanAsync` or `BanMemberAsync` that pass the `deleteMessageDays` named argument,
10+
which no longer exists — the parameter was renamed to `deleteMessageSeconds` and now expects a value in seconds.
11+
12+
## What the code fix handles
13+
14+
When a named argument `deleteMessageDays:` is detected, the fixer:
15+
16+
- Renames the argument label from `deleteMessageDays:` to `deleteMessageSeconds:`
17+
- If the argument expression is a **numeric literal** (e.g. `7`), multiplies it by `86400` so the intent is preserved (`7` → `604800`)
18+
- If the argument expression is **not** a literal (e.g. a variable or expression), renames the label only and prepends a `/* TODO: was days, now seconds - multiply by 86400 if needed */` comment so callers know to review the value manually
19+
20+
Positional callers that relied on the old days-to-seconds conversion shim must be reviewed and updated manually — they are out of scope for this auto-fix.
21+
22+
## Why this rule exists
23+
24+
Discord's ban API has always accepted seconds, not days.
25+
A temporary compatibility shim in DisCatSharp converted small integer values (1–7) from days to seconds automatically,
26+
but that shim has now been removed along with the `deleteMessageDays` parameter name.
27+
28+
Callers using the named argument `deleteMessageDays:` will get a compile error after the upgrade;
29+
this diagnostic surfaces the issue early with a one-click fix.
30+
31+
The maximum accepted value is `604800` seconds (7 days). Values outside `0`–`604800` are rejected by the API.

‎DisCatSharp.Docs/vs/index.md‎

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ Use this as the quick orientation guide.
4343
These rules keep their historical IDs for compatibility.
4444

4545
| Rule ID | Category | Severity | Notes |
46-
| ------------------------------------ | -------- | -------- | -------------------------------------- |
46+
|--------------------------------------|----------|----------|----------------------------------------|
4747
| [DCS0001](xref:vs_analyzer_dcs_0001) | Usage | Info | Experimental Attribute Analyzer |
4848
| [DCS0002](xref:vs_analyzer_dcs_0002) | Usage | Error | Deprecated Attribute Analyzer |
4949
| [DCS0101](xref:vs_analyzer_dcs_0101) | Usage | Warning | DiscordInExperiment Attribute Analyzer |
@@ -54,26 +54,27 @@ These rules keep their historical IDs for compatibility.
5454

5555
##### Reserved families
5656

57-
| Family | Purpose |
58-
| ------ | ------- |
59-
| `DCS1XXX` | Core `DisCatSharp` diagnostics, migrations, and cross-cutting code fixes |
60-
| `DCS2XXX` | `DisCatSharp.ApplicationCommands` diagnostics and code-fix families |
61-
| `DCS3XXX` | `DisCatSharp.CommandsNext` diagnostics and code-fix families |
62-
| `DCS4XXX` | `DisCatSharp.Interactivity` diagnostics and code-fix families |
63-
| `DCS5XXX` | `DisCatSharp.Voice` diagnostics and code-fix families |
64-
| `DCS6XXX` | `DisCatSharp.Lavalink` diagnostics and code-fix families |
65-
| `DCS7XXX` | `DisCatSharp.Common` diagnostics and code-fix families |
57+
| Family | Purpose |
58+
|-----------|------------------------------------------------------------------------------------|
59+
| `DCS1XXX` | Core `DisCatSharp` diagnostics, migrations, and cross-cutting code fixes |
60+
| `DCS2XXX` | `DisCatSharp.ApplicationCommands` diagnostics and code-fix families |
61+
| `DCS3XXX` | `DisCatSharp.CommandsNext` diagnostics and code-fix families |
62+
| `DCS4XXX` | `DisCatSharp.Interactivity` diagnostics and code-fix families |
63+
| `DCS5XXX` | `DisCatSharp.Voice` diagnostics and code-fix families |
64+
| `DCS6XXX` | `DisCatSharp.Lavalink` diagnostics and code-fix families |
65+
| `DCS7XXX` | `DisCatSharp.Common` diagnostics and code-fix families |
6666
| `DCS8XXX` | Hosting, dependency injection, and configuration diagnostics and code-fix families |
67-
| `DCS9XXX` | Reserved |
67+
| `DCS9XXX` | Reserved |
6868

6969
##### Application command family
7070

71-
| Rule ID | Category | Severity | Notes |
72-
| ------------------------------------ | -------- | -------- | ------------------------------------------------ |
71+
| Rule ID | Category | Severity | Notes |
72+
|--------------------------------------|----------|----------|-------------------------------------------------------|
7373
| [DCS2101](xref:vs_analyzer_dcs_2101) | Usage | Info | Application command checks-failed migration prototype |
7474

7575
##### Core family
7676

77-
| Rule ID | Category | Severity | Notes |
78-
| ------------------------------------ | -------- | -------- | ------------------------------------------ |
79-
| [DCS1101](xref:vs_analyzer_dcs_1101) | Usage | Info | Prefer `DiscordClient.GetPresences(userId)` over manual `Presences` filtering |
77+
| Rule ID | Category | Severity | Notes |
78+
|--------------------------------------|----------|----------|---------------------------------------------------------------------------------------------------------|
79+
| [DCS1101](xref:vs_analyzer_dcs_1101) | Usage | Warning | Prefer `DiscordClient.GetPresences(userId)` over manual `Presences` filtering |
80+
| [DCS1102](xref:vs_analyzer_dcs_1102) | Usage | Warning | `deleteMessageDays` renamed to `deleteMessageSeconds` on ban methods; named-argument callers auto-fixed |
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using Microsoft.CodeAnalysis;
2+
3+
namespace DisCatSharp.Analyzer.Tests;
4+
5+
public sealed class BanDeleteMessageDaysMigrationTests
6+
{
7+
[Fact]
8+
public async Task Reports_diagnostic_on_named_argument()
9+
{
10+
const string source =
11+
"""
12+
using System.Threading.Tasks;
13+
using DisCatSharp.Entities;
14+
15+
public sealed class Consumer
16+
{
17+
public async Task BanUser(DiscordMember member)
18+
=> await member.BanAsync(deleteMessageDays: 7);
19+
}
20+
""";
21+
22+
var diagnostics = await RoslynTestDocumentFactory.GetAnalyzerDiagnosticsAsync(source);
23+
24+
var diagnostic = Assert.Single(diagnostics, x => x.Id == DisCatSharpDiagnosticIds.BanDeleteMessageDaysMigration);
25+
Assert.Equal(DiagnosticSeverity.Warning, diagnostic.Severity);
26+
}
27+
28+
[Fact]
29+
public async Task Fix_converts_literal_days_to_seconds()
30+
{
31+
const string source =
32+
"""
33+
using System.Threading.Tasks;
34+
using DisCatSharp.Entities;
35+
36+
public sealed class Consumer
37+
{
38+
public async Task BanUser(DiscordMember member)
39+
=> await member.BanAsync(deleteMessageDays: 7);
40+
}
41+
""";
42+
43+
var fixedSource = await RoslynTestDocumentFactory.ApplyBanDeleteMessageDaysMigrationFixAsync(source);
44+
45+
Assert.Contains("deleteMessageSeconds: 604800", fixedSource);
46+
Assert.DoesNotContain("deleteMessageDays", fixedSource);
47+
}
48+
49+
[Fact]
50+
public async Task Fix_renames_non_literal_argument()
51+
{
52+
const string source =
53+
"""
54+
using System.Threading.Tasks;
55+
using DisCatSharp.Entities;
56+
57+
public sealed class Consumer
58+
{
59+
public async Task BanUser(DiscordMember member, int days)
60+
=> await member.BanAsync(deleteMessageDays: days);
61+
}
62+
""";
63+
64+
var fixedSource = await RoslynTestDocumentFactory.ApplyBanDeleteMessageDaysMigrationFixAsync(source);
65+
66+
Assert.Contains("deleteMessageSeconds:", fixedSource);
67+
Assert.Contains("TODO", fixedSource);
68+
Assert.DoesNotContain("deleteMessageDays:", fixedSource);
69+
}
70+
}

‎DisCatSharp.Tests/DisCatSharp.Analyzer.Tests/RoslynTestDocumentFactory.cs‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,25 @@ public static async Task<string> ApplyPresenceAccessMigrationFixAsync(string sou
103103
return text.ToString();
104104
}
105105

106+
public static async Task<string> ApplyBanDeleteMessageDaysMigrationFixAsync(string source)
107+
{
108+
using var workspace = new AdhocWorkspace();
109+
var (solution, documentIds) = CreateProjectSolution(workspace, ImmutableDictionary<string, string>.Empty.Add("Test.cs", source));
110+
var documentId = documentIds["Test.cs"];
111+
112+
var document = solution.GetDocument(documentId)!;
113+
var compilation = await document.Project.GetCompilationAsync().ConfigureAwait(false);
114+
var diagnostics = await compilation!
115+
.WithAnalyzers([new DisCatSharpAnalyzer()])
116+
.GetAnalyzerDiagnosticsAsync()
117+
.ConfigureAwait(false);
118+
var diagnostic = Assert.Single(diagnostics, x => x.Id == DisCatSharpDiagnosticIds.BanDeleteMessageDaysMigration);
119+
120+
var fixedDocument = await DisCatSharpBanDeleteMessageDaysMigrationCodeFix.ApplyFixAsync(document, diagnostic, CancellationToken.None).ConfigureAwait(false);
121+
var text = await fixedDocument.GetTextAsync().ConfigureAwait(false);
122+
return text.ToString();
123+
}
124+
106125
private static (Solution Solution, ImmutableDictionary<string, DocumentId> DocumentIds) CreateProjectSolution(
107126
AdhocWorkspace workspace,
108127
ImmutableDictionary<string, string> sources)

‎DisCatSharp.Tests/DisCatSharp.Configuration.Tests/ConfigurationExtensionTests.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ public void TestExtractDiscordConfig_Haphazard()
148148

149149
Assert.Equal(expectedIntents, config.Intents);
150150
Assert.True(config.MobileStatus);
151-
Assert.Equal(1000, config.LargeThreshold);
151+
Assert.Equal(200, config.LargeThreshold);
152152
Assert.Equal(TimeSpan.FromHours(10), config.HttpTimeout);
153153
}
154154

‎DisCatSharp.Tests/DisCatSharp.Configuration.Tests/haphazard-discord.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"Discord": {
44
"Intents": "GuildEmojisAndStickers,GuildMembers,Guilds",
55
"MobileStatus": true,
6-
"LargeThreshold": 1000,
6+
"LargeThreshold": 200,
77
"HttpTimeout": "10:00:00"
88
}
99
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using System.Collections.Immutable;
2+
using System.Composition;
3+
using System.Linq;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
7+
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.CodeActions;
9+
using Microsoft.CodeAnalysis.CodeFixes;
10+
using Microsoft.CodeAnalysis.CSharp;
11+
using Microsoft.CodeAnalysis.CSharp.Syntax;
12+
13+
namespace DisCatSharp.Analyzer;
14+
15+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DisCatSharpBanDeleteMessageDaysMigrationCodeFix))]
16+
[Shared]
17+
public sealed class DisCatSharpBanDeleteMessageDaysMigrationCodeFix : SingleDiagnosticCodeFixProvider
18+
{
19+
protected override string FixableDiagnosticId => DisCatSharpDiagnosticIds.BanDeleteMessageDaysMigration;
20+
21+
protected override Task RegisterCodeFixesAsync(CodeFixContext context, ImmutableArray<Diagnostic> diagnostics)
22+
{
23+
context.RegisterCodeFix(
24+
CodeAction.Create(
25+
"Rename to 'deleteMessageSeconds' (convert literal days -> seconds)",
26+
ct => ApplyFixAsync(context.Document, diagnostics[0], ct),
27+
equivalenceKey: nameof(DisCatSharpBanDeleteMessageDaysMigrationCodeFix)),
28+
diagnostics[0]);
29+
30+
return Task.CompletedTask;
31+
}
32+
33+
internal static async Task<Document> ApplyFixAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
34+
{
35+
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
36+
if (root is null)
37+
return document;
38+
39+
var nameColonNode = root.FindNode(diagnostic.Location.SourceSpan) as NameColonSyntax;
40+
if (nameColonNode is null)
41+
return document;
42+
43+
var argument = nameColonNode.Parent as ArgumentSyntax;
44+
if (argument is null)
45+
return document;
46+
47+
// New name colon: deleteMessageSeconds:
48+
var newNameColon = SyntaxFactory.NameColon(
49+
SyntaxFactory.IdentifierName("deleteMessageSeconds"))
50+
.WithTriviaFrom(nameColonNode);
51+
52+
// If the argument expression is a numeric literal, multiply by 86400
53+
ArgumentSyntax newArgument;
54+
if (argument.Expression is LiteralExpressionSyntax { Token.Value: int days })
55+
{
56+
var seconds = days * 86400;
57+
var newLiteral = SyntaxFactory.LiteralExpression(
58+
SyntaxKind.NumericLiteralExpression,
59+
SyntaxFactory.Literal(seconds))
60+
.WithTriviaFrom(argument.Expression);
61+
newArgument = argument.WithNameColon(newNameColon).WithExpression(newLiteral);
62+
}
63+
else
64+
{
65+
// Non-literal: rename only, add TODO comment
66+
var todoComment = SyntaxFactory.Comment("/* TODO: was days, now seconds - multiply by 86400 if needed */");
67+
var newExpression = argument.Expression.WithLeadingTrivia(
68+
argument.Expression.GetLeadingTrivia().Add(todoComment).Add(SyntaxFactory.Space));
69+
newArgument = argument.WithNameColon(newNameColon).WithExpression(newExpression);
70+
}
71+
72+
var newRoot = root.ReplaceNode(argument, newArgument);
73+
return document.WithSyntaxRoot(newRoot);
74+
}
75+
}

‎DisCatSharp.Tools/DisCatSharp.Analyzer/DisCatSharp.Analyzer.Package/DisCatSharp.Analyzer.Package.csproj‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<PropertyGroup>
1212
<Title>DisCatSharp Analyzer</Title>
1313
<PackageId>DisCatSharp.Analyzer</PackageId>
14-
<PackageVersion>1.0.2.0</PackageVersion>
14+
<PackageVersion>1.0.3.0</PackageVersion>
1515
<Authors>AITSYS</Authors>
1616
<PackageIcon>logo-pride.png</PackageIcon>
1717
<RepositoryUrl>https://github.com/Aiko-IT-Systems/DisCatSharp</RepositoryUrl>

‎DisCatSharp.Tools/DisCatSharp.Analyzer/DisCatSharp.Analyzer/AnalyzerReleases.Shipped.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,12 @@ DCS2101 | Usage | Error | Application-command checks-failed migration prot
2727
Rule ID | Category | Severity | Notes
2828
--------|----------|----------|--------------------
2929
DCS1101 | Usage | Warning | Presence access migration guidance, [Documentation](https://docs.dcs.aitsys.dev/vs/analyzer/dcs/1101)
30+
31+
## Release 1.0.3.0
32+
33+
### New Rules
34+
35+
Rule ID | Category | Severity | Notes
36+
--------|----------|----------|--------------------
37+
DCS1102 | Usage | Warning | Ban parameter renamed from 'deleteMessageDays' to 'deleteMessageSeconds'; named argument callers can be auto-fixed, [Documentation](https://docs.dcs.aitsys.dev/vs/analyzer/dcs/1102)
38+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

0 commit comments

Comments
 (0)