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
//! `Checker` implementation for Java.
#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
use super::*;
use crate::lang_helpers::doc_comment::{java_doc, node_bytes};
/// A declaration javac attaches a doc comment to, the package aside.
fn java_declaration(node: &Node) -> bool {
matches!(
node.kind_id().into(),
Java::ModuleDeclaration
| Java::ClassDeclaration
| Java::InterfaceDeclaration
| Java::EnumDeclaration
| Java::RecordDeclaration
| Java::AnnotationTypeDeclaration
| Java::MethodDeclaration
| Java::ConstructorDeclaration
| Java::CompactConstructorDeclaration
| Java::FieldDeclaration
| Java::ConstantDeclaration
| Java::AnnotationTypeElementDeclaration
| Java::EnumConstant
)
}
/// Whether two `///` doc comments on consecutive rows are one Markdown
/// doc comment, as javac's scanner lexes them.
fn java_markdown_lines_join(prev: &Node, next: &Node, code: &[u8]) -> bool {
let line = |node: &Node| node_bytes(node, code).starts_with(b"///");
line(prev) && line(next) && prev.end_row().saturating_add(1) == next.start_row()
}
impl Checker for JavaCode {
fn is_comment(node: &Node) -> bool {
node.kind_id() == Java::LineComment || node.kind_id() == Java::BlockComment
}
fn is_doc_comment(node: &Node, code: &[u8]) -> bool {
Self::is_comment(node) && java_doc(node_bytes(node, code))
}
// javac's `Token::docComment` gives a declaration the last doc comment
// before its first token, its annotations and modifiers included, and
// its dangling-doc-comments lint names every earlier one; blank rows
// and plain comments in between do not matter (#1613).
// The scanner lexes `///` lines on consecutive rows as one doc comment
// (`JavaTokenizer`, JEP 467).
const DOC_BINDING: Option<DocBinding> = Some(DocBinding::new(KeptDocs::Last {
joins: java_markdown_lines_join,
}));
// What javac attaches a doc comment to (`JavacParser`'s `attach`
// calls): a type, a member, an enum constant and a module. An import
// or a statement takes none, and an initializer's is dropped
// (`ignoreDanglingComments`). The package declaration takes the
// file's first doc comment, but javadoc reads a package's only from
// `package-info.java`, and javac's lint calls it dangling once a
// declaration follows; a file with none after its package is that
// file's shape. The scan stops at the next package too, so a file of
// repeated packages — invalid, but parsed without error — is read in
// disjoint stretches rather than to its end from each one (#1446).
fn is_doc_target<'a>(node: &Node<'a>, _code: &[u8], ancestors: Ancestors<'a, '_>) -> bool {
if node.kind_id() == Java::PackageDeclaration {
return !ancestors
.siblings_after(node)
.find(|n| java_declaration(n) || n.kind_id() == Java::PackageDeclaration)
.is_some_and(|n| java_declaration(&n));
}
java_declaration(node)
}
// A `static { … }` initializer opens a function space (#1184) without
// being `is_func`, and an instance initializer is a bare `block` in a
// class or enum body; javadoc reads a doc comment in either no more
// than one in a method body. Every `block` and `switch_block` is
// executable code — a field initializer's `switch` expression holds
// one outside any method — so the kind alone decides.
fn holds_doc_comment_in_body<'a>(
ancestor: &Node<'a>,
ancestors: Ancestors<'a, '_>,
_below: &Node<'a>,
) -> bool {
Self::is_func(ancestor, ancestors)
|| Self::is_closure(ancestor, ancestors)
|| matches!(
ancestor.kind_id().into(),
Java::StaticInitializer | Java::Block | Java::SwitchBlock
)
}
// `EnumDeclaration`, `RecordDeclaration`, and `AnnotationTypeDeclaration`
// are class-like declarations that can contain fields and methods,
// so they open a class space alongside `ClassDeclaration` /
// `InterfaceDeclaration` (issue #280). Without them, `Npa`/`Npm`/`Wmc`
// never see their bodies as class scopes and silently produce zero
// counts. Annotation types map to `Interface` in `get_space_kind`
// (their elements are abstract methods at the bytecode level);
// enums and records map to `Class`.
//
// An `object_creation_expression` carrying a `class_body` child is
// an anonymous class (`new Runnable() { ... }`); it opens its own
// Class space so its members are attributed to it, not the
// enclosing method (#463). A plain `new Foo()` has no `class_body`
// child and must not open a space, so the arm is gated on the
// body's presence. This mirrors PHP's `AnonymousClass` handling and
// brings Java to parity with PHP/C# anonymous forms.
fn is_func_space(node: &Node) -> bool {
if node.kind_id() == Java::ObjectCreationExpression as u16 {
return java_anonymous_class_body(node).is_some();
}
matches!(
node.kind_id().into(),
Java::Program
| Java::ClassDeclaration
| Java::InterfaceDeclaration
| Java::EnumDeclaration
| Java::RecordDeclaration
| Java::AnnotationTypeDeclaration
// `static { … }` carries executable code — conventionally
// the class's initialisation logic — but was referenced
// nowhere outside the generated enum, so it opened no
// space and its control flow was charged to the class
// (#1184). `is_func_space` rather than `is_func`: it is
// not a callable anyone names at a call site.
| Java::StaticInitializer
)
}
// A record's compact constructor (`record R(int a) { R { … } }`, JLS
// 8.10.4) parses as its own `compact_constructor_declaration` kind
// rather than as a `constructor_declaration`, so it has to be listed
// separately. It is a constructor in every sense that matters here —
// it has a body, it can be arbitrarily complex, and it is where
// record validation conventionally lives — so it opens a function
// space alongside the canonical spelling (#1160). Everything keyed on
// `is_func` follows for free: `Nom`, `NArgs`, `Npm`'s
// `direct_child_funcs`, and the space tree that `Wmc` and `NExits`
// are attributed through.
fn is_func<'a>(node: &Node<'a>, _ancestors: Ancestors<'a, '_>) -> bool {
matches!(
node.kind_id().into(),
Java::MethodDeclaration
| Java::ConstructorDeclaration
| Java::CompactConstructorDeclaration
)
}
fn is_closure<'a>(node: &Node<'a>, _ancestors: Ancestors<'a, '_>) -> bool {
node.kind_id() == Java::LambdaExpression
}
fn is_call(node: &Node) -> bool {
node.kind_id() == Java::MethodInvocation
}
/// `x -> x + 1` puts a bare `identifier` in the `parameters` field;
/// `(x) -> …` and `(x, y) -> …` put an `inferred_parameters` list
/// and `() -> …` a `formal_parameters` one (#1185).
fn is_bare_param(node: &Node) -> bool {
node.kind_id() == Java::Identifier as u16
}
fn is_non_arg(node: &Node) -> bool {
// Java's explicit receiver parameter (`void m(S this, int a)`, JLS
// 8.4.1) parses as a `receiver_parameter` child of
// `formal_parameters`, distinct from a real `formal_parameter`. It
// binds `this`, not a value, so it is not a formal parameter and
// must be excluded — matching Rust's `SelfParameter` (#457), Go's
// `receiver` field, and C++'s implicit `this` (#470).
matches!(
node.kind_id().into(),
Java::LPAREN | Java::COMMA | Java::RPAREN | Java::ReceiverParameter
)
}
impl_simple_is_string!(Java, StringLiteral, MultilineStringLiteral);
// tree-sitter-java models `else if` as an `Else` keyword token followed
// by a nested `if_statement` (no wrapping `else_clause` node).
impl_is_else_if_prev_sibling!(Java, IfStatement, Else);
}