Skip to content

Used import elided from emit where source is JavaScript  #48588

Closed
@wbt

Description

@wbt

Bug Report

🔎 Search Terms

import elided javascript used detect unused 6133

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about elided imports
  • I was unable to test this on prior versions because the Playground is only for TypeScript, not JavaScript that's supposed to just be copied through

⏯ Playground Link

Playground link with relevant code

💻 Code

Suppose the following JavaScript calls a function defined in TypeScript:
caller.js:

import * as fs from 'fs';
import TruffleContract from '@truffle/contract'; //Runtime err: this line dropped when JS is copied
import { initContract } from './functions.js';
initContract({}, fs, TruffleContract);
console.log('TruffleContract is ', typeof TruffleContract, TruffleContract); //still 'unused'

In functions.ts:

type fsType = typeof import('fs');
type TruffleContractConstructorType = typeof import('@truffle/contract');
import type TruffleContract from '@truffle/contract';
import type { ContractObject } from '@truffle/contract-schema/spec';
export const initContract = function(
    contractJSON: ContractObject | {}, //union type makes example smaller
    TruffleContractConstructor : TruffleContractConstructorType,
    fsDefinition: fsType //for demonstration in calling context
) : TruffleContract.Contract {
    if(typeof TruffleContractConstructor !== 'function') {
        throw('No valid TruffleContractConstructor passed to initContract.');
    }
    return TruffleContractConstructor(contractJSON);
}

A subset of tsConfig.js:

"compilerOptions": {
    "module": "ES2020",
    "moduleResolution": "node",
    "allowJs": true,
    "checkJs": false,
}

package.json has "type":"module" needed for other project requirements, so require is not available. Changing the import syntax to
import { TruffleContract } from '@truffle/contract'; causes TypeScript to maintain the import line but there is an (expected) runtime

SyntaxError: Named export 'TruffleContract' not found. The requested module '@truffle/contract' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from '@truffle/contract';
const { TruffleContract } = pkg;

That again requires manually re-inserting the import statement because TypeScript drops the line falsely asserting that it's not used even when it's used in an expression in the very next line, and it's undefined at runtime because the original syntax is the correct one to use.

🙁 Actual behavior

The TruffleContract import is dropped from the JavaScript file as part of copying that into the target location, leading to a runtime error that TruffleContract is not defined in the expression where it is used. Manually re-adding it to the copied JavaScript file after every transpilation avoids the runtime error.

🙂 Expected behavior

The documentation for allowJs does not give any indication that the TypeScript transpiler will break existing JavaScript, instead of just copying it through. Instead, it says:

This flag can be used as a way to incrementally add TypeScript files into JS projects by allowing the .ts and .tsx files to live along-side existing JavaScript files.

The FAQ states that TypeScript "removes module imports that aren't used in any expression." This one is, so by the reasoning in the FAQ, it isn't being elided, but in practice, it is, breaking what used to be running code.

The workaround discussed there and here is to include a general import line, such as import '@truffle/contract'; and while this does make it through to the target location, it doesn't overcome the runtime error. require is not available for use but the documentation indicates a single use of what's imported should suffice and there is one here.

Overall, the expected behavior is that the JavaScript file is copied directly with no alterations, and certainly no breaking changes.
More specifically, the expected behavior is that an import which is actually used (in this case, it is used for passing a module implementation as a parameter) is not removed. In this case, TypeScript appears to be wrongly detecting that the import is unused (ts(6133)), as the 'unused import' formatting/warning shows up in the editor as well. This incorrect 'unused' status detection even triggers when the imported module is used in e.g. a console.log statement on the very next line, by itself or with the typeof operator.

The implementation is not imported and used directly in functions.ts because different sources of implementations are used at runtime depending on factors which do not appear relevant to a minimal example. This is not in Angular.

Metadata

Metadata

Assignees

Labels

BugA bug in TypeScriptDomain: JS EmitThe issue relates to the emission of JavaScriptFix AvailableA PR has been opened for this issue

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions