eslint-plugin-import icon indicating copy to clipboard operation
eslint-plugin-import copied to clipboard

no-restricted-paths is slow when many imports/excepts

Open robatwilliams opened this issue 5 years ago • 13 comments

The rule appears to be building and validating the excepts for each zone every time an import is encountered. This makes it very slow when you have many files/imports, zones, and/or excepts.

I narrowed it down to this validation part here:

const hasValidExceptionPaths = absoluteExceptionPaths
            .every((absoluteExceptionPath) => isValidExceptionPath(absoluteFrom, absoluteExceptionPath))

https://github.com/benmosher/eslint-plugin-import/blob/master/src/rules/no-restricted-paths.js#L88

Could it instead validate the exception paths once when the rule is initialised?

robatwilliams avatar May 12 '20 16:05 robatwilliams

How many exception paths do you have? O.o

ljharb avatar May 12 '20 16:05 ljharb

The line you referenced happens when checkForRestrictedImportPath is called; and it's called when the rule visits nodes because it needs that node information (that's what absoluteFrom is). I don't think it can be moved at all.

ljharb avatar May 12 '20 16:05 ljharb

Quite a few (but we're working on that), but what we have more of is files and imports - it's a large codebase, and I'm trying to use this rule and/or no-restricted-imports to help improve and enforce separation.

I saw that absoluteFrom is derived from the basePath and zone.from, I don't see anything node-specific. The error it would produce is Restricted path exceptions must be descendants of the configured from path for that zone., which isn't node-specific. I might have missed something though.

robatwilliams avatar May 12 '20 16:05 robatwilliams

The importPath used in const absoluteImportPath = resolve(importPath, context) is the specifier that shows up in the code; it's not known in advance.

ljharb avatar May 12 '20 16:05 ljharb

Yes, I don't see that it's used for this validation though. Looks like a validation of the rule configuration, that the excepts does not backtrack to a parent of from.

Thanks for the quick responses, and apologies if I'm missing it.

robatwilliams avatar May 12 '20 17:05 robatwilliams

importPath comes from the node, it's used in https://github.com/benmosher/eslint-plugin-import/blob/master/src/rules/no-restricted-paths.js#L89 to validate the exception paths.

Just a thought - if we supported globs/stars in the exceptions, would that let you consolidate your list significantly?

ljharb avatar May 12 '20 19:05 ljharb

It does, but I don't see it (importPath) or absoluteImportPath used on L88-89 to validate the exception paths themselves:

const hasValidExceptionPaths = absoluteExceptionPaths
            .every((absoluteExceptionPath) => isValidExceptionPath(absoluteFrom, absoluteExceptionPath))

It's only used later on L96-97 to check if an offending import is excepted:

const pathIsExcepted = absoluteExceptionPaths
            .some((absoluteExceptionPath) => containsPath(absoluteImportPath, absoluteExceptionPath))

Looking at it from another angle, it's the validation for this part of the config, which doesn't sound node-specific. I'll take your word for it though if I'm still not making sense.

An optional except may be defined for a zone, allowing exception paths that would otherwise violate the related from. Note that except is relative to from and cannot backtrack to a parent directory.

Globs would help consolidate the list, yes (I see there's a PR #1708). Though I've gone with no-restricted-imports from ESLint core now.

robatwilliams avatar May 12 '20 20:05 robatwilliams

aha, i do see what you mean.

it would split up the validation a bit, but a PR that hoists whatever can be hoisted out of the visitor into create sounds perfect.

ljharb avatar May 12 '20 21:05 ljharb

@ljharb I prepared a PR, could you please have a look when you have a chance? On the project I work on, it made the check an order of magnitude faster.

malykhinvi avatar Apr 07 '21 05:04 malykhinvi

Absolutely! Note that tests are currently broken on master (#1986) so i might not be able to land it just yet.

ljharb avatar Apr 07 '21 23:04 ljharb

I'm experiencing slow performance for this rule as well. You can see the configuration in:

https://github.com/pixiebrix/pixiebrix-extension/blob/2604923b0f29c63bae5e1fbd690db4e33bd4b23c/.eslintrc.js#L15-L16 (note that dropping the glob only maybe saves 1 second)

In short, I don't want files in /A to import from /B and vice versa, for 5 such folders, but this rule appears to be taking a long time just for that:

Rule Time (ms) Relative
import/no-restricted-paths 30474.413 50.6%
@typescript-eslint/no-confusing-void-expression 5398.132 9.0%
@typescript-eslint/no-floating-promises 3957.692 6.6%
@typescript-eslint/promise-function-async 2753.125 4.6%
@typescript-eslint/no-redeclare 829.458 1.4%
react/display-name 825.262 1.4%
react/no-direct-mutation-state 723.626 1.2%
unicorn/prevent-abbreviations 690.028 1.1%
@typescript-eslint/no-misused-promises 662.684 1.1%
unicorn/template-indent 544.756 0.9%

I think it might not actually be the specific rule though:

See #2340 (comment); whichever is the first rule to build the ExportMap will be the slowest.

fregante avatar Nov 23 '22 05:11 fregante

Indeed, if you maximally enable the useful import rules, you may find another rule is the 30s one.

ljharb avatar Nov 24 '22 00:11 ljharb

I disabled the rule, nothing else came close to it, so it's definitely one of the slowest ones. The other one is import/no-cycle, which by itself takes twice the time, so it's disabled in regular runs.

See comparison in:

  • https://github.com/pixiebrix/pixiebrix-extension/pull/7593

fregante avatar Feb 09 '24 13:02 fregante