@putout/plugin-esm 8.0.1 → 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,
@@ -69,6 +69,7 @@ export const scan = (rootPath, {push, trackFile}) => {
69
69
 
70
70
  for (const [name, source, importedFilename] of importsTuples) {
71
71
  const is = hasImportDefault({
72
+ name,
72
73
  rootPath,
73
74
  importedFilename,
74
75
  privateImports,
@@ -113,7 +114,7 @@ function parseImportedFilename({importedFilename, privateImports}) {
113
114
  return importedFilename;
114
115
  }
115
116
 
116
- function hasImportDefault({rootPath, importedFilename, privateImports}) {
117
+ function hasImportDefault({name, rootPath, importedFilename, privateImports}) {
117
118
  const parsedName = parseImportedFilename({
118
119
  importedFilename,
119
120
  privateImports,
@@ -140,6 +141,13 @@ function hasImportDefault({rootPath, importedFilename, privateImports}) {
140
141
  if (defaultExport.length)
141
142
  return true;
142
143
 
144
+ for (const {message} of esm) {
145
+ const [, exportName] = message.split(':');
146
+
147
+ if (name === exportName)
148
+ return true;
149
+ }
150
+
143
151
  return !esm.length;
144
152
  }
145
153
 
@@ -1,4 +1,37 @@
1
- export const report = (path) => path.type;
1
+ import {types} from 'putout';
2
+
3
+ const {
4
+ isIdentifier,
5
+ isVariableDeclaration,
6
+ isFunction,
7
+ isExportNamedDeclaration,
8
+ } = types;
9
+
10
+ export const report = (path) => {
11
+ const {type} = path;
12
+
13
+ if (isExportNamedDeclaration(path)) {
14
+ const {declaration} = path.node;
15
+
16
+ if (isFunction(declaration))
17
+ return `${type}:${declaration.id.name}`;
18
+
19
+ if (isVariableDeclaration(declaration)) {
20
+ const {declarations} = declaration;
21
+
22
+ if (declarations.length !== 1)
23
+ return type;
24
+
25
+ const [first] = declarations;
26
+ const {id} = first;
27
+
28
+ if (isIdentifier(id))
29
+ return `${type}:${id.name}`;
30
+ }
31
+ }
32
+
33
+ return type;
34
+ };
2
35
 
3
36
  export const fix = (path) => {
4
37
  path.node.leadingComments = [
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.1",
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,