# See https://semgrep.dev/docs/writing-rules/rule-syntax/ for documentation on YAML rule syntax

rules:
  ####################### PYTHON RULES #######################
  - id: deprecated-render-usage
    pattern: django.shortcuts.render_to_response(...)
    message: "Use render() (from django.shortcuts) instead of render_to_response()"
    languages: [python]
    severity: ERROR

  - id: dont-use-stream-objects-filter
    pattern: Stream.objects.filter(...)
    message: "Please use access_stream_by_*() to fetch Stream objects"
    languages: [python]
    severity: ERROR
    paths:
      include:
        - zerver/views/

  - id: dont-import-models-in-migrations
    patterns:
      - pattern-not: from zerver.lib.redis_utils import get_redis_client
      - pattern-not: from zerver.models import filter_pattern_validator
      - pattern-not: from zerver.models import filter_format_validator
      - pattern-not: from zerver.models import generate_email_token_for_stream
      - pattern-either:
          - pattern: from zerver import $X
          - pattern: from analytics import $X
          - pattern: from confirmation import $X
    message: "Don't import models or other code in migrations; see https://zulip.readthedocs.io/en/latest/subsystems/schema-migrations.html"
    languages: [python]
    severity: ERROR
    paths:
      include:
        - "**/migrations"
      exclude:
        - zerver/migrations/0032_verify_all_medium_avatar_images.py
        - zerver/migrations/0104_fix_unreads.py
        - zerver/migrations/0206_stream_rendered_description.py
        - zerver/migrations/0209_user_profile_no_empty_password.py
        - zerver/migrations/0260_missed_message_addresses_from_redis_to_db.py
        - zerver/migrations/0387_reupload_realmemoji_again.py
        - pgroonga/migrations/0002_html_escape_subject.py

  - id: logging-format
    languages: [python]
    patterns:
      - pattern-either:
          - pattern: $LOGGER.debug($FORMATTED)
          - pattern: $LOGGER.info($FORMATTED)
          - pattern: $LOGGER.warning($FORMATTED)
          - pattern: $LOGGER.error($FORMATTED)
          - pattern: $LOGGER.critical($FORMATTED)
          - pattern: $LOGGER.exception($FORMATTED)
      - metavariable-pattern:
          metavariable: $LOGGER
          patterns:
            - pattern-either:
                - pattern: logging
                - pattern: logger
      - metavariable-pattern:
          metavariable: $FORMATTED
          patterns:
            - pattern-either:
                - pattern: ... .format(...)
                - pattern: f"..."
    severity: ERROR
    message: "Pass format arguments to logging (https://docs.python.org/3/howto/logging.html#optimization)"

  - id: sql-format
    languages: [python]
    pattern-either:
      - pattern: ... .execute("...".format(...))
      - pattern: ... .execute(f"...")
      - pattern: psycopg2.sql.SQL(... .format(...))
      - pattern: psycopg2.sql.SQL(f"...")
      - pattern: django.db.migrations.RunSQL(..., "..." .format(...), ...)
      - pattern: django.db.migrations.RunSQL(..., f"...", ...)
      - pattern: django.db.migrations.RunSQL(..., [..., "..." .format(...), ...], ...)
      - pattern: django.db.migrations.RunSQL(..., [..., f"...", ...], ...)
    severity: ERROR
    message: "Do not write a SQL injection vulnerability please"

  - id: translated-format
    languages: [python]
    pattern-either:
      - pattern: django.utils.translation.gettext(... .format(...))
      - pattern: django.utils.translation.gettext(f"...")
      - pattern: django.utils.translation.gettext_lazy(... .format(...))
      - pattern: django.utils.translation.gettext_lazy(f"...")
    severity: ERROR
    message: "Format strings after translation, not before"

  - id: translated-format-lazy
    languages: [python]
    pattern: django.utils.translation.gettext_lazy(...).format(...)
    severity: ERROR
    message: "Immediately formatting a lazily translated string destroys its laziness"

  - id: mutable-default-type
    languages: [python]
    pattern-either:
      - pattern: |
          def $F(..., $A: typing.List[...] = [...], ...) -> ...:
              ...
      - pattern: |
          def $F(..., $A: typing.Optional[typing.List[...]] = [...], ...) -> ...:
              ...
      - pattern: |
          def $F(..., $A: typing.List[...] = zerver.lib.request.REQ(..., default=[...], ...), ...) -> ...:
              ...
      - pattern: |
          def $F(..., $A: typing.Optional[typing.List[...]] = zerver.lib.request.REQ(..., default=[...], ...), ...) -> ...:
              ...
      - pattern: |
          def $F(..., $A: typing.Dict[...] = {}, ...) -> ...:
              ...
      - pattern: |
          def $F(..., $A: typing.Optional[typing.Dict[...]] = {}, ...) -> ...:
              ...
      - pattern: |
          def $F(..., $A: typing.Dict[...] = zerver.lib.request.REQ(..., default={}, ...), ...) -> ...:
              ...
      - pattern: |
          def $F(..., $A: typing.Optional[typing.Dict[...]] = zerver.lib.request.REQ(..., default={}, ...), ...) -> ...:
              ...
      - pattern: |
          def $F(..., $A: typing.Set[...] = set(), ...) -> ...:
              ...
      - pattern: |
          def $F(..., $A: typing.Optional[typing.Set[...]] = set(), ...) -> ...:
              ...
    severity: ERROR
    message: "Guard mutable default with read-only type (Sequence, Mapping, AbstractSet)"

  - id: percent-formatting
    languages: [python]
    pattern-either:
      - pattern: '"..." % ...'
      - pattern: django.utils.translation.gettext(...) % ...
      - pattern: django.utils.translation.gettext_lazy(...) % ...
    severity: ERROR
    message: "Prefer f-strings or .format for string formatting"

  - id: eval
    languages: [python]
    pattern: eval
    severity: ERROR
    message: "Do not use eval under any circumstances; consider json.loads instead"

  - id: typing-text
    languages: [python]
    pattern: typing.Text
    severity: ERROR
    message: "Use str instead of typing.Text"

  - id: change-user-is-active
    languages: [python]
    patterns:
      - pattern-either:
          - pattern: |
              $X.is_active = ...
          - pattern: |
              setattr($X, 'is_active', ...)
      - pattern-not-inside: |
          def change_user_is_active(...):
            ...
    message: "Use change_user_is_active to mutate user_profile.is_active"
    severity: ERROR
    paths:
      exclude:
        - zerver/migrations/0373_fix_deleteduser_dummies.py

  - id: confirmation-object-get
    languages: [python]
    patterns:
      - pattern-either:
          - pattern: Confirmation.objects.get(...)
          - pattern: Confirmation.objects.filter(..., confirmation_key=..., ...)
      - pattern-not-inside: |
          def get_object_from_key(...):
            ...
    paths:
      exclude:
        - zerver/tests/
    message: "Do not fetch a Confirmation object directly, use get_object_from_key instead"
    severity: ERROR
