Typescript Linting

Think of linting as your code’s first line of defense, like a spell checker for your syntax and structure. It analyzes source files to identify errors, enforce style guidelines, and highlight patterns that may lead to bugs. By integrating linting into your development workflow or CI pipeline, you create an automated checkpoint that prevents sloppy code from slipping through. This not only improves consistency across the codebase but also reduces review time and debugging later. With linting in place, every commit meets a standard of clarity and correctness before it moves forward.


Here we will be adding linting and code formatting to a nodejs project that includes javascript and typescript files.

We'll start with the Starting with Typescript for CommonJS project.

.
├── .gitignore
├── .nvmrc
├── package.json
├── src
│   └── index.ts
├── tsconfig.json
└── yarn.lock

Adding eslint and prettier for typescript

yarn -D add eslint prettier @eslint/js typescript-eslint eslint-plugin-prettier eslint-config-prettier globals

ESLint

Create an eslint.config.js

import globals from 'globals';
import pluginJs from '@eslint/js';
import tseslint from 'typescript-eslint';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';

/** @type {import('eslint').Linter.Config[]} */
export default [
  { files: ['**/*.{js,mjs,cjs,ts}'] },
  { languageOptions: { globals: globals.node } },
  pluginJs.configs.recommended,
  ...tseslint.configs.recommended,
  eslintPluginPrettierRecommended,
  {
    rules: {
      'no-console': 'warn'
    }
  },
  {
    ignores: ['node_modules', 'dist']
  }
];

Importing eslint-plugin-prettier/recommended and adding it as the last item in the configuration array in your eslint.config.js file so that eslint-config-prettier has the opportunity to override other configs:

Adding scripts to the package.json

npm pkg set "scripts.lint"="eslint ."
npm pkg set "scripts.lint:fix"="eslint . --fix"

Running yarn lint will return a number of warnings about using console.log

/Users/mark/src/everydaydevops/typescript-linting-example/src/index.ts
  1:1  warning  Unexpected console statement  no-console
  8:3  warning  Unexpected console statement  no-console

✖ 2 problems (0 errors, 2 warnings)

✨  Done in 1.79s.

These can be fixed by adding the following to each of the console.log lines

// eslint-disable-line no-console

Prettier

Create a prettier config and ignore file

cat << EOF > .prettierrc
{
  "semi": true,
  "trailingComma": "none",
  "singleQuote": true,
  "printWidth": 80
}
EOF
cat << EOF > .prettierignore
node_modules
dist
.build
EOF

Adding scripts for prettier

npm pkg set "scripts.prettier"="prettier . --check"
npm pkg set "scripts.prettier:fix"="prettier . --write"

Running yarn prettier will also show a few warnings

Checking formatting...
[warn] tsconfig.json
[warn] Code style issues found in the above file. Run Prettier with --write to fix.

Commit and Fix the Code

git add .
git commit -m "Added eslint and prettier for static analysis best practices" 

Now we can fix the code

yarn lint:fix
yarn prettier:fix

Join the Newsletter

Subscribe to get more DevOps Tips & Tricks

    We won't send you spam. Unsubscribe at any time.

    Protecting Git with husky & lint-staged

    Adding husky will make sure that linting is run before any commit and return an error if linting doesn't pass. This helps with keeping git clean without having to run github actions on code that will fail linting.

    Add husky and initialize it.

    yarn add -D husky
    npx husky init

    Out of the box husky will run the test script so if you don't have one you can add it with

    npm pkg set "scripts.test"="echo no tests"

    Now we need to run husky when specific files are commit to git. For this we'll use lint-staged

    yarn add -D lint-staged tsc-files

    Then adding directives to package.json on what to run when specific files are committed.

    npm pkg set "lint-staged[**/*.js][0]"="prettier --write"
    npm pkg set "lint-staged[**/*.js][1]"="eslint --fix"
    npm pkg set "lint-staged[**/*.ts][0]"="tsc-files --noEmit"
    npm pkg set "lint-staged[**/*.ts][1]"="prettier --write"
    npm pkg set "lint-staged[**/*.ts][2]"="eslint --fix"
    npm pkg set "lint-staged[**/*.{json,md,yaml,yml}][0]"="prettier --write"

    The lint-staged section of package.json looks like

      "lint-staged": {
        "**/*.js": [
          "prettier --write",
          "eslint --fix"
        ],
        "**/*.ts": [
          "tsc-files --noEmit",
          "prettier --write",
          "eslint --fix"
        ],
        "**/*.{json,md,yaml,yml}": [
          "prettier --write"
        ]
      }

    Now adding this to husky so that its run on each commit

    echo "npx lint-staged" >> .husky/pre-commit

    Adding all the new files and committing to git will run the linting.

    git add .
    git commit -m "Added husky and lint-staged to run pre-commit hooks on check-in" 

    This shows that the test script was run and the lint-staged for the matching files

    no tests
    ✔ Backed up original state in git stash (8d9bf2c)
    ✔ Running tasks for staged files...
    ✔ Applying modifications from tasks...
    ✔ Cleaning up temporary files...

    If there was a linting error then the files would not be committed to git.

    Github Action

    Adding a github action makes sure that the linting is run on each pull-request and will return an error if something isn't right:

    Create a new file .github/workflows/lint.yaml

    name: Lint
    
    on:
      pull_request:
        branches:
          - main
    
    jobs:
      run-linters:
        name: Run linters
        runs-on: ubuntu-latest
    
        steps:
          - name: Check out Git repository
            uses: actions/checkout@v4
    
          - name: Set up Node.js
            uses: actions/setup-node@v4
            with:
              node-version: 22.x
    
          - name: Install Node.js dependencies
            run: yarn --frozen-lockfile
    
          - name: Run linters
            run: yarn lint
            

    If I create a new branch for this:

    git checkout -b chore/adding_github_action_lint
    git add .
    git commit -m "Adding github action to run linting on all PRs" -a
    git push --set-upstream origin chore/adding_github_action_lint

    Now, creating a new PR in github will run the linting.

    Now I can safely merge this PR to main, knowing that it passes linting.

    I've shared this project on github as an example: https://github.com/markcallen/typescript-linting-example