Zulip also uses some home-grown code to perform tasks like validating
indentation in template files, enforcing coding standards that are unique
to Zulip, allowing certain errors from third party linters to pass through,
and exempting legacy files from lint checks.

Finally, you can rely on our Travis CI setup to run linters for you, but
it is good practice to run lint checks locally.

Important: We provide a
Git pre-commit hook
that can automatically run tools/lint on just the files that
changed (in a few 100ms) whenever you make a commit. This can save
you a lot of time, by automatically detecting linter errors as you
make them.

Note: The linters only check files that git tracks. Remember to gitadd
new files before running lint checks.

Our linting tools generally support the ability to lint files
individually–with some caveats–and those options will be described
later in this document.

We may eventually bundle run-mypy into lint, but mypy is pretty
resource intensive compared to the rest of the linters, because it does
static code analysis. So we keep mypy separate to allow folks to quickly run
the other lint checks.

Once you have read the Zulip coding guidelines, you can
be pretty confident that 99% of the code that you write will pass through
the linters fine, as long as you are thorough about keeping your code clean.
And, of course, for minor oversights, lint is your friend, not your foe.

Occasionally, our linters will complain about things that are more of
an artifact of the linter limitations than any actual problem with your
code. There is usually a mechanism where you can bypass the linter in
extreme cases, but often it can be a simple matter of writing your code
in a slightly different style to appease the linter. If you have
problems getting something to lint, you can submit an unfinished PR
and ask the reviewer to help you work through the lint problem, or you
can find other people in the Zulip Community
to help you.

Also, bear in mind that 100% of the lint code is open source, so if you
find limitations in either the Zulip home-grown stuff or our third party
tools, feedback will be highly appreciated.

Finally, one way to clean up your code is to thoroughly exercise it
with tests. The Zulip test documentation
describes our test system in detail.

Zulip has a script called lint that lives in our “tools” directory.
It is the workhorse of our linting system, although in some cases it
dispatches the heavy lifting to other components such as pyflakes,
eslint, and other home grown tools.

In order for our entire lint suite to run in a timely fashion, the lint
script performs several lint checks in parallel by forking out subprocesses. This mechanism
is still evolving, but you can look at the method run_parallel to get the
gist of how it works.

Note that our project does custom regex-based checks on the code, and we
also customize how we call pyflakes and pycodestyle (pep8). The code for these
types of checks mostly lives here.

You can use the -h option for lint to see its usage. One particular
flag to take note of is the --modified flag, which enables you to only run
lint checks against files that are modified in your git repo. Most of the
“sub-linters” respect this flag, but some will continue to process all the files.
Generally, a good workflow is to run with --modified when you are iterating on
the code, and then run without that option right before committing new code.

If you need to troubleshoot the linters, there is a --verbose option that
can give you clues about which linters may be running slow, for example.

We check almost our entire codebase for trailing whitespace. Also, we
disallow tab (\t) characters in all but two files.

We also have custom regex-based checks that apply to specific file types.
For relatively minor files like Markdown files and JSON fixtures, this
is the extent of our checking.

Finally, we’re checking line length in Python code (and hope to extend
this to other parts of the codebase soon). You can use
#ignorelinelength for special cases where a very long line makes
sense (e.g. a link in a comment to an extremely long URL).

The bulk of our Python linting gets outsourced to the “pyflakes” tool. We
call “pyflakes” in a fairly vanilla fashion, and then we post-process its
output to exclude certain types of errors that Zulip is comfortable
ignoring. (One notable class of error that Zulip currently tolerates is
unused imports–because of the way mypy type annotations work in Python 2,
it would be inconvenient to enforce this too strictly.)

Zulip also has custom regex-based rules that it applies to Python code.
Look for python_rules in the source code for lint. Note that we
provide a mechanism to exclude certain lines of codes from these checks.
Often, it is simply the case that our regex approach is too crude to
correctly exonerate certain valid constructs. In other cases, the code
that we exempt may be deemed not worthwhile to fix.

We verify that all addClass calls, with a few exceptions, explicitly
contain a CSS class.

The last check happens via a call to ./tools/find-add-class. This
particular check is a work in progress, as we are trying to evolve a
more rigorous system for weeding out legacy CSS styles, and the ability
to quickly introspect our JS code for addClass calls is part of our
vision.

Our goal is to have most common style issues by caught by the linters, so new
contributors to the codebase can efficiently fix produce code with correct
style without needing to go back-and-forth with a reviewer.