-
-
Notifications
You must be signed in to change notification settings - Fork 6
todo: separate private types into private.ts #1740
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+286
−0
Merged
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
dc1b5f3
todo: separate private types into private.ts
sergey-shandar 202f749
todo: prohibit typedefs in implementation files
sergey-shandar fbe4f48
todo: define private declaration cleanup
sergey-shandar f1154ec
todo: validate private types in packed artifact
sergey-shandar beef545
todo: add rtti.f.mjs type definition convention
sergey-shandar 9bddb33
todo: rename RTTI companion to meta
sergey-shandar a14d7b8
todo: allow function-local typedefs
sergey-shandar 18ab3d9
todo: put private declaration cleanup in prepack
sergey-shandar 0b1e6f9
todo: cover meta FunctionalScript files
sergey-shandar 3ddac53
todo: require import type in TypeScript type files
sergey-shandar c64c52e
todo: allow private helpers in public type graph
sergey-shandar de51397
todo: make public type relocation breaking
sergey-shandar 32eec93
todo: broaden meta constants convention
sergey-shandar 556e535
todo: make meta moves breaking API changes
sergey-shandar 7368e06
todo: reconcile private type policy
sergey-shandar 89e33c1
Merge branch 'main' into todo/private-types-ts
sergey-shandar 18c604e
todo: reconcile authored TypeScript policy
sergey-shandar 304e544
todo: define public declaration closure
sergey-shandar 5ffffd7
todo: apply typedef rule to all f.mjs files
sergey-shandar 0401908
todo: apply typedef rule to all mjs
sergey-shandar 8622f5a
todo: clarify optional meta proof imports
sergey-shandar 59c1468
todo: treat retained JSDoc imports as comments
sergey-shandar 0a948de
todo: keep meta coverage policy minimal
sergey-shandar 01e4309
todo: mark private metadata constants with underscore
sergey-shandar 0d839d9
todo: preserve type module dependency order
sergey-shandar 114fd58
todo: make mjs typedef rule repository-wide
sergey-shandar be38c09
todo: clarify recursive metadata placement
sergey-shandar 1e69c5a
todo: make private/meta files optional tools
sergey-shandar e785b3b
todo: make metadata an optional submodule
sergey-shandar addb6d9
Merge branch 'main' into todo/private-types-ts
sergey-shandar 21c444e
todo: keep dependency diagram intra-module
sergey-shandar File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,286 @@ | ||
| ## Keep private types out of public declarations | ||
|
|
||
| **Priority:** P2 | ||
| **Status:** open | ||
|
|
||
| ### Problem | ||
|
|
||
| TypeScript declaration emit turns file-scope JSDoc `@typedef`s in authored | ||
| `.mjs` files into declaration aliases. Implementation-private `_` types therefore | ||
| leak into generated `.d.ts` / `.d.mts` files and add noise to the public surface. | ||
|
|
||
| The requirement is a clean, self-contained public declaration/API boundary. | ||
| `private.ts` and subordinate modules such as `meta/module.f.mjs` are **tools** for | ||
| reaching that result, not required companion files. | ||
|
|
||
| ### Rules | ||
|
|
||
| #### No file-scope typedefs in authored `.mjs` | ||
|
|
||
| No authored `.mjs` anywhere in the repository may contain a **file-scope** JSDoc | ||
| `@typedef`, regardless of directory, basename, or whether the file is | ||
| FunctionalScript. This includes `module.f.mjs`, `proof.f.mjs`, host `.mjs` files, | ||
| descriptive companions such as `testlib.f.mjs`, and root/`todo/` files such as | ||
| `todo/proof.f.mjs`. | ||
|
|
||
| Function-local typedefs remain allowed. This is especially useful for compile-time | ||
| proofs that need lexical or downstream runtime values: | ||
|
|
||
| ```js | ||
| const signatures = () => { | ||
| /** @typedef {Assert<Equal<ReturnType<typeof step<...>>, Effect<...>>>} _Step */ | ||
| /** @typedef {Assert<Equal<ReturnType<typeof catchStep<...>>, Effect<...>>>} _CatchStep */ | ||
| } | ||
| ``` | ||
|
|
||
| Private type and runtime constant names continue to use a leading `_`. | ||
|
|
||
| #### Public declaration closure | ||
|
|
||
| `types.ts` describes the public declaration closure: | ||
|
|
||
| - public types; | ||
| - private `_` helpers required transitively by shipped public declarations, | ||
| including declarations of exported runtime functions/values. | ||
|
|
||
| For example, if an exported `find` declaration contains `_SortedArray<T>`, then | ||
| `_SortedArray` is part of the public declaration closure and stays in `types.ts` | ||
| (or is inlined). Moving it to an unshipped private module would make the public | ||
| declaration incomplete. | ||
|
|
||
| `types.ts` must not depend on `private.ts`. | ||
|
|
||
| `private.ts` is optional. Use it only when separating implementation-private | ||
| file-scope types outside the public declaration closure makes the design cleaner. | ||
| Do not create it mechanically for every `_` name. | ||
|
|
||
| #### Dependency order | ||
|
|
||
| Within one module directory, preserve the dependency direction for the roles that | ||
| exist: | ||
|
|
||
| ```text | ||
| types.ts <- private.ts <- module.f.mjs <- proof.f.mjs <- module.mjs <- proof.mjs | ||
| ``` | ||
|
|
||
| The arrow points from dependency to dependent. This is a layering guide, not a | ||
| requirement that every file or edge exists. A subordinate module such as | ||
| `meta/module.f.mjs` is a separate module and is therefore described separately | ||
| below rather than appearing in this intra-directory diagram. | ||
|
|
||
| Move verification downstream before moving implementation upstream. For example, | ||
| `fjs/effects/types.ts` currently imports implementation functions only to assert | ||
| `ReturnType<typeof ...>` signatures. Those assertions verify `module.f.mjs`, so | ||
| move them into one or more proof functions in `proof.f.mjs`; keep the functions | ||
| in `module.f.mjs`. | ||
|
|
||
| Analyze constrained and recursive cases individually rather than inventing broad | ||
| exceptions: | ||
|
|
||
| - `fjs/media/revision`: `LockMap` / `LockSchema` can remain in `types.ts`; recursive | ||
| `lock` can remain in `module.f.mjs` when it requires the named `LockSchema` | ||
| annotation; move `Assert<Check<...>>` consistency checks into a proof function. | ||
| - `fjs/edag`: recursive RTTI such as `exp` can remain in `module.f.mjs` when its | ||
| annotation depends on public EDAG types; move file-scope consistency asserts | ||
| into proof functions. | ||
|
|
||
| The goal is to preserve the dependency direction and simplify the public surface, | ||
| not to satisfy a mechanical file-placement rule. | ||
|
|
||
| ### Optional metaprogramming submodule | ||
|
|
||
| When declarative runtime constants are shared between TypeScript and runtime code, | ||
| it can be useful to split them into a normal subordinate module, for example: | ||
|
|
||
| ```text | ||
| meta/ | ||
| module.f.mjs | ||
| ``` | ||
|
|
||
| `meta` here means **metaprogramming**: declarative definitions of types/schema-like | ||
| information that are useful at both compile time (TypeScript through `typeof`, | ||
| RTTI conversion, indexed access, etc.) and runtime. | ||
|
|
||
| Typical examples are: | ||
|
|
||
| - RTTI/schema constants; | ||
| - `as const`-style literal data; | ||
| - declarative lookup tables whose literal shape defines or constrains types. | ||
|
|
||
| This is only a suggestion. Do not create `meta/` merely because a runtime value | ||
| appears in a type proof. Ordinary implementation functions stay in | ||
| `module.f.mjs`; recursively annotated metadata may also stay there when moving it | ||
| would reverse the dependency direction. | ||
|
|
||
| The parent module may depend on `meta/module.f.mjs` like any other lower-level | ||
| module. The `meta/` module itself follows the same normal module conventions and, | ||
| if it grows additional files, its own intra-directory dependency order. | ||
|
|
||
| A private constant exported from `meta/module.f.mjs` for sibling-module linkage | ||
| uses a leading `_`: | ||
|
|
||
| ```js | ||
| // meta/module.f.mjs | ||
| export const _framingKeywords = | ||
| /** @type {const} */ (['import', 'const', 'export', 'default', 'from']) | ||
| ``` | ||
|
|
||
| ```ts | ||
| // private.ts or types.ts | ||
| import type { _framingKeywords } from './meta/module.f.mjs' | ||
| ``` | ||
|
|
||
| ```js | ||
| // module.f.mjs | ||
| import { _framingKeywords } from './meta/module.f.mjs' | ||
| ``` | ||
|
|
||
| Exportability is linkage, not API status: `_` means consumers must not depend on | ||
| the name. Renaming/removing it is not breaking solely because it is exported. | ||
|
|
||
| Because `meta/module.f.mjs` is just another `module.f.mjs`, existing tooling | ||
| already handles it: | ||
|
|
||
| - emergent testing loads it as `*.f.mjs`; | ||
| - the existing Node `**/module.f.mjs` coverage filter includes it; | ||
| - the existing Deno `.*module\\.f\\.mjs` filter includes it. | ||
|
|
||
| No special metadata filename or coverage rule is needed. | ||
|
|
||
| ### Breaking migrations | ||
|
|
||
| Moving an existing public type from an authored `.mjs` declaration surface to | ||
| `types.ts` changes its public type import path. Moving an existing public runtime | ||
| constant into a subordinate module such as `meta/module.f.mjs` changes its runtime | ||
| import path. | ||
|
|
||
| When such moves are chosen, treat them as intentional breaking changes: | ||
|
|
||
| - update every repository importer; | ||
| - update the changelog; | ||
| - do **not** add compatibility typedefs, exports, or re-exports to preserve the | ||
| old entry point. | ||
|
|
||
| Private `_` names are not public API merely because declaration emit or module | ||
| linkage exposes them. | ||
|
|
||
| ### Declaration emission and packaging | ||
|
|
||
| If `private.ts` is used, keep it in the normal TypeScript program so source users | ||
| are checked. Declaration emit may therefore create an intermediate | ||
| `private.d.ts`. | ||
|
|
||
| Do not try to exclude `private.ts` from checking. Instead delete generated | ||
| `private.d.ts` files as the final `prepack` step, after declaration emit and the | ||
| existing declaration round-trip check, before package contents are selected. | ||
|
|
||
| Do **not** rewrite/post-process emitted declaration text. TypeScript may retain a | ||
| source comment such as: | ||
|
|
||
| ```js | ||
| /** @import { _Private } from './private.ts' */ | ||
| ``` | ||
|
|
||
| inside an emitted declaration. In `.d.ts` / `.d.mts` this is only a comment, not | ||
| a TypeScript module dependency, so it may remain after the private declaration is | ||
| removed. | ||
|
|
||
| Package validation must check semantic dependencies, not raw text: | ||
|
|
||
| - no authored/generated private type artifact that is intended to be unshipped is | ||
| present in the tarball; | ||
| - no packed declaration semantically depends on an unshipped private type module; | ||
| - a clean TypeScript consumer installed from the tarball type-checks successfully. | ||
|
|
||
| ### Repository policy | ||
|
|
||
| When this TODO is implemented: | ||
|
|
||
| - update root `AGENTS.md` with the repository-wide rule that authored `.mjs` files | ||
| may not contain file-scope JSDoc `@typedef`; | ||
| - update `fjs/AGENTS.md` with the public-declaration-closure rule, optional | ||
| `private.ts`, optional subordinate metaprogramming modules such as | ||
| `meta/module.f.mjs`, and the dependency-order guidance; | ||
| - update `fjs/fsc/README.md` and delete or narrow | ||
| `todo/blocked/jsdoc-typedef-strip-internal.md` so the repository does not keep | ||
| two conflicting private-type strategies. | ||
|
|
||
| Authored TypeScript type modules (`types.ts`, and `private.ts` when present) remain | ||
| type-only and use named `import type { ... }` imports. | ||
|
|
||
| ### Tasks | ||
|
|
||
| - [ ] Document the repository-wide prohibition on file-scope JSDoc `@typedef` in | ||
| authored `.mjs`; allow function-local typedefs. | ||
| - [ ] Migrate existing violations, including authored `.mjs` outside `fjs/` such | ||
| as `todo/proof.f.mjs`. | ||
| - [ ] Keep `types.ts` as the public declaration closure; retain/in-line private | ||
| helpers required by public declarations. | ||
| - [ ] Use `private.ts` only where separating implementation-private file-scope | ||
| types improves the design. | ||
| - [ ] Preserve the intra-directory dependency direction shown above; move | ||
| verification downstream when that is cleaner. | ||
| - [ ] Move the `fjs/effects/types.ts` implementation-signature asserts into proof | ||
| functions in `fjs/effects/proof.f.mjs`. | ||
| - [ ] Review recursive cases individually, including `fjs/media/revision` and | ||
| `fjs/edag`; keep recursive RTTI in `module.f.mjs` when required by layering | ||
| and move consistency asserts into proof functions. | ||
| - [ ] Where useful, split declarative compile-time/runtime constants into a normal | ||
| subordinate module such as `meta/module.f.mjs`; do not require it. | ||
| - [ ] Preserve leading `_` for private types and private runtime constants. | ||
| - [ ] Treat chosen public import-path moves as breaking changes with no | ||
| compatibility re-exports. | ||
| - [ ] If `private.ts` is used, delete generated `private.d.ts` as the final | ||
| `prepack` step. | ||
| - [ ] Do not text-postprocess emitted declarations; validate semantic private | ||
| dependencies and clean-consumer type checking instead. | ||
| - [ ] Add fixtures/examples covering: public-declaration helpers, optional | ||
| `private.ts`, function-local proof typedefs, recursive RTTI kept in | ||
| `module.f.mjs`, optional `meta/module.f.mjs`, retained non-semantic JSDoc | ||
| comments, and authored `.mjs` outside `fjs/`. | ||
| - [ ] Update root/fjs policy documentation and reconcile the old `_` leak policy. | ||
|
|
||
| ### Acceptance criteria | ||
|
|
||
| - The public declaration/API surface is clean and self-contained. | ||
| - No authored `.mjs` anywhere in the repository contains a file-scope JSDoc | ||
| `@typedef`; function-local typedefs are allowed. | ||
| - `types.ts` contains the public declaration closure and does not depend on an | ||
| unshipped private type module. | ||
| - `private.ts`, when present, is an optional implementation tool rather than a | ||
| required companion. | ||
| - A subordinate module such as `meta/module.f.mjs`, when present, is an optional | ||
| metaprogramming/design tool rather than a special file role or requirement. | ||
| - The intra-directory dependency direction is preserved; assertions do not create | ||
| reverse edges merely for convenience. | ||
| - Private types/constants use leading `_`, even when linkage requires an export. | ||
| - Existing `module.f.mjs` discovery and coverage rules automatically include | ||
| `meta/module.f.mjs`; no metadata-specific coverage convention exists. | ||
| - Chosen public import-path moves are breaking migrations with importers/changelog | ||
| updated and no compatibility re-exports. | ||
| - If declaration emit creates `private.d.ts`, final-`prepack` cleanup removes it | ||
| before packaging. | ||
| - Emitted declarations are not text-postprocessed; retained JSDoc `@import` | ||
| comments are allowed when they are non-semantic. | ||
| - The packed artifact has no semantic dependency on an unshipped private type | ||
| module, and a clean TypeScript consumer type-checks successfully. | ||
| - Root `AGENTS.md`, `fjs/AGENTS.md`, `fjs/fsc/README.md`, and the blocked | ||
| `@internal` TODO no longer prescribe conflicting rules. | ||
|
|
||
| ### Related | ||
|
|
||
| - [`../fsc/README.md`](../fsc/README.md) — current `_` leak-tolerance policy. | ||
| - [`../../AGENTS.md`](../../AGENTS.md) — root repository policy to update. | ||
| - [`../AGENTS.md`](../AGENTS.md) — `fjs/`-specific file/dependency policy. | ||
| - [`../../todo/blocked/jsdoc-typedef-strip-internal.md`](../../todo/blocked/jsdoc-typedef-strip-internal.md) | ||
| — current wait-for-`@internal`/`stripInternal` strategy. | ||
| - [microsoft/TypeScript#46407](https://github.com/microsoft/TypeScript/issues/46407) | ||
| — upstream JSDoc typedef stripping limitation. | ||
| - [`detect-unexported-types-referenced-by-exported-types.md`](./detect-unexported-types-referenced-by-exported-types.md) | ||
| — related declaration-leak detection. | ||
| - [`document-file-type-naming-conventions.md`](./document-file-type-naming-conventions.md) | ||
| — repository source-file roles. | ||
| - [`../../todo/migrate-typescript-to-mjs.md`](../../todo/migrate-typescript-to-mjs.md) | ||
| — current JavaScript/JSDoc migration and `_` convention. | ||
| - [`../ci/todo/f-mjs-package-support.md`](../ci/todo/f-mjs-package-support.md) | ||
| — declaration emission and clean package validation. | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.