Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
8a4e3a8
Prepare the upstream typedef-doc issue; retitle the formatter todo
claude Aug 13, 2026
6af5280
Check off the upstream-issue and formatter items in the migration TODO
claude Aug 13, 2026
c839da4
Merge branch 'main': resolve the module-header item both sides updated
claude Aug 13, 2026
4f3942e
Apply review: the final comment block never attaches; survivor count …
claude Aug 13, 2026
366be05
Merge branch 'main' into claude/upstream-issue-and-formatter-rename
sergey-shandar Aug 13, 2026
d7a1290
Merge branch 'main' into claude/upstream-issue-and-formatter-rename
sergey-shandar Aug 13, 2026
1822997
Apply review: blank-line trigger; move the last documented typedefs t…
claude Aug 13, 2026
1eae96f
Merge branch 'main' into claude/upstream-issue-and-formatter-rename
sergey-shandar Aug 13, 2026
016adf6
Merge branch 'main' into claude/upstream-issue-and-formatter-rename
sergey-shandar Aug 13, 2026
3d353f5
Apply review round 3: BREAKING prefix on the typedef-move entry
claude Aug 13, 2026
2902a1a
Merge branch 'main' into claude/upstream-issue-and-formatter-rename
sergey-shandar Aug 13, 2026
e021bef
Apply review round 4: drop the wrong positional claim
claude Aug 13, 2026
dcb8e86
Merge branch 'main' into claude/upstream-issue-and-formatter-rename
sergey-shandar Aug 13, 2026
7eb6834
Merge branch 'main' into claude/upstream-issue-and-formatter-rename
sergey-shandar Aug 13, 2026
4728f8a
Merge branch 'main' into claude/upstream-issue-and-formatter-rename
sergey-shandar Aug 13, 2026
c8268b6
Apply review round 5: the attachment rule is file-start AND blank line
claude Aug 14, 2026
afb2352
Merge branch 'main' into claude/upstream-issue-and-formatter-rename
sergey-shandar Aug 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 1 addition & 20 deletions fjs/djs/transpiler/module.f.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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<djsResult>
* readonly stack: List<string>
* 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)
Expand Down
26 changes: 26 additions & 0 deletions fjs/djs/transpiler/types.ts
Original file line number Diff line number Diff line change
@@ -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<djsResult>
readonly stack: List<string>
readonly error: ParseError | null
}
9 changes: 1 addition & 8 deletions fjs/protocol/mcp/stdio/module.f.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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<O, Response | null>} Step
*/

const stringifyJson = stringify(sort)

/** The parse-error response (`-32700`, `id: null`) for a malformed input line. */
Expand Down
2 changes: 1 addition & 1 deletion fjs/protocol/mcp/stdio/proof.f.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
16 changes: 16 additions & 0 deletions fjs/protocol/mcp/stdio/types.ts
Original file line number Diff line number Diff line change
@@ -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<O extends Operation> = (value: Unknown) => Effect<O, Response | null>
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
215 changes: 215 additions & 0 deletions todo/blocked/jsdoc-typedef-doc-declaration-emit.md
Original file line number Diff line number Diff line change
@@ -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.
Loading
Loading