-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Support for opting out of jsdoc @typedef exports #46011
Comments
Agreed this is not ideal. Not sure how best to indicate that it shouldnβt be exported. Maybe another JSDoc tag on the comment: /**
* @typedef {import("./FileA.js").Num} Num
* @local
*/
|
I'm guessing this would no longer be an issue if #22160 is fixed. |
I think the closest tag is jsdoc.app and Closure must have the problem to some degree since they have the concept of scope. What do they do? Are |
It seems JSDoc.app is having a similar issue jsdoc/jsdoc#1537 |
For Closure Compiler, at least, typedefs are attached to a real value-space declaration. For example: /** @typedef {(string|Element|Text|Array<Element>|Array<Text>)} */
goog.ElementContent; Types in the Closure Type System - Typedefs (it's been a few years since I regularly wrote Closure-able code, but I don't believe anything has changed) As a result, export/import for typedefs is explicit and the same as bringing in any dependency (though from google/closure-compiler#3041 it's clear this can lead to the same issues that lead to |
I suppose this might be a duplicate of #22160 |
Best workaround I've found for this so far is to assign a type to an unused variable, and then use /**
* @type {{
* foo: number,
* bar: string,
* }}
*/
let myType;
/**
* @param {typeof myType} x
*/
function takesMyType(x) {
} The downside of this that it creates an unused variable, so you might have to disable your linter every time you do this. Other than that I'm frequently importing types within the closure of a class or function, and I'm using tons and tons of inline type imports, which results in some truly horrendous looking code as can be seen in #22160 (comment) |
I'm running into this now; I have a CJS file with JSDoc comments that's generating types, and the resulting types have How can I define a type purely in jsdoc and not export it? |
@ljharb that actually sounds like a declaration emit bug separate from this feature request. You should be getting an While being able to avoid exporting a Can you show how to repro the bug youβre seeing? |
@andrewbranch ah, fair point. in that case, https://github.com/ljharb/set-function-name/tree/tsc, |
Opened #56002 |
No, definitely not. i like the idea of a modifier like It's unfortunately probably too late to change the semantics to require something like |
Yea, your PR is @export'ing them since 2018, lets see both cases:
I would rather aim for (2) to keep the ESM no-exporting-unless-stated behaviour "aligned", but based on legacy, we cannot simply change it. So the only option I see is to... make it an option π So once we have an option, we need to define how to export a
Is this exporting A or B? IMO a very specific naming is causing the least ambiguity:
The default option can for my sake be (1) - keep defaulting to exporting typedefs. I've seen several projects by now being affected by this, so if the fix comes in the form of an option: problem solved. I wonder why @DanielRosenwasser gave a thumbs-down on your 2018-typedef-export PR, maybe because of this issue? Lets just discuss a bit, decide on a solution and go for it? |
What about a completely new tag? Something like /**
* @localtype {object} A
*/ It would
Not sure about the name of the tag exactly, ideally it would look similar to An other idea could be to add two completely new tags and deprecate the |
I like
We're definitely not able to deprecate anything because I expect a lot of JSDoc's value is in using VS Code (or even VS!) to open ancient loose .js files and get some kind of help from jsdoc comments. |
One problem with Something like /**
* @typedef {number} Foo
* @export _
*/ to indicate that no type should be exported from that block comment could work. But honestly, that seems even more mysterious than If we don't want to deprecate anything, then So at this point the only options I see are:
|
That's a simple condition: const str = `
/**
* @typedef {number} Foo
* @export _, 123, Foo
*/
function add(a, b) {
return a + b;
}`;
const ast = ts.createSourceFile('repl.ts', str, ts.ScriptTarget.Latest, false /*setParentNodes*/);
const {tags} = ast.statements[0].jsDoc[0];
for (const tag of tags) {
if (tag.tagName.text === 'export') {
const names = tag.comment.split(',').map(_ => _.trim());
for (const name of names) {
const typedef = tags.find(tag => tag.kind === ts.SyntaxKind.JSDocTypedefTag && tag.name.text === name);
if (!typedef) {
console.log(`Trying to find @typedef called ${name}, but can't find it.`);
continue;
}
console.log("Exporting", typedef.name.text);
}
}
} Output:
Yes, solved by making it an option, maybe
Since JSDoc is about types, isn't it "implies that it mirrors all of TS exports"? In the beginning we can just limit it to typedef's in the same JSDoc as a "minimal working solution" and see what other ideas developers have. Idea for even more syntax: |
That works, but only if all your libraries are up to date and expect that option to be set. If one of your libraries doesn't have any of its types I agree I'm thinking of what it would be like to migrate an existing project, and adding a tag to every file just to opt out of type exporting is not really something I'd want to do. You might forget to set it in one file and accidentally export all your types without being aware of it. |
this is true of anything, and thatβs fine. If you want access to something thatβs not exported, you either ask the maintainer, fork it, or you donβt get access |
But it doesn't have to be. A new option would be very close to just deprecating exports. And to quote sandersn:
Projects with this new option enabled would lose this value just as much as if the behaviour was deprecated. Don't get me wrong, an option is better than not fixing this issue at all. I just think there are better alternatives :) Take JavaScripts |
Please note that they have also come to realize that certain aspects are not viable and as a result, they are now causing syntax errors, for example: "use strict";
var static = 1; IMO we are in the same situation here - ESM is inherently about limiting scope. If you want to make something available outside the module, you must explicitly export it using the I think we don't even need |
I'm using Deno and don't have any If a new JSDoc tag is not something we are happy with (though I still think it's the best option, maybe it just needs a better name than A new await Promise.resolve();
// ^^^^^--- 'await' expressions are only allowed at the top level of a file when that file is a module,
// but this file has no imports or exports. Consider adding an empty 'export {}' to make this file a module. Doing it like this wouldn't break legacy code, allowing you to gradually update your codebase, old libraries would keep working, and you wouldn't need to configure anything. The only downside is that this might be somewhat unexpected, but an error message similar to the 'top level await' one could take care of that. |
Then a file using this style still doesn't work: #46011 (comment) Or how could that be detected? Your idea is nice and I like it and we just make a special case for Edit: (Now I kinda have a fear that people will just export useless dummies simply because there is no good option) |
this would preclude CJS packages from having the benefit, which is my entire use case. |
I would say the use of
That's why I prefer a new JSDoc tag :) |
This works in VS Code for me as well, although I think it's currently only available in the Nightly build. You can also use it to import multiple typedefs from the same file, ex:
|
@stoicbuddha I see the value of |
@kungfooman As far as I can tell, it solves the original issue, which is that |
@stoicbuddha the issue is that |
@ljharb Based on my understanding of the OP, the issue is that using
then re-exports the type from that file. Using I'm having the exact same issue as the OP regarding a bunch of exports from within my project because of |
@stoicbuddha that's true if you are importing as part of the typedef. but typedefs aren't just for imports - It's great to know that |
@ljharb In that instance, wouldn't it make more sense to either: A) Use B) Add |
@stoicbuddha for A, no, because I want to reuse the defined type in multiple places, and for B no, because i don't want it accessible from outside the file, and if it's importable, then it's accessible anywhere. |
@ljharb I'm confused as to why the accessibility is an issue. Can you clarify the use case in which you define a type that is specific to code in a given file that gets imported somewhere else? I'm guessing you have some experience with that where I don't, and I'm not trying to discount it, I'm just not clear on why that would be happening. |
@stoicbuddha it's a type i don't want directly usable outside the file. The use case is "i don't want the name of the type to be part of my semver-compliant public API". It's the same reason you wouldn't want every variable in a module automatically become exported :-) |
@ljharb I understand what the effects are, but I think we'd agree that a type and a variable are wildly different levels of dangerous in this context; a variable is actually usable, whereas a type is just evaluated by the TS compiler and has no real bearing on the code itself. What I'm trying to ascertain is: what is the real-world instance where you have a |
Given editor autocompletion, it happens a lot. Additionally, in a published package, if i rename an exported type it's a semver-major change - the level of danger there is precisely as dangerous as for a variable because it can fail CI pipelines. |
@ljharb I could see editor autocompletion pollution being a use case for having it be specific to a file. It still seems like you'd need to intentionally use it, but the pollution would be annoying. Regarding the danger, I think we are talking about two different things (and perhaps have a disagreement as to what we'd regard as dangerous), but it's probably not important for solving the overall issue. |
Suggestion
π Search Terms
jsdoc typedef export #43207 #23692
β Viability Checklist
My suggestion meets these guidelines:
β Suggestion
Currently when using
/** @typedef {Number} Num */
the typeNum
is automatically exported when the comment is in the root scope. I'd like to prevent this from happening.π Motivating Example π» Use Cases
My problem:
FileA.js
declares/** @typedef {Number} Num */
FileB.js
uses this type a bunch of times, so instead of using/** @type {import("./FileA.js").Num} */
everywhere, it declares it once at the top of the file:/** @typedef {import("./FileA.js").Num} Num */
and then usesNum
everywhere in the file.The problem is that doing it this way in all files creates a lot of exports of the same type and it's not always clear which one is the original export. This creates a rather big list with Intellisense:
A woraround is to wrap the type in parentheses, this causes the typedef to be limited to that scope, but this is not always feasible. I.e:
The text was updated successfully, but these errors were encountered: