-
Notifications
You must be signed in to change notification settings - Fork 5k
Add LoggingGenerator #51064
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Add LoggingGenerator #51064
Changes from all commits
Commits
Show all changes
123 commits
Select commit
Hold shift + click to select a range
71cf1eb
Rejigger project names
afd85ab
Update namespaces
fe21a95
Nuke the temporary .Attributes namespace
b6281b5
Add error checking to prevent multiple logging messages from using th…
ca3ead4
Use ISyntaxReceiver to be more IDE friendly
jaredpar 470b160
Finish implementation of exception support
515f029
Make the generated type have the same access modifiers as the input i…
6affdd8
Enforce that logging methods must return void
7f15797
Cleanup how semantic models are handled
8b3cdaa
Prevent generic interfaces or methods.
1c0e756
Improve efficiency of the generated code for log messages without tem…
5893090
Various cleanup items
56943dd
Few small changes (#3)
davidfowl 056e81c
Minor refactoring
b72ec2d
More refactoring
4ab2acc
Support partial methods (#4)
davidfowl 0718293
Optimize some code gen.
d065c90
Simplify the model. You can now only annotate partial methods
d99cec0
Improve code generation by statically defining delegate types
b31bca8
Simplify generated code
1a4e336
Revamp code gen to shrink jitted size
128f5d2
Bunch of improvements.
3740b68
Enable localization of error messages
e275551
Substantially reduce the size of the generated code
fc8ce48
Add unit tests for Microsoft.Extensions.Logging.Attributes
951f5cc
Renamed the *Attributes assembly to *Extras
bf36548
Remove an allocation that snuck in during the last batch of optimizat…
b773ce8
Add a few more tests
6b25016
Use proper code to get fully-qualified type names
1eb4e9f
Add support for message strings containing linefeeds or quotes
bb45336
Add support for logging messages containing carriage returns.
e979d5f
Use static analysis consistently
c459d32
More tests, and a fix for string formatting of large cardinality log …
14530de
Add code coverage for logging generator error paths
d0ebfb2
Readme update
6354e8f
Add support for early termination via the cancellation token
8ece920
Improvements in the code generator's performance
0167f30
Produce an error when [LoggerMessage] is applied to a non-partial met…
9915bbd
Add support for generic parameters for logging methods
43f671c
Add support for 'protected internal' and 'protected private' logging …
66310a1
Minor cleanup
58709eb
Improved code gen for log levels, and add level unit tests
11b5ef1
Add support for ToString in the log state
da8099b
Introduce an analyzer.
dd39eba
Combine the fixer into the analyzer assembly.
a93b5c0
Introduce analyzer and fixer functionality
17d3a5b
More stuff.
561dbf3
More tests, and ensuing bug fixes.
8f94d10
Tests and fixes
34d2237
Finish support for nullable logging args, and extension logging methods
6fabebc
Add license file and a bit of info in the README
438f2bb
Improve generator's perf to minimize the impact on the IDE
3fd97a1
Refactor the main generator class to allow unit testing of the produc…
9c6c08b
More test coverage, more resulting fixes
71fd60a
Make the source generator robust to malformed source
4424c8b
More testing and tweaking
bb33d1c
More coverage, more fixes
c89bb35
Relocate using statements
755f3ff
Cleanup analyzer story
225b63d
Refactoring and cleanup
139e5bb
Get coverage to 100%
e8db38a
A few improvements
5e3d279
Variety of improvements.
00cac9b
Various improvements.
9e150d9
Add support for logging methods as instance methods
83eb36e
Minor cleanup
cc44192
Various improvements
b611bbd
Rename LogStateHolder to LogValues
60fef62
Improve test coverage
79dc2d4
Use the Invariant culture when formatting log messages
055cf3f
Improve the error message around id reuse
abb5010
A few improvements
9087427
Emit GenerateCodeAttribute instead of CompilerGeneratedAttribute
58b3369
Minor cleanup items
82f1611
Added preliminary support for an alternate code gen strategy
bb63032
Overhaul of the emitter.
5f210fc
Address more feedback and bugs
fe54581
Two nit fixes
maryamariyan 407a9e8
Merge pull request #2 from maryamariyan/nit-fixes
maryamariyan b1f5364
API Review feedback: LoggerMessageAttribute (#4)
maryamariyan 3afda5f
Merge branch 'logging-generator' of /Users/maryam/CodeHub/LoggingGene…
maryamariyan b7b2928
- Update header license
maryamariyan c41c463
Minor renames, also added project to solution file
maryamariyan 3ef2083
- Add LoggerMessageAttribute - ref/src
maryamariyan a7eba74
- Add localization and packaging
maryamariyan ba71c47
- ActiveIssue for Mono
maryamariyan 4cb1466
Complete one TODO: use available SYSLIBXXXX values
maryamariyan daf1a7a
Fix path to pick up root folder
maryamariyan 3a97c73
- Add versions to versions.props
maryamariyan 946f764
- Switch ActiveIssue from closed to dupe one.
maryamariyan 80840e6
- Move RoslynTestUtils to common folder
maryamariyan 5637b69
Remove suppressed warnings
maryamariyan 0cc8316
Added ActiveIssue for roslyn issue
maryamariyan 1297f11
Rename DiagDescriptors to DiagnosticDescriptors
maryamariyan 2add7ce
- Remove LangVersions from csproj
maryamariyan 13631d2
- Remove Moq
maryamariyan d9970f7
- Skip on browser Cant load Microsoft.CodeAnalysis
maryamariyan d809dad
- Add CLSCompliant on gen csproj
maryamariyan dd51c53
- Add record of SYSLIBXXXX in the md file
maryamariyan 70f24ef
- Rename md file to list-of-diagnostics.md
maryamariyan afb43d7
- Add a set of baseline files to test against
maryamariyan 2d41c6d
- Fix new line issue when comparing baselines
maryamariyan 7e68b1c
Add Generator and Resources to the Logging package
ericstj 9200131
Refactor part 1
maryamariyan 6a71553
Fixup whitespaces after removing try/finally
maryamariyan 7547f38
Refactor part 2
maryamariyan 6eab93d
- Remove duplication in the resource string values
maryamariyan cc0bb07
Replace operations using StringComparison.Ordinal
maryamariyan e56d447
Include generator project to src.proj
maryamariyan b970fdd
Upgrade generator package versions
maryamariyan bd93ccf
- Can't have a log level set twice
maryamariyan a6ecab3
Add three more diagnostics
maryamariyan 35a584c
Use wildcard, include gen projects under NonNetCoreApp
maryamariyan 0ec16c4
Renames only
maryamariyan e81015d
- Use other Define overload using skipEnabledCheck
maryamariyan 1b7a158
Revert "Upgrade generator package versions"
maryamariyan d94e42f
Block range for logging in MD file
maryamariyan a2f18d9
enable nullable on test project
maryamariyan e110684
Add missing "global::" to types
maryamariyan ba9b77f
Fix up triple slash comments for LoggerMessageAttribute
maryamariyan 843c0f3
lock strings
maryamariyan 65b1f51
- Feedback on RoslynTestUtils
maryamariyan c0c22ce
- Enable nullable on csproj
maryamariyan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
293 changes: 293 additions & 0 deletions
293
src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,293 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Reflection; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CodeActions; | ||
using Microsoft.CodeAnalysis.CodeFixes; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.CodeAnalysis.Text; | ||
using Xunit; | ||
|
||
namespace SourceGenerators.Tests | ||
{ | ||
internal static class RoslynTestUtils | ||
maryamariyan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
/// <summary> | ||
/// Creates a canonical Roslyn project for testing. | ||
/// </summary> | ||
/// <param name="references">Assembly references to include in the project.</param> | ||
/// <param name="includeBaseReferences">Whether to include references to the BCL assemblies.</param> | ||
public static Project CreateTestProject(IEnumerable<Assembly>? references, bool includeBaseReferences = true) | ||
{ | ||
string corelib = Assembly.GetAssembly(typeof(object))!.Location; | ||
string runtimeDir = Path.GetDirectoryName(corelib)!; | ||
|
||
var refs = new List<MetadataReference>(); | ||
if (includeBaseReferences) | ||
{ | ||
refs.Add(MetadataReference.CreateFromFile(corelib)); | ||
refs.Add(MetadataReference.CreateFromFile(Path.Combine(runtimeDir, "netstandard.dll"))); | ||
refs.Add(MetadataReference.CreateFromFile(Path.Combine(runtimeDir, "System.Runtime.dll"))); | ||
} | ||
|
||
if (references != null) | ||
{ | ||
foreach (var r in references) | ||
{ | ||
refs.Add(MetadataReference.CreateFromFile(r.Location)); | ||
} | ||
} | ||
|
||
return new AdhocWorkspace() | ||
.AddSolution(SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create())) | ||
.AddProject("Test", "test.dll", "C#") | ||
.WithMetadataReferences(refs) | ||
.WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithNullableContextOptions(NullableContextOptions.Enable)); | ||
} | ||
|
||
public static Task CommitChanges(this Project proj, params string[] ignorables) | ||
{ | ||
Assert.True(proj.Solution.Workspace.TryApplyChanges(proj.Solution)); | ||
return AssertNoDiagnostic(proj, ignorables); | ||
} | ||
|
||
public static async Task AssertNoDiagnostic(this Project proj, params string[] ignorables) | ||
{ | ||
foreach (Document doc in proj.Documents) | ||
{ | ||
SemanticModel? sm = await doc.GetSemanticModelAsync(CancellationToken.None).ConfigureAwait(false); | ||
Assert.NotNull(sm); | ||
|
||
foreach (Diagnostic d in sm!.GetDiagnostics()) | ||
{ | ||
bool ignore = ignorables.Any(ig => d.Id == ig); | ||
|
||
Assert.True(ignore, d.ToString()); | ||
} | ||
} | ||
} | ||
|
||
private static Project WithDocuments(this Project project, IEnumerable<string> sources, IEnumerable<string>? sourceNames = null) | ||
{ | ||
int count = 0; | ||
Project result = project; | ||
if (sourceNames != null) | ||
{ | ||
List<string> names = sourceNames.ToList(); | ||
foreach (string s in sources) | ||
result = result.WithDocument(names[count++], s); | ||
} | ||
else | ||
{ | ||
foreach (string s in sources) | ||
result = result.WithDocument($"src-{count++}.cs", s); | ||
} | ||
|
||
return result; | ||
} | ||
|
||
public static Project WithDocument(this Project proj, string name, string text) | ||
{ | ||
return proj.AddDocument(name, text).Project; | ||
} | ||
|
||
public static Document FindDocument(this Project proj, string name) | ||
{ | ||
foreach (Document doc in proj.Documents) | ||
{ | ||
if (doc.Name == name) | ||
{ | ||
return doc; | ||
} | ||
} | ||
|
||
throw new FileNotFoundException(name); | ||
} | ||
|
||
/// <summary> | ||
/// Looks for /*N+*/ and /*-N*/ markers in a string and creates a TextSpan containing the enclosed text. | ||
/// </summary> | ||
public static TextSpan MakeSpan(string text, int spanNum) | ||
{ | ||
int start = text.IndexOf($"/*{spanNum}+*/", StringComparison.Ordinal); | ||
if (start < 0) | ||
{ | ||
throw new ArgumentOutOfRangeException(nameof(spanNum)); | ||
} | ||
|
||
start += 6; | ||
|
||
int end = text.IndexOf($"/*-{spanNum}*/", StringComparison.Ordinal); | ||
if (end < 0) | ||
{ | ||
throw new ArgumentOutOfRangeException(nameof(spanNum)); | ||
} | ||
|
||
end -= 1; | ||
|
||
return new TextSpan(start, end - start); | ||
} | ||
|
||
/// <summary> | ||
/// Runs a Roslyn generator over a set of source files. | ||
/// </summary> | ||
public static async Task<(ImmutableArray<Diagnostic>, ImmutableArray<GeneratedSourceResult>)> RunGenerator( | ||
ISourceGenerator generator, | ||
IEnumerable<Assembly>? references, | ||
IEnumerable<string> sources, | ||
AnalyzerConfigOptionsProvider? optionsProvider = null, | ||
bool includeBaseReferences = true, | ||
CancellationToken cancellationToken = default) | ||
{ | ||
Project proj = CreateTestProject(references, includeBaseReferences); | ||
|
||
proj = proj.WithDocuments(sources); | ||
|
||
Assert.True(proj.Solution.Workspace.TryApplyChanges(proj.Solution)); | ||
|
||
Compilation? comp = await proj!.GetCompilationAsync(CancellationToken.None).ConfigureAwait(false); | ||
|
||
CSharpGeneratorDriver cgd = CSharpGeneratorDriver.Create(new[] { generator }, optionsProvider: optionsProvider); | ||
GeneratorDriver gd = cgd.RunGenerators(comp!, cancellationToken); | ||
|
||
GeneratorDriverRunResult r = gd.GetRunResult(); | ||
return (r.Results[0].Diagnostics, r.Results[0].GeneratedSources); | ||
} | ||
|
||
/// <summary> | ||
/// Runs a Roslyn analyzer over a set of source files. | ||
/// </summary> | ||
public static async Task<IList<Diagnostic>> RunAnalyzer( | ||
DiagnosticAnalyzer analyzer, | ||
IEnumerable<Assembly> references, | ||
IEnumerable<string> sources) | ||
{ | ||
Project proj = CreateTestProject(references); | ||
|
||
proj = proj.WithDocuments(sources); | ||
|
||
await proj.CommitChanges().ConfigureAwait(false); | ||
|
||
ImmutableArray<DiagnosticAnalyzer> analyzers = ImmutableArray.Create(analyzer); | ||
|
||
Compilation? comp = await proj!.GetCompilationAsync().ConfigureAwait(false); | ||
return await comp!.WithAnalyzers(analyzers).GetAllDiagnosticsAsync().ConfigureAwait(false); | ||
} | ||
|
||
/// <summary> | ||
/// Runs a Roslyn analyzer and fixer. | ||
/// </summary> | ||
public static async Task<IList<string>> RunAnalyzerAndFixer( | ||
DiagnosticAnalyzer analyzer, | ||
CodeFixProvider fixer, | ||
IEnumerable<Assembly> references, | ||
IEnumerable<string> sources, | ||
IEnumerable<string>? sourceNames = null, | ||
string? defaultNamespace = null, | ||
string? extraFile = null) | ||
{ | ||
Project proj = CreateTestProject(references); | ||
|
||
int count = sources.Count(); | ||
proj = proj.WithDocuments(sources, sourceNames); | ||
|
||
if (defaultNamespace != null) | ||
{ | ||
proj = proj.WithDefaultNamespace(defaultNamespace); | ||
} | ||
|
||
await proj.CommitChanges().ConfigureAwait(false); | ||
|
||
ImmutableArray<DiagnosticAnalyzer> analyzers = ImmutableArray.Create(analyzer); | ||
|
||
while (true) | ||
{ | ||
Compilation? comp = await proj!.GetCompilationAsync().ConfigureAwait(false); | ||
ImmutableArray<Diagnostic> diags = await comp!.WithAnalyzers(analyzers).GetAllDiagnosticsAsync().ConfigureAwait(false); | ||
if (diags.IsEmpty) | ||
{ | ||
// no more diagnostics reported by the analyzers | ||
break; | ||
} | ||
|
||
var actions = new List<CodeAction>(); | ||
foreach (Diagnostic d in diags) | ||
{ | ||
Document? doc = proj.GetDocument(d.Location.SourceTree); | ||
|
||
CodeFixContext context = new CodeFixContext(doc!, d, (action, _) => actions.Add(action), CancellationToken.None); | ||
await fixer.RegisterCodeFixesAsync(context).ConfigureAwait(false); | ||
} | ||
|
||
if (actions.Count == 0) | ||
{ | ||
// nothing to fix | ||
break; | ||
} | ||
|
||
ImmutableArray<CodeActionOperation> operations = await actions[0].GetOperationsAsync(CancellationToken.None).ConfigureAwait(false); | ||
Solution solution = operations.OfType<ApplyChangesOperation>().Single().ChangedSolution; | ||
Project? changedProj = solution.GetProject(proj.Id); | ||
if (changedProj != proj) | ||
{ | ||
proj = await RecreateProjectDocumentsAsync(changedProj!).ConfigureAwait(false); | ||
} | ||
} | ||
|
||
var results = new List<string>(); | ||
|
||
if (sourceNames != null) | ||
{ | ||
List<string> l = sourceNames.ToList(); | ||
for (int i = 0; i < count; i++) | ||
{ | ||
SourceText s = await proj.FindDocument(l[i]).GetTextAsync().ConfigureAwait(false); | ||
results.Add(s.ToString().Replace("\r\n", "\n", StringComparison.Ordinal)); | ||
} | ||
} | ||
else | ||
{ | ||
for (int i = 0; i < count; i++) | ||
{ | ||
SourceText s = await proj.FindDocument($"src-{i}.cs").GetTextAsync().ConfigureAwait(false); | ||
results.Add(s.ToString().Replace("\r\n", "\n", StringComparison.Ordinal)); | ||
} | ||
} | ||
|
||
if (extraFile != null) | ||
{ | ||
SourceText s = await proj.FindDocument(extraFile).GetTextAsync().ConfigureAwait(false); | ||
results.Add(s.ToString().Replace("\r\n", "\n", StringComparison.Ordinal)); | ||
} | ||
|
||
return results; | ||
} | ||
|
||
private static async Task<Project> RecreateProjectDocumentsAsync(Project project) | ||
{ | ||
foreach (DocumentId documentId in project.DocumentIds) | ||
{ | ||
Document? document = project.GetDocument(documentId); | ||
document = await RecreateDocumentAsync(document!).ConfigureAwait(false); | ||
project = document.Project; | ||
} | ||
|
||
return project; | ||
} | ||
|
||
private static async Task<Document> RecreateDocumentAsync(Document document) | ||
{ | ||
SourceText newText = await document.GetTextAsync().ConfigureAwait(false); | ||
return document.WithText(SourceText.From(newText.ToString(), newText.Encoding, newText.ChecksumAlgorithm)); | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.