Browse Source

Editor context actions - added context action which inserts null checks - for expressions like "a.b.c" inserts "if (a != null && a.b != null)".

pull/15/head
mkonicek 15 years ago
parent
commit
c20aafaee4
  1. 1
      src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.addin
  2. 1
      src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.csproj
  3. 18
      src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentCache.cs
  4. 11
      src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentNotNull.cs
  5. 8
      src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentNull.cs
  6. 133
      src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckMemberNotNull.cs
  7. 54
      src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/Extensions.cs
  8. 9
      src/Libraries/NRefactory/Project/Src/Visitors/SetRegionInclusionVisitor.cs

1
src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.addin

@ -48,6 +48,7 @@ @@ -48,6 +48,7 @@
<Class class="SharpRefactoring.ContextActions.CheckAssignmentNotNull" />
<Class class="SharpRefactoring.ContextActions.ParamCheckForNull" />
<Class class="SharpRefactoring.ContextActions.ParamRangeCheck" />
<Class class="SharpRefactoring.ContextActions.CheckMemberNotNull" />
</Path>
<Path name="/SharpDevelop/ViewContent/TextEditor/OverrideCompletionHandler">

1
src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.csproj

@ -77,6 +77,7 @@ @@ -77,6 +77,7 @@
<Compile Include="Src\ContextActions\CheckAssignmentCache.cs" />
<Compile Include="Src\ContextActions\CheckAssignmentNull.cs" />
<Compile Include="Src\ContextActions\CheckAssignmentNotNull.cs" />
<Compile Include="Src\ContextActions\CheckMemberNotNull.cs" />
<Compile Include="Src\ContextActions\Extensions.cs" />
<Compile Include="Src\ContextActions\FindTypeDeclarationsVisitor.cs" />
<Compile Include="Src\ContextActions\GenerateMember.cs" />

18
src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentCache.cs

@ -19,7 +19,8 @@ namespace SharpRefactoring.ContextActions @@ -19,7 +19,8 @@ namespace SharpRefactoring.ContextActions
{
this.VariableName = GetVariableName(context);
this.CodeGenerator = GetCodeGenerator(context);
this.ElementRegion = GetStatementRegion(context);
this.Element = GetElement(context);
this.ElementRegion = (Element == null) ? DomRegion.Empty : DomRegion.FromLocation(Element.StartLocation, Element.EndLocation);
}
public bool IsActionAvailable
@ -33,13 +34,17 @@ namespace SharpRefactoring.ContextActions @@ -33,13 +34,17 @@ namespace SharpRefactoring.ContextActions
public CodeGenerator CodeGenerator { get; private set; }
/// <summary>
/// Either AssignmentExpression or LocalVariableDeclaration.
/// </summary>
public AbstractNode Element { get; private set; }
public DomRegion ElementRegion { get; private set; }
protected string GetVariableName(EditorContext context)
{
// a = Foo() : AssignmentExpression.Left == IdentifierExpression(*identifier*)
// var a = Foo() : VariableDeclaration(*name*).Initializer != empty
var variableName = GetVariableNameFromAssignment(context.GetContainingElement<AssignmentExpression>());
if (variableName != null)
return variableName;
@ -50,19 +55,18 @@ namespace SharpRefactoring.ContextActions @@ -50,19 +55,18 @@ namespace SharpRefactoring.ContextActions
return null;
}
protected DomRegion GetStatementRegion(EditorContext context)
protected AbstractNode GetElement(EditorContext context)
{
// a = Foo() : AssignmentExpression.Left == IdentifierExpression(*identifier*)
// var a = Foo() : VariableDeclaration(*name*).Initializer != empty
var assignment = context.GetContainingElement<AssignmentExpression>();
if (assignment != null)
return DomRegion.FromLocation(assignment.StartLocation, assignment.EndLocation);
return assignment;
var declaration = context.GetContainingElement<LocalVariableDeclaration>();
if (declaration != null)
return DomRegion.FromLocation(declaration.StartLocation, declaration.EndLocation);
return declaration;
return DomRegion.Empty;
return null;
}
string GetVariableNameFromAssignment(AssignmentExpression assignment)

11
src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentNotNull.cs

@ -32,12 +32,12 @@ namespace SharpRefactoring.ContextActions @@ -32,12 +32,12 @@ namespace SharpRefactoring.ContextActions
var ifStatement = GenerateAstToInsert(cache.VariableName);
var editor = context.Editor;
string indent = DocumentUtilitites.GetWhitespaceAfter(editor.Document, editor.Document.GetLineStartOffset(cache.ElementRegion.GetStart()));
string code = cache.CodeGenerator.GenerateCode(ifStatement, indent);
int insertOffset = editor.Document.GetLineEndOffset(cache.ElementRegion.GetEnd());
var doc = editor.Document;
using (var undoGroup = editor.Document.OpenUndoGroup()) {
editor.Document.Insert(insertOffset, code);
var caretPos = editor.Document.Text.IndexOf(caretMarker, insertOffset);
editor.InsertCodeAfter(cache.Element, ifStatement);
// set caret to the position of the special marker
var caretPos = doc.Text.IndexOf(caretMarker, doc.GetLineEndOffset(cache.Element.EndLocation));
editor.Caret.Offset = caretPos;
editor.Document.RemoveRestOfLine(caretPos);
}
@ -46,6 +46,7 @@ namespace SharpRefactoring.ContextActions @@ -46,6 +46,7 @@ namespace SharpRefactoring.ContextActions
AbstractNode GenerateAstToInsert(string variableName)
{
var block = new BlockStatement();
// mark the place where to put caret
block.AddChild(new ExpressionStatement(new IdentifierExpression(caretMarker)));
return new IfElseStatement(
new BinaryOperatorExpression(new IdentifierExpression(variableName), BinaryOperatorType.InEquality, new PrimitiveExpression(null)),

8
src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentNull.cs

@ -29,13 +29,7 @@ namespace SharpRefactoring.ContextActions @@ -29,13 +29,7 @@ namespace SharpRefactoring.ContextActions
var cache = context.GetCached<CheckAssignmentCache>();
var ifStatement = GenerateAstToInsert(cache.VariableName);
var editor = context.Editor;
string indent = DocumentUtilitites.GetWhitespaceAfter(editor.Document, editor.Document.GetLineStartOffset(cache.ElementRegion.GetStart()));
string code = cache.CodeGenerator.GenerateCode(ifStatement, indent);
int insertOffset = editor.Document.GetLineEndOffset(cache.ElementRegion.GetEnd());
editor.Document.Insert(insertOffset, code);
editor.Caret.Offset = insertOffset + code.Length - 1;
context.Editor.InsertCodeAfter(cache.Element, ifStatement, true);
}
AbstractNode GenerateAstToInsert(string variableName)

133
src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckMemberNotNull.cs

@ -0,0 +1,133 @@ @@ -0,0 +1,133 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using System.Collections.Generic;
using ICSharpCode.NRefactory.Ast;
using ICSharpCode.SharpDevelop.Dom;
using ICSharpCode.SharpDevelop.Editor;
using ICSharpCode.SharpDevelop.Refactoring;
namespace SharpRefactoring.ContextActions
{
/// <summary>
/// For expressions like "a.b.c" adds checks for null "if (a != null && a.b != null)".
/// </summary>
public class CheckMemberNotNull : ContextAction
{
public override string Title {
get { return "Add null checks"; }
}
/// <summary>
/// Expression at caret.
/// </summary>
Expression currentExpr;
/// <summary>
/// Expression to be checked for null.
/// </summary>
Expression targetExpr;
public override bool IsAvailable(EditorContext context)
{
this.currentExpr = context.GetContainingElement<Expression>();
if (currentExpr is InvocationExpression) {
// InvocationExpression (e.g. "e.Foo()") has MemberReferenceExpression as TargetObject (e.g. "e.Foo")
// "e.Foo() -> e"
this.targetExpr = GetTarget(((InvocationExpression)currentExpr).TargetObject);
} else {
// "a.b" -> "a"
this.targetExpr = GetTarget(currentExpr);
}
return
this.targetExpr is MemberReferenceExpression ||
this.targetExpr is CastExpression ||
this.targetExpr is ParenthesizedExpression;
//this.targetExpr is IdentifierExpression; // "don't offer the action for just a.b, only for a.b.c"
}
/// <summary>
/// Gets "a.b" from "a.b.c"
/// </summary>
Expression GetTarget(Expression memberReferenceExpr)
{
if (memberReferenceExpr is MemberReferenceExpression) {
return ((MemberReferenceExpression)memberReferenceExpr).TargetObject;
}
return null;
}
public override void Execute(EditorContext context)
{
var conditionExpr = BuildCondition(this.targetExpr);
if (conditionExpr == null)
return;
var ifExpr = new IfElseStatement(conditionExpr, new BlockStatement());
context.Editor.InsertCodeBefore(this.currentExpr, ifExpr);
}
/// <summary>
/// Turns "(a.b as T).d.e" into "(a != null) && (a.b is T) && ((a.b as T).d != null)"
/// </summary>
Expression BuildCondition(Expression targetExpr)
{
var parts = GetConditionParts(targetExpr);
Expression condition = null;
foreach (var part in parts) {
if (condition == null) {
// first
condition = new ParenthesizedExpression(part);
} else {
condition = new BinaryOperatorExpression(new ParenthesizedExpression(part), BinaryOperatorType.LogicalAnd, condition);
}
}
return condition;
}
List<Expression> GetConditionParts(Expression targetExpr)
{
var result = new List<Expression>();
Expression current = targetExpr;
while (current != null)
{
// process current expr
if (current is MemberReferenceExpression) {
var memberExpr = (MemberReferenceExpression)current;
// expr != null
result.Add(new BinaryOperatorExpression(memberExpr, BinaryOperatorType.InEquality, new PrimitiveExpression(null)));
} else if (current is CastExpression) {
var castExpr = (CastExpression)current;
// expr is T
result.Add(new TypeOfIsExpression(castExpr.Expression, castExpr.CastTo));
// no need to check (a != null) && (a is T): (a is T) is enough - skip the "a"
current = StepIn(current);
}
else if (current is IdentifierExpression) {
result.Add(new BinaryOperatorExpression((IdentifierExpression)current, BinaryOperatorType.InEquality, new PrimitiveExpression(null)));
}
// step down the AST (e.g. from "a as T" to "a")
current = StepIn(current);
}
return result;
}
Expression StepIn(Expression expr)
{
if (expr is MemberReferenceExpression) {
return ((MemberReferenceExpression)expr).TargetObject;
} else if (expr is CastExpression) {
return ((CastExpression)expr).Expression;
}
else if (expr is ParenthesizedExpression) {
return ((ParenthesizedExpression)expr).Expression;
}
else if (expr is IdentifierExpression) {
return null;
} else {
return null;
}
}
}
}

54
src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/Extensions.cs

@ -4,9 +4,9 @@ @@ -4,9 +4,9 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using ICSharpCode.NRefactory;
using ICSharpCode.NRefactory.Ast;
using ICSharpCode.NRefactory.Visitors;
using ICSharpCode.SharpDevelop;
using ICSharpCode.SharpDevelop.Dom;
using ICSharpCode.SharpDevelop.Dom.Refactoring;
@ -77,6 +77,15 @@ namespace SharpRefactoring.ContextActions @@ -77,6 +77,15 @@ namespace SharpRefactoring.ContextActions
return line.Offset + line.TotalLength;
}
public static int GetPreviousLineEndOffset(this IDocument document, Location location)
{
var line = document.GetLineForOffset(document.PositionToOffset(location));
if (line.LineNumber == 1)
return -1;
var previousLine = document.GetLine(line.LineNumber - 1);
return previousLine.Offset + previousLine.TotalLength;
}
public static void RemoveRestOfLine(this IDocument document, int offset)
{
var line = document.GetLineForOffset(offset);
@ -84,6 +93,47 @@ namespace SharpRefactoring.ContextActions @@ -84,6 +93,47 @@ namespace SharpRefactoring.ContextActions
document.Remove(offset, lineEndOffset - offset);
}
public static DomRegion DomRegion(this INode node)
{
return new DomRegion(node.StartLocation.Line, node.StartLocation.Column, node.EndLocation.Line, node.EndLocation.Column);
}
/// <summary>
/// Inserts code at the next line after target AST node.
/// </summary>
public static void InsertCodeAfter(this ITextEditor editor, AbstractNode target, AbstractNode insert, bool updateCaretPos = false)
{
InsertCode(editor, target, insert, editor.Document.GetLineEndOffset(target.EndLocation), updateCaretPos);
}
/// <summary>
/// Inserts code at the line before target AST node.
/// </summary>
public static void InsertCodeBefore(this ITextEditor editor, AbstractNode target, AbstractNode insert)
{
InsertCode(editor, target, insert, editor.Document.GetPreviousLineEndOffset(target.StartLocation), false);
}
public static void InsertCode(this ITextEditor editor, AbstractNode target, AbstractNode insert, int insertOffset, bool updateCaretPos)
{
if (insertOffset < 0)
return;
var regionCorrectVisitor = new SetRegionInclusionVisitor();
insert.AcceptVisitor(regionCorrectVisitor, null);
var doc = editor.Document;
var codeGen = editor.Language.Properties.CodeGenerator;
string indent = DocumentUtilitites.GetWhitespaceAfter(doc, doc.GetLineStartOffset(target.StartLocation));
string code = codeGen.GenerateCode(insert, indent);
doc.Insert(insertOffset, code);
if (updateCaretPos) {
editor.Caret.Offset = insertOffset + code.Length - 1;
}
}
/// <summary>
/// C# only.
/// </summary>
@ -121,4 +171,4 @@ namespace SharpRefactoring.ContextActions @@ -121,4 +171,4 @@ namespace SharpRefactoring.ContextActions
return -1;
}
}
}
}

9
src/Libraries/NRefactory/Project/Src/Visitors/SetRegionInclusionVisitor.cs

@ -17,6 +17,11 @@ namespace ICSharpCode.NRefactory.Visitors @@ -17,6 +17,11 @@ namespace ICSharpCode.NRefactory.Visitors
{
Stack<INode> parentNodes = new Stack<INode>();
/// <summary>
/// Sets StartLocation and EndLocation (region) of every node to union of regions of its children.
/// Parsers don't do this by default:
/// e.g. "a.Foo()" is InvocationExpression, its region includes only the "()" and it has a child MemberReferenceExpression, with region ".Foo".
/// </summary>
public SetRegionInclusionVisitor()
{
parentNodes.Push(null);
@ -34,7 +39,7 @@ namespace ICSharpCode.NRefactory.Visitors @@ -34,7 +39,7 @@ namespace ICSharpCode.NRefactory.Visitors
if (node is PropertyDeclaration) {
// PropertyDeclaration has correctly set BodyStart and BodyEnd by the parser,
// but it has no subnode "body", just 2 children GetRegion and SetRegion which don't span
// the whole (BodyStart, BodyEnd) region => We have to handle PropertyDeclaration as a special case.
// the whole (BodyStart, BodyEnd) region => we have to handle PropertyDeclaration as a special case.
node.EndLocation = ((PropertyDeclaration)node).BodyEnd;
}
@ -60,7 +65,7 @@ namespace ICSharpCode.NRefactory.Visitors @@ -60,7 +65,7 @@ namespace ICSharpCode.NRefactory.Visitors
parent.EndLocation = node.EndLocation;
}
// Block statement as a special case - we want it without the '{' and '}'
// Block statement as a special case - we want block contents without the '{' and '}'
if (node is BlockStatement) {
var firstSatement = node.Children.FirstOrDefault();
if (firstSatement != null) {

Loading…
Cancel
Save