# coupling: if you add a check in this file, please add it first in
# the semgrep-proprietary repo
#
# Semgrep pre-commit configuration.
# See https://pre-commit.com/ for more information.
# Once pre-commit is installed, you can run it locally with `pre-commit run --all`.
# In case of troubles, try reset your cache with `pre-commit clean`
#
# This config defines also 2 "jobs" with the `stages: [manual]` directive
# which are only exercised in CI (see .github/workflows/lint.yml).

exclude: "^tests|^cli/tests/default/e2e.*/(targets|snapshots|rules/syntax)|^cli/tests/performance/targets_perf_sca|^cli/src/(semgrep|semdep)/external|^cli/bin|\\binvalid\\b|^libs/ocaml-tree-sitter-core|^libs/testo|^libs/pcre2|^cli/src/semgrep/semgrep_interfaces|languages/.*/semgrep-.*|tests/semgrep-rules"

default_stages: [commit]

# See https://pre-commit.com/#pre-commit-configyaml---repos
# for more information on the format of the content below
repos:
  # ----------------------------------------------------------
  # Standard pre-commit hooks
  # ----------------------------------------------------------
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: check-added-large-files
      - id: check-ast
      - id: check-builtin-literals
      - id: check-case-conflict
      - id: check-docstring-first
      - id: check-executables-have-shebangs
      - id: check-json
        exclude: ".devcontainer/devcontainer.json"
      - id: check-merge-conflict
      - id: check-shebang-scripts-are-executable
      - id: check-symlinks
        exclude: "^(bin|test|libs/testo/.*test|libs/spacegrep/bin/.*)$"
      - id: check-toml
      - id: check-vcs-permalinks
      - id: check-xml
      - id: check-yaml
        args: [--allow-multiple-documents]
      - id: debug-statements
      - id: destroyed-symlinks
      - id: detect-private-key
      - id: end-of-file-fixer
        exclude: "^\\.github/workflows/.+|semgrep\\.opam|.gitmodules$"
      - id: fix-byte-order-marker
      - id: fix-encoding-pragma
        args: [--remove]
      - id: mixed-line-ending
        args: [--fix=lf]
        exclude: "libs/testo/tests/snapshots"
      - id: no-commit-to-branch
      - id: requirements-txt-fixer
      - id: trailing-whitespace
        args: [--markdown-linebreak-ext=md]
        exclude: "snapshots"

  # ----------------------------------------------------------
  # Extra hooks with repositories defining their own hooks
  # ----------------------------------------------------------
  - repo: https://github.com/myint/autoflake
    rev: v2.2.1 # PLEASE DO NOT CHANGE [see NOTE "Python"]
    hooks:
      - id: autoflake
        args:
          - --in-place
          - --remove-unused-variables
          - --remove-duplicate-keys
          - --remove-all-unused-imports
          - --ignore-init-module-imports
          - --exclude=/cli/src/semgrep/output_from_core.py

  # Black is a Python code formatter
  # Note that you need at least pre-commit 3.2.0 to use black
  # See https://github.com/psf/black/issues/4065
  - repo: https://github.com/psf/black
    rev: 23.10.1 # PLEASE DO NOT CHANGE [see NOTE "Python"]
    hooks:
      - id: black
        args: [--exclude=/cli/src/semgrep/output_from_core.py]

  - repo: https://github.com/asottile/reorder_python_imports
    rev: v3.12.0 # PLEASE DO NOT CHANGE [see NOTE "Python"]
    hooks:
      - id: reorder-python-imports
        args: ["--application-directories=cli/src", --py37-plus]

  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.7.1 # PLEASE DO NOT CHANGE [see NOTE "Python"]
    hooks:
      - id: mypy
        exclude: ^cli/tests/.+$|^setup.py$|^cli/src/semdep/external/packaging/.*$|^cli/src/semdep/external/parsy/.*$|^cli/scripts/.*$|^scripts/.+$|^stats/parsing-stats/.+$|^perf/.+$|^js/language_server/targets/.+$
        args: [--config, mypy.ini, --show-error-codes, --python-version, "3.11"]
        additional_dependencies: &mypy-deps
          # versions must be manually synced:
          # - cli/setup.py lists dependencies
          # - cli/Pipfile lists type hint packages for dev env
          # - .pre-commit-config.yaml's mypy hooks also list type hint packages

          # some packages ship with type definitions
          - attrs~=21.3
          # click 8.1.4's new decorator type hints don't seem to work well
          - click<8.1.4
          - defusedxml~=0.7.1
          - packaging~=21.0
          - requests~=2.22
          - rich~=13.5.2
          - tomli~=2.0.1
          - opentelemetry-api~=1.25.0
          - opentelemetry-sdk~=1.25.0
          - opentelemetry-exporter-otlp-proto-http~=1.25.0
          - opentelemetry-instrumentation-requests~=0.46b0
          # other packages have separate typings published
          - types-colorama~=0.4.0
          - types-freezegun
          - types-Jinja2
          - types-jsonschema~=4.6.0
          - types-python-dateutil
          - types-requests
          - types-setuptools
      - id: mypy
        alias: mypy-test
        name: mypy (test files)
        files: ^cli/tests/.+$
        args: [--config, mypy-tests.ini, --show-error-codes]
        additional_dependencies: *mypy-deps

  - repo: https://github.com/PyCQA/flake8
    rev: 6.1.0 # PLEASE DO NOT CHANGE [see NOTE "Python"]
    hooks:
      - id: flake8
        additional_dependencies: ["flake8-bugbear==22.1.11"]
        args: ["--select=B,E9,F4,F63,F7,F82"]

  # Reformat YAML, JSON, and Markdown files
  # NOTE: There is a bug in the current version of the hook (see fix here: https://github.com/prettier/prettier/issues/15742#issuecomment-1838040082)
  - repo: https://github.com/pre-commit/mirrors-prettier
    rev: v4.0.0-alpha.3
    hooks:
      - id: prettier
        entry: env PRETTIER_LEGACY_CLI=1 prettier
        types: [file]  # override `types: [json]`
        files: \.(json|yml|yaml|md)$

  # ----------------------------------------------------------
  # Extra hooks using additional_dependencies
  # ----------------------------------------------------------
  - repo: local
    hooks:
      - id: gitignore-to-dockerignore
        name: Convert .gitignore to .dockerignore
        language: node
        entry: gitignore-to-dockerignore
        files: ^.(git|docker)ignore$
        additional_dependencies: ["gitignore-to-dockerignore@2.0.0"]

  # ----------------------------------------------------------
  # Simple docker-based hooks
  # ----------------------------------------------------------
  - repo: local
    hooks:
      - id: shellcheck
        name: shellcheck
        language: docker_image
        entry: koalaman/shellcheck:v0.8.0
        files: "[.]sh$"
      - id: hadolint
        name: hadolint
        language: docker_image
        types:
          - dockerfile
        exclude: "^scripts/release/linuxbrew[.]Dockerfile$"
        entry: hadolint/hadolint:v2.8.0-alpine hadolint
        args: ["--ignore", "DL3008", "--ignore", "DL3018"]

  # ----------------------------------------------------------
  # Local hooks
  # ----------------------------------------------------------
  # Reformat OCaml code (ml, mli) with ocamlformat if ocamlformat is
  # present. If ocamlformat is missing, this step will succeed.
  # This allows pure python users to run 'pre-commit run --all' without
  # installing opam and ocamlformat.
  - repo: local
    hooks:
      - id: lint-ocaml
        name: Reformat OCaml code
        entry: ./scripts/lint-ocaml
        language: script
        files: "\\.mli?$"

  # ----------------------------------------------------------
  # Dogfood! running semgrep in pre-commit!
  # ----------------------------------------------------------
  - repo: https://github.com/returntocorp/semgrep
    rev: v1.54.0
    hooks:
      - id: semgrep
        name: Semgrep Jsonnet
        # This uses the Python "hook" in .pre-commit-hooks.yaml and setup.py.
        # alt: use 'repo: local', and 'language: docker', which can use
        # the very latest docker image, which is nice, but is far slower.
        language: python

        # Note that both the .semgrepignore file and the --exclude option
        # do nothing because the target files are passed
        # explicitly on the command line by pre-commit!!!
        # TODO: remove once file targeting is revamped and supports
        # filtering for explicit targets (a command-line flag should do)
        exclude: "tests"
        # TODO: we could also set 'pass_filenames: false', see
        # https://stackoverflow.com/questions/57199833/run-pre-commit-com-hook-once-not-for-every-file-if-a-matched-file-is-detected

        #coupling: 'make check' and the SEMGREP_ARGS variable.
        args: [
            # use osemgrep!
            "--experimental",
            # use jsonnet!
            "--config",
            "semgrep.jsonnet",
            # classic flag to use in CI or pre-commit, return error code if findings
            "--error",
            # Quiet or not quiet? Debug or not debug?
            # Pre-commit can call multiple times semgrep in
            # one run if there are many files in a PR (or in CI where
            # it runs on all the files in a repo).  In that case I
            # think pre-commit splits the list of files in multiple
            # batches and run one semgrep per batch. This is why it's
            # important to use --quiet otherwise you can have the same
            # banner repeated many times in the output in case of errors.
            # It's a much bigger problem though to not see the error messages.
            #"--verbose",
            #
            # this last option is useful in a pre-commit context because
            # pre-commit calls the hooks with all the files in the PR
            # (or in CI with all the files in the repo) but we don't
            # want an OCaml rule to be applied on a script.
            "--skip-unknown-extensions",
            # this can save 3sec sometimes (a bit sad, would be good to dodgood
            # also our metrics, but the slowdown is annoying)
            "--metrics=off",
            # commented on purpose: "--exclude tests"
            # This is implemented via the exclude: above (see the comment
            # further above to understand why)
          ]
  # Dogfooding .pre-commit-hooks.yml and setup.py
  # Use a fixed version of p/python and p/bandit to not get new findings
  # We run `semgrep ci` for our main rulesets
  - repo: https://github.com/returntocorp/semgrep
    rev: v1.51.0
    hooks:
      - id: semgrep
        name: Semgrep Python
        types: [python]
        exclude: "^cli/tests/.+$|^scripts/.+$|^cli/setup.py$"
        args: ["--config", "tests/precommit_dogfooding/python.yml", "--error"]
      - id: semgrep
        name: Semgrep Bandit
        types: [python]
        exclude: "^cli/tests/.+$|^scripts/.+$|^cli/setup.py$"
        args: ["--config", "tests/precommit_dogfooding/bandit.yml", "--error"]

  # Run Semgrep Docker images. Only used in CI since it's slower for local developmemt.
  # To run locally use `pre-commit run --hook-stage manual semgrep-docker-develop`
  - repo: https://github.com/returntocorp/semgrep
    # Note that the 'rev:' below is the revision to use to clone the URL
    # above and to fetch its .pre-commit-hooks.yaml file. It does not always
    # mean which version of semgrep to use for the hook itself.
    # For example in .pre-commit-hooks.yaml if you use an entry with
    # the 'language: docker_image', the 'entry:' field will then specify
    # which version to use. In the case below, we're actually running
    # returntocorp/semgrep:develop if you look at .pre-commit-hooks.yaml
    rev: v1.51.0
    hooks:
      - id: semgrep-docker-develop
        name: Semgrep Develop Python
        types: [python]
        exclude: "^cli/tests/.+$|^scripts/.+$|^cli/setup.py$"
        args: ["--config", "tests/precommit_dogfooding/python.yml", "--error"]
        stages: [manual]
      - id: semgrep-docker-develop
        name: Semgrep Develop Bandit
        types: [python]
        exclude: "^cli/tests/.+$|^scripts/.+$|^cli/setup.py$"
        args: ["--config", "tests/precommit_dogfooding/bandit.yml", "--error"]
        stages: [manual]
