├── .editorconfig ├── .eslintignore ├── .github └── FUNDING.yml ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── lib ├── copyright-header.txt └── index.js ├── package.json ├── scripts ├── build-core.js └── node-tests.js └── tests ├── qunit.config.js ├── tests.nested.js ├── tests.parens.js └── tests.where.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = tab 8 | indent_size = 4 9 | 10 | [*.md] 11 | indent_style = space 12 | indent_size = 4 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | README.md 2 | node_modules/ 3 | dist/ 4 | coverage/ 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [getify] 4 | patreon: getify 5 | custom: ['https://www.paypal.com/paypalme2/getify','https://www.blockchain.com/btc/payment_request?address=3GrZuzooWAAjufEydb7c8MrUYznCiHHT9U&message=getify+Support+Donation'] 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .coveralls.yml 2 | .history 3 | node_modules/ 4 | dist/ 5 | coverage/ 6 | test-snippets/ 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .npmignore 2 | .gitignore 3 | .coveralls.yml 4 | .history 5 | node_modules/ 6 | coverage/ 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 12 5 | - 14 6 | - 16 7 | 8 | git: 9 | depth: 5 10 | 11 | cache: 12 | directories: 13 | - node_modules 14 | 15 | env: 16 | - TEST_PACKAGE=true 17 | branches: 18 | only: 19 | - master 20 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-2021 Kyle Simpson 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESLint Plugin: proper-ternary 2 | 3 | [![Build Status](https://travis-ci.org/getify/eslint-plugin-proper-ternary.svg?branch=master)](https://travis-ci.org/getify/eslint-plugin-proper-ternary) 4 | [![npm Module](https://badge.fury.io/js/%40getify%2Feslint-plugin-proper-ternary.svg)](https://www.npmjs.org/package/@getify/eslint-plugin-proper-ternary) 5 | [![Dependencies](https://david-dm.org/getify/eslint-plugin-proper-ternary.svg)](https://david-dm.org/getify/eslint-plugin-proper-ternary) 6 | [![devDependencies](https://david-dm.org/getify/eslint-plugin-proper-ternary/dev-status.svg)](https://david-dm.org/getify/eslint-plugin-proper-ternary?type=dev) 7 | [![Coverage Status](https://coveralls.io/repos/github/getify/eslint-plugin-proper-ternary/badge.svg?branch=master)](https://coveralls.io/github/getify/eslint-plugin-proper-ternary?branch=master) 8 | 9 | ## Overview 10 | 11 | The **proper-ternary** ESLint plugin provides rules that control the definitions of `? :` conditional expressions (aka, "ternary expressions"), restricting them to a narrower and more proper/readable form. 12 | 13 | The rules defined in this plugin: 14 | 15 | * [`"nested"`](#rule-nested): controls the nesting of `? :` ternary expressions. 16 | 17 | * [`"parens"`](#rule-parens): requires surrounding `( .. )` parentheses around specific kinds of expressions in ternary expression clauses. 18 | 19 | * [`"where"`](#rule-where): restricts where in program structure ternary expressions can be used: forbidding them as standalone statements, in object properties, as arguments, etc. 20 | 21 | ## Enabling The Plugin 22 | 23 | To use **proper-ternary**, load it as a plugin into ESLint and configure the rules as desired. 24 | 25 | ### `extends` 26 | 27 | If you'd like to use the **proper-ternary** plugin in a recommended configuration preset, you can add the plugin in the `extends` clause of your ESLint configuration, and pick a preset by name: 28 | 29 | ```js 30 | "extends": [ 31 | // .. 32 | "plugin:@getify/proper-ternary/CONFIG-PRESET-NAME", 33 | // .. 34 | ] 35 | ``` 36 | 37 | **Note:** All included configuration presets not only define specific rule configurations but also automatically load the plugin itself, so you *don't* need to list **proper-ternary** in the `plugins` clause. 38 | 39 | The available configuration presets to choose from: 40 | 41 | * `getify-says`: This is my personal configuration. See the [preset definition](/lib/index.js#L5-L12). 42 | 43 | * ..TBA.. 44 | 45 | It's important to note that you can still override any of the preset rule definitions in your configuration. Think of these presets as convenience "defaults" that can still be customized. 46 | 47 | ### `.eslintrc.json` 48 | 49 | To load the plugin and enable its rules via a local or global `.eslintrc.json` configuration file: 50 | 51 | ```json 52 | "plugins": [ 53 | "@getify/proper-ternary" 54 | ], 55 | "rules": { 56 | "@getify/proper-ternary/nested": "error", 57 | "@getify/proper-ternary/parens": "error", 58 | "@getify/proper-ternary/where": "error" 59 | } 60 | ``` 61 | 62 | ### `package.json` 63 | 64 | To load the plugin and enable its rules via a project's `package.json`: 65 | 66 | ```json 67 | "eslintConfig": { 68 | "plugins": [ 69 | "@getify/proper-ternary" 70 | ], 71 | "rules": { 72 | "@getify/proper-ternary/nested": "error", 73 | "@getify/proper-ternary/parens": "error", 74 | "@getify/proper-ternary/where": "error" 75 | } 76 | } 77 | ``` 78 | 79 | ### ESLint CLI parameters 80 | 81 | To load the plugin and enable its rules via ESLint CLI parameters, use `--plugin` and `--rule` flags: 82 | 83 | ```cmd 84 | eslint .. --plugin='@getify/proper-ternary' --rule='@getify/proper-ternary/nested: error' .. 85 | ``` 86 | 87 | ```cmd 88 | eslint .. --plugin='@getify/proper-ternary' --rule='@getify/proper-ternary/parens: error' .. 89 | ``` 90 | 91 | ```cmd 92 | eslint .. --plugin='@getify/proper-ternary' --rule='@getify/proper-ternary/where: error' .. 93 | ``` 94 | 95 | ### ESLint Node API 96 | 97 | To use this plugin in Node.js with the ESLint API, require the npm module, and then (for example) pass the rule's definition to `Linter#defineRule(..)`, similar to: 98 | 99 | ```js 100 | var properTernary = require("@getify/eslint-plugin-proper-ternary"); 101 | 102 | // .. 103 | 104 | var eslinter = new (require("eslint").Linter)(); 105 | 106 | eslinter.defineRule("@getify/proper-ternary/nested",properTernary.rules.nested); 107 | 108 | eslinter.defineRule("@getify/proper-ternary/parens",properTernary.rules.parens); 109 | 110 | eslinter.defineRule("@getify/proper-ternary/where",properTernary.rules.where); 111 | ``` 112 | 113 | Then lint some code like this: 114 | 115 | ```js 116 | eslinter.verify(".. some code ..",{ 117 | rules: { 118 | "@getify/proper-ternary/nested": "error", 119 | "@getify/proper-ternary/parens": "error", 120 | "@getify/proper-ternary/where": "error" 121 | } 122 | }); 123 | ``` 124 | 125 | ### Inline Comments 126 | 127 | Once the plugin is loaded, the rule can be configured using inline code comments if desired, such as: 128 | 129 | ```js 130 | /* eslint "@getify/proper-ternary/nested": "error" */ 131 | ``` 132 | 133 | ```js 134 | /* eslint "@getify/proper-ternary/parens": "error" */ 135 | ``` 136 | 137 | ```js 138 | /* eslint "@getify/proper-ternary/where": "error" */ 139 | ``` 140 | 141 | ## Rule: `"nested"` 142 | 143 | The **proper-ternary**/*nested* rule controls the nesting of `? :` ternary expressions. 144 | 145 | To turn this rule on: 146 | 147 | ```json 148 | "@getify/proper-ternary/nested": "error" 149 | ``` 150 | 151 | The main purpose of this rule is to avoid readability harm for `? :` ternary expressions with confusing nesting of other ternary expressions. By forbidding confusing nesting, the reader can more clearly understand what the ternary will result in. 152 | 153 | For example: 154 | 155 | ```js 156 | var name = userData ? userData.name : "-empty-"; 157 | ``` 158 | 159 | This ternary expression doesn't have any other ternary expression nested in it. It's much clearer to figure out what its behavior will be. Therefore, the **proper-ternary**/*nested* rule would not report any errors. 160 | 161 | By default, ternary expression nesting is forbidden **in all three ternary expression clauses**, and nesting depth is furthermore limited to one level. As such, this rule *would* default to **reporting errors** for each of these statements: 162 | 163 | ```js 164 | var name = 165 | (typeof isLoggedIn == "function" ? isLoggedIn() : false) 166 | ? userData.name 167 | : "-empty-"; 168 | 169 | var email = 170 | userData != null 171 | ? (userData.email != "" ? userData.email : "nobody@email.tld") 172 | : "-empty-"; 173 | 174 | var accountType = 175 | userData.type == 1 ? "admin" : 176 | userData.type == 2 ? "manager" : 177 | userData.type == 3 ? "vendor" : 178 | "customer"; 179 | ``` 180 | 181 | The `name` assignment statement has a ternary expression nested inside the "test" clause of the outer ternary expression. The `email` assignment statement has a ternary expression nested inside the "then" (aka "consequent") clause of the outer ternary expression. The `accountType` assignment statement nests ternary expressions in the "else" (aka "alternate") clauses of their outer ternary expressions. Also, the `accountType` assignment statement has **two levels of nesting**, whereas the `name` and `email` assignment statements each have ternary expressions with **one level of nesting**. 182 | 183 | To allow nesting in a specific clause (`"test"`, `"then"`, and `"else"`), that clause type must be configured on. To allow nesting beyond one level, the `"depth"` configuration must be increased. 184 | 185 | ### Rule Configuration 186 | 187 | The **proper-ternary**/*nested* rule can be configured with various combinations of these modes: 188 | 189 | * [`"test"`](#rule-nested-configuration-clauses) (default: `false`) allows a ternary expression nested in the "test" clause of another ternary expression. 190 | 191 | * [`"then"`](#rule-nested-configuration-clauses) (default: `false`) allows a ternary expression nested in the "then" (aka, "consequent") clause of another ternary expression. 192 | 193 | * [`"else"`](#rule-nested-configuration-clauses) (default: `false`) allows a ternary expression nested in the "else" (aka, "alternate") clause of another ternary expression. 194 | 195 | * [`"depth"`](#rule-nested-configuration-depth) (default: `1`) controls how many levels of nesting of ternary expressions are allowed. To effectively use this option, you must also enable at least one of the `"test"` / `"then"` / `"else"` clause modes. 196 | 197 | **Note:** This rule does not consider stylistic readability affordances like whitespace or parentheses (see [`"parens"` rule](#rule-parens)), only structural questions of nesting. 198 | 199 | #### Rule `"nested"` Configuration: Clauses 200 | 201 | To configure the `"test"`, `"then"`, or `"else"` rule modes (each default: `false`): 202 | 203 | ```json 204 | "@getify/proper-ternary/nested": [ "error", { "test": true, "then": true, "else": true } 205 | ``` 206 | 207 | Each clause must be explicitly enabled for nested ternary expressions to be allowed there. Leaving all three clause types disabled effectively disables all ternary expression nesting. 208 | 209 | ##### `"test"` Nesting 210 | 211 | If `"test"` mode is enabled, nesting a ternary expression in the *test* clause looks like this: 212 | 213 | ```js 214 | var name = 215 | (typeof isLoggedIn == "function" ? isLoggedIn() : false) 216 | ? userData.name 217 | : "-empty-"; 218 | ``` 219 | 220 | This form is equivalent to the fairly awkward: 221 | 222 | ```js 223 | var name; 224 | if ( 225 | (typeof isLoggedIn == "function" || false) && isLoggedIn() 226 | ) { 227 | name = userData.name; 228 | } 229 | else { 230 | name = "-empty-"; 231 | } 232 | ``` 233 | 234 | The awkward/confusing boolean logic in this `if..else` equivalent form suggests a simpler way to structure the logic: 235 | 236 | ```js 237 | var name; 238 | if (typeof isLoggedIn == "function" && isLoggedIn()) { 239 | name = userData.name; 240 | } 241 | else { 242 | name = "-empty-"; 243 | } 244 | 245 | ``` 246 | 247 | And while that logic certainly makes more sense, it illustrates why nesting ternary expressions in the *test* clause is rarer, as there's basically no need for the extra conditional in the first place: 248 | 249 | ```js 250 | var name = 251 | (typeof isLoggedIn == "function" && isLoggedIn()) 252 | ? userData.name 253 | : "-empty-"; 254 | ``` 255 | 256 | The main reason to prefer the ternary expression form in this case, over the `if..else` form, is that it's more clear in this latter form that there's a single variable `name` being assigned one of two values. With the `if..else` form, there are two separate assignments, so this detail is slightly less obvious. 257 | 258 | ##### `"then"` Nesting 259 | 260 | If the `"then"` mode is enabled, the more common nesting of a ternary expression in the *then* clause of another ternary expression looks like: 261 | 262 | ```js 263 | var email = 264 | userData != null 265 | ? (userData.email != "" ? userData.email : "nobody@email.tld") 266 | : "-empty-"; 267 | ``` 268 | 269 | In this form, it's clear that there's a single variable `email` being assigned. The `if..else` equivalent: 270 | 271 | ```js 272 | var email; 273 | if (userData != null) { 274 | if (userData.email != "") { 275 | email = userData.email; 276 | } 277 | else { 278 | email = "nobody@email.tld"; 279 | } 280 | } 281 | else { 282 | email = "-empty-"; 283 | } 284 | ``` 285 | 286 | In this form, the single assignment (with one of three values) is a little less obvious. Generally, the former ternary expression form would be preferred as a bit more readable in cases like this. 287 | 288 | ##### `"else"` Nesting 289 | 290 | If the `"else"` mode is enabled, nesting a ternary expression in the *else* clause of another ternary expression is perhaps the most readable of the ternary expression nesting variations: 291 | 292 | ```js 293 | var accountType = 294 | userData.type == 1 ? "admin" : 295 | userData.type == 2 ? "manager" : 296 | userData.type == 3 ? "vendor" : 297 | "customer"; 298 | ``` 299 | 300 | In this form, it's fairly clear that there's a single variable `accountType` being assigned one of four values, based on three specific comparisons, with the fourth value being the default "else" value. 301 | 302 | The more verbose `if..else if` equivalent: 303 | 304 | ```js 305 | var accountType; 306 | if (userData.type == 1) { 307 | accountType = "admin"; 308 | } 309 | else if (userData.type == 2) { 310 | accountType = "manager"; 311 | } 312 | else if (userData.type == 3) { 313 | accountType = "vendor"; 314 | } 315 | else { 316 | accountType = "customer"; 317 | } 318 | ``` 319 | 320 | The single variable (`accountType`) assignment is a little less obvious in this form, and there's more syntactic noise just to accomplish the same result. So, the ternary expression form may be a bit more preferable. 321 | 322 | #### Rule `"nested"` Configuration: `"depth"` 323 | 324 | To configure this rule mode (default: `1`): 325 | 326 | ```json 327 | "@getify/proper-ternary/nested": [ "error", { "depth": 1 } ] 328 | ``` 329 | 330 | If any of the [`"test"` / `"then"` / `"else"` modes](#rule-nested-configuration-clauses) are enabled, you can also control how many levels of ternary expression nesting are allowed with the `"depth"` setting. 331 | 332 | For example, by default this rule mode would not report any errors for this ternary expression: 333 | 334 | ```js 335 | var accountType = 336 | userData.type == 1 ? "admin" : 337 | userData.type == 2 ? "manager" : 338 | "customer"; 339 | ``` 340 | 341 | The nesting level is `1` (inside the second/outermost ternary expression). 342 | 343 | By contrast, this rule mode *would* by default report errors for: 344 | 345 | ```js 346 | var accountType = 347 | userData.type == 1 ? "admin" : 348 | userData.type == 2 ? "manager" : 349 | userData.type == 3 ? "vendor" : 350 | "customer"; 351 | ``` 352 | 353 | Here, the nesting level is `2` (inside the third/outermost ternary expression), so the default nesting level of `1` would cause an error to be reported for the `userData.type == 3 ? ..` ternary expression. 354 | 355 | ## Rule: `"parens"` 356 | 357 | The **proper-ternary**/*parens* rule requires `( .. )` parentheses surrounding various expression types when they appear in any clause of a ternary expression. 358 | 359 | To turn this rule on: 360 | 361 | ```json 362 | "@getify/proper-ternary/parens": "error" 363 | ``` 364 | 365 | The main purpose of this rule is to avoid readability harm for `? :` ternary expressions by requiring disambiguating `( .. )` around any clause's expression if that expression's boundary isn't obvious, such as operator associativity or precedence, for example. 366 | 367 | For example: 368 | 369 | ```js 370 | var total = 1 + base ? base * 2 : base * 3; 371 | ``` 372 | 373 | Without looking up operator precedence, a reader may not be confident whether the `1 + ` part belongs to the *test* clause of the ternary, or is added after the ternary is resolved. In other words, that example could reasonably be assumed as either of these: 374 | 375 | ```js 376 | var total = (1 + base) ? base * 2 : base * 3; 377 | 378 | // OR 379 | 380 | var total = 1 + (base ? base * 2 : base * 3); 381 | ``` 382 | 383 | Which is it? Because of operator precedence, it's the first one (`(1 + base) ? ..`). But this kind of ambiguity can really harm readability. Moreover, when quickly scanning the code, the `base * 2` and `base * 3` expressions can obscure the location of the `?` and `:` operators and thus the clause boundaries. 384 | 385 | Consider a more readable alternative: 386 | 387 | ```js 388 | var total = (1 + base) ? (base * 2) : (base * 3); 389 | ``` 390 | 391 | Yes, the `( .. )` are "unnecessary", but they certainly eliminate the ambiguity from such examples. Readability affordances such as this should be favored. 392 | 393 | The default behavior of this rule is aggressive, in that it requires parentheses around **all clause expression types** (except simple identifiers/literals); it will **report errors** for each of these ternary expression clauses here: 394 | 395 | ```js 396 | var total = base > 1 ? base * 2 : base * 3; 397 | ``` 398 | 399 | The `base > 1` expression is a *comparison* expression, and can be allowed by disabling the [`"comparison"`](#rule-parens-configuration-comparison) mode. The `base * 2` and `base * 3` expressions are *complex*; there is **no mode in this rule** to disable reporting errors for them. 400 | 401 | ### Rule Configuration 402 | 403 | The **proper-ternary**/*parens* rule can be configured with any combination of these modes, applied to expressions in **any of the clauses** of a ternary expression: 404 | 405 | * [`"ternary"`](#rule-parens-configuration-ternary) (default: `true`) requires a nested ternary expression to have `( .. )` surrounding it. 406 | 407 | * [`"comparison"`](#rule-parens-configuration-comparison) (default: `true`) requires a comparison expression (ie, `x == y`, `x > y`, etc) to have `( .. )` surrounding it. 408 | 409 | * [`"logical"`](#rule-parens-configuration-logical) (default: `true`) requires a logical expression (ie, `x && y`, `!x`, etc) to have `( .. )` surrounding it. 410 | 411 | * [`"call"`](#rule-parens-configuration-call) (default: `true`) requires a call expression (ie, `foo()`, `new Foo()`, etc) to have `( .. )` surrounding it. 412 | 413 | * [`"object"`](#rule-parens-configuration-object) (default: `true`) requires an object or array literal (ie, `{x:1}`, `[1,2]`, etc) to have `( .. )` surrounding it. 414 | 415 | * [`"simple"`](#rule-parens-configuration-simple) (**default: `false`**) requires a simple expression (ie, `x`, `x.y`, `42`, etc) to have `( .. )` surrounding it. It's likely you'll want to keep this mode disabled (default). 416 | 417 | **Note:** Any expression not covered by these modes, such as `x + y`, is considered a *complex* expression. If this rule is enabled, complex expressions always require `( .. )` surrounding them; there is no `"complex"` mode to disable them. Reasoning: if you feel that `x + y * z` is a sufficient expression to not need `( .. )`, then you almost certainly would be inclined to disable all the other above modes too, in which case you should just disable the rule entirely. 418 | 419 | #### Rule `"nested"` Configuration: Ternary 420 | 421 | To configure this rule mode off (on by default): 422 | 423 | ```json 424 | "@getify/proper-ternary/parens": [ "error", { "ternary": false } ] 425 | ``` 426 | 427 | If this mode is on (default), it will report an error for: 428 | 429 | ```js 430 | var x = y ? z : w ? u : v; 431 | ``` 432 | 433 | To avoid this error, use `( .. )` around the nested ternary: 434 | 435 | ```js 436 | var x = y ? z : (w ? u : v); 437 | ``` 438 | 439 | #### Rule `"nested"` Configuration: Comparison 440 | 441 | To configure this rule mode off (on by default): 442 | 443 | ```json 444 | "@getify/proper-ternary/parens": [ "error", { "comparison": false } ] 445 | ``` 446 | 447 | If this mode is on (default), it will report an error for: 448 | 449 | ```js 450 | var x = y > 3 ? y : z; 451 | ``` 452 | 453 | To avoid this error, use `( .. )` around the comparison expression: 454 | 455 | ```js 456 | var x = (y > 3) ? y : z; 457 | ``` 458 | 459 | #### Rule `"nested"` Configuration: Logical 460 | 461 | To configure this rule mode off (on by default): 462 | 463 | ```json 464 | "@getify/proper-ternary/parens": [ "error", { "logical": false } ] 465 | ``` 466 | 467 | If this mode is on (default), it will report an error for: 468 | 469 | ```js 470 | var x = y && z ? y : z; 471 | ``` 472 | 473 | To avoid this error, use `( .. )` around the logical expression: 474 | 475 | ```js 476 | var x = (y && z) ? y : z; 477 | ``` 478 | 479 | #### Rule `"nested"` Configuration: Call 480 | 481 | To configure this rule mode off (on by default): 482 | 483 | ```json 484 | "@getify/proper-ternary/parens": [ "error", { "call": false } ] 485 | ``` 486 | 487 | If this mode is on (default), it will report an error for: 488 | 489 | ```js 490 | var x = y ? foo(y,z) : z; 491 | ``` 492 | 493 | To avoid this error, use `( .. )` around the call expression: 494 | 495 | ```js 496 | var x = y ? ( foo(y,z) ) : z; 497 | ``` 498 | 499 | #### Rule `"nested"` Configuration: Object 500 | 501 | **Note:** This rule mode applies to both array literals (`[1,2]`) and object literals (`{x:1}`). 502 | 503 | To configure this rule mode off (on by default): 504 | 505 | ```json 506 | "@getify/proper-ternary/parens": [ "error", { "object": false } ] 507 | ``` 508 | 509 | If this mode is on (default), it will report an error for: 510 | 511 | ```js 512 | var x = y ? [y,z] : z; 513 | ``` 514 | 515 | To avoid this error, use `( .. )` around the array or object expression: 516 | 517 | ```js 518 | var x = y ? ( [y,z] ) : z; 519 | ``` 520 | 521 | #### Rule `"nested"` Configuration: Simple 522 | 523 | **Note:** It's very likely that you'll want to keep this mode off (default), as it's unlikely that you'll want to require `( .. )` around even simple identifiers and primitive literals. 524 | 525 | To configure this rule mode **on** (**off** by default): 526 | 527 | ```json 528 | "@getify/proper-ternary/parens": [ "error", { "simple": true } ] 529 | ``` 530 | 531 | If this mode is on, it will report errors for each clause: 532 | 533 | ```js 534 | var x = y ? w.u : 42; 535 | ``` 536 | 537 | To avoid these errors, use `( .. )` around each ternary clause's expression: 538 | 539 | ```js 540 | var x = (y) ? (w.u) : (42); 541 | ``` 542 | 543 | ## Rule: `"where"` 544 | 545 | The **proper-ternary**/*where* rule restricts where in program structure ternary expressions can be used. 546 | 547 | To turn this rule on: 548 | 549 | ```json 550 | "@getify/proper-ternary/where": "error" 551 | ``` 552 | 553 | The main purpose of this rule is to avoid readability harm for the program when `? :` ternary expressions are misused. By restricting ternary expressions to certain usages, the ternary-forbidden usages are structured using more appropriate syntax/logic. 554 | 555 | For example, some strongly feel ternary expressions should only be used as expressions (meaning conditionally selecting a value) and not as standalone statements like: 556 | 557 | ```js 558 | (isLoggedIn(user) && user.admin) 559 | ? renderAdminHeader() 560 | : renderBasicHeader(); 561 | ``` 562 | 563 | This construct can be confusing to the reader, as it's easy to miss side-effects in either the *then* or *else* clause. A more preferred approach is to use a standalone `if..else` statement: 564 | 565 | ```js 566 | if (isLoggedIn(user) && user.admin) { 567 | renderAdminHeader(); 568 | } 569 | else { 570 | renderBasicHeader(); 571 | } 572 | ``` 573 | 574 | This scenario is exactly what the `if..else` statement is best at; abusing a ternary expression to save a few characters is not helpful for readability. 575 | 576 | Another example: 577 | 578 | ```js 579 | var loginRecord = { 580 | name: userData.name, 581 | accountType: ( 582 | userData.type == 1 ? "admin" : 583 | userData.type == 2 ? "manager" : 584 | userData.type == 3 ? "vendor" : 585 | "customer" 586 | ) 587 | }; 588 | ``` 589 | 590 | Here a ternary is being used inside an object literal, but a perhaps more readable approach would be to first choose the value via a variable assignment: 591 | 592 | ```js 593 | var accountType = 594 | userData.type == 1 ? "admin" : 595 | userData.type == 2 ? "manager" : 596 | userData.type == 3 ? "vendor" : 597 | "customer"; 598 | 599 | var loginRecord = { 600 | name: userData.name, 601 | accountType 602 | }; 603 | ``` 604 | 605 | A similar situation arises with arguments to function calls: because arguments generally don't have obvious names at the call-site, using a ternary expression as an argument can be less readable if for no other reason than lack of any semantic name to describe the value selection. It's often better to perform the ternary conditional value selection in an assignment first, then pass that named variable as the argument. 606 | 607 | It can also be harder to read code when a ternary expression is a sub-expression in another expression, such as the unary `!` negation expression below: 608 | 609 | ```js 610 | var isAllowed = !( 611 | (userSession != null) 612 | ? userSession.user.accountType == "customer" 613 | : defaultAccountType == "vendor" 614 | ); 615 | ``` 616 | 617 | The indirect negation logic here is more confusing to the reader. A better approach: 618 | 619 | ```js 620 | var basicAccountType = 621 | (userSession != null) 622 | ? userSession.user.accountType == "customer" 623 | : defaultAccountType == "vendor"; 624 | 625 | var isAllowed = !basicAccountType; 626 | ``` 627 | 628 | By semantically naming the result of the ternary decision (`basicAccountType`), the negation is clearer to understand. 629 | 630 | Of course, in this example, the ternary itself isn't strictly necessary, as the logic could have been structured as: 631 | 632 | ```js 633 | var basicAccountType = ( 634 | (userSession != null && userSession.user.accountType == "customer") || 635 | (defaultAccountType == "vendor") 636 | ); 637 | 638 | var isAllowed = !basicAccountType; 639 | ``` 640 | 641 | Some will prefer the ternary version and others will prefer this non-ternary form. 642 | 643 | ### Rule Configuration 644 | 645 | The **proper-ternary**/*where* rule can be configured with any combination of these modes: 646 | 647 | * [`"statement"`](#rule-where-configuration-statement) (default: `true`) forbids a standalone ternary expression statement. 648 | 649 | * [`"property"`](#rule-where-configuration-property) (default: `true`) forbids a ternary expression in an object literal property assignment or array literal position assignment. 650 | 651 | * [`"argument"`](#rule-where-configuration-argument) (default: `true`) forbids a ternary expression as an argument to a function call. 652 | 653 | * [`"return"`](#rule-where-configuration-return) (default: `true`) forbids a ternary expression in a `return` statement of a function, as well as the concise return of an `=>` arrow function. 654 | 655 | * [`"default"`](#rule-where-configuration-default) (default: `true`) forbids a ternary expression in a default value expression (function parameters and destructuring patterns). 656 | 657 | * [`"sub"`](#rule-where-configuration-sub) (default: `true`) forbids a ternary expression as a sub-expression of a unary/binary operator expression (ie, `1 + (x ? y : z)`). 658 | 659 | **Note:** This rule mode does not control ternary expressions nested in other ternary expressions. For that, use the [`"nested"` rule](#rule-nested). 660 | 661 | * [`"assignment"`](#rule-where-configuration-assignment) (default: **`false`**) forbids a ternary expression in assignment statements (using the `=` operator). 662 | 663 | **Note:** Unlike the other rule modes here, this mode is turned off by default, because it's unlikely that you'll want to disable ternary expressions in assignment expressions (ie, `x = y ? z : w`), as this is basically where they're most naturally useful. It's included for completeness sake, but if you're inclined to turn this rule mode on, you perhaps might just consider disabling all ternary expressions with the built-in ["no-ternary" rule](https://eslint.org/docs/rules/no-ternary). 664 | 665 | #### Rule `"where"` Configuration: Statement 666 | 667 | To configure this rule mode off (on by default): 668 | 669 | ```json 670 | "@getify/proper-ternary/where": [ "error", { "statement": false } ] 671 | ``` 672 | 673 | If this mode is on (default), it will report an error for: 674 | 675 | ```js 676 | (isLoggedIn(user) && user.admin) 677 | ? renderAdminHeader() 678 | : renderBasicHeader(); 679 | ``` 680 | 681 | To avoid this error, use an `if..else` statement instead: 682 | 683 | ```js 684 | if (isLoggedIn(user) && user.admin) { 685 | renderAdminHeader(); 686 | } 687 | else { 688 | renderBasicHeader(); 689 | } 690 | ``` 691 | 692 | #### Rule `"where"` Configuration: Property 693 | 694 | To configure this rule mode off (on by default): 695 | 696 | ```json 697 | "@getify/proper-ternary/where": [ "error", { "property": false } ] 698 | ``` 699 | 700 | If this mode is on (default), it will report an error for: 701 | 702 | ```js 703 | var loginRecord = { 704 | name: userData.name, 705 | accountType: ( 706 | userData.type == 1 ? "admin" : 707 | userData.type == 2 ? "manager" : 708 | userData.type == 3 ? "vendor" : 709 | "customer" 710 | ) 711 | }; 712 | ``` 713 | 714 | To avoid this error, use an `if..else` statement instead: 715 | 716 | ```js 717 | var accountType = 718 | userData.type == 1 ? "admin" : 719 | userData.type == 2 ? "manager" : 720 | userData.type == 3 ? "vendor" : 721 | "customer"; 722 | 723 | var loginRecord = { 724 | name: userData.name, 725 | accountType 726 | }; 727 | ``` 728 | 729 | #### Rule `"where"` Configuration: Property 730 | 731 | To configure this rule mode off (on by default): 732 | 733 | ```json 734 | "@getify/proper-ternary/where": [ "error", { "property": false } ] 735 | ``` 736 | 737 | If this mode is on (default), it will report an error for: 738 | 739 | ```js 740 | var loginRecord = { 741 | name: userData.name, 742 | accountType: ( 743 | userData.type == 1 ? "admin" : 744 | userData.type == 2 ? "manager" : 745 | userData.type == 3 ? "vendor" : 746 | "customer" 747 | ) 748 | }; 749 | ``` 750 | 751 | To avoid this error, use an `if..else` statement instead: 752 | 753 | ```js 754 | var accountType = 755 | userData.type == 1 ? "admin" : 756 | userData.type == 2 ? "manager" : 757 | userData.type == 3 ? "vendor" : 758 | "customer"; 759 | 760 | var loginRecord = { 761 | name: userData.name, 762 | accountType 763 | }; 764 | ``` 765 | 766 | #### Rule `"where"` Configuration: Argument 767 | 768 | To configure this rule mode off (on by default): 769 | 770 | ```json 771 | "@getify/proper-ternary/where": [ "error", { "argument": false } ] 772 | ``` 773 | 774 | If this mode is on (default), it will report an error for: 775 | 776 | ```js 777 | checkAccount( 778 | (isLoggedIn(user) && user.admin) ? user : defaultUser 779 | ); 780 | ``` 781 | 782 | To avoid this error, first assign the result of the ternary expression to a variable: 783 | 784 | ```js 785 | var accountToCheck = 786 | (isLoggedIn(user) && user.admin) ? user : defaultUser; 787 | 788 | checkAccount(accountToCheck); 789 | ``` 790 | 791 | #### Rule `"where"` Configuration: Return 792 | 793 | To configure this rule mode off (on by default): 794 | 795 | ```json 796 | "@getify/proper-ternary/where": [ "error", { "argument": false } ] 797 | ``` 798 | 799 | If this mode is on (default), it will report an error for: 800 | 801 | ```js 802 | function lookupAccount(userID = -1) { 803 | return ( 804 | userID != -1 ? users[userID] : defaultUser 805 | ); 806 | } 807 | ``` 808 | 809 | To avoid this error, first assign the result of the ternary expression to a variable: 810 | 811 | ```js 812 | function lookupAccount(userID = -1) { 813 | var user = 814 | userID != -1 ? users[userID] : defaultUser; 815 | 816 | return user; 817 | } 818 | ``` 819 | 820 | #### Rule `"where"` Configuration: Default 821 | 822 | To configure this rule mode off (on by default): 823 | 824 | ```json 825 | "@getify/proper-ternary/where": [ "error", { "default": false } ] 826 | ``` 827 | 828 | If this mode is on (default), it will report an error for: 829 | 830 | ```js 831 | function createUser(data,cb = data.adminUser ? onAdminUser : () => {}) { 832 | // .. 833 | cb(user); 834 | } 835 | ``` 836 | 837 | To avoid this error, (re)assign the variable manually: 838 | 839 | ```js 840 | function createUser(data,cb) { 841 | cb = 842 | cb !== undefined ? cb : 843 | data.adminUser ? onAdminUser : 844 | () => {}; 845 | 846 | // .. 847 | cb(user); 848 | } 849 | ``` 850 | 851 | #### Rule `"where"` Configuration: Sub 852 | 853 | To configure this rule mode off (on by default): 854 | 855 | ```json 856 | "@getify/proper-ternary/where": [ "error", { "sub": false } ] 857 | ``` 858 | 859 | If this mode is on (default), it will report an error for: 860 | 861 | ```js 862 | var isAllowed = !( 863 | (userSession != null) 864 | ? userSession.user.accountType == "customer" 865 | : defaultAccountType == "vendor" 866 | ); 867 | ``` 868 | 869 | To avoid this error, first assign the result of the ternary expression to a variable: 870 | 871 | ```js 872 | var basicAccountType = 873 | (userSession != null) 874 | ? userSession.user.accountType == "customer" 875 | : defaultAccountType == "vendor"; 876 | 877 | var isAllowed = !basicAccountType; 878 | ``` 879 | 880 | #### Rule `"where"` Configuration: Assignment 881 | 882 | **Note:** It's unlikely that you'll want to disable ternary expressions in assignment expressions (ie, `x = y ? z : w`), as this is basically where they're most naturally useful. This rule mode is included for completeness sake, but if you're inclined to turn it on, you perhaps might just consider disabling all ternary expressions with the built-in ["no-ternary" rule](https://eslint.org/docs/rules/no-ternary). 883 | 884 | To configure this rule mode **on** (**off** by default): 885 | 886 | ```json 887 | "@getify/proper-ternary/where": [ "error", { "assignment": true } ] 888 | ``` 889 | 890 | If this mode is on (default), it will report an error for: 891 | 892 | ```js 893 | var name = userRecord != null ? userRecord.name : "Kyle"; 894 | ``` 895 | 896 | To avoid this error, use an `if..else` statement instead of a ternary expression: 897 | 898 | ```js 899 | var name; 900 | if (userRecord != null) { 901 | name = userRecord.name; 902 | } 903 | else { 904 | name = "Kyle"; 905 | } 906 | ``` 907 | 908 | ## npm Package 909 | 910 | To use this plugin with a global install of ESLint (recommended): 911 | 912 | ```cmd 913 | npm install -g @getify/eslint-plugin-proper-ternary 914 | ``` 915 | 916 | To use this plugin with a local install of ESLint: 917 | 918 | ```cmd 919 | npm install @getify/eslint-plugin-proper-ternary 920 | ``` 921 | 922 | ## Builds 923 | 924 | [![Build Status](https://travis-ci.org/getify/eslint-plugin-proper-ternary.svg?branch=master)](https://travis-ci.org/getify/eslint-plugin-proper-ternary) 925 | [![npm Module](https://badge.fury.io/js/%40getify%2Feslint-plugin-proper-ternary.svg)](https://www.npmjs.org/package/@getify/eslint-plugin-proper-ternary) 926 | 927 | If you need to bundle/distribute this eslint plugin, use `dist/eslint-plugin-proper-ternary.js`, which comes pre-built with the npm package distribution; you shouldn't need to rebuild it under normal circumstances. 928 | 929 | However, if you download this repository via Git: 930 | 931 | 1. The included build utility (`scripts/build-core.js`) builds (and minifies) `dist/eslint-plugin-proper-ternary.js` from source. 932 | 933 | 2. To install the build and test dependencies, run `npm install` from the project root directory. 934 | 935 | 3. To manually run the build utility with npm: 936 | 937 | ```cmd 938 | npm run build 939 | ``` 940 | 941 | 4. To run the build utility directly without npm: 942 | 943 | ```cmd 944 | node scripts/build-core.js 945 | ``` 946 | 947 | ## Tests 948 | 949 | A comprehensive test suite is included in this repository, as well as the npm package distribution. The default test behavior runs the test suite against `lib/index.js`. 950 | 951 | 1. The included Node.js test utility (`scripts/node-tests.js`) runs the test suite. 952 | 953 | 2. Ensure the test dependencies are installed by running `npm install` from the project root directory. 954 | 955 | - **Note:** Starting with npm v5, the test utility is **not** run automatically during this `npm install`. With npm v4 and before, the test utility automatically runs at this point. 956 | 957 | 3. To run the test utility with npm: 958 | 959 | ```cmd 960 | npm test 961 | ``` 962 | 963 | Other npm test scripts: 964 | 965 | * `npm run test:dist` will run the test suite against `dist/eslint-plugins-proper-ternary.js` instead of the default of `lib/index.js`. 966 | 967 | * `npm run test:package` will run the test suite as if the package had just been installed via npm. This ensures `package.json`:`main` properly references `dist/eslint-plugins-proper-ternary.js` for inclusion. 968 | 969 | * `npm run test:all` will run all three modes of the test suite. 970 | 971 | 4. To run the test utility directly without npm: 972 | 973 | ```cmd 974 | node scripts/node-tests.js 975 | ``` 976 | 977 | ### Test Coverage 978 | 979 | [![Coverage Status](https://coveralls.io/repos/github/getify/eslint-plugin-proper-ternary/badge.svg?branch=master)](https://coveralls.io/github/getify/eslint-plugin-proper-ternary?branch=master) 980 | 981 | If you have [Istanbul](https://github.com/gotwarlost/istanbul) already installed on your system (requires v1.0+), you can use it to check the test coverage: 982 | 983 | ```cmd 984 | npm run coverage 985 | ``` 986 | 987 | Then open up `coverage/lcov-report/index.html` in a browser to view the report. 988 | 989 | To run Istanbul directly without npm: 990 | 991 | ```cmd 992 | istanbul cover scripts/node-tests.js 993 | ``` 994 | 995 | **Note:** The npm script `coverage:report` is only intended for use by project maintainers; it sends coverage reports to [Coveralls](https://coveralls.io/). 996 | 997 | ## License 998 | 999 | All code and documentation are (c) 2019-2021 Kyle Simpson and released under the [MIT License](http://getify.mit-license.org/). A copy of the MIT License [is also included](LICENSE.txt). 1000 | -------------------------------------------------------------------------------- /lib/copyright-header.txt: -------------------------------------------------------------------------------- 1 | /*! @getify/eslint-plugin-proper-ternary 2 | v${version} (c) ${year} Kyle Simpson 3 | MIT License: http://getify.mit-license.org 4 | */ 5 | 6 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | configs: { 5 | "getify-says": { 6 | plugins: [ "@getify/proper-ternary", ], 7 | rules: { 8 | "@getify/proper-ternary/nested": [ "error", { "else": true, "depth": 10, }, ], 9 | "@getify/proper-ternary/parens": [ "error", { "ternary": false, "call": false, "object": false, }, ], 10 | "@getify/proper-ternary/where": [ "error", { "return": false, }, ], 11 | }, 12 | }, 13 | }, 14 | rules: { 15 | "nested": { 16 | meta: { 17 | type: "problem", 18 | docs: { 19 | description: "Control the kind and depth of nesting allowed with ternary/conditional expressions", 20 | category: "Best Practices", 21 | url: "https://github.com/getify/eslint-plugin-proper-ternary/#rule-nested", 22 | }, 23 | schema: [ 24 | { 25 | type: "object", 26 | properties: { 27 | test: { 28 | type: "boolean", 29 | }, 30 | then: { 31 | type: "boolean", 32 | }, 33 | else: { 34 | type: "boolean", 35 | }, 36 | depth: { 37 | type: "integer", 38 | min: 1, 39 | }, 40 | }, 41 | additionalProperties: false, 42 | }, 43 | ], 44 | messages: { 45 | tooDeep: "Ternary expression nested too deeply", 46 | notHere: "Ternary expression cannot be nested in another ternary expression '{{whichClause}}' clause ({{pattern}})", 47 | }, 48 | }, 49 | create(context) { 50 | var defaultsOnly = context.options.length == 0; 51 | var extraOptions = (!defaultsOnly) ? context.options[0] : null; 52 | var clauseTest = (!defaultsOnly && extraOptions && extraOptions.test === true); 53 | var clauseThen = (!defaultsOnly && extraOptions && extraOptions.then === true); 54 | var clauseElse = (!defaultsOnly && extraOptions && extraOptions.else === true); 55 | var depthLimit = (defaultsOnly || !("depth" in extraOptions)) ? 1 : extraOptions.depth; 56 | 57 | var ternaryStack = new Map(); 58 | 59 | return { 60 | "ConditionalExpression": function enter(node) { 61 | var ancestors = context.getAncestors(); 62 | var parentTernary = getOutermostTernary(ancestors); 63 | if (parentTernary) { 64 | if (!ternaryStack.has(parentTernary)) { 65 | ternaryStack.set(parentTernary,[]); 66 | } 67 | let stack = ternaryStack.get(parentTernary); 68 | stack.push(node); 69 | 70 | // handle nested "depth" mode 71 | if ( 72 | !stack.depthReported && 73 | stack.length > depthLimit 74 | ) { 75 | stack.depthReported = true; 76 | context.report({ 77 | node: stack[depthLimit], 78 | messageId: "tooDeep", 79 | }); 80 | } 81 | 82 | // handle "test" / "then" / "else" clause mode 83 | let whichClause = identifyParentClause(node,ancestors); 84 | if ( 85 | (whichClause == "test" && !clauseTest) || 86 | (whichClause == "then" && !clauseThen) || 87 | (whichClause == "else" && !clauseElse) 88 | ) { 89 | let pattern = 90 | (whichClause == "test") ? "▁ ? ░░ : ░░" : 91 | (whichClause == "then") ? "░░ ? ▁ : ░░" : 92 | "░░ ? ░░ : ▁"; 93 | 94 | context.report({ 95 | node: node, 96 | messageId: "notHere", 97 | data: { 98 | whichClause, 99 | pattern, 100 | }, 101 | }); 102 | } 103 | } 104 | }, 105 | }; 106 | }, 107 | }, 108 | "parens": { 109 | meta: { 110 | type: "problem", 111 | docs: { 112 | description: "Require ( .. ) parentheses delimiting for ternary clauses based on the type of expression", 113 | category: "Best Practices", 114 | url: "https://github.com/getify/eslint-plugin-proper-ternary/#rule-parens", 115 | }, 116 | schema: [ 117 | { 118 | type: "object", 119 | properties: { 120 | ternary: { 121 | type: "boolean", 122 | }, 123 | comparison: { 124 | type: "boolean", 125 | }, 126 | logical: { 127 | type: "boolean", 128 | }, 129 | call: { 130 | type: "boolean", 131 | }, 132 | object: { 133 | type: "boolean", 134 | }, 135 | simple: { 136 | type: "boolean", 137 | }, 138 | }, 139 | additionalProperties: false, 140 | }, 141 | ], 142 | messages: { 143 | needParens: "Ternary clause expression requires enclosing ( .. )", 144 | }, 145 | }, 146 | create(context) { 147 | var defaultsOnly = context.options.length == 0; 148 | var extraOptions = (!defaultsOnly) ? context.options[0] : null; 149 | var ternaryMode = defaultsOnly || !("ternary" in extraOptions) || extraOptions.ternary === true; 150 | var comparisonMode = defaultsOnly || !("comparison" in extraOptions) || extraOptions.comparison === true; 151 | var logicalMode = defaultsOnly || !("logical" in extraOptions) || extraOptions.logical === true; 152 | var callMode = defaultsOnly || !("call" in extraOptions) || extraOptions.call === true; 153 | var objectMode = defaultsOnly || !("object" in extraOptions) || extraOptions.object === true; 154 | var simpleMode = (!defaultsOnly && extraOptions && extraOptions.simple === true); 155 | 156 | var sourceCode = context.getSourceCode(); 157 | 158 | return { 159 | "ConditionalExpression": function enter(node) { 160 | for (let clause of [node.test,node.consequent,node.alternate,]) { 161 | let exprType = 162 | (clause.type == "ConditionalExpression") ? "ternary" : 163 | (clause.type == "BinaryExpression" && ["==","===","!=","!==","<",">","<=",">=","in","instanceof",].includes(clause.operator)) ? "comparison" : 164 | (clause.type == "LogicalExpression") ? "logical" : 165 | (clause.type == "UnaryExpression" && clause.operator == "!") ? "logical" : 166 | (["CallExpression","NewExpression",].includes(clause.type)) ? "call" : 167 | (["ArrayExpression","ObjectExpression",].includes(clause.type)) ? "object" : 168 | (["Identifier","MemberExpression","Literal","TemplateLiteral",].includes(clause.type)) ? "simple" : 169 | "complex"; 170 | 171 | if ( 172 | ( 173 | (ternaryMode && exprType == "ternary") || 174 | (comparisonMode && exprType == "comparison") || 175 | (logicalMode && exprType == "logical") || 176 | (callMode && exprType == "call") || 177 | (objectMode && exprType == "object") || 178 | (simpleMode && exprType == "simple") || 179 | exprType == "complex" 180 | ) && 181 | !parensSurrounding(sourceCode,clause) 182 | ) { 183 | context.report({ 184 | node: clause, 185 | messageId: "needParens", 186 | }); 187 | } 188 | } 189 | }, 190 | }; 191 | }, 192 | }, 193 | "where": { 194 | meta: { 195 | type: "problem", 196 | docs: { 197 | description: "Restrict where in program structure ternary expressions can be used", 198 | category: "Best Practices", 199 | url: "https://github.com/getify/eslint-plugin-proper-ternary/#rule-where", 200 | }, 201 | schema: [ 202 | { 203 | type: "object", 204 | properties: { 205 | statement: { 206 | type: "boolean", 207 | }, 208 | property: { 209 | type: "boolean", 210 | }, 211 | argument: { 212 | type: "boolean", 213 | }, 214 | return: { 215 | type: "boolean", 216 | }, 217 | default: { 218 | type: "boolean", 219 | }, 220 | sub: { 221 | type: "boolean", 222 | }, 223 | assignment: { 224 | type: "boolean", 225 | }, 226 | }, 227 | additionalProperties: false, 228 | }, 229 | ], 230 | messages: { 231 | notHere: "Ternary expression cannot be used {{usage}}", 232 | }, 233 | }, 234 | create(context) { 235 | var defaultsOnly = context.options.length == 0; 236 | var extraOptions = (!defaultsOnly) ? context.options[0] : null; 237 | var statementMode = defaultsOnly || !("statement" in extraOptions) || extraOptions.statement === true; 238 | var propertyMode = defaultsOnly || !("property" in extraOptions) || extraOptions.property === true; 239 | var argumentMode = defaultsOnly || !("argument" in extraOptions) || extraOptions.argument === true; 240 | var returnMode = defaultsOnly || !("return" in extraOptions) || extraOptions.return === true; 241 | var defaultMode = defaultsOnly || !("default" in extraOptions) || extraOptions.default === true; 242 | var subMode = defaultsOnly || !("sub" in extraOptions) || extraOptions.sub === true; 243 | var assignmentMode = (!defaultsOnly && extraOptions && extraOptions.assignment === true); 244 | 245 | return { 246 | "ConditionalExpression": function enter(node) { 247 | // handle "statement" mode 248 | if ( 249 | statementMode && 250 | node.parent.type == "ExpressionStatement" && 251 | node.parent.expression == node 252 | ) { 253 | context.report({ 254 | node: node, 255 | messageId: "notHere", 256 | data: { usage: "as a standalone statement", }, 257 | }); 258 | } 259 | 260 | // handle "property" mode 261 | if (propertyMode) { 262 | // object property? 263 | if ( 264 | node.parent.type == "Property" && 265 | node.parent.value == node 266 | ) { 267 | context.report({ 268 | node: node, 269 | messageId: "notHere", 270 | data: { usage: "in an object property", }, 271 | }); 272 | } 273 | // array value? 274 | else if ( 275 | node.parent.type == "ArrayExpression" && 276 | node.parent.elements.includes(node) 277 | ) { 278 | context.report({ 279 | node: node, 280 | messageId: "notHere", 281 | data: { usage: "in an array element position", }, 282 | }); 283 | } 284 | } 285 | 286 | // handle "argument" mode 287 | if ( 288 | argumentMode && 289 | ["CallExpression","NewExpression",].includes(node.parent.type) && 290 | node.parent.arguments.includes(node) 291 | ) { 292 | context.report({ 293 | node: node, 294 | messageId: "notHere", 295 | data: { usage: "as a function call argument", }, 296 | }); 297 | } 298 | 299 | // handle "property" mode 300 | if (returnMode) { 301 | // return statement? 302 | if ( 303 | node.parent.type == "ReturnStatement" && 304 | node.parent.argument == node 305 | ) { 306 | context.report({ 307 | node: node, 308 | messageId: "notHere", 309 | data: { usage: "as a function return", }, 310 | }); 311 | } 312 | // arrow concise return? 313 | else if ( 314 | node.parent.type == "ArrowFunctionExpression" && 315 | node.parent.body == node 316 | ) { 317 | context.report({ 318 | node: node, 319 | messageId: "notHere", 320 | data: { usage: "as a function return", }, 321 | }); 322 | } 323 | } 324 | 325 | // handle "default" mode 326 | if ( 327 | defaultMode && 328 | node.parent.type == "AssignmentPattern" && 329 | node.parent.right == node 330 | ) { 331 | context.report({ 332 | node: node, 333 | messageId: "notHere", 334 | data: { usage: "as a default value assignment", }, 335 | }); 336 | } 337 | 338 | // handle "sub" mode 339 | if (subMode) { 340 | // in unary expression? 341 | if ( 342 | node.parent.type == "UnaryExpression" && 343 | node.parent.argument == node 344 | ) { 345 | context.report({ 346 | node: node, 347 | messageId: "notHere", 348 | data: { usage: "in a unary operator expression", }, 349 | }); 350 | } 351 | // arrow concise return? 352 | else if ( 353 | node.parent.type == "BinaryExpression" && 354 | ( 355 | node.parent.left == node || 356 | node.parent.right == node 357 | ) 358 | ) { 359 | context.report({ 360 | node: node, 361 | messageId: "notHere", 362 | data: { usage: "in a binary operator expression", }, 363 | }); 364 | } 365 | } 366 | 367 | // handle "assignment" mode 368 | if (assignmentMode) { 369 | // in a variable declarator? 370 | if ( 371 | node.parent.type == "VariableDeclarator" && 372 | node.parent.init == node 373 | ) { 374 | context.report({ 375 | node: node, 376 | messageId: "notHere", 377 | data: { usage: "in a declaration assignment", }, 378 | }); 379 | } 380 | // in an assignment? 381 | else if ( 382 | node.parent.type == "AssignmentExpression" && 383 | node.parent.right == node 384 | ) { 385 | context.report({ 386 | node: node, 387 | messageId: "notHere", 388 | data: { usage: "in an assignment", }, 389 | }); 390 | } 391 | } 392 | }, 393 | }; 394 | }, 395 | }, 396 | }, 397 | }; 398 | 399 | 400 | // *************************** 401 | 402 | function parensSurrounding(sourceCode,node) { 403 | var before = sourceCode.getTokenBefore(node); 404 | var after = sourceCode.getTokenAfter(node); 405 | 406 | return ( 407 | before && 408 | before.type == "Punctuator" && 409 | before.value == "(" && 410 | after && 411 | after.type == "Punctuator" && 412 | after.value == ")" 413 | ); 414 | } 415 | 416 | function getOutermostTernary(nodes) { 417 | var ternary; 418 | for (let node of [...nodes,].reverse()) { 419 | if (node.type == "ConditionalExpression") { 420 | ternary = node; 421 | } 422 | else if ( 423 | node.type.includes("Statement") || 424 | node.type.includes("Declarat") || 425 | node.type.includes("Assignment") 426 | ) { 427 | return ternary; 428 | } 429 | } 430 | } 431 | 432 | function identifyParentClause(ternary,nodes) { 433 | var prevNode = ternary; 434 | for (let node of [...nodes,].reverse()) { 435 | if (node.type == "ConditionalExpression") { 436 | if (node.test === prevNode) return "test"; 437 | else if (node.consequent === prevNode) return "then"; 438 | else { 439 | // NOTE: these contortions/comments here are because of an 440 | // annoying bug with Istanbul's code coverage: 441 | // https://github.com/gotwarlost/istanbul/issues/781 442 | // 443 | /* eslint-disable no-lonely-if */ 444 | /* istanbul ignore else */ 445 | if (node.alternate === prevNode) { 446 | return "else"; 447 | } 448 | /* eslint-enable no-lonely-if */ 449 | } 450 | } 451 | prevNode = node; 452 | } 453 | } 454 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@getify/eslint-plugin-proper-ternary", 3 | "version": "3.1.1", 4 | "description": "ESLint rules to ensure proper usage of ternary/conditional expressions", 5 | "main": "./lib/index.js", 6 | "scripts": { 7 | "test": "node scripts/node-tests.js", 8 | "test:dist": "TEST_DIST=true npm test", 9 | "test:package": "TEST_PACKAGE=true npm test", 10 | "test:all": "npm test && npm run test:dist && npm run test:package", 11 | "coverage": "istanbul cover scripts/node-tests.js", 12 | "coverage:report": "npm run coverage && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js", 13 | "build-core": "node scripts/build-core.js", 14 | "build": "npm run build-core", 15 | "prepare": "npm run build", 16 | "prepublish": "npm run build && npm run test:all", 17 | "publish": "npm run coverage:report" 18 | }, 19 | "devDependencies": { 20 | "coveralls": "~3.1.0", 21 | "eslint": "~7.25.0", 22 | "qunit": "~2.15.0", 23 | "terser": "~5.7.0" 24 | }, 25 | "peerDependencies": { 26 | "eslint": ">= 7.25.0" 27 | }, 28 | "repository": "getify/eslint-plugin-proper-ternary", 29 | "keywords": [ 30 | "eslint", 31 | "plugin", 32 | "eslintplugin", 33 | "rule", 34 | "ternary" 35 | ], 36 | "bugs": { 37 | "url": "https://github.com/getify/eslint-plugin-proper-ternary/issues", 38 | "email": "getify@gmail.com" 39 | }, 40 | "homepage": "https://github.com/getify/eslint-plugin-proper-ternary", 41 | "author": "Kyle Simpson ", 42 | "license": "MIT" 43 | } 44 | -------------------------------------------------------------------------------- /scripts/build-core.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | "use strict"; 4 | 5 | var fs = require("fs"), 6 | path = require("path"), 7 | terser = require("terser"), 8 | year = (new Date()).getFullYear(), 9 | 10 | ROOT_DIR = path.join(__dirname,".."), 11 | SRC_DIR = path.join(ROOT_DIR,"lib"), 12 | DIST_DIR = path.join(ROOT_DIR,"dist"), 13 | 14 | LIB_SRC = [ 15 | path.join(SRC_DIR,"index.js"), 16 | ], 17 | LIB_DIST = [ 18 | path.join(DIST_DIR,"eslint-plugin-proper-ternary.js"), 19 | ] 20 | ; 21 | 22 | 23 | // *************************** 24 | 25 | console.log("*** Building ESLint Plugin 'proper-ternary' ***"); 26 | 27 | (async function main(){ 28 | // try to make the dist directory, if needed 29 | try { 30 | fs.mkdirSync(DIST_DIR,0o755); 31 | } 32 | catch (err) { } 33 | 34 | // read version number from package.json 35 | var packageJSON = JSON.parse( 36 | fs.readFileSync( 37 | path.join(ROOT_DIR,"package.json"), 38 | { encoding: "utf8", } 39 | ) 40 | ); 41 | var version = packageJSON.version; 42 | 43 | // read copyright-header text, render with version and year 44 | var copyrightHeader = fs.readFileSync( 45 | path.join(SRC_DIR,"copyright-header.txt"), 46 | { encoding: "utf8", } 47 | ).replace(/`/g,""); 48 | copyrightHeader = Function("version","year",`return \`${copyrightHeader}\`;`)( version, year ); 49 | 50 | 51 | // *************************** 52 | 53 | for (let [idx,SRC,] of LIB_SRC.entries()) { 54 | let DIST = LIB_DIST[idx]; 55 | 56 | console.log(`Building: ${DIST}`); 57 | 58 | try { 59 | let result = ""; 60 | 61 | result += fs.readFileSync(SRC,{ encoding: "utf8", }); 62 | 63 | result = await terser.minify(result,{ 64 | mangle: { 65 | keep_fnames: true, 66 | }, 67 | compress: { 68 | keep_fnames: true, 69 | }, 70 | output: { 71 | comments: /^!/, 72 | }, 73 | }); 74 | 75 | // was compression successful? 76 | if (!(result && result.code)) { 77 | if (result.error) throw result.error; 78 | else throw result; 79 | } 80 | 81 | // append copyright-header text 82 | result = `${copyrightHeader}${result.code}`; 83 | 84 | // write dist 85 | fs.writeFileSync( DIST, result, { encoding: "utf8", } ); 86 | } 87 | catch (err) { 88 | console.error(err); 89 | process.exit(1); 90 | } 91 | } 92 | 93 | console.log("Complete."); 94 | })(); 95 | -------------------------------------------------------------------------------- /scripts/node-tests.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | "use strict"; 4 | 5 | var path = require("path"); 6 | 7 | var Linter = require("eslint").Linter; 8 | var eslinter = global.eslinter = new Linter(); 9 | var properTernary; 10 | 11 | /* istanbul ignore next */ 12 | if (process.env.TEST_DIST) { 13 | properTernary = require(path.join(__dirname,"..","dist","eslint-plugin-proper-ternary.js")); 14 | } 15 | /* istanbul ignore next */ 16 | else if (process.env.TEST_PACKAGE) { 17 | properTernary = require(path.join(__dirname,"..")); 18 | } 19 | else { 20 | properTernary = require(path.join(__dirname,"..","lib","index.js")); 21 | } 22 | 23 | eslinter.defineRule("@getify/proper-ternary/nested",properTernary.rules.nested); 24 | eslinter.defineRule("@getify/proper-ternary/parens",properTernary.rules.parens); 25 | eslinter.defineRule("@getify/proper-ternary/where",properTernary.rules.where); 26 | 27 | global.QUnit = require("qunit"); 28 | 29 | require(path.join("..","tests","qunit.config.js")); 30 | require(path.join("..","tests","tests.nested.js")); 31 | require(path.join("..","tests","tests.parens.js")); 32 | require(path.join("..","tests","tests.where.js")); 33 | 34 | QUnit.start(); 35 | -------------------------------------------------------------------------------- /tests/qunit.config.js: -------------------------------------------------------------------------------- 1 | QUnit.config.requireExpects = true; 2 | 3 | QUnit.begin(begin); 4 | QUnit.log(testLog); 5 | QUnit.testDone(testDone); 6 | QUnit.done(done); 7 | 8 | var testLogEntries = {}; 9 | 10 | // ****************************** 11 | 12 | function begin(details){ 13 | printEnvNotification(); 14 | 15 | if (details.totalTests > 0) { 16 | console.log(`ESLint Plugin 'proper-ternary' Test Suite (${details.totalTests})`); 17 | console.log(""); 18 | } 19 | else { 20 | console.log("ESLint Plugin 'proper-ternarny' Test Suite: empty!"); 21 | process.exit(1); 22 | } 23 | } 24 | 25 | function testLog(details) { 26 | var testId = details.testId; 27 | 28 | testLogEntries[testId] = testLogEntries[testId] || {}; 29 | testLogEntries[testId][details.message] = { ...details, }; 30 | } 31 | 32 | function testDone(results){ 33 | var testId = results.testId; 34 | 35 | if (results.failed > 0) { 36 | console.log(`Failed: '${results.name}' (${results.failed}/${results.total})`); 37 | for (let i = 0; i < results.assertions.length; i++) { 38 | if (results.assertions[i].result === false) { 39 | let { message, expected, actual, source, } = testLogEntries[testId][results.assertions[i].message]; 40 | console.log(` ${message}`); 41 | // is there a JS exception stack trace included? 42 | if (source && /^\w*Error: .+/.test(source)) { 43 | console.log(""); 44 | console.log(` ${source}`); 45 | console.log(""); 46 | } 47 | else { 48 | console.log(` expected: ${prettyPrint(expected)}`); 49 | console.log(` actual: ${prettyPrint(actual)}`); 50 | } 51 | } 52 | } 53 | } 54 | else if (results.passed > 0) { 55 | console.log(`Passed: '${results.name}' (${results.passed}/${results.total})`); 56 | } 57 | else { 58 | console.log(`No assertions run: '${results.name}'`); 59 | } 60 | } 61 | 62 | function done(results){ 63 | console.log(""); 64 | 65 | if (results.failed > 0) { 66 | console.log(`Failed (${results.failed}/${results.total})`); 67 | printEnvNotification(); 68 | process.exit(1); 69 | } 70 | else if (results.passed > 0) { 71 | console.log(`Passed (${results.passed}/${results.total})`); 72 | printEnvNotification(); 73 | process.exit(0); 74 | } 75 | else { 76 | console.log("No tests run!"); 77 | printEnvNotification(); 78 | process.exit(1); 79 | } 80 | } 81 | 82 | function prettyPrint(v) { 83 | if (Array.isArray(v)) { 84 | return `[${ v.map( prettyPrint ).toString() }]`; 85 | } 86 | else if (typeof v == "bigint") { 87 | return `${v}n`; 88 | } 89 | else if (v && typeof v == "object") { 90 | return JSON.stringify(v,function json(k,v){ 91 | if (v === undefined) { 92 | return null; 93 | } 94 | return v; 95 | }); 96 | } 97 | return String(v); 98 | } 99 | 100 | function printEnvNotification() { 101 | console.log(""); 102 | console.log("**********************************"); 103 | if (process.env.TEST_DIST) { 104 | console.log("********** TESTING DIST **********"); 105 | } 106 | else if (process.env.TEST_PACKAGE) { 107 | console.log("******** TESTING PACKAGE *********"); 108 | } 109 | else { 110 | console.log("********** TESTING SRC ***********"); 111 | } 112 | console.log("**********************************"); 113 | console.log(""); 114 | } 115 | -------------------------------------------------------------------------------- /tests/tests.nested.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var linterOptions = { 4 | nestedDefault: { 5 | parserOptions: { ecmaVersion: 2015, }, 6 | rules: { "@getify/proper-ternary/nested": "error", }, 7 | }, 8 | nestedEmptyOptions: { 9 | parserOptions: { ecmaVersion: 2015, }, 10 | rules: { "@getify/proper-ternary/nested": [ "error", {}, ], }, 11 | }, 12 | nestedAllOnDepth1000: { 13 | parserOptions: { ecmaVersion: 2015, }, 14 | rules: { "@getify/proper-ternary/nested": [ "error", { test: true, then: true, else: true, depth: 1000, }, ], }, 15 | }, 16 | nestedTestOnly: { 17 | parserOptions: { ecmaVersion: 2015, }, 18 | rules: { "@getify/proper-ternary/nested": [ "error", { test: true, depth: 1000, }, ], }, 19 | }, 20 | nestedThenOnly: { 21 | parserOptions: { ecmaVersion: 2015, }, 22 | rules: { "@getify/proper-ternary/nested": [ "error", { then: true, depth: 1000, }, ], }, 23 | }, 24 | nestedElseOnly: { 25 | parserOptions: { ecmaVersion: 2015, }, 26 | rules: { "@getify/proper-ternary/nested": [ "error", { else: true, depth: 1000, }, ], }, 27 | }, 28 | nestedDepth2: { 29 | parserOptions: { ecmaVersion: 2015, }, 30 | rules: { "@getify/proper-ternary/nested": [ "error", { test: true, then: true, else: true, depth: 2, }, ], }, 31 | }, 32 | }; 33 | 34 | QUnit.test( "NESTED (default): conforming", function test(assert){ 35 | var code = ` 36 | var v = x ? y : z; 37 | `; 38 | 39 | var results = eslinter.verify( code, linterOptions.nestedDefault ); 40 | 41 | assert.expect( 1 ); 42 | assert.strictEqual( results.length, 0, "no errors" ); 43 | } ); 44 | 45 | QUnit.test( "NESTED (default): violating", function test(assert){ 46 | var code = ` 47 | var v = (x ? y : z) ? (w ? r : p) : g ? h : j; 48 | `; 49 | 50 | var results = eslinter.verify( code, linterOptions.nestedDefault ); 51 | var [ 52 | { ruleId: ruleId1, messageId: messageId1, message: message1, } = {}, 53 | { ruleId: ruleId2, messageId: messageId2, message: message2, } = {}, 54 | { ruleId: ruleId3, messageId: messageId3, message: message3, } = {}, 55 | { ruleId: ruleId4, messageId: messageId4, message: message4, } = {}, 56 | ] = results || []; 57 | 58 | assert.expect( 12 ); 59 | assert.strictEqual( results.length, 4, "only 4 errors" ); 60 | assert.strictEqual( ruleId1, "@getify/proper-ternary/nested", "ruleId1" ); 61 | assert.strictEqual( messageId1, "notHere", "messageId1" ); 62 | assert.ok( message1.includes("'test' clause"), "message1" ); 63 | assert.strictEqual( ruleId2, "@getify/proper-ternary/nested", "ruleId2" ); 64 | assert.strictEqual( messageId2, "tooDeep", "messageId2" ); 65 | assert.strictEqual( ruleId3, "@getify/proper-ternary/nested", "ruleId3" ); 66 | assert.strictEqual( messageId3, "notHere", "messageId3" ); 67 | assert.ok( message3.includes("'then' clause"), "message3" ); 68 | assert.strictEqual( ruleId4, "@getify/proper-ternary/nested", "ruleId4" ); 69 | assert.strictEqual( messageId4, "notHere", "messageId4" ); 70 | assert.ok( message4.includes("'else' clause"), "message4" ); 71 | } ); 72 | 73 | QUnit.test( "NESTED (empty options): violating", function test(assert){ 74 | var code = ` 75 | var v = (x ? y : z) ? (w ? r : p) : g ? h : j; 76 | `; 77 | 78 | var results = eslinter.verify( code, linterOptions.nestedEmptyOptions ); 79 | var [ 80 | { ruleId: ruleId1, messageId: messageId1, message: message1, } = {}, 81 | { ruleId: ruleId2, messageId: messageId2, message: message2, } = {}, 82 | { ruleId: ruleId3, messageId: messageId3, message: message3, } = {}, 83 | { ruleId: ruleId4, messageId: messageId4, message: message4, } = {}, 84 | ] = results || []; 85 | 86 | assert.expect( 12 ); 87 | assert.strictEqual( results.length, 4, "only 4 errors" ); 88 | assert.strictEqual( ruleId1, "@getify/proper-ternary/nested", "ruleId1" ); 89 | assert.strictEqual( messageId1, "notHere", "messageId1" ); 90 | assert.ok( message1.includes("'test' clause"), "message1" ); 91 | assert.strictEqual( ruleId2, "@getify/proper-ternary/nested", "ruleId2" ); 92 | assert.strictEqual( messageId2, "tooDeep", "messageId2" ); 93 | assert.strictEqual( ruleId3, "@getify/proper-ternary/nested", "ruleId3" ); 94 | assert.strictEqual( messageId3, "notHere", "messageId3" ); 95 | assert.ok( message3.includes("'then' clause"), "message3" ); 96 | assert.strictEqual( ruleId4, "@getify/proper-ternary/nested", "ruleId4" ); 97 | assert.strictEqual( messageId4, "notHere", "messageId4" ); 98 | assert.ok( message4.includes("'else' clause"), "message4" ); 99 | } ); 100 | 101 | QUnit.test( "NESTED (all on, depth: 1000): conforming", function test(assert){ 102 | var code = ` 103 | v = (x ? y : z) ? (w ? r : p) : foo(g ? h : j ? k : m); 104 | `; 105 | 106 | var results = eslinter.verify( code, linterOptions.nestedAllOnDepth1000 ); 107 | 108 | assert.expect( 1 ); 109 | assert.strictEqual( results.length, 0, "no errors" ); 110 | } ); 111 | 112 | QUnit.test( "NESTED (test): conforming", function test(assert){ 113 | var code = ` 114 | var v = ((x ? y : z) ? w : u) ? g : h; 115 | `; 116 | 117 | var results = eslinter.verify( code, linterOptions.nestedTestOnly ); 118 | 119 | assert.expect( 1 ); 120 | assert.strictEqual( results.length, 0, "no errors" ); 121 | } ); 122 | 123 | QUnit.test( "NESTED (test): violating", function test(assert){ 124 | var code = ` 125 | var v = ((x ? y : z) ? w : u) ? g : h ? k : m; 126 | `; 127 | 128 | var results = eslinter.verify( code, linterOptions.nestedTestOnly ); 129 | var [{ ruleId, messageId, } = {},] = results || []; 130 | 131 | assert.expect( 3 ); 132 | assert.strictEqual( results.length, 1, "only 1 error" ); 133 | assert.strictEqual( ruleId, "@getify/proper-ternary/nested", "ruleId" ); 134 | assert.strictEqual( messageId, "notHere", "messageId" ); 135 | } ); 136 | 137 | QUnit.test( "NESTED (then): conforming", function test(assert){ 138 | var code = ` 139 | var v = x ? (y ? (z ? w : u) : g) : h; 140 | `; 141 | 142 | var results = eslinter.verify( code, linterOptions.nestedThenOnly ); 143 | 144 | assert.expect( 1 ); 145 | assert.strictEqual( results.length, 0, "no errors" ); 146 | } ); 147 | 148 | QUnit.test( "NESTED (then): violating", function test(assert){ 149 | var code = ` 150 | var v = x ? (y ? (z ? w : u) : g) : h ? k : m; 151 | `; 152 | 153 | var results = eslinter.verify( code, linterOptions.nestedThenOnly ); 154 | var [{ ruleId, messageId, } = {},] = results || []; 155 | 156 | assert.expect( 3 ); 157 | assert.strictEqual( results.length, 1, "only 1 error" ); 158 | assert.strictEqual( ruleId, "@getify/proper-ternary/nested", "ruleId" ); 159 | assert.strictEqual( messageId, "notHere", "messageId" ); 160 | } ); 161 | 162 | QUnit.test( "NESTED (else): conforming", function test(assert){ 163 | var code = ` 164 | var v = x ? y : z ? w : u ? g : h; 165 | `; 166 | 167 | var results = eslinter.verify( code, linterOptions.nestedElseOnly ); 168 | 169 | assert.expect( 1 ); 170 | assert.strictEqual( results.length, 0, "no errors" ); 171 | } ); 172 | 173 | QUnit.test( "NESTED (else): violating", function test(assert){ 174 | var code = ` 175 | var v = (x ? k : m) ? y : z ? w : u ? g : h; 176 | `; 177 | 178 | var results = eslinter.verify( code, linterOptions.nestedElseOnly ); 179 | var [{ ruleId, messageId, } = {},] = results || []; 180 | 181 | assert.expect( 3 ); 182 | assert.strictEqual( results.length, 1, "only 1 error" ); 183 | assert.strictEqual( ruleId, "@getify/proper-ternary/nested", "ruleId" ); 184 | assert.strictEqual( messageId, "notHere", "messageId" ); 185 | } ); 186 | 187 | QUnit.test( "NESTED (depth: 2): conforming", function test(assert){ 188 | var code = ` 189 | var v = x ? y : z ? w : u ? g : h; 190 | `; 191 | 192 | var results = eslinter.verify( code, linterOptions.nestedDepth2 ); 193 | 194 | assert.expect( 1 ); 195 | assert.strictEqual( results.length, 0, "no errors" ); 196 | } ); 197 | 198 | QUnit.test( "NESTED (depth: 2): violating", function test(assert){ 199 | var code = ` 200 | var v = x ? y : z ? w : u ? g : h ? k : m 201 | `; 202 | 203 | var results = eslinter.verify( code, linterOptions.nestedDepth2 ); 204 | var [{ ruleId, messageId, } = {},] = results || []; 205 | 206 | assert.expect( 3 ); 207 | assert.strictEqual( results.length, 1, "only 1 error" ); 208 | assert.strictEqual( ruleId, "@getify/proper-ternary/nested", "ruleId" ); 209 | assert.strictEqual( messageId, "tooDeep", "messageId" ); 210 | } ); 211 | -------------------------------------------------------------------------------- /tests/tests.parens.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var linterOptions = { 4 | parensDefault: { 5 | parserOptions: { ecmaVersion: 2015, }, 6 | rules: { "@getify/proper-ternary/parens": "error", }, 7 | }, 8 | parensEmptyOptions: { 9 | parserOptions: { ecmaVersion: 2015, }, 10 | rules: { "@getify/proper-ternary/parens": [ "error", {}, ], }, 11 | }, 12 | parensAllOff: { 13 | parserOptions: { ecmaVersion: 2015, }, 14 | rules: { "@getify/proper-ternary/parens": [ "error", { ternary: false, comparison: false, logical: false, call: false, object: false, simple: false, }, ], }, 15 | }, 16 | parensOnlyTernary: { 17 | parserOptions: { ecmaVersion: 2015, }, 18 | rules: { "@getify/proper-ternary/parens": [ "error", { ternary: true, comparison: false, logical: false, call: false, object: false, simple: false, }, ], }, 19 | }, 20 | parensOnlyComparison: { 21 | parserOptions: { ecmaVersion: 2015, }, 22 | rules: { "@getify/proper-ternary/parens": [ "error", { ternary: false, comparison: true, logical: false, call: false, object: false, simple: false, }, ], }, 23 | }, 24 | parensOnlyLogical: { 25 | parserOptions: { ecmaVersion: 2015, }, 26 | rules: { "@getify/proper-ternary/parens": [ "error", { ternary: false, comparison: false, logical: true, call: false, object: false, simple: false, }, ], }, 27 | }, 28 | parensOnlyCall: { 29 | parserOptions: { ecmaVersion: 2015, }, 30 | rules: { "@getify/proper-ternary/parens": [ "error", { ternary: false, comparison: false, logical: false, call: true, object: false, simple: false, }, ], }, 31 | }, 32 | parensOnlyObject: { 33 | parserOptions: { ecmaVersion: 2015, }, 34 | rules: { "@getify/proper-ternary/parens": [ "error", { ternary: false, comparison: false, logical: false, call: false, object: true, simple: false, }, ], }, 35 | }, 36 | parensOnlySimple: { 37 | parserOptions: { ecmaVersion: 2015, }, 38 | rules: { "@getify/proper-ternary/parens": [ "error", { ternary: false, comparison: false, logical: false, call: false, object: false, simple: true, }, ], }, 39 | }, 40 | }; 41 | 42 | QUnit.test( "PARENS (default): conforming", function test(assert){ 43 | var code = ` 44 | var v1 = x ? y : (x ? y : z); 45 | var v2 = x ? y : (x == y); 46 | var v3 = x ? y : (x && y); 47 | var v4 = x ? y : (foo(x,y)); 48 | var v5 = x ? y : ({ x: y }); 49 | var v6 = x ? y : x.y; 50 | var v7 = x ? y : (x + y); 51 | `; 52 | 53 | var results = eslinter.verify( code, linterOptions.parensDefault ); 54 | 55 | assert.expect( 1 ); 56 | assert.strictEqual( results.length, 0, "no errors" ); 57 | } ); 58 | 59 | QUnit.test( "PARENS (default): violating", function test(assert){ 60 | var code = ` 61 | var v1 = x ? y : x ? y : z; 62 | var v2 = x ? y : x == y; 63 | var v3 = x ? y : x && y; 64 | var v4 = x ? y : foo(x,y); 65 | var v5 = x ? y : { x: y }; 66 | var v6 = x ? y : x.y; 67 | var v7 = x ? y : x + y; 68 | `; 69 | 70 | var results = eslinter.verify( code, linterOptions.parensDefault ); 71 | var [ 72 | { ruleId: ruleId1, messageId: messageId1, } = {}, 73 | { ruleId: ruleId2, messageId: messageId2, } = {}, 74 | { ruleId: ruleId3, messageId: messageId3, } = {}, 75 | { ruleId: ruleId4, messageId: messageId4, } = {}, 76 | { ruleId: ruleId5, messageId: messageId5, } = {}, 77 | { ruleId: ruleId6, messageId: messageId6, } = {}, 78 | ] = results || []; 79 | 80 | assert.expect( 13 ); 81 | assert.strictEqual( results.length, 6, "only 6 errors" ); 82 | assert.strictEqual( ruleId1, "@getify/proper-ternary/parens", "ruleId1" ); 83 | assert.strictEqual( messageId1, "needParens", "messageId1" ); 84 | assert.strictEqual( ruleId2, "@getify/proper-ternary/parens", "ruleId2" ); 85 | assert.strictEqual( messageId2, "needParens", "messageId2" ); 86 | assert.strictEqual( ruleId3, "@getify/proper-ternary/parens", "ruleId3" ); 87 | assert.strictEqual( messageId3, "needParens", "messageId3" ); 88 | assert.strictEqual( ruleId4, "@getify/proper-ternary/parens", "ruleId4" ); 89 | assert.strictEqual( messageId4, "needParens", "messageId4" ); 90 | assert.strictEqual( ruleId5, "@getify/proper-ternary/parens", "ruleId5" ); 91 | assert.strictEqual( messageId5, "needParens", "messageId5" ); 92 | assert.strictEqual( ruleId6, "@getify/proper-ternary/parens", "ruleId6" ); 93 | assert.strictEqual( messageId6, "needParens", "messageId6" ); 94 | } ); 95 | 96 | QUnit.test( "PARENS (empty options): violating", function test(assert){ 97 | var code = ` 98 | var v1 = x ? y : x ? y : z; 99 | var v2 = x ? y : x == y; 100 | var v3 = x ? y : x && y; 101 | var v4 = x ? y : foo(x,y); 102 | var v5 = x ? y : { x: y }; 103 | var v6 = x ? y : x.y; 104 | var v7 = x ? y : x + y; 105 | `; 106 | 107 | var results = eslinter.verify( code, linterOptions.parensEmptyOptions ); 108 | var [ 109 | { ruleId: ruleId1, messageId: messageId1, } = {}, 110 | { ruleId: ruleId2, messageId: messageId2, } = {}, 111 | { ruleId: ruleId3, messageId: messageId3, } = {}, 112 | { ruleId: ruleId4, messageId: messageId4, } = {}, 113 | { ruleId: ruleId5, messageId: messageId5, } = {}, 114 | { ruleId: ruleId6, messageId: messageId6, } = {}, 115 | ] = results || []; 116 | 117 | assert.expect( 13 ); 118 | assert.strictEqual( results.length, 6, "only 6 errors" ); 119 | assert.strictEqual( ruleId1, "@getify/proper-ternary/parens", "ruleId1" ); 120 | assert.strictEqual( messageId1, "needParens", "messageId1" ); 121 | assert.strictEqual( ruleId2, "@getify/proper-ternary/parens", "ruleId2" ); 122 | assert.strictEqual( messageId2, "needParens", "messageId2" ); 123 | assert.strictEqual( ruleId3, "@getify/proper-ternary/parens", "ruleId3" ); 124 | assert.strictEqual( messageId3, "needParens", "messageId3" ); 125 | assert.strictEqual( ruleId4, "@getify/proper-ternary/parens", "ruleId4" ); 126 | assert.strictEqual( messageId4, "needParens", "messageId4" ); 127 | assert.strictEqual( ruleId5, "@getify/proper-ternary/parens", "ruleId5" ); 128 | assert.strictEqual( messageId5, "needParens", "messageId5" ); 129 | assert.strictEqual( ruleId6, "@getify/proper-ternary/parens", "ruleId6" ); 130 | assert.strictEqual( messageId6, "needParens", "messageId6" ); 131 | } ); 132 | 133 | QUnit.test( "PARENS (all off): conforming", function test(assert){ 134 | var code = ` 135 | var v1 = x ? y : x ? y : z; 136 | var v2 = x ? y : x == y; 137 | var v3 = x ? y : x && y; 138 | var v4 = x ? y : foo(x,y); 139 | var v5 = x ? y : { x: y }; 140 | var v6 = x ? y : x.y; 141 | `; 142 | 143 | var results = eslinter.verify( code, linterOptions.parensAllOff ); 144 | 145 | assert.expect( 1 ); 146 | assert.strictEqual( results.length, 0, "no errors" ); 147 | } ); 148 | 149 | QUnit.test( "PARENS (ternary): conforming", function test(assert){ 150 | var code = ` 151 | var v1 = x ? y : (x ? y : z); 152 | var v2 = x ? y : x == y; 153 | var v3 = x ? y : x && y; 154 | var v4 = x ? y : foo(x,y); 155 | var v5 = x ? y : { x: y }; 156 | var v6 = x ? y : x.y; 157 | `; 158 | 159 | var results = eslinter.verify( code, linterOptions.parensOnlyTernary ); 160 | 161 | assert.expect( 1 ); 162 | assert.strictEqual( results.length, 0, "no errors" ); 163 | } ); 164 | 165 | QUnit.test( "PARENS (ternary): violating", function test(assert){ 166 | var code = ` 167 | var v1 = x ? y : x ? y : z; 168 | var v2 = x ? y : x == y; 169 | var v3 = x ? y : x && y; 170 | var v4 = x ? y : foo(x,y); 171 | var v5 = x ? y : { x: y }; 172 | var v6 = x ? y : x.y; 173 | `; 174 | 175 | var results = eslinter.verify( code, linterOptions.parensOnlyTernary ); 176 | var [{ ruleId, messageId, } = {},] = results || []; 177 | 178 | assert.expect( 3 ); 179 | assert.strictEqual( results.length, 1, "only 1 error" ); 180 | assert.strictEqual( ruleId, "@getify/proper-ternary/parens", "ruleId" ); 181 | assert.strictEqual( messageId, "needParens", "messageId" ); 182 | } ); 183 | 184 | QUnit.test( "PARENS (comparison): conforming", function test(assert){ 185 | var code = ` 186 | var v1 = x ? y : x ? y : z; 187 | var v2 = x ? y : (x == y); 188 | var v3 = x ? y : x && y; 189 | var v4 = x ? y : foo(x,y); 190 | var v5 = x ? y : { x: y }; 191 | var v6 = x ? y : x.y; 192 | `; 193 | 194 | var results = eslinter.verify( code, linterOptions.parensOnlyComparison ); 195 | 196 | assert.expect( 1 ); 197 | assert.strictEqual( results.length, 0, "no errors" ); 198 | } ); 199 | 200 | QUnit.test( "PARENS (comparison): violating", function test(assert){ 201 | var code = ` 202 | var v1 = x ? y : x ? y : z; 203 | var v2 = x ? y : x == y; 204 | var v3 = x ? y : x && y; 205 | var v4 = x ? y : foo(x,y); 206 | var v5 = x ? y : { x: y }; 207 | var v6 = x ? y : x.y; 208 | `; 209 | 210 | var results = eslinter.verify( code, linterOptions.parensOnlyComparison ); 211 | var [{ ruleId, messageId, } = {},] = results || []; 212 | 213 | assert.expect( 3 ); 214 | assert.strictEqual( results.length, 1, "only 1 error" ); 215 | assert.strictEqual( ruleId, "@getify/proper-ternary/parens", "ruleId" ); 216 | assert.strictEqual( messageId, "needParens", "messageId" ); 217 | } ); 218 | 219 | QUnit.test( "PARENS (logical): conforming", function test(assert){ 220 | var code = ` 221 | var v1 = x ? y : x ? y : z; 222 | var v2 = x ? y : x == y; 223 | var v3 = x ? y : (x && y); 224 | var v4 = x ? y : (!z); 225 | var v5 = x ? y : foo(x,y); 226 | var v6 = x ? y : { x: y }; 227 | var v7 = x ? y : x.y; 228 | `; 229 | 230 | var results = eslinter.verify( code, linterOptions.parensOnlyLogical ); 231 | 232 | assert.expect( 1 ); 233 | assert.strictEqual( results.length, 0, "no errors" ); 234 | } ); 235 | 236 | QUnit.test( "PARENS (logical): violating", function test(assert){ 237 | var code = ` 238 | var v1 = x ? y : x ? y : z; 239 | var v2 = x ? y : x == y; 240 | var v3 = x ? y : x && y; 241 | var v4 = x ? y : !z; 242 | var v5 = x ? y : foo(x,y); 243 | var v6 = x ? y : { x: y }; 244 | var v7 = x ? y : x.y; 245 | `; 246 | 247 | var results = eslinter.verify( code, linterOptions.parensOnlyLogical ); 248 | var [ 249 | { ruleId: ruleId1, messageId: messageId1, } = {}, 250 | { ruleId: ruleId2, messageId: messageId2, } = {}, 251 | ] = results || []; 252 | 253 | assert.expect( 5 ); 254 | assert.strictEqual( results.length, 2, "only 2 errors" ); 255 | assert.strictEqual( ruleId1, "@getify/proper-ternary/parens", "ruleId1" ); 256 | assert.strictEqual( messageId1, "needParens", "messageId1" ); 257 | assert.strictEqual( ruleId2, "@getify/proper-ternary/parens", "ruleId2" ); 258 | assert.strictEqual( messageId2, "needParens", "messageId2" ); 259 | } ); 260 | 261 | QUnit.test( "PARENS (call): conforming", function test(assert){ 262 | var code = ` 263 | var v1 = x ? y : x ? y : z; 264 | var v2 = x ? y : x == y; 265 | var v3 = x ? y : x && y; 266 | var v4 = x ? y : (foo(x,y)); 267 | var v5 = x ? y : { x: y }; 268 | var v6 = x ? y : x.y; 269 | `; 270 | 271 | var results = eslinter.verify( code, linterOptions.parensOnlyCall ); 272 | 273 | assert.expect( 1 ); 274 | assert.strictEqual( results.length, 0, "no errors" ); 275 | } ); 276 | 277 | QUnit.test( "PARENS (call): violating", function test(assert){ 278 | var code = ` 279 | var v1 = x ? y : x ? y : z; 280 | var v2 = x ? y : x == y; 281 | var v3 = x ? y : x && y; 282 | var v4 = x ? y : foo(x,y); 283 | var v5 = x ? y : { x: y }; 284 | var v6 = x ? y : x.y; 285 | `; 286 | 287 | var results = eslinter.verify( code, linterOptions.parensOnlyCall ); 288 | var [{ ruleId, messageId, } = {},] = results || []; 289 | 290 | assert.expect( 3 ); 291 | assert.strictEqual( results.length, 1, "only 1 error" ); 292 | assert.strictEqual( ruleId, "@getify/proper-ternary/parens", "ruleId" ); 293 | assert.strictEqual( messageId, "needParens", "messageId" ); 294 | } ); 295 | 296 | QUnit.test( "PARENS (object): conforming", function test(assert){ 297 | var code = ` 298 | var v1 = x ? y : x ? y : z; 299 | var v2 = x ? y : x == y; 300 | var v3 = x ? y : x && y; 301 | var v4 = x ? y : foo(x,y); 302 | var v5 = x ? y : ({ x: y }); 303 | var v6 = x ? y : x.y; 304 | `; 305 | 306 | var results = eslinter.verify( code, linterOptions.parensOnlyObject ); 307 | 308 | assert.expect( 1 ); 309 | assert.strictEqual( results.length, 0, "no errors" ); 310 | } ); 311 | 312 | QUnit.test( "PARENS (object): violating", function test(assert){ 313 | var code = ` 314 | var v1 = x ? y : x ? y : z; 315 | var v2 = x ? y : x == y; 316 | var v3 = x ? y : x && y; 317 | var v4 = x ? y : foo(x,y); 318 | var v5 = x ? y : { x: y }; 319 | var v6 = x ? y : x.y; 320 | `; 321 | 322 | var results = eslinter.verify( code, linterOptions.parensOnlyObject ); 323 | var [{ ruleId, messageId, } = {},] = results || []; 324 | 325 | assert.expect( 3 ); 326 | assert.strictEqual( results.length, 1, "only 1 error" ); 327 | assert.strictEqual( ruleId, "@getify/proper-ternary/parens", "ruleId" ); 328 | assert.strictEqual( messageId, "needParens", "messageId" ); 329 | } ); 330 | 331 | QUnit.test( "PARENS (simple): conforming", function test(assert){ 332 | var code = ` 333 | var v1 = (x) ? (y) : (x) ? (y) : (z); 334 | var v2 = (x) ? (y) : x == y; 335 | var v3 = (x) ? (y) : x && y; 336 | var v4 = (x) ? (y) : foo(x,y); 337 | var v5 = (x) ? (y) : { x: y }; 338 | var v6 = (x) ? (y) : (x.y); 339 | var v7 = (x) ? (y) : (\`z\`); 340 | `; 341 | 342 | var results = eslinter.verify( code, linterOptions.parensOnlySimple ); 343 | 344 | assert.expect( 1 ); 345 | assert.strictEqual( results.length, 0, "no errors" ); 346 | } ); 347 | 348 | QUnit.test( "PARENS (simple): violating", function test(assert){ 349 | var code = ` 350 | var v1 = (x) ? (y) : (x) ? (y) : z; 351 | var v2 = (x) ? (y) : x == y; 352 | var v3 = (x) ? (y) : x && y; 353 | var v4 = (x) ? (y) : foo(x,y); 354 | var v5 = (x) ? (y) : { x: y }; 355 | var v6 = (x) ? (y) : x.y; 356 | var v7 = (x) ? (y) : \`z\`; 357 | `; 358 | 359 | var results = eslinter.verify( code, linterOptions.parensOnlySimple ); 360 | var [ 361 | { ruleId: ruleId1, messageId: messageId1, } = {}, 362 | { ruleId: ruleId2, messageId: messageId2, } = {}, 363 | { ruleId: ruleId3, messageId: messageId3, } = {}, 364 | ] = results || []; 365 | 366 | assert.expect( 7 ); 367 | assert.strictEqual( results.length, 3, "only 3 errors" ); 368 | assert.strictEqual( ruleId1, "@getify/proper-ternary/parens", "ruleId1" ); 369 | assert.strictEqual( messageId1, "needParens", "messageId1" ); 370 | assert.strictEqual( ruleId2, "@getify/proper-ternary/parens", "ruleId2" ); 371 | assert.strictEqual( messageId2, "needParens", "messageId2" ); 372 | assert.strictEqual( ruleId3, "@getify/proper-ternary/parens", "ruleId3" ); 373 | assert.strictEqual( messageId3, "needParens", "messageId3" ); 374 | } ); 375 | -------------------------------------------------------------------------------- /tests/tests.where.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var linterOptions = { 4 | whereDefault: { 5 | parserOptions: { ecmaVersion: 2015, }, 6 | rules: { "@getify/proper-ternary/where": "error", }, 7 | }, 8 | whereEmptyOptions: { 9 | parserOptions: { ecmaVersion: 2015, }, 10 | rules: { "@getify/proper-ternary/where": [ "error", {}, ], }, 11 | }, 12 | whereAllOff: { 13 | parserOptions: { ecmaVersion: 2015, }, 14 | rules: { "@getify/proper-ternary/where": [ "error", { statement: false, property: false, argument: false, return: false, default: false, sub: false, assignment: false, }, ], }, 15 | }, 16 | whereOnlyStatement: { 17 | parserOptions: { ecmaVersion: 2015, }, 18 | rules: { "@getify/proper-ternary/where": [ "error", { statement: true, property: false, argument: false, return: false, default: false, sub: false, assignment: false, }, ], }, 19 | }, 20 | whereOnlyProperty: { 21 | parserOptions: { ecmaVersion: 2015, }, 22 | rules: { "@getify/proper-ternary/where": [ "error", { statement: false, property: true, argument: false, return: false, default: false, sub: false, assignment: false, }, ], }, 23 | }, 24 | whereOnlyArgument: { 25 | parserOptions: { ecmaVersion: 2015, }, 26 | rules: { "@getify/proper-ternary/where": [ "error", { statement: false, property: false, argument: true, return: false, default: false, sub: false, assignment: false, }, ], }, 27 | }, 28 | whereOnlyReturn: { 29 | parserOptions: { ecmaVersion: 2015, }, 30 | rules: { "@getify/proper-ternary/where": [ "error", { statement: false, property: false, argument: false, return: true, default: false, sub: false, assignment: false, }, ], }, 31 | }, 32 | whereOnlyDefault: { 33 | parserOptions: { ecmaVersion: 2015, }, 34 | rules: { "@getify/proper-ternary/where": [ "error", { statement: false, property: false, argument: false, return: false, default: true, sub: false, assignment: false, }, ], }, 35 | }, 36 | whereOnlySub: { 37 | parserOptions: { ecmaVersion: 2015, }, 38 | rules: { "@getify/proper-ternary/where": [ "error", { statement: false, property: false, argument: false, return: false, default: false, sub: true, assignment: false, }, ], }, 39 | }, 40 | whereOnlyAssignment: { 41 | parserOptions: { ecmaVersion: 2015, }, 42 | rules: { "@getify/proper-ternary/where": [ "error", { statement: false, property: false, argument: false, return: false, default: false, sub: false, assignment: true, }, ], }, 43 | }, 44 | }; 45 | 46 | QUnit.test( "WHERE (default): conforming", function test(assert){ 47 | var code = ` 48 | var v = x ? y : z; 49 | v = x ? y : z; 50 | `; 51 | 52 | var results = eslinter.verify( code, linterOptions.whereDefault ); 53 | 54 | assert.expect( 1 ); 55 | assert.strictEqual( results.length, 0, "no errors" ); 56 | } ); 57 | 58 | QUnit.test( "WHERE (default): violating", function test(assert){ 59 | var code = ` 60 | x ? y : z; 61 | var o = { p: x ? y : z }; 62 | var a = [ x ? y : z ]; 63 | foo( x ? y : z ); 64 | function foo() { return x ? y : z; } 65 | foo = () => x ? y : z; 66 | function bar(p = x ? y : z) {} 67 | [ v = x ? y : z ] = arr; 68 | !(x ? y : z); 69 | 1 + (x ? y : z); 70 | (x ? y : z) + 1; 71 | var v = x ? y : z; 72 | v = x ? y : z; 73 | `; 74 | 75 | var results = eslinter.verify( code, linterOptions.whereDefault ); 76 | var [ 77 | { ruleId: ruleId1, messageId: messageId1, message: message1, } = {}, 78 | { ruleId: ruleId2, messageId: messageId2, message: message2, } = {}, 79 | { ruleId: ruleId3, messageId: messageId3, message: message3, } = {}, 80 | { ruleId: ruleId4, messageId: messageId4, message: message4, } = {}, 81 | { ruleId: ruleId5, messageId: messageId5, message: message5, } = {}, 82 | { ruleId: ruleId6, messageId: messageId6, message: message6, } = {}, 83 | { ruleId: ruleId7, messageId: messageId7, message: message7, } = {}, 84 | { ruleId: ruleId8, messageId: messageId8, message: message8, } = {}, 85 | { ruleId: ruleId9, messageId: messageId9, message: message9, } = {}, 86 | { ruleId: ruleId10, messageId: messageId10, message: message10, } = {}, 87 | { ruleId: ruleId11, messageId: messageId11, message: message11, } = {}, 88 | ] = results || []; 89 | 90 | assert.expect( 34 ); 91 | assert.strictEqual( results.length, 11, "only 11 errors" ); 92 | assert.strictEqual( ruleId1, "@getify/proper-ternary/where", "ruleId1" ); 93 | assert.strictEqual( messageId1, "notHere", "messageId1" ); 94 | assert.ok( message1.includes("standalone statement"), "message1" ); 95 | assert.strictEqual( ruleId2, "@getify/proper-ternary/where", "ruleId2" ); 96 | assert.strictEqual( messageId2, "notHere", "messageId2" ); 97 | assert.ok( message2.includes("object property"), "message2" ); 98 | assert.strictEqual( ruleId3, "@getify/proper-ternary/where", "ruleId3" ); 99 | assert.strictEqual( messageId3, "notHere", "messageId3" ); 100 | assert.ok( message3.includes("array element"), "message3" ); 101 | assert.strictEqual( ruleId4, "@getify/proper-ternary/where", "ruleId4" ); 102 | assert.strictEqual( messageId4, "notHere", "messageId4" ); 103 | assert.ok( message4.includes("call argument"), "message4" ); 104 | assert.strictEqual( ruleId5, "@getify/proper-ternary/where", "ruleId5" ); 105 | assert.strictEqual( messageId5, "notHere", "messageId5" ); 106 | assert.ok( message5.includes("function return"), "message5" ); 107 | assert.strictEqual( ruleId6, "@getify/proper-ternary/where", "ruleId6" ); 108 | assert.strictEqual( messageId6, "notHere", "messageId6" ); 109 | assert.ok( message6.includes("function return"), "message6" ); 110 | assert.strictEqual( ruleId7, "@getify/proper-ternary/where", "ruleId7" ); 111 | assert.strictEqual( messageId7, "notHere", "messageId7" ); 112 | assert.ok( message7.includes("default value"), "message7" ); 113 | assert.strictEqual( ruleId8, "@getify/proper-ternary/where", "ruleId8" ); 114 | assert.strictEqual( messageId8, "notHere", "messageId8" ); 115 | assert.ok( message8.includes("default value"), "message8" ); 116 | assert.strictEqual( ruleId9, "@getify/proper-ternary/where", "ruleId9" ); 117 | assert.strictEqual( messageId9, "notHere", "messageId9" ); 118 | assert.ok( message9.includes("unary"), "message9" ); 119 | assert.strictEqual( ruleId10, "@getify/proper-ternary/where", "ruleId10" ); 120 | assert.strictEqual( messageId10, "notHere", "messageId10" ); 121 | assert.ok( message10.includes("binary"), "message10" ); 122 | assert.strictEqual( ruleId11, "@getify/proper-ternary/where", "ruleId11" ); 123 | assert.strictEqual( messageId11, "notHere", "messageId11" ); 124 | assert.ok( message11.includes("binary"), "message11" ); 125 | } ); 126 | 127 | QUnit.test( "WHERE (empty options): violating", function test(assert){ 128 | var code = ` 129 | x ? y : z; 130 | var o = { p: x ? y : z }; 131 | var a = [ x ? y : z ]; 132 | foo( x ? y : z ); 133 | function foo() { return x ? y : z; } 134 | foo = () => x ? y : z; 135 | function bar(p = x ? y : z) {} 136 | [ v = x ? y : z ] = arr; 137 | !(x ? y : z); 138 | 1 + (x ? y : z); 139 | (x ? y : z) + 1; 140 | var v = x ? y : z; 141 | v = x ? y : z; 142 | `; 143 | 144 | var results = eslinter.verify( code, linterOptions.whereEmptyOptions ); 145 | var [ 146 | { ruleId: ruleId1, messageId: messageId1, message: message1, } = {}, 147 | { ruleId: ruleId2, messageId: messageId2, message: message2, } = {}, 148 | { ruleId: ruleId3, messageId: messageId3, message: message3, } = {}, 149 | { ruleId: ruleId4, messageId: messageId4, message: message4, } = {}, 150 | { ruleId: ruleId5, messageId: messageId5, message: message5, } = {}, 151 | { ruleId: ruleId6, messageId: messageId6, message: message6, } = {}, 152 | { ruleId: ruleId7, messageId: messageId7, message: message7, } = {}, 153 | { ruleId: ruleId8, messageId: messageId8, message: message8, } = {}, 154 | { ruleId: ruleId9, messageId: messageId9, message: message9, } = {}, 155 | { ruleId: ruleId10, messageId: messageId10, message: message10, } = {}, 156 | { ruleId: ruleId11, messageId: messageId11, message: message11, } = {}, 157 | ] = results || []; 158 | 159 | assert.expect( 34 ); 160 | assert.strictEqual( results.length, 11, "only 11 errors" ); 161 | assert.strictEqual( ruleId1, "@getify/proper-ternary/where", "ruleId1" ); 162 | assert.strictEqual( messageId1, "notHere", "messageId1" ); 163 | assert.ok( message1.includes("standalone statement"), "message1" ); 164 | assert.strictEqual( ruleId2, "@getify/proper-ternary/where", "ruleId2" ); 165 | assert.strictEqual( messageId2, "notHere", "messageId2" ); 166 | assert.ok( message2.includes("object property"), "message2" ); 167 | assert.strictEqual( ruleId3, "@getify/proper-ternary/where", "ruleId3" ); 168 | assert.strictEqual( messageId3, "notHere", "messageId3" ); 169 | assert.ok( message3.includes("array element"), "message3" ); 170 | assert.strictEqual( ruleId4, "@getify/proper-ternary/where", "ruleId4" ); 171 | assert.strictEqual( messageId4, "notHere", "messageId4" ); 172 | assert.ok( message4.includes("call argument"), "message4" ); 173 | assert.strictEqual( ruleId5, "@getify/proper-ternary/where", "ruleId5" ); 174 | assert.strictEqual( messageId5, "notHere", "messageId5" ); 175 | assert.ok( message5.includes("function return"), "message5" ); 176 | assert.strictEqual( ruleId6, "@getify/proper-ternary/where", "ruleId6" ); 177 | assert.strictEqual( messageId6, "notHere", "messageId6" ); 178 | assert.ok( message6.includes("function return"), "message6" ); 179 | assert.strictEqual( ruleId7, "@getify/proper-ternary/where", "ruleId7" ); 180 | assert.strictEqual( messageId7, "notHere", "messageId7" ); 181 | assert.ok( message7.includes("default value"), "message7" ); 182 | assert.strictEqual( ruleId8, "@getify/proper-ternary/where", "ruleId8" ); 183 | assert.strictEqual( messageId8, "notHere", "messageId8" ); 184 | assert.ok( message8.includes("default value"), "message8" ); 185 | assert.strictEqual( ruleId9, "@getify/proper-ternary/where", "ruleId9" ); 186 | assert.strictEqual( messageId9, "notHere", "messageId9" ); 187 | assert.ok( message9.includes("unary"), "message9" ); 188 | assert.strictEqual( ruleId10, "@getify/proper-ternary/where", "ruleId10" ); 189 | assert.strictEqual( messageId10, "notHere", "messageId10" ); 190 | assert.ok( message10.includes("binary"), "message10" ); 191 | assert.strictEqual( ruleId11, "@getify/proper-ternary/where", "ruleId11" ); 192 | assert.strictEqual( messageId11, "notHere", "messageId11" ); 193 | assert.ok( message11.includes("binary"), "message11" ); 194 | } ); 195 | 196 | QUnit.test( "WHERE (all off): conforming", function test(assert){ 197 | var code = ` 198 | x ? y : z; 199 | var o = { p: x ? y : z }; 200 | var a = [ x ? y : z ]; 201 | foo( x ? y : z ); 202 | function foo() { return x ? y : z; } 203 | foo = () => x ? y : z; 204 | function bar(p = x ? y : z) {} 205 | [ v = x ? y : z ] = arr; 206 | !(x ? y : z); 207 | 1 + (x ? y : z); 208 | (x ? y : z) + 1; 209 | var v = x ? y : z; 210 | v = x ? y : z; 211 | `; 212 | 213 | var results = eslinter.verify( code, linterOptions.whereAllOff ); 214 | 215 | assert.expect( 1 ); 216 | assert.strictEqual( results.length, 0, "no errors" ); 217 | } ); 218 | 219 | QUnit.test( "WHERE (statement): violating", function test(assert){ 220 | var code = ` 221 | x ? y : z; 222 | var o = { p: x ? y : z }; 223 | var a = [ x ? y : z ]; 224 | foo( x ? y : z ); 225 | function foo() { return x ? y : z; } 226 | foo = () => x ? y : z; 227 | function bar(p = x ? y : z) {} 228 | [ v = x ? y : z ] = arr; 229 | !(x ? y : z); 230 | 1 + (x ? y : z); 231 | (x ? y : z) + 1; 232 | var v = x ? y : z; 233 | v = x ? y : z; 234 | `; 235 | 236 | var results = eslinter.verify( code, linterOptions.whereOnlyStatement ); 237 | var [{ ruleId, messageId, message, } = {},] = results || []; 238 | 239 | assert.expect( 4 ); 240 | assert.strictEqual( results.length, 1, "only 1 error" ); 241 | assert.strictEqual( ruleId, "@getify/proper-ternary/where", "ruleId" ); 242 | assert.strictEqual( messageId, "notHere", "messageId" ); 243 | assert.ok( message.includes("standalone statement"), "message" ); 244 | } ); 245 | 246 | QUnit.test( "WHERE (property): violating", function test(assert){ 247 | var code = ` 248 | x ? y : z; 249 | var o = { p: x ? y : z }; 250 | var a = [ x ? y : z ]; 251 | foo( x ? y : z ); 252 | function foo() { return x ? y : z; } 253 | foo = () => x ? y : z; 254 | function bar(p = x ? y : z) {} 255 | [ v = x ? y : z ] = arr; 256 | !(x ? y : z); 257 | 1 + (x ? y : z); 258 | (x ? y : z) + 1; 259 | var v = x ? y : z; 260 | v = x ? y : z; 261 | `; 262 | 263 | var results = eslinter.verify( code, linterOptions.whereOnlyProperty ); 264 | var [ 265 | { ruleId: ruleId1, messageId: messageId1, message: message1, } = {}, 266 | { ruleId: ruleId2, messageId: messageId2, message: message2, } = {}, 267 | ] = results || []; 268 | 269 | assert.expect( 7 ); 270 | assert.strictEqual( results.length, 2, "only 2 errors" ); 271 | assert.strictEqual( ruleId1, "@getify/proper-ternary/where", "ruleId1" ); 272 | assert.strictEqual( messageId1, "notHere", "messageId1" ); 273 | assert.ok( message1.includes("object property"), "message1" ); 274 | assert.strictEqual( ruleId2, "@getify/proper-ternary/where", "ruleId2" ); 275 | assert.strictEqual( messageId2, "notHere", "messageId2" ); 276 | assert.ok( message2.includes("array element"), "message2" ); 277 | } ); 278 | 279 | QUnit.test( "WHERE (argument): violating", function test(assert){ 280 | var code = ` 281 | x ? y : z; 282 | var o = { p: x ? y : z }; 283 | var a = [ x ? y : z ]; 284 | foo( x ? y : z ); 285 | function foo() { return x ? y : z; } 286 | foo = () => x ? y : z; 287 | function bar(p = x ? y : z) {} 288 | [ v = x ? y : z ] = arr; 289 | !(x ? y : z); 290 | 1 + (x ? y : z); 291 | (x ? y : z) + 1; 292 | var v = x ? y : z; 293 | v = x ? y : z; 294 | `; 295 | 296 | var results = eslinter.verify( code, linterOptions.whereOnlyArgument ); 297 | var [{ ruleId, messageId, message, } = {},] = results || []; 298 | 299 | assert.expect( 4 ); 300 | assert.strictEqual( results.length, 1, "only 1 error" ); 301 | assert.strictEqual( ruleId, "@getify/proper-ternary/where", "ruleId" ); 302 | assert.strictEqual( messageId, "notHere", "messageId" ); 303 | assert.ok( message.includes("call argument"), "message" ); 304 | } ); 305 | 306 | QUnit.test( "WHERE (return): violating", function test(assert){ 307 | var code = ` 308 | x ? y : z; 309 | var o = { p: x ? y : z }; 310 | var a = [ x ? y : z ]; 311 | foo( x ? y : z ); 312 | function foo() { return x ? y : z; } 313 | foo = () => x ? y : z; 314 | function bar(p = x ? y : z) {} 315 | [ v = x ? y : z ] = arr; 316 | !(x ? y : z); 317 | 1 + (x ? y : z); 318 | (x ? y : z) + 1; 319 | var v = x ? y : z; 320 | v = x ? y : z; 321 | `; 322 | 323 | var results = eslinter.verify( code, linterOptions.whereOnlyReturn ); 324 | var [ 325 | { ruleId: ruleId1, messageId: messageId1, message: message1, } = {}, 326 | { ruleId: ruleId2, messageId: messageId2, message: message2, } = {}, 327 | ] = results || []; 328 | 329 | assert.expect( 7 ); 330 | assert.strictEqual( results.length, 2, "only 2 errors" ); 331 | assert.strictEqual( ruleId1, "@getify/proper-ternary/where", "ruleId1" ); 332 | assert.strictEqual( messageId1, "notHere", "messageId1" ); 333 | assert.ok( message1.includes("function return"), "message1" ); 334 | assert.strictEqual( ruleId2, "@getify/proper-ternary/where", "ruleId2" ); 335 | assert.strictEqual( messageId2, "notHere", "messageId2" ); 336 | assert.ok( message2.includes("function return"), "message2" ); 337 | } ); 338 | 339 | QUnit.test( "WHERE (default): violating", function test(assert){ 340 | var code = ` 341 | x ? y : z; 342 | var o = { p: x ? y : z }; 343 | var a = [ x ? y : z ]; 344 | foo( x ? y : z ); 345 | function foo() { return x ? y : z; } 346 | foo = () => x ? y : z; 347 | function bar(p = x ? y : z) {} 348 | [ v = x ? y : z ] = arr; 349 | !(x ? y : z); 350 | 1 + (x ? y : z); 351 | (x ? y : z) + 1; 352 | var v = x ? y : z; 353 | v = x ? y : z; 354 | `; 355 | 356 | var results = eslinter.verify( code, linterOptions.whereOnlyDefault ); 357 | var [ 358 | { ruleId: ruleId1, messageId: messageId1, message: message1, } = {}, 359 | { ruleId: ruleId2, messageId: messageId2, message: message2, } = {}, 360 | ] = results || []; 361 | 362 | assert.expect( 7 ); 363 | assert.strictEqual( results.length, 2, "only 2 errors" ); 364 | assert.strictEqual( ruleId1, "@getify/proper-ternary/where", "ruleId1" ); 365 | assert.strictEqual( messageId1, "notHere", "messageId1" ); 366 | assert.ok( message1.includes("default value"), "message1" ); 367 | assert.strictEqual( ruleId2, "@getify/proper-ternary/where", "ruleId2" ); 368 | assert.strictEqual( messageId2, "notHere", "messageId2" ); 369 | assert.ok( message2.includes("default value"), "message2" ); 370 | } ); 371 | 372 | QUnit.test( "WHERE (sub): violating", function test(assert){ 373 | var code = ` 374 | x ? y : z; 375 | var o = { p: x ? y : z }; 376 | var a = [ x ? y : z ]; 377 | foo( x ? y : z ); 378 | function foo() { return x ? y : z; } 379 | foo = () => x ? y : z; 380 | function bar(p = x ? y : z) {} 381 | [ v = x ? y : z ] = arr; 382 | !(x ? y : z); 383 | 1 + (x ? y : z); 384 | (x ? y : z) + 1; 385 | var v = x ? y : z; 386 | v = x ? y : z; 387 | `; 388 | 389 | var results = eslinter.verify( code, linterOptions.whereOnlySub ); 390 | var [ 391 | { ruleId: ruleId1, messageId: messageId1, message: message1, } = {}, 392 | { ruleId: ruleId2, messageId: messageId2, message: message2, } = {}, 393 | { ruleId: ruleId3, messageId: messageId3, message: message3, } = {}, 394 | ] = results || []; 395 | 396 | assert.expect( 10 ); 397 | assert.strictEqual( results.length, 3, "only 3 errors" ); 398 | assert.strictEqual( ruleId1, "@getify/proper-ternary/where", "ruleId1" ); 399 | assert.strictEqual( messageId1, "notHere", "messageId1" ); 400 | assert.ok( message1.includes("unary"), "message1" ); 401 | assert.strictEqual( ruleId2, "@getify/proper-ternary/where", "ruleId2" ); 402 | assert.strictEqual( messageId2, "notHere", "messageId2" ); 403 | assert.ok( message2.includes("binary"), "message2" ); 404 | assert.strictEqual( ruleId3, "@getify/proper-ternary/where", "ruleId3" ); 405 | assert.strictEqual( messageId3, "notHere", "messageId3" ); 406 | assert.ok( message3.includes("binary"), "message3" ); 407 | } ); 408 | 409 | QUnit.test( "WHERE (assignment): violating", function test(assert){ 410 | var code = ` 411 | x ? y : z; 412 | var o = { p: x ? y : z }; 413 | var a = [ x ? y : z ]; 414 | foo( x ? y : z ); 415 | function foo() { return x ? y : z; } 416 | foo = () => x ? y : z; 417 | function bar(p = x ? y : z) {} 418 | [ v = x ? y : z ] = arr; 419 | !(x ? y : z); 420 | 1 + (x ? y : z); 421 | (x ? y : z) + 1; 422 | var v = x ? y : z; 423 | v = x ? y : z; 424 | `; 425 | 426 | var results = eslinter.verify( code, linterOptions.whereOnlyAssignment ); 427 | var [ 428 | { ruleId: ruleId1, messageId: messageId1, message: message1, } = {}, 429 | { ruleId: ruleId2, messageId: messageId2, message: message2, } = {}, 430 | ] = results || []; 431 | 432 | assert.expect( 7 ); 433 | assert.strictEqual( results.length, 2, "only 2 errors" ); 434 | assert.strictEqual( ruleId1, "@getify/proper-ternary/where", "ruleId1" ); 435 | assert.strictEqual( messageId1, "notHere", "messageId1" ); 436 | assert.ok( message1.includes("assignment"), "message1" ); 437 | assert.strictEqual( ruleId2, "@getify/proper-ternary/where", "ruleId2" ); 438 | assert.strictEqual( messageId2, "notHere", "messageId2" ); 439 | assert.ok( message2.includes("assignment"), "message2" ); 440 | } ); 441 | --------------------------------------------------------------------------------