Validation during Continuous Integration and Delivery

Use the same validations as in the IDE in the CI/CD setup.

Qodana to run the IntelliJ AsciiDoc plugin in CD/CD

JetBrains published Qodana that wraps the smart features of IntelliJ into a container, so it can be used on the command line as a Docker container. To make this simpler to use, it has been wrapped as a GitHub action.

For AsciiDoc files, it will highlight all the problems that are highlighted in the IDE as well, including broken links (with the help of the AsciiDoc plugin) as well as grammar errors (with the help of the Grazie and Grazie Professional plugins).

qodana concepts.dio
Figure 1. Overview Qodana process

The process is as follows:

  1. Read all or parts of the project’s sources. This can be source code in any programming language or documentation like AsciiDoc.

  2. Process the files like with all the inspections known from IntelliJ. Users can add plugins from the Marketplace like in their IDE to support the languages they need.

  3. Generate a report as HTML to be used in a browser, as well as a SARIF report that can be processed by GitHub Code Scanning or other subsequent steps in the CI/CD pipeline.

Example output

HTML report

See below for an HTML report that Qodana generates. With the GitHub Action setup below, it will be attached as a ZIP file to workflow run instance.

qodana html report
Figure 2. Qodana report with AsciiDoc inspection warnings

The CI/CD process can publish the HTML report to a webserver so users can access it. In the example below, the GitHub action pushes the latest report to the GitHub pages of the AsciiDoc plugin project.

Pull request annotation

See below for a screenshot of an annotated GitHub pull request. Using the SARIF integration, GitHub can compare the pull request with the base branch and will show only new problems introduced by the pull request.

These examples show AsciiDoc syntax errors as examples. This setup shows Grammar in the same way.

qodana pullrequest status
Figure 3. Status as shown in the pull request

Once the user clicks on the details of the code scanning, they see a list of all new violations.

qodana codescanning results
Figure 4. Details of GitHub Code Scanning

Once the user clicks on one of the annotations, GitHub shows the code.

qodana pullrequest annotation
Figure 5. Annotation in the source code

Example setup for GitHub Actions

This describes the setup used for this plugin. With the benefit of being a real-world scenario, some steps might be more complicated than a simple setup.

  1. Setup Qodana configuration file. It is placed in the root folder where it will run.

    doc/qodana.yaml
    version: 1.0
    profile:
      path: doc/asciidoc-inspection.xml
    exclude:
      - name: All
        paths:
          - doc/qodana-baseline.sarif.json
          - doc/.grazie.en.yaml
          - doc/asciidoc-inspection.xml
          - doc/contributors-guide/modules/ROOT/images
          - doc/users-guide/modules/ROOT/images
          - doc/users-guide/modules/ROOT/examples
          - CHANGELOG.adoc
          - README.adoc
          - build.gradle
          - buildLexer.xml
          - settings.gradle
          - .github
          - config
          - gradle
          - gradlew
          - gradlew.bat
          - src
          - testData
  2. Setup file with all inspections to run. It is placed in the root folder where it will run. For this setup, all inspections are specified one-by-one. Alternative setups could configure only deviations from a standard configuration.

    doc/asciidoc-inspection.xml
    <component name="InspectionProjectProfileManager">
      <profile version="1.0" is_locked="true">
        <!-- GrazieInspection normally has a TYPO preference -->
        <inspection_tool class="GrazieInspection" enabled="true" level="WARNING" enabled_by_default="true" />
        <inspection_tool class="AsciiDocAnchorWithoutId" enabled="true" level="WARNING" enabled_by_default="true" />
        <inspection_tool class="AsciiDocAttributeContinuation" enabled="true" level="WARNING" enabled_by_default="true" />
        <inspection_tool class="AsciiDocBlockMacroShouldBeInlineMacro" enabled="true" level="WARNING" enabled_by_default="true" />
        <inspection_tool class="AsciiDocDescriptionExists" enabled="true" level="WARNING" enabled_by_default="true" />
        <inspection_tool class="AsciiDocDescriptionLength" enabled="true" level="WARNING" enabled_by_default="true" />
        <inspection_tool class="AsciiDocHeadingStyle" enabled="true" level="WARNING" enabled_by_default="true" />
        <inspection_tool class="AsciiDocHorizontalRule" enabled="true" level="WARNING" enabled_by_default="true" />
        <inspection_tool class="AsciiDocInlineMacroShouldBeBlockOrPreprocessorMacro" enabled="true" level="WARNING" enabled_by_default="true" />
        <inspection_tool class="AsciiDocLinkResolve" enabled="true" level="ERROR" enabled_by_default="true" />
        <inspection_tool class="AsciiDocListingStyle" enabled="true" level="WARNING" enabled_by_default="true" />
        <inspection_tool class="AsciiDocPageBreak" enabled="true" level="WARNING" enabled_by_default="true" />
        <inspection_tool class="AsciiDocReferencePattern" enabled="true" level="ERROR" enabled_by_default="true" />
        <inspection_tool class="AsciiDocXrefWithFileExtension" enabled="true" level="WARNING" enabled_by_default="true" />
        <inspection_tool class="AsciiDocXrefWithNaturalCrossReference" enabled="true" level="WARNING" enabled_by_default="true" />
        <inspection_tool class="AsciiDocAttributeShouldBeDefined" enabled="true" level="WARNING" enabled_by_default="true" />
        <inspection_tool class="AsciiDocObsoletePassthrough" enabled="true" level="WARNING" enabled_by_default="true" />
        <inspection_tool class="AsciiDocUnresolvedAntoraModule" enabled="true" level="ERROR" enabled_by_default="true" />
        <inspection_tool class="SpellCheckingInspection" enabled="true" level="WARNING" enabled_by_default="true">
          <option name="processCode" value="true" />
          <option name="processLiterals" value="true" />
          <option name="processComments" value="true" />
        </inspection_tool>
        <inspection_tool class="Style" enabled="true" level="WARNING" enabled_by_default="true" />
      </profile>
    </component>
  3. Add a workflow the GitHub repository by adding the following file (see docs.yml in the GitHub repo):

    .github/workflows/docs.yml
    name: Qodana Documentation validation
    on:
      push:
        branches-ignore:
          - 'dependabot/**'
        paths:
          - 'doc/**'
          - '.github/workflows/docs.yml'
          - 'qodana.yaml'
      pull_request:
        paths:
          - 'doc/**'
          - '.github/workflows/docs.yml'
          - 'qodana.yaml'
    
    concurrency:
      # Only run once for latest commit per ref and cancel other (previous) runs.
      group: ${{ github.workflow }}-${{ github.ref }}
      cancel-in-progress: true
    
    jobs:
      qodana-docs:
        name: Qodana Docs
        runs-on: ubuntu-latest
        permissions:
          # necessary for the runs of push to store security events in the repo
          # GitHub code scanning will treat any grammar error like any security event.
          security-events: write
        steps:
    
          - name: Fetch Sources
            if: github.event_name != 'pull_request'
            uses: actions/checkout@v4
    
          - name: Fetch Sources
            if: github.event_name == 'pull_request'
            uses: actions/checkout@v4
            with:
              fetch-depth: ${{ github.event.pull_request.commits }}
    
          - name: Download AsciiDoc plugin for AsciiDoc checks
            run: |
              curl -L -o asciidoctor-intellij-plugin.zip https://github.com/asciidoctor/asciidoctor-intellij-plugin/releases/download/0.41.14/asciidoctor-intellij-plugin-0.41.14.zip
              unzip asciidoctor-intellij-plugin.zip
    
          - name: Download Grazie plugin for grammar checks
            # https://plugins.jetbrains.com/plugin/12175-grazie/versions
            run: |
              curl -L -o grazie.zip 'https://plugins.jetbrains.com/plugin/download?rel=true&updateId=508282'
              unzip grazie.zip
    
          - name: Download Grazie Professional plugin for grammar checks
            # https://plugins.jetbrains.com/plugin/16136-grazie-professional/versions
            run: |
              curl -L -o grazie-pro.zip 'https://plugins.jetbrains.com/plugin/download?rel=true&updateId=529722'
              unzip grazie-pro.zip
    
          - name: Create empty folder to overwrite disabled plugin
            run: |
              mkdir empty
    
          - name: Get two more commits so Qodana we can identify the changes
            if: github.event_name == 'pull_request'
            run: git fetch --deepen=2
    
          - name: 'Qodana for Docs'
            uses: JetBrains/qodana-action@v2024.3.3
            with:
              upload-result: true
              # https://hub.docker.com/r/jetbrains/qodana-jvm-community/tags
              # this disables the Gradle plugin to avoid the Gradle initialization and the dependency download
              # as that is not necessary for the Grazie and AsciiDoc plugins to check spelling and links.
              # TODO: the plugin `org.jetbrains.plugins.gradle` should also be suppressed, but the parameter doesn't allow
              # a comma when called from the Qodana action. Therefore overwrite it with an empty folder.
              args: >
                --linter,jetbrains/qodana-jvm-community:2024.2,
                --property=idea.suppressed.plugins.id=com.intellij.gradle,
                -v,${{ github.workspace }}/grazie:/opt/idea/plugins/grazie,
                -v,${{ github.workspace }}/empty:/opt/idea/plugins/gradle-java,
                -v,${{ github.workspace }}/grazie-pro:/opt/idea/plugins/grazie-pro,
                -v,${{ github.workspace }}/asciidoctor-intellij-plugin:/opt/idea/plugins/asciidoctor-intellij-plugin,
                --baseline,doc/qodana-baseline.sarif.json
    
          # https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/uploading-a-sarif-file-to-github#example-workflow-that-runs-the-eslint-analysis-tool
          - name: Upload SARIF report to GitHub
            # so that it is present on all pull requests and GitHub shows the comparison results
            uses: github/codeql-action/upload-sarif@v3
            with:
              # Path to SARIF file relative to the root of the repository
              sarif_file: ${{ runner.temp }}/qodana/results/qodana.sarif.json
    
          - name: Upload artifact
            uses: actions/upload-pages-artifact@v3
            with:
              path: ${{ runner.temp }}/qodana/results/report
    
      github-pages:
        environment:
          name: github-pages
          url: ${{ steps.deployment.outputs.page_url }}
        name: GitHub Pages
        runs-on: ubuntu-latest
        needs:
          - qodana-docs
        if: github.ref_name == 'main'
        permissions:
          pages: write
          id-token: write
        steps:
    
          - name: Setup Pages
            uses: actions/configure-pages@v5
    
          - name: Deploy to GitHub Pages
            id: deployment
            uses: actions/deploy-pages@v4

Open issues with Qodana

The following issues in the Qodana issue tracker are open. Voting for them will make this integration simpler.

  • QD-1291 Qodana easy plugin installation

  • QD-1290 Add soft wrap to code preview