@putout/plugin-esm 8.0.2 → 8.1.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/README.md CHANGED
@@ -34,7 +34,8 @@ npm i putout @putout/plugin-esm -D
34
34
 
35
35
  ## File rules
36
36
 
37
- - ✅ [apply-namespace-import-file](#resolve-imported-file);
37
+ - ✅ [apply-namespace-import-to-file](#apply-namespace-import-file]);
38
+ - ✅ [apply-named-import-to-file](#apply-named-import-to-file);
38
39
  - ✅ [apply-privately-imported-file](#apply-privately-imported-file);
39
40
  - ✅ [apply-js-imported-file](#apply-js-imported-file);
40
41
  - ✅ [resolve-imported-file](#resolve-imported-file);
@@ -61,7 +62,8 @@ npm i putout @putout/plugin-esm -D
61
62
  "esm/apply-js-imported-file": "off",
62
63
  "esm/resolve-imported-file": "off",
63
64
  "esm/shorten-imported-file": "off",
64
- "esm/apply-namespace-of-file": "off",
65
+ "esm/apply-namespace-import-to-file": "off",
66
+ "esm/apply-named-import-to-file": "off",
65
67
  "esm/apply-privately-imported-file": "off",
66
68
  "esm/remove-useless-export-specifiers": "off"
67
69
  }
@@ -399,6 +401,41 @@ import('foo.json', {
399
401
 
400
402
  ## File Rules
401
403
 
404
+ ### apply-named-import-to-file
405
+
406
+ The rule fixes:
407
+
408
+ > `SyntaxError: The requested module './a.js' does not provide an export named 'default'`
409
+
410
+ Check out in 🐊**Putout Editor**:
411
+
412
+ - ✅ [`get-imports`](https://putout.cloudcmd.io/#/gist/5d7687215e9fbdf705935c444503dded/75a98d2db9d3847c73017e41637924b1cfd5a598);
413
+ - ✅ [`has-export-default`](https://putout.cloudcmd.io/#/gist/b50ccfe5cc8c0c97e2fc98b37903ade4/fbc026e6f1027581f7aa4879dcafcaa7754bf8f4);
414
+ - ✅ [`is-esm`](https://putout.cloudcmd.io/#/gist/fa080be2bf3a6560e289d84b5873c2bc/2601091f6bf97148843767968c3afcb36dde31de);
415
+
416
+ Let's consider file structure:
417
+
418
+ ```
419
+ /
420
+ |-- lib/
421
+ | `-- index.js "import a from './a.js';"
422
+ | `-- a.js "export const a = 2;"
423
+ ```
424
+
425
+ In this case `index.js` can be fixed:
426
+
427
+ #### ❌ Example of incorrect code
428
+
429
+ ```js
430
+ import a from './a.js';
431
+ ```
432
+
433
+ #### ✅ Example of correct code
434
+
435
+ ```js
436
+ import {a} from './a.js';
437
+ ```
438
+
402
439
  ### apply-namespace-import-to-file
403
440
 
404
441
  The rule fixes:
@@ -0,0 +1,16 @@
1
+ export const report = (path) => {
2
+ const source = path.node.source.value;
3
+ const {specifiers} = path.node;
4
+ const [{local}] = specifiers;
5
+ const {name} = local;
6
+
7
+ return `'import ${name} from "${source}"' -> 'import {${name}} from "${source}"'`;
8
+ };
9
+
10
+ export const replace = ({options}) => {
11
+ const {name, source} = options;
12
+
13
+ return {
14
+ [`import ${name} from "${source}"`]: `import {${name}} from "${source}"`,
15
+ };
16
+ };
@@ -0,0 +1,176 @@
1
+ import {join, dirname} from 'node:path';
2
+ import {tryCatch} from 'try-catch';
3
+ import putout, {
4
+ parse,
5
+ print,
6
+ transform,
7
+ operator,
8
+ } from 'putout';
9
+ import {createGetPrivateImports} from '#private-imports';
10
+ import * as isESMPlugin from '#is-esm';
11
+ import * as hasExportDefaultPlugin from '#has-export-default';
12
+ import * as getImportsPlugin from '#get-default-imports';
13
+ import * as applyNamedImportPlugin from './apply-named-import/index.js';
14
+
15
+ const {
16
+ findFile,
17
+ getFilename,
18
+ readFileContent,
19
+ writeFileContent,
20
+ } = operator;
21
+
22
+ const getMessage = (a) => a.message;
23
+ const isESM = (a) => a.rule === 'is-esm';
24
+ const hasExportDefault = (a) => a.rule === 'has-export-default';
25
+
26
+ export const report = (file, {name, source}) => {
27
+ const filename = getFilename(file);
28
+ return `Use 'import {${name}} from '${source}' in '${filename}'`;
29
+ };
30
+
31
+ export const fix = (file, {name, source, content, ast}) => {
32
+ transform(ast, content, {
33
+ rules: {
34
+ 'apply-named-import': ['on', {
35
+ name,
36
+ source,
37
+ }],
38
+ },
39
+ plugins: [
40
+ ['apply-named-import', applyNamedImportPlugin],
41
+ ],
42
+ });
43
+
44
+ const newContent = print(ast);
45
+
46
+ writeFileContent(file, newContent);
47
+ };
48
+
49
+ export const scan = (rootPath, {push, trackFile}) => {
50
+ const mask = [
51
+ '*.js',
52
+ '*.mjs',
53
+ ];
54
+
55
+ const getPrivateImports = createGetPrivateImports();
56
+
57
+ for (const file of trackFile(rootPath, mask)) {
58
+ const content = readFileContent(file);
59
+ const [error, ast] = tryCatch(parse, content);
60
+
61
+ if (error)
62
+ continue;
63
+
64
+ const importsTuples = getImports(file, content, ast);
65
+
66
+ const privateImports = getPrivateImports(file, {
67
+ aliasBased: true,
68
+ });
69
+
70
+ for (const [name, source, importedFilename] of importsTuples) {
71
+ const is = hasNamedImport({
72
+ name,
73
+ rootPath,
74
+ importedFilename,
75
+ privateImports,
76
+ });
77
+
78
+ if (!is)
79
+ continue;
80
+
81
+ push(file, {
82
+ name,
83
+ source,
84
+ ast,
85
+ content,
86
+ });
87
+ }
88
+ }
89
+ };
90
+
91
+ function getImports(file, content, ast) {
92
+ if (!content.includes('import'))
93
+ return [];
94
+
95
+ const places = transform(ast, content, {
96
+ fix: false,
97
+ plugins: [
98
+ ['get-imports', getImportsPlugin],
99
+ ],
100
+ });
101
+
102
+ const filename = getFilename(file);
103
+ const dir = dirname(filename);
104
+
105
+ const imports = places.map(getMessage);
106
+
107
+ return buildImports(dir, imports);
108
+ }
109
+
110
+ function parseImportedFilename({importedFilename, privateImports}) {
111
+ if (privateImports.has(importedFilename))
112
+ return privateImports.get(importedFilename);
113
+
114
+ return importedFilename;
115
+ }
116
+
117
+ function hasNamedImport({name, rootPath, importedFilename, privateImports}) {
118
+ const parsedName = parseImportedFilename({
119
+ importedFilename,
120
+ privateImports,
121
+ });
122
+
123
+ const [importedFile] = findFile(rootPath, parsedName);
124
+
125
+ if (!importedFile)
126
+ return false;
127
+
128
+ const importedContent = readFileContent(importedFile);
129
+
130
+ const {places} = putout(importedContent, {
131
+ fix: false,
132
+ plugins: [
133
+ ['has-export-default', hasExportDefaultPlugin],
134
+ ['is-esm', isESMPlugin],
135
+ ],
136
+ });
137
+
138
+ const esm = places.filter(isESM);
139
+
140
+ if (!esm.length)
141
+ return false;
142
+
143
+ const defaultExport = places.filter(hasExportDefault);
144
+
145
+ if (defaultExport.length)
146
+ return false;
147
+
148
+ for (const {message} of esm) {
149
+ const [, exportName] = message.split(':');
150
+
151
+ if (name === exportName)
152
+ return true;
153
+ }
154
+
155
+ return false;
156
+ }
157
+
158
+ function parseFull(dir, source) {
159
+ if (source.startsWith('#'))
160
+ return source;
161
+
162
+ return join(dir, source);
163
+ }
164
+
165
+ function buildImports(dir, imports) {
166
+ const list = [];
167
+
168
+ for (const current of imports) {
169
+ const [name, source] = current.split(' <- ');
170
+ const full = parseFull(dir, source);
171
+
172
+ list.push([name, source, full]);
173
+ }
174
+
175
+ return list;
176
+ }
@@ -7,10 +7,10 @@ import putout, {
7
7
  operator,
8
8
  } from 'putout';
9
9
  import {createGetPrivateImports} from '#private-imports';
10
- import * as isESMPlugin from './is-esm/index.js';
11
- import * as hasExportDefaultPlugin from './has-export-default/index.js';
10
+ import * as isESMPlugin from '#is-esm';
11
+ import * as hasExportDefaultPlugin from '#has-export-default';
12
+ import * as getImportsPlugin from '#get-default-imports';
12
13
  import * as applyNamespaceImportPlugin from './apply-namespace-import/index.js';
13
- import * as getImportsPlugin from './get-imports/index.js';
14
14
 
15
15
  const {
16
16
  findFile,
package/lib/index.js CHANGED
@@ -13,6 +13,7 @@ import * as removeEmptyExport from './remove-empty-export/index.js';
13
13
  import * as mergeDuplicateImports from './merge-duplicate-imports/index.js';
14
14
  import * as convertAssertToWith from './convert-assert-to-with/index.js';
15
15
  import * as applyNamespaceImportToFile from './apply-namespace-import-to-file/index.js';
16
+ import * as applyNamedImportToFile from './apply-named-import-to-file/index.js';
16
17
  import * as applyPrivatelyImportedFile from './apply-privately-imported-file/index.js';
17
18
  import * as resolveImportedFile from './resolve-imported-file/index.js';
18
19
  import * as shortenImportedFile from './shorten-imported-file/index.js';
@@ -34,6 +35,7 @@ export const rules = {
34
35
 
35
36
  'resolve-imported-file': ['off', resolveImportedFile],
36
37
  'shorten-imported-file': ['off', shortenImportedFile],
38
+ 'apply-named-import-to-file': ['off', applyNamedImportToFile],
37
39
  'apply-namespace-import-to-file': ['off', applyNamespaceImportToFile],
38
40
  'apply-privately-imported-file': ['off', applyPrivatelyImportedFile],
39
41
  'apply-js-imported-file': ['off', applyJsImportedFile],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@putout/plugin-esm",
3
- "version": "8.0.2",
3
+ "version": "8.1.0",
4
4
  "type": "module",
5
5
  "author": "coderaiser <mnemonic.enemy@gmail.com> (https://github.com/coderaiser)",
6
6
  "description": "🐊Putout plugin improves ability to transform ESM code",
@@ -12,8 +12,11 @@
12
12
  },
13
13
  "imports": {
14
14
  "#get-imports": "./lib/shorten-imported-file/get-imports/index.js",
15
+ "#get-default-imports": "./lib/apply-namespace-import-to-file/get-imports/index.js",
15
16
  "#change-imports": "./lib/resolve-imported-file/change-imports/index.js",
16
- "#private-imports": "./lib/apply-privately-imported-file/private-imports.js"
17
+ "#private-imports": "./lib/apply-privately-imported-file/private-imports.js",
18
+ "#is-esm": "./lib/apply-namespace-import-to-file/is-esm/index.js",
19
+ "#has-export-default": "./lib/apply-namespace-import-to-file/has-export-default/index.js"
17
20
  },
18
21
  "release": false,
19
22
  "tag": false,