public static IEnumerable FromMarkdown()

in src/lib/Microsoft.Fx.Portability/BreakingChangeParser.cs [60:235]


        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 ((currentLine = sr.ReadLine()) != null)
                {
                    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 substring = currentLine.Substring("## ".Length).Trim();
                        var splitTitle = substring.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)
                        {
                            if (int.TryParse(splitTitle[0], out var id))
                            {
                                currentBreak.Id = id.ToString(CultureInfo.InvariantCulture);
                                currentBreak.Title = splitTitle[1].Trim();
                            }
                            else
                            {
                                currentBreak.Title = substring;
                            }
                        }

                        // Clear state
                        state = ParseState.None;
                    }

                    // Only parse breaking change if we've seen a breaking change header ("## ...")
                    else if (currentBreak != null)
                    {
                        // 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;
                        }

                        // Comments.
                        else if (currentLine.StartsWith("<!--", StringComparison.Ordinal))
                        {
                            if (state == ParseState.Suggestion)
                            {
                                // suggestions may contain comments; these are part of the suggestion
                                ParseNonStateChange(currentBreak, state, currentLine, allowedCategories);
                            }
                            else if (currentLine.EndsWith("-->", StringComparison.Ordinal))
                            {
                                // change IDs are in single-line comments, e.g. <!-- breaking change id: 144 -->
                                string id = currentLine.Split()
                                    .FirstOrDefault(token => int.TryParse(token, out _));
                                currentBreak.Id = currentBreak.Id ?? id;
                                state = ParseState.None;
                            }
                            else
                            {
                                // this line begins a multi-line comment not part of a suggestion
                                state = ParseState.Comment;
                            }
                        }

                        // 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;
        }