Skip to content

Commit 3d3e9ba

Browse files
committed
README: Add installation and linter documentation.
Update documentation on how to use Zulint. Describe RuleList, Rule and how to properly write them. Recommend pip for installation. Explan arguments to external linter and how to add one. Add example output.
1 parent 8cb430b commit 3d3e9ba

File tree

1 file changed

+201
-5
lines changed

1 file changed

+201
-5
lines changed

README.md

+201-5
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,48 @@ optional arguments:
8484
--fix Automatically fix problems where supported
8585
```
8686

87+
88+
**Example Output**
89+
90+
```
91+
❯ ./tools/lint
92+
js | Use channel module for AJAX calls at static/js/channel.js line 81:
93+
js | const jqXHR = $.ajax(args);
94+
py | avoid subject as a var at zerver/lib/email_mirror.py line 321:
95+
py | # strips RE and FWD from the subject
96+
py | Please use access_message() to fetch Message objects at zerver/worker/queue_processors.py line 579:
97+
py | message = Message.objects.get(id=event['message_id'])
98+
py | avoid subject as a var at zerver/lib/email_mirror.py line 327:
99+
py | Use do_change_is_admin function rather than setting UserProfile's is_realm_admin attribute directly. at file.py line 28:
100+
py | user.is_realm_admin = True
101+
puppet | /usr/lib/ruby/vendor_ruby/puppet/util.rb:461: warning: URI.escape is obsolete
102+
hbs | Avoid using the `style=` attribute; we prefer styling in CSS files at static/templates/group_pms.hbs line 6:
103+
hbs | <span class="user_circle_fraction" style="background:hsla(106, 74%, 44%, {{fraction_present}});"></span>
104+
pep8 | tools/linter_lib/custom_check.py:499:13: E121 continuation line under-indented for hanging indent
105+
pep8 | tools/linter_lib/custom_check.py:500:14: E131 continuation line unaligned for hanging indent
106+
```
107+
108+
To display `good_lines` and `bad_lines` along with errors, use `--verbose` option.
109+
110+
```
111+
❯ ./tools/lint --verbose
112+
py | Always pass update_fields when saving user_profile objects at zerver/lib/actions.py line 3160:
113+
py | user_profile.save() # Can't use update_fields because of how the foreign key works.
114+
py | Good code: user_profile.save(update_fields=["pointer"])
115+
py | Bad code: user_profile.save()
116+
py |
117+
py | Missing whitespace after ":" at zerver/tests/test_push_notifications.py line 535:
118+
py | 'realm_counts': '[{"id":1,"property":"invites_sent::day","subgroup":null,"end_time":574300800.0,"value":5,"realm":2}]',
119+
py | Good code: "foo": bar | "some:string:with:colons"
120+
py | Bad code: "foo":bar | "foo":1
121+
py |
122+
js | avoid subject in JS code at static/js/util.js line 279:
123+
js | message.topic = message.subject;
124+
js | Good code: topic_name
125+
js | Bad code: subject="foo" | MAX_SUBJECT_LEN
126+
js |
127+
```
128+
87129
### pre-commit hook mode
88130

89131
See https://github.com/zulip/zulip/blob/master/tools/pre-commit for an
@@ -93,17 +135,171 @@ run the hook from outside Vagrant).
93135

94136
## Adding zulint to a codebase
95137

96-
TODO. Will roughly include `pip install zulint`, copying an example
97-
`lint` script, and adding your rules.
138+
TODO: Make a pypi release
139+
140+
Add `zulint` to your codebase requirements file or just do:
141+
142+
```
143+
pip install zulint
144+
```
145+
146+
See [example-lint](./example-lint) file for a simple example of adding zulint to
147+
your codebase. For a more advanced usage example, you can look at
148+
[Zulip's linter](https://github.com/zulip/zulip/blob/master/tools/lint).
149+
150+
**NOTE**
151+
152+
* Remember to mark your lint file executable using:
153+
154+
```bash
155+
> chmod +x <YOUR LINT FILE>
156+
```
98157

158+
* Add `<YOUR LINTER VARIABLE NAME>.do_lint()` add the end of your lint file.
99159

100160
## Adding third-party linters
101161

102-
TODO: Document the linter_config API.
162+
First import the `LinterConfig` and initialize it with default arguments.
163+
You can then use the `external_linter` method to register the linter.
164+
165+
eg:
166+
167+
```python
168+
169+
linter_config.external_linter('eslint', ['node', 'node_modules/.bin/eslint',
170+
'--quiet', '--cache', '--ext', '.js,.ts'], ['js', 'ts'],
171+
fix_arg='--fix',
172+
description="Standard JavaScript style and formatting linter"
173+
"(config: .eslintrc).")
174+
```
175+
176+
The `external_linter` method takes the following arguments:
177+
178+
* name: Name of the linter. It will be printer before the failed code to show
179+
which linter is failing. | `REQUIRED`
180+
* command: The terminal command to execute your linter in "shell-like syntax".
181+
You can use `shlex.split("SHELL COMMAND TO RUN LINTER")` to split your
182+
command. | `REQUIRED`
183+
* target_langs: The language files this linter should run on. Leave this argument
184+
empty (= `[]`) to run on all the files. | `RECOMMENDED`
185+
* pass_targets: Pass target files (aka files in the specified `target_langs`) to
186+
the linter command when executing it. Default: `True` | `OPTIONAL`
187+
* fix_arg: Some linters support fixing the errors automatically. Set it to the flag
188+
used by the linter to fix the errors. | `OPTIONAL`
189+
* description: The description of your linter to be printed with `--list` argument. | `RECOMMENDED`
190+
191+
eg:
192+
193+
```
194+
❯ ./tools/lint --list
195+
Linter Description
196+
css Standard CSS style and formatting linter (config: .stylelintrc)
197+
eslint Standard JavaScript style and formatting linter(config: .eslintrc).
198+
puppet Runs the puppet parser validator, checking for syntax errors.
199+
puppet-lint Standard puppet linter(config: tools/linter_lib/exclude.py)
200+
templates Custom linter checks whitespace formattingof HTML templates.
201+
openapi Validates our OpenAPI/Swagger API documentation(zerver/openapi/zulip.yaml)
202+
shellcheck Standard shell script linter.
203+
mypy Static type checker for Python (config: mypy.ini)
204+
tsc TypeScript compiler (config: tsconfig.json)
205+
yarn-deduplicate Shares duplicate packages in yarn.lock
206+
gitlint Checks commit messages for common formatting errors.(config: .gitlint)
207+
semgrep-py Syntactic Grep (semgrep) Code Search Tool (config: ./tools/semgrep.yml)
208+
custom_py Runs custom checks for python files (config: tools/linter_lib/custom_check.py)
209+
custom_nonpy Runs custom checks for non-python files (config: tools/linter_lib/custom_check.py)
210+
pyflakes Standard Python bug and code smell linter (config: tools/linter_lib/pyflakes.py)
211+
pep8_1of2 Standard Python style linter on 50% of files (config: tools/linter_lib/pep8.py)
212+
pep8_2of2 Standard Python style linter on other 50% of files (config: tools/linter_lib/pep8.py)
213+
```
214+
215+
Please make sure external linter (here `eslint`) is accessible via bash or in the
216+
virtual env where this linter will run.
103217

104218
## Writing custom rules
105219

106-
TODO: Document all the features of the `RuleList` and `custom_check` system.
220+
You can write your own custom rules for any language using regular expression
221+
in zulint. Doing it is very simple and there are tons of examples available
222+
in [Zulip's custom_check.py file](https://github.com/zulip/zulip/blob/master/tools/linter_lib/custom_check.py).
223+
224+
In the [above example](#adding-third-party-linters) you can add custom rules via `@linter_config.lint` decorator.
225+
For eg:
226+
227+
```python
228+
229+
from zulint.custom_rules import RuleList
230+
231+
@linter_config.lint
232+
def check_custom_rules():
233+
# type: () -> int
234+
"""Check trailing whitespace for specified files"""
235+
trailing_whitespace_rule = RuleList(
236+
langs=file_types,
237+
rules=[{
238+
'pattern': r'\s+$',
239+
'strip': '\n',
240+
'description': 'Fix trailing whitespace'
241+
}]
242+
)
243+
failed = trailing_whitespace_rule.check(by_lang, verbose=args.verbose)
244+
return 1 if failed else 0
245+
```
246+
247+
#### RuleList
248+
A new custom rule is defined via the `RuleList` class. `RuleList` takes the following arguments:
249+
250+
```python
251+
langs # The languages this rule will run on. eg: ['py', 'bash']
252+
rules # List of custom `Rule`s to run. See definition of Rule below for more details.
253+
max_length # Set a max length value for each line in the files. eg: 79
254+
length_exclude # List of files to exclude from `max_length` limit. eg: ["README"]
255+
shebang_rules # List of shebang `Rule`s to run in `langs`. Default: []
256+
exclude_files_in # Directory to exclude from all rules. eg: 'app/' Default: None
257+
exclude_max_length_fns # List of file names to exclude from max_length limit. eg: [test, example] Defautl: []
258+
exclude_max_length_line_patterns # List of line patterns to exclude from max_length limit. eg: ["`\{\{ api_url \}\}[^`]+`"]
259+
```
260+
261+
#### Rule
262+
A rule is a python dictionary containing regular expression,
263+
which will be run on each line in the `langs`' files specified in the `RuleList`.
264+
It has a lot of additional features which you can use to run the pattern in
265+
specific areas of your codebase.
266+
267+
Find below all the keys that a `Rule` can have along with the
268+
type of inputs they take.
269+
270+
```python
271+
Rule = TypedDict("Rule", {
272+
"bad_lines": List[str],
273+
"description": str,
274+
"exclude": Set[str],
275+
"exclude_line": Set[Tuple[str, str]],
276+
"exclude_pattern": str,
277+
"good_lines": List[str],
278+
"include_only": Set[str],
279+
"pattern": str,
280+
"strip": str,
281+
"strip_rule": str,
282+
}, total=False)
283+
```
284+
285+
* `pattern` is your regular expression to be run on all the eligible lines (i.e. lines which haven't been excluded by you).
286+
* `description` is the message that will be displayed if a pattern match is found.
287+
* `good_lines` are the list of sample lines which shouldn't match the pattern.
288+
* `bad_lines` are like `good_lines` but they match the pattern.
289+
290+
**NOTE**: `patten` is run on `bad_lines` and `good_lines` and you can use them as an example to tell the developer
291+
what is wrong with their code and how to fix it.
292+
293+
* `exclude` List of folders to exclude.
294+
* `exclude_line` Tuple of filename and pattern to exclude from pattern check.
295+
eg:
296+
297+
```python
298+
('zerver/lib/actions.py', "user_profile.save() # Can't use update_fields because of how the foreign key works.")`
299+
```
300+
301+
* `exclude_pattern`: pattern to exclude from the matching patterns.
302+
* `include_only`: `pattern` is only run on these files.
107303

108304
## Development Setup
109305

@@ -112,5 +308,5 @@ Run the following commands in a terminal to install zulint.
112308
git clone [email protected]:zulip/zulint.git
113309
python3 -m venv zulint_env
114310
source zulint_env/bin/activate
115-
python3 setup.py install
311+
pip install -e .
116312
```

0 commit comments

Comments
 (0)