Skip to content

Handling irregular require statements #50

@tjcouch-sil

Description

@tjcouch-sil

Hi again! 👋

As I mentioned before, thanks for your work on this project! 🙂

Today I used patch-package to patch import-manager@0.4.2 for the project I'm working on (source code for the plugin configuration here).

It seems require statements that don't match the expected pattern of declaration and instantiation in one expression (e.g. typeof window<"u"&&typeof require<"u"&&(window.Buffer=require("buffer/").Buffer);) cause an error that closes the program and fails to build:

error during build:
TypeError: Cannot read properties of undefined (reading 'at')
    at #cjsNodeToUnit (file:///C:/Users/tj_co/source/repos/crowd.bible-extension-paranext/node_modules/import-manager/src/core.js:403:46)
    at file:///C:/Users/tj_co/source/repos/crowd.bible-extension-paranext/node_modules/import-manager/src/core.js:147:57
    at c (file:///C:/Users/tj_co/source/repos/crowd.bible-extension-paranext/node_modules/acorn-walk/dist/walk.mjs:75:7)
    at Object.skipThrough (file:///C:/Users/tj_co/source/repos/crowd.bible-extension-paranext/node_modules/acorn-walk/dist/walk.mjs:180:37)
    at c (file:///C:/Users/tj_co/source/repos/crowd.bible-extension-paranext/node_modules/acorn-walk/dist/walk.mjs:73:22)
    at base.UnaryExpression.base.UpdateExpression (file:///C:/Users/tj_co/source/repos/crowd.bible-extension-paranext/node_modules/acorn-walk/dist/walk.mjs:367:3)
    at c (file:///C:/Users/tj_co/source/repos/crowd.bible-extension-paranext/node_modules/acorn-walk/dist/walk.mjs:73:22)
    at Object.skipThrough (file:///C:/Users/tj_co/source/repos/crowd.bible-extension-paranext/node_modules/acorn-walk/dist/walk.mjs:180:37)
    at c (file:///C:/Users/tj_co/source/repos/crowd.bible-extension-paranext/node_modules/acorn-walk/dist/walk.mjs:73:22)
    at base.BinaryExpression.base.LogicalExpression (file:///C:/Users/tj_co/source/repos/crowd.bible-extension-paranext/node_modules/acorn-walk/dist/walk.mjs:370:3)
dep-79892de8.js:12548
Process exited with code 1

Essentially, it seems that core.js's #cjsNodeToUnit assumes the acorn node will be a straight-forward expression with declaration and instantiation without any complexity (e.g. var name = require('mod');). However, I just received the error when #cjsNodeToUnit was trying to parse the node when the code variable was set to typeof window<"u"&&typeof require<"u"&&(window.Buffer=require("buffer/").Buffer); (I think some dependency of mine was trying to polyfill Buffer). I stringified the node, whose contents follow in the dropdown:

JSON.stringify(node, null, 2)
{
  "type": "ExpressionStatement",
  "start": 21948,
  "end": 22029,
  "expression": {
    "type": "LogicalExpression",
    "start": 21948,
    "end": 22028,
    "left": {
      "type": "LogicalExpression",
      "start": 21948,
      "end": 21985,
      "left": {
        "type": "BinaryExpression",
        "start": 21948,
        "end": 21965,
        "left": {
          "type": "UnaryExpression",
          "start": 21948,
          "end": 21961,
          "operator": "typeof",
          "prefix": true,
          "argument": {
            "type": "Identifier",
            "start": 21955,
            "end": 21961,
            "name": "window"
          }
        },
        "operator": "<",
        "right": {
          "type": "Literal",
          "start": 21962,
          "end": 21965,
          "value": "u",
          "raw": "\"u\""
        }
      },
      "operator": "&&",
      "right": {
        "type": "BinaryExpression",
        "start": 21967,
        "end": 21985,
        "left": {
          "type": "UnaryExpression",
          "start": 21967,
          "end": 21981,
          "operator": "typeof",
          "prefix": true,
          "argument": {
            "type": "Identifier",
            "start": 21974,
            "end": 21981,
            "name": "require"
          }
        },
        "operator": "<",
        "right": {
          "type": "Literal",
          "start": 21982,
          "end": 21985,
          "value": "u",
          "raw": "\"u\""
        }
      }
    },
    "operator": "&&",
    "right": {
      "type": "AssignmentExpression",
      "start": 21988,
      "end": 22027,
      "operator": "=",
      "left": {
        "type": "MemberExpression",
        "start": 21988,
        "end": 22001,
        "object": {
          "type": "Identifier",
          "start": 21988,
          "end": 21994,
          "name": "window"
        },
        "property": {
          "type": "Identifier",
          "start": 21995,
          "end": 22001,
          "name": "Buffer"
        },
        "computed": false,
        "optional": false
      },
      "right": {
        "type": "MemberExpression",
        "start": 22002,
        "end": 22027,
        "object": {
          "type": "CallExpression",
          "start": 22002,
          "end": 22020,
          "callee": {
            "type": "Identifier",
            "start": 22002,
            "end": 22009,
            "name": "require"
          },
          "arguments": [
            {
              "type": "Literal",
              "start": 22010,
              "end": 22019,
              "value": "buffer/",
              "raw": "\"buffer/\""
            }
          ],
          "optional": false
        },
        "property": {
          "type": "Identifier",
          "start": 22021,
          "end": 22027,
          "name": "Buffer"
        },
        "computed": false,
        "optional": false
      }
    }
  }
}

It appears that there are no declarations in the statement, so node.declarations is undefined. When #cjsNodeToUnit accesses that property, it throws the error. acorn parses the expression with operator: '&&' and the actual assignment of the import (where it should have operator: '=') is nested in lefts and rights.

Fortunately for my current use case, I don't need to do any modification of this particular import statement, so I made a quick patch that essentially ignores the problematic line. However, I suggest a better long-term solution would be to traverse the node to find the operator: '=' and operate on that (accounting in any case for node.declarations being undefined).

Some other examples of different kinds of requires that likely don't match the expected pattern follow:

// Not tested, but I imagine declaring and assigning in two separate expressions would not work
var name; name = require('mod');
// Tested. If you run a require without any assignment or anything, it fails with the same error. These kinds of requires are useful for running modules' side effects
require('mod-with-side-effects');

Here is the diff that solved my problem:

diff --git a/node_modules/import-manager/cjs/import-manager.cjs b/node_modules/import-manager/cjs/import-manager.cjs
index 92314ec..56c5d3f 100644
--- a/node_modules/import-manager/cjs/import-manager.cjs
+++ b/node_modules/import-manager/cjs/import-manager.cjs
@@ -395,7 +395,6 @@ class ImportManagerUnitMethods {
  */
 
 
-
 class ImportManager {
 
     /**
@@ -520,6 +519,7 @@ class ImportManager {
                     
                     else if (part.type === "Identifier" && part.name === "require") {
                         const unit = this.#cjsNodeToUnit(node);
+                        if (!unit) return;
                         unit.id = cjsId ++;
                         unit.index = cjsIndex ++;
                         unit.hash = this.#makeHash(unit);
@@ -772,6 +772,7 @@ class ImportManager {
      * @returns {object} - Import Manager Unit Object.
      */
     #cjsNodeToUnit(node) {
+        if (!node || !node.declarations || node.declarations.length >= 0) return;
 
         const code = this.code.slice(node.start, node.end);
 
diff --git a/node_modules/import-manager/cjs/import-manager.cjs.map b/node_modules/import-manager/cjs/import-manager.cjs.map
index a701837..f890886 100644
--- a/node_modules/import-manager/cjs/import-manager.cjs.map
+++ b/node_modules/import-manager/cjs/import-manager.cjs.map
 SOURCEMAP OMITTED
diff --git a/node_modules/import-manager/src/core.js b/node_modules/import-manager/src/core.js
index 717400c..f3cf31f 100644
--- a/node_modules/import-manager/src/core.js
+++ b/node_modules/import-manager/src/core.js
@@ -145,6 +145,7 @@ class ImportManager {
                     
                     else if (part.type === "Identifier" && part.name === "require") {
                         const unit = this.#cjsNodeToUnit(node);
+                        if (!unit) return;
                         unit.id = cjsId ++;
                         unit.index = cjsIndex ++;
                         unit.hash = this.#makeHash(unit);
@@ -397,6 +398,7 @@ class ImportManager {
      * @returns {object} - Import Manager Unit Object.
      */
     #cjsNodeToUnit(node) {
+        if (!node || !node.declarations || node.declarations.length >= 0) return;
 
         const code = this.code.slice(node.start, node.end);

Thank you again for all your hard work on this awesome library!

This issue body was partially generated by patch-package.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions