TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Inconsistent typechecking with require() in JS and TS

Open u130b8 opened this issue 1 year ago • 5 comments

🔎 Search Terms

require import js ts module esm esmodule cjs commonjs

🕗 Version & Regression Information

  • This happens in the nightly version of TS

⏯ Playground Link

Multiple files not supported in playground, see bug workbench

💻 Code

// @types: ["node"]
// @allowJs
// @checkJs

// @filename: module-cjs-js.js
const Value = "module-cjs-js";
module.exports = { Value };

// @filename: module-cjs-ts.ts
const Value = "module-cjs-ts";
module.exports = { Value };

// @filename: module-esm-js.js
const Value = "module-esm-js";
export { Value };

// @filename: module-esm-ts.ts
const Value = "module-esm-ts";
export { Value };

// @filename: main-js.js
const ConstRequireCjsJs = require("./module-cjs-js");
const ConstRequireEsmJs = require("./module-esm-js");
const ConstRequireCjsTs = require("./module-cjs-ts");
const ConstRequireEsmTs = require("./module-esm-ts");
console.log(ConstRequireCjsJs.Value); // (alias) const Value: "module-cjs-js"
//                            ^?
console.log(ConstRequireEsmJs.Value); // (alias) const Value: "module-esm-js"
//                            ^?
console.log(ConstRequireCjsTs.Value); // Error: Property 'Value' does not exist on type 'typeof import("./module-cjs-ts")'
//                            ^?
console.log(ConstRequireEsmTs.Value); // (alias) const Value: "module-esm-ts"
//                            ^?

import * as ImportFromCjsJs from "./module-cjs-js";
import * as ImportFromEsmJs from "./module-esm-js";
import * as ImportFromCjsTs from "./module-cjs-ts";
import * as ImportFromEsmTs from "./module-esm-ts";
console.log(ImportFromCjsJs.Value); // (alias) const Value: "module-cjs-js"
//                          ^?
console.log(ImportFromEsmJs.Value); // (alias) const Value: "module-esm-js"
//                          ^?
console.log(ImportFromCjsTs.Value); // Error: Property 'Value' does not exist on type 'typeof import("./module-cjs-ts")'
//                          ^?
console.log(ImportFromEsmTs.Value); // (alias) const Value: "module-esm-ts"
//                          ^?

// @filename: main-ts.ts
const ConstRequireCjsJs = require("./module-cjs-js");
const ConstRequireEsmJs = require("./module-esm-js");
const ConstRequireCjsTs = require("./module-cjs-ts");
const ConstRequireEsmTs = require("./module-esm-ts");
console.log(ConstRequireCjsJs.Value); // any
//                            ^?
console.log(ConstRequireEsmJs.Value); // any
//                            ^?
console.log(ConstRequireCjsTs.Value); // any
//                            ^?
console.log(ConstRequireEsmTs.Value); // any
//                            ^?

import * as ImportFromCjsJs from "./module-cjs-js";
import * as ImportFromEsmJs from "./module-esm-js";
import * as ImportFromCjsTs from "./module-cjs-ts";
import * as ImportFromEsmTs from "./module-esm-ts";
console.log(ImportFromCjsJs.Value); // (alias) const Value: "module-cjs-js"
//                          ^?
console.log(ImportFromEsmJs.Value); // (alias) const Value: "module-esm-js"
//                          ^?
console.log(ImportFromCjsTs.Value); // Error: Property 'Value' does not exist on type 'typeof import("./module-cjs-ts")'
//                          ^?
console.log(ImportFromEsmTs.Value); // (alias) const Value: "module-esm-ts"
//                          ^?

import ImportRequireCjsJs = require("./module-cjs-js");
import ImportRequireEsmJs = require("./module-esm-js");
import ImportRequireCjsTs = require("./module-cjs-ts");
import ImportRequireEsmTs = require("./module-esm-ts");
console.log(ImportRequireCjsJs.Value); // (alias) const Value: "module-cjs-js"
//                             ^?
console.log(ImportRequireEsmJs.Value); // (alias) const Value: "module-esm-js"
//                             ^?
console.log(ImportRequireCjsTs.Value); // Error: Property 'Value' does not exist on type 'typeof import("./module-cjs-ts")'
//                             ^?
console.log(ImportRequireEsmTs.Value); // (alias) const Value: "module-esm-ts"
//                             ^?

Workbench Repro

🙁 Actual behavior

Type resolution is inconsistent when using require() from .js and .ts files:

  • CommonJS modules with .ts extension have no properties regardless of import type (no error if ESModule.ts or CommonJS.js)
  • Using require() in a .ts file is always unchecked (checked in a .js file or in .ts file with import X = syntax)
MainExt Type Ext RequireOrImport Issue
JS CJS JS const X = require("Y")
JS ESM JS const X = require("Y")
JS CJS TS const X = require("Y") Error
JS ESM TS const X = require("Y")
JS CJS JS import * as X from "Y"
JS ESM JS import * as X from "Y"
JS CJS TS import * as X from "Y" Error
JS ESM TS import * as X from "Y"
JS CJS JS const X = require("Y")
JS ESM JS const X = require("Y")
JS CJS TS const X = require("Y") Error
JS ESM TS const X = require("Y")
TS CJS JS const X = require("Y") Any
TS ESM JS const X = require("Y") Any
TS CJS TS const X = require("Y") Any
TS ESM TS const X = require("Y") Any
TS CJS JS import * as X from "Y"
TS ESM JS import * as X from "Y"
TS CJS TS import * as X from "Y" Error
TS ESM TS import * as X from "Y"
TS CJS JS import X = require("Y")
TS ESM JS import X = require("Y")
TS CJS TS import X = require("Y") Error
TS ESM TS import X = require("Y")

🙂 Expected behavior

I expected require() to be typechecked in .ts files because it's typechecked in .js files. I expected imports of CommonJS .ts files to work because CommonJS .js files work and are typechecked.

Additional information about the issue

This problem happens when porting an existing Node.JS codebase that uses CommonJS require() modules to TypeScript. It's not possible to port the code without also forcing it into ES Modules because:

  • CommonJS .ts files don't work
  • require() from .ts files is not typechecked

u130b8 avatar Sep 23 '24 13:09 u130b8

Have you tried changing your tsconfig.json settings and using esModuleInterop?"

YehuditLiba avatar Sep 25 '24 13:09 YehuditLiba

I expected require() to be typechecked in .ts files because it's typechecked in .js files.

Only import i = require( is supported in TS

I expected imports of CommonJS .ts files to work because CommonJS .js files work and are typechecked.

What specifically is the repro for this?

RyanCavanaugh avatar Sep 25 '24 17:09 RyanCavanaugh

Bug workbench doesn't seem to work with types: ["node"], but you can repro it like this:

// @types: ["node"]

// @filename: test-cjs.ts
module.exports = { Value: 1 };

// @filename: main.ts
import test = require("./test-cjs");
test.Value; //  Error: Property 'Value' does not exist on type 'typeof import("./test-cjs")'
//   ^? any

If the module has a JS extension, then the import works. But if the module has a TS extension, it no longer works.

Only import i = require( is supported in TS

Is it possible to add support for const i = require( as well? It seems strange because the functionality is there, the const require works and gets typechecked, but only with a JS extension, why not have it work if the file extension is TS as well? It would make the developer experience much better, and const require is much more flexible than import require:

// Can't do this with import X = require()
let X;
if (cond) {
    X = require("x-impl-1");
}
else {
    X = require("x-impl-2");
}

u130b8 avatar Sep 25 '24 21:09 u130b8

:wave: Hi, I'm the Repro bot. I can help narrow down and track compiler bugs across releases! This comment reflects the current state of the repro in the issue body running against the nightly TypeScript.


Issue body code block by @u130b8

:bangbang: Exception: Error - error TS5107: Option 'moduleResolution=node10' is deprecated and will stop functioning in TypeScript 7.0. Specify compilerOption '"ignoreDeprecations": "6.0"' to silence this error. Visit https://aka.ms/ts6 for migration information.

Error: error TS5107: Option 'moduleResolution=node10' is deprecated and will stop functioning in TypeScript 7.0. Specify compilerOption '"ignoreDeprecations": "6.0"' to silence this error.
  Visit https://aka.ms/ts6 for migration information.

    at Object.createVirtualTypeScriptEnvironment (/home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:8128:11)
    at twoslasher (/home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:7618:17)
    at /home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:439:44
    at runTwoslashRequests (/home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:406:56)
    at run (/home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:20096:75)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
Historical Information
Version Reproduction Outputs Time
5.2.2, 5.3.2, 5.4.2, 5.5.2, 5.6.2

:x: Failed: -

  • Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.
  • File '/home/runner/work/TypeScript/TypeScript/module-cjs-ts.ts' is not a module.
  • File '/home/runner/work/TypeScript/TypeScript/module-cjs-ts.ts' is not a module.
  • Cannot find name 'require'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.
  • Cannot find name 'require'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.
  • Cannot find name 'require'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.
  • Cannot find name 'require'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.
  • File '/home/runner/work/TypeScript/TypeScript/module-cjs-ts.ts' is not a module.
  • Import assignment cannot be used when targeting ECMAScript modules. Consider using 'import * as ns from "mod"', 'import {a} from "mod"', 'import d from "mod"', or another module format instead.
  • Import assignment cannot be used when targeting ECMAScript modules. Consider using 'import * as ns from "mod"', 'import {a} from "mod"', 'import d from "mod"', or another module format instead.
  • Import assignment cannot be used when targeting ECMAScript modules. Consider using 'import * as ns from "mod"', 'import {a} from "mod"', 'import d from "mod"', or another module format instead.
  • File '/home/runner/work/TypeScript/TypeScript/module-cjs-ts.ts' is not a module.
  • Import assignment cannot be used when targeting ECMAScript modules. Consider using 'import * as ns from "mod"', 'import {a} from "mod"', 'import d from "mod"', or another module format instead.
⚠️ Way slower

typescript-bot avatar Sep 26 '24 08:09 typescript-bot

:wave: Hi, I'm the Repro bot. I can help narrow down and track compiler bugs across releases! This comment reflects the current state of this repro running against the nightly TypeScript.


Comment by @u130b8

:bangbang: Exception: Error - error TS5107: Option 'moduleResolution=node10' is deprecated and will stop functioning in TypeScript 7.0. Specify compilerOption '"ignoreDeprecations": "6.0"' to silence this error. Visit https://aka.ms/ts6 for migration information.

Error: error TS5107: Option 'moduleResolution=node10' is deprecated and will stop functioning in TypeScript 7.0. Specify compilerOption '"ignoreDeprecations": "6.0"' to silence this error.
  Visit https://aka.ms/ts6 for migration information.

    at Object.createVirtualTypeScriptEnvironment (/home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:8128:11)
    at twoslasher (/home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:7618:17)
    at /home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:439:44
    at runTwoslashRequests (/home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:406:56)
    at run (/home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:20096:75)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
Historical Information
Version Reproduction Outputs Time
5.2.2, 5.3.2, 5.4.2, 5.5.2, 5.6.2

:x: Failed: -

  • Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.
  • Import assignment cannot be used when targeting ECMAScript modules. Consider using 'import * as ns from "mod"', 'import {a} from "mod"', 'import d from "mod"', or another module format instead.
  • File '/home/runner/work/TypeScript/TypeScript/test-cjs.ts' is not a module.
⚠️ Way slower

typescript-bot avatar Sep 26 '24 08:09 typescript-bot