-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
97 lines (77 loc) · 3.18 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
export type TyplateArgs<Fn> = Fn extends (a: infer A) => string ? A : never;
const genInfer = "type TyplateArgs<Fn> = Fn extends (a: infer A) => string ? A : never;\n";
// what the fuck
const RUN_REGEX = /(^[^\S\r\n]*)?<%((?:[^%]|%(?!>))+)%>([^\S\r\n]*\n)?/gm;
export function toTypescript(path: string, contents: string) {
let start = 0;
const head = [
`export const filepath = ${JSON.stringify(path)};`
];
const segments: string[] = [];
for(const match of contents.matchAll(RUN_REGEX)) {
const [fullString, leadingWhitespace, captureGroup, trailingWhitespace] = match;
if(match.index == null) throw "Unknown match index";
// Since we match on the newline, and the start index may have progressed past the newline, we
// need this check. If it progressed past the newline, this is all templating whitespace
const encodePlain = start <= match.index;
if(encodePlain) {
const plainText = contents.substring(start, match.index);
segments.push(encodePlainText(plainText));
}
const isStartOfLine = leadingWhitespace != null
|| match.index === 0
|| contents[match.index - 1] === "\n";
const isEndOfLine = trailingWhitespace != null
|| match.index + fullString.length === contents.length;
const isEntireLine = isStartOfLine && isEndOfLine;
function addWhitespaceIfNecessary() {
if(!isEntireLine) {
if(leadingWhitespace) segments.push(encodePlainText(leadingWhitespace));
if(trailingWhitespace) segments.push(encodePlainText(trailingWhitespace));
}
}
// Handle the various template tag types
// First, head tags
if(captureGroup.startsWith("--")) {
addWhitespaceIfNecessary();
head.push(encodeRun(captureGroup.substring(2)));
}
// Print expression tags
else if(captureGroup[0] === "=") {
if(leadingWhitespace) segments.push(encodePlainText(leadingWhitespace));
segments.push(printExpr(captureGroup.substring(1)));
if(trailingWhitespace) segments.push(encodePlainText(trailingWhitespace));
}
// Arbitrary TS tags
else {
addWhitespaceIfNecessary();
segments.push(encodeRun(captureGroup));
}
// Update the indexes. If the templating is the start of the line, and there's an immediate
// trailing newline, advance the start index to skip the trailing newline.
start = match.index + fullString.length;
}
// Clean up any remaining strings
if(start < contents.length - 1) {
segments.push(encodePlainText(contents.substring(start)));
}
// Add the infer type, concat the head TS, and then wrap everything else in the render function
return genInfer + head.join("\n") + "\n" + wrapFn(segments);
}
const printArray = "__typelate_print_segments";
function wrapFn(segments: string[]) {
return `export default function render(args: Args) {
const ${printArray}: string[] = [];
${segments.join("\n")}
return ${printArray}.join("");
}`;
}
function encodePlainText(text: string) {
return `${printArray}.push(${JSON.stringify(text)});`;
}
function encodeRun(text: string) {
return text;
}
function printExpr(text: string) {
return `${printArray}.push(${text});`;
}