Example #1
0
        public void IdInComments()
        {
            BreakingChange bc = new BreakingChange
            {
                Id                   = "144",
                Title                = "Application.FilterMessage no longer throws for re-entrant implementations of IMessageFilter.PreFilterMessage",
                VersionBroken        = Version.Parse("4.6.1"),
                ImpactScope          = BreakingChangeImpact.Edge,
                SourceAnalyzerStatus = BreakingChangeAnalyzerStatus.Planned,
                Details              = "Prior to the .NET Framework 4.6.1, calling Application.FilterMessage with an IMessageFilter.PreFilterMessage which called AddMessageFilter or RemoveMessageFilter (while also calling Application.DoEvents) would cause an IndexOutOfRangeException."
                                       + "\n\n"
                                       + "Beginning with applications targeting the .NET Framework 4.6.1, this exception is no longer thrown, and re-entrant filters as described above may be used.",
                IsQuirked  = true,
                Suggestion = "Be aware that Application.FilterMessage will no longer throw for the re-entrant IMessageFilter.PreFilterMessage behavior described above. This only affects applications targeting the .NET Framework 4.6.1.",
                Categories = new List <string> {
                    "Windows Forms"
                },
                Link           = "https://msdn.microsoft.com/en-us/library/mt620031%28v=vs.110%29.aspx#WinForms",
                ApplicableApis = new List <string>
                {
                    "M:System.Windows.Forms.Application.FilterMessage(System.Windows.Forms.Message@)"
                },
                Notes = "It's unclear if this one will be better analyzed by Application.FilterMessage callers (who would have seen the exception previously)"
                        + "\n" + "or the IMessageFilter.PreFilterMessage implementers (who caused the exception previously). Unfortunately, the analyzer on the caller is probably"
                        + "\n" + "more useful, even though it would be easier to be 'precise' if we analyzed the interface implementer."
            };

            ValidateParse(GetBreakingChangeMarkdown("Application.FilterMessage.md"), bc);
        }
Example #2
0
        public static void EqualityTests()
        {
            var breakingChangeSame1 = new BreakingChange {
                Id = "id1"
            };
            var breakingChangeSame2 = new BreakingChange {
                Id = "id1"
            };
            var breakingChangeDifferent1 = new BreakingChange {
                Id = "ID1"
            };
            var breakingChangeDifferent2 = new BreakingChange {
                Id = "id2"
            };

            Assert.Equal(breakingChangeSame1.GetHashCode(), breakingChangeSame2.GetHashCode());
            Assert.NotEqual(breakingChangeSame1.GetHashCode(), breakingChangeDifferent1.GetHashCode());
            Assert.NotEqual(breakingChangeSame2.GetHashCode(), breakingChangeDifferent1.GetHashCode());
            Assert.NotEqual(breakingChangeSame1.GetHashCode(), breakingChangeDifferent2.GetHashCode());

            Assert.False(breakingChangeSame1.Equals(null));
            Assert.False(breakingChangeSame1.Equals(new BreakingChange()));
            Assert.False(breakingChangeSame1.Equals("other type"));
            Assert.True(breakingChangeSame1.Equals(breakingChangeSame1));
            Assert.True(breakingChangeSame1.Equals(breakingChangeSame2));
            Assert.False(breakingChangeSame1.Equals(breakingChangeDifferent1));
            Assert.False(breakingChangeSame2.Equals(breakingChangeDifferent1));
            Assert.False(breakingChangeSame1.Equals(breakingChangeDifferent2));
        }
        public void MissingApis()
        {
            BreakingChange bc = UriBC.DeepCopy();

            bc.ApplicableApis = null;
            ValidateParse(GetBreakingChangeMarkdown("MissingApis.md"), bc);
        }
Example #4
0
        public void BreakingChangeWithComments()
        {
            var expected = new BreakingChange
            {
                Title                = "ASP.NET Accessibility Improvements in .NET 4.7.3",
                ImpactScope          = BreakingChangeImpact.Minor,
                VersionBroken        = Version.Parse("4.7.3"),
                SourceAnalyzerStatus = BreakingChangeAnalyzerStatus.NotPlanned,
                IsQuirked            = true,
                IsBuildTime          = false,
                Details              = "Starting with the .NET Framework 4.7.1, ASP.NET has improved how ASP.NET Web Controls work with accessibility technology in Visual Studio to better support ASP.NET customers.",
                Suggestion           = @"In order for the Visual Studio Designer to benefit from these changes
- Install Visual Studio 2017 15.3 or later, which supports the new accessibility features with the following AppContext Switch by default.
```xml
<?xml version=""1.0"" encoding=""utf-8""?>
<configuration>
<runtime>
...
<!-- AppContextSwitchOverrides value attribute is in the form of 'key1=true|false;key2=true|false  -->
<AppContextSwitchOverrides value=""...;Switch.UseLegacyAccessibilityFeatures=false"" />
...
</runtime>
</configuration>
```".Replace(Environment.NewLine, "\n")
            };

            ValidateParse(GetBreakingChangeMarkdown("CommentsInRecommendedChanges.md"), expected);
        }
        private void TestEquality(BreakingChange expected, BreakingChange actual)
        {
            if (expected == null)
            {
                Assert.Null(actual);
                return;
            }
            else
            {
                Assert.NotNull(actual);
            }

            Assert.Equal(expected.Id, actual.Id, StringComparer.Ordinal);
            Assert.Equal(expected.Title, actual.Title, StringComparer.Ordinal);
            Assert.Equal(expected.Details, actual.Details, StringComparer.Ordinal);
            Assert.Equal(expected.Suggestion, actual.Suggestion, StringComparer.Ordinal);
            Assert.Equal(expected.Link, actual.Link, StringComparer.Ordinal);
            Assert.Equal(expected.BugLink, actual.BugLink, StringComparer.Ordinal);
            Assert.Equal(expected.Notes, actual.Notes, StringComparer.Ordinal);
            Assert.Equal(expected.Markdown, actual.Markdown, StringComparer.Ordinal);
            Assert.Equal(expected.Related, actual.Related, StringComparer.Ordinal);
            Assert.Equal(expected.ApplicableApis, actual.ApplicableApis, StringComparer.Ordinal);
            Assert.Equal(expected.VersionBroken, actual.VersionBroken);
            Assert.Equal(expected.VersionFixed, actual.VersionFixed);
            Assert.Equal(expected.IsBuildTime, actual.IsBuildTime);
            Assert.Equal(expected.IsQuirked, actual.IsQuirked);
            Assert.Equal(expected.SourceAnalyzerStatus, actual.SourceAnalyzerStatus);
            Assert.Equal(expected.ImpactScope, actual.ImpactScope);
        }
        public void MissingData()
        {
            BreakingChange bc = ListTBC.DeepCopy();

            bc.ImpactScope    = BreakingChangeImpact.Unknown;
            bc.VersionBroken  = null;
            bc.ApplicableApis = null;
            ValidateParse(GetBreakingChangeMarkdown("MissingData.md"), bc);
        }
        public void RandomText()
        {
            BreakingChange bc = new BreakingChange {
                Title = "Chapter 2. The Mail"
            };

            ValidateParse(GetBreakingChangeMarkdown("RandomText.md"), bc);

            ValidateParse(GetBreakingChangeMarkdown("RandomText2.md"), new BreakingChange[0]);
        }
        public void CorruptData()
        {
            BreakingChange bc = UriBC.DeepCopy();

            bc.VersionBroken  = null;
            bc.ImpactScope    = BreakingChangeImpact.Unknown;
            bc.IsQuirked      = false;
            bc.ApplicableApis = bc.ApplicableApis.Concat(new[] { "##" });
            bc.Suggestion     = "\\0\0\0\0\0" + bc.Suggestion + "\u0001\u0002";
            ValidateParse(GetBreakingChangeMarkdown("CorruptData.md"), bc);
        }
Example #9
0
        public void CategoryWithSpace()
        {
            var expected = new BreakingChange
            {
                Title                = "List<T>.ForEach",
                ImpactScope          = BreakingChangeImpact.Minor,
                VersionBroken        = Version.Parse("4.6.2"),
                SourceAnalyzerStatus = BreakingChangeAnalyzerStatus.Available
            };

            ValidateParse(GetBreakingChangeMarkdown("CategoryWithSpaces.md"), expected);
        }
        public void DuplicateSections()
        {
            BreakingChange bc = UriBC.DeepCopy();

            bc.VersionFixed   = new Version(1, 0);
            bc.Id             = ListTBC.Id;
            bc.Title          = ListTBC.Title;
            bc.Details        = ListTBC.Details + "\n\n\n" + UriBC.Details;
            bc.Suggestion     = ListTBC.Suggestion + "\n\n" + UriBC.Suggestion;
            bc.ApplicableApis = ListTBC.ApplicableApis.Concat(UriBC.ApplicableApis).ToList();
            ValidateParse(GetBreakingChangeMarkdown("DupSections.md"), bc);
        }
        public void PartialData()
        {
            BreakingChange bc = new BreakingChange
            {
                Id            = ListTBC.Id,
                Title         = ListTBC.Title,
                ImpactScope   = ListTBC.ImpactScope,
                VersionBroken = ListTBC.VersionBroken,
                Details       = ListTBC.Details
            };

            ValidateParse(GetBreakingChangeMarkdown("PartialData.md"), bc);
        }
        public void PartialData()
        {
            BreakingChange bc = new BreakingChange
            {
                Id = ListTBC.Id,
                Title = ListTBC.Title,
                ImpactScope = ListTBC.ImpactScope,
                VersionBroken = ListTBC.VersionBroken,
                Details = ListTBC.Details
            };

            ValidateParse(GetBreakingChangeMarkdown("PartialData.md"), bc);
        }
Example #13
0
        private static IApiRecommendations GenerateTestRecommendationsWithoutFixedEntry()
        {
            var recommendations = Substitute.For <IApiRecommendations>();

            var breakingChange1 = new BreakingChange
            {
                ApplicableApis = new[] { TestDocId1 },
                Id             = "5",
                VersionBroken  = Version.Parse("4.5")
            };

            recommendations.GetBreakingChanges(TestDocId1).Returns(new[] { breakingChange1 });

            return(recommendations);
        }
Example #14
0
        /// <summary>
        /// This method generates breaking changes recomendations for testing the ShowRetargettingIssues
        /// </summary>
        /// <param name="numOfRetargettingIssues">Number of retargetting issues to put in the recommendations result</param>
        /// <param name="numOfRuntimeIssues">Number of runtime issues to put in the recommendations result</param>
        /// <returns>API recommendations result containing the number of issues to be included</returns>
        private static IApiRecommendations GenerateTestRecommendationsForShowRetargetting(int numOfRetargettingIssues = 1,
                                                                                          int numOfRuntimeIssues      = 1)
        {
            int lastIDUsed      = 1;
            var recommendations = Substitute.For <IApiRecommendations>();
            List <BreakingChange> breakingChanges = new List <BreakingChange>();

            //add requested number of retargetting issues
            for (int i = 0; i < numOfRetargettingIssues; i++)
            {
                //add a new breaking change
                BreakingChange bc = new BreakingChange
                {
                    ApplicableApis = new[] { TestDocId1 },
                    Id             = lastIDUsed.ToString(),
                    VersionBroken  = Version.Parse("4.5"),
                    IsQuirked      = true
                };

                breakingChanges.Add(bc);

                lastIDUsed++;
            }

            //add requested number of runtime issues
            for (int i = 0; i < numOfRuntimeIssues; i++)
            {
                //add a new breaking change
                BreakingChange bc = new BreakingChange
                {
                    ApplicableApis = new[] { TestDocId1 },
                    Id             = lastIDUsed.ToString(),
                    VersionBroken  = Version.Parse("4.5"),
                    IsQuirked      = false,
                    IsBuildTime    = false
                };

                breakingChanges.Add(bc);
                lastIDUsed++;
            }

            recommendations.GetBreakingChanges(TestDocId1).Returns(breakingChanges.ToArray());

            return(recommendations);
        }
        public void Compare_Detects_MethodParametersBeingAdded_as_removal()
        {
            // Arrange
            var v1ApiListing = CreateApiListingDocument(V1Assembly);
            var v2ApiListing = CreateApiListingDocument(V2Assembly);
            var comparer     = new ApiListingComparer(v1ApiListing, v2ApiListing);

            // Act
            var breakingChanges = comparer.GetDifferences();

            // Assert
            var expected = new BreakingChange(
                "public class ComparisonScenarios.ClassWithMethods",
                "public System.Void MethodToAddParameters()",
                kind: ChangeKind.Removal);

            Assert.Contains(expected, breakingChanges);
        }
        public void Compare_Detects_GenericTypeConstraintsBeingAdded_as_removal()
        {
            // Arrange
            var v1ApiListing = CreateApiListingDocument(V1Assembly);
            var v2ApiListing = CreateApiListingDocument(V2Assembly);
            var comparer     = new ApiListingComparer(v1ApiListing, v2ApiListing);

            // Act
            var breakingChanges = comparer.GetDifferences();

            // Assert
            var expected = new BreakingChange(
                "public class ComparisonScenarios.GenericTypeWithConstraintsToBeAdded<T0>",
                memberId: null,
                kind: ChangeKind.Removal);

            Assert.Contains(expected, breakingChanges);
        }
        public void Compare_Detects_ChangesInTypeVisibility_as_removal()
        {
            // Arrange
            var v1ApiListing = CreateApiListingDocument(V1Assembly);
            var v2ApiListing = CreateApiListingDocument(V2Assembly);
            var comparer     = new ApiListingComparer(v1ApiListing, v2ApiListing);

            // Act
            var breakingChanges = comparer.GetDifferences();

            // Assert
            var expected = new BreakingChange(
                "public class ComparisonScenarios.PublicToInternalClass",
                memberId: null,
                kind: ChangeKind.Removal);

            Assert.Contains(expected, breakingChanges);
        }
        public void Compare_Detects_TypeRenames_as_removal()
        {
            // Arrange
            var v1ApiListing = CreateApiListingDocument(V1Assembly);
            var v2ApiListing = CreateApiListingDocument(V2Assembly);
            var comparer     = new ApiListingComparer(v1ApiListing, v2ApiListing);

            // Act
            var breakingChanges = comparer.GetDifferences();

            // Assert
            var expected = new BreakingChange(
                "public interface ComparisonScenarios.TypeToRename",
                memberId: null,
                kind: ChangeKind.Removal);

            Assert.Contains(expected, breakingChanges);
        }
        public void Compare_Detects_ClassBeingUnnested_as_removal()
        {
            // Arrange
            var v1ApiListing = CreateApiListingDocument(V1Assembly);
            var v2ApiListing = CreateApiListingDocument(V2Assembly);
            var comparer     = new ApiListingComparer(v1ApiListing, v2ApiListing);

            // Act
            var breakingChanges = comparer.GetDifferences();

            // Assert
            var expected = new BreakingChange(
                "public class ComparisonScenarios.ClassToUnnestContainer+ClassToUnnest",
                memberId: null,
                kind: ChangeKind.Removal);

            Assert.Contains(expected, breakingChanges);
        }
        public void EqualityTests()
        {
            var breakingChangeSame1 = new BreakingChange { Id = "id1" };
            var breakingChangeSame2 = new BreakingChange { Id = "id1" };
            var breakingChangeDifferent1 = new BreakingChange { Id = "ID1" };
            var breakingChangeDifferent2 = new BreakingChange { Id = "id2" };

            Assert.Equal(breakingChangeSame1.GetHashCode(), breakingChangeSame2.GetHashCode());
            Assert.NotEqual(breakingChangeSame1.GetHashCode(), breakingChangeDifferent1.GetHashCode());
            Assert.NotEqual(breakingChangeSame2.GetHashCode(), breakingChangeDifferent1.GetHashCode());
            Assert.NotEqual(breakingChangeSame1.GetHashCode(), breakingChangeDifferent2.GetHashCode());

            Assert.False(breakingChangeSame1.Equals(null));
            Assert.False(breakingChangeSame1.Equals(new BreakingChange()));
            Assert.False(breakingChangeSame1.Equals("other type"));
            Assert.True(breakingChangeSame1.Equals(breakingChangeSame1));
            Assert.True(breakingChangeSame1.Equals(breakingChangeSame2));
            Assert.False(breakingChangeSame1.Equals(breakingChangeDifferent1));
            Assert.False(breakingChangeSame2.Equals(breakingChangeDifferent1));
            Assert.False(breakingChangeSame1.Equals(breakingChangeDifferent2));
        }
        public void Compare_DetectsChangesInForwardedType()
        {
            // Arrange
            var v1ApiListing  = CreateApiListingDocument(V1Assembly);
            var v2ApiListing  = CreateApiListingDocument(V2Assembly);
            var comparer      = new ApiListingComparer(v1ApiListing, v2ApiListing);
            var typeToCheck   = "public class ComparisonScenarios.TypeToBeForwardedAndChanged";
            var getterRemoval = new BreakingChange(
                typeToCheck,
                "public System.String get_PropertyToBeRemoved()",
                ChangeKind.Removal);
            var setterRemoval = new BreakingChange(
                typeToCheck,
                "public System.Void set_PropertyToBeRemoved(System.String value)",
                ChangeKind.Removal);

            // Act
            var breakingChanges = comparer.GetDifferences();

            // Assert
            Assert.Equal(2, breakingChanges.Count(bc => bc.TypeId == typeToCheck));
            Assert.Contains(getterRemoval, breakingChanges);
            Assert.Contains(setterRemoval, breakingChanges);
        }
        private static void CleanAndAddBreak(List<BreakingChange> breakingChanges, BreakingChange currentBreak)
        {
            // Clean up trailing white-space, etc. from long-form text entries
            if (currentBreak.Details != null) currentBreak.Details = currentBreak.Details.Trim();
            if (currentBreak.Suggestion != null) currentBreak.Suggestion = currentBreak.Suggestion.Trim();
            if (currentBreak.Notes != null) currentBreak.Notes = currentBreak.Notes.Trim();

            breakingChanges.Add(currentBreak);
        }
 private static void ParseNonStateChange(BreakingChange currentBreak, ParseState state, string currentLine)
 {
     switch (state)
     {
         case ParseState.None:
             return;
         case ParseState.OriginalBug:
             currentBreak.BugLink = currentLine.Trim();
             break;
         case ParseState.Scope:
             BreakingChangeImpact scope;
             if (Enum.TryParse<BreakingChangeImpact>(currentLine.Trim(), out scope))
             {
                 currentBreak.ImpactScope = scope;
             }
             break;
         case ParseState.VersionBroken:
             Version verBroken;
             if (Version.TryParse(currentLine.Trim(), out verBroken))
             {
                 currentBreak.VersionBroken = verBroken;
             }
             break;
         case ParseState.VersionFixed:
             Version verFixed;
             if (Version.TryParse(currentLine.Trim(), out verFixed))
             {
                 currentBreak.VersionFixed = verFixed;
             }
             break;
         case ParseState.AffectedAPIs:
             // Trim md list markers, as well as comment tags (in case the affected APIs section is followed by a comment)
             string api = currentLine.Trim().TrimStart('*', '-', ' ', '\t', '<', '!', '-');
             if (string.IsNullOrWhiteSpace(api)) return;
             if (currentBreak.ApplicableApis == null)
             {
                 currentBreak.ApplicableApis = new List<string>();
             }
             ((List<string>)currentBreak.ApplicableApis).Add(api);
             break;
         case ParseState.Details:
             if (currentBreak.Details == null)
             {
                 currentBreak.Details = currentLine;
             }
             else
             {
                 currentBreak.Details += ("\n" + currentLine);
             }
             break;
         case ParseState.Suggestion:
             if (currentBreak.Suggestion == null)
             {
                 currentBreak.Suggestion = currentLine;
             }
             else
             {
                 currentBreak.Suggestion += ("\n" + currentLine);
             }
             break;
         case ParseState.Notes:
             // Special-case the fact that 'notes' will often come at the end of a comment section and we don't need the closing --> in the note.
             if (currentLine.Trim().Equals("-->")) return;
             if (currentBreak.Notes == null)
             {
                 currentBreak.Notes = currentLine;
             }
             else
             {
                 currentBreak.Notes += ("\n" + currentLine);
             }
             break;
         default:
             throw new InvalidOperationException("Unhandled breaking change parse state: " + state.ToString());
     }
 }
        public void RandomText()
        {
            BreakingChange bc = new BreakingChange { Title = "Chapter 2. The Mail" };
            ValidateParse(GetBreakingChangeMarkdown("RandomText.md"), bc);

            ValidateParse(GetBreakingChangeMarkdown("RandomText2.md"), new BreakingChange[0]);
        }
        private void TestEquality(BreakingChange expected, BreakingChange actual)
        {
            if (expected == null)
            {
                Assert.Null(actual);
                return;
            }
            else
            {
                Assert.NotNull(actual);
            }

            Assert.Equal(expected.Id, actual.Id, StringComparer.Ordinal);
            Assert.Equal(expected.Title, actual.Title, StringComparer.Ordinal);
            Assert.Equal(expected.Details, actual.Details, StringComparer.Ordinal);
            Assert.Equal(expected.Suggestion, actual.Suggestion, StringComparer.Ordinal);
            Assert.Equal(expected.Link, actual.Link, StringComparer.Ordinal);
            Assert.Equal(expected.BugLink, actual.BugLink, StringComparer.Ordinal);
            Assert.Equal(expected.Notes, actual.Notes, StringComparer.Ordinal);
            Assert.Equal(expected.Markdown, actual.Markdown, StringComparer.Ordinal);
            Assert.Equal(expected.Related, actual.Related, StringComparer.Ordinal);
            Assert.Equal(expected.ApplicableApis, actual.ApplicableApis, StringComparer.Ordinal);
            Assert.Equal(expected.VersionBroken, actual.VersionBroken);
            Assert.Equal(expected.VersionFixed, actual.VersionFixed);
            Assert.Equal(expected.IsBuildTime, actual.IsBuildTime);
            Assert.Equal(expected.IsQuirked, actual.IsQuirked);
            Assert.Equal(expected.IsSourceAnalyzerAvailable, actual.IsSourceAnalyzerAvailable);
            Assert.Equal(expected.ImpactScope, actual.ImpactScope);
        }
        static void Main(string[] args)
        {
            if (args == null || args.Length == 0)
            {
                Console.WriteLine("Bad argument.");
                Console.WriteLine("You need to specify location of breaking change documents.");
            }

            string bcpath = args[0];

            var          bcList            = new Dictionary <string, List <BreakingChange> >();
            var          template          = "README-template.md";
            string       templateText      = null;
            string       bcpathREADME      = Path.Combine(bcpath, "README.md");
            var          bcdir             = new DirectoryInfo(bcpath);
            const string versionIntroduced = "### Version Introduced";


            foreach (var changeFile in bcdir.GetFiles("*.md"))
            {
                if (changeFile.Name == "! Template.md" || changeFile.Name == "README.md" || changeFile.Name == "!categories.md")
                {
                    continue;
                }

                var change = new BreakingChange();

                using (var reader = changeFile.OpenText())
                {
                    var titleLine = reader.ReadLine();
                    var title     = titleLine.Substring(3);
                    change.Title = title;
                    change.Path  = changeFile.Name;

                    string versionLine = null;
                    while ((versionLine = reader.ReadLine()) != null)
                    {
                        if (versionLine != versionIntroduced)
                        {
                            continue;
                        }

                        var version = reader.ReadLine();
                        change.Version = version;
                        break;
                    }

                    if (!bcList.ContainsKey(change.Version))
                    {
                        bcList.Add(change.Version, new List <BreakingChange>());
                    }

                    var versionChanges = bcList[change.Version];
                    versionChanges.Add(change);
                }
            }

            using (var templateReader = File.OpenText(template))
            {
                templateText = templateReader.ReadToEnd();
            }

            var keysArrayLength = bcList.Keys.Count;
            var keysArray       = new string[keysArrayLength];

            bcList.Keys.CopyTo(keysArray, 0);

            Array.Sort(keysArray);
            Array.Reverse(keysArray);

            using (var writer = File.CreateText(bcpathREADME))
            {
                writer.Write(templateText);
                writer.WriteLine();

                foreach (var ver in keysArray)
                {
                    var hashVersion = new StringBuilder();
                    foreach (var c in ver)
                    {
                        if (c != '.')
                        {
                            hashVersion.Append(c);
                        }
                    }

                    var hashLink = $"net-framework-{hashVersion.ToString()}";
                    writer.WriteLine($"- [.NET Framework {ver}](#{hashLink})");
                }

                foreach (var ver in keysArray)
                {
                    writer.WriteLine();
                    writer.WriteLine($"## .NET Framework {ver}");
                    writer.WriteLine();

                    var breaks = bcList[ver];

                    breaks.Sort((break1, break2) => break1.Title.CompareTo(break2.Title));

                    foreach (var b in breaks)
                    {
                        writer.WriteLine($"- [{b.Title}]({b.Path})");
                    }
                }

                writer.WriteLine();
                writer.WriteLine("This file was generated by [Breaking Change Readme Generator](https://github.com/Microsoft/dotnet/blob/master/src/bc-readme-gen).");
            }
        }
        /// <summary>
        /// This method generates breaking changes recomendations for testing the ShowRetargettingIssues
        /// </summary>
        /// <param name="numOfRetargettingIssues">Number of retargetting issues to put in the recommendations result</param>
        /// <param name="numOfRuntimeIssues">Number of runtime issues to put in the recommendations result</param>
        /// <returns>API recommendations result containing the number of issues to be included</returns>
        private static IApiRecommendations GenerateTestRecommendationsForShowRetargetting(int numOfRetargettingIssues = 1,
                                                                                          int numOfRuntimeIssues = 1)
        {
            int lastIDUsed = 1;
            var recommendations = Substitute.For<IApiRecommendations>();
            List<BreakingChange> breakingChanges = new List<BreakingChange>();

            //add requested number of retargetting issues
            for (int i = 0; i < numOfRetargettingIssues; i++)
            {
                //add a new breaking change
                BreakingChange bc = new BreakingChange
                {
                    ApplicableApis = new[] { TestDocId1 },
                    Id = lastIDUsed.ToString(),
                    VersionBroken = Version.Parse("4.5"),
                    IsQuirked = true
                };

                breakingChanges.Add(bc);

                lastIDUsed++;
            }

            //add requested number of runtime issues
            for (int i = 0; i < numOfRuntimeIssues; i++)
            {
                //add a new breaking change
                BreakingChange bc = new BreakingChange
                {
                    ApplicableApis = new[] { TestDocId1 },
                    Id = lastIDUsed.ToString(),
                    VersionBroken = Version.Parse("4.5"),
                    IsQuirked = false,
                    IsBuildTime = false
                };

                breakingChanges.Add(bc);
                lastIDUsed++;
            }

            recommendations.GetBreakingChanges(TestDocId1).Returns(breakingChanges.ToArray());

            return recommendations;
        }
        private static IApiRecommendations GenerateTestRecommendationsWithoutFixedEntry()
        {
            var recommendations = Substitute.For<IApiRecommendations>();

            var breakingChange1 = new BreakingChange
            {
                ApplicableApis = new[] { TestDocId1 },
                Id = "5",
                VersionBroken = Version.Parse("4.5")
            };

            recommendations.GetBreakingChanges(TestDocId1).Returns(new[] { breakingChange1 });

            return recommendations;
        }
 private static void ParseNonStateChange(BreakingChange currentBreak, ParseState state, string currentLine, IEnumerable<string> allowedCategories)
 {
     switch (state)
     {
         case ParseState.None:
             return;
         case ParseState.OriginalBug:
             currentBreak.BugLink = currentLine.Trim();
             break;
         case ParseState.Scope:
             BreakingChangeImpact scope;
             if (Enum.TryParse<BreakingChangeImpact>(currentLine.Trim(), out scope))
             {
                 currentBreak.ImpactScope = scope;
             }
             break;
         case ParseState.VersionBroken:
             Version verBroken;
             if (Version.TryParse(currentLine.Trim(), out verBroken))
             {
                 currentBreak.VersionBroken = verBroken;
             }
             break;
         case ParseState.VersionFixed:
             Version verFixed;
             if (Version.TryParse(currentLine.Trim(), out verFixed))
             {
                 currentBreak.VersionFixed = verFixed;
             }
             break;
         case ParseState.AffectedAPIs:
             // Trim md list and code markers, as well as comment tags (in case the affected APIs section is followed by a comment)
             string api = currentLine.Trim().TrimStart('*', '-', '`', ' ', '\t', '<', '!', '-').TrimEnd('`');
             if (string.IsNullOrWhiteSpace(api)) return;
             if (currentBreak.ApplicableApis == null)
             {
                 currentBreak.ApplicableApis = new List<string>();
             }
             currentBreak.ApplicableApis.Add(api);
             break;
         case ParseState.Details:
             if (currentBreak.Details == null)
             {
                 currentBreak.Details = currentLine;
             }
             else
             {
                 currentBreak.Details += ("\n" + currentLine);
             }
             break;
         case ParseState.Suggestion:
             if (currentBreak.Suggestion == null)
             {
                 currentBreak.Suggestion = currentLine;
             }
             else
             {
                 currentBreak.Suggestion += ("\n" + currentLine);
             }
             break;
         case ParseState.Notes:
             // Special-case the fact that 'notes' will often come at the end of a comment section and we don't need the closing --> in the note.
             if (currentLine.Trim().Equals("-->")) return;
             if (currentBreak.Notes == null)
             {
                 currentBreak.Notes = currentLine;
             }
             else
             {
                 currentBreak.Notes += ("\n" + currentLine);
             }
             break;
         case ParseState.SourceAnalyzerStatus:
             BreakingChangeAnalyzerStatus status;
             if (Enum.TryParse<BreakingChangeAnalyzerStatus>(currentLine.Trim().Replace(" ", ""), true, out status))
             {
                 currentBreak.SourceAnalyzerStatus = status;
             }
             break;
         case ParseState.Categories:
             // Trim md list and code markers, as well as comment tags (in case the categories section is followed by a comment)
             var category = currentLine.Trim().TrimStart('*', '-', '!', '<', '>');
             if (string.IsNullOrWhiteSpace(category)) break;
             if (currentBreak.Categories == null)
             {
                 currentBreak.Categories = new List<string>();
             }
             // If a list of allowed categories was provided, make sure that the category found is on the list
             if (!allowedCategories?.Contains(category, StringComparer.OrdinalIgnoreCase) ?? false)
             {
                 throw new InvalidOperationException($"Invalid category detected: {category}");
             }
             currentBreak.Categories.Add(category);
             break;
         default:
             throw new InvalidOperationException("Unhandled breaking change parse state: " + state.ToString());
     }
 }
Example #30
0
        private static bool BreakingChangeIsInVersionRange(IEnumerable <Version> targetVersions, BreakingChange breakingChange)
        {
            foreach (var targetVersion in targetVersions)
            {
                // Include breaking changes that were broken before a target version and fixed after it,
                // and also breaking changes that were introduced in a targeted version, _even if they were fixed in that same version_
                //
                // Some breaking changes have VersionBroken==VersionFixed if the break was corrected in GDR-level servicing. We want to report those to
                // users who are targeting that version so that they understand the importance of updating their NetFX via WU (or whatever
                // enterprise-specific patch rollout system they have).
                if (targetVersion == breakingChange.VersionBroken ||
                    (targetVersion > breakingChange.VersionBroken && (breakingChange.VersionFixed == null || targetVersion < breakingChange.VersionFixed)))
                {
                    return(true);
                }
            }

            return(false);
        }
        /// <summary>
        /// Parses markdown files into BrekaingChange objects
        /// </summary>
        /// <param name="stream">The markdown to parse</param>
        /// <param name="allowedCategories">Valid category strings. Pass null to allow any category. A breaking change using an invalid category will throw an exception while parsing the breaking change.</param>
        /// <returns>BreakingChanges parsed from the markdown</returns>
        public static IEnumerable<BreakingChange> FromMarkdown(Stream stream, IEnumerable<string> allowedCategories)
        {
            var breakingChanges = new List<BreakingChange>();
            var state = ParseState.None;

            using (var sr = new StreamReader(stream))
            {
                BreakingChange currentBreak = null;
                string currentLine;

                while (null != (currentLine = sr.ReadLine()))
                {
                    currentLine = currentLine.Trim();

                    // New breaking change
                    if (currentLine.StartsWith("## ", StringComparison.Ordinal))
                    {
                        // Save previous breaking change and reset currentBreak
                        if (currentBreak != null)
                        {
                            CleanAndAddBreak(breakingChanges, currentBreak);
                        }
                        currentBreak = new BreakingChange();

                        // Separate ID and title
                        var splitTitle = currentLine.Substring("## ".Length).Split(new[] { ':' }, 2);
                        if (splitTitle.Length == 1)
                        {
                            // Breaking changes are keyed on title, not ID, so if ':' is missing, just take the line as a title.
                            // Note that this will make it impossible to suppress the breaking change, though.
                            currentBreak.Title = splitTitle[0].Trim();
                        }
                        else if (splitTitle.Length == 2)
                        {
                            currentBreak.Id = splitTitle[0].Trim();
                            currentBreak.Title = splitTitle[1].Trim();
                        }

                        // Clear state
                        state = ParseState.None;
                    }
                    else if (currentBreak != null) // Only parse breaking change if we've seen a breaking change header ("## ...")
                    {
                        // State changes
                        if (currentLine.StartsWith("###", StringComparison.Ordinal))
                        {
                            switch (currentLine.Substring("###".Length).Trim().ToLowerInvariant())
                            {
                                case "scope":
                                    state = ParseState.Scope;
                                    break;
                                case "version introduced":
                                case "version broken":
                                    state = ParseState.VersionBroken;
                                    break;
                                case "version reverted":
                                case "version fixed":
                                    state = ParseState.VersionFixed;
                                    break;
                                case "change description":
                                case "details":
                                    state = ParseState.Details;
                                    break;
                                case "recommended action":
                                case "suggestion":
                                    state = ParseState.Suggestion;
                                    break;
                                case "affected apis":
                                case "applicableapis":
                                    state = ParseState.AffectedAPIs;
                                    break;
                                case "original bug":
                                case "buglink":
                                case "bug":
                                    state = ParseState.OriginalBug;
                                    break;
                                case "notes":
                                    state = ParseState.Notes;
                                    break;
                                case "source analyzer status":
                                    state = ParseState.SourceAnalyzerStatus;
                                    break;
                                case "category":
                                case "categories":
                                    state = ParseState.Categories;
                                    break;
                                default:
                                    ParseNonStateChange(currentBreak, state, currentLine, allowedCategories);
                                    break;
                            }
                        }

                        // Bool properties
                        else if (currentLine.StartsWith("- [ ]", StringComparison.Ordinal) ||
                                 currentLine.StartsWith("- [x]", StringComparison.OrdinalIgnoreCase))
                        {
                            bool isChecked = currentLine.StartsWith("- [x]", StringComparison.OrdinalIgnoreCase);
                            switch (currentLine.Substring("- [x]".Length).Trim().ToLowerInvariant())
                            {
                                case "quirked":
                                case "isquirked":
                                    currentBreak.IsQuirked = isChecked;
                                    state = ParseState.None;
                                    break;
                                case "build-time break":
                                case "isbuildtime":
                                    currentBreak.IsBuildTime = isChecked;
                                    state = ParseState.None;
                                    break;
                                default:
                                    ParseNonStateChange(currentBreak, state, currentLine, allowedCategories);
                                    break;
                            }
                        }

                        // More info link
                        else if (currentLine.StartsWith("[More information]", StringComparison.OrdinalIgnoreCase))
                        {
                            currentBreak.Link = currentLine.Substring("[More information]".Length)
                                .Trim(' ', '(', ')', '[', ']', '\t', '\n', '\r')      // Remove markdown link enclosures
                                .Replace("\\(", "(").Replace("\\)", ")");             // Unescape parens in link
                            state = ParseState.None;
                        }

                        // Otherwise, process according to our current state
                        else
                        {
                            ParseNonStateChange(currentBreak, state, currentLine, allowedCategories);
                        }
                    }
                }

                // Add the final break from the file
                if (currentBreak != null)
                {
                    CleanAndAddBreak(breakingChanges, currentBreak);
                }
            }

            return breakingChanges;
        }