@@ -35,7 +35,8 @@ module Vue { |
35 | 35 | result = |
36 | 36 | [ |
37 | 37 | "beforeCreate", "created", "beforeMount", "mounted", "beforeUpdate", "updated", "activated", |
38 | | - "deactivated", "beforeDestroy", "destroyed", "errorCaptured" |
| 38 | + "deactivated", "beforeDestroy", "destroyed", "errorCaptured", "beforeRouteEnter", |
| 39 | + "beforeRouteUpdate", "beforeRouteLeave" |
39 | 40 | ] |
40 | 41 | } |
41 | 42 |
|
@@ -162,6 +163,13 @@ module Vue { |
162 | 163 | result = getAsClassComponent().getDecoratorOption(name) |
163 | 164 | } |
164 | 165 |
|
| 166 | + /** |
| 167 | + * Gets a source node flowing into the option `name` of this instance, including those from |
| 168 | + * extended objects and mixins. |
| 169 | + */ |
| 170 | + pragma[nomagic] |
| 171 | + DataFlow::SourceNode getOptionSource(string name) { result = getOption(name).getALocalSource() } |
| 172 | + |
165 | 173 | /** |
166 | 174 | * Gets the template element used by this instance, if any. |
167 | 175 | */ |
@@ -189,36 +197,54 @@ module Vue { |
189 | 197 | /** |
190 | 198 | * Gets the node for the `template` option of this instance. |
191 | 199 | */ |
192 | | - DataFlow::Node getTemplate() { result = getOption("template") } |
| 200 | + pragma[nomagic] |
| 201 | + DataFlow::SourceNode getTemplate() { result = getOptionSource("template") } |
193 | 202 |
|
194 | 203 | /** |
195 | 204 | * Gets the node for the `render` option of this instance. |
196 | 205 | */ |
197 | | - DataFlow::Node getRender() { |
198 | | - result = getOption("render") |
| 206 | + pragma[nomagic] |
| 207 | + DataFlow::SourceNode getRender() { |
| 208 | + result = getOptionSource("render") |
199 | 209 | or |
200 | 210 | result = getAsClassComponent().getInstanceMethod("render") |
201 | 211 | } |
202 | 212 |
|
203 | 213 | /** |
204 | 214 | * Gets the node for the `methods` option of this instance. |
205 | 215 | */ |
206 | | - DataFlow::Node getMethods() { result = getOption("methods") } |
| 216 | + pragma[nomagic] |
| 217 | + DataFlow::SourceNode getMethods() { result = getOptionSource("methods") } |
207 | 218 |
|
208 | 219 | /** |
209 | 220 | * Gets the node for the `computed` option of this instance. |
210 | 221 | */ |
211 | | - DataFlow::Node getComputed() { result = getOption("computed") } |
| 222 | + pragma[nomagic] |
| 223 | + DataFlow::SourceNode getComputed() { result = getOptionSource("computed") } |
212 | 224 |
|
213 | 225 | /** |
214 | | - * Gets a node for a member of the `methods` option of this instance. |
| 226 | + * Gets the node for the `watch` option of this instance. |
| 227 | + */ |
| 228 | + pragma[nomagic] |
| 229 | + DataFlow::SourceNode getWatch() { result = getOptionSource("watch") } |
| 230 | + |
| 231 | + /** |
| 232 | + * Gets the function responding to changes to the given `propName`. |
215 | 233 | */ |
216 | | - pragma[noinline] |
217 | | - private DataFlow::Node getAMethod() { |
218 | | - exists(DataFlow::ObjectLiteralNode methods | |
219 | | - methods.flowsTo(getMethods()) and |
220 | | - result = methods.getAPropertyWrite().getRhs() |
| 234 | + DataFlow::FunctionNode getWatchHandler(string propName) { |
| 235 | + exists(DataFlow::SourceNode watcher | watcher = getWatch().getAPropertySource(propName) | |
| 236 | + result = watcher |
| 237 | + or |
| 238 | + result = watcher.getAPropertySource("handler") |
221 | 239 | ) |
| 240 | + } |
| 241 | + |
| 242 | + /** |
| 243 | + * Gets a node for a member of the `methods` option of this instance. |
| 244 | + */ |
| 245 | + pragma[nomagic] |
| 246 | + private DataFlow::SourceNode getAMethod() { |
| 247 | + result = getMethods().getAPropertySource() |
222 | 248 | or |
223 | 249 | result = getAsClassComponent().getAnInstanceMethod() and |
224 | 250 | not result = getAsClassComponent().getInstanceMethod([lifecycleHookName(), "render", "data"]) |
@@ -227,19 +253,11 @@ module Vue { |
227 | 253 | /** |
228 | 254 | * Gets a node for a member of the `computed` option of this instance that matches `kind`. |
229 | 255 | */ |
230 | | - pragma[noinline] |
231 | | - private DataFlow::Node getAnAccessor(DataFlow::MemberKind kind) { |
232 | | - exists(DataFlow::ObjectLiteralNode computedObj, DataFlow::Node accessorObjOrGetter | |
233 | | - computedObj.flowsTo(getComputed()) and |
234 | | - computedObj.getAPropertyWrite().getRhs() = accessorObjOrGetter |
235 | | - | |
236 | | - result = accessorObjOrGetter and kind = DataFlow::MemberKind::getter() |
237 | | - or |
238 | | - exists(DataFlow::ObjectLiteralNode accessorObj | |
239 | | - accessorObj.flowsTo(accessorObjOrGetter) and |
240 | | - result = accessorObj.getAPropertyWrite(memberKindVerb(kind)).getRhs() |
241 | | - ) |
242 | | - ) |
| 256 | + pragma[nomagic] |
| 257 | + private DataFlow::SourceNode getAnAccessor(DataFlow::MemberKind kind) { |
| 258 | + result = getComputed().getAPropertySource() and kind = DataFlow::MemberKind::getter() |
| 259 | + or |
| 260 | + result = getComputed().getAPropertySource().getAPropertySource(memberKindVerb(kind)) |
243 | 261 | or |
244 | 262 | result = getAsClassComponent().getAnInstanceMember(kind) and |
245 | 263 | kind.isAccessor() |
@@ -248,18 +266,10 @@ module Vue { |
248 | 266 | /** |
249 | 267 | * Gets a node for a member `name` of the `computed` option of this instance that matches `kind`. |
250 | 268 | */ |
251 | | - private DataFlow::Node getAccessor(string name, DataFlow::MemberKind kind) { |
252 | | - exists(DataFlow::ObjectLiteralNode computedObj, DataFlow::SourceNode accessorObjOrGetter | |
253 | | - computedObj.flowsTo(getComputed()) and |
254 | | - accessorObjOrGetter.flowsTo(computedObj.getAPropertyWrite(name).getRhs()) |
255 | | - | |
256 | | - result = accessorObjOrGetter and kind = DataFlow::MemberKind::getter() |
257 | | - or |
258 | | - exists(DataFlow::ObjectLiteralNode accessorObj | |
259 | | - accessorObj.flowsTo(accessorObjOrGetter) and |
260 | | - result = accessorObj.getAPropertyWrite(memberKindVerb(kind)).getRhs() |
261 | | - ) |
262 | | - ) |
| 269 | + private DataFlow::SourceNode getAccessor(string name, DataFlow::MemberKind kind) { |
| 270 | + result = getComputed().getAPropertySource(name) and kind = DataFlow::MemberKind::getter() |
| 271 | + or |
| 272 | + result = getComputed().getAPropertySource(name).getAPropertySource(memberKindVerb(kind)) |
263 | 273 | or |
264 | 274 | result = getAsClassComponent().getInstanceMember(name, kind) and |
265 | 275 | kind.isAccessor() |
@@ -268,11 +278,11 @@ module Vue { |
268 | 278 | /** |
269 | 279 | * Gets the node for the life cycle hook of the `hookName` option of this instance. |
270 | 280 | */ |
271 | | - pragma[noinline] |
272 | | - private DataFlow::Node getALifecycleHook(string hookName) { |
| 281 | + pragma[nomagic] |
| 282 | + DataFlow::SourceNode getALifecycleHook(string hookName) { |
273 | 283 | hookName = lifecycleHookName() and |
274 | 284 | ( |
275 | | - result = getOption(hookName) |
| 285 | + result = getOptionSource(hookName) |
276 | 286 | or |
277 | 287 | result = getAsClassComponent().getInstanceMethod(hookName) |
278 | 288 | ) |
@@ -281,16 +291,21 @@ module Vue { |
281 | 291 | /** |
282 | 292 | * Gets a node for a function that will be invoked with `this` bound to this instance. |
283 | 293 | */ |
284 | | - DataFlow::Node getABoundFunction() { |
| 294 | + DataFlow::FunctionNode getABoundFunction() { |
285 | 295 | result = getAMethod() |
286 | 296 | or |
287 | 297 | result = getAnAccessor(_) |
288 | 298 | or |
289 | 299 | result = getALifecycleHook(_) |
| 300 | + or |
| 301 | + result = getOptionSource(_) |
| 302 | + or |
| 303 | + result = getOptionSource(_).getAPropertySource() |
290 | 304 | } |
291 | 305 |
|
292 | 306 | /** |
293 | | - * Gets a node for the value for property `name` of this instance. |
| 307 | + * Gets the data flow node that flows into the property `name` of this instance, or is |
| 308 | + * returned form a getter defining that property. |
294 | 309 | */ |
295 | 310 | DataFlow::Node getAPropertyValue(string name) { |
296 | 311 | exists(DataFlow::SourceNode obj | obj.getAPropertyWrite(name).getRhs() = result | |
@@ -552,4 +567,67 @@ module Vue { |
552 | 567 | HTML::Element getElement() { result = elem } |
553 | 568 | } |
554 | 569 | } |
| 570 | + |
| 571 | + /** An API node referring to a `RouteConfig` being passed to `vue-router`. */ |
| 572 | + private API::Node routeConfig() { |
| 573 | + result = API::moduleImport("vue-router").getParameter(0).getMember("routes").getAMember() |
| 574 | + or |
| 575 | + result = routeConfig().getMember("children").getAMember() |
| 576 | + } |
| 577 | + |
| 578 | + /** Gets a data flow node that refers to a `Route` object from `vue-router`. */ |
| 579 | + private DataFlow::SourceNode routeObject(DataFlow::TypeTracker t) { |
| 580 | + t.start() and |
| 581 | + ( |
| 582 | + exists(API::Node router | router = API::moduleImport("vue-router") | |
| 583 | + result = router.getInstance().getMember("currentRoute").getAnImmediateUse() |
| 584 | + or |
| 585 | + result = |
| 586 | + router |
| 587 | + .getInstance() |
| 588 | + .getMember(["beforeEach", "beforeResolve", "afterEach"]) |
| 589 | + .getParameter(0) |
| 590 | + .getParameter([0, 1]) |
| 591 | + .getAnImmediateUse() |
| 592 | + or |
| 593 | + result = |
| 594 | + router |
| 595 | + .getParameter(0) |
| 596 | + .getMember("scrollBehavior") |
| 597 | + .getParameter([0, 1]) |
| 598 | + .getAnImmediateUse() |
| 599 | + ) |
| 600 | + or |
| 601 | + result = routeConfig().getMember("beforeEnter").getParameter([0, 1]).getAnImmediateUse() |
| 602 | + or |
| 603 | + exists(Instance i | |
| 604 | + result = i.getABoundFunction().getAFunctionValue().getReceiver().getAPropertyRead("$route") |
| 605 | + or |
| 606 | + result = |
| 607 | + i.getALifecycleHook(["beforeRouteEnter", "beforeRouteUpdate", "beforeRouteLeave"]) |
| 608 | + .getAFunctionValue() |
| 609 | + .getParameter([0, 1]) |
| 610 | + or |
| 611 | + result = i.getWatchHandler("$route").getParameter([0, 1]) |
| 612 | + ) |
| 613 | + ) |
| 614 | + or |
| 615 | + exists(DataFlow::TypeTracker t2 | result = routeObject(t2).track(t2, t)) |
| 616 | + } |
| 617 | + |
| 618 | + /** Gets a data flow node that refers to a `Route` object from `vue-router`. */ |
| 619 | + DataFlow::SourceNode routeObject() { result = routeObject(DataFlow::TypeTracker::end()) } |
| 620 | + |
| 621 | + private class VueRouterFlowSource extends RemoteFlowSource { |
| 622 | + VueRouterFlowSource() { |
| 623 | + this = routeObject().getAPropertyRead(["params", "query", "hash", "path", "fullPath"]) |
| 624 | + or |
| 625 | + exists(Instance i, string prop | |
| 626 | + this = i.getWatchHandler(prop).getParameter([0, 1]) and |
| 627 | + prop.regexpMatch("\\$route\\.(params|query|hash|path|fullPath)\\b.*") |
| 628 | + ) |
| 629 | + } |
| 630 | + |
| 631 | + override string getSourceType() { result = "Vue route parameter" } |
| 632 | + } |
555 | 633 | } |