• Home
  • Blog
  • How We Handle Our Code Standards

How We Handle Our Code Standards

Many teams benefit from setting an expected coding standard. With this comes an array of benefits which help ongoing development of projects, such as:

  • Ease of maintenance
  • Avoid wheel re-invention
  • IDE integrations
  • Bug reduction
  • ISO certifcation support

But as we're here to talk about how Selesti handles them, I wont go into detail (hopefuly you're already sold on the idea of them!) but below is a small guide to getting started with implementing your standards of choice using IDE integrations and GIT Hooks.

 

Javascript

Firstly we're going to look at Javascript. Our tool of choice for linting is ESLint, which we ended up choosing this over alternatives like JSHint due to its more modern take on things like ES2015 support.

We typically have 4 points of checking (sometimes we don't use them all — we make this decision based on the project), and this is pretty consistent across all the languages we use. These are:

  • Task Runners (Gulp)
  • Text Editor / IDE
  • GIT Hooks
  • CI / Deployment System

To start with, our task running, IDE and GIT hooks all use the eslint cli. To install this you'll need node/npm set up on your machine with the node_modules linked to your PATH correctly.

If you do not have eslint already installed (try typing `eslint` into your terminal and see what happens), simply run `npm install eslint -g` and you should see it starting to install!

Once you've done that you should be able to type `eslint` and it will wait for input from you. You can just cancel that as it's now set up, and you're ready to start using it as a part of your development process.

The first method of integration we'll look at is IDE. This does require all your team to be on board with using IDEs with plugins, but if they choose not to then they'll need to make sure their GIT Hooks are set up :) or the CI is set up!

The 2 IDEs we'll be looking at are Sublime 3 and Atom.

ESLint with Atom

Visit the webpage https://atom.io/packages/linter-eslint and it will give you some basic commands on how to install it and configure it. Once it's installed you can view the Packages section of Atom and configure it a little more.

We typically base our rule set off the eslint recommended set, with some very minor rule tweaks, so we create a ".eslintrc.yml" with the following content.

*note* With ESLint:Recommended, it only enables default "possible error prevention, best practice and variables" and if you want to use their defaults for the rest, you need to enable them individually, using the defaults, which is how you end up with a similar config to ours.


extends: "eslint:recommended"

parserOptions:
  ecmaVersion: 6
  sourceType: module

env:
  browser: true
  node: true

rules:
  no-undef: 0
  no-console: 0
  quotes: ["error", "single"]
  curly: ["error", "all"]
  default-case: "error"
  vars-on-top: "error"
  one-var: ["error", "always"]
  brace-style: ["error", "1tbs"]
  camelcase: ['error', {properties: "never"}]
  comma-dangle: ["error", {"after": true, "before": false}]
  comma-spacing: ["error", {"after": true, "before": false}]
  comma-style: ["error", "last"]
  spaced-comment: ["error", "always"]
  space-unary-ops: "error"
  space-infix-ops: "error"
  space-in-parens: ["error", "never"]
  space-before-blocks: "error"
  semi-spacing: ["error", {"before": false, "after": true}]
  keyword-spacing: ["error", {"after": true, "before": true}]
  no-trailing-spaces: "error"
  no-unneeded-ternary: "error"
  no-multiple-empty-lines: ["error", {"max": 1}]
  newline-per-chained-call: ["error", {"ignoreChainWithDepth": 2}]
  newline-before-return: "error"
  newline-after-var: ["error", "always"]
  multiline-ternary: ["error", "never"]
  max-params: ["error", 3]
  max-nested-callbacks: ["error", 3]
  lines-around-directive: ["error", "always"]
  lines-around-comment: ["error", {"beforeBlockComment": true}]
  linebreak-style: ["error", "unix"]
  line-comment-position: ["error", {"position": "above"}]
  key-spacing: ["error", {"beforeColon": false, "afterColon": true, "mode": "strict"}]
  indent: ["error", 4]
  func-style: ["error", "expression"]
  func-call-spacing: ["error", "never"]
  eol-last: ["error", "always"]
  consistent-this: ["error", "that"]


You can of course tweak these, but as a whole the defaults for eslint are near identical to PHPs PSR-1 and PSR-2 so we stick to them to make it easier to get used to one style.

Once we've got our config in place, we register it within the `package.json` - it's not compulsory to do this, but the hint helps plugins a bit better! We simply add (if you do not have a package.json, you can create one by typing `npm init` and following the instructions):

"eslintConfig": "./.eslintrc.yml"

Now you should be good to go! If you open up one of your javascript files, and you will start to see the IDE hinting at potential code violation issues and syntax errors.

A crude example can be seen below.

ESLint with Sublime

The steps to getting Sublime set up are almost identical: make sure your rule file is set up, and that it's registered. The main difference is the plugin that you'll be using. Firstly you'll need to make sure you have the "SublimeLinter" framework installed, you can read how to install it http://sublimelinter.readthedocs.io/en/latest/installation.html it should take no more than 5 minutes!

Once that is set up you can start installing linters! We'll be using https://github.com/roadhump/SublimeLinter-eslint and you can follow the instructions here. If you're comfortable with Package Manager, you can just search eslint and pick the SublimeLinter3 package.

Again, you should be ready to go. Sublime is not as pretty as Atom, but still gives you the same information. You get a little marker and an explanation in the status bar:

GIT Hooks

So it's all good and well your team using IDE plugins, but what happens if they ignore them? Or they choose not to use them? You still do not want incorrect code pushed to your repos — and this is why we get the GIT Hooks involved.

The first part of this will assume you have a composer project set up on a non-Windows based machine, however if you do not then the setup will be a little more manual.

If you happen to have Linux/OSX and composer you can set up a script within your `composer.json` that looks similar to  this:


"githooks": [
    "rm -rf $(pwd)/.git/hooks",
    "ln -s $(pwd)/git-hooks/ $(pwd)/.git/hooks",
    "echo Hooks Copied to $(pwd)/.git/hooks"
]

This means when we run `composer githooks` later it will link your project-specific hooks into the git repo hook folder.

If you're not using composer but are on OSX, you can just run the commands above manually once the hooks have been created.

If you're on Windows, then you can either find out how to symlink them yourself or just manually copy them into `project/.git/hooks

Moving on! So the hook itself gets built up over time, and will have more than this in it, but at a minimum it will start to look similar to this.

*note* You need to replace the path with the glob pattern to your javascript files. You can see below what it looks like for our Laravel files (if your files are shared with vendor files you'll need to read the eslint documentation for how to exclude files).

This is what your hook file might start to look like in a minute.


#!/bin/sh

# Javascript Code Style Checking
eslint "./resources/assets/js/**/*.js" &> /dev/null

if [ $? -ne 0 ]; then
    echo "\033[0;31m";
    echo "###########################################################################";
    echo "######### ESLINT VIOLATION DETECTED :: ESLINT VIOLATION DETECTED ##########";
    echo "###########################################################################";
    echo "\033[0m";
    echo "run the below command to see the output";
    echo "\033[1;36m";
    echo "eslint './resources/assets/js/**/*.js'";
    echo "";
    exit 1;
fi

What this will do is: if the command within the hook is run and it returns an exit code then we know it failed and a violation has been made.

So to get this working we need to create a folder called "git-hooks" within the project and add a file called `pre-push` and to make sure it's executable `chmod +x pre-push` with the above content in it, you'll need to adjust the paths to where your files are stored.

The reason we dont use GIT Templates is because the projects are so different that it would not make sense, so this way the developer can see the hook folder and can install them the best way for their system.

To verify it is working you can run `./git-hooks/pre-push` from your project root. If not a lot happens, then you're good! Otherwise it will say it failed, and you can then run the command it suggests to see the output (the reason we pass it to /dev/null is because some developers use SourceTree which crashes when a large output is present, so we mute it until it's needed). There is also a note at the bottom of this post for a "fix" if you use sourcetree which has PATH issues.

When you try and push, if everything went to plan, you should get an error (assuming your code has errors of course!)

Once you're happy that it's working as expected, you'll be able to start adding other languages. Next up is our SCSS set up!

SCSS/SASS

This is all based off the same as the javascript set up, however it uses slightly different commands and files.

To start with make sure you have both `node-sass` and `sass-lint` installed. If you do not then run `npm install node-sass sass-lint -g` then try running `sass-lint` on your terminal.

Very similar to the ESLint set up, we'll need to create a `.sass-lint.yml` file which will store our rules, and we'll need to register the file within our `package.json` using


"sasslintConfig": "./.sass-lint.yml"

Once it's registered you can add your rule set. The config we use is pretty default, but we tweak the tab length to 4, nest depth to 5, and ordering to smacss. You can copy this config and try it out.


options:
  formatter: stylish
files:
  include: '**/*.s+(a|c)ss'
rules:
  # Extends
  extends-before-mixins: 1
  extends-before-declarations: 1
  placeholder-in-extend: 1

  # Mixins
  mixins-before-declarations: 1

  # Line Spacing
  one-declaration-per-line: 1
  empty-line-between-blocks: 1
  single-line-per-selector: 1

  # Disallows
  no-attribute-selectors: 0
  no-color-hex: 0
  no-color-keywords: 1
  no-color-literals: 0
  no-combinators: 0
  no-css-comments: 1
  no-debug: 1
  no-disallowed-properties: 0
  no-duplicate-properties: 1
  no-empty-rulesets: 1
  no-extends: 0
  no-ids: 1
  no-important: 0
  no-invalid-hex: 1
  no-mergeable-selectors: 1
  no-misspelled-properties: 1
  no-qualifying-elements: 1
  no-trailing-whitespace: 1
  no-trailing-zero: 1
  no-transition-all: 1
  no-universal-selectors: 0
  no-url-protocols: 0
  no-vendor-prefixes: 1
  no-warn: 1
  property-units: 0
  no-url-domains: 0

  # Nesting
  force-attribute-nesting: 1
  force-element-nesting: 1
  force-pseudo-nesting: 1

  # Name Formats
  class-name-format: 1
  function-name-format: 1
  id-name-format: 0
  mixin-name-format: 1
  placeholder-name-format: 1
  variable-name-format: 1

  # Style Guide
  attribute-quotes: 1
  bem-depth: 0
  border-zero: 1
  brace-style: 1
  clean-import-paths: 1
  empty-args: 1
  hex-length: 1
  hex-notation: 1
  indentation:
    - 1
    -
      size: 4
  leading-zero: 1
  nesting-depth:
    - 1
    -
      max-depth: 5
  property-sort-order:
    - 1
    -
      order: 'smacss'
  pseudo-element: 1
  quotes: 1
  shorthand-values: 1
  url-quotes: 1
  variable-for-property: 1
  zero-unit: 1

  # Inner Spacing
  space-after-comma: 1
  space-before-colon: 1
  space-after-colon: 1
  space-before-brace: 1
  space-before-bang: 1
  space-after-bang: 1
  space-between-parens: 1
  space-around-operator: 1

  # Final Items
  trailing-semicolon: 1
  final-newline: 1

Now you've got a rule set in place, you can start to validate against it!

SASS Lint with Atom

Same as before (if you skipped to here, read the Javascript setup as the process is the same, simply install https://atom.io/packages/linter-sass-lint and you should be away!

SASS Lint with Sublime

Same as before (notice a pattern?) Install https://github.com/skovhus/SublimeLinter-contrib-sass-lint

 

GIT Hooks

If you've jumped here, you'll need to follow the initial git hooks setup first, so open up your existing (or new) `pre-push` file and add the following (this is a little different due to the way sass-lint works) — remember to update the path to your project scss files


# SCSS Code Style Checking
if [[ $(sass-lint -c ./.sass-lint.yml './resources/assets/sass/**/*.scss' -v) ]]; then
    echo "\033[0;31m";
    echo "###########################################################################";
    echo "########## SCSS VIOLATION DETECTED :: SCSS VIOLATION DETECTED #############";
    echo "###########################################################################";
    echo "\033[0m";
    echo "run the below command to see the output";
    echo "\033[1;36m";
    echo "sass-lint -c ./.sass-lint.yml './resources/assets/sass/**/*.scss' -v -q";
    echo "";
    exit 1;
fi

Hopefuly this is all starting to make sense now, and you can see there is a general pattern emerging,

PHP Linting

That being said, the final steps I've simplified. However there are 2 sections to how we handle our PHP — we check both against PHPs built in linter for syntax issues, and we also test the code styling against PSR1/2, so I'll split them out.

I'll go over PHP Linting first, before code styling.

PHP Lint with Atom

Install https://atom.io/packages/linter-php 

PHP Lint with Sublime

Install https://github.com/SublimeLinter/SublimeLinter-php 

*note* Once these are installed, there is no guarantee it will work out of the box. First you'll need make sure the version of PHP you have installed on your machine is newer than php 5.4. If it's not, then I recommend you download a copy for your OS and store it somewhere.

*note 2* You need to make sure you have the PATH to your php bin. If your environment is setup nicely and you can access `php -v`from the terminal, you're safe! Otherwise you'll need to find where the binary for the PHP install is and either add it to your PATH or hard code it in your IDE. If you do not have the PHP install in your PATH then if you adjust the settings in the plugin of choice to where it is stored, you should be able to run it.

GIT Hooks

Continuing inside the `pre-push` hook we've been using, we'll add a new section with the following (change the path you want to scan):


# PHP Linting
PHPAppPath=./app

for file in `find $PHPAppPath -type f -name "*.php"` ; do
    RESULTS=`php -l $file`

    if [ "$RESULTS" != "No syntax errors detected in $file" ] ; then
        echo "\033[0;31m";
        echo "###########################################################################";
        echo "######### PHP SYNTAX ERROR DETECTED :: PHP SYNTAX ERROR DETECTED ##########";
        echo "###########################################################################";
        echo "\033[0m";
        echo "run the below command to see the output";
        echo "\033[1;36m";
        echo "php -l $file";
        echo "";
        exit 1;
    fi
done

You can test it out by doing something like removing a `;` from your code and running `./git-hooks/pre-push` and all should be good!

PHP Code Styling

The last thing is to ensure the code styling adhears to PSR1/2 .

For a super simple basic introduction to PSR, it stands for "Proposed Standard Recommendations" and gets collaboratively drafted by the community and those around the PHP-FIG who then agree and release it. You can read more about PHP-FIG on their website: http://www.php-fig.org/ 

Additionally you can read about the two standards we'll be using here: http://www.php-fig.org/psr/psr-1/ and http://www.php-fig.org/psr/psr-2/

There's not much to them, it's mainly things like conventions for naming methods and brace positioning.

Now that you've obviously memorised the standards, we'll start our familiar steps again:

PHPCS for Atom

Install https://atom.io/packages/linter-phpcs 

PHPCS for Sublime

Install https://github.com/benmatselby/sublime-phpcs 

GIT Hooks

One of the differences here is the way you install PHPCS, which is a php library called CodeSniffer. You can read more about it: https://github.com/squizlabs/PHP_CodeSniffer 

You'll need to pick a method of installing if you do not already have it.

You can see if its globally installed by typing `phpcs` or locally installed by`./vendor/bin/phpcs` and if it starts to take input then it's working!

You might find it easiest to install it globally by first installing Composer on your system, then making sure the global binary path is set up. This should include something like `$HOME/.composer/vendor/bin`

Once you're satisfied that your PATH is set up correctly, you can install PHPCS by running `composer global require "squizlabs/php_codesniffer=*"`then running `phpcs` again. If it still cant find it, try a fresh terminal session, and if that fails go back to making sure your PATH is set up correctly :)

Additionally if you're using composer on your project anyway, you can just include it in your project by running `composer require "squizlabs/php_codesniffer=*` then you should get the local version installed. If you're having trouble with your PATH this might be the best way to go.

Once it's all working and you can run phpcs, you can set up the hook in the same `pre-push` folder, remembering to change the paths:


# PHP Code Style Checking
./vendor/bin/phpcs --standard=PSR2 --warning-severity=8 ./app &> /dev/null

if [ $? -ne 0 ]; then
    echo "\033[0;31m";
    echo "###########################################################################";
    echo "########## PHPCS VIOLATION DETECTED :: PHPCS VIOLATION DETECTED ###########";
    echo "###########################################################################";
    echo "\033[0m";
    echo "run the below command to see the output";
    echo "\033[1;36m";
    echo "./vendor/bin/phpcs --standard=PSR2 --warning-severity=8 ./app";
    echo "";
    exit 1;
fi

As you can see we're running a local version of phpcs. This is because occasionally we run this on a CI like DeployBot, so having all the dependencies bundled with the project helps!

The Rundown

So now everything should be set up and working a treat, you can run the scripts on demand by doing `./git-hooks/pre-push` in your terminal, or by setting up a composer script.

So the theory now is, that developers in your team will be able to get visual feedback from their IDE regarding potential errors so they can fix them as they work, then the GIT Hooks will prevent code violations being pushed to your central repo.

FAQ

  • Why not use Gulp/Grunt to check?
    • You can! In fact we do for certain projects, however over time as a project grows, we've noticed that the task runners slow down by having lots of tasks, so by putting the usage into the IDE instead, it keeps our sass/js/php compiling tasks fast and lean!
  • Why use "pre-push" and not "pre-commit"
    • You can! But the reason we don't is that often developers are commiting to keep a logged history of work, and when they're under pressure having pristine commits can be tricky, and having a fairly slow set of scripts running on every commit can be a pain, so by moving it to the "pre-push" we give the developers a chance to commit as much as they like, before pushing to the main repo
  • Why not check using your CI?
    • You can! But we do not as by that time, the code shared is already poluted, then other developers might pull it and get issues which they have to fix for their deployment etc, so by catching it before it's too late we keep things speedy for everybody.

Extras

As with any project, you'll get niche things you'll want to run, however if you're using composer we've got some generic scripts which are very handy to run on demand! You can drop this in with your scripts if you use composer — just update the paths and any dependencies.


"scripts": {
    "psrcheck": [
        "./vendor/bin/phpcs --standard=PSR2 --warning-severity=8 ./app",
        "./vendor/bin/phpcs --standard=PSR2 --warning-severity=8 ./routes"
    ],
    "psrfix": [
        "./vendor/bin/phpcbf --standard=PSR2 --warning-severity=8 ./app",
        "./vendor/bin/phpcbf --standard=PSR2 --warning-severity=8 ./routes"
    ],
    "phplint": [
        "find -L ./app -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l",
        "find -L ./routes -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l"
    ],
    "clean": [
        "find . -name '.DS_Store' -type f -delete",
        "find . -name '*.orig' -type f -delete"
    ],
    "githooks": [
        "rm -rf $(pwd)/.git/hooks",
        "ln -s $(pwd)/git-hooks/ $(pwd)/.git/hooks",
        "echo Hooks Copied to $(pwd)/.git/hooks"
    ]
}

Additionally if you want to add some colour and fancy styling to your hook, you can view our finished hook just below in all its colourful glory!


#!/bin/sh

# Hack to fix the path for sourcetree
if [[ $PATH == *"SourceTree"* ]]; then
    source ~/.selesti_profile;
fi

# SCSS Code Style Checking
if [[ $(sass-lint -c ./.sass-lint.yml './resources/assets/sass/**/*.scss' -v) ]]; then
    echo "\033[0;31m";
    echo "###########################################################################";
    echo "########## SCSS VIOLATION DETECTED :: SCSS VIOLATION DETECTED #############";
    echo "###########################################################################";
    echo "\033[0m";
    echo "run the below command to see the output";
    echo "\033[1;36m";
    echo "sass-lint -c ./.sass-lint.yml './resources/assets/sass/**/*.scss' -v -q";
    echo "";
    exit 1;
fi

# Javascript Code Style Checking
eslint "./resources/assets/js/**/*.js" &> /dev/null

if [ $? -ne 0 ]; then
    echo "\033[0;31m";
    echo "###########################################################################";
    echo "######### ESLINT VIOLATION DETECTED :: ESLINT VIOLATION DETECTED ##########";
    echo "###########################################################################";
    echo "\033[0m";
    echo "run the below command to see the output";
    echo "\033[1;36m";
    echo "eslint './resources/assets/js/**/*.js'";
    echo "";
    exit 1;
fi

# PHP Code Style Checking
./vendor/bin/phpcs --standard=PSR2 --warning-severity=8 ./app &> /dev/null

if [ $? -ne 0 ]; then
    echo "\033[0;31m";
    echo "###########################################################################";
    echo "########## PHPCS VIOLATION DETECTED :: PHPCS VIOLATION DETECTED ###########";
    echo "###########################################################################";
    echo "\033[0m";
    echo "run the below command to see the output";
    echo "\033[1;36m";
    echo "./vendor/bin/phpcs --standard=PSR2 --warning-severity=8 ./app";
    echo "";
    exit 1;
fi

./vendor/bin/phpcs --standard=PSR2 --warning-severity=8 ./routes &> /dev/null

if [ $? -ne 0 ]; then
    echo "\033[0;31m";
    echo "###########################################################################";
    echo "########## PHPCS VIOLATION DETECTED :: PHPCS VIOLATION DETECTED ###########";
    echo "###########################################################################";
    echo "\033[0m";
    echo "run the below command to see the output";
    echo "\033[1;36m";
    echo "./vendor/bin/phpcs --standard=PSR2 --warning-severity=8 ./routes";
    echo "";
    exit 1;
fi

# PHP Linting
PHPAppPath=./app

for file in `find $PHPAppPath -type f -name "*.php"` ; do
    RESULTS=`php -l $file`

    if [ "$RESULTS" != "No syntax errors detected in $file" ] ; then
        echo "\033[0;31m";
        echo "###########################################################################";
        echo "######### PHP SYNTAX ERROR DETECTED :: PHP SYNTAX ERROR DETECTED ##########";
        echo "###########################################################################";
        echo "\033[0m";
        echo "run the below command to see the output";
        echo "\033[1;36m";
        echo "php -l $file";
        echo "";
        exit 1;
    fi
done

# Success Message
echo "\033[0;32m";
echo "###########################################################################";
echo "############# PRE-POST TESTS ALL PASSED, TODAY IS A GOOD DAY. #############";
echo "###########################################################################";
echo "\033[0m";

Notes

Sourcetree has an annoying bug where it sets its own PATH, so things like your node modules or composer packages might not be detected. To counteract this we have a custom bash profile which sets up all our paths correctly. We call this `.selesti_profile` and it's placed in the home directory. Then at the top of our GIT Hooks we have a custom snippet which loads this file, and it fixes the PATHS for us by setting them up manually:

.selesti_profile

# Default Path
DEFAULT_PATH="/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";

# Other Paths
COMPOSER_PATH="~/.composer/vendor/bin"
NODE_PATH="/usr/local/lib/node/bin"

# Final Path
export PATH="$COMPOSER_PATH:$NODE_PATH:$DEFAULT_PATH";

git-hooks/pre-push

# Hack to fix the path for sourcetree, put this just after the #!/bin/sh
if [[ $PATH == *"SourceTree"* ]]; then
    source ~/.selesti_profile;
fi

The End!

Hopefuly this has given you some inspiration as to how your team can implement some simple coding standards, but remember to pick the standard/style that works best for your team. If this means that you have three different projects, and they need to use three different standards, that's fine — as long as each project sticks to one style.

If you've got any suggestions for ways to improve this flow, drop us a line at [email protected] 

You might also like