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

Support subpath import with arbitrary extensions #723

Merged
merged 1 commit into from
Jul 12, 2024

Conversation

geigerzaehler
Copy link
Contributor

@geigerzaehler geigerzaehler commented Jul 11, 2024

Knip stops reporting issues for subpath imports with arbitrary extensions (e.g. import "#src/foo.ext"). This already worked for path mappings defined in tsconfig.json but now it also works for mappings defined in package.json.

When trying different approaches I found it easiest to go back to the initial idea of fooling TypeScript into believing files with non-code extensions exists. But the implementation is much simpler now. And I tried to document the approach in the code.

Copy link

netlify bot commented Jul 11, 2024

‼️ Deploy request for knip rejected.

Name Link
🔨 Latest commit 2fb135c

Copy link

pkg-pr-new bot commented Jul 11, 2024

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

commit: a269b0c

knip

npm i https://pkg.pr.new/knip@723


templates

Copy link
Collaborator

@webpro webpro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! Love the idea. Had virtual file paths before but this is a more generic one. Good stuff. Some preliminary comments before I'll play a bit with it later today or tomorrow. Tests and integration test are passing so looking great already!

...ts.sys,
// We trick TypeScript into resolving paths with arbitrary extensions. When
// a module "./foo.ext" is imported TypeScript only tries to resolve it to
// "./foo.d.ext.ts". TypeScript never checks whether "./foo.ext" itself exists.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you mean module.ext.ts and module.ext.d.ts (and same for .mts + .cts)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was confused by this as well but it is correct. If the import has an unknown extension like .../foo.ext typescript looks for .../foo.d.ext.ts (code in TypeScript).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL! Thanks for figuring that out + the link, much appreciated.

@@ -147,3 +155,19 @@ export function createCustomModuleResolver(

return timerify(resolveModuleNames);
}

const declarationPathRe = /^(.*)\.d(\.[^.]+)\.ts$/;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This matches a lot, but I guess I'm having the same confusion as in the comment: I think you're saying "TypeScript tries to resolve foo.ext to foo.d.ext.ts", which I doubt is true? Doesn't it resolve index to index.d.ts or anything.really to anything.really.d.ts?

@webpro
Copy link
Collaborator

webpro commented Jul 11, 2024

We stop raising issues for subpath imports with arbitrary extensions

Just to be sure: do you mean Knip doesn't report false positives, or we've stopped opening issues for bugs? :)

@webpro webpro force-pushed the main branch 3 times, most recently from d91d007 to d4121d9 Compare July 12, 2024 06:07
@geigerzaehler
Copy link
Contributor Author

geigerzaehler commented Jul 12, 2024

Just to be sure: do you mean Knip doesn't report false positives, or we've stopped opening issues for bugs? :)

Good point, 😅. Knip stops reporting “unresolved” for imports like #src/foo.ext where the file after, subpath import mapping, exists. I changed the description

Copy link
Collaborator

@webpro webpro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic! Thanks for coming back to this. Tested a bit, works great. Slightly faster in my testing too. What more could I ask for?

@@ -35,6 +34,32 @@ export function createCustomModuleResolver(
const customCompilerExtensionsSet = new Set(customCompilerExtensions);
const extensions = [...DEFAULT_EXTENSIONS, ...customCompilerExtensions];

const virtualDeclarationFiles = new Map<string, { path: string; ext: string }>();

const tsSys: ts.System = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless there's a good reason not to, I think we could move virtualDeclarationFiles and tsSys out of the function scope, easier for moving/refactors later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking behind this was that virtualDeclarationFiles is specific to the instance of resolveModuleName that is created by createCustomModuleResolver. I think it is theoretically possible for two instances with different compiler settings to interfere with each other. Also, keeping virtualDeclarationFiles local and not global allows it to be garbage collected. Whether all this is important in practice, I don’t know. But conceptually I feel like stateful things should be localized.

If you agree, I’ll keep it there, otherwise I can move it out.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right!

const declarationPathRe = /^(.*)\.d(\.[^.]+)\.ts$/;

/**
* For paths that look like `.../foo.d.yyy.ts` returns path `.../foo.yyy` and
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to use "foo" as little as possible myself, any chance you could rid of that too? I guess "module" could do.

packages/knip/fixtures/subpath-patterns/package.json Outdated Show resolved Hide resolved
@@ -75,7 +75,6 @@
"smol-toml": "^1.1.4",
"strip-json-comments": "5.0.1",
"summary": "2.1.0",
"tsconfig-paths": "^4.2.0",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

Stop reporting unresolved filesfor subpath imports with arbitrary
extensions (e.g. `import "#src/foo.ext"`). This already worked for path
mappings defined in `tsconfig.json` but now it also works for mappings
defined in `package.json`.

The implementation drops `tsconfig-paths` and reverts back to using
virtual declaration files, albeit much simpler.
@webpro webpro merged commit c35bad7 into webpro-nl:main Jul 12, 2024
13 checks passed
@webpro
Copy link
Collaborator

webpro commented Jul 12, 2024

Thanks @geigerzaehler, excellent stuff!

@geigerzaehler geigerzaehler deleted the all-path-mapping branch July 12, 2024 21:22
@webpro
Copy link
Collaborator

webpro commented Jul 13, 2024

🚀 This pull request is included in v5.26.0. See Release 5.26.0 for release notes.

Using Knip in a commercial project? Please consider becoming a sponsor.

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

Successfully merging this pull request may close these issues.

2 participants