* Extract a class field declaration (e.g. Java field_declaration, C# field_declaration). * Extracts each declarator as a 'field' kind node inside the owning class.
(node: SyntaxNode)
| 2050 | * Extracts each declarator as a 'field' kind node inside the owning class. |
| 2051 | */ |
| 2052 | private extractField(node: SyntaxNode): void { |
| 2053 | if (!this.extractor) return; |
| 2054 | |
| 2055 | const docstring = getPrecedingDocstring(node, this.source); |
| 2056 | const visibility = this.extractor.getVisibility?.(node); |
| 2057 | const isStatic = this.extractor.isStatic?.(node) ?? false; |
| 2058 | |
| 2059 | // A class field that is actually a CONSTANT (Java `static final`, C# `const` |
| 2060 | // / `static readonly`) is extracted as `constant` kind, not `field`, so |
| 2061 | // value-reference edges treat it as a target (the gate accepts |
| 2062 | // constant/variable, not field). Scoped to languages whose `isConst` |
| 2063 | // predicate is field-shaped — other languages' fields stay `field`. |
| 2064 | const fieldKind: NodeKind = |
| 2065 | (this.language === 'java' || this.language === 'csharp') && |
| 2066 | (this.extractor.isConst?.(node) ?? false) |
| 2067 | ? 'constant' |
| 2068 | : 'field'; |
| 2069 | |
| 2070 | // Java field_declaration: "private final String name = value;" → variable_declarator(s) are direct children |
| 2071 | // C# field_declaration: wraps in variable_declaration → variable_declarator(s) |
| 2072 | let declarators = node.namedChildren.filter( |
| 2073 | c => c.type === 'variable_declarator' |
| 2074 | ); |
| 2075 | // C#: look inside variable_declaration wrapper |
| 2076 | if (declarators.length === 0) { |
| 2077 | const varDecl = node.namedChildren.find(c => c.type === 'variable_declaration'); |
| 2078 | if (varDecl) { |
| 2079 | declarators = varDecl.namedChildren.filter(c => c.type === 'variable_declarator'); |
| 2080 | } |
| 2081 | } |
| 2082 | |
| 2083 | // PHP property_declaration: property_element → variable_name → name |
| 2084 | if (declarators.length === 0) { |
| 2085 | const propElements = node.namedChildren.filter(c => c.type === 'property_element'); |
| 2086 | if (propElements.length > 0) { |
| 2087 | // Get type annotation if present (e.g. "string", "int", "?Foo") |
| 2088 | const typeNode = node.namedChildren.find( |
| 2089 | c => c.type !== 'visibility_modifier' && c.type !== 'static_modifier' |
| 2090 | && c.type !== 'readonly_modifier' && c.type !== 'property_element' |
| 2091 | && c.type !== 'var_modifier' |
| 2092 | ); |
| 2093 | const typeText = typeNode ? getNodeText(typeNode, this.source) : undefined; |
| 2094 | |
| 2095 | for (const elem of propElements) { |
| 2096 | const varName = elem.namedChildren.find(c => c.type === 'variable_name'); |
| 2097 | const nameNode = varName?.namedChildren.find(c => c.type === 'name'); |
| 2098 | if (!nameNode) continue; |
| 2099 | const name = getNodeText(nameNode, this.source); |
| 2100 | const signature = typeText ? `${typeText} $${name}` : `$${name}`; |
| 2101 | this.createNode('field', name, elem, { |
| 2102 | docstring, |
| 2103 | signature, |
| 2104 | visibility, |
| 2105 | isStatic, |
| 2106 | }); |
| 2107 | } |
| 2108 | return; |
| 2109 | } |
no test coverage detected