Skip to content

Commit c8a958b

Browse files
committed
Add JavaScript compatibility patch script
1 parent 52e406e commit c8a958b

1 file changed

Lines changed: 229 additions & 0 deletions

File tree

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
from pathlib import Path
2+
3+
4+
def replace_once(text, old, new, label):
5+
count = text.count(old)
6+
if count != 1:
7+
raise SystemExit(f"{label}: expected 1 match, got {count}")
8+
return text.replace(old, new, 1)
9+
10+
11+
binder = Path("AdvancedCore/src/main/java/com/bencodez/advancedcore/api/javascript/JavascriptPlaceholderBinder.java")
12+
text = binder.read_text()
13+
14+
text = replace_once(
15+
text,
16+
' private static final Pattern PLACEHOLDER = Pattern.compile("%([^%\\\\s]+)%");',
17+
' private static final Pattern PLACEHOLDER = Pattern.compile("%([^%\\\\s]+)%|(?<!\\\\$)\\\\{([^{}%\\\\s]+)\\\\}");',
18+
"placeholder pattern",
19+
)
20+
21+
old = ''' matches.add(new PlaceholderMatch(matcher.start(), matcher.end(), token, value));
22+
// Keep all source offsets unchanged while making a bare %placeholder%
23+
// parse as an ordinary identifier.
24+
for (int i = matcher.start(); i < matcher.end(); i++) {
25+
sanitized.setCharAt(i, 'p');
26+
}
27+
'''
28+
new = ''' matches.add(new PlaceholderMatch(matcher.start(), matcher.end(), token, value));
29+
// Keep all source offsets unchanged while making resolved placeholders parse
30+
// as an ordinary identifier. Unresolved brace syntax may be valid JavaScript
31+
// (for example an object/block), so only sanitize brace placeholders when
32+
// they actually resolve as AdvancedCore custom data.
33+
boolean bracePlaceholder = token.charAt(0) == '{';
34+
if (!bracePlaceholder || (value != null && !value.equals(token))) {
35+
for (int i = matcher.start(); i < matcher.end(); i++) {
36+
sanitized.setCharAt(i, 'p');
37+
}
38+
}
39+
'''
40+
text = replace_once(text, old, new, "sanitization")
41+
42+
old = ''' if (regex != null) {
43+
replacements[i] = escapeRegex(match.value, expression, regex, match.start);
44+
} else if (string != null) {
45+
replacements[i] = escapeString(match.value, expression.charAt(string.start));
46+
} else if (template != null && !contexts.insideTemplateExpression(match.start)) {
47+
replacements[i] = escapeTemplate(match.value);
48+
} else {
49+
'''
50+
new = ''' if (regex != null) {
51+
replacements[i] = escapeRegex(match.value, expression, regex, match.start);
52+
} else if (string != null) {
53+
char delimiter = literalDelimiter(expression, string);
54+
if (delimiter == '`' && !contexts.insideTemplateExpression(match.start)) {
55+
replacements[i] = escapeTemplate(match.value);
56+
} else {
57+
replacements[i] = escapeString(match.value, delimiter);
58+
}
59+
} else if (template != null && !contexts.insideTemplateExpression(match.start)) {
60+
replacements[i] = escapeTemplate(match.value);
61+
} else {
62+
'''
63+
text = replace_once(text, old, new, "context block")
64+
65+
old = ''' private static String resolve(String token, OfflinePlayer player, Map<String, String> placeholders) {
66+
AdvancedCorePlugin plugin = AdvancedCorePlugin.getInstance();
67+
if (player != null && plugin != null && plugin.isPlaceHolderAPIEnabled()) {
68+
String resolved = PlaceholderAPI.setPlaceholders(player, token);
69+
if (resolved != null && !resolved.equals(token)) {
70+
return resolved;
71+
}
72+
}
73+
74+
if (placeholders != null) {
75+
String name = token.substring(1, token.length() - 1);
76+
'''
77+
new = ''' private static String resolve(String token, OfflinePlayer player, Map<String, String> placeholders) {
78+
AdvancedCorePlugin plugin = AdvancedCorePlugin.getInstance();
79+
// PlaceholderAPI uses percent-delimited placeholders. Brace-delimited tokens
80+
// are AdvancedCore's legacy custom placeholder form and are resolved below.
81+
if (token.startsWith("%") && player != null && plugin != null && plugin.isPlaceHolderAPIEnabled()) {
82+
String resolved = PlaceholderAPI.setPlaceholders(player, token);
83+
if (resolved != null && !resolved.equals(token)) {
84+
return resolved;
85+
}
86+
}
87+
88+
if (placeholders != null) {
89+
String name = token.substring(1, token.length() - 1);
90+
'''
91+
text = replace_once(text, old, new, "resolve block")
92+
93+
marker = ''' private static String escapeString(String value, char quote) {
94+
'''
95+
helper = ''' private static char literalDelimiter(String expression, Range range) {
96+
int[] candidates = { range.start - 1, range.start, range.end, range.end - 1 };
97+
for (int candidate : candidates) {
98+
if (candidate < 0 || candidate >= expression.length()) {
99+
continue;
100+
}
101+
char value = expression.charAt(candidate);
102+
if (value == '\\'' || value == '"' || value == '`') {
103+
return value;
104+
}
105+
}
106+
for (int i = Math.max(0, range.start - 2);
107+
i <= Math.min(expression.length() - 1, range.start + 1); i++) {
108+
char value = expression.charAt(i);
109+
if (value == '\\'' || value == '"' || value == '`') {
110+
return value;
111+
}
112+
}
113+
return '\\'';
114+
}
115+
116+
'''
117+
text = replace_once(text, marker, helper + marker, "escape helper marker")
118+
119+
old = ''' private static ClassLoader parserClassLoader() {
120+
JavascriptEngineHandler handler = JavascriptEngineHandler.getInstance();
121+
if (handler.getNashornClassLoader() != null) {
122+
return handler.getNashornClassLoader();
123+
}
124+
ScriptEngine cached = handler.getCachedEngine();
125+
if (cached != null && cached.getClass().getClassLoader() != null) {
126+
return cached.getClass().getClassLoader();
127+
}
128+
ClassLoader own = JavascriptPlaceholderBinder.class.getClassLoader();
129+
try {
130+
Class.forName(PARSER_CLASS, false, own);
131+
return own;
132+
} catch (ClassNotFoundException ignored) {
133+
return null;
134+
}
135+
}
136+
'''
137+
new = ''' private static ClassLoader parserClassLoader() {
138+
JavascriptEngineHandler handler = JavascriptEngineHandler.getInstance();
139+
ClassLoader downloaded = handler.getNashornClassLoader();
140+
if (canLoadParser(downloaded)) {
141+
return downloaded;
142+
}
143+
144+
// nashorn-core is packaged with AdvancedCore so parser support remains
145+
// available even when the active ScriptEngine is Rhino/GraalJS.
146+
ClassLoader own = JavascriptPlaceholderBinder.class.getClassLoader();
147+
if (canLoadParser(own)) {
148+
return own;
149+
}
150+
151+
ScriptEngine cached = handler.getCachedEngine();
152+
ClassLoader cachedLoader = cached == null ? null : cached.getClass().getClassLoader();
153+
return canLoadParser(cachedLoader) ? cachedLoader : null;
154+
}
155+
156+
private static boolean canLoadParser(ClassLoader loader) {
157+
if (loader == null) {
158+
return false;
159+
}
160+
try {
161+
Class.forName(PARSER_CLASS, false, loader);
162+
return true;
163+
} catch (ClassNotFoundException | LinkageError ignored) {
164+
return false;
165+
}
166+
}
167+
'''
168+
text = replace_once(text, old, new, "parser loader")
169+
binder.write_text(text)
170+
171+
pom = Path("AdvancedCore/pom.xml")
172+
text = pom.read_text()
173+
old = ''' <dependency>
174+
<groupId>org.openjdk.nashorn</groupId>
175+
<artifactId>nashorn-core</artifactId>
176+
<version>15.7</version>
177+
<scope>provided</scope>
178+
</dependency>'''
179+
new = ''' <dependency>
180+
<groupId>org.openjdk.nashorn</groupId>
181+
<artifactId>nashorn-core</artifactId>
182+
<version>15.7</version>
183+
<scope>compile</scope>
184+
</dependency>'''
185+
text = replace_once(text, old, new, "nashorn dependency")
186+
pom.write_text(text)
187+
188+
test = Path("AdvancedCore/src/test/java/com/bencodez/advancedcore/api/javascript/JavascriptPlaceholderBinderTest.java")
189+
text = test.read_text()
190+
insert = '''
191+
@Test
192+
void exactQuotedNumericLookingPlaceholderRemainsAString() {
193+
HashMap<String, Object> bindings = new HashMap<>();
194+
195+
String prepared = JavascriptPlaceholderBinder.bind("'%code%' === '001'", ignored -> "001", bindings::put);
196+
197+
assertEquals("'001' === '001'", prepared);
198+
assertTrue(bindings.isEmpty());
199+
}
200+
201+
@Test
202+
void braceDelimitedCustomPlaceholderIsAutomaticallyBound() {
203+
HashMap<String, Object> bindings = new HashMap<>();
204+
205+
String prepared = JavascriptPlaceholderBinder.bind("{count} > 0",
206+
token -> token.equals("{count}") ? "5" : token, bindings::put);
207+
208+
assertEquals("__advancedCorePlaceholder0 > 0", prepared);
209+
assertEquals(Long.valueOf(5), bindings.get("__advancedCorePlaceholder0"));
210+
}
211+
212+
@Test
213+
void unresolvedBraceSyntaxRemainsOrdinaryJavascript() {
214+
HashMap<String, Object> bindings = new HashMap<>();
215+
216+
String prepared = JavascriptPlaceholderBinder.bind("var value = {count: 1}; %name%",
217+
token -> token.equals("%name%") ? "Ben" : token, bindings::put);
218+
219+
assertEquals("var value = {count: 1}; __advancedCorePlaceholder0", prepared);
220+
assertEquals("Ben", bindings.get("__advancedCorePlaceholder0"));
221+
}
222+
223+
'''
224+
marker = '''
225+
@Test
226+
void unresolvedTokensRemainUntouched() {
227+
'''
228+
text = replace_once(text, marker, "\n" + insert + marker.lstrip("\n"), "test marker")
229+
test.write_text(text)

0 commit comments

Comments
 (0)