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

Use of the satisfies keyword removes require statements when TS compiler invoked programmatically #51642

Closed
cjdell opened this issue Nov 25, 2022 · 6 comments · Fixed by #51704
Assignees
Labels
Bug A bug in TypeScript

Comments

@cjdell
Copy link

cjdell commented Nov 25, 2022

Bug Report

Use of the satisfies keyword changes the output of the compiler. Vitally important require statements are missing. Does not appear to happen when using tsc but only when invoking the compiler programmatically as is done by ts-loader from the WebPack project.

🔎 Search Terms

satisfies, webpack, ts-loader, 4.9

🕗 Version & Regression Information

This bug only occurs when using the satisfies keyword.

⏯ Playground Link

Unable to reproduce with Playground so here is a repo showing the issue:
https://github.com/cjdell/typescript-satisfies-missing-modules-references-bug

Also another repo showing the original discovery of the bug including a working WebPack setup and ts-loader plugin:
https://github.com/cjdell/ts-loader-satisfies-bug

💻 Code

Note: This is fully demonstrated in the above GitHub repo.

import { z } from "zod";
import { isValid } from "./lib";

interface MyObject {
    name: string;
}

export const MyObjectSchema = z.object({
    name: z.string().refine(isValid),
}) satisfies z.ZodSchema<MyObject>;     // Removing the `satisfies` keyword will change the output of the compiler

console.log(MyObjectSchema.parse({ name: 'foo' }));

Output with satisfies:

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MyObjectSchema = void 0;
exports.MyObjectSchema = zod_1.z.object({
    name: zod_1.z.string().refine(lib_1.isValid),
});
console.log(exports.MyObjectSchema.parse({ name: 'foo' }));

Output without satisfies:

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MyObjectSchema = void 0;
const zod_1 = require("zod");               // THIS IS MISSING FROM ABOVE
const lib_1 = require("./lib");             // THIS IS MISSING FROM ABOVE
exports.MyObjectSchema = zod_1.z.object({
    name: zod_1.z.string().refine(lib_1.isValid),
});
console.log(exports.MyObjectSchema.parse({ name: 'foo' }));

🙁 Actual behavior

As with above, using the satisfies keyword results in missing require statements.

🙂 Expected behavior

I expect the use of the satisfies keyword to have no effect on the compiler's outputted JavaScript.

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Nov 30, 2022

This happens subtly when the asserted type itself is invalid (I'm guessing there's a constraint violation or just missing type in this example); we bail out of checkSatisfiesExpression before checking the asserted expression (which in turn marks the expressions as used). But since transpileModule doesn't resolve references, these imported types are unsubtly always invalid. The fix is straightforward, I'll put up a PR shortly.

@cjdell
Copy link
Author

cjdell commented Nov 30, 2022

@RyanCavanaugh Thanks for sorting this so quickly and I'm glad it was an easy fix.

However I'm not sure why you say the asserted type is invalid. In my example the code does not have any compiler errors and the satisfies keyword is working as expected (at least as far as displayed compiler messages go). It was just the JS output that was bad. Is this something to do with the way compiler is being invoked? As I say this issue does not manifest when using tsc. It was identified by the way WebPack invokes the compiler, specifically the ts-loader.

@fatcerberus
Copy link

As I say this issue does not manifest when using tsc. It was identified by the way WebPack invokes the compiler, specifically the ts-loader.

Yep, hence this part:

But since transpileModule [a TypeScript compiler API for use by third-party tools] doesn't resolve references, these imported types are unsubtly always invalid.

When the compiler is invoked that way, my understanding is that it doesn't disable type checking, but it does disable resolving references and any type errors that result are simply suppressed.

@cjdell
Copy link
Author

cjdell commented Dec 1, 2022

@fatcerberus Thanks for taking the time to explain it to me. I guess the compiler couldn't resolve references even if it wanted to because it is being invoked with an out-of-context string of source code. What an interesting edge case.

@Cellule
Copy link

Cellule commented Dec 1, 2022

Just mentioning this also occurs in with ts-node when using --transpile-only aka without type information
I cherry-picked the fix in the PR and can confirm it fixes the problem for me

@fatcerberus
Copy link

Yeah, I'm pretty sure using TS in transpile mode doesn't actually disable type checking, it just suppresses all the type errors. It's kind of like setting noEmitOnError: false in your tsconfig and then just ignoring the errors.

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

Successfully merging a pull request may close this issue.

5 participants