big_code_analysis_ast/getter/java.rs
1//! `Getter` implementation for Java.
2#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
3
4use super::*;
5
6const GENERIC_ANGLES: GenericAngleKinds = GenericAngleKinds {
7 lists: &[Java::TypeArguments as u16, Java::TypeParameters as u16],
8 openers: &[Java::LT as u16],
9 closers: &[Java::GT as u16],
10 is_misparse: never_misparsed,
11};
12
13impl Getter for JavaCode {
14 /// Names the space, synthesising one for constructs that carry no
15 /// name token (#1184).
16 ///
17 /// `get_func_space_name` returns `Option<&'a str>` borrowed from
18 /// `code`, so the only name available for a nameless construct is a
19 /// `&'static str` — a per-property spelling like `<get-foo>` would
20 /// need a signature change. Angle brackets follow the existing
21 /// `<anonymous>` convention and cannot collide with a real
22 /// identifier in any of these grammars.
23 ///
24 /// Sibling collisions are accepted, exactly as multiple
25 /// `<anonymous>` siblings already are: two properties each with a
26 /// getter, or two `static { }` blocks in one class, produce two
27 /// spaces with the same name. Nothing enforces name uniqueness
28 /// among siblings, and inventing an index would make the name
29 /// unstable under an unrelated edit.
30 ///
31 /// `<static-init>` rather than the JVM's `<clinit>`: the same
32 /// construct exists in JavaScript, where `<clinit>` would mean
33 /// nothing, and one spelling across languages is worth more here
34 /// than JVM precision.
35 fn get_func_space_name<'a, 'tree>(
36 node: &Node<'tree>,
37 code: &'a [u8],
38 ancestors: Ancestors<'tree, '_>,
39 ) -> Option<&'a str> {
40 if node.kind_id() == Java::StaticInitializer as u16 {
41 return Some("<static-init>");
42 }
43 crate::getter::default_func_space_name(node, code, ancestors)
44 }
45
46 fn get_space_kind(node: &Node) -> SpaceKind {
47 use Java::*;
48
49 // `EnumDeclaration` and `RecordDeclaration` are class-like
50 // (they extend `Object`, hold fields, and can declare methods)
51 // so they share `SpaceKind::Class`. `AnnotationTypeDeclaration`
52 // implicitly extends `java.lang.annotation.Annotation` (an
53 // interface) and its elements are abstract methods at the
54 // bytecode level, so it maps to `SpaceKind::Interface`.
55 match node.kind_id().into() {
56 ClassDeclaration | EnumDeclaration | RecordDeclaration => SpaceKind::Class,
57 // `CompactConstructorDeclaration` is a record's compact
58 // constructor (`R { … }`); it is a constructor with an
59 // implicit parameter list, so it shares the canonical
60 // constructor's space kind (#1160). It carries a required
61 // `name` field holding the record's simple name, so the
62 // default `get_func_space_name` already names the space `R` —
63 // identical to what the canonical spelling would produce.
64 // `StaticInitializer` is `static { … }` (#1184).
65 MethodDeclaration
66 | ConstructorDeclaration
67 | CompactConstructorDeclaration
68 | LambdaExpression
69 | StaticInitializer => SpaceKind::Function,
70 InterfaceDeclaration | AnnotationTypeDeclaration => SpaceKind::Interface,
71 // An anonymous class (`new Runnable() { ... }`) is an
72 // `object_creation_expression` carrying a `class_body` child;
73 // it opens a Class space, matching PHP's `AnonymousClass` and
74 // C#'s anonymous forms (#463). A plain `new Foo()` has no
75 // `class_body`, so it falls through to `Unknown` (no space).
76 // The shared helper keeps this gate identical to the one in
77 // `JavaCode::is_func_space`.
78 ObjectCreationExpression
79 if crate::checker::java_anonymous_class_body(node).is_some() =>
80 {
81 SpaceKind::Class
82 }
83 Program => SpaceKind::Unit,
84 _ => SpaceKind::Unknown,
85 }
86 }
87
88 fn get_op_type<'a>(node: &Node<'a>, ancestors: Ancestors<'a, '_>) -> TokenRole {
89 use Java::*;
90
91 if GENERIC_ANGLES.is_closer(node, ancestors) {
92 return TokenRole::Unknown;
93 }
94 // Some guides that informed grammar choice for Halstead
95 // keywords, operators, literals: https://docs.oracle.com/javase/specs/jls/se18/html/jls-3.html#jls-3.12
96 // https://www.geeksforgeeks.org/software-engineering-halsteads-software-metrics/?msclkid=5e181114abef11ecbb03527e95a34828
97 match node.kind_id().into() {
98 // `class` declares a class (billed below, #1552) except as the
99 // tail of a class literal (`C.class`): that `class_literal` is
100 // already one operand keyed by its whole text, so billing its
101 // leaf too would pay the same bytes twice (grammar-dispatch
102 // section 5, the compound-leaf guard).
103 Class if ancestors.parent_has_kind(node, ClassLiteral as u16) => TokenRole::Unknown,
104 // A type name is an operand (#1560, the operand arm below)
105 // except inside a class literal, whose whole text is already
106 // one operand — the `class` guard above, for the same
107 // reason. The type sits there directly (`Foo.class`) or
108 // under an array or scoped type (`Foo[].class`,
109 // `java.util.List.class`), so the climb skips those two.
110 TypeIdentifier
111 if ancestors
112 .iter(node)
113 .map(|(ancestor, _)| ancestor.kind_id())
114 .find(|&id| id != ArrayType as u16 && id != ScopedTypeIdentifier as u16)
115 == Some(ClassLiteral as u16) =>
116 {
117 TokenRole::Unknown
118 }
119 // Operator: control flow
120 | If | Else | Switch | Case | Try | Catch | Throw | Throws | Throws2 | For
121 | While | Continue | Break | Do | Finally
122 // Operator: keywords
123 | New | Return | Default | Abstract | Assert | Instanceof | Extends | Final
124 | Implements | Transient | Synchronized | VoidType
125 // Operator: type-declaration keywords, billed as C# bills
126 // its own (#1552). Only the leaves: no `*_declaration`
127 // wrapper is listed, so a declaration bills its keyword once.
128 | Class | Interface | Enum | Record | ATinterface
129 // Operator: brackets and comma and terminators (separators)
130 | SEMI | COMMA | COLONCOLON | DOT | DASHGT | LBRACE | LBRACK | LPAREN
131 // Operator: operators
132 | EQ | LT | GT | BANG | TILDE | QMARK | COLON
133 | EQEQ | LTEQ | GTEQ | BANGEQ | AMPAMP | PIPEPIPE | PLUSPLUS | DASHDASH
134 | PLUS | DASH | STAR | SLASH | AMP | PIPE | CARET | PERCENT | LTLT | GTGT | GTGTGT
135 | PLUSEQ | DASHEQ | STAREQ | SLASHEQ | AMPEQ | PIPEEQ | CARETEQ | PERCENTEQ
136 | LTLTEQ | GTGTEQ | GTGTGTEQ
137 // primitive types
138 | Byte | Short | Int | Long | Char | Float | Double | BooleanType
139 => {
140 TokenRole::Operator
141 },
142 // `super` is the receiver of `super.f()` / `super(x)` /
143 // `super::f` everywhere except a wildcard type bound, where
144 // `? super String` denotes no value and is the mirror image
145 // of `? extends String` — whose `extends` this same match
146 // bills as an operator. Both keywords are direct children of
147 // the same `wildcard` node, so the parent alone separates
148 // the bound from the reference (#1380).
149 Super => match ancestors.parent(node).map(|p| p.kind_id().into()) {
150 Some(Wildcard) => TokenRole::Operator,
151 _ => TokenRole::Operand,
152 },
153 // Operands: variables, constants, literals. `This` joined
154 // them in #1380: a self-reference names the receiver a `.`
155 // or `::` acts on, so billing it as an operator made
156 // `this.x` a binary operator with one operand while `p.x`
157 // has one operator and two. The explicit receiver parameter
158 // (`void m(J J.this)`) reaches this arm too, and is an
159 // operand for the same reason a parameter name is — which is
160 // the opposite call from C#'s indexer declarator in
161 // `csharp.rs`, deliberately: a parameter name is an operand
162 // everywhere here, while a *member* named by a keyword sits
163 // with `operator +`.
164 //
165 // `TypeIdentifier` joined in #1560, as C#, Rust, the C family,
166 // Go, Groovy and TypeScript bill type names: a field or
167 // parameter type, an `implements` / `extends` entry, a type
168 // parameter, a generic argument, each segment of
169 // `java.util.List` (the `.` bills as itself). It is the one
170 // type-name kind and no listed node wraps it, so each name
171 // bills once.
172 Identifier | TypeIdentifier | NullLiteral | ClassLiteral | True | False
173 | StringLiteral | CharacterLiteral | HexIntegerLiteral | OctalIntegerLiteral
174 | BinaryIntegerLiteral | DecimalIntegerLiteral | HexFloatingPointLiteral
175 | DecimalFloatingPointLiteral | This => {
176 TokenRole::Operand
177 },
178 _ => {
179 TokenRole::Unknown
180 },
181 }
182 }
183
184 // `var x = …` parses `var` as a `type_identifier` (it is a reserved
185 // type name, not a keyword token), so the #1560 operand arm would
186 // bill an operand spelled `var` that names no type. It stays
187 // unbilled, as C#'s `var` (an `implicit_type` wrapping its own
188 // keyword) is; a variable *named* `var` is an `identifier` and is
189 // unaffected. Only the bytes tell the two type names apart, so the
190 // byte-less `get_op_type` above still answers `Operand` for it; the
191 // walk calls this spelling (grammar-dispatch section 7).
192 fn get_op_type_with_code<'a>(
193 node: &Node<'a>,
194 code: &[u8],
195 ancestors: Ancestors<'a, '_>,
196 ) -> TokenRole {
197 if node.kind_id() == Java::TypeIdentifier && node_text(code, node) == Some("var") {
198 return TokenRole::Unknown;
199 }
200 Self::get_op_type(node, ancestors)
201 }
202
203 fn get_operator_spelling<'a>(
204 node: &Node<'a>,
205 ancestors: Ancestors<'a, '_>,
206 ) -> Option<&'static str> {
207 GENERIC_ANGLES.opener_spelling(node, ancestors)
208 }
209
210 fn get_operator_id_as_str(id: u16) -> &'static str {
211 let typ = id.into();
212 match typ {
213 Java::LPAREN => "()",
214 Java::LBRACK => "[]",
215 Java::LBRACE => "{}",
216 Java::VoidType => "void",
217 _ => typ.into(),
218 }
219 }
220}