@putout/plugin-variables 1.1.0 → 1.3.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
@@ -3,7 +3,11 @@
3
3
  [NPMIMGURL]: https://img.shields.io/npm/v/@putout/plugin-variables.svg?style=flat&longCache=true
4
4
  [NPMURL]: https://npmjs.org/package/@putout/plugin-variables "npm"
5
5
 
6
- 🐊[**Putout**](https://github.com/coderaiser/putout) plugin adds ability to find and remove `useless variables`.
6
+ > A **variable** is a named reference to a **value**.
7
+ >
8
+ > (c) [MDN](https://developer.mozilla.org/en-US/docs/Glossary/Variable)
9
+
10
+ 🐊[**Putout**](https://github.com/coderaiser/putout) plugin adds ability to transform `variables`.
7
11
 
8
12
  ## Install
9
13
 
@@ -13,24 +17,30 @@ npm i @putout/plugin-variables -D
13
17
 
14
18
  ## Rules
15
19
 
20
+ - ✅ [convert-const-to-let](#convert-const-to-let');
21
+ - ✅ [extract-keywords](#extract-keywords');
16
22
  - ✅ [remove-useless-assignment](#remove-useless-assignmenn);
17
23
  - ✅ [remove-useless-declaration](#remove-useless-declaration);
18
24
  - ✅ [remove-useless-duplicate](#remove-useless-duplicate);
19
25
  - ✅ [remove-useless-variables](#remove-useless-variables);
20
26
  - ✅ [remove-useless-rename](#remove-useless-rename);
27
+ - ✅ [split-declarations](#split-declarations);
21
28
 
22
29
  ## Config
23
30
 
24
31
  ```json
25
32
  {
26
33
  "rules": {
34
+ "variables/convert-const-to-let": "on",
35
+ "variables/extract-keywords": "on",
27
36
  "variables/remove-useless-assignment": "on",
28
37
  "variables/remove-useless-declaration": ["on", {
29
38
  "maxLength": 20
30
39
  }],
31
40
  "variables/remove-useless-duplicate": "on",
32
41
  "variables/remove-useless-rename": "on",
33
- "variables/remove-useless-remove": "on"
42
+ "variables/remove-useless-remove": "on",
43
+ "variables/split-declarations": "on"
34
44
  }
35
45
  }
36
46
  ```
@@ -157,10 +167,6 @@ bc = b.c.replace('x', 'y');
157
167
 
158
168
  ## remove-unreferenced
159
169
 
160
- > A **variable** is a named reference to a **value**.
161
- >
162
- > (c) [MDN](https://developer.mozilla.org/en-US/docs/Glossary/Variable)
163
-
164
170
  ### ❌ Example of incorrect code
165
171
 
166
172
  ```js
@@ -183,6 +189,123 @@ a = 5;
183
189
  console.log(a);
184
190
  ```
185
191
 
192
+ ## split-declarations
193
+
194
+ > - The [`let`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let) statement declares a block-scoped local variable, optionally initializing it to a value.
195
+ > - [`const`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const) statements are also block-scoped. The value of a constant can't be changed through reassignment, and it can't be redeclared. However, if a constant is an **object** or **array** its properties or items can be updated or removed.
196
+ >
197
+ > (c) MDN
198
+
199
+ Add ability to find and split variable declarations because (re)moving a line is simpler and less error prone then changing coma (`,`) to colon (`;`).
200
+ For the same reason, **diff** of changed declarations are more comfortable to read.
201
+
202
+ ### ❌ Example of incorrect code
203
+
204
+ ```js
205
+ let a, b;
206
+ ```
207
+
208
+ ### ✅ Example of correct code
209
+
210
+ ```js
211
+ let a;
212
+ let b;
213
+ ```
214
+
215
+ ### Comparison
216
+
217
+ Linter | Rule | Fix
218
+ --------|-------|------------|
219
+ 🐊 **Putout** | [`remove-debugger`](https://github.com/coderaiser/putout/tree/master/packages/plugin-split-variable-declarations#readme) | ✅
220
+ ⏣ **ESLint** | [`no-var`](https://eslint.org/docs/latest/rules/one-var) | ✅
221
+
222
+ ## convert-const-to-let
223
+
224
+ > The `TypeError` object represents an error when attempting to modify a value that cannot be changed.
225
+ >
226
+ > (c) [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError)
227
+
228
+ Convert `const` to `let` to avoid `TypeError`.
229
+ Check out in 🐊[**Putout Editor**](https://putout.cloudcmd.io/#/gist/61ffff64a356c47e66af4ea17a9a755d/e7f5fa455c208a7faa9319d94130996d39afcbf7).
230
+
231
+ ### ❌ Example of incorrect code
232
+
233
+ ```js
234
+ let a = 5;
235
+
236
+ a = 3;
237
+ ```
238
+
239
+ ### ✅ Example of correct code
240
+
241
+ ```js
242
+ let a = 5;
243
+
244
+ a = 3;
245
+ ```
246
+
247
+ ## remove-unused
248
+
249
+ > A variable is a container for a value, like a `number` we might use in a sum, or a `string` that we might use as part of a sentence.
250
+ >
251
+ > (c) [MDN](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Variables)
252
+
253
+ 🐊[**Putout**](https://github.com/coderaiser/putout) plugin adds ability to find and remove the variables that are declared, but:
254
+
255
+ - not passed as **argument** to a **function**;
256
+ - not used as **operand** in **expression**;
257
+
258
+ That is **unused variables**. Most likely it is a leftovers due to incomplete transforming of the code. Such variables take up space and gives no value so they must be removed.
259
+
260
+ ☝️*Remember, when you [writing a transform](https://github.com/coderaiser/putout/tree/master/packages/engine-runner#readme) you can skip all parts related to **removing unused variables** and just reuse current **plugin** it will make your code simpler and less error prone.*
261
+
262
+ ☝️*No, you cannot just look at [`referenced` and `constant` fields](https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md#user-content-bindings) to determine if you can remove variable and [here is why](https://putout.cloudcmd.io/#/gist/4277392f74b56b74911b779c9624af8d/cfec476f857dfb4f4c7a6247bdcc6b521fed8e70) one of the biggest plugins exists*.
263
+
264
+ ### ❌ Example of incorrect code
265
+
266
+ ```js
267
+ const a = 'hello';
268
+ const b = 'world';
269
+
270
+ console.log(a);
271
+ ```
272
+
273
+ ### ✅ Example of correct code
274
+
275
+ ```js
276
+ const a = 'hello';
277
+ console.log(a);
278
+ ```
279
+
280
+ ## Comparison
281
+
282
+ Linter | Rule | Fix
283
+ --------|-------|------------|
284
+ 🐊 **Putout**| [`remove-unused-variables`](https://github.com/coderaiser/putout/tree/master/packages/plugin-remove-unused-variables#readme)| ✅
285
+ ⏣ **ESLint** | [`no-unused-vars`](https://eslint.org/docs/rules/no-unused-vars) | ❌
286
+
287
+ ## extract-keywords
288
+
289
+ > The JavaScript exceptions "unexpected token" occur when the parser does not see a token it recognizes at the given position, so it cannot make sense of the structure of the program. This might be a simple typo.
290
+ >
291
+ > (c) [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Unexpected_token)
292
+
293
+ Extract `keywords` from variables. Check out in 🐊[**Putout Editor**](https://putout.cloudcmd.io/#/gist/fcaedaa9daf7f3a771274aca0da9ab1b/00850a5d28aec86b1b4083ba2ef9f81bd49aaaac).
294
+
295
+ ```diff
296
+ -export const isTemplateMiddle = (a) => a?.type === 'TemplateMiddle',
297
+ +export const isTemplateMiddle = (a) => a?.type === 'TemplateMiddle';
298
+ export const isTemplateTail = (a) => a?.type === 'TemplateTail';
299
+
300
+ -const a 5;
301
+ +const a = 5;
302
+
303
+ -export const packContent = (content) {
304
+ +export const packContent = (content) => {
305
+ console.log(a);
306
+ }
307
+ ```
308
+
186
309
  ## License
187
310
 
188
311
  MIT
@@ -0,0 +1,69 @@
1
+ import {types} from 'putout';
2
+
3
+ const {
4
+ isBlockStatement,
5
+ isProgram,
6
+ } = types;
7
+
8
+ const {values} = Object;
9
+
10
+ const isKeyword = (a) => [
11
+ 'export',
12
+ 'const',
13
+ 'let',
14
+ 'var',
15
+ ].includes(a);
16
+
17
+ const isInsideBlock = ({parentPath}) => isProgram(parentPath) || isBlockStatement(parentPath);
18
+
19
+ export const report = () => `Use 'let' when reassign`;
20
+
21
+ export const fix = (path) => {
22
+ path.node.kind = 'let';
23
+ };
24
+
25
+ export const traverse = ({push}) => ({
26
+ VariableDeclaration: (path) => {
27
+ if (path.parentPath.isTSModuleBlock())
28
+ return;
29
+
30
+ const {scope} = path;
31
+ const {declare} = path.node;
32
+
33
+ if (declare)
34
+ return;
35
+
36
+ for (const binding of values(scope.bindings)) {
37
+ const {parentPath, node} = binding.path;
38
+ const {init} = node;
39
+
40
+ if (init && binding.constant)
41
+ continue;
42
+
43
+ if (isLoop(parentPath) && binding.constant)
44
+ continue;
45
+
46
+ if (!binding.path.isVariableDeclarator())
47
+ continue;
48
+
49
+ if (isKeyword(binding.path.node.id.name))
50
+ continue;
51
+
52
+ if (!binding.path.node.init && isInsideBlock(binding.path.parentPath))
53
+ continue;
54
+
55
+ if (parentPath.node.kind === 'const')
56
+ push(binding.path.parentPath);
57
+ }
58
+ },
59
+ });
60
+
61
+ const isLoop = ({parentPath}) => {
62
+ if (!parentPath)
63
+ return false;
64
+
65
+ if (parentPath.isForOfStatement())
66
+ return true;
67
+
68
+ return parentPath.isForInStatement();
69
+ };
@@ -0,0 +1,178 @@
1
+ import {types, operator} from 'putout';
2
+
3
+ const {
4
+ ifStatement,
5
+ importDefaultSpecifier,
6
+ importDeclaration,
7
+ exportNamedDeclaration,
8
+ variableDeclarator,
9
+ variableDeclaration,
10
+ isExportDeclaration,
11
+ isArrowFunctionExpression,
12
+ isLiteral,
13
+ isAssignmentExpression,
14
+ isExportNamedDeclaration,
15
+ isIdentifier,
16
+ isImportDeclaration,
17
+ isMemberExpression,
18
+ isBlockStatement,
19
+ arrowFunctionExpression,
20
+ } = types;
21
+
22
+ const {
23
+ removeParens,
24
+ remove,
25
+ replaceWith,
26
+ isDeclarationKeyword,
27
+ isConditionKeyword,
28
+ isModuleDeclarationKeyword,
29
+ } = operator;
30
+
31
+ const isInit = (a) => isIdentifier(a) || isLiteral(a) || isMemberExpression(a) || isArrowFunctionExpression(a);
32
+
33
+ const buildDeclaration = (type) => (nextPath, path) => {
34
+ const {expression} = nextPath.node;
35
+ let left;
36
+ let right;
37
+
38
+ if (isBlockStatement(nextPath)) {
39
+ left = path.node.id;
40
+ const {node: init} = removeParens(path.get('init'));
41
+ const params = [init];
42
+
43
+ right = arrowFunctionExpression(params, nextPath.node);
44
+ } else if (isAssignmentExpression(expression)) {
45
+ ({
46
+ left,
47
+ right,
48
+ } = expression);
49
+ } else {
50
+ left = path.node.id;
51
+ right = nextPath.node.expression;
52
+ }
53
+
54
+ replaceWith(nextPath, variableDeclaration(type, [variableDeclarator(left, right)]));
55
+
56
+ const {name} = path.node.id;
57
+
58
+ if (isDeclarationKeyword(name))
59
+ return;
60
+
61
+ if (isExportNamedDeclaration(path.parentPath.parentPath))
62
+ replaceWith(nextPath, exportNamedDeclaration(nextPath.node));
63
+ };
64
+
65
+ const builders = {
66
+ const: buildDeclaration('const'),
67
+ var: buildDeclaration('var'),
68
+ let: buildDeclaration('let'),
69
+ import: buildImport,
70
+ export: buildExport,
71
+ if: buildIf,
72
+ };
73
+
74
+ export const report = ({name}) => `Extract '${name}' from variable`;
75
+
76
+ export const fix = ({name, path, nextPath}) => {
77
+ builders[name](nextPath, path);
78
+ remove(path);
79
+ };
80
+
81
+ export const traverse = ({push}) => ({
82
+ VariableDeclarator(path) {
83
+ const {name} = path.node.id;
84
+
85
+ if (isDeclarationKeyword(name) || isConditionKeyword(name) || isModuleDeclarationKeyword(name)) {
86
+ const topPath = getTopPath(path);
87
+ const nextPath = topPath.getNextSibling();
88
+
89
+ if (nextPath.isVariableDeclaration())
90
+ push({
91
+ name,
92
+ path,
93
+ nextPath,
94
+ });
95
+
96
+ if (nextPath.isExpressionStatement())
97
+ push({
98
+ name,
99
+ path,
100
+ nextPath,
101
+ });
102
+
103
+ return;
104
+ }
105
+
106
+ const {kind} = path.parentPath.node;
107
+
108
+ if (!path.node.init) {
109
+ const topPath = getTopPath(path);
110
+ const nextPath = topPath.getNextSibling();
111
+
112
+ if (!nextPath.isExpressionStatement())
113
+ return;
114
+
115
+ const {expression} = nextPath.node;
116
+
117
+ if (isInit(expression))
118
+ push({
119
+ name: kind,
120
+ path,
121
+ nextPath,
122
+ });
123
+
124
+ return;
125
+ }
126
+
127
+ if (isExportDeclaration(path.parentPath.parentPath)) {
128
+ const nextPath = path.parentPath.parentPath.getNextSibling();
129
+
130
+ if (!isBlockStatement(nextPath))
131
+ return;
132
+
133
+ if (!nextPath.node.body.length)
134
+ return;
135
+
136
+ const count = nextPath.node.body.filter(isImportDeclaration).length;
137
+
138
+ if (!count)
139
+ push({
140
+ name: kind,
141
+ path,
142
+ nextPath,
143
+ });
144
+ }
145
+ },
146
+ });
147
+
148
+ function getTopPath(path) {
149
+ if (path.parentPath.parentPath.isExportDeclaration())
150
+ return path.parentPath.parentPath;
151
+
152
+ return path.parentPath;
153
+ }
154
+
155
+ function buildExport(path) {
156
+ replaceWith(path, exportNamedDeclaration(path.node));
157
+ }
158
+
159
+ function buildImport(path) {
160
+ const fromPath = path.getNextSibling();
161
+ const sourcePath = fromPath.getNextSibling();
162
+ const source = sourcePath.node.expression;
163
+ const local = path.node.expression;
164
+
165
+ replaceWith(path, importDeclaration([importDefaultSpecifier(local, local)], source));
166
+ remove(sourcePath);
167
+ remove(fromPath);
168
+ }
169
+
170
+ function buildIf(path) {
171
+ const {expression} = path.node;
172
+ delete expression.extra.parenthesized;
173
+ const nextPath = path.getNextSibling();
174
+ const next = nextPath.node;
175
+
176
+ remove(nextPath);
177
+ replaceWith(path, ifStatement(expression, next));
178
+ }
package/lib/index.js CHANGED
@@ -1,15 +1,23 @@
1
+ import * as convertConstToLet from './convert-const-to-let/index.js';
2
+ import * as extractKeywords from './extract-keywords/index.js';
1
3
  import * as removeUseless from './remove-useless/index.js';
2
4
  import * as removeUselessAssignment from './remove-useless-assignment/index.js';
3
5
  import * as removeUselessDeclarations from './remove-useless-declarations/index.js';
4
6
  import * as removeUselessDuplicates from './remove-useless-duplicates/index.js';
5
7
  import * as removeUselessRename from './remove-useless-rename/index.js';
6
8
  import * as removeUnreferenced from './remove-unreferenced/index.js';
9
+ import * as removeUnused from './remove-unused/index.js';
10
+ import * as splitDeclarations from './split-declarations/index.js';
7
11
 
8
12
  export const rules = {
13
+ 'convert-const-to-let': convertConstToLet,
14
+ 'extract-keywords': extractKeywords,
9
15
  'remove-useless': removeUseless,
10
16
  'remove-useless-assignment': removeUselessAssignment,
11
17
  'remove-useless-declarations': removeUselessDeclarations,
12
18
  'remove-useless-duplicates': removeUselessDuplicates,
13
19
  'remove-useless-rename': removeUselessRename,
14
20
  'remove-unreferenced': removeUnreferenced,
21
+ 'remove-unused': removeUnused,
22
+ 'split-declarations': splitDeclarations,
15
23
  };
@@ -4,7 +4,9 @@ const {remove} = operator;
4
4
 
5
5
  export const report = () => 'Avoid unreferenced variables';
6
6
 
7
- export const fix = (path) => remove(path);
7
+ export const fix = (path) => {
8
+ remove(path);
9
+ };
8
10
 
9
11
  export const traverse = ({push}) => ({
10
12
  'return __a'(path) {
@@ -95,4 +97,9 @@ function getPropertyPath(path, name) {
95
97
  return propPath;
96
98
  }
97
99
 
98
- const isInsideForOf = (path) => path.__putout_for_of_reduce;
100
+ function isInsideForOf(path) {
101
+ if (path.isForOfStatement())
102
+ return true;
103
+
104
+ return path.__putout_for_of_reduce;
105
+ }
@@ -0,0 +1,5 @@
1
+ const onlyDeclared = ({declared, used}) => declared && !used;
2
+
3
+ export default (items) => {
4
+ return items.filter(onlyDeclared);
5
+ };