Skip to content

Commit 63434b7

Browse files
committed
Fixed #2026 and #2027
1 parent 40c163a commit 63434b7

15 files changed

Lines changed: 1303 additions & 615 deletions
Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
namespace StyleCop.Analyzers.OrderingRules
5+
{
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Collections.Immutable;
9+
using System.Linq;
10+
using Microsoft.CodeAnalysis;
11+
using Microsoft.CodeAnalysis.CSharp;
12+
using Microsoft.CodeAnalysis.CSharp.Syntax;
13+
14+
/// <summary>
15+
/// Implements a code fix for all misaligned using statements.
16+
/// </summary>
17+
internal sealed partial class UsingCodeFixProvider
18+
{
19+
/// <summary>
20+
/// Contains a map of the different regions of a source file.
21+
/// Used source file regions are:
22+
/// - conditional directives (#if, #else, #elif, #endif)
23+
/// - pragma warning directives
24+
/// - region directives
25+
/// </summary>
26+
private class SourceMap
27+
{
28+
private readonly TreeTextSpan regionRoot;
29+
private readonly TreeTextSpan pragmaWarningRoot;
30+
31+
private SourceMap(TreeTextSpan conditionalRoot, TreeTextSpan regionRoot, TreeTextSpan pragmaWarningRoot)
32+
{
33+
this.ConditionalRoot = conditionalRoot;
34+
this.regionRoot = regionRoot;
35+
this.pragmaWarningRoot = pragmaWarningRoot;
36+
}
37+
38+
/// <summary>
39+
/// Gets the root entry for all conditional directive spans.
40+
/// </summary>
41+
/// <value>A <see cref="TreeTextSpan"/> object representing the root conditional directive span.</value>
42+
internal TreeTextSpan ConditionalRoot { get; }
43+
44+
/// <summary>
45+
/// Constructs the directive map for the given <paramref name="compilationUnit"/>
46+
/// </summary>
47+
/// <param name="compilationUnit">The compilation unit to scan for directive trivia.</param>
48+
/// <returns>A new <see cref="SourceMap"/> object containing the directive trivia information from the passed <paramref name="compilationUnit"/>.</returns>
49+
internal static SourceMap FromCompilationUnit(CompilationUnitSyntax compilationUnit)
50+
{
51+
TreeTextSpan conditionalRoot;
52+
TreeTextSpan regionRoot;
53+
TreeTextSpan pragmaWarningRoot;
54+
55+
BuildDirectiveTriviaMaps(compilationUnit, out conditionalRoot, out regionRoot, out pragmaWarningRoot);
56+
57+
return new SourceMap(conditionalRoot, regionRoot, pragmaWarningRoot);
58+
}
59+
60+
/// <summary>
61+
/// Gets the containing span for the given <paramref name="node"/>.
62+
/// </summary>
63+
/// <param name="node">The node for which the containing span will be determined.</param>
64+
/// <returns>The span that contains the node.</returns>
65+
internal TreeTextSpan GetContainingSpan(SyntaxNode node)
66+
{
67+
var textSpan = node.GetLocation().SourceSpan;
68+
69+
var containingSpans = this.pragmaWarningRoot.Children
70+
.Where(child => (textSpan.Start >= child.Start) && (textSpan.End <= child.End))
71+
.ToList();
72+
73+
var containingConditionalSpan = this.ConditionalRoot.GetContainingSpan(textSpan);
74+
if (containingConditionalSpan != this.ConditionalRoot)
75+
{
76+
containingSpans.Add(containingConditionalSpan);
77+
}
78+
79+
var containingRegionSpan = this.regionRoot.GetContainingSpan(textSpan);
80+
if (containingRegionSpan != this.regionRoot)
81+
{
82+
containingSpans.Add(containingRegionSpan);
83+
}
84+
85+
if (containingSpans.Count == 0)
86+
{
87+
return TreeTextSpan.Empty;
88+
}
89+
90+
for (var i = containingSpans.Count - 1; i > 0; i--)
91+
{
92+
if (containingSpans[i].Contains(containingSpans[i - 1]))
93+
{
94+
containingSpans.RemoveAt(i);
95+
}
96+
else if (containingSpans[i - 1].Contains(containingSpans[i]))
97+
{
98+
containingSpans.RemoveAt(i - 1);
99+
}
100+
}
101+
102+
if (containingSpans.Count == 1)
103+
{
104+
return containingSpans[0];
105+
}
106+
107+
var newStart = int.MinValue;
108+
var newEnd = int.MaxValue;
109+
110+
foreach (var span in containingSpans)
111+
{
112+
newStart = Math.Max(newStart, span.Start);
113+
newEnd = Math.Min(newEnd, span.End);
114+
}
115+
116+
return new TreeTextSpan(newStart, newEnd, ImmutableArray<TreeTextSpan>.Empty);
117+
}
118+
119+
private static void ProcessNodeMembers(TreeTextSpan.Builder builder, SyntaxList<MemberDeclarationSyntax> members)
120+
{
121+
foreach (var namespaceDeclaration in members.OfType<NamespaceDeclarationSyntax>())
122+
{
123+
var childBuilder = builder.AddChild(namespaceDeclaration.FullSpan.Start);
124+
childBuilder.SetEnd(namespaceDeclaration.FullSpan.End);
125+
126+
ProcessNodeMembers(childBuilder, namespaceDeclaration.Members);
127+
}
128+
}
129+
130+
private static void BuildDirectiveTriviaMaps(CompilationUnitSyntax compilationUnit, out TreeTextSpan conditionalRoot, out TreeTextSpan regionRoot, out TreeTextSpan pragmaWarningRoot)
131+
{
132+
var conditionalStack = new Stack<TreeTextSpan.Builder>();
133+
var regionStack = new Stack<TreeTextSpan.Builder>();
134+
var pragmaWarningList = new List<DirectiveTriviaSyntax>();
135+
136+
var conditionalBuilder = SetupBuilder(compilationUnit, conditionalStack);
137+
var regionBuilder = SetupBuilder(compilationUnit, regionStack);
138+
139+
for (var directiveTrivia = compilationUnit.GetFirstDirective(); directiveTrivia != null; directiveTrivia = directiveTrivia.GetNextDirective())
140+
{
141+
switch (directiveTrivia.Kind())
142+
{
143+
case SyntaxKind.IfDirectiveTrivia:
144+
AddNewDirectiveTriviaSpan(conditionalBuilder, conditionalStack, directiveTrivia);
145+
break;
146+
147+
case SyntaxKind.ElifDirectiveTrivia:
148+
case SyntaxKind.ElseDirectiveTrivia:
149+
var previousSpan = conditionalStack.Pop();
150+
previousSpan.SetEnd(directiveTrivia.FullSpan.Start);
151+
152+
AddNewDirectiveTriviaSpan(conditionalBuilder, conditionalStack, directiveTrivia);
153+
break;
154+
155+
case SyntaxKind.EndIfDirectiveTrivia:
156+
CloseDirectiveTriviaSpan(conditionalBuilder, conditionalStack, directiveTrivia);
157+
break;
158+
159+
case SyntaxKind.RegionDirectiveTrivia:
160+
AddNewDirectiveTriviaSpan(regionBuilder, regionStack, directiveTrivia);
161+
break;
162+
163+
case SyntaxKind.EndRegionDirectiveTrivia:
164+
CloseDirectiveTriviaSpan(regionBuilder, regionStack, directiveTrivia);
165+
break;
166+
167+
case SyntaxKind.PragmaWarningDirectiveTrivia:
168+
pragmaWarningList.Add(directiveTrivia);
169+
break;
170+
171+
default:
172+
// ignore all other directive trivia
173+
break;
174+
}
175+
}
176+
177+
conditionalRoot = FinalizeBuilder(conditionalBuilder, conditionalStack, compilationUnit.Span.End);
178+
regionRoot = FinalizeBuilder(regionBuilder, regionStack, compilationUnit.Span.End);
179+
pragmaWarningRoot = BuildPragmaWarningSpans(pragmaWarningList, compilationUnit);
180+
}
181+
182+
private static TreeTextSpan.Builder SetupBuilder(CompilationUnitSyntax compilationUnit, Stack<TreeTextSpan.Builder> stack)
183+
{
184+
var rootBuilder = TreeTextSpan.CreateBuilder(compilationUnit.SpanStart);
185+
stack.Push(rootBuilder);
186+
187+
return rootBuilder;
188+
}
189+
190+
private static void AddNewDirectiveTriviaSpan(TreeTextSpan.Builder spanBuilder, Stack<TreeTextSpan.Builder> spanStack, DirectiveTriviaSyntax directiveTrivia)
191+
{
192+
var parent = spanStack.Peek();
193+
var newDirectiveSpan = parent.AddChild(directiveTrivia.FullSpan.Start);
194+
spanStack.Push(newDirectiveSpan);
195+
}
196+
197+
private static void CloseDirectiveTriviaSpan(TreeTextSpan.Builder spanBuilder, Stack<TreeTextSpan.Builder> spanStack, DirectiveTriviaSyntax directiveTrivia)
198+
{
199+
var previousSpan = spanStack.Pop();
200+
previousSpan.SetEnd(directiveTrivia.FullSpan.End);
201+
}
202+
203+
private static TreeTextSpan FinalizeBuilder(TreeTextSpan.Builder builder, Stack<TreeTextSpan.Builder> stack, int end)
204+
{
205+
// close all spans (including the root) that have not been closed yet
206+
while (stack.Count > 0)
207+
{
208+
var span = stack.Pop();
209+
span.SetEnd(end);
210+
}
211+
212+
// Fill the gaps to make sure that directives on either side of an conditional directive group are not combined
213+
builder.FillGaps();
214+
215+
return builder.ToSpan();
216+
}
217+
218+
private static TreeTextSpan BuildPragmaWarningSpans(List<DirectiveTriviaSyntax> pragmaWarningList, CompilationUnitSyntax compilationUnit)
219+
{
220+
var map = new Dictionary<string, PragmaWarningDirectiveTriviaSyntax>();
221+
var builder = TreeTextSpan.CreateBuilder(compilationUnit.SpanStart);
222+
223+
foreach (var pragmaWarning in pragmaWarningList.Cast<PragmaWarningDirectiveTriviaSyntax>())
224+
{
225+
var errorCodes = GetErrorCodes(pragmaWarning);
226+
227+
switch (pragmaWarning.DisableOrRestoreKeyword.Kind())
228+
{
229+
case SyntaxKind.DisableKeyword:
230+
foreach (var errorCode in errorCodes)
231+
{
232+
if (!map.ContainsKey(errorCode))
233+
{
234+
// only add it if the warning isn't disabled already
235+
map[errorCode] = pragmaWarning;
236+
}
237+
}
238+
239+
break;
240+
241+
case SyntaxKind.RestoreKeyword:
242+
foreach (var errorCode in errorCodes)
243+
{
244+
PragmaWarningDirectiveTriviaSyntax startOfSpan;
245+
246+
if (map.TryGetValue(errorCode, out startOfSpan))
247+
{
248+
map.Remove(errorCode);
249+
250+
var childSpan = builder.AddChild(startOfSpan.FullSpan.Start);
251+
childSpan.SetEnd(pragmaWarning.FullSpan.End);
252+
}
253+
}
254+
255+
break;
256+
}
257+
}
258+
259+
// create spans for all pragma warning disable statements that have not been closed.
260+
foreach (var pragmaWarning in map.Values)
261+
{
262+
var childSpan = builder.AddChild(pragmaWarning.FullSpan.Start);
263+
childSpan.SetEnd(compilationUnit.FullSpan.End);
264+
}
265+
266+
builder.SetEnd(compilationUnit.FullSpan.End);
267+
return builder.ToSpan();
268+
}
269+
270+
private static List<string> GetErrorCodes(PragmaWarningDirectiveTriviaSyntax pragmaWarningDirectiveTrivia)
271+
{
272+
return pragmaWarningDirectiveTrivia.ErrorCodes
273+
.OfType<IdentifierNameSyntax>()
274+
.Select(x => x.Identifier.ValueText)
275+
.ToList();
276+
}
277+
}
278+
}
279+
}

0 commit comments

Comments
 (0)