【问题标题】:How to combine multiple custom StyleCop rules under a single "Custom Rules" node in StyleCop settings and concurrency如何在 StyleCop 设置和并发中的单个“自定义规则”节点下组合多个自定义 StyleCop 规则
【发布时间】:2025-09-29 17:05:01
【问题描述】:

基于几篇优秀的文章,我已经能够成功地创建一些自定义 StyleCop 规则。作为参考,这里列出了一些我发现对这个主题非常有用的文章:

我正在使用 Visual Studio 2010 Ultimate 版本和 StyleCop 版本 4.4.0.14。

创建自定义 StyleCop 规则需要创建一个类文件以及相应的 XML 文件,该文件用于将规则添加到 StyleCop 设置中。当我这样做时,我的所有自定义规则都会正确执行。但是,我不喜欢的是,在 StyleCop 设置树中,您最终会获得多个“自定义规则”节点,每个 XML 文件都有一个。

跳过不同规则的实现细节,这就是我所做的。让我们将以下两个简单的自定义规则类与它们对应的 XML 文件一起使用:

文件:CustomRule1.cs

namespace StyleCop.CustomRules
{
    [SourceAnalyzer(typeof(CsParser))]
    public class CustomRule1 : SourceAnalyzer
    {
        public override void AnalyzeDocument(CodeDocument document)
        {
            Param.RequireNotNull(document, "document");
            CsDocument csDocument = document as CsDocument;
            if ((csDocument.RootElement != null) && !csDocument.RootElement.Generated)
            {
                // Do something...
            }
        }
    }
}

文件:CustomRule2.cs

namespace StyleCop.CustomRules
{
    [SourceAnalyzer(typeof(CsParser))]
    public class CustomRule2 : SourceAnalyzer
    {
        public override void AnalyzeDocument(CodeDocument document)
        {
            Param.RequireNotNull(document, "document");
            CsDocument csDocument = document as CsDocument;
            if ((csDocument.RootElement != null) && !csDocument.RootElement.Generated)
            {
                // Do something...
            }
        }
    }
}

文件:CustomRule1.xml

<?xml version="1.0" encoding="utf-8" ?>
<SourceAnalyzer Name="Custom Rules">
  <Description>
    These custom rules provide extensions to the ones provided with StyleCop.
  </Description>
  <Rules>
    <Rule Name="CustomRule1" CheckId="CR1001">
      <Context>Test rule 1.</Context>
      <Description>Test rule 1.</Description>
    </Rule>
  </Rules>
</SourceAnalyzer>

文件:CustomRule2.xml

<?xml version="1.0" encoding="utf-8" ?>
<SourceAnalyzer Name="Custom Rules">
  <Description>
    These custom rules provide extensions to the ones provided with StyleCop.
  </Description>
  <Rules>
    <Rule Name="CustomRule2" CheckId="CR1002">
      <Context>Test rule 2.</Context>
      <Description>Test rule 2.</Description>
    </Rule>
  </Rules>
</SourceAnalyzer>

通过以上内容,我的所有(两个)规则都得到了正确执行。 StyleCop 设置树中出现以下内容(方括号表示复选框):

[] C#
    [] {} Custom Rules
        [] {} CR1001: CustomRule1
    [] {} Custom Rules
        [] {} CR1002: CustomRule2
    [] {} Documentation Rules
    [] {} Layout Rules
    etc.

我想要的是将我的自定义规则放在 StyleCop 设置文件中名为“自定义规则”的一个节点下,如下所示:

[] C#
    [] {} Custom Rules
        [] {} CR1001: CustomRule1
        [] {} CR1002: CustomRule2
    [] {} Documentation Rules
    [] {} Layout Rules
    etc.

通过将两个 XML 文件合并为一个,我能够在 StyleCop 设置中将规则合并为一个“自定义规则”节点,如下所示:

文件:CustomRule1.xml

<?xml version="1.0" encoding="utf-8" ?>
<SourceAnalyzer Name="Custom Rules">
  <Description>
    These custom rules provide extensions to the ones provided with StyleCop.
  </Description>
  <Rules>
    <Rule Name="CustomRule1" CheckId="CR1001">
      <Context>Test rule 1.</Context>
      <Description>Test rule 1.</Description>
    </Rule>
    <Rule Name="CustomRule2" CheckId="CR1002">
      <Context>Test rule 2.</Context>
      <Description>Test rule 2.</Description>
    </Rule>
  </Rules>
</SourceAnalyzer>

但是,一旦我这样做了,只有一个自定义规则被执行,那就是 CustomRule1,类(文件)名称与 XML 文件名匹配的规则。

我尝试在 CustomRule2 上设置属性来指示 XML 文件,如下所示:

namespace StyleCop.CustomRules
{
    [SourceAnalyzer(typeof(CsParser), "CustomRule1.xml")]
    public class CustomRule2 : SourceAnalyzer
    {
        public override void AnalyzeDocument(CodeDocument document)
        {
            Param.RequireNotNull(document, "document");
            CsDocument csDocument = document as CsDocument;
            if ((csDocument.RootElement != null) && !csDocument.RootElement.Generated)
            {
                // Do nothing.
            }
        }
    }
}

将如上所示的属性设置为 XML 文件也不能解决此问题。这两个规则都出现在 StyleCop 设置中,但只有 CustomRule1 被执行。

我该如何解决这个问题?

更新:

根据接受的答案,我采取了在单个分析器中检查所有自定义规则的方法。

据我了解,每个表达式树遍历器都运行在自己的线程上,因此在此过程中无法轻松共享状态。如果我采用使用单个分析仪的方法,我可以安全地执行以下操作吗?

[SourceAnalyzer(typeof(CsParser))]
public class CustomRules : SourceAnalyzer
{
    private enum CustomRuleName
    {
        CustomRule1,
        CustomRule2
    }

    private CustomRuleName currentRule;

    public override void AnalyzeDocument(CodeDocument document)
    {
        Param.RequireNotNull(document, "document");
        CsDocument doc = document as CsDocument;

        // Do not analyze empty documents, code generated files and files that
        // are to be skipped.
        if (doc.RootElement == null || doc.RootElement.Generated)
        {
            return;
        }

        // Check Rule: CustomRule1
        this.currentRule = CustomRuleName.CustomRule1;
        doc.WalkDocument(VisitElement);

        // Check Rule: CustomRule2
        this.currentRule = CustomRuleName.CustomRule2;
        doc.WalkDocument(VisitElement);
    }

    private bool VisitElement(CsElement element, CsElement parentElement, object context)
    {
        if (this.currentRule == CustomRuleName.CustomRule1)
        {
            // Do checks only applicable to custom rule #1
        }
        else if (this.currentRule == CustomRuleName.CustomRule2)
        {
            // Do checks only applicable to custom rule #2
        }
    }
}

更新:

根据进一步测试,上述内容安全。不能使用实例字段来维护状态。

  1. 在具有多个源代码文件的项目上运行 StyleCop 时,多个线程将共享同一个分析器实例。

  2. 此外,给定以下代码,当调用doc.WalkDocument(...) 方法时,多线程和并发性也会在每个正在分析的源代码文档中发挥作用。每个表达式树遍历器都在自己的线程上运行。

换句话说,除了可以在多个线程上同时分析多个源代码文件之外,回调VisitElementStatementWalkerExpressionWalker也可以在不同的线程上执行。

[SourceAnalyzer(typeof(CsParser))]
public class CustomRules : SourceAnalyzer
{
    public override void AnalyzeDocument(CodeDocument document)
    {
        Param.RequireNotNull(document, "document");
        CsDocument doc = document as CsDocument;

        // Do not analyze empty documents, code generated files and files that
        // are to be skipped.
        if (doc.RootElement == null || doc.RootElement.Generated)
        {
            return;
        }

        IDictionary<string, Field> fields = new Dictionary<string, Field>();
        doc.WalkDocument(VisitElement, StatementWalker, ExpressionWalker, fields);
    }

    private bool VisitElement(CsElement element, CsElement parentElement, object context)
    {
        // Do something...
        return true;
    }

    private bool StatementWalker(Statement statement, Expression parentExpression, Statement parentStatement, CsElement parentElement, object context)
    {
        // Do something...
        return true;
    }

    private bool ExpressionWalker(Expression expression, Expression parentExpression, Statement parentStatement, CsElement parentElement, object context)
    {
        // Do something...
        return true;
    }
}

【问题讨论】:

    标签: c# stylecop


    【解决方案1】:

    通常分析器包含多个规则(否则会很奇怪)。 每个分析器在设置 UI 中显示为一个单独的节点。

    如果您想要设置 UI 中的单个节点,您肯定只需要一个分析器,它会执行您的两项检查。

    namespace StyleCop.CustomRules
    {
        [SourceAnalyzer(typeof(CsParser))]
        public class MyCustomRules : SourceAnalyzer
        {
            public override void AnalyzeDocument(CodeDocument document)
            {
                // ...
                // code here can raise CR1001 as well as CR1002
            }
        }
    }
    

    文件:MyCustomRules.xml

    <?xml version="1.0" encoding="utf-8" ?>
    <SourceAnalyzer Name="My Custom Rules">
      <Description>
        These custom rules provide extensions to the ones provided with StyleCop.
      </Description>
      <Rules>
        <Rule Name="CustomRule1" CheckId="CR1001">
          <Context>Test rule 1.</Context>
          <Description>Test rule 1.</Description>
        </Rule>
        <Rule Name="CustomRule2" CheckId="CR1002">
          <Context>Test rule 2.</Context>
          <Description>Test rule 2.</Description>
        </Rule>
      </Rules>
    </SourceAnalyzer>
    

    【讨论】:

    • 感谢您的 cmets。请看我的更新。在调用 doc.WalkDocument(...) 时维护正在检查哪个规则的状态是否安全?
    • @Elan:StyleCop 解析器提供了丰富的方法,让你可以用上百种方式编写分析器代码。在您的特定模式中,您似乎不需要 currentRule 字段。刚开始走路并从 VisitElement() 方法引发适当的错误。
    • 谢谢,我用一个分析器就能很好地工作,这是有道理的。如果我共享相同的 VitElement() 方法,那么我必须区分正在运行的规则,以便检查适用于正在验证的规则。如果我为每个规则创建单独的 VisitElement() 回调,那么我自然不需要 currentRule 字段。
    【解决方案2】:

    如果有人对此主题感兴趣,我已经制定了一些自定义规则,并且我能够对这些规则进行分组。事实上,如果有人对将参数传递给警告消息感兴趣,我的规则也会这样做:)。

    规则:1. NamespaceMustBeginWithAllowedCompanyName 2. FieldNamesMustBeginWithUnderscore

    文件名:StyleCopExtensions.cs

    private bool VisitElement(CsElement element, CsElement parentElement, object context)
        {
            #region Namespace rules
            if (!element.Generated && element.ElementType == ElementType.Namespace)
            {
                var @namespace = element.Declaration.Name;
                var companyName = NamespaceUtils.GetNamespaceToken(@namespace, NamespaceTokenType.Company);
                //Flag a "Violation" is the element doesn't start with an allowed company name
                if (!NamespaceUtils.ValidateNamespaceCompany(companyName))
                {
                    AddViolation(parentElement, element.LineNumber, "NamespaceMustBeginWithAllowedCompanyName", companyName);
                }
            #endregion
    
            #region Fields rules
            if (!element.Generated && element.ElementType == ElementType.Field)
            {
                var fieldName = element.Declaration.Name;
                // Flag a violation if the instance variables are not prefixed with an underscore.
                if (element.ActualAccess != AccessModifierType.Public &&
                    element.ActualAccess != AccessModifierType.Internal &&
                    fieldName.ToCharArray()[0] != '_')
                {
                    AddViolation(parentElement, element.LineNumber, "FieldNamesMustBeginWithUnderscore", fieldName);
                }
            }
    
    
            #endregion
    
            return true;
        }
    

    Xml 文件名:StyleCopExtensions.xml - 类似于下面发布的其他 xml 文件。 - 您可以像“string.Format()”一样使用消息中发送的参数:只需在 xml 文件中包含 {0}、{1}、{N}。

    【讨论】:

      最近更新 更多