ts-patch icon indicating copy to clipboard operation
ts-patch copied to clipboard

In-program compiling for unit tests. `preparePluginsFromCompilerOptions` discards `transformProgram` ?

Open Griffork opened this issue 10 months ago • 2 comments

Hello!

I've been using ts-patch for quite a while now (it's very good) and I've decided to upgrade my usage by creating unit tests for the transformers so that I can verify that they work when new versions of the compiler release.

I am using vitest (though that's not particularly important) and am trying to run the typescript file as an imported script, but I've run into a snag.

It looks like calling the transpileModule function directly in the patched typescript doesn't allow for transformers to run. This appears to be because preparePluginsFromCompilerOptions adds all transformers with a stripped-down config (either just { transform: string } or { transform: string, after: true } but TspPlugin's constructor wants { transform: string, transformProgram: true }.

As preparePluginsFromCompilerOptions is called immediately before new tsp.PluginCreator in tsp's createProgram I don't see a way to change the property of the plugin to allow for program transformers so that my transformers can operate when calling transpileModule.

This is the entire code that I have so far in attempting to do this:

import { MatcherState } from "@vitest/expect";
import Path from "node:path";
import { expect } from "vitest";
import * as ts from "typescript";
import { getTsPackage } from "ts-patch/ts-package.js";
import * as tspatch from "ts-patch/patch/get-patched-source.js";
import * as tsmodule from "ts-patch/module/ts-module.js";

const tsPackage = getTsPackage(Path.join(process.cwd(), "./node_modules/typescript/"));
const tsModule = tsmodule.getTsModule(tsPackage, "typescript.js" as any);
const tspSource = tspatch.getPatchedSource(tsModule, { log: console.log.bind(console) }); 
const tsp: typeof ts = eval(tspSource.js);

const extensions = {
    toCompileTo(this: MatcherState, received: string, expected: string): {message: () => string, pass: boolean} {
        
        const source = "let x: string  = 'string'";
        
        let result = tsp.transpileModule(received, { 
            compilerOptions: { 
                module: ts.ModuleKind.ES2022,
                target: ts.ScriptTarget.ES2022,
                strict: true,
                skipLibCheck: true,
                skipDefaultLibCheck: true,
                noErrorTruncation: true,
                noEmitOnError: false,
                experimentalDecorators: true,
                plugins: [
                    { "customTransformers": {after: ["../Compiler/Transformers/MixinTransformer.js"], transformProgram: true } }
                ] as any
            }
        });
        
        let resultMessage = { message: () => `expected raw code to compile into provided sample (view diff)`, actual: result.outputText.trim(), expected: expected.trim() };
        
        return {
            ...resultMessage,
            pass: result.outputText.trim() === expected.trim()
        };
    }
};

expect.extend(extensions);

If you have vitest configured it can be called like so:

import { test, expect } from "vitest";

test("Test my custom transformer works", ()=> {
    expect(`some code`).toCompileTo(`some output`);
});

Griffork avatar Apr 27 '24 01:04 Griffork