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
//! `Loc` implementation for Php.
#![allow(
clippy::enum_glob_use,
clippy::match_same_arms,
clippy::struct_field_names,
clippy::wildcard_imports
)]
#![allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
use super::*;
impl Loc for PhpCode {
fn compute<'a>(
node: &Node<'a>,
code: &[u8],
ancestors: Ancestors<'a, '_>,
stats: &mut Stats,
is_func_space: bool,
) {
use Php::*;
let start = init(node, stats, is_func_space);
match node.kind_id().into() {
Program => {}
Comment => {
add_comment_lines::<Self>(node, code, ancestors, stats);
}
// Every PHP literal that can span rows, so its interior rows
// reach PLOC instead of being claimed by
// `blank = sloc - ploc - cloc` (#778, #1396) — the decision #415
// took for Python and #1260 took for the last four languages.
//
// #778 routed the quoted forms and excluded the heredoc, on the
// premise that its body "already reaches PLOC through its inner
// statement nodes". Half true, and it cost a phantom blank row
// per spelling: tree-sitter-php emits a body child only for a row
// that *has* text. Heredoc drops just the row empty inside the
// literal; nowdoc also drops its last body row, because each
// `nowdoc_string` after the first starts at the end of the row
// before it, so the catch-all's start-row insertion credits
// every body row to its predecessor and the last to nothing.
//
// The wrapper is routed rather than `HeredocBody` / `NowdocBody`
// because a body of one empty row emits no body node at all —
// `heredoc` is the node present for every spelling
// (`.claude/rules/grammar-dispatch.md` section 6). Its span runs
// from `<<<` to the closing marker, so its interior is the body
// rows plus that marker's row, which is code either way.
//
// `ShellCommandExpression` (`` `…` ``) is the fifth form, and the
// worst: its body is one multi-row `string_content` child, so
// every interior row was lost. It is routed here for the same
// reason, which makes this arm agree with
// `PhpCode::is_string` (`big-code-analysis-ast/src/checker/php.rs`)
// on every kind that grammar can span rows with — section 7's
// parity cross-walk.
//
// Aliases (section 1): none of the five routed kinds has a
// numeric-suffix variant. `String2` is the anonymous `string`
// *type* keyword and `String3` is the hidden `_string` supertype
// the parser never emits (section 2) — neither is a literal, so
// neither belongs here.
EncapsedString | String | Heredoc | Nowdoc | ShellCommandExpression => {
add_multiline_string_ploc(node, ancestors, stats, start);
}
// Statement kinds that contribute one logical line each.
ExpressionStatement
| EchoStatement
| EmptyStatement
| IfStatement
| SwitchStatement
| ForStatement
| ForeachStatement
| WhileStatement
| DoStatement
| TryStatement
| ReturnStatement
| BreakStatement
| ContinueStatement
| GotoStatement
| UnsetStatement
| DeclareStatement
| NamespaceUseDeclaration
| GlobalDeclaration
| FunctionStaticDeclaration
| ConstDeclaration
| ConstDeclaration2
| PropertyDeclaration
| NamedLabelStatement => {
stats.lloc.count_logical_line();
}
_ => {
check_comment_ends_on_code_line(stats, start);
stats.ploc.lines.insert(start);
}
}
}
}