Skip to content

Coding Standards

Taufik Nurrohman edited this page Sep 7, 2022 · 31 revisions

Mecha mostly contains CSS, HTML, JavaScript, JSON, PHP, and YAML files. Mecha is open-source where anyone can contribute to the code. The purpose of these coding standards is to make the source code appear as if it was written by one person.

The core application will probably be much more restrictive so there’s not much you can contribute to it. However, Mecha can be enriched by extensions and layouts which you are free to develop without my intervention.

You will probably have a popular extension and/or layout in the future and it is not impossible that I will also contribute to your code. And when I write the code, it will most likely look like this.

CSS

TL;DR:

  1. Braces with control statement.
  2. Space after property colon.
  3. Space before control statement.
aaa {
  bbb: ccc;
  ddd: eee;
}

@aaa (bbb: ccc) {
  ddd {
    eee: fff;
  }
}

Color

Always use HEX color code to declare a solid color, and RGBA color code to declare a color with opacity. Always use lower-case letter, and use the shortest color code version:

button {
  background-color: #b4d455;
  border-color: rgba(255, 255, 0, .5);
  color: #def;
}

Declaration

Sort declarations alphabetically, unless you want to override the previous declaration:

button {
  border: 1px solid #000;
  border-top-width: 0;
  margin-left: 1px;
  margin-right: 1px;
}

Fraction Value

Always remove zero prefix in fractions:

button {
  margin: 1.25em;
  padding: .25em .5em;
}

Indentation

Use two <Space>s to represent single indent:

@media print {
  .hidden-print {
    display: none;
  }
}

Key-Value Pairs

Add a <Space> after colon:

@media (max-width: 1024px) {
  body {
    font-size: 80%;
  }
}f

Pseudo Class

Pseudo classes don’t have to be in alphabetical order. In common, they will be ordered like this:

button {}
button:focus {}
button:hover {}
button:active {}

But you can also order them like this to make sure that focus state will remain as-is when hovered:

button {}
button:hover {}
button:focus {}
button:active {}

Be sure to put disabled states at the end, so it will be easier to override other states:

input {}
input:hover {}
input:focus {}
input:active {}

input:valid {}
input:invalid {}

input:read-only {}
input:disabled {} 

Selector

Add a line-break after comma, sort selectors alphabetically:

h1,
h2,
h3,
h4,
h5,
h6 {}

Use single quote for attribute selector value, and for non-empty string value. Use double quote for empty string value (except for @charset rule value that must be using double quote):

@charset "utf-8";
@import url('./style.css');
[rel='nofollow']::before {
  background-image: url('./image.jpg');
  content: "";
}

Semi-Colon

Ensure semi-colon at the end of declaration:

body {
  margin-bottom: 1px;
  margin-top: 1px;
}

Zero Value

Remove unit in zero values except 0% and 0deg:

body {
  margin: 0 0 1px 1px;
  margin-top: 0%;
}

HTML

Attribute and Name

Use lower-case letter, sort attributes alphabetically:

<input class="input" id="input-0" name="input-0" type="text">

Attribute’s Value

Always use double quote, even on empty value:

<img alt="" src="./image.jpg">

Exception for attribute that contains JSON or JavaScript commands:

<div class="gallery" data-state='{"caption":true,"overlay":true}'></div>

Boolean Attribute

Always remove values:

<button disabled type="submit">

Indentation

Use two <Space>s to represent single indent:

<ul>
  <li></li>
  <li></li>
</ul>

Void Element

Do not add / before > in void elements:

<img alt="" src="/photo.jpg">
<hr>

JavaScript

TL;DR:

  1. Braces with control statement.
  2. Space after object property colon.
  3. Space before control statement.
function aaa() {}

if (bbb) {
    ccc();
} else {
    ddd();
}

switch (eee) {
    case fff:
    case ggg:
        hhh();
    break;
}

try {
    iii();
} catch (jjj) {
    kkk();
}

while (lll) {
    mmm();
}

const nnn = function () {};

Comparison

Use Yoda notation in equal/not-equal comparison to quickly detect typos:

if (-1 !== pairs.indexOf(pair)) {}

Function

Use parseFloat() and parseInt() sparingly, simply prefix your variable with a + sign if you know that the value will always be a valid number:

const value = input.value;

// :(
console.log(parseFloat(value));

// :)
console.log(+value);

Increment/Decrement

Prefers pre-increment/decrement over post-increment/decrement; always cache the data length before iteration using for loop:

// :(
for (let i = 0, j = data.length; i < j; i++) {}

// :)
for (let i = 0, j = data.length; i < j; ++i) {}

Indentation

Use four <Space>s to represent single indent:

function foo(bar = 'baz') {
    return bar ?? 'qux';
}

Operator

Always add a <Space> around operators:

let value = a + b * (1 / (c - 2));
let value = a + 'asdf' + b;

value += 'asdf';
value += 'asdf';

String

Use single quote for non-empty string or for string that contains " character, so you don’t have to escape. Use double quote for empty string or for string that contains ' character, so you don’t have to escape:

'asdf'
"asdf's"
""
'"asdf"'
'"asdf\'s"'
"'asdf'"
"'asdf\"s'"

Only use backtick-style string for templating. E.g. to write a block of CSS and HTML snippet in a JavaScript file.

Variable

Join multiple variables, unless its indentation looks ugly such as when used with const, or when making undefined variables. Sort them aplhabetically where possible:

let bar = 1,
    baz = 2,
    x, y, z;

const bar = 1;
const baz = 2;

PHP

TL;DR:

  1. Braces with control statement.
  2. Space after property colon.
  3. Space before control statement.
function aaa() {}

if ($bbb) {
    ccc();
} else {
    ddd();
}

switch ($eee) {
    case $fff:
    case $ggg:
        hhh();
    break;
}

try {
    iii();
} catch (Throwable $jjj) {
    kkk();
}

while ($lll) {
    mmm();
}

const nnn = static function () {};

Class

Order constants, methods and properties alphabetically, including the visibility state:

class Foo implements A, B, C {
    private function _internal() {}
    public function get() {}
    public function let() {}
    public function set() {}
    public function __construct() {}
    public static function __callStatic() {}
}

Comparison

Use Yoda notation in equal/not-equal comparison to quickly detect typos:

if (false !== strpos($foo, $bar)) {}

Function

Use is_dir or is_file instead of file_exists:

// :(
if (file_exists($path)) {}

// :)
if (is_file($path)) {}

If you just want to check whether a path does exist, use stream_resolve_include_path instead of file_exists:

// :(
if (file_exists($path)) {}

// :\
if (is_dir($path) || is_file($path)) {}

// :)
if (stream_resolve_include_path($path)) {}

Use strtr instead of str_replace:

// :(
echo str_replace('a', 'b', $value);
echo str_replace('a', "", $value);
echo str_replace(['a', 'b'], ['c', 'd'], $value);
echo str_replace(['aa', 'bb'], ["", ""], $value);

// :)
echo strtr($value, 'a', 'b');
echo strtr($value, ['a' => ""]);
echo strtr($value, 'ab', 'cd');
echo strtr($value, [
    'aa' => "",
    'bb' => ""
]);

If you know that a path exists, use stream_resolve_include_path to normalize the path instead of realpath:

$path = stream_resolve_include_path($path);

If you just want to escape/un-escape HTML, use htmlspecialchars and htmlspecialchars_decode instead of htmlentities:

// :(
echo '<input value="' . htmlentities($value) . '">';

// :)
echo '<input value="' . htmlspecialchars($value) . '">';

Prefers static anonymous function if $this context is not used:

$map = static function (array $array, callable $fn) {
    foreach ($array as &$v) {
        $v = $fn($v);
    }
    unset($v);
    return $array;
};

Do not use count() to detect empty array, and strlen() to detect empty string. These should be enough:

if (!$array) {}
if ("" === $string) {}

Increment/Decrement

Prefers pre-increment/decrement over post-increment/decrement; always cache the data length before iteration using for loop:

// :(
for ($i = 0, $j = count($data); $i < $j; $i++) {}

// :)
for ($i = 0, $j = count($data); $i < $j; ++$i) {}

Indentation

Use four <Space>s to represent single indent:

function foo(string $bar = 'baz') {
    return $bar ?? 'qux';
}

Language Construct

Use isset() sparingly:

// :(
echo '<' . $m[1] . (isset($m[2]) ? $m[2] : "") . '>';

// :)
echo '<' . $m[1] . ($m[2] ?? "") . '>';

Do not use empty() to detect empty string. Use empty() as a shortcut for !(isset($var) && $var):

$name = $_POST['name'] ?? "";

// :(
// If `$name` contains `0` string, this will return `true`
if (empty($name)) {}

// :)
// Use `trim()` as a guard, since even a single space is not considered empty
if ("" !== trim($name)) {}

// :)
// You don’t have to use `empty()` to detect empty array. This should be enough!
if (!$array) {}

Use empty() and isset() only to detect undefined variable as a whole.

Markup

Always think of PHP statements as other kind of HTML markup. They should get the same indentation treatment as the surrounding HTML markup.

<!-- :( -->
<h1>
  <a href="<?= $link; ?>"><?= $title; ?></a>
</h1>

<!-- :) -->
<h1>
  <a href="<?= $link; ?>">
    <?= $title; ?>
  </a>
</h1>
<!-- :( -->
<?php foreach ($pages as $page): ?>
<article>
  <h2><?= $page->title; ?></h2>
  <div><?= $page->content; ?></div>
</article>
<?php endforeach; ?>

<!-- :) -->
<?php foreach ($pages as $page): ?>
  <article>
    <h2>
      <?= $page->title; ?>
    </h2>
    <div>
      <?= $page->content; ?>
    </div>
  </article>
<?php endforeach; ?>

Operator

Always add a <Space> around operators:

$value = $a + $b * (1 / ($c - 2));
$value = $a . 'asdf' . $b;

$value .= 'asdf';
$value .= 'asdf';

Output

Use echo or not at all. Always add a semi-colon at the end of declaration, even if you are using the <?= syntax:

<h1>
  <?php

  $title  = do_task(1);
  $title .= do_task(2);
  $title .= do_task(3);

  echo $title;

  ?>
</h1>
<h1>
  <?= $title; ?>
</h1>

String

Use single quote for non-empty string or for string that contains " character, so you don’t have to escape. Use double quote for empty string or for string that contains ' character, so you don’t have to escape:

'asdf'
"asdf's"
""
'"asdf"'
'"asdf\'s"'
"'asdf'"
"'asdf\"s'"

Only use HEREDOC-style string for templating. E.g. to write a block of CSS and HTML snippet in a PHP region.

Always store HEREDOC string in a variable to overcome our first home-made PHP minifier bug.

// :(
echo implode("\n", ['<b></b>', <<<HTML
  <div></div>
HTML
]);

// :)
$content = <<<HTML
  <div></div>
HTML;
echo implode("\n", ['<b></b>', $content]);

Variable

Combine all variables with the same predefined value into one line. Sort them alphabetically:

$current = $next = $prev = "";

SQL

Indentation

Use two <Space>s to represent single indent:

SELECT
  *
FROM
  "page"
WHERE
  "id" = 1
AND
  "parent" IS NULL
;

Key-Value Pairs

Always add a <Space> around operators:

SELECT "content", "title" FROM "page" WHERE "name" = 'lorem-ipsum';

Variable

TODO

YAML

Indentation

Use two <Space>s to represent single indent:

foo:
  bar: 1
  baz: 2
  qux: 3

Do not indent sequence list:

# :(
foo:
  - bar
  - baz
  - qux

# :)
foo:
- bar
- baz
- qux

Key-Value Pairs

Enclose values containing special characters in quotation marks:

foo: Bar Baz
qux: 'foo@bar.baz'

String

Use single quote for non-empty string or for string that contains " character, so you don’t have to escape. Use double quote for empty string or for string that contains ' character, so you don’t have to escape:

foo:
  bar: 'http://example.com'
  baz: ""

To be continued… 🧠