docs(tools): record the case-mapping read point and narrow the class-name quantifier

`camelCase`'s `toUpperCase()` is a fourth reader of the engine's Unicode
tables, on a table distinct from XID membership and with a wider window: a
tool named U+019B passes `isBareIdentifier` and compiles as `async def` on
CPython 3.9.6, but Node maps the head to U+A7DC and the declared
`class ꟜArgs` fails there with `invalid non-printable character`. Record it
alongside the three XID read points in the renderer docs and in the note's
CPython-floor obligation, and pin the derivation with a test.

Correct three over-quantified sentences: a `camelCase`-derived class name is
evaluated for every tool but only reaches emitted text when some object shape
in the schema declares a `TypedDict`. Attribute the `str.isidentifier()`
equivalence to `IDENTIFIER` rather than to the predicate, which is
deliberately stricter, and restore the antecedent the mode qualification
dropped.
This commit is contained in:
Chinesezjc
2026-08-05 21:33:30 +08:00
parent 2914a87eda
commit be98a0b978
5 changed files with 54 additions and 20 deletions

View File

@@ -472,6 +472,27 @@ describe('renderToolsSdkPy', () => {
expect(text).not.toContain('async def find')
})
it('derives a class name through the case-mapping table, independently of the bare-name predicate', () => {
// The head capitalization reads a table `isBareIdentifier` never consults,
// so the class-name path can carry a character the predicate cleared. ƛ
// (U+019B) is XID_Start and NFKC-stable, so the method is emitted bare;
// the head maps to Ƛ (U+A7DC), a code point the engine's tables assign and
// an older interpreter's do not. This pins which table produced the name,
// so a change to the mapping step shows up here rather than only in a
// downstream Python parse.
const text = renderToolsSdkPy([
{
name: 'ƛ',
description: 'Lambda with stroke.',
parameters: { type: 'object', additionalProperties: false, properties: { q: { type: 'string' } }, required: ['q'] },
output: { type: 'string' },
},
])
expect(text).toContain('async def ƛ(self, args: ꟜArgs) -> str:')
expect(text).toContain('class ꟜArgs(TypedDict):')
expect(text).not.toContain('class ƛArgs')
})
it('drops a surrogate half rather than cutting a pair when capping an astral class-name base', () => {
// Class-name bases are capped by `slice`, which counts UTF-16 code units,
// so a boundary landing inside an astral pair would leave a lone high