public void BaseInitialize() { Configuration = new OrderUsingsConfiguration { GroupsAndSpaces = GetRules() }; }
/// <summary> /// Recursively walk namespace declaration blocks, cleaning any import lists they /// contain. /// </summary> /// <param name="namespaceDeclarationNodes">The namespace declaration blocks /// to check.</param> /// <param name="configuration">The configuration defining the correct order /// and spacing.</param> private void WalkNamespaceDeclarations( TreeNodeCollection <ICSharpNamespaceDeclaration> namespaceDeclarationNodes, OrderUsingsConfiguration configuration) { foreach (var ns in namespaceDeclarationNodes) { CleanUsings(ns, configuration); WalkNamespaceDeclarations(ns.NamespaceDeclarations, configuration); } }
/// <summary> /// Invoked by ReSharper each time it wants us to perform some background processing /// of a file. /// </summary> /// <param name="process">Provides information about and services relating to the /// work we are being asked to do.</param> /// <param name="settings">Settings information.</param> /// <param name="processKind">The kind of processing we're being asked to do.</param> /// <param name="file">The file to be processed.</param> /// <returns>A process object representing the work, or null if no work will be done.</returns> protected override IDaemonStageProcess CreateProcess( IDaemonProcess process, IContextBoundSettingsStore settings, DaemonProcessKind processKind, ICSharpFile file) { if (process == null) { throw new ArgumentNullException("process"); } // StyleCop's daemon stage looks for a processKind of DaemonProcessKind.OTHER // and does nothing (returns null) if it sees it. This turns out to prevent // highlights from showing up when you ask ReSharper to inspect code issues // across the whole solution. I'm not sure why StyleCop deliberately opts out // of it. Perhaps something goes horribly wrong, but I've not seen any sign // of that yet, and we really do want solution-wide inspection to work. try { // I guess the base class checks that this is actually a C# file? if (!IsSupported(process.SourceFile)) { return(null); } // StyleCop checks to see if there are already any errors in the file, and if // there are, it decides to do nothing. // TODO: Do we need to do that? // TODO: We should probably check for exemptions, e.g. generated source files. } catch (ProcessCancelledException) { return(null); } // TODO: should we get an injected ISettingsOptimization? var orderUsingSettings = settings.GetKey <OrderUsingsSettings>(SettingsOptimization.DoMeSlowly); OrderUsingsConfiguration config = null; if (!string.IsNullOrWhiteSpace(orderUsingSettings.OrderSpecificationXml)) { config = ConfigurationSerializer.FromXml(new StringReader(orderUsingSettings.OrderSpecificationXml)); } return(new OrderUsingsDaemonStageProcess(process, file, config)); }
/// <inheritdoc/> public void Process( IPsiSourceFile sourceFile, IRangeMarker rangeMarker, CodeCleanupProfile profile, IProgressIndicator progressIndicator) { IPsiServices psiServices = sourceFile.GetPsiServices(); IPsiFiles psiFiles = psiServices.Files; IContextBoundSettingsStore settings = sourceFile.GetSettingsStore(); var orderUsingSettings = settings.GetKey <OrderUsingsSettings>(SettingsOptimization.DoMeSlowly); OrderUsingsConfiguration config = null; if (!string.IsNullOrWhiteSpace(orderUsingSettings.OrderSpecificationXml)) { config = ConfigurationSerializer.FromXml(new StringReader(orderUsingSettings.OrderSpecificationXml)); } if (config == null) { return; } var file = psiFiles.GetDominantPsiFile <CSharpLanguage>(sourceFile) as ICSharpFile; if (file == null) { return; } if (!profile.GetSetting(DescriptorInstance)) { return; } file.GetPsiServices().Transactions.Execute( "Code cleanup", () => { using (_shellLocks.UsingWriteLock()) { CleanUsings(file, config); WalkNamespaceDeclarations(file.NamespaceDeclarations, config); } }); }
/// <summary> /// Takes a configuration and a description of a using directive list, and /// produces two things: a list containing just the directives (with the /// items representing blank lines removed), and a description of the /// correct order and grouping for these items for the given configuration. /// </summary> /// <param name="configuration">The configuration that will determine the /// correct order and grouping.</param> /// <param name="items">A list of using directives and the blank lines /// interspersed therein. (To simplify processing, this may be null to /// represent the absence of any using directives.)</param> /// <param name="imports">The 'flattened' list (just the using directives, /// with any blank lines stripped out) will be written to this argument, /// unless <c>items</c> is null, in which case this will be set to null.</param> /// <param name="requiredOrderByGroups">The correct ordering and spacing /// for the using directives (as determined by the configuration) will be /// written to this argument (unless <c>items</c> is null, in which case /// this will be set to null).</param> /// <remarks> /// The correct order and spacing is represented as a list of lists. Each /// nested list represents a group of usings, where each group should be /// separated by a blank line. /// </remarks> public static void FlattenImportsAndDetermineOrderAndSpacing( OrderUsingsConfiguration configuration, List <UsingDirectiveOrSpace> items, out List <UsingDirective> imports, out List <List <UsingDirective> > requiredOrderByGroups) { imports = null; requiredOrderByGroups = null; if (items != null) { imports = items .Where(i => !i.IsBlankLine) .Select(i => i.Directive) .ToList(); requiredOrderByGroups = OrderAndSpacingGenerator.DetermineOrderAndSpacing(imports, configuration); } }
/// <summary> /// Fixes the order of the using directives in a given file or namespace block /// to match the specified configuration. /// </summary> /// <param name="holder">The file or namespace block in which to fix the order /// of using directives (if any are present).</param> /// <param name="configuration">The configuration determining the correct order.</param> public static void FixOrder( ICSharpTypeAndNamespaceHolderDeclaration holder, OrderUsingsConfiguration configuration) { // The reordering proceeds one item at a time, so we just keep reapplying it // until there's nothing left to do. // To avoid hanging VS in the event that an error in the logic causes the // sequence of modifications not to terminate, we ensure we don't try to // apply more changes than there are using directives. int tries = 0; int itemCount = 0; while (tries == 0 || tries <= itemCount) { List <UsingDirectiveOrSpace> items = ImportReader.ReadImports(holder); List <UsingDirective> imports; List <List <UsingDirective> > requiredOrderByGroups; ImportInspector.FlattenImportsAndDetermineOrderAndSpacing( configuration, items, out imports, out requiredOrderByGroups); if (requiredOrderByGroups == null) { break; } itemCount = imports.Count; Relocation nextChange = ImportInspector.GetNextUsingToMove(requiredOrderByGroups, imports); if (nextChange != null) { IUsingDirective toMove = holder.Imports[nextChange.From]; IUsingDirective before = holder.Imports[nextChange.To]; holder.RemoveImport(toMove); holder.AddImportBefore(toMove, before); tries += 1; } else { break; } } }
/// <summary> /// Initializes a <see cref="BaseHighlighting"/>. /// </summary> /// <param name="typeAndNamespaceHolder">The file or namespace block that contains /// the import list being highlighted.</param> /// <param name="config">The configuration that was active when we determined that /// the import list does not match the requirements.</param> internal BaseHighlighting( ICSharpTypeAndNamespaceHolderDeclaration typeAndNamespaceHolder, OrderUsingsConfiguration config) { _config = config; _typeAndNamespaceHolder = typeAndNamespaceHolder; }
/// <summary> /// Fixes the spacing of the using directives in a given file or namespace block /// to match the specified configuration. (The directives must already be in /// the correct order.) /// </summary> /// <param name="holder">The file or namespace block in which to fix the spacing /// of using directives (if any are present).</param> /// <param name="configuration">The configuration determining the correct spacing.</param> public static void FixSpacing( ICSharpTypeAndNamespaceHolderDeclaration holder, OrderUsingsConfiguration configuration) { // The reordering proceeds one item at a time, so we just keep reapplying it // until there's nothing left to do. // To avoid hanging VS in the event that an error in the logic causes the // sequence of modifications not to terminate, we ensure we don't try to // apply more changes than there are either using directives or blank // lines in the usings list. int tries = 0; int itemCount = 0; while (tries == 0 || tries <= itemCount) { List <UsingDirectiveOrSpace> items = ImportReader.ReadImports(holder); if (items == null) { return; } itemCount = items.Count; List <UsingDirective> imports; List <List <UsingDirective> > requiredOrderByGroups; ImportInspector.FlattenImportsAndDetermineOrderAndSpacing( configuration, items, out imports, out requiredOrderByGroups); SpaceChange nextChange = ImportInspector.GetNextSpacingModification(requiredOrderByGroups, items); if (nextChange != null) { IUsingDirective usingBeforeSpace = holder.Imports[nextChange.Index - 1]; if (nextChange.ShouldInsert) { using (WriteLockCookie.Create()) { var newLineText = new StringBuffer("\r\n"); LeafElementBase newLine = TreeElementFactory.CreateLeafElement( CSharpTokenType.NEW_LINE, newLineText, 0, newLineText.Length); LowLevelModificationUtil.AddChildAfter(usingBeforeSpace, newLine); } } else { var syb = usingBeforeSpace.NextSibling; for (; syb != null && !(syb is IUsingDirective); syb = syb.NextSibling) { if (syb.NodeType == CSharpTokenType.NEW_LINE) { LowLevelModificationUtil.DeleteChild(syb); } } } } else { break; } tries += 1; } }
/// <summary> /// Initializes a <see cref="UsingOrderHighlighting"/>. /// </summary> /// <param name="typeAndNamespaceHolder">The file or namespace block that contains /// the import list being highlighted.</param> /// <param name="config">The configuration that was active when we determined that /// the import list does not match the requirements.</param> internal UsingSpacingHighlighting( ICSharpTypeAndNamespaceHolderDeclaration typeAndNamespaceHolder, OrderUsingsConfiguration config) : base(typeAndNamespaceHolder, config) { }
/// <summary> /// Calculates how using directives should be ordered and, where appropriate, /// split into groups separated by blank lines. /// </summary> /// <param name="directives">The directives for which to determine the order /// and spacing.</param> /// <param name="configuration">The configuration settings describing the /// required ordering and spacing.</param> /// <returns>A list of lists. This will be empty if the input list was empty. /// Otherwise there will be at least one list; if the rules require that any /// of the directives be separated by spaces this will be indicated by /// returning multiple lists - a blank line should appear between each of /// the lists. Within each of the nested lists returned, the directives are /// in the order they should appear.</returns> public static List <List <UsingDirective> > DetermineOrderAndSpacing( IEnumerable <UsingDirective> directives, OrderUsingsConfiguration configuration) { if (directives == null) { throw new ArgumentNullException("directives"); } Dictionary <GroupRule, Predicate <UsingDirective> > groupNamespaceMatchers = configuration.GroupsAndSpaces .Where(gs => !gs.IsSpace) .ToDictionary <ConfigurationRule, GroupRule, Predicate <UsingDirective> >( gs => gs.Rule, gs => { Regex nsRegex = null; Regex aliasRegex = null; MatchType matchType = gs.Rule.Type; switch (matchType) { case MatchType.Import: nsRegex = RegexForPattern(gs.Rule.NamespacePattern); break; case MatchType.Alias: aliasRegex = RegexForPattern(gs.Rule.AliasPattern); break; case MatchType.ImportOrAlias: nsRegex = RegexForPattern(gs.Rule.NamespacePattern); aliasRegex = RegexForPattern(gs.Rule.AliasPattern); break; default: throw new ArgumentOutOfRangeException(); } return(directive => { bool hasAlias = directive.Alias != null; if (matchType == MatchType.Alias && !hasAlias) { return false; } if (matchType == MatchType.Import && hasAlias) { return false; } return (nsRegex == null || nsRegex.IsMatch(directive.Namespace)) && (aliasRegex == null || !hasAlias || aliasRegex.IsMatch(directive.Alias)); }); }); ILookup <GroupRule, UsingDirective> directivesByGroup = directives.ToLookup( d => groupNamespaceMatchers .Where(e => e.Value(d)) .OrderBy(e => e.Key.Priority) .First().Key); List <UsingDirective> currentItemSet = null; var results = new List <List <UsingDirective> >(); GroupRule lastRule = null; foreach (ConfigurationRule ruleEntry in configuration.GroupsAndSpaces) { if (ruleEntry.IsSpace) { MakeGroupFromCurrentItemsIfAny(ref currentItemSet, lastRule, results); } else { UsingComparer comparer = ruleEntry.Rule.OrderAliasesBy == OrderAliasBy.Alias ? CompareByAlias : CompareByNamespace; if (currentItemSet == null) { currentItemSet = new List <UsingDirective>(); } currentItemSet.AddRange(directivesByGroup[ruleEntry.Rule].OrderBy(d => d, comparer)); lastRule = ruleEntry.Rule; } } MakeGroupFromCurrentItemsIfAny(ref currentItemSet, lastRule, results); return(results); }
/// <summary> /// Initializes a <see cref="OrderUsingsDaemonStageProcess"/>. /// </summary> /// <param name="process">The process object supplied by R# for this work.</param> /// <param name="file">The file to process.</param> /// <param name="config">The order and spacing configuration to use.</param> public OrderUsingsDaemonStageProcess(IDaemonProcess process, ICSharpFile file, OrderUsingsConfiguration config) { _file = file; _config = config; DaemonProcess = process; }
/// <summary> /// Fixes the order and spacing for the using blocks in a file or namespace declaration block. /// </summary> /// <param name="holder">The file or namespace declaration blocks to check.</param> /// <param name="configuration">The configuration defining the correct order /// and spacing.</param> private void CleanUsings( ICSharpTypeAndNamespaceHolderDeclaration holder, OrderUsingsConfiguration configuration) { Fixes.FixOrder(holder, configuration); Fixes.FixSpacing(holder, configuration); }