Static analysis is a tool that runs during each CI build to ensure that the changes being introduced in a pull request follow PowerShell standards and patterns, don't make any breaking changes, contain the appropriate markdown help files for cmdlets, and don't introduce any mismatching package dependencies. Each of these tasks are contained within their own analyzer, as described in the sections down below.
Each analyzer implements the IStaticAnalyzer interface, which describes how to analyze and validate the given modules. The IStaticAnalyzer interface defines overloads of the Analyze method that each analyzer needs to implement.
The breaking change analyzer can be found in the BreakingChangeAnalyzer folder. In this folder, you will find the following classes:
BreakingChangeAnalyzer- The implementation of the
IStaticAnalyzerinterface; determines which modules to analyze, deserializes the modules'.jsonfile from theSerializedCmdletsfolder, and runs breaking change checks between the old (serialized)ModuleMetadataobject and the newModuleMetadataobject constructed from theCmdletLoaderin theTools.Commonproject
- The implementation of the
BreakingChangeIssue- The implementation of the
IReportRecordinterface; defines what a breaking change exception looks like when it's reported in theBreakingChangeIssues.csvfile that is found in the build artifacts of a CI run, as well as how to compare a new record to a record found in the existingBreakingChangeIssues.csvfile used for exception suppressions
- The implementation of the
CmdletMetadataHelper- This class contains methods used to find breaking changes between one set of
CmdletMetadataobjects and another. The following methods are defined in this class:CompareCmdletMetadata()- Compares two sets of
CmdletMetadataobjects. Each old cmdlet is mapped to a new cmdlet and the different breaking change checks are performed. If an old cmdlet cannot be mapped, a breaking change is logged.
- Compares two sets of
CheckForRemovedCmdletAlias()- Checks to see if all of the previous aliases remain in the new cmdlet
CheckForRemovedSupportsShouldProcess()- Checks to see if the new cmdlet continues to implement
SupportsShouldProcessif it previously did
- Checks to see if the new cmdlet continues to implement
CheckForRemovedSupportsPaging()- Checks to see if the new cmdlet continues to implement
SupportsPagingif it previously did
- Checks to see if the new cmdlet continues to implement
CheckForChangedOutputType()- Checks to make sure there was no breaking change in the output type using the
TypeMetadataHelperclass
- Checks to make sure there was no breaking change in the output type using the
CheckDefaultParameterName()- Checks to make sure there was no breaking change in the default parameter set using the
ParameterSetMetadataHelperclass
- Checks to make sure there was no breaking change in the default parameter set using the
- This class contains methods used to find breaking changes between one set of
ParameterMetadataHelper- This class contains methods used to find breaking changes between the global properties of a
ParameterMetadataobject and another. The following methods are defined in this class:CompareParameterMetadata()- Compares two sets of
ParameterMetadataobjects. Each old parameter is mapped to a new parameter and the different breaking change checks are performed. If an old parameter cannot be mapped a breaking change is logged.
- Compares two sets of
CheckForChangedParameterType()- Checks to make sure there was no breaking change in the type of the parameter using the
TypeMetadataHelperclass
- Checks to make sure there was no breaking change in the type of the parameter using the
CheckForRemovedParameterAlias()- Checks to see if all of the previous aliases remain in the new parameter
CheckParameterValidateSets()- Checks to see if all of the previous values defined in the parameter's
ValidateSetattribute remain in the new parameter
- Checks to see if all of the previous values defined in the parameter's
CheckParameterValidateRange()- Checks to make sure the new
ValidateRangeinterval includes the previous parameter'sValidateRangeinterval
- Checks to make sure the new
CheckForValidateNotNullOrEmpty()- Checks to see if the new parameter now disallows null or empty values for the parameter
- This class contains methods used to find breaking changes between the global properties of a
ParameterSetMetadataHelper- This class contains methods used to find breaking changes between one set of
ParameterSetMetadataand another. The following method is defined in this class:CompareParameterSetMetadata()- Compares two sets of
ParameterSetMetadataobjects. Iterating over the old set of parameter sets, the method attempts to map each one to a parameter set in the new parameter sets. To be successfully mapped to a set, each previous parameter must exist in the new set, as well as have the same properties (must continue to be optional if previously, position remains the same, piping scenario remains the same, etc.)
- Compares two sets of
- This class contains methods used to find breaking changes between one set of
TypeMetadataHelper- This class contains methods used to find breaking changes between
TypeMetadataobjects. The following methods are defined in this class:CompareTypeMetadata()- Checks to make sure no breaking changes were introduced between two types by recursively checking the properties of the given types and those properties' types
CompareMethodSignatures()- Checks to make sure no breaking changes were introduced in the method signature of two types
CheckParameterType()- Checks to make sure no breaking changes were introduced in the type of a parameter (by recursively calling
CompareTypeMetadata())
- Checks to make sure no breaking changes were introduced in the type of a parameter (by recursively calling
CheckOutputType()- Checks to make sure no breaking changes were introduced in the output type of a cmdlet (by recursively calling
CompareTypeMetadata())
- Checks to make sure no breaking changes were introduced in the output type of a cmdlet (by recursively calling
IsElementType()- Checks to make sure no breaking changes were introduced between two element types (by recursively calling
CompareTypeMetadata())
- Checks to make sure no breaking changes were introduced between two element types (by recursively calling
IsGenericType()- Checks to make sure no breaking changes were introduced from one generic type to another, as well as checks for breaking changes in the arguments of the generic types (by recursively calling
CompareTypeMetadata())
- Checks to make sure no breaking changes were introduced from one generic type to another, as well as checks for breaking changes in the arguments of the generic types (by recursively calling
HasSameGenericType()- Checks to make sure that the type of the generic remains the same between two generic types
HasSameGenericArgumentSize()- Checks to make sure that the number of arguments remain the same between two generic types
- This class contains methods used to find breaking changes between
The dependency analyzer can be found in the DependencyAnalyzer folder. In this folder, you will find the following classes:
AssemblyVersionConflict- The implementation of the
IReportRecordinterface; defines what a assembly version conflict exception looks like when it's reported in theAssemblyVersionConflict.csvfile that is found in the build artifacts of a CI run, as well as how to compare a new record to a record found in the existingAssemblyVersionConflict.csvfile used for exception suppressions
- The implementation of the
DependencyAnalyzer- The implementation of the
IStaticAnalyzerinterface; determines which modules to analyze and then checks the following:- If an assembly is found to be referenced by the cmdlet assembly, but it's not copied over to the output directory of the module, then a
MissingAssemblyrecord is written - If an assembly is found to be referenced by multiple projects, but the assembly file version is different between the projects, then a
SharedAssemblyConflictrecord is written
- If an assembly is found to be referenced by the cmdlet assembly, but it's not copied over to the output directory of the module, then a
- The implementation of the
DependencyMap- The implementation of the
IReportRecordinterface; defines what a dependency map exception looks like when it's reported in theDependencyMap.csvfile that is found in the build artifacts of a CI run, as well as how to compare a new record to a record found in the existingDependencyMap.csvfile used for exception suppressions
- The implementation of the
ExtraAssembly- The implementation of the
IReportRecordinterface; defines what an extra assembly exception looks like when it's reported in theExtraAssembly.csvfile that is found in the build artifacts of a CI run, as well as how to compare a new record to a record found in the existingExtraAssembly.csvfile used for exception suppressions
- The implementation of the
MissingAssembly- The implementation of the
IReportRecordinterface; defines what a missing assembly exception looks like when it's reported in theMissingAssembly.csvfile that is found in the build artifacts of a CI run, as well as how to compare a new record to a record found in the existingMissingAssembly.csvfile used for exception suppressions
- The implementation of the
SharedAssemblyConflict- The implementation of the
IReportRecordinterface; defines what a shared conflict exception looks like when it's reported in theSharedAssemblyConflict.csvfile that is found in the build artifacts of a CI run, as well as how to compare a new record to a record found in the existingSharedAssemblyConflict.csvfile used for exception suppressions
- The implementation of the
ExampleIssue- The implementation of the
IReportRecordinterface; defines what an example issue exception looks like when it's reported in theExampleIssues.csvfile that is found in the build artifacts of a CI run, as well as how to compare a new record to a record found in the existingExampleIssues.csvfile used for exception suppressions
- The implementation of the
The help analyzer can be found in the HelpAnalyzer folder. In this folder, you will find the following classes:
HelpAnalyzer- The implementation of the
IStaticAnalyzerinterface; determines which modules to analyze and checks to see which cmdlets within those modules don't have a corresponding markdown help file. It also checks the content structure of markdown help conforms to PlatyPS Schema using theMicrosoft.PowerShell.PlatyPSmodule
- The implementation of the
HelpIssues- The implementation of the
IReportRecordinterface; defines what a help exception looks like when it's reported in theHelpIssues.csvfile that is found in the build artifacts of a CI run, as well as how to compare a new record to a record found in the existingHelpIssues.csvfile used for exception suppressions
- The implementation of the
The signature verifier can be found in the SignatureVerifier folder. In this folder, you will find the following classes:
SignatureIssues- The implementation of the
IReportRecordinterface; defines what a signature exception looks like when it's reported in theSignatureIssues.csvfile that is found in the build artifacts of a CI run, as well as how to compare a new record to a record found in the existingSignatureIssues.csvfile used for exception suppressions
- The implementation of the
SignatureVerifier- The implementation of the
IStaticAnalyzerinterface; determines which modules to analyze and runs the signature verifier on theModuleMetadataobject constructed from theCmdletLoaderin theToools.Commonproject. The following checks are made by the signature verifier:- If the cmdlet has a
-Forceparameter, the cmdlet should implementSupportsShouldProcess - If the cmdlet has a
ConfirmImpactlevel that's notMedium, the cmdlet should implementSupportsShouldProcess - If the cmdlet has a verb that signals it would make a change on the server, the cmdlet should implement
SupportsShouldProcess - Verify that the cmdlet has an approved PowerShell verb
- Verify that the cmdlet is using a singular noun
- Verify that the cmdlet has an output type
- Verify that all parameters are singular
- Verify that no parameter has a position greater than 4
- If the cmdlet has more than one parameter, the cmdlet should have a default parameter set defined
- If the cmdlet has a
- The implementation of the
Static analysis is run as a part of the Azure DevOps CI for every pull request opened in the azure-powershell repository; it is run with the Analyze {OS} jobs in the pipeline. In this job, all of the modules that were built as a part of the Build {OS} job will have static analysis run on them, and any exceptions will can be found in the artifacts that are published once the job has completed.
To see the failures in static analysis, click on any of the failed analyze-{OS} artifact folders, and expand the StaticAnalysisResults folder. In this folder, you will want to search through the resulting .csv files for any exceptions with severity 0 or 1 (anything other than these severities are warnings and will not cause build failures).
Note: The DependencyMap.csv file only have severity 3 records and the ExtraAssemblies.csv file almost always has severity 2 records, so you can typically ignore these files.
If an exception is found in one of the resulting .csv files that should be ignored (false-positive or should be ignored), then the following steps should be taken:
- Open the
.csvfile with the exception(s) to suppress in a text editor (such as VS Code) - Copy the lines that need to be suppressed
- Copying these lines using Excel rather than a text editor will cause parsing issues
- Open the corresponding
.csvfile in thetools/StaticAnalysis/Exceptionsfolder in a text editor- Find the module that these exceptions are being suppressed for
- If a folder for the module does not exist, or a file for the corresponding
.csvfile doesn't exist, create it
- Paste the lines that were previously copied to the end of the file
The .csv files found in tools/StaticAnalysis/Exceptions are used by the IReportRecord implementations mentioned previously to check if an exception thrown during static analysis already exists in the suppressed file, and ignoring it if it does.