dsherret/code-block-writer

TypeScript error TS2351: This expression is not constructable.

ajvincent opened this issue · 11 comments

Steps to reproduce:

  1. npm install --save code-block-writer
  2. Add this file to your TypeScript project. (I named mine CodeBlockWriter.mts, to leverage TypeScript's ECMAScript module support.)
import CodeBlockWriter from "code-block-writer";

it("CodeBlockWriter can be assembled", () => {
  const writer = new CodeBlockWriter;
  expect(writer).toBeTruthy();
});
  1. Invoke tsc for this file.
_00_shared_utilities/spec/CodeBlockWriterTest.mts(4,22): error TS2351: This expression is not constructable.
  Type 'typeof import("/home/ajvincent/code-generation/cross-stitch/node_modules/code-block-writer/types/mod")' has no construct signatures.

Please advise.

TypeScript version 4.7.4
NodeJS version 16.14.0

That's strange... I'm not sure why TypeScript is erroring there as the declaration file has a default export for the class. It executes fine in a regular mjs file.

For all I know, this could be a compiler bug in tsc. I've bumped into one of those already. Would it be helpful for me to provide my tsconfig? What else might I offer to help? (Can you reproduce it? If not, how can I help?)

I did search for this compiler error, but it is a very obscure one.

I wonder if this has to do with my tsconfig, which defaults to:

{
  "compilerOptions": {
    "lib": ["es2021"],
    "module": "es2022",
    "target": "es2022",
    "moduleResolution": "node16",
    "sourceMap": true,
    "declaration": true,
  },
}

Also, I found:

I’m able to reproduce the issue as well. I think I’m just going to revert the esm module because it seems typescript is still buggy with esm. Just this file needs to be updated to only do a cjs build https://github.com/dsherret/code-block-writer/blob/main/scripts/build_npm.ts and then the repo retagged. I will try to remember to do it tomorrow (just falling asleep now)

This should work in 11.0.2. Sorry about the issues

I have some more information, which I found buried deep in TypeScript bugs.

microsoft/TypeScript#21621 (comment)

Basically,

import { default as CodeBlockWriter } from "code-block-writer";

should have worked in the old code.

I'd like to reopen this issue. I think it may have regressed.

import CodeBlockWriter from "code-block-writer";
void(new CodeBlockWriter); // TypeError: CodeBlockWriter is not a constructor

I found a work-around, based on earlier comments in this issue. First, I created a module, CodeBlockWriter.mts, which exports the constructor:

import CBW_, {
  type Options as CodeBlockWriterOptions
} from "code-block-writer";

const CodeBlockWriter = (CBW_ as unknown as {
  default: new (opts?: Partial<CodeBlockWriterOptions>) => CBW_
}).default;

export default CodeBlockWriter;
export type {
  CodeBlockWriterOptions
};

Second, in code wishing to create a CodeBlockWriter, I have to import both the original CodeBlockWriter to use as a type, and the de-wrapped constructor from the custom export.

import CBW_ from "code-block-writer";
import CodeBlockWriter from "../CodeBlockWriter.mjs";

// ...

export default 
abstract class Foo {
  readonly #classWriter = new CodeBlockWriter({ indentNumberOfSpaces: 2 });
  protected abstract buildMethodBody(
    methodName: string,
    signature: MethodSignature,
    writer: CBW_
  ) : void;
}
// ...

Third, for files just using the API, importing the original CodeBlockWriter as a type is fine:

import CodeBlockWriter from "code-block-writer";
import Foo from "./Foo.mjs";

class Bar extends Foo {
  protected buildMethodBody(
    methodName: string,
    signature: MethodSignature,
    writer: CodeBlockWriter,
  ): void
  {
    void(methodName);
    signature.args.forEach(arg => writer.writeLine(`void(${arg.key});`));
    writer.writeLine(`throw new Error("not yet implemented");`);
  }
}

Using TypeScript 5.0.2, with tsconfig:

{
  "compilerOptions": {
    "lib": [
      "es2022"
    ],
    "module": "es2022",
    "target": "es2022",
    "moduleResolution": "node",
    "sourceMap": true,
    "declaration": true
  },
  "extends": "@tsconfig/node18/tsconfig.json"
}

I just took a look at the types (and I now know way too much about node module resolution than I care to know compared to back in July when I knew basically nothing) and yeah the types are incorrect.

Actually, I initially thought the code was doing export = and the types were doing export default, but the code is doing the exports.default thing in cjs. I think it should be able to be consumed by doing CodeBlockWriter.default in the mjs, but I'm not sure why TypeScript doesn't understand it that way because the package is distributed as cjs. I'm just going to convert back to a dual esm/cjs package, but I think I have it right this time (maybe lol). I'll try to do it tomorrow though because it's late here.

Ok, I ended up publishing this tonight anyway. Should be fixed in 12.0.0 as this is now republished as an esm and cjs package, but let me know if it doesn't work. This cjs/esm situation is a huge pain.

Confirmed, the updated types work without my wrapper code.