Skip to content

Commit

Permalink
feat: new command keep-aligned (#32)
Browse files Browse the repository at this point in the history
  • Loading branch information
zojize authored Jan 9, 2025
1 parent 419052b commit 45a5e04
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { hoistRegExp } from './hoist-regexp'
import { inlineArrow } from './inline-arrow'
import { keepAligned } from './keep-aligned'
import { keepSorted } from './keep-sorted'
import { keepUnique } from './keep-unique'
import { noShorthand } from './no-shorthand'
Expand All @@ -21,6 +22,7 @@ import { toTernary } from './to-ternary'
export {
hoistRegExp,
inlineArrow,
keepAligned,
keepSorted,
keepUnique,
noShorthand,
Expand All @@ -43,6 +45,7 @@ export {
export const builtinCommands = [
hoistRegExp,
inlineArrow,
keepAligned,
keepSorted,
keepUnique,
noShorthand,
Expand Down
65 changes: 65 additions & 0 deletions src/commands/keep-aligned.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# `keep-aligned`

Keep specific symbols within a block of code are aligned vertically.

## Triggers

- `/// keep-aligned <symbols>`
- `// @keep-aligned <symbols>`

## Examples

<!-- eslint-skip -->

```typescript
// @keep-aligned , , ,
export const matrix = [
1, 0, 0,
0.866, -0.5, 0,
0.5, 0.866, 42,
]
```

Will be converted to:

<!-- eslint-skip -->

```typescript
// @keep-aligned , , ,
export const matrix = [
1 , 0 , 0 ,
0.866, -0.5 , 0 ,
0.5 , 0.866, 42,
]
```

### Repeat Mode

For the example above where `,` is the only repeating symbol for alignment, `keep-aligned*` could be used instead to indicate a repeating pattern:

<!-- eslint-skip -->

```typescript
// @keep-aligned* ,
export const matrix = [
1, 0, 0,
0.866, -0.5, 0,
0.5, 0.866, 42,
]
```

Will produce the same result.

> [!TIP]
> This rule does not work well with other spacing rules, namely `style/no-multi-spaces, style/comma-spacing, antfu/consistent-list-newline` were disabled for the example above to work. Consider adding `/* eslint-disable */` to specific ESLint rules for lines affected by this command.
>
> ```typescript
> /* eslint-disable style/no-multi-spaces, style/comma-spacing, antfu/consistent-list-newline */
> // @keep-aligned , , ,
> export const matrix = [
> 1 , 0 , 0 ,
> 0.866, -0.5 , 0 ,
> 0.5 , 0.866, 42,
> ]
> /* eslint-enable style/no-multi-spaces, style/comma-spacing, antfu/consistent-list-newline */
> ```
86 changes: 86 additions & 0 deletions src/commands/keep-aligned.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { $, run } from './_test-utils'
import { keepAligned } from './keep-aligned'

run(
keepAligned,
{
code: $`
// @keep-aligned , , ,
export const matrix = [
1, 0, 0,
0.866, -0.5, 0,
0.5, 0.866, 42,
]
`,
output(output) {
expect(output).toMatchInlineSnapshot(`
"// @keep-aligned , , ,
export const matrix = [
1 , 0 , 0 ,
0.866, -0.5 , 0 ,
0.5 , 0.866, 42,
]"
`)
},
},
{
code: $`
// @keep-aligned , , ]
export const matrix = [
[1, 0, 0],
[0.866, -0.5, 0],
[0.5, 0.866, 42],
]
`,
output(output) {
expect(output).toMatchInlineSnapshot(`
"// @keep-aligned , , ]
export const matrix = [
[1 , 0 , 0 ],
[0.866, -0.5 , 0 ],
[0.5 , 0.866, 42],
] "
`)
},
},
{
code: $`
/// keep-aligned* ,
export const matrix = [
1, 0, 0, 0.866, 0.5, 0.866, 42,
0.866, -0.5, 0, 0.5, 0.121212,
0.5, 0.866, 118, 1, 0, 0, 0.866, -0.5, 12,
]
`,
output(output) {
expect(output).toMatchInlineSnapshot(`
"/// keep-aligned* ,
export const matrix = [
1 , 0 , 0 , 0.866, 0.5 , 0.866, 42 ,
0.866, -0.5 , 0 , 0.5 , 0.121212,
0.5 , 0.866, 118, 1 , 0 , 0 , 0.866, -0.5, 12,
] "
`)
},
},
{
code: $`
function foo(arr: number[][], i: number, j: number) {
// @keep-aligned arr[ ] arr[ ] ][j
return arr[i - 1][j - 1] + arr[i - 1][j] + arr[i - 1][j + 1]
+ arr[i][j - 1] + arr[i][j] + arr[i][j + 1]
+ arr[i + 1][j - 1] + arr[i + 1][j] + arr[i][j + 1]
}
`,
output(output) {
expect(output).toMatchInlineSnapshot(`
"function foo(arr: number[][], i: number, j: number) {
// @keep-aligned arr[ ] arr[ ] ][j
return arr[i - 1][j - 1] + arr[i - 1][j] + arr[i - 1][j + 1]
+ arr[i ][j - 1] + arr[i ][j] + arr[i ][j + 1]
+ arr[i + 1][j - 1] + arr[i + 1][j] + arr[i ][j + 1]
}"
`)
},
},
)
68 changes: 68 additions & 0 deletions src/commands/keep-aligned.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { Command } from '../types'

const reLine = /^[/@:]\s*keep-aligned(?<repeat>\*?)(?<symbols>(\s+\S+)+)$/

export const keepAligned: Command = {
name: 'keep-aligned',
commentType: 'line',
match: comment => comment.value.trim().match(reLine),
action(ctx) {
// this command applies to any node below
const node = ctx.findNodeBelow(() => true)
if (!node)
return

const alignmentSymbols = ctx.matches.groups?.symbols?.trim().split(/\s+/)
if (!alignmentSymbols)
return ctx.reportError('No alignment symbols provided')
const repeat = ctx.matches.groups?.repeat

const nLeadingSpaces = node.range[0] - ctx.comment.range[1] - 1
const text = ctx.context.sourceCode.getText(node, nLeadingSpaces)
const lines = text.split('\n')
const symbolIndices: number[] = []

const nSymbols = alignmentSymbols.length
if (nSymbols === 0)
return ctx.reportError('No alignment symbols provided')

const n = repeat ? Number.MAX_SAFE_INTEGER : nSymbols
let lastPos = 0
for (let i = 0; i < n && i < 20; i++) {
const symbol = alignmentSymbols[i % nSymbols]
const maxIndex = lines.reduce((maxIndex, line) =>
Math.max(line.indexOf(symbol, lastPos), maxIndex), -1)
symbolIndices.push(maxIndex)

if (maxIndex < 0) {
if (!repeat)
return ctx.reportError(`Alignment symbol "${symbol}" not found`)
else
break
}

for (let j = 0; j < lines.length; j++) {
const line = lines[j]
const index = line.indexOf(symbol, lastPos)
if (index < 0)
continue
if (index !== maxIndex) {
const padding = maxIndex - index
lines[j] = line.slice(0, index) + ' '.repeat(padding) + line.slice(index)
}
}
lastPos = maxIndex + symbol.length
}

const modifiedText = lines.join('\n')
if (text === modifiedText)
return

ctx.report({
node,
message: 'Keep aligned',
removeComment: false,
fix: fixer => fixer.replaceText(node, modifiedText.slice(nLeadingSpaces)),
})
},
}

0 comments on commit 45a5e04

Please sign in to comment.