superlint/docs/add-new-linter.md
Marco Ferrari 6fdc091361
feat: local fix mode (#5978)
Certain linters and formatters support fixing linting and formatting
issues (fix mode). Before this change, Super-linter runs linters and
formatters in a mode that doesn't modify the source code in any way
(check only mode).

With this change, Super-linter supports running linters and formatters
in fix mode if explicitly requested by the configuration. If the
configuration includes a variable named FIX_<language_name>,
Super-linters modifies the command to run the linter or formatter for
<language_name> to enable fix mode.

The modifications to the linter or formatter command that Super-linter
applies depend on what is the default for a particular linter: it either
removes or adds options to the command to run the linter or formatter.
2024-08-07 15:36:16 +02:00

7.3 KiB

How to add support for a new tool to super-linter

If you want to propose a Pull Request to add new language support or a new tool, it should include:

  • Update documentation:

    • README.md
  • Provide test cases:

    1. Create the test/linters/<LANGUGAGE> directory.
    2. Provide at least one test case with a file that is supposed to pass validation, with the right file extension if needed: test/linters/<LANGUAGE>/<name-of-tool>-good
    3. Provide at least one test case with a file that is supposed to fail validation, with the right file extension if needed: test/linters/<LANGUAGE>/<name-of-tool>-bad
  • Update the test suite to check for installed packages, the commands that your new tool needs in the PATH, and the expected version command:

    • test/inspec/super-linter/controls/super_linter.rb
  • Install the tool by pointing to specific package or container image versions:

    • If there are PyPi packages, create a text file named dependencies/python/<name-of-tool>.txt and list the packages there.

    • If there are npm packages, update dependencies/package.json and dependencies/package-lock.json. by adding the new packages.

    • If there are Ruby Gems, update dependencies/Gemfile and dependencies/Gemfile.lock

    • If there are Maven or Java packages:

      1. Create a directory named dependencies/<name-of-tool>.

      2. Create a dependencies/<name-of-tool>/build.gradle file with the following contents:

        repositories {
          mavenLocal()
          mavenCentral()
        }
        
        // Hold this dependency here so we can get automated updates using DependaBot
        dependencies {
          implementation 'your:dependency-here:version'
        }
        
        group 'com.github.super-linter'
        version '1.0.0-SNAPSHOT'
        
      3. Update the dependencies section in dependencies/<name-of-tool>/build.gradle to install your dependencies.

      4. Add the following content to the Dockerfile:

        COPY scripts/install-<name-of-tool>.sh /
        RUN --mount=type=secret,id=GITHUB_TOKEN /<name-of-tool>.sh && rm -rf /<name-of-tool>.sh
        
      5. Create scripts/install-<name-of-tool>.sh, and implement the logic to install your tool. You get the version of a dependency from build.gradle. Example:

        GOOGLE_JAVA_FORMAT_VERSION="$(grep <"google-java-format/build.gradle" "google-java-format" | awk -F ':' '{print $3}' | tr -d "'")"
        
      6. Add the new to DependaBot configuration:

          - package-ecosystem: "gradle"
            directory: "/dependencies/<name-of-tool>"
            schedule:
              interval: "weekly"
            open-pull-requests-limit: 10
        
    • If there is a container (Docker) image:

      1. Add a new build stage to get the image:

        FROM your/image:version as <name-of-tool>
        
      2. Copy the necessary binaries and libraries to the relevant locations. Example:

        COPY --from=<name-of-tool> /usr/local/bin/<name-of-command> /usr/bin/
        
  • Configure the new tool:

    • Provide a default configuration file only if the tool cannot function without one: TEMPLATES/<template file for language>
    • Provide a configuration file for the new linter only if the default configuration is unsuitable for the super-linter repository: .github/linters/.<lintrc>
  • Update the orchestration scripts to run the new tool:

    • lib/globals/languages.sh: add a new item to LANGUAGES_ARRAY array. Use the "name" of the language, then a _, and finally the name of the linter. Example: PYTHON_RUFF. In the context of this document, to avoid repetitions we reference this new item as <LANGUAGE_NAME>.

    • Linter configuration:

      • Create a new minimal configuration file in the TEMPLATES directory with the same name as the default configuration filename. Example: TEMPLATES/.ruff.toml.
      • lib/globals/linterRules.sh:
        • If the new linter accepts a configuration files from the command line, define a new variable: <LANGUAGE_NAME>_FILE_NAME="${<LANGUAGE_NAME>_CONFIG_FILE:-"default-config-file-name.conf"}" where default-config-file-name.conf is the name of the new, minimal configuration for the linter. Example: PYTHON_RUFF_FILE_NAME="${PYTHON_RUFF_CONFIG_FILE:-.ruff.toml}".

        • If there are arguments that you can only pass using the command line, and you think users might want to customize them, define a new variable using <LANGUAGE_NAME>_COMMAND_ARGS and add it to the command if the configuration provides it. Example:

          <LANGUAGE_NAME>_COMMAND_ARGS="${<LANGUAGE_NAME>_COMMAND_ARGS:-""}"
          if [ -n "${<LANGUAGE_NAME>_COMMAND_ARGS:-}" ]; then
            export <LANGUAGE_NAME>_COMMAND_ARGS
            LINTER_COMMANDS_ARRAY_<LANGUAGE_NAME>+=("${<LANGUAGE_NAME>_COMMAND_ARGS}")
          fi
          
    • Define the command to invoke the new linter:

      • lib/functions/linterCommands.sh: add the command to invoke the linter. Define a new variable: LINTER_COMMANDS_ARRAY_<LANGUAGE_NAME>. Example: LINTER_COMMANDS_ARRAY_GO_MODULES=(golangci-lint run --allow-parallel-runners -c "${GO_LINTER_RULES}")

        If the linter needs to load a configuration file, add the relevant options and paths to the command you just defined. The path to the configuration file is automatically initialized by Super-linter using in the <LANGUAGE_NAME>_LINTER_RULES variable, as in the GO_LINTER_RULES example above for the GO language.

      • lib/globals/linterCommandsOptions.sh: add "check only mode" and "fix linting and formatting issues mode" options if the linter supports it. Super-linter will automatically add them to the command to run the linter.

        • If the linter runs in "fix linting and formatting issues mode" by default, define a new variable with the options to add to the linter command to enable "check only mode": <LANGUAGE_NAME>_CHECK_ONLY_MODE_OPTIONS=(....). Example: PYTHON_BLACK_CHECK_ONLY_MODE_OPTIONS=(--diff --check)

        • If the linter runs in "check only mode" by default, define a new variable with the options to add to the linter command to enable "fix linting and formatting issues mode": <LANGUAGE_NAME>_FIX_MODE_OPTIONS=(...). Example: ANSIBLE_FIX_MODE_OPTIONS=(--fix)

    • Provide the logic to populate the list of files or directories to examine: lib/functions/buildFileList.sh

    • If necessary, provide elaborate logic to detect if the tool should examine a file or a directory: lib/functions/detectFiles.sh

    • If the tool needs to take into account special cases, reach out to the maintainers by creating a draft pull request and ask relevant questions there. For example, you might need to provide new logic or customize the existing one to:

      • Validate the runtime environment: lib/functions/validation.sh.
      • Get the installed version of the linter: scripts/linterVersions.sh
      • Load configuration files: lib/functions/linterRules.sh
      • Run the linter: lib/functions/worker.sh
      • Compose the linter command: lib/functions/linterCommands.sh
      • Modify the core Super-linter logic: lib/linter.sh