Showcase how to move a string to a resource file

This shows how modifying multiple files works, how the preview window continues to work, and the initial boilerplate of what a unit test for the code fix provider would be (WIP).

Fixes https://devdiv.visualstudio.com/DevDiv/_workitems/edit/821925/
This commit is contained in:
Daniel Cazzulino 2019-03-20 17:12:32 -03:00
Родитель cb18d0ac5f
Коммит fb27299c40
18 изменённых файлов: 410 добавлений и 8 удалений

61
Xamarin.CodeAnalysis.sln Normal file
Просмотреть файл

@ -0,0 +1,61 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28223.52
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.CodeAnalysis", "src\Xamarin.CodeAnalysis\Xamarin.CodeAnalysis.csproj", "{A83DFC4B-4270-4A1A-8438-897A8AA61AE0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.CodeAnalysis.Completion", "src\Xamarin.CodeAnalysis.Completion\Xamarin.CodeAnalysis.Completion.csproj", "{E5EA94F5-3B00-40E9-B45A-0CF3C995D9E8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.CodeAnalysis.Tests", "src\Xamarin.CodeAnalysis.Tests\Xamarin.CodeAnalysis.Tests.csproj", "{366C371C-0FCE-4357-87AE-C03F5DB8D47E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.CodeAnalysis.Extension", "src\Xamarin.CodeAnalysis.Extension\Xamarin.CodeAnalysis.Extension.csproj", "{D84C73D3-0DA3-4723-9C3C-6F4B9FD07514}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{ADBB9E34-F6F8-4E54-82AA-279EE5DDFD15}"
ProjectSection(SolutionItems) = preProject
azure-pipelines.yml = azure-pipelines.yml
src\Directory.Build.props = src\Directory.Build.props
src\Directory.Build.targets = src\Directory.Build.targets
src\GenerateInternalsVisibleTo.targets = src\GenerateInternalsVisibleTo.targets
src\GitInfo.txt = src\GitInfo.txt
src\global.json = src\global.json
src\NuGet.Config = src\NuGet.Config
src\Packages.props = src\Packages.props
README.md = README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{43589FA6-E3DD-4308-89BF-889C333CB946}"
ProjectSection(SolutionItems) = preProject
docs\XAA1001.md = docs\XAA1001.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A83DFC4B-4270-4A1A-8438-897A8AA61AE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A83DFC4B-4270-4A1A-8438-897A8AA61AE0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A83DFC4B-4270-4A1A-8438-897A8AA61AE0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A83DFC4B-4270-4A1A-8438-897A8AA61AE0}.Release|Any CPU.Build.0 = Release|Any CPU
{E5EA94F5-3B00-40E9-B45A-0CF3C995D9E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E5EA94F5-3B00-40E9-B45A-0CF3C995D9E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E5EA94F5-3B00-40E9-B45A-0CF3C995D9E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E5EA94F5-3B00-40E9-B45A-0CF3C995D9E8}.Release|Any CPU.Build.0 = Release|Any CPU
{366C371C-0FCE-4357-87AE-C03F5DB8D47E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{366C371C-0FCE-4357-87AE-C03F5DB8D47E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{366C371C-0FCE-4357-87AE-C03F5DB8D47E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{366C371C-0FCE-4357-87AE-C03F5DB8D47E}.Release|Any CPU.Build.0 = Release|Any CPU
{D84C73D3-0DA3-4723-9C3C-6F4B9FD07514}.Debug|Any CPU.ActiveCfg = Debug|x86
{D84C73D3-0DA3-4723-9C3C-6F4B9FD07514}.Debug|Any CPU.Build.0 = Debug|x86
{D84C73D3-0DA3-4723-9C3C-6F4B9FD07514}.Release|Any CPU.ActiveCfg = Release|x86
{D84C73D3-0DA3-4723-9C3C-6F4B9FD07514}.Release|Any CPU.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C136E0D2-8637-4CDE-803F-4FCB33350FD8}
EndGlobalSection
EndGlobal

Просмотреть файл

@ -5,6 +5,11 @@ steps:
- checkout: self
clean: true
- task: DotNetCoreInstaller@0
displayName: 'Use .NET Core SDK 3.0.100-preview3-010431'
inputs:
version: 3.0.100-preview3-010431
- task: MSBuild@1
displayName: Restore
inputs:
@ -31,4 +36,4 @@ steps:
PathtoPublish: $(Build.ArtifactStagingDirectory)
ArtifactName: out
ArtifactType: Container
condition: always()
condition: always()

25
docs/XAA1001.md Normal file
Просмотреть файл

@ -0,0 +1,25 @@
# XAA1001
## Cause
A string literal can be moved to a strings.xml file
## Rule description
Moving strings to strings.xml allows them to be localized
## How to fix violations
To fix a violation of this rule, apply the provided code fix, or manually move
the string literal to the `strings.xml` resource file
## How to suppress violations
```csharp
[SuppressMessage("Xamarin.CodeAnalysis", "XAA1001:StringLiteralToResource", Justification = "Reviewed.")]
```
```csharp
#pragma warning disable XAA1001 // StringLiteralToResource
#pragma warning restore XAA1001 // StringLiteralToResource
```

Просмотреть файл

@ -8,6 +8,7 @@
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)xamarin.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
<PublicKey>002400000480000094000000060200000024000052534131000400000100010079159977d2d03a8e6bea7a2e74e8d1afcc93e8851974952bb480a12c9134474d04062447c37e0e68c080536fcf3c3fbe2ff9c979ce998475e506e8ce82dd5b0f350dc10e93bf2eeecf874b24770c5081dbea7447fddafa277b22de47d6ffea449674a4f9fccf84d15069089380284dbdd35f46cdff12a1bd78e4ef0065d016df</PublicKey>
</PropertyGroup>
<PropertyGroup Condition="'$(CI)' == ''">

Просмотреть файл

@ -56,4 +56,6 @@
</ItemGroup>
</Target>
<Import Project="GenerateInternalsVisibleTo.targets" />
</Project>

Просмотреть файл

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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. -->
<!-- See https://github.com/dotnet/arcade/blob/master/src/Microsoft.DotNet.Arcade.Sdk/tools/GenerateInternalsVisibleTo.targets -->
<Project>
<PropertyGroup>
<GeneratedInternalsVisibleToFile>$(IntermediateOutputPath)$(MSBuildProjectName).InternalsVisibleTo$(DefaultLanguageSourceExtension)</GeneratedInternalsVisibleToFile>
</PropertyGroup>
<ItemDefinitionGroup>
<InternalsVisibleTo>
<Visible>false</Visible>
</InternalsVisibleTo>
</ItemDefinitionGroup>
<Target Name="PrepareGenerateInternalsVisibleToFile" Condition="'@(InternalsVisibleTo)' != ''">
<ItemGroup>
<_InternalsVisibleToAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1 Condition="'%(InternalsVisibleTo.Key)' != ''">%(InternalsVisibleTo.Identity), PublicKey=%(InternalsVisibleTo.Key)</_Parameter1>
<_Parameter1 Condition="'%(InternalsVisibleTo.Key)' == '' and '$(PublicKey)' != ''">%(InternalsVisibleTo.Identity), PublicKey=$(PublicKey)</_Parameter1>
<_Parameter1 Condition="'%(InternalsVisibleTo.Key)' == '' and '$(PublicKey)' == ''">%(InternalsVisibleTo.Identity)</_Parameter1>
</_InternalsVisibleToAttribute>
</ItemGroup>
</Target>
<!--
Dependency on PrepareForBuild is necessary so that we don't accidentally get ordered before it.
We rely on PrepareForBuild to create the IntermediateOutputDirectory if it doesn't exist.
-->
<Target Name="GenerateInternalsVisibleToFile"
Inputs="$(MSBuildThisFileFullPath);$(MSBuildProjectFile)"
Outputs="$(GeneratedInternalsVisibleToFile)"
DependsOnTargets="PrepareGenerateInternalsVisibleToFile;PrepareForBuild"
Condition="'@(InternalsVisibleTo)' != ''"
BeforeTargets="CoreCompile">
<WriteCodeFragment AssemblyAttributes="@(_InternalsVisibleToAttribute)"
Language="$(Language)"
OutputFile="$(GeneratedInternalsVisibleToFile)">
<Output TaskParameter="OutputFile" ItemName="CompileBefore" Condition="'$(Language)' == 'F#'" />
<Output TaskParameter="OutputFile" ItemName="Compile" Condition="'$(Language)' != 'F#'" />
<Output TaskParameter="OutputFile" ItemName="FileWrites" />
</WriteCodeFragment>
</Target>
</Project>

Просмотреть файл

@ -18,6 +18,7 @@
<PackageReference Update="MSBuilder.ThisAssembly.Metadata" Version="[0.1.3]" />
<PackageReference Update="xunit" Version="[2.4.0]" />
<PackageReference Update="xunit.runner.visualstudio" Version="[2.4.0]" />
<PackageReference Update="GuiLabs.Language.Xml" Version="[1.2.35]" />
</ItemGroup>
</Project>

Просмотреть файл

@ -2,7 +2,6 @@
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
@ -11,7 +10,6 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Tags;
using Microsoft.CodeAnalysis.Text;

Просмотреть файл

@ -45,7 +45,6 @@
<ProjectReference Include="..\Xamarin.CodeAnalysis.Completion\Xamarin.CodeAnalysis.Completion.csproj">
<Name>Xamarin.CodeAnalysis.Completion</Name>
</ProjectReference>
<ProjectReference Include="..\Xamarin.CodeAnalysis.Remote\Xamarin.CodeAnalysis.Remote.csproj" />
<ProjectReference Include="..\Xamarin.CodeAnalysis\Xamarin.CodeAnalysis.csproj">
<Name>Xamarin.CodeAnalysis</Name>
</ProjectReference>

Просмотреть файл

@ -13,6 +13,7 @@
</Prerequisites>
<Assets>
<Asset Type="Microsoft.VisualStudio.VsPackage" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%;PkgdefProjectOutputGroup|" />
<Asset Type="Microsoft.VisualStudio.Analyzer" d:Source="Project" d:ProjectName="Xamarin.CodeAnalysis" Path="|Xamarin.CodeAnalysis|" />
<Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="Xamarin.CodeAnalysis" Path="|Xamarin.CodeAnalysis|" />
<Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="Xamarin.CodeAnalysis.Completion" Path="|Xamarin.CodeAnalysis.Completion|" />
</Assets>

Просмотреть файл

@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
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.Host.Mef;
using Xunit;
using static Xamarin.CodeAnalysis.Tests.TestHelpers;
namespace Xamarin.CodeAnalysis.Tests
{
public class AnalysisTests
{
[Theory]
[InlineData(@"using Android.App;
using Android.Support.Design.Widget;
using Android.Support.V7.App;
using Android.Views;
[Activity(Label = ""Main"", MainLauncher = true)]
public class MainActivity : AppCompatActivity, NavigationView.IOnNavigationItemSelectedListener
{
public bool OnNavigationItemSelected(IMenuItem menuItem) => true;
}
", XAA1001StringLiteralToResource.DiagnosticId)]
public async Task can_get_diagnostics(string code, string diagnosticId)
{
var workspace = new AdhocWorkspace();
var document = workspace
.AddProject("TestProject", LanguageNames.CSharp)
.WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.WithMetadataReferences(new MetadataReference[]
{
#pragma warning disable CS0436 // Type conflicts with imported type
MetadataReference.CreateFromFile(ThisAssembly.Metadata.NETStandardReference),
#pragma warning restore CS0436 // Type conflicts with imported type
MetadataReference.CreateFromFile("Xamarin.CodeAnalysis.dll"),
})
.AddAdditionalDocument("strings.xml", @"<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name='app_name'>TestApp</string>
</resources>", new[] { "Resources", "values" }, "Resources\\values\\strings.xml")
.Project
.AddAdditionalDocument("styles.xml", @"<?xml version='1.0' encoding='utf-8'?>
<resources>
<style name='AppTheme' parent='Theme.AppCompat.Light.DarkActionBar' />
</resources>", new[] { "Resources", "values" }, "Resources\\values\\styles.xml")
.Project
.AddDocument("TestDocument.cs", code.Replace("`", ""));
var compilation = await document.Project.GetCompilationAsync(TimeoutToken(5));
var withAnalyzers = compilation.WithAnalyzers(ImmutableArray.Create<DiagnosticAnalyzer>(new XAA1001StringLiteralToResource()));
var diagnostic = (await withAnalyzers.GetAnalyzerDiagnosticsAsync())
.Where(d => d.Id == diagnosticId)
.OrderBy(d => d.Location.SourceSpan.Start)
.FirstOrDefault() ?? throw new ArgumentException($"Analyzer did not produce diagnostic {diagnosticId}.");
var actions = new List<CodeAction>();
var context = new CodeFixContext(document, diagnostic, (a, d) => actions.Add(a), TimeoutToken(5));
await new XAA1001CodeFixProvider().RegisterCodeFixesAsync(context);
var changed = actions
.SelectMany(x => x.GetOperationsAsync(TimeoutToken(2)).Result)
.OfType<ApplyChangesOperation>()
.First()
.ChangedSolution;
var changes = changed.GetChanges(document.Project.Solution);
}
}
}

Просмотреть файл

@ -38,7 +38,9 @@ public class MainActivity : AppCompatActivity, NavigationView.IOnNavigationItemS
.WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.WithMetadataReferences(new MetadataReference[]
{
#pragma warning disable CS0436 // Type conflicts with imported type
MetadataReference.CreateFromFile(ThisAssembly.Metadata.NETStandardReference),
#pragma warning restore CS0436 // Type conflicts with imported type
MetadataReference.CreateFromFile("Xamarin.CodeAnalysis.dll"),
MetadataReference.CreateFromFile("Xamarin.CodeAnalysis.Completion.dll"),
})
@ -147,7 +149,9 @@ public class MainActivity : AppCompatActivity, NavigationView.IOnNavigationItemS
.WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.WithMetadataReferences(new MetadataReference[]
{
#pragma warning disable CS0436 // Type conflicts with imported type
MetadataReference.CreateFromFile(ThisAssembly.Metadata.NETStandardReference),
#pragma warning restore CS0436 // Type conflicts with imported type
MetadataReference.CreateFromFile("Xamarin.CodeAnalysis.dll"),
MetadataReference.CreateFromFile("Xamarin.CodeAnalysis.Completion.dll"),
})
@ -191,6 +195,14 @@ public class Foo
{
Console.`WriteLine("""");
}
}")]
[InlineData(@"using System;
public class Foo
{
public void Do()
{
Console.WriteLine(""`"");
}
}")]
[InlineData(@"using System.ComponentModel;
[Description(""`"")]
@ -212,7 +224,9 @@ public class Foo
.WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.WithMetadataReferences(new MetadataReference[]
{
#pragma warning disable CS0436 // Type conflicts with imported type
MetadataReference.CreateFromFile(ThisAssembly.Metadata.NETStandardReference),
#pragma warning restore CS0436 // Type conflicts with imported type
MetadataReference.CreateFromFile("Xamarin.CodeAnalysis.dll"),
MetadataReference.CreateFromFile("Xamarin.CodeAnalysis.Completion.dll"),
})

Просмотреть файл

@ -0,0 +1,14 @@
using System;
using System.Diagnostics;
using System.Threading;
namespace Xamarin.CodeAnalysis.Tests
{
static class TestHelpers
{
public static CancellationToken TimeoutToken(int seconds)
=> Debugger.IsAttached ?
CancellationToken.None :
new CancellationTokenSource(TimeSpan.FromSeconds(seconds)).Token;
}
}

Просмотреть файл

@ -0,0 +1,7 @@
namespace Xamarin.CodeAnalysis
{
internal static class Constants
{
public const string AnalyzerCategory = "Xamarin.CodeAnalysis";
}
}

Просмотреть файл

@ -0,0 +1,146 @@
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Text;
using Microsoft.Language.Xml;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace Xamarin.CodeAnalysis
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class XAA1001StringLiteralToResource : DiagnosticAnalyzer
{
/// <summary>
/// The ID for diagnostics produced by the <see cref="XAA1001StringLiteralToResource"/> analyzer.
/// </summary>
public const string DiagnosticId = "XAA1001";
const string Title = "Move string to resource";
const string MessageFormat = "Literal strings should be placed in a resource file.";
const string Description = "By placing literal strings in a resource file, it can be easily localized.";
const string HelpLink = "https://github.com/xamarin/CodeAnalysis/blob/master/docs/XAA1001.md";
static readonly DiagnosticDescriptor Descriptor =
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Constants.AnalyzerCategory, Microsoft.CodeAnalysis.DiagnosticSeverity.Info, true, Description, HelpLink);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Descriptor);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterSyntaxNodeAction(AnalyzeLiteral, Microsoft.CodeAnalysis.CSharp.SyntaxKind.StringLiteralExpression);
}
void AnalyzeLiteral(SyntaxNodeAnalysisContext context)
{
if (context.Node is LiteralExpressionSyntax literal &&
context.Node.Parent is AttributeArgumentSyntax &&
context.Node.Parent?.Parent?.Parent is AttributeSyntax &&
!literal.GetText().ToString().StartsWith("@"))
{
context.ReportDiagnostic(Diagnostic.Create(Descriptor, context.Node.GetLocation()));
}
}
}
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(XAA1001CodeFixProvider))]
[Shared]
internal class XAA1001CodeFixProvider : CodeFixProvider
{
const string Title = "Move string to resource";
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
ImmutableArray.Create(XAA1001StringLiteralToResource.DiagnosticId);
public override FixAllProvider GetFixAllProvider() => null;
public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
foreach (var diagnostic in context.Diagnostics)
{
context.RegisterCodeFix(
CodeAction.Create(
Title,
cancellation => CreateChangedSolutionAsync(context, diagnostic, cancellation),
nameof(XAA1001CodeFixProvider)),
diagnostic);
}
return Task.CompletedTask;
}
private async Task<Solution> CreateChangedSolutionAsync(CodeFixContext context, Diagnostic diagnostic, CancellationToken cancellation)
{
var root = await context.Document.GetSyntaxRootAsync(cancellation).ConfigureAwait(false);
var token = root.FindToken(diagnostic.Location.SourceSpan.Start);
var literal = (LiteralExpressionSyntax)token.Parent;
var argument = literal.FirstAncestorOrSelf<AttributeArgumentSyntax>();
TextDocument resourceDoc = default;
if (argument.NameEquals.Name.ToString() == "Label")
resourceDoc = context.Document.Project.AdditionalDocuments.FirstOrDefault(doc => doc.FilePath.EndsWith(@"Resources\values\strings.xml"));
// Potentially support moving resources to other files?
if (resourceDoc == null)
return null;
var declaration = literal.FirstAncestorOrSelf<ClassDeclarationSyntax>();
var identifier = new StringBuilder();
foreach (var c in declaration.Identifier.ValueText)
{
if (char.IsUpper(c))
{
if (identifier.Length > 0)
identifier = identifier.Append("_");
identifier.Append(char.ToLowerInvariant(c));
}
else
{
identifier.Append(c);
}
}
var key = identifier.Append("_").Append(argument.NameEquals.Name.ToString().ToLowerInvariant()).ToString();
var documentSyntax = Parser.ParseText((await resourceDoc.GetTextAsync()).ToString());
// the XML mutation model is a bit cumbersome, and the factory API is very hard to follow, it's easy to
// loose track of what you're building. So we parse a new doc instead and append that to the previous one.
var elementSyntax = Parser.ParseText("\r\n\t" + new XElement("string", new XAttribute("name", key), literal.ToString().TrimStart('"').TrimEnd('"')).ToString());
var newXml = documentSyntax.RootSyntax.AddChild(elementSyntax.RootSyntax);
var docNode = SyntaxFactory.XmlDocument(
documentSyntax.Prologue, documentSyntax.PrecedingMisc,
newXml.AsNode, documentSyntax.FollowingMisc,
documentSyntax.SkippedTokens, documentSyntax.Eof);
var text = await resourceDoc.GetTextAsync(cancellation);
var newDoc = context.Document.Project.Solution
.WithAdditionalDocumentText(resourceDoc.Id, SourceText.From(
docNode.ToFullString(), text.Encoding))
.GetProject(context.Document.Project.Id)
.GetDocument(context.Document.Id);
var generator = SyntaxGenerator.GetGenerator(newDoc);
newDoc = newDoc.WithSyntaxRoot(generator.ReplaceNode(
await newDoc.GetSyntaxRootAsync(),
literal,
literal.WithToken(Literal($"@string/{key}"))));
return newDoc.Project.Solution;
}
}
}

Просмотреть файл

@ -6,7 +6,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GuiLabs.Language.Xml" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" />
<InternalsVisibleTo Include="Xamarin.CodeAnalysis.Tests" />
</ItemGroup>
</Project>
</Project>

Просмотреть файл

@ -1,5 +1,8 @@
{
"msbuild-sdks": {
"Microsoft.Build.CentralPackageVersions": "2.0.1"
}
"sdk": {
"version": "3.0.100-preview3-010431"
},
"msbuild-sdks": {
"Microsoft.Build.CentralPackageVersions": "2.0.1"
}
}

Просмотреть файл