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
use ;
use crateChecker;
pub
/// Check if the "current expression" being visited is followed
/// in the source code by a chain of `.replace()` calls followed by `.astimezone`.
/// The function operates on the assumption that the current expression
/// is a [`datetime.datetime`][datetime] object.
///
/// For example, given the following Python source code:
///
/// ```py
/// import datetime
///
/// datetime.now().replace(hours=4).replace(minutes=46).astimezone()
/// ```
///
/// The last line will produce an AST looking something like this
/// (this is pseudocode approximating our AST):
///
/// ```rs
/// Call {
/// func: Attribute {
/// value: Call {
/// func: Attribute {
/// value: Call {
/// func: Attribute {
/// value: Call { // We are visiting this
/// func: Attribute { // expression node here
/// value: Call { //
/// func: Name { //
/// id: "datetime", //
/// }, //
/// }, //
/// attr: "now" //
/// }, //
/// }, //
/// attr: "replace"
/// },
/// },
/// attr: "replace"
/// },
/// },
/// attr: "astimezone"
/// },
/// }
/// ```
///
/// The node we are visiting as the "current expression" is deeply
/// nested inside many other expressions. As such, in order to check
/// whether the `datetime.now()` call is followed by 0-or-more `.replace()`
/// calls and then an `.astimezone()` call, we must iterate up through the
/// "parent expressions" in the semantic model, checking if they match this
/// AST pattern.
///
/// [datetime]: https://docs.python.org/3/library/datetime.html#datetime-objects
pub