Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save itsacoyote/eacc1f343e2845aa1aee6a77cb0656b4 to your computer and use it in GitHub Desktop.

Select an option

Save itsacoyote/eacc1f343e2845aa1aee6a77cb0656b4 to your computer and use it in GitHub Desktop.
Project configuration sanity guide

Linters and Formatters

Having a standard set of rules for formatting and linting makes projects a little more sane to work with. Particularly when working with others in a project.

There are a number of opinionated ways to configure a project with linting and formatting, this is just mine for reference whenever I set up a new project or ask myself, "why the hell did I do this?"

Lint with linters, format with stylers

There are two common toolings to use in projects to ensure projects follow common standards. Linting is to ensure that code follow standards to avoid bugs and other problems. Formatters are mostly opinionated styling of code for visual presentation.

Stylers are the only thing that should run to format project code. Linting can provide warnings and errors for code and it should be up to the developer to manually make the fixes to pass.

Linters and formatters to use in a project

These are the common tools I use for a project:

  • ESLint
  • Prettier
  • EditorConfig
  • Commitlint

To ensure these run and format the project, I use the following:

  • VSCode workspace settings
  • Husky

How to work together

ESLint, Prettier and EditorConfig have overlaps in rules. Some minor adjustments and additional packages need to be added to ensure they work together and don't step on each others toes.

The general logic goes as follows:

  • Start with .editorconfig. It's designed for use across any editor and IDE. Define all of the available configuration rules in the file.
  • Prettier is next. It inherits rules from .editorconfig. Don't re-define the rules in prettier that are already defined in editorconfig.
  • ESLint is last. Since it has lint rules that overlap with Prettier, add in the eslint-config-prettier plugin to disable them.
  • Add additional rules and configurations into the .vscode/settings.json file. Main thing we want to do is ensure that Prettier is run on save for a file.
  • Define the recommended list of .vscode/extensions.json to enhance the use of these various plugins and packages.

Add Husky for git hooks

With the setup above, most of the linting and formatting should be managed automatically. If we add linting beyond the project into git commits, we need Husky.

We can also add additional sanity checks using Husky so we can catch fools who don't install the VScode extensions or have their linters or formatters working automatically. lint-staged is useful for this.

  • Set up commitlint.config.js. Just the basic @commitlint/config-conventional is fine.
  • Add the commitlint action to the commit-msg husky hook.
  • Setup a .lintstagedrc.yml file for linting staged files.
  • Add lint-staged to the pre-commit husky hook.

Commitlint

Haven't found anything special to do with Commitlint yet.

export default { extends: ["@commitlint/config-conventional"] };

Default commitlint convention uses Angular.

Primary types to use

@commitlint/config-conventional

Must be one of the following:

  • build
  • chore
  • ci
  • docs
  • feat
  • fix
  • perf
  • refactor
  • revert
  • style
  • test

The footer should contain any information about Breaking Changes and is also the place to reference GitHub issues that this commit Closes.

Breaking Changes should start with the word BREAKING CHANGE: with a space or two newlines. The rest of the commit message is then used for this.

Editorconfig Configuration

Nothing fancy here. Just copy and paste.

# EditorConfig is awesome: https://EditorConfig.org

# top-most EditorConfig file
root = true

# some of these configurations are also
# defined in the .vscode/settings.json file
# prettier automatically inherits these rules
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 80

[*.md]
max_line_length = 100

ESLint Configuration

Add the eslint-config-prettier package to ignore Prettier rules for linting.

Uses .gitignore to define files to ignore.

To review eslint configuration, npx @eslint/config-inspector

import path from "node:path";
import { fileURLToPath } from "node:url";
import { includeIgnoreFile } from "@eslint/compat";
import pluginJs from "@eslint/js";
import eslintConfigPrettier from "eslint-config-prettier";
import globals from "globals";
import tseslint from "typescript-eslint";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const gitignorePath = path.resolve(__dirname, ".gitignore");

export default [
  includeIgnoreFile(gitignorePath),
  { languageOptions: { globals: globals.node } },
  { files: ["**/*.{js,ts,mjs,cjs}"] },
  pluginJs.configs.recommended,
  ...tseslint.configs.recommended,
  {
    rules: {
      "no-console": process.env.NODE_ENV === "production" ? "error" : "warn",
      "no-debugger": process.env.NODE_ENV === "production" ? "error" : "warn",
      "@typescript-eslint/consistent-type-imports": [
        "error",
        {
          prefer: "type-imports",
          disallowTypeAnnotations: true,
        },
      ],
    },
  },
  eslintConfigPrettier,
];

gitattributes Configuration

# Set default behavior to automatically normalize line endings.
* text=auto

# Force batch scripts to always use CRLF line endings so that if a repo is accessed
# in Windows via a file share from Linux, the scripts will work.
*.{cmd,[cC][mM][dD]} text eol=crlf
*.{bat,[bB][aA][tT]} text eol=crlf
*.{ics,[iI][cC][sS]} text eol=crlf

# Force bash scripts to always use LF line endings so that if a repo is accessed
# in Unix via a file share from Windows, the scripts will work.
*.sh text eol=lf

Husky Configuration

Use Husky primarily with lint-staged and commitlint.

npx --no -- lint-staged
npx --no-install commitlint --edit "$1"

Do I have to have the following all the time with Husky? I don't know, but had issues with CI at one point and this was a quick way to silence it.

// Skip Husky install in production and CI
if (process.env.NODE_ENV === "production" || process.env.CI === "true") {
  process.exit(0);
}
const husky = (await import("husky")).default;
// eslint-disable-next-line no-console
console.log(husky());

Bypass husky

Don't.

git commit --no-verify

Lintstaged Configuration

Setup lint-staged to run formatters and linting on pre-commit with Husky.

Sample .lintstagedrc.yml to start with. YMMV.

"*.{js,ts}":
  - cspell lint --no-must-find-files --files
  - prettier --write --ignore-unknown
  - eslint --fix --max-warnings=0
"*.ts":
  - tsc-files --noEmit
"*.md":
  - cspell lint --no-must-find-files --files

NPM Scripts

Common NPM scripts.

{
  "scripts": {
    "lint": "npm run lint:eslint && npm run lint:prettier && npm run lint:spelling",
    "lint:eslint": "eslint . --max-warnings=0",
    "lint:prettier": "prettier --check .",
    "lint:spelling": "cspell lint .",
    "format": "eslint . --fix --no-error-on-unmatched-pattern --max-warnings=0 && prettier --write ."
  }
}

Prettier Configuration

Add the @ianvs/prettier-plugin-sort-imports to have Prettier sort imports when it formats.

Why double quotes? Prettier says they picked it for the one with the fewest number of escapes. I switched to it because I was losing my mind over switching between Ruby and Javascript. I find it's less mental overhead between different languages and frameworks.

{
  "plugins": ["@ianvs/prettier-plugin-sort-imports"],
  "bracketSameLine": false,
  "semi": true,
  "trailingComma": "es5",
  "singleQuote": false,
  "bracketSpacing": true,
  "vueIndentScriptAndStyle": false,
  "singleAttributePerLine": true,
  "importOrder": [
    "<BUILTIN_MODULES>",
    "<THIRD_PARTY_MODULES>",
    "",
    "^[.]",
    "",
    "<TYPES>^(node:)",
    "<TYPES>",
    "<TYPES>^[.]"
  ]
}
node_modules
.github
.idea
public
**/*.md
bin
dist
coverage
build

VSCode Extensions

General collection of extensions to use in a project.

{
  "recommendations": [
    "christian-kohler.npm-intellisense",
    "christian-kohler.path-intellisense",
    "dbaeumer.vscode-eslint",
    "editorconfig.editor",
    "streetsidesoftware.code-spell-checker",
    "yoavbls.pretty-ts-errors",
    "davidanson.vscode-markdownlint",
    "stkb.rewrap"
  ]
}

VScode Settings

General workspace settings to use. Need to clean it up a bit.

{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "eslint.useFlatConfig": true,
  "typescript.suggest.paths": false,
  "npm-intellisense.importES6": true,
  "editor.formatOnType": false,
  "editor.formatOnPaste": false,
  "editor.formatOnSave": true,
  "editor.formatOnSaveMode": "file",
  "editor.rulers": [80],
  "[typescript]": {
    "editor.codeActionsOnSave": {
      "source.removeUnusedImports": "always"
    }
  },
  "[markdown]": {
    "editor.rulers": [100],
    "editor.wordWrap": "wordWrapColumn",
    "rewrap.autoWrap.enabled": true,
    "editor.codeActionsOnSave": {
      "source.fixAll.markdownlint": "never"
    },
    "editor.quickSuggestions": {
      "other": "off",
      "comments": "off",
      "strings": "off"
    },
    "editor.suggest.showWords": false,
    "editor.tabCompletion": "onlySnippets"
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment