DEV Community

Julian Cook
Julian Cook

Posted on • Edited on

Eslint Code Insights from Bitbucket pipelines

This guide explains how to integrate ESLint results into your Bitbucket Pull Requests using Bitbucket Pipelines and creating a Report with Annotations.

Generate ESLint Report as JSON

First, you'll need to run ESLint and output the results in JSON format. This file will later be used to create a report and annotations.

Add -f and -o args to your eslint command. E.g:

eslint . --ext .ts -f json -o eslint-report.json
Enter fullscreen mode Exit fullscreen mode

Post ESLint Report and Annotations to Bitbucket

To display ESLint findings directly in your Pull Requests, you'll use Bitbucket's Report API and Annotations API.

  1. Read the ESLint JSON report.
  2. Generate a report with the total number of errors and warnings.
  3. Post inline annotations based on ESLint messages.
const fs = require('fs')
const path = require('path')
const util = require('util')

// Must be unique per report on a commit
const REPORT_ID = 'com.yorcompany.reports.eslint'
const BB_USER = 'YOUR_USER'
const BB_REPO = 'YOUR_REPO'
const BB_URL = 'https://api.bitbucket.org/2.0'
// This is available by default in the pipeline.
const COMMIT = process.env.BITBUCKET_COMMIT
// For this to be availble you need to create an access token with read access to the repo
//  and set it an environment variable in the pipeline.
const TOKEN = process.env.BITBUCKET_TOKEN

// Map ESLint severities to Bitbucket report severities
const severities = {
    0: 'LOW',
    1: 'MEDIUM',
    2: 'HIGH'
}

if (!fs.existsSync(path.join(process.cwd(), 'eslint-report.json'))) {
    console.log('eslint-report.json file does not exist.');
    process.exit();
}

doIt().catch(e => {
    console.error('Error posting insights:', e.response ? e.response.data : e.message)
})

async function doIt() {
    const data = await util.promisify(fs.readFile)(path.join(process.cwd(), 'eslint-report.json'), 'utf8')
        .catch(err => {
            console.error('Error reading eslint-report.json:', err)
            throw err
        })

    const eslintOutput = JSON.parse(data)
    let totalErrorCount = 0
    let totalWarningCount = 0
    const annotations = []
    let i = 1
    eslintOutput.forEach(file => {
        totalErrorCount += file.errorCount
        totalWarningCount += file.warningCount

        const relativePath = path.relative(process.cwd(), file.filePath)

        file.messages.forEach(message => {
            annotations.push({
                external_id: `${REPORT_ID}.${COMMIT}.${i++}`,
                path: relativePath,
                annotation_type: 'CODE_SMELL',
                summary: message.message,
                line: message.line,
                severity: severities[message.severity]
            })
        })
    })

    const report = {
        title: 'ESLint report',
        details: 'ESLint report',
        report_type: 'TEST',
        logoUrl: 'https://eslint.org/img/logo.svg',
        data: [
            {
                title: 'Error Count',
                type: 'NUMBER',
                value: totalErrorCount
            },
            {
                title: 'Warning Count',
                type: 'NUMBER',
                value: totalWarningCount
            }
        ]
    }

    await postReport(report)
    await postAnnotations(annotations)
}

async function postReport(report) {
    // https://developer.atlassian.com/cloud/bitbucket/rest/api-group-reports/#api-repositories-workspace-repo-slug-commit-commit-reports-reportid-put
    const reportUrl = `${BB_URL}/repositories/${BB_USER}/${BB_REPO}/commit/${COMMIT}/reports/${REPORT_ID}`
    const response = await fetch(reportUrl, {
        method: 'PUT',
        body: JSON.stringify(report),
        headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'Authorization': `Bearer ${TOKEN}`
        }
    })

    if (!response.ok) {
        console.error(await response.text())
        throw new Error(`Error posting report: ${response.statusText}`)
    }

    console.log('Report posted successfully!')
    console.log(await response.json())
}

async function postAnnotations(annotations) {
    if (annotations.length > 0) {
        // https://developer.atlassian.com/cloud/bitbucket/rest/api-group-reports/#api-repositories-workspace-repo-slug-commit-commit-reports-reportid-annotations-post
        const annotationsUrl = `${BB_URL}/repositories/${BB_USER}/${BB_REPO}/commit/${COMMIT}/reports/${REPORT_ID}/annotations`
        const response = await fetch(annotationsUrl, {
            method: 'POST',
            body: JSON.stringify(annotations),
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json',
                'Authorization': `Bearer ${TOKEN}`
            }
        })

        if (!response.ok) {
            console.error(await response.text())
            throw new Error(`Error posting annotations: ${response.statusText}`)
        }

        console.log('Annotations posted successfully!')
        console.log(await response.json())
    }
}
Enter fullscreen mode Exit fullscreen mode

Configure Bitbucket Pipeline

To automate this process as part of your CI/CD workflow, you can set up a Bitbucket pipeline to run ESLint, generate the JSON report, and post the results. Below is a sample bitbucket-pipelines.yml file to get you started:

image: node:18.13.0

pipelines:
  default:
    - step:
        name: ESLint
        caches:
          - node
        script:
          - npm install
          - npx eslint . --ext .ts -f json -o eslint-report.json  # Run ESLint and save the report
        after-script:
          - node post-eslint-results.js  # Post results to Bitbucket
        artifacts:
          - eslint-report.json
Enter fullscreen mode Exit fullscreen mode

Note

Report is posted to Bitbucket in after-script because subsequent scripts will not be called if eslint returns non 0 exit code (if ESLint has errors).

Top comments (0)