@putout/plugin-esm 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) coderaiser
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,221 @@
1
+ # @putout/plugin-esm [![NPM version][NPMIMGURL]][NPMURL]
2
+
3
+ [NPMIMGURL]: https://img.shields.io/npm/v/@putout/plugin-esm.svg?style=flat&longCache=true
4
+ [NPMURL]: https://npmjs.org/package/@putout/plugin-esm "npm"
5
+
6
+ > The static `import` statement is used to `import` read only live bindings which are exported by another module.
7
+ > The imported bindings are called live bindings because they are updated by the module that exported the binding, but cannot be re-assigned by the importing module.
8
+ >
9
+ > (c) [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import)
10
+
11
+
12
+ 🐊[**Putout**](https://github.com/coderaiser/putout) plugin adds ability to transform to new **Node.js** API and apply best practices.
13
+
14
+ ## Install
15
+
16
+ ```
17
+ npm i putout @putout/plugin-esm -D
18
+ ```
19
+
20
+ ## Rules
21
+
22
+ - βœ… [declare-imports-first](#declare-imports-first);
23
+ - βœ… [group-imports-by-source](#group-imports-by-source);
24
+ - βœ… [merge-duplicate-imports](#merge-duplicate-imports);
25
+ - βœ… [remove-quotes-from-import-assertions](#remove-quotes-from-import-assertions);
26
+ - βœ… [remove-empty-import](#remove-empty-import);
27
+ - βœ… [remove-empty-export](#remove-empty-export);
28
+ - βœ… [sort-imports-by-specifiers](#sort-imports-by-specifiers);
29
+
30
+ ## Config
31
+
32
+ ```json
33
+ {
34
+ "rules": {
35
+ "esm/declare-imports-first": "on",
36
+ "esm/group-imports-by-source": "on",
37
+ "esm/merge-duplicate-imports": "on",
38
+ "esm/remove-quotes-from-import-assertions": "on",
39
+ "esm/remove-empty-export": "on",
40
+ "esm/remove-empty-import": ["on", {
41
+ "ignore": []
42
+ }],
43
+ "esm/sort-imports-by-specifiers": "on"
44
+ }
45
+ }
46
+ ```
47
+
48
+ ## declare-imports-first
49
+
50
+ Check out in 🐊[**Putout Editor**](https://putout.cloudcmd.io/#/gist/b1c18e5d726afe4ebb69d6b7a7dda82b/8189590815a1b8adb35bb8a846e28228e3c7fadf). For **CommonJS** use [nodejs/declare-after-require](https://github.com/coderaiser/putout/tree/master/packages/plugin-nodejs#declare-after-require).
51
+
52
+ ## ❌ Example of incorrect code
53
+
54
+ ```js
55
+ const [arg] = process.argv;
56
+ import esbuild from 'esbuild';
57
+ ```
58
+
59
+ ## βœ… Example of correct code
60
+
61
+ ```js
62
+ import esbuild from 'esbuild';
63
+
64
+ const [arg] = process.argv;
65
+ ```
66
+
67
+ ## group-imports-by-source
68
+
69
+ Group order:
70
+
71
+ - βœ… builtins;
72
+ - βœ… external;
73
+ - βœ… internal;
74
+
75
+ Checkout in 🐊[**Putout Editor**](https://putout.cloudcmd.io/#/gist/3cc782acf95211f9d456d63a99032ee1/0674223d050bba572f5271ffdccf8616cb441af5).
76
+
77
+ ## ❌ Example of incorrect code
78
+
79
+ ```js
80
+ import fs from 'node:fs';
81
+ import {lodash} from 'lodash';
82
+ import react from 'react';
83
+ import d from '../hello.js';
84
+ import ss from '../../bb/ss.js';
85
+ import b from './ss.js';
86
+
87
+ const c = 5;
88
+ ```
89
+
90
+ ## βœ… Example of correct code
91
+
92
+ ```js
93
+ import fs from 'node:fs';
94
+ import react from 'react';
95
+ import {lodash} from 'lodash';
96
+ import b from './ss.js';
97
+ import d from '../hello.js';
98
+ import ss from '../../bb/ss.js';
99
+
100
+ const c = 5;
101
+ ```
102
+
103
+ ## merge-duplicate-imports
104
+
105
+ ### join
106
+
107
+ To disable use:
108
+
109
+ ```json
110
+ {
111
+ "rules": {
112
+ "esm/merge-duplicate-imports-join": "off"
113
+ }
114
+ }
115
+ ```
116
+
117
+ #### ❌ Example of incorrect code
118
+
119
+ ```js
120
+ import test from 'supertape';
121
+ import {stub} from 'supertape';
122
+ ```
123
+
124
+ #### βœ… Example of correct code
125
+
126
+ ```js
127
+ import test, {stub} from 'supertape';
128
+ ```
129
+
130
+ ### rename
131
+
132
+ Checkout in 🐊[**Putout Editor**](https://putout.cloudcmd.io/#/gist/6604936dec6b1eed8ce0d143f2962f15/17b310a6e4d85b0b8615a8b91d0e27414e8af291).
133
+
134
+ To disable use:
135
+
136
+ ```json
137
+ {
138
+ "rules": {
139
+ "esm/merge-duplicate-imports-rename": "off"
140
+ }
141
+ }
142
+ #### ❌ Example of incorrect code
143
+
144
+ ```js
145
+ import putout from './putout.js';
146
+ import all from './putout.js';
147
+ import x from './putout.js';
148
+
149
+ console.log(all);
150
+ console.log(x);
151
+ ```
152
+
153
+ #### βœ… Example of correct code
154
+
155
+ ```js
156
+ import putout from './putout.js';
157
+
158
+ console.log(putout);
159
+ console.log(putout);
160
+ ```
161
+
162
+ ## remove-empty-export
163
+
164
+ ```diff
165
+ -export {};
166
+ ```
167
+
168
+ ## remove-empty-import
169
+
170
+ ```diff
171
+ -import 'abc';
172
+ ```
173
+
174
+
175
+ ## remove-quotes-from-import-assertions
176
+
177
+ Checkout in 🐊[**Putout Editor**](https://putout.cloudcmd.io/#/gist/f9f34acddbefba0ded53225ca10fa44e/7b4dba44602b9b2d28fe3a98989474a4b0d8d73d).
178
+
179
+ ## ❌ Example of incorrect code
180
+
181
+ ```js
182
+ import json from './mod.json' with { type: 'json' };
183
+ ```
184
+
185
+ ## βœ… Example of correct code
186
+
187
+ ```js
188
+ import json from './mod.json' with { type: 'json' };
189
+ ```
190
+
191
+ ## sort-imports-by-specifiers
192
+
193
+ Checkout in 🐊[**Putout Editor**](https://putout.cloudcmd.io/#/gist/521e2ff199243a7ce1f65db7140c272e/28c0588281286f8a6765b8aa2ecabbfcde2973a7).
194
+
195
+ ## ❌ Example of incorrect code
196
+
197
+ ```js
198
+ import {
199
+ a,
200
+ b,
201
+ c,
202
+ d,
203
+ } from 'd';
204
+ import a1 from 'a1';
205
+ ```
206
+
207
+ ## βœ… Example of correct code
208
+
209
+ ```js
210
+ import a1 from 'a1';
211
+ import {
212
+ a,
213
+ b,
214
+ c,
215
+ d,
216
+ } from 'd';
217
+ ```
218
+
219
+ ## License
220
+
221
+ MIT
@@ -0,0 +1,63 @@
1
+ 'use strict';
2
+
3
+ const {replaceWith} = require('putout').operator;
4
+
5
+ module.exports.report = () => `Declare imports first`;
6
+
7
+ module.exports.fix = ({path, importPath}) => {
8
+ let prev = path;
9
+ let preventInfiniteLoop = 500;
10
+
11
+ while (--preventInfiniteLoop) {
12
+ const {node} = importPath;
13
+
14
+ replaceWith(importPath, prev.node);
15
+ replaceWith(prev, node);
16
+
17
+ importPath = prev;
18
+ prev = prev.getPrevSibling();
19
+
20
+ if (!prev.node || prev.isImportDeclaration())
21
+ return;
22
+ }
23
+ };
24
+
25
+ module.exports.traverse = ({push, pathStore}) => ({
26
+ ImportDeclaration: (path) => {
27
+ pathStore(path);
28
+ },
29
+ ExportNamedDeclaration: (path) => {
30
+ const {source} = path.node;
31
+
32
+ if (!source)
33
+ return;
34
+
35
+ pathStore(path);
36
+ },
37
+ ExportAllDeclaration: (path) => {
38
+ pathStore(path);
39
+ },
40
+ Program: {
41
+ exit: () => {
42
+ for (const importPath of pathStore()) {
43
+ if (importPath) {
44
+ const path = importPath.getPrevSibling();
45
+
46
+ if (!path.node)
47
+ continue;
48
+
49
+ if (path.isImportDeclaration())
50
+ continue;
51
+
52
+ if (path.isExportDeclaration())
53
+ continue;
54
+
55
+ push({
56
+ path,
57
+ importPath,
58
+ });
59
+ }
60
+ }
61
+ },
62
+ },
63
+ });
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+
3
+ const {isDeepStrictEqual} = require('node:util');
4
+ const {types, operator} = require('putout');
5
+ const {isImportDeclaration} = types;
6
+
7
+ const {
8
+ replaceWithMultiple,
9
+ remove,
10
+ } = operator;
11
+
12
+ module.exports.report = () => `Group imports by source: 'builtins', 'external', 'internal'`;
13
+
14
+ module.exports.fix = ({grouped}) => {
15
+ const [first, ...others] = grouped;
16
+ const nodes = [first.node];
17
+
18
+ for (const current of others) {
19
+ const {node} = current;
20
+ remove(current);
21
+ nodes.push(node);
22
+ }
23
+
24
+ replaceWithMultiple(first, nodes);
25
+ };
26
+
27
+ module.exports.traverse = ({pathStore, push}) => ({
28
+ ImportDeclaration: pathStore,
29
+ Program: {
30
+ exit(path) {
31
+ const external = [];
32
+ const internal = [];
33
+ const builtin = [];
34
+ const all = pathStore().filter(isImportDeclaration);
35
+
36
+ if (!all.length)
37
+ return;
38
+
39
+ for (const current of all) {
40
+ const {value} = current.node.source;
41
+
42
+ if (value.startsWith('.')) {
43
+ internal.push(current);
44
+ continue;
45
+ }
46
+
47
+ if (value.startsWith('node:')) {
48
+ builtin.push(current);
49
+ continue;
50
+ }
51
+
52
+ external.push(current);
53
+ }
54
+
55
+ const grouped = [
56
+ ...builtin,
57
+ ...external,
58
+ ...internal,
59
+ ];
60
+
61
+ if (isDeepStrictEqual(all, grouped))
62
+ return;
63
+
64
+ push({
65
+ path,
66
+ grouped,
67
+ });
68
+ },
69
+ },
70
+ });
package/lib/index.js ADDED
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ const declareImportsFirst = require('./declare-imports-first');
4
+ const groupImportsBySource = require('./group-imports-by-source');
5
+ const mergeDuplicateImportsJoin = require('./merge-duplicate-imports-join');
6
+ const mergeDuplicateImportsRename = require('./merge-duplicate-imports-rename');
7
+ const removeQuotesFromImportAssertions = require('./remove-quotes-from-import-assertions');
8
+ const sortImportsBySpecifiers = require('./sort-imports-by-specifiers');
9
+ const removeEmptyImport = require('./remove-empty-import');
10
+ const removeEmptyExport = require('./remove-empty-export');
11
+
12
+ module.exports.rules = {
13
+ 'declare-imports-first': declareImportsFirst,
14
+ 'group-imports-by-source': groupImportsBySource,
15
+ 'merge-duplicate-imports-join': mergeDuplicateImportsJoin,
16
+ 'merge-duplicate-imports-rename': mergeDuplicateImportsRename,
17
+ 'remove-quotes-from-import-assertions': removeQuotesFromImportAssertions,
18
+ 'remove-empty-import': removeEmptyImport,
19
+ 'remove-empty-export': removeEmptyExport,
20
+ 'sort-imports-by-specifiers': sortImportsBySpecifiers,
21
+ };
@@ -0,0 +1,109 @@
1
+ 'use strict';
2
+
3
+ const {types, operator} = require('putout');
4
+
5
+ const {remove} = operator;
6
+ const {values} = Object;
7
+
8
+ const {
9
+ isImportDefaultSpecifier,
10
+ isImportNamespaceSpecifier,
11
+ isImportDeclaration,
12
+ } = types;
13
+
14
+ module.exports.report = () => `Avoid duplicate imports`;
15
+
16
+ module.exports.fix = ({path, imports}) => {
17
+ const all = [];
18
+
19
+ for (const imp of imports) {
20
+ const {specifiers} = imp.node;
21
+
22
+ for (const spec of specifiers) {
23
+ if (isImportDefaultSpecifier(spec)) {
24
+ path.node.specifiers.unshift(spec);
25
+ continue;
26
+ }
27
+
28
+ all.push(spec);
29
+ }
30
+
31
+ remove(imp);
32
+ }
33
+
34
+ path.node.specifiers.push(...all);
35
+ };
36
+
37
+ module.exports.traverse = ({push, pathStore}) => ({
38
+ ImportDeclaration(path) {
39
+ pathStore(path);
40
+ },
41
+ Program: {
42
+ exit: () => {
43
+ const imports = pathStore().filter(isImportDeclaration);
44
+ processImports(push, imports);
45
+ },
46
+ },
47
+ });
48
+
49
+ function processImports(push, imports) {
50
+ const {get, add} = duplicatesStore();
51
+ const importDefaults = new Map();
52
+
53
+ for (const path of imports) {
54
+ const {source, specifiers} = path.node;
55
+
56
+ const {value} = source;
57
+
58
+ if (specifiers.find(isImportNamespaceSpecifier))
59
+ continue;
60
+
61
+ const count = importDefaults.get(value) || 0;
62
+
63
+ const importDefaultCount = count + specifiers.filter(isImportDefaultSpecifier).length;
64
+
65
+ importDefaults.set(value, importDefaultCount);
66
+ }
67
+
68
+ for (const path of imports) {
69
+ const {source, specifiers} = path.node;
70
+
71
+ const {value} = source;
72
+
73
+ if (specifiers.find(isImportNamespaceSpecifier))
74
+ continue;
75
+
76
+ if (importDefaults.get(value) > 1)
77
+ continue;
78
+
79
+ add(value, path);
80
+ }
81
+
82
+ for (const list of get()) {
83
+ if (list.length === 1)
84
+ continue;
85
+
86
+ const [path, ...imports] = list;
87
+
88
+ push({
89
+ path,
90
+ imports,
91
+ });
92
+ }
93
+ }
94
+
95
+ function duplicatesStore() {
96
+ const duplicates = [];
97
+
98
+ const get = () => values(duplicates);
99
+
100
+ const add = (value, path) => {
101
+ duplicates[value] = duplicates[value] || [];
102
+ duplicates[value].push(path);
103
+ };
104
+
105
+ return {
106
+ get,
107
+ add,
108
+ };
109
+ }
@@ -0,0 +1,53 @@
1
+ 'use strict';
2
+
3
+ const {types, operator} = require('putout');
4
+ const {rename, remove} = operator;
5
+ const {isImportDefaultSpecifier} = types;
6
+
7
+ module.exports.report = () => 'Avoid duplicate imports';
8
+
9
+ module.exports.fix = ({path, imports}) => {
10
+ const {name} = path.node.specifiers[0].local;
11
+ remove(path);
12
+
13
+ const [first, ...other] = imports;
14
+
15
+ const firstName = first.node.specifiers[0].local.name;
16
+ rename(first, firstName, name);
17
+
18
+ for (const current of other) {
19
+ const currentName = current.node.specifiers[0].local.name;
20
+ rename(current, currentName, name);
21
+ remove(current);
22
+ }
23
+ };
24
+
25
+ module.exports.traverse = ({push, uplist}) => ({
26
+ ImportDeclaration: (path) => {
27
+ const {value} = path.node.source;
28
+ const [specifier, ...other] = path.node.specifiers;
29
+
30
+ if (other.length)
31
+ return;
32
+
33
+ if (!isImportDefaultSpecifier(specifier))
34
+ return;
35
+
36
+ uplist(value, path);
37
+ },
38
+ Program: {
39
+ exit() {
40
+ for (const items of uplist()) {
41
+ if (items.length < 2)
42
+ continue;
43
+
44
+ const [path, ...imports] = items;
45
+
46
+ push({
47
+ path,
48
+ imports,
49
+ });
50
+ }
51
+ },
52
+ },
53
+ });
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ const {operator} = require('putout');
4
+ const {remove} = operator;
5
+
6
+ module.exports.report = () => 'Remove empty export';
7
+
8
+ module.exports.fix = (path) => {
9
+ remove(path);
10
+ };
11
+
12
+ module.exports.include = () => [
13
+ 'ExportNamedDeclaration',
14
+ ];
15
+
16
+ module.exports.filter = (path) => {
17
+ const {specifiers, declaration} = path.node;
18
+
19
+ return !declaration && !specifiers.length;
20
+ };
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+
3
+ const {operator} = require('putout');
4
+ const {remove} = operator;
5
+
6
+ module.exports.report = () => `Avoid empty 'import' statement`;
7
+
8
+ module.exports.fix = (path) => {
9
+ remove(path);
10
+ };
11
+
12
+ const isCSS = (a) => /\.css/.test(a);
13
+ const isMin = (a) => /\.min\./.test(a);
14
+
15
+ module.exports.include = () => [
16
+ 'ImportDeclaration',
17
+ ];
18
+
19
+ module.exports.filter = (path, {options}) => {
20
+ const {specifiers, source} = path.node;
21
+
22
+ const {ignore = []} = options;
23
+ const {value} = source;
24
+
25
+ if (ignore.includes(value))
26
+ return false;
27
+
28
+ if (specifiers.length)
29
+ return false;
30
+
31
+ if (isCSS(value))
32
+ return false;
33
+
34
+ return !isMin(value);
35
+ };
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ module.exports.report = () => 'Remove quotes from import assertions';
4
+
5
+ module.exports.replace = () => ({
6
+ 'import __imports from "__a" with {"type": "__b"}': 'import __imports from "__a" with {type: "__b"}',
7
+ });
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ const {parseImportSpecifiers} = require('parse-import-specifiers');
4
+ const {operator} = require('putout');
5
+ const {insertBefore, remove} = operator;
6
+
7
+ module.exports.report = () => `Sort imports by specifiers count`;
8
+
9
+ module.exports.fix = ({path, nextPath}) => {
10
+ const {node} = nextPath;
11
+ remove(nextPath);
12
+ insertBefore(path, node);
13
+ };
14
+
15
+ module.exports.traverse = ({push}) => ({
16
+ ImportDeclaration(path) {
17
+ const {node} = path;
18
+ const {source, specifiers} = node;
19
+ const {imports} = parseImportSpecifiers(specifiers);
20
+
21
+ if (imports.length < 4)
22
+ return;
23
+
24
+ const nextPath = path.getNextSibling();
25
+
26
+ if (!nextPath.isImportDeclaration())
27
+ return;
28
+
29
+ if (nextPath.node.specifiers.length !== 1)
30
+ return;
31
+
32
+ if (!source.value.startsWith('./') && nextPath.node.source.value.startsWith('./'))
33
+ return;
34
+
35
+ if (source.value.startsWith('node:') && !nextPath.node.source.value.startsWith('node:'))
36
+ return;
37
+
38
+ push({
39
+ path,
40
+ nextPath,
41
+ });
42
+ },
43
+ });
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@putout/plugin-esm",
3
+ "version": "1.0.0",
4
+ "type": "commonjs",
5
+ "author": "coderaiser <mnemonic.enemy@gmail.com> (https://github.com/coderaiser)",
6
+ "description": "🐊Putout plugin improves ability to transform ESM code",
7
+ "homepage": "https://github.com/coderaiser/putout/tree/master/packages/plugin-esm#readme",
8
+ "main": "lib/index.js",
9
+ "exports": {
10
+ ".": "./lib/index.js"
11
+ },
12
+ "release": false,
13
+ "tag": false,
14
+ "changelog": false,
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/coderaiser/putout.git"
18
+ },
19
+ "scripts": {
20
+ "test": "madrun test",
21
+ "watch:test": "madrun watch:test",
22
+ "lint": "madrun lint",
23
+ "fresh:lint": "madrun fresh:lint",
24
+ "lint:fresh": "madrun lint:fresh",
25
+ "fix:lint": "madrun fix:lint",
26
+ "coverage": "madrun coverage",
27
+ "report": "madrun report"
28
+ },
29
+ "dependencies": {},
30
+ "keywords": [
31
+ "putout",
32
+ "putout-plugin",
33
+ "plugin",
34
+ "esm"
35
+ ],
36
+ "devDependencies": {
37
+ "@putout/eslint-flat": "^2.0.0",
38
+ "@putout/plugin-declare": "*",
39
+ "@putout/plugin-declare-before-reference": "*",
40
+ "@putout/plugin-putout": "*",
41
+ "@putout/plugin-reuse-duplicate-init": "*",
42
+ "@putout/plugin-typescript": "*",
43
+ "@putout/test": "^11.0.0",
44
+ "c8": "^10.0.0",
45
+ "eslint": "^9.0.0",
46
+ "eslint-plugin-n": "^17.0.0",
47
+ "eslint-plugin-putout": "^23.0.0",
48
+ "lerna": "^6.0.1",
49
+ "madrun": "^10.0.0",
50
+ "montag": "^1.2.1",
51
+ "nodemon": "^3.0.1"
52
+ },
53
+ "peerDependencies": {
54
+ "putout": ">=37"
55
+ },
56
+ "license": "MIT",
57
+ "engines": {
58
+ "node": ">=18"
59
+ },
60
+ "publishConfig": {
61
+ "access": "public"
62
+ }
63
+ }