Skip to content

Commit

Permalink
feat(to-ternary): new command (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
9romise authored May 18, 2024
1 parent d6032a9 commit c443ea1
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 0 deletions.
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),
})
},
}

0 comments on commit c443ea1

Please sign in to comment.