codemod iterations round 1 (#2274)
This commit is contained in:
parent
1b53a415ea
commit
f563440cfd
4 changed files with 96 additions and 32 deletions
|
|
@ -87,6 +87,7 @@ const LOCAL_PACKAGE_DIRS: Record<string, string> = {
|
|||
'@modelcontextprotocol/client': path.join(SDK_ROOT, 'packages/client'),
|
||||
'@modelcontextprotocol/core': path.join(SDK_ROOT, 'packages/core'),
|
||||
'@modelcontextprotocol/server': path.join(SDK_ROOT, 'packages/server'),
|
||||
'@modelcontextprotocol/server-legacy': path.join(SDK_ROOT, 'packages/server-legacy'),
|
||||
'@modelcontextprotocol/express': path.join(SDK_ROOT, 'packages/middleware/express'),
|
||||
'@modelcontextprotocol/fastify': path.join(SDK_ROOT, 'packages/middleware/fastify'),
|
||||
'@modelcontextprotocol/hono': path.join(SDK_ROOT, 'packages/middleware/hono'),
|
||||
|
|
|
|||
|
|
@ -62,7 +62,11 @@ export const IMPORT_MAP: Record<string, ImportMapping> = {
|
|||
StreamableHTTPServerTransport: 'NodeStreamableHTTPServerTransport'
|
||||
},
|
||||
symbolTargetOverrides: {
|
||||
StreamableHTTPServerTransport: '@modelcontextprotocol/node'
|
||||
StreamableHTTPServerTransport: '@modelcontextprotocol/node',
|
||||
// The companion options type moved with the transport. @modelcontextprotocol/node
|
||||
// re-exports it under the same name (a backward-compat alias for
|
||||
// WebStandardStreamableHTTPServerTransportOptions), so route it there without renaming.
|
||||
StreamableHTTPServerTransportOptions: '@modelcontextprotocol/node'
|
||||
}
|
||||
},
|
||||
'@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js': {
|
||||
|
|
|
|||
|
|
@ -101,28 +101,15 @@ function handleReference(
|
|||
return rewriteCapturedSafeParse(safeParseCall, localName, typeName, sourceFile, diagnostics);
|
||||
}
|
||||
|
||||
diagnostics.push(
|
||||
actionRequired(
|
||||
sourceFile.getFilePath(),
|
||||
ref,
|
||||
`${localName}.safeParse() not available in v2. Use \`isSpecType.${typeName}(value)\` for boolean validation, ` +
|
||||
`or \`specTypeSchemas.${typeName}['~standard'].validate(value)\` for full result.`
|
||||
)
|
||||
);
|
||||
return false;
|
||||
return rewriteUnsupportedSchemaCall(ref, safeParseCall, localName, typeName, 'safeParse', sourceFile, diagnostics);
|
||||
}
|
||||
|
||||
// Pattern: XSchema.parse(v) — diagnostic only
|
||||
// Pattern: XSchema.parse(v) — rewrite to the StandardSchema validate() primitive (or, when the
|
||||
// result is used, swap the identifier) so we never leave behind an import of a non-exported schema.
|
||||
if (isParsePattern(ref)) {
|
||||
diagnostics.push(
|
||||
actionRequired(
|
||||
sourceFile.getFilePath(),
|
||||
ref,
|
||||
`${localName}.parse() not available in v2. Use \`isSpecType.${typeName}(value)\` for validation, ` +
|
||||
`or \`specTypeSchemas.${typeName}['~standard'].validate(value)\` and check for issues.`
|
||||
)
|
||||
);
|
||||
return false;
|
||||
const parseAccess = ref.getParent() as import('ts-morph').PropertyAccessExpression;
|
||||
const parseCall = parseAccess.getParent() as import('ts-morph').CallExpression;
|
||||
return rewriteUnsupportedSchemaCall(ref, parseCall, localName, typeName, 'parse', sourceFile, diagnostics);
|
||||
}
|
||||
|
||||
// Pattern: XSchema used as value (function arg, assignment, etc.)
|
||||
|
|
@ -327,6 +314,68 @@ function rewriteCapturedSafeParse(
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles spec-schema usages that have no behavior-preserving v2 equivalent: the Zod-only
|
||||
* methods `.parse()` and (uncaptured) `.safeParse()`. In v2 these schemas are StandardSchemaV1
|
||||
* values that are NOT named public exports, so leaving the original import in place produces an
|
||||
* unresolved-import error (e.g. `PromptSchema` is not exported by `@modelcontextprotocol/server`).
|
||||
*
|
||||
* - Result discarded (validation for side-effect only): rewrite `XSchema.parse(v)` →
|
||||
* `specTypeSchemas.T['~standard'].validate(v)` so the code compiles. NOTE: `validate()` does not
|
||||
* throw, so `.parse()`'s throw-on-invalid behavior is lost — flagged via an actionRequired comment.
|
||||
* - Result used: swap only the identifier to `specTypeSchemas.T` so the import resolves; the
|
||||
* `.parse()`/`.safeParse()` call and its result shape still need a manual fix (flagged).
|
||||
*
|
||||
* Either way the original (now non-exported) schema import is dropped by the caller's
|
||||
* removeUnusedImport, so no dangling import survives.
|
||||
*/
|
||||
function rewriteUnsupportedSchemaCall(
|
||||
ref: import('ts-morph').Node,
|
||||
callNode: import('ts-morph').CallExpression,
|
||||
localName: string,
|
||||
typeName: string,
|
||||
method: 'parse' | 'safeParse',
|
||||
sourceFile: SourceFile,
|
||||
diagnostics: Diagnostic[]
|
||||
): boolean {
|
||||
const resultDiscarded = Node.isExpressionStatement(callNode.getParent());
|
||||
|
||||
if (resultDiscarded) {
|
||||
const argText = callNode
|
||||
.getArguments()
|
||||
.map(a => a.getText())
|
||||
.join(', ');
|
||||
const semantics =
|
||||
method === 'parse'
|
||||
? 'validate() does NOT throw on invalid input (parse() did) — if you relied on that, add `if (result.issues) throw …`.'
|
||||
: 'the result shape changed from { success, data, error } to { value, issues }.';
|
||||
diagnostics.push(
|
||||
actionRequired(
|
||||
sourceFile.getFilePath(),
|
||||
callNode,
|
||||
`Rewrote ${localName}.${method}() to specTypeSchemas.${typeName}['~standard'].validate(): ` +
|
||||
`v2 spec schemas are StandardSchemaV1, not Zod. Note: ${semantics}`
|
||||
)
|
||||
);
|
||||
callNode.replaceWithText(`specTypeSchemas.${typeName}['~standard'].validate(${argText})`);
|
||||
ensureImport(sourceFile, 'specTypeSchemas');
|
||||
return true;
|
||||
}
|
||||
|
||||
diagnostics.push(
|
||||
actionRequired(
|
||||
sourceFile.getFilePath(),
|
||||
ref,
|
||||
`${localName}.${method}() is not available on v2 spec schemas (StandardSchemaV1, not Zod). ` +
|
||||
`Replaced ${localName} with specTypeSchemas.${typeName}; rewrite the .${method}(...) call using ` +
|
||||
`specTypeSchemas.${typeName}['~standard'].validate(...) (returns { value, issues }, does not throw).`
|
||||
)
|
||||
);
|
||||
ref.replaceWithText(`specTypeSchemas.${typeName}`);
|
||||
ensureImport(sourceFile, 'specTypeSchemas');
|
||||
return true;
|
||||
}
|
||||
|
||||
function ensureImport(sourceFile: SourceFile, symbol: string): void {
|
||||
const existingImport = sourceFile.getImportDeclarations().find(imp => {
|
||||
if (!isAnyMcpSpecifier(imp.getModuleSpecifierValue())) return false;
|
||||
|
|
|
|||
|
|
@ -210,10 +210,12 @@ describe('spec-schema-access transform', () => {
|
|||
expect(text).not.toContain('return result.value');
|
||||
});
|
||||
|
||||
it('falls back to diagnostic for non-captured safeParse (bare expression)', () => {
|
||||
it('rewrites non-captured safeParse (bare expression) to validate()', () => {
|
||||
const input = [`import { ToolSchema } from '@modelcontextprotocol/server';`, `ToolSchema.safeParse(data);`, ''].join('\n');
|
||||
const { result } = applyTransform(input);
|
||||
expect(result.changesCount).toBe(0);
|
||||
const { text, result } = applyTransform(input);
|
||||
expect(text).toContain("specTypeSchemas.Tool['~standard'].validate(data)");
|
||||
expect(text).not.toMatch(/import\s*\{[^}]*ToolSchema[^}]*\}/);
|
||||
expect(result.changesCount).toBeGreaterThan(0);
|
||||
expect(result.diagnostics.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -327,16 +329,24 @@ describe('spec-schema-access transform', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('diagnostic only: .parse(v)', () => {
|
||||
it('emits diagnostic for parse usage', () => {
|
||||
describe('.parse(v)', () => {
|
||||
it('rewrites discarded parse() to the validate() primitive', () => {
|
||||
const input = [`import { ToolSchema } from '@modelcontextprotocol/server';`, `ToolSchema.parse(raw);`, ''].join('\n');
|
||||
const { text, result } = applyTransform(input);
|
||||
expect(text).toContain("specTypeSchemas.Tool['~standard'].validate(raw)");
|
||||
expect(text).not.toMatch(/import\s*\{[^}]*ToolSchema[^}]*\}/);
|
||||
expect(result.changesCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('swaps the identifier (import stays resolvable) when the parse() result is used', () => {
|
||||
const input = [`import { ToolSchema } from '@modelcontextprotocol/server';`, `const tool = ToolSchema.parse(raw);`, ''].join(
|
||||
'\n'
|
||||
);
|
||||
const { text, result } = applyTransform(input);
|
||||
expect(text).toContain('ToolSchema.parse');
|
||||
expect(result.changesCount).toBe(0);
|
||||
expect(result.diagnostics.length).toBe(1);
|
||||
expect(result.diagnostics[0]!.message).toContain('isSpecType.Tool');
|
||||
expect(text).toContain('specTypeSchemas.Tool.parse(raw)');
|
||||
expect(text).not.toMatch(/import\s*\{[^}]*ToolSchema[^}]*\}/);
|
||||
expect(result.changesCount).toBeGreaterThan(0);
|
||||
expect(result.diagnostics[0]!.message).toContain('specTypeSchemas.Tool');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -392,7 +402,7 @@ describe('spec-schema-access transform', () => {
|
|||
expect(text).not.toMatch(/import\s*\{[^}]*CallToolRequestSchema[^}]*\}/);
|
||||
});
|
||||
|
||||
it('keeps original schema import when some refs are diagnostic-only', () => {
|
||||
it('removes the schema import even when a ref falls back to a parse()/safeParse() rewrite', () => {
|
||||
const input = [
|
||||
`import { CallToolRequestSchema } from '@modelcontextprotocol/server';`,
|
||||
`const valid = CallToolRequestSchema.safeParse(data).success;`,
|
||||
|
|
@ -401,8 +411,8 @@ describe('spec-schema-access transform', () => {
|
|||
].join('\n');
|
||||
const { text } = applyTransform(input);
|
||||
expect(text).toContain('isSpecType.CallToolRequest(data)');
|
||||
expect(text).toContain('CallToolRequestSchema.parse');
|
||||
expect(text).toMatch(/import\s*\{[^}]*CallToolRequestSchema[^}]*\}/);
|
||||
expect(text).toContain('specTypeSchemas.CallToolRequest.parse(data)');
|
||||
expect(text).not.toMatch(/import\s*\{[^}]*CallToolRequestSchema[^}]*\}/);
|
||||
});
|
||||
|
||||
it('removes schema specifier from import that also has other symbols', () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue