Skip to content

Strict coding standard for Kotlin and a custom set of rules for detecting code smells, code style issues and bugs

License

Notifications You must be signed in to change notification settings

saveourtool/diktat

Repository files navigation

Build and test deteKT static analysis diKTat code style codecov

Releases Maven Central FOSSA Status Chat on Telegram

Hits-of-Code Lines of code GitHub repo size Awesome Kotlin Badge

DiKTat is a strict coding standard for Kotlin, consisting of a collection of Kotlin code style rules implemented as Abstract Syntax Tree (AST) visitors built on top of KTlint. It serves the purpose of detecting and automatically fixing code smells in the Continuous Integration/Continuous Deployment (CI/CD) process. You can find the comprehensive list of supported rules and inspections here.

DiKTat has gained recognition and has been added to the lists of static analysis tools, kotlin-awesome, and kompar. We extend our gratitude to the community for this support!

See first

Codestyle Inspections Examples Demo White Paper Groups of Inspections

Why Choose DiKTat for CI/CD?

While there are other tools like detekt and ktlint performing static analysis, you might wonder why DiKTat is necessary. Here are the key reasons:

  1. More Inspections: DiKTat boasts over 100 inspections tightly coupled with its Codestyle.

  2. Unique Inspections: DiKTat introduces unique inspections not found in other linters.

  3. Highly Configurable: Every inspection is highly configurable, allowing customization and suppression. Check configuration options and suppression.

  4. Strict Codestyle: DiKTat enforces a detailed Codestyle that can be adopted and applied in your project.

Download binary

  1. Download diKTat manually: here

    OR use curl:

    curl -sSLO https://github.com/saveourtool/diktat/releases/download/v2.0.0/diktat && chmod a+x diktat

For Windows only. Download diKTat.cmd manually: here

Run diKTat

Finally, run KTlint (with diKTat injected) to check your '*.kt' files in 'dir/your/dir':

$ ./diktat "dir/your/dir/**/*.kt"

On Windows

diktat.bat "dir/your/dir/**/*.kt"

To autofix all code style violations, use --mode fix option.

Run with Maven using diktat-maven-plugin

You can see how it is configured in our examples:

Add this plugin to your pom.xml:
            <plugin>
                <groupId>com.saveourtool.diktat</groupId>
                <artifactId>diktat-maven-plugin</artifactId>
                <version>${diktat.version}</version>
                <executions>
                    <execution>
                        <id>diktat</id>
                        <phase>none</phase>
                        <goals>
                            <goal>check</goal>
                            <goal>fix</goal>
                        </goals>
                        <configuration>
                            <inputs>
                                <input>${project.basedir}/src/main/kotlin</input>
                                <input>${project.basedir}/src/test/kotlin</input>
                            </inputs>
                            <diktatConfigFile>diktat-analysis.yml</diktatConfigFile>
                           <excludes>
                              <exclude>${project.basedir}/src/test/kotlin/excluded</exclude>
                           </excludes>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

To run diktat in only-check mode use command $ mvn diktat:check@diktat. To run diktat in autocorrect mode use command $ mvn diktat:fix@diktat.

Requesting a specific Maven executionId on the command line (the trailing diktat in the above example) may be essential in these cases:

  • In your pom.xml, you have multiple executions with different configurations (e. g.: multiple rule sets):

    <executions>
    
        <execution>
            <id>diktat-basic</id>
            <configuration>
                <diktatConfigFile>diktat-analysis.yml</diktatConfigFile>
            </configuration>
        </execution>
    
        <execution>
            <id>diktat-advanced</id>
            <configuration>
                <diktatConfigFile>diktat-analysis-advanced.yml</diktatConfigFile>
            </configuration>
        </execution>
    
    </executions>
  • Your YAML file with DiKTat rules has a non-default name and/or resides in a non-default location:

    <executions>
        <execution>
            <id>diktat</id>
            <configuration>
                <diktatConfigFile>/non/default/rule-set-file.yml</diktatConfigFile>
            </configuration>
        </execution>
    </executions>
    • You can omit the diktatConfigFile or if it points to non-existed file then DiKTat runs with default configuration.

If you omit the executionId:

$ mvn diktat:check

— the plug-in will use the default configuration and search for diktat-analysis.yml file in the project directory (you can still customize the rule sets by editing the YAML file).

Run with Gradle using diktat-gradle-plugin

Requires a gradle version no lower than 7.0

You can see how the plugin is configured in our examples:

Add this plugin to your `build.gradle.kts`:
plugins {
    id("com.saveourtool.diktat") version "2.0.0"
}

Note If you want to apply the plugin to multi-module projects"

import com.saveourtool.diktat.plugin.gradle.DiktatGradlePlugin

plugins {
    id("com.saveourtool.diktat") version "2.0.0" apply false
}

allprojects {
    apply<DiktatGradlePlugin>()
}

You can then configure diktat using diktat extension:

diktat {
    inputs {
        include("src/**/*.kt")  // path matching this pattern (per PatternFilterable) that will be checked by diktat
        exclude("src/test/kotlin/excluded/**")  // path matching this pattern will not be checked by diktat
    }
    debug = true  // turn on debug logging
}

Also in diktat extension you can configure different reporters and their output. You can specify json, html, sarif, plain (default). If output is set, it should be a file path. If not set, results will be printed to stdout. You can specify multiple reporters. If no reporter is specified, plain will be used with stdout as output.

diktat {
    reporters {
        plain()
        json()
        html {
            output = file("someFile.html")
        }
        // checkstyle()
        // sarif()
        // gitHubActions()
    }
}

You can run diktat checks using task ./gradlew diktatCheck and automatically fix errors with task ./gradlew diktatFix.

Run with Spotless

Spotless is a linter aggregator.

Gradle

Diktat can be run via spotless-gradle-plugin since version 5.10.0

Add this plugin to your build.gradle.kts
plugins {
   id("com.diffplug.spotless") version "5.10.0"
}

spotless {
   kotlin {
      diktat()
   }
   kotlinGradle {
      diktat()
   }
}
You can provide a version and configuration path manually as configFile.
spotless {
   kotlin {
      diktat("2.0.0").configFile("full/path/to/diktat-analysis.yml")
   }
}

Maven

Diktat can be run via spotless-maven-plugin since version 2.8.0

Add this plugin to your pom.xml
<plugin>
   <groupId>com.diffplug.spotless</groupId>
   <artifactId>spotless-maven-plugin</artifactId>
   <version>${spotless.version}</version>
   <configuration>
      <kotlin>
         <diktat />
      </kotlin>
   </configuration>
</plugin>
You can provide a version and configuration path manually as configFile
<diktat>
  <version>2.0.0</version> <!-- optional -->
  <configFile>full/path/to/diktat-analysis.yml</configFile> <!-- optional, configuration file path -->
</diktat>

GitHub Integration

We suggest everyone to use common "sarif" format as a reporter in CI/CD. GitHub has an integration with SARIF format and provides you a native reporting of diktat issues in Pull Requests.

img.png

Github Integration 1) Add the following configuration to your project's setup for GitHub Actions:

Gradle Plugin:

    githubActions = true

Maven Plugin (pom.xml):

    <githubActions>true</githubActions>

Maven Plugin (cli options):

mvn -B diktat:check@diktat -Ddiktat.githubActions=true
  1. Add the following code to your GitHub Action to upload diktat SARIF report (after it was generated):
      - name: Upload SARIF to Github using the upload-sarif action
        uses: github/codeql-action/upload-sarif@v1
        if: ${{ always() }}
        with:
          sarif_file: ${{ github.workspace }}

Note: codeql-action/upload-sarif limits the number of uploaded files at 15. If your project has more than 15 subprojects, the limit will be exceeded and the step will fail. To solve this issue one can merge SARIF reports.

diktat-gradle-plugin provides this capability with mergeDiktatReports task. This task aggregates reports of all diktat tasks of all Gradle project, which produce SARIF reports, and outputs the merged report into root project's build directory. Then this single file can be used as an input for GitHub action:

with:
    sarif_file: build/reports/diktat/diktat-merged.sarif

Customizations via diktat-analysis.yml

In Diktat we have supported diktat-analysis.yml that can be easily changed and help in customization of your own rule set. It has simple fields: name — name of the rule, enabled (true/false) — to enable or disable that rule (all rules are enabled by the default), configuration — a simple map of some extra unique configurations for this particular rule. For example:

- name: HEADER_MISSING_OR_WRONG_COPYRIGHT
  # all rules are enabled by the default. To disable add 'enabled: false' to the config.
  enabled: true
  configuration:
    isCopyrightMandatory: true
    copyrightText: Copyright (c) Jeff Lebowski, 2012-2020. All rights reserved.

Note, that you can specify and put diktat-analysis.yml that contains configuration of diktat in the parent directory of your project on the same level where build.gradle/pom.xml is stored.
See default configuration in diktat-analysis.yml
Also see the list of all rules supported by diKTat.

Suppress warnings/inspections

Suppress warnings on individual code blocks In addition to enabling/disabling warning globally via config file (`enable = false`), you can suppress warnings by adding `@Suppress` annotation on individual code blocks or `@file:Suppress()` annotation on a file-level.

For example:

@Suppress("FUNCTION_NAME_INCORRECT_CASE")
class SomeClass {
    fun methODTREE(): String {

    }
}
Disable all inspections on selected code blocks Also you can suppress **all** warnings by adding `@Suppress("diktat")` annotation on individual code blocks.

For example:

@Suppress("diktat")
class SomeClass {
    fun methODTREE(): String {

    }
}
ignoreAnnotated: disable inspections on blocks with predefined annotation In the `diktat-analysis.yml` file for each inspection it is possible to define a list of annotations that will cause disabling of the inspection on that particular code block:
- name: HEADER_NOT_BEFORE_PACKAGE
  enabled: true
  ignoreAnnotated: [MyAnnotation, Compose, Controller]
Suppress groups of inspections by chapters It is easy to suppress even groups of inspections in diKTat.

These groups are linked to chapters of Codestyle.

To disable chapters, you will need to add the following configuration to common configuration (- name: DIKTAT_COMMON):

    disabledChapters: "1, 2, 3"

Mapping of inspections to chapters can be found in Groups of Inspections.

Running against the baseline

When setting up code style analysis on a large existing project, one often doesn't have an ability to fix all findings at once. To allow gradual adoption, diktat and ktlint support baseline mode. When running ktlint for the first time with active baseline, the baseline file will be generated. It is a xml file with a complete list of findings by the tool. On later invocations, only the findings that are not in the baseline file will be reported. Baseline can be activated with CLI flag:

./diktat --baseline=diktat-baseline.xml **/*.kt

or with corresponding configuration options in maven or gradle plugins. Baseline report is intended to be added into the VCS, but it can be removed and re-generated later, if needed.

Contribution

See our Contributing Policy and Code of Conduct

Kotlin Coding Style Guide (Diktat Code Style), v.1.0.0

I Preface

1. Naming

2. Comments

3. General formatting (typesetting)

4. Variables and types

5. Functions

6. Classes, interfaces, and extension functions

Preface

Purpose of this document

The purpose of this document is to provide a specification that software developers could reference to enhance their ability to write consistent, easy-to-read, and high-quality code. Such a specification will ultimately improve software development efficiency and product competitiveness. For the code to be considered high-quality, it must entail the following characteristics:

  1. Simplicity
  2. Maintainability
  3. Reliability
  4. Testability
  5. Efficiency
  6. Portability
  7. Reusability

General principles

Like other modern programming languages, Kotlin is an advanced programming language that complies with the following general principles:

  1. Clarity — a necessary feature of programs that are easy to maintain and refactor.
  2. Simplicity — a code is easy to understand and implement.
  3. Consistency — enables a code to be easily modified, reviewed, and understood by the team members. Unification is particularly important when the same team works on the same project, utilizing similar styles enabling a code to be easily modified, reviewed, and understood by the team members.

Also, we need to consider the following factors when programming on Kotlin:

  1. Writing clean and simple Kotlin code

    Kotlin combines two of the main programming paradigms: functional and object-oriented. Both of these paradigms are trusted and well-known software engineering practices. As a young programming language, Kotlin is built on top of well-established languages such as Java, C++, C#, and Scala. This enables Kotlin to introduce many features that help a developer write cleaner, more readable code while also reducing the number of complex code structures. For example, type and null safety, extension functions, infix syntax, immutability, val/var differentiation, expression-oriented features, "when" statements, much easier work with collections, type auto conversion, and other syntactic sugar.

  2. Following Kotlin idioms

    The author of Kotlin, Andrey Breslav, mentioned that Kotlin is both pragmatic and practical, but not academic. Its pragmatic features enable ideas to be transformed into real working software easily. Kotlin is closer to natural languages than its predecessors, and it implements the following design principles: readability, reusability, interoperability, security, and tool-friendliness (https://blog.jetbrains.com/kotlin/2018/10/kotlinconf-2018-announcements/).

  3. Using Kotlin efficiently

    Some Kotlin features can help you to write higher-performance code: including rich coroutine library, sequences, inline functions/classes, arrays of basic types, tailRec, and CallsInPlace of contract.

Terminology

Rules — conventions that should be followed when programming.

Recommendations — conventions that should be considered when programming.

Explanation — necessary explanations of rules and recommendations.

Valid Example — recommended examples of rules and recommendations.

Invalid Example — not recommended examples of rules and recommendations.

Unless otherwise stated, this specification applies to versions 1.3 and later of Kotlin.

Exceptions

Even though exceptions may exist, it is essential to understand why rules and recommendations are needed. Depending on a project situation or personal habits, you can break some of the rules. However, remember that one exception may lead to many and eventually can destroy code consistency. As such, there should be very few exceptions. When modifying open-source code or third-party code, you can choose to use the code style from this open-source project (instead of using the existing specifications) to maintain consistency. Software that is directly based on the Android native operating system interface, such as the Android Framework, remains consistent with the Android style.

1. Naming

In programming, it is not always easy to meaningfully and appropriately name variables, functions, classes, etc. Using meaningful names helps to clearly express your code's main ideas and functionality and avoid misinterpretation, unnecessary coding and decoding, "magic" numbers, and inappropriate abbreviations.

Note: The source file encoding format (including comments) must be UTF-8 only. The ASCII horizontal space character (0x20, that is, space) is the only permitted whitespace character. Tabs should not be used for indentation.

1.1 Identifiers

This section describes the general rules for naming identifiers.

1.1.1 Identifiers naming conventions

For identifiers, use the following naming conventions:

  1. All identifiers should use only ASCII letters or digits, and the names should match regular expressions \w{2,64}. Explanation: Each valid identifier name should match the regular expression \w{2,64}. {2,64} means that the name length is 2 to 64 characters, and the length of the variable name should be proportional to its life range, functionality, and responsibility. Name lengths of less than 31 characters are generally recommended. However, this depends on the project. Otherwise, a class declaration with generics or inheritance from a superclass can cause line breaking. No special prefix or suffix should be used in names. The following examples are inappropriate names: name_, mName, s_name, and kName.

  2. Choose file names that would describe the content. Use camel case (PascalCase) and .kt extension.

  3. Typical examples of naming:

Meaning Correct Incorrect
"XML Http Request" XmlHttpRequest XMLHTTPRequest
"new customer ID" newCustomerId newCustomerID
"inner stopwatch" innerStopwatch innerStopWatch
"supports IPv6 on iOS" supportsIpv6OnIos supportsIPv6OnIOS
"YouTube importer" YouTubeImporter YoutubeImporter
  1. The usage of (``) and free naming for functions and identifiers are prohibited. For example, the following code is not recommended:
val `my dummy name-with-minus` = "value"

The only exception is function names in Unit tests.

  1. Backticks (``) should not be used for identifiers, except the names of test methods (marked with @Test annotation):
 @Test fun `my test`() { /*...*/ }
  1. The following table contains some characters that may cause confusion. Be careful when using them as identifiers. To avoid issues, use other names instead.
Expected Confusing name Suggested name
0 (zero) O, D obj, dgt
1 (one) I, l it, ln, line
2 (two) Z n1, n2
5 (five) S xs, str
6 (six) e ex, elm
8 (eight) B bt, nxt
n,h h,n nr, head, height
rn, m m,rn mbr, item

Exceptions:

  • The i,j,k variables used in loops are part of the industry standard. One symbol can be used for such variables.
  • The e variable can be used to catch exceptions in catch block: catch (e: Exception) {}
  • The Java community generally does not recommend the use of prefixes. However, when developing Android code, you can use the s and m prefixes for static and non-public non-static fields, respectively. Note that prefixing can also negatively affect the style and the auto-generation of getters and setters.
Type Naming style
Interfaces, classes, annotations, enumerated types, and object type names Camel case, starting with a capital letter. Test classes have a Test suffix. The filename is 'TopClassName'.kt.
Class fields, local variables, methods, and method parameters Camel case starting with a low case letter. Test methods can be underlined with '_'; the only exception is backing properties.
Static constants and enumerated values Only uppercase underlined with '_'
Generic type variable Single capital letter, which can be followed by a number, for example: E, T, U, X, T2
Exceptions Same as class names, but with a suffix Exception, for example: AccessException and NullPointerException

1.2 Packages

Rule 1.2.1 Package names dots

Package names are in lower case and separated by dots. Code developed within your company should start with your.company.domain. Numbers are permitted in package names. Each file should have a package directive. Package names are all written in lowercase, and consecutive words are concatenated together (no underscores). Package names should contain both the product or module names and the department (or team) name to prevent conflicts with other teams. Numbers are not permitted. For example: org.apache.commons.lang3, xxx.yyy.v2.

Exceptions:

  • In certain cases, such as open-source projects or commercial cooperation, package names should not start with your.company.domain.
  • If the package name starts with a number or other character that cannot be used at the beginning of the Java/Kotlin package name, then underscores are allowed. For example: com.example._123name.
  • Underscores are sometimes permitted if the package name contains reserved Java/Kotlin keywords, such as org.example.hyphenated_name, int_.example.

Valid example:

package your.company.domain.mobilecontrol.views

1.3 Classes, enumerations, typealias, interfaces

This section describes the general rules for naming classes, enumerations, and interfaces.

1.3.1 Classes, enumerations, typealias, interface names use Camel case

Classes, enumerations, and interface names use UpperCamelCase nomenclature. Follow the naming rules described below:

  1. A class name is usually a noun (or a noun phrase) denoted using the camel case nomenclature, such as UpperCamelCase. For example: Character or ImmutableList. An interface name can also be a noun or noun phrase (such as List) or an adjective or adjective phrase (such as Readable). Note that verbs are not used to name classes. However, nouns (such as Customer, WikiPage, and Account) can be used. Try to avoid using vague words such as Manager and Process.

  2. Test classes start with the name of the class they are testing and end with 'Test'. For example, HashTest or HashIntegrationTest.

Invalid example:

class marcoPolo {}
class XMLService {}
interface TAPromotion {}
class info {}

Valid example:

class MarcoPolo {}
class XmlService {}
interface TaPromotion {}
class Order {}

1.4 Functions

This section describes the general rules for naming functions.

1.4.1 Function names should be in camel case

Function names should use lowerCamelCase nomenclature. Follow the naming rules described below:

  1. Function names are usually verbs or verb phrases denoted with the camel case nomenclature (lowerCamelCase). For example: sendMessage, stopProcess, or calculateValue. To name functions, use the following formatting rules:

a) To get, modify, or calculate a certain value: get + non-boolean field(). Note that the Kotlin compiler automatically generates getters for some classes, applying the special syntax preferred for the 'get' fields: kotlin private val field: String get() { }. kotlin private val field: String get() { }.

private val field: String
get() {
}

Note: The calling property access syntax is preferred to call getter directly. In this case, the Kotlin compiler automatically calls the corresponding getter.

b) is + boolean variable name()

c) set + field/attribute name(). However, note that the syntax and code generation for Kotlin are completely the same as those described for the getters in point a.

d) has + Noun / adjective ()

e) verb() Note: Note: Verb are primarily used for the action objects, such as document.print ()

f) verb + noun()

g) The Callback function allows the names that use the preposition + verb format, such as: onCreate(), onDestroy(), toString().

Invalid example:

fun type(): String
fun Finished(): Boolean
fun visible(boolean)
fun DRAW()
fun KeyListener(Listener)

Valid example:

fun getType(): String
fun isFinished(): Boolean
fun setVisible(boolean)
fun draw()
fun addKeyListener(Listener)
  1. An underscore (_) can be included in the JUnit test function name and should be used as a separator. Each logical part is denoted in lowerCamelCase, for example, a typical pattern of using underscore: pop_emptyStack.

1.5 Constants

This section describes the general rules for naming constraints.

1.5.1 Using UPPER case and underscore characters in a constraint name

Constant names should be in UPPER case, words separated by underscore. The general constant naming conventions are listed below:

  1. Constants are attributes created with the const keyword or top-level/val local variables of an object that holds immutable data. In most cases, constants can be identified as a const val property from the object/companion object/file top level. These variables contain fixed constant values that typically should never be changed by programmers. This includes basic types, strings, immutable types, and immutable collections of immutable types. The value is not constant for the object, which state can be changed.
  2. Constant names should contain only uppercase letters separated by an underscores. They should have a val or const val modifier to make them final explicitly. In most cases, if you need to specify a constant value, then you need to create it with the "const val" modifier. Note that not all val variables are constants.
  3. Objects with immutable content, such as Logger and Lock, can be in uppercase as constants or have camel case as regular variables.
  4. Use meaningful constants instead of magic numbers. SQL or logging strings should not be treated as magic numbers, nor should they be defined as string constants. Magic constants, such as NUM_FIVE = 5 or NUM_5 = 5 should not be treated as constants. This is because mistakes will easily be made if they are changed to NUM_5 = 50 or 55. These constants typically represent business logic values, such as measures, capacity, scope, location, tax rate, promotional discounts, and power base multiples in algorithms. You can avoid using magic numbers with the following method:
  • Using library functions and APIs. For example, instead of checking that size == 0, use isEmpty() function. To work with time, use built-ins from java.time API.
  • Enumerations can be used to name patterns. Refer to Recommended usage scenario for enumeration in 3.9.

Invalid example:

var int MAXUSERNUM = 200;
val String sL = "Launcher";

Valid example:

const val int MAX_USER_NUM = 200;
const val String APPLICATION_NAME = "Launcher";

1.6 Non-constant fields (variables)

This section describes the general rules for naming variables.

1.6.1 Non-constant field name

Non-constant field names should use camel case and start with a lowercase letter. A local variable cannot be treated as constant even if it is final and immutable. Therefore, it should not use the preceding rules. Names of collection type variables (sets, lists, etc.) should contain plural nouns. For example: var namesList: List<String>

Names of non-constant variables should use lowerCamelCase. The name of the final immutable field used to store the singleton object can use the same camel case notation.

Invalid example:

customername: String
user: List<String> = listof()

Valid example:

var customerName: String
val users: List<String> = listOf();
val mutableCollection: MutableSet<String> = HashSet()

1.6.2 Boolean variable names with negative meaning

Avoid using Boolean variable names with a negative meaning. When using a logical operator and name with a negative meaning, the code may be difficult to understand, which is referred to as the "double negative". For instance, it is not easy to understand the meaning of !isNotError. The JavaBeans specification automatically generates isXxx() getters for attributes of Boolean classes. However, not all methods returning Boolean type have this notation. For Boolean local variables or methods, it is highly recommended that you add non-meaningful prefixes, including is (commonly used by JavaBeans), has, can, should, and must. Modern integrated development environments (IDEs) such as Intellij are already capable of doing this for you when you generate getters in Java. For Kotlin, this process is even more straightforward as everything is on the byte-code level under the hood.

Invalid example:

val isNoError: Boolean
val isNotFound: Boolean
fun empty()
fun next();

Valid example:

val isError: Boolean
val isFound: Boolean
val hasLicense: Boolean
val canEvaluate: Boolean
val shouldAbort: Boolean
fun isEmpty()
fun hasNext()

2. Comments

The best practice is to begin your code with a summary, which can be one sentence. Try to balance between writing no comments at all and obvious commentary statements for each line of code. Comments should be accurately and clearly expressed, without repeating the name of the class, interface, or method. Comments are not a solution to the wrong code. Instead, you should fix the code as soon as you notice an issue or plan to fix it (by entering a TODO comment, including a Jira number). Comments should accurately reflect the code's design ideas and logic and further describe its business logic. As a result, other programmers will be able to save time when trying to understand the code. Imagine that you are writing the comments to help yourself to understand the original ideas behind the code in the future.

2.1 General form of Kdoc

KDoc is a combination of JavaDoc's block tags syntax (extended to support specific constructions of Kotlin) and Markdown's inline markup. The basic format of KDoc is shown in the following example:

 /**
 * There are multiple lines of KDoc text,
 * Other ...
 */
fun method(arg: String) {
    // ...
}

It is also shown in the following single-line form:

 /** Short form of KDoc. */

Use a single-line form when you store the entire KDoc block in one line (and there is no KDoc mark @XXX). For detailed instructions on how to use KDoc, refer to Official Document.

2.1.1 Using KDoc for the public, protected, or internal code elements

At a minimum, KDoc should be used for every public, protected, or internal decorated class, interface, enumeration, method, and member field (property). Other code blocks can also have KDocs if needed. Instead of using comments or KDocs before properties in the primary constructor of a class - use @property tag in a KDoc of a class. All properties of the primary constructor should also be documented in a KDoc with a @property tag.

Incorrect example:

/**
 * Class description
 */
class Example(
 /**
  * property description
  */
  val foo: Foo,
  // another property description
  val bar: Bar
)

Correct example:

/**
 * Class description
 * @property foo property description
 * @property bar another property description
 */
class Example(
  val foo: Foo,
  val bar: Bar
)
  • Don't use Kdoc comments inside code blocks as block comments

Incorrect Example:

class Example {
  fun doGood() {
    /**
     * wrong place for kdoc
     */
    1 + 2
  }
}

Correct Example:

class Example {
  fun doGood() {
    /*
     * right place for block comment
    */
    1 + 2
  }
}

Exceptions:

  • For setters/getters of properties, obvious comments (like this getter returns field) are optional. Note that Kotlin generates simple get/set methods under the hood.

  • It is optional to add comments for simple one-line methods, such as shown in the example below:

val isEmpty: Boolean
    get() = this.size == 0

or

fun isEmptyList(list: List<String>) = list.size == 0

Note: You can skip KDocs for a method's override if it is almost the same as the superclass method.

2.1.2 Describing methods that have arguments, a return value, or can throw an exception in the KDoc block

When the method has such details as arguments, return value, or can throw exceptions, it must be described in the KDoc block (with @param, @return, @throws, etc.).

Valid examples:

/**
* This is the short overview comment for the example interface.
*     / * Add a blank line between the comment text and each KDoc tag underneath * /
* @since 1.6
*/
protected abstract class Sample {
   /**
    * This is a long comment with whitespace that should be split in
    * comments on multiple lines if the line comment formatting is enabled.
    *     / * Add a blank line between the comment text and each KDoc tag underneath * /
    * @param fox A quick brown fox jumps over the lazy dog
    * @return battle between fox and dog
    */
   protected abstract fun foo(Fox fox)
    /**
     * These possibilities include: Formatting of header comments
     *     / * Add a blank line between the comment text and each KDoc tag underneath * /
     * @return battle between fox and dog
     * @throws ProblemException if lazy dog wins
     */
   protected fun bar() throws ProblemException {
       // Some comments / * No need to add a blank line here * /
       var aVar = ...

       // Some comments  / * Add a blank line before the comment * /
       fun doSome()
   }
}

2.1.3 Only one space between the Kdoc tag and content. Tags are arranged in the order.

There should be only one space between the Kdoc tag and content. Tags are arranged in the following order: @param, @return, and @throws.

Therefore, Kdoc should contain the following:

  • Functional and technical description, explaining the principles, intentions, contracts, API, etc.
  • The function description and @tags (implSpec, apiNote, and implNote) require an empty line after them.
  • @implSpec: A specification related to API implementation, and it should let the implementer decide whether to override it.
  • @apiNote: Explain the API precautions, including whether to allow null and whether the method is thread-safe, as well as the algorithm complexity, input, and output range, exceptions, etc.
  • @implNote: A note related to API implementation, which implementers should keep in mind.
  • One empty line, followed by regular @param, @return, @throws, and other comments.
  • The conventional standard "block labels" are arranged in the following order: @param, @return, @throws. Kdoc should not contain:
  • Empty descriptions in tag blocks. It is better not to write Kdoc than waste code line space.
  • There should be no empty lines between the method/class declaration and the end of Kdoc (*/ symbols).
  • @author tag. It doesn't matter who originally created a class when you can use git blame or VCS of your choice to look through the changes history. Important notes:
  • KDoc does not support the @deprecated tag. Instead, use the @Deprecated annotation.
  • The @since tag should be used for versions only. Do not use dates in @since tag, it's confusing and less accurate.

If a tag block cannot be described in one line, indent the content of the new line by four spaces from the @ position to achieve alignment (@ counts as one + three spaces).

Exception:

When the descriptive text in a tag block is too long to wrap, you can indent the alignment with the descriptive text in the last line. The descriptive text of multiple tags does not need to be aligned. See 3.8 Horizontal space.

In Kotlin, compared to Java, you can put several classes inside one file, so each class should have a Kdoc formatted comment (as stated in rule 2.1). This comment should contain @since tag. The right style is to write the application version when its functionality is released. It should be entered after the @since tag.

Examples:

/**
 * Description of functionality
 *
 * @since 1.6
 */

Other KDoc tags (such as @param type parameters and @see.) can be added as follows:

/**
 * Description of functionality
 *
 * @apiNote: Important information about API
 *
 * @since 1.6
 */

2.2 Adding comments on the file header

This section describes the general rules of adding comments on the file header.

2.2.1 Formatting of comments in the file header

Comments on the file header should be placed before the package name and imports. If you need to add more content to the comment, subsequently add it in the same format.

Comments on the file header must include copyright information, without the creation date and author's name (use VCS for history management). Also, describe the content inside files that contain multiple or no classes.

The following examples for Huawei describe the format of the copyright license:
Chinese version: 版权所有 (c) 华为技术有限公司 2012-2020
English version: Copyright (c) Huawei Technologies Co., Ltd. 2012-2020. All rights reserved. 2012 and 2020 are the years the file was first created and the current year, respectively.

Do not place release notes in header, use VCS to keep track of changes in file. Notable changes can be marked in individual KDocs using @since tag with version.

Invalid example:

/**
 * Release notes:
 * 2019-10-11: added class Foo
 */

class Foo

Valid example:

/**
 * @since 2.4.0
 */
class Foo
  • The copyright statement can use your company's subsidiaries, as shown in the below examples:
    Chinese version: 版权所有 (c) 海思半导体 2012-2020
    English version: Copyright (c) Hisilicon Technologies Co., Ltd. 2012-2020. All rights reserved.

  • The copyright information should not be written in KDoc style or use single-line comments. It must start from the beginning of the file. The following example is a copyright statement for Huawei, without other functional comments:

/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2012-2020. All rights reserved.
 */

The following factors should be considered when writing the file header or comments for top-level classes:

  • File header comments must start from the top of the file. If it is a top-level file comment, there should be a blank line after the last Kdoc */ symbol. If it is a comment for a top-level class, the class declaration should start immediately without using a newline.
  • Maintain a unified format. The specific format can be formulated by the project (for example, if you use an existing opensource project), and you need to follow it.
  • A top-level file-Kdoc must include a copyright and functional description, especially if there is more than one top-level class.
  • Do not include empty comment blocks. If there is no content after the option @apiNote, the entire tag block should be deleted.
  • The industry practice is not to include historical information in the comments. The corresponding history can be found in VCS (git, svn, etc.). Therefore, it is not recommended to include historical data in the comments of the Kotlin source code.

2.3 Comments on the function header

Comments on the function header are placed above function declarations or definitions. A newline should not exist between a function declaration and its Kdoc. Use the preceding <<c2.1,KDoc>> style rules.

As stated in Chapter 1, the function name should reflect its functionality as much as possible. Therefore, in the Kdoc, try to describe the functionality that is not mentioned in the function name. Avoid unnecessary comments on dummy coding.

The function header comment's content is optional, but not limited to function description, return value, performance constraints, usage, memory conventions, algorithm implementation, reentrant requirements, etc.

2.4 Code comments

This section describes the general rules of adding code comments.

2.4.1 Add a blank line between the body of the comment and Kdoc tag-blocks.

It is a good practice to add a blank line between the body of the comment and Kdoc tag-blocks. Also, consider the following rules:

  • There must be one space between the comment character and the content of the comment.
  • There must be a newline between a Kdoc and the presiding code.
  • An empty line should not exist between a Kdoc and the code it is describing. You do not need to add a blank line before the first comment in a particular namespace (code block) (for example, between the function declaration and first comment in a function body).

Valid Examples:

/**
 * This is the short overview comment for the example interface.
 *
 * @since 1.6
 */
 public interface Example {
    // Some comments  /* Since it is the first member definition in this code block, there is no need to add a blank line here */
    val aField: String = ...
                     /* Add a blank line above the comment */
    // Some comments
    val bField: String = ...
                      /* Add a blank line above the comment */
    /**
     * This is a long comment with whitespace that should be split in
     * multiple line comments in case the line comment formatting is enabled.
     *                /* blank line between description and Kdoc tag */
     * @param fox A quick brown fox jumps over the lazy dog
     * @return the rounds of battle of fox and dog
     */
    fun foo(Fox fox)
                      /* Add a blank line above the comment */
     /**
      * These possibilities include: Formatting of header comments
      *
      * @return the rounds of battle of fox and dog
      * @throws ProblemException if lazy dog wins
      */
    fun bar() throws ProblemException {
        // Some comments  /* Since it is the first member definition in this range, there is no need to add a blank line here */
        var aVar = ...

        // Some comments  /* Add a blank line above the comment */
        fun doSome()
    }
 }
  • Leave one single space between the comment on the right side of the code and the code. If you use conditional comments in the if-else-if scenario, put the comments inside the else-if branch or in the conditional block, but not before the else-if. This makes the code more understandable. When the if-block is used with curly braces, the comment should be placed on the next line after opening the curly braces. Compared to Java, the if statement in Kotlin statements returns a value. For this reason, a comment block can describe a whole if-statement.

Valid examples: