Vannevelj / VSDiagnostics

A collection of static analyzers based on Roslyn that integrate with VS
GNU General Public License v2.0
65 stars 16 forks source link

String.Format(Method(),params) #643

Closed 0Lucifer0 closed 5 years ago

0Lucifer0 commented 5 years ago

Hi,

People who are doing the translation in resx files on my project are not developers and sometime it happen they put an argument like {0} in the text. Which make a runtime exception on my app. Is there anyway to prevent this to tell the developer to update the code to match the text ? or would it be too difficult to add on string.Format analyzer ?

i wonder if diagnostic is able to check string in a ressources files.

example of my code:

string.Format(LogLanguage.Instance.GetMessageFromKey(LanguageKey.TEST));

with key:TEST
and value:"this is a {0} test of runtime error"
    public sealed class LogLanguage
    {
        private static LogLanguage _instance;

        public static RegionType Language { get; set; }

        private static readonly CultureInfo _resourceCulture = new CultureInfo(Language.ToString());

        private readonly ResourceManager _manager;

        private LogLanguage()
        {
            Assembly assem = typeof(LogLanguage).Assembly;
            if (assem != null)
            {
                _manager = new ResourceManager(
                    assem.GetName().Name + ".Resource.LocalizedResources",
                    assem);
            }
        }

        public static LogLanguage Instance => _instance ?? (_instance = new LogLanguage());

        public string GetMessageFromKey(LanguageKey messageKey) => GetMessageFromKey(messageKey, null);
        public string GetMessageFromKey(LanguageKey messageKey, string culture)
        {
            var cult = culture != null ? new CultureInfo(culture) : _resourceCulture;
            var resourceMessage = _manager != null && messageKey.ToString() != null
                ? _manager.GetResourceSet(cult, true,
                        cult.TwoLetterISOLanguageName == default(RegionType).ToString().ToLower())
                    ?.GetString(messageKey.ToString()) : string.Empty;

            return !string.IsNullOrEmpty(resourceMessage) ? resourceMessage : $"#<{messageKey.ToString()}>";
        }

        public ResourceSet GetRessourceSet() => GetRessourceSet(null);
        public ResourceSet GetRessourceSet(string culture)
        {
            return _manager?.GetResourceSet(culture != null ? new CultureInfo(culture) : _resourceCulture, true, true);
        }
    }
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
    public class LocalizedResources {

        private static global::System.Resources.ResourceManager resourceMan;

        private static global::System.Globalization.CultureInfo resourceCulture;

        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        internal LocalizedResources() {
        }

        /// <summary>
        ///   Retourne l'instance ResourceManager mise en cache utilisée par cette classe.
        /// </summary>
        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
        public static global::System.Resources.ResourceManager ResourceManager {
            get {
                if (object.ReferenceEquals(resourceMan, null)) {
                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NosCore.Shared.Resource.LocalizedResources", typeof(LocalizedResources).Assembly);
                    resourceMan = temp;
                }
                return resourceMan;
            }
        }
 /// <summary>
        ///   Remplace la propriété CurrentUICulture du thread actuel pour toutes
        ///   les recherches de ressources à l'aide de cette classe de ressource fortement typée.
        /// </summary>
        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
        public static global::System.Globalization.CultureInfo Culture {
            get {
                return resourceCulture;
            }
            set {
                resourceCulture = value;
            }
        }

        public static string TEST {
            get {
                return ResourceManager.GetString("TEST", resourceCulture);
            }
        }
}
Vannevelj commented 5 years ago

This won't be possible with the string.Format analyzer since it requires a constant value (i.e. a string) to inspect the format pattern. It is obviously out of the scope of this project but what you could do yourself is write an analyzer that reads a projects AdditionalFiles, finds your translation file and then parses the XML inside of it. You can then do something like a simple string.Contains("{0}") check and report an error if it finds one. Alternatively you could write a github hook that does a similar same thing and reports that the PR can't be merged.

0Lucifer0 commented 5 years ago

Thank you for this answer :) It is exactly what i though. Was also thinking about doing a specific tools for those. The github hook is actually a really good idea!