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

[api-extractor] Use ts.getCheckFlags to fix TS 5.0 #3897

Merged
merged 1 commit into from
Jan 18, 2023

Conversation

jakebailey
Copy link
Member

Summary

As of microsoft/TypeScript#51880, the internal property checkFlags is no longer defined on Symbol directly, but on symbol.links. This causes api-extractor to fail when used with TS 5.0.

Details

Rather than trying to figure out if we should use links, just use ts.getCheckFlags, which has been around since at least TypeScript 2.3. (It'd be better if internals weren't used at all, of course.)

How it was tested

Patched api-extractor in a project's node_modules with this change, and saw that it worked.

Impacted documentation

No updated docs.

@jakebailey
Copy link
Member Author

jakebailey commented Jan 16, 2023

This doesn't fix TypeScript 5.0 when used with rushstack entirely; heft patches TypeScript at runtime, which no longer works in TS 5.0 as we have switched to modules (and so our exports cannot be modified from the outside). But it does fix api-extractor.

@iclanton
Copy link
Member

Thanks for fixing this @jakebailey.

WRT Heft - can you help us support TS 5.0 there?

@iclanton iclanton merged commit 02d9ddd into microsoft:main Jan 18, 2023
@jakebailey jakebailey deleted the check-flags branch January 18, 2023 22:47
@jakebailey
Copy link
Member Author

That I'm not sure of; I saw it being hotpatched at runtime and couldn't believe it ever worked (pushing onto the ts object at runtime wouldn't guarantee those functions are actually called internally in TS since they are just the exports and there can be internal calls).

I'd have to look harder at it (maybe next week once we have 5.0 in beta).

@jakebailey
Copy link
Member Author

The ts.readJson patch doesn't seem to be needed; I'm looking at the other but that's more complicated. It only worked at all because emitFiles happened to be declared in a different file than it was called!

@dmichon-msft
Copy link
Contributor

@jakebailey
The readJson call should be irrelevant now since I'm pretty sure TypeScript added JSON read caching internally for the same performance optimization.
The emitFiles call is the interesting one and is the magic trick that lets us emit AMD, CommonJS, and ESM from a single invocation of the TypeScript compiler without doing redundant parsing and type-checking. I'd love a way to do that that doesn't require monkey-patching the TypeScript internals; maybe an indirection layer on compilerHost invoked during emit? An officially supported way of doing multi-target emit in a single compilation would naturally be preferred, but most discussions I've seen on the topic always seem to come back to the TypeScript team not believing that it would actually produce valid output (even though it does and we've been using it for years).

Funnily enough, even with splitting type-checking from transpilation, it is still faster to use the same multi-emit hack when running the transpileModule API than to call it three times, since it still does type-checking internally to bind the variables.

@jakebailey
Copy link
Member Author

jakebailey commented Jan 24, 2023

discussions I've seen on the topic always seem to come back to the TypeScript team not believing that it would actually produce valid output

My understanding is that there is a pretty non-trivial amount of the checker which is conditionalized on things like the module format. Given d.ts emit requires type information (it's not just erasure of function bodies and such), the output should in theory depend on what was resolved in that mode. The immediate examples I can think of are default exports and such. It's possible that supporting only a subset of d.ts syntax means you are managing to avoid all of the problems.

since it still does type-checking internally to bind the variables.

Binding is a phase before checking, but as above, the checker is required for d.ts emit because it's not just stripping syntax; you have to have types, then it has to fabricate syntax nodes out of thin air to print out in the d.ts files.

In any case, I spend a bit of time trying to figure out if there was any combination that would allow you to perform this hack by instead calling emit in the project in the "done emitting" helper (effectively, moving the loop until after the first emit), but doing that requires some state that's sitting in a closure and is unavailable. @sheetalkamat may have an easier time suggesting a solution here.

@jakebailey
Copy link
Member Author

I've managed to get things working without patching; I'm still finalizing the change and it's still unsafe (from our team's point of view), but it should at least unblock heft for 5.0 if I can get it out.

image

Roughly, the change is to not patch ts.emitFiles, but modify the compilerOptions on the program temporarily and recall program.emit.

@jakebailey
Copy link
Member Author

jakebailey commented Feb 25, 2023

Of course, if you think you can use the above to more quickly remove this patch, that's great too. I can't really keep everything in my head.

(Also, there are other tools which we think do a better job at this sort of "write once run everywhere" than tsc, but, that's another change.)

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.

3 participants