Skip to content

CLQL vs StyleCop#

CLQL, like StyleCop, can express C# style rules and use them to analyze a project, file, repository, or Pull Request. CLQL, like StyleCop can customize a set of predefined rules to determine how they should apply to a given project, and both can define custom rules.

StyleCop supports custom rules by providing a SourceAnalyzer class with CodeWalker methods. The rule author can iterate through elements of the document and raise violations when the code matches a certain pattern.

CLQL can express all rules that can be expressed in StyleCop. By abstracting away the details of document walking, CLQL can express in 9 lines a rule that takes ~50 lines of StyleCop. In addition to requiring, on average, 5x less code to express these patterns, CLQL queries can be generated by selecting the code elements in an IDE.

CLQL is not limited to C# like StyleCop. CLQL can express logic about other domains of logic outside of the scope of StyleCop, like Version Control.

Empty Block Statements#

StyleCop can use a custom rule to raise a violation for all empty block statements:

namespace Testing.EmptyBlockRule {
    using global::StyleCop;
    using global::styleCop.CSharp;

    [SourceAnalyzer(typeof(CsParser))]
    public class EmptyBlocks : SourceAnalyzer
    {
        public override void AnalyzeDocument(CodeDocument document)
        {
            CsDocument csdocument = (CsDocument)document;
            if (csdocument.RootElement != null && !csdocument.RootElement.Generated)
            {
                csdocument.WalkDocument(
                    new CodeWalkerElementVisitor<object>(this.VisitElement),
                    null,
                    null);
            }
        }

        private bool VisitElement(CsElement element, CsElement parentElement, object context)
        {
            if (statement.StatementType == StatementType.Block && statement.ChildStatements.Count == 0)
            {
                this.AddViolation(parentElement, statement.LineNumber, "BlockStatementsShouldNotBeEmpty");
            }
        }


        private bool VisitStatement(Statement statement, Expression parentExpression, Statement parentStatement, CsElement parentElement, object context)
        {
            if (statement.StatementType == StatementType.Block && statement.ChildStatements.Count == 0)
            {
                this.AddViolation(parentElement, statement.LineNumber, "BlockStatementsShouldNotBeEmpty");
            }
        }
    }
}
<SourceAnalyzer Name="EmptyBlocks">
  <Description>
    Code blocks should not be empty.
  </Description>
  <Rules>
    <RuleGroup Name="Fun Rules You Will Love">
      <Rule Name="BlockStatementsShouldNotBeEmpty" CheckId="MY1000">
        <Context>A block statement should always contain child statements.</Context>
        <Description>Validates that the code does not contain any empty block statements.</Description>
      </Rule>
    </RuleGroup>
  </Rules>
</SourceAnalyzer>

The same rule can be expressed in CLQL as the following Rule:

rules:
  - name: "EmptyBlock"
    actions:
      codelingo/docs:
        title: "Validates that the code does not contain any empty block statements."
      codelingo/review:
        comment: This function block is empty.
    query:
      import codelingo/ast/cpp
      @review comment
      cs.block_stmt(depth = any):
        exclude:
          cs.element

The VisitStatement function contains the core logic of this StyleCop rule:

private bool VisitStatement(Statement statement, Expression parentExpression, Statement parentStatement, CsElement parentElement, object context)
{
    if (statement.StatementType == StatementType.Block && statement.ChildStatements.Count == 0)
    {
        this.AddViolation(parentElement, statement.LineNumber, "BlockStatementsShouldNotBeEmpty");
    }
}

The VisitStatement method is run at every node of the AST tree, then a violation is added if the node is a block statement with no children. In CLQL, the match statement expresses the logic of the query. Traversal is entirely abstracted away, and the Rule author only needs to express the condition for a "rule violation":

cs.block_stmt:
  exclude:
    cs.element

The above query will match against any block statement that does not contain anything at all. cs.element matches all C# elements, and the exclude operator performs exclusion.