sverweij / dependency-cruiser

Validate and visualize dependencies. Your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.
https://npmjs.com/dependency-cruiser
MIT License
5.27k stars 250 forks source link

Question: Resolving typescript source files imported from outside specified package #935

Closed harrisonzhu-db closed 4 months ago

harrisonzhu-db commented 5 months ago

Summary

I'm using the dependency-cruiser JS API to recursively construct a list of all source files referenced by an entry point. When this typescript entry point or any of its dependencies imports some module, my IDE is able to jump to the typescript where that is defined, and in essence, I want to do the same thing using dependency-cruiser.

The following code (abstracted)

import { cruise, ICruiseOptions, ICruiseResult } from "dependency-cruiser";
// other imports

async function cruiseDirs() {
  const ARRAY_OF_FILES_AND_DIRS_TO_CRUISE: string[] = [
    getAbsolutePathFromUniverseRelativePath(
      "redash/managed_redash/packages/app/src/open-marketplace/OpenMarketplace.tsx"
    ),
  ];

  try {
    const cruiseOptions: ICruiseOptions = {
      exclude: {
        path: ".*node_modules",
      },
    };
    const cruiseResult = cruise(
      ARRAY_OF_FILES_AND_DIRS_TO_CRUISE,
      cruiseOptions
    );
    console.log(JSON.stringify(cruiseResult.output, null, 2));
  } catch (error) {
    console.error(error);
  }
}
cruiseDirs();

prints out generally what I want. Within redash/managed_redash/packages/app, dependency-cruiser knows how to resolve dependencies to typescript code. In the above example, the OpenMarketplace.tsx file that we start from has these two imports

import { Marketplace } from '@databricks/dbsql/src/app/pages/marketplace/Marketplace';
import { MarketplaceFilterContextProvider } from '@databricks/dbsql/src/app/pages/marketplace/filter/contexts/MarketplaceFilterProvider';

And dependency-cruiser resolves these to ../../../redash/managed_redash/packages/app/src/app/pages/marketplace/filter/contexts/MarketplaceFilterProvider.tsx and ../../../redash/managed_redash/packages/app/src/app/pages/marketplace/Marketplace.tsx

which is what i want!

However, when we import from directories outside of the one specified initially, we run into some issues with resolving imports to their source typescript. Take the following output from dependency-cruiser

  {
    "source": "../../../redash/managed_redash/packages/app/src/app/services/location.ts",
    "dependencies": [
      {
        "module": "@databricks/web-shared/mfe-services",
        "moduleSystem": "es6",
        "dynamic": false,
        "exoticallyRequired": false,
        "resolved": "../../../js/packages/web-shared/mfe-services.mjs",
        "coreModule": false,
        "followable": true,
        "couldNotResolve": false,
        "dependencyTypes": [
          "npm"
        ],
        "matchesDoNotFollow": false,
        "circular": false,
        "valid": true
      },

@databricks/web-shared/mfe-services resolves to some js, which I don’t expect (maybe naively), since the IDE somehow recognizes in import { isRpcSupported } from '@databricks/web-shared/mfe-services'; That isRpcSupported resolves to a specific typescript file.

Further, we also get some unresolved paths:

{
    "source": "@databricks/persona-nav",
    "followable": false,
    "coreModule": false,
    "couldNotResolve": true,
    "matchesDoNotFollow": false,
    "dependencyTypes": [
      "unknown"
    ],
    "dependencies": [],
    "dependents": [
"../../../redash/managed_redash/packages/app/src/app/pages/marketplace/hooks/useMarketplaceTracking.tsx",
      "../../../redash/managed_redash/packages/app/src/app/extensions/edge/components/ApplicationLayout/ApplicationWrapper.tsx",
… CUT FOR BREVITY

These other packages (@databricks/web-shared/…, @databricks/persona-nav) are included in the monorepo root package.json as part of a yarn workspace. Here's the abbreviated structure of our monorepo

universe
├── package.json <- specifies the packages in the yarn workspace
├── redash
│   └── managed_redash
│       ├── packages
│       │   ├── app
│       │   │   └── package.json
├── js
│   ├── packages
│   │   ├── persona-nav
│   │   │   └── package.json
│   │   ├── web-shared
│   │   │   └── package.json

My question: What do I need to add to dependency-cruiser’s options in order to resolve these js files to their typescript source? I tried to require("dependency-cruiser/config-utl/extract-ts-config");, and add redash/managed_redash/packages/app/tsconfig.json to cruise as a parameter, but the output did not change. Here’s the tsconfig of the package that has the entry point that I’m trying to cruise.

{
 "extends": "../../tsconfig.base.json", // <-- this does not include any path aliases
 "compilerOptions": {
   "target": "es2019",
   "lib": ["dom", "dom.iterable", "esnext", "esnext.intl", "es2017.intl", "es2018.intl"],
   "allowJs": true,
   "skipLibCheck": true,
   "strict": true,
   "forceConsistentCasingInFileNames": true,
   "noEmit": true,
   "esModuleInterop": true,
   "module": "esnext",
   "moduleResolution": "node",
   "resolveJsonModule": true,
   "isolatedModules": true,
   "jsx": "preserve",
   "jsxImportSource": "@emotion/react",
   "baseUrl": "./",
   "paths": {
     "@databricks/dbsql/*": ["./*"]
   },
   "types": ["node", "jest", "@testing-library/jest-dom", "ace", "@databricks/config-webpack/env"],
   "incremental": true
 },
 "include": ["next-env.d.ts", "globals.d.ts", "emotion.d.ts", "recoil/testing.d.ts", "**/*.ts", "**/*.tsx"]
}

Context

For more context, I'm building a script that takes some set of typescript entry points and checks if their usages (or any of their dependencies' usages) of a certain library are correct (based on some other validation) with respect to that entry point.

Environment

harrisonzhu-db commented 5 months ago

Interestingly enough, was able to get some more modules to resolve by creating a mock tsconfig.dependency-cruiser.json in the target directory. It extends the tsconfig.json and adds path aliases:

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "paths": {
      "@databricks/web-shared/*": [
        "../../../../js/packages/web-shared/src/*"
      ],
      "@databricks/design-system/*": [
        "../../../../design-system/src/*"
      ],
      "@databricks/i18n/*": [
        "../../../../js/packages/i18n/src/*"
      ],
      "@databricks/persona-nav/*": [
        "../../../../js/packages/persona-nav/src/*"
      ],
      "@redash/viz/*": [
        "../../../../js/packages/visualization/src/*"
      ]
    }
  }
}

Poking around through the source code of version 12.2.2, I saw that for some reason, the property ruleSet.options.tsConfig.fileName of the passed in options were accessed, so I got the cruise to resolve certain dependencies by passing in a tsconfig in two fields: the CruiseOptions and the TSConfig option

  const { output } = cruise(
    [absolutePath],
    {
      exclude: {
        path: ".*node_modules",
      },
      ruleSet: {
        // todo: this is quite hacky... try to use .dependency-cruiser.js instead
        //@ts-ignore
        options: {
          tsConfig: {
            fileName: getAbsolutePathFromUniverseRelativePath(
              "redash/managed_redash/packages/app/tsconfig.dependency-cruiser.json"
            ),
          },
        },
      },
    },
    undefined,
    extractTsConfig(
      getAbsolutePathFromUniverseRelativePath(
        "redash/managed_redash/packages/app/tsconfig.dependency-cruiser.json"
      )
    )
  );

for some reason, this does not work if you only pass this in in one argument or the other

github-actions[bot] commented 4 months ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.