|
| 1 | +[](https://badge.fury.io/js/tslint-filter) |
| 2 | +[](https://snyk.io/test/github/jens-duttke/tslint-filter?targetFile=package.json) |
| 3 | + |
| 4 | +# TSLint-Filter |
| 5 | +Suppress and modify TSLint warnings, before they get returned to the console or your code editor. |
| 6 | + |
| 7 | +Many TSLint rules are very limited by their configurability, and some rules looks like they are not thought to the end. |
| 8 | + |
| 9 | +For example, I want to prevent the usage of "I" as prefix for interface names. The TSLint rule for that is called "interface-name". |
| 10 | +Unfortunately, this rule also shows an error for "I18N", which is an absolutely valid interface name for me. |
| 11 | + |
| 12 | +Or, in my React projects I want to get a warning, if I forgot to specify a Components class method as `private` or `public` using the "member-access" rule. But for the React methods `componentDidMount`, `render`, `componentDidUpdate` etc. I don't want to specify that, because they are always public. |
| 13 | +Unfortunately, by now, it's not possible to specify a whitelist here. |
| 14 | + |
| 15 | +Or, I want to prefer conditional expressions for small, simple alignments, but "prefer-conditional-expression" also complains about complex statements, which wouldn't be easy readable in a single line, because this line would have a size of 300 characters or more. |
| 16 | +Why isn't there a way to show the warning only, if the conditional expression would be a ...let's say... less-than-120-chars-one-liner? |
| 17 | +Using TSLint Filter, you have to possibility to easily extend existing rules and suppress specific warnings, based on regular expressions. |
| 18 | + |
| 19 | +It's even possible to use integer ranges in these regular expression, to filter by a range of numbers in the error message. |
| 20 | + |
| 21 | +**Table of Contents** |
| 22 | + |
| 23 | +- [Installation](#installation) |
| 24 | +- [Basic Usage](#basic-usage) |
| 25 | +- [Extended usage](#extended-usage) |
| 26 | +- [Location of rule directories](#location-of-rule-directories) |
| 27 | +- [Rule file names](#rule-file-names) |
| 28 | + |
| 29 | +## Installation |
| 30 | + |
| 31 | +Install with npm |
| 32 | + |
| 33 | +```sh |
| 34 | +npm install tslint-filter --save-dev |
| 35 | +``` |
| 36 | + |
| 37 | +## Basic Usage |
| 38 | + |
| 39 | +Since TSLint does not provide an easy way to modify warnings before they get returned, we need to create own rules, with TSLint-Filter as wrapper for the orignal rule. |
| 40 | + |
| 41 | +But that's very easy: |
| 42 | + |
| 43 | +Create a folder for custom rules in your project folder |
| 44 | + |
| 45 | +In this folder create a JavaScript file like: |
| 46 | +```javascript |
| 47 | +module.exports = require('tslint-filter')('tslint/lib/rules/memberAccessRule'); |
| 48 | +``` |
| 49 | +"tslint/lib/rules/memberAccessRule" is the name of the original rule, which you want to extend. |
| 50 | + |
| 51 | +You can name the file to whatever you want, but it must end with "Rule.js". I prefer th use the name of the original rule, and prefix it with "___", so in this case "___memberAccessRule.js". |
| 52 | + |
| 53 | +In your `tslint.json` add the folder to the "rulesDirectory" section: |
| 54 | +```json |
| 55 | +{ |
| 56 | + "rulesDirectory": [ |
| 57 | + "script/custom-tslint-rules" |
| 58 | + ], |
| 59 | + "rules": { |
| 60 | + ... |
| 61 | + } |
| 62 | +} |
| 63 | +``` |
| 64 | + |
| 65 | +Now, instead of using the rule "member-access", I'm able to use the rule "___member-access". |
| 66 | +The last parameter **must** be always an array with regular expressions. Warnings which match these expressions will be ignored. |
| 67 | + |
| 68 | +```json |
| 69 | +"___member-access": [true, [ |
| 70 | + "'(getDerivedStateFromProps|componentDidMount|shouldComponentUpdate|render|getSnapshotBeforeUpdate|componentDidUpdate|componentWillUnmount)'" |
| 71 | +]], |
| 72 | +``` |
| 73 | + |
| 74 | +## Extended usage |
| 75 | + |
| 76 | +Beside simply ignoring warnings, you can also manipulate them. You can change the message, implement a fix or whatever you like. |
| 77 | + |
| 78 | +For example, we want to extend the "interface-name" rule, to allow the interface name "I18N", even if it starts with "I". |
| 79 | +Unfortunately, the message of this rule does not provide the name of the interface, so first, we have to include the name into the message: |
| 80 | +```javascript |
| 81 | +const Utils = require('tsutils'); |
| 82 | + |
| 83 | +module.exports = require('tslint-filter')('tslint/lib/rules/interfaceNameRule', { |
| 84 | + modifyFailure (failure) { |
| 85 | + const node = Utils.getTokenAtPosition(failure.sourceFile, failure.getStartPosition().getPosition()); |
| 86 | + |
| 87 | + if (node.escapedText) { |
| 88 | + failure.failure = `Interface name "${node.escapedText}" must not have an "I" prefix`; |
| 89 | + } |
| 90 | + |
| 91 | + return failure; |
| 92 | + } |
| 93 | +}); |
| 94 | +``` |
| 95 | + |
| 96 | +Now you can ignore interface names, starting with "I" followed by an digit: |
| 97 | +```json |
| 98 | +"___interface-name": [true, "never-prefix", [ |
| 99 | + "Interface name \"I[\\d]" |
| 100 | +]], |
| 101 | +``` |
| 102 | + |
| 103 | + |
| 104 | +For example the "prefer-conditional-expression" rule could be extended to show the approximated number of characters you could save, and also the approximated size if you write the statement as conditional expression: |
| 105 | + |
| 106 | +```javascript |
| 107 | +const Utils = require('tsutils'); |
| 108 | + |
| 109 | +module.exports = require('tslint-filter')('tslint/lib/rules/preferConditionalExpressionRule', { |
| 110 | + modifyFailure (failure) { |
| 111 | + const match = failure.getFailure().match(/'([^\0]+)'/); |
| 112 | + |
| 113 | + if (match !== null) { |
| 114 | + const node = Utils.getTokenAtPosition(failure.sourceFile, failure.getStartPosition().getPosition()); |
| 115 | + const parent = node.parent; |
| 116 | + |
| 117 | + const originalSize = (parent.end - parent.pos); |
| 118 | + |
| 119 | + const assigneeLength = match[1].length; |
| 120 | + const expressionLength = parent.expression.end - parent.expression.pos; |
| 121 | + const thenStatementLength = parent.thenStatement.getText().replace(/^{?[\s\n]+|[\s\n]+}?$/g, '').length; |
| 122 | + const elseStatementLength = parent.elseStatement.getText().replace(/^{?[\s\n]+|[\s\n]+}?$/g, '').length; |
| 123 | + |
| 124 | + // That's only an approximated size, depending on the wrapping characters |
| 125 | + newLength = expressionLength + thenStatementLength + elseStatementLength - assigneeLength + 1; |
| 126 | + |
| 127 | + if (newLength > originalSize) { |
| 128 | + return; |
| 129 | + } |
| 130 | + |
| 131 | + failure.failure = `${failure.failure} (save about ${originalSize - newLength} characters, conditional expression size would be about ${newLength} characters)` |
| 132 | + } |
| 133 | + |
| 134 | + return failure; |
| 135 | + } |
| 136 | +}); |
| 137 | +``` |
| 138 | +Save this file under the name "___preferConditionalExpressionRule.js" in your custom rule folder. |
| 139 | + |
| 140 | +Now you can use this pattern, to prevent warnings, where the conditional expression size would be 120 characters or more: |
| 141 | +```json |
| 142 | +"___prefer-conditional-expression": [true, "check-else-if", [ |
| 143 | + "conditional expression size would be about [120...]" |
| 144 | +]], |
| 145 | +``` |
| 146 | + |
| 147 | +Ranges can be specified with: |
| 148 | +| RegExp character sets | Meaning |
| 149 | +|---|--- |
| 150 | +| `[-5...5]` | Any integer number from -5 to 5 |
| 151 | +| `[...100]` | Any integer number from -999999999999999 to 100 |
| 152 | +| `[10...]` | Any integer number from 10 to 999999999999999 |
| 153 | +| `[...]` | Any integer number from -999999999999999 to 999999999999999, but if possible you should prefer `-?\d+` |
| 154 | + |
| 155 | +`-999999999999999` and `999999999999999` are required, because the expression is converted into a valid RegExp, and here we always need to specify a range. |
| 156 | +These numbers are choosen because they are very near to Number.MIN_SAFE_INTEGER and Number.MAX_SAFE_INTEGER, but the RegExp representation is still very short. |
| 157 | + |
| 158 | +## Location of rule directories |
| 159 | + |
| 160 | +> It's an open to-do to determine the directory and rule file name automatically, based on the `rulesDirectory`, but I havn't found an easy way to do that yet. |
| 161 | +
|
| 162 | +So, for now, it's required to specify the whole path to the rule, instead of just using the rule name. |
| 163 | + |
| 164 | +| Rule Package | Directory | Number of rules\* |
| 165 | +|---|---|---: |
| 166 | +| [tslint](https://www.npmjs.com/package/tslint) | tslint/lib/rules/ | 152 |
| 167 | +| [tslint-microsoft-contrib](https://www.npmjs.com/package/tslint-microsoft-contrib) | tslint-microsoft-contrib/ | 93 |
| 168 | +| [tslint-sonarts](https://www.npmjs.com/package/tslint-sonarts) | tslint-sonarts/lib/rules/ | 62 |
| 169 | +| [tslint-eslint-rules](https://www.npmjs.com/package/tslint-eslint-rules) | tslint-eslint-rules/dist/rules/ | 38 |
| 170 | +| [rxjs-tslint-rules](https://www.npmjs.com/package/rxjs-tslint-rules) | rxjs-tslint-rules/dist/rules/ | 37 |
| 171 | +| [tslint-consistent-codestyle](https://www.npmjs.com/package/tslint-consistent-codestyle) | tslint-consistent-codestyle/rules/ | 19 |
| 172 | +| [tslint-config-security](https://www.npmjs.com/package/tslint-config-security) | tslint-config-security/dist/rules/ | 16 |
| 173 | +| [tslint-immutable](https://www.npmjs.com/package/tslint-immutable) | tslint-immutable/rules/ | 15 |
| 174 | +| [tslint-misc-rules](https://www.npmjs.com/package/tslint-misc-rules) | tslint-misc-rules/rules/ | 15 |
| 175 | +| [tslint-react](https://www.npmjs.com/package/tslint-react) | tslint-react/rules/ | 15 |
| 176 | +| [tslint-clean-code](https://www.npmjs.com/package/tslint-clean-code) | tslint-clean-code/dist/src/ | 12 |
| 177 | +| [tslint-stencil](https://www.npmjs.com/package/tslint-stencil) | tslint-stencil/rules/ | 7 |
| 178 | +| [vrsource-tslint-rules](https://www.npmjs.com/package/vrsource-tslint-rules) | vrsource-tslint-rules/rules/ | 7 |
| 179 | +| [rxjs-tslint](https://www.npmjs.com/package/rxjs-tslint) | rxjs-tslint/ | 4 |
| 180 | +| [tslint-jasmine-rules](https://www.npmjs.com/package/tslint-jasmine-rules) | tslint-jasmine-rules/dist/ | 3 |
| 181 | +| [tslint-defocus](https://www.npmjs.com/package/tslint-defocus) | tslint-defocus/dist/ | 1 |
| 182 | +| [tslint-lines-between-class-members](https://www.npmjs.com/package/tslint-lines-between-class-members) | tslint-lines-between-class-members/ | 1 |
| 183 | +| [tslint-no-unused-expression-chai](https://www.npmjs.com/package/tslint-no-unused-expression-chai) | tslint-no-unused-expression-chai/rules/ | 1 |
| 184 | +| [tslint-origin-ordered-imports-rule](https://www.npmjs.com/package/tslint-origin-ordered-imports-rule) | tslint-origin-ordered-imports-rule/dist/ | 1 |
| 185 | +| [tslint-plugin-prettier](https://www.npmjs.com/package/tslint-plugin-prettier) | tslint-plugin-prettier/rules/ | 1 |
| 186 | + |
| 187 | +\* as of 2019-01-16. List ordered by number of rules. |
| 188 | + |
| 189 | +## Rule file names |
| 190 | + |
| 191 | +Dashes in the file names are converted to camel case, but leading and trailing dashes are keeped. "Rule" is appeneded |
| 192 | + |
| 193 | +So, the rule name `-ab-cd-ef-` is located in the file `-abCdEf-Rule`. |
0 commit comments