Skip to content
Merged
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
126 changes: 116 additions & 10 deletions todo/migrate-typescript-to-mjs.md
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,13 @@ this rename.

### Tasks

**Stage 1 source migration is complete.** No authored `.f.ts` remains
(`find . -name '*.f.ts'` returns 0 outside `node_modules`); the last twelve
migrated in [#1505](https://github.com/functionalscript/functionalscript/pull/1505).
What is left is the packaging and cleanup work the source migration was
blocking, plus the prose sweep. The remaining items are listed under
[Remaining after stage 1](#remaining-after-stage-1) below.

- [ ] Complete
[`f-mjs-package-support.md`](../fjs/ci/todo/f-mjs-package-support.md),
including `allowJs` / `checkJs`, authored `types.ts`, Deno validation, and
Expand All @@ -693,23 +700,23 @@ this rename.
- [ ] Identify type-only `.ts` / `.f.ts` files and convert them directly to
`types.ts`; identify truly runtime-empty declaration-only `.f.mjs` files
that should become `types.ts` as well when that is the cleaner design.
- [ ] Rename `fjs/types/phantom/module.f.ts` to
- [x] Rename `fjs/types/phantom/module.f.ts` to
`fjs/types/phantom/types.ts` and update its type-only consumers to use the
real `types.ts` source path; do not introduce a runtime phantom value.
- [ ] For mixed modules where a type-level API should stay in TypeScript, split
- [x] For mixed modules where a type-level API should stay in TypeScript, split
that API into sibling `types.ts` before migrating JavaScript consumers.
- [ ] Identify runtime-dependency-leaf `.ts` / `.f.ts` implementation files and
- [x] Identify runtime-dependency-leaf `.ts` / `.f.ts` implementation files and
migrate those first; `types.ts` companions do not participate in that
runtime ordering.
- [ ] Migrate `proof.f.ts` to `proof.f.mjs` when the proof is JavaScript/JSDoc
- [x] Migrate `proof.f.ts` to `proof.f.mjs` when the proof is JavaScript/JSDoc
ready and its authored runtime dependencies are migrated; allow stable
type-only imports from `types.ts` and do not gate this on compiler support.
- [ ] Validate a migrated `.mjs` / `.f.mjs` fixture with an authored `types.ts`,
using the same real `types.ts` path from `.ts` and `.mjs`, including
TypeScript, Deno, Bun, package emit, and clean consumers.
- [ ] Verify emitted declarations reference package paths that actually exist and
determine whether generated `types.js` is required for portable consumers.
- [ ] Keep migrated JavaScript free of runtime **and type-only source**
- [x] Keep migrated JavaScript free of runtime **and type-only source**
dependencies on remaining implementation `.ts` / `.f.ts`; split required
declarations into `types.ts` first.
- [ ] Translate TypeScript generic constraints and `in` / `out` variance that
Expand Down Expand Up @@ -764,8 +771,9 @@ this rename.
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`).
already lose their header. `fjs/common/monoid` is now fixed; four remain
(`fjs/types/btree/remove`, `fjs/types/btree/set`, `fjs/types/list`,
`fjs/types/nullable`), each emitting a declaration with no `@module`.
- [ ] 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 All @@ -776,9 +784,14 @@ this rename.
- [ ] Once a module is `.mjs`, treat any later move of a public JSDoc typedef to
a `_` name as an ordinary breaking API change with its own changelog entry
and importer updates, not as a visibility cleanup.
- [ ] Continue upward through the runtime dependency graph in reviewable groups
until no authored TypeScript implementation/proof source remains.
- [ ] Translate `.ts` to `.mjs` and `.f.ts` to `.f.mjs`, moving static type
- [x] Continue upward through the runtime dependency graph in reviewable groups
until no authored TypeScript implementation/proof source remains. Done for
every module in the migration group: no `.f.ts` is left anywhere. The
`fjs/emergent_testing/scenarios/*.pass.ts` fixtures are still authored
TypeScript that `run.sh` hard-links to `_scenario.proof.ts`, but their
extension is the thing under test rather than an unmigrated module — see
the scenario item under [Remaining after stage 1](#remaining-after-stage-1).
- [x] Translate `.ts` to `.mjs` and `.f.ts` to `.f.mjs`, moving static type
information either to JSDoc or to an intentionally separate `types.ts`
without weakening public type semantics.
- [ ] Update imports, proofs, tests, coverage globs, scripts, generated CI, and
Expand All @@ -801,6 +814,99 @@ this rename.
- [ ] Keep the compiler-compatibility migration explicitly **blocked by** this
task.

#### Remaining after stage 1

Each item below is stated with the measurement that produced it, so the next
person can re-check rather than re-derive. Counts are as of
[#1505](https://github.com/functionalscript/functionalscript/pull/1505).

- [ ] **Make `npm run cov` report real coverage.** It has been vacuous for
several PRs — reviewers keep reporting `100.00` over 0 tests and having to
exclude coverage from their verification — but *not* because of the
include globs, so do not start there. The script already passes
`--test-coverage-include=**/module.f.mjs` alongside the now-dead
`**/module.f.ts` (the `.mjs` glob was added in #1422). Dropping the dead
glob is worth doing but changes nothing measurable:
`--test-coverage-include` only filters which files appear in the report,
so it cannot make a run that executed nothing report something.

The cause is test *discovery*. With no path arguments `node --test` looks
for its own default patterns (`*.test.*`, `test.*`, `*-test.*`,
`test-*.*`, `*_test.*`, `test/**`); the repo's proofs are `proof.f.mjs` /
`module.f.mjs` and match none of them. The only file in the tree that does
match is `fjs/emergent_testing/all.test.ts`, and what happens then is
Node-version-dependent — on v23 the run reports 0 tests, while on v22.22.2
it discovers that file and executes the suite through it. The real suite
runs via the repo's own runner (`npm test` -> `node ./fjs/module.mjs t`,
2495 tests). `scenarios/run.sh` corroborates the discovery problem: it
hard-links `all.ts` to `_all.test.ts` precisely so `node --test` will find
it.

So restoring the signal means giving `node --test` entrypoints it actually
runs, or collecting coverage through `fjs`'s runner — not editing globs.
The cheapest candidate is naming the entrypoint in the script, which is
measured to work on both versions: adding
`fjs/emergent_testing/all.test.ts` as a path argument to `cov` yields
`tests 2431 / pass 2431 / fail 0` and a real per-file report on v22.22.2,
and the same file named explicitly also runs on v23. Confirm it reports on
whichever Node CI uses before adopting it, and pin that version — the
2431 here against 2495 from `npm test` is a second discrepancy worth
understanding rather than papering over.
- [ ] **Settle whether generated `types.js` is required for portable
resolution.** This gates the two items after it and is the one open
correctness risk for published consumers. After `npm run prepack`,
`grep -rhoE "from '[^']*\.ts'" --include='*.d.ts' --include='*.d.mts' .`
(minus `node_modules` and `.d.ts` specifiers) counts **801** imports
written `from '…/types.ts'`, and `types.ts` is *not* in the
tarball: `package.json`'s `files` lists `**/*.d.ts` but no `**/*.ts`, so a
consumer resolves those specifiers only if its toolchain substitutes
`.ts` -> `.d.ts`. TypeScript does; per the `types.ts` section above, Deno
does not. Verify against real clean consumers on Node, Deno and Bun, and
record whether `types.js`, `types.d.ts`, or both must ship. Not introduced
by the source migration — the same command at `3859e7d4`, the commit
before stage 1 finished, counts **778** — but it is now the last thing
standing between stage 1 and a release. Quote the command with any count:
narrower scopes and regexes give figures a few apart, which has already
caused two rounds of harmless disagreement. Tracked in
[`f-mjs-package-support.md`](../fjs/ci/todo/f-mjs-package-support.md).
- [ ] **Then remove the JavaScript-emitting `tsc` pass, if the experiment
allows.** `prepack`'s second pass (`tsc --noEmit false --declaration
false`) now emits exactly 96 files: 85 `types.js`, one per authored
`types.ts`, and 11 from `fjs/emergent_testing/scenarios` plus
`all.test.ts`. It therefore cannot simply be deleted — its remaining
output is the `types.js` whose necessity the item above decides, and the
scenario fixtures the item below decides. Sequence it after both.
- [ ] **Then drop the blanket `.gitignore` rule** for generated JavaScript
(`.gitignore` line 131). Blocked on the
same two: while the emit pass runs, 96 generated `.js` land in the tree
and need the blanket ignore.
- [ ] **Decide what happens to the `emergent_testing` scenario fixtures.**
`fjs/emergent_testing/scenarios/*.ts`, `scenarios/all.ts` and
`all.test.ts` are the only authored non-`types.ts` TypeScript left. Their
extension is load-bearing: `run.sh` dispatches on `*.pass.ts` /
`*.fail.ts` and hard-links the scenario to `_scenario.proof.ts` and
`all.ts` to `_all.test.ts`, so what they exercise is `node --test`,
`bun test` and `deno test` executing a **TypeScript** proof natively.
Porting them to `.mjs` would delete that coverage rather than move it, so
this is a decision about whether native-TypeScript execution should still
be tested — keep them, replace the coverage some other way, or drop it.
- [ ] **Sweep the remaining stale prose.** 88 mentions across 42 `.md` files
name an `X.f.ts` whose `X.f.mjs` now exists (measured by resolving each
mention against the tree, excluding `CHANGELOG.md`, whose history is
correctly left alone). The largest are
`fjs/bnf/todo/proof-recognizer-and-fixtures.md` (11), `fjs/fsc/README.md`
(5) and `fjs/types/rtti/README.md` (5); this file itself has 6. Snippets
copied out of these produce broken imports. Separately, `AGENTS.md` has 24
`.f.ts` mentions and 5 `import type` references whose guidance should now
lead with the JavaScript/JSDoc form and keep the TypeScript form only
where it still applies (`types.ts`). This subsumes the existing sweep task
above; the numbers are here so progress is measurable.
- [ ] **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
rather than an extension change. It is the only broken relative link to a
source file left in the tree.

### Acceptance criteria

- `allowJs` and `checkJs` are enabled before the first authored TypeScript
Expand Down
Loading