[BUG] Dependencies missing from tree when using transform flag

#55 · closed · 2 comments

View on GitHub ↗

umbralOptimatum

**Describe the bug** I have a large TypeScript project and have set up DPDM in my test process to detect circular dependencies imported by `index.ts`. The project has several circular type dependencies so I'd like to use `dpdm -T src/modules/index.ts`. However, this fails to detect several actual cycles: the one that prompted this test throws a `ReferenceError: can't access lexical declaration before initialization` when run. In addition the tree for `index.ts` doesn't show 97% of its direct dependencies (none of which are types) and 40% of them are missing from the tree entirely! Most of them are referenced after being imported (being `Object.assign`ed to `window`) For the moment I'm using `dpdm --no-tree -T src/modules/**/*.ts` instead as this does seem to detect all the actual cycles, but resolving them is much harder without reliable trees. This approach also scans a handful of files that aren't accessible from `index.ts`, though that's not a big deal. <!-- A clear and concise description of what the bug is. --> **To Reproduce** I can reproduce it with this simplification of the circular dependency I first noticed the issue with. index.ts: ```ts import A from './A'; import B from './B'; import C from './C'; ``` A.ts: ```ts import C from './C'; export default class A { static list = [{ text: 'Example A', class: new C(0) }]; } ``` B.ts: ```ts import A from './A'; export default class B { static list = [{ text: 'Example B' }]; static getText(i) { return `${B.list[i]} ${A.list[i]}`; } } ``` C.ts: ```ts import B from './B'; export default class C { constructor(public i: number) { }; public getText() { return B.list[this.i].text; } } ``` `dpdm index.ts` correctly detects the cycle. `dpdm -T index.ts` does not detect the cycle and shows no dependencies for `index.ts` in its tree. `dpdm -T A.ts` detects the cycle and shows the correct tree, as does `dpdm -T B.ts` and `dpdm -T C.ts`. Referencing any of the imports in `index.ts` causes it to detect the cycle again, but it isn't consistent in ways I don't understand. Adding `Object.assign(<any>window, { A, B });` works in this example but doesn't in my project, and even with that line `C` is still missing from `index.ts`'s direct dependencies. **Expected behavior** Using the transform flag should ignore type dependencies without other changes to the dependency tree. **Additional context** Thanks for making DPDM! Even with the current weirdness, it's been extremely helpful for tracking down a couple evasive bugs.

Comments

acrazing

This is because `-T` uses typescript to translate `.ts` files into `.js` files. If the import is detected as a type reference (no value reference), it will be removed. You can get the compiled result here: <https://www.typescriptlang.org/play/?#code/JYWwDg9gTgLgBAQTgMyhEcDkA6A9AzAbgChRJY4AhFNDHXSo08aeAYRvSzzaKA>. In your case, `import A from './A'` without read/write on `A` will be treated as type reference, so it will be removed. If you want to keep it, you have three options: 1. Remove `-T` option 2. Make sure `A` is value referenced. Such as `console.log(A)` 3. Change the import line to `import './A'`

umbralOptimatum

Thank you for explaining. I suspected the issue was related to some nuance of the TypeScript compiler I haven't encountered before. Good to know I'm not completely losing my mind here. That said, your reasoning doesn't explain the issue I'm running into. As mentioned, the misbehaving imports for my `index.ts` *are* referenced by value. (All of the non-side-effect imports are, or eslint would be very loud right now.) I did some more hunting and the problem seems to be a specific syntax combo: ```ts // A.ts export const example = 1; // B.ts export const example2 = 2; // index.ts import * as A from './A'; import * as B from './B'; // doesn't work with -T, no dependencies shown Object.assign(<any>window, { rename: A, B, }); // doesn't work with -T, no dependencies shown Object.assign(<any>window, { rename: B, A, }); // doesn't work with -T, only A shown as dependency Object.assign(<any>window, { A, rename: B, }); // works correctly Object.assign(<any>window, { A, B, }); // works correctly Object.assign(window as any, { rename: A, B, }); ``` The TypeScript compiler handles all of these as expected so the issue must be somewhere in DPDM's parsing.