Last active
July 14, 2024 14:01
-
-
Save hyrious/a2d3b22009a6d5a3b09e368254f139f9 to your computer and use it in GitHub Desktop.
Write you a code formatter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {parser} from '@lezer/javascript' | |
export function format(input: string) { | |
let tree = parser.parse(input) | |
let spaceAfter = (s: string) => s + ' ' | |
let spaceBefore = (s: string) => ' ' + s | |
let spaceAround = (s: string) => ' ' + s + ' ' | |
let spec = { | |
[',']: spaceAfter, | |
[':']: spaceAfter, | |
[';']: spaceAfter, | |
['{']: spaceBefore, | |
['('](s: string, scope: string[]) { | |
if (scope.includes('ForSpec')) return spaceBefore(s); | |
return s | |
}, | |
['}'](s: string, scope: string[], node: { from: number }) { | |
if (scope.includes('ImportGroup')) return spaceAfter(s); | |
if (scope.includes('ObjectExpression') && input[node.from - 1] != '{') return spaceBefore(s); | |
return s | |
}, | |
new: spaceAfter, | |
const: spaceAfter, | |
let: spaceAfter, | |
import: spaceAfter, | |
from: spaceAround, | |
as: spaceAround, | |
Star: spaceAround, | |
Equals: spaceAround, | |
LineComment(s) { return spaceBefore(s.replace(/^\/\/\s*/, '// ')) }, | |
ArithOp(s: string, scope: string[]) { | |
if (scope.includes('UnaryExpression') || scope.includes('PostfixExpression')) return s; | |
return spaceAround(s) | |
}, | |
CompareOp: spaceAround, | |
Arrow: spaceAround, | |
PropertyDefinition: spaceBefore, | |
} | |
let out = '' | |
let enter = false, last = { from: 0, to: 0 } | |
let set = new Set<string>() | |
let scope: string[] = [] | |
function count_newline(s: string) { | |
let count = 0, index = -1 | |
do { | |
index = s.indexOf('\n', index + 1) | |
if (index >= 0) count++; | |
} while (index >= 0) | |
return count | |
} | |
function get_indent(at: number) { | |
let i = at - 1 | |
while (i >= 0 && input[i] != '\n') { | |
if (input[i] != ' ') return ''; | |
i--; | |
} | |
return ' '.repeat(at - i - 1) | |
} | |
// Ensure there's atmost 1 space between a and b. | |
function append(a: string, b: string) { | |
let end = a.at(-1) | |
if ((end == ' ' || end == '\n') && b[0] == ' ') { | |
return a + b.trimStart() | |
} | |
return a + b | |
} | |
tree.iterate({ | |
enter(node) { | |
enter = true | |
scope.push(node.name) | |
}, | |
leave(node) { | |
if (enter) { | |
let newline = count_newline(input.slice(last.to, node.from)) | |
if (newline) { | |
out = out.trimEnd() | |
out += newline > 1 ? '\n\n' : '\n' | |
} | |
out += get_indent(node.from) | |
let f = spec[node.name], s = input.slice(node.from, node.to) | |
f || set.add(node.name) | |
out = append(out, (f && f.call(spec, s, scope, node)) ?? s) | |
last = { from: node.from, to: node.to } | |
} | |
enter = false | |
scope.pop() | |
} | |
}) | |
return { out, set } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://github.com/hyrious/lezer-fmt