Skip to content

Commit ab552c3

Browse files
codemod improvements (#2156)
1 parent db28156 commit ab552c3

18 files changed

Lines changed: 680 additions & 110 deletions

packages/codemod/batch-test/repos.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[
22
{
3-
"repo": "KKonstantinov/mcp-servers-fork",
4-
"ref": "feature/upgrade-zod-v4",
3+
"repo": "modelcontextprotocol/servers",
4+
"ref": "main",
55
"packages": [
66
{
77
"dir": "src/everything",

packages/codemod/src/bin/batchTest.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ function main(): void {
353353
codemodResult = run(migration, { targetDir: fullSourceDir, verbose: true });
354354
} catch (error) {
355355
console.log(` ERROR: codemod threw: ${error}`);
356-
codemodResult = { filesChanged: 0, totalChanges: 0, diagnostics: [], fileResults: [] };
356+
codemodResult = { filesChanged: 0, totalChanges: 0, diagnostics: [], fileResults: [], commentCount: 0 };
357357
}
358358
console.log(
359359
` Codemod: files=${codemodResult.filesChanged} changes=${codemodResult.totalChanges} diags=${codemodResult.diagnostics.length}`

packages/codemod/src/cli.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { Command } from 'commander';
99
import { listMigrations } from './migrations/index.js';
1010
import { run } from './runner.js';
1111
import { DiagnosticLevel } from './types.js';
12-
import { formatDiagnostic } from './utils/diagnostics.js';
12+
import { CODEMOD_ERROR_PREFIX, formatDiagnostic } from './utils/diagnostics.js';
1313

1414
const require = createRequire(import.meta.url);
1515
const { version } = require('../package.json') as { version: string };
@@ -140,6 +140,13 @@ for (const [name, migration] of listMigrations()) {
140140
console.log('');
141141
}
142142

143+
if (result.commentCount > 0) {
144+
console.log(
145+
`${result.commentCount} location(s) marked with ${CODEMOD_ERROR_PREFIX} comments — search your code to find them:\n` +
146+
` grep -r '${CODEMOD_ERROR_PREFIX}' "${resolvedDir}"\n`
147+
);
148+
}
149+
143150
if (opts['dryRun']) {
144151
console.log('Run without --dry-run to apply changes.\n');
145152
} else {

packages/codemod/src/migrations/v1-to-v2/mappings/importMap.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,8 @@ export const IMPORT_MAP: Record<string, ImportMapping> = {
3434
status: 'moved'
3535
},
3636
'@modelcontextprotocol/sdk/client/stdio.js': {
37-
target: '@modelcontextprotocol/client',
38-
status: 'moved',
39-
symbolTargetOverrides: {
40-
StdioClientTransport: '@modelcontextprotocol/client/stdio',
41-
DEFAULT_INHERITED_ENV_VARS: '@modelcontextprotocol/client/stdio',
42-
getDefaultEnvironment: '@modelcontextprotocol/client/stdio',
43-
StdioServerParameters: '@modelcontextprotocol/client/stdio'
44-
}
37+
target: '@modelcontextprotocol/client/stdio',
38+
status: 'moved'
4539
},
4640
'@modelcontextprotocol/sdk/client/websocket.js': {
4741
target: '',

packages/codemod/src/migrations/v1-to-v2/transforms/contextTypes.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import type { SourceFile } from 'ts-morph';
22
import { Node, SyntaxKind } from 'ts-morph';
33

44
import type { Diagnostic, Transform, TransformContext, TransformResult } from '../../../types.js';
5-
import { info, warning } from '../../../utils/diagnostics.js';
5+
import { isKeyPositionIdentifier } from '../../../utils/astUtils.js';
6+
import { actionRequired, info } from '../../../utils/diagnostics.js';
67
import { hasMcpImports } from '../../../utils/importUtils.js';
78
import { CONTEXT_PROPERTY_MAP, CTX_PARAM_NAME, EXTRA_PARAM_NAME } from '../mappings/contextPropertyMap.js';
89

@@ -32,9 +33,9 @@ function processCallback(
3233
const paramNameNode = extraParam.getNameNode();
3334
if (Node.isObjectBindingPattern(paramNameNode)) {
3435
diagnostics.push(
35-
warning(
36+
actionRequired(
3637
sourceFile.getFilePath(),
37-
extraParam.getStartLineNumber(),
38+
extraParam,
3839
`Destructuring of context parameter in signature: "${paramNameNode.getText()}". ` +
3940
'Properties have been reorganized in v2 (e.g., signal is now ctx.mcpReq.signal). Manual refactoring required.'
4041
)
@@ -49,9 +50,9 @@ function processCallback(
4950
const otherParams = callbackNode.getParameters().filter(p => p !== extraParam);
5051
if (otherParams.some(p => p.getName() === CTX_PARAM_NAME)) {
5152
diagnostics.push(
52-
warning(
53+
actionRequired(
5354
sourceFile.getFilePath(),
54-
extraParam.getStartLineNumber(),
55+
extraParam,
5556
`Cannot rename '${EXTRA_PARAM_NAME}' to '${CTX_PARAM_NAME}': another parameter is already named '${CTX_PARAM_NAME}'. Manual migration required.`
5657
)
5758
);
@@ -74,9 +75,9 @@ function processCallback(
7475
});
7576
if (ctxAlreadyInScope) {
7677
diagnostics.push(
77-
warning(
78+
actionRequired(
7879
sourceFile.getFilePath(),
79-
extraParam.getStartLineNumber(),
80+
extraParam,
8081
`Cannot rename '${EXTRA_PARAM_NAME}' to '${CTX_PARAM_NAME}': '${CTX_PARAM_NAME}' is already referenced in this scope. Manual migration required.`
8182
)
8283
);
@@ -98,11 +99,7 @@ function processCallback(
9899
body.forEachDescendant(node => {
99100
if (!Node.isIdentifier(node) || node.getText() !== EXTRA_PARAM_NAME) return;
100101
const parent = node.getParent();
101-
// Skip property-name positions (e.g., meta.extra, { extra: value }, { extra }, { extra: x } = obj)
102-
if (parent && Node.isPropertyAccessExpression(parent) && parent.getNameNode() === node) return;
103-
if (parent && Node.isPropertyAssignment(parent) && parent.getNameNode() === node) return;
104-
if (parent && Node.isShorthandPropertyAssignment(parent)) return;
105-
if (parent && Node.isBindingElement(parent) && parent.getPropertyNameNode() === node) return;
102+
if (parent && isKeyPositionIdentifier(node)) return;
106103
identifiers.push(node);
107104
});
108105

@@ -131,6 +128,11 @@ function processCallback(
131128
}
132129
}
133130
}
131+
// Shorthand property assignment: { extra } → { extra: ctx }
132+
if (parent && Node.isShorthandPropertyAssignment(parent)) {
133+
replacements.push({ node: parent, newText: `${EXTRA_PARAM_NAME}: ${CTX_PARAM_NAME}` });
134+
continue;
135+
}
134136
replacements.push({ node: id, newText: CTX_PARAM_NAME });
135137
}
136138

@@ -163,9 +165,9 @@ function processCallback(
163165
const nameNode = node.getNameNode();
164166
if (!Node.isObjectBindingPattern(nameNode)) return;
165167
diagnostics.push(
166-
warning(
168+
actionRequired(
167169
sourceFile.getFilePath(),
168-
node.getStartLineNumber(),
170+
node,
169171
`Destructuring of context parameter detected: "const ${nameNode.getText()} = ${CTX_PARAM_NAME}". ` +
170172
'Properties have been reorganized in v2 (e.g., signal is now ctx.mcpReq.signal). Manual refactoring required.'
171173
)

packages/codemod/src/migrations/v1-to-v2/transforms/handlerRegistration.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import type { SourceFile } from 'ts-morph';
22
import { Node, SyntaxKind } from 'ts-morph';
33

44
import type { Diagnostic, Transform, TransformContext, TransformResult } from '../../../types.js';
5-
import { warning } from '../../../utils/diagnostics.js';
6-
import { isImportedFromMcp, removeUnusedImport, resolveOriginalImportName } from '../../../utils/importUtils.js';
5+
import { actionRequired } from '../../../utils/diagnostics.js';
6+
import { hasMcpImports, isImportedFromMcp, removeUnusedImport, resolveOriginalImportName } from '../../../utils/importUtils.js';
77
import { NOTIFICATION_SCHEMA_TO_METHOD, SCHEMA_TO_METHOD } from '../mappings/schemaToMethodMap.js';
88

99
const ALL_SCHEMA_TO_METHOD: Record<string, string> = {
@@ -15,6 +15,10 @@ export const handlerRegistrationTransform: Transform = {
1515
name: 'Handler registration migration',
1616
id: 'handlers',
1717
apply(sourceFile: SourceFile, _context: TransformContext): TransformResult {
18+
if (!hasMcpImports(sourceFile)) {
19+
return { changesCount: 0, diagnostics: [] };
20+
}
21+
1822
let changesCount = 0;
1923
const diagnostics: Diagnostic[] = [];
2024

@@ -40,9 +44,9 @@ export const handlerRegistrationTransform: Transform = {
4044
const methodString = ALL_SCHEMA_TO_METHOD[originalName];
4145
if (!methodString) {
4246
diagnostics.push(
43-
warning(
47+
actionRequired(
4448
sourceFile.getFilePath(),
45-
call.getStartLineNumber(),
49+
call,
4650
`Custom method handler: ${methodName}(${schemaName}, ...). ` +
4751
`In v2, use the 3-arg form: ${methodName}('method/name', { params, result? }, handler). ` +
4852
`See migration.md for details.`

packages/codemod/src/migrations/v1-to-v2/transforms/importPaths.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { SourceFile } from 'ts-morph';
22

33
import type { Diagnostic, Transform, TransformContext, TransformResult } from '../../../types.js';
44
import { renameAllReferences } from '../../../utils/astUtils.js';
5-
import { info, v2Gap, warning } from '../../../utils/diagnostics.js';
5+
import { actionRequired, info, v2Gap, warning } from '../../../utils/diagnostics.js';
66
import { addOrMergeImport, getSdkExports, getSdkImports, isTypeOnlyImport } from '../../../utils/importUtils.js';
77
import { resolveTypesPackage } from '../../../utils/projectAnalyzer.js';
88
import { IMPORT_MAP, isAuthImport } from '../mappings/importMap.js';
@@ -82,7 +82,7 @@ export const importPathsTransform: Transform = {
8282
}
8383

8484
if (!mapping) {
85-
diagnostics.push(warning(filePath, line, `Unknown SDK import path: ${specifier}. Manual migration required.`));
85+
diagnostics.push(actionRequired(filePath, imp, `Unknown SDK import path: ${specifier}. Manual migration required.`));
8686
continue;
8787
}
8888

@@ -125,9 +125,9 @@ export const importPathsTransform: Transform = {
125125
effectiveTarget = mapping.symbolTargetOverrides[namedImports[0]!.getName()]!;
126126
} else if (namedImports.some(n => n.getName() in mapping.symbolTargetOverrides!)) {
127127
diagnostics.push(
128-
warning(
128+
actionRequired(
129129
filePath,
130-
line,
130+
imp,
131131
`Aliased import from ${specifier} mixes symbols that belong to different v2 packages. ` +
132132
`Split the import manually so each symbol targets the correct package.`
133133
)
@@ -145,9 +145,9 @@ export const importPathsTransform: Transform = {
145145
}
146146
if (namespaceImport) {
147147
diagnostics.push(
148-
warning(
148+
actionRequired(
149149
filePath,
150-
line,
150+
imp,
151151
`Namespace import of ${specifier}: exported symbol(s) ${Object.keys(mapping.renamedSymbols).join(', ')} ` +
152152
`were renamed in ${effectiveTarget}. Update qualified accesses manually.`
153153
)
@@ -234,7 +234,7 @@ function rewriteExportDeclarations(
234234
}
235235

236236
if (!mapping) {
237-
diagnostics.push(warning(filePath, line, `Unknown SDK export path: ${specifier}. Manual migration required.`));
237+
diagnostics.push(actionRequired(filePath, exp, `Unknown SDK export path: ${specifier}. Manual migration required.`));
238238
continue;
239239
}
240240

@@ -269,9 +269,9 @@ function rewriteExportDeclarations(
269269
targetPackage = mapping.symbolTargetOverrides[namedExports[0]!.getName()]!;
270270
} else if (namedExports.some(s => s.getName() in mapping.symbolTargetOverrides!)) {
271271
diagnostics.push(
272-
warning(
272+
actionRequired(
273273
filePath,
274-
line,
274+
exp,
275275
`Re-export from ${specifier} mixes symbols that belong to different v2 packages. ` +
276276
`Split the export manually so each symbol targets the correct package.`
277277
)
@@ -288,7 +288,7 @@ function rewriteExportDeclarations(
288288
spec.setName(newName);
289289
}
290290
if (REEXPORT_WARNINGS[name]) {
291-
diagnostics.push(warning(filePath, line, REEXPORT_WARNINGS[name]!));
291+
diagnostics.push(actionRequired(filePath, exp, REEXPORT_WARNINGS[name]!));
292292
}
293293
}
294294
changesCount++;

0 commit comments

Comments
 (0)