@putout/plugin-nodejs 8.1.0 → 9.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,6 +20,8 @@ npm i putout @putout/plugin-nodejs -D
20
20
  ```json
21
21
  {
22
22
  "rules": {
23
+ "nodejs/convert-commonjs-to-esm": "off",
24
+ "nodejs/convert-esm-to-commonjs": "off",
23
25
  "nodejs/add-node-prefix": "on",
24
26
  "nodejs/convert-buffer-to-buffer-alloc": "on",
25
27
  "nodejs/convert-fs-promises": "on",
@@ -269,6 +271,99 @@ const {readFile} = require('fs/promises');
269
271
  const name = 'hello.txt';
270
272
  ```
271
273
 
274
+ ### convert-commonjs-to-esm
275
+
276
+ Convert **CommonJS** **EcmaScript Modules**.
277
+
278
+ > **CommonJS** is a module system supported in Node, it provides a `require` function, which can be used to access the `exports` object exposed by another file.
279
+ >
280
+ > **EcmaScript module** syntax is the standard way to import and export values between files in **JavaScript**. The `import` statement can be used to reference a value exposed by the `export` statement in another file.
281
+ >
282
+ > (c) [parceljs](https://parceljs.org/languages/javascript/)
283
+
284
+ #### require
285
+
286
+ ##### ❌ Example of incorrect code
287
+
288
+ ```js
289
+ const {join} = require('path');
290
+
291
+ const args = require('minimist')({
292
+ string: ['a', 'b'],
293
+ });
294
+ ```
295
+
296
+ ##### ✅ Example of correct code
297
+
298
+ ```js
299
+ import {join} from 'path';
300
+ import minimist from 'minimist';
301
+
302
+ const args = minimist({
303
+ string: ['a', 'b'],
304
+ });
305
+ ```
306
+
307
+ #### exports
308
+
309
+ ##### ❌ Example of incorrect code
310
+
311
+ ```js
312
+ module.exports = () => {};
313
+ ```
314
+
315
+ ##### ✅ Example of correct code
316
+
317
+ ```js
318
+ export default () => {};
319
+ ```
320
+
321
+ #### Commons
322
+
323
+ ##### ❌ Example of incorrect code
324
+
325
+ ```js
326
+ const {readFile} = require('fs/promises');
327
+
328
+ await readFile(__filename);
329
+ ```
330
+
331
+ ##### ✅ Example of correct code
332
+
333
+ ```js
334
+ import {readFile} from 'fs/promises';
335
+ import {fileURLToPath} from 'url';
336
+
337
+ const __filename = fileURLToPath(import.meta.url);
338
+ await readFile(__filename);
339
+ ```
340
+
341
+ ### convert-esm-to-commonjs
342
+
343
+ > **EcmaScript module** syntax is the standard way to import and export values between files in **JavaScript**. The `import` statement can be used to reference a value exposed by the `export` statement in another file.
344
+ >
345
+ > **CommonJS** is a module system supported in Node, it provides a `require` function, which can be used to access the `exports` object exposed by another file.
346
+ >
347
+ > (c) [parceljs](https://parceljs.org/languages/javascript/)
348
+
349
+ Convert **EcmaScript Modules** to **CommonJS**.
350
+
351
+ ## ❌ Example of incorrect code
352
+
353
+ ```js
354
+ import hello from 'world';
355
+ ```
356
+
357
+ ## ✅ Example of correct code
358
+
359
+ ```js
360
+ const hello = require('world');
361
+ ```
362
+
363
+ ## License
364
+
365
+ MIT
366
+
272
367
  ## License
273
368
 
274
369
  MIT
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ const {template, operator} = require('putout');
4
+
5
+ const {
6
+ getPathAfterImports,
7
+ insertBefore,
8
+ } = operator;
9
+
10
+ const initCommons = template.ast(`
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = dirname(__filename);
13
+ const require = createRequire(import.meta.url);
14
+ `);
15
+
16
+ module.exports.report = () => '"__filename", "__dirname" and "require" should be declared in ESM';
17
+
18
+ module.exports.fix = ({scope}) => {
19
+ const programScope = scope.getProgramParent();
20
+ const body = programScope.path.get('body');
21
+ const afterImportPath = getPathAfterImports(body);
22
+
23
+ insertBefore(afterImportPath, initCommons);
24
+ };
25
+
26
+ module.exports.include = () => [
27
+ '__filename',
28
+ '__dirname',
29
+ 'require',
30
+ ];
31
+
32
+ module.exports.filter = ({scope}) => {
33
+ const isDirname = scope.hasBinding('__dirname');
34
+ const isFilename = scope.hasBinding('__filename');
35
+ const isRequire = scope.hasBinding('require');
36
+
37
+ return !isDirname && !isFilename && !isRequire;
38
+ };
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ const {types, operator} = require('putout');
4
+ const {replaceWith} = operator;
5
+
6
+ const {
7
+ isIdentifier,
8
+ ExportNamedDeclaration,
9
+ } = types;
10
+
11
+ module.exports.report = () => `Use 'ESM' instead of 'CommonJS'`;
12
+
13
+ module.exports.exclude = () => ['__, __'];
14
+
15
+ module.exports.replace = () => ({
16
+ 'module.exports = __a': 'export default __a',
17
+ 'module.exports.__a = __b': ({__a, __b}, path) => {
18
+ if (isIdentifier(__b, {name: __a.name}))
19
+ return addExportToBinding(__b.name, path);
20
+
21
+ return 'export const __a = __b';
22
+ },
23
+ });
24
+
25
+ function addExportToBinding(name, path) {
26
+ const {scope} = path;
27
+ const binding = scope.bindings[name];
28
+ const bindingPath = parseBindingPath(binding.path);
29
+
30
+ const exportNode = ExportNamedDeclaration(bindingPath.node);
31
+
32
+ replaceWith(bindingPath, exportNode);
33
+
34
+ return '';
35
+ }
36
+
37
+ function parseBindingPath(path) {
38
+ if (path.isVariableDeclarator())
39
+ return path.parentPath;
40
+
41
+ return path;
42
+ }
@@ -0,0 +1,159 @@
1
+ 'use strict';
2
+
3
+ const justCamelCase = require('just-camel-case');
4
+
5
+ const {
6
+ types,
7
+ operator,
8
+ template,
9
+ } = require('putout');
10
+
11
+ const {replaceWith, insertBefore} = operator;
12
+
13
+ const {
14
+ isFunction,
15
+ isObjectPattern,
16
+ isIdentifier,
17
+ isStringLiteral,
18
+ AwaitExpression,
19
+ ImportDeclaration,
20
+ ImportDefaultSpecifier,
21
+ Identifier,
22
+ } = types;
23
+
24
+ const camelCase = (a) => justCamelCase(a.replace('@', ''));
25
+
26
+ module.exports.report = () => `Use 'ESM' instead of 'CommonJS'`;
27
+
28
+ const __B = 'declarations.0.init.arguments.0';
29
+
30
+ const createImport = ({name, source}) => {
31
+ const specifiers = [
32
+ ImportDefaultSpecifier(name),
33
+ ];
34
+
35
+ return ImportDeclaration(specifiers, source);
36
+ };
37
+
38
+ const createFnDeclaration = template('const NAME1 = FN(NAME2)');
39
+ const isPackage = ({value}) => /package(\.json)?$/.test(value);
40
+
41
+ module.exports.match = () => ({
42
+ 'const __a = require(__b)': ({__b}, path) => {
43
+ // exclude jsons while not supported
44
+ if (/\.json/.test(__b.value))
45
+ return false;
46
+
47
+ if (path.scope.getBinding('require'))
48
+ return false;
49
+
50
+ const __bPath = path.get(__B);
51
+
52
+ const {confident, value} = __bPath.evaluate();
53
+
54
+ if (isPackage(__b))
55
+ return false;
56
+
57
+ return value && confident;
58
+ },
59
+ 'require("__a")': (vars, path) => Boolean(path.parentPath.parentPath.isProgram()),
60
+ 'const __a = __b(require(__c))': ({__a, __c}) => {
61
+ return isIdentifier(__a) && isStringLiteral(__c);
62
+ },
63
+ 'const __a = require("__b")(__args)': checkCall,
64
+ 'const __a = require("__b").__c(__args)': checkCall,
65
+ });
66
+
67
+ module.exports.replace = () => ({
68
+ 'const __a = require(".")': 'import __a from "./index.js"',
69
+ 'const __a = require(__b).default': 'import __a from "__b"',
70
+ 'const __a = require(__b).__c': `{
71
+ const {__c} = require(__b);
72
+ const __a = __c;
73
+ }`,
74
+ 'require("__a")': 'import("__a")',
75
+ 'const __a = require(__b)': ({__a}, path) => {
76
+ let {value} = path
77
+ .get(__B)
78
+ .evaluate();
79
+
80
+ if (value.includes('./') && !/\.js(on)?$/.test(value) && !value.endsWith('..'))
81
+ value += '.js';
82
+
83
+ const fnPath = path.findParent(isFunction);
84
+
85
+ if (fnPath) {
86
+ fnPath.node.async = true;
87
+ return applyDynamicImport(path);
88
+ }
89
+
90
+ // disabled while not supported // https://babeljs.io/blog/2023/05/26/7.22.0#import-attributes-15536-15620 // // const isJSON = /\.json$/.test(value); // const assertion = !isJSON ? '' : 'with { type: "json" }';
91
+ const assertion = '';
92
+
93
+ if (isObjectPattern(__a)) {
94
+ const imports = [];
95
+
96
+ for (const {key, value} of __a.properties)
97
+ imports.push(`${key.name} as ${value.name}`);
98
+
99
+ const importsStr = imports.join(',');
100
+
101
+ return `import {${importsStr}} from "${value}" ${assertion}`;
102
+ }
103
+
104
+ return `import __a from "${value}" ${assertion}`;
105
+ },
106
+ 'const __a = __b(require(__c))': ({__a, __b, __c}, path) => {
107
+ const name = `_${__a.name}`;
108
+
109
+ const importNode = createImport({
110
+ name: Identifier(name),
111
+ source: __c,
112
+ });
113
+
114
+ const declarationNode = createFnDeclaration({
115
+ NAME1: __a,
116
+ FN: __b,
117
+ NAME2: Identifier(name),
118
+ });
119
+
120
+ insertBefore(path, [importNode]);
121
+
122
+ return declarationNode;
123
+ },
124
+ 'const __a = require("__b")(__args)': ({__b}) => {
125
+ const name = camelCase(__b.value);
126
+
127
+ return `{
128
+ import ${name} from "__b";
129
+ const __a = ${name}(__args);
130
+ }`;
131
+ },
132
+ 'const __a = require("__b").__c(__args)': ({__b}) => {
133
+ const name = camelCase(__b.value);
134
+
135
+ return `{
136
+ import ${name} from "__b";
137
+ const __a = ${name}.__c(__args);
138
+ }`;
139
+ },
140
+ });
141
+
142
+ function checkCall({__b}, path) {
143
+ const name = camelCase(__b.value);
144
+
145
+ if (!name)
146
+ return false;
147
+
148
+ return !path.scope.bindings[name];
149
+ }
150
+
151
+ function applyDynamicImport(path) {
152
+ const initPath = path.get('declarations.0.init');
153
+ const {node} = initPath;
154
+
155
+ initPath.node.callee.name = 'import';
156
+ replaceWith(initPath, AwaitExpression(node));
157
+
158
+ return path;
159
+ }
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ const convertCommonjsToEsmExports = require('./convert-commonjs-to-esm-exports');
4
+ const convertCommonjsToEsmCommons = require('./convert-commonjs-to-esm-commons');
5
+ const convertCommonjsToEsmRequire = require('./convert-commonjs-to-esm-require');
6
+
7
+ module.exports.rules = {
8
+ 'convert-commonjs-to-esm-exports': convertCommonjsToEsmExports,
9
+ 'convert-commonjs-to-esm-common': convertCommonjsToEsmCommons,
10
+ 'convert-commonjs-to-esm-require': convertCommonjsToEsmRequire,
11
+ };
@@ -0,0 +1,57 @@
1
+ 'use strict';
2
+
3
+ const {types, template} = require('putout');
4
+ const {isImportDefaultSpecifier} = types;
5
+
6
+ module.exports.report = () => `Use 'CommonJS' instead of 'ESM'`;
7
+
8
+ module.exports.replace = () => ({
9
+ 'export default __a': 'module.exports = __a',
10
+ 'export class __a {}': 'module.exports.__a = class __a {}',
11
+ 'export function __a(__args) {}': replaceFn,
12
+ 'export async function __a(__args) {}': replaceFn,
13
+ 'export const __a = __b': 'module.exports.__a = __b',
14
+
15
+ 'import "__a"': 'require("__a")',
16
+ 'import * as __a from "__b"': 'const __a = require("__b")',
17
+ 'import __imports from "__a"': ({__imports, __a}) => {
18
+ let assignment = '';
19
+ let destructuring = 'const {\n';
20
+ let hasSpecifiers = false;
21
+
22
+ for (const currentImport of __imports) {
23
+ const {imported, local} = currentImport;
24
+
25
+ if (isImportDefaultSpecifier(currentImport)) {
26
+ assignment = `const ${local.name} = require("__a")`;
27
+ continue;
28
+ }
29
+
30
+ hasSpecifiers = true;
31
+ destructuring += `${imported.name}: ${local.name},\n`;
32
+ }
33
+
34
+ destructuring += `\n} = require("${__a.value}");`;
35
+
36
+ if (assignment && !hasSpecifiers)
37
+ return assignment;
38
+
39
+ if (!assignment && hasSpecifiers)
40
+ return destructuring;
41
+
42
+ return `{
43
+ ${assignment};
44
+ ${destructuring};
45
+ }`;
46
+ },
47
+ });
48
+
49
+ function replaceFn({__a}, path) {
50
+ const {name} = __a;
51
+ const {declaration} = path.node;
52
+ const node = template.ast.fresh(`module.exports.${name} = __x`);
53
+
54
+ node.right = declaration;
55
+
56
+ return node;
57
+ }
package/lib/index.js CHANGED
@@ -12,6 +12,12 @@ const removeProcessExit = require('./remove-process-exit');
12
12
  const addNodePrefix = require('./add-node-prefix');
13
13
  const convertExportsToModuleExports = require('./convert-exports-to-module-exports');
14
14
 
15
+ const convertEsmToCommonjs = require('./convert-esm-to-commonjs');
16
+
17
+ const convertCommonjsToEsmExports = require('./convert-commonjs-to-esm-exports');
18
+ const convertCommonjsToEsmCommons = require('./convert-commonjs-to-esm-commons');
19
+ const convertCommonjsToEsmRequire = require('./convert-commonjs-to-esm-require');
20
+
15
21
  module.exports.rules = {
16
22
  'convert-buffer-to-buffer-alloc': convertBufferToBufferAlloc,
17
23
  'convert-fs-promises': convertFsPromises,
@@ -24,4 +30,9 @@ module.exports.rules = {
24
30
  'remove-process-exit': removeProcessExit,
25
31
  'add-node-prefix': addNodePrefix,
26
32
  'convert-exports-to-module-exports': convertExportsToModuleExports,
33
+
34
+ 'convert-esm-to-commonjs': ['off', convertEsmToCommonjs],
35
+ 'convert-commonjs-to-esm-exports': ['off', convertCommonjsToEsmExports],
36
+ 'convert-commonjs-to-esm-common': ['off', convertCommonjsToEsmCommons],
37
+ 'convert-commonjs-to-esm-require': ['off', convertCommonjsToEsmRequire],
27
38
  };
package/package.json CHANGED
@@ -1,11 +1,16 @@
1
1
  {
2
2
  "name": "@putout/plugin-nodejs",
3
- "version": "8.1.0",
3
+ "version": "9.0.1",
4
4
  "type": "commonjs",
5
5
  "author": "coderaiser <mnemonic.enemy@gmail.com> (https://github.com/coderaiser)",
6
6
  "description": "🐊Putout plugin adds ability to transform code to new API of Node.js",
7
7
  "homepage": "https://github.com/coderaiser/putout/tree/master/packages/plugin-nodejs#readme",
8
8
  "main": "lib/index.js",
9
+ "exports": {
10
+ ".": "./lib/index.js",
11
+ "./convert-esm-to-commonjs": "./lib/convert-esm-to-commonjs/index.js",
12
+ "./convert-commonjs-to-esm": "./lib/convert-commonjs-to-esm.js"
13
+ },
9
14
  "release": false,
10
15
  "tag": false,
11
16
  "changelog": false,
@@ -23,7 +28,9 @@
23
28
  "coverage": "madrun coverage",
24
29
  "report": "madrun report"
25
30
  },
26
- "dependencies": {},
31
+ "dependencies": {
32
+ "just-camel-case": "^6.2.0"
33
+ },
27
34
  "keywords": [
28
35
  "putout",
29
36
  "putout-plugin",
@@ -31,8 +38,6 @@
31
38
  "nodejs"
32
39
  ],
33
40
  "devDependencies": {
34
- "@putout/plugin-convert-commonjs-to-esm": "*",
35
- "@putout/plugin-convert-esm-to-commonjs": "*",
36
41
  "@putout/plugin-putout": "*",
37
42
  "@putout/test": "^7.0.0",
38
43
  "c8": "^8.0.0",
@@ -45,7 +50,7 @@
45
50
  "nodemon": "^3.0.1"
46
51
  },
47
52
  "peerDependencies": {
48
- "putout": ">=32"
53
+ "putout": ">=33"
49
54
  },
50
55
  "license": "MIT",
51
56
  "engines": {