Skip to content

Instantly share code, notes, and snippets.

@hyrious
Last active June 19, 2024 07:33
Show Gist options
  • Save hyrious/a2d3b22009a6d5a3b09e368254f139f9 to your computer and use it in GitHub Desktop.
Save hyrious/a2d3b22009a6d5a3b09e368254f139f9 to your computer and use it in GitHub Desktop.
Write you a code formatter
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