-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathpreprocess.ts
More file actions
135 lines (113 loc) · 3.33 KB
/
preprocess.ts
File metadata and controls
135 lines (113 loc) · 3.33 KB
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import { Preprocessor } from 'content-tag';
export interface Template {
contents: string;
type: string;
range: {
start: number;
end: number;
};
utf16Range: {
start: number;
end: number;
};
}
const BufferMap: Map<string, Buffer> = new Map();
const PLACEHOLDER = '~';
function getBuffer(s: string): Buffer {
let buf = BufferMap.get(s);
if (!buf) {
buf = Buffer.from(s);
BufferMap.set(s, buf);
}
return buf;
}
/** Slice string using byte range */
function sliceByteRange(s: string, a: number, b?: number): string {
const buf = getBuffer(s);
return buf.subarray(a, b).toString();
}
/** Converts byte index to js char index (utf16) */
function byteToCharIndex(s: string, byteOffset: number): number {
const buf = getBuffer(s);
return buf.subarray(0, byteOffset).toString().length;
}
/** Calculate byte length */
function byteLength(s: string): number {
return getBuffer(s).length;
}
function replaceRange(
s: string,
start: number,
end: number,
substitute: string,
): string {
return sliceByteRange(s, 0, start) + substitute + sliceByteRange(s, end);
}
/**
* Replace the template with a parsable placeholder that takes up the same
* range.
*/
export function preprocessTemplateRange(
template: Template,
code: string,
): string {
let prefix: string;
let suffix: string;
if (template.type === 'class-member') {
// Replace with StaticBlock
prefix = 'static{/*';
suffix = '*/}';
} else {
// Replace with BlockStatement or ObjectExpression
prefix = '{/*';
suffix = '*/}';
const nextToken = code.slice(template.range.end).toString().match(/\S+/);
if (nextToken && (nextToken[0] === 'as' || nextToken[0] === 'satisfies')) {
// Replace with parenthesized ObjectExpression
prefix = '(' + prefix;
suffix = suffix + ')';
}
}
// We need to replace forward slash with _something else_, because
// forward slash breaks the parsed templates.
const content = template.contents.replaceAll('/', PLACEHOLDER);
const tplLength = template.range.end - template.range.start;
const spaces =
tplLength - byteLength(content) - prefix.length - suffix.length;
const total = prefix + content + ' '.repeat(spaces) + suffix;
return replaceRange(code, template.range.start, template.range.end, total);
}
const p = new Preprocessor();
/** Pre-processes the template info, parsing the template content to Glimmer AST. */
export function codeToGlimmerAst(code: string, filename: string): Template[] {
const rawTemplates = p.parse(code, { filename });
const templates: Template[] = rawTemplates.map((r) => ({
type: r.type,
range: r.range,
contentRange: r.contentRange,
contents: r.contents,
utf16Range: {
start: byteToCharIndex(code, r.range.start),
end: byteToCharIndex(code, r.range.end),
},
}));
return templates;
}
/**
* Pre-processes the template info, parsing the template content to Glimmer AST,
* fixing the offsets and locations of all nodes also calculates the block
* params locations & ranges and adding it to the info
*/
export function preprocess(
code: string,
fileName: string,
): {
code: string;
templates: Template[];
} {
const templates = codeToGlimmerAst(code, fileName);
for (const template of templates) {
code = preprocessTemplateRange(template, code);
}
return { templates, code };
}