| File: RuleSet\RuleSetProcessor.cs | Web Access |
| Project: src\roslyn\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable disable using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.IO; using System.Xml; using System.Xml.Linq; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis { /// <summary> /// This type is responsible for parsing a ruleset xml file and producing a <see cref="RuleSet"/> object. /// </summary> internal class RuleSetProcessor { // Strings for the RuleSet node private const string RuleSetNodeName = "RuleSet"; private const string RuleSetNameAttributeName = "Name"; private const string RuleSetToolsVersionAttributeName = "ToolsVersion"; // Strings for the Rules node private const string RulesNodeName = "Rules"; private const string RulesAnalyzerIdAttributeName = "AnalyzerId"; private const string RulesNamespaceAttributeName = "RuleNamespace"; // Strings for the Rule node private const string RuleNodeName = "Rule"; private const string RuleIdAttributeName = "Id"; // Strings for the Include node private const string IncludeNodeName = "Include"; private const string IncludePathAttributeName = "Path"; // Strings for the IncludeAll node private const string IncludeAllNodeName = "IncludeAll"; // Strings for the Action attribute and its values private const string RuleActionAttributeName = "Action"; private const string RuleActionNoneValue = "None"; private const string RuleActionHiddenValue = "Hidden"; private const string RuleActionInfoValue = "Info"; private const string RuleActionWarningValue = "Warning"; private const string RuleActionErrorValue = "Error"; private const string RuleActionDefaultValue = "Default"; /// <summary> /// Creates and loads the rule set from a file /// </summary> /// <param name="filePath">The file path to load the rule set</param> #nullable enable public static RuleSet LoadFromFile(string filePath) { // First read the file without doing any validation filePath = FileUtilities.NormalizeAbsolutePath(filePath); XmlReaderSettings settings = GetDefaultXmlReaderSettings(); XDocument? ruleSetDocument = null; XElement? ruleSetNode = null; using (Stream stream = FileUtilities.OpenRead(filePath)) using (XmlReader xmlReader = XmlReader.Create(stream, settings)) { try { ruleSetDocument = XDocument.Load(xmlReader); } catch (Exception e) { throw new InvalidRuleSetException(e.Message); } // Find the top level rule set node List<XElement> nodeList = ruleSetDocument.Elements(RuleSetNodeName).ToList(); Debug.Assert(nodeList.Count == 1, "Multiple top-level nodes!"); Debug.Assert(nodeList[0].Name == RuleSetNodeName); ruleSetNode = nodeList[0]; } return ReadRuleSet(ruleSetNode, filePath); } /// <summary> /// Load the rule set from the XML node /// </summary> /// <param name="ruleSetNode">The rule set node from which to create a rule set object</param> /// <param name="filePath">The file path to the rule set file</param> /// <returns>A rule set object with data from the given XML node</returns> private static RuleSet ReadRuleSet(XElement ruleSetNode, string filePath) { var specificOptions = ImmutableDictionary.CreateBuilder<string, ReportDiagnostic>(); var generalOption = ReportDiagnostic.Default; var includes = ImmutableArray.CreateBuilder<RuleSetInclude>(); ValidateAttribute(ruleSetNode, RuleSetToolsVersionAttributeName); ValidateAttribute(ruleSetNode, RuleSetNameAttributeName); // Loop through each rules or include node in this rule set foreach (XElement childNode in ruleSetNode.Elements()) { if (childNode.Name == RulesNodeName) { var rules = ReadRules(childNode); foreach (var rule in rules) { var ruleId = rule.Key; var action = rule.Value; ReportDiagnostic existingAction; if (specificOptions.TryGetValue(ruleId, out existingAction)) { if (existingAction != action) { throw new InvalidRuleSetException(string.Format(CodeAnalysisResources.RuleSetHasDuplicateRules, ruleId, existingAction, action)); } } else { specificOptions.Add(ruleId, action); } } } else if (childNode.Name == IncludeNodeName) { includes.Add(ReadRuleSetInclude(childNode)); } else if (childNode.Name == IncludeAllNodeName) { generalOption = ReadIncludeAll(childNode); } } return new RuleSet(filePath, generalOption, specificOptions.ToImmutable(), includes.ToImmutable()); } #nullable disable /// <summary> /// Load the rules from the XML node /// </summary> /// <param name="rulesNode">The rules node from which to loop through each child rule node</param> /// <returns>A list of rule objects with data from the given XML node</returns> private static List<KeyValuePair<string, ReportDiagnostic>> ReadRules(XElement rulesNode) { _ = ReadNonEmptyAttribute(rulesNode, RulesAnalyzerIdAttributeName); _ = ReadNonEmptyAttribute(rulesNode, RulesNamespaceAttributeName); var rules = new List<KeyValuePair<string, ReportDiagnostic>>(); // Loop through each rule node foreach (XElement ruleNode in rulesNode.Elements()) { if (ruleNode.Name == RuleNodeName) { rules.Add(ReadRule(ruleNode)); } else { // Schema validation should prevent us from getting here Debug.Assert(false, "Unknown child node in Rules node"); } } return rules; } /// <summary> /// Load the rule from the XML node /// </summary> /// <param name="ruleNode">The rule node from which to create a rule object</param> /// <returns>A rule object with data from the given XML node</returns> private static KeyValuePair<string, ReportDiagnostic> ReadRule(XElement ruleNode) { string ruleId = ReadNonEmptyAttribute(ruleNode, RuleIdAttributeName); ReportDiagnostic action = ReadAction(ruleNode, allowDefault: false); return new KeyValuePair<string, ReportDiagnostic>(ruleId, action); } /// <summary> /// Load the included rule set from the XML node /// </summary> /// <param name="includeNode">The include node from which to create a RuleSetInclude object</param> /// <returns>A RuleSetInclude object with data from the given XML node</returns> private static RuleSetInclude ReadRuleSetInclude(XElement includeNode) { string includePath = ReadNonEmptyAttribute(includeNode, IncludePathAttributeName); ReportDiagnostic action = ReadAction(includeNode, allowDefault: true); return new RuleSetInclude(includePath, action); } /// <summary> /// Reads the action from the given node /// </summary> /// <param name="node">The node to read the action, it can be a rule node or an include node.</param> /// <param name="allowDefault">Whether or not the default value is allowed.</param> /// <returns>The rule action</returns> private static ReportDiagnostic ReadAction(XElement node, bool allowDefault) { string action = ReadNonEmptyAttribute(node, RuleActionAttributeName); if (string.Equals(action, RuleActionWarningValue)) { return ReportDiagnostic.Warn; } else if (string.Equals(action, RuleActionErrorValue)) { return ReportDiagnostic.Error; } else if (string.Equals(action, RuleActionInfoValue)) { return ReportDiagnostic.Info; } else if (string.Equals(action, RuleActionHiddenValue)) { return ReportDiagnostic.Hidden; } else if (string.Equals(action, RuleActionNoneValue)) { return ReportDiagnostic.Suppress; } else if (string.Equals(action, RuleActionDefaultValue) && allowDefault) { return ReportDiagnostic.Default; } throw new InvalidRuleSetException(string.Format(CodeAnalysisResources.RuleSetBadAttributeValue, RuleActionAttributeName, action)); } /// <summary> /// Load the IncludedAll from the XML node /// </summary> /// <param name="includeAllNode">The IncludeAll node from which to create a IncludeAll object</param> /// <returns>A IncludeAll object with data from the given XML node</returns> private static ReportDiagnostic ReadIncludeAll(XElement includeAllNode) { return ReadAction(includeAllNode, allowDefault: false); } /// <summary> /// Reads an attribute from a node and validates that it is not empty. /// </summary> /// <param name="node">The XML node that contains the attribute</param> /// <param name="attributeName">The name of the attribute to read</param> /// <returns>The attribute value</returns> private static string ReadNonEmptyAttribute(XElement node, string attributeName) { XAttribute attribute = node.Attribute(attributeName); if (attribute == null) { throw new InvalidRuleSetException(string.Format(CodeAnalysisResources.RuleSetMissingAttribute, node.Name, attributeName)); } if (string.IsNullOrEmpty(attribute.Value)) { throw new InvalidRuleSetException(string.Format(CodeAnalysisResources.RuleSetBadAttributeValue, attributeName, attribute.Value)); } return attribute.Value; } /// <summary> /// Gets the default settings to read the ruleset xml file. /// </summary> private static XmlReaderSettings GetDefaultXmlReaderSettings() { XmlReaderSettings xmlReaderSettings = new XmlReaderSettings(); xmlReaderSettings.CheckCharacters = true; xmlReaderSettings.CloseInput = true; xmlReaderSettings.ConformanceLevel = ConformanceLevel.Document; xmlReaderSettings.IgnoreComments = true; xmlReaderSettings.IgnoreProcessingInstructions = true; xmlReaderSettings.IgnoreWhitespace = true; xmlReaderSettings.DtdProcessing = DtdProcessing.Prohibit; return xmlReaderSettings; } private static void ValidateAttribute(XElement node, string attributeName) { ReadNonEmptyAttribute(node, attributeName); } } }