1 | # tslint-immutable
|
2 |
|
3 | [![npm version][version-image]][version-url]
|
4 | [![travis build][travis-image]][travis-url]
|
5 | [![Coverage Status][coveralls-image]][coveralls-url]
|
6 | [![code style: prettier][prettier-image]][prettier-url]
|
7 | [![MIT license][license-image]][license-url]
|
8 |
|
9 | [TSLint](https://palantir.github.io/tslint/) rules to disable mutation in TypeScript.
|
10 |
|
11 | ## Background
|
12 |
|
13 | In some applications it is important to not mutate any data, for example when using Redux to store state in a React application. Moreover immutable data structures has a lot of advantages in general so I want to use them everywhere in my applications.
|
14 |
|
15 | I originally used [immutablejs](https://github.com/facebook/immutable-js/) for this purpose. It is a really nice library but I found it had some drawbacks. Specifically when debugging it was hard to see the structure, creating JSON was not straightforward, and passing parameters to other libraries required converting to regular mutable arrays and objects. The [seamless-immutable](https://github.com/rtfeldman/seamless-immutable) project seems to have the same conclusions and they use regular objects and arrays and check for immutability at run-time. This solves all the aformentioned drawbacks but introduces a new drawback of only being enforced at run-time. (Altough you loose the structural sharing feature of immutablejs with this solution so you would have to consider if that is something you need).
|
16 |
|
17 | Then typescript 2.0 came along and introduced [readonly](https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#read-only-properties-and-index-signatures) options for properties, indexers and arrays. This enables us to use regular object and arrays and have the immutability enfored at compile time instead of run-time. Now the only drawback is that there is nothing enforcing the use of readonly in typescript.
|
18 |
|
19 | This can be solved by using linting rules. So the aim of this project is to leverage the type system in typescript to enforce immutability at compile-time while still using regular objects and arrays.
|
20 |
|
21 | ## Installing
|
22 |
|
23 | `npm install tslint-immutable --save-dev`
|
24 |
|
25 | See the [example](#sample-configuration-file) tslint.json file for configuration.
|
26 |
|
27 | ## Compability
|
28 |
|
29 | * tslint-immutable 3.x.x is compatible with tslint 5.x.x.
|
30 | * tslint-immutable 2.x.x is compatible with tslint 4.x.x.
|
31 | * tslint-immutable 1.x.x is compatible with tslint 3.x.x.
|
32 |
|
33 | ## TSLint Rules
|
34 |
|
35 | In addition to immutable rules this project also contains a few rules for enforcing a functional style of programming. The following rules are available:
|
36 |
|
37 | * [Immutability rules](#immutability-rules)
|
38 | * [readonly-keyword](#readonly-keyword)
|
39 | * [readonly-array](#readonly-array)
|
40 | * [no-let](#no-let)
|
41 | * [no-array-mutation](#no-array-mutation)
|
42 | * [no-object-mutation](#no-object-mutation)
|
43 | * [no-method-signature](#no-method-signature)
|
44 | * [no-delete](#no-delete)
|
45 | * [Functional style rules](#functional-style-rules)
|
46 | * [no-this](#no-this-no-class)
|
47 | * [no-class](#no-this-no-class)
|
48 | * [no-mixed-interface](#no-mixed-interface)
|
49 | * [no-expression-statement](#no-expression-statement)
|
50 | * [no-if-statement](#no-if-statement)
|
51 | * [no-loop-statement](#no-loop-statement)
|
52 | * [Recommended built-in rules](#recommended-built-in-rules)
|
53 |
|
54 | ## Immutability rules
|
55 |
|
56 | ### readonly-keyword
|
57 |
|
58 | This rule enforces use of the `readonly` modifier. The `readonly` modifier can appear on property signatures in interfaces, property declarations in classes, and index signatures.
|
59 |
|
60 | Below is some information about the `readonly` modifier and the benefits of using it:
|
61 |
|
62 | You might think that using `const` would eliminate mutation from your TypeScript code. **Wrong.** Turns out that there's a pretty big loophole in `const`.
|
63 |
|
64 | ```typescript
|
65 | interface Point {
|
66 | x: number;
|
67 | y: number;
|
68 | }
|
69 | const point: Point = { x: 23, y: 44 };
|
70 | point.x = 99; // This is legal
|
71 | ```
|
72 |
|
73 | This is why the `readonly` modifier exists. It prevents you from assigning a value to the result of a member expression.
|
74 |
|
75 | ```typescript
|
76 | interface Point {
|
77 | readonly x: number;
|
78 | readonly y: number;
|
79 | }
|
80 | const point: Point = { x: 23, y: 44 };
|
81 | point.x = 99; // <- No object mutation allowed.
|
82 | ```
|
83 |
|
84 | This is just as effective as using Object.freeze() to prevent mutations in your Redux reducers. However the `readonly` modifier has **no run-time cost**, and is enforced at **compile time**. A good alternative to object mutation is to use the ES2016 object spread [syntax](https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#object-spread-and-rest) that was added in typescript 2.1:
|
85 |
|
86 | ```typescript
|
87 | interface Point {
|
88 | readonly x: number;
|
89 | readonly y: number;
|
90 | }
|
91 | const point: Point = { x: 23, y: 44 };
|
92 | const transformedPoint = { ...point, x: 99 };
|
93 | ```
|
94 |
|
95 | Note that you can also use object spread when destructuring to [delete keys](http://stackoverflow.com/questions/35342355/remove-data-from-nested-objects-without-mutating/35676025#35676025) in an object:
|
96 |
|
97 | ```typescript
|
98 | let { [action.id]: deletedItem, ...rest } = state;
|
99 | ```
|
100 |
|
101 | The `readonly` modifier also works on indexers:
|
102 |
|
103 | ```typescript
|
104 | const foo: { readonly [key: string]: number } = { a: 1, b: 2 };
|
105 | foo["a"] = 3; // Error: Index signature only permits reading
|
106 | ```
|
107 |
|
108 | #### Has Fixer
|
109 |
|
110 | Yes
|
111 |
|
112 | #### Options
|
113 |
|
114 | * [ignore-local](#using-the-ignore-local-option)
|
115 | * [ignore-class](#using-the-ignore-class-option)
|
116 | * [ignore-interface](#using-the-ignore-interface-option)
|
117 | * [ignore-prefix](#using-the-ignore-prefix-option)
|
118 |
|
119 | #### Example config
|
120 |
|
121 | ```javascript
|
122 | "readonly-keyword": true
|
123 | ```
|
124 |
|
125 | ```javascript
|
126 | "readonly-keyword": [true, "ignore-local"]
|
127 | ```
|
128 |
|
129 | ```javascript
|
130 | "readonly-keyword": [true, "ignore-local", {"ignore-prefix": "mutable"}]
|
131 | ```
|
132 |
|
133 | ### readonly-array
|
134 |
|
135 | This rule enforces use of `ReadonlyArray<T>` instead of `Array<T>` or `T[]`.
|
136 |
|
137 | Below is some information about the `ReadonlyArray<T>` type and the benefits of using it:
|
138 |
|
139 | Even if an array is declared with `const` it is still possible to mutate the contents of the array.
|
140 |
|
141 | ```typescript
|
142 | interface Point {
|
143 | readonly x: number;
|
144 | readonly y: number;
|
145 | }
|
146 | const points: Array<Point> = [{ x: 23, y: 44 }];
|
147 | points.push({ x: 1, y: 2 }); // This is legal
|
148 | ```
|
149 |
|
150 | Using the `ReadonlyArray<T>` type will stop this mutation:
|
151 |
|
152 | ```typescript
|
153 | interface Point {
|
154 | readonly x: number;
|
155 | readonly y: number;
|
156 | }
|
157 | const points: ReadonlyArray<Point> = [{ x: 23, y: 44 }];
|
158 | points.push({ x: 1, y: 2 }); // Unresolved method push()
|
159 | ```
|
160 |
|
161 | #### Has Fixer
|
162 |
|
163 | Yes
|
164 |
|
165 | #### Options
|
166 |
|
167 | * [ignore-local](#using-the-ignore-local-option)
|
168 | * [ignore-prefix](#using-the-ignore-prefix-option)
|
169 |
|
170 | #### Example config
|
171 |
|
172 | ```javascript
|
173 | "readonly-array": true
|
174 | ```
|
175 |
|
176 | ```javascript
|
177 | "readonly-array": [true, "ignore-local"]
|
178 | ```
|
179 |
|
180 | ```javascript
|
181 | "readonly-array": [true, "ignore-local", {"ignore-prefix": "mutable"}]
|
182 | ```
|
183 |
|
184 | ### no-let
|
185 |
|
186 | This rule should be combined with tslint's built-in `no-var-keyword` rule to enforce that all variables are declared as `const`.
|
187 |
|
188 | There's no reason to use `let` in a Redux/React application, because all your state is managed by either Redux or React. Use `const` instead, and avoid state bugs altogether.
|
189 |
|
190 | ```typescript
|
191 | let x = 5; // <- Unexpected let or var, use const.
|
192 | ```
|
193 |
|
194 | What about `for` loops? Loops can be replaced with the Array methods like `map`, `filter`, and so on. If you find the built-in JS Array methods lacking, use [ramda](http://ramdajs.com/), or [lodash-fp](https://github.com/lodash/lodash/wiki/FP-Guide).
|
195 |
|
196 | ```typescript
|
197 | const SearchResults = ({ results }) => (
|
198 | <ul>
|
199 | {results.map(result => <li>result</li>) // <- Who needs let?
|
200 | }
|
201 | </ul>
|
202 | );
|
203 | ```
|
204 |
|
205 | #### Has Fixer
|
206 |
|
207 | Yes
|
208 |
|
209 | #### Options
|
210 |
|
211 | * [ignore-local](#using-the-ignore-local-option)
|
212 | * [ignore-prefix](#using-the-ignore-prefix-option)
|
213 |
|
214 | #### Example config
|
215 |
|
216 | ```javascript
|
217 | "no-let": true
|
218 | ```
|
219 |
|
220 | ```javascript
|
221 | "no-let": [true, "ignore-local"]
|
222 | ```
|
223 |
|
224 | ```javascript
|
225 | "no-let": [true, "ignore-local", {"ignore-prefix": "mutable"}]
|
226 | ```
|
227 |
|
228 | ### no-array-mutation
|
229 |
|
230 | [![Type Info Required][type-info-badge]][type-info-url]
|
231 |
|
232 | This rule prohibits mutating an array via assignment to or deletion of their elements/properties. This rule enforces array immutability without the use of `ReadonlyArray<T>` (as apposed to [readonly-array](#readonly-array)).
|
233 |
|
234 | ```typescript
|
235 | const x = [0, 1, 2];
|
236 |
|
237 | x[0] = 4; // <- Mutating an array is not allowed.
|
238 | x.length = 1; // <- Mutating an array is not allowed.
|
239 | x.push(3); // <- Mutating an array is not allowed.
|
240 | ```
|
241 |
|
242 | #### Has Fixer
|
243 |
|
244 | No
|
245 |
|
246 | #### Options
|
247 |
|
248 | * [ignore-prefix](#using-the-ignore-prefix-option)
|
249 |
|
250 | #### Example config
|
251 |
|
252 | ```javascript
|
253 | "no-array-mutation": true
|
254 | ```
|
255 |
|
256 | ```javascript
|
257 | "no-array-mutation": [true, {"ignore-prefix": "mutable"}]
|
258 | ```
|
259 |
|
260 | ### no-object-mutation
|
261 |
|
262 | This rule prohibits syntax that mutates existing objects via assignment to or deletion of their properties. While requiring the `readonly` modifier forces declared types to be immutable, it won't stop assignment into or modification of untyped objects or external types declared under different rules. Forbidding forms like `a.b = 'c'` is one way to plug this hole. Inspired by the no-mutation rule of [eslint-plugin-immutable](https://github.com/jhusain/eslint-plugin-immutable).
|
263 |
|
264 | ```typescript
|
265 | const x = { a: 1 };
|
266 |
|
267 | x.foo = "bar"; // <- Modifying properties of existing object not allowed.
|
268 | x.a += 1; // <- Modifying properties of existing object not allowed.
|
269 | delete x.a; // <- Modifying properties of existing object not allowed.
|
270 | ```
|
271 |
|
272 | #### Has Fixer
|
273 |
|
274 | No
|
275 |
|
276 | #### Options
|
277 |
|
278 | * [ignore-prefix](#using-the-ignore-prefix-option)
|
279 |
|
280 | #### Example config
|
281 |
|
282 | ```javascript
|
283 | "no-object-mutation": true
|
284 | ```
|
285 |
|
286 | ```javascript
|
287 | "no-object-mutation": [true, {"ignore-prefix": "mutable"}]
|
288 | ```
|
289 |
|
290 | ### no-method-signature
|
291 |
|
292 | There are two ways function members can be declared in an interface or type alias:
|
293 |
|
294 | ```typescript
|
295 | interface Zoo {
|
296 | foo(): string; // MethodSignature, cannot have readonly modifier
|
297 | readonly bar: () => string; // PropertySignature
|
298 | }
|
299 | ```
|
300 |
|
301 | The `MethodSignature` and the `PropertySignature` forms seem equivalent, but only the `PropertySignature` form can have a `readonly` modifier. Becuase of this any `MethodSignature` will be mutable. Therefore the `no-method-signature` rule disallows usage of this form and instead proposes to use the `PropertySignature` which can have a `readonly` modifier. It should be noted however that the `PropertySignature` form for declaring functions does not support overloading.
|
302 |
|
303 | ### no-delete
|
304 |
|
305 | The delete operator allows for mutating objects by deleting keys. This rule disallows any delete expressions.
|
306 |
|
307 | ```typescript
|
308 | delete object.property; // Unexpected delete, objects should be considered immutable.
|
309 | ```
|
310 |
|
311 | As an alternative the spread operator can be used to delete a key in an object (as noted [here](https://stackoverflow.com/a/35676025/2761797)):
|
312 |
|
313 | ```typescript
|
314 | const { [action.id]: deletedItem, ...rest } = state;
|
315 | ```
|
316 |
|
317 | ## Functional style rules
|
318 |
|
319 | ### no-this, no-class
|
320 |
|
321 | Thanks to libraries like [recompose](https://github.com/acdlite/recompose) and Redux's [React Container components](http://redux.js.org/docs/basics/UsageWithReact.html), there's not much reason to build Components using `React.createClass` or ES6 classes anymore. The `no-this` rule makes this explicit.
|
322 |
|
323 | ```typescript
|
324 | const Message = React.createClass({
|
325 | render: function() {
|
326 | return <div>{this.props.message}</div>; // <- no this allowed
|
327 | }
|
328 | });
|
329 | ```
|
330 |
|
331 | Instead of creating classes, you should use React 0.14's [Stateless Functional Components](https://medium.com/@joshblack/stateless-components-in-react-0-14-f9798f8b992d#.t5z2fdit6) and save yourself some keystrokes:
|
332 |
|
333 | ```typescript
|
334 | const Message = ({ message }) => <div>{message}</div>;
|
335 | ```
|
336 |
|
337 | What about lifecycle methods like `shouldComponentUpdate`? We can use the [recompose](https://github.com/acdlite/recompose) library to apply these optimizations to your Stateless Functional Components. The [recompose](https://github.com/acdlite/recompose) library relies on the fact that your Redux state is immutable to efficiently implement shouldComponentUpdate for you.
|
338 |
|
339 | ```typescript
|
340 | import { pure, onlyUpdateForKeys } from "recompose";
|
341 |
|
342 | const Message = ({ message }) => <div>{message}</div>;
|
343 |
|
344 | // Optimized version of same component, using shallow comparison of props
|
345 | // Same effect as React's PureRenderMixin
|
346 | const OptimizedMessage = pure(Message);
|
347 |
|
348 | // Even more optimized: only updates if specific prop keys have changed
|
349 | const HyperOptimizedMessage = onlyUpdateForKeys(["message"], Message);
|
350 | ```
|
351 |
|
352 | ### no-mixed-interface
|
353 |
|
354 | Mixing functions and data properties in the same interface is a sign of object-orientation style. This rule enforces that an inteface only has one type of members, eg. only data properties or only functions.
|
355 |
|
356 | ### no-expression-statement
|
357 |
|
358 | When you call a function and don’t use it’s return value, chances are high that it is being called for its side effect. e.g.
|
359 |
|
360 | ```typescript
|
361 | array.push(1);
|
362 | alert("Hello world!");
|
363 | ```
|
364 |
|
365 | This rule checks that the value of an expression is assigned to a variable and thus helps promote side-effect free (pure) functions.
|
366 |
|
367 | #### Options
|
368 |
|
369 | * [ignore-prefix](#using-the-ignore-prefix-option-with-no-expression-statement)
|
370 |
|
371 | #### Example config
|
372 |
|
373 | ```javascript
|
374 | "no-expression-statement": true
|
375 | ```
|
376 |
|
377 | ```javascript
|
378 | "no-expression-statement": [true, {"ignore-prefix": "console."}]
|
379 | ```
|
380 |
|
381 | ```javascript
|
382 | "no-expression-statement": [true, {"ignore-prefix": ["console.log", "console.error"]}]
|
383 | ```
|
384 |
|
385 | ### no-if-statement
|
386 |
|
387 | If statements is not a good fit for functional style programming as they are not expresssions and do not return a value. This rule disallows if statements.
|
388 |
|
389 | ```typescript
|
390 | let x;
|
391 | if (i === 1) {
|
392 | x = 2;
|
393 | } else {
|
394 | x = 3;
|
395 | }
|
396 | ```
|
397 |
|
398 | Instead consider using the [tenary operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator) which is an expression that returns a value:
|
399 |
|
400 | ```typescript
|
401 | const x = i === 1 ? 2 : 3;
|
402 | ```
|
403 |
|
404 | For more background see this [blog post](https://hackernoon.com/rethinking-javascript-the-if-statement-b158a61cd6cb) and discussion in [#54](https://github.com/jonaskello/tslint-immutable/issues/54).
|
405 |
|
406 | ### no-loop-statement
|
407 |
|
408 | In functional programming we want everthing to be an expression that returns a value. Loops in typescript are statements so they are not a good fit for a functional programming style. This rule disallows for loop statements, including `for`, `for...of`, `for...in`, `while`, and `do...while`.
|
409 |
|
410 | ```typescript
|
411 | const numbers = [1, 2, 3];
|
412 | const double = [];
|
413 | for (let i = 0; i < numbers.length; i++) {
|
414 | double[i] = numbers[i] * 2;
|
415 | }
|
416 | ```
|
417 |
|
418 | Instead consider using `map` or `reduce`:
|
419 |
|
420 | ```typescript
|
421 | const numbers = [1, 2, 3];
|
422 | const double = numbers.map(n => n * 2);
|
423 | ```
|
424 |
|
425 | For more background see this [blog post](https://hackernoon.com/rethinking-javascript-death-of-the-for-loop-c431564c84a8) and discussion in [#54](https://github.com/jonaskello/tslint-immutable/issues/54).
|
426 |
|
427 | ## Options
|
428 |
|
429 | ### Using the `ignore-local` option
|
430 |
|
431 | > If a tree falls in the woods, does it make a sound?
|
432 | > If a pure function mutates some local data in order to produce an immutable return value, is that ok?
|
433 |
|
434 | The quote above is from the [clojure docs](https://clojure.org/reference/transients). In general, it is more important to enforce immutability for state that is passed in and out of functions than for local state used for internal calculations within a function. For example in Redux, the state going in and out of reducers needs to be immutable while the reducer may be allowed to mutate local state in its calculations in order to achieve higher performance. This is what the `ignore-local` option enables. With this option enabled immutability will be enforced everywhere but in local state. Function parameters and return types are not considered local state so they will still be checked.
|
435 |
|
436 | Note that using this option can lead to more imperative code in functions so use with care!
|
437 |
|
438 | ### Using the `ignore-class` option
|
439 |
|
440 | Doesn't check for `readonly` in classes.
|
441 |
|
442 | ### Using the `ignore-interface` option
|
443 |
|
444 | Doesn't check for `readonly` in interfaces.
|
445 |
|
446 | ### Using the `ignore-prefix` option
|
447 |
|
448 | Some languages are immutable by default but allows you to explicitly declare mutable variables. For example in [reason](https://facebook.github.io/reason/) you can declare mutable record fields like this:
|
449 |
|
450 | ```reason
|
451 | type person = {
|
452 | name: string,
|
453 | mutable age: int
|
454 | };
|
455 | ```
|
456 |
|
457 | Typescript is not immutable by default but it can be if you use this package. So in order to create an escape hatch similar to how it is done in reason the `ignore-prefix` option can be used. For example if you configure it to ignore variables with names that has the prefix "mutable" you can emulate the above example in typescript like this:
|
458 |
|
459 | ```typescript
|
460 | type person = {
|
461 | readonly name: string;
|
462 | mutableAge: number; // This is OK with ignore-prefix = "mutable"
|
463 | };
|
464 | ```
|
465 |
|
466 | Yes, variable names like `mutableAge` are ugly, but then again mutation is an ugly business :-).
|
467 |
|
468 | ### Using the `ignore-prefix` option with `no-expression-statement`
|
469 |
|
470 | Expression statements typically cause side effects, however not all side effects are undesirable. One example of a helpful side effect is logging. To not get warning of every log statement, we can configure the linter to ignore well known expression statement prefixes.
|
471 |
|
472 | One such prefix could be `console.`, which would cover both these cases:
|
473 |
|
474 | ```typescript
|
475 | const doSomething(arg:string) => {
|
476 | if (arg) {
|
477 | console.log("Argument is", arg);
|
478 | } else {
|
479 | console.warn("Argument is empty!");
|
480 | }
|
481 | return `Hello ${arg}`;
|
482 | }
|
483 | ```
|
484 |
|
485 | ## Recommended built-in rules
|
486 |
|
487 | ### [no-var-keyword](https://palantir.github.io/tslint/rules/no-var-keyword/)
|
488 |
|
489 | Without this rule, it is still possible to create `var` variables that are mutable.
|
490 |
|
491 | ### [no-parameter-reassignment](https://palantir.github.io/tslint/rules/no-parameter-reassignment/)
|
492 |
|
493 | Without this rule, function parameters are mutable.
|
494 |
|
495 | ### [typedef](https://palantir.github.io/tslint/rules/typedef/) with call-signature option
|
496 |
|
497 | For performance reasons, tslint-immutable does not check implicit return types. So for example this function will return an mutable array but will not be detected (see [#18](https://github.com/jonaskello/tslint-immutable/issues/18) for more info):
|
498 |
|
499 | ```javascript
|
500 | function foo() {
|
501 | return [1, 2, 3];
|
502 | }
|
503 | ```
|
504 |
|
505 | To avoid this situation you can enable the built in typedef rule like this:
|
506 |
|
507 | `"typedef": [true, "call-signature"]`
|
508 |
|
509 | Now the above function is forced to declare the return type becomes this and will be detected.
|
510 |
|
511 | ## Sample Configuration File
|
512 |
|
513 | Here's a sample TSLint configuration file (tslint.json) that activates all the rules:
|
514 |
|
515 | ```javascript
|
516 | {
|
517 | "extends": [
|
518 | "tslint-immutable"
|
519 | ],
|
520 | "rules": {
|
521 |
|
522 | // Recommended built-in rules
|
523 | "no-var-keyword": true,
|
524 | "no-parameter-reassignment": true,
|
525 | "typedef": [true, "call-signature"],
|
526 |
|
527 | // Immutability rules
|
528 | "readonly-keyword": true,
|
529 | "readonly-array": true,
|
530 | "no-let": true,
|
531 | "no-object-mutation": true,
|
532 | "no-delete": true,
|
533 | "no-method-signature": true,
|
534 |
|
535 | // Functional style rules
|
536 | "no-this": true,
|
537 | "no-class": true,
|
538 | "no-mixed-interface": true,
|
539 | "no-expression-statement": true,
|
540 | "no-if-statement": true
|
541 |
|
542 | }
|
543 | }
|
544 | ```
|
545 |
|
546 | ## How to contribute
|
547 |
|
548 | For new features file an issue. For bugs, file an issue and optionally file a PR with a failing test. Tests are really easy to do, you just have to edit the `*.ts.lint` files under the test directory. Read more here about [tslint testing](https://palantir.github.io/tslint/develop/testing-rules/).
|
549 |
|
550 | ## How to develop
|
551 |
|
552 | To execute the tests first run `yarn build` and then run `yarn test`.
|
553 |
|
554 | While working on the code you can run `yarn test:work`. This script also builds before running the tests. To run a subset of the tests, change the path for `yarn test:work` in `package.json`.
|
555 |
|
556 | Please review the [tslint performance tips](https://palantir.github.io/tslint/develop/custom-rules/performance-tips.html) in order to write rules that run efficiently at run-time. For example, note that using `SyntaxWalker` or any subclass thereof like `RuleWalker` is inefficient. Note that tslint requires the use of `class` as an entrypoint, but you can make a very small class that inherits from `AbstractRule` which directly calls `this.applyWithFunction` and from there you can switch to using a more functional programming style.
|
557 |
|
558 | In order to know which AST nodes are created for a snippet of typescript code you can use [ast explorer](https://astexplorer.net/).
|
559 |
|
560 | To release a new package version run `yarn publish:patch`, `yarn publish:minor`, or `yarn publish:major`.
|
561 |
|
562 | ## Prior work
|
563 |
|
564 | This work was originally inspired by [eslint-plugin-immutable](https://github.com/jhusain/eslint-plugin-immutable).
|
565 |
|
566 | [version-image]: https://img.shields.io/npm/v/tslint-immutable.svg?style=flat
|
567 | [version-url]: https://www.npmjs.com/package/tslint-immutable
|
568 | [travis-image]: https://travis-ci.org/jonaskello/tslint-immutable.svg?branch=master&style=flat
|
569 | [travis-url]: https://travis-ci.org/jonaskello/tslint-immutable
|
570 | [coveralls-image]: https://coveralls.io/repos/github/jonaskello/tslint-immutable/badge.svg?branch=master
|
571 | [coveralls-url]: https://coveralls.io/github/jonaskello/tslint-immutable?branch=master
|
572 | [license-image]: https://img.shields.io/github/license/jonaskello/tslint-immutable.svg?style=flat
|
573 | [license-url]: https://opensource.org/licenses/MIT
|
574 | [prettier-image]: https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat
|
575 | [prettier-url]: https://github.com/prettier/prettier
|
576 | [type-info-badge]: https://img.shields.io/badge/type_info-requried-d51313.svg?style=flat
|
577 | [type-info-url]: https://palantir.github.io/tslint/usage/type-checking
|