Files
codegraph/__tests__/objc-super-send.test.ts
Colby Mchenry de74c050bc fix(objc): a message to super reaches the superclass (#2179)
`[super init]` was judged like a self-send, so the sending class's own
override passed (SDDiskCache's [super init] came back to itself), and
where tree-sitter-objc lost an @implementation's range no hierarchy was
read at all, so any class's init passed (NSObject subclasses' [super
init] -> SDDiskCache::init).

- a super-send excludes the class it is written in
- a lost @implementation's class is read from the enclosing method's
  qualified name (SDWebImageDownloaderDecryptor::initWithBlock:)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 17:04:59 +00:00

85 lines
2.2 KiB
TypeScript

/**
* `[super init]` goes past the class it is written in: to the superclass's
* `init`, or — for an NSObject subclass — to no project method at all.
* SDWebImage's NSObject subclasses sent `[super init]` to SDDiskCache's
* `init` (their `@implementation` range was lost, so no hierarchy was read
* and any `init` passed), and SDDiskCache's own `[super init]` came back to
* itself.
*/
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { CodeGraph } from '../src';
let root = '';
let cg: CodeGraph;
beforeAll(async () => {
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-objc-super-'));
const files: Record<string, string> = {
'Base.h': `#import <Foundation/Foundation.h>
@interface Base : NSObject
- (instancetype)init;
@end
`,
'Base.m': `#import "Base.h"
@implementation Base
- (instancetype)init {
self = [super init];
return self;
}
@end
`,
'Child.h': `#import "Base.h"
@interface Child : Base
@end
`,
'Child.m': `#import "Child.h"
@implementation Child
- (instancetype)init {
self = [super init];
return self;
}
@end
`,
'Other.m': `#import <Foundation/Foundation.h>
@interface Other : NSObject
@end
@implementation Other
- (instancetype)init {
self = [super init];
return self;
}
@end
`,
};
for (const [rel, content] of Object.entries(files)) {
fs.writeFileSync(path.join(root, rel), content);
}
cg = await CodeGraph.init(root, { index: true });
});
afterAll(() => {
cg?.close();
if (root) fs.rmSync(root, { recursive: true, force: true });
});
/** `Owner::member` of every call edge out of a file. */
function callsFrom(file: string): string[] {
const ids = cg.getNodesInFile(file).map((n) => n.id);
return cg
.getOutgoingEdgesFrom(ids)
.filter((e) => e.kind === 'calls')
.map((e) => cg.getNode(e.target)!.qualifiedName)
.sort();
}
describe('Objective-C messages to super', () => {
it('reach the superclass, never the sender itself or an unrelated class', () => {
expect(callsFrom('Child.m')).toEqual(['Base::init']);
expect(callsFrom('Base.m')).toEqual([]);
expect(callsFrom('Other.m')).toEqual([]);
});
});