Skip to main content

big_code_analysis_ast/getter/
csharp.rs

1//! `Getter` implementation for C#.
2#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
3
4use super::*;
5
6impl Getter for CsharpCode {
7    fn get_space_kind(node: &Node) -> SpaceKind {
8        use Csharp::*;
9
10        // `EnumDeclaration` maps to `SpaceKind::Class` for cross-language
11        // parity with Java/PHP/Groovy (issue #429): a C# enum opens a
12        // FuncSpace via `is_func_space`, so it must classify here too or
13        // it falls through to `_ => SpaceKind::Unknown`.
14        // A bodied indexer (#464) or property (#472) defers to its
15        // `accessor_declaration` children; only the accessor-less
16        // expression-bodied form opens a Function space directly. Keep this
17        // gate in lockstep with `CsharpCode::is_func` / `is_func_space` so the
18        // walker and the space-kind classifier agree on which nodes promote.
19        if matches!(
20            node.kind_id().into(),
21            IndexerDeclaration | PropertyDeclaration
22        ) {
23            return if crate::checker::csharp_member_has_accessors(node) {
24                SpaceKind::Unknown
25            } else {
26                SpaceKind::Function
27            };
28        }
29        match node.kind_id().into() {
30            ClassDeclaration | StructDeclaration | RecordDeclaration | EnumDeclaration => {
31                SpaceKind::Class
32            }
33            InterfaceDeclaration => SpaceKind::Interface,
34            MethodDeclaration
35            | ConstructorDeclaration
36            | DestructorDeclaration
37            | LocalFunctionStatement
38            | LambdaExpression
39            | AnonymousMethodExpression
40            | AccessorDeclaration
41            | OperatorDeclaration
42            | ConversionOperatorDeclaration => SpaceKind::Function,
43            CompilationUnit => SpaceKind::Unit,
44            _ => SpaceKind::Unknown,
45        }
46    }
47
48    fn get_op_type<'a>(node: &Node<'a>, ancestors: Ancestors<'a, '_>) -> TokenRole {
49        use Csharp::*;
50
51        match node.kind_id().into() {
52            // Control-flow keywords
53            If | Else | Switch | Case | Default | Try | Catch | Finally | Throw
54            | Return | Yield | Break | Continue | Goto | For | Foreach | While | Do
55            // Declaration / namespace keywords
56            | Class | Struct | Interface | Enum | Record | Delegate | Namespace | Using
57            // Modifiers
58            | Public | Private | Protected | Internal | Static | Abstract | Virtual
59            | Override | Sealed | Partial | Readonly | Const | Extern | Unsafe
60            | Volatile | Async | Required | File | New | Fixed | Implicit | Explicit
61            // Expression-keyword operators
62            | Await | Is | As | Typeof | Sizeof | Checked | Unchecked | Ref | Out | In
63            | Params | Lock | Stackalloc | Where | With | When | Operator
64            | Scoped | Not | And | Or
65            // Property/event accessor keywords
66            | Get | Set | Init | Add | Remove
67            // Structural punctuation
68            | LBRACE | LBRACK | LPAREN | COMMA | SEMI | COLON | COLONCOLON | DOT
69            | DOTDOT | EQGT | DASHGT | QMARK
70            // Arithmetic / comparison / logical / bitwise / assignment operators
71            | EQ | EQEQ | BANGEQ | LT | GT | LTEQ | GTEQ
72            | PLUS | DASH | STAR | SLASH | PERCENT
73            | AMP | PIPE | CARET | TILDE | BANG
74            | AMPAMP | PIPEPIPE | QMARKQMARK
75            | LTLT | GTGT | GTGTGT
76            | PLUSPLUS | DASHDASH
77            | PLUSEQ | DASHEQ | STAREQ | SLASHEQ | PERCENTEQ
78            | AMPEQ | PIPEEQ | CARETEQ | LTLTEQ | GTGTEQ | GTGTGTEQ | QMARKQMARKEQ
79            // Predefined / primitive types
80            | PredefinedType
81                => TokenRole::Operator,
82            // `boolean_literal: choice('true', 'false')` wraps the
83            // keyword leaf, so a literal reaches the walker twice;
84            // listing both kinds inflated `N2` by one per literal while
85            // the text-keyed `n2` hid it (#1253). The leaf cannot simply
86            // be dropped: the overloadable-operator list emits a bare
87            // `true` / `false` under `operator_declaration` — the
88            // grammar's only unwrapped position — and suppressing that
89            // would leave two such declarations with identical Halstead
90            // vocabularies whenever their bodies match. That position
91            // keeps its pre-#1253 `Operand` classification here; whether
92            // an overloaded operator's *name* is better counted as an
93            // operator, as `operator +` already is, is #1296.
94            True | False => match ancestors.parent(node).map(|p| p.kind_id().into()) {
95                Some(BooleanLiteral) => TokenRole::Unknown,
96                _ => TokenRole::Operand,
97            },
98            // `this` is a self-reference everywhere (`this.x`,
99            // `this[i]`, `: this(1)`, `f(this)`) except directly under
100            // an `indexer_declaration`, where the keyword *names* the
101            // member being declared rather than denoting a value —
102            // `public int this[int i] { … }`. That position keeps its
103            // operator classification alongside the `operator` keyword
104            // of an overload declaration, which this match already
105            // bills as an operator (#1380). The extension-method
106            // receiver (`static void M(this Foo f)`) is a childless
107            // `modifier` node, kind 249, not this kind at all; it is
108            // classified — as an operand, so the two positions agree —
109            // by `get_op_type_with_code` below (#1418).
110            //
111            // Java's receiver parameter (`void m(J J.this)`) is the
112            // other declarator use of the keyword in this workspace and
113            // `java.rs` calls it an operand — a parameter *name* is an
114            // operand in every language here, where a member named by a
115            // keyword belongs with `operator +`.
116            This => match ancestors.parent(node).map(|p| p.kind_id().into()) {
117                Some(IndexerDeclaration) => TokenRole::Operator,
118                _ => TokenRole::Operand,
119            },
120            // Operands: identifiers and literals. `NullLiteral` is a
121            // childless leaf, so it needs no such guard.
122            //
123            // `QualifiedName` (`System.Text`), `GenericName`
124            // (`List<int>`) and `AliasQualifiedName` (`global::Foo`)
125            // were listed here until #1263. Each is a container whose
126            // every part the walker already reaches and classifies —
127            // the identifier leaves as operands, `.` / `::` / `<` / `>`
128            // and any `predefined_type` argument as operators — so
129            // listing the container too billed one occurrence twice
130            // (grammar-dispatch section 5). Probed with `bca ops`:
131            // dropping them leaves `System`+`Text`+`.`,
132            // `List`+`<`+`int`+`>`, and `global`+`::`+`Foo` intact.
133            //
134            // `Base` joined this list in #1380 for the same structural
135            // reason `This` did: it is the receiver of `base.M()` /
136            // `base[i]` / `: base(x)`. The `base_list` container that
137            // spells inheritance (`class D : B`) holds the base type's
138            // identifier and no `base` keyword, so nothing bills this
139            // text twice.
140            Identifier | Base
141            | IntegerLiteral | RealLiteral | BooleanLiteral | NullLiteral
142            | CharacterLiteral | StringLiteral | VerbatimStringLiteral | RawStringLiteral
143                => TokenRole::Operand,
144            // `$"..."` counts as one operand when inert. When it carries
145            // any `Interpolation` child the inner expressions are
146            // already walked and classified as operands; counting the
147            // wrapping literal too would double-count the inner
148            // identifiers' contribution to `N2` (issue #183, same
149            // pattern as #180 for Elixir/Bash).
150            InterpolatedStringExpression => {
151                Self::string_operand_type(node, &[Interpolation as u16])
152            }
153            _ => TokenRole::Unknown,
154        }
155    }
156
157    // The parameter-position spelling of six keywords (#1418). The
158    // grammar's `_parameter_type_with_modifiers` rule aliases the bare
159    // tokens `this scoped ref out in readonly` to `$.modifier`, so in
160    // that one position each is a **childless** `modifier` node rather
161    // than the kind its own arm above lists. Nothing billed them, so an
162    // extension receiver, a `ref` parameter and an `out` parameter each
163    // contributed to neither Halstead half.
164    //
165    // A blanket `Modifier` arm is not available: the `modifier` *rule*
166    // is a wrapper around a `public` / `static` / `async` /
167    // field-`readonly` keyword leaf this match already classifies, so
168    // listing the wrapper too would bill those twice (grammar-dispatch
169    // section 5). The gate is child-presence, which section 6 prescribes
170    // over deleting either side, and which
171    // `csharp_is_aliased_modifier` reads as "aliased bare token" rather
172    // than "parameter modifier" — see its doc for the second alias site.
173    //
174    // `Checker::is_bare_param` deliberately refuses to infer from
175    // `child_count() == 0`, and this is the case that refusal excludes
176    // rather than a contradiction of it: there, childlessness would be
177    // a *guess* that a MISSING or zero-width ERROR node is a real
178    // parameter. Here the grammar aliases a bare token onto this named
179    // kind, so having no children is the shape the alias produces, not
180    // evidence about a broken parse.
181    //
182    // The role follows the text, never the position: each keyword keeps
183    // the role its own kind carries elsewhere in this match, so
184    // `M(ref Foo f)` and `f(ref x)` agree, and `this` stays the operand
185    // #1380 made it. That decision's `IndexerDeclaration` carve-out
186    // cannot arise here — an indexer declarator is not a parameter, and
187    // this arm is reached only from `_parameter_type_with_modifiers`.
188    //
189    // Reading the bytes needs this spelling, which is the one the walk
190    // calls (`compute_halstead` -> `get_op_type_with_code`), so the
191    // classification reaches every count; `get_op_type` above stays the
192    // byte-less answer for the callers that have no source to offer
193    // (grammar-dispatch section 7).
194    //
195    // `CsharpCode::is_primitive` routes the operator half through the
196    // lexeme-keyed map, because these five keywords now have two kind
197    // spellings for one operator; its comment carries that argument.
198    fn get_op_type_with_code<'a>(
199        node: &Node<'a>,
200        code: &[u8],
201        ancestors: Ancestors<'a, '_>,
202    ) -> TokenRole {
203        if !crate::checker::csharp_is_aliased_modifier(node) {
204            return Self::get_op_type(node, ancestors);
205        }
206        match node_text(code, node) {
207            Some("this") => TokenRole::Operand,
208            Some("ref" | "out" | "in" | "scoped" | "readonly") => TokenRole::Operator,
209            // Live, not merely defensive: `_lambda_expression_init` and
210            // `anonymous_method_expression` alias a bare `static` /
211            // `async` onto `modifier` too, so `static (int x) => x`
212            // arrives here. Leaving those unclassified is what they
213            // already were before #1418, and closing that gap is a
214            // decision about lambdas rather than about parameters — the
215            // *declaration* spelling of both keywords is billed through
216            // its leaf, so they are unbilled only in this one position.
217            // A grammar bump that adds a seventh parameter spelling
218            // lands here as well, unclassified rather than billed as
219            // whichever arm happened to be last.
220            _ => TokenRole::Unknown,
221        }
222    }
223
224    get_operator!(Csharp);
225}