GithubHelp home page GithubHelp logo

umamiappearance / importmanager Goto Github PK

View Code? Open in Web Editor NEW
1.0 1.0 1.0 366 KB

A class to analyze and manipulate JavaScript import statements from source code files.

License: MIT License

JavaScript 100.00%
cjs code-manipulation dynamic es6 import modules rollup-plugin-import-manager

importmanager's People

Contributors

dependabot[bot] avatar tjcouch-sil avatar umamiappearance avatar

Stargazers

 avatar

Watchers

 avatar

Forkers

tjcouch-sil

importmanager's Issues

Support Regex in module value, function in rename value when selecting `module`

Hi! ๐Ÿ‘‹

Firstly, thanks for your work on this project! ๐Ÿ™‚

Today I used patch-package to patch [email protected] for the project I'm working on source code for the plugin configuration here.

I needed to make exact matches to module names (instead of the current matching where it looks for the config-provided module name anywhere in the module name string) so that I could remove a specific module but not modules whose names included that module name, so I added Regex support in the module name.

I also needed to modify the relative path to modules programmatically when renaming them, not only replace module names one-for-one, so I added the ability to provide a function that receives the full raw module source path and returns a new full raw module source path.

Here is the diff that solved my problem:

diff --git a/node_modules/import-manager/README.md b/node_modules/import-manager/README.md
index 5754a5f..7451c36 100644
--- a/node_modules/import-manager/README.md
+++ b/node_modules/import-manager/README.md
@@ -49,7 +49,7 @@ Methods, callable from manager instance.
 Analyzes the source and stores all import statements as unit objects in the [imports object](#imports-object).
 
 ##### `selectModByName(name, type, allowNull)`
-Searches `this.imports` for the given module _name_. If _type_ is provided (`cjs`/`dynamic`/`es6`), it only searches for the module in that category. If _allowNull_ `false` the module must be found or a [`MatchError`](#matcherror
+Searches `this.imports` for the given module _name_ (`String` | `RegExp`). If _type_ is provided (`cjs`/`dynamic`/`es6`), it only searches for the module in that category. If _allowNull_ `false` the module must be found or a [`MatchError`](#matcherror
 ) is thrown.
 
 ##### `selectModById(id, allowNull)`
@@ -100,6 +100,8 @@ Methods callable from a unit object.
 ##### `renameModule(name, modType)`
 Changes the _name_ -> module (path). _modType_ can be `"string"` which adds quotation marks around _name_ or `"raw"`, which doesn't and can be used to pass variables if valid for the import type.
 
+EDIT: name can alternatively be (moduleSourceRaw: string) => string where moduleSourceRaw is the module's source code including quotes if _modType_ is `"string"`.
+
 ##### `addDefaultMembers(names)`
 _names_ is an array of strings (even for the most common case of a single member) of default members to add to the unit. _[es6 only]_
 
diff --git a/node_modules/import-manager/cjs/import-manager.cjs b/node_modules/import-manager/cjs/import-manager.cjs
index 7e54a1e..c6766a3 100644
--- a/node_modules/import-manager/cjs/import-manager.cjs
+++ b/node_modules/import-manager/cjs/import-manager.cjs
~~~ FILE REGENERATED ~~~
diff --git a/node_modules/import-manager/cjs/import-manager.cjs.map b/node_modules/import-manager/cjs/import-manager.cjs.map
index 7c48792..74fad9d 100644
--- a/node_modules/import-manager/cjs/import-manager.cjs.map
+++ b/node_modules/import-manager/cjs/import-manager.cjs.map
~~~ FILE REGENERATED ~~~
diff --git a/node_modules/import-manager/src/core.js b/node_modules/import-manager/src/core.js
index 28e1703..c806ecd 100644
--- a/node_modules/import-manager/src/core.js
+++ b/node_modules/import-manager/src/core.js
@@ -335,7 +335,9 @@ class ImportManager {
             start: node.source.start - node.start,
             end: node.source.end - node.start,
             type: "string",
-            quotes: node.source.raw.at(0)
+            quotes: node.source.raw.at(0),
+            // EDIT: added module's raw source code
+            sourceRaw: node.source.raw
         };
 
         
@@ -367,7 +369,9 @@ class ImportManager {
         const module = {
             name: importObject.source.value.split("/").at(-1) || "N/A",
             start: importObject.source.start - node.start,
-            end: importObject.source.end - node.start
+            end: importObject.source.end - node.start,
+            // EDIT: added module's raw source code
+            sourceRaw: importObject.source.raw
         };
 
         if (importObject.source.type === "Literal") {
@@ -403,7 +407,9 @@ class ImportManager {
         const module = {
             name: modulePart.value.split("/").at(-1) || "N/A",
             start: modulePart.start - node.start,
-            end: modulePart.end - node.start
+            end: modulePart.end - node.start,
+            // EDIT: added module's raw source code
+            sourceRaw: modulePart.raw
         };
 
         if (modulePart.type === "Literal") {
@@ -464,7 +470,7 @@ class ImportManager {
     
     /**
      * Selects a unit by its module name.
-     * @param {string} name - Module Name. 
+     * @param {string|RegExp} name - Module Name. 
      * @param {string|string[]} [type] - "cjs", "dynamic", "es6" one as a string or multiple as array of strings
      * @param {boolean} allowNull - If false the module must be found or a MatchError is thrown.
      * @returns {Object} - An explicit unit.
@@ -502,7 +508,8 @@ class ImportManager {
 
         // filter for unit name
         const units = unitList.filter(unit => {
-            const match = unit.module.name.indexOf(name) > -1;
+            // EDIT: unit.module can now be a string or a regex
+            const match = name instanceof RegExp ? unit.module.name !== undefined && name.test(unit.module.name) : unit.module.name.indexOf(name) > -1;
 
             // ignore deleted units
             if (match && unit.module.name.match(/^\(deleted\)/)) {
diff --git a/node_modules/import-manager/src/unit-methods.js b/node_modules/import-manager/src/unit-methods.js
index 26534df..1a50d0d 100644
--- a/node_modules/import-manager/src/unit-methods.js
+++ b/node_modules/import-manager/src/unit-methods.js
@@ -48,21 +48,27 @@ export default class ImportManagerUnitMethods {
 
     /**
      * Changes the module part of a import statement.
-     * @param {string} name - The new module part/path.
+     * @param {string | (moduleSourceRaw: string) => string} name - The new module part/path or a function
+     * that receives the module's raw source code (including quotes if present) and returns the new module part/path.
      * @param {*} modType - Module type (literal|raw).
+     * EDIT: Added support for name (aka rename in rollup plugin action) to be a function
      */
     renameModule(name, modType) {
-        if (modType === "string") {
-            if (!this.unit.module.quotes) {
-                this.unit.module.quotes = "\"";
+        const isNameAFn = typeof name === 'function';
+
+        if (!isNameAFn) {
+            if (modType === "string") {
+                if (!this.unit.module.quotes) {
+                    this.unit.module.quotes = "\"";
+                }
+                const q = this.unit.module.quotes;
+                name = q + name + q;
+            } else if (modType !== "raw") {
+                throw new TypeError(`Unknown modType '${modType}'. Valid types are 'string' and 'raw'.`);
             }
-            const q = this.unit.module.quotes;
-            name = q + name + q;
-        } else if (modType !== "raw") {
-            throw new TypeError(`Unknown modType '${modType}'. Valid types are 'string' and 'raw'.`);
         }
         
-        this.unit.code.overwrite(this.unit.module.start, this.unit.module.end, name);
+        this.unit.code.overwrite(this.unit.module.start, this.unit.module.end, isNameAFn ? name(this.unit.module.sourceRaw) : name);
 
         if (this.unit.type === "es6") {
             this.updateUnit();

This change corresponds to UmamiAppearance/rollup-plugin-import-manager#54

Please let me know if you would like me to submit a pull request with these changes.

This issue body was partially generated by patch-package.

Handling irregular `require` statements

Hi again! ๐Ÿ‘‹

As I mentioned before, thanks for your work on this project! ๐Ÿ™‚

Today I used patch-package to patch [email protected] 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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.