Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { noShorthand } from './no-shorthand'
import { noType } from './no-type'
import { keepUnique } from './keep-unique'
import { regex101 } from './regex101'
import { toTernary } from './to-ternary'

// @keep-sorted
export {
Expand All @@ -31,6 +32,7 @@ export {
toPromiseAll,
toStringLiteral,
toTemplateLiteral,
toTernary,
}

// @keep-sorted
Expand All @@ -50,4 +52,5 @@ export const builtinCommands = [
toPromiseAll,
toStringLiteral,
toTemplateLiteral,
toTernary,
]
37 changes: 37 additions & 0 deletions src/commands/to-ternary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# `to-ternary`

Convert an `if-else` statement to a [`ternary expression`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_operator).

## Triggers

- `/// to-ternary`
- `/// to-3`
- `/// 2ternary`
- `/// 23`

## Examples

```js
/// to-ternary
if (condition)
foo()
else
bar = 1

// For conditional assignments to the same variable
/// to-ternary
if (condition1)
foo = 1
else if (condition2)
foo = bar
else
foo = baz()
```

Will be converted to (the command comment will be removed along the way):

```js
condition ? foo() : bar = 1

foo = condition1 ? 1 : condition2 ? bar : baz()
```
96 changes: 96 additions & 0 deletions src/commands/to-ternary.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { toTernary as command } from './to-ternary'
import { $, run } from './_test-utils'

run(
command,
// no `else`
{
code: $`
/// to-ternary
if (c1)
foo()
else if (c2)
bar = 1
`,
errors: ['command-error'],
},
// too many lines in a `if`
{
code: $`
/// 2ternary
if (c1) {
foo()
bar = 1
}
else {
bar = 2
}
`,
errors: ['command-error'],
},
// normal
{
code: $`
/// to-3
if (c1)
foo()
else
bar = 1
`,
output: $`
c1 ? foo() : bar = 1
`,
errors: ['command-fix'],
},
// more `else-if` and block
{
code: $`
/// 23
if (a > b) {
foo()
}
else if (c2) {
bar = 1
}
else {
baz()
}
`,
output: $`
a > b ? foo() : c2 ? bar = 1 : baz()
`,
errors: ['command-fix'],
},
// same name assignment
{
code: $`
/// to-ternary
if (c1)
foo = 1
else if (c2)
foo = bar
else
foo = baz()
`,
output: $`
foo = c1 ? 1 : c2 ? bar : baz()
`,
errors: ['command-fix'],
},
// different names assignment
{
code: $`
/// to-ternary
if (c1)
foo = 1
else if (c2)
bar = 2
else
baz()
`,
output: $`
c1 ? foo = 1 : c2 ? bar = 2 : baz()
`,
errors: ['command-fix'],
},
)
74 changes: 74 additions & 0 deletions src/commands/to-ternary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import type { Command, Tree } from '../types'

export const toTernary: Command = {
name: 'to-ternary',
match: /^\s*[/:@]\s*(?:to-|2)(?:ternary|3)$/,
action(ctx) {
const node = ctx.findNodeBelow('IfStatement')

if (!node)
return ctx.reportError('Unable to find an `if` statement to convert')

let result = ''
let isAssignment = true

const normalizeStatement = (n: Tree.Statement | null) => {
if (!n)
return ctx.reportError('Unable to convert `if` statement without an `else` clause')
if (n.type === 'BlockStatement') {
if (n.body.length !== 1)
return ctx.reportError('Unable to convert statement contains more than one expression')
else return n.body[0]
}
else {
return n
}
}

const getAssignmentId = (n: Tree.Statement) => {
if (n.type === 'IfStatement')
n = n.consequent
if (n.type !== 'ExpressionStatement' || n.expression.type !== 'AssignmentExpression' || n.expression.left.type !== 'Identifier')
return
return ctx.getTextOf(n.expression.left)
}

let ifNode: Tree.IfStatement = node
while (ifNode) {
const consequent = normalizeStatement(ifNode.consequent)
const alternate = normalizeStatement(ifNode.alternate)

if (!consequent || !alternate)
return

if (isAssignment) {
const ifId = getAssignmentId(consequent)
const elseId = getAssignmentId(alternate)

if (!ifId || ifId !== elseId)
isAssignment = false
}

result += `${ctx.getTextOf(ifNode.test)} ? ${ctx.getTextOf(consequent)} : `

if (alternate.type !== 'IfStatement') {
result += ctx.getTextOf(alternate)
break
}
else {
ifNode = alternate
}
}

if (isAssignment) {
const id = getAssignmentId(normalizeStatement(node.consequent)!)
result = `${id} = ${result.replaceAll(`${id} = `, '')}`
}

ctx.report({
node,
message: 'Convert to ternary',
fix: fix => fix.replaceTextRange(node.range, result),
})
},
}