private static void EvaluateDefaultValueChanges( ItemMatch <IParameterDefinition> match, ComparerOptions options, IChangeResultAggregator aggregator) { var oldItem = match.OldItem; var newItem = match.NewItem; if (string.IsNullOrWhiteSpace(oldItem.DefaultValue) && string.IsNullOrWhiteSpace(newItem.DefaultValue) == false) { // A default value has been added, this is technically a feature because the consuming assembly can treat it like an overload var args = new FormatArguments( "{DefinitionType} {Identifier} has added the default value {NewValue}", match.NewItem.FullName, null, newItem.DefaultValue); aggregator.AddElementChangedResult(SemVerChangeType.Feature, match, options.MessageFormatter, args); } else if (string.IsNullOrWhiteSpace(oldItem.DefaultValue) == false && string.IsNullOrWhiteSpace(newItem.DefaultValue)) { // A default value has been removed // This will not be a breaking change for existing applications that happen to use a new binary without recompilation // however it does cause a breaking change for compiling existing applications against this API var args = new FormatArguments( "{DefinitionType} {Identifier} has removed the default value {OldValue}", match.NewItem.FullName, oldItem.DefaultValue, null); aggregator.AddElementChangedResult(SemVerChangeType.Breaking, match, options.MessageFormatter, args); } }
private static bool EvaluateArgumentCount(ItemMatch <IAttributeDefinition> match, ComparerOptions options, IChangeResultAggregator aggregator) { var oldArguments = match.OldItem.Arguments.Count; var newArguments = match.NewItem.Arguments.Count; var argumentShift = oldArguments - newArguments; if (argumentShift != 0) { // One or more arguments have been added or removed var changeLabel = argumentShift > 0 ? "removed" : "added"; var shiftAmount = Math.Abs(argumentShift); var suffix = shiftAmount == 1 ? "" : "s"; var args = new FormatArguments( $"{{DefinitionType}} {{Identifier}} has {changeLabel} {shiftAmount} argument{suffix}", match.NewItem.Name, null, null); aggregator.AddElementChangedResult(SemVerChangeType.Breaking, match, options.MessageFormatter, args); // No need to look into how the attribute has changed return(true); } return(false); }
private static bool EvaluateOrdinalArgument(ItemMatch <IAttributeDefinition> match, ComparerOptions options, IChangeResultAggregator aggregator) { var oldArguments = match.OldItem.Arguments.Where(x => x.ArgumentType == ArgumentType.Ordinal).ToList(); var newArguments = match.NewItem.Arguments.Where(x => x.ArgumentType == ArgumentType.Ordinal).ToList(); for (var index = 0; index < oldArguments.Count; index++) { var oldArgument = oldArguments[index]; var newArgument = newArguments[index]; if (oldArgument.Value != newArgument.Value) { var args = new FormatArguments( "{DefinitionType} {Identifier} has changed value from {OldValue} to {NewValue}", (index + 1).ToString(), oldArgument.Value, newArgument.Value); // Create a match for the arguments var argumentMatch = new ItemMatch <IArgumentDefinition>(oldArgument, newArgument); aggregator.AddElementChangedResult(SemVerChangeType.Breaking, argumentMatch, options.MessageFormatter, args); return(true); } } return(false); }
public IEnumerable <ComparisonResult> CompareMatch(ItemMatch <ITypeDefinition> match, ComparerOptions options) { match = match ?? throw new ArgumentNullException(nameof(match)); options = options ?? throw new ArgumentNullException(nameof(options)); // Check for a change in type if (match.OldItem.GetType() != match.NewItem.GetType()) { var newType = DetermineTypeChangeDescription(match.NewItem); var args = new FormatArguments( "{DefinitionType} {Identifier} has changed to {NewValue}", match.OldItem.FullName, null, newType); var message = options.MessageFormatter.FormatItemChangedMessage(match, args); var result = new ComparisonResult( SemVerChangeType.Breaking, match.OldItem, match.NewItem, message); // We are not going to process any other changes return(new[] { result }); } if (match.OldItem is IClassDefinition oldClass && match.NewItem is IClassDefinition newClass) { var itemMatch = new ItemMatch <IClassDefinition>(oldClass, newClass); return(_classComparer.CompareMatch(itemMatch, options)); } if (match.OldItem is IStructDefinition oldStruct && match.NewItem is IStructDefinition newStruct) { var itemMatch = new ItemMatch <IStructDefinition>(oldStruct, newStruct); return(_structComparer.CompareMatch(itemMatch, options)); } if (match.OldItem is IInterfaceDefinition oldInterface && match.NewItem is IInterfaceDefinition newInterface) { var itemMatch = new ItemMatch <IInterfaceDefinition>(oldInterface, newInterface); return(_interfaceComparer.CompareMatch(itemMatch, options)); } throw new NotSupportedException( $"There is no {nameof(ITypeComparer<ITypeDefinition>)} implementation for {match.OldItem.GetType()}"); }
public IEnumerable <ComparisonResult> CompareMatch(ItemMatch <IGenericTypeElement> match, ComparerOptions options) { match = match ?? throw new ArgumentNullException(nameof(match)); options = options ?? throw new ArgumentNullException(nameof(options)); var oldTypeParameters = match.OldItem.GenericTypeParameters.FastToList(); var newTypeParameters = match.NewItem.GenericTypeParameters.FastToList(); if (oldTypeParameters.Count == 0 && newTypeParameters.Count == 0) { return(Array.Empty <ComparisonResult>()); } var typeParameterShift = oldTypeParameters.Count - newTypeParameters.Count; var aggregator = new ChangeResultAggregator(); if (typeParameterShift != 0) { var changeLabel = typeParameterShift > 0 ? "removed" : "added"; var shiftAmount = Math.Abs(typeParameterShift); // One or more generic type parameters have been removed var suffix = shiftAmount == 1 ? "" : "s"; var args = new FormatArguments( $"{{DefinitionType}} {{Identifier}} has {changeLabel} {shiftAmount} generic type parameter{suffix}", match.NewItem.FullName, null, null); aggregator.AddElementChangedResult(SemVerChangeType.Breaking, match, options.MessageFormatter, args); // No need to look into how the generic type has changed return(aggregator.Results); } // We have the same number of generic types, evaluate the constraints for (var index = 0; index < oldTypeParameters.Count; index++) { var oldName = oldTypeParameters[index]; var newName = newTypeParameters[index]; var oldConstraints = match.OldItem.GenericConstraints.FirstOrDefault(x => x.Name == oldName); var newConstraints = match.NewItem.GenericConstraints.FirstOrDefault(x => x.Name == newName); EvaluateGenericConstraints(match, oldConstraints, newConstraints, options, aggregator); } return(aggregator.Results); }
public void FormatItemAddedMessageMapsKnownDefinitionTypes(Type definitionType) { const string?messageFormat = "{DefinitionType} {Identifier}"; var identifier = Guid.NewGuid().ToString(); var arguments = new FormatArguments(messageFormat, identifier, null, null); var definition = (IItemDefinition)Substitute.For(new[] { definitionType }, Array.Empty <object>()); var sut = new DefaultMessageFormatter(); var actual = sut.FormatItemAddedMessage(definition, arguments !); actual.Should().NotStartWith("Element "); }
private static void EvaluateNamedArgument(ItemMatch <IAttributeDefinition> match, ComparerOptions options, IChangeResultAggregator aggregator) { var oldArguments = match.OldItem.Arguments.Where(x => x.ArgumentType == ArgumentType.Named).ToList(); var newArguments = match.NewItem.Arguments.Where(x => x.ArgumentType == ArgumentType.Named).ToList(); // At this point we have the same number of named arguments // Assuming there is a change, it can either be // All parameter names match but a value has changed, or // There is a different in parameter names which can only mean at least one has changed (old removed, new added) // The later will just be recorded as the old named parameter has been removed for (var index = 0; index < oldArguments.Count; index++) { var oldArgument = oldArguments[index]; var newArgument = newArguments.FirstOrDefault(x => x.ParameterName == oldArgument.ParameterName); if (newArgument == null) { // This argument has been removed var args = new FormatArguments( "{DefinitionType} {Identifier} has been removed", oldArgument.ParameterName, null, null); var message = options.MessageFormatter.FormatItemRemovedMessage(oldArgument, args); var result = new ComparisonResult(SemVerChangeType.Breaking, oldArgument, null, message); aggregator.AddResult(result); return; } if (oldArgument.Value != newArgument.Value) { // There is a match on the parameter names but the value has changed var args = new FormatArguments( "{DefinitionType} {Identifier} has changed value from {OldValue} to {NewValue}", oldArgument.ParameterName, oldArgument.Value, newArgument.Value); // Create a match for the arguments var argumentMatch = new ItemMatch <IArgumentDefinition>(oldArgument, newArgument); aggregator.AddElementChangedResult(SemVerChangeType.Breaking, argumentMatch, options.MessageFormatter, args); return; } } }
public void PropertiesReturnConstructorParameters() { var messageFormat = Guid.NewGuid().ToString(); var identifier = Guid.NewGuid().ToString(); var oldValue = Guid.NewGuid().ToString(); var newValue = Guid.NewGuid().ToString(); var sut = new FormatArguments(messageFormat, identifier, oldValue, newValue); sut.MessageFormat.Should().Be(messageFormat); sut.Identifier.Should().Be(identifier); sut.OldValue.Should().Be(oldValue); sut.NewValue.Should().Be(newValue); }
public void FormatItemAddedMessageDeterminesDefinitionTypeBasedOnDefinitionType(Type definitionType, string expected) { const string?messageFormat = "{DefinitionType} {Identifier}"; var identifier = Guid.NewGuid().ToString(); var arguments = new FormatArguments(messageFormat, identifier, null, null); var definition = (IItemDefinition)Substitute.For(new[] { definitionType }, Array.Empty <object>()); var sut = new DefaultMessageFormatter(); var actual = sut.FormatItemAddedMessage(definition, arguments !); actual.Should().Be($"{expected} {identifier}"); }
public void FormatItemChangedMessageFormatsMessageWithMarkup() { var identifier = Guid.NewGuid().ToString(); var oldValue = Guid.NewGuid().ToString(); var newValue = Guid.NewGuid().ToString(); var match = new ItemMatch <IPropertyDefinition>(new TestPropertyDefinition(), new TestPropertyDefinition()); var arguments = new FormatArguments("{DefinitionType} {Identifier} {OldValue} {NewValue}", identifier, oldValue, newValue); var sut = new GitHubMarkdownMessageFormatter(); var actual = sut.FormatItemChangedMessage(match, arguments); actual.Should().Be($"Property `{identifier}` `{oldValue}` `{newValue}`"); }
public void FormatItemAddedMessageFormatsMessageWithProvidedArguments() { const string?messageFormat = "{DefinitionType} - {Identifier} - {OldValue} - {NewValue}"; var identifier = Guid.NewGuid().ToString(); var oldValue = Guid.NewGuid().ToString(); var newValue = Guid.NewGuid().ToString(); var arguments = new FormatArguments(messageFormat, identifier, oldValue, newValue); var definition = Substitute.For <IClassDefinition>(); var sut = new DefaultMessageFormatter(); var actual = sut.FormatItemAddedMessage(definition, arguments !); actual.Should().Be($"Class - {identifier} - {oldValue} - {newValue}"); }
public void AddElementChangedResultThrowsExceptionWithNullMessageFormatter() { var oldItem = new TestClassDefinition(); var newItem = new TestClassDefinition(); var match = new ItemMatch <IClassDefinition>(oldItem, newItem); var arguments = new FormatArguments(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), oldItem.Name, newItem.Name); var aggregator = Substitute.For <IChangeResultAggregator>(); Action action = () => aggregator.AddElementChangedResult(SemVerChangeType.Breaking, match, null !, arguments); action.Should().Throw <ArgumentNullException>(); }
private void EvaluateParameterChanges( ItemMatch <IMethodDefinition> match, ComparerOptions options, IChangeResultAggregator aggregator) { var oldParameters = match.OldItem.Parameters.FastToList(); var newParameters = match.NewItem.Parameters.FastToList(); if (oldParameters.Count == 0 && newParameters.Count == 0) { return; } var parameterShift = oldParameters.Count - newParameters.Count; if (parameterShift != 0) { var changeLabel = parameterShift > 0 ? "removed" : "added"; var shiftAmount = Math.Abs(parameterShift); // One or more generic type parameters have been added or removed var suffix = shiftAmount == 1 ? "" : "s"; var args = new FormatArguments( $"{{DefinitionType}} {{Identifier}} has {changeLabel} {shiftAmount} parameter{suffix}", match.NewItem.FullName, null, null); aggregator.AddElementChangedResult(SemVerChangeType.Breaking, match, options.MessageFormatter, args); // No need to look into how the generic type has changed return; } // We have the same number of parameters, compare them for (var index = 0; index < oldParameters.Count; index++) { var oldParameter = oldParameters[index]; var newParameter = newParameters[index]; var parameterMatch = new ItemMatch <IParameterDefinition>(oldParameter, newParameter); var parameterChanges = _parameterComparer.CompareMatch(parameterMatch, options); aggregator.AddResults(parameterChanges); } }
public void FormatItemAddedMessageDeterminesDefinitionTypeBasedOnArgumentType(ArgumentType argumentType, string expected) { const string?messageFormat = "{DefinitionType} {Identifier}"; var identifier = Guid.NewGuid().ToString(); var arguments = new FormatArguments(messageFormat, identifier, null, null); var definition = Substitute.For <IArgumentDefinition>(); definition.ArgumentType.Returns(argumentType); var sut = new DefaultMessageFormatter(); var actual = sut.FormatItemAddedMessage(definition, arguments !); actual.Should().Be($"{expected} {identifier}"); }
private static void EvaluateMethodNameChanges( ItemMatch <IMethodDefinition> match, ComparerOptions options, IChangeResultAggregator aggregator) { if (match.OldItem.RawName == match.NewItem.RawName) { return; } var args = new FormatArguments( "{DefinitionType} {Identifier} has been renamed to {NewValue}", match.OldItem.FullName, null, match.NewItem.Name); aggregator.AddElementChangedResult(SemVerChangeType.Breaking, match, options.MessageFormatter, args); }
private static void EvaluateParameterTypeChanges( ItemMatch <IParameterDefinition> match, ComparerOptions options, IChangeResultAggregator aggregator) { var oldType = match.OldItem.Type; var newType = match.NewItem.Type; var oldMappedType = match.OldItem.DeclaringMethod.GetMatchingGenericType(oldType, match.NewItem.DeclaringMethod); if (oldMappedType != newType) { var args = new FormatArguments( "{DefinitionType} {Identifier} has change type from {OldValue} to {NewValue}", match.NewItem.FullName, oldType, newType); aggregator.AddElementChangedResult(SemVerChangeType.Breaking, match, options.MessageFormatter, args); } }
public void AddElementChangedResultAddsMessageResultToAggregator() { var oldItem = new TestClassDefinition(); var newItem = new TestClassDefinition(); var match = new ItemMatch <IClassDefinition>(oldItem, newItem); var arguments = new FormatArguments(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), oldItem.Name, newItem.Name); var message = Guid.NewGuid().ToString(); var changeType = Model.UsingModule <ConfigurationModule>().Create <SemVerChangeType>(); var aggregator = Substitute.For <IChangeResultAggregator>(); var formatter = Substitute.For <IMessageFormatter>(); formatter.FormatItemChangedMessage(match, arguments).Returns(message); aggregator.AddElementChangedResult(changeType, match, formatter, arguments); aggregator.Received(1).AddResult(Arg.Any <ComparisonResult>()); aggregator.Received().AddResult(Arg.Is <ComparisonResult>(x => x.OldItem == oldItem)); aggregator.Received().AddResult(Arg.Is <ComparisonResult>(x => x.NewItem == newItem)); aggregator.Received().AddResult(Arg.Is <ComparisonResult>(x => x.Message == message)); aggregator.Received().AddResult(Arg.Is <ComparisonResult>(x => x.ChangeType == changeType)); }
/// <summary> /// just debugger string /// </summary> /// <returns>debugger string</returns> public string DebugDisplay() { return $"Number arguments {FormatArguments?.ToString()} "; }
private void AddIfNotExists(string key, string value) { if (FormatArguments.ContainsKey(key)) return; FormatArguments.Add(key, value); }
protected virtual IEnumerable <ComparisonResult> CompareMatch( ItemMatch <IElementDefinition> match, T oldValue, T newValue, ComparerOptions options) { match = match ?? throw new ArgumentNullException(nameof(match)); var change = _changeTable.CalculateChange(oldValue, newValue); if (change == SemVerChangeType.None) { yield break; } var newModifiers = GetDeclaredModifiers(match.NewItem); var oldModifiers = GetDeclaredModifiers(match.OldItem); if (string.IsNullOrWhiteSpace(oldModifiers)) { // Modifiers have been added where there were previously none defined var suffix = string.Empty; if (newModifiers.Contains(" ")) { // There is more than one modifier suffix = "s"; } var args = new FormatArguments( $"{{DefinitionType}} {{Identifier}} has added the {{NewValue}} {ModifierLabel}{suffix}", match.NewItem.FullName, null, newModifiers); var message = options.MessageFormatter.FormatItemChangedMessage(match, args); var result = new ComparisonResult( change, match.OldItem, match.NewItem, message); yield return(result); } else if (string.IsNullOrWhiteSpace(newModifiers)) { // All previous modifiers have been removed var suffix = string.Empty; if (oldModifiers.Contains(" ")) { // There is more than one modifier suffix = "s"; } var args = new FormatArguments( $"{{DefinitionType}} {{Identifier}} has removed the {{OldValue}} {ModifierLabel}{suffix}", match.NewItem.FullName, oldModifiers, null); var message = options.MessageFormatter.FormatItemChangedMessage(match, args); var result = new ComparisonResult( change, match.OldItem, match.NewItem, message); yield return(result); } else { // Modifiers have been changed var suffix = string.Empty; if (oldModifiers.Contains(" ") || newModifiers.Contains(" ")) { // There is more than one modifier suffix = "s"; } var args = new FormatArguments( $"{{DefinitionType}} {{Identifier}} has changed the {ModifierLabel}{suffix} from {{OldValue}} to {{NewValue}}", match.NewItem.FullName, oldModifiers, newModifiers); var message = options.MessageFormatter.FormatItemChangedMessage(match, args); var result = new ComparisonResult( change, match.OldItem, match.NewItem, message); yield return(result); } }
public virtual IEnumerable <ComparisonResult> CalculateChanges( IEnumerable <T> oldItems, IEnumerable <T> newItems, ComparerOptions options) { oldItems = oldItems ?? throw new ArgumentNullException(nameof(oldItems)); newItems = newItems ?? throw new ArgumentNullException(nameof(newItems)); options = options ?? throw new ArgumentNullException(nameof(options)); IMatchResults <T> matchingNodes = _evaluator.FindMatches(oldItems, newItems); // Record any visible items that have been added // Items added which are not publicly visible are ignored foreach (var memberAdded in matchingNodes.ItemsAdded) { var isVisible = IsVisible(memberAdded); var name = memberAdded.Name; if (memberAdded is IElementDefinition element) { name = element.FullName; } if (isVisible) { var args = new FormatArguments("{DefinitionType} {Identifier} has been added", name, null, null); var message = options.MessageFormatter.FormatItemAddedMessage(memberAdded, args); var changeType = GetItemAddedChangeType(memberAdded); var result = new ComparisonResult(changeType, null, memberAdded, message); yield return(result); } else if (_logger != null && _logger.IsEnabled(LogLevel.Debug)) { var args = new FormatArguments("{DefinitionType} {Identifier} has been added but is not visible", name, null, null); var message = options.MessageFormatter.FormatItemAddedMessage(memberAdded, args); _logger.LogDebug(message); } } // Record any visible items that have been removed // Items removed which are not publicly visible are ignored foreach (var memberRemoved in matchingNodes.ItemsRemoved) { var isVisible = IsVisible(memberRemoved); var name = memberRemoved.Name; if (memberRemoved is IElementDefinition element) { name = element.FullName; } if (isVisible) { var args = new FormatArguments("{DefinitionType} {Identifier} has been removed", name, null, null); var message = options.MessageFormatter.FormatItemRemovedMessage(memberRemoved, args); var result = new ComparisonResult(SemVerChangeType.Breaking, memberRemoved, null, message); yield return(result); } else if (_logger != null && _logger.IsEnabled(LogLevel.Debug)) { var args = new FormatArguments("{DefinitionType} {Identifier} has been removed but is not visible", name, null, null); var message = options.MessageFormatter.FormatItemAddedMessage(memberRemoved, args); _logger.LogDebug(message); } } // Check all the matches for a breaking change or feature added foreach (var match in matchingNodes.MatchingItems) { var comparisonResults = CompareMatchingItems(match, options); foreach (var result in comparisonResults) { yield return(result); } } }
private static void EvaluateGenericConstraints( ItemMatch <IGenericTypeElement> match, IConstraintListDefinition?oldConstraintList, IConstraintListDefinition?newConstraintList, ComparerOptions options, IChangeResultAggregator aggregator) { var oldConstraintCount = oldConstraintList?.Constraints.Count ?? 0; var newConstraintCount = newConstraintList?.Constraints.Count ?? 0; if (oldConstraintCount == 0 && newConstraintCount == 0) { // There are no generic constraints defined on either type return; } if (oldConstraintCount == 0) { var suffix = string.Empty; if (newConstraintCount != 1) { // There is more than one modifier suffix = "s"; } var args = new FormatArguments( $"{{DefinitionType}} {{Identifier}} has added {newConstraintCount} generic type constraint{suffix}", match.NewItem.FullName, null, null); aggregator.AddElementChangedResult(SemVerChangeType.Breaking, match, options.MessageFormatter, args); // No need to look into the constraints themselves return; } if (newConstraintCount == 0) { var suffix = string.Empty; if (oldConstraintCount != 1) { // There is more than one modifier suffix = "s"; } var args = new FormatArguments( $"{{DefinitionType}} {{Identifier}} has removed {oldConstraintCount} generic type constraint{suffix}", match.NewItem.FullName, null, null); aggregator.AddElementChangedResult(SemVerChangeType.Feature, match, options.MessageFormatter, args); // No need to look into the constraints themselves return; } // Find the old constraints that have been removed var removedConstraints = oldConstraintList !.Constraints.Except(newConstraintList !.Constraints); foreach (var constraint in removedConstraints) { var args = new FormatArguments( "{DefinitionType} {Identifier} has removed the {OldValue} generic type constraint", match.NewItem.FullName, constraint, null); aggregator.AddElementChangedResult(SemVerChangeType.Feature, match, options.MessageFormatter, args); } // Find the new constraints that have been added var addedConstraints = newConstraintList !.Constraints.Except(oldConstraintList !.Constraints); foreach (var constraint in addedConstraints) { var args = new FormatArguments( "{DefinitionType} {Identifier} has added the {NewValue} generic type constraint", match.NewItem.FullName, null, constraint); aggregator.AddElementChangedResult(SemVerChangeType.Breaking, match, options.MessageFormatter, args); } }