-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Auto-import prefers parent index.ts which leads to circular reference #45953
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
Comments
Since my branch was auto closed I'll move some information to this one. It broke in this release: It broke in [email protected] Most likely in this commit: My thoughts: Another solution could be to have an option for or always ignoring re-exports from the entry point or all index.ts (or all re-exports and always resolve the source file). |
Can exhibit as well with a '.' import, seems to just depend on how far up the symbol is exported. My example from #46817 was: Given a folder with: // index.ts
export * from './a'; // a.ts
export class A {} // b.ts
export class B extends A {} ...an attempt to use auto-import to resolve the reference to import { A } from '.'; ...instead of the preferred (and the one provided by previous versions of VS Code): import { A } from './a'; |
It is a huge relief to hear that there is an intent to fix here, auto-importing is effectively useless in VS Code right now due to this issue :-( Have been working around it but if I don't notice it happens it inevitably causes problems at runtime, usually due to decorator references or runtime type metadata. |
Sorry to pitch in without any additional info (I think the problem description is spot on), I would only like to note on the severity of this: right now VS Code + TypeScript >= 4.4.0 is effectively unusable if the modules are structured using re-exports. In the codebases that adopt this pattern at scale, all auto-imports must be essentially re-written manually, which is a huge bummer. The only sensible alternative at the moment is downgrading to TypeScript < 4.4.0 which can also tricky because the codebase may already have adopted the 4.4's breaking changes. Hoping this will get resolved soon 🤞 |
I noticed there is a fix and it prevents '../' like paths. Good enough for relative path users. |
@ziofat Not sure I understand that right, but how can absolute path consist of only |
I think the point is that when auto importing files in the root (src for instance), "src" is the shortest path, but the dev may wish to use a relative import instead of "src". Is that right? |
@inca Sorry for my poor expression. And when I try to import some module which is re-exported it always give me If I set the setting to 'relative' it will give me In this example, My project is enforced to use absolute path importing because it provides more context about project structure to improve readability. And we have to remove all contents in In my opinion this issue should stay open for better solution. For example, providing a 'deepest' option to always ignore re-export? |
I do think the "shortest path" model is too simplistic. I thought previously it was using the shortest path below the current directory so as to avoid loops while still taking advantage of barreled modules appropriately, was this not the case before? So for instance if I was in Under the shorter rule wouldn't it still prefer I was going to test this behavior with the new fix using Insiders to better understand but for some reason auto-importing didn't work at all in Code Insiders (no fix recommendation at all). Fresh Insiders installation with no extensions. EDIT: I got autocomplete to work in Insiders.... it requires you to have the files open in the editor in order for the suggestion to be available (and if you close the file providing the symbol, it loses the suggestion). Seems like there's bugs to fix there unrelated to this. I was able to check the current behavior but it looks like the "no dots and slashes" change is not in Insiders yet as it still suggests "." pretty readily. |
🤔 Why write a re-export if you never want to use it? The behavior you’re asking for will happen if you simply delete your barrel re-export. |
@andrewbranch |
I agree with this. Current best-practice seems to me to result in never using the barrel file with a relative path. The barrel file is there to provide a defined interface for the package, and therefore will normally be accessed via package imports. Files within the package will import from the files which contain the actual definitions, never re-exports. |
In our case ignoring re-exports, or barrel files if it's possible to identify them, and always use the relative path to the file would be the best option. As other have state barrel files are usually used outside of the application. I undestand some people might still want to use them, so I guess an option could be a nice way to accommodate everyone. If I have to choose between always or never use a barrel file inside my own package, it choose never everyday of the week. It's really not needed anymore since VSCode IntelliSense has improved over the years. And now when it also refactor paths when renaming and moving files around I don't see a use for internal barrel files. |
Does this not break down when your app gets big enough? I used to work on a web app frontend at Microsoft where we had 5 or so individual teams working on sections of the app, all consuming an internal library of UI components that were part of the same app (there was no reason to separate these into separate packages, it was just a big app with code splitting). We defined Webpack aliases and TypeScript import { Card } from "@common/ui/card/Card";
import { Table } from "@common/ui/table/Table";
import { List } from "@common/ui/list/List"; That’s preferable?? I suspect the gold standard heuristic would be “do not import from a re-export that also imports me (the importing file).” So in a conventional structure, you would be fine importing from |
I definitely use barrels within my applications for modules from outside the module. Inside the module I don't use the barrel for that module for obvious reasons |
@rezonant can you rephrase or expound on that? A module is any file with a top-level import or export. |
I think I’m going to go with “don’t import from a re-exporting file that is a direct child of any ancestor directory of the importing file.” That should cover 100% of cases that #47432 did, plus scenarios with non-relative paths, ESM resolution, and barrels not named “index.” It’s probably not perfect, but it’s fast. |
I think I can provide an example. Let's assume the following directory structure:
In In I'm assuming the #47432 will work just fine for covering these. |
Our options are
(2) and (3) are deprioritized.
(3) is deprioritized, leaving (1) and (2) to move on to other sorting mechanisms. If you’re using CommonJS resolution leaving off |
@andrewbranch I meant it in the sense of "feature module", for instance if I had a folder "accounts" that had all my accounts related classes, models, etc in it, I would stick an index.ts in accounts folder and expect to import it via the barrel from outside of the module. Within the module I use relative imports, so for example in accounts/user.ts perhaps it would import Profile from "./profile". If the accounts module needed to use another sibling module, say for instance next to "accounts" I've got "util" or so, I would import from that util module from within an accounts file as "../util". In some very large apps I make "@" a top level paths entry and set VS to absolute imports such that I would get `import { ... } from "@/util" or so, but I haven't checked if the current revision of auto import gets this right off the bat or not. The obvious benefit of both barrel imports internally in an app as well as absolute imports via tsconfig paths is to aid in refactoring to reduce the amount of import fixups required when things change or are restructured, as well as reducing the number of imports needed when importing many classes from one "feature module". Another benefit is when the internal modules are expected to eventually spin out into their own NPM modules. Restructuring module directory structures to ensure non-cyclical dependencies is extremely important when using decorators unfortunately, since the decorator format does not use the accessor pattern (ie |
I think this satisfies all my use cases 👍 |
Counterexample against my own proposed heuristic: suppose you have a // project/ui/utils.ts
export function sum(a, b) { return a + b }
export { tryParseJson } from "../api/long/path/internal/utils";
// project/ui/pages/home.ts
tryParseJson/*|*/ Here, by my rule, we would avoid importing from |
One helpful hypothesis might be that the files we want to avoid importing from are
|
Yes I think treating I think a more general solution would require doing more complex import analysis -- ie if the import candidate "depends" on the current file via zero or more indirect import paths, then deprioritize it. Probably would be expensive. |
I'm still getting barrel files as the top autocomplete. My project is set up as:
Files from |
This is definitely not resolved. Running into the same behaviour as @ivancuric @andrewbranch can this be re-opened? |
Please open a new issue with concrete repro steps |
This was exactly what mattered, in fact. The original fix was incorrectly limited to |
@andrewbranch do we have any plan to backport this to the earlier versions? |
Nope. We almost never patch anything but the current minor release, and then only for critical regressions. |
Bug Report
This appears to be recently changed behaviour. I have a package with an index.ts at the root:
a/Foo.ts
which containsexport const Foo = 'Foo'
index.ts
which containsexport * from './a/Foo'
b/Bar.ts
and attempt to auto-importFoo
import { Foo } from '..'
.I think that importing from an index directly above the file like this is highly likely to lead to a circular reference.
🔎 Search Terms
auto-import
auto-import relative
🕗 Version & Regression Information
⏯ Playground Link
See simple repro.
💻 Code
🙁 Actual behavior
Imports from parent index.ts. This file is highly likely to also import the importing file, causing a circular reference.
🙂 Expected behavior
Import from
'./a/Foo'
instead. Or, put another way, prefer not to import from a path which has only one or more..
in it.The text was updated successfully, but these errors were encountered: