codeql/docs/ql-style-guide.md

15 KiB

QL Style Guide

Introduction

This document describes how to format the QL code you contribute to this repository. It covers aspects such as layout, white-space, naming and documentation. Adhering to consistent standards makes code easier to read and maintain. Of course, these are only guidelines, and can be overridden as the need arises on a case-by-case basis. Where existing code deviates from these guidelines, prefer consistency with the surrounding code.

Words in italic are defined in the Glossary.

Indentation

  1. Always use 2 spaces for indentation.
  2. Always indent:
    • The body of a module, newtype, class or predicate
    • The second and subsequent lines after you use a line break to split a long line
    • The body of a from, where or select clause where it spans multiple lines
    • The body of a quantifier that spans multiple lines

Examples

module Helpers {
  /** ... */
  class X ... {
    /** ... */
    int getNumberOfChildren () {
      result = count(int child |
        exists(this.getChild(child))
      )
    }
  }
}
from Call c, string reason
where isDeprecated(c, reason)
select c, "This call to '$@' is deprecated because " + reason + ".",
  c.getTarget(), c.getTarget().getName()

Line breaks

  1. Use UNIX line endings.
  2. Lines must not exceed 100 characters.
  3. Long lines should be split with a line break, and the following lines must be indented one level until the next "regular" line break.
  4. There should be a single blank line:
    • Between the file documentation and the first import
    • Before each declaration, except for the first declaration in a body
    • Before the from-where-select section in a query file
  5. Avoid two or more adjacent blank lines.
  6. There must be a new line after the annotations cached, pragma, language and bindingset. Other annotations do not have a new line.
  7. There should not be additional blank lines within a predicate.
  8. There may be a new line:
    • Immediately after the from, where or select keywords in a query.
    • Immediately after if, then, or else keywords.
  9. Avoid other line breaks in declarations, other than to break long lines.
  10. When operands of binary operators span two lines, the operator should be placed at the end of the first line.
  11. If the parameter list needs to be broken across multiple lines then there must be a line break after the opening (, the parameter declarations indented one level, and the ) { must be on its own line at the outer indentation.

Examples

cached
private int getNumberOfParameters() {
  ...
}
predicate methodStats(
  string qualifiedName, string name, int numberOfParameters,
  int numberOfStatements, int numberOfExpressions, int linesOfCode,
  int nestingDepth, int numberOfBranches
) {
  ...
}
from Method main
where main.getName() = "Main"
select main, "This is the program entry point."
from Method main
where
  main.getName() = "Main" and
  main.getNumberOfParameters() = 0
select main, "Main method has no parameters."
  if x.isPublic()
  then result = "public"
  else result = "private"
  if
    x.isPublic()
  then
    result = "public"
  else
    result = "private"
  if x.isPublic()
  then result = "public"
  else
    if x.isPrivate()
    then result = "private"
    else result = "protected"

Braces

  1. Braces follow Stroustrup style. The opening { must be placed at the end of the preceding line.
  2. The closing } must be placed on its own line, indented to the outer level, or be on the same line as the opening {.
  3. Braces of empty blocks may be placed on a single line, with a single space separating the braces.
  4. Short predicates, not exceeding the maximum line width, may be placed on a single line, with a space following the opening brace and preceding the closing brace.

Examples

class ThrowException extends ThrowExpr {
  Foo() {
    this.getTarget() instanceof ExceptionClass
  }

  override string toString() { result = "Throw Exception" }
}

Spaces

  1. There must be a space or line break:
    • Surrounding each = and |
    • After each ,
  2. There should be a space or line break:
    • Surrounding each binary operator, which must be balanced
    • Surrounding .. in a range
    • Exceptions to this may be made to save space or to improve readability.
  3. Avoid other spaces, for example:
    • After a quantifier/aggregation keyword
    • After the predicate name in a call
    • Inside brackets used for calls, single-line quantifiers, and parenthesised formulas
    • Surrounding a .
    • Inside the opening or closing [ ] in a range expression
    • Inside casts a.(X)
  4. Avoid multiple spaces, except for indentation, and avoid additional indentation to align formulas, parameters or arguments.
  5. Do not put whitespace on blank lines, or trailing on the end of a line.
  6. Do not use tabs.

Examples

cached
private predicate foo(Expr e, Expr p) {
  exists(int n |
    n in [0 .. 1] |
    e = p.getChild(n + 1)
  )
}

Naming

  1. Use PascalCase for:
    • class names
    • module names
    • newtype names
  2. Use camelCase for:
    • Predicate names
    • Variable names
  3. Newtype predicate names should begin with T.
  4. Predicates that have a result should be named get...
  5. Predicates that can return multiple results should be named getA... or getAn...
  6. Predicates that don't have a result or parameters should be named is... or has...
  7. Avoid underscores in names.
  8. Avoid short or single-letter names for classes, predicates and fields.
  9. Short or single letter names for parameters and quantifiers may be used provided that they are sufficiently clear.
  10. Use names as they are used in the target-language specification.
  11. Use American English.

Examples

/** ... */
predicate calls(Callable caller, Callable callee) {
  ...
}
/** ... */
class Type extends ... {
  /** ... */
  string getName() { ... }

  /** ... */
  predicate declares(Member m) { ... }

  /** ... */
  predicate isGeneric() { ... }

  /** ... */
  Type getTypeParameter(int n) { ... }

  /** ... */
  Type getATypeParameter() { ... }
}

Documentation

General requirements:

  1. Documentation must adhere to the QLDoc specification.
  2. Use /** ... */ for documentation, even for single line comments.
  3. For single-line documentation, the /** and */ are written on the same line as the comment.
  4. For multi-line documentation, the /** and */ are written on separate lines. There is a * preceding each comment line, aligned on the first *.
  5. Use full sentences, with capital letters and full stops.
  6. Use American English.
  7. Documentation comments should be appropriate for users of the code.
  8. Documentation for maintainers of the code must use normal comments.

Documentation for specific items:

  1. Public declarations must be documented.
  2. Non-public declarations should be documented.
  3. Declarations in query files should be documented.
  4. Library files (.qll files) should be have a documentation comment at the top of the file.
  5. Query files, except for tests, must have a QLDoc query documentation comment at the top of the file.
  6. Predicates that do not have a result should be documented /** Holds if ... */
  7. Predicates that have a result should be documented /** Gets ... */
  8. All predicate parameters should be referred to in the predicate documentation.
  9. Reference names, such as types and parameters, using backticks `.
  10. Give examples of code in the target language, enclosed in ``` or `.
  11. Classes should be documented in the singular, for example /* An expression. */
  12. Where a class denotes a generic concept with subclasses, list those subclasses.
  13. Declarations that are deprecated should be documented as DEPRECATED: ...
  14. Declarations that are for internal use should be documented as INTERNAL: Do not use.

Examples

/** Provides logic for determining constant expressions. */
/**
 * Holds if the qualifier of this call has type `qualifierType`.
 * `isExactType` indicates whether the type is exact, that is, whether
 * the qualifier is guaranteed not to be a subtype of `qualifierType`.
 */
/**
 * A delegate declaration, for example
 * ```
 * delegate void Logger(string text);
 * ```
 */
class Delegate extends ...
/**
 * An element that can be called.
 *
 * Either a method (`Method`), a constructor (`Constructor`), a destructor
 * (`Destructor`), an operator (`Operator`), an accessor (`Accessor`),
 * an anonymous function (`AnonymousFunctionExpr`), or a local function
 * (`LocalFunction`).
 */
class Callable extends ...
/** DEPRECATED: Use `getAnExpr()` instead. */
deprecated Expr getInitializer()
/**
 * INTERNAL: Do not use.
 */

Formulas

  1. Prefer one conjunct per line.
  2. Write the and at the end of the line. This also applies in where clauses.
  3. Prefer to write the or keyword on its own line.
  4. The or keyword may be written at the end of a line, or within a line, provided that it has no and operands.
  5. Single-line formulas may be used in order to save space or add clarity, particularly in the body of a quantifier/aggregation.
  6. Always use brackets to clarify the precedence of:
    • implies
    • if-then-else
  7. Avoid using brackets to clarify the precedence of:
    • not
    • and
    • or
  8. Parenthesised formulas can be written:
    • Within a single line. There should not be an additional space following the opening parenthesis or preceding the closing parenthesis.
    • Spanning multiple lines. The opening parenthesis should be placed at the end of the preceding line, the body should be indented one level, and the closing bracket should be placed on a new line at the outer indentation.
  9. Quantifiers/aggregations can be written:
    • Within a single line. In this case, there is no space to the inside of the parentheses, or after the quantifier keyword.
    • Across multiple lines. In this case, type declarations are on the same line as the quantifier with the first | at the same line as the quantifier, the second | must be at the end of the same line as the quantifier or on its own line at the outer indentation, and the body of the quantifier must be indented one level. The closing ) is written on a new line, at the outer indentation. If the type declarations need to be broken across multiple lines then there must must be a line break after the opening (, the type declarations indented one level, and the first | on its own line at the outer indentation.
  10. if-then-else can be written:
    • On a single line
    • With the body after the if/then/else keyword
    • With the body indented on the next line
    • Always parenthesise the else part if it is a compound formula.
  11. If an if-then-else is broken across multiple lines then the then and else keywords should be at the start of lines aligned with the if.
  12. The and and else keywords may be placed on the same line as the closing parenthesis.
  13. The and and else keywords may be "cuddled": ) else (
  14. Always qualify calls to predicates of the same class with this.
  15. Prefer postfix casts a.(Expr) to prefix casts (Expr)a.

Examples

  argumentType.isImplicitlyConvertibleTo(parameterType)
  or
  argumentType instanceof NullType and
  result.getParameter(i).isOut() and
  parameterType instanceof SimpleType
  or
  reflectionOrDynamicArg(argumentType, parameterType)
  this.getName() = "Finalize" and not exists(this.getAParameter())
  e1.getType() instanceof BoolType and (
    b1 = true
    or
    b1 = false
  ) and (
    b2 = true
    or
    b2 = false
  )
  if e1 instanceof BitwiseOrExpr or e1 instanceof LogicalOrExpr
  then (
    impliesSub(e1.(BinaryOperation).getAnOperand(), e2, b1, b2) and
    b1 = false
  ) else (
    e1.getType() instanceof BoolType and
    e1 = e2 and
    b1 = b2 and
    (b1 = true or b1 = false)
  )
  (x instanceof Exception implies x.isPublic()) and y instanceof Exception
  x instanceof Exception implies (x.isPublic() and y instanceof Exception)
  exists(Type arg | arg = this.getAChild() | arg instanceof TypeParameter)
  exists(Type qualifierType |
    this.hasNonExactQualifierType(qualifierType)
  |
    result = getANonExactQualifierSubType(qualifierType)
  )
  methods = count(Method m | t = m.getDeclaringType() and not ilc(m))
  if n = 0 then result = 1 else result = n * f(n - 1)
  if n = 0
  then result = 1
  else result = n * f(n - 1)
  if
    n = 0
  then
    result = 1
  else
    result = n * f(n - 1)
  if exists(this.getContainingType())
  then (
    result = "A nested class" and
    parentName = this.getContainingType().getFullyQualifiedName()
  ) else (
    result = parentName + "." + this.getName() and
    parentName = this.getNamespace().getFullyQualifiedName()
  )

Glossary

Phrase Meaning
annotation An additional specifier used to modify a declaration, such as private, override, deprecated, pragma, bindingset, or cached.
body The text inside { }, ( ), or each section of an if-then-else or from-where-select.
binary operator An operator with two operands, such as comparison operators, and, or, implies, or arithmetic operators.
call A formula that invokes a predicate, e.g. this.isStatic() or calls(a,b).
conjunct A formula that is an operand to an and.
declaration A class, module, predicate, field or newtype.
disjunct A formula that is an operand to an or.
formula A logical expression, such as A = B, a call, a quantifier, and, or, not, in or instanceof.
should/should not/avoid/prefer Adhere to this rule wherever possible, where it makes sense.
may/can This is a reasonable alternative, to be used with discretion.
must/always/do not Always adhere to this rule.
quantifier/aggregation exists, count, strictcount, any, forall, forex and so on.
variable A parameter to a predicate, a field, a from variable, or a variable introduced by a quantifier or aggregation.