mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-11-10 08:27:27 +00:00
434 lines
22 KiB
TypeScript
434 lines
22 KiB
TypeScript
import { afterEach, describe, expect, it } from 'vitest';
|
|
import fs from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { runPipelineFromRepo } from '../../src/core/ingestion/pipeline.js';
|
|
import { planGraphRename } from '../../src/mcp/local/rename/graph-rename.js';
|
|
import { renameSymbol } from '../../src/mcp/local/rename/rename-plan.js';
|
|
|
|
const roots: string[] = [];
|
|
afterEach(async () => {
|
|
await Promise.all(roots.splice(0).map((root) => fs.rm(root, { recursive: true, force: true })));
|
|
});
|
|
|
|
async function fixture(files: Record<string, string>, qualifiedName: string) {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'rename-objc-'));
|
|
roots.push(root);
|
|
await Promise.all(
|
|
Object.entries(files).map(([file, content]) => fs.writeFile(path.join(root, file), content)),
|
|
);
|
|
const result = await runPipelineFromRepo(root, () => undefined, {
|
|
workerPoolSize: 1,
|
|
skipGraphPhases: true,
|
|
});
|
|
const nodes = [...result.graph.iterNodes()];
|
|
const target = nodes.find((node) => node.properties.qualifiedName === qualifiedName)!;
|
|
expect(target).toBeDefined();
|
|
const symbol = {
|
|
uid: target.id,
|
|
name: target.properties.name,
|
|
filePath: target.properties.filePath,
|
|
startLine: target.properties.startLine! + 1,
|
|
endLine: target.properties.endLine! + 1,
|
|
kind: target.label,
|
|
};
|
|
return { root, nodes, symbol };
|
|
}
|
|
|
|
describe('Objective-C semantic rename from the production parsing pipeline', () => {
|
|
it('renames a class through forward declarations, categories, annotations and class receivers', async () => {
|
|
const files = {
|
|
'Worker.h':
|
|
'@class Worker;\n@interface Worker\n+ (id)build;\n@end\n@interface Worker (Extra)\n- (void)run;\n@end\n',
|
|
'Worker.m':
|
|
'#import "Worker.h"\n// 😀 Worker\n@implementation Worker\n+ (id)build { return nil; }\n- (void)accept:(Worker *)worker { Worker *local = worker; [Worker build]; }\n@end\n@implementation Worker (Extra)\n- (void)run {}\n@end\n',
|
|
};
|
|
const { root, nodes, symbol } = await fixture(files, 'objc:class:Worker');
|
|
const result = await renameSymbol(root, symbol, { new_name: 'Agent', dry_run: false }, nodes);
|
|
expect(result, JSON.stringify(result)).toMatchObject({ applied: true });
|
|
expect(await fs.readFile(path.join(root, 'Worker.h'), 'utf8')).toBe(
|
|
files['Worker.h'].replaceAll('Worker', 'Agent'),
|
|
);
|
|
expect(await fs.readFile(path.join(root, 'Worker.m'), 'utf8')).toBe(
|
|
files['Worker.m']
|
|
.replaceAll('Worker', 'Agent')
|
|
.replace('"Agent.h"', '"Worker.h"')
|
|
.replace('// 😀 Agent', '// 😀 Worker'),
|
|
);
|
|
}, 60000);
|
|
|
|
it('renames a C helper and its uses while preserving a shadowing parameter', async () => {
|
|
const content =
|
|
'static int helper(int value) { return value; }\nint caller(void) { return helper(1); }\nint shadow(int helper) { return helper; }\nint indirect(int (*helper)(int)) { return helper(1); }\nint multiple(void) { int first = 1, helper = 2; return helper; }\nint ordered(void) { helper(1); int helper = 2; return helper; }\nint external(int helper);\n';
|
|
const { root, nodes, symbol } = await fixture(
|
|
{ 'Helpers.m': content },
|
|
'objc:function:static:Helpers.m:helper',
|
|
);
|
|
const result = await renameSymbol(
|
|
root,
|
|
symbol,
|
|
{ new_name: 'calculate', dry_run: false },
|
|
nodes,
|
|
);
|
|
expect(result, JSON.stringify(result)).toMatchObject({ applied: true, total_edits: 3 });
|
|
expect(await fs.readFile(path.join(root, 'Helpers.m'), 'utf8')).toBe(
|
|
content
|
|
.replace('int helper(', 'int calculate(')
|
|
.replace('return helper(1)', 'return calculate(1)')
|
|
.replace('{ helper(1); int helper', '{ calculate(1); int helper'),
|
|
);
|
|
}, 60000);
|
|
|
|
it('renames a protocol selector together with conforming implementations and inherited overrides', async () => {
|
|
const content =
|
|
'@protocol Runnable\n- (void)run;\n@end\n@interface Worker <Runnable>\n- (void)run;\n@end\n@interface Child : Worker\n- (void)run;\n@end\n@implementation Worker\n- (void)run {}\n- (void)use:(id<Runnable>)worker { [worker run]; }\n@end\n@implementation Child\n- (void)run { [super run]; [self run]; }\n@end\n@interface Other\n- (void)run;\n@end\n';
|
|
const { root, nodes, symbol } = await fixture(
|
|
{ 'Worker.m': content },
|
|
'objc:method:objc:protocol:Runnable:-:run',
|
|
);
|
|
const result = await renameSymbol(root, symbol, { new_name: 'execute', dry_run: false }, nodes);
|
|
expect(result, JSON.stringify(result)).toMatchObject({ applied: true });
|
|
expect(await fs.readFile(path.join(root, 'Worker.m'), 'utf8')).toBe(
|
|
content
|
|
.replaceAll('run', 'execute')
|
|
.replace('@interface Other\n- (void)execute;', '@interface Other\n- (void)run;'),
|
|
);
|
|
}, 60000);
|
|
|
|
it('blocks an unresolved dynamic send of the selected selector', async () => {
|
|
const content =
|
|
'@interface Worker\n- (void)run;\n@end\n@implementation Worker\n- (void)run {}\n- (void)use:(id)worker { [worker run]; }\n@end\n';
|
|
const { root, nodes, symbol } = await fixture(
|
|
{ 'Worker.m': content },
|
|
'objc:method:objc:class:Worker:-:run',
|
|
);
|
|
const result = await renameSymbol(root, symbol, { new_name: 'execute', dry_run: false }, nodes);
|
|
expect(result, JSON.stringify(result)).toMatchObject({
|
|
applied: false,
|
|
code: 'ambiguous_reference',
|
|
});
|
|
expect(await fs.readFile(path.join(root, 'Worker.m'), 'utf8')).toBe(content);
|
|
}, 60000);
|
|
|
|
it('renames a property, ordinary accessors and its synthesized backing ivar on the owning class', async () => {
|
|
const content =
|
|
'@interface Worker\n@property int value;\n@end\n@interface Other\n@property int value;\n@end\n@implementation Worker\n@synthesize value = _value;\n- (int)value { return _value; }\n- (void)setValue:(int)value { _value = value; }\n- (void)run:(Worker *)worker other:(Other *)other { self.value = 1; worker.value = 2; other.value = 3; [self setValue:4]; [worker value]; }\n@end\n';
|
|
const { root, nodes, symbol } = await fixture(
|
|
{ 'Worker.m': content },
|
|
'objc:property:objc:class:Worker:value',
|
|
);
|
|
const result = await renameSymbol(root, symbol, { new_name: 'count', dry_run: false }, nodes);
|
|
expect(result, JSON.stringify(result)).toMatchObject({ applied: true });
|
|
expect(await fs.readFile(path.join(root, 'Worker.m'), 'utf8')).toBe(
|
|
'@interface Worker\n@property int count;\n@end\n@interface Other\n@property int value;\n@end\n@implementation Worker\n@synthesize count = _count;\n- (int)count { return _count; }\n- (void)setCount:(int)value { _count = value; }\n- (void)run:(Worker *)worker other:(Other *)other { self.count = 1; worker.count = 2; other.value = 3; [self setCount:4]; [worker count]; }\n@end\n',
|
|
);
|
|
}, 60000);
|
|
|
|
it('renames an ivar and its accesses while preserving a local shadow', async () => {
|
|
const content =
|
|
'@interface Worker { int _value; }\n@end\n@implementation Worker\n- (int)read { return _value; }\n- (int)shadow:(int)_value { return _value; }\n- (int)multiple { int first = 1, _value = 2; return _value; }\n- (void)block { void (^block)(int) = ^(int _value) { (void)_value; }; }\n@end\n';
|
|
const { root, nodes, symbol } = await fixture(
|
|
{ 'Worker.m': content },
|
|
'objc:ivar:objc:class:Worker:_value',
|
|
);
|
|
const result = await renameSymbol(root, symbol, { new_name: '_count', dry_run: false }, nodes);
|
|
expect(result, JSON.stringify(result)).toMatchObject({ applied: true, total_edits: 2 });
|
|
expect(await fs.readFile(path.join(root, 'Worker.m'), 'utf8')).toBe(
|
|
content.replace('int _value;', 'int _count;').replace('return _value;', 'return _count;'),
|
|
);
|
|
}, 60000);
|
|
|
|
it('preserves a block parameter whose type owns an unrelated same-name selector', async () => {
|
|
const content =
|
|
'@interface Worker\n- (void)run;\n@end\n@interface Other\n- (void)run;\n@end\n@implementation Worker\n- (void)run {}\n- (void)use:(Worker *)worker { [worker run]; void (^block)(Other *) = ^(Other *worker) { [worker run]; }; }\n@end\n';
|
|
const { root, nodes, symbol } = await fixture(
|
|
{ 'Worker.m': content },
|
|
'objc:method:objc:class:Worker:-:run',
|
|
);
|
|
const result = await renameSymbol(root, symbol, { new_name: 'execute', dry_run: false }, nodes);
|
|
expect(result, JSON.stringify(result)).toMatchObject({ applied: true, total_edits: 3 });
|
|
expect(await fs.readFile(path.join(root, 'Worker.m'), 'utf8')).toBe(
|
|
content
|
|
.replace('- (void)run;', '- (void)execute;')
|
|
.replace('- (void)run {}', '- (void)execute {}')
|
|
.replace('[worker run]', '[worker execute]'),
|
|
);
|
|
}, 60000);
|
|
|
|
it('renames category selector declarations and implementations through typed calls', async () => {
|
|
const content =
|
|
'@interface Worker\n@end\n@interface Worker (Extra)\n- (void)trace;\n@end\n@implementation Worker (Extra)\n- (void)trace {}\n- (void)run { [self trace]; }\n@end\n@interface Other\n- (void)trace;\n@end\n';
|
|
const { root, nodes, symbol } = await fixture(
|
|
{ 'Worker.m': content },
|
|
'objc:method:objc:category:Worker:Extra:-:trace',
|
|
);
|
|
const result = await renameSymbol(root, symbol, { new_name: 'record', dry_run: false }, nodes);
|
|
expect(result, JSON.stringify(result)).toMatchObject({ applied: true, total_edits: 3 });
|
|
expect(await fs.readFile(path.join(root, 'Worker.m'), 'utf8')).toBe(
|
|
content
|
|
.replaceAll('trace', 'record')
|
|
.replace('@interface Other\n- (void)record;', '@interface Other\n- (void)trace;'),
|
|
);
|
|
}, 60000);
|
|
|
|
it('renames a category name on its host while preserving another host category', async () => {
|
|
const content =
|
|
'@interface Worker\n@end\n@interface Worker (Extra)\n@end\n@implementation Worker (Extra)\n@end\n@interface Other\n@end\n@interface Other (Extra)\n@end\n';
|
|
const { root, nodes, symbol } = await fixture(
|
|
{ 'Worker.m': content },
|
|
'objc:category:Worker:Extra',
|
|
);
|
|
const result = await renameSymbol(root, symbol, { new_name: 'Tracing', dry_run: false }, nodes);
|
|
expect(result, JSON.stringify(result)).toMatchObject({ applied: true, total_edits: 2 });
|
|
expect(await fs.readFile(path.join(root, 'Worker.m'), 'utf8')).toBe(
|
|
content.replaceAll('Worker (Extra)', 'Worker (Tracing)'),
|
|
);
|
|
}, 60000);
|
|
|
|
it('renames a protocol name in conformance and qualified object annotations', async () => {
|
|
const content =
|
|
'@protocol Runnable\n- (void)run;\n@end\n@interface Worker <Runnable>\n- (void)use:(id<Runnable>)other;\n@end\n';
|
|
const { root, nodes, symbol } = await fixture(
|
|
{ 'Worker.m': content },
|
|
'objc:protocol:Runnable',
|
|
);
|
|
const result = await renameSymbol(
|
|
root,
|
|
symbol,
|
|
{ new_name: 'Executable', dry_run: false },
|
|
nodes,
|
|
);
|
|
expect(result, JSON.stringify(result)).toMatchObject({ applied: true, total_edits: 3 });
|
|
expect(await fs.readFile(path.join(root, 'Worker.m'), 'utf8')).toBe(
|
|
content.replaceAll('Runnable', 'Executable'),
|
|
);
|
|
}, 60000);
|
|
|
|
it('renames the entire static selector family when a category replaces a host method', async () => {
|
|
const content =
|
|
'@interface Worker\n- (void)run;\n@end\n@interface Worker (Extra)\n- (void)run;\n@end\n@implementation Worker\n- (void)run {}\n- (void)call { [self run]; }\n@end\n@implementation Worker (Extra)\n- (void)run {}\n@end\n';
|
|
const { root, nodes, symbol } = await fixture(
|
|
{ 'Worker.m': content },
|
|
'objc:method:objc:class:Worker:-:run',
|
|
);
|
|
const result = await renameSymbol(root, symbol, { new_name: 'execute', dry_run: false }, nodes);
|
|
expect(result, JSON.stringify(result)).toMatchObject({ applied: true, total_edits: 5 });
|
|
expect(await fs.readFile(path.join(root, 'Worker.m'), 'utf8')).toBe(
|
|
content.replaceAll('run', 'execute'),
|
|
);
|
|
}, 60000);
|
|
|
|
it('keeps protocol and inherited property contracts in the same rename family', async () => {
|
|
const content =
|
|
'@protocol HasValue\n@property int value;\n@end\n@interface Worker <HasValue>\n@property int value;\n@end\n@interface Child : Worker\n@property int value;\n@end\n@implementation Child\n- (void)run:(id<HasValue>)other { self.value = other.value; }\n@end\n';
|
|
const { root, nodes, symbol } = await fixture(
|
|
{ 'Worker.m': content },
|
|
'objc:property:objc:protocol:HasValue:value',
|
|
);
|
|
const result = await renameSymbol(root, symbol, { new_name: 'count', dry_run: false }, nodes);
|
|
expect(result, JSON.stringify(result)).toMatchObject({ applied: true, total_edits: 5 });
|
|
expect(await fs.readFile(path.join(root, 'Worker.m'), 'utf8')).toBe(
|
|
content.replaceAll('int value;', 'int count;').replaceAll('.value', '.count'),
|
|
);
|
|
}, 60000);
|
|
|
|
it('blocks a selector literal that has no statically bound owner', async () => {
|
|
const content =
|
|
'@interface Worker\n- (void)send:(int)value completion:(int)done;\n@end\n@implementation Worker\n- (void)send:(int)value completion:(int)done {}\n- (void)run { [self performSelector:@selector(send:completion:)]; }\n@end\n';
|
|
const { root, nodes, symbol } = await fixture(
|
|
{ 'Worker.m': content },
|
|
'objc:method:objc:class:Worker:-:send:completion:',
|
|
);
|
|
const result = await renameSymbol(
|
|
root,
|
|
symbol,
|
|
{ new_name: 'deliver:finished:', dry_run: false },
|
|
nodes,
|
|
);
|
|
expect(result, JSON.stringify(result)).toMatchObject({
|
|
applied: false,
|
|
code: 'unsupported_symbol_family',
|
|
});
|
|
expect(await fs.readFile(path.join(root, 'Worker.m'), 'utf8')).toBe(content);
|
|
}, 60000);
|
|
|
|
it('blocks custom accessor property contracts with an explicit reason', async () => {
|
|
const content =
|
|
'@interface Worker\n@property(getter=enabled, setter=assignFlag:) int flag;\n@end\n';
|
|
const { root, nodes, symbol } = await fixture(
|
|
{ 'Worker.m': content },
|
|
'objc:property:objc:class:Worker:flag',
|
|
);
|
|
const result = await renameSymbol(root, symbol, { new_name: 'active', dry_run: false }, nodes);
|
|
expect(result, JSON.stringify(result)).toMatchObject({
|
|
applied: false,
|
|
code: 'unsupported_symbol_family',
|
|
});
|
|
expect(await fs.readFile(path.join(root, 'Worker.m'), 'utf8')).toBe(content);
|
|
}, 60000);
|
|
|
|
it('renames automatically synthesized accessors and backing storage', async () => {
|
|
const content =
|
|
'@interface Worker\n@property int value;\n@end\n@implementation Worker\n- (void)run { _value = 1; [self setValue:2]; self.value; }\n@end\n';
|
|
const { root, nodes, symbol } = await fixture(
|
|
{ 'Worker.m': content },
|
|
'objc:property:objc:class:Worker:value',
|
|
);
|
|
const result = await renameSymbol(root, symbol, { new_name: 'count', dry_run: false }, nodes);
|
|
expect(result, JSON.stringify(result)).toMatchObject({ applied: true, total_edits: 4 });
|
|
expect(await fs.readFile(path.join(root, 'Worker.m'), 'utf8')).toBe(
|
|
content
|
|
.replace('int value;', 'int count;')
|
|
.replace('_value', '_count')
|
|
.replace('setValue:', 'setCount:')
|
|
.replace('self.value', 'self.count'),
|
|
);
|
|
}, 60000);
|
|
|
|
it('renames the coupled property when the selected symbol is its ordinary setter', async () => {
|
|
const content =
|
|
'@interface Worker\n@property int value;\n- (void)setValue:(int)value;\n@end\n@implementation Worker\n- (void)run { _value = 1; [self setValue:2]; }\n@end\n';
|
|
const { root, nodes, symbol } = await fixture(
|
|
{ 'Worker.m': content },
|
|
'objc:method:objc:class:Worker:-:setValue:',
|
|
);
|
|
const result = await renameSymbol(
|
|
root,
|
|
symbol,
|
|
{ new_name: 'setCount:', dry_run: false },
|
|
nodes,
|
|
);
|
|
expect(result, JSON.stringify(result)).toMatchObject({ applied: true, total_edits: 4 });
|
|
expect(await fs.readFile(path.join(root, 'Worker.m'), 'utf8')).toBe(
|
|
content
|
|
.replace('int value;', 'int count;')
|
|
.replaceAll('setValue:', 'setCount:')
|
|
.replace('_value', '_count'),
|
|
);
|
|
}, 60000);
|
|
|
|
it('preserves an explicitly named backing ivar when renaming its property', async () => {
|
|
const content =
|
|
'@interface Worker { int storage; }\n@property int value;\n@end\n@implementation Worker\n@synthesize value = storage;\n- (void)run { storage = 1; self.value = 2; }\n@end\n';
|
|
const { root, nodes, symbol } = await fixture(
|
|
{ 'Worker.m': content },
|
|
'objc:property:objc:class:Worker:value',
|
|
);
|
|
const result = await renameSymbol(root, symbol, { new_name: 'count', dry_run: false }, nodes);
|
|
expect(result, JSON.stringify(result)).toMatchObject({ applied: true, total_edits: 3 });
|
|
expect(await fs.readFile(path.join(root, 'Worker.m'), 'utf8')).toBe(
|
|
content.replaceAll('value', 'count'),
|
|
);
|
|
}, 60000);
|
|
|
|
it('renames a readonly public property together with its readwrite extension', async () => {
|
|
const content =
|
|
'@interface Worker\n@property(readonly) int value;\n@end\n@interface Worker ()\n@property(readwrite) int value;\n@end\n@implementation Worker\n- (void)run { [self setValue:1]; self.value; }\n@end\n';
|
|
const { root, nodes, symbol } = await fixture(
|
|
{ 'Worker.m': content },
|
|
'objc:property:objc:class:Worker:value',
|
|
);
|
|
const result = await renameSymbol(root, symbol, { new_name: 'count', dry_run: false }, nodes);
|
|
expect(result, JSON.stringify(result)).toMatchObject({ applied: true, total_edits: 4 });
|
|
expect(await fs.readFile(path.join(root, 'Worker.m'), 'utf8')).toBe(
|
|
content
|
|
.replaceAll('int value;', 'int count;')
|
|
.replace('setValue:', 'setCount:')
|
|
.replace('self.value', 'self.count'),
|
|
);
|
|
}, 60000);
|
|
|
|
it('blocks a property whose accessors are supplied at runtime', async () => {
|
|
const content =
|
|
'@interface Worker\n@property int value;\n@end\n@implementation Worker\n@dynamic value;\n@end\n';
|
|
const { root, nodes, symbol } = await fixture(
|
|
{ 'Worker.m': content },
|
|
'objc:property:objc:class:Worker:value',
|
|
);
|
|
const result = await renameSymbol(root, symbol, { new_name: 'count', dry_run: false }, nodes);
|
|
expect(result, JSON.stringify(result)).toMatchObject({
|
|
applied: false,
|
|
code: 'unsupported_symbol_family',
|
|
});
|
|
expect(await fs.readFile(path.join(root, 'Worker.m'), 'utf8')).toBe(content);
|
|
}, 60000);
|
|
|
|
it('rejects a property rename that would capture an existing setter', async () => {
|
|
const content = '@interface Worker\n@property int value;\n- (void)setCount:(int)count;\n@end\n';
|
|
const { root, nodes, symbol } = await fixture(
|
|
{ 'Worker.m': content },
|
|
'objc:property:objc:class:Worker:value',
|
|
);
|
|
const result = await renameSymbol(root, symbol, { new_name: 'count', dry_run: false }, nodes);
|
|
expect(result, JSON.stringify(result)).toMatchObject({
|
|
applied: false,
|
|
code: 'name_collision',
|
|
});
|
|
expect(await fs.readFile(path.join(root, 'Worker.m'), 'utf8')).toBe(content);
|
|
}, 60000);
|
|
|
|
it('applies multipart selector changes across header, implementation and typed sends, preserving other owners and method kinds', async () => {
|
|
const files = {
|
|
'Worker.h':
|
|
'@interface Worker\n- (void)send:(int)value completion:(int)done;\n+ (void)send:(int)value completion:(int)done;\n@end\n@interface Other\n- (void)send:(int)value completion:(int)done;\n@end\n',
|
|
'Worker.m':
|
|
'#import "Worker.h"\n@implementation Worker\n- (void)send:(int)value completion:(int)done {}\n+ (void)send:(int)value completion:(int)done {}\n- (void)run:(Worker *)worker other:(Other *)other {\n [self send:1 completion:2];\n [worker send:1 completion:2];\n [other send:1 completion:2];\n [Worker send:1 completion:2];\n}\n@end\n',
|
|
};
|
|
const { root, nodes, symbol } = await fixture(
|
|
files,
|
|
'objc:method:objc:class:Worker:-:send:completion:',
|
|
);
|
|
const result = await renameSymbol(
|
|
root,
|
|
symbol,
|
|
{ new_name: 'deliver:finished:', dry_run: false },
|
|
nodes,
|
|
);
|
|
expect(result, JSON.stringify(result)).toMatchObject({ applied: true, total_edits: 8 });
|
|
expect(await fs.readFile(path.join(root, 'Worker.h'), 'utf8')).toBe(
|
|
files['Worker.h'].replace(
|
|
'- (void)send:(int)value completion:',
|
|
'- (void)deliver:(int)value finished:',
|
|
),
|
|
);
|
|
expect(await fs.readFile(path.join(root, 'Worker.m'), 'utf8')).toBe(
|
|
files['Worker.m']
|
|
.replace('- (void)send:(int)value completion:', '- (void)deliver:(int)value finished:')
|
|
.replace('[self send:1 completion:2]', '[self deliver:1 finished:2]')
|
|
.replace('[worker send:1 completion:2]', '[worker deliver:1 finished:2]'),
|
|
);
|
|
}, 60000);
|
|
|
|
it('rejects a selector arity change without modifying either file', async () => {
|
|
const files = {
|
|
'Worker.m': '@interface Worker\n- (void)send:(int)value completion:(int)done;\n@end\n',
|
|
};
|
|
const { root, nodes, symbol } = await fixture(
|
|
files,
|
|
'objc:method:objc:class:Worker:-:send:completion:',
|
|
);
|
|
await expect(
|
|
planGraphRename(root, symbol, { new_name: 'deliver:' }, nodes),
|
|
).rejects.toMatchObject({ code: 'invalid_name' });
|
|
expect(await fs.readFile(path.join(root, 'Worker.m'), 'utf8')).toBe(files['Worker.m']);
|
|
}, 60000);
|
|
|
|
it('blocks a selector with an unnamed piece instead of treating it as a shorter selector', async () => {
|
|
const content = '@interface Worker\n- (void)send:(int)a :(int)b;\n@end\n';
|
|
const { root, nodes, symbol } = await fixture(
|
|
{ 'Worker.m': content },
|
|
'objc:method:objc:class:Worker:-:send:',
|
|
);
|
|
const result = await renameSymbol(
|
|
root,
|
|
symbol,
|
|
{ new_name: 'deliver:', dry_run: false },
|
|
nodes,
|
|
);
|
|
expect(result, JSON.stringify(result)).toMatchObject({
|
|
applied: false,
|
|
code: 'unsupported_symbol_family',
|
|
});
|
|
expect(await fs.readFile(path.join(root, 'Worker.m'), 'utf8')).toBe(content);
|
|
}, 60000);
|
|
});
|