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:
Родитель
cb18d0ac5f
Коммит
fb27299c40
|
@ -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:
|
||||
|
|
|
@ -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>
|
|
@ -1,4 +1,7 @@
|
|||
{
|
||||
"sdk": {
|
||||
"version": "3.0.100-preview3-010431"
|
||||
},
|
||||
"msbuild-sdks": {
|
||||
"Microsoft.Build.CentralPackageVersions": "2.0.1"
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче