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(
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); }
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); } }
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); }
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); } } }
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)); } } } }