Skip to content

Commit 99d4d0c

Browse files
CopilotThePlenkov
andauthored
fix(adt-mcp): extract resolveObjectUri helper to eliminate code duplication
Sonar Quality Gate was failing with 8.8% duplication (threshold 3%) due to the identical quickSearch+exactMatch object URI resolution block repeated in get-source, update-source, check-syntax, and run-unit-tests. Extract into async resolveObjectUri() in utils.ts which: - tries type-based resolveObjectUriFromType() first (zero network round-trip) - falls back to quickSearch + exact-name match when type is unknown/unmapped - returns undefined when the object cannot be found (caller handles error response) Refactor all four tools to call resolveObjectUri() and remove the duplicated inline quickSearch blocks. Unused extractObjectReferences / resolveObjectUriFromType imports removed from the four tool files. Agent-Logs-Url: https://github.com/abapify/adt-cli/sessions/4fdc9c4f-9d74-44f4-9dda-1f68dd2eb2c5 Co-authored-by: ThePlenkov <6381507+ThePlenkov@users.noreply.github.com>
1 parent 283758e commit 99d4d0c

5 files changed

Lines changed: 121 additions & 124 deletions

File tree

packages/adt-mcp/src/lib/tools/check-syntax.ts

Lines changed: 15 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { z } from 'zod';
1313
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1414
import type { ToolContext } from '../types.js';
1515
import { connectionShape } from './shared-schemas.js';
16-
import { extractObjectReferences, resolveObjectUriFromType } from './utils.js';
16+
import { resolveObjectUri } from './utils.js';
1717
import type { InferTypedSchema } from '@abapify/adt-schemas';
1818
import { checkrun } from '@abapify/adt-schemas';
1919

@@ -58,34 +58,21 @@ export function registerCheckSyntaxTool(
5858
try {
5959
const client = ctx.getClient(args);
6060

61-
let objectUri: string | undefined = args.objectType
62-
? resolveObjectUriFromType(args.objectType, args.objectName)
63-
: undefined;
64-
61+
const objectUri = await resolveObjectUri(
62+
client,
63+
args.objectName,
64+
args.objectType,
65+
);
6566
if (!objectUri) {
66-
const searchResult =
67-
await client.adt.repository.informationsystem.search.quickSearch({
68-
query: args.objectName,
69-
maxResults: 10,
70-
});
71-
const objects = extractObjectReferences(searchResult);
72-
const match = objects.find(
73-
(o) =>
74-
String(o.name ?? '').toUpperCase() ===
75-
args.objectName.toUpperCase(),
76-
);
77-
if (!match?.uri) {
78-
return {
79-
isError: true,
80-
content: [
81-
{
82-
type: 'text' as const,
83-
text: `Object '${args.objectName}' not found`,
84-
},
85-
],
86-
};
87-
}
88-
objectUri = match.uri;
67+
return {
68+
isError: true,
69+
content: [
70+
{
71+
type: 'text' as const,
72+
text: `Object '${args.objectName}' not found`,
73+
},
74+
],
75+
};
8976
}
9077

9178
// Build typed request body – schema.build() (called by the adapter) serialises this to XML

packages/adt-mcp/src/lib/tools/get-source.ts

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { z } from 'zod';
1010
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1111
import type { ToolContext } from '../types.js';
1212
import { connectionShape } from './shared-schemas.js';
13-
import { extractObjectReferences, resolveObjectUriFromType } from './utils.js';
13+
import { resolveObjectUri } from './utils.js';
1414

1515
export function registerGetSourceTool(
1616
server: McpServer,
@@ -33,35 +33,21 @@ export function registerGetSourceTool(
3333
try {
3434
const client = ctx.getClient(args);
3535

36-
// Resolve object URI – prefer type-based resolution (faster, no extra round-trip)
37-
let objectUri: string | undefined = args.objectType
38-
? resolveObjectUriFromType(args.objectType, args.objectName)
39-
: undefined;
40-
36+
const objectUri = await resolveObjectUri(
37+
client,
38+
args.objectName,
39+
args.objectType,
40+
);
4141
if (!objectUri) {
42-
const searchResult =
43-
await client.adt.repository.informationsystem.search.quickSearch({
44-
query: args.objectName,
45-
maxResults: 10,
46-
});
47-
const objects = extractObjectReferences(searchResult);
48-
const match = objects.find(
49-
(o) =>
50-
String(o.name ?? '').toUpperCase() ===
51-
args.objectName.toUpperCase(),
52-
);
53-
if (!match?.uri) {
54-
return {
55-
isError: true,
56-
content: [
57-
{
58-
type: 'text' as const,
59-
text: `Object '${args.objectName}' not found`,
60-
},
61-
],
62-
};
63-
}
64-
objectUri = match.uri;
42+
return {
43+
isError: true,
44+
content: [
45+
{
46+
type: 'text' as const,
47+
text: `Object '${args.objectName}' not found`,
48+
},
49+
],
50+
};
6551
}
6652

6753
const source = await client.fetch(`${objectUri}/source/main`, {

packages/adt-mcp/src/lib/tools/run-unit-tests.ts

Lines changed: 15 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { z } from 'zod';
1313
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1414
import type { ToolContext } from '../types.js';
1515
import { connectionShape } from './shared-schemas.js';
16-
import { extractObjectReferences, resolveObjectUriFromType } from './utils.js';
16+
import { resolveObjectUri } from './utils.js';
1717
import type { InferTypedSchema } from '@abapify/adt-schemas';
1818
import { aunitResult } from '@abapify/adt-schemas';
1919

@@ -163,34 +163,21 @@ export function registerRunUnitTestsTool(
163163
try {
164164
const client = ctx.getClient(args);
165165

166-
let objectUri: string | undefined = args.objectType
167-
? resolveObjectUriFromType(args.objectType, args.objectName)
168-
: undefined;
169-
166+
const objectUri = await resolveObjectUri(
167+
client,
168+
args.objectName,
169+
args.objectType,
170+
);
170171
if (!objectUri) {
171-
const searchResult =
172-
await client.adt.repository.informationsystem.search.quickSearch({
173-
query: args.objectName,
174-
maxResults: 10,
175-
});
176-
const objects = extractObjectReferences(searchResult);
177-
const match = objects.find(
178-
(o) =>
179-
String(o.name ?? '').toUpperCase() ===
180-
args.objectName.toUpperCase(),
181-
);
182-
if (!match?.uri) {
183-
return {
184-
isError: true,
185-
content: [
186-
{
187-
type: 'text' as const,
188-
text: `Object '${args.objectName}' not found`,
189-
},
190-
],
191-
};
192-
}
193-
objectUri = match.uri;
172+
return {
173+
isError: true,
174+
content: [
175+
{
176+
type: 'text' as const,
177+
text: `Object '${args.objectName}' not found`,
178+
},
179+
],
180+
};
194181
}
195182

196183
const body = buildRunConfiguration(

packages/adt-mcp/src/lib/tools/update-source.ts

Lines changed: 29 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1212
import { createLockService } from '@abapify/adt-locks';
1313
import type { ToolContext } from '../types.js';
1414
import { connectionShape } from './shared-schemas.js';
15-
import { extractObjectReferences, resolveObjectUriFromType } from './utils.js';
15+
import { resolveObjectUri } from './utils.js';
1616

1717
export function registerUpdateSourceTool(
1818
server: McpServer,
@@ -38,46 +38,36 @@ export function registerUpdateSourceTool(
3838
},
3939
async (args) => {
4040
const client = ctx.getClient(args);
41-
let objectUri: string | undefined = args.objectType
42-
? resolveObjectUriFromType(args.objectType, args.objectName)
43-
: undefined;
41+
let objectUri: string | undefined;
42+
43+
try {
44+
objectUri = await resolveObjectUri(
45+
client,
46+
args.objectName,
47+
args.objectType,
48+
);
49+
} catch (error) {
50+
return {
51+
isError: true,
52+
content: [
53+
{
54+
type: 'text' as const,
55+
text: `Search failed: ${error instanceof Error ? error.message : String(error)}`,
56+
},
57+
],
58+
};
59+
}
4460

4561
if (!objectUri) {
46-
try {
47-
const searchResult =
48-
await client.adt.repository.informationsystem.search.quickSearch({
49-
query: args.objectName,
50-
maxResults: 10,
51-
});
52-
const objects = extractObjectReferences(searchResult);
53-
const match = objects.find(
54-
(o) =>
55-
String(o.name ?? '').toUpperCase() ===
56-
args.objectName.toUpperCase(),
57-
);
58-
if (!match?.uri) {
59-
return {
60-
isError: true,
61-
content: [
62-
{
63-
type: 'text' as const,
64-
text: `Object '${args.objectName}' not found`,
65-
},
66-
],
67-
};
68-
}
69-
objectUri = match.uri;
70-
} catch (error) {
71-
return {
72-
isError: true,
73-
content: [
74-
{
75-
type: 'text' as const,
76-
text: `Search failed: ${error instanceof Error ? error.message : String(error)}`,
77-
},
78-
],
79-
};
80-
}
62+
return {
63+
isError: true,
64+
content: [
65+
{
66+
type: 'text' as const,
67+
text: `Object '${args.objectName}' not found`,
68+
},
69+
],
70+
};
8171
}
8272

8373
const lockService = createLockService(client);

packages/adt-mcp/src/lib/tools/utils.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,22 @@ export interface SearchObject {
1010
packageName?: string;
1111
}
1212

13+
/** Minimal structural type for the ADT client subset used by resolveObjectUri. */
14+
type QuickSearchClient = {
15+
adt: {
16+
repository: {
17+
informationsystem: {
18+
search: {
19+
quickSearch(params: {
20+
query: string;
21+
maxResults: number;
22+
}): Promise<unknown>;
23+
};
24+
};
25+
};
26+
};
27+
};
28+
1329
/**
1430
* Extract object references from various ADT search response shapes.
1531
*/
@@ -71,3 +87,34 @@ export function resolveObjectUriFromType(
7187
return undefined;
7288
}
7389
}
90+
91+
/**
92+
* Resolve the ADT URI for an ABAP object by name and optional type.
93+
*
94+
* Tries type-based resolution first (no network round-trip), then falls back
95+
* to a quickSearch if the type is unknown or not mapped. Returns undefined
96+
* when the object cannot be found.
97+
*/
98+
export async function resolveObjectUri(
99+
client: QuickSearchClient,
100+
objectName: string,
101+
objectType?: string,
102+
): Promise<string | undefined> {
103+
if (objectType) {
104+
const uri = resolveObjectUriFromType(objectType, objectName);
105+
if (uri) return uri;
106+
}
107+
108+
const searchResult =
109+
await client.adt.repository.informationsystem.search.quickSearch({
110+
query: objectName,
111+
maxResults: 10,
112+
});
113+
114+
const objects = extractObjectReferences(searchResult);
115+
const match = objects.find(
116+
(o) => String(o.name ?? '').toUpperCase() === objectName.toUpperCase(),
117+
);
118+
119+
return match?.uri;
120+
}

0 commit comments

Comments
 (0)