-
Notifications
You must be signed in to change notification settings - Fork 46
/
TemplateParserContext.java
346 lines (304 loc) · 11.8 KB
/
TemplateParserContext.java
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
package com.axellience.vuegwt.processors.component.template.parser.context;
import com.axellience.vuegwt.core.client.component.IsVueComponent;
import com.axellience.vuegwt.core.client.tools.JsUtils;
import com.axellience.vuegwt.core.client.tools.VForExpressionUtil;
import com.axellience.vuegwt.processors.component.template.parser.context.localcomponents.LocalComponent;
import com.axellience.vuegwt.processors.component.template.parser.context.localcomponents.LocalComponents;
import com.axellience.vuegwt.processors.component.template.parser.variable.DestructuredPropertyInfo;
import com.axellience.vuegwt.processors.component.template.parser.variable.LocalVariableInfo;
import com.axellience.vuegwt.processors.component.template.parser.variable.VariableInfo;
import com.squareup.javapoet.TypeName;
import elemental2.dom.Event;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.lang.model.element.TypeElement;
import net.htmlparser.jericho.Segment;
/**
* Context of the parser. This holds information about imports and variable that exist in the
* Component. It also holds information about the current node being processed.
*
* @author Adrien Baron
*/
public class TemplateParserContext {
private final TypeElement componentTypeElement;
private final LocalComponents localComponents;
private final ContextLayer rootLayer;
private final Deque<ContextLayer> contextLayers = new ArrayDeque<>();
// For import
private Map<String, String> classNameToFullyQualifiedName = new HashMap<>();
// For static imports
private Map<String, String> methodNameToFullyQualifiedName = new HashMap<>();
private Map<String, String> propertyNameToFullyQualifiedName = new HashMap<>();
private Segment currentSegment;
/**
* In some cases mandatory attributes must be added to each element during template parsing, for
* example to support scoped styles
*/
private final Map<String, String> mandatoryAttributes = new HashMap<>();
/**
* Build the context based on a given {@link IsVueComponent} Class.
*
* @param componentTypeElement The {@link IsVueComponent} class we process in this context
* @param localComponents Components registered locally, used to check property bindings
*/
public TemplateParserContext(TypeElement componentTypeElement, LocalComponents localComponents) {
this.componentTypeElement = componentTypeElement;
this.localComponents = localComponents;
this.addImport(Event.class.getCanonicalName());
this.addImport(Math.class.getCanonicalName());
this.addImport(JsUtils.class.getCanonicalName());
this.addImport(VForExpressionUtil.class.getCanonicalName());
this.addStaticImport(JsUtils.class.getCanonicalName() + ".map");
this.addStaticImport(JsUtils.class.getCanonicalName() + ".e");
this.addStaticImport(JsUtils.class.getCanonicalName() + ".array");
this.rootLayer = new ContextLayer(0, false);
this.rootLayer.addMethod("vue");
this.contextLayers.add(this.rootLayer);
}
/**
* Add a variable to the root context.
*
* @param type The type of the variable to add
* @param name The name of the variable to add
*/
public void addRootVariable(TypeName type, String name) {
this.rootLayer.addVariable(type, name);
}
public void addRootComputedProperty(TypeName type, String computedPropertyName,
String fieldName) {
this.rootLayer.addComputedVariable(type, computedPropertyName, fieldName);
}
/**
* Register a method in the root context
*
* @param methodName The name of the method
*/
public void addRootMethod(String methodName) {
this.rootLayer.addMethod(methodName);
}
/**
* Add a context layer. Used when entering a node with v-for.
*/
public void addContextLayer(boolean isVFor) {
contextLayers
.push(new ContextLayer(contextLayers.getFirst().getUniqueContextVariableCount(), isVFor));
}
/**
* Pop a context layer. Used when leaving a node with v-for.
*/
public void popContextLayer() {
contextLayers.pop();
}
/**
* Add a local variable in the current context.
*
* @param typeQualifiedName The type of the variable
* @param name The name of the variable
* @return {@link LocalVariableInfo} for the added variable
*/
public LocalVariableInfo addLocalVariable(String typeQualifiedName, String name) {
return contextLayers.getFirst().addLocalVariable(typeQualifiedName, name);
}
/**
* Add a local variable coming from a Variable destructuring to the current context.
*
* @param propertyType The type of the property on the destructured variable
* @param propertyName The name of the property on the destructured variable
* @param destructuredVariable The local variable that is getting destructured
* @return {@link DestructuredPropertyInfo} for the added variable
*/
public DestructuredPropertyInfo addDestructuredProperty(String propertyType,
String propertyName,
LocalVariableInfo destructuredVariable) {
return contextLayers.getFirst()
.addDestructuredProperty(propertyType, propertyName, destructuredVariable);
}
/**
* Add a unique local variable to the current context and return it's info
*
* @param typeQualifiedName The type of the variable
* @return {@link LocalVariableInfo} for the added variable
*/
public LocalVariableInfo addUniqueLocalVariable(String typeQualifiedName) {
return contextLayers.getFirst().addUniqueLocalVariable(typeQualifiedName);
}
/**
* Find a variable in the context stack.
*
* @param name Name of the variable to get
* @return Information about the variable
*/
public VariableInfo findVariable(String name) {
for (ContextLayer contextLayer : contextLayers) {
VariableInfo variableInfo = contextLayer.getVariableInfo(name);
if (variableInfo != null) {
return variableInfo;
}
}
return null;
}
/**
* Return info about a root variable
*
* @param name the name of the variable to get
* @return Information about the variable
*/
public VariableInfo findRootVariable(String name) {
return rootLayer.getVariableInfo(name);
}
/**
* Search if the method with the given name exist in the context stack. This will allow to catch
* basic error at the parser level, also this allow us to know when a method is used in a template
* expression and use a method call instead of a computed property. We only look in the the root
* context, because methods can't be declared on the fly in the template, so they can only exist
* in the root context. This doesn't check that parameters from the call match, we leave this to
* the Java compiler.
*
* @param name The name of the method to look for
* @return True if it exists, false otherwise
*/
public boolean hasMethod(String name) {
return rootLayer.hasMethod(name);
}
/**
* Add a Java Import to the context.
*
* @param fullyQualifiedName The fully qualified name of the class to import
*/
public void addImport(String fullyQualifiedName) {
String[] importSplit = fullyQualifiedName.split("\\.");
String className = importSplit[importSplit.length - 1];
classNameToFullyQualifiedName.put(className, fullyQualifiedName);
}
/**
* Return the fully qualified name for a given class. Only works if the class has been imported.
*
* @param className The name of the class to get the fully qualified name of
* @return The fully qualified name, or the className if it's unknown
*/
public String getFullyQualifiedNameForClassName(String className) {
if (!classNameToFullyQualifiedName.containsKey(className)) {
return className;
}
return classNameToFullyQualifiedName.get(className);
}
/**
* Return true if we have an import for the given className
*
* @param className The className we want to check
* @return True if we have an import, false otherwise
*/
public boolean hasImport(String className) {
return classNameToFullyQualifiedName.containsKey(className);
}
/**
* Add a Java Static Import to the context.
*
* @param fullyQualifiedName The fully qualified name of the method to import
*/
public void addStaticImport(String fullyQualifiedName) {
String[] importSplit = fullyQualifiedName.split("\\.");
String symbolName = importSplit[importSplit.length - 1];
methodNameToFullyQualifiedName.put(symbolName, fullyQualifiedName);
propertyNameToFullyQualifiedName.put(symbolName, fullyQualifiedName);
}
/**
* Return the fully qualified name for a given method. Only works if the method has been
* statically imported.
*
* @param methodName The name of the method to get the fully qualified name of
* @return The fully qualified name, or the method name if it's unknown
*/
public String getFullyQualifiedNameForMethodName(String methodName) {
if (!methodNameToFullyQualifiedName.containsKey(methodName)) {
return methodName;
}
return methodNameToFullyQualifiedName.get(methodName);
}
/**
* Return the fully qualified name for a given property. Only works if the property has been
* statically imported.
*
* @param propertyName The name of the property to get the fully qualified name of
* @return The fully qualified name, or the property name if it's unknown
*/
public String getFullyQualifiedNameForPropertyName(String propertyName) {
if (!propertyNameToFullyQualifiedName.containsKey(propertyName)) {
return propertyName;
}
return propertyNameToFullyQualifiedName.get(propertyName);
}
/**
* Return whether we are inside a v-for loop
* @return true if we are in a v-for loop
*/
public boolean isInVFor() {
for (ContextLayer contextLayer : contextLayers) {
if (contextLayer.isVFor()) {
return true;
}
}
return false;
}
/**
* Return true if we have a static import for the given methodName
*
* @param methodName The methodName we want to check
* @return True if we have an import, false otherwise
*/
public boolean hasStaticMethod(String methodName) {
return methodNameToFullyQualifiedName.containsKey(methodName)
|| methodNameToFullyQualifiedName.containsValue(methodName);
}
/**
* Return true if we have a static import for the given propertyName
*
* @param propertyName The propertyName we want to check
* @return True if we have an import, false otherwise
*/
public boolean hasStaticProperty(String propertyName) {
return methodNameToFullyQualifiedName.containsKey(propertyName)
|| methodNameToFullyQualifiedName.containsValue(propertyName);
}
/**
* Return the number of the line currently processed in the HTML
*
* @return The number of the current line being processed or empty
*/
public Optional<Integer> getCurrentLine() {
if (currentSegment == null) {
return Optional.empty();
}
return Optional.of(currentSegment.getSource().getRow(currentSegment.getBegin()));
}
/**
* Set the current HTML {@link Segment} being processed. Used for error message and comment on
* expressions
*
* @param currentSegment The current HTML {@link Segment}
*/
public void setCurrentSegment(Segment currentSegment) {
this.currentSegment = currentSegment;
}
/**
* Simple getter for the currently processed {@link IsVueComponent} Template name. Used for
* debugging.
*
* @return The currently process {@link IsVueComponent} Template name
*/
public String getTemplateName() {
return componentTypeElement.getSimpleName().toString() + ".html";
}
public Optional<LocalComponent> getLocalComponent(String tagName) {
return localComponents.getLocalComponent(tagName);
}
public TypeElement getComponentTypeElement() {
return componentTypeElement;
}
public Map<String, String> getMandatoryAttributes() {
return mandatoryAttributes;
}
}