-
Notifications
You must be signed in to change notification settings - Fork 98
/
Copy pathsnipTagContent.ts
116 lines (99 loc) · 4.33 KB
/
snipTagContent.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
import { base64ToString, stringToBase64 } from '../base64-string';
export const snippedTagContentAttribute = '✂prettier:content✂';
const scriptRegex =
/<!--[^]*?-->|<script((?:\s+[^=>'"\/\s]+=(?:"[^"]*"|'[^']*'|[^>\s]+)|\s+[^=>'"\/\s]+)*\s*)>([^]*?)<\/script>/g;
const styleRegex =
/<!--[^]*?-->|<style((?:\s+[^=>'"\/\s]+=(?:"[^"]*"|'[^']*'|[^>\s]+)|\s+[^=>'"\/\s]+)*\s*)>([^]*?)<\/style>/g;
const langTsRegex = /\slang=["']?ts["']?/;
export function snipScriptAndStyleTagContent(source: string): {
text: string;
isTypescript: boolean;
} {
let scriptMatchSpans = getMatchIndexes('script');
let styleMatchSpans = getMatchIndexes('style');
let isTypescript = false;
const text = snipTagContent(
snipTagContent(source, 'script', '{}', styleMatchSpans),
'style',
'',
scriptMatchSpans,
);
return { text, isTypescript };
function getMatchIndexes(tagName: string) {
const regex = getRegexp(tagName);
const indexes: [number, number][] = [];
let match = null;
while ((match = regex.exec(source)) != null) {
if (source.slice(match.index, match.index + 4) !== '<!--') {
indexes.push([match.index, regex.lastIndex]);
}
}
return indexes;
}
function snipTagContent(
_source: string,
tagName: string,
placeholder: string,
otherSpans: [number, number][],
) {
const regex = getRegexp(tagName);
let newScriptMatchSpans = scriptMatchSpans;
let newStyleMatchSpans = styleMatchSpans;
// Replace valid matches
const newSource = _source.replace(regex, (match, attributes, content, index) => {
if (match.startsWith('<!--') || withinOtherSpan(index)) {
return match;
}
if (langTsRegex.test(attributes)) {
isTypescript = true;
}
const encodedContent = stringToBase64(content);
const newContent = `<${tagName}${attributes} ${snippedTagContentAttribute}="${encodedContent}">${placeholder}</${tagName}>`;
// Adjust the spans because the source now has a different content length
const lengthDiff = match.length - newContent.length;
newScriptMatchSpans = adjustSpans(scriptMatchSpans, newScriptMatchSpans);
newStyleMatchSpans = adjustSpans(styleMatchSpans, newStyleMatchSpans);
function adjustSpans(
oldSpans: [number, number][],
newSpans: [number, number][],
): [number, number][] {
return oldSpans.map((oldSpan, idx) => {
const newSpan = newSpans[idx];
// Do the check using the old spans because the replace function works
// on the old spans. Replace oldSpans with newSpans afterwards.
if (oldSpan[0] > index) {
// span is after the match -> adjust start and end
return [newSpan[0] - lengthDiff, newSpan[1] - lengthDiff];
} else if (oldSpan[0] === index) {
// span is the match -> adjust end only
return [newSpan[0], newSpan[1] - lengthDiff];
} else {
// span is before the match -> nothing to adjust
return newSpan;
}
});
}
return newContent;
});
// Now that the replacement function ran, we can adjust the spans for the next run
scriptMatchSpans = newScriptMatchSpans;
styleMatchSpans = newStyleMatchSpans;
return newSource;
function withinOtherSpan(idx: number) {
return otherSpans.some((otherSpan) => idx > otherSpan[0] && idx < otherSpan[1]);
}
}
function getRegexp(tagName: string) {
return tagName === 'script' ? scriptRegex : styleRegex;
}
}
export function hasSnippedContent(text: string) {
return text.includes(snippedTagContentAttribute);
}
const regex = /(<\w+.*?)\s*✂prettier:content✂="(.*?)">.*?(?=<\/)/gi;
export function unsnipContent(text: string): string {
return text.replace(regex, (_, start, encodedContent) => {
const content = base64ToString(encodedContent);
return `${start}>${content}`;
});
}