]> git.proxmox.com Git - pve-eslint.git/blob - eslint/docs/developer-guide/working-with-rules-deprecated.md
63d7bb2fc69181db6c8c54f2aaf4ef6e277f2a85
[pve-eslint.git] / eslint / docs / developer-guide / working-with-rules-deprecated.md
1 # Working with Rules (Deprecated)
2
3 **Note:** This page covers the deprecated rule format for ESLint <= 2.13.1. [This is the most recent rule format](./working-with-rules.md).
4
5 Each rule in ESLint has two files named with its identifier (for example, `no-extra-semi`).
6
7 * in the `lib/rules` directory: a source file (for example, `no-extra-semi.js`)
8 * in the `tests/lib/rules` directory: a test file (for example, `no-extra-semi.js`)
9
10 **Important:** If you submit a **core** rule to the ESLint repository, you **must** follow some conventions explained below.
11
12 Here is the basic format of the source file for a rule:
13
14 ```js
15 /**
16 * @fileoverview Rule to disallow unnecessary semicolons
17 * @author Nicholas C. Zakas
18 */
19
20 "use strict";
21
22 //------------------------------------------------------------------------------
23 // Rule Definition
24 //------------------------------------------------------------------------------
25
26 module.exports = function(context) {
27 return {
28 // callback functions
29 };
30 };
31
32 module.exports.schema = []; // no options
33 ```
34
35 ## Rule Basics
36
37 `schema` (array) specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../user-guide/configuring/rules.md#configuring-rules)
38
39 `create` (function) returns an object with methods that ESLint calls to "visit" nodes while traversing the abstract syntax tree (AST as defined by [ESTree](https://github.com/estree/estree)) of JavaScript code:
40
41 * if a key is a node type, ESLint calls that **visitor** function while going **down** the tree
42 * if a key is a node type plus `:exit`, ESLint calls that **visitor** function while going **up** the tree
43 * if a key is an event name, ESLint calls that **handler** function for [code path analysis](./code-path-analysis.md)
44
45 A rule can use the current node and its surrounding tree to report or fix problems.
46
47 Here are methods for the [array-callback-return](../rules/array-callback-return.md) rule:
48
49 ```js
50 function checkLastSegment (node) {
51 // report problem for function if last code path segment is reachable
52 }
53
54 module.exports = function(context) {
55 // declare the state of the rule
56 return {
57 ReturnStatement: function(node) {
58 // at a ReturnStatement node while going down
59 },
60 // at a function expression node while going up:
61 "FunctionExpression:exit": checkLastSegment,
62 "ArrowFunctionExpression:exit": checkLastSegment,
63 onCodePathStart: function (codePath, node) {
64 // at the start of analyzing a code path
65 },
66 onCodePathEnd: function(codePath, node) {
67 // at the end of analyzing a code path
68 }
69 };
70 };
71 ```
72
73 ## The Context Object
74
75 The `context` object contains additional functionality that is helpful for rules to do their jobs. As the name implies, the `context` object contains information that is relevant to the context of the rule. The `context` object has the following properties:
76
77 * `parserOptions` - the parser options configured for this run (more details [here](../user-guide/configuring/language-options.md#specifying-parser-options)).
78 * `id` - the rule ID.
79 * `options` - an array of rule options.
80 * `settings` - the `settings` from configuration.
81 * `parserPath` - the full path to the `parser` from configuration.
82
83 Additionally, the `context` object has the following methods:
84
85 * `getAncestors()` - returns an array of ancestor nodes based on the current traversal.
86 * `getDeclaredVariables(node)` - returns the declared variables on the given node.
87 * `getFilename()` - returns the filename associated with the source.
88 * `getScope()` - returns the current scope.
89 * `getSourceCode()` - returns a `SourceCode` object that you can use to work with the source that was passed to ESLint
90 * `markVariableAsUsed(name)` - marks the named variable in scope as used. This affects the [no-unused-vars](../rules/no-unused-vars.md) rule.
91 * `report(descriptor)` - reports a problem in the code.
92
93 **Deprecated:** The following methods on the `context` object are deprecated. Please use the corresponding methods on `SourceCode` instead:
94
95 * `getAllComments()` - returns an array of all comments in the source. Use `sourceCode.getAllComments()` instead.
96 * `getComments(node)` - returns the leading and trailing comments arrays for the given node. Use `sourceCode.getComments(node)` instead.
97 * `getFirstToken(node)` - returns the first token representing the given node. Use `sourceCode.getFirstToken(node)` instead.
98 * `getFirstTokens(node, count)` - returns the first `count` tokens representing the given node. Use `sourceCode.getFirstTokens(node, count)` instead.
99 * `getJSDocComment(node)` - returns the JSDoc comment for a given node or `null` if there is none. Use `sourceCode.getJSDocComment(node)` instead.
100 * `getLastToken(node)` - returns the last token representing the given node. Use `sourceCode.getLastToken(node)` instead.
101 * `getLastTokens(node, count)` - returns the last `count` tokens representing the given node. Use `sourceCode.getLastTokens(node, count)` instead.
102 * `getNodeByRangeIndex(index)` - returns the deepest node in the AST containing the given source index. Use `sourceCode.getNodeByRangeIndex(index)` instead.
103 * `getSource(node)` - returns the source code for the given node. Omit `node` to get the whole source. Use `sourceCode.getText(node)` instead.
104 * `getSourceLines()` - returns the entire source code split into an array of string lines. Use `sourceCode.lines` instead.
105 * `getTokenAfter(nodeOrToken)` - returns the first token after the given node or token. Use `sourceCode.getTokenAfter(nodeOrToken)` instead.
106 * `getTokenBefore(nodeOrToken)` - returns the first token before the given node or token. Use `sourceCode.getTokenBefore(nodeOrToken)` instead.
107 * `getTokenByRangeStart(index)` - returns the token whose range starts at the given index in the source. Use `sourceCode.getTokenByRangeStart(index)` instead.
108 * `getTokens(node)` - returns all tokens for the given node. Use `sourceCode.getTokens(node)` instead.
109 * `getTokensAfter(nodeOrToken, count)` - returns `count` tokens after the given node or token. Use `sourceCode.getTokensAfter(nodeOrToken, count)` instead.
110 * `getTokensBefore(nodeOrToken, count)` - returns `count` tokens before the given node or token. Use `sourceCode.getTokensBefore(nodeOrToken, count)` instead.
111 * `getTokensBetween(node1, node2)` - returns the tokens between two nodes. Use `sourceCode.getTokensBetween(node1, node2)` instead.
112 * `report(node, [location], message)` - reports a problem in the code.
113
114 ### context.report()
115
116 The main method you'll use is `context.report()`, which publishes a warning or error (depending on the configuration being used). This method accepts a single argument, which is an object containing the following properties:
117
118 * `message` - the problem message.
119 * `node` - (optional) the AST node related to the problem. If present and `loc` is not specified, then the starting location of the node is used as the location of the problem.
120 * `loc` - (optional) an object specifying the location of the problem. If both `loc` and `node` are specified, then the location is used from `loc` instead of `node`.
121 * `line` - the 1-based line number at which the problem occurred.
122 * `column` - the 0-based column number at which the problem occurred.
123 * `data` - (optional) placeholder data for `message`.
124 * `fix` - (optional) a function that applies a fix to resolve the problem.
125
126 Note that at least one of `node` or `loc` is required.
127
128 The simplest example is to use just `node` and `message`:
129
130 ```js
131 context.report({
132 node: node,
133 message: "Unexpected identifier"
134 });
135 ```
136
137 The node contains all of the information necessary to figure out the line and column number of the offending text as well the source text representing the node.
138
139 You can also use placeholders in the message and provide `data`:
140
141 ```js
142 {% raw %}
143 context.report({
144 node: node,
145 message: "Unexpected identifier: {{ identifier }}",
146 data: {
147 identifier: node.name
148 }
149 });
150 {% endraw %}
151 ```
152
153 Note that leading and trailing whitespace is optional in message parameters.
154
155 The node contains all of the information necessary to figure out the line and column number of the offending text as well the source text representing the node.
156
157 ### Applying Fixes
158
159 If you'd like ESLint to attempt to fix the problem you're reporting, you can do so by specifying the `fix` function when using `context.report()`. The `fix` function receives a single argument, a `fixer` object, that you can use to apply a fix. For example:
160
161 ```js
162 context.report({
163 node: node,
164 message: "Missing semicolon".
165 fix: function(fixer) {
166 return fixer.insertTextAfter(node, ";");
167 }
168 });
169 ```
170
171 Here, the `fix()` function is used to insert a semicolon after the node. Note that the fix is not immediately applied and may not be applied at all if there are conflicts with other fixes. If the fix cannot be applied, then the problem message is reported as usual; if the fix can be applied, then the problem message is not reported.
172
173 The `fixer` object has the following methods:
174
175 * `insertTextAfter(nodeOrToken, text)` - inserts text after the given node or token
176 * `insertTextAfterRange(range, text)` - inserts text after the given range
177 * `insertTextBefore(nodeOrToken, text)` - inserts text before the given node or token
178 * `insertTextBeforeRange(range, text)` - inserts text before the given range
179 * `remove(nodeOrToken)` - removes the given node or token
180 * `removeRange(range)` - removes text in the given range
181 * `replaceText(nodeOrToken, text)` - replaces the text in the given node or token
182 * `replaceTextRange(range, text)` - replaces the text in the given range
183
184 Best practices for fixes:
185
186 1. Make fixes that are as small as possible. Anything more than a single character is risky and could prevent other, simpler fixes from being made.
187 1. Only make one fix per message. This is enforced because you must return the result of the fixer operation from `fix()`.
188 1. Fixes should not introduce clashes with other rules. You can accidentally introduce a new problem that won't be reported until ESLint is run again. Another good reason to make as small a fix as possible.
189
190 ### context.options
191
192 Some rules require options in order to function correctly. These options appear in configuration (`.eslintrc`, command line, or in comments). For example:
193
194 ```json
195 {
196 "quotes": [2, "double"]
197 }
198 ```
199
200 The `quotes` rule in this example has one option, `"double"` (the `2` is the error level). You can retrieve the options for a rule by using `context.options`, which is an array containing every configured option for the rule. In this case, `context.options[0]` would contain `"double"`:
201
202 ```js
203 module.exports = function(context) {
204
205 var isDouble = (context.options[0] === "double");
206
207 // ...
208 }
209 ```
210
211 Since `context.options` is just an array, you can use it to determine how many options have been passed as well as retrieving the actual options themselves. Keep in mind that the error level is not part of `context.options`, as the error level cannot be known or modified from inside a rule.
212
213 When using options, make sure that your rule has some logic defaults in case the options are not provided.
214
215 ### context.getSourceCode()
216
217 The `SourceCode` object is the main object for getting more information about the source code being linted. You can retrieve the `SourceCode` object at any time by using the `getSourceCode()` method:
218
219 ```js
220 module.exports = function(context) {
221
222 var sourceCode = context.getSourceCode();
223
224 // ...
225 }
226 ```
227
228 Once you have an instance of `SourceCode`, you can use the methods on it to work with the code:
229
230 * `getAllComments()` - returns an array of all comments in the source.
231 * `getComments(node)` - returns the leading and trailing comments arrays for the given node.
232 * `getFirstToken(node)` - returns the first token representing the given node.
233 * `getFirstTokens(node, count)` - returns the first `count` tokens representing the given node.
234 * `getJSDocComment(node)` - returns the JSDoc comment for a given node or `null` if there is none.
235 * `getLastToken(node)` - returns the last token representing the given node.
236 * `getLastTokens(node, count)` - returns the last `count` tokens representing the given node.
237 * `getNodeByRangeIndex(index)` - returns the deepest node in the AST containing the given source index.
238 * `isSpaceBetweenTokens(first, second)` - returns true if there is a whitespace character between the two tokens.
239 * `getText(node)` - returns the source code for the given node. Omit `node` to get the whole source.
240 * `getTokenAfter(nodeOrToken)` - returns the first token after the given node or token.
241 * `getTokenBefore(nodeOrToken)` - returns the first token before the given node or token.
242 * `getTokenByRangeStart(index)` - returns the token whose range starts at the given index in the source.
243 * `getTokens(node)` - returns all tokens for the given node.
244 * `getTokensAfter(nodeOrToken, count)` - returns `count` tokens after the given node or token.
245 * `getTokensBefore(nodeOrToken, count)` - returns `count` tokens before the given node or token.
246 * `getTokensBetween(node1, node2)` - returns the tokens between two nodes.
247
248 There are also some properties you can access:
249
250 * `hasBOM` - the flag to indicate whether or not the source code has Unicode BOM.
251 * `text` - the full text of the code being linted. Unicode BOM has been stripped from this text.
252 * `ast` - the `Program` node of the AST for the code being linted.
253 * `lines` - an array of lines, split according to the specification's definition of line breaks.
254
255 You should use a `SourceCode` object whenever you need to get more information about the code being linted.
256
257 ### Options Schemas
258
259 Rules may export a `schema` property, which is a [JSON schema](http://json-schema.org/) format description of a rule's options which will be used by ESLint to validate configuration options and prevent invalid or unexpected inputs before they are passed to the rule in `context.options`.
260
261 There are two formats for a rule's exported `schema`. The first is a full JSON Schema object describing all possible options the rule accepts, including the rule's error level as the first argument and any optional arguments thereafter.
262
263 However, to simplify schema creation, rules may also export an array of schemas for each optional positional argument, and ESLint will automatically validate the required error level first. For example, the `yoda` rule accepts a primary mode argument, as well as an extra options object with named properties.
264
265 ```js
266 // "yoda": [2, "never", { "exceptRange": true }]
267 module.exports.schema = [
268 {
269 "enum": ["always", "never"]
270 },
271 {
272 "type": "object",
273 "properties": {
274 "exceptRange": {
275 "type": "boolean"
276 }
277 },
278 "additionalProperties": false
279 }
280 ];
281 ```
282
283 In the preceding example, the error level is assumed to be the first argument. It is followed by the first optional argument, a string which may be either `"always"` or `"never"`. The final optional argument is an object, which may have a Boolean property named `exceptRange`.
284
285 To learn more about JSON Schema, we recommend looking at some [examples](http://json-schema.org/examples.html) to start, and also reading [Understanding JSON Schema](http://spacetelescope.github.io/understanding-json-schema/) (a free ebook).
286
287 ### Getting the Source
288
289 If your rule needs to get the actual JavaScript source to work with, then use the `sourceCode.getText()` method. This method works as follows:
290
291 ```js
292
293 // get all source
294 var source = sourceCode.getText();
295
296 // get source for just this AST node
297 var nodeSource = sourceCode.getText(node);
298
299 // get source for AST node plus previous two characters
300 var nodeSourceWithPrev = sourceCode.getText(node, 2);
301
302 // get source for AST node plus following two characters
303 var nodeSourceWithFollowing = sourceCode.getText(node, 0, 2);
304 ```
305
306 In this way, you can look for patterns in the JavaScript text itself when the AST isn't providing the appropriate data (such as location of commas, semicolons, parentheses, etc.).
307
308 ### Accessing comments
309
310 If you need to access comments for a specific node you can use `sourceCode.getComments(node)`:
311
312 ```js
313 // the "comments" variable has a "leading" and "trailing" property containing
314 // its leading and trailing comments, respectively
315 var comments = sourceCode.getComments(node);
316 ```
317
318 Keep in mind that comments are technically not a part of the AST and are only attached to it on demand, i.e. when you call `getComments()`.
319
320 **Note:** One of the libraries adds AST node properties for comments - do not use these properties. Always use `sourceCode.getComments()` as this is the only guaranteed API for accessing comments (we will likely change how comments are handled later).
321
322 ### Accessing Code Paths
323
324 ESLint analyzes code paths while traversing AST.
325 You can access that code path objects with five events related to code paths.
326
327 [details here](./code-path-analysis.md)
328
329 ## Rule Unit Tests
330
331 Each rule must have a set of unit tests submitted with it to be accepted. The test file is named the same as the source file but lives in `tests/lib/`. For example, if your rule source file is `lib/rules/foo.js` then your test file should be `tests/lib/rules/foo.js`.
332
333 For your rule, be sure to test:
334
335 1. All instances that should be flagged as warnings.
336 1. At least one pattern that should **not** be flagged as a warning.
337
338 The basic pattern for a rule unit test file is:
339
340 ```js
341 /**
342 * @fileoverview Tests for no-with rule.
343 * @author Nicholas C. Zakas
344 */
345
346 "use strict";
347
348 //------------------------------------------------------------------------------
349 // Requirements
350 //------------------------------------------------------------------------------
351
352 var rule = require("../../../lib/rules/no-with"),
353 RuleTester = require("../../../lib/testers/rule-tester");
354
355 //------------------------------------------------------------------------------
356 // Tests
357 //------------------------------------------------------------------------------
358
359 var ruleTester = new RuleTester();
360 ruleTester.run("no-with", rule, {
361 valid: [
362 "foo.bar()"
363 ],
364 invalid: [
365 {
366 code: "with(foo) { bar() }",
367 errors: [{ message: "Unexpected use of 'with' statement.", type: "WithStatement"}]
368 }
369 ]
370 });
371 ```
372
373 Be sure to replace the value of `"no-with"` with your rule's ID. There are plenty of examples in the `tests/lib/rules/` directory.
374
375 ### Valid Code
376
377 Each valid case can be either a string or an object. The object form is used when you need to specify additional global variables or arguments for the rule. For example, the following defines `window` as a global variable for code that should not trigger the rule being tested:
378
379 ```js
380 valid: [
381 {
382 code: "window.alert()",
383 globals: [ "window" ]
384 }
385 ]
386 ```
387
388 You can also pass options to the rule (if it accepts them). These arguments are equivalent to how people can configure rules in their `.eslintrc` file. For example:
389
390 ```js
391 valid: [
392 {
393 code: "var msg = 'Hello';",
394 options: [ "single" ]
395 }
396 ]
397 ```
398
399 The `options` property must be an array of options. This gets passed through to `context.options` in the rule.
400
401 ### Invalid Code
402
403 Each invalid case must be an object containing the code to test and at least one message that is produced by the rule. The `errors` key specifies an array of objects, each containing a message (your rule may trigger multiple messages for the same code). You should also specify the type of AST node you expect to receive back using the `type` key. The AST node should represent the actual spot in the code where there is a problem. For example:
404
405 ```js
406 invalid: [
407 {
408 code: "function doSomething() { var f; if (true) { var build = true; } f = build; }",
409 errors: [
410 { message: "build used outside of binding context.", type: "Identifier" }
411 ]
412 }
413 ]
414 ```
415
416 In this case, the message is specific to the variable being used and the AST node type is `Identifier`.
417
418 Similar to the valid cases, you can also specify `options` to be passed to the rule:
419
420 ```js
421 invalid: [
422 {
423 code: "function doSomething() { var f; if (true) { var build = true; } f = build; }",
424 options: [ "double" ],
425 errors: [
426 { message: "build used outside of binding context.", type: "Identifier" }
427 ]
428 }
429 ]
430 ```
431
432 For simpler cases where the only thing that really matters is the error message, you can also specify any `errors` as strings. You can also have some strings and some objects, if you like.
433
434 ```js
435 invalid: [
436 {
437 code: "'single quotes'",
438 options: ["double"],
439 errors: ["Strings must use doublequote."]
440 }
441 ]
442 ```
443
444 ### Specifying Parser Options
445
446 Some tests require that a certain parser configuration must be used. This can be specified in test specifications via the `parserOptions` setting.
447
448 For example, to set `ecmaVersion` to 6 (in order to use constructs like `for ... of`):
449
450 ```js
451 valid: [
452 {
453 code: "for (x of a) doSomething();",
454 parserOptions: { ecmaVersion: 6 }
455 }
456 ]
457 ```
458
459 If you are working with ES6 modules:
460
461 ```js
462 valid: [
463 {
464 code: "export default function () {};",
465 parserOptions: { ecmaVersion: 6, sourceType: "module" }
466 }
467 ]
468 ```
469
470 For non-version specific features such as JSX:
471
472 ```js
473 valid: [
474 {
475 code: "var foo = <div>{bar}</div>",
476 parserOptions: { ecmaFeatures: { jsx: true } }
477 }
478 ]
479 ```
480
481 The options available and the expected syntax for `parserOptions` is the same as those used in [configuration](../user-guide/configuring/language-options.md#specifying-parser-options).
482
483 ### Write Several Tests
484
485 Provide as many unit tests as possible. Your pull request will never be turned down for having too many tests submitted with it!
486
487 ## Performance Testing
488
489 To keep the linting process efficient and unobtrusive, it is useful to verify the performance impact of new rules or modifications to existing rules.
490
491 ### Overall Performance
492
493 The `npm run perf` command gives a high-level overview of ESLint running time with default rules (`eslint:recommended`) enabled.
494
495 ```bash
496 $ git checkout main
497 Switched to branch 'main'
498
499 $ npm run perf
500 CPU Speed is 2200 with multiplier 7500000
501 Performance Run #1: 1394.689313ms
502 Performance Run #2: 1423.295351ms
503 Performance Run #3: 1385.09515ms
504 Performance Run #4: 1382.406982ms
505 Performance Run #5: 1409.68566ms
506 Performance budget ok: 1394.689313ms (limit: 3409.090909090909ms)
507
508 $ git checkout my-rule-branch
509 Switched to branch 'my-rule-branch'
510
511 $ npm run perf
512 CPU Speed is 2200 with multiplier 7500000
513 Performance Run #1: 1443.736547ms
514 Performance Run #2: 1419.193291ms
515 Performance Run #3: 1436.018228ms
516 Performance Run #4: 1473.605485ms
517 Performance Run #5: 1457.455283ms
518 Performance budget ok: 1443.736547ms (limit: 3409.090909090909ms)
519 ```
520
521 ### Per-rule Performance
522
523 ESLint has a built-in method to track performance of individual rules. Setting the `TIMING` environment variable will trigger the display, upon linting completion, of the ten longest-running rules, along with their individual running time and relative performance impact as a percentage of total rule processing time.
524
525 ```bash
526 $ TIMING=1 eslint lib
527 Rule | Time (ms) | Relative
528 :-----------------------|----------:|--------:
529 no-multi-spaces | 52.472 | 6.1%
530 camelcase | 48.684 | 5.7%
531 no-irregular-whitespace | 43.847 | 5.1%
532 valid-jsdoc | 40.346 | 4.7%
533 handle-callback-err | 39.153 | 4.6%
534 space-infix-ops | 35.444 | 4.1%
535 no-undefined | 25.693 | 3.0%
536 no-shadow | 22.759 | 2.7%
537 no-empty-class | 21.976 | 2.6%
538 semi | 19.359 | 2.3%
539 ```
540
541 To test one rule explicitly, combine the `--no-eslintrc`, and `--rule` options:
542
543 ```bash
544 $ TIMING=1 eslint --no-eslintrc --rule "quotes: [2, 'double']" lib
545 Rule | Time (ms) | Relative
546 :------|----------:|--------:
547 quotes | 18.066 | 100.0%
548 ```
549
550 ## Rule Naming Conventions
551
552 The rule naming conventions for ESLint are fairly simple:
553
554 * If your rule is disallowing something, prefix it with `no-` such as `no-eval` for disallowing `eval()` and `no-debugger` for disallowing `debugger`.
555 * If your rule is enforcing the inclusion of something, use a short name without a special prefix.
556 * Keep your rule names as short as possible, use abbreviations where appropriate, and no more than four words.
557 * Use dashes between words.
558
559 ## Rule Acceptance Criteria
560
561 Because rules are highly personal (and therefore very contentious), accepted rules should:
562
563 * Not be library-specific.
564 * Demonstrate a possible issue that can be resolved by rewriting the code.
565 * Be general enough so as to apply for a large number of developers.
566 * Not be the opposite of an existing rule.
567 * Not overlap with an existing rule.
568
569 ## Runtime Rules
570
571 The thing that makes ESLint different from other linters is the ability to define custom rules at runtime. This is perfect for rules that are specific to your project or company and wouldn't make sense for ESLint to ship with. With runtime rules, you don't have to wait for the next version of ESLint or be disappointed that your rule isn't general enough to apply to the larger JavaScript community, just write your rules and include them at runtime.
572
573 Runtime rules are written in the same format as all other rules. Create your rule as you would any other and then follow these steps:
574
575 1. Place all of your runtime rules in the same directory (i.e., `eslint_rules`).
576 2. Create a [configuration file](../user-guide/configuring/) and specify your rule ID error level under the `rules` key. Your rule will not run unless it has a value of `1` or `2` in the configuration file.
577 3. Run the [command line interface](../user-guide/command-line-interface.md) using the `--rulesdir` option to specify the location of your runtime rules.