Publié samedi 10 mars 2012 14:50 par Groc

Roslyn : De l’auto complétion pour le nommage de mes variables

Quand j’écris du code, je suis un peu maniaque dans ma manière de nommer mes variables, propriétés, méthodes etc. Si je dois reprendre du code écrit par quelqu’un d’autre et que ce code ne respecte pas les conventions de nommage que j’ai l’habitude d’utiliser, ça me stresse (attitude totalement égocentrique et j’imagine que ça agace mes collègues… Smile).

Je ne sais jamais si cela s’appelle du pascal case, du camel case ou autre, toujours est-il que le bout de code ci-dessous reprend les conventions que j’utilise.

class Program { private readonly IEnumerable<ProductDetail> _productDetails = new List<ProductDetail>(); public IEnumerable<ProductDetail> ProductDetails { get { return _productDetails; } } static void Main(string[] args) { Program program; } public void AddProductDetail(ProductDetail productDetail) { } } class ProductDetail { public string Name { get; set; } }

Grâce à Roslyn, je vais pouvoir me développer un add in qui m’aidera à automatiquement nommer les éléments de mon code comme ci dessus.

La CTP de roslyn est livrée avec plusieurs template de projet, j’utilise ici un Completion Provider. L’idée étant d’avoir une classe qui implémente l’interface ICompletionProvider, et donc une méthode GetItems qui retourne une collection de ICompletionItem.

Voici déjà quelques helpers pour formatter correctement mes chaines de caractères et pour les passer facilement au pluriel.

public static class StringExtensions { private static readonly IList<string> Unpluralizables = new List<string> { "equipment", "information", "rice", "money", "species", "series", "fish", "sheep", "deer" }; private static readonly IDictionary<string, string> Pluralizations = new Dictionary<string, string> { { "person", "people" }, { "ox", "oxen" }, { "child", "children" }, { "foot", "feet" }, { "tooth", "teeth" }, { "goose", "geese" }, { "(.*)fe?", "$1ves" }, { "(.*)man$", "$1men" }, { "(.+[aeiou]y)$", "$1s" }, { "(.+[^aeiou])y$", "$1ies" }, { "(.+z)$", "$1zes" }, { "([m|l])ouse$", "$1ice" }, { "(.+)(e|i)x$", @"$1ices"}, { "(octop|vir)us$", "$1i"}, { "(.+(s|x|sh|ch))$", @"$1es"}, { "(.+)", @"$1s" } }; public static string Pluralize(this string str) { if (Unpluralizables.Contains(str)) return str; var plural = ""; foreach (var pluralization in Pluralizations) { if (Regex.IsMatch(str, pluralization.Key)) { plural = Regex.Replace(str, pluralization.Key, pluralization.Value, RegexOptions.IgnoreCase); break; } } return plural; } public static string ToLowerCamelCase(this string str) { var chars = str.ToCharArray(); chars[0] = char.ToLower(chars[0]); return new string(chars); } }

Tous les éléments de syntaxe dans mon code sont des SyntaxNode (j’y reviendrais plus tard dans ce billet). Lorsque l’élément représente un paramètre ou une propriété par exemple, il est donc lié à un type représenté par la classe TypeSyntax. Attention, le TypeSyntax représente un type dans l’arbre syntaxique, en gros, juste une chaine de caractères, pas un Type tel qu’on manipule habituellement en .net. Pour pouvoir faire le lien entre ce TypeSyntax et le Type qu’il représente, on peut utiliser une instance de ISemanticModel. Etant liée au fichier de code source courant, et donc à a son contexte, elle saura retrouver le bon type correspondant.

Voici donc une méthodes d’extension pour les TypeSyntax qui me renverra la chaine de caractère adéquat. Grâce au semantic model, je peux vérifier si le type implémente IEnumrable<T> et donc pluraliser le type T en conséquence.

public enum CompletionFormat { UpperCase, LowerCase, UnderscoreWithLowerCase } public static class TypeSyntaxExtensions { public static IEnumerable<CompletionItem> ToCompletionItems(this TypeSyntax typeSyntax, ISemanticModel semanticModel, CompletionFormat completionFormat) { var completionItems = new List<CompletionItem>(); var typeSymbol = semanticModel.GetSemanticInfo(typeSyntax).Type as NamedTypeSymbol; // doest type reppresent a generic list ? if (typeSymbol != null && typeSymbol.AllInterfaces.Any(i => i.Name == typeof(IEnumerable).Name) && typeSymbol.TypeArguments.Count > 0) { var firstTypeArgument = typeSymbol.TypeArguments.First(); var pluralizedName = firstTypeArgument.Name.Pluralize(); switch (completionFormat) { case CompletionFormat.UpperCase: completionItems.Add(new CompletionItem(pluralizedName)); break; case CompletionFormat.LowerCase: completionItems.Add(new CompletionItem(pluralizedName.ToLowerCamelCase())); break; case CompletionFormat.UnderscoreWithLowerCase: completionItems.Add(new CompletionItem(string.Format("_{0}", pluralizedName.ToLowerCamelCase()))); break; } } else { switch (completionFormat) { case CompletionFormat.UpperCase: completionItems.Add(new CompletionItem(typeSyntax.PlainName)); break; case CompletionFormat.LowerCase: completionItems.Add(new CompletionItem(typeSyntax.PlainName.ToLowerCamelCase())); break; case CompletionFormat.UnderscoreWithLowerCase: completionItems.Add(new CompletionItem(string.Format("_{0}", typeSyntax.PlainName.ToLowerCamelCase()))); break; } } return completionItems; } }

Pour terminer, il ne me reste plus que la méthode GetItems à compléter. Je récupère le syntax tree du document courant, je récupère le semantic model, je récupère l’élément courant dans mon syntax tree et je regarde son type. Le syntax tree est composé de syntax node qui peuvent être des FieldDeclarationSyntax, des PropertyDeclaractionSyntax, des VariableDeclarationSyntax etc. Selon ce type, j’appelle la méthode d’extension ToCompletionItems sur le TypeSyntax associé en lui passant la valeur de mon énumération CompletionFormat qui correspond à mes attentes en métière de nommage.

[Order(After = PredefinedCompletionProviderNames.Keyword)] [ExportCompletionProvider("CompletionProvider1", LanguageNames.CSharp)] class CompletionProvider : ICompletionProvider { public IEnumerable<ICompletionItem> GetItems(IDocument document, int position, CancellationToken cancellationToken) { var syntaxTree = (SyntaxTree)document.GetSyntaxTree(cancellationToken); var semanticModel = document.Project.GetCompilation().GetSemanticModel(syntaxTree); var token = (SyntaxToken)syntaxTree.Root.FindToken(position); var ancestors = token.Parent.AncestorsAndSelf(); if (ancestors.OfType<FieldDeclarationSyntax>().Any()) { var fieldDeclarationSyntax = ancestors.OfType<FieldDeclarationSyntax>().First(); if (fieldDeclarationSyntax.Modifiers.Any(c => c.Kind == SyntaxKind.PrivateKeyword)) return fieldDeclarationSyntax.Declaration.Type.ToCompletionItems(semanticModel, CompletionFormat.UnderscoreWithLowerCase); else return fieldDeclarationSyntax.Declaration.Type.ToCompletionItems(semanticModel, CompletionFormat.UpperCase); } else if (ancestors.OfType<PropertyDeclarationSyntax>().Any()) { var propertyDeclarationSyntax = ancestors.OfType<PropertyDeclarationSyntax>().First(); return propertyDeclarationSyntax.Type.ToCompletionItems(semanticModel, CompletionFormat.UpperCase); } else if (ancestors.OfType<VariableDeclarationSyntax>().Any()) { var variableDeclarationSyntax = ancestors.OfType<VariableDeclarationSyntax>().First(); return variableDeclarationSyntax.Type.ToCompletionItems(semanticModel, CompletionFormat.LowerCase); } else if (ancestors.OfType<ParameterSyntax>().Any()) { var parameterSyntax = ancestors.OfType<ParameterSyntax>().First(); return parameterSyntax.TypeOpt.ToCompletionItems(semanticModel, CompletionFormat.LowerCase); } else if (ancestors.OfType<ParameterListSyntax>().Any()) { var parameterListSyntax = ancestors.OfType<ParameterListSyntax>().First(); var parameterSyntax = parameterListSyntax.ChildNodes().OfType<ParameterSyntax>().LastOrDefault(); if (parameterSyntax != null) { return parameterSyntax.TypeOpt.ToCompletionItems(semanticModel, CompletionFormat.LowerCase); } } return Enumerable.Empty<ICompletionItem>(); } }

En espérant que ça puisse vous être utile Smile

Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Classé sous ,

Les 10 derniers blogs postés

- Conf’SharePoint : 10 bonnes raisons pour ne pas la rater par Le petit blog de Pierre / Pierre's little blog le 05-14-2013, 02:24

- [Event] Soirée de lancement Agile .NET France à Lyon par Blog Agile/ALM de Vincent THAVONEKHAM le 05-13-2013, 01:29

- .NET / Debug : inspection de la mémoire d'applications .NET (dump ou processus live) : première livraison d'une librairie .NET par Microsoft par CoqBlog le 05-11-2013, 22:21

- SharePoint : Incompatibilité avec Internet Explorer 10 (IE10) par Blog Technique de Romelard Fabrice le 05-08-2013, 16:29

- AutoSPInstaller pour SharePoint 2013 maintenant disponible en “RTM” par Julien Chable le 05-06-2013, 23:30

- [TFS2010] A la recherche du Shelveset perdu par Blog de Jérémy Jeanson le 05-03-2013, 21:46

- .NET / Debug post-mortem : obtenir le fichier mscordacwks.dll correspondant à un dump quand on n'a plus d'accès direct à ce fichier par CoqBlog le 04-28-2013, 19:57

- [W8] Afficher un graphe par CPU dans le gestionnaire des tâches par Blog de Jérémy Jeanson le 04-28-2013, 17:48

- [WCF] Limiter proprement l’accès à vos ressources serveur par Blog de Jérémy Jeanson le 04-26-2013, 22:59

- Event : Je serai speaker à la Conf’SharePoint par Blog Technique de Romelard Fabrice le 04-26-2013, 12:00