diff --git a/Engine/Helper.cs b/Engine/Helper.cs
index 0e59ad803..16304ae86 100644
--- a/Engine/Helper.cs
+++ b/Engine/Helper.cs
@@ -2085,6 +2085,11 @@ public object VisitConvertExpression(ConvertExpressionAst convAst)
{
if (convAst != null)
{
+ if (convAst.Type.TypeName.GetReflectionType() != null)
+ {
+ return convAst.Type.TypeName.GetReflectionType().FullName;
+ }
+
return convAst.Type.TypeName.FullName;
}
@@ -2135,6 +2140,11 @@ public object VisitTypeConstraint(TypeConstraintAst typeAst)
{
if (typeAst != null)
{
+ if (typeAst.TypeName.GetReflectionType() != null)
+ {
+ return typeAst.TypeName.GetReflectionType().FullName;
+ }
+
return typeAst.TypeName.FullName;
}
@@ -2160,6 +2170,11 @@ public object VisitTypeExpression(TypeExpressionAst typeExpressionAst)
{
if (typeExpressionAst != null)
{
+ if (typeExpressionAst.TypeName.GetReflectionType() != null)
+ {
+ return typeExpressionAst.TypeName.GetReflectionType().FullName;
+ }
+
return typeExpressionAst.TypeName.FullName;
}
diff --git a/RuleDocumentation/DscExamplesPresent.md b/RuleDocumentation/DscExamplesPresent.md
new file mode 100644
index 000000000..ab2743642
--- /dev/null
+++ b/RuleDocumentation/DscExamplesPresent.md
@@ -0,0 +1,55 @@
+#DscExamplesPresent
+**Severity Level: Information**
+
+
+##Description
+
+Checks that DSC examples for given resource are present.
+
+##How to Fix
+
+To fix a violation of this rule, please make sure Examples directory is present:
+* For non-class based resources it should exist at the same folder level as DSCResources folder.
+* For class based resources it should be present at the same folder level as resource psm1 file.
+
+Examples folder should contain sample configuration for given resource - file name should contain resource's name.
+
+##Example
+
+### Non-class based resource
+
+Let's assume we have non-class based resource with a following file structure:
+
+* xAzure
+ * DSCResources
+ * MSFT_xAzureSubscription
+ * MSFT_xAzureSubscription.psm1
+ * MSFT_xAzureSubscription.schema.mof
+
+In this case, to fix this warning, we should add examples in a following way:
+
+* xAzure
+ * DSCResources
+ * MSFT_xAzureSubscription
+ * MSFT_xAzureSubscription.psm1
+ * MSFT_xAzureSubscription.schema.mof
+ * Examples
+ * MSFT_xAzureSubscription_AddSubscriptionExample.ps1
+ * MSFT_xAzureSubscription_RemoveSubscriptionExample.ps1
+
+### Class based resource
+
+Let's assume we have class based resource with a following file structure:
+
+* MyDscResource
+ * MyDscResource.psm1
+ * MyDscresource.psd1
+
+In this case, to fix this warning, we should add examples in a following way:
+
+* MyDscResource
+ * MyDscResource.psm1
+ * MyDscresource.psd1
+ * Tests
+ * MyDscResource_Example1.ps1
+ * MyDscResource_Example2.ps1
diff --git a/RuleDocumentation/DscTestsPresent.md b/RuleDocumentation/DscTestsPresent.md
new file mode 100644
index 000000000..0ed054337
--- /dev/null
+++ b/RuleDocumentation/DscTestsPresent.md
@@ -0,0 +1,54 @@
+#DscTestsPresent
+**Severity Level: Information**
+
+
+##Description
+
+Checks that DSC tests for given resource are present.
+
+##How to Fix
+
+To fix a violation of this rule, please make sure Tests directory is present:
+* For non-class based resources it should exist at the same folder level as DSCResources folder.
+* For class based resources it should be present at the same folder level as resource psm1 file.
+
+Tests folder should contain test script for given resource - file name should contain resource's name.
+
+##Example
+
+### Non-class based resource
+
+Let's assume we have non-class based resource with a following file structure:
+
+* xAzure
+ * DSCResources
+ * MSFT_xAzureSubscription
+ * MSFT_xAzureSubscription.psm1
+ * MSFT_xAzureSubscription.schema.mof
+
+In this case, to fix this warning, we should add tests in a following way:
+
+* xAzure
+ * DSCResources
+ * MSFT_xAzureSubscription
+ * MSFT_xAzureSubscription.psm1
+ * MSFT_xAzureSubscription.schema.mof
+ * Tests
+ * MSFT_xAzureSubscription_Tests.ps1
+
+### Class based resource
+
+Let's assume we have class based resource with a following file structure:
+
+* MyDscResource
+ * MyDscResource.psm1
+ * MyDscresource.psd1
+
+In this case, to fix this warning, we should add tests in a following way:
+
+* MyDscResource
+ * MyDscResource.psm1
+ * MyDscresource.psd1
+ * Tests
+ * MyDscResource_Tests.ps1
+
diff --git a/RuleDocumentation/UseOutputTypeCorrectly.md b/RuleDocumentation/UseOutputTypeCorrectly.md
new file mode 100644
index 000000000..577f430cb
--- /dev/null
+++ b/RuleDocumentation/UseOutputTypeCorrectly.md
@@ -0,0 +1,37 @@
+#UseOutputTypeCorrectly
+**Severity Level: Information**
+
+
+##Description
+
+If a return type is declared, the cmdlet must return that type. If a type is returned, a return type must be declared.
+
+##How to Fix
+
+To fix a violation of this rule, please check the OuputType attribute lists and the types that are returned in your code. You can get more details by running “Get-Help about_Functions_OutputTypeAttribute” command in Windows PowerShell.
+
+##Example
+
+##Example
+Wrong:
+
+ function Get-Foo
+ {
+ [CmdletBinding()]
+ [OutputType([String])]
+ Param(
+ )
+ return "4
+ }
+
+Correct:
+
+ function Get-Foo
+ {
+ [CmdletBinding()]
+ [OutputType([String])]
+ Param(
+ )
+
+ return "bar"
+ }
diff --git a/Rules/DscExamplesPresent.cs b/Rules/DscExamplesPresent.cs
new file mode 100644
index 000000000..4d3129c4c
--- /dev/null
+++ b/Rules/DscExamplesPresent.cs
@@ -0,0 +1,164 @@
+//
+// Copyright (c) Microsoft Corporation.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation.Language;
+using Microsoft.Windows.Powershell.ScriptAnalyzer.Generic;
+using System.ComponentModel.Composition;
+using System.Globalization;
+using System.IO;
+using System.Management.Automation;
+
+namespace Microsoft.Windows.Powershell.ScriptAnalyzer.BuiltinRules
+{
+ ///
+ /// DscExamplesPresent: Checks that DSC examples for given resource are present.
+ /// Rule expects directory Examples to be present:
+ /// For non-class based resources it should exist at the same folder level as DSCResources folder.
+ /// For class based resources it should be present at the same folder level as resource psm1 file.
+ /// Examples folder should contain sample configuration for given resource - file name should contain resource's name.
+ ///
+ [Export(typeof(IDSCResourceRule))]
+ public class DscExamplesPresent : IDSCResourceRule
+ {
+ ///
+ /// AnalyzeDSCResource: Analyzes given DSC Resource
+ ///
+ /// The script's ast
+ /// The name of the script file being analyzed
+ /// The results of the analysis
+ public IEnumerable AnalyzeDSCResource(Ast ast, string fileName)
+ {
+ String fileNameOnly = Path.GetFileName(fileName);
+ String resourceName = Path.GetFileNameWithoutExtension(fileNameOnly);
+ String examplesQuery = String.Format("*{0}*", resourceName);
+ Boolean examplesPresent = false;
+ String expectedExamplesPath = Path.Combine(new String[] {fileName, "..", "..", "..", "Examples"});
+
+ // Verify examples are present
+ if (Directory.Exists(expectedExamplesPath))
+ {
+ DirectoryInfo examplesFolder = new DirectoryInfo(expectedExamplesPath);
+ FileInfo[] exampleFiles = examplesFolder.GetFiles(examplesQuery);
+ if (exampleFiles.Length != 0)
+ {
+ examplesPresent = true;
+ }
+ }
+
+ // Return error if no examples present
+ if (!examplesPresent)
+ {
+ yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.DscExamplesPresentNoExamplesError, resourceName),
+ ast.Extent, GetName(), DiagnosticSeverity.Information, fileName);
+ }
+ }
+
+ ///
+ /// AnalyzeDSCClass: Analyzes given DSC class
+ ///
+ ///
+ ///
+ ///
+ public IEnumerable AnalyzeDSCClass(Ast ast, string fileName)
+ {
+ String resourceName = null;
+
+ IEnumerable dscClasses = ast.FindAll(item =>
+ item is TypeDefinitionAst
+ && ((item as TypeDefinitionAst).IsClass)
+ && (item as TypeDefinitionAst).Attributes.Any(attr => String.Equals("DSCResource", attr.TypeName.FullName, StringComparison.OrdinalIgnoreCase)), true);
+
+ foreach (TypeDefinitionAst dscClass in dscClasses)
+ {
+ resourceName = dscClass.Name;
+
+ String examplesQuery = String.Format("*{0}*", resourceName);
+ Boolean examplesPresent = false;
+ String expectedExamplesPath = Path.Combine(new String[] {fileName, "..", "Examples"});
+
+ // Verify examples are present
+ if (Directory.Exists(expectedExamplesPath))
+ {
+ DirectoryInfo examplesFolder = new DirectoryInfo(expectedExamplesPath);
+ FileInfo[] exampleFiles = examplesFolder.GetFiles(examplesQuery);
+ if (exampleFiles.Length != 0)
+ {
+ examplesPresent = true;
+ }
+ }
+
+ // Return error if no examples present
+ if (!examplesPresent)
+ {
+ yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.DscExamplesPresentNoExamplesError, resourceName),
+ dscClass.Extent, GetName(), DiagnosticSeverity.Information, fileName);
+ }
+ }
+ }
+
+ ///
+ /// GetName: Retrieves the name of this rule.
+ ///
+ /// The name of this rule
+ public string GetName()
+ {
+ return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.DscExamplesPresent);
+ }
+
+ ///
+ /// GetCommonName: Retrieves the Common name of this rule.
+ ///
+ /// The common name of this rule
+ public string GetCommonName()
+ {
+ return string.Format(CultureInfo.CurrentCulture, Strings.DscExamplesPresentCommonName);
+ }
+
+ ///
+ /// GetDescription: Retrieves the description of this rule.
+ ///
+ /// The description of this rule
+ public string GetDescription()
+ {
+ return string.Format(CultureInfo.CurrentCulture, Strings.DscExamplesPresentDescription);
+ }
+
+ ///
+ /// GetSourceType: Retrieves the type of the rule: builtin, managed or module.
+ ///
+ public SourceType GetSourceType()
+ {
+ return SourceType.Builtin;
+ }
+
+ ///
+ /// GetSeverity: Retrieves the severity of the rule: error, warning or information.
+ ///
+ ///
+ public RuleSeverity GetSeverity()
+ {
+ return RuleSeverity.Information;
+ }
+
+ ///
+ /// GetSourceName: Retrieves the module/assembly name the rule is from.
+ ///
+ public string GetSourceName()
+ {
+ return string.Format(CultureInfo.CurrentCulture, Strings.DSCSourceName);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/Rules/DscTestsPresent.cs b/Rules/DscTestsPresent.cs
new file mode 100644
index 000000000..15fbbda23
--- /dev/null
+++ b/Rules/DscTestsPresent.cs
@@ -0,0 +1,164 @@
+//
+// Copyright (c) Microsoft Corporation.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation.Language;
+using Microsoft.Windows.Powershell.ScriptAnalyzer.Generic;
+using System.ComponentModel.Composition;
+using System.Globalization;
+using System.IO;
+using System.Management.Automation;
+
+namespace Microsoft.Windows.Powershell.ScriptAnalyzer.BuiltinRules
+{
+ ///
+ /// DscTestsPresent: Checks that DSC tests for given resource are present.
+ /// Rule expects directory Tests to be present:
+ /// For non-class based resources it should exist at the same folder level as DSCResources folder.
+ /// For class based resources it should be present at the same folder level as resource psm1 file.
+ /// Tests folder should contain test script for given resource - file name should contain resource's name.
+ ///
+ [Export(typeof(IDSCResourceRule))]
+ public class DscTestsPresent : IDSCResourceRule
+ {
+ ///
+ /// AnalyzeDSCResource: Analyzes given DSC Resource
+ ///
+ /// The script's ast
+ /// The name of the script file being analyzed
+ /// The results of the analysis
+ public IEnumerable AnalyzeDSCResource(Ast ast, string fileName)
+ {
+ String fileNameOnly = Path.GetFileName(fileName);
+ String resourceName = Path.GetFileNameWithoutExtension(fileNameOnly);
+ String testsQuery = String.Format("*{0}*", resourceName);
+ Boolean testsPresent = false;
+ String expectedTestsPath = Path.Combine(new String[] { fileName, "..", "..", "..", "Tests" });
+
+ // Verify tests are present
+ if (Directory.Exists(expectedTestsPath))
+ {
+ DirectoryInfo testsFolder = new DirectoryInfo(expectedTestsPath);
+ FileInfo[] testFiles = testsFolder.GetFiles(testsQuery);
+ if (testFiles.Length != 0)
+ {
+ testsPresent = true;
+ }
+ }
+
+ // Return error if no tests present
+ if (!testsPresent)
+ {
+ yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.DscTestsPresentNoTestsError, resourceName),
+ ast.Extent, GetName(), DiagnosticSeverity.Information, fileName);
+ }
+ }
+
+ ///
+ /// AnalyzeDSCClass: Analyzes given DSC class
+ ///
+ ///
+ ///
+ ///
+ public IEnumerable AnalyzeDSCClass(Ast ast, string fileName)
+ {
+ String resourceName = null;
+
+ IEnumerable dscClasses = ast.FindAll(item =>
+ item is TypeDefinitionAst
+ && ((item as TypeDefinitionAst).IsClass)
+ && (item as TypeDefinitionAst).Attributes.Any(attr => String.Equals("DSCResource", attr.TypeName.FullName, StringComparison.OrdinalIgnoreCase)), true);
+
+ foreach (TypeDefinitionAst dscClass in dscClasses)
+ {
+ resourceName = dscClass.Name;
+
+ String testsQuery = String.Format("*{0}*", resourceName);
+ Boolean testsPresent = false;
+ String expectedTestsPath = Path.Combine(new String[] { fileName, "..", "Tests" });
+
+ // Verify tests are present
+ if (Directory.Exists(expectedTestsPath))
+ {
+ DirectoryInfo testsFolder = new DirectoryInfo(expectedTestsPath);
+ FileInfo[] testFiles = testsFolder.GetFiles(testsQuery);
+ if (testFiles.Length != 0)
+ {
+ testsPresent = true;
+ }
+ }
+
+ // Return error if no tests present
+ if (!testsPresent)
+ {
+ yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.DscTestsPresentNoTestsError, resourceName),
+ dscClass.Extent, GetName(), DiagnosticSeverity.Information, fileName);
+ }
+ }
+ }
+
+ ///
+ /// GetName: Retrieves the name of this rule.
+ ///
+ /// The name of this rule
+ public string GetName()
+ {
+ return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.DscTestsPresent);
+ }
+
+ ///
+ /// GetCommonName: Retrieves the Common name of this rule.
+ ///
+ /// The common name of this rule
+ public string GetCommonName()
+ {
+ return string.Format(CultureInfo.CurrentCulture, Strings.DscTestsPresentCommonName);
+ }
+
+ ///
+ /// GetDescription: Retrieves the description of this rule.
+ ///
+ /// The description of this rule
+ public string GetDescription()
+ {
+ return string.Format(CultureInfo.CurrentCulture, Strings.DscTestsPresentDescription);
+ }
+
+ ///
+ /// GetSourceType: Retrieves the type of the rule: builtin, managed or module.
+ ///
+ public SourceType GetSourceType()
+ {
+ return SourceType.Builtin;
+ }
+
+ ///
+ /// GetSeverity: Retrieves the severity of the rule: error, warning or information.
+ ///
+ ///
+ public RuleSeverity GetSeverity()
+ {
+ return RuleSeverity.Information;
+ }
+
+ ///
+ /// GetSourceName: Retrieves the module/assembly name the rule is from.
+ ///
+ public string GetSourceName()
+ {
+ return string.Format(CultureInfo.CurrentCulture, Strings.DSCSourceName);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/Rules/ReturnCorrectTypesForDSCFunctions.cs b/Rules/ReturnCorrectTypesForDSCFunctions.cs
index 2890660fb..90aeeeabc 100644
--- a/Rules/ReturnCorrectTypesForDSCFunctions.cs
+++ b/Rules/ReturnCorrectTypesForDSCFunctions.cs
@@ -210,7 +210,7 @@ public SourceType GetSourceType()
}
///
- /// GetSeverity: Retrieves the severity of the rule: error, warning of information.
+ /// GetSeverity: Retrieves the severity of the rule: error, warning or information.
///
///
public RuleSeverity GetSeverity()
diff --git a/Rules/ScriptAnalyzerBuiltinRules.csproj b/Rules/ScriptAnalyzerBuiltinRules.csproj
index 7cc1c0446..7401ac269 100644
--- a/Rules/ScriptAnalyzerBuiltinRules.csproj
+++ b/Rules/ScriptAnalyzerBuiltinRules.csproj
@@ -68,6 +68,9 @@
+
+
+
diff --git a/Rules/Strings.Designer.cs b/Rules/Strings.Designer.cs
index 6c1285181..641440213 100644
--- a/Rules/Strings.Designer.cs
+++ b/Rules/Strings.Designer.cs
@@ -825,6 +825,42 @@ internal static string CommandNotFoundName {
}
}
+ ///
+ /// Looks up a localized string similar to DscExamplesPresent.
+ ///
+ internal static string DscExamplesPresent {
+ get {
+ return ResourceManager.GetString("DscExamplesPresent", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to DSC examples are present.
+ ///
+ internal static string DscExamplesPresentCommonName {
+ get {
+ return ResourceManager.GetString("DscExamplesPresentCommonName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Every DSC resource module should contain folder "Examples" with sample configurations for every resource. Sample configurations should have resource name they are demonstrating in the title..
+ ///
+ internal static string DscExamplesPresentDescription {
+ get {
+ return ResourceManager.GetString("DscExamplesPresentDescription", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No examples found for resource '{0}'.
+ ///
+ internal static string DscExamplesPresentNoExamplesError {
+ get {
+ return ResourceManager.GetString("DscExamplesPresentNoExamplesError", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to PSDSC.
///
@@ -834,6 +870,42 @@ internal static string DSCSourceName {
}
}
+ ///
+ /// Looks up a localized string similar to DscTestsPresent.
+ ///
+ internal static string DscTestsPresent {
+ get {
+ return ResourceManager.GetString("DscTestsPresent", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Dsc tests are present.
+ ///
+ internal static string DscTestsPresentCommonName {
+ get {
+ return ResourceManager.GetString("DscTestsPresentCommonName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Every DSC resource module should contain folder "Tests" with tests for every resource. Test scripts should have resource name they are testing in the file name..
+ ///
+ internal static string DscTestsPresentDescription {
+ get {
+ return ResourceManager.GetString("DscTestsPresentDescription", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No tests found for resource '{0}'.
+ ///
+ internal static string DscTestsPresentNoTestsError {
+ get {
+ return ResourceManager.GetString("DscTestsPresentNoTestsError", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Module Manifest Fields.
///
@@ -1545,6 +1617,42 @@ internal static string UseIdenticalParametersDSCName {
}
}
+ ///
+ /// Looks up a localized string similar to Use OutputType Correctly.
+ ///
+ internal static string UseOutputTypeCorrectlyCommonName {
+ get {
+ return ResourceManager.GetString("UseOutputTypeCorrectlyCommonName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The return types of a cmdlet should be declared using the OutputType attribute..
+ ///
+ internal static string UseOutputTypeCorrectlyDescription {
+ get {
+ return ResourceManager.GetString("UseOutputTypeCorrectlyDescription", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The cmdlet '{0}' returns an object of type '{1}' but this type is not declared in the OutputType attribute..
+ ///
+ internal static string UseOutputTypeCorrectlyError {
+ get {
+ return ResourceManager.GetString("UseOutputTypeCorrectlyError", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to UseOutputTypeCorrectly.
+ ///
+ internal static string UseOutputTypeCorrectlyName {
+ get {
+ return ResourceManager.GetString("UseOutputTypeCorrectlyName", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to PSCredential.
///
diff --git a/Rules/Strings.resx b/Rules/Strings.resx
index 607777527..b26e4d6fa 100644
--- a/Rules/Strings.resx
+++ b/Rules/Strings.resx
@@ -678,4 +678,40 @@
AvoidUsingWMICmdlet
+
+ Use OutputType Correctly
+
+
+ The return types of a cmdlet should be declared using the OutputType attribute.
+
+
+ The cmdlet '{0}' returns an object of type '{1}' but this type is not declared in the OutputType attribute.
+
+
+ UseOutputTypeCorrectly
+
+
+ DscTestsPresent
+
+
+ Dsc tests are present
+
+
+ Every DSC resource module should contain folder "Tests" with tests for every resource. Test scripts should have resource name they are testing in the file name.
+
+
+ No tests found for resource '{0}'
+
+
+ DscExamplesPresent
+
+
+ DSC examples are present
+
+
+ Every DSC resource module should contain folder "Examples" with sample configurations for every resource. Sample configurations should have resource name they are demonstrating in the title.
+
+
+ No examples found for resource '{0}'
+
\ No newline at end of file
diff --git a/Rules/UseOutputTypeCorrectly.cs b/Rules/UseOutputTypeCorrectly.cs
new file mode 100644
index 000000000..f79ccea15
--- /dev/null
+++ b/Rules/UseOutputTypeCorrectly.cs
@@ -0,0 +1,179 @@
+//
+// Copyright (c) Microsoft Corporation.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation.Language;
+using Microsoft.Windows.Powershell.ScriptAnalyzer.Generic;
+using System.ComponentModel.Composition;
+using System.Globalization;
+using System.Management.Automation;
+
+namespace Microsoft.Windows.Powershell.ScriptAnalyzer.BuiltinRules
+{
+ ///
+ /// ProvideCommentHelp: Checks that objects return in a cmdlet have their types declared in OutputType Attribute
+ ///
+ [Export(typeof(IScriptRule))]
+ public class UseOutputTypeCorrectly : SkipTypeDefinition, IScriptRule
+ {
+ private IEnumerable _classes;
+
+ ///
+ /// AnalyzeScript: Checks that objects return in a cmdlet have their types declared in OutputType Attribute
+ ///
+ /// The script's ast
+ /// The name of the script
+ /// A List of diagnostic results of this rule
+ public IEnumerable AnalyzeScript(Ast ast, string fileName)
+ {
+ if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage);
+
+ DiagnosticRecords.Clear();
+ this.fileName = fileName;
+
+ _classes = ast.FindAll(item => item is TypeDefinitionAst && ((item as TypeDefinitionAst).IsClass), true).Cast();
+
+ ast.Visit(this);
+
+ return DiagnosticRecords;
+ }
+
+ ///
+ /// Visit function and checks that it is a cmdlet. If yes, then checks that any object returns must have a type declared
+ /// in the output type (the only exception is if the type is object)
+ ///
+ ///
+ ///
+ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst funcAst)
+ {
+ if (funcAst == null || funcAst.Body == null || funcAst.Body.ParamBlock == null
+ || funcAst.Body.ParamBlock.Attributes == null || funcAst.Body.ParamBlock.Attributes.Count == 0
+ || !funcAst.Body.ParamBlock.Attributes.Any(attr => attr.TypeName.GetReflectionType() == typeof(CmdletBindingAttribute)))
+ {
+ return AstVisitAction.Continue;
+ }
+
+ HashSet outputTypes = new HashSet();
+
+ foreach (AttributeAst attrAst in funcAst.Body.ParamBlock.Attributes)
+ {
+ if (attrAst.TypeName != null && attrAst.TypeName.GetReflectionType() == typeof(OutputTypeAttribute)
+ && attrAst.PositionalArguments != null)
+ {
+ foreach (ExpressionAst expAst in attrAst.PositionalArguments)
+ {
+ if (expAst is StringConstantExpressionAst)
+ {
+ Type type = Type.GetType((expAst as StringConstantExpressionAst).Value);
+ if (type != null)
+ {
+ outputTypes.Add(type.FullName);
+ }
+ }
+ else
+ {
+ TypeExpressionAst typeAst = expAst as TypeExpressionAst;
+ if (typeAst != null && typeAst.TypeName != null)
+ {
+ if (typeAst.TypeName.GetReflectionType() != null)
+ {
+ outputTypes.Add(typeAst.TypeName.GetReflectionType().FullName);
+ }
+ else
+ {
+ outputTypes.Add(typeAst.TypeName.FullName);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ List> returnTypes = FindPipelineOutput.OutputTypes(funcAst, _classes);
+
+ foreach (Tuple returnType in returnTypes)
+ {
+ string typeName = returnType.Item1;
+
+ if (String.IsNullOrEmpty(typeName)
+ || String.Equals(typeof(Unreached).FullName, typeName, StringComparison.OrdinalIgnoreCase)
+ || String.Equals(typeof(Undetermined).FullName, typeName, StringComparison.OrdinalIgnoreCase)
+ || String.Equals(typeof(object).FullName, typeName, StringComparison.OrdinalIgnoreCase)
+ || outputTypes.Contains(typeName, StringComparer.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+ else
+ {
+ DiagnosticRecords.Add(new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.UseOutputTypeCorrectlyError,
+ funcAst.Name, typeName), returnType.Item2.Extent, GetName(), DiagnosticSeverity.Information, fileName));
+ }
+ }
+
+ return AstVisitAction.Continue;
+ }
+
+ ///
+ /// GetName: Retrieves the name of this rule.
+ ///
+ /// The name of this rule
+ public string GetName()
+ {
+ return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.UseOutputTypeCorrectlyName);
+ }
+
+ ///
+ /// GetCommonName: Retrieves the common name of this rule.
+ ///
+ /// The common name of this rule
+ public string GetCommonName()
+ {
+ return string.Format(CultureInfo.CurrentCulture, Strings.UseOutputTypeCorrectlyCommonName);
+ }
+
+ ///
+ /// GetDescription: Retrieves the description of this rule.
+ ///
+ /// The description of this rule
+ public string GetDescription()
+ {
+ return string.Format(CultureInfo.CurrentCulture, Strings.UseOutputTypeCorrectlyDescription);
+ }
+
+ ///
+ /// Method: Retrieves the type of the rule: builtin, managed or module.
+ ///
+ public SourceType GetSourceType()
+ {
+ return SourceType.Builtin;
+ }
+
+ ///
+ /// GetSeverity: Retrieves the severity of the rule: error, warning of information.
+ ///
+ ///
+ public RuleSeverity GetSeverity()
+ {
+ return RuleSeverity.Information;
+ }
+
+ ///
+ /// Method: Retrieves the module/assembly name the rule is from.
+ ///
+ public string GetSourceName()
+ {
+ return string.Format(CultureInfo.CurrentCulture, Strings.SourceName);
+ }
+ }
+}
diff --git a/Tests/Rules/BadCmdlet.ps1 b/Tests/Rules/BadCmdlet.ps1
index 546dc31a8..2958a0778 100644
--- a/Tests/Rules/BadCmdlet.ps1
+++ b/Tests/Rules/BadCmdlet.ps1
@@ -7,6 +7,8 @@
ConfirmImpact='Medium')]
[Alias()]
[OutputType([String])]
+ [OutputType("System.Int32", ParameterSetName="ID")]
+
Param
(
# Param1 help description
@@ -46,6 +48,14 @@
}
Process
{
+ $a = 4.5
+
+ if ($true)
+ {
+ $a
+ }
+
+ return @{"hash"="true"}
}
End
{
@@ -53,6 +63,7 @@
}
# Provide comment help should not be raised here because this is not exported
+#Output type rule should also not be raised here as this is not a cmdlet
function NoComment
{
Write-Verbose "No Comment"
diff --git a/Tests/Rules/DscExamplesPresent.tests.ps1 b/Tests/Rules/DscExamplesPresent.tests.ps1
new file mode 100644
index 000000000..e27459a9a
--- /dev/null
+++ b/Tests/Rules/DscExamplesPresent.tests.ps1
@@ -0,0 +1,70 @@
+Import-Module -Verbose PSScriptAnalyzer
+
+$currentPath = Split-Path -Parent $MyInvocation.MyCommand.Path
+$ruleName = "PSDSCDscExamplesPresent"
+
+Describe "DscExamplesPresent rule in class based resource" {
+
+ $examplesPath = "$currentPath\DSCResources\MyDscResource\Examples"
+ $classResourcePath = "$currentPath\DSCResources\MyDscResource\MyDscResource.psm1"
+
+ Context "When examples absent" {
+
+ $violations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $classResourcePath | Where-Object {$_.RuleName -eq $ruleName}
+ $violationMessage = "No examples found for resource 'FileResource'"
+
+ It "has 1 missing examples violation" {
+ $violations.Count | Should Be 1
+ }
+
+ It "has the correct description message" {
+ $violations[0].Message | Should Match $violationMessage
+ }
+ }
+
+ Context "When examples present" {
+ New-Item -Path $examplesPath -ItemType Directory
+ New-Item -Path "$examplesPath\FileResource_Example.psm1" -ItemType File
+
+ $noViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $classResourcePath | Where-Object {$_.RuleName -eq $ruleName}
+
+ It "returns no violations" {
+ $noViolations.Count | Should Be 0
+ }
+
+ Remove-Item -Path $examplesPath -Recurse -Force
+ }
+}
+
+Describe "DscExamplesPresent rule in regular (non-class) based resource" {
+
+ $examplesPath = "$currentPath\Examples"
+ $resourcePath = "$currentPath\DSCResources\MSFT_WaitForAll\MSFT_WaitForAll.psm1"
+
+ Context "When examples absent" {
+
+ $violations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $resourcePath | Where-Object {$_.RuleName -eq $ruleName}
+ $violationMessage = "No examples found for resource 'MSFT_WaitForAll'"
+
+ It "has 1 missing examples violation" {
+ $violations.Count | Should Be 1
+ }
+
+ It "has the correct description message" {
+ $violations[0].Message | Should Match $violationMessage
+ }
+ }
+
+ Context "When examples present" {
+ New-Item -Path $examplesPath -ItemType Directory
+ New-Item -Path "$examplesPath\MSFT_WaitForAll_Example.psm1" -ItemType File
+
+ $noViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $resourcePath | Where-Object {$_.RuleName -eq $ruleName}
+
+ It "returns no violations" {
+ $noViolations.Count | Should Be 0
+ }
+
+ Remove-Item -Path $examplesPath -Recurse -Force
+ }
+}
\ No newline at end of file
diff --git a/Tests/Rules/DscTestsPresent.tests.ps1 b/Tests/Rules/DscTestsPresent.tests.ps1
new file mode 100644
index 000000000..03fbeaa66
--- /dev/null
+++ b/Tests/Rules/DscTestsPresent.tests.ps1
@@ -0,0 +1,70 @@
+Import-Module -Verbose PSScriptAnalyzer
+
+$currentPath = Split-Path -Parent $MyInvocation.MyCommand.Path
+$ruleName = "PSDSCDscTestsPresent"
+
+Describe "DscTestsPresent rule in class based resource" {
+
+ $testsPath = "$currentPath\DSCResources\MyDscResource\Tests"
+ $classResourcePath = "$currentPath\DSCResources\MyDscResource\MyDscResource.psm1"
+
+ Context "When tests absent" {
+
+ $violations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $classResourcePath | Where-Object {$_.RuleName -eq $ruleName}
+ $violationMessage = "No tests found for resource 'FileResource'"
+
+ It "has 1 missing test violation" {
+ $violations.Count | Should Be 1
+ }
+
+ It "has the correct description message" {
+ $violations[0].Message | Should Be $violationMessage
+ }
+ }
+
+ Context "When tests present" {
+ New-Item -Path $testsPath -ItemType Directory
+ New-Item -Path "$testsPath\FileResource_Test.psm1" -ItemType File
+
+ $noViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $classResourcePath | Where-Object {$_.RuleName -eq $ruleName}
+
+ It "returns no violations" {
+ $noViolations.Count | Should Be 0
+ }
+
+ Remove-Item -Path $testsPath -Recurse -Force
+ }
+}
+
+Describe "DscTestsPresent rule in regular (non-class) based resource" {
+
+ $testsPath = "$currentPath\Tests"
+ $resourcePath = "$currentPath\DSCResources\MSFT_WaitForAll\MSFT_WaitForAll.psm1"
+
+ Context "When tests absent" {
+
+ $violations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $resourcePath | Where-Object {$_.RuleName -eq $ruleName}
+ $violationMessage = "No tests found for resource 'MSFT_WaitForAll'"
+
+ It "has 1 missing tests violation" {
+ $violations.Count | Should Be 1
+ }
+
+ It "has the correct description message" {
+ $violations[0].Message | Should Be $violationMessage
+ }
+ }
+
+ Context "When tests present" {
+ New-Item -Path $testsPath -ItemType Directory
+ New-Item -Path "$testsPath\MSFT_WaitForAll_Test.psm1" -ItemType File
+
+ $noViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $resourcePath | Where-Object {$_.RuleName -eq $ruleName}
+
+ It "returns no violations" {
+ $noViolations.Count | Should Be 0
+ }
+
+ Remove-Item -Path $testsPath -Recurse -Force
+ }
+}
\ No newline at end of file
diff --git a/Tests/Rules/GoodCmdlet.ps1 b/Tests/Rules/GoodCmdlet.ps1
index 7c1dfa240..73ea8399c 100644
--- a/Tests/Rules/GoodCmdlet.ps1
+++ b/Tests/Rules/GoodCmdlet.ps1
@@ -28,7 +28,9 @@ function Get-File
HelpUri = 'http://www.microsoft.com/',
ConfirmImpact='Medium')]
[Alias()]
- [OutputType([String])]
+ [OutputType([String], [System.Double], [Hashtable])]
+ [OutputType("System.Int32", ParameterSetName="ID")]
+
Param
(
# Param1 help description
@@ -75,6 +77,15 @@ function Get-File
Write-Verbose "Write Verbose"
Get-Process
}
+
+ $a = 4.5
+
+ if ($true)
+ {
+ $a
+ }
+
+ return @{"hash"="true"}
}
End
{
diff --git a/Tests/Rules/UseOutputTypeCorrectly.tests.ps1 b/Tests/Rules/UseOutputTypeCorrectly.tests.ps1
new file mode 100644
index 000000000..666d281d0
--- /dev/null
+++ b/Tests/Rules/UseOutputTypeCorrectly.tests.ps1
@@ -0,0 +1,29 @@
+Import-Module PSScriptAnalyzer
+$violationMessage = "The cmdlet 'Verb-Files' returns an object of type 'System.Double' but this type is not declared in the OutputType attribute."
+$violationName = "PSUseOutputTypeCorrectly"
+$directory = Split-Path -Parent $MyInvocation.MyCommand.Path
+$violations = Invoke-ScriptAnalyzer $directory\BadCmdlet.ps1 | Where-Object {$_.RuleName -eq $violationName}
+$dscViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $directory\DSCResources\MyDscResource\MyDscResource.psm1 | Where-Object {$_.RuleName -eq $violationName}
+$noViolations = Invoke-ScriptAnalyzer $directory\GoodCmdlet.ps1 | Where-Object {$_.RuleName -eq $violationName}
+
+Describe "UseOutputTypeCorrectly" {
+ Context "When there are violations" {
+ It "has 2 Use OutputType Correctly violations" {
+ $violations.Count | Should Be 2
+ }
+
+ It "has the correct description message" {
+ $violations[0].Message | Should Match $violationMessage
+ }
+
+ It "Does not count violation in DSC class" {
+ $dscViolations.Count | Should Be 0
+ }
+ }
+
+ Context "When there are no violations" {
+ It "returns no violations" {
+ $noViolations.Count | Should Be 0
+ }
+ }
+}
\ No newline at end of file