Skip to content
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

Bug: no-restricted-exports fails on export { default, ... } from ... #15617

Closed
1 task done
patcon opened this issue Feb 17, 2022 · 28 comments · Fixed by #16785
Closed
1 task done

Bug: no-restricted-exports fails on export { default, ... } from ... #15617

patcon opened this issue Feb 17, 2022 · 28 comments · Fixed by #16785
Assignees
Labels
accepted There is consensus among the team that this change meets the criteria for inclusion archived due to age This issue has been archived; please open a new issue for any further discussion enhancement This change enhances an existing feature of ESLint rule Relates to ESLint's core rules
Projects

Comments

@patcon
Copy link

patcon commented Feb 17, 2022

Environment

Node version: v14.18.1
npm version: v8.3.0
Local ESLint version: v8.9.0 (Currently used)
Global ESLint version: Not found
Operating System: darwin 20.6.0

What parser are you using?

Default (Espree)

What did you do?

Minimal reproduction repo here: https://github.com/patcon/eslint-bug-reproduction-default-export

What did you expect to happen?

Expected npm run lint to pass, since

export { default, function2 } from 'bar.js';

is just an alternative format, recommended in Mozilla web MDN docs :)

What actually happened?

Both forms of this default failed:

Screen Shot 2022-02-17 at 3 51 58 PM

Participation

  • I am willing to submit a pull request for this issue.

Additional comments

Re-ticketed from airbnb/javascript#2500

Thanks a bunch! Open to pointers in the right direction, or just affirmation that this is a bug 🎉 🐛

@patcon patcon added bug ESLint is working incorrectly repro:needed labels Feb 17, 2022
@eslint-github-bot eslint-github-bot bot added this to Needs Triage in Triage Feb 17, 2022
@patcon patcon changed the title Bug: (fill in) Bug: no-restricted-exports fails on export { default, ... } from ... Feb 17, 2022
@nzakas
Copy link
Member

nzakas commented Feb 18, 2022

This does look like a bug because, in this case, “default” is not a named export. @mdjermanovic do you agree?

@nzakas nzakas moved this from Needs Triage to Triaging in Triage Feb 18, 2022
@mdjermanovic
Copy link
Member

This works as intended, per the documentation for this rule on default exports:

https://eslint.org/docs/rules/no-restricted-exports#default-exports

An example of incorrect code in that section is basically the same as the example from the original post in regard to default:

/*eslint no-restricted-exports: ["error", { "restrictedNamedExports": ["default"] }]*/

export { default } from "some_module";

Adding "default" to restrictedNamedExports[] means that you want to disallow exporting "default" in named export declarations.

@patcon
Copy link
Author

patcon commented Feb 18, 2022

Thanks @mdjermanovic and @nzakas! Sorry, I am admittedly quite new to this export from syntax (and even a bit rookie with ES6), but am I trying to reconcile the existing eslint doc (that you linked) with the MDN docs...

The "export from" syntax allows the as token to be omitted; however this will mean the default item cannot be imported as a named import:

export { default, function2 } from 'bar.js';

Thoughts @ljharb? (as you initially suggested this was an upstream fix in airbnb/javascript#2500 (comment))

EDIT: Ah seeing both you and ljharb were involved in the initial rule this came in on, so I'll just listen:

@ljharb
Copy link
Sponsor Contributor

ljharb commented Feb 18, 2022

The original intention for the rule didn’t consider this, because default really isn’t a named export, conceptually - it’s just unfortunately implemented and specified as one.

I think that if “default” is restricted, then it should apply to default exports whether named or not. If it doesn’t apply to both, it shouldn’t apply to either.

@mdjermanovic
Copy link
Member

What did you expect to happen?

Expected npm run lint to pass, since

export { default, function2 } from 'bar.js';

is just an alternative format, recommended in Mozilla web MDN docs :)

@patcon can you please clarify the expected behavior of this rule with your configuration?

In particular, if the intent is not to disallow export { default }, then why is "default" in restrictedNamedExports[] in the configuration? What did you expect to be disallowed by restrictedNamedExports: ["default"]?

@mdjermanovic
Copy link
Member

I think that if “default” is restricted, then it should apply to default exports whether named or not. If it doesn’t apply to both, it shouldn’t apply to either.

We had the following options on how should the rule behave when it's configured with restrictedNamedExports: ["default"]:

  1. Throw configuration error.
  2. Disallow export { default }.
  3. Disallow export { default } and export default.

This was discussed in #12546, and we eventually decided on 2. as that seemed the most flexible. The behavior is, I believe, well-documented in the Default Exports section.

We didn't implement an option to disallow export default, which was mentioned in the PR, but it can be easily done with no-restricted-syntax, so all combinations are covered.

  • Disallow export default:

        "rules": {
             "no-restricted-syntax": ["error", "ExportDefaultDeclaration"]
         } 
  • Disallow export { default }:

        "rules": {
             "no-restricted-exports": ["error", { "restrictedNamedExports": ["default"] }]
         } 
  • Disallow export default and export { default }:

        "rules": {
             "no-restricted-exports": ["error", { "restrictedNamedExports": ["default"] }],
             "no-restricted-syntax": ["error", "ExportDefaultDeclaration"]
         } 

@ljharb
Copy link
Sponsor Contributor

ljharb commented Feb 18, 2022

@mdjermanovic i think that's fine for export { default }, but this is export { default } from, which is a completely distinct form - meaning, it's not as simple as those 3 options.

@mdjermanovic
Copy link
Member

mdjermanovic commented Feb 18, 2022

i think that's fine for export { default }, but this is export { default } from, which is a completely distinct form - meaning, it's not as simple as those 3 options.

Ah, ok! In that case, if there's a need to disallow only specific ways to export "default" while allowing others, I think the best option is to configure no-restricted-syntax with the desired combination of selectors.

{
    "no-restricted-syntax": ["error", 

        // export default foo; 
        "ExportDefaultDeclaration",

        // export { foo as default };
        "ExportNamedDeclaration[source=null] > ExportSpecifier > :matches(Identifier[name='default'], Literal[value='default']).exported",

        // export { default } from "mod"; export { foo as default } from "mod";
        "ExportNamedDeclaration[source] > ExportSpecifier > :matches(Identifier[name='default'], Literal[value='default']).exported",

        // export * as default from "mod";
        "ExportAllDeclaration > :matches(Identifier[name='default'], Literal[value='default']).exported"      
    ]
}

@patcon
Copy link
Author

patcon commented Feb 18, 2022

In particular, if the intent is not to disallow export { default }, then why is "default" in restrictedNamedExports[] in the configuration? What did you expect to be disallowed by restrictedNamedExports: ["default"]?

@mdjermanovic Though I suspect maybe the convo has moved past this question: that choice comes from airbnb's rules, and I have no specific expectations (other than that something so highly used must know more than me 🙂 )

https://github.com/airbnb/javascript/blob/b4377fb03089dd7f08955242695860d47f9caab4/packages/eslint-config-airbnb-base/rules/es6.js#L65-L70

@ljharb
Copy link
Sponsor Contributor

ljharb commented Feb 18, 2022

Airbnb's intention was to force default exports to use export default and NOT confusingly imply that "default" is Just Another Name, but export { default } from is the only way to do that, so we do NOT want to forbid that.

Using no-restricted-syntax, as always, isn't a viable option for a shared config because it's a) used for many things, and is hard to disable for just one of the items; and b) it doesn't convey meaning/intent the way a dedicated rule does.

@mdjermanovic
Copy link
Member

Since the current behavior is intentional and the rule works as documented, changing this would be a breaking change. Some users might be relying on this rule in combination with "no-restricted-syntax": ["error", "ExportDefaultDeclaration"] to entirely disallow default exports in their codebase.

If we are going to make a breaking change, I think we should either include export default or throw a configuration error, rather than replacing the current subset of ways to export "default" with another subset.

@ljharb
Copy link
Sponsor Contributor

ljharb commented Feb 21, 2022

Could it be an option?

@nzakas
Copy link
Member

nzakas commented Feb 22, 2022

An option seems like the fastest way to a solution. @mdjermanovic ?

@mdjermanovic
Copy link
Member

@ljharb which of the following would you like to disallow?

  1. export default foo;
  2. export { foo as default };
  3. export { default } from "mod";
  4. export { foo as default } from "mod";
  5. export * as default from "mod";

The current behavior with restrictedNamedExports: ["default"] disallows 2, 3, 4, and 5.

@ljharb
Copy link
Sponsor Contributor

ljharb commented Feb 22, 2022

The Airbnb config strongly pushes default export usage, so we’d want to disallow only number 3 (and i guess 5, but that’s less important). 2 and 4 are valid re-export forms of the preferred export method, 1.

@mdjermanovic
Copy link
Member

The Airbnb config strongly pushes default export usage, so we’d want to disallow only number 3 (and i guess 5, but that’s less important). 2 and 4 are valid re-export forms of the preferred export method, 1.

Isn't 3. export { default } from "mod"; the same as the example this issue is about? If the rule continues to disallow it, then it doesn't seem this issue will be fixed.

@ljharb
Copy link
Sponsor Contributor

ljharb commented Feb 22, 2022

ohhh right, sorry, i confused myself. We do want to allow option 3 :-) so looking again, we want to prohibit 2 (and maybe 5), but 1 is ideal, and 3 and 4 are the only ways to do that, so they'd also be allowed.

@mdjermanovic mdjermanovic added enhancement This change enhances an existing feature of ESLint evaluating The team will evaluate this issue to decide whether it meets the criteria for inclusion rule Relates to ESLint's core rules and removed bug ESLint is working incorrectly repro:needed labels Feb 23, 2022
@mdjermanovic
Copy link
Member

mdjermanovic commented Feb 23, 2022

Okay, here are some ideas.

a) Add a boolean option to re-allow 3 and 4. The option can be specified only if restrictedNamedExports contains "default". The following configuration would allow 1, 3, 4, and disallow 2, 5:

{
    "no-restricted-exports": ["error", {
        restrictedNamedExports: [
            "then",
            "default" // disallows 2, 3, 4, 5
        ],
        allowDefaultFrom: true // re-allows 3 and 4
    }]
}

b) Add an object option with boolean properties for each 1-5. The option can be specified only if restrictedNamedExports does not contain "default". The following configuration would allow 1, 3, 4, and disallow 2, 5:

{
    "no-restricted-exports": ["error", {
        restrictedNamedExports: [
            "then"
            // no "default" here
        ],
        restrictDefaultExports: {
            direct: false, // 1. export default foo; export default 42; export default function foo() {}
            named: true, // 2. export { foo as default };
            defaultFrom: false, // 3. export { default } from "mod"; export { default as default } from "mod";
            namedFrom: false, // 4. export { foo as default } from "mod";
            namespaceFrom: true // 5. export * as default from "mod"
        }
    }]
}

The b) is a bit more work but provides more than a), and I think it's easier to understand. I'd vote for b).

@mdjermanovic mdjermanovic moved this from Triaging to Feedback Needed in Triage Feb 23, 2022
@ljharb
Copy link
Sponsor Contributor

ljharb commented Feb 23, 2022

Either would work for me, but i agree b is clearer and preferred.

@nzakas
Copy link
Member

nzakas commented Feb 24, 2022

I agree that the second option is clearer.

@mdjermanovic mdjermanovic added accepted There is consensus among the team that this change meets the criteria for inclusion and removed evaluating The team will evaluate this issue to decide whether it meets the criteria for inclusion labels Mar 20, 2022
@mdjermanovic mdjermanovic moved this from Feedback Needed to Ready to Implement in Triage Mar 20, 2022
@mdjermanovic
Copy link
Member

It seems that we have agreed to fix this problem by implementing option b) from #15617 (comment), so I marked this as accepted.

@patcon would you like to submit a PR for the new option?

@patcon
Copy link
Author

patcon commented Mar 20, 2022

@mdjermanovic I'd love to take a swing at it :) Sorry for not stepping up earlier -- the conversation felt a little beyond my ability to weigh in, but happy to try to make my first contrib here 🙂

Any pointers to specific areas of docs/codebase related to this, or that might be similar? (aside from things I'd learn in general docs, which I'm sure I can navigate on my own without taking your time)

@patcon
Copy link
Author

patcon commented Mar 20, 2022

(and pls feel welcome to assign me so that it stays in my dashboard)

@nzakas
Copy link
Member

nzakas commented Mar 22, 2022

The rule docs page is a good place to start:
https://eslint.org/docs/rules/no-restricted-exports#resources

@amareshsm
Copy link
Member

@patcon
I would like to work on this feature. let me know if anyone working on this.

@upeguiborja
Copy link

Hi @amareshsm are you still working on this?

@shirshendubhowmick
Copy link

Any update on this ? This issue is still there.

@snitin315
Copy link
Contributor

@shirshendubhowmick There is an open PR for this - #16785. It will be fixed soon.

@eslint-github-bot eslint-github-bot bot locked and limited conversation to collaborators Jul 28, 2023
@eslint-github-bot eslint-github-bot bot added the archived due to age This issue has been archived; please open a new issue for any further discussion label Jul 28, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
accepted There is consensus among the team that this change meets the criteria for inclusion archived due to age This issue has been archived; please open a new issue for any further discussion enhancement This change enhances an existing feature of ESLint rule Relates to ESLint's core rules
Projects
Archived in project
Triage
Ready to Implement
Development

Successfully merging a pull request may close this issue.

9 participants