October 2021

JS Array of Objects to Dictionary

We will examine what is going on with this snippet of code? But first, what is the goal. The files variable is an array of objects. Each object has two fields: ‘name’, and ‘fullpath’. We want to return a dictionary with ‘name’ as the key, and the ‘fullpath’ as the value. We will look at this snippet of code piecemeal.

const fileMap = Object.assign({}, ...files.map(x=>({[x.name] : x.fullpath})));

Spread Syntax

The JS spread syntax allows iterable expressions, such as an array, to be expanded into an argument list during a function call. A simplified example is shown below.

const A = [1, 3, 5];
function sum(a, b, c){
      return a + b + c;
}
console.log(sum(...A));
> 9

In the case of the Object.assign function the first parameter to the object to write to, all remaining arguments are source objects. Using the spread syntax on the map function (which returns an array of objects) passes each object into ‘assign’ treating them each as individual sources.

Array.prototype.map

The map function of Array, accepts a callback function and return a new array. It fills each element of the array by passing the source value into the callback function. You can achieve the same thing by iterating through the array.

const A = [1, 1, 3, 5, 8];
const B = [];
for (let i = 0; i < A.length; i++){
      B[i] = A[i] + i;
}

Equivalent operation using map:

const A = [1, 1, 3, 5, 8];
const B = A.map((x, i)=>x + i);

Lambda Functions

The snippet x=>({[x.name] : x.fullpath}) seems a little odd at first. Why are there parentheses around the curly braces. To understand this we need to look at the two ways JS allows lambda functions.

Implicit return statement:

x => {return x + 1}

Implied return statement:

x => x + 1

An implicit return statement has curly braces, while the implied does not. Because the object constructor uses the same braces as the function body, if you were to do this: x => {"name" : "apple"} JS would think you are trying to do an implicit-return lambda function. So we need to encapsulate the object declaration with parentheses to turn that statement into an implied-return lambda function: x => ({"name" : "apple"}).

ES2016 Computed Property Name Shorthand

The second oddity of the statement x=>({[x.name] : x.fullpath}) , is why is [x.name] in square brackets. This was introduced in ES1015 as shorthand to tell JS to use the value in the variable and not to treat it as a key value. This is a bit more obvious if we look at it outside of the context of a lambda function.

Given an object with two fields, you want to use one of the fields for the key of a new object. The vanilla JS way of things:

const key = "name";
const obj2 = {};
obj2[key] = "apple";
console.log(obj2);
> { name: 'apple' }

The shorthand way of doing this, which fits nicely into a compact lambda function, is as follows:

const key = "name";
const obj2 = {[key] : "apple"};

NodeJS Code Coverage With C8

Code coverage allows you to determine how well unit-tests cover your codebase. The standard code coverage suite for JavaScript is the NYC interface for Istanbul. However, this package doesn’t play well with NodeJS modules. Instead we are going to use the C8 package. C8 uses functionality native to NodeJS to create output that is compatible with Istanbul reporters.

Installation

npm i --save-dev c8

Execution

You can run c8 on any source file by simply passing in the appropriate node command.

$ npx c8 node src/index.js
-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files  |   88.23 |      100 |      50 |   88.23 |
 MooYou.js |   84.61 |      100 |      50 |   84.61 | 9-10
 index.js  |     100 |      100 |     100 |     100 |
-----------|---------|----------|---------|---------|-------------------

You can also easily run it with your Mocha test files.

$ npx c8 mocha
  MooYou Class Test
    #who
      ✔ returns 'you'


  1 passing (3ms)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files  |   84.61 |      100 |      50 |   84.61 |
 MooYou.js |   84.61 |      100 |      50 |   84.61 | 9-10
-----------|---------|----------|---------|---------|-------------------

Configuration

C8 can be configured from the command line, in your package.json file, or in a configuration file in your project directory. In particular you can instruct C8 to report one files as opposed to just those in your tests (npx c8 --all mocha). You can also specify which files to include and/or ignore. See the configuration documentation for specific details.

Generating Reports

By default the coverage data will be located in the coverage/tmp directory. You can change this directory with the --temp-directory flag. Don’t forget to add this to your .gitignore file.

Run npx c8 report to regenerate reports after c8 has already been run. Use the -r flag to specify which reporter to use, and the -o flag to specify where to output the report. By default generated reports can be found in the coverage/ dirctory.

Vanilla C8 command with HTML report:

npx c8 -r html mocha

Generate C8 report after the fact:

npx c8 -r html report

Two-part C8 command with custom directories and HTML report:

npx c8 --temp-directory .c8 mocha
npx c8 report --temp-directory .c8 -o html_coverage -r html

In Code Markup

You can add comments into your source code that tells c8 to ignore certain portions of code.

Ignoring all lines until told to stop.

/* c8 ignore start */
function foo() {
  ...
}
/* c8 ignore stop */

Ignore next line.

/* c8 ignore next */

External References

https://www.npmjs.com/package/c8

https://github.com/bcoe/c8

GIT Branches Quick Tips

Delete a Remote Branch

git push -d [remote] [branch]

Create a New Branch From a Hash

git branch [branch] [sha]

Force a Push

Use with caution it can delete or overwrite existing commits.

git push -f

Push a New Branch

This creates an upstream tracking branch related to your local branch.

git push -u origin [branch]

Push All Branches

git push --all

Publish Tags to the Remote Repository

git push --tags

Git: Checkout a Single File from a Previous Commit

Often we alter or delete a file that we didn’t really want to. Sometimes this goes unnoticed for a few commits. It’s fairly easy in git to retrieve a specific file from a specific commit.

git checkout <commit> <path>
git checkout f08b32 ./src/main.c
git checkout HEAD~2 ./src/main.c

How to reattach a GIT detached head.

$ git status
HEAD detached at 36bc359
nothing to commit, working tree clean

$ git branch fix-detached
$ git checkout main
$ git merge fix-detached -Xtheirs
$ git branch -d fix-detached

Get all files in directory

Get all files in directory

This code snippet recursively examines a directory, and all it’s subdirectories, to identify files. It returns an array of objects with the fullpath, and the filename as strings.

import FS from 'fs';
import Path from 'path';

/**
 * Recursively retrieve a list of files from the specified directory.
 * @param {String} directory 
 * @returns An array of {fullpath, name} obects.
 */
 function getFiles(directory = "."){
      const dirEntries = FS.readdirSync(directory, { withFileTypes: true });
      const files = dirEntries.map((dirEntry) => {
      return dirEntry.isDirectory() ? getFiles(resolved) : {fullpath : resolved, name : dirEntry.name};
      });
      return files.flat();
  }

  export default getFiles;

The filesystem library for JS provides the synchronous read directory function (line 10). Setting withFileTypes to true in the options, directs readdirSync to return directory entry objects.

The array’s map method passes in each directory entry object into a provided callback function (lines 12-15). The return value of this function is inserted into a new array.

The ternary operator on line 12 is the callback, and works as follows:

If the dirEntry object is not a function then add the fullpath and name to the array. If it is, then recursively call the getFiles function and and add the result to the array. Because the recursive call nests arrays into arrays, we call the array’s flat method which creates a new single 1-dimensional array.

Updating NPM Packages

Updating NPM Packages

  1. Navigate to the root directory of your project and ensure it contains a package.json file:
    • cd /path/to/project
  2. In your project root directory, run the outdated command to view outdated packages:
    • npm outdated
  3. In your project root directory, run the update command:
    • npm update

package.json

The way the wanted version is set in the package.json file may affect how updates are decided.

Version numbers are declared as a [major, minor, patch] tuple.

Caret Ranges ^1.2.3

Allows changes that do not modify the left-most non-zero element of the version specification. So 1.3.1 will update to 1.3.2 or 1.4.0. Though, 0.3.0 will update to 0.3.1 but not update to 0.4.0.

Version will update if:

  • ^1.2.3 : 1.2.3 < version < 2.0.0
  • ^0.3.0 : 0.3.0 < version < 0.4.0

X-Ranges

Allows ‘X’, ‘x’, or ‘*’ to stand in for a number in the version specification.

Version will update if:

  • “” : any version
    • same as “*” or “x.x.x”
  • 1.x.x : 1.0.0 < version < 2.0.0
    • same as “1”
  • 1.2.x : 1.2.0 < version < 1.3.0
    • same as “1.2”