Files
codegraph/__tests__/objc-implementation-docs.test.ts
T
Colby Mchenry ed8957710f fix(objc): a file-scope constant or global is indexed, in an @implementation too (#2699)
Objective-C read a file-scope declaration through the generic variable
fallback, which takes only a declarator that is a bare name (`int count;`).
Its most common globals were never indexed: `NSString *const
SDWebImageErrorDomain = @"…";`, `static const NSInteger kMaxRetries = 3;`,
`static dispatch_queue_t queues[4];`. A declaration written inside an
@implementation, file-scope C in the language, was not read at all,
because the class counts as an enclosing scope.

- tree-sitter.ts: Objective-C takes C's declarator branch in
  extractVariable (initialized, pointer and array declarators, with the
  initializer as the signature), keeping the bare names it always read.
  A declaration whose parent is an implementation_definition takes the
  variable branch; its globals are qualified under the class, as the
  C functions written there already are. A block or function-pointer
  variable reads as a prototype, as in C, and stays out.
- c-cpp.ts: isConstDeclaration, C's isConst, is shared, and objc.ts uses
  it, so `static const NSInteger kMaxRetries` is a constant.
- name-matcher.ts: a global that a .m or .mm file defines joins the C
  types that #2690 made file-local (OBJC_FILE_LOCAL_KINDS). A new
  isObjcFileLocalElsewhere drops such a candidate in the exact-match and
  fuzzy chains, before one is picked, and isVisibleAcrossFiles applies it
  to the pick. Before the pick, so that a header's `extern NSString
  *MJConfigFile;` stands for the definition in variables.m. Rejecting only
  the pick left Hammerspoon's MJConfigFile references unresolved. Without
  the rule, React Native's completionHandler(nil, image) block-parameter
  calls reached a completionHandler in another .mm, and #include <vector>
  a .mm's vector.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 01:43:14 +00:00

77 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.',
'variable 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: -',
]);
});
}
});