Skip to main content

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`), where it is an operand:
100            // JLS §15.8.2 makes the literal a primary expression, not a
101            // literal (§3.10), so its parts bill as they would anywhere
102            // else — the type name an operand, the primitive or `void`
103            // and the `.` / `[]` operators — and the `class` tail is the
104            // operand they act on, as Kotlin's and PHP's `Foo::class` and
105            // Groovy's `Foo.class` bill theirs (#1566).
106            Class if ancestors.parent_has_kind(node, ClassLiteral as u16) => TokenRole::Operand,
107            // `static` is a modifier (below, #1565) except in an import
108            // or module directive (`import static`, `requires static`),
109            // whose own keyword — `import`, `requires` — bills nothing
110            // here, so neither does the word it qualifies.
111            Static
112                if ancestors.parent(node).is_some_and(|p| {
113                    matches!(p.kind_id().into(), ImportDeclaration | RequiresModifier)
114                }) =>
115            {
116                TokenRole::Unknown
117            }
118            // Operator: control flow
119            | If | Else | Switch | Case | Try | Catch | Throw | Throws | Throws2 | For
120            | While | Continue | Break | Do | Finally
121            // Operator: keywords
122            | New | Return | Default | Abstract | Assert | Instanceof | Extends | Final
123            | Implements | Transient | Synchronized | VoidType
124            // Operator: the rest of the modifiers, billed as C# and Kotlin
125            // bill theirs (#1565). Each is its keyword leaf, one kind
126            // apiece; the `modifiers` wrapper stays unlisted, so a
127            // modifier bills once and an annotation inside it adds only
128            // its own tokens. `default` above is one kind for a `switch`
129            // label and an interface method's modifier. `sealed` and
130            // `permits` (`Permits2`; `Permits` is the clause) are
131            // contextual, and the grammar parses each used as a name
132            // (`var sealed = 1`, `int permits`) as an `identifier`, so
133            // they need no guard; `non-sealed` cannot be a name.
134            | Public | Private | Protected | Static | Native | Strictfp | Volatile | Sealed
135            | NonDASHsealed | Permits2
136            // Operator: type-declaration keywords, billed as C# bills
137            // its own (#1552). Only the leaves: no `*_declaration`
138            // wrapper is listed, so a declaration bills its keyword once.
139            | Class | Interface | Enum | Record | ATinterface
140            // Operator: brackets and comma and terminators (separators)
141            | SEMI | COMMA | COLONCOLON | DOT | DASHGT | LBRACE | LBRACK | LPAREN
142            // Operator: operators
143            | EQ | LT | GT | BANG | TILDE | QMARK | COLON
144            | EQEQ | LTEQ | GTEQ | BANGEQ | AMPAMP | PIPEPIPE | PLUSPLUS | DASHDASH
145            | PLUS | DASH | STAR | SLASH | AMP | PIPE | CARET | PERCENT | LTLT | GTGT | GTGTGT
146            | PLUSEQ | DASHEQ | STAREQ | SLASHEQ | AMPEQ | PIPEEQ | CARETEQ | PERCENTEQ
147            | LTLTEQ | GTGTEQ | GTGTGTEQ
148            // primitive types
149            | Byte | Short | Int | Long | Char | Float | Double | BooleanType
150            => {
151                TokenRole::Operator
152            },
153            // `super` is the receiver of `super.f()` / `super(x)` /
154            // `super::f` everywhere except a wildcard type bound, where
155            // `? super String` denotes no value and is the mirror image
156            // of `? extends String` — whose `extends` this same match
157            // bills as an operator. Both keywords are direct children of
158            // the same `wildcard` node, so the parent alone separates
159            // the bound from the reference (#1380).
160            Super => match ancestors.parent(node).map(|p| p.kind_id().into()) {
161                Some(Wildcard) => TokenRole::Operator,
162                _ => TokenRole::Operand,
163            },
164            // Operands: variables, constants, literals. `This` joined
165            // them in #1380: a self-reference names the receiver a `.`
166            // or `::` acts on, so billing it as an operator made
167            // `this.x` a binary operator with one operand while `p.x`
168            // has one operator and two. The explicit receiver parameter
169            // (`void m(J J.this)`) reaches this arm too, and is an
170            // operand for the same reason a parameter name is — which is
171            // the opposite call from C#'s indexer declarator in
172            // `csharp.rs`, deliberately: a parameter name is an operand
173            // everywhere here, while a *member* named by a keyword sits
174            // with `operator +`.
175            //
176            // `TypeIdentifier` joined in #1560, as C#, Rust, the C family,
177            // Go, Groovy and TypeScript bill type names: a field or
178            // parameter type, an `implements` / `extends` entry, a type
179            // parameter, a generic argument, each segment of
180            // `java.util.List` (the `.` bills as itself), and since #1566
181            // the type of a class literal (`Foo.class`). It is the one
182            // type-name kind and no listed node wraps it, so each name
183            // bills once.
184            Identifier | TypeIdentifier | NullLiteral | True | False
185            | StringLiteral | CharacterLiteral | HexIntegerLiteral | OctalIntegerLiteral
186            | BinaryIntegerLiteral | DecimalIntegerLiteral | HexFloatingPointLiteral
187            | DecimalFloatingPointLiteral | This => {
188                TokenRole::Operand
189            },
190            _ => {
191                TokenRole::Unknown
192            },
193        }
194    }
195
196    // `var x = …` parses `var` as a `type_identifier` (it is a reserved
197    // type name, not a keyword token), so the #1560 operand arm would
198    // bill an operand spelled `var` that names no type. It stays
199    // unbilled, as C#'s `var` (an `implicit_type` wrapping its own
200    // keyword) is; a variable *named* `var` is an `identifier` and is
201    // unaffected. Only the bytes tell the two type names apart, so the
202    // byte-less `get_op_type` above still answers `Operand` for it; the
203    // walk calls this spelling (grammar-dispatch section 7).
204    fn get_op_type_with_code<'a>(
205        node: &Node<'a>,
206        code: &[u8],
207        ancestors: Ancestors<'a, '_>,
208    ) -> TokenRole {
209        if node.kind_id() == Java::TypeIdentifier && node_text(code, node) == Some("var") {
210            return TokenRole::Unknown;
211        }
212        Self::get_op_type(node, ancestors)
213    }
214
215    fn get_operator_spelling<'a>(
216        node: &Node<'a>,
217        ancestors: Ancestors<'a, '_>,
218    ) -> Option<&'static str> {
219        GENERIC_ANGLES.opener_spelling(node, ancestors)
220    }
221
222    fn get_operator_id_as_str(id: u16) -> &'static str {
223        let typ = id.into();
224        match typ {
225            Java::LPAREN => "()",
226            Java::LBRACK => "[]",
227            Java::LBRACE => "{}",
228            Java::VoidType => "void",
229            // The kind is `boolean_type`; spelled as written, as Groovy's
230            // text-keyed primitives are (#1568).
231            Java::BooleanType => "boolean",
232            _ => typ.into(),
233        }
234    }
235}