~wegtam/smederee

~wegtam/smederee/CODINGSTYLE.md
 ..

Coding Style Guidelines

This document tries to describe the coding style to make it easier for everyone to contribute.

1. Language

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

2. General style and tooling

  1. The tools Scalafmt and Scalafix MUST be used to format the code.
  2. The configuration file for Scalafmt SHALL be the file .scalafmt.conf in the project root and SHALL be based upon the preset defaultWithAlign using the runner dialect scala3 of Scalafmt with some additions.
  3. The configuration file for Scalafix SHALL be the file .scalafix.conf in the project root and SHALL be configured to organize (rewrite) import rules.
  4. In the rare case of formatting interfering with code functionality, it is RECOMMENDED to disable automatic code formatting for the affected parts of the code only.
  5. An .editorconfig file in the project root MUST exist and SHALL contain the necessary settings.
  6. All names (e.g. variables, functions, etc.) SHALL use the camel case notation and MUST start with a lowercase letter. Please follow these guidelines for naming things:
    1. Names SHOULD NOT be ambiguous or unclear.
    2. Names MUST NOT use abbreviations that could be confusing.
    3. Names MUST NOT use hungarian notation i.e. never prefix or suffix names with their types like for example stringNameOption.
    4. Names SHOULD NOT consist of more than 4 words.
    5. Similar names SHOULD be used for similar objects in the code.

2.1. Additions to the defaultWithAlign preset

  1. No unicode characters or literals are allowed in the source code e.g. SHALL be written as => or as <- and so on.
  2. Rewrite rules SHOULD exist in the configuration to rewrite common unicode characters to their ASCII counterparts.
  3. Each level of indentation (for Scala code) MUST be 4 space characters.
    val foo = ???
    val bar = foo match {
       case something => ???
       case _ => ???
    }
  4. The maximum line length in the source code SHOULD not be longer than 120 characters. There MAY be rare cases where exceptions are allowed.
  5. Imports SHOULD be written after the following guidelines and automatically applied by running Scalafix:
    1. Imports from Java core (e.g. import java.time.*) MUST be written (grouped) first, followed by a blank line.
    2. Imports from the Scala core (e.g. import scala.util.Failure) MUST be written (grouped) last, prefixed by a blank line.
    3. Other imports SHOULD be grouped in between the Java and Scala core imports.
    4. Within the other imports test framework imports (e.g. munit. or org.scalacheck.) SHOULD be put last and grouped.
    5. Multiple imports from the same package in curly braces SHALL be rewritten as single import per line. This intends to reduce possible merge conflicts by import changes. The option groupedImports = Explode for Scalafix is intended to rewrite affected code automatically.
    6. Imports SHALL be sorted by their ASCII names. This SHOULD be enforced by the importsOrder setting of Scalafix.
  6. Redundant braces SHALL be removed (enforced by rewrite.rules including RedundantBraces).
  7. Redundant parentheses SHALL be removed (enforced by rewrite.rules including RedundantParens).
  8. Configuration files for sbt (e.g. *.sbt) MUST NOT be formatted via Scalafmt.

TODO: Describe danglingParentheses.preset = true

2.2 Twirl Templates

We are using the Twirl templating engine to render templates which reside under src/main/twirl folders. The most common use case is to render HTML output which is indicated by the .scala.html file extension. However others are possible for example .scala.txt for rendering plain text emails.

  1. Each level of indentation MUST be 2 space characters.
  2. There is no maximum line length constraint for Twirl templates.
  3. Template tags (e.g. HTML tags) following within a control structure (e.g. @for or @defining) MAY not be indented to avoid too deeply nested code.
  4. Imports SHOULD be sorted by their ASCII names.
  5. Template parameters MUST be broken up upon multiple lines with the parentheses on separate lines if there are 3 or more parameters or they are curried. This rule MAY be broken if this generates undesired whitespace characters in the rendered output.
    @(
     a: A,
     b: Option[B],
     c: Either[A, B]
    )(
     d: D,
     e: Option[E]
    )

3. Build configuration file style (sbt)

  1. Build definition and settings SHOULD be formatted to have one setting per line if possible.
  2. Dependencies SHALL be defined in the lazy val library block inside the build.sbt file, following these rules:
    1. The version number SHALL be defined as a string in the object Version having a name clearly identifying the dependency e.g. val cats = "2.8.0".
    2. The dependencies SHALL be defined as values having meaningful names and they should be vertically aligned like in the following example:
      val catsCore   = "org.typelevel" %% "cats-core"   % Version.cats
      val catsEffect = "org.typelevel" %% "cats-effect" % Version.catsEffect
  3. Plugins for sbt SHALL be defined inside the project/plugins.sbt file and be vertically aligned like in the following example:
    addSbtPlugin("io.spray"      % "sbt-revolver" % "0.9.1")
    addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.10.4")
    addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6")
  4. Build definition and settings files (i.e. build.sbt and project/plugins.sbt) SHALL not be formatted automatically via scalafmt.

4. SQL

The application contains database migrations files and SQL commands / queries for these the following rules apply:

  1. All SQL keywords MUST be written in uppercase.
  2. All table, column and variable names MUST be written in lowercase.
  3. All table and column names MUST NOT be quoted e.g. do not use [table].[column] or "table"."column".
  4. Created tables MUST be described using the COMMENT command. This also applies to each column of a table.
  5. A COMMENT statement SHALL NOT be wrapped even if it exceeds 80 characters.
  6. A CREATE TABLE statement MUST be split over multiple lines to have the CREATE TABLE, the starting parenthesis, each column and constraint and the closing parenthesis on a separate line.
  7. A CONSTRAINT line MAY be wrapped to span multiple lines if it exceeds 80 characters.
  8. The parameters for the column definitions SHOULD be vertically aligned.
  9. A created table MUST NOT use OIDs i.e. the CREATE TABLE statement MUST end with WITH (OIDS=FALSE);.

An example for a database migration SQL file looks like this:

CREATE TABLE sessions
(
  id         VARCHAR(32)              NOT NULL,
  uid        UUID                     NOT NULL,
  created_at TIMESTAMP WITH TIME ZONE NOT NULL,
  updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
  CONSTRAINT sessions_pk     PRIMARY KEY (id),
  CONSTRAINT sessions_fk_uid FOREIGN KEY (uid)
    REFERENCES accounts (uid) ON UPDATE CASCADE ON DELETE CASCADE
)
WITH (
  OIDS=FALSE
);

COMMENT ON TABLE sessions IS 'Keeps the sessions of users.';
COMMENT ON COLUMN sessions.id IS 'A globally unique session ID.';
COMMENT ON COLUMN sessions.uid IS 'The unique ID of the user account to whom the session belongs.';
COMMENT ON COLUMN sessions.created_at IS 'The timestamp of when the session was created.';
COMMENT ON COLUMN sessions.updated_at IS 'The session ID should be re-generated in regular intervals resulting in a copy of the old session entry with a new ID and the corresponding timestamp in this column.';