fix(marshal): remove ambient types, generate declarations from JSDoc#1025
fix(marshal): remove ambient types, generate declarations from JSDoc#1025
Conversation
mhofman
left a comment
There was a problem hiding this comment.
Cool stuff! I'd be so much more confident in this kind of changes if we had noImplicitAny.
Requesting change as I believe the default convert slot got flipped.
| import { makeTagged } from './makeTagged.js'; | ||
| import { passStyleOf } from './passStyleOf.js'; | ||
|
|
||
| /** @typedef {import('./types').Passable} Passable */ |
There was a problem hiding this comment.
Nit: while tsc doesn't care as much, I think we should keep the habit of using fully qualified names, aka with extensions, for imports
There was a problem hiding this comment.
Reasons to leave it this way:
- There should never be any ambiguity what file it refers to. (There must not be a
types.tsnext to atypes.js.) - We can change the syntax of the imported file between
.tsand.jswithout touching everywhere it's imported.
There was a problem hiding this comment.
Even if there is a .d.ts or .ts file, the import should always be of .js file per TypeScript conventions. TypeScript will internally use the .d.ts file if it exists. We use explicit .js extensions for our imports as automatic extension resolution is something we left behind when switching to native ESM.
There was a problem hiding this comment.
Thanks for the correction on the second bullet about changing extensions. I didn't know that TypeScript conventions are that if the file is foo.ts you should still import it as foo.js.
I neglected to include what I think is the most compelling reason to leave it this way: less to type and read. I think simplicity wins unless there is a compelling reason to add the extension. Is it to be consistent with the JSM module imports? I think consistency is important when it reduces confusion or misunderstanding, but I have a hard time imagining someone being confused by the absence of .js here in the JSDoc.
There was a problem hiding this comment.
Right I was looking for consistency. I've never found terseness to be very compelling. We require extensions for ESM imports (static, and in the future dynamic), and since TS style jsdoc type imports use a dynamic-like ESM notion, I figure we should stay consistent.
erights
left a comment
There was a problem hiding this comment.
A worthy experiment. It is much less painful than I expected. Enough so that, assuming this is representative, I'm ok moving in this direction in general.
vscode has a very nice feature that when I use a name I have not yet imported but should, it often (not always) prompts me with the correct choice and writes the import itself. If it did so for this /** @typedef {import('../types').Foo} Foo */ pattern as well, then I wouldn't even hesitate. Maybe eventually we'll have such tooling.
Thanks for pushing ahead with this. A very pleasant surprise. LGTM!!!
| export function makeMarshal( | ||
| convertValToSlot = defaultValToSlotFn, | ||
| convertSlotToVal = defaultSlotToValFn, | ||
| export const makeMarshal = ( |
There was a problem hiding this comment.
I missed that one. Thanks!
There was a problem hiding this comment.
Have we considered a global code mod to convert functions? https://github.com/facebook/jscodeshift works great
There was a problem hiding this comment.
Not yet, but maybe sometime.
There was a problem hiding this comment.
I would love to see this done automatically!
It does for One drawback I believe is that going to the type definition would take 2 clicks, first one going to the |
packages/marshal/exported.js
Outdated
| // eslint-disable-next-line import/export | ||
| export * from './src/types.js'; |
There was a problem hiding this comment.
+1 to this pattern of explicit type exports over implicit.
We shouldn't have to disable the linter to do the right thing. https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/export.md says it prevents "funny business"; doesn't TypeScript cover that? I propose we consider disabling this rule in our eslint config.
There was a problem hiding this comment.
@michaelfig, could we not include this export * from './src/types.js'; in the index.js, so that we can get rid of the exported.js? I believe the only purpose it served was for making sure marshal ambient types were loaded, which we want to get rid of, right?
There was a problem hiding this comment.
I have gone ahead and done that. It reads much better.
| /** @typedef {import('../types').Checker} Checker */ | ||
| /** @typedef {import('../types').PassStyle} PassStyle */ |
There was a problem hiding this comment.
If we're sticking with JSDoc, maybe we can submit a PR for TypeScript to have a nicer syntax for this.
In regular TS this is clean:
import type {Checker, PassStyle} from '../types`
In JSDoc TS maybe,
/**
* @typeimport '../types' {Checker, PassStyle}
*/
There was a problem hiding this comment.
Not the first to want this :) microsoft/TypeScript#22160
turadg
left a comment
There was a problem hiding this comment.
LGTM. I left some non-blocking comments.
Glad to see support for type imports, which came up in Agoric/agoric-sdk#4410 (comment)
| export function makeMarshal( | ||
| convertValToSlot = defaultValToSlotFn, | ||
| convertSlotToVal = defaultSlotToValFn, | ||
| export const makeMarshal = ( |
There was a problem hiding this comment.
Have we considered a global code mod to convert functions? https://github.com/facebook/jscodeshift works great
f7345c8 to
b55a2be
Compare
|
I took over the PR and extended it with generation of declaration files for marshal, and rebased on latest master + preliminary TS 4.5 compat PR I opened in #1043. I've checked that with the appropriate updates to Agoric/agoric-sdk#4413, all seem to be working well. This shows how we can move away from ambient types, and stop requiring TS from trying to parse the JS of |
turadg
left a comment
There was a problem hiding this comment.
Left some non-blocking suggestions and questions.
4ecf8ab to
2c0bff0
Compare
c5f2ef7 to
b0d11fa
Compare
|
Reverted some of the changes so this PR is now purely type changes (and a couple |
kriskowal
left a comment
There was a problem hiding this comment.
Moving away from ambient types is a good but breaking change. This will require some ceremony.
In each affected NEWS.md, we need a note for # Next release of what end-users will need to do when they upgrade.
At least one commit must have ! in the conventional commit and a *BREAKING CHANGE*: ... paragraph that will get rolled into the changelog and bump the version appropriately.
When integrating into Agoric SDK, I use scripts/sync-versions.sh ../endo and that rolls over all the semver bumps, so all of the alignment will have to occur in the Sync Endo PR.
2bbf300 to
8513cfb
Compare
4a62453 to
be9d5ac
Compare
|
In the commit message, |
kriskowal
left a comment
There was a problem hiding this comment.
Please address my comment about the commit message having *BREAKING CHANGE:* verbatim. Otherwise, this is great progress.
be9d5ac to
ac69e63
Compare
Done. @michaelfig could you re-review as a comment? I'll carry your review as mine (since you are the original author...) |
michaelfig
left a comment
There was a problem hiding this comment.
Looking good! Just a few minor cleanups I'd recommend, but not blocking.
Keep using JSdoc, remove exported.js *BREAKING CHANGE*: This change switches to exported types and removes the `exported.js` file which provided ambient types.
Also add NEWS.md in artifact
1e0194b to
5235b7f
Compare
mhofman
left a comment
There was a problem hiding this comment.
Carrying @michaelfig's approval
|
I just noticed that building the d.ts files (e.g. by running prepack) spits them out adjacent to the .js files, which aren't gitignored and could end up being checked in. Any reasons not to output the build into a |
|
Because we'd need to also copy the .js source, which would be incompatible with non-publish same repo usage |
We'd only need to copy the .js source if we pointed A problem with how it is now (besides the gitignore) is that |
|
I am not ok with anything that requires same repo build step to have anything work. It's a foot gun. How can they be out of sync? They're only generated when publishing a package, and not needed same repo as tsc will happily parse the JS to get type definitions when the dependency is not in node_modules |
I'm not familiar with the foot injuries you have in mind. In my experience watch mode suffices to keep things up to date.
They're generated when publishing a package, but don't go away on their own. Then the source .js can be edited and is out of sync. Not "expected", but perhaps a "foot gun". |
True, we need a clean step after publish, and / or move publishing to CI. Watch step requires developer environment setup. I think the fact these build artifacts are not ignored is probably a good thing as it'll remind a clean is needed. |
|
I would very much prefer not to need a watch mode. |
@turadg, there's a |
After my last publish, the |
|
I find with the following, things work as they should (at least wrt. clean). Explain again why we're including diff --git a/packages/marshal/jsconfig.json b/packages/marshal/jsconfig.json
index b455fcf0d..0611838b4 100644
--- a/packages/marshal/jsconfig.json
+++ b/packages/marshal/jsconfig.json
@@ -15,5 +15,5 @@
"strictNullChecks": true,
"moduleResolution": "node",
},
- "include": ["*.js", "*.ts", "src/**/*.js", "src/**/*.ts", "test/**/*.js"]
+ "include": ["*.js", "src/**/*.js", "test/**/*.js"]
} |
|
That’s good to know. That patch is acceptable. I made the change to cover |
Maybe the correct solution then is to do: diff --git a/packages/marshal/jsconfig.build.json b/packages/marshal/jsconfig.build.json
index 13018509f..ea9465e90 100644
--- a/packages/marshal/jsconfig.build.json
+++ b/packages/marshal/jsconfig.build.json
@@ -6,5 +6,5 @@
"emitDeclarationOnly": true,
"declarationMap": true
},
- "exclude": ["test/"]
+ "exclude": ["test/", "**/*.ts"]
} |
Experiment to remove dependency on ambient types. The process:
export {};near the top of every JSDoctypes.jsfile. This switches it from an ambient file to a module with exported types.exported.jsso that ambient type users break hard.export * from './src/types.js';toindex.jsso that upstream consumers can import types from the package identifier. Season with eslint-ignores to taste.import './types.js';,import './internal-types.js';etc./** @typedef {import('./types.js').MemberName} MemberName */or for template types/** @template T @typedef {import('./types.js').MemberName2<T>} MemberName2 */. as needed to the top of.jssources.package.jsonto include generated files./** @typedef {import('@agoric/marshal').MyType} MyType */, and remove anyimport '@agoric/marshal/exported';.--maxNodeModulesJsDepthhack that used to be needed with JSDoc ambient type innode_modules.