-
-
Notifications
You must be signed in to change notification settings - Fork 533
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
Override isExternalLibraryImport
as needed; re-add realpath
#970
Conversation
Implement resolveModuleNames that forces modules to be considered internal (not from an external library) when we need them to be emitted
…t-node_modules-files
Of note, TS has itself a bug with workspaces being marked as external sources (cf microsoft/TypeScript#37270). Maybe the |
…t-node_modules-files
…ll require()d files to force them to be included in compilation without modifying the rootFiles array
Codecov Report
@@ Coverage Diff @@
## master #970 +/- ##
==========================================
- Coverage 79.63% 79.32% -0.32%
==========================================
Files 7 7
Lines 658 711 +53
Branches 147 158 +11
==========================================
+ Hits 524 564 +40
- Misses 84 90 +6
- Partials 50 57 +7
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
83518b8
to
32d7c37
Compare
https://github.com/TypeStrong/ts-node/blob/master/src/index.ts#L688-L710 I noticed some weirdness in the The bug is: The problem is that a sourcefile might exist, but it might be considered "external." When this happens, typescript refuses to emit .js for it. We can force TS to emit If we mark all non-ignored files as "internal", this problem goes away. Alternatively, we keep track of which files are classified "external" and we add them to |
Importantly, we must ensure that tests properly trigger all possible patterns for a file being classified external, being omitted from rootFileNames, being unknown to the compiler, etc.
|
@@ -0,0 +1,63 @@ | |||
## How we override isExternalLibraryImport |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file is notes to myself and any other ts-node developers. Given the audience, it's not as polished as the rest of ts-node, and I can remove it if needed.
…t-node_modules-files
@blakeembrey this is finally(!!) done, tested, and ready for a review. I added my own review comments to hopefully make the changes clearer. |
@blakeembrey Despite the red X, tests are passing. The only failure is coverage, and I think that's only because compiler docs ask us to implement a function that the compiler never actually calls. If you think you'll be busy for the next week, and if this sounds ok to you, I was thinking of asking for a code review on the TypeScript Discord. Compiler experts hang out there, so I think I'd be able to find a reviewer with the right skillset. |
@@ -515,6 +527,102 @@ export function create (rawOptions: CreateOptions = {}): Register { | |||
let getOutput: (code: string, fileName: string) => SourceOutput | |||
let getTypeInfo: (_code: string, _fileName: string, _position: number) => TypeInfo | |||
|
|||
const getCanonicalFileName = (ts as unknown as TSInternal).createGetCanonicalFileName(ts.sys.useCaseSensitiveFileNames) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems dangerous, shouldn't case sensitivity be determined based on the OS, or potentially the forceConsistentCasingInFileNames option?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair question. ts.sys.useCaseSensitiveFileNames
is OS-dependent: true on Posix, false on Windows. As far as I can tell, createGetCanonicalFileName
is how the compiler internally converts filenames to cache keys.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I know, forceConsistentCasingInFileNames
generates additional semantic diagnostics but doesn't affect resolver behavior. Even when forceConsistentCasingInFileNames
is turned off, FOO.ts
and foo.ts
still resolve to the same file on Windows, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, that makes sense. I was reading useCaseSensitiveFileNames
as if there was another useCaseInsensitiveFileNames
option, which it makes sense that there isn't. This should be fine then.
Absolutely feel free to do this anyway! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be honest, I'll just trust you on this release 😄
This work is really great, it'll probably take me a while to verify the investigation you've already done!
import sourceMapSupport = require('source-map-support') | ||
import * as ynModule from 'yn' | ||
import { BaseError } from 'make-error' | ||
import * as util from 'util' | ||
import { fileURLToPath } from 'url' | ||
import * as _ts from 'typescript' | ||
import type * as _ts from 'typescript' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🙏
src/index.ts
Outdated
|
||
// Get bucket for a source filename. Bucket is the containing `./node_modules/*/` directory | ||
// For '/project/node_modules/foo/node_modules/bar/lib/index.js' bucket is '/project/node_modules/foo/node_modules/bar/' | ||
const moduleBucketRe = /.*\/node_modules\/[^\/]+\// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about namespaced @
node modules?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ooh, good catch. I'll add logic for this. I should also add a test with 2x peer modules, @org/foo and @org/bar, and make sure one bucket doesn't affect the other.
bbfe788
to
1d80028
Compare
…t-node_modules-files
The various
realpath
issues we've seen don't strictly relate to symlinks.They're caused by TypeScript considering a file to be an "external library import" when it's nested within a
node_modules
directory, and refusing to emit.js
for it. Sometimes a symlink's path does not includenode_modules
and its resolved path does, or vice versa, so enabling or disablingrealpath
causes or fixes this bug.The "external library import" determination is handled by the module resolver. If we implement our own in the
LanguageServiceHost
, we can tell the compiler to consider allrootFileNames
to be "internal" no matter what.This lets us re-enable
realpath
.Related / should fix:
fix #876
fix #797
fix #956
fix #819
fix #990
fix #1093
Notes for code review
It's been a while since I started this PR, and I've since learned more about the compiler.
rootFiles
are always emitted, but we want to avoid modifyingrootFiles
as much as possible, because it throws away a bunch of compiler state. How much, precisely, I'm not sure, but modifyrootFiles
is way slower than leaving it as-isrootFiles
.This PR hooks into TypeScript's resolver and does 2x things:
(a) forces some modules to be classified internal using our own logic, overriding TypeScript's logic
(b) keeps track of modules that have already been classified as internal
(a) enables compiling files nested within ./node_modules
(b) allows us to avoid modifying rootFiles unnecessary
TS files in node_modules are always forced to be internal, because I can't see any downsides to this.
JS files in node_modules are only forced internal on-demand (when required), because the "internal/external" distinction affects maxNodeModuleJsDepth, which affects typechecking.
Old, irrelevant notes
I hate deleting old research in case I need it later, but stuff below this line is not relevant anymore.
Gotchas / notes
(this is not true anymore) Implementation overrides
isExternalLibraryImport
according to ourignored()
logic, which is the "nuclear option" described here:microsoft/TypeScript#28432 (comment)
There's another option:
node_modules
nested within therootDir
are excluded, but ifrootDir
hasnode_modules
in its own path, it is still compiled.