Skip to content
This repository has been archived by the owner on Jan 1, 2025. It is now read-only.

Code Style

Sim edited this page Dec 31, 2021 · 19 revisions

In order for the source code of the launcher to be cohesive, there are a few rules to follow. Current code might not respect these because it was written before these guidelines, but should be revised and refactored eventually.

Having ESLint installed (it’s already added as a devDependency in package.json, so you should) and a formatter enabled should normalize everything when you’re coding.

Overview


Code Style

Use 2 spaces for indentation

Always use 2 spaces for indentation (this is configurable in Visual Studio Code).

More about spaces

  • Always put spaces after commas, and on both sides of logical, comparison, string and assignment operators.
  • Put spaces on both sides of the opening and closing parentheses of if, elseif, for, and switch blocks.
function download() {
  return new Promise( ( resolve, reject ) => {
    this.dl.run();
    this.dl.events.on( 'error', () => {} );
    this.dl.events.on( 'end', _fileName => {
      if ( this.dl.hasFailed() ) {
        return reject( global.locale.FSO_NETWORK_ERROR );
      }
      resolve();
    } );
    this.updateDownloadProgress();
  } );
}

Use CRLF file endings

Always use CRLF for file endings (this is configurable in Visual Studio Code).

Use const/let instead of var

const somethingThatWontChange = "";
let somethingThatWillChange = "";

No unused variables

Don’t leave unused variables, delete them. If you must keep them unused, comment them out and explain why in the comment.

Prefer single object arguments instead of many arguments

When a function requires many arguments, a single object should be used as the argument:

function someFnWithManyParameters( { param1, param2, param3, param4 } ) {}

someFnWithManyParameters( {
  param1: "Some param",
  param2: 2,
  param3: await api.getSomething(),
  param4: somethingElse
} );

Use the factory function pattern where possible instead of classes

Some examples in the repo: download.js unzip.js

Note that dependencies shouldn’t be directly imported (required) and used inside the function. The previous examples do not respect that, but will be corrected in future versions of the launcher. Here’s a correct example:

utils/download.js

// No requires in top of file.

// The http client used is imported here.
function makeDownload( { http } ) {
  // Here we return the actual download function that now has access to the http dependency.
  return function download( { originUrl } ) {
    // Some init code here.
    const dl = http.get( originUrl );

    // Define functions.
    function run() {
      return dl.start();
    }

    function getProgress() {
      return dl.getProgress().toFixed(2);
    }

    return {
      // These functions will be usable outside.
      run,
      getProgress
    }
  }
}

utils/index.js

// Here the dependencies can be imported and initialized for functions in utils/ to use.
const someHttpClient = require( 'some-http-client');
const makeDownload = require( './download' );

// Preconfigure and initiate the httpClient.
const http = someHttpClient( { defaultHeaders: { 'auth': 'bearer ...' } } );

module.exports = {
  // Pass in the already configured httpClient.
  download: makeDownload( { http } ),

  // ... the same for any other file in utils/
}

main.js

// Import the download function.
const { download } = require( './utils' );

// Use it, passing in parameters needed for the download.
const myFileDownload = download( { originUrl: 'https://example.com/someFile.zip' } );

// Call the public functions.
myFileDownload.run();
console.log( myFileDownload.getProgress() );

Inheritance using factory functions

Inheritance (if really necessary...) can be achieved in this manner (completely different example):

const Person = (name) => {
  const sayName = () => console.log(`my name is ${name}`)
  return {sayName}
}

const Nerd = (name) => {
  // simply create a person and pull out the sayName function with destructuring assignment syntax!
  const {sayName} = Person(name)
  const doSomethingNerdy = () => console.log('nerd stuff')
  return {sayName, doSomethingNerdy}
}

const jeff = Nerd('jeff')

jeff.sayName() //my name is jeff
jeff.doSomethingNerdy() // nerd stuff

Source: https://www.theodinproject.com/paths/full-stack-javascript/courses/javascript/lessons/factory-functions-and-the-module-pattern

Always define types in comments

Always define types in function comments for parameters and return types.

Since we do not use TypeScript, defining types in comments is the next best thing. Visual Studio Code will provide code-completion features and will be aware of types when provided this way:

/**
 * Runs a child process and returns the output.
 *
 * @param {string} file File to run.
 * @returns {Promise} Promise that resolves when the process has finished.
 */
function run( file ) {
  return new Promise( ( resolve, _reject ) => {
    const child = require( 'child_process' ).exec( file, { cwd: 'bin' } );
    child.on( 'close', _code => resolve() );
    child.stderr.on( 'data', data => console.log( 'stdout: ' + data ) );
  } );
}

Use multi-line and single-line comments adequately

/**
 * This is a multiline comment.
 * Second line of this comment
 */

// This is a single line comment.

Classes and functions should always have multi-line comments preceding them, such as the previous example run().