Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
82 changes: 72 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,16 @@ permanently.
A `proof.f.mjs` is authored `.f.mjs` like any other. Its relative **runtime**
imports must target migrated `.f.mjs` modules. Type-only APIs may live in an
authored `types.ts` companion and are referenced directly through that real
source path. For example:
source path. Its leading module JSDoc block may include, for example:

```js
/** @import { Phantom } from '../phantom/types.ts' */
/**
* ...
*
* @module
*
* @import { Phantom } from '../phantom/types.ts'
*/
```

The same path is used by TypeScript `import type`; JSDoc `@import` introduces no
Expand Down Expand Up @@ -251,18 +257,66 @@ normally the part of the contract that matters.

## 4. Documentation

Use JSDoc for documenting TypeScript files. Every module should start with this
header:
Use JSDoc for module documentation in both TypeScript and JavaScript source.
Every implementation module starts with one module JSDoc block, followed by one
blank line before the first source-level import or declaration.

For TypeScript, put type-only imports first, external or built-in runtime imports
second, then repository-owned relative runtime imports: already-migrated
JavaScript before remaining TypeScript. Separate the import groups with one blank
line:

```ts
/**
* <...Module documentation...>
*
* @module
*/

import type ...
import type ...

import ... from 'node:...'
import ... from 'package'

import ... from '...mjs'
import ... from '...mjs'

import ... from '...ts'
import ... from '...ts'
```

where `<...Module documentation...>` should be documentation for the module.
For JavaScript, put all module-level `@import` tags in the same leading JSDoc
block as `@module`, then put one blank line before runtime imports. External or
built-in runtime imports come first, followed by repository-owned relative
`.mjs` runtime imports:

```js
/**
* <...Module documentation...>
*
* @module
*
* @import ...
* @import ...
*/

import ... from 'node:...'
import ... from 'package'

import ... from '...mjs'
import ... from '...mjs'
```

Do not put module-level `@import` tags in separate JSDoc comments. The `.mjs` /
`.ts` grouping and the Stage 1 migration restriction apply to repository-owned
relative runtime imports, not to external or built-in modules. During Stage 1, a
migrated JavaScript module has no remaining relative runtime `.ts` / `.f.ts`
import group: migrated JavaScript may depend at runtime on external modules and
migrated repository JavaScript, but not on remaining authored TypeScript
implementations. The blank line after the module JSDoc block is required even
when the module has no `@import` tags; it keeps the header detached from the first
import/declaration and preserves it through declaration emit.

Where each kind of documentation belongs:

Expand Down Expand Up @@ -529,11 +583,18 @@ forms such as `@template {Operation} out O`. Variance modifiers belong to type
parameters of a JSDoc type alias (`@typedef`); do not put `in` / `out` on an
ordinary function's `@template`, where TypeScript rejects them.

When JavaScript needs a type from an authored `types.ts`, use JSDoc `@import`
with that real source path:
When JavaScript needs a type from an authored `types.ts`, put JSDoc `@import`
with that real source path in the leading module JSDoc block; do not create a
separate `@import` comment. For example:

```js
/** @import { Types } from './types.ts' */
/**
* ...
*
* @module
*
* @import { Types } from './types.ts'
*/
```

The TypeScript implementation uses the same path:
Expand All @@ -546,7 +607,8 @@ Both forms are type-only and introduce no runtime import. The `types.ts` file
itself exists and is checked as ordinary TypeScript source, so this convention
does not rely on `.d.ts` substitution and works with Deno's source resolver.
Declaration-only `module.f.ts` files should normally become `types.ts` instead of
acquiring an artificial JavaScript runtime representation.
acquiring an artificial JavaScript runtime representation. See [§4](#4-documentation)
for the complete module-header and import-order convention.

Do not make migrated JavaScript point back to a remaining implementation `.ts` /
`.f.ts` merely for a type. If that type should survive independently, split it
Expand Down Expand Up @@ -1166,4 +1228,4 @@ Only add CHANGELOG entries for code changes — PRs that only touch `todo/`,
- Releasing is its own commit: the version lives in `package.json` (`"version"`)
— `deno.json` holds tasks and formatting only. When it's bumped, create a new
`## X.Y.Z` section in `CHANGELOG.md` immediately after `## Unreleased` and move
all entries from `## Unreleased` into it, leaving `## Unreleased` empty.
all entries from `## Unreleased` into it, leaving `## Unreleased` empty.
128 changes: 99 additions & 29 deletions todo/migrate-typescript-to-mjs.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,22 @@ language, including constructs that cannot be represented faithfully or
conveniently in JSDoc.

Both TypeScript and JavaScript implementations reference the same real source
file:
file. TypeScript uses a normal type-only import:

```ts
import type { Phantom } from './types.ts'
```

JavaScript puts the corresponding `@import` in its leading module JSDoc block:

```js
/** @import { Phantom } from './types.ts' */
/**
* ...
*
* @module
*
* @import { Phantom } from './types.ts'
*/
```

Both forms are type-only, and the referenced `types.ts` physically exists. This
Expand Down Expand Up @@ -188,6 +196,10 @@ The transition is intentionally asymmetric for runtime dependencies:
`.f.ts`, split that type into `types.ts` before migrating the JavaScript
consumer rather than retaining a JavaScript-to-TypeScript implementation edge.

These migration restrictions classify repository-owned authored source
relationships. External or built-in runtime imports such as `node:http` are not
migration edges and may remain where the module design requires them.

FunctionalScript parser support is not an eligibility condition. A `.f.ts`
implementation may move to `.f.mjs` even if the current FunctionalScript
compiler does not yet support all syntax in that file.
Expand Down Expand Up @@ -227,19 +239,29 @@ Use `@template out T`, `@template in T`, or constrained forms such as

A JavaScript implementation must not gain a real JavaScript import just because
it uses a separately declared type. Use JSDoc `@import` with the same real source
path used by `import type`:

```js
/** @import { Types } from './types.ts' */
```
path used by `import type`. All module-level `@import` tags belong in the leading
module JSDoc block together with `@module`; do not create separate `@import`
comment blocks.

This introduces no runtime dependency. The corresponding TypeScript
implementation uses `import type` with the same specifier:
The corresponding TypeScript implementation uses `import type` with the same
specifier:

```ts
import type { Types } from './types.ts'
```

JavaScript uses:

```js
/**
* ...
*
* @module
*
* @import { Types } from './types.ts'
*/
```

Do not point migrated JavaScript back at a remaining implementation `.ts` /
`.f.ts` merely for a type. If that type must survive independently of the
implementation, move or split it into `types.ts` first. If it is naturally
Expand Down Expand Up @@ -355,25 +377,68 @@ regression, not a type-contract one. Record it, keep writing the documentation i
the source, and file an upstream issue so the gap is tracked rather than
rediscovered by each migration.

#### Separate the `@module` header from the first import with a blank line
#### Module header and import ordering

A module's `@module` header can disappear from the emitted declaration too, but
that one is **not** an upstream gap — it is a source-formatting requirement, and
a blank line fixes it:
Every implementation module starts with one module JSDoc block. Always put one
blank line after that block before the first source-level import or declaration.

For TypeScript, put type-only imports first, external or built-in runtime imports
second, then repository-owned relative runtime imports: already-migrated
JavaScript before remaining TypeScript. Separate these groups with one blank
line:

```ts
/**
* <Module documentation>
*
* @module
*/

import type ...
import type ...

import ... from 'node:...'
import ... from 'package'

import ... from '...mjs'
import ... from '...mjs'

import ... from '...ts'
import ... from '...ts'
```

For JavaScript, put all module-level `@import` tags in the same leading JSDoc
block as `@module`, then put one blank line before runtime imports. External or
built-in runtime imports come first, followed by repository-owned relative
`.mjs` runtime imports:

```js
/**
* ...
* <Module documentation>
*
* @module
*
* @import ...
* @import ...
*/
// <- this blank line is load-bearing
/** @import { Tuple } from './types.ts' */
import { mask } from '...'

import ... from 'node:...'
import ... from 'package'

import ... from '...mjs'
import ... from '...mjs'
```

Without the blank line, the header is the leading comment of the first `import`
*statement* (an `@import` tag is a comment, not a statement, so it does not
separate them). Declaration emit rewrites the import list — dropping
The `.mjs` / `.ts` grouping and the Stage 1 migration restriction apply only to
repository-owned relative runtime imports. External or built-in imports are not
migration edges and may remain where the module design requires them. A migrated
JavaScript implementation has no remaining relative runtime `.ts` / `.f.ts`
import group: Stage 1 still forbids JavaScript runtime dependencies on remaining
authored TypeScript implementations.

The blank line after the module JSDoc block is load-bearing for declaration
emit. Without it, the header can become the leading comment of the first
`import` statement. Declaration emit rewrites the import list — dropping
runtime-only imports and synthesizing `import type` for what the declarations
actually reference — and when the statement carrying the header is not among the
survivors, the header goes with it. With the blank line the header detaches from
Expand Down Expand Up @@ -646,11 +711,13 @@ this rename.
than by what the `.f.ts` happened to export or by what a pending refactor
plans to delete. Types intentionally moved to `types.ts` use normal
TypeScript source visibility instead.
- [ ] Keep a blank line between a module's `@module` header and its first
`import` statement so the header survives declaration emit; fix the
modules that already lost theirs (`fjs/common/monoid`,
`fjs/types/btree/remove`, `fjs/types/btree/set`, `fjs/types/list`,
`fjs/types/nullable`).
- [ ] Apply the module-header/import convention: keep module-level JavaScript
`@import` tags in the same JSDoc block as `@module`, always put one blank
line after that block, group external/built-in runtime imports separately,
and order repository-owned relative runtime imports as migrated `.mjs`
before remaining `.ts`; fix the modules that already lose their header
(`fjs/common/monoid`, `fjs/types/btree/remove`, `fjs/types/btree/set`,
`fjs/types/list`, `fjs/types/nullable`).
- [ ] 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`
Expand Down Expand Up @@ -723,9 +790,12 @@ this rename.
JSDoc `@typedef` is recorded as a known upstream gap; an intentionally separate
`types.ts` may preserve declaration documentation through normal TypeScript
emit.
- Every migrated module's `@module` header survives into its emitted
declaration, which requires a blank line between that header and the first
`import` statement.
- Every implementation module follows the module-header/import convention:
JavaScript keeps module-level `@import` tags in the same JSDoc block as
`@module`, one blank line follows that block, external/built-in runtime imports
form their own group, and repository-owned relative runtime imports are ordered
as migrated `.mjs` before remaining `.ts`. The `@module` header survives
declaration emit.
- Every exported function's return type survives into its emitted declaration as
a named type, not `any` or `/*elided*/`; curried generic exports carry an
explicit `@returns` rather than relying on inference, and the per-arrow
Expand Down Expand Up @@ -781,4 +851,4 @@ this rename.
— upstream request for `stripInternal` support on JSDoc typedefs.
- [`fjs-nanvm-integration.md`](./fjs-nanvm-integration.md) — existing compiler
integration and compiler-compatibility migration.
- [`plan/roadmap.md`](./plan/roadmap.md) — project roadmap.
- [`plan/roadmap.md`](./plan/roadmap.md) — project roadmap.
Loading