1
|
|
|
<?php |
2
|
|
|
/****************************************************************************** |
3
|
|
|
* An implementation of dicto (scg.unibe.ch/dicto) in and for PHP. |
4
|
|
|
* |
5
|
|
|
* Copyright (c) 2016 Richard Klees <[email protected]> |
6
|
|
|
* |
7
|
|
|
* This software is licensed under The MIT License. You should have received |
8
|
|
|
* a copy of the licence along with the code. |
9
|
|
|
*/ |
10
|
|
|
|
11
|
|
|
namespace Lechimp\Dicto\Rules; |
12
|
|
|
|
13
|
|
|
use Lechimp\Dicto\Definition as Def; |
14
|
|
|
use Lechimp\Dicto\Indexer\Insert; |
15
|
|
|
use Lechimp\Dicto\Indexer\Location; |
16
|
|
|
use Lechimp\Dicto\Analysis\Query; |
17
|
|
|
use Lechimp\Dicto\Analysis\Violation; |
18
|
|
|
use \Lechimp\Dicto\Variables\Variable; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* This is a rule that checks a relation between two entities |
22
|
|
|
* in the code. |
23
|
|
|
*/ |
24
|
|
|
abstract class Relation extends Schema { |
25
|
|
|
/** |
26
|
|
|
* @inheritdoc |
27
|
|
|
*/ |
28
|
146 |
|
public function fluid_interface(Def\RT $rt, $name, $mode, array $arguments) { |
29
|
146 |
|
if (count($arguments) != 0) { |
30
|
|
|
throw new \InvalidArgumentException( |
31
|
|
|
"No arguments are allowed when using a relational rule schema."); |
32
|
|
|
} |
33
|
146 |
|
return new Def\Fluid\Relation($rt, $name, $mode, $this); |
34
|
|
|
} |
35
|
|
|
|
36
|
188 |
|
public function check_arguments(array $arguments) { |
37
|
188 |
|
if (count($arguments) != 1) { |
38
|
|
|
throw new \InvalidArgumentException( |
39
|
|
|
"One argument is required when using a relational rule schema."); |
40
|
|
|
} |
41
|
188 |
|
if (!($arguments[0] instanceof Variable)) { |
42
|
|
|
throw new \InvalidArgumentException( |
43
|
|
|
"Expected variable, got '".get_class($arguments[0])."' when using a relational schema."); |
44
|
|
|
} |
45
|
|
|
|
46
|
188 |
|
} |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* @inheritdoc |
50
|
|
|
*/ |
51
|
3 |
|
public function pprint(Rule $rule) { |
52
|
3 |
|
return $this->printable_name()." ".$rule->argument(0)->name(); |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* @inheritdoc |
57
|
|
|
*/ |
58
|
37 |
|
public function compile(Query $query, Rule $rule) { |
59
|
37 |
|
$builder = $query->builder(); |
60
|
37 |
|
$b = $builder->expr(); |
61
|
37 |
|
$mode = $rule->mode(); |
62
|
37 |
|
$entity = $rule->checked_on(); |
63
|
37 |
|
$reference = $rule->argument(0); |
64
|
37 |
|
if ($mode == Rule::MODE_CANNOT || $mode == Rule::MODE_ONLY_CAN) { |
65
|
|
|
return $builder |
66
|
29 |
|
->select |
67
|
29 |
|
( "rel.entity_id as entity_id" |
68
|
29 |
|
, "rel.reference_id as reference_id" |
69
|
29 |
|
, "r.file as file" |
70
|
29 |
|
, "r.line as line" |
71
|
29 |
|
, "src.source as source" |
72
|
29 |
|
) |
73
|
29 |
|
->from($query->relations_table(), "rel") |
74
|
29 |
|
->innerJoin("rel", $query->entity_table(), "e", "rel.entity_id = e.id") |
75
|
29 |
|
->innerJoin("rel", $query->reference_table(), "r", "rel.reference_id = r.id") |
76
|
|
|
->innerJoin |
77
|
29 |
|
( "rel", $query->source_file_table(), "src" |
78
|
29 |
|
, $b->andX |
79
|
29 |
|
( $b->eq("src.line", "r.line") |
80
|
29 |
|
, $b->eq("src.name", "r.file") |
81
|
29 |
|
) |
82
|
29 |
|
) |
83
|
|
|
->where |
84
|
29 |
|
( $b->eq("rel.name", $b->literal($this->name())) |
85
|
29 |
|
, $query->compile_var("e", $entity) |
86
|
29 |
|
, $query->compile_var("r", $reference) |
87
|
29 |
|
) |
88
|
29 |
|
->execute(); |
89
|
|
|
} |
90
|
8 |
|
if ($mode == Rule::MODE_MUST) { |
91
|
|
|
return $builder |
92
|
8 |
|
->select |
93
|
8 |
|
( "e.id as entity_id" |
94
|
8 |
|
, "e.file as file" |
95
|
8 |
|
, "e.start_line as line" |
96
|
8 |
|
, "src.source as source" |
97
|
8 |
|
) |
98
|
8 |
|
->from($query->entity_table(), "e") |
99
|
|
|
->leftJoin |
100
|
8 |
|
("e", $query->relations_table(), "rel" |
101
|
8 |
|
, $b->andX |
102
|
8 |
|
( $b->eq("rel.name", $b->literal($this->name())) |
103
|
8 |
|
, $b->eq("rel.entity_id", "e.id") |
104
|
8 |
|
) |
105
|
8 |
|
) |
106
|
|
|
->leftJoin |
107
|
8 |
|
("rel", $query->reference_table(), "r" |
108
|
8 |
|
, $b->andX |
109
|
8 |
|
( $b->eq("rel.reference_id", "r.id") |
110
|
8 |
|
, $query->compile_var("r", $reference) |
111
|
8 |
|
) |
112
|
8 |
|
) |
113
|
|
|
->innerJoin |
114
|
8 |
|
( "e", $query->source_file_table(), "src" |
115
|
8 |
|
, $b->andX |
116
|
8 |
|
( $b->eq("src.line", "e.start_line") |
117
|
8 |
|
, $b->eq("src.name", "e.file") |
118
|
8 |
|
) |
119
|
8 |
|
) |
120
|
|
|
|
121
|
|
|
->where |
122
|
8 |
|
( $query->compile_var("e", $entity) |
123
|
8 |
|
, $b->isNull("r.id") |
124
|
8 |
|
) |
125
|
8 |
|
->execute(); |
126
|
|
|
} |
127
|
|
|
throw new \LogicException("Unknown rule mode: '$mode'"); |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* Insert this relation somewhere, where it is recorded for all |
132
|
|
|
* entities that the current location is in, except for files. |
133
|
|
|
* |
134
|
|
|
* @param Insert $insert |
135
|
|
|
* @param Location $location |
136
|
|
|
* @param int $ref_id |
137
|
|
|
* @return null |
138
|
|
|
*/ |
139
|
17 |
|
protected function insert_relation_into(Insert $insert, Location $location, $ref_id) { |
140
|
17 |
|
assert('is_int($ref_id)'); |
141
|
17 |
|
foreach ($location->in_entities() as $entity) { |
142
|
17 |
|
if ($entity[0] == Variable::FILE_TYPE) { |
143
|
17 |
|
continue; |
144
|
|
|
} |
145
|
17 |
|
$insert->relation |
146
|
17 |
|
( $this->name() |
147
|
17 |
|
, $entity[1] |
148
|
17 |
|
, $ref_id |
149
|
17 |
|
, $location->file_path() |
|
|
|
|
150
|
17 |
|
); |
151
|
17 |
|
} |
152
|
17 |
|
} |
153
|
|
|
} |
154
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.