Skip to content

Add UseSupportsShouldProcess rule #758

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 93 commits into from
May 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
e91ee78
Add skeleton of UseSupportsShouldProcess rule
Mar 31, 2017
1364172
Add basic checks for whatif and confirm
Mar 31, 2017
634056f
Add tests for manual adition of whatif and confirm
Apr 1, 2017
537695e
Update error string for UseSupportsShouldProcess rule
Apr 5, 2017
0534133
Add diagnostic record for UseSupportShouldProcess violation
Apr 5, 2017
ad527df
Output ParamBlockAst from method to get parameter ast
Apr 11, 2017
d824b53
Update parameter retrieval logic
Apr 13, 2017
04eada9
Update tests to check suggested corrections
Apr 13, 2017
2a7c4e0
Add TextEdit class
Apr 17, 2017
f85ee3c
Add IScriptExtent.Contains extension method
Apr 17, 2017
5db9110
Add more IScriptExtension.Contains tests
Apr 18, 2017
8cdb366
Add methods to apply edits to a string
Apr 18, 2017
84f2cc0
Remove unnecessary extension methods
Apr 18, 2017
ca43a43
Add EditableText class to edit strings
Apr 18, 2017
8efd95d
Add more tests to check IScriptExtent.Contains
Apr 18, 2017
dd236b7
Fix extensions test file name
Apr 18, 2017
4e80f05
Make EditableText class public
Apr 18, 2017
da1e8a5
Add new contructor for TextEdit class
Apr 25, 2017
f5f84b4
Remove IScriptExtent.Contains method
Apr 25, 2017
a15c25e
Update validation logic for TextEdit extent
Apr 25, 2017
30eedf0
Add tests for EditableText class
Apr 25, 2017
144863e
Remove TextEdit type's dependency on IScriptExtent
Apr 28, 2017
93f0ace
Remove EditableText type's dependency on IScriptExtent
Apr 28, 2017
842a26b
Derive CorrectionExtent from TextEdit
Apr 28, 2017
bce6563
Add tests for TextEdit class
Apr 28, 2017
b8e38a1
Remove redundant cases from CorrectionExtent tests
Apr 28, 2017
cbf182b
Add extension method to translate text coordinates
Apr 28, 2017
e386685
Add tests for IScriptExtent.Translate
Apr 28, 2017
ecda864
Add type to represent position in a text file
Apr 29, 2017
be1bb45
Add type to represent range in a text file
Apr 29, 2017
5988f3d
Derive TextEdit class from Range class
Apr 29, 2017
2f88672
Add incremental edits to correction extent
Apr 29, 2017
87a7814
Add param block and cmdletbinding in correction
May 1, 2017
26204c7
Apply all edits after getting all correction extents
May 1, 2017
6e039bd
Fix correction extents
May 1, 2017
4811c94
Add more tests
May 2, 2017
af898ec
Fix correction text when remove whatif/confirm
May 2, 2017
0ab9182
Add more tests
May 2, 2017
76635b9
Make ast and tokens members of the rule class
May 2, 2017
1e54182
Add method to get tokens corresponding to an ast
May 2, 2017
74f67c7
Remove whatif/params only if in param block
May 2, 2017
3cd8b24
Add method to remove function parameter declaration
May 2, 2017
ce345cc
Add method to check whatif/confirm parameters
May 2, 2017
aa324dc
Add correction for writing param block
May 2, 2017
784c420
Add a property to get lines in TextEdit class
May 3, 2017
17510d9
Add CorrectionExtent constructor to take lines
May 3, 2017
470416d
Update GetLines extension method
May 3, 2017
254b601
Add alternative apply edit method
May 3, 2017
dccde57
Return lines instead of entire text in correction extent
May 3, 2017
b5f9a8a
Add more tests
May 3, 2017
dc091f1
Update violation extent of UseSupportShouldProcess rule
May 3, 2017
0410d9e
Rename methods to get correction extent
May 4, 2017
536d46a
Remove hardcoding whitespace and indentation size
May 4, 2017
b0a40c9
Add extension method to convert IScriptExtent to Range
May 4, 2017
3583517
Update method to normalize range
May 4, 2017
85edbad
Fix correction getter method name
May 4, 2017
717da67
Derive UseSupportsShouldProcess from IScriptRule
May 4, 2017
12139fa
Add argument checking to textedit constructors
May 4, 2017
f3f3b25
Add docs to EditableText class
May 4, 2017
a01d552
Add TextLines type for efficient line insertion and removal
May 5, 2017
240b21c
Make accessing lines in TextLines faster (very likely)
May 5, 2017
15d6741
Change EditableText.ApplyEdit1 to EditableText.ApplyEdit
May 5, 2017
24a83ed
Update EditableText to use TextLines class
May 5, 2017
1cf1d0e
Move TextLines type to its own file
May 5, 2017
ab2ae46
Update TextLines.GetNodeAt implementation
May 5, 2017
9a9ae0b
Add documentation to TextLines class
May 5, 2017
bf4c44f
Add argument checking to TextLines public methods
May 5, 2017
5293675
Localize error strings
May 5, 2017
1627e2d
Add TextLines.cs to engine project
May 5, 2017
4ed988b
Remove duplicate key in engine resources
May 5, 2017
bc483e7
Handle line addition to empty TextLines object
May 6, 2017
413a6ef
Handle TextLines.Insert when index equals number of items
May 6, 2017
847ac16
Localize error strings in Range class
May 6, 2017
cb6f9c5
Fix TextEdit test cases
May 6, 2017
064eb77
Add documentation to Range class
May 6, 2017
b1d6168
Localize error strings in Position class
May 6, 2017
c34f370
Add documentation to Position class
May 6, 2017
dbbe574
Increment total number of rules by 1
May 6, 2017
5d0edd3
Remove IScriptExtent.Translate method from extensions
May 6, 2017
71ac64a
Make GetParameterAsts an extension method
May 6, 2017
49b5e0a
Use extension method to get cmdletbinding attribute
May 6, 2017
489938a
Add method to check for cmdletbinding attribute
May 8, 2017
a0a84c1
Add extension method get supportsshouldprocess attribute ast
May 8, 2017
4fbdb3a
Add extension method to get named attribute arg value
May 8, 2017
86e580c
Suggest setting SupportsShouldProcess to true
May 8, 2017
add80dc
Remove IScriptExtent.Translate extension tests
May 8, 2017
3226819
Add tests for extension methods
May 8, 2017
5902d24
Change return type of FunctionDefinitionAst.GetParameters
May 8, 2017
b418142
Add documentation to extension methods
May 8, 2017
d4353b7
Change IsTrue method name to GetValue
May 8, 2017
ce07993
Add documentation for UseSupportsShouldProcess rule
May 9, 2017
489b37f
Add access qualifier to UseSupportsShouldProcess class
May 9, 2017
d6200d7
Merge branch 'development' into kapilmb/use-should-process-rule
May 16, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions Engine/EditableText.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Management.Automation.Language;
using System.Text;
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Extensions;

namespace Microsoft.Windows.PowerShell.ScriptAnalyzer
{
/// <summary>
/// A class to represent text to which `TextEdit`s can be applied.
/// </summary>
public class EditableText
{
private TextLines lines { get; set; }

/// <summary>
/// The text that is available for editing.
/// </summary>
public string Text { get { return String.Join(NewLine, lines); } }

/// <summary>
/// The lines in the Text.
/// </summary>
public string[] Lines { get { return lines.ToArray(); } }

/// <summary>
/// The new line character in the Text.
/// </summary>
public string NewLine { get; private set; }

/// <summary>
/// Construct an EditableText type object.
/// </summary>
/// <param name="text"></param>
public EditableText(string text)
{
if (text == null)
{
throw new ArgumentNullException(nameof(text));
}

string[] lines;
NewLine = GetNewLineCharacters(text, out lines);
this.lines = new TextLines(lines);
}

/// <summary>
/// Apply edits defined by a TextEdit object to Text.
/// </summary>
/// <param name="textEdit">A TextEdit object that encapsulates the text and the range that need to be replaced.</param>
/// <returns>An editable object which contains the supplied edit.</returns>
public EditableText ApplyEdit(TextEdit textEdit)
{
ValidateTextEdit(textEdit);

var editLines = textEdit.Lines;

// Get the first fragment of the first line
string firstLineFragment =
lines[textEdit.StartLineNumber - 1]
.Substring(0, textEdit.StartColumnNumber - 1);

// Get the last fragment of the last line
string endLine = lines[textEdit.EndLineNumber - 1];
string lastLineFragment =
endLine.Substring(
textEdit.EndColumnNumber - 1,
lines[textEdit.EndLineNumber - 1].Length - textEdit.EndColumnNumber + 1);

// Remove the old lines
for (int i = 0; i <= textEdit.EndLineNumber - textEdit.StartLineNumber; i++)
{
lines.RemoveAt(textEdit.StartLineNumber - 1);
}

// Build and insert the new lines
int currentLineNumber = textEdit.StartLineNumber;
for (int changeIndex = 0; changeIndex < editLines.Length; changeIndex++)
{
// Since we split the lines above using \n, make sure to
// trim the ending \r's off as well.
string finalLine = editLines[changeIndex].TrimEnd('\r');

// Should we add first or last line fragments?
if (changeIndex == 0)
{
// Append the first line fragment
finalLine = firstLineFragment + finalLine;
}
if (changeIndex == editLines.Length - 1)
{
// Append the last line fragment
finalLine = finalLine + lastLineFragment;
}

lines.Insert(currentLineNumber - 1, finalLine);
currentLineNumber++;
}

return new EditableText(String.Join(NewLine, lines));
}

// TODO Add a method that takes multiple edits, checks if they are unique and applies them.

public override string ToString()
{
return Text;
}

private void ValidateTextEdit(TextEdit textEdit)
{
if (textEdit == null)
{
throw new NullReferenceException(nameof(textEdit));
}

ValidateTextEditExtent(textEdit);
}

private void ValidateTextEditExtent(TextEdit textEdit)
{
if (textEdit.StartLineNumber > Lines.Length
|| textEdit.EndLineNumber > Lines.Length
|| textEdit.StartColumnNumber > Lines[textEdit.StartLineNumber - 1].Length
|| textEdit.EndColumnNumber > Lines[textEdit.EndLineNumber - 1].Length + 1)
{
throw new ArgumentException(String.Format(
CultureInfo.CurrentCulture,
Strings.EditableTextRangeIsNotContained));
}
}

private static string GetNewLineCharacters(string text, out string[] lines)
{
int numNewLineChars = GetNumNewLineCharacters(text, out lines);
if (lines.Length == 1)
{
return Environment.NewLine;
}

return text.Substring(lines[0].Length, numNewLineChars);
}

private static int GetNumNewLineCharacters(string text, out string[] lines)
{
lines = text.GetLines().ToArray();
if (lines.Length == 1)
{
return Environment.NewLine.Length;
}

var charsInLines = lines.Sum(line => line.Length);
var numCharDiff = text.Length - charsInLines;
int remainder = numCharDiff % (lines.Length - 1);
if (remainder != 0)
{
throw new ArgumentException(
String.Format(CultureInfo.CurrentCulture, Strings.EditableTextInvalidLineEnding),
nameof(text));
}

return numCharDiff / (lines.Length - 1);
}
}
}
155 changes: 155 additions & 0 deletions Engine/Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Language;

namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Extensions
{
public static class Extensions
{
/// <summary>
/// Return the lines in a text string.
/// </summary>
/// <param name="text">Text string to be split around new lines.</param>
/// <returns></returns>
public static IEnumerable<string> GetLines(this string text)
{
return text.Split('\n').Select(line => line.TrimEnd('\r'));
}

/// <summary>
/// Converts IScriptExtent to Range
/// </summary>
public static Range ToRange(this IScriptExtent extent)
{
return new Range(
extent.StartLineNumber,
extent.StartColumnNumber,
extent.EndLineNumber,
extent.EndColumnNumber);
}

/// <summary>
/// Get the parameter Asts from a function definition Ast.
///
/// If not parameters are found, return null.
/// </summary>
/// <param name="paramBlockAst">If a parameter block is present, set this argument's value to the parameter block.</param>
/// <returns></returns>
public static IEnumerable<ParameterAst> GetParameterAsts(
this FunctionDefinitionAst functionDefinitionAst,
out ParamBlockAst paramBlockAst)
{
paramBlockAst = null;
if (functionDefinitionAst.Parameters != null)
{
return functionDefinitionAst.Parameters;
}
else if (functionDefinitionAst.Body.ParamBlock?.Parameters != null)
{
paramBlockAst = functionDefinitionAst.Body.ParamBlock;
return functionDefinitionAst.Body.ParamBlock.Parameters;
}

return null;
}

/// <summary>
/// Get the CmdletBinding attribute ast
/// </summary>
/// <param name="attributeAsts"></param>
/// <returns>Returns CmdletBinding attribute ast if it exists, otherwise returns null</returns>
public static AttributeAst GetCmdletBindingAttributeAst(this ParamBlockAst paramBlockAst)
{
var attributeAsts = paramBlockAst.Attributes;
if (attributeAsts == null)
{
return null;
}

foreach (var attributeAst in attributeAsts)
{
if (attributeAst != null && attributeAst.IsCmdletBindingAttributeAst())
{
return attributeAst;
}
}

return null;
}

/// <summary>
/// Check if an attribute Ast is of CmdletBindingAttribute type.
/// </summary>
public static bool IsCmdletBindingAttributeAst(this AttributeAst attributeAst)
{
return attributeAst.TypeName.GetReflectionAttributeType() == typeof(CmdletBindingAttribute);
}

/// <summary>
/// Given a CmdletBinding attribute ast, return the SupportsShouldProcess argument Ast.
///
/// If no SupportsShouldProcess argument is found, return null.
/// </summary>
public static NamedAttributeArgumentAst GetSupportsShouldProcessAst(this AttributeAst attributeAst)
{
if (!attributeAst.IsCmdletBindingAttributeAst()
|| attributeAst.NamedArguments == null)
{
return null;
}

foreach (var namedAttrAst in attributeAst.NamedArguments)
{
if (namedAttrAst != null
&& namedAttrAst.ArgumentName.Equals(
"SupportsShouldProcess",
StringComparison.OrdinalIgnoreCase))
{
return namedAttrAst;
}
}

return null;
}

/// <summary>
/// Return the boolean value of a named attribute argument.
/// </summary>
/// <param name="argumentAst">The ast of the argument's value</param>
public static bool GetValue(this NamedAttributeArgumentAst attrAst, out ExpressionAst argumentAst)
{
argumentAst = null;
if (attrAst.ExpressionOmitted)
{
return true;
}

var varExpAst = attrAst.Argument as VariableExpressionAst;
argumentAst = attrAst.Argument;
if (varExpAst == null)
{
var constExpAst = attrAst.Argument as ConstantExpressionAst;
if (constExpAst == null)
{
return false;
}

bool constExpVal;
if (LanguagePrimitives.TryConvertTo(constExpAst.Value, out constExpVal))
{
return constExpVal;
}
}
else
{
return varExpAst.VariablePath.UserPath.Equals(
bool.TrueString,
StringComparison.OrdinalIgnoreCase);
}

return false;
}
}
}
Loading