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
98const DEFAULT_INDENTATION_CHARACTER = '';
const DEFAULT_LINE_JOIN = '\n';
export type PostProcessor = (unreplacedCode: string) => string;
export interface CodeBuilderOptions {
/**
* Desired indentation character for aggregated lines of code
* @default ''
*/
indent?: string;
/**
* Desired character to join each line of code
* @default \n
*/
join?: string;
}
export class CodeBuilder {
postProcessors: PostProcessor[] = [];
code: string[] = [];
indentationCharacter: string = DEFAULT_INDENTATION_CHARACTER;
lineJoin = DEFAULT_LINE_JOIN;
/**
* Helper object to format and aggragate lines of code.
* Lines are aggregated in a `code` array, and need to be joined to obtain a proper code snippet.
*/
constructor({ indent, join }: CodeBuilderOptions = {}) {
this.indentationCharacter = indent || DEFAULT_INDENTATION_CHARACTER;
this.lineJoin = join ?? DEFAULT_LINE_JOIN;
}
/**
* Add given indentation level to given line of code
*/
indentLine = (line: string, indentationLevel = 0) => {
const indent = this.indentationCharacter.repeat(indentationLevel);
return `${indent}${line}`;
};
/**
* Add the line at the beginning of the current lines
*/
unshift = (line: string, indentationLevel?: number) => {
const newLine = this.indentLine(line, indentationLevel);
this.code.unshift(newLine);
};
/**
* Add the line at the end of the current lines
*/
push = (line: string, indentationLevel?: number) => {
const newLine = this.indentLine(line, indentationLevel);
this.code.push(newLine);
};
/**
* Add the line to the end of the last line. Creates a new line
* if no lines exist yet.
*/
pushToLast = (line: string) => {
if (!this.code) {
this.push(line);
}
const updatedLine = `${this.code[this.code.length - 1]}${line}`;
this.code[this.code.length - 1] = updatedLine;
};
/**
* Add an empty line at the end of current lines
*/
blank = () => {
this.code.push('');
};
/**
* Concatenate all current lines using the given lineJoin, then apply any replacers that may have been added
*/
join = () => {
const unreplacedCode = this.code.join(this.lineJoin);
const replacedOutput = this.postProcessors.reduce(
(accumulator, replacer) => replacer(accumulator),
unreplacedCode,
);
return replacedOutput;
};
/**
* Often when writing modules you may wish to add a literal tag or bit of metadata that you wish to transform after other processing as a final step.
* To do so, you can provide a PostProcessor function and it will be run automatically for you when you call `join()` later on.
*/
addPostProcessor = (postProcessor: PostProcessor) => {
this.postProcessors = [...this.postProcessors, postProcessor];
};
}