/// <summary> /// Check for any values that were removed from a validation set of a parameter. /// </summary> /// <param name="cmdlet">The cmdlet whose parameter metadata is currently being checked.</param> /// <param name="oldParameter">The parameter metadata from the old (serialized) assembly.</param> /// <param name="newParameter">The parameter metadata from new assembly</param> /// <param name="issueLogger">ReportLogger that will keep track of issues found.</param> private void CheckParameterValidationSets( CmdletBreakingChangeMetadata cmdlet, ParameterMetadata oldParameter, ParameterMetadata newParameter, ReportLogger <BreakingChangeIssue> issueLogger) { var oldValidateSet = oldParameter.ValidateSet; var newValidateSet = newParameter.ValidateSet; // If there is no validate set in the new assembly, return if (newValidateSet.Count == 0) { return; } // If there was no validate set in the old assembly, but there is // one in the new assembly, log an issue if (oldValidateSet.Count == 0 && newValidateSet.Count > 0) { issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.AddedValidateSet, description: string.Format(Properties.Resources.AddedValidateSetDescription, oldParameter.Name, cmdlet.Name), remediation: string.Format(Properties.Resources.AddedValidateSetRemediation, oldParameter.Name)); return; } // This set will contain all of the values in the validate set from the new metadata HashSet <string> valueSet = new HashSet <string>(); // Add each value in the validate set from the new metadata to the set foreach (var newValue in newValidateSet) { valueSet.Add(newValue); } // For each value in the validate set from the old metadata, check if // it exists in the new metadata foreach (var oldValue in oldValidateSet) { // If the value cannot be found, log an issue if (!valueSet.Contains(oldValue)) { issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.RemovedValidateSetValue, description: string.Format(Properties.Resources.RemovedValidateSetValueDescription, oldParameter.Name, cmdlet.Name, oldValue), remediation: string.Format(Properties.Resources.RemovedValidateSetValueRemediation, oldValue, oldParameter.Name)); } } }
/// <summary> /// Check if the parameter gained a validation range for values, or if the /// existing validation range for values excludes values previously accepted. /// </summary> /// <param name="cmdlet">The cmdlet whose parameter metadata is currently being checked.</param> /// <param name="oldParameter">The parameter metadata from the old (serialized) assembly.</param> /// <param name="newParameter">The parameter metadata from new assembly.</param> /// <param name="issueLogger">ReportLogger that will keep track of the issues found.</param> private void CheckParameterValidateRange( CmdletBreakingChangeMetadata cmdlet, ParameterMetadata oldParameter, ParameterMetadata newParameter, ReportLogger <BreakingChangeIssue> issueLogger) { if (newParameter.ValidateRangeMin != null && newParameter.ValidateRangeMax != null) { // If the old parameter had no validation range, but the new parameter does, log an issue if (oldParameter.ValidateRangeMin == null && oldParameter.ValidateRangeMax == null) { issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.AddedValidateRange, description: string.Format(Properties.Resources.AddedValidateRangeDescription, oldParameter.Name, cmdlet.Name), remediation: string.Format(Properties.Resources.AddedValidateRangeRemediation, oldParameter.Name)); } else { // If the minimum value of the range has increased, log an issue if (oldParameter.ValidateRangeMin < newParameter.ValidateRangeMin) { issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.ChangedValidateRangeMinimum, description: string.Format(Properties.Resources.ChangedValidateRangeMinimumDescription, oldParameter.Name, oldParameter.ValidateRangeMin, newParameter.ValidateRangeMin), remediation: string.Format(Properties.Resources.ChangedValidateRangeMinimumRemediation, oldParameter.Name, oldParameter.ValidateRangeMin)); } // If the maximum value of the range has decreased, log an issue if (oldParameter.ValidateRangeMax > newParameter.ValidateRangeMax) { issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.ChangedValidateRangeMaximum, description: string.Format(Properties.Resources.ChangedValidateRangeMaximumDescription, oldParameter.Name, oldParameter.ValidateRangeMax, newParameter.ValidateRangeMax), remediation: string.Format(Properties.Resources.ChangedValidateRangeMaximumRemediation, oldParameter.Name, oldParameter.ValidateRangeMax)); } } } }
/// <summary> /// Check if the given types are generics, and if so, see if their generic properties are the same. If not, log an issue. /// </summary> /// <param name="cmdlet">The cmdlet metadata currently being checked.</param> /// <param name="oldTypeMetadata">The type metadata from the old (serialized) assembly.</param> /// <param name="newTypeMetadata">The type metadata from the new assembly.</param> /// <param name="target">Target of the generic type breaking change.</param> /// <param name="issueLogger">ReportLogger that will keep track of issues found.</param> /// <returns></returns> private bool IsGenericType( CmdletMetadata cmdlet, TypeMetadata oldTypeMetadata, TypeMetadata newTypeMetadata, string target, ReportLogger <BreakingChangeIssue> issueLogger) { // If the type is a generic, check for any breaking changes if (oldTypeMetadata.GenericTypeArguments.Count > 0 && newTypeMetadata.GenericTypeArguments.Count > 0) { // Check if the generic type has changed if (HasSameGenericType(cmdlet, oldTypeMetadata, newTypeMetadata, target, issueLogger)) { // Check if the number of generic type arguments is the same if (HasSameGenericArgumentSize(cmdlet, oldTypeMetadata, newTypeMetadata, target, issueLogger)) { // For each element in the generic type arguments list, // make sure that the types are the same for (int idx = 0; idx < oldTypeMetadata.GenericTypeArguments.Count; idx++) { var oldArgument = oldTypeMetadata.GenericTypeArguments[idx]; var newArgument = newTypeMetadata.GenericTypeArguments[idx]; var oldElementType = _oldTypeDictionary[oldArgument]; var newElementType = _newTypeDictionary[newArgument]; if (string.Equals(oldArgument, newArgument, StringComparison.OrdinalIgnoreCase)) { // If we have not previously seen this generic type argument, // run this method on the type if (!_typeSet.Contains(oldArgument)) { _typeSet.Add(oldArgument); CompareTypeMetadata(cmdlet, oldElementType, newElementType, issueLogger); } } else { // If the generic type arguments have changed, throw a specific exception for generics if (!CompareTypeMetadata(cmdlet, oldElementType, newElementType, issueLogger)) { // If the generic type arguments aren't the same, log an issue issueLogger?.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.ChangedGenericTypeArgument, description: string.Format(Properties.Resources.ChangedGenericTypeArgumentDescription, target, oldArgument, newArgument), remediation: string.Format(Properties.Resources.ChangedGenericTypeArgumentRemediation, target, oldArgument)); } } } } } return(true); } return(false); }
/// <summary> /// Check if the type of the generic is the same for two generics. If not, log an issue. /// </summary> /// <param name="cmdlet">The cmdlet metadata currently being checked.</param> /// <param name="oldTypeMetadata">The type metadata from the old (serialized) assembly.</param> /// <param name="newTypeMetadata">The type metadata from the new assembly.</param> /// <param name="target">Target of the generic type breaking change..</param> /// <param name="issueLogger">ReportLogger that will keep track of issues found.</param> private bool HasSameGenericType( CmdletBreakingChangeMetadata cmdlet, TypeMetadata oldTypeMetadata, TypeMetadata newTypeMetadata, string target, ReportLogger <BreakingChangeIssue> issueLogger) { // If the type of the generics are the same, return true if (oldTypeMetadata.Name.Equals(newTypeMetadata.Name, StringComparison.OrdinalIgnoreCase)) { return(true); } // Otherwise, log an issue and return false issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.ChangedGenericType, description: string.Format(Properties.Resources.ChangedGenericTypeDescription, target, oldTypeMetadata.Name, newTypeMetadata.Name), remediation: string.Format(Properties.Resources.ChangedGenericTypeRemediation, target, oldTypeMetadata.Name)); return(false); }
/// <summary> /// Check if an alias to a parameter has been removed. /// </summary> /// <param name="cmdlet">The cmdlet whose parameter metadata is currently being checked.</param> /// <param name="oldParameter">The parameter metadata from the old (serialized) assembly.</param> /// <param name="newParameter">The parameter metadata from new assembly</param> /// <param name="issueLogger">ReportLogger that will keep track of issues found.</param> private void CheckForRemovedParameterAlias( CmdletMetadata cmdlet, ParameterMetadata oldParameter, ParameterMetadata newParameter, ReportLogger <BreakingChangeIssue> issueLogger) { // This set will contain all of the aliases in the new metadata HashSet <string> aliasSet = new HashSet <string>(); // Add each of the aliases from the parameter in the new metadata foreach (var newAlias in newParameter.AliasList) { aliasSet.Add(newAlias); } // For each alias from the parameter in the old metadata, // see if it exists in the new metadata foreach (var oldAlias in oldParameter.AliasList) { // If the alias cannot be found, log an issue if (!aliasSet.Contains(oldAlias)) { issueLogger?.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.RemovedParameterAlias, description: string.Format(Resources.RemovedParameterAliasDescription, cmdlet.Name, oldAlias, oldParameter.Name), remediation: string.Format(Resources.RemovedParameterAliasRemediation, oldAlias, oldParameter.Name)); } } }
/// <summary> /// Check if the argument size for the generics are the same. If they are not, log an issue. /// </summary> /// <param name="cmdlet">The cmdlet metadata currently being checked.</param> /// <param name="oldTypeMetadata">The type metadata from the old (serialized) assembly.</param> /// <param name="newTypeMetadata">The type metadata from the new assembly.</param> /// <param name="target">Target of the generic type breaking change.</param> /// <param name="issueLogger">ReportLogger that will keep track of issues found.</param> /// <returns></returns> private bool HasSameGenericArgumentSize( CmdletBreakingChangeMetadata cmdlet, TypeMetadata oldTypeMetadata, TypeMetadata newTypeMetadata, string target, ReportLogger <BreakingChangeIssue> issueLogger) { // If the arguments are the same size, return true if (oldTypeMetadata.GenericTypeArguments.Count == newTypeMetadata.GenericTypeArguments.Count) { return(true); } // Otherwise, log an issue and return false issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.DifferentGenericTypeArgumentSize, description: string.Format(Properties.Resources.DifferentGenericTypeArgumentSizeDescription, oldTypeMetadata.Name, target, oldTypeMetadata.GenericTypeArguments.Count, newTypeMetadata.GenericTypeArguments.Count), remediation: string.Format(Properties.Resources.DifferentGenericTypeArgumentSizeRemediation, oldTypeMetadata.Name, oldTypeMetadata.GenericTypeArguments.Count)); return(false); }
/// <summary> /// Check for breaking changes in the public API of types. /// </summary> /// <param name="oldTypeMetadataDictionary">Dictionary of type names mapped to metadata from the old module.</param> /// <param name="newTypeMetadataDictionary">Dictionary of type names mapped to metadata from the new module.</param> /// <param name="issueLogger">Temporary logger used to track the breaking changes found.</param> private void CheckBreakingChangesInTypes( Dictionary <string, TypeMetadata> oldTypeMetadataDictionary, Dictionary <string, TypeMetadata> newTypeMetadataDictionary, ReportLogger <BreakingChangeIssue> issueLogger) { var typeMetadataHelper = new TypeMetadataHelper(oldTypeMetadataDictionary, newTypeMetadataDictionary); foreach (var type in oldTypeMetadataDictionary.Keys) { var cmdletMetadata = new CmdletMetadata() { NounName = "Common", VerbName = type }; if (!newTypeMetadataDictionary.ContainsKey(type)) { issueLogger?.LogBreakingChangeIssue( cmdlet: cmdletMetadata, severity: 0, problemId: 0, description: string.Empty, remediation: string.Empty); continue; } var oldTypeMetadata = oldTypeMetadataDictionary[type]; var newTypeMetadata = newTypeMetadataDictionary[type]; typeMetadataHelper.CompareTypeMetadata(cmdletMetadata, oldTypeMetadata, newTypeMetadata, issueLogger); typeMetadataHelper.CompareMethodSignatures(cmdletMetadata, oldTypeMetadata.Methods, newTypeMetadata.Methods, issueLogger); typeMetadataHelper.CompareMethodSignatures(cmdletMetadata, oldTypeMetadata.Constructors, newTypeMetadata.Constructors, issueLogger); } }
/// <summary> /// Checks if an alias to a cmdlet has been removed. /// </summary> /// <param name="oldCmdlet">The cmdlet metadata from the old (serialized) assembly.</param> /// <param name="newCmdlet">The cmdlet metadata from the new assembly.</param> /// <param name="issueLogger">ReportLogger that will keep track of issues found.</param> private void CheckForRemovedCmdletAlias( CmdletBreakingChangeMetadata oldCmdlet, CmdletBreakingChangeMetadata newCmdlet, ReportLogger <BreakingChangeIssue> issueLogger) { // This set will contain all of the aliases in the new metadata HashSet <string> aliasSet = new HashSet <string>(); // Add each alias to the set foreach (var newAlias in newCmdlet.AliasList) { aliasSet.Add(newAlias); } // For each alias in the old metadata, check to see // if it exists in the new metadata foreach (var oldAlias in oldCmdlet.AliasList) { // If the alias cannot be found, log an issue if (!aliasSet.Contains(oldAlias)) { issueLogger.LogBreakingChangeIssue( cmdlet: oldCmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.RemovedCmdletAlias, description: string.Format(Properties.Resources.RemovedCmdletAliasDescription, oldCmdlet.Name, oldAlias), remediation: string.Format(Properties.Resources.RemovedCmdletAliasRemediation, oldAlias, oldCmdlet.Name)); } } }
/// <summary> /// Check if the default parameter set has changed, and if so, make sure /// that the parameters are the same /// </summary> /// <param name="oldCmdlet">The cmdlet metadata from the old (serialized) assembly.</param> /// <param name="newCmdlet">The cmdlet metadata from the new assembly.</param> /// <param name="issueLogger">ReportLogger that will keep track of issues found.</param> private void CheckDefaultParameterName( CmdletMetadata oldCmdlet, CmdletMetadata newCmdlet, ReportLogger <BreakingChangeIssue> issueLogger) { // If the default parameter name hasn't changed, or if there wasn't a // default parameter set defined before, return if (oldCmdlet.DefaultParameterSetName.Equals(newCmdlet.DefaultParameterSetName) || oldCmdlet.DefaultParameterSetName.Equals("__AllParameterSets")) { return; } // Get the metadata for the old default parameter set ParameterSetMetadata oldDefaultParameterSet = oldCmdlet.ParameterSets .FirstOrDefault(p => p.Name.Equals(oldCmdlet.DefaultParameterSetName, StringComparison.OrdinalIgnoreCase)); // Get the metadata for the new default parameter set ParameterSetMetadata newDefaultParameterSet = newCmdlet.ParameterSets .FirstOrDefault(p => p.Name.Equals(newCmdlet.DefaultParameterSetName, StringComparison.OrdinalIgnoreCase)); if (oldDefaultParameterSet == null || newDefaultParameterSet == null) { return; } // This dictionary will map a parameter name and aliases to the corresponding Parameter object Dictionary <string, Parameter> parameterDictionary = new Dictionary <string, Parameter>(); // Add each parameter name and aliases to the dictionary foreach (var newParameter in newDefaultParameterSet.Parameters) { parameterDictionary.Add(newParameter.ParameterMetadata.Name, newParameter); foreach (var alias in newParameter.ParameterMetadata.AliasList) { parameterDictionary.Add(alias, newParameter); } } // For each parameter in the old default parameter set, check if it // exists in the new default parameter set foreach (var oldParameter in oldDefaultParameterSet.Parameters) { // If the parameter cannot be found, log an issue if (!parameterDictionary.ContainsKey(oldParameter.ParameterMetadata.Name)) { issueLogger?.LogBreakingChangeIssue( cmdlet: oldCmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.ChangeDefaultParameter, description: string.Format(Resources.ChangeDefaultParameterDescription, oldCmdlet.DefaultParameterSetName, oldCmdlet.Name), remediation: string.Format(Resources.ChangeDefaultParameterRemediation, oldCmdlet.Name, oldCmdlet.DefaultParameterSetName)); } } }
/// <summary> /// Compares the metadata of cmdlets with the same name (or alias) for any breaking changes. /// /// Breaking changes for cmdlets include /// - Removing a cmdlet /// - Removing an alias to a cmdlet /// - Removing SupportsShouldProcess /// - Removing SupportsPaging /// - Output type (or any of its properties) of cmdlet has changed /// - Default parameter set has changed /// /// This method will also check for breaking changes in the cmdlets' parameters and /// parameter sets using the appropriate helpers. /// </summary> /// <param name="oldCmdlets">The list of cmdlets from the old (serialized) metadata.</param> /// <param name="newCmdlets">The list of cmdlets from the new metadata.</param> /// <param name="issueLogger">ReportLogger that will keep track of issues found.</param> public void CompareCmdletMetadata( List <CmdletBreakingChangeMetadata> oldCmdlets, List <CmdletBreakingChangeMetadata> newCmdlets, ReportLogger <BreakingChangeIssue> issueLogger) { // This dictionary will map a cmdlet name (or alias) to the corresponding metadata Dictionary <string, CmdletBreakingChangeMetadata> cmdletDictionary = new Dictionary <string, CmdletBreakingChangeMetadata>(); // Add each cmdlet and its aliases to the dictionary foreach (var newCmdlet in newCmdlets) { cmdletDictionary.Add(newCmdlet.Name, newCmdlet); foreach (var alias in newCmdlet.AliasList) { if (!cmdletDictionary.ContainsKey(alias)) { cmdletDictionary.Add(alias, newCmdlet); } } } // For each cmdlet in the old metadata, see if it has been // added to the dictionary (exists in the new metadata) foreach (var oldCmdlet in oldCmdlets) { if (cmdletDictionary.ContainsKey(oldCmdlet.Name)) { var newCmdlet = cmdletDictionary[oldCmdlet.Name]; // Go through all of the breaking change checks for cmdlets CheckForRemovedCmdletAlias(oldCmdlet, newCmdlet, issueLogger); CheckForRemovedSupportsShouldProcess(oldCmdlet, newCmdlet, issueLogger); CheckForRemovedSupportsPaging(oldCmdlet, newCmdlet, issueLogger); CheckForChangedOutputType(oldCmdlet, newCmdlet, issueLogger); CheckDefaultParameterName(oldCmdlet, newCmdlet, issueLogger); // Go through all of the breaking change checks for parameters using the ParameterMetadataHelper _parameterMetadataHelper.CompareParameterMetadata(oldCmdlet, oldCmdlet.Parameters, newCmdlet.Parameters, issueLogger); // Go through all of the breaking change checks for parameter sets using the ParmaterSetMetadataHelper _parameterSetMetadataHelper.CompareParameterSetMetadata(oldCmdlet, oldCmdlet.ParameterSets, newCmdlet.ParameterSets, issueLogger); } // If the cmdlet cannot be found, log an issue else { issueLogger.LogBreakingChangeIssue( cmdlet: oldCmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.RemovedCmdlet, description: string.Format(Properties.Resources.RemovedCmdletDescription, oldCmdlet.Name), remediation: string.Format(Properties.Resources.RemovedCmdletRemediation, oldCmdlet.Name)); } } }
public void CompareMethodSignatures( CmdletMetadata cmdlet, List <TypeMetadata.MethodSignature> oldMethods, List <TypeMetadata.MethodSignature> newMethods, ReportLogger <BreakingChangeIssue> issueLogger) { foreach (var oldMethod in oldMethods) { bool found = false; var candidateMethods = newMethods.Where(m => string.Equals(m.Name, oldMethod.Name)).ToList(); foreach (var newMethod in candidateMethods) { var oldParameters = oldMethod.Parameters; var newParameters = newMethod.Parameters; if (oldParameters.Count != newParameters.Count) { continue; } bool match = true; for (int idx = 0; idx < oldParameters.Count; idx++) { var oldParameter = oldParameters[idx]; var newParameter = newParameters[idx]; if (oldParameter.Name != newParameter.Name || oldParameter.Type != newParameter.Type) { match = false; break; } } if (oldMethod.ReturnType != null && newMethod.ReturnType != null && !string.Equals(oldMethod.ReturnType, newMethod.ReturnType)) { match = false; } if (match) { found = true; break; } } if (!found) { issueLogger?.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: 0, description: string.Empty, remediation: string.Empty); } } }
/// <summary> /// Check if the OutputType of the cmdlet has been removed, or if any property /// of the OutputType has been removed or changed /// </summary> /// <param name="oldCmdlet">The cmdlet metadata from the old (serialized) assembly.</param> /// <param name="newCmdlet">The cmdlet metadata from the new assembly.</param> /// <param name="issueLogger">ReportLogger that will keep track of issues found.</param> private void CheckForChangedOutputType( CmdletMetadata oldCmdlet, CmdletMetadata newCmdlet, ReportLogger <BreakingChangeIssue> issueLogger) { // This dictionary will map an output type name to the corresponding type metadata Dictionary <string, TypeMetadata> outputDictionary = new Dictionary <string, TypeMetadata>(new TypeNameComparer()); // Add each output in the new metadata to the dictionary if (newCmdlet != null && newCmdlet.OutputTypes != null) { foreach (var newOutput in newCmdlet.OutputTypes) { if (!outputDictionary.ContainsKey(newOutput.Type.Name)) { outputDictionary.Add(newOutput.Type.Name, newOutput.Type); } } } // For each output in the old metadata, see if it // exists in the new metadata foreach (var oldOutput in oldCmdlet.OutputTypes) { // If the output can be found, use the TypeMetadataHelper to // check the type for any breaking changes if (outputDictionary.ContainsKey(oldOutput.Type.Name)) { var newOutputType = outputDictionary[oldOutput.Type.Name]; _typeMetadataHelper.CheckOutputType(oldCmdlet, oldOutput.Type, newOutputType, issueLogger); } // If the output cannot be found by name, check if the old output can be mapped // to any of the new output types else { var foundOutput = outputDictionary.Values.Any(o => _typeMetadataHelper.CompareTypeMetadata(oldCmdlet, oldOutput.Type, o, null)); if (!foundOutput) { issueLogger?.LogBreakingChangeIssue( cmdlet: oldCmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.ChangedOutputType, description: string.Format(Resources.ChangedOutputTypeDescription, oldCmdlet.Name, oldOutput.Type.Name), remediation: string.Format(Resources.ChangedOutputTypeRemediation, oldCmdlet.Name, oldOutput.Type.Name)); } } } }
/// <summary> /// Compares the metadata of parameters with the same name (or alias) for any breaking changes. /// /// Breaking changes for parameters include /// - Removing a parameter /// - Removing an alias to a parameter /// - Type (or any of its properties) of parameter has changed /// - A value in the validate set of a parameter has been removed /// - ValidateNotNullOrEmpty attribute has been added to a parameter /// </summary> /// <param name="cmdlet">Reference to the cmdlet whose parameters are being checked.</param> /// <param name="oldParameters">The list of parameters from the old cmdlet metadata.</param> /// <param name="newParameters">The list of parameters from the new cmdlet metadata.</param> /// <param name="issueLogger">ReportLogger that will keep track of the issues found.</param> public void CompareParameterMetadata( CmdletBreakingChangeMetadata cmdlet, List <ParameterMetadata> oldParameters, List <ParameterMetadata> newParameters, ReportLogger <BreakingChangeIssue> issueLogger) { // This dictionary will map a parameter name (or alias) to the corresponding metadata Dictionary <string, ParameterMetadata> parameterDictionary = new Dictionary <string, ParameterMetadata>(); // For each parameter, add its name (and aliases) to the dictionary foreach (var newParameter in newParameters) { parameterDictionary.Add(newParameter.Name, newParameter); foreach (var alias in newParameter.AliasList) { parameterDictionary.Add(alias, newParameter); } } // For each parameter in the old metadata, see if it has been // added to the dictionary (exists in the new metadata) foreach (var oldParameter in oldParameters) { if (parameterDictionary.ContainsKey(oldParameter.Name)) { var newParameter = parameterDictionary[oldParameter.Name]; // Go through all of the breaking change checks for parameters CheckForChangedParameterType(cmdlet, oldParameter, newParameter, issueLogger); CheckForRemovedParameterAlias(cmdlet, oldParameter, newParameter, issueLogger); CheckParameterValidationSets(cmdlet, oldParameter, newParameter, issueLogger); CheckForValidateNotNullOrEmpty(cmdlet, oldParameter, newParameter, issueLogger); CheckParameterValidateRange(cmdlet, oldParameter, newParameter, issueLogger); } // If the parameter cannot be found, log an issue else { issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.RemovedParameter, description: string.Format(Properties.Resources.RemovedParameterDescription, cmdlet.Name, oldParameter.Name), remediation: string.Format(Properties.Resources.RemovedParameterRemediation, oldParameter.Name, cmdlet.Name)); } } }
/// <summary> /// Check if a cmdlet no longer implements SupportsPaging. /// </summary> /// <param name="oldCmdlet">The cmdlet metadata from the old (serialized) assembly.</param> /// <param name="newCmdlet">The cmdlet metadata from the new assembly.</param> /// <param name="issueLogger">ReportLogger that will keep track of issues found.</param> private void CheckForRemovedSupportsPaging( CmdletBreakingChangeMetadata oldCmdlet, CmdletBreakingChangeMetadata newCmdlet, ReportLogger <BreakingChangeIssue> issueLogger) { // If the old cmdlet implements SupportsPaging and the new cmdlet does not, log an issue if (oldCmdlet.SupportsPaging && !newCmdlet.SupportsPaging) { issueLogger.LogBreakingChangeIssue( cmdlet: oldCmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.RemovedPaging, description: string.Format(Properties.Resources.RemovedPagingDescription, oldCmdlet.Name), remediation: string.Format(Properties.Resources.RemovedPagingRemediation, oldCmdlet.Name)); } }
/// <summary> /// Check if a cmdlet no longer implements SupportsShouldProcess. /// </summary> /// <param name="oldCmdlet">The cmdlet metadata from the old (serialized) assembly.</param> /// <param name="newCmdlet">The cmdlet metadata from the new assembly.</param> /// <param name="issueLogger">ReportLogger that will keep track of issues found.</param> private void CheckForRemovedSupportsShouldProcess( CmdletMetadata oldCmdlet, CmdletMetadata newCmdlet, ReportLogger <BreakingChangeIssue> issueLogger) { // If the old cmdlet implements SupportsShouldProcess and the new cmdlet does not, log an issue if (oldCmdlet.SupportsShouldProcess && !newCmdlet.SupportsShouldProcess) { issueLogger?.LogBreakingChangeIssue( cmdlet: oldCmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.RemovedShouldProcess, description: string.Format(Resources.RemovedShouldProcessDescription, oldCmdlet.Name), remediation: string.Format(Resources.RemovedShouldProcessRemediation, oldCmdlet.Name)); } }
/// <summary> /// Checks if the type of the parameter is an array or a generic, and makes sure there are no breaking changes. /// If the type is not an array or a generic, it proceeds with the normal type checking with CompareTypeMetadata. /// </summary> /// <param name="cmdlet">The cmdlet whose parameter metadata is being checked for breaking changes.</param> /// <param name="parameter">The parameter whose type metadata is being checked for breaking changes.</param> /// <param name="oldTypeMetadata">The type metadata from the old (serialized) assembly.</param> /// <param name="newTypeMetadata">The type metadata from the new assembly.</param> /// <param name="issueLogger">ReportLogger that will keep track of issues found.</param> public void CheckParameterType( CmdletMetadata cmdlet, ParameterMetadata parameter, TypeMetadata oldTypeMetadata, TypeMetadata newTypeMetadata, ReportLogger <BreakingChangeIssue> issueLogger) { int problemId = ProblemIds.BreakingChangeProblemId.ChangedParameterElementType; string description = string.Format(Resources.ChangedParameterElementTypeDescription, parameter.Name, oldTypeMetadata.ElementType, newTypeMetadata.ElementType); string remediation = string.Format(Resources.ChangedParameterElementTypeRemediation, parameter.Name, oldTypeMetadata.ElementType); // If the type is an array, check for any breaking changes if (IsElementType(cmdlet, oldTypeMetadata, newTypeMetadata, problemId, description, remediation, issueLogger)) { return; } string target = string.Format("parameter {0}", parameter.Name); // If the type is a generic, check for any breaking changes if (IsGenericType(cmdlet, oldTypeMetadata, newTypeMetadata, target, issueLogger)) { return; } // If the types are different, log an issue string oldTypeName = oldTypeMetadata.GetClassNameWithoutApiVersion(oldTypeMetadata.Name); string newTypeName = newTypeMetadata.GetClassNameWithoutApiVersion(newTypeMetadata.Name); if (!oldTypeName.Equals(newTypeName, StringComparison.OrdinalIgnoreCase)) { issueLogger?.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.ChangedParameterType, description: string.Format(Resources.ChangedParameterTypeDescription, cmdlet.Name, oldTypeMetadata.Name, parameter.Name), remediation: string.Format(Resources.ChangedParameterTypeRemediation, parameter.Name, oldTypeMetadata.Name)); } else { CompareTypeMetadata(cmdlet, oldTypeMetadata, newTypeMetadata, issueLogger); } }
/// <summary> /// Check if the given types are arrays, and if so, see if their element types are the same. If not, log an issue. /// </summary> /// <param name="cmdlet">The cmdlet metadata currently being checked.</param> /// <param name="oldTypeMetadata">The type metadata from the old (serialized) assembly.</param> /// <param name="newTypeMetadata">The type metadata from the new assembly.</param> /// <param name="problemId">The problemId used for logging if there is an issue.</param> /// <param name="description">The description used for logging if there is an issue.</param> /// <param name="remediation">The remediation used for logging if there is an issue.</param> /// <param name="issueLogger">ReportLogger that will keep track of issues found.</param> private bool IsElementType( CmdletMetadata cmdlet, TypeMetadata oldTypeMetadata, TypeMetadata newTypeMetadata, int problemId, string description, string remediation, ReportLogger <BreakingChangeIssue> issueLogger) { // Check if the type is an array if (oldTypeMetadata.ElementType != null && newTypeMetadata.ElementType != null) { string oldTypeName = oldTypeMetadata.GetClassNameWithoutApiVersion(oldTypeMetadata.ElementType); string newTypeName = newTypeMetadata.GetClassNameWithoutApiVersion(newTypeMetadata.ElementType); // Check if the element of the array is the same in the old and new metadata if (oldTypeName.Equals(newTypeName, StringComparison.OrdinalIgnoreCase)) { // If we have not previously seen this element type, // run this method on the type if (!_typeSet.Contains(oldTypeMetadata.ElementType)) { _typeSet.Add(oldTypeMetadata.ElementType); var oldElementType = _oldTypeDictionary[oldTypeMetadata.ElementType]; var newElementType = _newTypeDictionary[newTypeMetadata.ElementType]; CompareTypeMetadata(cmdlet, oldElementType, newElementType, issueLogger); } } // If the element type has changed, log an issue else { issueLogger?.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: problemId, description: description, remediation: remediation); } return(true); } return(false); }
/// <summary> /// Check if the OutputType of the cmdlet has been removed, or if any property /// of the OutputType has been removed or changed /// </summary> /// <param name="oldCmdlet">The cmdlet metadata from the old (serialized) assembly.</param> /// <param name="newCmdlet">The cmdlet metadata from the new assembly.</param> /// <param name="issueLogger">ReportLogger that will keep track of issues found.</param> private void CheckForChangedOutputType( CmdletBreakingChangeMetadata oldCmdlet, CmdletBreakingChangeMetadata newCmdlet, ReportLogger <BreakingChangeIssue> issueLogger) { // This dictionary will map an output type name to the corresponding type metadata Dictionary <string, TypeMetadata> outputDictionary = new Dictionary <string, TypeMetadata>(); // Add each output in the new metadata to the dictionary foreach (var newOutput in newCmdlet.OutputTypes) { if (!outputDictionary.ContainsKey(newOutput.Type.AssemblyQualifiedName)) { outputDictionary.Add(newOutput.Type.AssemblyQualifiedName, newOutput.Type); } } // For each output in the old metadata, see if it // exists in the new metadata foreach (var oldOutput in oldCmdlet.OutputTypes) { // If the output can be found, use the TypeMetadataHelper to // check the type for any breaking changes if (outputDictionary.ContainsKey(oldOutput.Type.AssemblyQualifiedName)) { var newOutputType = outputDictionary[oldOutput.Type.AssemblyQualifiedName]; _typeMetadataHelper.CheckOutputType(oldCmdlet, oldOutput.Type, newOutputType, issueLogger); } // If the output cannot be found, log an issue else { issueLogger.LogBreakingChangeIssue( cmdlet: oldCmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.ChangedOutputType, description: string.Format(Properties.Resources.ChangedOutputTypeDescription, oldCmdlet.Name, oldOutput.Type.Name), remediation: string.Format(Properties.Resources.ChangedOutputTypeRemediation, oldCmdlet.Name, oldOutput.Type.Name)); } } }
/// <summary> /// Check if the parameter now supports the ValidateNotNullOrEmpty attribute /// </summary> /// <param name="cmdlet">The cmdlet whose parameter metadata is currently being checked.</param> /// <param name="oldParameter">The parameter metadata from the old (serialized) assembly.</param> /// <param name="newParameter">The parameter metadata from new assembly</param> /// <param name="issueLogger">ReportLogger that will keep track of issues found.</param> private void CheckForValidateNotNullOrEmpty( CmdletBreakingChangeMetadata cmdlet, ParameterMetadata oldParameter, ParameterMetadata newParameter, ReportLogger <BreakingChangeIssue> issueLogger) { // If the parameter didn't have the ValidateNotNullOrEmpty attribute in the // old assembly, but has it in the new assembly, log an issue if (!oldParameter.ValidateNotNullOrEmpty && newParameter.ValidateNotNullOrEmpty) { issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.AddedValidateNotNullOrEmpty, description: string.Format(Properties.Resources.AddedValidateNotNullOrEmptyDescription, oldParameter.Name, cmdlet.Name), remediation: string.Format(Properties.Resources.AddedValidateNotNullOrEmptyRemediation, oldParameter.Name)); } }
/// <summary> /// Check if the arguments of a generic type are the same. If they are not, log an issue. /// </summary> /// <param name="cmdlet">The cmdlet metadata currently being checked.</param> /// <param name="oldArgument">The old argument from the generic.</param> /// <param name="newArgument">The new argument from the generic</param> /// <param name="target">Target of the generic type breaking change.</param> /// <param name="issueLogger">ReportLogger that will keep track of issues found.</param> private bool CheckGenericTypeArguments( CmdletBreakingChangeMetadata cmdlet, string oldArgument, string newArgument, string target, ReportLogger <BreakingChangeIssue> issueLogger) { if (oldArgument.Equals(newArgument, StringComparison.OrdinalIgnoreCase)) { return(true); } // If the generic type arguments aren't the same, log an issue issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.ChangedGenericTypeArgument, description: string.Format(Properties.Resources.ChangedGenericTypeArgumentDescription, target, oldArgument, newArgument), remediation: string.Format(Properties.Resources.ChangedGenericTypeArgumentRemediation, target, oldArgument)); return(false); }
/// <summary> /// Compare two types by recursively checking their properties and property /// types, making sure that nothing has been removed or changed. /// </summary> /// <param name="cmdlet">The cmdlet metadata currently being checked.</param> /// <param name="oldType">The type metadata from the old (serialized) assembly.</param> /// <param name="newType">The type metadata from the new assembly.</param> /// <param name="issueLogger">ReportLogger that will keep track of issues found.</param> public void CompareTypeMetadata( CmdletBreakingChangeMetadata cmdlet, TypeMetadata oldType, TypeMetadata newType, ReportLogger <BreakingChangeIssue> issueLogger) { // For each property in the old assembly type, find the corresponding // property in the new assembly type foreach (var oldProperty in oldType.Properties.Keys) { if (newType.Properties.ContainsKey(oldProperty)) { var oldPropertyType = oldType.Properties[oldProperty]; var newPropertyType = newType.Properties[oldProperty]; var oldTypeMetadata = _oldTypeDictionary[oldPropertyType]; var newTypeMetadata = _newTypeDictionary[newPropertyType]; // Check if the type is an array if (oldTypeMetadata.ElementType != null && newTypeMetadata.ElementType != null) { // Check if the element of the array is the same in the old and new metadata if (oldTypeMetadata.ElementType.Equals(newTypeMetadata.ElementType, StringComparison.OrdinalIgnoreCase)) { // If we have not previously seen this element type, // run this method on the type if (!_typeSet.Contains(oldTypeMetadata.ElementType)) { _typeSet.Add(oldTypeMetadata.ElementType); var oldElementType = _oldTypeDictionary[oldTypeMetadata.ElementType]; var newElementType = _newTypeDictionary[newTypeMetadata.ElementType]; CompareTypeMetadata(cmdlet, oldElementType, newElementType, issueLogger); } } // If the element type has changed, log an issue else { issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.ChangedElementType, description: string.Format(Properties.Resources.ChangedElementTypeDescription, oldProperty, oldTypeMetadata.ElementType, newTypeMetadata.ElementType), remediation: string.Format(Properties.Resources.ChangedElementTypeRemediation, oldProperty, oldTypeMetadata.ElementType)); } continue; } // Check if the type is a generic if (oldTypeMetadata.GenericTypeArguments.Count > 0 && newTypeMetadata.GenericTypeArguments.Count > 0) { // Check if the generic type has changed if (oldTypeMetadata.Name.Equals(newTypeMetadata.Name, StringComparison.OrdinalIgnoreCase)) { // Check if the number of generic type arguments is the same if (oldTypeMetadata.GenericTypeArguments.Count == newTypeMetadata.GenericTypeArguments.Count) { // For each element in the generic type arguments list, make sure that the types // are the same for (int idx = 0; idx < oldTypeMetadata.GenericTypeArguments.Count; idx++) { if (oldTypeMetadata.GenericTypeArguments[idx].Equals(newTypeMetadata.GenericTypeArguments[idx], StringComparison.OrdinalIgnoreCase)) { // If we have not previously seen this generic type argument, // run this method on the type if (!_typeSet.Contains(oldTypeMetadata.GenericTypeArguments[idx])) { _typeSet.Add(oldTypeMetadata.GenericTypeArguments[idx]); var oldElementType = _oldTypeDictionary[oldTypeMetadata.GenericTypeArguments[idx]]; var newElementType = _newTypeDictionary[newTypeMetadata.GenericTypeArguments[idx]]; CompareTypeMetadata(cmdlet, oldElementType, newElementType, issueLogger); } } // If the generic type arguments aren't the same, log an issue else { issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.ChangedGenericTypeArgument, description: string.Format(Properties.Resources.ChangedGenericTypeArgumentDescription, oldProperty, oldTypeMetadata.GenericTypeArguments[idx], newTypeMetadata.GenericTypeArguments[idx]), remediation: string.Format(Properties.Resources.ChangedGenericTypeArgumentRemediation, oldProperty, oldTypeMetadata.GenericTypeArguments[idx])); } } } // If the number of generic type arguments is different, log an issue else { issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.DifferentGenericTypeArgumentSize, description: string.Format(Properties.Resources.DifferentGenericTypeArgumentSizeDescription, oldTypeMetadata.Name, oldProperty, oldTypeMetadata.GenericTypeArguments.Count, newTypeMetadata.GenericTypeArguments.Count), remediation: string.Format(Properties.Resources.DifferentGenericTypeArgumentSizeRemediation, oldTypeMetadata.Name, oldTypeMetadata.GenericTypeArguments.Count)); } } // If the generic type has changed, log an issue else { issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.ChangedGenericType, description: string.Format(Properties.Resources.ChangedGenericTypeDescription, oldProperty, oldTypeMetadata.Name, newTypeMetadata.Name), remediation: string.Format(Properties.Resources.ChangedGenericTypeRemediation, oldProperty, oldTypeMetadata.Name)); } continue; } // If the types are the same, compare their properties if (oldPropertyType.Equals(newPropertyType, StringComparison.OrdinalIgnoreCase)) { // If we have not previously seen this type, run this // method on the type if (!_typeSet.Contains(oldPropertyType)) { _typeSet.Add(oldPropertyType); CompareTypeMetadata(cmdlet, oldTypeMetadata, newTypeMetadata, issueLogger); } } else { // If the type of the property has been changed, log an issue issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.ChangedPropertyType, description: string.Format(Properties.Resources.ChangedPropertyTypeDescription, oldProperty, oldType.Name, oldPropertyType, newPropertyType), remediation: string.Format(Properties.Resources.ChangedPropertyTypeRemediation, oldProperty, oldPropertyType)); } } else { // If the property has been removed, log an issue issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.RemovedProperty, description: string.Format(Properties.Resources.RemovedPropertyDescription, oldProperty, oldType.Name), remediation: string.Format(Properties.Resources.RemovedPropertyRemediation, oldProperty, oldType.Name)); } } }
/// <summary> /// Checks if the type of the output is an array or a generic, and makes sure there are no breaking changes. /// If the type is not an array or a generic, it proceeds with the normal type checking with CompareTypeMetadata. /// </summary> /// <param name="cmdlet">The cmdlet whose output metadata is being checked for breaking changes.</param> /// <param name="oldTypeMetadata">The type metadata from the old (serialized) assembly.</param> /// <param name="newTypeMetadata">The type metadata from the new assembly.</param> /// <param name="issueLogger">ReportLogger that will keep track of issues found.</param> public void CheckOutputType( CmdletBreakingChangeMetadata cmdlet, TypeMetadata oldTypeMetadata, TypeMetadata newTypeMetadata, ReportLogger <BreakingChangeIssue> issueLogger) { // Check if the type is an array if (oldTypeMetadata.ElementType != null && newTypeMetadata.ElementType != null) { // Check if the element of the array is the same in the old and new metadata if (oldTypeMetadata.ElementType.Equals(newTypeMetadata.ElementType, StringComparison.OrdinalIgnoreCase)) { // If we have not previously seen this element type, // run this method on the type if (!_typeSet.Contains(oldTypeMetadata.ElementType)) { _typeSet.Add(oldTypeMetadata.ElementType); var oldElementType = _oldTypeDictionary[oldTypeMetadata.ElementType]; var newElementType = _newTypeDictionary[newTypeMetadata.ElementType]; CompareTypeMetadata(cmdlet, oldElementType, newElementType, issueLogger); } } // If the element type has changed, log an issue else { issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.ChangedOutputElementType, description: string.Format(Properties.Resources.ChangedOutputElementTypeDescription, oldTypeMetadata.ElementType, newTypeMetadata.ElementType), remediation: string.Format(Properties.Resources.ChangedOutputElementTypeRemediation, oldTypeMetadata.ElementType)); } return; } // Check if the type is a generic if (oldTypeMetadata.GenericTypeArguments.Count > 0 && newTypeMetadata.GenericTypeArguments.Count > 0) { // Check if the generic type has changed if (oldTypeMetadata.Name.Equals(newTypeMetadata.Name, StringComparison.OrdinalIgnoreCase)) { // Check if the number of generic type arguments is the same if (oldTypeMetadata.GenericTypeArguments.Count == newTypeMetadata.GenericTypeArguments.Count) { // For each element in the generic type arguments list, make sure that the types // are the same for (int idx = 0; idx < oldTypeMetadata.GenericTypeArguments.Count; idx++) { if (oldTypeMetadata.GenericTypeArguments[idx].Equals(newTypeMetadata.GenericTypeArguments[idx], StringComparison.OrdinalIgnoreCase)) { // If we have not previously seen this generic type argument, // run this method on the type if (!_typeSet.Contains(oldTypeMetadata.GenericTypeArguments[idx])) { _typeSet.Add(oldTypeMetadata.GenericTypeArguments[idx]); var oldElementType = _oldTypeDictionary[oldTypeMetadata.GenericTypeArguments[idx]]; var newElementType = _newTypeDictionary[newTypeMetadata.GenericTypeArguments[idx]]; CompareTypeMetadata(cmdlet, oldElementType, newElementType, issueLogger); } } // If the generic type arguments aren't the same, log an issue else { issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.ChangedOutputGenericTypeArgument, description: string.Format(Properties.Resources.ChangedOutputGenericTypeArgumentDescription, oldTypeMetadata.GenericTypeArguments[idx], newTypeMetadata.GenericTypeArguments[idx]), remediation: string.Format(Properties.Resources.ChangedOutputGenericTypeArgumentRemediation, oldTypeMetadata.GenericTypeArguments[idx])); } } } // If the number of generic type arguments is different, log an issue else { issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.DifferentOutputGenericTypeArgumentSize, description: string.Format(Properties.Resources.DifferentOutputGenericTypeArgumentSizeDescription, oldTypeMetadata.Name, oldTypeMetadata.GenericTypeArguments.Count, newTypeMetadata.GenericTypeArguments.Count), remediation: string.Format(Properties.Resources.DifferentOutputGenericTypeArgumentSizeRemediation, oldTypeMetadata.Name, oldTypeMetadata.GenericTypeArguments.Count)); } } // If the generic type has changed, log an issue else { issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.ChangedOutputGenericType, description: string.Format(Properties.Resources.ChangedOutputGenericTypeDescription, oldTypeMetadata.Name, newTypeMetadata.Name), remediation: string.Format(Properties.Resources.ChangedOutputGenericTypeRemediation, oldTypeMetadata.Name)); } return; } CompareTypeMetadata(cmdlet, oldTypeMetadata, newTypeMetadata, issueLogger); }
/// <summary> /// Compare two types by recursively checking their properties and property /// types, making sure that nothing has been removed or changed. /// </summary> /// <param name="cmdlet">The cmdlet metadata currently being checked.</param> /// <param name="oldType">The type metadata from the old (serialized) assembly.</param> /// <param name="newType">The type metadata from the new assembly.</param> /// <param name="issueLogger">ReportLogger that will keep track of issues found.</param> public void CompareTypeMetadata( CmdletBreakingChangeMetadata cmdlet, TypeMetadata oldType, TypeMetadata newType, ReportLogger <BreakingChangeIssue> issueLogger) { // For each property in the old assembly type, find the corresponding // property in the new assembly type foreach (var oldProperty in oldType.Properties.Keys) { if (newType.Properties.ContainsKey(oldProperty)) { var oldPropertyType = oldType.Properties[oldProperty]; var newPropertyType = newType.Properties[oldProperty]; var oldTypeMetadata = _oldTypeDictionary[oldPropertyType]; var newTypeMetadata = _newTypeDictionary[newPropertyType]; // Define variables for logging int problemId = ProblemIds.BreakingChangeProblemId.ChangedElementType; string description = string.Format(Properties.Resources.ChangedElementTypeDescription, oldProperty, oldTypeMetadata.ElementType, newTypeMetadata.ElementType); string remediation = string.Format(Properties.Resources.ChangedElementTypeRemediation, oldProperty, oldTypeMetadata.ElementType); // If the type is an array, check for any breaking changes if (IsElementType(cmdlet, oldTypeMetadata, newTypeMetadata, problemId, description, remediation, issueLogger)) { continue; } string target = string.Format("property {0}", oldProperty); // If the type is a generic, check for any breaking changes if (IsGenericType(cmdlet, oldTypeMetadata, newTypeMetadata, target, issueLogger)) { continue; } // If the types are the same, compare their properties if (oldPropertyType.Equals(newPropertyType, StringComparison.OrdinalIgnoreCase)) { // If we have not previously seen this type, run this // method on the type if (!_typeSet.Contains(oldPropertyType)) { _typeSet.Add(oldPropertyType); CompareTypeMetadata(cmdlet, oldTypeMetadata, newTypeMetadata, issueLogger); } } else { // If the type of the property has been changed, log an issue issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.ChangedPropertyType, description: string.Format(Properties.Resources.ChangedPropertyTypeDescription, oldProperty, oldType.Name, oldPropertyType, newPropertyType), remediation: string.Format(Properties.Resources.ChangedPropertyTypeRemediation, oldProperty, oldPropertyType)); } } else { // If the property has been removed, log an issue issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.RemovedProperty, description: string.Format(Properties.Resources.RemovedPropertyDescription, oldProperty, oldType.Name), remediation: string.Format(Properties.Resources.RemovedPropertyRemediation, oldProperty, oldType.Name)); } } }
/// <summary> /// Compares the metadata of parameter sets with the same name for any breaking changes. /// /// Breaking changes for parameter sets include /// - Removing a parameter set /// - Making an optional parameter mandatory /// - Changing the position of a parameter /// - A parameter that previously could get its value from the pipeline no longer does /// - A parameter that previously could get its value from the pipeline by property name no longer does /// - A parameter has been removed from the parameter set /// </summary> /// <param name="cmdlet">Reference to the cmdlet whose parameter sets are being checked.</param> /// <param name="oldParameterSets">The list of parameter sets from the old (serialized) metadata.</param> /// <param name="newParameterSets">The list of parameter sets from the new metadata</param> /// <param name="issueLogger">ReportLogger that will keep track of the issues found.</param> public void CompareParameterSetMetadata( CmdletMetadata cmdlet, List <ParameterSetMetadata> oldParameterSets, List <ParameterSetMetadata> newParameterSets, ReportLogger <BreakingChangeIssue> issueLogger) { // This dictionary will map a parameter set name to the corresponding metadata Dictionary <string, ParameterSetMetadata> parameterSetDictionary = new Dictionary <string, ParameterSetMetadata>(); // Add each parameter set to the dictionary foreach (var newParameterSet in newParameterSets) { parameterSetDictionary.Add(newParameterSet.Name, newParameterSet); } // For each parameter set in the old metadata, see if it has been // added to the dictionary (exists in the new metadata) foreach (var oldParameterSet in oldParameterSets) { bool foundMatch = false; // Find matching parameter set foreach (var newParameterSet in newParameterSets) { Dictionary <string, Parameter> parameterDictionary = new Dictionary <string, Parameter>(); foreach (var parameter in newParameterSet.Parameters) { parameterDictionary.Add(parameter.ParameterMetadata.Name, parameter); foreach (var alias in parameter.ParameterMetadata.AliasList) { parameterDictionary.Add(alias, parameter); } } // Check if set has minimum parameters required to match bool minimumRequired = true; foreach (var parameter in oldParameterSet.Parameters) { if (!parameterDictionary.ContainsKey(parameter.ParameterMetadata.Name)) { minimumRequired = false; break; } else { var newParameter = parameterDictionary[parameter.ParameterMetadata.Name]; if (!parameter.Mandatory && newParameter.Mandatory || parameter.Position >= 0 && parameter.Position != newParameter.Position || parameter.ValueFromPipeline && !newParameter.ValueFromPipeline || parameter.ValueFromPipelineByPropertyName && !newParameter.ValueFromPipelineByPropertyName) { minimumRequired = false; break; } } } if (!minimumRequired) { continue; } parameterDictionary = new Dictionary <string, Parameter>(); foreach (var parameter in oldParameterSet.Parameters) { parameterDictionary.Add(parameter.ParameterMetadata.Name, parameter); foreach (var alias in parameter.ParameterMetadata.AliasList) { parameterDictionary.Add(alias, parameter); } } // Check if set has any additional mandatory parameters bool foundAdditional = false; foreach (var parameter in newParameterSet.Parameters) { if (parameterDictionary.ContainsKey(parameter.ParameterMetadata.Name)) { continue; } if (parameter.Mandatory) { foundAdditional = true; break; } } if (!foundAdditional) { foundMatch = true; break; } } if (!foundMatch) { issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.RemovedParameterSet, description: string.Format(Properties.Resources.RemovedParameterSetDescription, oldParameterSet.Name, cmdlet.Name), remediation: string.Format(Properties.Resources.RemovedParameterSetRemediation, oldParameterSet.Name, cmdlet.Name)); } } }
/// <summary> /// Compares the metadata of parameter sets with the same name for any breaking changes. /// /// Breaking changes for parameter sets include /// - Removing a parameter set /// - Making an optional parameter mandatory /// - Changing the position of a parameter /// - A parameter that previously could get its value from the pipeline no longer does /// - A parameter that previously could get its value from the pipeline by property name no longer does /// - A parameter has been removed from the parameter set /// </summary> /// <param name="cmdlet">Reference to the cmdlet whose parameter sets are being checked.</param> /// <param name="oldParameterSets">The list of parameter sets from the old (serialized) metadata.</param> /// <param name="newParameterSets">The list of parameter sets from the new metadata</param> /// <param name="issueLogger">ReportLogger that will keep track of the issues found.</param> public void CompareParameterSetMetadata( CmdletBreakingChangeMetadata cmdlet, List <ParameterSetMetadata> oldParameterSets, List <ParameterSetMetadata> newParameterSets, ReportLogger <BreakingChangeIssue> issueLogger) { // This dictionary will map a parameter set name to the corresponding metadata Dictionary <string, ParameterSetMetadata> parameterSetDictionary = new Dictionary <string, ParameterSetMetadata>(); // Add each parameter set to the dictionary foreach (var newParameterSet in newParameterSets) { parameterSetDictionary.Add(newParameterSet.Name, newParameterSet); } // For each parameter set in the old metadata, see if it has been // added to the dictionary (exists in the new metadata) foreach (var oldParameterSet in oldParameterSets) { if (parameterSetDictionary.ContainsKey(oldParameterSet.Name)) { var newParameterSet = parameterSetDictionary[oldParameterSet.Name]; // This dictionary will map a parameter to the corresponding Parameter object Dictionary <string, Parameter> parameterDictionary = new Dictionary <string, Parameter>(); // For each parameter in the parameter set, add its name and alias to the dictionary foreach (var newParameter in newParameterSet.Parameters) { if (!parameterDictionary.ContainsKey(newParameter.ParameterMetadata.Name)) { parameterDictionary.Add(newParameter.ParameterMetadata.Name, newParameter); } foreach (var alias in newParameter.ParameterMetadata.AliasList) { if (!parameterDictionary.ContainsKey(alias)) { parameterDictionary.Add(alias, newParameter); } } } // For each parameter in the old metadata, see if it has been // added to the dictionary (exists in the new metadata) foreach (var oldParameter in oldParameterSet.Parameters) { if (parameterDictionary.ContainsKey(oldParameter.ParameterMetadata.Name)) { var newParameter = parameterDictionary[oldParameter.ParameterMetadata.Name]; // If the parameter was optional in the old assembly and // mandatory in the new assembly, log an issue if (!oldParameter.Mandatory && newParameter.Mandatory) { issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.MandatoryParameter, description: string.Format(Properties.Resources.MandatoryParameterDescription, oldParameter.ParameterMetadata.Name, oldParameterSet.Name, cmdlet.Name), remediation: string.Format(Properties.Resources.MandatoryParameterRemediation, oldParameter.ParameterMetadata.Name, oldParameterSet.Name)); } // If the parameter had a position and it has changed in the // new assembly, log an issue if (oldParameter.Position >= 0 && oldParameter.Position != newParameter.Position) { issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.PositionChange, description: string.Format(Properties.Resources.PositionChangeDescription, oldParameter.ParameterMetadata.Name, oldParameterSet.Name, cmdlet.Name), remediation: string.Format(Properties.Resources.PositionChangeRemediation, oldParameter.ParameterMetadata.Name, oldParameterSet.Name)); } // If the parameter can no longer get its value from // the pipeline, log an issue if (oldParameter.ValueFromPipeline && !newParameter.ValueFromPipeline) { issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.ValueFromPipeline, description: string.Format(Properties.Resources.RemovedValueFromPipelineDescription, oldParameter.ParameterMetadata.Name, oldParameterSet.Name, cmdlet.Name), remediation: string.Format(Properties.Resources.RemovedValueFromPipelineRemediation, oldParameter.ParameterMetadata.Name, oldParameterSet.Name)); } // If the parameter can no longer get its value from // the pipeline by property name, log an issue if (oldParameter.ValueFromPipelineByPropertyName && !newParameter.ValueFromPipelineByPropertyName) { issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.ValueFromPipelineByPropertyName, description: string.Format(Properties.Resources.RemovedValueFromPipelineByPropertyNameDescription, oldParameter.ParameterMetadata.Name, oldParameterSet.Name, cmdlet.Name), remediation: string.Format(Properties.Resources.RemovedValueFromPipelineByPropertyNameRemediation, oldParameter.ParameterMetadata.Name, oldParameterSet.Name)); } } // If the parameter cannot be found, log an issue else { issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.RemovedParameterFromParameterSet, description: string.Format(Properties.Resources.RemovedParameterFromParameterSetDescription, oldParameter.ParameterMetadata.Name, cmdlet.Name, oldParameterSet.Name), remediation: string.Format(Properties.Resources.RemovedParameterFromParameterSetRemediation, oldParameter.ParameterMetadata.Name, oldParameterSet.Name)); } } } // If the parameter set cannot be found, and the parameter set // was not the default (no parameter set) name, log an issue else if (!parameterSetDictionary.ContainsKey(oldParameterSet.Name) && !oldParameterSet.Name.Equals("__AllParameterSets")) { issueLogger.LogBreakingChangeIssue( cmdlet: cmdlet, severity: 0, problemId: ProblemIds.BreakingChangeProblemId.RemovedParameterSet, description: string.Format(Properties.Resources.RemovedParameterSetDescription, oldParameterSet.Name, cmdlet.Name), remediation: string.Format(Properties.Resources.RemovedParameterSetRemediation, oldParameterSet.Name, cmdlet.Name)); } } }