rome / tools

Unified developer tools for JavaScript, TypeScript, and the web
https://docs.rome.tools/
MIT License
23.71k stars 660 forks source link

☂️ Create `useImportRestrictions` rule #4678

Open arendjr opened 1 year ago

arendjr commented 1 year ago

Description

Based on the original discussion in the PR for the noNestedModuleImports rule, I will start working on a new rule called useImportRestrictions. This will be done in several steps:

Other suggestions (such as options from eslint-plugin-import) are welcome to be discussed here :)

Conaclos commented 1 year ago

Thanks for opening the discussion about the rule :)

I wonder if we could get rid of the packagePrivate option altogether.

If there is an index file, then all exports of sibling modules (i.e. modules in the same directory) are package-scoped. Otherwise, they are all public.

Regardless of the presence or absence of an index file, any module that has at least one export annotated with @package or @access package has the following behavior: any annotated exports are package-scoped; other exports are public.

By the way, if the rule checks for cycles, I no longer see the value of forbidding imports from parent indexes? Do we really need a config to disable cycle checks?

I also wonder if the rule should not be divided into three rules: one focused on package-scoped exports (index file and annotated exports) ; another one to forbid cycles ; and a last one for strict dependencies ? Could you explain your rationale behind an all-inclusive rule?

ematipico commented 1 year ago

Note: The initial version may perform repetitive fs calls to check for the existence of index files. I think this should be acceptable for this initial version given that Rome has no accepted solution for import resolutions yet.

I highly suggest to avoid this, for the following reasons:

We should just assume that the code is there and it works. The rules from the plugin import can wait

arendjr commented 1 year ago

@ematipico wrote:

I highly suggest to avoid this

Fair enough :) It does mean I wouldn’t be able to implement the requireIndexFile option initially, but I can postpone that until a better import resolution mechanism is available. It does also mean @Conaclos’s suggestion to use the presence of an index file to determine whether siblings are exported publicly or not would not work yet either, but the packagePrivate option as I had it in mind would have needed to wait until we have project-level metadata anyway, so that’s also not an immediate concern.

@Conaclos also wrote:

By the way, if the rule checks for cycles, I no longer see the value of forbidding imports from parent indexes?

Good point. If we forbid parent indices initially, we can probably remove it again at that point. Which makes me think we might as well save ourselves the effort :)

Do we really need a config to disable cycle checks?

I felt it might be a good idea to allow disabling. Technically they’re allowed by ESM, and they only lead to problems in some cases. At the same time, I wouldn’t disable such check myself, and we could also opt to not offer it as an option until someone makes a convincing case for it.

I also wonder if the rule should not be divided into three rules: one focused on package-scoped exports (index file and annotated exports) ; another one to forbid cycles ; and a last one for strict dependencies ? Could you explain your rationale behind an all-inclusive rule?

The main reason is because I felt they’re all highly related and operate on the same syntax nodes. But as you point out, there’s also room for splitting if you feel that makes more sense.

Maybe having it as one also makes the options more easily discoverable? Personally if I find a valuable rule I would intuitively read the list of options once to get an idea of what it offers, but I wouldn’t have a complete overview of all the rules Rome offers (on a barely related note, I would like if I could enable Rome’s React rules with a single toggle instead of enabling individual rules).

arendjr commented 1 year ago

Thinking about the implementation a bit more, I think my preference grows towards a single rule. After all, it doesn’t matter if an import that violates the strictDependencies also introduces a cycle, or if a cycle-introducing import happens to do so through a package private symbol. But if the rules are split, users will get multiple errors, even if it’s likely it only takes a single action to correct.

Performance-wise, especially when things like import resolutions come into play, it’s probably better if only a single rule has to perform import resolution rather than multiple ones. We probably may want a cache anyway to prevent repetitive resolutions, but such a cache would be easier to implement if it doesn’t need to be shared across rules.

Conaclos commented 1 year ago

Without module resolution, the rule seems impossible to implement. In this case, I have another idea: the rule could focus on strictDependencies For example, a user could restrict imports to the modules of the same directory and make indexes public with the following configuration:

{
  "./**/*.js": ["$targetDirectory/*.js"],
  "./**/index.js": ["./**/*.js"],
}

$targetDirectory corresponds to the directory where the imported module is. I don't like the $targetDirectory. However, this allows to express the idea.

arendjr commented 1 year ago

Well, the original PR I made implemented an MVP and didn't rely on module resolution, so that's what I intend to focus on again for the first version. It doesn't even require any configuration if we postpone the requireIndexFile option.