예제 #1
0
        private static async Task <Solution> AddAndUseResourceAsync(Document document, LiteralExpressionSyntax literal, ExpressionSyntax expression, string key, INamedTypeSymbol resourcesType, CancellationToken cancellationToken)
        {
            if (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false) is { } root&&
                resourcesType.DeclaringSyntaxReferences.TrySingle(out var declaration) &&
                document.Project.Documents.TrySingle(x => x.FilePath == declaration.SyntaxTree.FilePath, out var designerDoc) &&
                await designerDoc.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false) is { } designerRoot&&
                ResxFile.TryGetDefault(resourcesType, out var resx))
            {
                resx.Add(key, literal.Token.ValueText);
                return(document.Project.Solution.WithDocumentSyntaxRoot(
                           document.Id,
                           root.ReplaceNode(literal, expression))
                       .WithDocumentSyntaxRoot(
                           designerDoc.Id,
                           AddProperty()));
            }

            return(document.Project.Solution);

            SyntaxNode AddProperty()
            {
                // Adding a temp key so that we don't have a build error until next gen.
                // public static string Key => ResourceManager.GetString("Key", resourceCulture);
                if (designerRoot.DescendantNodes().TryLastOfType <SyntaxNode, PropertyDeclarationSyntax>(out var property))
                {
                    return(designerRoot.InsertNodesAfter(
                               property,
                               new[]
                    {
                        SyntaxFactory.PropertyDeclaration(
예제 #2
0
        private static async Task <Solution> RenameAsync(Document document, IPropertySymbol property, string newName, CancellationToken cancellationToken)
        {
            if (ResxFile.TryGetDefault(property.ContainingType, out var resx))
            {
                resx.RenameKey(property.Name, newName);
                foreach (var cultureResx in resx.CultureSpecific())
                {
                    cultureResx.RenameKey(property.Name, newName);
                }

                UpdateXaml(document.Project, property, newName);
                foreach (var project in document.Project.ReferencingProjects())
                {
                    UpdateXaml(project, property, newName);
                }

                var solution = await Renamer.RenameSymbolAsync(document.Project.Solution, property, newName, null, cancellationToken);

                if (property.TrySingleDeclaration(cancellationToken, out PropertyDeclarationSyntax declaration))
                {
                    var root = await document.GetSyntaxRootAsync(cancellationToken);

                    return(solution.WithDocumentSyntaxRoot(
                               document.Id,
                               root.ReplaceNode(
                                   declaration,
                                   Property.Rewrite(declaration, newName))));
                }

                return(solution);
            }

            return(document.Project.Solution);
        }
        private static bool IsIdentical(ResxFile resx, string key1, string key2)
        {
            foreach (var cultureResx in resx.CultureSpecific())
            {
                if (cultureResx.TryGetString(key1, out var string1))
                {
                    if (cultureResx.TryGetString(key2, out var string2))
                    {
                        if (string1 != string2)
                        {
                            return(false);
                        }
                    }
                    else
                    {
                        return(false);
                    }
                }
                else if (cultureResx.TryGetString(key2, out _))
                {
                    return(false);
                }
            }

            return(true);
        }
예제 #4
0
        private static ResxFile Update(string fileName, ResxFile old)
        {
            if (old == null ||
                old.lastWriteTimeUtc != new FileInfo(fileName).LastWriteTimeUtc)
            {
                return(Create(fileName));
            }

            return(old);
        }
        private static async Task <Solution> AddAndUseResourceAsync(Document document, LiteralExpressionSyntax literal, ExpressionSyntax expression, string key, INamedTypeSymbol resourcesType, CancellationToken cancellationToken)
        {
            if (await document.GetSyntaxRootAsync(cancellationToken) is SyntaxNode root &&
                resourcesType.DeclaringSyntaxReferences.TrySingle(out var declaration) &&
                document.Project.Documents.TrySingle(x => x.FilePath == declaration.SyntaxTree.FilePath, out var designerDoc) &&
                await designerDoc.GetSyntaxRootAsync(cancellationToken) is SyntaxNode designerRoot &&
                ResxFile.TryGetDefault(resourcesType, out var resx))
            {
                resx.Add(key, literal.Token.ValueText);
                return(document.Project.Solution.WithDocumentSyntaxRoot(
                           document.Id,
                           root.ReplaceNode(literal, expression))
                       .WithDocumentSyntaxRoot(
                           designerDoc.Id,
                           AddProperty()));
            }

            return(document.Project.Solution);

            SyntaxNode AddProperty()
            {
                // Adding a temp key so that we don't have a build error until next gen.
                // public static string Key => ResourceManager.GetString("Key", resourceCulture);
                if (designerRoot.DescendantNodes().TryLastOfType(out PropertyDeclarationSyntax property))
                {
                    return(designerRoot.InsertNodesAfter(
                               property,
#pragma warning disable SA1118 // Parameter must not span multiple lines
                               new[]
                    {
                        SyntaxFactory.PropertyDeclaration(
                            default(SyntaxList <AttributeListSyntax>),
                            SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword)),
                            SyntaxFactory.ParseTypeName("string"),
                            default(ExplicitInterfaceSpecifierSyntax),
                            SyntaxFactory.ParseToken(key),
                            default(AccessorListSyntax),
                            SyntaxFactory.ArrowExpressionClause(SyntaxFactory.ParseExpression($"ResourceManager.GetString(\"{key}\", resourceCulture)")),
                            default(EqualsValueClauseSyntax),
                            SyntaxFactory.Token(SyntaxKind.SemicolonToken))
                    }));

#pragma warning restore SA1118 // Parameter must not span multiple lines
                }

                return(designerRoot);
            }
        }
예제 #6
0
        internal static bool TryGetDefault(INamedTypeSymbol resourcesType, out ResxFile resxFile)
        {
            resxFile = null;
            if (resourcesType != null &&
                resourcesType.Name == "Resources" &&
                resourcesType.TryFindProperty("ResourceManager", out _) &&
                resourcesType.DeclaringSyntaxReferences.TrySingle(out var reference) &&
                reference.SyntaxTree?.FilePath is string resourcesFileName &&
                resourcesFileName.Replace("Resources.Designer.cs", "Resources.resx") is string fileName &&
                File.Exists(fileName))
            {
                resxFile = Cache.AddOrUpdate(fileName, s => Create(s), (s, file) => Update(s, file));
            }

            return(resxFile != null);
        }
예제 #7
0
        public override async Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            var syntaxRoot = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);

            var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);

            foreach (var diagnostic in context.Diagnostics)
            {
                if (syntaxRoot.TryFindNodeOrAncestor <PropertyDeclarationSyntax>(diagnostic, out var propertyDeclaration) &&
                    semanticModel.TryGetSymbol(propertyDeclaration, context.CancellationToken, out var property) &&
                    diagnostic.Properties.TryGetValue("Key", out var name) &&
                    !property.ContainingType.TryFindFirstMember(name, out _) &&
                    ResxFile.TryGetDefault(property.ContainingType, out _))
                {
                    context.RegisterCodeFix(
                        new PreviewCodeAction(
                            "Rename resource",
                            cancellationToken => Renamer.RenameSymbolAsync(context.Document.Project.Solution, property, name, null, cancellationToken),
                            cancellationToken => RenameAsync(context.Document, property, name, cancellationToken)),
                        diagnostic);
                }
            }
        }
예제 #8
0
        public override async Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            if (await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false) is { } syntaxRoot&&
                await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false) is { } semanticModel)
            {
                foreach (var diagnostic in context.Diagnostics)
                {
                    if (syntaxRoot.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true) is LiteralExpressionSyntax literal &&
                        Resources.TryGetKey(literal.Token.ValueText, out var key))
                    {
                        foreach (var resourcesType in semanticModel.LookupResourceTypes(diagnostic.Location.SourceSpan.Start, name: "Resources"))
                        {
                            if (resourcesType.TryFindProperty(key, out _))
                            {
                                var memberAccess = resourcesType.ToMinimalDisplayString(semanticModel, literal.SpanStart, SymbolDisplayFormat.MinimallyQualifiedFormat);
                                if (semanticModel.ReferencesGuLocalization())
                                {
                                    if (TryFindCustomTranslate(resourcesType, out var customTranslate))
                                    {
                                        var translateKey = $"{customTranslate.ContainingType.ToMinimalDisplayString(semanticModel, literal.SpanStart, SymbolDisplayFormat.MinimallyQualifiedFormat)}.{customTranslate.Name}";
                                        context.RegisterCodeFix(
                                            CodeAction.Create(
                                                $"Use existing {memberAccess}.{key} in {translateKey}({memberAccess}.{key})",
                                                _ => Task.FromResult(
                                                    context.Document.WithSyntaxRoot(
                                                        syntaxRoot.ReplaceNode(
                                                            literal,
                                                            SyntaxFactory
                                                            .ParseExpression(
                                                                $"{translateKey}(nameof({memberAccess}.{key}))")
                                                            .WithSimplifiedNames()))),
                                                $"Use existing {memberAccess}.{key} in {translateKey}({memberAccess}.{key})"),
                                            diagnostic);
                                    }

                                    context.RegisterCodeFix(
                                        CodeAction.Create(
                                            $"Use existing {memberAccess}.{key} in Translator.Translate",
                                            _ => Task.FromResult(
                                                context.Document.WithSyntaxRoot(
                                                    syntaxRoot.ReplaceNode(
                                                        literal,
                                                        SyntaxFactory
                                                        .ParseExpression(
                                                            $"Gu.Localization.Translator.Translate({memberAccess}.ResourceManager, nameof({memberAccess}.{key}))")
                                                        .WithSimplifiedNames()))),
                                            $"Use existing {memberAccess}.{key} in Translator.Translate"),
                                        diagnostic);
                                }

                                context.RegisterCodeFix(
                                    CodeAction.Create(
                                        $"Use existing {memberAccess}.{key}.",
                                        _ => Task.FromResult(
                                            context.Document.WithSyntaxRoot(
                                                syntaxRoot.ReplaceNode(
                                                    literal,
                                                    SyntaxFactory.ParseExpression($"{memberAccess}.{key}")
                                                    .WithSimplifiedNames()))),
                                        $"Use existing {memberAccess}.{key}."),
                                    diagnostic);
                            }
                            else if (ResxFile.TryGetDefault(resourcesType, out _))
                            {
                                var memberAccess = resourcesType.ToMinimalDisplayString(semanticModel, literal.SpanStart, SymbolDisplayFormat.MinimallyQualifiedFormat);
                                if (semanticModel.ReferencesGuLocalization())
                                {
                                    if (TryFindCustomTranslate(resourcesType, out var customTranslate))
                                    {
                                        var translateKey = $"{customTranslate.ContainingType.ToMinimalDisplayString(semanticModel, literal.SpanStart, SymbolDisplayFormat.MinimallyQualifiedFormat)}.{customTranslate.Name}";
                                        context.RegisterCodeFix(
                                            new PreviewCodeAction(
                                                $"Move to {memberAccess}.{key} and use {translateKey}({memberAccess}.{key}).",
                                                _ => Task.FromResult(
                                                    context.Document.WithSyntaxRoot(
                                                        syntaxRoot.ReplaceNode(
                                                            literal,
                                                            SyntaxFactory.ParseExpression($"{translateKey}(nameof({memberAccess}.{key}))")
                                                            .WithSimplifiedNames()))),
                                                cancellationToken => AddAndUseResourceAsync(
                                                    context.Document,
                                                    literal,
                                                    SyntaxFactory.ParseExpression($"{translateKey}(nameof({memberAccess}.{key}))")
                                                    .WithSimplifiedNames(),
                                                    key,
                                                    resourcesType,
                                                    cancellationToken)),
                                            diagnostic);
                                    }

                                    context.RegisterCodeFix(
                                        new PreviewCodeAction(
                                            $"Move to {memberAccess}.{key} and use Translator.Translate.",
                                            _ => Task.FromResult(
                                                context.Document.WithSyntaxRoot(
                                                    syntaxRoot.ReplaceNode(
                                                        literal,
                                                        SyntaxFactory.ParseExpression($"Gu.Localization.Translator.Translate({memberAccess}.ResourceManager, nameof({memberAccess}.{key}))")
                                                        .WithSimplifiedNames()))),
                                            cancellationToken => AddAndUseResourceAsync(
                                                context.Document,
                                                literal,
                                                SyntaxFactory.ParseExpression(
                                                    $"Gu.Localization.Translator.Translate({memberAccess}.ResourceManager, nameof({memberAccess}.{key}))")
                                                .WithSimplifiedNames(),
                                                key,
                                                resourcesType,
                                                cancellationToken)),
                                        diagnostic);
                                }

                                context.RegisterCodeFix(
                                    new PreviewCodeAction(
                                        $"Move to {memberAccess}.{key}.",
                                        _ => Task.FromResult(
                                            context.Document.WithSyntaxRoot(
                                                syntaxRoot.ReplaceNode(
                                                    literal,
                                                    SyntaxFactory.ParseExpression($"{memberAccess}.{key}")
                                                    .WithSimplifiedNames()))),
                                        cancellationToken => AddAndUseResourceAsync(
                                            context.Document,
                                            literal,
                                            SyntaxFactory.ParseExpression($"{memberAccess}.{key}")
                                            .WithSimplifiedNames(),
                                            key,
                                            resourcesType,
                                            cancellationToken)),
                                    diagnostic);
                            }
                        }
                    }
                }
            }
        }
        private static void Handle(SyntaxNodeAnalysisContext context)
        {
            if (!context.IsExcludedFromAnalysis() &&
                context.Node is PropertyDeclarationSyntax propertyDeclaration &&
                context.ContainingSymbol is IPropertySymbol {
                Type : { SpecialType : SpecialType.System_String }
            } property&&
                ResxFile.TryGetDefault(property.ContainingType, out var resx) &&
                resx.TryGetString(property.Name, out var neutral))
            {
                if (Resources.TryGetKey(neutral, out var key) &&
                    key != property.Name)
                {
                    context.ReportDiagnostic(
                        Diagnostic.Create(
                            Descriptors.GULOC07KeyDoesNotMatch,
                            propertyDeclaration.Identifier.GetLocation(),
                            ImmutableDictionary <string, string> .Empty.Add("Key", key),
                            key));
                }

                foreach (var data in resx.Document.Root.Elements("data"))
                {
                    if (ResxFile.TryGetString(data, out var candidateText) &&
                        candidateText == neutral &&
                        ResxFile.TryGetName(data, out var candidateName) &&
                        candidateName != property.Name)
                    {
                        if (IsIdentical(resx, property.Name, candidateName))
                        {
                            context.ReportDiagnostic(
                                Diagnostic.Create(
                                    Descriptors.GULOC09Duplicate,
                                    propertyDeclaration.Identifier.GetLocation(),
                                    neutral));
                        }
                        else
                        {
                            context.ReportDiagnostic(
                                Diagnostic.Create(
                                    Descriptors.GULOC08DuplicateNeutral,
                                    propertyDeclaration.Identifier.GetLocation(),
                                    neutral));
                        }
                    }
                }

                foreach (var cultureResx in resx.CultureSpecific())
                {
                    if (!cultureResx.TryGetString(property.Name, out _))
                    {
                        var culture = Regex.Match(cultureResx.FileName, @"\.(?<culture>[^.]+)\.resx", RegexOptions.IgnoreCase | RegexOptions.RightToLeft | RegexOptions.Singleline)
                                      .Groups["culture"]
                                      .Value;
                        context.ReportDiagnostic(
                            Diagnostic.Create(
                                Descriptors.GULOC10MissingTranslation,
                                propertyDeclaration.Identifier.GetLocation(),
                                culture,
                                neutral));
                    }
                }
            }
        }