@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1@title Arcanist User Guide: Lint
2@group userguide
3
4Guide to lint, linters, and linter configuration.
5
6This is a configuration guide that helps you set up advanced features. If you're
7just getting started, you don't need to look at this yet. Instead, start with
8the @{article:Arcanist User Guide}.
9
10This guide explains how lint works when configured in an `arc` project. If
11you haven't set up a project yet, do that first. For instructions, see
12@{article:Arcanist User Guide: Configuring a New Project}.
13
14
15Overview
16========
17
18"Lint" refers to a general class of programming tools which analyze source code
19and raise warnings and errors about it. For example, a linter might raise
20warnings about syntax errors, uses of undeclared variables, calls to deprecated
21functions, spacing and formatting conventions, misuse of scope, implicit
22fallthrough in switch statements, missing license headers, use of dangerous
23language features, or a variety of other issues.
24
25Integrating lint into your development pipeline has two major benefits:
26
27 - you can detect and prevent a large class of programming errors; and
28 - you can simplify code review by addressing many mechanical and formatting
29 problems automatically.
30
31When arc is integrated with a lint toolkit, it enables the `arc lint` command
32and runs lint on changes during `arc diff`. The user is prompted to fix errors
33and warnings before sending their code for review, and lint issues which are
34not fixed are visible during review.
35
36There are many lint and static analysis tools available for a wide variety of
37languages. Arcanist ships with bindings for many popular tools, and you can
38write new bindings fairly easily if you have custom tools.
39
40
41Available Linters
42=================
43
44To see a list of available linters, run:
45
46 $ arc linters
47
48Arcanist ships with bindings for a number of linters that can check for errors
49or problems in JS, CSS, PHP, Python, C, C++, C#, Less, Puppet, Ruby, JSON, XML,
50and several other languages.
51
52Some general purpose linters are also available. These linters can check for
53cross-language issues like sensible filenames, trailing or mixed whitespace,
54character sets, spelling mistakes, and unresolved merge conflicts.
55
56If you have a tool you'd like to use as a linter that isn't supported by
57default, you can write bindings for it. For information on writing new linter
58bindings, see @{article:Arcanist User Guide: Customizing Lint, Unit Tests and
59Workflows}.
60
61
62Configuring Lint
63================
64
65To configure lint integration for your project, create a file called `.arclint`
66at the project root. This file should be in JSON format, and look like this:
67
68```lang=js
69{
70 "linters": {
71 "sample": {
72 "type": "pep8"
73 }
74 }
75}
76```
77
78Here, the key ("sample") is a human-readable label identifying the linter. It
79does not affect linter behavior, so just choose something that makes sense to
80you.
81
82The `type` specifies which linter to run. Use `arc linters` to find the names of
83the available linters.
84
85**Including and Excluding Files**: By default, a linter will run on every file.
86This is appropriate for some linters (like the Filename linter), but normally
87you only want to run a linter like **pep8** on Python files. To include or
88exclude files, use `include` and `exclude`:
89
90```lang=js
91{
92 "linters": {
93 "sample": {
94 "type": "pep8",
95 "include": "(\\.py$)",
96 "exclude": "(^third-party/)"
97 }
98 }
99}
100```
101
102The `include` key is a regular expression (or list of regular expressions)
103identifying paths the linter should be run on, while `exclude` is a regular
104expression (or list of regular expressions) identifying paths which it should
105not run on.
106
107Thus, this configures a **pep8** linter named "sample" which will run on files
108ending in ".py", unless they are inside the "third-party/" directory.
109
110In these examples, regular expressions are written in this style:
111
112 "(example/path)"
113
114They can be specified with any delimiters, but using `(` and `)` means you don't
115have to escape slashes in the expression, so it may be more convenient to
116specify them like this. If you prefer, these are all equivalent:
117
118 "(example/path)i"
119 "/example\\/path/i"
120 "@example/path@i"
121
122You can also exclude files globally, so no linters run on them at all. Do this
123by specifying `exclude` at top level:
124
125```lang=js
126{
127 "exclude": "(^tests/data/)",
128 "linters": {
129 "sample": {
130 "type": "pep8",
131 "include": "(\\.py$)",
132 "exclude": "(^third-party/)"
133 }
134 }
135}
136```
137
138Here, the addition of a global `exclude` rule means no linter will be run on
139files in "tests/data/".
140
141**Running Multiple Linters**: Often, you will want to run several different
142linters. Perhaps your project has a mixture of Python and Javascript code, or
143you have some PHP and some JSON files. To run multiple linters, just list
144them in the `linters` map:
145
146```lang=js
147{
148 "linters": {
149 "jshint": {
150 "type": "jshint",
151 "include": "(\\.js$)"
152 },
153 "xml": {
154 "type": "xml",
155 "include": "(\\.xml$)"
156 }
157 }
158}
159```
160
161This will run JSHint on `.js` files, and SimpleXML on `.xml` files.
162
163**Adjusting Message Severities**: Arcanist raises lint messages at various
164severities. Each message has a different severity: for example, lint might
165find a syntax error and raise an `error` about it, and find trailing whitespace
166and raise a `warning` about it.
167
168Normally, you will be presented with lint messages as you are sending code for
169review. In that context, the severities behave like this:
170
171 - `error` When a file contains lint errors, they are always reported. These
172 are intended to be severe problems, like a syntax error. Unresolved lint
173 errors require you to confirm that you want to continue.
174 - `warning` When a file contains warnings, they are reported by default only
175 if they appear on lines that you have changed. They are intended to be
176 minor problems, like unconventional whitespace. Unresolved lint warnings
177 require you to confirm that you want to continue.
178 - `autofix` This level is like `warning`, but if the message includes patches
179 they will be applied automatically without prompting.
180 - `advice` Like warnings, these messages are only reported on changed lines.
181 They are intended to be very minor issues which may merit a note, like a
182 "TODO" comment. They do not require confirmation.
183 - `disabled` This level suppresses messages. They are not displayed. You can
184 use this to turn off a message if you don't care about the issue it
185 detects.
186
187By default, Arcanist tries to select reasonable severities for each message.
188However, you may want to make a message more or less severe, or disable it
189entirely.
190
191For many linters, you can do this by providing a `severity` map:
192
193```lang=js
194{
195 "linters": {
196 "sample": {
197 "type": "pep8",
198 "severity": {
199 "E221": "disabled",
200 "E401": "warning"
201 }
202 }
203 }
204}
205```
206
207Here, the lint message `E221` (which is "multiple spaces before operator") is
208disabled, so it won't be shown. The message `E401` (which is "multiple imports
209on one line") is set to "warning" severity.
210
211If you want to remap a large number of messages, you can use `severity.rules`
212and specify regular expressions:
213
214```lang=js
215{
216 "linters": {
217 "sample": {
218 "type": "pep8",
219 "severity.rules": {
220 "(^E)": "warning",
221 "(^W)": "advice"
222 }
223 }
224 }
225}
226```
227
228This adjusts the severity of all "E" codes to "warning", and all "W" codes to
229"advice".
230
231**Locating Binaries and Interpreters**: Normally, Arcanist expects to find
232external linters (like `pep8`) in `$PATH`, and be able to run them without any
233special qualifiers. That is, it will run a command similar to:
234
235 $ pep8 example.py
236
237If you want to use a different copy of a linter binary, or invoke it in an
238explicit way, you can use `interpreter` and `bin`. These accept strings (or
239lists of strings) identifying places to look for linters. For example:
240
241
242```lang=js
243{
244 "linters": {
245 "sample": {
246 "type": "pep8",
247 "interpreter": ["python2.6", "python"],
248 "bin": ["/usr/local/bin/pep8-1.5.6", "/usr/local/bin/pep8"]
249 }
250 }
251}
252```
253
254When configured like this, `arc` will walk the `interpreter` list to find an
255available interpreter, then walk the `bin` list to find an available binary.
256If it can locate an appropriate interpreter and binary, it will execute those
257instead of the defaults. For example, this might cause it to execute a command
258similar to:
259
260 $ python2.6 /usr/local/bin/pep8-1.5.6 example.py
261
262**Additional Options**: Some linters support additional options to configure
263their behavior. You can run this command get a list of these options and
264descriptions of what they do and how to configure them:
265
266 $ arc linters --verbose
267
268This will show the available options for each linter in detail.
269
270**Running Different Rules on Different Files**: Sometimes, you may want to
271run the same linter with different rulesets on different files. To do this,
272create two copies of the linter and just give them different keys in the
273`linters` map:
274
275```lang=js
276{
277 "linters": {
278 "pep8-relaxed": {
279 "type": "pep8",
280 "include": "(^legacy/.*\\.py$)",
281 "severity.rules": {
282 "(.*)": "advice"
283 }
284 },
285 "pep8-normal": {
286 "type": "pep8",
287 "include": "(\\.py$)",
288 "exclude": "(^legacy/)"
289 }
290 }
291}
292```
293
294This example will run a relaxed version of the linter (which raises every
295message as advice) on Python files in "legacy/", and a normal version everywhere
296else.
297
298**Example .arclint Files**: You can find a collection of example files in
299`arcanist/resources/arclint/` to use as a starting point or refer to while
300configuring your own `.arclint` file.
301
302Advanced Configuration: Lint Engines
303====================================
304
305If you need to specify how linters execute in greater detail than is possible
306with `.arclint`, you can write a lint engine in PHP to extend Arcanist. This is
307an uncommon, advanced use case. The remainder of this section overviews how the
308lint internals work, and discusses how to extend Arcanist with a custom lint
309engine. If your needs are met by `.arclint`, you can skip to the next section
310of this document.
311
312The lint pipeline has two major components: linters and lint engines.
313
314**Linters** are programs which detect problems in a source file. Usually a
315linter is an external script, which Arcanist runs and passes a path to, like
316`jshint` or `pep8`.
317
318The script emits some messages, and Arcanist parses the output into structured
319errors. A piece of glue code (like @{class@arcanist:ArcanistJSHintLinter} or
320@{class@arcanist:ArcanistPEP8Linter}) handles calling the external script and
321interpreting its output.
322
323**Lint engines** coordinate linters, and decide which linters should run on
324which files. For instance, you might want to run `jshint` on all your `.js`
325files, and `pep8.py` on all your `.py` files. And you might not want to lint
326anything in `externals/` or `third-party/`, and maybe there are other files
327which you want to exclude or apply special rules for.
328
329By default, Arcanist uses the
330@{class@arcanist:ArcanistConfigurationDrivenLintEngine} engine if there is
331an `.arclint` file present in the working copy. This engine reads the `.arclint`
332file and uses it to decide which linters should be run on which paths. If no
333`.arclint` is present, Arcanist does not select an engine by default.
334
335You can write a custom lint engine instead, which can make a more powerful set
336of decisions about which linters to run on which paths. For instructions on
337writing a custom lint engine, see @{article:Arcanist User Guide: Customizing
338Lint, Unit Tests and Workflows}.
339
340To name an alternate lint engine, set `lint.engine` in your `.arcconfig` to the
341name of a class which extends @{class@arcanist:ArcanistLintEngine}. For more
342information on `.arcconfig`, see @{article:Arcanist User Guide: Configuring a
343New Project}.
344
345You can also set a default lint engine by setting `lint.engine` in your global
346user config with `arc set-config lint.engine`, or specify one explicitly with
347`arc lint --engine <engine>`. This can be useful for testing.
348
349There are several other engines bundled with Arcanist, but they are primarily
350predate `.arclint` and are effectively obsolete.
351
352
353Using Lint to Improve Code Review
354=================================
355
356Code review is most valuable when it's about the big ideas in a change. It is
357substantially less valuable when it devolves into nitpicking over style,
358formatting, and naming conventions.
359
360The best response to receiving a review request full of style problems is
361probably to reject it immediately, point the author at your coding convention
362documentation, and ask them to fix it before sending it for review. But even
363this is a pretty negative experience for both parties, and less experienced
364reviewers sometimes go through the whole review and point out every problem
365individually.
366
367Lint can greatly reduce the negativity of this whole experience (and the amount
368of time wasted arguing about these things) by enforcing style and formatting
369rules automatically. Arcanist supports linters that not only raise warnings
370about these problems, but provide patches and fix the problems for the author --
371before the code goes to review.
372
373Good linter integration means that code is pretty much mechanically correct by
374the time any reviewer sees it, provides clear rules about style which are
375especially helpful to new authors, and has the overall effect of pushing
376discussion away from stylistic nitpicks and toward useful examination of large
377ideas.
378
379It can also provide a straightforward solution to arguments about style, if you
380adopt a policy like this:
381
382 - If a rule is important enough that it should be enforced, the proponent must
383 add it to lint so it is automatically detected or fixed in the future and
384 no one has to argue about it ever again.
385 - If it's not important enough for them to do the legwork to add it to lint,
386 they have to stop complaining about it.
387
388This may or may not be an appropriate methodology to adopt at your organization,
389but it generally puts the incentives in the right places.
390
391
392Philosophy of Lint
393==================
394
395Some general thoughts on how to develop lint effectively, based on building
396lint tools at Facebook:
397
398 - Don't write regex-based linters to enforce language rules. Use a real parser
399 or AST-based tool. This is not a domain you can get right at any nontrivial
400 complexity with raw regexes. That is not a challenge. Just don't do this.
401 - False positives are pretty bad and should be avoided. You should aim to
402 implement only rules that have very few false positives, and provide ways to
403 mark false positives as OK. If running lint always raises 30 warnings about
404 irrelevant nonsense, it greatly devalues the tool.
405 - Move toward autocorrect rules. Most linters do not automatically correct
406 the problems they detect, but Arcanist supports this and it's quite
407 valuable to have the linter not only say "the convention is to put a space
408 after comma in a function call" but to fix it for you.
409
410= Next Steps =
411
412Continue by:
413
414 - integrating and customizing built-in linters and lint bindings with
415 @{article:Arcanist User Guide: Customizing Existing Linters}; or
416 - use a linter that hasn't been integrated into Arcanist with
417 @{article:Arcanist User Guide: Script and Regex Linter}; or
418 - learning how to add new linters and lint engines with
419 @{article:Arcanist User Guide: Customizing Lint, Unit Tests and Workflows}.