Skip to content
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

[ts-morph] Unable to get full qualified name of an identifier with path Mapping in tsconfig.json #1430

Open
theskcd opened this issue Jul 15, 2023 · 3 comments

Comments

@theskcd
Copy link

theskcd commented Jul 15, 2023

Unable to resolve the fullQualifiedName of a identifier in a function block

"ts-morph": "^19.0.0",

Hi I have this use-case where I want to get the full qualified type of each function call happening inside a function, for some reason when the tsconfig.json has paths its not able to resolve the full qualified name properly even when I have a custom resolveModuleNames

tsconfig looks like this:

{
  "exclude": ["./cypress", "./cypress.config.ts"],
  "include": ["remix.env.d.ts", "reset.d.ts", "**/*.ts", "**/*.tsx"],
  "compilerOptions": {
    "lib": ["DOM", "DOM.Iterable", "ES2019"],
    "types": ["vitest/globals"],
    "isolatedModules": true,
    "esModuleInterop": true,
    "jsx": "react-jsx",
    "module": "CommonJS",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "target": "ES2019",
    "strict": true,
    "allowJs": true,
    "forceConsistentCasingInFileNames": true,
    "baseUrl": ".",
    "paths": {
      "~/*": ["./app/*"]
    },
    "skipLibCheck": true,

    // Remix takes care of building everything in `remix build`.
    "noEmit": true
  }
}

the project invocation for ts-morph looks like this:

        tsProject = new Project({
            tsConfigFilePath: config.typescriptTSConfig ?? undefined,
            compilerOptions: {
                ...compilerOptions,
            },
            resolutionHost: (moduleResolutionHost, getCompilerOptions) => {

                return {
                    resolveModuleNames: (moduleNames, containingFile) => {
                        return moduleNames.map(moduleName => {
                            let matchedPathKey: string | undefined = undefined;

                            for (const pathKey in pathMapping) {
                                const regex = new RegExp('^' + pathKey.replace('*', '(.*)'));
                                if (regex.test(moduleName)) {
                                    matchedPathKey = pathKey;
                                    break;
                                }
                            }

                            if (matchedPathKey === undefined) {
                                logger.appendLine(`Could not find a path mapping for ${moduleName} ${containingFile}`);
                                return undefined;
                            }

                            // Replace '*' with the captured substring from moduleName
                            const possiblePaths = pathMapping[matchedPathKey].map(path =>
                                path.replace('*', moduleName.replace(
                                    new RegExp('^' + (matchedPathKey as string).replace('*', '(.*)')), '$1')
                                )
                            );

                            // Try to resolve the module path
                            for (const possiblePath of possiblePaths) {
                                let fullPath = undefined;
                                if (baseUrl !== undefined) {
                                    fullPath = path.join(baseUrl, possiblePath);
                                } else {
                                    fullPath = possiblePath;
                                }
                                const absolutePath = ts.resolveModuleName(
                                    fullPath,
                                    containingFile,
                                    getCompilerOptions(),
                                    moduleResolutionHost,
                                ).resolvedModule;
                                if (absolutePath) {
                                    logger.appendLine(`Resolved ${moduleName} to ${absolutePath.resolvedFileName}`);
                                    return absolutePath;
                                }
                            }

                            // Fallback to default resolution
                            logger.appendLine(`Falling back to default Could not resolve ${moduleName} ${containingFile} to any of the following paths:` + possiblePaths.join(', '));
                            return ts.resolveModuleName(moduleName, containingFile, compilerOptions, moduleResolutionHost).resolvedModule;
                        });
                    }
                };
            },
        });

And an example file is:

import { trimFormattedPhoneNumber, trimPhone } from "~/utils";


export function testing() {
    trimFormattedPhoneNumber("1234567890");
}

here when I am at the node trimFormattedPhoneNumber inside the function testing I do symbol.getFullyQualifiedName but it just gives me back trimFormattedPhoneNumber missing out the whole import where its originally from?

I would have expected it to resolve the module and give me utils.trimFormattedPhoneNumber (notice the utils at the start)

Happy to provide more context as well. Thanks a lot!

@theskcd
Copy link
Author

theskcd commented Jul 15, 2023

Could this have something to do with remix? I am not sure honestly

@theskcd
Copy link
Author

theskcd commented Jul 15, 2023

Answering my own question, we can just do:

getAliasedSymbol()?.getDeclarations

for things to work 🙌

@tryggvigy
Copy link

@theskcd I'm facing the same problem. Can you post your full solution that worked for you? 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants