/
cleanString.ts
134 lines (121 loc) · 3.37 KB
/
cleanString.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
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
import type { RollupError } from 'rollup'
// bank on the non-overlapping nature of regex matches and combine all filters into one giant regex
// /`([^`\$\{\}]|\$\{(`|\g<1>)*\})*`/g can match nested string template
// but js not support match expression(\g<0>). so clean string template(`...`) in other ways.
const stringsRE = /"([^"\r\n]|(?<=\\)")*"|'([^'\r\n]|(?<=\\)')*'/.source
const commentsRE = /\/\*(.|[\r\n])*?\*\/|\/\/.*/.source
const cleanerRE = new RegExp(`${stringsRE}|${commentsRE}`, 'g')
const blankReplacer = (s: string) => ' '.repeat(s.length)
const stringBlankReplacer = (s: string) =>
`${s[0]}${'\0'.repeat(s.length - 2)}${s[0]}`
export function emptyString(raw: string): string {
let res = raw.replace(cleanerRE, (s: string) =>
s[0] === '/' ? blankReplacer(s) : stringBlankReplacer(s)
)
let lastEnd = 0
let start = 0
while ((start = res.indexOf('`', lastEnd)) >= 0) {
let clean
;[clean, lastEnd] = lexStringTemplateExpression(res, start)
res = replaceAt(res, start, lastEnd, clean)
}
return res
}
const enum LexerState {
// template string
inTemplateString,
inInterpolationExpression,
inObjectExpression,
// strings
inSingleQuoteString,
inDoubleQuoteString,
// comments
inMultilineCommentsRE,
inSinglelineCommentsRE
}
function replaceAt(
string: string,
start: number,
end: number,
replacement: string
): string {
return string.slice(0, start) + replacement + string.slice(end)
}
/**
* lex string template and clean it.
*/
function lexStringTemplateExpression(
code: string,
start: number
): [string, number] {
let state = LexerState.inTemplateString as LexerState
let clean = '`'
const opStack: LexerState[] = [state]
function pushStack(newState: LexerState) {
state = newState
opStack.push(state)
}
function popStack() {
opStack.pop()
state = opStack[opStack.length - 1]
}
let i = start + 1
outer: for (; i < code.length; i++) {
const char = code.charAt(i)
switch (state) {
case LexerState.inTemplateString:
if (char === '$' && code.charAt(i + 1) === '{') {
pushStack(LexerState.inInterpolationExpression)
clean += '${'
i++ // jump next
} else if (char === '`') {
popStack()
clean += char
if (opStack.length === 0) {
break outer
}
} else {
clean += '\0'
}
break
case LexerState.inInterpolationExpression:
if (char === '{') {
pushStack(LexerState.inObjectExpression)
clean += char
} else if (char === '}') {
popStack()
clean += char
} else if (char === '`') {
pushStack(LexerState.inTemplateString)
clean += char
} else {
clean += char
}
break
case LexerState.inObjectExpression:
if (char === '}') {
popStack()
clean += char
} else if (char === '`') {
pushStack(LexerState.inTemplateString)
clean += char
} else {
clean += char
}
break
default:
throw new Error('unknown string template lexer state')
}
}
if (opStack.length !== 0) {
error(start)
}
return [clean, i + 1]
}
function error(pos: number) {
const err = new Error(
`can not match string template expression.`
) as RollupError
err.pos = pos
throw err
}