/// <summary> /// Parses an editor config file text located at the given path. No parsing /// errors are reported. If any line contains a parse error, it is dropped. /// </summary> public static AnalyzerConfig Parse(SourceText text, string?pathToFile) { if (pathToFile is null || !Path.IsPathRooted(pathToFile) || string.IsNullOrEmpty(Path.GetFileName(pathToFile))) { throw new ArgumentException("Must be an absolute path to an editorconfig file", nameof(pathToFile)); } Section?globalSection = null; var namedSectionBuilder = ImmutableArray.CreateBuilder <Section>(); // N.B. The editorconfig documentation is quite loose on property interpretation. // Specifically, it says: // Currently all properties and values are case-insensitive. // They are lowercased when parsed. // To accommodate this, we use a lower case Unicode mapping when adding to the // dictionary, but we also use a case-insensitive key comparer when doing lookups var activeSectionProperties = ImmutableDictionary.CreateBuilder <string, string>( Section.PropertiesKeyComparer); string activeSectionName = ""; foreach (var textLine in text.Lines) { string line = textLine.ToString(); if (string.IsNullOrWhiteSpace(line)) { continue; } if (IsComment(line)) { continue; } var sectionMatches = s_sectionMatcher.Matches(line); if (sectionMatches.Count > 0 && sectionMatches[0].Groups.Count > 0) { addNewSection(); var sectionName = sectionMatches[0].Groups[1].Value; Debug.Assert(!string.IsNullOrEmpty(sectionName)); activeSectionName = sectionName; activeSectionProperties = ImmutableDictionary.CreateBuilder <string, string>( Section.PropertiesKeyComparer); continue; } var propMatches = s_propertyMatcher.Matches(line); if (propMatches.Count > 0 && propMatches[0].Groups.Count > 1) { var key = propMatches[0].Groups[1].Value; var value = propMatches[0].Groups[2].Value; Debug.Assert(!string.IsNullOrEmpty(key)); Debug.Assert(key == key.Trim()); Debug.Assert(value == value?.Trim()); key = CaseInsensitiveComparison.ToLower(key); if (ReservedKeys.Contains(key) || ReservedValues.Contains(value)) { value = CaseInsensitiveComparison.ToLower(value); } activeSectionProperties[key] = value ?? ""; continue; } } // Add the last section addNewSection(); return(new AnalyzerConfig(globalSection !, namedSectionBuilder.ToImmutable(), pathToFile)); void addNewSection() { // Close out the previous section var previousSection = new Section(activeSectionName, activeSectionProperties.ToImmutable()); if (activeSectionName == "") { // This is the global section globalSection = previousSection; } else { namedSectionBuilder.Add(previousSection); } } }
internal static bool Equals(string lhs, string rhs) { return(CaseInsensitiveComparison.Equals(lhs, rhs)); }