@@ -84,6 +84,48 @@ optional arguments:
84
84
--fix Automatically fix problems where supported
85
85
```
86
86
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
+
87
129
### pre-commit hook mode
88
130
89
131
See https://github.com/zulip/zulip/blob/master/tools/pre-commit for an
@@ -93,17 +135,171 @@ run the hook from outside Vagrant).
93
135
94
136
## Adding zulint to a codebase
95
137
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
+ ```
98
157
158
+ * Add ` <YOUR LINTER VARIABLE NAME>.do_lint() ` add the end of your lint file.
99
159
100
160
## Adding third-party linters
101
161
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.
103
217
104
218
## Writing custom rules
105
219
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.
107
303
108
304
## Development Setup
109
305
@@ -112,5 +308,5 @@ Run the following commands in a terminal to install zulint.
112
308
git clone [email protected] :zulip/zulint.git
113
309
python3 -m venv zulint_env
114
310
source zulint_env/bin/activate
115
- python3 setup.py install
311
+ pip install -e .
116
312
```
0 commit comments