~ubuntu-branches/ubuntu/trusty/monodevelop/trusty-proposed

« back to all changes in this revision

Viewing changes to external/nrefactory/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/VariableDeclaredInWideScopeIssue.cs

  • Committer: Package Import Robot
  • Author(s): Jo Shields
  • Date: 2013-05-12 09:46:03 UTC
  • mto: This revision was merged to the branch mainline in revision 29.
  • Revision ID: package-import@ubuntu.com-20130512094603-mad323bzcxvmcam0
Tags: upstream-4.0.5+dfsg
ImportĀ upstreamĀ versionĀ 4.0.5+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
//
 
2
// VariableDeclaredWideScopeIssue.cs
 
3
//
 
4
// Author:
 
5
//       Simon Lindgren <simon.n.lindgren@gmail.com>
 
6
//
 
7
// Copyright (c) 2012 Simon Lindgren
 
8
//
 
9
// Permission is hereby granted, free of charge, to any person obtaining a copy
 
10
// of this software and associated documentation files (the "Software"), to deal
 
11
// in the Software without restriction, including without limitation the rights
 
12
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 
13
// copies of the Software, and to permit persons to whom the Software is
 
14
// furnished to do so, subject to the following conditions:
 
15
//
 
16
// The above copyright notice and this permission notice shall be included in
 
17
// all copies or substantial portions of the Software.
 
18
//
 
19
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 
20
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 
21
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 
22
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 
23
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 
24
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 
25
// THE SOFTWARE.
 
26
using System.Collections.Generic;
 
27
using System.Linq;
 
28
using System;
 
29
using ICSharpCode.NRefactory.Semantics;
 
30
using ICSharpCode.NRefactory.TypeSystem;
 
31
 
 
32
namespace ICSharpCode.NRefactory.CSharp.Refactoring
 
33
{
 
34
        [IssueDescription("The variable can be declared in a nested scope",
 
35
                           Description = "Highlights variables that can be declared in a nested scope.",
 
36
                           Category = IssueCategories.Opportunities,
 
37
                           Severity = Severity.Suggestion)]
 
38
        public class VariableDeclaredInWideScopeIssue : ICodeIssueProvider
 
39
        {
 
40
                #region ICodeIssueProvider implementation
 
41
                public IEnumerable<CodeIssue> GetIssues(BaseRefactoringContext context)
 
42
                {
 
43
                        return new GatherVisitor(context, this).GetIssues();
 
44
                }
 
45
                #endregion
 
46
 
 
47
                class GatherVisitor : GatherVisitorBase<VariableDeclaredInWideScopeIssue>
 
48
                {
 
49
                        readonly BaseRefactoringContext context;
 
50
                        
 
51
                        public GatherVisitor(BaseRefactoringContext context, VariableDeclaredInWideScopeIssue issueProvider) : base (context, issueProvider)
 
52
                        {
 
53
                                this.context = context;
 
54
                        }
 
55
 
 
56
                        static readonly IList<Type> moveTargetBlacklist = new List<Type> {
 
57
                                typeof(WhileStatement),
 
58
                                typeof(ForeachStatement),
 
59
                                typeof(ForStatement),
 
60
                                typeof(DoWhileStatement),
 
61
                                typeof(TryCatchStatement),
 
62
                                typeof(AnonymousMethodExpression),
 
63
                                typeof(LambdaExpression),
 
64
                                typeof(LockStatement)
 
65
                        };
 
66
 
 
67
                        class CheckInitializer : DepthFirstAstVisitor
 
68
                        {
 
69
                                public bool IsValid {
 
70
                                        get;
 
71
                                        private set;
 
72
                                }
 
73
 
 
74
                                public CheckInitializer()
 
75
                                {
 
76
                                        IsValid = true;
 
77
                                }
 
78
 
 
79
                                public override void VisitInvocationExpression(InvocationExpression invocationExpression)
 
80
                                {
 
81
                                        base.VisitInvocationExpression(invocationExpression);
 
82
                                        IsValid = false;
 
83
                                }
 
84
                        }
 
85
                        
 
86
                        bool CheckForInvocations(Expression initializer)
 
87
                        {
 
88
                                var visitor = new CheckInitializer();
 
89
                                initializer.AcceptVisitor(visitor);
 
90
                                return visitor.IsValid;
 
91
                        }
 
92
 
 
93
                        public override void VisitVariableDeclarationStatement(VariableDeclarationStatement variableDeclarationStatement)
 
94
                        {
 
95
                                base.VisitVariableDeclarationStatement(variableDeclarationStatement);
 
96
 
 
97
                                var rootNode = variableDeclarationStatement.Parent as BlockStatement;
 
98
                                if (rootNode == null)
 
99
                                        // We are somewhere weird, like a the ResourceAquisition of a using statement
 
100
                                        return;
 
101
 
 
102
                                // TODO: Handle declarations with more than one variable?
 
103
                                if (variableDeclarationStatement.Variables.Count > 1)
 
104
                                        return;
 
105
 
 
106
                                var variableInitializer = variableDeclarationStatement.Variables.First();
 
107
                                var identifiers = GetIdentifiers(rootNode.Descendants, variableInitializer.Name).ToList();
 
108
 
 
109
                                if (identifiers.Count == 0)
 
110
                                        // variable is not used
 
111
                                        return;
 
112
 
 
113
                                if (!CheckForInvocations(variableInitializer.Initializer))
 
114
                                        return;
 
115
 
 
116
                                AstNode deepestCommonAncestor = GetDeepestCommonAncestor(rootNode, identifiers);
 
117
                                var path = GetPath(rootNode, deepestCommonAncestor);
 
118
 
 
119
                                // The node that will follow the moved declaration statement
 
120
                                AstNode anchorNode = GetInitialAnchorNode(rootNode, identifiers, path);
 
121
 
 
122
                                // Restrict path to only those where the initializer has not changed
 
123
                                var pathToCheck = path.Skip(1).ToList();
 
124
                                var firstInitializerChangeNode = GetFirstInitializerChange(variableDeclarationStatement, pathToCheck, variableInitializer.Initializer);
 
125
                                if (firstInitializerChangeNode != null) {
 
126
                                        // The node changing the initializer expression may not be on the path
 
127
                                        // to the actual usages of the variable, so we need to merge the paths
 
128
                                        // so we get the part of the paths that are common between them
 
129
                                        var pathToChange = GetPath(rootNode, firstInitializerChangeNode);
 
130
                                        var deepestCommonIndex = GetLowestCommonAncestorIndex(path, pathToChange);
 
131
                                        anchorNode = pathToChange [deepestCommonIndex + 1];
 
132
                                        path = pathToChange.Take(deepestCommonIndex).ToList();
 
133
                                }
 
134
 
 
135
                                // Restrict to locations outside of blacklisted node types
 
136
                                var firstBlackListedNode = path.Where(node => moveTargetBlacklist.Contains(node.GetType())).FirstOrDefault();
 
137
                                if (firstBlackListedNode != null) {
 
138
                                        path = GetPath(rootNode, firstBlackListedNode.Parent);
 
139
                                        anchorNode = firstBlackListedNode;
 
140
                                }
 
141
 
 
142
                                anchorNode = GetInsertionPoint(anchorNode);
 
143
 
 
144
                                if (anchorNode != null && anchorNode != rootNode && anchorNode.Parent != rootNode) {
 
145
                                        AddIssue(variableDeclarationStatement, context.TranslateString("Variable could be moved to a nested scope"),
 
146
                                                 GetActions(variableDeclarationStatement, (Statement)anchorNode));
 
147
                                }
 
148
                        }
 
149
 
 
150
                        static bool IsBannedInsertionPoint(AstNode anchorNode)
 
151
                        {
 
152
                                var parent = anchorNode.Parent;
 
153
 
 
154
                                // Don't split 'else if ...' into else { if ... }
 
155
                                if (parent is IfElseStatement && anchorNode is IfElseStatement)
 
156
                                        return true;
 
157
                                // Don't allow moving the declaration into the resource aquisition of a using statement
 
158
                                if (parent is UsingStatement)
 
159
                                        return true;
 
160
                                // Don't allow moving things into arbitrary positions of for statements
 
161
                                if (parent is ForStatement && anchorNode.Role != Roles.EmbeddedStatement)
 
162
                                        return true;
 
163
                                return false;
 
164
                        }
 
165
 
 
166
                        static AstNode GetInsertionPoint(AstNode node)
 
167
                        {
 
168
                                while (true) {
 
169
                                        if (node == null)
 
170
                                                break;
 
171
                                        if (node is Statement && !IsBannedInsertionPoint(node))
 
172
                                                break;
 
173
                                        node = node.Parent;
 
174
                                }
 
175
                                return node;
 
176
                        }
 
177
 
 
178
                        AstNode GetInitialAnchorNode (BlockStatement rootNode, List<IdentifierExpression> identifiers, IList<AstNode> path)
 
179
                        {
 
180
                                if (identifiers.Count > 1) {
 
181
                                        // Assume the first identifier is the first in the execution flow
 
182
                                        // firstPath will always be longer than path since path is the
 
183
                                        // combination of a least two (different) paths.
 
184
                                        var firstPath = GetPath(rootNode, identifiers [0]);
 
185
                                        if (firstPath [path.Count].Role == IfElseStatement.TrueRole) {
 
186
                                                // IfElseStatement has a slightly weird structure; Don't
 
187
                                                // consider the true role eligible for anchor node in this case
 
188
                                                return firstPath [path.Count - 1];
 
189
                                        }
 
190
                                        return firstPath [path.Count];
 
191
                                }
 
192
                                // We only have one path, and a statement in itself cannot be an identifier
 
193
                                // so we're safe
 
194
                                return path [path.Count - 1];
 
195
                        }
 
196
 
 
197
                        static IEnumerable<IdentifierExpression> GetIdentifiers(IEnumerable<AstNode> candidates, string name = null)
 
198
                        {
 
199
                                return 
 
200
                                        from node in candidates
 
201
                                        let identifier = node as IdentifierExpression
 
202
                                        where identifier != null && (name == null || identifier.Identifier == name)
 
203
                                        select identifier;
 
204
                        }
 
205
 
 
206
                        AstNode GetFirstInitializerChange(AstNode variableDeclarationStatement, IList<AstNode> path, Expression initializer)
 
207
                        {
 
208
                                var identifiers = GetIdentifiers(initializer.DescendantsAndSelf).ToList();
 
209
                                var mayChangeInitializer = GetChecker (initializer, identifiers);
 
210
                                AstNode lastChange = null;
 
211
                                for (int i = path.Count - 1; i >= 0; i--) {
 
212
                                        for (AstNode node = path[i].PrevSibling; node != null && node != variableDeclarationStatement; node = node.PrevSibling) {
 
213
                                                // Special case for IfElseStatement: The AST nesting does not match the scope nesting, so
 
214
                                                // don't handle branches here: The correct one has already been checked anyway.
 
215
                                                // This also works to our advantage: No special checking is needed for the condition since
 
216
                                                // it is a the same level in the tree as the false branch
 
217
                                                if (node.Role == IfElseStatement.TrueRole || node.Role == IfElseStatement.FalseRole)
 
218
                                                        continue;
 
219
                                                foreach (var expression in node.DescendantsAndSelf.Where(n => n is Expression).Cast<Expression>()) {
 
220
                                                        if (mayChangeInitializer(expression)) {
 
221
                                                                lastChange = expression;
 
222
                                                        }
 
223
                                                }
 
224
                                        }
 
225
                                }
 
226
                                return lastChange;
 
227
                        }
 
228
 
 
229
                        Func<Expression, bool> GetChecker(Expression expression, IList<IdentifierExpression> identifiers)
 
230
                        {
 
231
                                // TODO: This only works for simple cases.
 
232
                                IList<IMember> members;
 
233
                                IList<IVariable> locals;
 
234
                                var identifierResolveResults = identifiers.Select(identifier => context.Resolve(identifier)).ToList();
 
235
                                SplitResolveResults(identifierResolveResults, out members, out locals);
 
236
                                
 
237
                                if (expression is InvocationExpression || expression is ObjectCreateExpression) {
 
238
                                        return node => {
 
239
                                                if (node is InvocationExpression || node is ObjectCreateExpression)
 
240
                                                        // We don't know what these might do, so assume it will change the initializer
 
241
                                                        return true;
 
242
                                                var binaryOperator = node as BinaryOperatorExpression;
 
243
                                                if (binaryOperator != null) {
 
244
                                                        var resolveResult = context.Resolve(binaryOperator) as OperatorResolveResult;
 
245
                                                        if (resolveResult == null)
 
246
                                                                return false;
 
247
                                                        // Built-in operators are ok, user defined ones not so much
 
248
                                                        return resolveResult.UserDefinedOperatorMethod != null;
 
249
                                                }
 
250
                                                return IsConflictingAssignment(node, identifiers, members, locals);
 
251
                                        };
 
252
                                } else if (expression is IdentifierExpression) {
 
253
                                        var initializerDependsOnMembers = identifierResolveResults.Any(result => result is MemberResolveResult);
 
254
                                        var initializerDependsOnReferenceType = identifierResolveResults.Any(result => result.Type.IsReferenceType == true);
 
255
                                        return node => {
 
256
                                                if ((node is InvocationExpression || node is ObjectCreateExpression) &&
 
257
                                                    (initializerDependsOnMembers || initializerDependsOnReferenceType))
 
258
                                                        // Anything can happen...
 
259
                                                        return true;
 
260
                                                var binaryOperator = node as BinaryOperatorExpression;
 
261
                                                if (binaryOperator != null) {
 
262
                                                        var resolveResult = context.Resolve(binaryOperator) as OperatorResolveResult;
 
263
                                                        if (resolveResult == null)
 
264
                                                                return false;
 
265
                                                        return resolveResult.UserDefinedOperatorMethod != null;
 
266
                                                }
 
267
                                                return IsConflictingAssignment(node, identifiers, members, locals);
 
268
                                        };
 
269
                                }
 
270
 
 
271
                                return node => false;
 
272
                        }
 
273
 
 
274
                        bool IsConflictingAssignment (Expression node, IList<IdentifierExpression> identifiers, IList<IMember> members, IList<IVariable> locals)
 
275
                        {
 
276
                                var assignmentExpression = node as AssignmentExpression;
 
277
                                if (assignmentExpression != null) {
 
278
                                        IList<IMember> targetMembers;
 
279
                                        IList<IVariable> targetLocals;
 
280
                                        var identifierResolveResults = identifiers.Select(identifier => context.Resolve(identifier)).ToList();
 
281
                                        SplitResolveResults(identifierResolveResults, out targetMembers, out targetLocals);
 
282
 
 
283
                                        return members.Any(member => targetMembers.Contains(member)) ||
 
284
                                                locals.Any(local => targetLocals.Contains(local));
 
285
                                }
 
286
                                return false;
 
287
                        }
 
288
 
 
289
                        static void SplitResolveResults(List<ResolveResult> identifierResolveResults, out IList<IMember> members, out IList<IVariable> locals)
 
290
                        {
 
291
                                members = new List<IMember>();
 
292
                                locals = new List<IVariable>();
 
293
                                foreach (var resolveResult in identifierResolveResults) {
 
294
                                        var memberResolveResult = resolveResult as MemberResolveResult;
 
295
                                        if (memberResolveResult != null) {
 
296
                                                members.Add(memberResolveResult.Member);
 
297
                                        }
 
298
                                        var localResolveResult = resolveResult as LocalResolveResult;
 
299
                                        if (localResolveResult != null) {
 
300
                                                locals.Add(localResolveResult.Variable);
 
301
                                        }
 
302
                                }
 
303
                        }
 
304
 
 
305
                        bool IsScopeContainer(AstNode node)
 
306
                        {
 
307
                                if (node == null)
 
308
                                        return false;
 
309
 
 
310
                                var blockStatement = node as BlockStatement;
 
311
                                if (blockStatement != null)
 
312
                                        return true;
 
313
 
 
314
                                var statement = node as Statement;
 
315
                                if (statement == null)
 
316
                                        return false;
 
317
 
 
318
                                var role = node.Role;
 
319
                                if (role == Roles.EmbeddedStatement ||
 
320
                                        role == IfElseStatement.TrueRole ||
 
321
                                        role == IfElseStatement.FalseRole) {
 
322
                                        return true;
 
323
                                }
 
324
                                return false;
 
325
                        }
 
326
 
 
327
                        IEnumerable<CodeAction> GetActions(Statement oldStatement, Statement followingStatement)
 
328
                        {
 
329
                                yield return new CodeAction(context.TranslateString("Move to nested scope"), script => {
 
330
                                        var parent = followingStatement.Parent;
 
331
                                        if (parent is SwitchSection || parent is BlockStatement) {
 
332
                                                script.InsertBefore(followingStatement, oldStatement.Clone());
 
333
                                        } else {
 
334
                                                var newBlockStatement = new BlockStatement {
 
335
                                                        Statements = {
 
336
                                                                oldStatement.Clone(),
 
337
                                                                followingStatement.Clone()
 
338
                                                        }
 
339
                                                };
 
340
                                                script.Replace(followingStatement, newBlockStatement);
 
341
                                                script.FormatText(parent);
 
342
                                        }
 
343
                                        script.Remove(oldStatement);
 
344
                                }, oldStatement);
 
345
                        }
 
346
 
 
347
                        AstNode GetDeepestCommonAncestor(AstNode assumedRoot, IEnumerable<AstNode> leaves)
 
348
                        {
 
349
                                var previousPath = GetPath(assumedRoot, leaves.First());
 
350
                                int lowestIndex = previousPath.Count - 1;
 
351
                                foreach (var leaf in leaves.Skip(1)) {
 
352
                                        var currentPath = GetPath(assumedRoot, leaf);
 
353
                                        lowestIndex = GetLowestCommonAncestorIndex(previousPath, currentPath, lowestIndex);
 
354
                                        previousPath = currentPath;
 
355
                                }
 
356
                                return previousPath [lowestIndex];
 
357
                        }
 
358
                        
 
359
                        int GetLowestCommonAncestorIndex(IList<AstNode> path1, IList<AstNode> path2, int maxIndex = int.MaxValue)
 
360
                        {
 
361
                                var max = Math.Min(Math.Min(path1.Count, path2.Count), maxIndex);
 
362
                                for (int i = 0; i <= max; i++) {
 
363
                                        if (path1 [i] != path2 [i])
 
364
                                                return i - 1;
 
365
                                }
 
366
                                return max;
 
367
                        }
 
368
 
 
369
                        IList<AstNode> GetPath(AstNode from, AstNode to)
 
370
                        {
 
371
                                var reversePath = new List<AstNode>();
 
372
                                do {
 
373
                                        reversePath.Add(to);
 
374
                                        to = to.Parent;
 
375
                                } while (to != from.Parent);
 
376
                                reversePath.Reverse();
 
377
                                return reversePath;
 
378
                        }
 
379
                }
 
380
        }
 
381
}
 
382