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

Add --allowArbitraryExtensions, a flag for allowing arbitrary extensions on import paths #51435

Merged

Conversation

weswigham
Copy link
Member

And, correspondingly, the .d.ext.ts files needed to describe the shape of whatever it is your importing.

Implements #50133

Option name, as always, open to 🚴🏚️.

@weswigham weswigham added the Breaking Change Would introduce errors in existing code label Nov 8, 2022
@typescript-bot typescript-bot added Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Nov 8, 2022
@Thundercraft5
Copy link

Thundercraft5 commented Nov 8, 2022

A name suggestion: How about --resolveArbitraryExtensions or --allowArbitraryFileExtensions?

Copy link
Member

@jakebailey jakebailey left a comment

Choose a reason for hiding this comment

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

This all seems good and straightforward to me; mostly test changes and the actual changes are really nice and minimal.

My only question is about performance; we're doing quick checks to avoid extra FS calls, right?

@@ -9775,7 +9776,7 @@ namespace IncrementalParser {

/** @internal */
export function isDeclarationFileName(fileName: string): boolean {
return fileExtensionIsOneOf(fileName, supportedDeclarationExtensions);
return fileExtensionIsOneOf(fileName, supportedDeclarationExtensions) || (fileExtensionIs(fileName, Extension.Ts) && stringContains(fileName, ".d."));
Copy link
Member

Choose a reason for hiding this comment

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

I guess we're going so far as to support foo.d.tar.gz.ts? i.e. multiple dots?

Copy link
Member Author

@weswigham weswigham Nov 8, 2022

Choose a reason for hiding this comment

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

Right now we interpret such a name as a declaration file, but will never match it, since we only consider everything after the last dot for the extracted extension. Somewhat on the fence about it right now tbh - I dunno if we need to handle compound extensions or not. Maybe I should adjust the lookup logic so it does?

Copy link
Member

Choose a reason for hiding this comment

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

I honestly have no clue. It seems like someone's going to try to do it... but maybe not?

src/compiler/moduleNameResolver.ts Outdated Show resolved Hide resolved
@weswigham
Copy link
Member Author

Yeah, we only do the new fs check when the extension in the import isn't a TS or JS one.

@andrewbranch
Copy link
Member

I’ve only just glanced at the implementation, but re: namingβ€”in #51171 I introduce allowImportingTsExtensions, and I think I’ve managed to convince myself that its functionality really shouldn’t be combined with this flag you’ve added, because

  • Importing .ts extensions requires noEmit or emitDeclarationOnly, whereas this flag does not.
  • If you specifically indicate that you want to import .ts extensions, we can use that knowledge in path completions and auto-imports.

To that end, I would like to brainstorm flag names that don’t seem to imply .ts. I think β€œarbitrary” is a step in the right direction; maybe β€œunrecognized”? I haven’t thought of anything I really like yet, just wanted to throw that out and get thoughts.

@andrewbranch
Copy link
Member

Also: don’t you need corresponding changes in moduleSpecifiers.ts and path completions? E.g., what happens in declaration emit when you have:

// foo.d.html.ts
export declare class CustomHtmlRepresentationThing {}
export declare const someHtml: CustomHtmlRepresentationThing;


// index.ts
import { someHtml } from "./foo.html";
export function getHtml() {
  return someHtml;
}

@weswigham
Copy link
Member Author

@andrewbranch Done - I've also simplified things in the module name resolver quite a bit, in part thanks to your refactorings. As an added bonus, .ts file imports now resolve during module resolution, but we still issue retain an error on the import in the checker, for our usual "ts files won't resolve at runtime" reasons. Should make allowImportingTsExtensions pretty easy to implement~

@@ -444,6 +450,8 @@ FsWatches::
{}

FsWatchesRecursive::
/user/username/projects/myproject/lib1:
Copy link
Member Author

Choose a reason for hiding this comment

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

Just as an FYI, all these extra watchers are here because these tests have an import that looks like "./tools/tools.interface" which now looks for a ./tools/tools.d.interface.ts (for the potential literal tools.interface file) before adding a .js or .ts extension onto the import to find what it currently finds. The additional failed lookup location triggers adding a directory watcher, since now a new file may appear which changes the resolution result~

Copy link
Member

Choose a reason for hiding this comment

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

can you instead change the test to import "./tools/toolsInterface" since the extension part is irrelevant for these tests?


import z from "./z.d.ts";
>z : any
>z : number
Copy link
Member Author

Choose a reason for hiding this comment

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

It is probably worth noting that while these types refering to a ts source file in the import do now resolve, the error on the import is still present - it's just added in the checker even on successful resolution now.

@andrewbranch
Copy link
Member

As an added bonus, .ts file imports now resolve during module resolution, but we still issue retain an error on the import in the checker, for our usual "ts files won't resolve at runtime" reasons. Should make allowImportingTsExtensions pretty easy to implement~

I’ve already done this too πŸ˜… let the merge conflicts continue!

@@ -4753,6 +4753,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
}

if (moduleNotFoundError) {
if (tryGetExtensionFromPath(sourceFile.fileName) === tryGetExtensionFromPath(moduleReference)) {
Copy link
Member

Choose a reason for hiding this comment

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

Doesn’t this falsely trigger on import {} from "./foo.ts" when the source file is foo.ts.ts? In my implementation, I couldn’t find a better way than to add a property onto ResolvedModule that indicated whether the .ts extension was actually used to match a .ts file extension.

Copy link
Member Author

Choose a reason for hiding this comment

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

The error says that the "import path can't end in .ts" not that it can't resolve to TS, so I'd argue it's not a false positive. Honestly, a "./foo.ts" that resolves to "./foo.ts.js" is expempted from the error right now, but I'm questioning more if that's OK - blanket saying "relative import specifiers can't end in .ts unless you pass some flag" just seems easier to understand, and covers weird cjs corners like this, if overzealously.

@@ -444,6 +450,8 @@ FsWatches::
{}

FsWatchesRecursive::
/user/username/projects/myproject/lib1:
Copy link
Member

Choose a reason for hiding this comment

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

can you instead change the test to import "./tools/toolsInterface" since the extension part is irrelevant for these tests?

@@ -43,6 +43,9 @@
"File '/node_modules/exports-and-types-versions/package.json' exists according to earlier cached lookups.",
"Matched 'exports' condition 'types'.",
"Using 'exports' subpath './yep' with target './types/foo.d.ts'.",
"File name '/node_modules/exports-and-types-versions/types/foo.d.ts' has a '.d.ts' extension - stripping it.",
"File '/node_modules/exports-and-types-versions/types/foo.ts' does not exist.",
Copy link
Member

Choose a reason for hiding this comment

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

These checks seem non intuitive. When types or exports entry already has "d.ts" extension and we are saying we prefer ".ts" over ".d.ts" even though field said to use ".d.ts" .. Just more configuration error prone i think.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is generally true all the time - .ts files that correspond to a .d.ts file are higher priority than the .d.ts file, even if your path explicitly uses the .d.ts extension. This is basically to mirror our wildcard include behavior which likewise prefers the .ts file to the .d.ts file.

@sandersn sandersn added this to Not started in PR Backlog Dec 1, 2022
@sandersn sandersn moved this from Not started to Waiting on author in PR Backlog Dec 1, 2022
@weswigham weswigham force-pushed the arbitrary-declaration-file-extensions branch from 17e1a6b to b1fe76f Compare January 4, 2023 19:11
@weswigham
Copy link
Member Author

@andrewbranch conflicts resolved, there's a minor change in the bundler resolution mode baseline; namely changing module resolution to consistently prefer loading the associated .ts source file, even when the import explicitly says .d.ts. There's also some weirdness with .tsx loading that we really haven't resolved (historically a .js file of the same name would get loaded at a higher priority than the .jsx file, even if you imported the .jsx directly - bunder mode kinda changed that with it's exact match branch, and I've formally encoded the change in behavior for tsx/ts while removing the bundler mode special case in module loading), though maybe it's not terribly important to resolve, since having a a.ts and a.tsx side-by-side is, as far as we're concerned, not really supported anyway.

@weswigham weswigham changed the title Add --allowNonJsExtensions, a flag for allowing arbitrary extensions on import paths Add --allowArbitraryExtensions, a flag for allowing arbitrary extensions on import paths Jan 9, 2023
Copy link
Member

@jakebailey jakebailey left a comment

Choose a reason for hiding this comment

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

Not an expert but the code seems fine to me; I think the new name is fine as well. (I'm guessing Andrew will look click a button too but here's my click.)

PR Backlog automation moved this from Waiting on author to Needs merge Jan 9, 2023
};
const lib1ToolsInterface: File = {
path: `/user/username/projects/myproject/lib1/tools/tools.interface.ts`,
path: `/user/username/projects/myproject/lib1/tools/toolsinterface.ts`,
Copy link
Member

Choose a reason for hiding this comment

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

Are filenames like this problematic now?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, they just add a (higher priority) lookup location which has knock-on effects regarding file watchers in the baseline, so to avoid baseline noise Sheetal asked I simply change the filename.

Copy link
Member

@andrewbranch andrewbranch left a comment

Choose a reason for hiding this comment

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

Looks good once moduleResolutionSupportsResolvingTsExtensions is deleted.

src/compiler/moduleNameResolver.ts Show resolved Hide resolved
src/compiler/program.ts Outdated Show resolved Hide resolved
PR Backlog automation moved this from Needs merge to Waiting on author Jan 9, 2023
@weswigham
Copy link
Member Author

@andrewbranch done~

{
name: "allowArbitraryExtensions",
type: "boolean",
affectsModuleResolution: true,
Copy link
Member

Choose a reason for hiding this comment

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

I was gathering module-resolution-affecting compiler options for docs, and noticed this. AFAICT it doesn’t affect module resolution, just whether an error is issued. This should probably be swapped for affectsSemanticDiagnostics.

Copy link
Member

Choose a reason for hiding this comment

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

Is this something that should be in 5.0 beta? I just got to this comment in my email and noticed a PR wasn't sent for this (but I don't know how these two settings matter myself)

@weswigham
Copy link
Member Author

weswigham commented Jan 26, 2023 via email

@jakebailey
Copy link
Member

I sent #52437 for it just to have it off of my mind πŸ˜…

@sheetalkamat
Copy link
Member

Apart from incremental build, it also means that source files are not reused in LS if affectsModuleResolution is set to true. And that has perf impact so something good to have i guess.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Author: Team Breaking Change Would introduce errors in existing code For Uncommitted Bug PR for untriaged, rejected, closed or missing bug
Projects
Archived in project
PR Backlog
  
Done
Development

Successfully merging this pull request may close these issues.

None yet

6 participants