Skip to content

Commit

Permalink
feat: new /// to-one-line command (#35)
Browse files Browse the repository at this point in the history
* docs: fix typo

* feat: added to-one-line command
  • Loading branch information
onmax authored Feb 15, 2025
1 parent 4903d91 commit e449cbf
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ async function foo<T>(msg: T): void {
}
```

One more example that `/// to-promis-all` converts a sequence of `await` expressions to `await Promise.all()`:
One more example that `/// to-promise-all` converts a sequence of `await` expressions to `await Promise.all()`:

<!-- eslint-skip -->

Expand Down
25 changes: 25 additions & 0 deletions src/commands/to-one-line.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# `to-one-line`

Convert multiple lines object to one line object.

## Triggers

- `/// to-one-line`
- `/// tol`
- `/// 21l`

## Examples

```js
/// to-one-line
const foo = {
bar: 1,
baz: 2
}
```

Will be converted to:

```js
const foo = { bar: 1, baz: 2 }
```
146 changes: 146 additions & 0 deletions src/commands/to-one-line.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { $, run } from './_test-utils'
import { toOneLine as command } from './to-one-line'

run(
command,
{
code: $`
/// to-one-line
const foo = {
bar: 1,
baz: 2,
}
`,
output: $`
const foo = { bar: 1, baz: 2 }
`,
errors: ['command-fix'],
},
{
code: $`
/// to-one-line
const arr = [
1,
2,
3,
4,
]
`,
output: $`
const arr = [1, 2, 3, 4]
`,
errors: ['command-fix'],
},
{
code: $`
/// tol
obj = {
x: 100,
y: 200,
}
`,
output: $`
obj = { x: 100, y: 200 }
`,
errors: ['command-fix'],
},
{
code: $`
/// to-one-line
const data = {
user: {
name: 'Alice',
age: 30,
},
scores: [
10,
20,
30,
],
}
`,
output: $`
const data = { user: { name: 'Alice', age: 30 }, scores: [10, 20, 30] }
`,
errors: ['command-fix'],
},
{
code: $`
/// 21l
const alreadyOneLine = { a: 1, b: 2 }
`,
output: $`
const alreadyOneLine = { a: 1, b: 2 }
`,
errors: ['command-fix'],
},
{
code: $`
/// to-one-line
const fruits = [
"apple",
"banana",
"cherry",
]
`,
output: $`
const fruits = ["apple", "banana", "cherry"]
`,
errors: ['command-fix'],
},
{
code: $`
/// to-one-line
whichFruitIsTheBest([
"apple",
"banana",
"cherry",
])
`,
output: $`
whichFruitIsTheBest(["apple", "banana", "cherry"])
`,
errors: ['command-fix'],
},
{
code: $`
/// to-one-line
function whichFruitIsTheBest({
apple,
banana,
cherry,
}) {}
`,
output: $`
function whichFruitIsTheBest({ apple, banana, cherry }) {}
`,
errors: ['command-fix'],
},
{
code: $`
/// to-one-line
function f([
a,
b,
c,
]) {}
`,
output: $`
function f([a, b, c]) {}
`,
errors: ['command-fix'],
},
{
code: $`
/// to-one-line
return {
foo: 1,
bar: 2,
}
`,
output: $`
return { foo: 1, bar: 2 }
`,
errors: ['command-fix'],
},
)
87 changes: 87 additions & 0 deletions src/commands/to-one-line.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import type { Command, NodeType, Tree } from '../types'

export const toOneLine: Command = {
name: 'to-one-line',
match: /^[/@:]\s*(?:to-one-line|21l|tol)$/,
action(ctx) {
const node = ctx.findNodeBelow(
'VariableDeclaration',
'AssignmentExpression',
'CallExpression',
'FunctionDeclaration',
'FunctionExpression',
'ReturnStatement',
)
if (!node)
return ctx.reportError('Unable to find node to convert')

let target: Tree.Node | null = null

// For a variable declaration we use the initializer.
if (node.type === 'VariableDeclaration') {
const decl = node.declarations[0]
if (decl && decl.init && isAllowedType(decl.init.type))
target = decl.init
}
// For an assignment we use the right side.
else if (node.type === 'AssignmentExpression') {
if (node.right && isAllowedType(node.right.type))
target = node.right
}
// In a call we search the arguments.
else if (node.type === 'CallExpression') {
target = node.arguments.find(arg => isAllowedType(arg.type)) || null
}
// In a function we search the parameters.
else if (
node.type === 'FunctionDeclaration'
|| node.type === 'FunctionExpression'
) {
target = node.params.find(param => isAllowedType(param.type)) || null
}
// For a return statement we use its argument.
else if (node.type === 'ReturnStatement') {
if (node.argument && isAllowedType(node.argument.type))
target = node.argument
}

if (!target)
return ctx.reportError('Unable to find object/array literal or pattern to convert')

// Get the text of the node to reformat it.
const original = ctx.getTextOf(target)
// Replace line breaks with spaces and remove extra spaces.
let oneLine = original.replace(/\n/g, ' ').replace(/\s{2,}/g, ' ').trim()
// Remove a comma that comes before a closing bracket or brace.
oneLine = oneLine.replace(/,\s*([}\]])/g, '$1')

if (target.type === 'ArrayExpression' || target.type === 'ArrayPattern') {
// For arrays, add a missing space before a closing bracket.
oneLine = oneLine.replace(/\[\s+/g, '[').replace(/\s+\]/g, ']')
}
else {
// For objects, add a missing space before a closing bracket or brace.
oneLine = oneLine.replace(/([^ \t])([}\]])/g, '$1 $2')
// Add a space between a ']' and a '}' if they touch.
oneLine = oneLine.replace(/\](\})/g, '] $1')
}

// Fix any nested array formatting.
oneLine = oneLine.replace(/\[\s+/g, '[').replace(/\s+\]/g, ']')

ctx.report({
node: target,
message: 'Convert object/array to one line',
fix: fixer => fixer.replaceTextRange(target.range, oneLine),
})

function isAllowedType(type: NodeType): boolean {
return (
type === 'ObjectExpression'
|| type === 'ArrayExpression'
|| type === 'ObjectPattern'
|| type === 'ArrayPattern'
)
}
},
}

0 comments on commit e449cbf

Please sign in to comment.