Skip to content

Commit e449cbf

Browse files
authored
feat: new /// to-one-line command (#35)
* docs: fix typo * feat: added to-one-line command
1 parent 4903d91 commit e449cbf

File tree

4 files changed

+259
-1
lines changed

4 files changed

+259
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ async function foo<T>(msg: T): void {
3232
}
3333
```
3434

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

3737
<!-- eslint-skip -->
3838

src/commands/to-one-line.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# `to-one-line`
2+
3+
Convert multiple lines object to one line object.
4+
5+
## Triggers
6+
7+
- `/// to-one-line`
8+
- `/// tol`
9+
- `/// 21l`
10+
11+
## Examples
12+
13+
```js
14+
/// to-one-line
15+
const foo = {
16+
bar: 1,
17+
baz: 2
18+
}
19+
```
20+
21+
Will be converted to:
22+
23+
```js
24+
const foo = { bar: 1, baz: 2 }
25+
```

src/commands/to-one-line.test.ts

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { $, run } from './_test-utils'
2+
import { toOneLine as command } from './to-one-line'
3+
4+
run(
5+
command,
6+
{
7+
code: $`
8+
/// to-one-line
9+
const foo = {
10+
bar: 1,
11+
baz: 2,
12+
}
13+
`,
14+
output: $`
15+
const foo = { bar: 1, baz: 2 }
16+
`,
17+
errors: ['command-fix'],
18+
},
19+
{
20+
code: $`
21+
/// to-one-line
22+
const arr = [
23+
1,
24+
2,
25+
3,
26+
4,
27+
]
28+
`,
29+
output: $`
30+
const arr = [1, 2, 3, 4]
31+
`,
32+
errors: ['command-fix'],
33+
},
34+
{
35+
code: $`
36+
/// tol
37+
obj = {
38+
x: 100,
39+
y: 200,
40+
}
41+
`,
42+
output: $`
43+
obj = { x: 100, y: 200 }
44+
`,
45+
errors: ['command-fix'],
46+
},
47+
{
48+
code: $`
49+
/// to-one-line
50+
const data = {
51+
user: {
52+
name: 'Alice',
53+
age: 30,
54+
},
55+
scores: [
56+
10,
57+
20,
58+
30,
59+
],
60+
}
61+
`,
62+
output: $`
63+
const data = { user: { name: 'Alice', age: 30 }, scores: [10, 20, 30] }
64+
`,
65+
errors: ['command-fix'],
66+
},
67+
{
68+
code: $`
69+
/// 21l
70+
const alreadyOneLine = { a: 1, b: 2 }
71+
`,
72+
output: $`
73+
const alreadyOneLine = { a: 1, b: 2 }
74+
`,
75+
errors: ['command-fix'],
76+
},
77+
{
78+
code: $`
79+
/// to-one-line
80+
const fruits = [
81+
"apple",
82+
"banana",
83+
"cherry",
84+
]
85+
`,
86+
output: $`
87+
const fruits = ["apple", "banana", "cherry"]
88+
`,
89+
errors: ['command-fix'],
90+
},
91+
{
92+
code: $`
93+
/// to-one-line
94+
whichFruitIsTheBest([
95+
"apple",
96+
"banana",
97+
"cherry",
98+
])
99+
`,
100+
output: $`
101+
whichFruitIsTheBest(["apple", "banana", "cherry"])
102+
`,
103+
errors: ['command-fix'],
104+
},
105+
{
106+
code: $`
107+
/// to-one-line
108+
function whichFruitIsTheBest({
109+
apple,
110+
banana,
111+
cherry,
112+
}) {}
113+
`,
114+
output: $`
115+
function whichFruitIsTheBest({ apple, banana, cherry }) {}
116+
`,
117+
errors: ['command-fix'],
118+
},
119+
{
120+
code: $`
121+
/// to-one-line
122+
function f([
123+
a,
124+
b,
125+
c,
126+
]) {}
127+
`,
128+
output: $`
129+
function f([a, b, c]) {}
130+
`,
131+
errors: ['command-fix'],
132+
},
133+
{
134+
code: $`
135+
/// to-one-line
136+
return {
137+
foo: 1,
138+
bar: 2,
139+
}
140+
`,
141+
output: $`
142+
return { foo: 1, bar: 2 }
143+
`,
144+
errors: ['command-fix'],
145+
},
146+
)

src/commands/to-one-line.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import type { Command, NodeType, Tree } from '../types'
2+
3+
export const toOneLine: Command = {
4+
name: 'to-one-line',
5+
match: /^[/@:]\s*(?:to-one-line|21l|tol)$/,
6+
action(ctx) {
7+
const node = ctx.findNodeBelow(
8+
'VariableDeclaration',
9+
'AssignmentExpression',
10+
'CallExpression',
11+
'FunctionDeclaration',
12+
'FunctionExpression',
13+
'ReturnStatement',
14+
)
15+
if (!node)
16+
return ctx.reportError('Unable to find node to convert')
17+
18+
let target: Tree.Node | null = null
19+
20+
// For a variable declaration we use the initializer.
21+
if (node.type === 'VariableDeclaration') {
22+
const decl = node.declarations[0]
23+
if (decl && decl.init && isAllowedType(decl.init.type))
24+
target = decl.init
25+
}
26+
// For an assignment we use the right side.
27+
else if (node.type === 'AssignmentExpression') {
28+
if (node.right && isAllowedType(node.right.type))
29+
target = node.right
30+
}
31+
// In a call we search the arguments.
32+
else if (node.type === 'CallExpression') {
33+
target = node.arguments.find(arg => isAllowedType(arg.type)) || null
34+
}
35+
// In a function we search the parameters.
36+
else if (
37+
node.type === 'FunctionDeclaration'
38+
|| node.type === 'FunctionExpression'
39+
) {
40+
target = node.params.find(param => isAllowedType(param.type)) || null
41+
}
42+
// For a return statement we use its argument.
43+
else if (node.type === 'ReturnStatement') {
44+
if (node.argument && isAllowedType(node.argument.type))
45+
target = node.argument
46+
}
47+
48+
if (!target)
49+
return ctx.reportError('Unable to find object/array literal or pattern to convert')
50+
51+
// Get the text of the node to reformat it.
52+
const original = ctx.getTextOf(target)
53+
// Replace line breaks with spaces and remove extra spaces.
54+
let oneLine = original.replace(/\n/g, ' ').replace(/\s{2,}/g, ' ').trim()
55+
// Remove a comma that comes before a closing bracket or brace.
56+
oneLine = oneLine.replace(/,\s*([}\]])/g, '$1')
57+
58+
if (target.type === 'ArrayExpression' || target.type === 'ArrayPattern') {
59+
// For arrays, add a missing space before a closing bracket.
60+
oneLine = oneLine.replace(/\[\s+/g, '[').replace(/\s+\]/g, ']')
61+
}
62+
else {
63+
// For objects, add a missing space before a closing bracket or brace.
64+
oneLine = oneLine.replace(/([^ \t])([}\]])/g, '$1 $2')
65+
// Add a space between a ']' and a '}' if they touch.
66+
oneLine = oneLine.replace(/\](\})/g, '] $1')
67+
}
68+
69+
// Fix any nested array formatting.
70+
oneLine = oneLine.replace(/\[\s+/g, '[').replace(/\s+\]/g, ']')
71+
72+
ctx.report({
73+
node: target,
74+
message: 'Convert object/array to one line',
75+
fix: fixer => fixer.replaceTextRange(target.range, oneLine),
76+
})
77+
78+
function isAllowedType(type: NodeType): boolean {
79+
return (
80+
type === 'ObjectExpression'
81+
|| type === 'ArrayExpression'
82+
|| type === 'ObjectPattern'
83+
|| type === 'ArrayPattern'
84+
)
85+
}
86+
},
87+
}

0 commit comments

Comments
 (0)