# Code style and conventions This page documents code style policies that every Zulip developer should understand. We aim for this document to be short and focused only on details that cannot be easily enforced another way (e.g., through linters, automated tests, or subsystem design that makes classes of mistakes unlikely). This approach minimizes the cognitive load of ensuring a consistent coding style for both contributors and maintainers. One can summarize Zulip's coding philosophy as a relentless focus on making the codebase easy to understand and difficult to make dangerous mistakes in (see the sections on [dangerous constructs](#dangerous-constructs-in-django) at the end of this page). The majority of work in any large software development project is understanding the existing code so one can debug or modify it, and investments in code readability usually end up paying for themselves when someone inevitably needs to debug or improve the code. When there's something subtle or complex to explain or ensure in the implementation, we try hard to make it clear through a combination of clean and intuitive interfaces, well-named variables and functions, comments/docstrings, and commit messages (roughly in that order of priority -- if you can make something clear with a good interface, that's a lot better than writing a comment explaining how the bad interface works). After an introduction to our lint tools and test suites, this document outlines some general [conventions and practices](#follow-zulip-conventions-and-practices) applicable to all languages used in the codebase, as well as specific guidance on [Python](#python-specific-conventions-and-practices) and [JavaScript and TypeScript](#javascript-and-typescript-conventions-and-practices). ([HTML and CSS](../subsystems/html-css.md) are outlined in their own documentation.) At the end of the document, you can read about [dangerous constructs in Django](#dangerous-constructs-in-django) and [JavaScript and TypeScript](#dangerous-constructs-in-javascript-and-typescript) that you should absolutely avoid. ## Be consistent with existing code Look at the surrounding code, or a similar part of the project, and try to do the same thing. If you think the other code has actively bad style, fix it (in a separate commit). When in doubt, ask in [#development help](https://chat.zulip.org/#narrow/stream/49-development-help). ### Use the linters You can run all of the linters at once: ```bash $ ./tools/lint ``` Note that that takes a little time. `./tools/lint` runs many lint checks in parallel, including: - JavaScript ([ESLint](https://eslint.org/), [Prettier](https://prettier.io/)) - Python ([mypy](http://mypy-lang.org/), [ruff](https://github.com/astral-sh/ruff), [Black](https://github.com/psf/black), [isort](https://pycqa.github.io/isort/)) - templates - Puppet configuration - custom checks (e.g., trailing whitespace and spaces-not-tabs) To speed things up, you can [pass specific files or directories to the linter](../testing/linters.md): ``` $ ./tools/lint web/src/compose.js ``` If you'd like, you can also set up a local Git commit hook that will lint only your changed files each time you commit: ```bash $ ./tools/setup-git-repo ``` ### Use tests to verify your logic Clear, readable code is important for [tests](../testing/testing.md); familiarize yourself with our [testing frameworks](../testing/testing.md#major-test-suites) and [testing philosophy](../testing/philosophy.md) so that you can write clean, readable tests. In-test comments about anything subtle that is being verified are appreciated. You can run all of the tests like this: ``` $ ./tools/test-all ``` But consult [our documentation on running tests](../testing/testing.md#running-tests), which covers more targeted approaches to commanding the test-runners. ## Follow Zulip conventions and practices What follows is language-neutral advice that is beyond the bounds of linters and automated tests. ### Observe a reasonable line length We have an absolute hard limit on line length only for some files, but we should still avoid extremely long lines. A general guideline is: refactor stuff to get it under 85 characters, unless that makes the code a lot uglier, in which case it's fine to go up to 120 or so. ### Tag user-facing strings for translation Remember to [tag all user-facing strings for translation](../translating/translating.md), whether the strings are in HTML templates or output by JavaScript/TypeScript that injects or modifies HTML (e.g., error messages). ### Correctly prepare paths destined for state or log files When writing out state or log files, always pass an absolute path through `zulip_path` (found in `zproject/computed_settings.py`), which will do the right thing in both development and production. ### Never include secrets inline with code Please don't put any passwords, secret access keys, etc. inline in the code. Instead, use the `get_secret` function or the `get_mandatory_secret` function in `zproject/config.py` to read secrets from `/etc/zulip/secrets.conf`. ### Familiarize yourself with rules about third-party code See [our docs on dependencies](../subsystems/dependencies.md) for discussion of rules about integrating third-party projects. ## Python-specific conventions and practices - Our Python code is formatted with [Black](https://github.com/psf/black) and [isort](https://pycqa.github.io/isort/). The [linter tool](../testing/linters.md) enforces this by running Black and isort in check mode, or in write mode with `tools/lint --only=black,isort --fix`. You may find it helpful to [integrate Black](https://black.readthedocs.io/en/stable/integrations/editors.html) and [isort](https://pycqa.github.io/isort/#installing-isorts-for-your-preferred-text-editor) with your editor. - Don't put a shebang line on a Python file unless it's meaningful to run it as a script. (Some libraries can also be run as scripts, e.g., to run a test suite.) - Scripts should be executed directly (`./script.py`), so that the interpreter is implicitly found from the shebang line, rather than explicitly overridden (`python script.py`). - Put all imports together at the top of the file, absent a compelling reason to do otherwise. - Unpacking sequences doesn't require list brackets: ```python [x, y] = xs # unnecessary x, y = xs # better ``` - For string formatting, use `x % (y,)` rather than `x % y`, to avoid ambiguity if `y` happens to be a tuple. ## JavaScript and TypeScript conventions and practices Our JavaScript and TypeScript code is formatted with [Prettier](https://prettier.io/). You can ask Prettier to reformat all code via our [linter tool](../testing/linters.md) with `tools/lint --only=prettier --fix`. You can also [integrate it with your editor](https://prettier.io/docs/en/editors.html). ### Build DOM elements in Handlebars The best way to build complicated DOM elements is a Handlebars template like `web/templates/message_reactions.hbs`. For simpler things you can use jQuery DOM-building APIs like this: ```js const $new_tr = $('