Skip to content

Es6 import specifier and require handling #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
ESLint-Plugin-Alternate-Import
===============================

# Installation

Install [ESLint](https://www.github.com/eslint/eslint) either locally or globally. (Note that locally, per project, is strongly preferred)

```sh
$ npm install eslint --save-dev
```

Install Plugin

```sh
$ npm install eslint-plugin-alternate-import --save-dev
```

# Configuration

Following is a sample configuration for how to restrict a package and suggest alternatives. It support both ES6 `import` and ES5 `require()` syntax.

`.eslintrc`

```json5
{
"settings": {
"alternate-import": {
"alternatePackagesMap": [
{
"original": "restricted-package-name",
"alternate": "alternate-package-name" // use alternate instead of original
},
{
"original": "restricted-package-name",
"alternate": "/old-package-imports.js" // import from file instead of original package
},
{
"original": "restricted-package-name" // Restrict package but do not suggest any alternative
}
],
"customFileImportRootPrefix": "@" // Prepend static path to alternate custom file(s)
},
}
}
```


## Why this plugin?

As an owner of the project, you need to be sure that your project does not include any of the known possible bad or unnecessary npm package(s). With this, you can restrict the use of those kinds of npm packages in your project and can suggest a better alternative.

Eg: Instead of using moment.js for some basic time manipulation you can use some lightweight date library or you can use your custom utility. But, now you also want to restrict everyone contributing to your project to follow this particular rule. Here you can use this plugin and it will help you in the following cases:

1. Restrict use of Deprecated package(s).
2. Restrict the use of package(s) with known Security Vulnerabilities.
3. Restrict the use of package(s) for which we know some better alternatives.
4. Restrict use of package(s) which may have compatibility issues with current dependencies and environment.

`.eslintrc`

```json5
{
"settings": {
"alternate-import": {
"alternatePackagesMap": [
{
"original": "package-which-is-deprecated-and-not-secure-and-compatible", //restrict
"alternate": "much-better-package" // better suggestion
},
{
"original": "package-with-es5-require", // automatic support for both ES6 Import and ES5 require() syntax
"alternate": "much-better-package" // better suggestion
},
{
"original": "package-with-lots-of-unwanted-code",
"alternate": "/old-package-imports.js" // use import from file instead of original package - less code
}

]
},
}
}
```

## License
102 changes: 87 additions & 15 deletions rules/restricted-direct-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function reportErrorAndFix(
fix: function(fixer) {
return fixer.replaceText(
node,
`import { ${importedVariableNames.join(', ')} } from '${
`import ${importedVariableNames} from '${
!isAlternateCustomPackage
? `${matchedRestrictedPackage.alternate}`
: `${customFileImportRootPrefix}${matchedRestrictedPackage.alternate.replace(
Expand Down Expand Up @@ -79,12 +79,21 @@ function checkCustomFileImport(

return traverse(alternatePackageAST, {
ExportNamedDeclaration: function(path) {
const exportedVariableNames = path.node.specifiers.map(({ exported: { name } }) => name);

const missingExportInAlternatePackage = importedVariableNames.filter(
const exportedVariableNames = path.node.specifiers.map(({ exported: { name } }) => name);
const missingExportInAlternatePackage = (typeof importedVariableNames == 'string')?
([importedVariableNames]) : (importedVariableNames.map((importedVariableName) => {
if(importedVariableName.indexOf('as') != -1) {
// remove local name and use orignal export name
// to verify its exported from the module by removing its
// alias name
let arr = importedVariableName.split('as')
return arr[0] && arr[0].trim();
} else {
return importedVariableName
}
}).filter(
importedVariableName => exportedVariableNames.indexOf(importedVariableName) === -1
);

));
return reportErrorAndFix(
context,
node,
Expand Down Expand Up @@ -114,36 +123,99 @@ module.exports = {
];

return {
VariableDeclaration(node) {
node.declarations.map(obj => {
if (obj.init.type === "CallExpression") {
// we just need package name and we know it will be always first
// argument that's how require works.
// const package = require('package-name')
const packageName = obj.init.arguments[0].value

const matchedRestrictedPackage = alternatePackagesMap.find(
obj => obj.original === packageName
);
if (!matchedRestrictedPackage) return;

const isAlternateCustomPackage = !!(
matchedRestrictedPackage.alternate && matchedRestrictedPackage.alternate.match(/.js$/)
);

// No Alternate Import Provided
if (!matchedRestrictedPackage.alternate) {
return context.report({
node,
message: `Direct import restricted for "${node.source.value}" package.`
});
}

// Alternate Node Package Import
if (!isAlternateCustomPackage) {
} else {
return context.report({
node,
message: `Require restricted for "${packageName}" package, Please use ES6 "import" syntax and use "import from '${
matchedRestrictedPackage.alternate
}'"`
});
}
}
});
},
ImportDeclaration(node) {
const matchedRestrictedPackage = alternatePackagesMap.find(
obj => obj.original === node.source.value
);

if (!matchedRestrictedPackage) return;

const importedVariableNames = node.specifiers.map(({ imported: { name } }) => name);

const isAlternateCustomPackage = !!(
matchedRestrictedPackage.alternate && matchedRestrictedPackage.alternate.match(/.js$/)
);

let importedVariableNames = [];
let checkItem = node.specifiers[0];
if(checkItem.type == 'ImportSpecifier') {
importedVariableNames = node.specifiers.reduce((specifierList, item) => {
if(item.imported.name != item.local.name) {
specifierList.push(`${item.imported.name} as ${item.local.name}`)
} else {
specifierList.push(item.imported.name)
}
return specifierList;
}, []);
importedVariableNames = `{ ${importedVariableNames.join(', ')} }`
} else if(checkItem.type == 'ImportNamespaceSpecifier') {
importedVariableNames = `* as ${node.specifiers.shift().local.name}`
} else {
// for default import specifier
importedVariableNames = node.specifiers.shift().local.name
}
// No Alternate Import Provided
if (!matchedRestrictedPackage.alternate) {
return context.report({
node,
message: `Direct import restricted for "${node.source.value}" package.`
});
}

// Alternate Node Package Import
if (!isAlternateCustomPackage) {
return reportErrorAndFix(
context,
return context.report({
node,
matchedRestrictedPackage,
importedVariableNames,
isAlternateCustomPackage
);
message: `Direct import restricted for "${node.source.value}" package.`,
fix: function(fixer) {
return fixer.replaceText(
node,
`import ${importedVariableNames} from '${
!isAlternateCustomPackage
? `${matchedRestrictedPackage.alternate}`
: `${customFileImportRootPrefix}${matchedRestrictedPackage.alternate.replace(
/.js$/,
''
)}`
}';`
);
}
});
}

// Alternate Custom File Import
Expand Down