mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-21 00:38:18 +08:00
fix(objc): a doc comment inside an @implementation documents the method below it (#2696)
Inside an Objective-C @implementation, tree-sitter-objc wraps each method, function and declaration in an implementation_definition of its own, and the comment above it is the wrapper's sibling, not the method's. getPrecedingDocstring looked only at the method's own siblings, so no method, function or static written in an @implementation had its doc comment, while the same comment above an @interface property was read. - tree-sitter-helpers.ts: implementation_definition joins DOCSTRING_WRAPPER_TYPES. Each wraps exactly one item, so the climb cannot hand a comment to a neighbour: a comment above an @synthesize stays its own. - codegraph-kernel docstring.rs: the faithful port's wrapper list gains it too. The kernel does not walk Objective-C, so the entry only keeps the two lists alike. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -184,6 +184,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
- In Objective-C, a struct, union or enum defined in a variable declaration, like `static struct { Class isa; } placeholder;` or `enum { stateType, stateParamName } state;`, is now indexed with its enum values, next to the variable, as in C and C++. Before, only the variable was, so the type was missing from search and `codegraph_explore`. The same went for a `typedef enum { … } Name;` or `typedef struct … Name;` written after a line holding only a macro, like `OF_ASSUME_NONNULL_BEGIN` or `RCT_EXTERN_C_BEGIN`: ObjFW's `OFZIPArchiveEntryCompressionMethod` and every value in it were missing, and so was React Native's `RCTMethodInfo`. A struct, union or enum with no name of its own takes the name of the variable it declares, so ObjFW's class-cluster placeholders are found as `placeholder`, one declared inside a function or method is named after its variable instead of `<anonymous>`, and the comment above the declaration becomes the type's description. As in C, a type that a `.m` file defines links only from that file, so a call elsewhere that just shares its name, like ObjFW's calls of AmigaOS's `Lock(path, mode)`, doesn't link to the runtime's private `struct Lock`. Re-index Objective-C projects after upgrading.
|
||||
- In Objective-C, a macro written alone on its line, like `NS_ASSUME_NONNULL_BEGIN`, `OF_SUBCLASSING_RESTRICTED` or React Native's `RCT_EXTERN_C_BEGIN`, no longer hides the declaration below it. Before, the parser read such a macro as the start of a declaration, so an `@interface` under it could come out as a function and a `typedef` under it as two variables: many of ObjFW's classes, with their properties and methods and the classes they extend and protocols they adopt, were missing, and so were classes and protocols in React Native and KSCrash headers. A message to `super` from a class whose `@interface` follows an attribute macro like `NS_SWIFT_NAME(…)` still reaches the superclass. Re-index Objective-C projects after upgrading.
|
||||
- In Objective-C, and in the C and C++ headers that sit beside it, an enum declared with Foundation's macros, like `typedef NS_ENUM(NSInteger, SDImageCacheType) { … };`, `NS_OPTIONS`, `NS_CLOSED_ENUM`, `NS_ERROR_ENUM` or CoreFoundation's `CF_ENUM` and `CF_OPTIONS`, is now indexed with its values. Before, the parser read the macro as a broken typedef, so these enums were missing (SDWebImage had none at all) and a type alias named after the first value stood in their place. A Swift annotation naming such an enum, like KSCrash's `let unwindMethods: KSBacktraceUnwindMethods`, now links to it. Re-index Objective-C, C and C++ projects after upgrading.
|
||||
- In Objective-C, the doc comment above a method, function or static written inside an `@implementation` is now read as its description, as the one above an `@interface` property already was. Before, the parser's wrapper around each item of an `@implementation` hid the comment, so search and `codegraph_explore` showed these methods without their documentation. Re-index Objective-C projects after upgrading.
|
||||
- In TypeScript React code, a styled-components or emotion component declared with a type argument, like `const Wrapper = styled.div<WrapperProps>` followed by its CSS, or `styled(Base)<Props>`, is now indexed as a component, as one written without a type argument already was. Before, it was indexed as a plain constant, so `codegraph callers` and impact found none of the code that renders it, and its tags could link to another file's component of the same name instead: in outline/outline, `NudeButton` and `HStack` had no callers, and `<Text>` tags linked to a different `Text` than the one each file imports. Re-index TypeScript React projects after upgrading.
|
||||
- `codegraph sync` and the file watcher now move a React Router route to its page when the router file loads that page lazily into a value of its own, like `const Docs = lazy(() => import('./pages/Docs'))` rendered by `<Route path="/docs" element={<Docs />} />`, and the page's file is added after the router was indexed, comes back after being deleted, or gets or changes its default export in a later edit. Before, the route kept pointing at the `Docs` declaration, or at the page's old component, until the router file itself changed or the project was re-indexed, unless the page's component happened to share the declaration's name. The same now holds for a `lazy: () => import('./pages/Docs')` route whose page switches its default export to another component. Re-index React projects after upgrading to correct routes an earlier version left behind.
|
||||
- `codegraph sync` and the file watcher now link a component to the JSX that renders it, like `<Team />`, when the component is added in a file with no JSX of its own, as in `export default function Team() { return null; }`. Before, the link waited for an unrelated change to a file with JSX, or for a re-index, so the new component looked unused.
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Inside an Objective-C `@implementation`, the parser wraps each method,
|
||||
* function and declaration in an `implementation_definition` of its own, and
|
||||
* the comment above it is that wrapper's sibling, not the method's. So no
|
||||
* method, function or static written in an `@implementation` had its doc
|
||||
* comment, while the same comment above an `@interface` property was read.
|
||||
* The wrapper is now climbed like an `export` statement's.
|
||||
*/
|
||||
import { describe, it, expect, beforeAll } from 'vitest';
|
||||
import { extractFromSource } from '../src/extraction';
|
||||
import { initGrammars, loadGrammarsForLanguages } from '../src/extraction/grammars';
|
||||
|
||||
describe('a doc comment inside an Objective-C @implementation', () => {
|
||||
beforeAll(async () => {
|
||||
await initGrammars();
|
||||
await loadGrammarsForLanguages(['objc']);
|
||||
});
|
||||
|
||||
for (const crlf of [false, true]) {
|
||||
const eol = (s: string) => (crlf ? s.replace(/\n/g, '\r\n') : s);
|
||||
const label = crlf ? ' (CRLF)' : '';
|
||||
|
||||
it(`documents the method, function or static below it${label}`, () => {
|
||||
const source = [
|
||||
'@interface Tests : NSObject',
|
||||
'/** The number of runs. */',
|
||||
'@property (nonatomic) int runs;',
|
||||
'@end',
|
||||
'',
|
||||
'@implementation Tests',
|
||||
'',
|
||||
'/** Flags the callbacks set. */',
|
||||
'static struct {',
|
||||
' int calls;',
|
||||
'} g_callbackData;',
|
||||
'',
|
||||
'/** Runs it. */',
|
||||
'- (void)testIt {',
|
||||
'}',
|
||||
'',
|
||||
'// A helper.',
|
||||
'static void helper(void) {',
|
||||
'}',
|
||||
'',
|
||||
'/**',
|
||||
' * The shared instance.',
|
||||
' */',
|
||||
'+ (instancetype)shared {',
|
||||
' return nil;',
|
||||
'}',
|
||||
'',
|
||||
'/** Synthesized. */',
|
||||
'@synthesize runs = _runs;',
|
||||
'',
|
||||
'- (void)undocumented {',
|
||||
'}',
|
||||
'',
|
||||
'@end',
|
||||
'',
|
||||
].join('\n');
|
||||
const result = extractFromSource('src/Tests.m', eol(source), 'objc');
|
||||
const docs = result.nodes
|
||||
.filter((n) => n.kind !== 'file' && n.kind !== 'class')
|
||||
.map((n) => `${n.kind} ${n.qualifiedName}: ${n.docstring ?? '-'}`);
|
||||
expect(docs).toEqual([
|
||||
'property Tests::runs: The number of runs.',
|
||||
'struct Tests::g_callbackData: Flags the callbacks set.',
|
||||
'method Tests::testIt: Runs it.',
|
||||
'function Tests::helper: A helper.',
|
||||
'method Tests::shared: The shared instance.',
|
||||
'method Tests::undocumented: -',
|
||||
]);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -15,6 +15,8 @@ fn is_wrapper(kind: &str) -> bool {
|
||||
| "variable_declaration"
|
||||
| "variable_declarator"
|
||||
| "ambient_declaration"
|
||||
// Objective-C, which the kernel does not walk: kept for parity.
|
||||
| "implementation_definition"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -87,8 +87,10 @@ export const BUILTIN_TYPE_NAMES: ReadonlySet<string> = new Set([
|
||||
* inner node, so before looking for its preceding comment we climb out through
|
||||
* these. Examples: `export class X {}` (export_statement), `@dec\ndef f()`
|
||||
* (decorated_definition), `const f = () => {}` (lexical_declaration →
|
||||
* variable_declarator). Each wraps exactly one declaration, so climbing can't
|
||||
* mis-attribute a comment to a sibling. (#780)
|
||||
* variable_declarator), and each method, function or declaration of an
|
||||
* Objective-C `@implementation` (implementation_definition). Each wraps
|
||||
* exactly one declaration, so climbing can't mis-attribute a comment to a
|
||||
* sibling. (#780)
|
||||
*/
|
||||
const DOCSTRING_WRAPPER_TYPES = new Set([
|
||||
'export_statement', // JS/TS: export class/function/const ...
|
||||
@@ -97,6 +99,7 @@ const DOCSTRING_WRAPPER_TYPES = new Set([
|
||||
'variable_declaration', // JS/TS: var x = ...
|
||||
'variable_declarator', // JS/TS: the `x = () => {}` inside the declaration
|
||||
'ambient_declaration', // TS: declare ...
|
||||
'implementation_definition', // ObjC: each method, function or declaration of an @implementation
|
||||
]);
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user