microsoft/typescript-go

`ts-check` used with `@typedef` causes an error on `module.exports = ...`

Opened this issue · 3 comments

Steps to reproduce

// @ts-check

/** @typedef {number} Foo */

module.exports = {};

Behavior with typescript@5.8

No reported errors:

https://www.typescriptlang.org/play/?ts=5.9.2#code/PTAEAEBcGcFoGMAWBTeBrAUB4AqHFIBPAB2QBNkAzUAbwDsBXAWwCNkAnAX1ADEB7PqBzAsTPmQYAbZADpkAD2J92MUAF5anANwYgA

Behavior with tsgo

index.cjs:5:1 - error TS2309: An export assignment cannot be used in a module with other exported elements.

5 module.exports = {};
  ~~~~~~~~~~~~~~~~~~~


Found 1 error in index.cjs:5

This doesn't appear unless ts-check is set, even if allowJs is set to true.

#1688 would have fixed this but was closed

Here is a test case that shows a variant of this problem:

// @module: nodenext
// @target: esnext
// @outDir: ./out
// @allowJs: true
// @checkJs: true
// @noUnusedLocals: true
// @noUnusedParameters: true
// @declaration: true

// @filename: test.js
const t = require("./types");

/** @type {t.MyType} */
const obj = { a: 42, b: "hello" };
console.log(obj);

// @filename: types.js

/** @typedef {{ a: number, b: string }} MyType */

module.exports = {};

Nets:

test.js(1,7): error TS6133: 't' is declared but its value is never read.
test.js(3,12): error TS2503: Cannot find namespace 't'.
types.js(3,1): error TS2309: An export assignment cannot be used in a module with other exported elements.


==== test.js (2 errors) ====
    const t = require("./types");
          ~
!!! error TS6133: 't' is declared but its value is never read.
    
    /** @type {t.MyType} */
               ~
!!! error TS2503: Cannot find namespace 't'.
    const obj = { a: 42, b: "hello" };
    console.log(obj);
    
==== types.js (1 errors) ====
    /** @typedef {{ a: number, b: string }} MyType */
    
    module.exports = {};
    ~~~~~~~~~~~~~~~~~~~
!!! error TS2309: An export assignment cannot be used in a module with other exported elements. 

Along with:

//// [types.d.ts]
/** @typedef {{ a: number, b: string }} MyType */
export type MyType = {
    a: number;
    b: string;
};
declare const _default: {};
export = _default;

If you remove module.exports, we still add the export modifier (another different bug), but the emit is correctish

//// [types.d.ts]
export type MyType = {
    a: number;
    b: string;
};
/** @typedef {{ a: number, b: string }} MyType */

A workaround for type-only files like this is to not use module.exports = {}, but instead write something like module.exports.nothing = undefined which instead generates separate declarations rather than an export = node.