Skip to content

Commit

Permalink
Merge pull request #30 from UmamiAppearance/0.2.0
Browse files Browse the repository at this point in the history
0.2.0
  • Loading branch information
UmamiAppearance authored Jan 11, 2023
2 parents 8ee427e + 89fd5e0 commit 3f0cc21
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 63 deletions.
3 changes: 3 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ImportManager.code-workspace
.vscode
.github
117 changes: 111 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@

This is the outsourced home of the underlying module for the rollup plugin: [rollup-plugin-import-manager](https://github.com/UmamiAppearance/rollup-plugin-import-manager). To have the ability to analyze and modify JavaScript source code for import statements (cjs/es6/dynamic) without having to deal with rollup or rollup dependencies, the source code for the **ImportManager** class itself has its own repository. It can be downloaded and used independently from the rollup plugin and or a building process.

_Note:_ A detailed documentation will follow soon.


## Install
Using npm:
Expand All @@ -15,7 +13,7 @@ npm install import-manager
```

## How it works
**ImportManager** analyzes a given source code for import statements. Those are collected as so called unit objects, on which the user can interact with. Also the creation of new units → import statements is possible.
**ImportManager** analyzes a given source code for import statements. Those are collected (in the [imports object](#imports-object)) as so called unit objects, on which the user can interact with. Also the creation of new units → import statements is possible.


## Usage
Expand All @@ -29,11 +27,118 @@ import ImportManager from "import-manager
```js
const manager = new ImportManager(<sourceCodeAsString>, <filename>)
```
#### `constructor(source, filename, warnSpamProtection=new Set(), warnings=true, pluginInstance=null)`
* _source_ - The unmodified source code.
* _filename_ (optional) - The path/name of the input file (used for hash generation only).
* _warnSpamProtection_ (empty `Set()` by default) - A Set which contains all previously printed warning hashes.
* _warnings_ (default `true`) - Pass false to suppress warning messages.
* _pluginInstance_ (optional) - Rollup plugin instance if used as a plugin.
## License
### `imports` [object]
`this.imports` contains all _units_ (if [`analyze`](#analyze) was called) which are objects for every import statement. It has three sub-objects for the import statement types:
* cjs
* dynamic
* es6
### Methods
#### Global Methods
Methods, callable from manager instance.
##### `analyze()`
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
) is thrown.
##### `selectModById(id, allowNull)`
Searches `this.imports` for the given module _id_. If _allowNull_ `false` the module must be found or a [`MatchError`](#matcherror
) is thrown.
##### `selectModByHash(hash, allowNull)`
Searches `this.imports` for the given module _hash_. If _allowNull_ `false` the module must be found or a [`MatchError`](#matcherror
) is thrown.
##### `makeCJSStatement(module, declarator, varname)`
Generates a CJS Import Statement String from the _module_, _declarator_ (`const`/`let`/`var`/`global`) and the _varname_.
`<declarator> <varname> = require(<module>)`
##### `makeDynamicStatement(module, declarator, varname)`
Generates a Dynamic Import Statement String including with a `await` call from the _module_, _declarator_ (`const`/`let`/`var`/`global`) and the _varname_.
`<declarator> <varname> = await import(<module>)`
##### `makeES6Statement(module, defaultMembers, members)`
Generates an ES6 Import Statement String from the _module_ and _defaultMember_ and/or _members_ if provided.
`import <defaultMembers>, { <members> } from <module>`
##### `insertStatement(statement, pos, type)`
Inserts an import _statement_ to the <i>pos</i>ition `top` of the file or the `bottom` which is after the last found import statement.
##### `insertAtUnit(unit, mode, statement)`
Inserts an import _statement_ at a given _unit_-object. There are three different <i>mode</i>s available:
* `append` - _statement_ gets inserted after the given _unit_
* `prepend` - _statement_ gets inserted before the given _unit_
* `replace` - _statement_ replaces the given _unit_
##### `logUnits()`
Debugging method to stop the building process to list all import units with its `id`, `hash` and `module`.
##### `logUnitObjects()`
Debugging method to stop the building process to list the complete import object with all its units, which is much more verbose than [`logUnits`](#logunits).
##### `remove(unit)`
Removes a unit from the code instance.
##### `commitChanges(unit)`
All manipulation done via a [unit method](#unit-methods) is made on the code slice of the _unit_. This methods finally writes it to the main code instance.
[MIT](https://opensource.org/licenses/MIT)
Copyright (c) 2022, UmamiAppearance
#### Unit Methods
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.
##### `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]_
##### `addMembers(names)`
_names_ is an array of strings of members to add to the unit. _[es6 only]_
##### `removeMember(memberType, name)`
Removes a singular `defaultMember` if _memberType_ is set to it or a `member` with the specified _name_. _[es6 only]_
##### `removeMembers(membersType)`
Removes the group of `defaultMember(s)` if _memberType_ is set to it or all `member(s)`. _[es6 only]_
##### `renameMember(memberType, name, newName, keepAlias)`
Renames a singular member of _memberType_ `defaultMember`/`member` matching the given _name_ to a _newName_. It is possible to _keepAlias_ if it should not be changed. _[es6 only]_
##### `setAlias(memberType, name, set)`
Sets a alias of _memberType_ `defaultMember`/`member` of the given member _name_. You can either _set_ (pass a string) a new name or don't define _set_ to delete the alias. _[es6 only]_
##### `makeUntraceable()`
Method to call after a unit was completely removed or replaced, to prevent matching it again afterwards.
##### `log()`
Debugging method to stop the building process and list the unit properties.
##### `updateUnit()`
If multiple changes should be performed on a `es6` unit, this method should be called after a change. If called the unit gets generated again with the updates code.
### Errors
#### `MatchError`
Extends the generic JavaScript `Error`. An error to inform, that it is not possible to select a specific unit.
#### `DebuggingError`
Extends the generic JavaScript `Error`. An error to deliberately abort the building process for retrieving information about the imports/units.
## License
[MIT](https://opensource.org/licenses/MIT)
Copyright (c) 2022-2023, UmamiAppearance
79 changes: 54 additions & 25 deletions cjs/import-manager.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,8 @@ class ImportManagerUnitMethods {
* @param {string} [set] - A new name or nothing for removal
*/
setAlias(memberType, name, set) {
this.#ES6only();

if (memberType === "defaultMember") {
if (name !== "*") {
throw new TypeError("The modification of a default member alias is only possible if the module is an asterisk. For other changes use the 'rename' method.");
Expand Down Expand Up @@ -370,7 +372,7 @@ class ImportManagerUnitMethods {
* It handles code analysis, creates units from import
* statements, attaches methods to the units and more.
*
* @version 0.1.7
* @version 0.2.0
* @author UmamiAppearance [mail@umamiappearance.eu]
* @license MIT
* @see https://github.com/UmamiAppearance/rollup-plugin-import-manager
Expand All @@ -386,8 +388,9 @@ class ImportManager {
* @param {string} filename - The path/name of the input file (used for hash generation).
* @param {object} [warnSpamProtection] - A Set which contains all previously printed warning hashes.
* @param {boolean} [warnings=true] - Pass false to suppress warning messages.
* @param {object} [pluginInstance] - Rollup plugin instance if used as a plugin.
*/
constructor(source, filename, warnSpamProtection=new Set(), warnings=true) {
constructor(source, filename, warnSpamProtection=new Set(), warnings=true, pluginInstance=null) {

if (!source) {
source="";
Expand Down Expand Up @@ -441,14 +444,26 @@ class ImportManager {
};
}

else {
if (pluginInstance) {
this.warn = pluginInstance.warn;
} else {
this.warn = msg => {
console.warn(
colorette.bold(colorette.yellow(`ImportManager: ${msg}`))
);
};
}
}

this.analyze();
}


/**
* Analyzes the source and stores all import
* statements as unit objects in the class
* variable "imports".
* statements as unit objects in the object
* "this.imports"
*/
analyze() {

Expand All @@ -464,7 +479,7 @@ class ImportManager {
this.parsedCode.body.forEach(node => {

if (node.type === "ImportDeclaration") {
const unit = this.es6NodeToUnit(node);
const unit = this.#es6NodeToUnit(node);
unit.id = es6Id ++;
unit.index = es6Index ++;
unit.hash = this.#makeHash(unit);
Expand All @@ -478,7 +493,7 @@ class ImportManager {
acornWalk.full(node, part => {

if (part.type === "ImportExpression") {
const unit = this.dynamicNodeToUnit(node, part);
const unit = this.#dynamicNodeToUnit(node, part);
unit.id = dynamicId ++;
unit.index = dynamicIndex ++;
unit.hash = this.#makeHash(unit);
Expand All @@ -487,7 +502,7 @@ class ImportManager {
}

else if (part.type === "Identifier" && part.name === "require") {
const unit = this.cjsNodeToUnit(node);
const unit = this.#cjsNodeToUnit(node);
unit.id = cjsId ++;
unit.index = cjsIndex ++;
unit.hash = this.#makeHash(unit);
Expand Down Expand Up @@ -565,9 +580,9 @@ class ImportManager {
* @param {Object|string} node - acorn node or es6 import statement string.
* @param {number} [oStart] - For updating units the original start index has to be passed.
* @param {number} [oEnd] - For updating units the original end index has to be passed.
* @returns
* @returns {object} - Import Manager Unit Object.
*/
es6NodeToUnit(node, oStart, oEnd) {
#es6NodeToUnit(node, oStart, oEnd) {

let code;
if (typeof node === "string") {
Expand Down Expand Up @@ -696,7 +711,14 @@ class ImportManager {
}


dynamicNodeToUnit(node, importObject) {
/**
* Method to generate a unit object from an acorn
* node, originated from a Dynamic Import Statement.
* @param {object} node - Complete acorn node.
* @param {object} importObject - Actual import part.
* @returns {object} - Import Manager Unit Object.
*/
#dynamicNodeToUnit(node, importObject) {

const code = this.code.slice(node.start, node.end);

Expand Down Expand Up @@ -724,7 +746,14 @@ class ImportManager {
return unit;
}

cjsNodeToUnit(node) {

/**
* Method to generate a unit object from an acorn
* node, originated from a Common JS Import Statement.
* @param {object} node - Complete acorn node.
* @returns {object} - Import Manager Unit Object.
*/
#cjsNodeToUnit(node) {

const code = this.code.slice(node.start, node.end);

Expand Down Expand Up @@ -795,6 +824,7 @@ class ImportManager {
* Selects a unit by its module name.
* @param {string} 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.
*/
selectModByName(name, type, allowNull) {
Expand Down Expand Up @@ -870,16 +900,17 @@ class ImportManager {

// finally add methods for manipulation to the unit
const unit = units[0];
unit.methods = new ImportManagerUnitMethods(unit, this.es6NodeToUnit);
unit.methods = new ImportManagerUnitMethods(unit, this.#es6NodeToUnit);

return unit;
}


/**
* Selects a unit by its id. Should only be used
* for test purposes.
* for testing purposes.
* @param {number} id - Unit id.
* @param {boolean} allowNull - If false the module must be found or a MatchError is thrown.
* @returns {Object} - An explicit unit.
*/
selectModById(id, allowNull) {
Expand Down Expand Up @@ -915,7 +946,7 @@ class ImportManager {

// add unit methods
const unit = units[0];
unit.methods = new ImportManagerUnitMethods(unit, this.es6NodeToUnit);
unit.methods = new ImportManagerUnitMethods(unit, this.#es6NodeToUnit);

return unit;
}
Expand All @@ -927,7 +958,8 @@ class ImportManager {
* All hashes for one file are stored in a list, with
* the corresponding id. The id-match method can there-
* fore be used, to find the unit.
* @param {string} hash - The hash string of the unit.
* @param {string} hash - The hash string of the unit.
* @param {boolean} allowNull - If false the module must be found or a MatchError is thrown.
* @returns {object} - An explicit unit.
*/
selectModByHash(hash, allowNull) {
Expand Down Expand Up @@ -1039,10 +1071,10 @@ class ImportManager {


/**
* Inserts an ES6 Import Statement to the top
* Inserts an Import Statement to the top
* of the file or after the last found import
* statement.
* @param {string} statement - ES6 Import Statement.
* @param {string} statement - Import Statement.
* @param {number} pos - 'top' or 'bottom'
*/
insertStatement(statement, pos, type) {
Expand Down Expand Up @@ -1081,12 +1113,12 @@ class ImportManager {


/**
* Inserts an ES6 Import Statement before or after
* Inserts an Import Statement before or after
* a given unit. Also an existing statement can be
* replaced.
* @param {Object} unit - Unit Object
* @param {string} mode - 'append'|'prepend'|'replace'
* @param {string} statement - ES6 Import Statement.
* @param {string} statement - Import Statement.
*/
insertAtUnit(unit, mode, statement) {

Expand Down Expand Up @@ -1145,8 +1177,8 @@ class ImportManager {


/**
* Bold, yellow warning messages in the mould
* of rollup warnings. With spam protection.
* Warnings with spam protection. Can use internal
* and native rollup method.
* @param {string} msg - Warning Message.
*/
warning(msg) {
Expand All @@ -1157,10 +1189,7 @@ class ImportManager {
}

this.warnSpamProtection.add(hash);

console.warn(
colorette.bold(colorette.yellow(`(!) (plugin ImportManager) ${msg}`))
);
this.warn(msg);
}
}

Expand Down
2 changes: 1 addition & 1 deletion cjs/import-manager.cjs.map

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 3f0cc21

Please sign in to comment.