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
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'
)
}
},
}