From 6108021d0d9a308d1b008c4fcce4e4067d781940 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Jan 2025 08:30:09 +0000 Subject: [PATCH 1/2] chore(deps): bump the dependencies group across 1 directory with 3 updates Bumps the dependencies group with 3 updates in the / directory: [@swc/core](https://github.com/swc-project/swc), [@swc/core-linux-x64-gnu](https://github.com/swc-project/swc) and [@swc/core-darwin-arm64](https://github.com/swc-project/swc). Updates `@swc/core` from 1.5.7 to 1.10.6 - [Release notes](https://github.com/swc-project/swc/releases) - [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md) - [Commits](https://github.com/swc-project/swc/compare/v1.5.7...v1.10.6) Updates `@swc/core-linux-x64-gnu` from 1.5.7 to 1.10.6 - [Release notes](https://github.com/swc-project/swc/releases) - [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md) - [Commits](https://github.com/swc-project/swc/compare/v1.5.7...v1.10.6) Updates `@swc/core-darwin-arm64` from 1.5.7 to 1.10.6 - [Release notes](https://github.com/swc-project/swc/releases) - [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md) - [Commits](https://github.com/swc-project/swc/compare/v1.5.7...v1.10.6) --- updated-dependencies: - dependency-name: "@swc/core" dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: "@swc/core-linux-x64-gnu" dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: "@swc/core-darwin-arm64" dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- package-lock.json | 104 ++++++++++++------------ packages/openapi-generator/package.json | 6 +- 2 files changed, 55 insertions(+), 55 deletions(-) diff --git a/package-lock.json b/package-lock.json index 059b0ffd..be6d5d17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1624,13 +1624,13 @@ } }, "node_modules/@swc/core": { - "version": "1.5.7", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.5.7.tgz", - "integrity": "sha512-U4qJRBefIJNJDRCCiVtkfa/hpiZ7w0R6kASea+/KLp+vkus3zcLSB8Ub8SvKgTIxjWpwsKcZlPf5nrv4ls46SQ==", + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.6.tgz", + "integrity": "sha512-zgXXsI6SAVwr6XsXyMnqlyLoa1lT+r09bAWI1xT3679ejWqI1Vnl14eJG0GjWYXCEMKHCNytfMq3OOQ62C39QQ==", "hasInstallScript": true, "dependencies": { - "@swc/counter": "^0.1.2", - "@swc/types": "0.1.7" + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.17" }, "engines": { "node": ">=10" @@ -1640,19 +1640,19 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.5.7", - "@swc/core-darwin-x64": "1.5.7", - "@swc/core-linux-arm-gnueabihf": "1.5.7", - "@swc/core-linux-arm64-gnu": "1.5.7", - "@swc/core-linux-arm64-musl": "1.5.7", - "@swc/core-linux-x64-gnu": "1.5.7", - "@swc/core-linux-x64-musl": "1.5.7", - "@swc/core-win32-arm64-msvc": "1.5.7", - "@swc/core-win32-ia32-msvc": "1.5.7", - "@swc/core-win32-x64-msvc": "1.5.7" + "@swc/core-darwin-arm64": "1.10.6", + "@swc/core-darwin-x64": "1.10.6", + "@swc/core-linux-arm-gnueabihf": "1.10.6", + "@swc/core-linux-arm64-gnu": "1.10.6", + "@swc/core-linux-arm64-musl": "1.10.6", + "@swc/core-linux-x64-gnu": "1.10.6", + "@swc/core-linux-x64-musl": "1.10.6", + "@swc/core-win32-arm64-msvc": "1.10.6", + "@swc/core-win32-ia32-msvc": "1.10.6", + "@swc/core-win32-x64-msvc": "1.10.6" }, "peerDependencies": { - "@swc/helpers": "^0.5.0" + "@swc/helpers": "*" }, "peerDependenciesMeta": { "@swc/helpers": { @@ -1661,9 +1661,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.5.7", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.5.7.tgz", - "integrity": "sha512-bZLVHPTpH3h6yhwVl395k0Mtx8v6CGhq5r4KQdAoPbADU974Mauz1b6ViHAJ74O0IVE5vyy7tD3OpkQxL/vMDQ==", + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.10.6.tgz", + "integrity": "sha512-USbMvT8Rw5PvIfF6HyTm+yW84J9c45emzmHBDIWY76vZHkFsS5MepNi+JLQyBzBBgE7ScwBRBNhRx6VNhkSoww==", "cpu": [ "arm64" ], @@ -1676,9 +1676,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.5.7", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.5.7.tgz", - "integrity": "sha512-RpUyu2GsviwTc2qVajPL0l8nf2vKj5wzO3WkLSHAHEJbiUZk83NJrZd1RVbEknIMO7+Uyjh54hEh8R26jSByaw==", + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.10.6.tgz", + "integrity": "sha512-7t2IozcZN4r1p27ei+Kb8IjN4aLoBDn107fPi+aPLcVp2uFgJEUzhCDuZXBNW2057Mx1OHcjzrkaleRpECz3Xg==", "cpu": [ "x64" ], @@ -1691,9 +1691,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.5.7", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.5.7.tgz", - "integrity": "sha512-cTZWTnCXLABOuvWiv6nQQM0hP6ZWEkzdgDvztgHI/+u/MvtzJBN5lBQ2lue/9sSFYLMqzqff5EHKlFtrJCA9dQ==", + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.10.6.tgz", + "integrity": "sha512-CPgWT+D0bDp/qhXsLkIJ54LmKU1/zvyGaf/yz8A4iR+YoF6R5CSXENXhNJY8cIrb6+uNWJZzHJ+gefB5V51bpA==", "cpu": [ "arm" ], @@ -1706,9 +1706,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.5.7", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.5.7.tgz", - "integrity": "sha512-hoeTJFBiE/IJP30Be7djWF8Q5KVgkbDtjySmvYLg9P94bHg9TJPSQoC72tXx/oXOgXvElDe/GMybru0UxhKx4g==", + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.10.6.tgz", + "integrity": "sha512-5qZ6hVnqO/ShETXdGSzvdGUVx372qydlj1YWSYiaxQzTAepEBc8TC1NVUgYtOHOKVRkky1d7p6GQ9lymsd4bHw==", "cpu": [ "arm64" ], @@ -1721,9 +1721,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.5.7", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.5.7.tgz", - "integrity": "sha512-+NDhK+IFTiVK1/o7EXdCeF2hEzCiaRSrb9zD7X2Z7inwWlxAntcSuzZW7Y6BRqGQH89KA91qYgwbnjgTQ22PiQ==", + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.10.6.tgz", + "integrity": "sha512-hB2xZFmXCKf2iJF5y2z01PSuLqEoUP3jIX/XlIHN+/AIP7PkSKsValE63LnjlnWPnSEI0IxUyRE3T3FzWE/fQQ==", "cpu": [ "arm64" ], @@ -1736,9 +1736,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.5.7", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.5.7.tgz", - "integrity": "sha512-25GXpJmeFxKB+7pbY7YQLhWWjkYlR+kHz5I3j9WRl3Lp4v4UD67OGXwPe+DIcHqcouA1fhLhsgHJWtsaNOMBNg==", + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.10.6.tgz", + "integrity": "sha512-PRGPp0I22+oJ8RMGg8M4hXYxEffH3ayu0WoSDPOjfol1F51Wj1tfTWN4wVa2RibzJjkBwMOT0KGLGb/hSEDDXQ==", "cpu": [ "x64" ], @@ -1751,9 +1751,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.5.7", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.5.7.tgz", - "integrity": "sha512-0VN9Y5EAPBESmSPPsCJzplZHV26akC0sIgd3Hc/7S/1GkSMoeuVL+V9vt+F/cCuzr4VidzSkqftdP3qEIsXSpg==", + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.10.6.tgz", + "integrity": "sha512-SoNBxlA86lnoV9vIz/TCyakLkdRhFSHx6tFMKNH8wAhz1kKYbZfDmpYoIzeQqdTh0tpx8e/Zu1zdK4smovsZqQ==", "cpu": [ "x64" ], @@ -1766,9 +1766,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.5.7", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.5.7.tgz", - "integrity": "sha512-RtoNnstBwy5VloNCvmvYNApkTmuCe4sNcoYWpmY7C1+bPR+6SOo8im1G6/FpNem8AR5fcZCmXHWQ+EUmRWJyuA==", + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.10.6.tgz", + "integrity": "sha512-6L5Y2E+FVvM+BtoA+mJFjf/SjpFr73w2kHBxINxwH8/PkjAjkePDr5m0ibQhPXV61bTwX49+1otzTY85EsUW9Q==", "cpu": [ "arm64" ], @@ -1781,9 +1781,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.5.7", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.5.7.tgz", - "integrity": "sha512-Xm0TfvcmmspvQg1s4+USL3x8D+YPAfX2JHygvxAnCJ0EHun8cm2zvfNBcsTlnwYb0ybFWXXY129aq1wgFC9TpQ==", + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.10.6.tgz", + "integrity": "sha512-kxK3tW8DJwEkAkwy0vhwoBAShRebH1QTe0mvH9tlBQ21rToVZQn+GCV/I44dind80hYPw0Tw2JKFVfoEJyBszg==", "cpu": [ "ia32" ], @@ -1796,9 +1796,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.5.7", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.5.7.tgz", - "integrity": "sha512-tp43WfJLCsKLQKBmjmY/0vv1slVywR5Q4qKjF5OIY8QijaEW7/8VwPyUyVoJZEnDgv9jKtUTG5PzqtIYPZGnyg==", + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.10.6.tgz", + "integrity": "sha512-4pJka/+t8XcHee12G/R5VWcilkp5poT2EJhrybpuREkpQ7iC/4WOlOVrohbWQ4AhDQmojYQI/iS+gdF2JFLzTQ==", "cpu": [ "x64" ], @@ -1816,9 +1816,9 @@ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" }, "node_modules/@swc/types": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.7.tgz", - "integrity": "sha512-scHWahbHF0eyj3JsxG9CFJgFdFNaVQCNAimBlT6PzS3n/HptxqREjsm4OH6AN3lYcffZYSPxXW8ua2BEHp0lJQ==", + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.17.tgz", + "integrity": "sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==", "dependencies": { "@swc/counter": "^0.1.3" } @@ -14453,7 +14453,7 @@ "version": "0.0.0-semantically-released", "license": "Apache-2.0", "dependencies": { - "@swc/core": "1.5.7", + "@swc/core": "1.10.6", "cmd-ts": "0.13.0", "comment-parser": "1.4.1", "fp-ts": "2.16.9", @@ -14473,8 +14473,8 @@ "typescript": "4.7.4" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.5.7", - "@swc/core-linux-x64-gnu": "1.5.7" + "@swc/core-darwin-arm64": "1.10.6", + "@swc/core-linux-x64-gnu": "1.10.6" } }, "packages/openapi-generator/node_modules/typescript": { diff --git a/packages/openapi-generator/package.json b/packages/openapi-generator/package.json index c0ef1378..142b19fa 100644 --- a/packages/openapi-generator/package.json +++ b/packages/openapi-generator/package.json @@ -22,7 +22,7 @@ "test:target": "c8 --all --src src node --require @swc-node/register" }, "dependencies": { - "@swc/core": "1.5.7", + "@swc/core": "1.10.6", "cmd-ts": "0.13.0", "comment-parser": "1.4.1", "fp-ts": "2.16.9", @@ -39,8 +39,8 @@ "typescript": "4.7.4" }, "optionalDependencies": { - "@swc/core-linux-x64-gnu": "1.5.7", - "@swc/core-darwin-arm64": "1.5.7" + "@swc/core-linux-x64-gnu": "1.10.6", + "@swc/core-darwin-arm64": "1.10.6" }, "publishConfig": { "access": "public" From 8c4b71281853ae80f3dde4634d8245fd92429ccc Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Jan 2026 22:07:26 +0000 Subject: [PATCH 2/2] fix(openapi-generator): handle multibyte UTF-8 characters in comment extraction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SWC (written in Rust) provides byte-based span offsets, but JavaScript strings use character-based offsets (UTF-16 code units). When source code contains multibyte UTF-8 characters (e.g., À, 日, 😀), directly using SWC's byte offsets with String.slice() results in incorrect string extraction. This commit introduces byteOffsetToCharOffset() which properly converts byte offsets to character offsets by iterating through the string and accumulating byte lengths until reaching the target byte position. Test cases added for: - Extended Latin characters (2-byte UTF-8: À, ÿ, ñ, ü) - CJK characters (3-byte UTF-8: 日本語, 中文, 한국어) - Mixed multibyte characters at multiple positions - Multibyte characters at the very start of a file Fixes: DX-2788 --- packages/openapi-generator/src/comments.ts | 35 +- .../test/openapi/comments.test.ts | 502 ++++++++++++++++++ 2 files changed, 536 insertions(+), 1 deletion(-) diff --git a/packages/openapi-generator/src/comments.ts b/packages/openapi-generator/src/comments.ts index 54723da8..48c04059 100644 --- a/packages/openapi-generator/src/comments.ts +++ b/packages/openapi-generator/src/comments.ts @@ -1,13 +1,46 @@ import { parse as parseComment, Block } from 'comment-parser'; import { Schema } from './ir'; +/** + * Convert a UTF-8 byte offset to a JavaScript string character offset. + * SWC (written in Rust) uses byte offsets, but JavaScript strings use + * UTF-16 code unit offsets. This function handles the conversion by + * iterating through the string and accumulating byte lengths. + * + * @param str The source string + * @param byteOffset The byte offset to convert + * @returns The corresponding character offset + */ +function byteOffsetToCharOffset(str: string, byteOffset: number): number { + let charCount = 0; + let byteCount = 0; + + for (const char of str) { + const charBytes = Buffer.byteLength(char, 'utf8'); + if (byteCount + charBytes > byteOffset) break; + byteCount += charBytes; + charCount++; + } + + return charCount; +} + export function leadingComment( src: string, srcSpanStart: number, start: number, end: number, ): Block[] { - let commentString = src.slice(start - srcSpanStart, end - srcSpanStart).trim(); + // SWC uses byte offsets, but JavaScript strings use character offsets. + // When there are multibyte UTF-8 characters (e.g., À, 日, 😀), we need to + // convert byte offsets to character offsets for correct string slicing. + const startByteOffset = start - srcSpanStart; + const endByteOffset = end - srcSpanStart; + + const startCharOffset = byteOffsetToCharOffset(src, startByteOffset); + const endCharOffset = byteOffsetToCharOffset(src, endByteOffset); + + let commentString = src.slice(startCharOffset, endCharOffset).trim(); if (commentString.includes(' * ') && !/\/\*\*([\s\S]*?)\*\//.test(commentString)) { // The comment block seems to be JSDoc but was sliced incorrectly diff --git a/packages/openapi-generator/test/openapi/comments.test.ts b/packages/openapi-generator/test/openapi/comments.test.ts index fc8930e6..a61b2863 100644 --- a/packages/openapi-generator/test/openapi/comments.test.ts +++ b/packages/openapi-generator/test/openapi/comments.test.ts @@ -1488,4 +1488,506 @@ testCase("route with overriden comments in union", ROUTE_WITH_OVERRIDEN_COMMENTS } } } +}); + +// ============================================================================ +// Multibyte Character Tests +// These tests verify that SWC byte offsets are correctly converted to +// JavaScript character offsets when the source contains multibyte UTF-8 chars. +// ============================================================================ + +// Test case 1: Extended Latin characters (2-byte UTF-8) +const ROUTE_WITH_LATIN_EXTENDED_CHARS = ` +import * as t from 'io-ts'; +import * as h from '@api-ts/io-ts-http'; + +export const Body = t.type({ + /** + * Name with accented characters (À-ÿ, Ā-ſ) + * @pattern ^[A-Za-zÀ-ÿĀ-ſ\\s'-]+$ + */ + firstName: t.string, + /** + * Surname field (supports ñ, ü, ø, etc.) + * @pattern ^[A-Za-zÀ-ÿĀ-ſ\\s'-]+$ + */ + lastName: t.string, +}); + +/** + * Route testing Latin extended characters + * + * @operationId api.v1.latinChars + * @tag Test Routes + */ +export const route = h.httpRoute({ + path: '/latin-chars', + method: 'POST', + request: h.httpRequest({ + body: Body, + }), + response: { + 200: { + result: t.string + } + }, +}); +`; + +testCase('route with latin extended characters', ROUTE_WITH_LATIN_EXTENDED_CHARS, { + openapi: '3.0.3', + info: { + title: 'Test', + version: '1.0.0', + }, + paths: { + '/latin-chars': { + post: { + summary: 'Route testing Latin extended characters', + operationId: 'api.v1.latinChars', + tags: ['Test Routes'], + parameters: [], + requestBody: { + content: { + 'application/json': { + schema: { + properties: { + firstName: { + type: 'string', + description: "Name with accented characters (À-ÿ, Ā-ſ)", + pattern: "^[A-Za-zÀ-ÿĀ-ſ\\s'-]+$", + }, + lastName: { + type: 'string', + description: "Surname field (supports ñ, ü, ø, etc.)", + pattern: "^[A-Za-zÀ-ÿĀ-ſ\\s'-]+$", + }, + }, + required: ['firstName', 'lastName'], + type: 'object', + }, + }, + }, + }, + responses: { + 200: { + description: 'OK', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + result: { + type: 'string', + }, + }, + required: ['result'], + }, + }, + }, + }, + }, + }, + }, + }, + components: { + schemas: { + Body: { + title: 'Body', + type: 'object', + properties: { + firstName: { + type: 'string', + description: "Name with accented characters (À-ÿ, Ā-ſ)", + pattern: "^[A-Za-zÀ-ÿĀ-ſ\\s'-]+$", + }, + lastName: { + type: 'string', + description: "Surname field (supports ñ, ü, ø, etc.)", + pattern: "^[A-Za-zÀ-ÿĀ-ſ\\s'-]+$", + }, + }, + required: ['firstName', 'lastName'], + }, + }, + }, +}); + +// Test case 2: CJK characters (3-byte UTF-8) +const ROUTE_WITH_CJK_CHARS = ` +import * as t from 'io-ts'; +import * as h from '@api-ts/io-ts-http'; + +export const Body = t.type({ + /** + * 日本語の名前フィールド (Japanese name field) + * @example 山田太郎 + */ + japaneseName: t.string, + /** + * 中文名字字段 (Chinese name field) + * @example 张三 + */ + chineseName: t.string, + /** + * 한국어 이름 필드 (Korean name field) + * @example 김철수 + */ + koreanName: t.string, +}); + +/** + * Route testing CJK characters (日本語, 中文, 한국어) + * + * @operationId api.v1.cjkChars + * @tag Test Routes + */ +export const route = h.httpRoute({ + path: '/cjk-chars', + method: 'POST', + request: h.httpRequest({ + body: Body, + }), + response: { + 200: { + result: t.string + } + }, +}); +`; + +testCase('route with CJK characters', ROUTE_WITH_CJK_CHARS, { + openapi: '3.0.3', + info: { + title: 'Test', + version: '1.0.0', + }, + paths: { + '/cjk-chars': { + post: { + summary: 'Route testing CJK characters (日本語, 中文, 한국어)', + operationId: 'api.v1.cjkChars', + tags: ['Test Routes'], + parameters: [], + requestBody: { + content: { + 'application/json': { + schema: { + properties: { + japaneseName: { + type: 'string', + description: '日本語の名前フィールド (Japanese name field)', + example: '山田太郎', + }, + chineseName: { + type: 'string', + description: '中文名字字段 (Chinese name field)', + example: '张三', + }, + koreanName: { + type: 'string', + description: '한국어 이름 필드 (Korean name field)', + example: '김철수', + }, + }, + required: ['japaneseName', 'chineseName', 'koreanName'], + type: 'object', + }, + }, + }, + }, + responses: { + 200: { + description: 'OK', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + result: { + type: 'string', + }, + }, + required: ['result'], + }, + }, + }, + }, + }, + }, + }, + }, + components: { + schemas: { + Body: { + title: 'Body', + type: 'object', + properties: { + japaneseName: { + type: 'string', + description: '日本語の名前フィールド (Japanese name field)', + example: '山田太郎', + }, + chineseName: { + type: 'string', + description: '中文名字字段 (Chinese name field)', + example: '张三', + }, + koreanName: { + type: 'string', + description: '한국어 이름 필드 (Korean name field)', + example: '김철수', + }, + }, + required: ['japaneseName', 'chineseName', 'koreanName'], + }, + }, + }, +}); + +// Test case 3: Mixed multibyte characters at multiple positions +const ROUTE_WITH_MIXED_MULTIBYTE = ` +import * as t from 'io-ts'; +import * as h from '@api-ts/io-ts-http'; + +/** + * Café menu item (note: café has 2-byte é) + */ +const CaféItem = t.type({ + /** Item name (日本語 OK) */ + name: t.string, + /** Price in € (euros) */ + price: t.number, +}); + +export const Body = t.type({ + /** + * Order at Müller's café + * @example Crème brûlée + */ + item: CaféItem, + /** + * Customer name (supports: José, François, 田中) + */ + customerName: t.string, +}); + +/** + * Route with mixed multibyte: é, ü, è, û, 日本語 + * + * @operationId api.v1.mixedMultibyte + * @tag Test Routes + */ +export const route = h.httpRoute({ + path: '/mixed-multibyte', + method: 'POST', + request: h.httpRequest({ + body: Body, + }), + response: { + 200: { + result: t.string + } + }, +}); +`; + +testCase('route with mixed multibyte characters', ROUTE_WITH_MIXED_MULTIBYTE, { + openapi: '3.0.3', + info: { + title: 'Test', + version: '1.0.0', + }, + paths: { + '/mixed-multibyte': { + post: { + summary: 'Route with mixed multibyte: é, ü, è, û, 日本語', + operationId: 'api.v1.mixedMultibyte', + tags: ['Test Routes'], + parameters: [], + requestBody: { + content: { + 'application/json': { + schema: { + properties: { + item: { + allOf: [ + { + $ref: '#/components/schemas/CaféItem', + }, + ], + description: "Order at Müller's café", + example: 'Crème brûlée', + }, + customerName: { + type: 'string', + description: 'Customer name (supports: José, François, 田中)', + }, + }, + required: ['item', 'customerName'], + type: 'object', + }, + }, + }, + }, + responses: { + 200: { + description: 'OK', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + result: { + type: 'string', + }, + }, + required: ['result'], + }, + }, + }, + }, + }, + }, + }, + }, + components: { + schemas: { + CaféItem: { + title: 'CaféItem', + description: 'Café menu item (note: café has 2-byte é)', + type: 'object', + properties: { + name: { + type: 'string', + description: 'Item name (日本語 OK)', + }, + price: { + type: 'number', + description: 'Price in € (euros)', + }, + }, + required: ['name', 'price'], + }, + Body: { + title: 'Body', + type: 'object', + properties: { + item: { + allOf: [ + { + $ref: '#/components/schemas/CaféItem', + }, + ], + description: "Order at Müller's café", + example: 'Crème brûlée', + }, + customerName: { + type: 'string', + description: 'Customer name (supports: José, François, 田中)', + }, + }, + required: ['item', 'customerName'], + }, + }, + }, +}); + +// Test case 4: Multibyte characters at the very start of the file +const ROUTE_WITH_MULTIBYTE_AT_START = `/** + * 日本語コメント at the very start + */ +import * as t from 'io-ts'; +import * as h from '@api-ts/io-ts-http'; + +export const Body = t.type({ + /** Normal field after multibyte start */ + value: t.string, +}); + +/** + * Route where file starts with multibyte chars + * + * @operationId api.v1.multibyteStart + * @tag Test Routes + */ +export const route = h.httpRoute({ + path: '/multibyte-start', + method: 'POST', + request: h.httpRequest({ + body: Body, + }), + response: { + 200: { + result: t.string + } + }, +}); +`; + +testCase('route with multibyte at file start', ROUTE_WITH_MULTIBYTE_AT_START, { + openapi: '3.0.3', + info: { + title: 'Test', + version: '1.0.0', + }, + paths: { + '/multibyte-start': { + post: { + summary: 'Route where file starts with multibyte chars', + operationId: 'api.v1.multibyteStart', + tags: ['Test Routes'], + parameters: [], + requestBody: { + content: { + 'application/json': { + schema: { + properties: { + value: { + type: 'string', + description: 'Normal field after multibyte start', + }, + }, + required: ['value'], + type: 'object', + }, + }, + }, + }, + responses: { + 200: { + description: 'OK', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + result: { + type: 'string', + }, + }, + required: ['result'], + }, + }, + }, + }, + }, + }, + }, + }, + components: { + schemas: { + Body: { + title: 'Body', + type: 'object', + properties: { + value: { + type: 'string', + description: 'Normal field after multibyte start', + }, + }, + required: ['value'], + }, + }, + }, }); \ No newline at end of file