Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat(eslint-plugin): [require-types-exports] add new rule#8443

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Conversation

StyleShit
Copy link
Contributor

@StyleShitStyleShit commentedFeb 12, 2024
edited
Loading

Closes#7670


Dear Reviewer,
Good luck on your journey!

StyleShit.

JoshuaKGoldberg reacted with laugh emoji
@typescript-eslint
Copy link
Contributor

Thanks for the PR,@StyleShit!

typescript-eslint is a 100% community driven project, and we are incredibly grateful that you are contributing to that community.

The core maintainers work on this in their personal time, so please understand that it may not be possible for them to review your work immediately.

Thanks again!


🙏Please, if you or your company is finding typescript-eslint valuable, help us sustain the project by sponsoring it transparently onhttps://opencollective.com/typescript-eslint.

@netlifyNetlify
Copy link

netlifybot commentedFeb 12, 2024
edited
Loading

Deploy Preview fortypescript-eslint ready!

NameLink
🔨 Latest commit2fd30aa
🔍 Latest deploy loghttps://app.netlify.com/sites/typescript-eslint/deploys/668bc85478b8da00082e5a6c
😎 Deploy Previewhttps://deploy-preview-8443--typescript-eslint.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 99 (🟢 up 5 from production)
Accessibility: 100 (no change from production)
Best Practices: 92 (no change from production)
SEO: 90 (no change from production)
PWA: 80 (no change from production)
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to yourNetlify site configuration.

@nx-cloudNx Cloud
Copy link

nx-cloudbot commentedFeb 12, 2024
edited
Loading

☁️ Nx Cloud Report

CI is running/has finished running commands for commit2fd30aa. As they complete they will appear below. Click to see the status, the terminal output, and the build insights.

📂 See all runs for this CI Pipeline Execution


🟥 Failed Commands
nx test eslint-plugin --coverage=false
✅ Successfully ran 28 targets

Sent with 💌 fromNxCloud.

@StyleShitStyleShit marked this pull request as ready for reviewFebruary 13, 2024 19:46
@StyleShit
Copy link
ContributorAuthor

No idea why the lint/tests are failing, seems to be fine... Am I missing something?

Comment on lines 378 to 379
'ImportDeclaration[importKind="type"] ImportSpecifier, ImportSpecifier[importKind="type"]':
collectImportedTypes,
Copy link
ContributorAuthor

@StyleShitStyleShitFeb 15, 2024
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Currently, it doesn't support this:

import{Type}from'./types';exportfunction(arg:Type){/* ... */}

but only things like this:

importtype{Type1}from'./types';import{typeType2}from'./types';import{typeType3asType4}from'./types';

because IDK if I can know for sure whether the imported name is a type or not
any idea if this could be done?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

The type checker can determine whether something is in type space and/or value space.

But for that case ofexport function (arg: Type) { /* ... */ }, do we need to know whether it's type and/or value? I'd say that should be a complaint in the rule no matter what, no?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Done

Copy link
ContributorAuthor

@StyleShitStyleShitApr 24, 2024
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

OK,
After thinking about it for a little bit...

What should we do in cases where someone imports a type from another module and uses it in their exported function?

// my-package/src/types.tsexporttypeA=number;
// my-package/src/function.tsimporttype{A}from'./types';exportfunctionf():A{return1;}
// my-package/src/index.tsexport{f}from'./function';

Then, in user-land:

// my-code.tsimport{f}from'my-package';letvalue;// What type is it?value=f();

I mean... on the one hand, we can't know whether the type is exported somehow to the user.
On the other hand, (I think?) we don't want to force everyone to (re)export the types they imported 😓

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

On apure scope analysis level, we do know this. TheA identifier infunction.ts is part of what's exported withf().

I think it'd be very preferable to stick with the pure scope analysis strategy. Dipping into types land is much harder to work with, sincetype aliases sometimes become new things and sometimes are just references to existing things.

Does that answer what you're looking for?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

The rule would need to check: for every identifier in the return type, is that also exported in the current file (module)?

My current logic is:
"Does this type exist in the outside world (whether it's imported/exported in the current file)?"
- If so: Great!
- Otherwise: Report

I think we do, and that that's the goal of this rule!

I don't fully agree
I mean... yeah, that's the goal of the rule, but the type might already be exported through the index file, for example, and we'll force developers to export it multiple times in their code.

Let's take my example above and modify it a little bit:

// my-package/src/types.tsexporttypeA=number;
// my-package/src/f1.tsimporttype{A}from'./types';exportfunctionf1():A{return1;}
// my-package/src/f2.tsimporttype{A}from'./types';exportfunctionf2():A{return2;}
// my-package/src/index.tsexport{f1}from'./f1';export{f2}from'./f2';export*from'./types';

Then, in user-land:

// my-code.tsimport{f1}from'my-package';importtype{A}from'my-package';letvalue:A;// I know what the type is! YAY!value=f1();

This code is totally fine. But with the changes you suggest, it'll have 2 more redundant exports (and it'll increase linearly based on the type usage inside other modules).
Yeah, it probably won't hurt anybody, but it will make the DX awful, don't you think?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

If we don't enforce that aexport function fn(): A; is in a file that also exportsA, what is the point of this rule?

Copy link
ContributorAuthor

@StyleShitStyleShitApr 24, 2024
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

you could say the same about not exporting it from the index, no?
I mean, you'll always have holes in the rule unless you read the whole codebase, starting from the bundler entrypoint(s)

It's a small code change to enforce that imported types are also exported, I'm just trying to understand where the line lies in this case

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

For lint rules, the line generally lies with the single file being linted. Because lint rules operate on a single file at a time, that naturally falls out of the architecture.

It's pretty common that lint rules will only be able to catch issues within the small scope of a single rule at a time. For example,no-unused-vars won't detect things that are exported from a file but never imported. HenceKnip.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

do you wanna loop in the triage team so we can agree on something together?

JoshuaKGoldberg reacted with thumbs up emoji
Copy link
Member

@JoshuaKGoldbergJoshuaKGoldberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Lovely start! I've wanted this for ages, so very excited to see you working on it 😄.

Leaving a preliminary review as I think it'll need to be expanded to at least variables. Really, anything that can refer to a type: classes, interfaces / type aliases, namespaces, ...

Good luck onyour journey. 😜


const returnStatements = new Set<TSESTree.Node>();

simpleTraverse(functionNode, {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

[Refactor] I don't thinksimpleTraverse is what we should use here. It's tailored to the way it's currently used inpackages/typescript-estree:

  • There's no way to break, but it looks like this usage would want to halt traversal after finding aReturnStatement
  • It allows configurable selectors with'enter', but this code just usesReturnStatement

I think it'd be better to avoidsimpleTraverse here, and instead do some or all of:

  • Look into the existingforEachReturnStatement, maybe it's exactly what you need?
  • Failing that, write a dedicated simpler traverser for just this rule that halts onReturnStatements, omitting anything around parent pointers or'enter'
  • Reuse some of the design and logic from one of the rules that does similar things:explicit-module-boundary-types,explicit-function-return-type
    • This one I'm low confidence in, but figured I'd mention...

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Created a custom traverser, inspired by the originalforEachReturnStatement

Copy link
Member

@JoshuaKGoldbergJoshuaKGoldberg left a comment
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Progress! 🚀

I think this is making great headway through the slog of edge cases and tests that such a nuanced rule would need. Very nice to see.

Requesting changes on:

  • Edge cases: left a few comments suggesting them, marked as[Bug] or[Testing]
  • Structure: streamlining traversals a bit to not do extra work (avoidingsimpleTraverse)

I also started a thread for any open questions I saw. If I'm missing one of yours, sorry, please yell at me. 🙂

'ImportDeclaration ImportNamespaceSpecifier': collectImportedTypes,
'ImportDeclaration ImportDefaultSpecifier': collectImportedTypes,

'Program > ExportNamedDeclaration > TSTypeAliasDeclaration':

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

[Testing] Nothing fails with theProgram > removed. Either there's a missing test or this is unnecessary.

Suggested change
'Program >ExportNamedDeclaration > TSTypeAliasDeclaration':
'ExportNamedDeclaration > TSTypeAliasDeclaration':

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Added missing tests

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

[Testing] I'd thinkkeyof and/ortypeof types should be flagged too:

constfruits={apple:true};exportfunctiongetFruit<Keyextendskeyoftypeoffruits>(key:Key,):(typeoffruits)[Key]{returnfruits[key];}

Thoughts?

Copy link
ContributorAuthor

@StyleShitStyleShitJul 7, 2024
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

That's interesting. So we'll need to flag both types and values?

If so - I'll probably need to rework the reporting logic to also support values, and we should probably rename the rule (?)

EDIT:
Not sure that the developers will always want to export the value, since it's being exported by reference, which means external consumers will be able to change the internals of the code.
Maybe that's the case most of the times?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

In this case, it's really thekeyof typeof fruits that needs to be exported. So I'd assume the squiggly would be on there:

exportfunctiongetFruit<Keyextendskeyoftypeoffruits>(//                                   ~~~~~~~~~~~~~~~~~~~

This would certainly make it difficult to write an auto-fixer 😅 glad that that's out of scope for this PR.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I think we need to report only thetypeof part, because that's what really blocks the developers from using it, no?

Do you think we should have a different message for this case? Maybe something like "typeof needs to be exported. please extract it to a type alias"?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

only thetypeof part, because that's what really blocks the developers from using it, no?

Well, it's certainlybetter...

exporttypeFruits=typeoffruits;exportfunctiongetFruit<KeyextendskeyofFruits>(//                                   ~~~~~~~~~~~~

...but it strikes me as a kind of arbitrary exception to allow akeyof ... to be inline. What would make this so special?

different message for this case

+1, yes!

Copy link
ContributorAuthor

@StyleShitStyleShitJul 8, 2024
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

What would make this so special?

IDK, what's the difference between these two?

exportdeclarefunction(key:keyofItems);exportdeclarefunction({ items}:{items:Items});

In both cases you construct a type inline based on a type reference. So why wouldn't we force every inline type to be extracted to an alias?

What makeskeyof special compared to a regularTSTypeLiteral in this case?


EDIT:
And sorry for all of the notifications you get from my "Done" comments 😅
it helps me keep track of my progress, because I come back to this PR multiple times after switching context

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Taking a step back: the core idea of the rule is that you should be able todirectly import anything that's visible as a type from a module. You shouldn't have tokeyof something,something['some-key'],ReturnType<typeof something>, or use any other indirection in the type system.

Looking at those two examples, here's how the rule should squiggly them:

exportdeclarefunction(key:keyofItems);//                           ~~~~~~~~~~~exportdeclarefunction({ items}:{items:Items});//                                 ~~~~~~~~~~~~~~~~

So why wouldn't we force every inline type to be extracted to an alias?

We should.

Copy link
ContributorAuthor

@StyleShitStyleShitJul 8, 2024
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

umm... great
now I'll need to redo a lot of my work 😂

(I didn't understand this until now, which is stupid, because it's literally in the issue)

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

anyway, Ithink it's gonna simplifya lot of my code

@JoshuaKGoldbergJoshuaKGoldberg added the awaiting responseIssues waiting for a reply from the OP or another party labelJul 6, 2024
@StyleShit
Copy link
ContributorAuthor

@JoshuaKGoldberg

OK, so before I delete most of the code and start basically from scratch, let's go over some cases to make sure we're on the same page?

  1. I'm assuming this is fine (easy):
typeA=number;exportdeclarefunctionfunc(a:A):void;//                              ~
  1. Also this one:
typeA=number;exportdeclarefunctionfunc<TextendsA>(a:T):void;//                                     ~
  1. And as we said before, this one should also report:
typeA=number;exportdeclarefunctionfunc({ a}:{a:A}):void;//                                  ~~~~~~~~
  1. But what about this one?
typeA=number;exportdeclarefunctionfunc([a]:[A]):void;//                                  ~~~~~
  1. Or this one?
typeA=number;exportdeclarefunctionfunc([a]:A[]):void;//                                  ~~~
  1. You can also have something like this, should it be reported?
typeA=number;exportdeclarefunctionfunc(a:Record<string,A>):void;//                              ~~~~~~~~~~~~~~~~~

I mean... you can still construct all of them (3-6) easily if you haveA, so where do we draw the line where we say "ok, this one should be reported"?

Should we just say "everything has to be a type alias, including things like simple unions and arrays"?
(well... it's impractical and probably impossible [think about returningT | null for example], so probably no... but I'm talking hypothetically)

In addition, should we also force everyone to have explicit return types on their functions?
It'll be easier to implement & report, but again, will probably have its own issues...

Anyway, my point is - seems like there are a lot of open questions.
Should we go back to the issue and discuss this again? Should we start with a smaller scope that includes some specific cases and expand in the future?
I'm fine with any decision, I just think we need a better definition for this rule.


OK, that was a little bit longer than I expected 😅

@kirkwaiblingerkirkwaiblinger removed the awaiting responseIssues waiting for a reply from the OP or another party labelJul 15, 2024
@JoshuaKGoldberg
Copy link
Member

open questions ... go back to the issue and discuss this again?

Yeah, agreed, you bring up a lot of important edge cases. I'll post there.

StyleShit reacted with rocket emoji

@JoshuaKGoldberg
Copy link
Member

👋 Just checking in@StyleShit, is this still something you have time & energy for?

@StyleShit
Copy link
ContributorAuthor

@JoshuaKGoldberg

Thanks for checking 😄
I have the energy, but unfortunately don't have the time right now due to being in reserve duty.
Anyway, I think I'll open a new PR anyway when I have the time to work on it, because this one is a total mess 😅

JoshuaKGoldberg reacted with thumbs up emojiJoshuaKGoldberg reacted with heart emoji

@JoshuaKGoldberg
Copy link
Member

Cool! I'd really like to get this one and#8509 in by the end of the year, they're great rules to have that I've wanted for a while. So let's timebox these two to, say, the end of the month. If you don't end up having time by then, no worries, one of us on the team can send in a PR with the same commit history & a co-author attribution.

Good luck + best wishes on reserve duty. ❤️

Sign up for freeto subscribe to this conversation on GitHub. Already have an account?Sign in.
Reviewers

@Omerisra6Omerisra6Omerisra6 left review comments

@JoshuaKGoldbergJoshuaKGoldbergAwaiting requested review from JoshuaKGoldberg

Assignees
No one assigned
Labels
enhancement: new plugin ruleNew rule request for eslint-plugin
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

Rule proposal: Export all types used in exports
5 participants
@StyleShit@JoshuaKGoldberg@Omerisra6@bradzacher@kirkwaiblinger

[8]ページ先頭

©2009-2025 Movatter.jp