diff --git a/CHANGELOG.md b/CHANGELOG.md index 30c8b6cae..36171a85c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,12 @@ history. ## Unreleased +- **BREAKING CHANGES:** the last documented public typedefs authored in `.mjs` + move to sibling `types.ts` files — `ParseContext`/`djsResult` to + `fjs/djs/transpiler/types.ts` and `Step` to + `fjs/protocol/mcp/stdio/types.ts` — so their documentation survives + declaration emit; importers must use the `types.ts` specifier for these types + [#1530](https://github.com/functionalscript/functionalscript/pull/1530) - `text/utf16`: `u16` now rejects non-integers, so a fractional word is reported invalid (`0xFFFFFFFF`) instead of being misclassified by the surrogate/BMP range checks — which only partition the integers in diff --git a/fjs/djs/transpiler/module.f.mjs b/fjs/djs/transpiler/module.f.mjs index 533203f44..3995034b8 100644 --- a/fjs/djs/transpiler/module.f.mjs +++ b/fjs/djs/transpiler/module.f.mjs @@ -5,12 +5,11 @@ * * @import { Unknown } from '../types.ts' * @import { Result } from '../../types/result/types.ts' - * @import { List } from '../../types/list/types.ts' - * @import { OrderedMap } from '../../types/ordered_map/types.ts' * @import { ParseError } from '../parser/types.ts' * @import { AstModule } from '../ast/types.ts' * @import { Effect } from '../../effects/types.ts' * @import { ReadFile } from '../../effects/node/types.ts' + * @import { ParseContext } from './types.ts' */ import { error, ok } from '../../types/result/module.f.mjs' @@ -24,24 +23,6 @@ import { run } from '../ast/module.f.mjs' import { foldStep, pure, step } from '../../effects/module.f.mjs' import { readUtf8File } from '../../effects/node/module.f.mjs' -/** - * State threaded through the recursive transpilation of a DJS module graph. - * - * - `complete`: modules that have been fully parsed and evaluated, keyed by path. - * - `stack`: import chain currently being resolved (used to detect circular dependencies). - * - `error`: the first parse error encountered, or `null` while everything is clean. - * @typedef {{ - * readonly complete: OrderedMap - * readonly stack: List - * readonly error: ParseError | null - * }} ParseContext - */ - -/** - * The evaluated DJS value produced for one successfully transpiled module. - * @typedef {{ djs: Unknown }} djsResult - */ - /** @type {(context: ParseContext) => (path: string) => Unknown} */ const mapDjs = context => path => { const res = at(path)(context.complete) diff --git a/fjs/djs/transpiler/types.ts b/fjs/djs/transpiler/types.ts new file mode 100644 index 000000000..3cde8c4ee --- /dev/null +++ b/fjs/djs/transpiler/types.ts @@ -0,0 +1,26 @@ +/** + * Types for the DJS transpiler. + * + * @module + */ + +import type { Unknown } from '../types.ts' +import type { List } from '../../types/list/types.ts' +import type { OrderedMap } from '../../types/ordered_map/types.ts' +import type { ParseError } from '../parser/types.ts' + +/** The evaluated DJS value produced for one successfully transpiled module. */ +export type djsResult = { djs: Unknown } + +/** + * State threaded through the recursive transpilation of a DJS module graph. + * + * - `complete`: modules that have been fully parsed and evaluated, keyed by path. + * - `stack`: import chain currently being resolved (used to detect circular dependencies). + * - `error`: the first parse error encountered, or `null` while everything is clean. + */ +export type ParseContext = { + readonly complete: OrderedMap + readonly stack: List + readonly error: ParseError | null +} diff --git a/fjs/protocol/mcp/stdio/module.f.mjs b/fjs/protocol/mcp/stdio/module.f.mjs index 5a2e1624b..2a1fc42a6 100644 --- a/fjs/protocol/mcp/stdio/module.f.mjs +++ b/fjs/protocol/mcp/stdio/module.f.mjs @@ -29,10 +29,10 @@ * * @module * - * @import { Unknown } from '../../../media/json/types.ts' * @import { Effect, Operation } from '../../../effects/types.ts' * @import { IoResult, Read, Write } from '../../../effects/node/types.ts' * @import { Response } from '../../json_rpc/types.ts' + * @import { Step } from './types.ts' */ import { pure, step } from '../../../effects/module.f.mjs' @@ -43,13 +43,6 @@ import { sort } from '../../../types/object/module.f.mjs' import { internalError, jsonrpc, parseError } from '../../json_rpc/module.f.mjs' import { error, ok } from '../../../types/result/module.f.mjs' -/** - * A transport step: maps one parsed JSON-RPC message to a response, or `null` - * for a notification that needs no reply. The shape of `mcpStep(config)(handlers)(key)`. - * @template {Operation} O - * @typedef {(value: Unknown) => Effect} Step - */ - const stringifyJson = stringify(sort) /** The parse-error response (`-32700`, `id: null`) for a malformed input line. */ diff --git a/fjs/protocol/mcp/stdio/proof.f.mjs b/fjs/protocol/mcp/stdio/proof.f.mjs index 43f295a11..1a5cceaa9 100644 --- a/fjs/protocol/mcp/stdio/proof.f.mjs +++ b/fjs/protocol/mcp/stdio/proof.f.mjs @@ -3,7 +3,7 @@ * @import { Effect } from '../../../effects/types.ts' * @import { State } from '../../../effects/node/virtual/types.ts' * @import { Id, Response } from '../../json_rpc/types.ts' - * @import { Step } from './module.f.mjs' + * @import { Step } from './types.ts' */ import { assertEq } from '../../../asserts/module.f.mjs' diff --git a/fjs/protocol/mcp/stdio/types.ts b/fjs/protocol/mcp/stdio/types.ts new file mode 100644 index 000000000..baab24454 --- /dev/null +++ b/fjs/protocol/mcp/stdio/types.ts @@ -0,0 +1,16 @@ +/** + * Types for the stdio transport of JSON-RPC / MCP servers. + * + * @module + */ + +import type { Unknown } from '../../../media/json/types.ts' +import type { Effect, Operation } from '../../../effects/types.ts' +import type { Response } from '../../json_rpc/types.ts' + +/** + * A transport step: maps one parsed JSON-RPC message to a response, or `null` + * for a notification that needs no reply. The shape of + * `mcpStep(config)(handlers)(key)`. + */ +export type Step = (value: Unknown) => Effect diff --git a/fjs/todo/formatter-for-f-js-and-f-ts-files.md b/fjs/todo/formatter-for-f-js-files.md similarity index 67% rename from fjs/todo/formatter-for-f-js-and-f-ts-files.md rename to fjs/todo/formatter-for-f-js-files.md index ee79ac7b7..c2cf698e0 100644 --- a/fjs/todo/formatter-for-f-js-and-f-ts-files.md +++ b/fjs/todo/formatter-for-f-js-files.md @@ -1,11 +1,11 @@ -## Formatter for `.f.js` and `.f.ts` files +## Formatter for `.f.js` files **Priority:** P3 **Status:** open Find a third-party formatter (or, failing that, build one) that handles -`.f.js` and `.f.ts` files correctly for everyday coding: indentation, line -width, spacing. +`.f.mjs` (and, after stage 2, `.f.js`) files correctly for everyday coding: +indentation, line width, spacing. Out of scope: language-level normalization, such as rewriting JS string spellings into JSON ones — that belongs to FunctionalScript tooling itself, diff --git a/todo/blocked/jsdoc-typedef-doc-declaration-emit.md b/todo/blocked/jsdoc-typedef-doc-declaration-emit.md new file mode 100644 index 000000000..c141d98d4 --- /dev/null +++ b/todo/blocked/jsdoc-typedef-doc-declaration-emit.md @@ -0,0 +1,215 @@ +# JSDoc `@typedef` documentation is dropped by tsgo declaration emit + +**Priority:** P2 +**Status:** blocked + +## Trigger + +The upstream issue below is filed at +[microsoft/typescript-go](https://github.com/microsoft/typescript-go/issues), +fixed, and a TypeScript release containing the fix is picked up by this +repository's `devDependencies`. Until then, substantial documented type APIs +live in `types.ts` (whose declaration comments emit through the normal +TypeScript pipeline), per `todo/migrate-typescript-to-mjs.md`. + +## Problem + +Documentation written on a JSDoc `@typedef` in authored `.mjs` can vanish from +the emitted `.d.mts`, so the published package loses exactly its type +documentation while the repository stays green. `fjs/crypto/sha2` was the +original case (its documented `V8`/`V16`/`State`/`Sha2` types have since moved +to `types.ts`, which is the workaround, not the fix). As of +[#1530](https://github.com/functionalscript/functionalscript/pull/1530) the +repository's exposure is limited to future code: the last three documented +public typedefs authored in `.mjs` (`ParseContext`/`djsResult` in +`fjs/djs/transpiler`, `Step` in `fjs/protocol/mcp/stdio`) moved to sibling +`types.ts` files, and a sweep measures zero remaining. Implementation-local +`_`-prefixed typedefs keep their (fewer) doc comments in `.mjs` and are still +subject to the bug. + +Measured with the minimal reproduction below, the loss is *shape-dependent*, +which the earlier record did not know: + +| typedef block shape | tsc 5.9.3 (strada) | tsc 7.0.2 (tsgo) | +| --- | --- | --- | +| first thing in the file, blank line before what follows (comment block or statement) | prose kept on `export type`, trimmed (tags such as `@example` stripped from the attached copy) | **full block kept, verbatim** | +| standalone — last comment block in the file | prose kept on `export type`, trimmed | **`export type` emitted bare; the block dangles detached** | +| two comment blocks with no blank line between them | prose kept on both `export type`s, trimmed | **both emitted bare; the adjacent blocks form one trivia run and neither attaches** | +| block directly followed by a declaration | prose kept on `export type`, trimmed; the original block also emitted in full on the declaration, duplicated | **`export type` emitted bare; the doc attaches to the *following* declaration** | +| one block declaring two `@typedef`s | prose kept on both `export type`s, trimmed | **both emitted bare; the block dangles detached** | + +(Where a detached block lands varies with the surrounding statements; the +per-reproduction statements below are each measured. "Emitted bare" is the +invariant.) + +The rule that fits every measured case on tsgo (review's 2×5 matrix of +{file-start, statement-preceded} × {EOF, adjacent statement, blank+statement, +adjacent comment, blank+comment}, plus the header-preceded case below): **a +typedef block attaches to its emitted `export type` only when it is the first +thing in the file *and* a blank line separates it from whatever follows** — +statement or comment block alike; both conditions are necessary, and in every +other measured shape the type is emitted bare. Since a real module starts +with its header comment, no typedef block in one is the first thing in the +file, so in practice a module's typedef documentation never attaches on tsgo +(measured: with a header block preceding, the header rides above the bare +`export type` while the typedef's own block mis-attaches to the following +declaration). All of it is a regression relative to strada, which kept the +prose in every measured shape. +(Strada's own tag-stripping and duplication are the older, adjacent bugs: +microsoft/TypeScript#43534, fixed for the services layer only, and +microsoft/TypeScript#61664.) + +## Reproduction + +`repro.mjs`, compiled with +`tsc --allowJs --checkJs --declaration --emitDeclarationOnly --strict`: + +```js +/** + * Doc on a typedef whose block also precedes a declaration. + * + * @typedef {8 | 16} Width + */ +export const width = 8 + +/** + * Multi-tag block doc. + * + * @typedef {{ readonly a: number }} Rec + * @typedef {{ readonly b: number }} Rec2 + */ + +export {} +``` + +tsgo 7.0.2 emits: + +```ts +export type Width = 8 | 16; +/** + * Doc on a typedef whose block also precedes a declaration. + * + * @typedef {8 | 16} Width + */ +export declare const width = 8; +export type Rec = { + readonly a: number; +}; +export type Rec2 = { + readonly b: number; +}; +/** + * Multi-tag block doc. + * + * @typedef {{ readonly a: number }} Rec + * @typedef {{ readonly b: number }} Rec2 + */ +export {}; +``` + +`Width`, `Rec`, and `Rec2` are bare; `Width`'s documentation decorates +`width` instead. strada 5.9.3 on the same input keeps (trimmed) prose on all +three `export type`s. + +The smallest reproduction is a file containing nothing but one documented +typedef block: + +```js +/** + * Only block, then EOF. + * + * @typedef {8} Only + */ +``` + +tsgo 7.0.2 emits `export type Only = 8;` bare, with the block dangling after +it; strada 5.9.3 attaches the (trimmed) prose to the type. + +The practically important case is a file that begins with a module header — +the shape of every real module: + +```js +/** + * Module header. + */ + +/** + * doc + * + * @typedef {8} T + */ + +export const post = 1 +``` + +tsgo 7.0.2 emits: + +```ts +/** + * Module header. + */ +export type T = 8; +/** + * doc + * + * @typedef {8} T + */ +export declare const post = 1; +``` + +`T`'s own documentation never attaches — the header rides above the bare +type, and the doc block lands on `post`. Delete the header block (making the +typedef block the first thing in the file) and the same input attaches the +doc to `export type T` in full. + +## Ready-to-file upstream issue + +Title: **Declaration emit loses JSDoc `@typedef` documentation when the block +precedes a declaration or declares multiple typedefs** + +Body: + +> **Repro:** the `repro.mjs` + flags above (also reproduces with a +> `tsconfig.json` carrying the same options). +> +> **Expected:** each emitted `export type` carries the documentation written +> on its `@typedef`, as TypeScript 5.9.3 does (modulo #43534-style tag +> stripping), and as tsgo itself already does when the typedef block is +> followed by another comment block. +> +> **Actual (tsgo 7.0.2):** `export type Width = 8 | 16;` is emitted with no +> documentation and the block attaches to the following `export declare const +> width`; a block declaring two typedefs loses its documentation on both; a +> file whose only content is one documented typedef block emits the type bare +> with the block dangling after it. The rule that fits every case measured so +> far: a typedef block attaches to its emitted type only when it is the first +> thing in the file *and* a blank line separates it from whatever follows — +> both conditions necessary. In particular, in a file that begins with a +> module header comment, no typedef documentation ever attaches: the header +> rides above the bare `export type` while the typedef's own block +> mis-attaches to the following declaration. +> +> **Impact:** for JavaScript-authored packages (JSDoc types, `declaration: +> true`), the published `.d.mts` silently loses its type documentation; the +> authoring repository sees no error. Related: #43534 (services-layer fix, +> declaration emit untouched), #61664 (proposes stripping redundant JSDoc +> type directives while keeping documentation). + +## Tasks + +- [ ] File the issue at `microsoft/typescript-go` (the regression is in tsgo; + strada's milder trimming/duplication is already tracked upstream) and + record the issue link here. +- [ ] When the trigger fires, re-run the reproduction, then reconsider which + type-level APIs still need the `types.ts` placement solely for + documentation fidelity. + +## Related + +- [`todo/migrate-typescript-to-mjs.md`](../migrate-typescript-to-mjs.md) — + "Typedef documentation does not survive declaration emit". +- [`jsdoc-typedef-strip-internal.md`](./jsdoc-typedef-strip-internal.md) — + the adjacent `@internal` + `stripInternal` gap for JSDoc typedefs. +- [microsoft/TypeScript#43534](https://github.com/microsoft/TypeScript/issues/43534), + [microsoft/TypeScript#61664](https://github.com/microsoft/TypeScript/issues/61664) + — adjacent strada behaviors. diff --git a/todo/migrate-typescript-to-mjs.md b/todo/migrate-typescript-to-mjs.md index cf9a32325..36ad5cc88 100644 --- a/todo/migrate-typescript-to-mjs.md +++ b/todo/migrate-typescript-to-mjs.md @@ -393,13 +393,23 @@ comment attached to the *following* declaration instead fixed for the services layer), and [microsoft/TypeScript#61664](https://github.com/microsoft/TypeScript/issues/61664) proposes stripping redundant JSDoc type directives from declaration emit while -keeping documentation. Neither tracks this loss directly; no upstream issue for -it has been identified yet. +keeping documentation. Neither tracks this loss directly. + +Re-measured in +[#1530](https://github.com/functionalscript/functionalscript/pull/1530) +across three review rounds, the rule on tsc 7.0.2 is: a typedef block keeps +its documentation only when it is the first thing in the file *and* a blank +line separates it from whatever follows — so in a real module, whose header +comment comes first, typedef documentation never attaches at all. A +regression relative to strada 5.9.3, which kept trimmed prose in every +measured shape. The minimal reproduction and a +paste-ready upstream body (targeting `microsoft/typescript-go`) live in +[`blocked/jsdoc-typedef-doc-declaration-emit.md`](./blocked/jsdoc-typedef-doc-declaration-emit.md); +filing it is that issue's first task. This does not block any migration group — it is a documentation-fidelity -regression, not a type-contract one. Record it, keep writing the documentation in -the source, and file an upstream issue so the gap is tracked rather than -rediscovered by each migration. +regression, not a type-contract one. Keep writing the documentation in the +source meanwhile. #### Module header and import ordering @@ -835,10 +845,15 @@ blocking, plus the prose sweep. The remaining items are listed under block, so all 127 emitted module declarations carry the header. Still open in this item: sweeping scattered `@import` comments into the leading block and the import-group ordering. -- [ ] File an upstream issue for JSDoc typedef documentation being dropped from +- [x] File an upstream issue for JSDoc typedef documentation being dropped from declaration emit, and keep writing type documentation in the source meanwhile; substantial type APIs may instead live directly in `types.ts` - when that is the cleaner module design. + when that is the cleaner module design. Spun out in + [#1530](https://github.com/functionalscript/functionalscript/pull/1530) + into + [`blocked/jsdoc-typedef-doc-declaration-emit.md`](./blocked/jsdoc-typedef-doc-declaration-emit.md) + with a measured reproduction and a paste-ready upstream body; the filing + itself, an external action, is that issue's first task. - [ ] Treat `_`-prefixed JSDoc typedef names as private even when declarations emit them as exports, but still require `**BREAKING CHANGES:**` whenever a change to one alters the assignability of a public declaration. @@ -1025,10 +1040,16 @@ person can re-check rather than re-derive. Counts are as of (`fjs/bnf/todo/unicode-rules.md`, `fjs/effects/todo/node-module-layering.md` and ~40 others), since no such file may be authored any more. - All 21 files that still contain the old extension anywhere (22 before + All 21 files that still contain the old extension anywhere are listed + below, and each one is deliberate. (History of the count: `205.md` left + the set when [#1520](https://github.com/functionalscript/functionalscript/pull/1520) - deleted `205.md`) are listed - below, and each one is deliberate. Describing the migration or the + deleted it, the formatter issue left when + [#1530](https://github.com/functionalscript/functionalscript/pull/1530) + retitled it, and earlier revisions of this paragraph ran one short — + `fjs/emergent_testing/scenarios.md`, which quotes the deleted `run.sh` + verbatim, was in the measured set but never enumerated. Review on #1530 + caught it.) Describing the migration or the extension itself: this file, `AGENTS.md`, [`fjs/fsc/README.md`](../fjs/fsc/README.md), [`f-mjs-package-support.md`](../fjs/ci/todo/f-mjs-package-support.md), @@ -1039,8 +1060,12 @@ person can re-check rather than re-derive. Counts are as of [`plan/roadmap.md`](./plan/roadmap.md), [`lang/README.md`](./lang/README.md), [`demo/README.md`](./demo/README.md), [`nanvm-lib/todo/mvp-roadmap.md`](../nanvm-lib/todo/mvp-roadmap.md), + and [`blocked/js-extension-type-annotations.md`](./blocked/js-extension-type-annotations.md) - and [`formatter-for-f-js-and-f-ts-files.md`](../fjs/todo/formatter-for-f-js-and-f-ts-files.md). + (the formatter issue left the set when + [#1530](https://github.com/functionalscript/functionalscript/pull/1530) + retitled it to + [`formatter-for-f-js-files.md`](../fjs/todo/formatter-for-f-js-files.md)). Quoting `shouldLoad`, which still matches `.f.ts`: [`664-emergent-testing-module-files.md`](../fjs/emergent_testing/todo/664-emergent-testing-module-files.md) and [`skip-property.md`](../fjs/emergent_testing/todo/skip-property.md) @@ -1049,8 +1074,10 @@ person can re-check rather than re-derive. Counts are as of deleted it with the scenario suite it described). Recording a superseded convention or a completed move: [`028-unit-test-examples-api.md`](../fjs/emergent_testing/todo/028-unit-test-examples-api.md), - [`throw-payload-assertions.md`](../fjs/emergent_testing/todo/throw-payload-assertions.md) - and [`group-fs-subdirectories-by-concern.md`](../fjs/todo/group-fs-subdirectories-by-concern.md). + [`throw-payload-assertions.md`](../fjs/emergent_testing/todo/throw-payload-assertions.md), + [`group-fs-subdirectories-by-concern.md`](../fjs/todo/group-fs-subdirectories-by-concern.md) + and [`scenarios.md`](../fjs/emergent_testing/scenarios.md), which quotes + the deleted scenario harness verbatim. Plus [`browser-testing.md`](../fjs/emergent_testing/todo/browser-testing.md) and [`serializable-data.md`](../fjs/types/rtti/todo/serializable-data.md), each its own item below. Re-measure with the same resolve-against-the-tree @@ -1067,11 +1094,10 @@ person can re-check rather than re-derive. Counts are as of have produced a plan that no longer describes anything. Decide whether the transpile step survives for `types.ts`-only reasons, shrinks to a bundling concern, or goes away — then rewrite the document to match. -- [ ] **Retitle `formatter-for-f-js-and-f-ts-files.md`.** - [`fjs/todo/formatter-for-f-js-and-f-ts-files.md`](../fjs/todo/formatter-for-f-js-and-f-ts-files.md) - names `.f.ts` in its filename and title. A formatter no longer needs to - handle that extension; renaming the file is a trivial follow-up left out of - the prose sweep because it changes a path rather than prose. +- [x] **Retitle `formatter-for-f-js-and-f-ts-files.md`.** Done in + [#1530](https://github.com/functionalscript/functionalscript/pull/1530): + now [`fjs/todo/formatter-for-f-js-files.md`](../fjs/todo/formatter-for-f-js-files.md), + naming `.f.mjs` (and the stage-2 `.f.js`) as the formatter's targets. - [ ] **Fix the one broken doc link that is not a rename artifact.** `fjs/types/rtti/todo/serializable-data.md` links to `../data/module.f.ts`; `fjs/types/rtti/data/` has never existed, so this needs an author decision