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

Update: fix no-restricted-imports importNames reporting (fixes #12282) #12711

Merged
merged 10 commits into from Jan 16, 2020

Conversation

fanich37
Copy link
Contributor

What is the purpose of this pull request? (put an "X" next to item)

[X] Bug fix (it should fix #12282)

What changes did you make? (Give an overview)
Now rule can produce multiple errors depends on importNames option. This change allows applying directive comments on each item imported from the module.

Is there anything you'd like reviewers to focus on?
Yes, the test cases since i'm too bad at it.

@jsf-clabot
Copy link

jsf-clabot commented Dec 25, 2019

CLA assistant check
All committers have signed the CLA.

@eslint-deprecated eslint-deprecated bot added the triage An ESLint team member will look at this issue soon label Dec 25, 2019
@fanich37 fanich37 changed the title No restricted imports multiple error (#12282) Fix: handle multiple errors in no-restricted-imports (fixes #12282) Dec 25, 2019
@mdjermanovic mdjermanovic added accepted There is consensus among the team that this change meets the criteria for inclusion bug ESLint is working incorrectly rule Relates to ESLint's core rules and removed triage An ESLint team member will look at this issue soon labels Dec 25, 2019
Copy link
Member

@mdjermanovic mdjermanovic left a comment

Choose a reason for hiding this comment

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

Thanks for the PR! I didn't check everything, just noticed one change and have a question about that.

lib/rules/no-restricted-imports.js Outdated Show resolved Hide resolved
Copy link
Member

@mdjermanovic mdjermanovic left a comment

Choose a reason for hiding this comment

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

I left some notes about * and default.

Perhaps we should also consider refactoring the rule.

The actual flow in this PR is:

  1. checkNode() collects importNames and calls isRestrictedPath() to find out is the import invalid.
  2. isRestrictedPath() uses importNames and configuration to return true or false.
  3. If it's true, reportPath() is called. Then, this function is supposed to report something, and do it with the precise locations.

The problem is - reportPath() doesn't get any useful info from the previous steps, apart from knowing that there's something invalid because it's being called? So, it basically repeats steps 1 and 2., meaning that these steps were almost useless. Maybe we should do everything in checkNode(), instead.

lib/rules/no-restricted-imports.js Outdated Show resolved Hide resolved
@fanich37
Copy link
Contributor Author

fanich37 commented Jan 2, 2020

I left some notes about * and default.

Perhaps we should also consider refactoring the rule.

The actual flow in this PR is:

  1. checkNode() collects importNames and calls isRestrictedPath() to find out is the import invalid.
  2. isRestrictedPath() uses importNames and configuration to return true or false.
  3. If it's true, reportPath() is called. Then, this function is supposed to report something, and do it with the precise locations.

The problem is - reportPath() doesn't get any useful info from the previous steps, apart from knowing that there's something invalid because it's being called? So, it basically repeats steps 1 and 2., meaning that these steps were almost useless. Maybe we should do everything in checkNode(), instead.

I refactored it a bit.

@mdjermanovic
Copy link
Member

Thanks for the changes!

I think there is no need to keep the existing structure of the code if it doesn't provide value for the new behavior.

In particular, it feels like isRestrictedPath() and isRestrictedObject() are not very useful now. I believe that if we add this simple if at the beginning of reportPath() in the actual version of this PR:

function reportPath(importSource, importNames, node) {
    if (!restrictedPathMessages[importSource]) {
        return;
    }
    // ....
}

then we could rename reportPath(), call it directly and completely remove isRestrictedPath() and isRestrictedObject() functions from the code. Thoughts?

My idea was actually to simplify the code and do most of the logic in checkNode(): in short, iterate over specifiers and report them right away (including *). Your a bit more complex approach with the map is also fine for me 👍

@fanich37
Copy link
Contributor Author

fanich37 commented Jan 3, 2020

Thanks for the changes!

I think there is no need to keep the existing structure of the code if it doesn't provide value for the new behavior.

In particular, it feels like isRestrictedPath() and isRestrictedObject() are not very useful now. I believe that if we add this simple if at the beginning of reportPath() in the actual version of this PR:

function reportPath(importSource, importNames, node) {
    if (!restrictedPathMessages[importSource]) {
        return;
    }
    // ....
}

then we could rename reportPath(), call it directly and completely remove isRestrictedPath() and isRestrictedObject() functions from the code. Thoughts?

My idea was actually to simplify the code and do most of the logic in checkNode(): in short, iterate over specifiers and report them right away (including *). Your a bit more complex approach with the map is also fine for me 👍

Seems like a good idea! In this case I think that isRestrictedForEverythingImported and reportPathForEverythingImported are also useless. * could be reported in reportPath.
One thing I doubt about is reporting warnings in the specifiers loop. The clause to check whether report warning or not become quite big and difficult to reason about.

@mdjermanovic
Copy link
Member

Seems like a good idea! In this case I think that isRestrictedForEverythingImported and reportPathForEverythingImported are also useless. * could be reported in reportPath.

I agree if it seems that the code would become easier to follow.

One thing I doubt about is reporting warnings in the specifiers loop. The clause to check whether report warning or not become quite big and difficult to reason about.

Agreed, making the importNames map first (as it's implemented) seems like a better idea and a nice separation of logic 👍

Copy link
Member

@mdjermanovic mdjermanovic left a comment

Choose a reason for hiding this comment

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

Thanks! I left a couple of notes just about naming, and one detail that I believe should be fixed.

lib/rules/no-restricted-imports.js Outdated Show resolved Hide resolved
lib/rules/no-restricted-imports.js Outdated Show resolved Hide resolved
lib/rules/no-restricted-imports.js Outdated Show resolved Hide resolved
Copy link
Member

@mdjermanovic mdjermanovic left a comment

Choose a reason for hiding this comment

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

This looks very good! I just found a couple of minor details and I think there is one possible regression.

lib/rules/no-restricted-imports.js Outdated Show resolved Hide resolved
lib/rules/no-restricted-imports.js Outdated Show resolved Hide resolved
lib/rules/no-restricted-imports.js Outdated Show resolved Hide resolved
tests/lib/rules/no-restricted-imports.js Outdated Show resolved Hide resolved
@fanich37
Copy link
Contributor Author

fanich37 commented Jan 6, 2020

This looks very good! I just found a couple of minor details and I think there is one possible regression.

Thank you for review and suggestions. And sorry for the long journey with this PR :)

@mdjermanovic
Copy link
Member

Thanks for making all the changes and quickly addressing all concerns! It turned out that fixing this issue requires a quite big diff in the code.

As for the title of this PR, it might be safe to mark this as a semver-minor change (Update: instead of Fix:), because it can produce more warnings in some cases, caused by locations of eslint-disable-* comments as we are now changing reported locations. Also, 'handle multiple errors' maybe doesn't quite describe this change.

Thoughts about something like this?

Update: fix no-restricted-imports importNames reporting (fixes #12282)

@mdjermanovic
Copy link
Member

About edge cases where the same name is imported multiple times in the same declaration, such as these:

import { a, a as b } from "mod";

export { x as y, x as z } from "mod";

import foo, { default as bar } from "mod";

It might be correct to report all specifiers for a restricted name instead of just the last one, if it doesn't add too much complexity to the code. Maybe we could store arrays in importNames (e.g., find out name in the if-else-ifs, then either .set(name, [specifierData]) or push to existing). or do something else if this doesn't seem like a good idea.

It also seems that the configuration allows duplicates in importNames, but that should be a separate issue (it would be better to throw configuration error than to account for this in the code). It's okay for now to report the same specifier multiple times when the rule is misconfigured in such way.

@fanich37
Copy link
Contributor Author

fanich37 commented Jan 7, 2020

Thanks for making all the changes and quickly addressing all concerns! It turned out that fixing this issue requires a quite big diff in the code.

As for the title of this PR, it might be safe to mark this as a semver-minor change (Update: instead of Fix:), because it can produce more warnings in some cases, caused by locations of eslint-disable-* comments as we are now changing reported locations. Also, 'handle multiple errors' maybe doesn't quite describe this change.

Thoughts about something like this?

Update: fix no-restricted-imports importNames reporting (fixes #12282)

Sounds good!

@mdjermanovic mdjermanovic changed the title Fix: handle multiple errors in no-restricted-imports (fixes #12282) Update: fix no-restricted-imports importNames reporting (fixes #12282) Jan 7, 2020
@mdjermanovic
Copy link
Member

Changed the title to Update:...

Regarding the cases where the same name is imported multiple times, I think it's fine to report just 1 specifier for now. Maybe we could change that later in a separate PR, if it looks useful.

@fanich37
Copy link
Contributor Author

fanich37 commented Jan 7, 2020

About edge cases where the same name is imported multiple times in the same declaration, such as these:

import { a, a as b } from "mod";

export { x as y, x as z } from "mod";

import foo, { default as bar } from "mod";

It might be correct to report all specifiers for a restricted name instead of just the last one, if it doesn't add too much complexity to the code. Maybe we could store arrays in importNames (e.g., find out name in the if-else-ifs, then either .set(name, [specifierData]) or push to existing). or do something else if this doesn't seem like a good idea.

It also seems that the configuration allows duplicates in importNames, but that should be a separate issue (it would be better to throw configuration error than to account for this in the code). It's okay for now to report the same specifier multiple times when the rule is misconfigured in such way.

The problem here is the clause if (specifier.imported) ... else if (specifier.local). Since if specifier has imported property we never get into local branch of the clause...

@mdjermanovic
Copy link
Member

The problem here is the clause if (specifier.imported) ... else if (specifier.local). Since if specifier has imported property we never get into local branch of the clause...

I don't think it would be a problem. If there is imported, it really shouldn't take local.

In the if-else-if chain, imported is for specifiers that appear in import declarations, while the
local is (probably unintuitive) for those in export declarations.

In the above examples:

import { a, a as b } from "mod";

export { x as y, x as z } from "mod";

import has 2 specifiers, both have a as imported.

export has also 2 specifiers, both have x as local, and nothing as imported (y and z are exported).

AST explorer link with the examples

What I was thinking is, for example in the first case if a is a restricted name from mod, that it might be correct to report 2 errors. One error with the location of the first a specifier, the other with the location of the a as b specifier. At the moment, a as b specifier will overwrite already set a location in the map. A solution might be to have [{ loc }, { loc } ...] in the map instead of just a single { loc }.

Copy link
Member

@mdjermanovic mdjermanovic left a comment

Choose a reason for hiding this comment

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

This looks great, thanks for the fix and the refactoring!

@fanich37
Copy link
Contributor Author

This looks great, thanks for the fix and the refactoring!

Sorry for the delay. It's just new year holidays that is over and I've got a big task at work :) But I would like to add your proposal about duplicate import names. I think I could add it till Monday.

And also don't you think it would be better to put all logic from checkPathAndReport into checkNode, checkPathAndReport rename back into reportPath. I just think it's a bit weird we have two check... functions in a rule? 😕

@mdjermanovic mdjermanovic self-requested a review January 10, 2020 22:55
@mdjermanovic
Copy link
Member

Sorry for the delay. It's just new year holidays that is over and I've got a big task at work :) But I would like to add your proposal about duplicate import names. I think I could add it till Monday.

That would be great, thanks!

And also don't you think it would be better to put all logic from checkPathAndReport into checkNode, checkPathAndReport rename back into reportPath.

Something like my initial idea? I'm not sure about that anymore, the way it's implemented now looks very nice (i.e. deal with different kinds of imports/exports first to get the importNames map and then do the main logic on a simplified input).

Also, it looks like the actual version in this PR might make it easier to add some new things in the future, like to fix #12737 and support #12629. At least I think so, could be wrong. If you think that the other approach might be better for future maintenance and easier to follow, I'm fine with both.

lib/rules/no-restricted-imports.js Outdated Show resolved Hide resolved
lib/rules/no-restricted-imports.js Outdated Show resolved Hide resolved
@mdjermanovic mdjermanovic self-requested a review January 11, 2020 23:18
Copy link
Member

@mdjermanovic mdjermanovic left a comment

Choose a reason for hiding this comment

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

Thanks, looks very good! Just two small details.

lib/rules/no-restricted-imports.js Outdated Show resolved Hide resolved
lib/rules/no-restricted-imports.js Outdated Show resolved Hide resolved
Copy link
Member

@mdjermanovic mdjermanovic left a comment

Choose a reason for hiding this comment

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

LGTM, thanks!

Both rule and the tests look great 👍

@@ -95,6 +99,11 @@ module.exports = {
const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || [];
const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];

// if no imports are restricted we don"t need to check
if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) {
Copy link
Member

Choose a reason for hiding this comment

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

👍

Copy link
Member

@kaicataldo kaicataldo left a comment

Choose a reason for hiding this comment

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

LGTM. Thanks for contributing to ESLint!

@kaicataldo kaicataldo merged commit 68becbd into eslint:master Jan 16, 2020
@fanich37 fanich37 deleted the bugfix/nri-multiple-error branch January 16, 2020 07:26
@fanich37
Copy link
Contributor Author

Thanks, looks very good! Just two small details.

Thank you for your review and suggestions 👍

@mdjermanovic
Copy link
Member

@fanich37 thanks again for the really good work and I hope to see more PRs from you in the future! 👍

montmanu pushed a commit to montmanu/eslint that referenced this pull request Mar 4, 2020
…#12282) (eslint#12711)

* Process importNames

* Update test cases

* Fix rebase issue

* Update importNames logic

* Remove useless funcs, update tests

* Fix naming, fix everything imported w/o importNames

* Fix typo, fix specifier clause, fix rebase issue

* Process importNames with the same name

* Clean code in receiving specifier data, remove debug

* Fix type def, add empty name check, replace concat with push
@eslint-deprecated eslint-deprecated bot locked and limited conversation to collaborators Jul 16, 2020
@eslint-deprecated eslint-deprecated bot added the archived due to age This issue has been archived; please open a new issue for any further discussion label Jul 16, 2020
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 bug ESLint is working incorrectly rule Relates to ESLint's core rules
Projects
None yet
Development

Successfully merging this pull request may close these issues.

no-restricted-imports: disable comments do not work for individual named imports
4 participants