未验证 提交 e6afc959 编写于 作者: C Chris Sienkiewicz 提交者: GitHub

Sg embedded sources (#44768)

Embed generated source texts in the portable PDB:
- Allows stepping in etc. in the IDE as well as a dump debugging after compilation
- Consistent ordering of generated syntax trees.
- Unique hint names
- Restrict hint names to file path legal characters
- Add tests
上级 d1532d6e
Microsoft.CodeAnalysis.CSharp.CSharpGeneratorDriver
Microsoft.CodeAnalysis.CSharp.CSharpGeneratorDriver.CSharpGeneratorDriver(Microsoft.CodeAnalysis.ParseOptions parseOptions, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.ISourceGenerator> generators, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.AdditionalText> additionalTexts) -> void
Microsoft.CodeAnalysis.CSharp.Syntax.FunctionPointerTypeSyntax Microsoft.CodeAnalysis.CSharp.Syntax.FunctionPointerTypeSyntax
Microsoft.CodeAnalysis.CSharp.Syntax.FunctionPointerTypeSyntax.AddParameters(params Microsoft.CodeAnalysis.CSharp.Syntax.ParameterSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.FunctionPointerTypeSyntax Microsoft.CodeAnalysis.CSharp.Syntax.FunctionPointerTypeSyntax.AddParameters(params Microsoft.CodeAnalysis.CSharp.Syntax.ParameterSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.FunctionPointerTypeSyntax
Microsoft.CodeAnalysis.CSharp.Syntax.FunctionPointerTypeSyntax.AsteriskToken.get -> Microsoft.CodeAnalysis.SyntaxToken Microsoft.CodeAnalysis.CSharp.Syntax.FunctionPointerTypeSyntax.AsteriskToken.get -> Microsoft.CodeAnalysis.SyntaxToken
......
...@@ -17,7 +17,6 @@ ...@@ -17,7 +17,6 @@
#nullable enable #nullable enable
namespace Microsoft.CodeAnalysis.CSharp namespace Microsoft.CodeAnalysis.CSharp
{ {
[System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0016:Add public types and members to the declared API", Justification = "In Progress")]
public sealed class CSharpGeneratorDriver : GeneratorDriver public sealed class CSharpGeneratorDriver : GeneratorDriver
{ {
public CSharpGeneratorDriver(ParseOptions parseOptions, ImmutableArray<ISourceGenerator> generators, ImmutableArray<AdditionalText> additionalTexts) public CSharpGeneratorDriver(ParseOptions parseOptions, ImmutableArray<ISourceGenerator> generators, ImmutableArray<AdditionalText> additionalTexts)
...@@ -30,8 +29,8 @@ private CSharpGeneratorDriver(GeneratorDriverState state) ...@@ -30,8 +29,8 @@ private CSharpGeneratorDriver(GeneratorDriverState state)
{ {
} }
internal override SyntaxTree ParseGeneratedSourceText(GeneratedSourceText input, CancellationToken cancellationToken) internal override SyntaxTree ParseGeneratedSourceText(GeneratedSourceText input, string fileName, CancellationToken cancellationToken)
=> SyntaxFactory.ParseSyntaxTree(input.Text, _state.ParseOptions, input.HintName, cancellationToken); // https://github.com/dotnet/roslyn/issues/42628: hint path/ filename uniqueness => SyntaxFactory.ParseSyntaxTree(input.Text, _state.ParseOptions, fileName, cancellationToken);
internal override GeneratorDriver FromState(GeneratorDriverState state) => new CSharpGeneratorDriver(state); internal override GeneratorDriver FromState(GeneratorDriverState state) => new CSharpGeneratorDriver(state);
......
...@@ -2399,7 +2399,6 @@ private static void ValidateEmbeddedSources_Portable(Dictionary<string, string> ...@@ -2399,7 +2399,6 @@ private static void ValidateEmbeddedSources_Portable(Dictionary<string, string>
continue; continue;
} }
Assert.True(embeddedSource.Encoding is UTF8Encoding && embeddedSource.Encoding.GetPreamble().Length == 0);
Assert.Equal(expectedEmbeddedMap[docPath], embeddedSource.ToString()); Assert.Equal(expectedEmbeddedMap[docPath], embeddedSource.ToString());
Assert.True(expectedEmbeddedMap.Remove(docPath)); Assert.True(expectedEmbeddedMap.Remove(docPath));
} }
...@@ -12194,6 +12193,58 @@ public void TestAnalyzerFilteringBasedOnSeverity(DiagnosticSeverity defaultSever ...@@ -12194,6 +12193,58 @@ public void TestAnalyzerFilteringBasedOnSeverity(DiagnosticSeverity defaultSever
} }
} }
[Fact]
public void SourceGenerators_EmbeddedSources()
{
var dir = Temp.CreateDirectory();
var src = dir.CreateFile("temp.cs").WriteAllText(@"
class C
{
}");
var generatedSource = "public class D { }";
var generator = new SimpleGenerator(generatedSource, "generatedSource.cs");
VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/langversion:preview", "/debug:embedded", "/out:embed.exe" }, generators: new[] { generator }, analyzers: null);
var generatorPrefix = $"{generator.GetType().Module.ModuleVersionId}_{generator.GetType().FullName}";
ValidateEmbeddedSources_Portable(new Dictionary<string, string> { { Path.Combine(dir.Path, $"{generatorPrefix}_generatedSource.cs"), generatedSource } }, dir, true);
// Clean up temp files
CleanupAllGeneratedFiles(src.Path);
}
[Theory]
[InlineData("partial class D {}", "file1.cs", "partial class E {}", "file2.cs")] // different files, different names
[InlineData("partial class D {}", "file1.cs", "partial class E {}", "file1.cs")] // different files, same names
[InlineData("partial class D {}", "file1.cs", "partial class D {}", "file2.cs")] // same files, different names
[InlineData("partial class D {}", "file1.cs", "partial class D {}", "file1.cs")] // same files, same names
[InlineData("partial class D {}", "file1.cs", "", "file2.cs")] // empty second file
public void SourceGenerators_EmbeddedSources_MultipleFiles(string source1, string source1Name, string source2, string source2Name)
{
var dir = Temp.CreateDirectory();
var src = dir.CreateFile("temp.cs").WriteAllText(@"
class C
{
}");
var generator = new SimpleGenerator(source1, source1Name);
var generator2 = new SimpleGenerator2(source2, source2Name);
VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/langversion:preview", "/debug:embedded", "/out:embed.exe" }, generators: new[] { generator, generator2 }, analyzers: null);
var generator1Prefix = $"{generator.GetType().Module.ModuleVersionId}_{generator.GetType().FullName}";
var generator2Prefix = $"{generator2.GetType().Module.ModuleVersionId}_{generator2.GetType().FullName}";
ValidateEmbeddedSources_Portable(new Dictionary<string, string>
{
{ $"{dir.Path}{Path.DirectorySeparatorChar}{generator1Prefix}_{source1Name}", source1},
{ $"{dir.Path}{Path.DirectorySeparatorChar}{generator2Prefix}_{source2Name}", source2},
}, dir, true);
// Clean up temp files
CleanupAllGeneratedFiles(src.Path);
}
[Fact] [Fact]
[WorkItem(44087, "https://github.com/dotnet/roslyn/issues/44087")] [WorkItem(44087, "https://github.com/dotnet/roslyn/issues/44087")]
public void SourceGeneratorsAndAnalyzerConfig() public void SourceGeneratorsAndAnalyzerConfig()
...@@ -12207,9 +12258,9 @@ class C ...@@ -12207,9 +12258,9 @@ class C
[*.cs] [*.cs]
key = value"); key = value");
var generator = new SimpleGenerator("public class D {}"); var generator = new SimpleGenerator("public class D {}", "generated.cs");
VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/langversion:preview", "/analyzerconfig:" + analyzerConfig.Path }, generators: new[] { generator }, analyzers: new HiddenDiagnosticAnalyzer()); VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/langversion:preview", "/analyzerconfig:" + analyzerConfig.Path }, generators: new[] { generator }, analyzers: null);
} }
[DiagnosticAnalyzer(LanguageNames.CSharp)] [DiagnosticAnalyzer(LanguageNames.CSharp)]
...@@ -12455,6 +12506,7 @@ public override void CreateAnalyzerWithinCompilation(CompilationStartAnalysisCon ...@@ -12455,6 +12506,7 @@ public override void CreateAnalyzerWithinCompilation(CompilationStartAnalysisCon
internal class SimpleGenerator : ISourceGenerator internal class SimpleGenerator : ISourceGenerator
{ {
private readonly string _sourceToAdd; private readonly string _sourceToAdd;
private readonly string _fileName;
/// <remarks> /// <remarks>
/// Required for reflection based tests /// Required for reflection based tests
...@@ -12464,21 +12516,26 @@ public SimpleGenerator() ...@@ -12464,21 +12516,26 @@ public SimpleGenerator()
_sourceToAdd = string.Empty; _sourceToAdd = string.Empty;
} }
public SimpleGenerator(string sourceToAdd) public SimpleGenerator(string sourceToAdd, string fileName)
{ {
_sourceToAdd = sourceToAdd; _sourceToAdd = sourceToAdd;
_fileName = fileName;
} }
public void Execute(SourceGeneratorContext context) public void Execute(SourceGeneratorContext context)
{ {
if (!string.IsNullOrWhiteSpace(_sourceToAdd)) context.AddSource(_fileName, SourceText.From(_sourceToAdd, Encoding.UTF8));
{
context.AddSource("addedSource.cs", SourceText.From(_sourceToAdd, Encoding.UTF8));
}
} }
public void Initialize(InitializationContext context) public void Initialize(InitializationContext context)
{ {
} }
} }
internal class SimpleGenerator2 : SimpleGenerator
{
public SimpleGenerator2(string sourceToAdd, string fileName) : base(sourceToAdd, fileName)
{
}
}
} }
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Xunit;
#nullable enable
namespace Microsoft.CodeAnalysis.CSharp.Semantic.UnitTests.SourceGeneration
{
public class AdditionalSourcesCollectionTests
: CSharpTestBase
{
[Theory]
[InlineData("abc")] // abc.cs
[InlineData("abc.cs")] //abc.cs
[InlineData("abc.vb")] // abc.vb.cs
[InlineData("abc.generated.cs")]
[InlineData("abc_-_")]
[InlineData("abc - generated.cs")]
[InlineData("abc\u0064.cs")] //acbd.cs
[InlineData("abc(1).cs")]
[InlineData("abc[1].cs")]
[InlineData("abc{1}.cs")]
public void HintName_ValidValues(string hintName)
{
AdditionalSourcesCollection asc = new AdditionalSourcesCollection();
asc.Add(hintName, SourceText.From("public class D{}", Encoding.UTF8));
Assert.True(asc.Contains(hintName));
var sources = asc.ToImmutableAndFree();
Assert.True(sources[0].HintName.EndsWith(".cs"));
}
[Theory]
[InlineData("/abc/def.cs")]
[InlineData("\\")]
[InlineData(":")]
[InlineData("*")]
[InlineData("?")]
[InlineData("\"")]
[InlineData("<")]
[InlineData(">")]
[InlineData("|")]
[InlineData("\0")]
[InlineData("abc\u00A0.cs")] // unicode non-breaking space
public void HintName_InvalidValues(string hintName)
{
AdditionalSourcesCollection asc = new AdditionalSourcesCollection();
Assert.Throws<ArgumentException>(nameof(hintName), () => asc.Add(hintName, SourceText.From("public class D{}", Encoding.UTF8)));
}
[Fact]
public void AddedSources_Are_Deterministic()
{
// a few manual simple ones
AdditionalSourcesCollection asc = new AdditionalSourcesCollection();
asc.Add("file3.cs", SourceText.From("", Encoding.UTF8));
asc.Add("file1.cs", SourceText.From("", Encoding.UTF8));
asc.Add("file2.cs", SourceText.From("", Encoding.UTF8));
asc.Add("file5.cs", SourceText.From("", Encoding.UTF8));
asc.Add("file4.cs", SourceText.From("", Encoding.UTF8));
var sources = asc.ToImmutableAndFree();
var hintNames = sources.Select(s => s.HintName).ToArray();
Assert.Equal(new[]
{
"file3.cs",
"file1.cs",
"file2.cs",
"file5.cs",
"file4.cs"
}, hintNames);
// generate a long random list, remembering the order we added them
Random r = new Random();
string[] names = new string[1000];
asc = new AdditionalSourcesCollection();
for (int i = 0; i < 1000; i++)
{
names[i] = r.NextDouble().ToString() + ".cs";
asc.Add(names[i], SourceText.From("", Encoding.UTF8));
}
sources = asc.ToImmutableAndFree();
hintNames = sources.Select(s => s.HintName).ToArray();
Assert.Equal(names, hintNames);
}
[Theory]
[InlineData("file.cs", "file.cs")]
[InlineData("file", "file")]
[InlineData("file", "file.cs")]
[InlineData("file.cs", "file")]
[InlineData("file.cs", "file.CS")]
[InlineData("file.CS", "file.cs")]
[InlineData("file", "file.CS")]
[InlineData("file.CS", "file")]
[InlineData("File", "file")]
[InlineData("file", "File")]
public void Hint_Name_Must_Be_Unique(string hintName1, string hintName2)
{
AdditionalSourcesCollection asc = new AdditionalSourcesCollection();
asc.Add(hintName1, SourceText.From("", Encoding.UTF8));
Assert.Throws<ArgumentException>("hintName", () => asc.Add(hintName2, SourceText.From("", Encoding.UTF8)));
}
[Theory]
[InlineData("file.cs", "file.cs")]
[InlineData("file", "file")]
[InlineData("file", "file.cs")]
[InlineData("file.cs", "file")]
[InlineData("file.CS", "file")]
[InlineData("file", "file.CS")]
[InlineData("File", "file.cs")]
[InlineData("File.cs", "file")]
[InlineData("File.cs", "file.CS")]
public void Contains(string addHintName, string checkHintName)
{
AdditionalSourcesCollection asc = new AdditionalSourcesCollection();
asc.Add(addHintName, SourceText.From("", Encoding.UTF8));
Assert.True(asc.Contains(checkHintName));
}
[Theory]
[InlineData("file.cs", "file.cs")]
[InlineData("file", "file")]
[InlineData("file", "file.cs")]
[InlineData("file.cs", "file")]
public void Remove(string addHintName, string removeHintName)
{
AdditionalSourcesCollection asc = new AdditionalSourcesCollection();
asc.Add(addHintName, SourceText.From("", Encoding.UTF8));
asc.RemoveSource(removeHintName);
var sources = asc.ToImmutableAndFree();
Assert.Empty(sources);
}
}
}
...@@ -99,7 +99,7 @@ class C { } ...@@ -99,7 +99,7 @@ class C { }
Assert.Single(compilation.SyntaxTrees); Assert.Single(compilation.SyntaxTrees);
int initCount = 0, executeCount = 0; int initCount = 0, executeCount = 0;
var generator = new CallbackGenerator((ic) => initCount++, (sgc) => executeCount++, sourceOpt: "public class C { }"); var generator = new CallbackGenerator((ic) => initCount++, (sgc) => executeCount++, source: "public class C { }");
GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create<ISourceGenerator>(generator), ImmutableArray<AdditionalText>.Empty); GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create<ISourceGenerator>(generator), ImmutableArray<AdditionalText>.Empty);
driver = driver.RunFullGeneration(compilation, out var outputCompilation, out _); driver = driver.RunFullGeneration(compilation, out var outputCompilation, out _);
...@@ -266,7 +266,7 @@ class C { } ...@@ -266,7 +266,7 @@ class C { }
Assert.Single(outputCompilation.SyntaxTrees); Assert.Single(outputCompilation.SyntaxTrees);
// create an edit // create an edit
AdditionalFileAddedEdit edit = new AdditionalFileAddedEdit(new InMemoryAdditionalText("c:\\a\\file2.cs", "")); AdditionalFileAddedEdit edit = new AdditionalFileAddedEdit(new InMemoryAdditionalText("a\\file2.cs", ""));
driver = driver.WithPendingEdits(ImmutableArray.Create<PendingEdit>(edit)); driver = driver.WithPendingEdits(ImmutableArray.Create<PendingEdit>(edit));
// now try apply edits (which will fail) // now try apply edits (which will fail)
...@@ -297,7 +297,7 @@ class C { } ...@@ -297,7 +297,7 @@ class C { }
Assert.Single(outputCompilation.SyntaxTrees); Assert.Single(outputCompilation.SyntaxTrees);
// create an edit // create an edit
AdditionalFileAddedEdit edit = new AdditionalFileAddedEdit(new InMemoryAdditionalText("c:\\a\\file1.cs", "")); AdditionalFileAddedEdit edit = new AdditionalFileAddedEdit(new InMemoryAdditionalText("a\\file1.cs", ""));
driver = driver.WithPendingEdits(ImmutableArray.Create<PendingEdit>(edit)); driver = driver.WithPendingEdits(ImmutableArray.Create<PendingEdit>(edit));
// now try apply edits // now try apply edits
...@@ -322,14 +322,14 @@ class C { } ...@@ -322,14 +322,14 @@ class C { }
GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions,
generators: ImmutableArray.Create<ISourceGenerator>(testGenerator), generators: ImmutableArray.Create<ISourceGenerator>(testGenerator),
additionalTexts: ImmutableArray.Create<AdditionalText>(new InMemoryAdditionalText("c:\\a\\file1.cs", ""))); additionalTexts: ImmutableArray.Create<AdditionalText>(new InMemoryAdditionalText("a\\file1.cs", "")));
// run initial generation pass // run initial generation pass
driver = driver.RunFullGeneration(compilation, out var outputCompilation, out _); driver = driver.RunFullGeneration(compilation, out var outputCompilation, out _);
Assert.Equal(2, outputCompilation.SyntaxTrees.Count()); Assert.Equal(2, outputCompilation.SyntaxTrees.Count());
// create an edit // create an edit
AdditionalFileAddedEdit edit = new AdditionalFileAddedEdit(new InMemoryAdditionalText("c:\\a\\file2.cs", "")); AdditionalFileAddedEdit edit = new AdditionalFileAddedEdit(new InMemoryAdditionalText("a\\file2.cs", ""));
driver = driver.WithPendingEdits(ImmutableArray.Create<PendingEdit>(edit)); driver = driver.WithPendingEdits(ImmutableArray.Create<PendingEdit>(edit));
// now try apply edits // now try apply edits
...@@ -342,9 +342,9 @@ class C { } ...@@ -342,9 +342,9 @@ class C { }
Assert.Equal(3, outputCompilation.SyntaxTrees.Count()); Assert.Equal(3, outputCompilation.SyntaxTrees.Count());
// lets add multiple edits // lets add multiple edits
driver = driver.WithPendingEdits(ImmutableArray.Create<PendingEdit>(new AdditionalFileAddedEdit(new InMemoryAdditionalText("c:\\a\\file3.cs", "")), driver = driver.WithPendingEdits(ImmutableArray.Create<PendingEdit>(new AdditionalFileAddedEdit(new InMemoryAdditionalText("a\\file3.cs", "")),
new AdditionalFileAddedEdit(new InMemoryAdditionalText("c:\\a\\file4.cs", "")), new AdditionalFileAddedEdit(new InMemoryAdditionalText("a\\file4.cs", "")),
new AdditionalFileAddedEdit(new InMemoryAdditionalText("c:\\a\\file5.cs", "")))); new AdditionalFileAddedEdit(new InMemoryAdditionalText("a\\file5.cs", ""))));
// now try apply edits // now try apply edits
driver = driver.TryApplyEdits(compilation, out editedCompilation, out succeeded); driver = driver.TryApplyEdits(compilation, out editedCompilation, out succeeded);
Assert.True(succeeded); Assert.True(succeeded);
...@@ -364,11 +364,11 @@ class C { } ...@@ -364,11 +364,11 @@ class C { }
Assert.Single(compilation.SyntaxTrees); Assert.Single(compilation.SyntaxTrees);
AdditionalFileAddedGenerator testGenerator = new AdditionalFileAddedGenerator(); AdditionalFileAddedGenerator testGenerator = new AdditionalFileAddedGenerator();
var text = new InMemoryAdditionalText("c:\\a\\file1.cs", ""); var text = new InMemoryAdditionalText("a\\file1.cs", "");
GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions,
generators: ImmutableArray.Create<ISourceGenerator>(testGenerator), generators: ImmutableArray.Create<ISourceGenerator>(testGenerator),
additionalTexts: ImmutableArray.Create<AdditionalText>(new InMemoryAdditionalText("c:\\a\\file1.cs", ""))); additionalTexts: ImmutableArray.Create<AdditionalText>(new InMemoryAdditionalText("a\\file1.cs", "")));
driver = driver.RunFullGeneration(compilation, out var outputCompilation, out _); driver = driver.RunFullGeneration(compilation, out var outputCompilation, out _);
...@@ -384,7 +384,7 @@ class C { } ...@@ -384,7 +384,7 @@ class C { }
Assert.Equal(2, outputCompilation.SyntaxTrees.Count()); Assert.Equal(2, outputCompilation.SyntaxTrees.Count());
// create an edit // create an edit
AdditionalFileAddedEdit edit = new AdditionalFileAddedEdit(new InMemoryAdditionalText("c:\\a\\file2.cs", "")); AdditionalFileAddedEdit edit = new AdditionalFileAddedEdit(new InMemoryAdditionalText("a\\file2.cs", ""));
driver = driver.WithPendingEdits(ImmutableArray.Create<PendingEdit>(edit)); driver = driver.WithPendingEdits(ImmutableArray.Create<PendingEdit>(edit));
// now try apply edits // now try apply edits
...@@ -410,19 +410,19 @@ class C { } ...@@ -410,19 +410,19 @@ class C { }
Assert.Single(compilation.SyntaxTrees); Assert.Single(compilation.SyntaxTrees);
AdditionalFileAddedGenerator testGenerator = new AdditionalFileAddedGenerator(); AdditionalFileAddedGenerator testGenerator = new AdditionalFileAddedGenerator();
var text = new InMemoryAdditionalText("c:\\a\\file1.cs", ""); var text = new InMemoryAdditionalText("a\\file1.cs", "");
GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions,
generators: ImmutableArray.Create<ISourceGenerator>(testGenerator), generators: ImmutableArray.Create<ISourceGenerator>(testGenerator),
additionalTexts: ImmutableArray.Create<AdditionalText>(new InMemoryAdditionalText("c:\\a\\file1.cs", ""))); additionalTexts: ImmutableArray.Create<AdditionalText>(new InMemoryAdditionalText("a\\file1.cs", "")));
driver.RunFullGeneration(compilation, out var outputCompilation, out _); driver.RunFullGeneration(compilation, out var outputCompilation, out _);
Assert.Equal(2, outputCompilation.SyntaxTrees.Count()); Assert.Equal(2, outputCompilation.SyntaxTrees.Count());
// add multiple edits // add multiple edits
driver = driver.WithPendingEdits(ImmutableArray.Create<PendingEdit>(new AdditionalFileAddedEdit(new InMemoryAdditionalText("c:\\a\\file2.cs", "")), driver = driver.WithPendingEdits(ImmutableArray.Create<PendingEdit>(new AdditionalFileAddedEdit(new InMemoryAdditionalText("a\\file2.cs", "")),
new AdditionalFileAddedEdit(new InMemoryAdditionalText("c:\\a\\file3.cs", "")), new AdditionalFileAddedEdit(new InMemoryAdditionalText("a\\file3.cs", "")),
new AdditionalFileAddedEdit(new InMemoryAdditionalText("c:\\a\\file4.cs", "")))); new AdditionalFileAddedEdit(new InMemoryAdditionalText("a\\file4.cs", ""))));
// but just do a full generation (don't try apply) // but just do a full generation (don't try apply)
driver.RunFullGeneration(compilation, out outputCompilation, out _); driver.RunFullGeneration(compilation, out outputCompilation, out _);
...@@ -442,7 +442,7 @@ class C { } ...@@ -442,7 +442,7 @@ class C { }
Assert.Single(compilation.SyntaxTrees); Assert.Single(compilation.SyntaxTrees);
SingleFileTestGenerator testGenerator1 = new SingleFileTestGenerator("public class D { }"); SingleFileTestGenerator testGenerator1 = new SingleFileTestGenerator("public class D { }");
SingleFileTestGenerator testGenerator2 = new SingleFileTestGenerator("public class E { }"); SingleFileTestGenerator2 testGenerator2 = new SingleFileTestGenerator2("public class E { }");
GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions,
generators: ImmutableArray.Create<ISourceGenerator>(testGenerator1), generators: ImmutableArray.Create<ISourceGenerator>(testGenerator1),
...@@ -510,7 +510,7 @@ class C { } ...@@ -510,7 +510,7 @@ class C { }
Assert.Single(compilation.SyntaxTrees); Assert.Single(compilation.SyntaxTrees);
var exception = new InvalidOperationException("init error"); var exception = new InvalidOperationException("init error");
var generator = new CallbackGenerator((ic) => throw exception, (sgc) => { }, sourceOpt: "class D { }"); var generator = new CallbackGenerator((ic) => throw exception, (sgc) => { }, source: "class D { }");
GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create<ISourceGenerator>(generator), ImmutableArray<AdditionalText>.Empty); GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create<ISourceGenerator>(generator), ImmutableArray<AdditionalText>.Empty);
driver.RunFullGeneration(compilation, out var outputCompilation, out _); driver.RunFullGeneration(compilation, out var outputCompilation, out _);
...@@ -559,7 +559,7 @@ class C { } ...@@ -559,7 +559,7 @@ class C { }
var exception = new InvalidOperationException("generate error"); var exception = new InvalidOperationException("generate error");
var generator = new CallbackGenerator((ic) => { }, (sgc) => throw exception); var generator = new CallbackGenerator((ic) => { }, (sgc) => throw exception);
var generator2 = new CallbackGenerator((ic) => { }, (sgc) => { }, sourceOpt: "public class D { }"); var generator2 = new CallbackGenerator2((ic) => { }, (sgc) => { }, source: "public class D { }");
GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create<ISourceGenerator>(generator, generator2), ImmutableArray<AdditionalText>.Empty); GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create<ISourceGenerator>(generator, generator2), ImmutableArray<AdditionalText>.Empty);
driver.RunFullGeneration(compilation, out var outputCompilation, out var generatorDiagnostics); driver.RunFullGeneration(compilation, out var outputCompilation, out var generatorDiagnostics);
...@@ -595,7 +595,7 @@ class C ...@@ -595,7 +595,7 @@ class C
var exception = new InvalidOperationException("generate error"); var exception = new InvalidOperationException("generate error");
var generator = new CallbackGenerator((ic) => { }, (sgc) => throw exception, sourceOpt: "public class D { }"); var generator = new CallbackGenerator((ic) => { }, (sgc) => throw exception, source: "public class D { }");
GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create<ISourceGenerator>(generator), ImmutableArray<AdditionalText>.Empty); GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create<ISourceGenerator>(generator), ImmutableArray<AdditionalText>.Empty);
driver.RunFullGeneration(compilation, out var outputCompilation, out var generatorDiagnostics); driver.RunFullGeneration(compilation, out var outputCompilation, out var generatorDiagnostics);
...@@ -636,99 +636,62 @@ class C { } ...@@ -636,99 +636,62 @@ class C { }
Diagnostic("TG001").WithLocation(1, 1) Diagnostic("TG001").WithLocation(1, 1)
); );
} }
}
internal class SingleFileTestGenerator : ISourceGenerator
{
private readonly string _content;
private readonly string _hintName;
public SingleFileTestGenerator(string content, string hintName = "generatedFile")
{
_content = content;
_hintName = hintName;
}
public void Execute(SourceGeneratorContext context)
{
context.AdditionalSources.Add(this._hintName, SourceText.From(_content, Encoding.UTF8));
}
public void Initialize(InitializationContext context)
{
}
}
internal class CallbackGenerator : ISourceGenerator
{
private readonly Action<InitializationContext> _onInit;
private readonly Action<SourceGeneratorContext> _onExecute;
private readonly string _sourceOpt;
public CallbackGenerator(Action<InitializationContext> onInit, Action<SourceGeneratorContext> onExecute, string sourceOpt = "") [Fact]
public void Generator_HintName_MustBe_Unique()
{ {
_onInit = onInit; var source = @"
_onExecute = onExecute; class C { }
_sourceOpt = sourceOpt; ";
} var parseOptions = TestOptions.Regular;
Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions);
compilation.VerifyDiagnostics();
Assert.Single(compilation.SyntaxTrees);
public void Execute(SourceGeneratorContext context) var generator = new CallbackGenerator((ic) => { }, (sgc) =>
{
_onExecute(context);
if (!string.IsNullOrWhiteSpace(_sourceOpt))
{ {
context.AdditionalSources.Add("source.cs", SourceText.From(_sourceOpt, Encoding.UTF8)); sgc.AddSource("test", SourceText.From("public class D{}", Encoding.UTF8));
}
}
public void Initialize(InitializationContext context) => _onInit(context);
}
internal class AdditionalFileAddedGenerator : ISourceGenerator // the assert should swallow the exception, so we'll actually succesfully generate
{ Assert.Throws<ArgumentException>("hintName", () => sgc.AddSource("test", SourceText.From("public class D{}", Encoding.UTF8)));
public bool CanApplyChanges { get; set; } = true;
public void Execute(SourceGeneratorContext context) // also throws for <name> vs <name>.cs
{ Assert.Throws<ArgumentException>("hintName", () => sgc.AddSource("test.cs", SourceText.From("public class D{}", Encoding.UTF8)));
foreach (var file in context.AdditionalFiles) });
{
AddSourceForAdditionalFile(context.AdditionalSources, file);
}
}
public void Initialize(InitializationContext context) GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create<ISourceGenerator>(generator), ImmutableArray<AdditionalText>.Empty);
{ driver.RunFullGeneration(compilation, out var outputCompilation, out var generatorDiagnostics);
context.RegisterForAdditionalFileChanges(UpdateContext); outputCompilation.VerifyDiagnostics();
generatorDiagnostics.Verify();
Assert.Equal(2, outputCompilation.SyntaxTrees.Count());
} }
bool UpdateContext(EditContext context, AdditionalFileEdit edit) [Fact]
public void Generator_HintName_Is_Appended_With_GeneratorName()
{ {
if (edit is AdditionalFileAddedEdit add && CanApplyChanges) var source = @"
{ class C { }
AddSourceForAdditionalFile(context.AdditionalSources, add.AddedText); ";
return true; var parseOptions = TestOptions.Regular;
} Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions);
return false; compilation.VerifyDiagnostics();
} Assert.Single(compilation.SyntaxTrees);
private void AddSourceForAdditionalFile(AdditionalSourcesCollection sources, AdditionalText file) => sources.Add(GetGeneratedFileName(file.Path), SourceText.From("", Encoding.UTF8));
private string GetGeneratedFileName(string path) => $"{Path.GetFileNameWithoutExtension(path)}.generated"; var generator = new SingleFileTestGenerator("public class D {}", "source.cs");
} var generator2 = new SingleFileTestGenerator2("public class E {}", "source.cs");
internal class InMemoryAdditionalText : AdditionalText GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create<ISourceGenerator>(generator, generator2), ImmutableArray<AdditionalText>.Empty);
{ driver.RunFullGeneration(compilation, out var outputCompilation, out var generatorDiagnostics);
private readonly SourceText _content; outputCompilation.VerifyDiagnostics();
generatorDiagnostics.Verify();
Assert.Equal(3, outputCompilation.SyntaxTrees.Count());
public InMemoryAdditionalText(string path, string content) var filePaths = outputCompilation.SyntaxTrees.Skip(1).Select(t => t.FilePath).ToArray();
{ Assert.Equal(new[] {
Path = path; $"{generator.GetType().Module.ModuleVersionId}_{generator.GetType().FullName}_source.cs",
_content = SourceText.From(content); $"{generator2.GetType().Module.ModuleVersionId}_{generator2.GetType().FullName}_source.cs"
}, filePaths);
} }
public override string Path { get; }
public override SourceText GetText(CancellationToken cancellationToken = default) => _content;
} }
} }
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System;
using System.IO;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.CSharp.Semantic.UnitTests.SourceGeneration
{
internal class SingleFileTestGenerator : ISourceGenerator
{
private readonly string _content;
private readonly string _hintName;
public SingleFileTestGenerator(string content, string hintName = "generatedFile")
{
_content = content;
_hintName = hintName;
}
public void Execute(SourceGeneratorContext context)
{
context.AdditionalSources.Add(this._hintName, SourceText.From(_content, Encoding.UTF8));
}
public void Initialize(InitializationContext context)
{
}
}
internal class SingleFileTestGenerator2 : SingleFileTestGenerator
{
public SingleFileTestGenerator2(string content, string hintName = "generatedFile") : base(content, hintName)
{
}
}
internal class CallbackGenerator : ISourceGenerator
{
private readonly Action<InitializationContext> _onInit;
private readonly Action<SourceGeneratorContext> _onExecute;
private readonly string? _source;
public CallbackGenerator(Action<InitializationContext> onInit, Action<SourceGeneratorContext> onExecute, string? source = "")
{
_onInit = onInit;
_onExecute = onExecute;
_source = source;
}
public void Execute(SourceGeneratorContext context)
{
_onExecute(context);
if (!string.IsNullOrWhiteSpace(_source))
{
context.AdditionalSources.Add("source.cs", SourceText.From(_source, Encoding.UTF8));
}
}
public void Initialize(InitializationContext context) => _onInit(context);
}
internal class CallbackGenerator2 : CallbackGenerator
{
public CallbackGenerator2(Action<InitializationContext> onInit, Action<SourceGeneratorContext> onExecute, string? source = "") : base(onInit, onExecute, source)
{
}
}
internal class AdditionalFileAddedGenerator : ISourceGenerator
{
public bool CanApplyChanges { get; set; } = true;
public void Execute(SourceGeneratorContext context)
{
foreach (var file in context.AdditionalFiles)
{
AddSourceForAdditionalFile(context.AdditionalSources, file);
}
}
public void Initialize(InitializationContext context)
{
context.RegisterForAdditionalFileChanges(UpdateContext);
}
bool UpdateContext(EditContext context, AdditionalFileEdit edit)
{
if (edit is AdditionalFileAddedEdit add && CanApplyChanges)
{
AddSourceForAdditionalFile(context.AdditionalSources, add.AddedText);
return true;
}
return false;
}
private void AddSourceForAdditionalFile(AdditionalSourcesCollection sources, AdditionalText file) => sources.Add(GetGeneratedFileName(file.Path), SourceText.From("", Encoding.UTF8));
private string GetGeneratedFileName(string path) => $"{Path.GetFileNameWithoutExtension(path.Replace('\\', Path.DirectorySeparatorChar))}.generated";
}
internal class InMemoryAdditionalText : AdditionalText
{
private readonly SourceText _content;
public InMemoryAdditionalText(string path, string content)
{
Path = path;
_content = SourceText.From(content, Encoding.UTF8);
}
public override string Path { get; }
public override SourceText GetText(CancellationToken cancellationToken = default) => _content;
}
}
...@@ -689,6 +689,7 @@ ...@@ -689,6 +689,7 @@
</data> </data>
<data name="Single_type_per_generator_0" xml:space="preserve"> <data name="Single_type_per_generator_0" xml:space="preserve">
<value>Only a single {0} can be registered per generator.</value> <value>Only a single {0} can be registered per generator.</value>
<comment>{0}: type name</comment>
</data> </data>
<data name="WRN_MultipleGlobalAnalyzerKeys" xml:space="preserve"> <data name="WRN_MultipleGlobalAnalyzerKeys" xml:space="preserve">
<value>Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</value> <value>Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</value>
...@@ -696,4 +697,11 @@ ...@@ -696,4 +697,11 @@
<data name="WRN_MultipleGlobalAnalyzerKeys_Title" xml:space="preserve"> <data name="WRN_MultipleGlobalAnalyzerKeys_Title" xml:space="preserve">
<value>Multiple global analyzer config files set the same key. It has been unset.</value> <value>Multiple global analyzer config files set the same key. It has been unset.</value>
</data> </data>
<data name="HintNameUniquePerGenerator" xml:space="preserve">
<value>The hintName of the added source file must be unique within a generator.</value>
</data>
<data name="HintNameInvalidChar" xml:space="preserve">
<value>The hintName contains an invalid character '{0}' at position {1}.</value>
<comment>{0}: the invalid character, {1} the position it occured at</comment>
</data>
</root> </root>
\ No newline at end of file
...@@ -791,13 +791,15 @@ private int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancellat ...@@ -791,13 +791,15 @@ private int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancellat
// We pass it to the generators, which will realize any symbols they require. // We pass it to the generators, which will realize any symbols they require.
compilation = RunGenerators(compilation, Arguments.ParseOptions, generators, additionalTexts, diagnostics); compilation = RunGenerators(compilation, Arguments.ParseOptions, generators, additionalTexts, diagnostics);
// https://github.com/dotnet/roslyn/issues/44087 if (generators.Length > 0)
// Workaround by getting options for any generated trees that were produced.
// In the future we'll want to apply the config set rules at parse time, return the options, and add them in here
if (!sourceFileAnalyzerConfigOptions.IsDefault && generators.Length > 0)
{ {
var generatedOptions = compilation.SyntaxTrees.Skip(sourceFileAnalyzerConfigOptions.Length).Select(f => analyzerConfigSet.GetOptionsForSourcePath(f.FilePath)); var generatedSyntaxTrees = compilation.SyntaxTrees.Skip(Arguments.SourceFiles.Length);
sourceFileAnalyzerConfigOptions = sourceFileAnalyzerConfigOptions.AddRange(generatedOptions); if (!sourceFileAnalyzerConfigOptions.IsDefault)
{
sourceFileAnalyzerConfigOptions = sourceFileAnalyzerConfigOptions.AddRange(generatedSyntaxTrees.Select(f => analyzerConfigSet.GetOptionsForSourcePath(f.FilePath)));
}
embeddedTexts = embeddedTexts.AddRange(generatedSyntaxTrees.Select(t => EmbeddedText.FromSource(t.FilePath, t.GetText())));
} }
CompileAndEmit( CompileAndEmit(
......
...@@ -3,7 +3,9 @@ ...@@ -3,7 +3,9 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.IO;
using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities; using Roslyn.Utilities;
...@@ -13,44 +15,96 @@ namespace Microsoft.CodeAnalysis ...@@ -13,44 +15,96 @@ namespace Microsoft.CodeAnalysis
{ {
internal sealed class AdditionalSourcesCollection internal sealed class AdditionalSourcesCollection
{ {
private readonly PooledDictionary<string, SourceText> _sourcesAdded; private readonly ArrayBuilder<GeneratedSourceText> _sourcesAdded;
private const StringComparison _hintNameComparison = StringComparison.OrdinalIgnoreCase;
private static readonly StringComparer s_hintNameComparer = StringComparer.OrdinalIgnoreCase;
internal AdditionalSourcesCollection() internal AdditionalSourcesCollection()
{ {
_sourcesAdded = PooledDictionary<string, SourceText>.GetInstance(); _sourcesAdded = ArrayBuilder<GeneratedSourceText>.GetInstance();
} }
internal AdditionalSourcesCollection(ImmutableArray<GeneratedSourceText> sources) internal AdditionalSourcesCollection(ImmutableArray<GeneratedSourceText> existingSources)
: this() : this()
{ {
foreach (var source in sources) _sourcesAdded.AddRange(existingSources);
{
_sourcesAdded.Add(source.HintName, source.Text);
}
} }
public void Add(string hintName, SourceText source) public void Add(string hintName, SourceText source)
{ {
_sourcesAdded.Add(hintName, source); if (string.IsNullOrWhiteSpace(hintName))
{
throw new ArgumentNullException(nameof(hintName));
}
// allow any identifier character or [.,-_ ()[]{}]
for (int i = 0; i < hintName.Length; i++)
{
char c = hintName[i];
if (!UnicodeCharacterUtilities.IsIdentifierPartCharacter(c)
&& c != '.'
&& c != ','
&& c != '-'
&& c != '_'
&& c != ' '
&& c != '('
&& c != ')'
&& c != '['
&& c != ']'
&& c != '{'
&& c != '}')
{
throw new ArgumentException(string.Format(CodeAnalysisResources.HintNameInvalidChar, c, i), nameof(hintName));
}
}
hintName = AppendExtensionIfRequired(hintName);
if (this.Contains(hintName))
{
throw new ArgumentException(CodeAnalysisResources.HintNameUniquePerGenerator, nameof(hintName));
}
_sourcesAdded.Add(new GeneratedSourceText(hintName, source));
} }
public void RemoveSource(string hintName) public void RemoveSource(string hintName)
{ {
_sourcesAdded.Remove(hintName); hintName = AppendExtensionIfRequired(hintName);
for (int i = 0; i < _sourcesAdded.Count; i++)
{
if (s_hintNameComparer.Equals(_sourcesAdded[i].HintName, hintName))
{
_sourcesAdded.RemoveAt(i);
return;
}
}
}
public bool Contains(string hintName)
{
hintName = AppendExtensionIfRequired(hintName);
for (int i = 0; i < _sourcesAdded.Count; i++)
{
if (s_hintNameComparer.Equals(_sourcesAdded[i].HintName, hintName))
{
return true;
}
}
return false;
} }
public bool Contains(string hintName) => _sourcesAdded.ContainsKey(hintName); internal ImmutableArray<GeneratedSourceText> ToImmutableAndFree() => _sourcesAdded.ToImmutableAndFree();
internal ImmutableArray<GeneratedSourceText> ToImmutableAndFree() private static string AppendExtensionIfRequired(string hintName)
{ {
// https://github.com/dotnet/roslyn/issues/42627: This needs to be consistently ordered if (!hintName.EndsWith(".cs", _hintNameComparison))
ArrayBuilder<GeneratedSourceText> builder = ArrayBuilder<GeneratedSourceText>.GetInstance();
foreach (var (hintName, sourceText) in _sourcesAdded)
{ {
builder.Add(new GeneratedSourceText(hintName, sourceText)); hintName = string.Concat(hintName, ".cs");
} }
_sourcesAdded.Free();
return builder.ToImmutableAndFree(); return hintName;
} }
} }
} }
...@@ -13,8 +13,7 @@ namespace Microsoft.CodeAnalysis ...@@ -13,8 +13,7 @@ namespace Microsoft.CodeAnalysis
/// <summary> /// <summary>
/// A source text created by an <see cref="ISourceGenerator"/> /// A source text created by an <see cref="ISourceGenerator"/>
/// </summary> /// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0016:Add public types and members to the declared API", Justification = "In Progress")] internal readonly struct GeneratedSourceText
public readonly struct GeneratedSourceText
{ {
public SourceText Text { get; } public SourceText Text { get; }
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
using System; using System;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading; using System.Threading;
using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.PooledObjects;
...@@ -35,12 +37,13 @@ public abstract class GeneratorDriver ...@@ -35,12 +37,13 @@ public abstract class GeneratorDriver
internal GeneratorDriver(GeneratorDriverState state) internal GeneratorDriver(GeneratorDriverState state)
{ {
Debug.Assert(state.Generators.GroupBy(s => s.GetType()).Count() == state.Generators.Length); // ensure we don't have duplicate generator types
_state = state; _state = state;
} }
internal GeneratorDriver(ParseOptions parseOptions, ImmutableArray<ISourceGenerator> generators, ImmutableArray<AdditionalText> additionalTexts) internal GeneratorDriver(ParseOptions parseOptions, ImmutableArray<ISourceGenerator> generators, ImmutableArray<AdditionalText> additionalTexts)
{ {
_state = new GeneratorDriverState(parseOptions, generators, additionalTexts, ImmutableDictionary<ISourceGenerator, GeneratorState>.Empty, ImmutableArray<PendingEdit>.Empty, editsFailed: true); _state = new GeneratorDriverState(parseOptions, generators, additionalTexts, ImmutableArray.Create(new GeneratorState[generators.Length]), ImmutableArray<PendingEdit>.Empty, editsFailed: true);
} }
public GeneratorDriver RunFullGeneration(Compilation compilation, out Compilation outputCompilation, out ImmutableArray<Diagnostic> diagnostics, CancellationToken cancellationToken = default) public GeneratorDriver RunFullGeneration(Compilation compilation, out Compilation outputCompilation, out ImmutableArray<Diagnostic> diagnostics, CancellationToken cancellationToken = default)
...@@ -55,22 +58,21 @@ public GeneratorDriver RunFullGeneration(Compilation compilation, out Compilatio ...@@ -55,22 +58,21 @@ public GeneratorDriver RunFullGeneration(Compilation compilation, out Compilatio
// run the actual generation // run the actual generation
var state = StateWithPendingEditsApplied(_state); var state = StateWithPendingEditsApplied(_state);
var stateBuilder = PooledDictionary<ISourceGenerator, GeneratorState>.GetInstance(); var stateBuilder = ArrayBuilder<GeneratorState>.GetInstance();
var receivers = PooledDictionary<ISourceGenerator, ISyntaxReceiver>.GetInstance(); var receivers = PooledDictionary<ISourceGenerator, ISyntaxReceiver>.GetInstance();
var diagnosticsBag = new DiagnosticBag(); var diagnosticsBag = new DiagnosticBag();
foreach (var generator in state.Generators) for (int i = 0; i < state.Generators.Length; i++)
{ {
var generator = state.Generators[i];
var generatorState = state.GeneratorStates[i];
// initialize the generator if needed // initialize the generator if needed
if (!state.GeneratorStates.TryGetValue(generator, out GeneratorState generatorState)) if (!generatorState.Info.Initialized)
{ {
generatorState = InitializeGenerator(generator, diagnosticsBag, cancellationToken); generatorState = InitializeGenerator(generator, diagnosticsBag, cancellationToken);
} }
stateBuilder.Add(generatorState);
if (generatorState.Info.Initialized)
{
stateBuilder.Add(generator, generatorState);
}
// create the syntax receiver if requested // create the syntax receiver if requested
if (generatorState.Info.SyntaxReceiverCreator is object) if (generatorState.Info.SyntaxReceiverCreator is object)
...@@ -91,22 +93,30 @@ public GeneratorDriver RunFullGeneration(Compilation compilation, out Compilatio ...@@ -91,22 +93,30 @@ public GeneratorDriver RunFullGeneration(Compilation compilation, out Compilatio
} }
// https://github.com/dotnet/roslyn/issues/42629: should be possible to parallelize this // https://github.com/dotnet/roslyn/issues/42629: should be possible to parallelize this
foreach (var (generator, generatorState) in stateBuilder.ToImmutableArray()) for (int i = 0; i < state.Generators.Length; i++)
{ {
var generator = state.Generators[i];
var generatorState = stateBuilder[i];
try try
{ {
// don't try and generate if initialization failed
if (!generatorState.Info.Initialized)
{
continue;
}
// we create a new context for each run of the generator. We'll never re-use existing state, only replace anything we have // we create a new context for each run of the generator. We'll never re-use existing state, only replace anything we have
_ = receivers.TryGetValue(generator, out var syntaxReceiverOpt); _ = receivers.TryGetValue(generator, out var syntaxReceiverOpt);
var context = new SourceGeneratorContext(compilation, state.AdditionalTexts.NullToEmpty(), syntaxReceiverOpt, diagnosticsBag); var context = new SourceGeneratorContext(compilation, state.AdditionalTexts.NullToEmpty(), syntaxReceiverOpt, diagnosticsBag);
generator.Execute(context); generator.Execute(context);
stateBuilder[generator] = generatorState.WithSources(ParseAdditionalSources(context.AdditionalSources.ToImmutableAndFree(), cancellationToken)); stateBuilder[i] = generatorState.WithSources(ParseAdditionalSources(generator, context.AdditionalSources.ToImmutableAndFree(), cancellationToken));
} }
catch catch
{ {
diagnosticsBag.Add(Diagnostic.Create(MessageProvider, MessageProvider.WRN_GeneratorFailedDuringGeneration, generator.GetType().Name)); diagnosticsBag.Add(Diagnostic.Create(MessageProvider, MessageProvider.WRN_GeneratorFailedDuringGeneration, generator.GetType().Name));
} }
} }
state = state.With(generatorStates: stateBuilder.ToImmutableDictionaryAndFree()); state = state.With(generatorStates: stateBuilder.ToImmutableAndFree());
diagnostics = diagnosticsBag.ToReadOnlyAndFree(); diagnostics = diagnosticsBag.ToReadOnlyAndFree();
// build the final state, and return // build the final state, and return
...@@ -146,14 +156,25 @@ public GeneratorDriver TryApplyEdits(Compilation compilation, out Compilation ou ...@@ -146,14 +156,25 @@ public GeneratorDriver TryApplyEdits(Compilation compilation, out Compilation ou
public GeneratorDriver AddGenerators(ImmutableArray<ISourceGenerator> generators) public GeneratorDriver AddGenerators(ImmutableArray<ISourceGenerator> generators)
{ {
// set editsFailed true, as we won't be able to apply edits with a new generator // set editsFailed true, as we won't be able to apply edits with a new generator
var newState = _state.With(generators: _state.Generators.AddRange(generators), editsFailed: true); var newState = _state.With(generators: _state.Generators.AddRange(generators), generatorStates: _state.GeneratorStates.AddRange(new GeneratorState[generators.Length]), editsFailed: true);
return FromState(newState); return FromState(newState);
} }
public GeneratorDriver RemoveGenerators(ImmutableArray<ISourceGenerator> generators) public GeneratorDriver RemoveGenerators(ImmutableArray<ISourceGenerator> generators)
{ {
var newState = _state.With(generators: _state.Generators.RemoveRange(generators), generatorStates: _state.GeneratorStates.RemoveRange(generators)); var newGenerators = _state.Generators;
return FromState(newState); var newStates = _state.GeneratorStates;
for (int i = 0; i < newGenerators.Length; i++)
{
if (generators.Contains(newGenerators[i]))
{
newGenerators = newGenerators.RemoveAt(i);
newStates = newStates.RemoveAt(i);
i--;
}
}
return FromState(_state.With(generators: newGenerators, generatorStates: newStates));
} }
public GeneratorDriver AddAdditionalTexts(ImmutableArray<AdditionalText> additionalTexts) public GeneratorDriver AddAdditionalTexts(ImmutableArray<AdditionalText> additionalTexts)
...@@ -175,8 +196,10 @@ private GeneratorDriverState ApplyPartialEdit(GeneratorDriverState state, Pendin ...@@ -175,8 +196,10 @@ private GeneratorDriverState ApplyPartialEdit(GeneratorDriverState state, Pendin
// see if any generators accept this particular edit // see if any generators accept this particular edit
var stateBuilder = PooledDictionary<ISourceGenerator, GeneratorState>.GetInstance(); var stateBuilder = PooledDictionary<ISourceGenerator, GeneratorState>.GetInstance();
foreach (var (generator, generatorState) in state.GeneratorStates) for (int i = 0; i < initialState.Generators.Length; i++)
{ {
var generator = initialState.Generators[i];
var generatorState = initialState.GeneratorStates[i];
if (edit.AcceptedBy(generatorState.Info)) if (edit.AcceptedBy(generatorState.Info))
{ {
// attempt to apply the edit // attempt to apply the edit
...@@ -190,7 +213,7 @@ private GeneratorDriverState ApplyPartialEdit(GeneratorDriverState state, Pendin ...@@ -190,7 +213,7 @@ private GeneratorDriverState ApplyPartialEdit(GeneratorDriverState state, Pendin
} }
// update the state with the new edits // update the state with the new edits
state = state.With(generatorStates: state.GeneratorStates.SetItem(generator, generatorState.WithSources(ParseAdditionalSources(context.AdditionalSources.ToImmutableAndFree(), cancellationToken)))); state = state.With(generatorStates: state.GeneratorStates.SetItem(i, generatorState.WithSources(ParseAdditionalSources(generator, context.AdditionalSources.ToImmutableAndFree(), cancellationToken))));
} }
} }
state = edit.Commit(state); state = edit.Commit(state);
...@@ -230,7 +253,7 @@ private GeneratorState InitializeGenerator(ISourceGenerator generator, Diagnosti ...@@ -230,7 +253,7 @@ private GeneratorState InitializeGenerator(ISourceGenerator generator, Diagnosti
private static Compilation RemoveGeneratedSyntaxTrees(GeneratorDriverState state, Compilation compilation) private static Compilation RemoveGeneratedSyntaxTrees(GeneratorDriverState state, Compilation compilation)
{ {
ArrayBuilder<SyntaxTree> trees = ArrayBuilder<SyntaxTree>.GetInstance(); ArrayBuilder<SyntaxTree> trees = ArrayBuilder<SyntaxTree>.GetInstance();
foreach (var (_, generatorState) in state.GeneratorStates) foreach (var generatorState in state.GeneratorStates)
{ {
foreach (var (_, tree) in generatorState.Sources) foreach (var (_, tree) in generatorState.Sources)
{ {
...@@ -246,12 +269,14 @@ private static Compilation RemoveGeneratedSyntaxTrees(GeneratorDriverState state ...@@ -246,12 +269,14 @@ private static Compilation RemoveGeneratedSyntaxTrees(GeneratorDriverState state
return comp; return comp;
} }
private ImmutableDictionary<GeneratedSourceText, SyntaxTree> ParseAdditionalSources(ImmutableArray<GeneratedSourceText> generatedSources, CancellationToken cancellationToken) private ImmutableDictionary<GeneratedSourceText, SyntaxTree> ParseAdditionalSources(ISourceGenerator generator, ImmutableArray<GeneratedSourceText> generatedSources, CancellationToken cancellationToken)
{ {
var trees = PooledDictionary<GeneratedSourceText, SyntaxTree>.GetInstance(); var trees = PooledDictionary<GeneratedSourceText, SyntaxTree>.GetInstance();
var type = generator.GetType();
var prefix = $"{type.Module.ModuleVersionId}_{type.FullName}";
foreach (var source in generatedSources) foreach (var source in generatedSources)
{ {
trees.Add(source, ParseGeneratedSourceText(source, cancellationToken)); trees.Add(source, ParseGeneratedSourceText(source, $"{prefix}_{source.HintName}", cancellationToken));
} }
return trees.ToImmutableDictionaryAndFree(); return trees.ToImmutableDictionaryAndFree();
} }
...@@ -259,7 +284,7 @@ private static Compilation RemoveGeneratedSyntaxTrees(GeneratorDriverState state ...@@ -259,7 +284,7 @@ private static Compilation RemoveGeneratedSyntaxTrees(GeneratorDriverState state
private GeneratorDriver BuildFinalCompilation(Compilation compilation, out Compilation outputCompilation, GeneratorDriverState state, CancellationToken cancellationToken) private GeneratorDriver BuildFinalCompilation(Compilation compilation, out Compilation outputCompilation, GeneratorDriverState state, CancellationToken cancellationToken)
{ {
ArrayBuilder<SyntaxTree> trees = ArrayBuilder<SyntaxTree>.GetInstance(); ArrayBuilder<SyntaxTree> trees = ArrayBuilder<SyntaxTree>.GetInstance();
foreach (var (generator, generatorState) in state.GeneratorStates) foreach (var generatorState in state.GeneratorStates)
{ {
trees.AddRange(generatorState.Sources.Values); trees.AddRange(generatorState.Sources.Values);
} }
...@@ -275,6 +300,6 @@ private GeneratorDriver BuildFinalCompilation(Compilation compilation, out Compi ...@@ -275,6 +300,6 @@ private GeneratorDriver BuildFinalCompilation(Compilation compilation, out Compi
internal abstract GeneratorDriver FromState(GeneratorDriverState state); internal abstract GeneratorDriver FromState(GeneratorDriverState state);
internal abstract SyntaxTree ParseGeneratedSourceText(GeneratedSourceText input, CancellationToken cancellationToken); internal abstract SyntaxTree ParseGeneratedSourceText(GeneratedSourceText input, string fileName, CancellationToken cancellationToken);
} }
} }
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics;
using System.Text; using System.Text;
#nullable enable #nullable enable
...@@ -15,7 +16,7 @@ namespace Microsoft.CodeAnalysis ...@@ -15,7 +16,7 @@ namespace Microsoft.CodeAnalysis
internal GeneratorDriverState(ParseOptions parseOptions, internal GeneratorDriverState(ParseOptions parseOptions,
ImmutableArray<ISourceGenerator> generators, ImmutableArray<ISourceGenerator> generators,
ImmutableArray<AdditionalText> additionalTexts, ImmutableArray<AdditionalText> additionalTexts,
ImmutableDictionary<ISourceGenerator, GeneratorState> generatorStates, ImmutableArray<GeneratorState> generatorStates,
ImmutableArray<PendingEdit> edits, ImmutableArray<PendingEdit> edits,
bool editsFailed) bool editsFailed)
{ {
...@@ -25,6 +26,8 @@ namespace Microsoft.CodeAnalysis ...@@ -25,6 +26,8 @@ namespace Microsoft.CodeAnalysis
Edits = edits; Edits = edits;
ParseOptions = parseOptions; ParseOptions = parseOptions;
EditsFailed = editsFailed; EditsFailed = editsFailed;
Debug.Assert(Generators.Length == GeneratorStates.Length);
} }
/// <summary> /// <summary>
...@@ -40,10 +43,10 @@ namespace Microsoft.CodeAnalysis ...@@ -40,10 +43,10 @@ namespace Microsoft.CodeAnalysis
/// The last run state of each generator, by the generator that created it /// The last run state of each generator, by the generator that created it
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// If the driver this state belongs to has yet to perform generation, this will be empty. /// There will be a 1-to-1 mapping for each generator. If a generator has yet to
/// After generation there *should* be a 1-to-1 mapping for each generator, unless that generator failed to initialize. /// be initialized or failed during initialization it's state will be <c>default(GeneratorState)</c>
/// </remarks> /// </remarks>
internal readonly ImmutableDictionary<ISourceGenerator, GeneratorState> GeneratorStates; internal readonly ImmutableArray<GeneratorState> GeneratorStates;
/// <summary> /// <summary>
/// The set of <see cref="AdditionalText"/>s available to source generators during a run /// The set of <see cref="AdditionalText"/>s available to source generators during a run
...@@ -68,7 +71,7 @@ namespace Microsoft.CodeAnalysis ...@@ -68,7 +71,7 @@ namespace Microsoft.CodeAnalysis
internal GeneratorDriverState With( internal GeneratorDriverState With(
ParseOptions? parseOptions = null, ParseOptions? parseOptions = null,
ImmutableArray<ISourceGenerator>? generators = null, ImmutableArray<ISourceGenerator>? generators = null,
ImmutableDictionary<ISourceGenerator, GeneratorState>? generatorStates = null, ImmutableArray<GeneratorState>? generatorStates = null,
ImmutableArray<AdditionalText>? additionalTexts = null, ImmutableArray<AdditionalText>? additionalTexts = null,
ImmutableArray<PendingEdit>? edits = null, ImmutableArray<PendingEdit>? edits = null,
bool? editsFailed = null) bool? editsFailed = null)
......
...@@ -3,14 +3,9 @@ ...@@ -3,14 +3,9 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Text;
using System.Threading; using System.Threading;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
#nullable enable #nullable enable
namespace Microsoft.CodeAnalysis namespace Microsoft.CodeAnalysis
...@@ -59,8 +54,20 @@ internal SourceGeneratorContext(Compilation compilation, ImmutableArray<Addition ...@@ -59,8 +54,20 @@ internal SourceGeneratorContext(Compilation compilation, ImmutableArray<Addition
internal AdditionalSourcesCollection AdditionalSources { get; } internal AdditionalSourcesCollection AdditionalSources { get; }
/// <summary>
/// Adds a <see cref="SourceText"/> to the compilation
/// </summary>
/// <param name="hintName">An identifier that can be used to reference this source text, must be unique within this generator</param>
/// <param name="sourceText">The <see cref="SourceText"/> to add to the compilation</param>
public void AddSource(string hintName, SourceText sourceText) => AdditionalSources.Add(hintName, sourceText); public void AddSource(string hintName, SourceText sourceText) => AdditionalSources.Add(hintName, sourceText);
/// <summary>
/// Adds a <see cref="Diagnostic"/> to the users compilation
/// </summary>
/// <param name="diagnostic">The diagnostic that should be added to the compilation</param>
/// <remarks>
/// The severity of the diagnostic may cause the compilation to fail, depending on the <see cref="Compilation"/> settings.
/// </remarks>
public void ReportDiagnostic(Diagnostic diagnostic) => _diagnostics.Add(diagnostic); public void ReportDiagnostic(Diagnostic diagnostic) => _diagnostics.Add(diagnostic);
} }
......
...@@ -29,6 +29,16 @@ ...@@ -29,6 +29,16 @@
<target state="translated">Pokud chcete tento analyzátor zakázat, potlačte následující diagnostiku: {0}</target> <target state="translated">Pokud chcete tento analyzátor zakázat, potlačte následující diagnostiku: {0}</target>
<note>{0}: Comma-separated list of diagnostic IDs</note> <note>{0}: Comma-separated list of diagnostic IDs</note>
</trans-unit> </trans-unit>
<trans-unit id="HintNameInvalidChar">
<source>The hintName contains an invalid character '{0}' at position {1}.</source>
<target state="new">The hintName contains an invalid character '{0}' at position {1}.</target>
<note>{0}: the invalid character, {1} the position it occured at</note>
</trans-unit>
<trans-unit id="HintNameUniquePerGenerator">
<source>The hintName of the added source file must be unique within a generator.</source>
<target state="new">The hintName of the added source file must be unique within a generator.</target>
<note />
</trans-unit>
<trans-unit id="InvalidDiagnosticSuppressionReported"> <trans-unit id="InvalidDiagnosticSuppressionReported">
<source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source> <source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source>
<target state="translated">Potlačené ID diagnostiky {0} neodpovídá potlačitelnému ID {1} pro daný popisovač potlačení.</target> <target state="translated">Potlačené ID diagnostiky {0} neodpovídá potlačitelnému ID {1} pro daný popisovač potlačení.</target>
...@@ -96,8 +106,8 @@ ...@@ -96,8 +106,8 @@
</trans-unit> </trans-unit>
<trans-unit id="Single_type_per_generator_0"> <trans-unit id="Single_type_per_generator_0">
<source>Only a single {0} can be registered per generator.</source> <source>Only a single {0} can be registered per generator.</source>
<target state="translated">Na jeden generátor je možné zaregistrovat jen jedno {0}.</target> <target state="needs-review-translation">Na jeden generátor je možné zaregistrovat jen jedno {0}.</target>
<note /> <note>{0}: type name</note>
</trans-unit> </trans-unit>
<trans-unit id="SupportedDiagnosticsHasNullDescriptor"> <trans-unit id="SupportedDiagnosticsHasNullDescriptor">
<source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source> <source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source>
......
...@@ -29,6 +29,16 @@ ...@@ -29,6 +29,16 @@
<target state="translated">Unterdrücken Sie die folgende Diagnose, um dieses Analysetool zu deaktivieren: {0}</target> <target state="translated">Unterdrücken Sie die folgende Diagnose, um dieses Analysetool zu deaktivieren: {0}</target>
<note>{0}: Comma-separated list of diagnostic IDs</note> <note>{0}: Comma-separated list of diagnostic IDs</note>
</trans-unit> </trans-unit>
<trans-unit id="HintNameInvalidChar">
<source>The hintName contains an invalid character '{0}' at position {1}.</source>
<target state="new">The hintName contains an invalid character '{0}' at position {1}.</target>
<note>{0}: the invalid character, {1} the position it occured at</note>
</trans-unit>
<trans-unit id="HintNameUniquePerGenerator">
<source>The hintName of the added source file must be unique within a generator.</source>
<target state="new">The hintName of the added source file must be unique within a generator.</target>
<note />
</trans-unit>
<trans-unit id="InvalidDiagnosticSuppressionReported"> <trans-unit id="InvalidDiagnosticSuppressionReported">
<source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source> <source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source>
<target state="translated">Die unterdrückte Diagnose-ID "{0}" entspricht nicht der unterdrückbaren ID "{1}" für den angegebenen Deskriptor zur Unterdrückung.</target> <target state="translated">Die unterdrückte Diagnose-ID "{0}" entspricht nicht der unterdrückbaren ID "{1}" für den angegebenen Deskriptor zur Unterdrückung.</target>
...@@ -96,8 +106,8 @@ ...@@ -96,8 +106,8 @@
</trans-unit> </trans-unit>
<trans-unit id="Single_type_per_generator_0"> <trans-unit id="Single_type_per_generator_0">
<source>Only a single {0} can be registered per generator.</source> <source>Only a single {0} can be registered per generator.</source>
<target state="translated">Pro Generator kann nur ein einzelner {0} registriert werden.</target> <target state="needs-review-translation">Pro Generator kann nur ein einzelner {0} registriert werden.</target>
<note /> <note>{0}: type name</note>
</trans-unit> </trans-unit>
<trans-unit id="SupportedDiagnosticsHasNullDescriptor"> <trans-unit id="SupportedDiagnosticsHasNullDescriptor">
<source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source> <source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source>
......
...@@ -29,6 +29,16 @@ ...@@ -29,6 +29,16 @@
<target state="translated">Suprima el diagnóstico siguiente para deshabilitar este analizador: {0}</target> <target state="translated">Suprima el diagnóstico siguiente para deshabilitar este analizador: {0}</target>
<note>{0}: Comma-separated list of diagnostic IDs</note> <note>{0}: Comma-separated list of diagnostic IDs</note>
</trans-unit> </trans-unit>
<trans-unit id="HintNameInvalidChar">
<source>The hintName contains an invalid character '{0}' at position {1}.</source>
<target state="new">The hintName contains an invalid character '{0}' at position {1}.</target>
<note>{0}: the invalid character, {1} the position it occured at</note>
</trans-unit>
<trans-unit id="HintNameUniquePerGenerator">
<source>The hintName of the added source file must be unique within a generator.</source>
<target state="new">The hintName of the added source file must be unique within a generator.</target>
<note />
</trans-unit>
<trans-unit id="InvalidDiagnosticSuppressionReported"> <trans-unit id="InvalidDiagnosticSuppressionReported">
<source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source> <source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source>
<target state="translated">El id. de diagnóstico "{0}" suprimido no coincide con el id. "{1}" que se puede suprimir para el descriptor de supresión dado.</target> <target state="translated">El id. de diagnóstico "{0}" suprimido no coincide con el id. "{1}" que se puede suprimir para el descriptor de supresión dado.</target>
...@@ -96,8 +106,8 @@ ...@@ -96,8 +106,8 @@
</trans-unit> </trans-unit>
<trans-unit id="Single_type_per_generator_0"> <trans-unit id="Single_type_per_generator_0">
<source>Only a single {0} can be registered per generator.</source> <source>Only a single {0} can be registered per generator.</source>
<target state="translated">Solo puede registrarse un único tipo de {0} por generador.</target> <target state="needs-review-translation">Solo puede registrarse un único tipo de {0} por generador.</target>
<note /> <note>{0}: type name</note>
</trans-unit> </trans-unit>
<trans-unit id="SupportedDiagnosticsHasNullDescriptor"> <trans-unit id="SupportedDiagnosticsHasNullDescriptor">
<source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source> <source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source>
......
...@@ -29,6 +29,16 @@ ...@@ -29,6 +29,16 @@
<target state="translated">Supprimez les diagnostics suivants pour désactiver cet analyseur : {0}</target> <target state="translated">Supprimez les diagnostics suivants pour désactiver cet analyseur : {0}</target>
<note>{0}: Comma-separated list of diagnostic IDs</note> <note>{0}: Comma-separated list of diagnostic IDs</note>
</trans-unit> </trans-unit>
<trans-unit id="HintNameInvalidChar">
<source>The hintName contains an invalid character '{0}' at position {1}.</source>
<target state="new">The hintName contains an invalid character '{0}' at position {1}.</target>
<note>{0}: the invalid character, {1} the position it occured at</note>
</trans-unit>
<trans-unit id="HintNameUniquePerGenerator">
<source>The hintName of the added source file must be unique within a generator.</source>
<target state="new">The hintName of the added source file must be unique within a generator.</target>
<note />
</trans-unit>
<trans-unit id="InvalidDiagnosticSuppressionReported"> <trans-unit id="InvalidDiagnosticSuppressionReported">
<source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source> <source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source>
<target state="translated">L'ID de diagnostic supprimé '{0}' ne correspond pas à l'ID supprimable '{1}' pour le descripteur de suppression spécifié.</target> <target state="translated">L'ID de diagnostic supprimé '{0}' ne correspond pas à l'ID supprimable '{1}' pour le descripteur de suppression spécifié.</target>
...@@ -96,8 +106,8 @@ ...@@ -96,8 +106,8 @@
</trans-unit> </trans-unit>
<trans-unit id="Single_type_per_generator_0"> <trans-unit id="Single_type_per_generator_0">
<source>Only a single {0} can be registered per generator.</source> <source>Only a single {0} can be registered per generator.</source>
<target state="translated">Un seul {0} peut être inscrit par générateur.</target> <target state="needs-review-translation">Un seul {0} peut être inscrit par générateur.</target>
<note /> <note>{0}: type name</note>
</trans-unit> </trans-unit>
<trans-unit id="SupportedDiagnosticsHasNullDescriptor"> <trans-unit id="SupportedDiagnosticsHasNullDescriptor">
<source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source> <source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source>
......
...@@ -29,6 +29,16 @@ ...@@ -29,6 +29,16 @@
<target state="translated">Per disabilitare questo analizzatore, eliminare la diagnostica seguente: {0}</target> <target state="translated">Per disabilitare questo analizzatore, eliminare la diagnostica seguente: {0}</target>
<note>{0}: Comma-separated list of diagnostic IDs</note> <note>{0}: Comma-separated list of diagnostic IDs</note>
</trans-unit> </trans-unit>
<trans-unit id="HintNameInvalidChar">
<source>The hintName contains an invalid character '{0}' at position {1}.</source>
<target state="new">The hintName contains an invalid character '{0}' at position {1}.</target>
<note>{0}: the invalid character, {1} the position it occured at</note>
</trans-unit>
<trans-unit id="HintNameUniquePerGenerator">
<source>The hintName of the added source file must be unique within a generator.</source>
<target state="new">The hintName of the added source file must be unique within a generator.</target>
<note />
</trans-unit>
<trans-unit id="InvalidDiagnosticSuppressionReported"> <trans-unit id="InvalidDiagnosticSuppressionReported">
<source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source> <source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source>
<target state="translated">L'ID diagnostica '{0}' eliminato non corrisponde all'ID eliminabile '{1}' per il descrittore di eliminazione specificato.</target> <target state="translated">L'ID diagnostica '{0}' eliminato non corrisponde all'ID eliminabile '{1}' per il descrittore di eliminazione specificato.</target>
...@@ -96,8 +106,8 @@ ...@@ -96,8 +106,8 @@
</trans-unit> </trans-unit>
<trans-unit id="Single_type_per_generator_0"> <trans-unit id="Single_type_per_generator_0">
<source>Only a single {0} can be registered per generator.</source> <source>Only a single {0} can be registered per generator.</source>
<target state="translated">È possibile registrare solo un tipo {0} per generatore.</target> <target state="needs-review-translation">È possibile registrare solo un tipo {0} per generatore.</target>
<note /> <note>{0}: type name</note>
</trans-unit> </trans-unit>
<trans-unit id="SupportedDiagnosticsHasNullDescriptor"> <trans-unit id="SupportedDiagnosticsHasNullDescriptor">
<source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source> <source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source>
......
...@@ -29,6 +29,16 @@ ...@@ -29,6 +29,16 @@
<target state="translated">次の診断を抑制して、このアナライザーを無効にします: {0}</target> <target state="translated">次の診断を抑制して、このアナライザーを無効にします: {0}</target>
<note>{0}: Comma-separated list of diagnostic IDs</note> <note>{0}: Comma-separated list of diagnostic IDs</note>
</trans-unit> </trans-unit>
<trans-unit id="HintNameInvalidChar">
<source>The hintName contains an invalid character '{0}' at position {1}.</source>
<target state="new">The hintName contains an invalid character '{0}' at position {1}.</target>
<note>{0}: the invalid character, {1} the position it occured at</note>
</trans-unit>
<trans-unit id="HintNameUniquePerGenerator">
<source>The hintName of the added source file must be unique within a generator.</source>
<target state="new">The hintName of the added source file must be unique within a generator.</target>
<note />
</trans-unit>
<trans-unit id="InvalidDiagnosticSuppressionReported"> <trans-unit id="InvalidDiagnosticSuppressionReported">
<source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source> <source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source>
<target state="translated">抑制された診断 ID '{0}' が、指定された抑制記述子の抑制可能な ID '{1}' と一致しません。</target> <target state="translated">抑制された診断 ID '{0}' が、指定された抑制記述子の抑制可能な ID '{1}' と一致しません。</target>
...@@ -96,8 +106,8 @@ ...@@ -96,8 +106,8 @@
</trans-unit> </trans-unit>
<trans-unit id="Single_type_per_generator_0"> <trans-unit id="Single_type_per_generator_0">
<source>Only a single {0} can be registered per generator.</source> <source>Only a single {0} can be registered per generator.</source>
<target state="translated">ジェネレーターごとに登録できるのは 1 つの {0} のみです。</target> <target state="needs-review-translation">ジェネレーターごとに登録できるのは 1 つの {0} のみです。</target>
<note /> <note>{0}: type name</note>
</trans-unit> </trans-unit>
<trans-unit id="SupportedDiagnosticsHasNullDescriptor"> <trans-unit id="SupportedDiagnosticsHasNullDescriptor">
<source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source> <source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source>
......
...@@ -29,6 +29,16 @@ ...@@ -29,6 +29,16 @@
<target state="translated">이 분석기를 사용하지 않도록 설정하려면 다음 진단이 표시되지 않도록 설정하세요. {0}</target> <target state="translated">이 분석기를 사용하지 않도록 설정하려면 다음 진단이 표시되지 않도록 설정하세요. {0}</target>
<note>{0}: Comma-separated list of diagnostic IDs</note> <note>{0}: Comma-separated list of diagnostic IDs</note>
</trans-unit> </trans-unit>
<trans-unit id="HintNameInvalidChar">
<source>The hintName contains an invalid character '{0}' at position {1}.</source>
<target state="new">The hintName contains an invalid character '{0}' at position {1}.</target>
<note>{0}: the invalid character, {1} the position it occured at</note>
</trans-unit>
<trans-unit id="HintNameUniquePerGenerator">
<source>The hintName of the added source file must be unique within a generator.</source>
<target state="new">The hintName of the added source file must be unique within a generator.</target>
<note />
</trans-unit>
<trans-unit id="InvalidDiagnosticSuppressionReported"> <trans-unit id="InvalidDiagnosticSuppressionReported">
<source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source> <source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source>
<target state="translated">표시되지 않는 진단 ID '{0}'이(가) 지정된 비표시 설명자의 표시하지 않을 수 있는 ID '{1}'과(와) 일치하지 않습니다.</target> <target state="translated">표시되지 않는 진단 ID '{0}'이(가) 지정된 비표시 설명자의 표시하지 않을 수 있는 ID '{1}'과(와) 일치하지 않습니다.</target>
...@@ -96,8 +106,8 @@ ...@@ -96,8 +106,8 @@
</trans-unit> </trans-unit>
<trans-unit id="Single_type_per_generator_0"> <trans-unit id="Single_type_per_generator_0">
<source>Only a single {0} can be registered per generator.</source> <source>Only a single {0} can be registered per generator.</source>
<target state="translated">생성기당 하나의 {0}만 등록할 수 있습니다.</target> <target state="needs-review-translation">생성기당 하나의 {0}만 등록할 수 있습니다.</target>
<note /> <note>{0}: type name</note>
</trans-unit> </trans-unit>
<trans-unit id="SupportedDiagnosticsHasNullDescriptor"> <trans-unit id="SupportedDiagnosticsHasNullDescriptor">
<source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source> <source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source>
......
...@@ -29,6 +29,16 @@ ...@@ -29,6 +29,16 @@
<target state="translated">Pomiń następującą diagnostykę, aby wyłączyć ten analizator: {0}</target> <target state="translated">Pomiń następującą diagnostykę, aby wyłączyć ten analizator: {0}</target>
<note>{0}: Comma-separated list of diagnostic IDs</note> <note>{0}: Comma-separated list of diagnostic IDs</note>
</trans-unit> </trans-unit>
<trans-unit id="HintNameInvalidChar">
<source>The hintName contains an invalid character '{0}' at position {1}.</source>
<target state="new">The hintName contains an invalid character '{0}' at position {1}.</target>
<note>{0}: the invalid character, {1} the position it occured at</note>
</trans-unit>
<trans-unit id="HintNameUniquePerGenerator">
<source>The hintName of the added source file must be unique within a generator.</source>
<target state="new">The hintName of the added source file must be unique within a generator.</target>
<note />
</trans-unit>
<trans-unit id="InvalidDiagnosticSuppressionReported"> <trans-unit id="InvalidDiagnosticSuppressionReported">
<source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source> <source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source>
<target state="translated">Pominięty identyfikator diagnostyki „{0}” nie pasuje do możliwego do pominięcia identyfikatora „{1}” dla danego deskryptora pomijania.</target> <target state="translated">Pominięty identyfikator diagnostyki „{0}” nie pasuje do możliwego do pominięcia identyfikatora „{1}” dla danego deskryptora pomijania.</target>
...@@ -96,8 +106,8 @@ ...@@ -96,8 +106,8 @@
</trans-unit> </trans-unit>
<trans-unit id="Single_type_per_generator_0"> <trans-unit id="Single_type_per_generator_0">
<source>Only a single {0} can be registered per generator.</source> <source>Only a single {0} can be registered per generator.</source>
<target state="translated">Można zarejestrować tylko jeden element {0} na generator.</target> <target state="needs-review-translation">Można zarejestrować tylko jeden element {0} na generator.</target>
<note /> <note>{0}: type name</note>
</trans-unit> </trans-unit>
<trans-unit id="SupportedDiagnosticsHasNullDescriptor"> <trans-unit id="SupportedDiagnosticsHasNullDescriptor">
<source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source> <source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source>
......
...@@ -29,6 +29,16 @@ ...@@ -29,6 +29,16 @@
<target state="translated">Suprimir os seguintes diagnósticos para desabilitar este analisador: {0}</target> <target state="translated">Suprimir os seguintes diagnósticos para desabilitar este analisador: {0}</target>
<note>{0}: Comma-separated list of diagnostic IDs</note> <note>{0}: Comma-separated list of diagnostic IDs</note>
</trans-unit> </trans-unit>
<trans-unit id="HintNameInvalidChar">
<source>The hintName contains an invalid character '{0}' at position {1}.</source>
<target state="new">The hintName contains an invalid character '{0}' at position {1}.</target>
<note>{0}: the invalid character, {1} the position it occured at</note>
</trans-unit>
<trans-unit id="HintNameUniquePerGenerator">
<source>The hintName of the added source file must be unique within a generator.</source>
<target state="new">The hintName of the added source file must be unique within a generator.</target>
<note />
</trans-unit>
<trans-unit id="InvalidDiagnosticSuppressionReported"> <trans-unit id="InvalidDiagnosticSuppressionReported">
<source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source> <source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source>
<target state="translated">A ID de diagnóstico suprimida '{0}' não corresponde à ID suprimível '{1}' para o descritor de supressão fornecido.</target> <target state="translated">A ID de diagnóstico suprimida '{0}' não corresponde à ID suprimível '{1}' para o descritor de supressão fornecido.</target>
...@@ -96,8 +106,8 @@ ...@@ -96,8 +106,8 @@
</trans-unit> </trans-unit>
<trans-unit id="Single_type_per_generator_0"> <trans-unit id="Single_type_per_generator_0">
<source>Only a single {0} can be registered per generator.</source> <source>Only a single {0} can be registered per generator.</source>
<target state="translated">Somente um único {0} pode ser registrado por gerador.</target> <target state="needs-review-translation">Somente um único {0} pode ser registrado por gerador.</target>
<note /> <note>{0}: type name</note>
</trans-unit> </trans-unit>
<trans-unit id="SupportedDiagnosticsHasNullDescriptor"> <trans-unit id="SupportedDiagnosticsHasNullDescriptor">
<source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source> <source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source>
......
...@@ -29,6 +29,16 @@ ...@@ -29,6 +29,16 @@
<target state="translated">Отключите следующую диагностику, чтобы отключить этот анализатор: {0}</target> <target state="translated">Отключите следующую диагностику, чтобы отключить этот анализатор: {0}</target>
<note>{0}: Comma-separated list of diagnostic IDs</note> <note>{0}: Comma-separated list of diagnostic IDs</note>
</trans-unit> </trans-unit>
<trans-unit id="HintNameInvalidChar">
<source>The hintName contains an invalid character '{0}' at position {1}.</source>
<target state="new">The hintName contains an invalid character '{0}' at position {1}.</target>
<note>{0}: the invalid character, {1} the position it occured at</note>
</trans-unit>
<trans-unit id="HintNameUniquePerGenerator">
<source>The hintName of the added source file must be unique within a generator.</source>
<target state="new">The hintName of the added source file must be unique within a generator.</target>
<note />
</trans-unit>
<trans-unit id="InvalidDiagnosticSuppressionReported"> <trans-unit id="InvalidDiagnosticSuppressionReported">
<source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source> <source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source>
<target state="translated">Подавленный идентификатор диагностики "{0}" не соответствует подавленному идентификатору "{1}" для заданного дескриптора подавления.</target> <target state="translated">Подавленный идентификатор диагностики "{0}" не соответствует подавленному идентификатору "{1}" для заданного дескриптора подавления.</target>
...@@ -96,8 +106,8 @@ ...@@ -96,8 +106,8 @@
</trans-unit> </trans-unit>
<trans-unit id="Single_type_per_generator_0"> <trans-unit id="Single_type_per_generator_0">
<source>Only a single {0} can be registered per generator.</source> <source>Only a single {0} can be registered per generator.</source>
<target state="translated">Для каждого генератора можно зарегистрировать только один {0}.</target> <target state="needs-review-translation">Для каждого генератора можно зарегистрировать только один {0}.</target>
<note /> <note>{0}: type name</note>
</trans-unit> </trans-unit>
<trans-unit id="SupportedDiagnosticsHasNullDescriptor"> <trans-unit id="SupportedDiagnosticsHasNullDescriptor">
<source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source> <source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source>
......
...@@ -29,6 +29,16 @@ ...@@ -29,6 +29,16 @@
<target state="translated">Bu çözümleyiciyi devre dışı bırakmak için şu tanılamaları gizleyin: {0}</target> <target state="translated">Bu çözümleyiciyi devre dışı bırakmak için şu tanılamaları gizleyin: {0}</target>
<note>{0}: Comma-separated list of diagnostic IDs</note> <note>{0}: Comma-separated list of diagnostic IDs</note>
</trans-unit> </trans-unit>
<trans-unit id="HintNameInvalidChar">
<source>The hintName contains an invalid character '{0}' at position {1}.</source>
<target state="new">The hintName contains an invalid character '{0}' at position {1}.</target>
<note>{0}: the invalid character, {1} the position it occured at</note>
</trans-unit>
<trans-unit id="HintNameUniquePerGenerator">
<source>The hintName of the added source file must be unique within a generator.</source>
<target state="new">The hintName of the added source file must be unique within a generator.</target>
<note />
</trans-unit>
<trans-unit id="InvalidDiagnosticSuppressionReported"> <trans-unit id="InvalidDiagnosticSuppressionReported">
<source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source> <source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source>
<target state="translated">'{0}' gizlenmiş tanılama kimliği, belirtilen gizleme tanımlayıcısı için gizlenebilir '{1}' kimliği ile eşleşmiyor.</target> <target state="translated">'{0}' gizlenmiş tanılama kimliği, belirtilen gizleme tanımlayıcısı için gizlenebilir '{1}' kimliği ile eşleşmiyor.</target>
...@@ -96,8 +106,8 @@ ...@@ -96,8 +106,8 @@
</trans-unit> </trans-unit>
<trans-unit id="Single_type_per_generator_0"> <trans-unit id="Single_type_per_generator_0">
<source>Only a single {0} can be registered per generator.</source> <source>Only a single {0} can be registered per generator.</source>
<target state="translated">Oluşturucu başına tek bir {0} kaydedilebilir.</target> <target state="needs-review-translation">Oluşturucu başına tek bir {0} kaydedilebilir.</target>
<note /> <note>{0}: type name</note>
</trans-unit> </trans-unit>
<trans-unit id="SupportedDiagnosticsHasNullDescriptor"> <trans-unit id="SupportedDiagnosticsHasNullDescriptor">
<source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source> <source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source>
......
...@@ -29,6 +29,16 @@ ...@@ -29,6 +29,16 @@
<target state="translated">取消以下诊断以禁用此分析器: {0}</target> <target state="translated">取消以下诊断以禁用此分析器: {0}</target>
<note>{0}: Comma-separated list of diagnostic IDs</note> <note>{0}: Comma-separated list of diagnostic IDs</note>
</trans-unit> </trans-unit>
<trans-unit id="HintNameInvalidChar">
<source>The hintName contains an invalid character '{0}' at position {1}.</source>
<target state="new">The hintName contains an invalid character '{0}' at position {1}.</target>
<note>{0}: the invalid character, {1} the position it occured at</note>
</trans-unit>
<trans-unit id="HintNameUniquePerGenerator">
<source>The hintName of the added source file must be unique within a generator.</source>
<target state="new">The hintName of the added source file must be unique within a generator.</target>
<note />
</trans-unit>
<trans-unit id="InvalidDiagnosticSuppressionReported"> <trans-unit id="InvalidDiagnosticSuppressionReported">
<source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source> <source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source>
<target state="translated">对于给定的禁止显示描述符,禁止显示的诊断 ID“{0}”与可禁止显示的 ID“{1}”不匹配。</target> <target state="translated">对于给定的禁止显示描述符,禁止显示的诊断 ID“{0}”与可禁止显示的 ID“{1}”不匹配。</target>
...@@ -96,8 +106,8 @@ ...@@ -96,8 +106,8 @@
</trans-unit> </trans-unit>
<trans-unit id="Single_type_per_generator_0"> <trans-unit id="Single_type_per_generator_0">
<source>Only a single {0} can be registered per generator.</source> <source>Only a single {0} can be registered per generator.</source>
<target state="translated">每个生成器仅可注册一个 {0}。</target> <target state="needs-review-translation">每个生成器仅可注册一个 {0}。</target>
<note /> <note>{0}: type name</note>
</trans-unit> </trans-unit>
<trans-unit id="SupportedDiagnosticsHasNullDescriptor"> <trans-unit id="SupportedDiagnosticsHasNullDescriptor">
<source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source> <source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source>
......
...@@ -29,6 +29,16 @@ ...@@ -29,6 +29,16 @@
<target state="translated">請隱藏下列診斷以停用此分析器: {0}</target> <target state="translated">請隱藏下列診斷以停用此分析器: {0}</target>
<note>{0}: Comma-separated list of diagnostic IDs</note> <note>{0}: Comma-separated list of diagnostic IDs</note>
</trans-unit> </trans-unit>
<trans-unit id="HintNameInvalidChar">
<source>The hintName contains an invalid character '{0}' at position {1}.</source>
<target state="new">The hintName contains an invalid character '{0}' at position {1}.</target>
<note>{0}: the invalid character, {1} the position it occured at</note>
</trans-unit>
<trans-unit id="HintNameUniquePerGenerator">
<source>The hintName of the added source file must be unique within a generator.</source>
<target state="new">The hintName of the added source file must be unique within a generator.</target>
<note />
</trans-unit>
<trans-unit id="InvalidDiagnosticSuppressionReported"> <trans-unit id="InvalidDiagnosticSuppressionReported">
<source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source> <source>Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor.</source>
<target state="translated">隱藏的診斷識別碼 '{0}' 不符合指定隱藏描述項的可隱藏識別碼 '{1}'。</target> <target state="translated">隱藏的診斷識別碼 '{0}' 不符合指定隱藏描述項的可隱藏識別碼 '{1}'。</target>
...@@ -96,8 +106,8 @@ ...@@ -96,8 +106,8 @@
</trans-unit> </trans-unit>
<trans-unit id="Single_type_per_generator_0"> <trans-unit id="Single_type_per_generator_0">
<source>Only a single {0} can be registered per generator.</source> <source>Only a single {0} can be registered per generator.</source>
<target state="translated">每個產生器只能註冊一個 {0}。</target> <target state="needs-review-translation">每個產生器只能註冊一個 {0}。</target>
<note /> <note>{0}: type name</note>
</trans-unit> </trans-unit>
<trans-unit id="SupportedDiagnosticsHasNullDescriptor"> <trans-unit id="SupportedDiagnosticsHasNullDescriptor">
<source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source> <source>Analyzer '{0}' contains a null descriptor in its 'SupportedDiagnostics'.</source>
......
...@@ -62,7 +62,7 @@ private static Project CreateEmptyProject() ...@@ -62,7 +62,7 @@ private static Project CreateEmptyProject()
Assert.NotSame(originalCompilation, newCompilation); Assert.NotSame(originalCompilation, newCompilation);
var generatedTree = Assert.Single(newCompilation.SyntaxTrees); var generatedTree = Assert.Single(newCompilation.SyntaxTrees);
Assert.Equal("Test.generated", Path.GetFileName(generatedTree.FilePath)); Assert.Equal($"{typeof(AdditionalFileAddedGenerator).Module.ModuleVersionId}_{typeof(AdditionalFileAddedGenerator).FullName}_Test.generated.cs", Path.GetFileName(generatedTree.FilePath));
} }
[Theory] [Theory]
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册