mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-11-12 08:41:34 +00:00
688 lines
22 KiB
TypeScript
688 lines
22 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { SupportedLanguages } from 'gitnexus-shared';
|
|
import { createRenameFixtureHelpers } from '../helpers/rename-provider-fixture.js';
|
|
import { SCOPE_RESOLVERS } from '../../src/core/ingestion/scope-resolution/pipeline/registry.js';
|
|
import { renameSymbol } from '../../src/mcp/local/rename/rename-plan.js';
|
|
|
|
const { fixture, expectRename } = createRenameFixtureHelpers();
|
|
|
|
describe('managed provider rename through the production parsing pipeline', () => {
|
|
it('preserves C# named argument labels that share a method name', async () => {
|
|
const content =
|
|
'class Model { static void target() {} static void other(int target) {} static void caller() { target(); other(target: 1); } }\n';
|
|
await expectRename(
|
|
{ 'model.cs': content },
|
|
{ 'model.cs': content.replaceAll('target()', 'renamed()') },
|
|
'target',
|
|
'Method',
|
|
);
|
|
}, 60_000);
|
|
|
|
it('keeps independent Dart static methods out of inherited rename families', async () => {
|
|
const content =
|
|
'class Base { static void target() {} }\nclass Child extends Base { static void target() {} }\nvoid use() { Base.target(); Child.target(); }\n';
|
|
await expectRename(
|
|
{ 'model.dart': content },
|
|
{ 'model.dart': content.replace('target', 'renamed').replace('Base.target', 'Base.renamed') },
|
|
'target',
|
|
'Method',
|
|
);
|
|
}, 60_000);
|
|
|
|
it.each([
|
|
['Class', 'public class Writer { public Writer() {} }\n'],
|
|
['Constructor', 'public class Writer { public Writer() {} }\n'],
|
|
['Interface', 'public interface Writer {}\n'],
|
|
['Enum', 'public enum Writer { VALUE }\n'],
|
|
['Record', 'public record Writer() {}\n'],
|
|
['Annotation', 'public @interface Writer {}\n'],
|
|
])(
|
|
'refuses a public top-level Java %s rename requiring a file move',
|
|
async (kind, content) => {
|
|
const { root, nodes, symbol } = await fixture({ 'Writer.java': content }, 'Writer', kind);
|
|
for (const dry_run of [true, false]) {
|
|
const result = await renameSymbol(root, symbol, { new_name: 'Reader', dry_run }, nodes);
|
|
expect(result, JSON.stringify(result)).toMatchObject({
|
|
status: 'error',
|
|
code: 'unsupported_occurrence',
|
|
planning_status: 'unsupported',
|
|
application_status: 'not_started',
|
|
applied: false,
|
|
total_edits: 0,
|
|
});
|
|
expect(result.error).toContain('file move');
|
|
expect(await fs.readFile(path.join(root, 'Writer.java'), 'utf8')).toBe(content);
|
|
expect(await fs.readdir(root)).not.toContain('Reader.java');
|
|
}
|
|
},
|
|
60_000,
|
|
);
|
|
|
|
it.each([
|
|
['Class', 'class Writer { Writer() {} }\n'],
|
|
['Constructor', 'class Writer { Writer() {} }\n'],
|
|
['Class', 'public class Outer { public static class Writer { public Writer() {} } }\n'],
|
|
['Constructor', 'public class Outer { public static class Writer { public Writer() {} } }\n'],
|
|
['Annotation', '@interface Writer {}\n'],
|
|
['Annotation', 'public class Outer { public @interface Writer {} }\n'],
|
|
])(
|
|
'renames a package-private or nested Java %s without requiring a file move (case %#)',
|
|
async (kind, content) => {
|
|
await expectRename(
|
|
{ 'Outer.java': content },
|
|
{ 'Outer.java': content.replaceAll('Writer', 'Reader') },
|
|
'Writer',
|
|
kind,
|
|
'Reader',
|
|
);
|
|
},
|
|
60_000,
|
|
);
|
|
|
|
it('allows a Java public type rename that matches the existing file name', async () => {
|
|
await expectRename(
|
|
{ 'Reader.java': 'public class Writer { public Writer() {} }\n' },
|
|
{ 'Reader.java': 'public class Reader { public Reader() {} }\n' },
|
|
'Writer',
|
|
'Class',
|
|
'Reader',
|
|
);
|
|
}, 60_000);
|
|
|
|
it.each([
|
|
[
|
|
'Constructor',
|
|
'class Outer { static class Writer { Writer() {} } }\nclass Other { static class Writer { Writer() {} } }\n',
|
|
],
|
|
['Annotation', 'class Outer { @interface Writer {} }\nclass Other { @interface Writer {} }\n'],
|
|
])(
|
|
'refuses collapsed same-name nested Java %s graph identities',
|
|
async (kind, content) => {
|
|
const { root, nodes, symbol } = await fixture({ 'Outer.java': content }, 'Writer', kind);
|
|
for (const dry_run of [true, false]) {
|
|
const result = await renameSymbol(root, symbol, { new_name: 'Reader', dry_run }, nodes);
|
|
expect(result, JSON.stringify(result)).toMatchObject({
|
|
status: 'error',
|
|
code: 'stale_graph',
|
|
planning_status: 'blocked',
|
|
application_status: 'not_started',
|
|
applied: false,
|
|
total_edits: 0,
|
|
});
|
|
expect(await fs.readFile(path.join(root, 'Outer.java'), 'utf8')).toBe(content);
|
|
}
|
|
},
|
|
60_000,
|
|
);
|
|
|
|
it('renames a C# class and its destructor without changing another class', async () => {
|
|
const content =
|
|
'class Writer { public Writer() {} ~Writer() {} }\nclass Other { ~Other() {} Writer Create() { return new Writer(); } }\n';
|
|
await expectRename(
|
|
{ 'model.cs': content },
|
|
{ 'model.cs': content.replaceAll('Writer', 'Renamed') },
|
|
'Writer',
|
|
'Class',
|
|
'Renamed',
|
|
);
|
|
}, 60_000);
|
|
it.each([
|
|
[
|
|
'model.java',
|
|
'class Model { static int target() { return 1; } static int caller() { return target(); } }\n// target\n',
|
|
'Method',
|
|
],
|
|
[
|
|
'model.kt',
|
|
'fun target() {}\nfun caller() { val value = ::target; target() }\n// target\n',
|
|
'Function',
|
|
],
|
|
[
|
|
'model.cs',
|
|
'class Model { static void target() {} static void caller() { System.Action value = target; target(); } }\n// target\n',
|
|
'Method',
|
|
],
|
|
[
|
|
'model.swift',
|
|
'func target() {}\nfunc caller() { let value = target; target() }\n// target\n',
|
|
'Function',
|
|
],
|
|
[
|
|
'model.dart',
|
|
'void target() {}\nvoid caller() { var value = target; target(); }\n// target\n',
|
|
'Function',
|
|
],
|
|
])(
|
|
'previews and applies calls and bare values in %s',
|
|
async (file, content, kind) => {
|
|
await expectRename(
|
|
{ [file]: content },
|
|
{ [file]: content.replaceAll('target', 'renamed').replace('// renamed', '// target') },
|
|
'target',
|
|
kind,
|
|
);
|
|
},
|
|
60000,
|
|
);
|
|
|
|
it.each([
|
|
[
|
|
'model.java',
|
|
'class Model { static int target() { return 1; } int caller(int target) { return target; } int use() { return target(); } }\n',
|
|
'Method',
|
|
],
|
|
[
|
|
'model.kt',
|
|
'fun target() {}\nfun caller(target: Int): Int { return target }\nfun use() { target() }\n',
|
|
'Function',
|
|
],
|
|
[
|
|
'model.cs',
|
|
'class Model { static int target() { return 1; } int caller(int target) { return target; } int use() { return target(); } }\n',
|
|
'Method',
|
|
],
|
|
[
|
|
'model.swift',
|
|
'func target() {}\nfunc caller(target: Int) -> Int { return target }\nfunc use() { target() }\n',
|
|
'Function',
|
|
],
|
|
[
|
|
'model.dart',
|
|
'int target() { return 1; }\nint caller(int target) { return target; }\nint use() { return target(); }\n',
|
|
'Function',
|
|
],
|
|
])(
|
|
'preserves shadow parameters in %s',
|
|
async (file, content, kind) => {
|
|
await expectRename(
|
|
{ [file]: content },
|
|
{ [file]: content.replaceAll('target()', 'renamed()') },
|
|
'target',
|
|
kind,
|
|
);
|
|
},
|
|
60000,
|
|
);
|
|
|
|
it.each([
|
|
[
|
|
'model.java',
|
|
'class Target { Target() {} }\nclass User { Target value; Target make() { return new Target(); } }\n',
|
|
],
|
|
['model.kt', 'class Target\nfun use(value: Target): Target { return Target() }\n'],
|
|
[
|
|
'model.cs',
|
|
'class Target { Target() {} }\nclass User { Target value; Target make() { return new Target(); } }\n',
|
|
],
|
|
['model.swift', 'class Target {}\nfunc use(value: Target) -> Target { return Target() }\n'],
|
|
['model.dart', 'class Target { Target(); }\nTarget use(Target value) { return Target(); }\n'],
|
|
])(
|
|
'renames type uses and named constructors in %s',
|
|
async (file, content) => {
|
|
await expectRename(
|
|
{ [file]: content },
|
|
{ [file]: content.replaceAll('Target', 'Renamed') },
|
|
'Target',
|
|
'Class',
|
|
'Renamed',
|
|
);
|
|
},
|
|
60000,
|
|
);
|
|
|
|
it.each([
|
|
[
|
|
'model.java',
|
|
'class Model { void target() {} void caller(Model value) { value.target(); } }\nclass Other { void target() {} void caller(Other value) { value.target(); } }\n',
|
|
'Method',
|
|
],
|
|
[
|
|
'model.kt',
|
|
'class Model {\nfun target() {}\nfun caller(value: Model) { value.target() }\n}\nclass Other {\nfun target() {}\n}\n',
|
|
'Method',
|
|
],
|
|
[
|
|
'model.cs',
|
|
'class Model { void target() {} void caller(Model value) { value.target(); } }\nclass Other { void target() {} void caller(Other value) { value.target(); } }\n',
|
|
'Method',
|
|
],
|
|
[
|
|
'model.swift',
|
|
'class Model { func target() {}\nfunc caller(value: Model) { value.target() } }\nclass Other { func target() {} }\n',
|
|
'Function',
|
|
],
|
|
[
|
|
'model.dart',
|
|
'class Model { void target() {} void caller(Model value) { value.target(); } }\nclass Other { void target() {} }\n',
|
|
'Method',
|
|
],
|
|
])(
|
|
'preserves unrelated owners in %s',
|
|
async (file, content, kind) => {
|
|
const boundary = content.indexOf('class Other');
|
|
await expectRename(
|
|
{ [file]: content },
|
|
{
|
|
[file]:
|
|
content.slice(0, boundary).replaceAll('target', 'renamed') + content.slice(boundary),
|
|
},
|
|
'target',
|
|
kind,
|
|
);
|
|
},
|
|
60000,
|
|
);
|
|
|
|
it.each([
|
|
[
|
|
SupportedLanguages.Kotlin,
|
|
'model.kt',
|
|
'fun `target`() {}\nfun caller() { target() }',
|
|
'`target`',
|
|
],
|
|
[
|
|
SupportedLanguages.CSharp,
|
|
'model.cs',
|
|
'class Model { void @target() {} void caller() { target(); } }',
|
|
'@target',
|
|
],
|
|
[
|
|
SupportedLanguages.Swift,
|
|
'model.swift',
|
|
'func `target`() {}\nfunc caller() { target() }',
|
|
'`target`',
|
|
],
|
|
])(
|
|
'inventories canonical escaped identifiers in %s',
|
|
async (language, filePath, content, spelling) => {
|
|
const resolver = SCOPE_RESOLVERS.get(language)!;
|
|
const source = await resolver.languageProvider.rename!.parseSource({
|
|
filePath,
|
|
content,
|
|
languageProvider: resolver.languageProvider,
|
|
scopeResolver: resolver,
|
|
});
|
|
const occurrence = source.occurrences.find((entry) => entry.node?.text === spelling)!;
|
|
expect(occurrence.name).toBe('target');
|
|
expect(occurrence.spans).toEqual([
|
|
{ start: content.indexOf(spelling), length: spelling.length },
|
|
]);
|
|
},
|
|
);
|
|
|
|
it.each([
|
|
[
|
|
'model.kt',
|
|
'package demo\nfun target() {}\n',
|
|
'caller.kt',
|
|
'package app\nimport demo.target as local\nfun use() { local() }\n',
|
|
'Function',
|
|
],
|
|
[
|
|
'model.cs',
|
|
'namespace Demo { public class Target {} }\n',
|
|
'caller.cs',
|
|
'using Local = Demo.Target;\nclass User { Local value; }\n',
|
|
'Class',
|
|
],
|
|
[
|
|
'model.dart',
|
|
'void target() {}\n',
|
|
'caller.dart',
|
|
"import 'model.dart' as lib;\nvoid use() { lib.target(); }\n",
|
|
'Function',
|
|
],
|
|
])(
|
|
'renames imported identity while preserving aliases in %s',
|
|
async (file, content, callerFile, caller, kind) => {
|
|
const name = kind === 'Class' ? 'Target' : 'target';
|
|
await expectRename(
|
|
{ [file]: content, [callerFile]: caller },
|
|
{
|
|
[file]: content.replaceAll(name, 'renamed'),
|
|
[callerFile]: caller.replaceAll(name, 'renamed'),
|
|
},
|
|
name,
|
|
kind,
|
|
'renamed',
|
|
file,
|
|
);
|
|
},
|
|
60000,
|
|
);
|
|
|
|
it.each([
|
|
[
|
|
'model.kt',
|
|
'fun `target`() {}\nfun caller() { target() }\n',
|
|
'`target`',
|
|
'Function',
|
|
'fun `renamed`() {}\nfun caller() { renamed() }\n',
|
|
],
|
|
[
|
|
'model.cs',
|
|
'class Model { void @target() {} void caller() { target(); } }\n',
|
|
'@target',
|
|
'Method',
|
|
'class Model { void @renamed() {} void caller() { renamed(); } }\n',
|
|
],
|
|
[
|
|
'model.swift',
|
|
'func `target`() {}\nfunc caller() { target() }\n',
|
|
'`target`',
|
|
'Function',
|
|
'func `renamed`() {}\nfunc caller() { renamed() }\n',
|
|
],
|
|
])(
|
|
'renames escaped declarations and ordinary references in %s',
|
|
async (file, content, name, kind, expected) => {
|
|
await expectRename({ [file]: content }, { [file]: expected }, name, kind);
|
|
},
|
|
60000,
|
|
);
|
|
|
|
it.each([
|
|
[
|
|
'model.java',
|
|
'interface Base { void target(); }\nclass Child implements Base { public void target() {} }\n',
|
|
'Method',
|
|
],
|
|
[
|
|
'model.kt',
|
|
'interface Base {\nfun target()\n}\nclass Child: Base {\noverride fun target() {}\n}\n',
|
|
'Method',
|
|
],
|
|
[
|
|
'model.cs',
|
|
'interface Base { void target(); }\nclass Child : Base { public void target() {} }\n',
|
|
'Method',
|
|
],
|
|
[
|
|
'model.swift',
|
|
'protocol Base { func target() }\nclass Child: Base { func target() {} }\n',
|
|
'Function',
|
|
],
|
|
[
|
|
'model.dart',
|
|
'abstract class Base { void target(); }\nclass Child implements Base { void target() {} }\n',
|
|
'Method',
|
|
],
|
|
])(
|
|
'renames proven interface declaration families in %s',
|
|
async (file, content, kind) => {
|
|
await expectRename(
|
|
{ [file]: content },
|
|
{ [file]: content.replaceAll('target', 'renamed') },
|
|
'target',
|
|
kind,
|
|
);
|
|
},
|
|
60000,
|
|
);
|
|
|
|
it.each([
|
|
[
|
|
'model.java',
|
|
'class Model { void target() {} void renamed() {} void use() { target(); } }\n',
|
|
'Method',
|
|
],
|
|
['model.kt', 'fun target() {}\nfun renamed() {}\nfun use() { target() }\n', 'Function'],
|
|
[
|
|
'model.cs',
|
|
'class Model { void target() {} void renamed() {} void use() { target(); } }\n',
|
|
'Method',
|
|
],
|
|
['model.swift', 'func target() {}\nfunc renamed() {}\nfunc use() { target() }\n', 'Function'],
|
|
['model.dart', 'void target() {}\nvoid renamed() {}\nvoid use() { target(); }\n', 'Function'],
|
|
])(
|
|
'refuses scope collisions without writing %s',
|
|
async (file, content, kind) => {
|
|
const { root, nodes, symbol } = await fixture({ [file]: content }, 'target', kind);
|
|
const result = await renameSymbol(
|
|
root,
|
|
symbol,
|
|
{ new_name: 'renamed', dry_run: false },
|
|
nodes,
|
|
);
|
|
expect(result).toMatchObject({ status: 'error', code: 'name_collision', applied: false });
|
|
expect(await fs.readFile(path.join(root, file), 'utf8')).toBe(content);
|
|
},
|
|
60000,
|
|
);
|
|
|
|
it('retains Swift conditional directives and original source offsets', async () => {
|
|
const content =
|
|
'class Model {\n#if DEBUG // 日本語 😀\nfunc target() {}\n#endif\nfunc use() { target() }\n}\n';
|
|
await expectRename(
|
|
{ 'model.swift': content },
|
|
{ 'model.swift': content.replaceAll('target', 'renamed') },
|
|
'target',
|
|
'Function',
|
|
);
|
|
}, 60000);
|
|
|
|
it('fails closed when the Java grammar cannot inventory Unicode escape syntax', async () => {
|
|
const resolver = SCOPE_RESOLVERS.get(SupportedLanguages.Java)!;
|
|
await expect(
|
|
resolver.languageProvider.rename!.parseSource({
|
|
filePath: 'model.java',
|
|
content: 'class Model { void t\\u0061rget() {} }',
|
|
languageProvider: resolver.languageProvider,
|
|
scopeResolver: resolver,
|
|
}),
|
|
).rejects.toMatchObject({ code: 'parse_error' });
|
|
});
|
|
|
|
it.each([
|
|
[
|
|
'model.java',
|
|
'class Model { void target() {} void target(int value) {} void use() { target(); target(1); } }\n',
|
|
'Method',
|
|
],
|
|
[
|
|
'model.kt',
|
|
'fun target() {}\nfun target(value: Int) {}\nfun use() { target(); target(1) }\n',
|
|
'Function',
|
|
],
|
|
[
|
|
'model.cs',
|
|
'class Model { void target() {} void target(int value) {} void use() { target(); target(1); } }\n',
|
|
'Method',
|
|
],
|
|
[
|
|
'model.swift',
|
|
'func target() {}\nfunc target(value: Int) {}\nfunc use() { target(); target(value: 1) }\n',
|
|
'Function',
|
|
],
|
|
])(
|
|
'renames overload declaration groups and calls in %s',
|
|
async (file, content, kind) => {
|
|
await expectRename(
|
|
{ [file]: content },
|
|
{ [file]: content.replaceAll('target', 'renamed') },
|
|
'target',
|
|
kind,
|
|
);
|
|
},
|
|
60000,
|
|
);
|
|
|
|
it.each([
|
|
[SupportedLanguages.Java, 'class'],
|
|
[SupportedLanguages.Kotlin, 'fun'],
|
|
[SupportedLanguages.CSharp, 'class'],
|
|
[SupportedLanguages.Swift, 'func'],
|
|
[SupportedLanguages.Dart, 'class'],
|
|
])('rejects reserved replacement names in %s', (language, name) => {
|
|
const provider = SCOPE_RESOLVERS.get(language)!.languageProvider.rename;
|
|
expect(provider).toBeDefined();
|
|
expect(provider!.validateName!(name)).toMatchObject({ code: 'invalid_name' });
|
|
});
|
|
});
|
|
|
|
async function expectLabelRefusal(
|
|
file: string,
|
|
content: string,
|
|
name: string,
|
|
newName: string,
|
|
code = 'ambiguous_reference',
|
|
) {
|
|
const { root, nodes, symbol } = await fixture({ [file]: content }, name, 'Property');
|
|
for (const dry_run of [true, false]) {
|
|
const result = await renameSymbol(root, symbol, { new_name: newName, dry_run }, nodes);
|
|
expect(result, `${name} -> ${newName} dry_run=${dry_run}`).toMatchObject({
|
|
status: 'error',
|
|
code,
|
|
total_edits: 0,
|
|
applied: false,
|
|
});
|
|
expect(await fs.readFile(path.join(root, file), 'utf8')).toBe(content);
|
|
}
|
|
}
|
|
|
|
describe('named argument labels spelled like a renamed property', () => {
|
|
const dataClass = 'data class Pair2(val first: Int, val second: Int)\n';
|
|
it.each([
|
|
['a primary-constructor property', dataClass + 'fun make() = Pair2(first = 1, second = 2)\n'],
|
|
[
|
|
'a data class copy() parameter',
|
|
dataClass + 'fun move(pair: Pair2): Pair2 = pair.copy(first = 3)\n',
|
|
],
|
|
[
|
|
'a supertype initializer of the owner',
|
|
'open class Base(val first: Int)\nclass Child : Base(first = 1)\n',
|
|
],
|
|
['an enum entry argument', 'enum class Level(val first: Int) { LOW(first = 1) }\n'],
|
|
[
|
|
'a typealias of the owner',
|
|
dataClass + 'typealias Alias = Pair2\nfun make() = Alias(first = 1)\n',
|
|
],
|
|
['an unresolved callee', dataClass + 'fun make(factory: Any) = factory.build(first = 1)\n'],
|
|
])(
|
|
'refuses a Kotlin label that may bind %s',
|
|
async (_description, content) => {
|
|
await expectLabelRefusal('model.kt', content, 'first', 'head');
|
|
},
|
|
60_000,
|
|
);
|
|
|
|
it('refuses a Kotlin destination label of a constructor that would gain the property', async () => {
|
|
await expectLabelRefusal(
|
|
'model.kt',
|
|
'class Pair2(val first: Int) {\n constructor(head: Int, extra: Int) : this(head)\n}\nfun make() = Pair2(head = 1, extra = 2)\n',
|
|
'first',
|
|
'head',
|
|
);
|
|
}, 60_000);
|
|
|
|
it('renames a Kotlin property beside labels of other functions and types', async () => {
|
|
const calls =
|
|
'class Unrelated(val first: Int)\nfun configure(first: Int) {}\nopen class Base(val first: Int)\n' +
|
|
'class Child(first2: Int) : Base(first2)\n' +
|
|
'fun make() {\n Unrelated(first = 1)\n configure(first = 2)\n Child(first2 = 3)\n}\n';
|
|
await expectRename(
|
|
{ 'model.kt': dataClass + calls },
|
|
{ 'model.kt': dataClass.replace('first', 'head') + calls },
|
|
'first',
|
|
'Property',
|
|
'head',
|
|
);
|
|
}, 60_000);
|
|
|
|
const struct = 'struct Point {\n var x: Int\n var y: Int\n}\n';
|
|
it.each([
|
|
['the memberwise initializer', struct + 'let origin = Point(x: 0, y: 0)\n'],
|
|
[
|
|
'an explicit .init call of the memberwise initializer',
|
|
struct + 'let origin = Point.init(x: 0, y: 0)\n',
|
|
],
|
|
[
|
|
'a memberwise initializer beside an extension initializer',
|
|
struct + 'extension Point {\n init(other: Int) { self.init(x: other, y: other) }\n}\n',
|
|
],
|
|
[
|
|
'a typealias of the owner',
|
|
struct + 'typealias Spot = Point\nlet origin = Spot(x: 0, y: 0)\n',
|
|
],
|
|
])(
|
|
'refuses a Swift label that may bind %s',
|
|
async (_description, content) => {
|
|
await expectLabelRefusal('model.swift', content, 'x', 'px');
|
|
},
|
|
60_000,
|
|
);
|
|
|
|
it('renames a Swift property whose type declares its own initializer', async () => {
|
|
const custom = (name: string) =>
|
|
`struct Custom {\n var ${name}: Int\n init(x: Int) { self.${name} = x }\n}\n`;
|
|
const calls = 'func configure(x: Int) {}\nlet custom = Custom(x: 1)\nconfigure(x: 2)\n';
|
|
await expectRename(
|
|
{ 'model.swift': custom('x') + struct + calls },
|
|
{ 'model.swift': custom('px') + struct + calls },
|
|
'x',
|
|
'Property',
|
|
'px',
|
|
);
|
|
}, 60_000);
|
|
|
|
it('renames a Swift class property beside its labeled initializer', async () => {
|
|
const box = (name: string) =>
|
|
`class Box {\n var ${name}: Int\n init(x: Int) { self.${name} = x }\n}\nlet box = Box(x: 1)\n`;
|
|
await expectRename(
|
|
{ 'model.swift': box('x') },
|
|
{ 'model.swift': box('px') },
|
|
'x',
|
|
'Property',
|
|
'px',
|
|
);
|
|
}, 60_000);
|
|
|
|
it('binds a C# named argument to the constructor parameter, not the property', async () => {
|
|
const counter = (name: string) =>
|
|
`public class Counter {\n public int ${name} { get; set; }\n public Counter(int Count) { this.${name} = Count; }\n}\n` +
|
|
'public class Maker { public Counter Make() { return new Counter(Count: 1); } }\n';
|
|
await expectRename(
|
|
{ 'model.cs': counter('Count') },
|
|
{ 'model.cs': counter('Total') },
|
|
'Count',
|
|
'Property',
|
|
'Total',
|
|
);
|
|
}, 60_000);
|
|
|
|
it('refuses a C# positional record whose explicit property shares the parameter name', async () => {
|
|
await expectLabelRefusal(
|
|
'model.cs',
|
|
'public record Pair(int First) { public int First { get; init; } = First; }\n' +
|
|
'public class Maker { public Pair Make() { return new Pair(First: 1); } }\n',
|
|
'First',
|
|
'Head',
|
|
'incomplete_semantics',
|
|
);
|
|
}, 60_000);
|
|
|
|
it('refuses a Dart field whose initializing formal parameter a named argument binds', async () => {
|
|
await expectLabelRefusal(
|
|
'model.dart',
|
|
'class Point {\n int x;\n Point({required this.x});\n}\nvoid make() { Point(x: 1); }\n',
|
|
'x',
|
|
'px',
|
|
'name_collision',
|
|
);
|
|
}, 60_000);
|
|
|
|
it('renames a Dart field beside a named argument of another function', async () => {
|
|
const point = (name: string) => `class Point {\n int ${name} = 0;\n}\n`;
|
|
const use = (name: string) =>
|
|
`void configure({int x = 0}) {}\nvoid make(Point p) { configure(x: 1); p.${name} = 2; }\n`;
|
|
await expectRename(
|
|
{ 'model.dart': point('x') + use('x') },
|
|
{ 'model.dart': point('px') + use('px') },
|
|
'x',
|
|
'Property',
|
|
'px',
|
|
);
|
|
}, 60_000);
|
|
});
|