Files
codegraph/__tests__/objc-implementation-docs.test.ts
T
Colby Mchenry 3273f20f47 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>
2026-10-20 01:16:23 +00:00

76 lines
2.5 KiB
TypeScript

/**
* 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: -',
]);
});
}
});