Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DiagnosticCategoryAndIdRanges.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# DO NOT remove ID ranges already defined or merge this file in git.
#
Design: LuceneDev1000-LuceneDev1008
Globalization:
Globalization: LuceneDev2000-LuceneDev2008
Mobility:
Performance:
Security:
Expand Down
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,6 @@
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="$(RoslynAnalyzerPackageVersion)" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.4.0" />
<PackageVersion Include="Microsoft.VSSDK.BuildTools" Version="17.14.2094" />
<PackageVersion Include="J2N" Version="2.1.0" />
Comment thread
paulirwin marked this conversation as resolved.
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file for additional information.
* The ASF licenses this file under the Apache License, Version 2.0.
*/

using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Lucene.Net.CodeAnalysis.Dev.Utility;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Lucene.Net.CodeAnalysis.Dev.CodeFixes.LuceneDev2xxx
{
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(LuceneDev2000_2001_2002_2004_AddInvariantCultureCodeFixProvider)), Shared]
public sealed class LuceneDev2000_2001_2002_2004_AddInvariantCultureCodeFixProvider : CodeFixProvider
{
private const string TitleInvariant = "Add CultureInfo.InvariantCulture";
private const string TitleCurrent = "Add CultureInfo.CurrentCulture";

public override ImmutableArray<string> FixableDiagnosticIds =>
ImmutableArray.Create(
Descriptors.LuceneDev2000_BclNumericParseMissingFormatProvider.Id,
Descriptors.LuceneDev2001_BclNumericToStringMissingFormatProvider.Id,
Descriptors.LuceneDev2002_ConvertNumericMissingFormatProvider.Id,
Descriptors.LuceneDev2004_J2NNumericMissingFormatProvider.Id);

public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
if (root is null) return;

foreach (var diagnostic in context.Diagnostics)
{
var invocation = root.FindToken(diagnostic.Location.SourceSpan.Start)
.Parent?
.AncestorsAndSelf()
.OfType<InvocationExpressionSyntax>()
.FirstOrDefault();
if (invocation is null) continue;

RegisterFix(context, invocation, "InvariantCulture", TitleInvariant, diagnostic);
RegisterFix(context, invocation, "CurrentCulture", TitleCurrent, diagnostic);
}
}

private static void RegisterFix(
CodeFixContext context,
InvocationExpressionSyntax invocation,
string cultureMember,
string title,
Diagnostic diagnostic)
{
context.RegisterCodeFix(CodeAction.Create(
title: title,
createChangedDocument: c => AddCultureArgumentAsync(context.Document, invocation, cultureMember, c),
equivalenceKey: title),
diagnostic);
}

private static async Task<Document> AddCultureArgumentAsync(
Document document,
InvocationExpressionSyntax invocation,
string cultureMember,
CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
if (root is null) return document;

var cultureExpr = SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.IdentifierName("CultureInfo"),
SyntaxFactory.IdentifierName(cultureMember));
var newArg = SyntaxFactory.Argument(cultureExpr);

var newInvocation = invocation.WithArgumentList(invocation.ArgumentList.AddArguments(newArg));
var newRoot = EnsureGlobalizationUsing(root).ReplaceNode(invocation, newInvocation);
return document.WithSyntaxRoot(newRoot);
}

internal static SyntaxNode EnsureGlobalizationUsing(SyntaxNode root)
{
if (root is CompilationUnitSyntax compilationUnit)
{
var hasUsing = compilationUnit.Usings.Any(u => u.Name?.ToString() == "System.Globalization");
if (!hasUsing)
{
var usingDirective = SyntaxFactory.UsingDirective(
SyntaxFactory.QualifiedName(
SyntaxFactory.IdentifierName("System"),
SyntaxFactory.IdentifierName("Globalization")))
.WithTrailingTrivia(SyntaxFactory.ElasticEndOfLine("\n"));
Comment thread
paulirwin marked this conversation as resolved.
Outdated
return compilationUnit.AddUsings(usingDirective);
}
}
return root;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file for additional information.
* The ASF licenses this file under the Apache License, Version 2.0.
*/

using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Lucene.Net.CodeAnalysis.Dev.Utility;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Lucene.Net.CodeAnalysis.Dev.CodeFixes.LuceneDev2xxx
{
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(LuceneDev2003_AddInvariantCultureToStringFormatCodeFixProvider)), Shared]
public sealed class LuceneDev2003_AddInvariantCultureToStringFormatCodeFixProvider : CodeFixProvider
{
private const string TitleInvariant = "Add CultureInfo.InvariantCulture";
private const string TitleCurrent = "Add CultureInfo.CurrentCulture";

public override ImmutableArray<string> FixableDiagnosticIds =>
ImmutableArray.Create(Descriptors.LuceneDev2003_StringFormatNumericMissingFormatProvider.Id);

public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
if (root is null) return;

foreach (var diagnostic in context.Diagnostics)
{
var invocation = root.FindToken(diagnostic.Location.SourceSpan.Start)
.Parent?
.AncestorsAndSelf()
.OfType<InvocationExpressionSyntax>()
.FirstOrDefault();
if (invocation is null) continue;

Register(context, invocation, "InvariantCulture", TitleInvariant, diagnostic);
Register(context, invocation, "CurrentCulture", TitleCurrent, diagnostic);
}
}

private static void Register(
CodeFixContext context,
InvocationExpressionSyntax invocation,
string cultureMember,
string title,
Diagnostic diagnostic)
{
context.RegisterCodeFix(CodeAction.Create(
title: title,
createChangedDocument: c => InsertProviderAsync(context.Document, invocation, cultureMember, c),
equivalenceKey: title),
diagnostic);
}

private static async Task<Document> InsertProviderAsync(
Document document,
InvocationExpressionSyntax invocation,
string cultureMember,
CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
if (root is null) return document;

var cultureExpr = SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.IdentifierName("CultureInfo"),
SyntaxFactory.IdentifierName(cultureMember));
var providerArg = SyntaxFactory.Argument(cultureExpr);

// Insert as the new first argument; existing args shift right.
var newArgs = invocation.ArgumentList.Arguments.Insert(0, providerArg);
var newArgList = invocation.ArgumentList.WithArguments(newArgs);
var newInvocation = invocation.WithArgumentList(newArgList);

var newRoot = LuceneDev2000_2001_2002_2004_AddInvariantCultureCodeFixProvider
.EnsureGlobalizationUsing(root)
.ReplaceNode(invocation, newInvocation);
return document.WithSyntaxRoot(newRoot);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file for additional information.
* The ASF licenses this file under the Apache License, Version 2.0.
*/

using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Lucene.Net.CodeAnalysis.Dev.Utility;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Lucene.Net.CodeAnalysis.Dev.CodeFixes.LuceneDev2xxx
{
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(LuceneDev2005_2006_WrapNumericWithInvariantCodeFixProvider)), Shared]
public sealed class LuceneDev2005_2006_WrapNumericWithInvariantCodeFixProvider : CodeFixProvider
{
private const string TitleInvariant = "Wrap with .ToString(CultureInfo.InvariantCulture)";
private const string TitleCurrent = "Wrap with .ToString(CultureInfo.CurrentCulture)";

public override ImmutableArray<string> FixableDiagnosticIds =>
ImmutableArray.Create(
Descriptors.LuceneDev2005_NumericStringConcatenation.Id,
Descriptors.LuceneDev2006_NumericStringInterpolation.Id);

public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
if (root is null) return;

foreach (var diagnostic in context.Diagnostics)
{
var token = root.FindToken(diagnostic.Location.SourceSpan.Start);
var expression = token.Parent?
.AncestorsAndSelf()
.OfType<ExpressionSyntax>()
.FirstOrDefault(e => e.Span == diagnostic.Location.SourceSpan);

if (expression is null) continue;

Register(context, expression, "InvariantCulture", TitleInvariant, diagnostic);
Register(context, expression, "CurrentCulture", TitleCurrent, diagnostic);
}
}

private static void Register(
CodeFixContext context,
ExpressionSyntax expression,
string cultureMember,
string title,
Diagnostic diagnostic)
{
context.RegisterCodeFix(CodeAction.Create(
title: title,
createChangedDocument: c => WrapAsync(context.Document, expression, cultureMember, c),
equivalenceKey: title),
diagnostic);
}

private static async Task<Document> WrapAsync(
Document document,
ExpressionSyntax expression,
string cultureMember,
CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
if (root is null) return document;

// Parenthesize complex expressions so we don't accidentally bind .ToString to part of a larger expression.
ExpressionSyntax receiver = expression is IdentifierNameSyntax || expression is LiteralExpressionSyntax || expression is InvocationExpressionSyntax || expression is MemberAccessExpressionSyntax
? expression.WithoutTrivia()
: SyntaxFactory.ParenthesizedExpression(expression.WithoutTrivia());

var cultureExpr = SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.IdentifierName("CultureInfo"),
SyntaxFactory.IdentifierName(cultureMember));

var toStringCall = SyntaxFactory.InvocationExpression(
SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
receiver,
SyntaxFactory.IdentifierName("ToString")),
SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(cultureExpr))))
.WithLeadingTrivia(expression.GetLeadingTrivia())
.WithTrailingTrivia(expression.GetTrailingTrivia());

var newRoot = LuceneDev2000_2001_2002_2004_AddInvariantCultureCodeFixProvider
.EnsureGlobalizationUsing(root)
.ReplaceNode(expression, toStringCall);
return document.WithSyntaxRoot(newRoot);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ under the License.
<PackageReference Include="Lucene.Net.CodeAnalysis.Dev" VersionOverride="$(_CodeAnalysisPackageVersion)" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="J2N" />
</ItemGroup>

<Target Name="EnsureNuGetPackageBuilt" BeforeTargets="PrepareForBuild" Condition="'$(DesignTimeBuild)' != 'true'">

<Message Importance="high" Text="Running NuGet Package Build..." />
Expand Down
Loading
Loading