@putout/printer 1.3.1 → 1.4.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/ChangeLog ADDED
@@ -0,0 +1,15 @@
1
+ 2023.03.17, v1.4.1
2
+
3
+ feature:
4
+ - 1948ce5 @putout/printer: improve support of ObjectExpression
5
+ - 09e1a5c @putout/printer: improve support of ObjectExpression
6
+ - 2d13bc2 @putout/printer: ArrayExpression: newlines
7
+ - 9fc6a00 @putout/printer: improve support of ArrayExpression
8
+ - b91d908 @putout/printer: UnaryExpression: add support of delete
9
+ - 337492b @putout/printer: improve support of NumericLiteral
10
+ - 166085c @putout/printer: add support of hex numbers
11
+
12
+ 2023.03.17, v1.4.0
13
+
14
+ feature:
15
+ - 1faf3ff @putout/printer: add support of leadingComments
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [NPMIMGURL]: https://img.shields.io/npm/v/@putout/printer.svg?style=flat&longCache=true
4
4
  [NPMURL]: https://npmjs.org/package/@putout/printer "npm"
5
5
 
6
- **EasyPrint** prints [**Babel AST**](https://github.com/coderaiser/estree-to-babel) to readable **JavaScript**.
6
+ **@putout/printer** prints [**Babel AST**](https://github.com/coderaiser/estree-to-babel) to readable **JavaScript**.
7
7
 
8
8
  - ☝️ Similar to **Recast**, but simpler and easier in maintenance, since it supports only **Babel**.
9
9
  - ☝️ As opinionated as **Prettier**, but has more user-friendly output and works directly with **AST**.
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ module.exports.parseComments = (path, {write}) => {
4
+ const {leadingComments} = path.node;
5
+
6
+ if (leadingComments)
7
+ parseLeadingComments(path, {write});
8
+ };
9
+
10
+ function parseLeadingComments(path, {write}) {
11
+ const {leadingComments} = path.node;
12
+
13
+ for (const {value}of leadingComments) {
14
+ write(`//${value}`);
15
+ write.newline();
16
+ }
17
+ }
@@ -1,16 +1,17 @@
1
1
  'use strict';
2
2
 
3
+ const {isObjectProperty} = require('@babel/types');
3
4
  const {entries} = Object;
4
5
  const isForOf = ({parentPath}) => parentPath.isForOfStatement();
5
6
 
6
- module.exports.ArrayExpression = (path, {write, indent, maybe, traverse}) => {
7
- write('[');
8
-
7
+ module.exports.ArrayExpression = (path, {write, maybe, traverse}) => {
9
8
  const elements = path.get('elements');
9
+ const elementIsObject = isElementObject(path);
10
10
 
11
- indent.inc();
11
+ write('[');
12
+ maybe.indent.inc(!elementIsObject);
12
13
 
13
- const isNewLine = !isNumbers(elements) && !isForOf(path);
14
+ const isNewLine = !isNumbers(elements) && !isForOf(path) && isLastArg(path) && !isParentProperty(path);
14
15
  const n = elements.length - 1;
15
16
 
16
17
  maybe.write(isNewLine && elements.length, '\n');
@@ -24,8 +25,7 @@ module.exports.ArrayExpression = (path, {write, indent, maybe, traverse}) => {
24
25
  maybe.write(!isNewLine && index < n, ', ');
25
26
  }
26
27
 
27
- indent.dec();
28
-
28
+ maybe.indent.dec(!elementIsObject);
29
29
  maybe.indent(elements.length && isNewLine);
30
30
 
31
31
  write(']');
@@ -40,3 +40,27 @@ function isNumbers(elements) {
40
40
  return false;
41
41
  }
42
42
 
43
+ function isLastArg(path) {
44
+ const {parentPath} = path;
45
+
46
+ if (!parentPath.isCallExpression())
47
+ return true;
48
+
49
+ const args = parentPath.get('arguments');
50
+ const n = args.length - 1;
51
+
52
+ return path === args[n];
53
+ }
54
+
55
+ function isParentProperty(path) {
56
+ return path.find(isObjectProperty);
57
+ }
58
+
59
+ function isElementObject(path) {
60
+ const elements = path.get('elements');
61
+
62
+ if (!elements.length)
63
+ return false;
64
+
65
+ return elements[0].isObjectExpression();
66
+ }
@@ -9,6 +9,7 @@ module.exports.ArrayPattern = (path, {write, indent, maybe, traverse}) => {
9
9
  const elements = path.get('elements');
10
10
 
11
11
  indent.inc();
12
+
12
13
  const isNewLine = !isForOf(path) && elements.length > 2;
13
14
  const n = elements.length - 1;
14
15
 
@@ -1,16 +1,17 @@
1
1
  'use strict';
2
2
 
3
- const {hasPrevNewline, isMarkedParent} = require('../mark');
3
+ const {
4
+ isMarkedParentBefore,
5
+ isMarkedPrevAfter,
6
+ } = require('../mark');
4
7
 
5
8
  const {entries} = Object;
6
9
 
7
10
  module.exports.CallExpression = (path, {traverse, indent, write, incIndent, decIndent, maybeWrite}) => {
8
11
  const isParentCall = toLong(path) && path.parentPath.isCallExpression();
9
12
 
10
- if (shouldAddNewLine(path) && !hasPrevNewline(path.parentPath) && !isMarkedParent(path)) {
11
- write.newline();
12
- write.indent();
13
- }
13
+ if (shouldAddNewLine(path) && !isMarkedParentBefore(path) && !isMarkedPrevAfter(path.parentPath))
14
+ write.linebreak();
14
15
 
15
16
  traverse(path.get('callee'));
16
17
  write('(');
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const {isMarkedPrevAfter} = require('../mark');
3
4
  const isFirst = (path) => path.node === path.parentPath.node.body[0];
4
5
 
5
6
  module.exports.ArrowFunctionExpression = (path, {write, traverse}) => {
@@ -38,7 +39,7 @@ module.exports.ObjectMethod = (path, {write, traverse}) => {
38
39
  };
39
40
 
40
41
  module.exports.FunctionDeclaration = (path, {write, traverse}) => {
41
- if (!isFirst(path))
42
+ if (!isFirst(path) && !isMarkedPrevAfter(path))
42
43
  write('\n');
43
44
 
44
45
  write('function ');
@@ -13,8 +13,8 @@ const {AssignmentExpression} = require('./assignment-expression');
13
13
  const {ArrayExpression} = require('./array-expression');
14
14
  const {ArrayPattern} = require('./array-pattern');
15
15
  const {AssignmentPattern} = require('./assignment-pattern');
16
- const {RestElement} = require('./RestElement');
17
- const {SpreadElement} = require('./SpreadElement');
16
+ const {RestElement} = require('./rest-element');
17
+ const {SpreadElement} = require('./spread-element');
18
18
 
19
19
  module.exports = {
20
20
  ...functions,
@@ -1,36 +1,33 @@
1
1
  'use strict';
2
2
 
3
+ const {isCoupleLines} = require('../is');
3
4
  const isBodyOfArrow = (path) => path.parentPath.node.body === path.node;
4
5
  const isForOf = (path) => {
5
6
  if (path.parentPath.isForOfStatement())
6
7
  return true;
7
8
 
8
- if (path.parentPath?.parentPath?.isForOfStatement())
9
- return true;
10
-
11
- return false;
9
+ return path.parentPath?.parentPath?.isForOfStatement();
12
10
  };
13
11
 
14
12
  module.exports.ObjectExpression = (path, {traverse, write, maybe, indent}) => {
15
13
  indent.inc();
16
14
 
17
15
  const properties = path.get('properties');
18
- const parens = isBodyOfArrow(path);
19
- const isCall = path.parentPath.isCallExpression();
20
16
  const {length} = properties;
21
- const isOneLine = !length || (isCall && length < 2 || isForOf(path));
17
+ const parens = isBodyOfArrow(path);
18
+ const manyLines = !isOneLine(path);
22
19
 
23
20
  maybe.write(parens, '(');
24
21
  write('{');
25
22
 
26
- maybe.write(!isOneLine, '\n');
23
+ maybe.write(manyLines, '\n');
27
24
 
28
25
  for (const property of properties) {
29
26
  if (property.isSpreadElement()) {
30
- maybe.indent(properties.length > 1);
27
+ maybe.indent(length > 1);
31
28
  traverse(property);
32
29
 
33
- if (properties.length > 1) {
30
+ if (length > 1) {
34
31
  write(',');
35
32
  write.newline();
36
33
  }
@@ -40,7 +37,7 @@ module.exports.ObjectExpression = (path, {traverse, write, maybe, indent}) => {
40
37
 
41
38
  const {shorthand, computed} = property.node;
42
39
 
43
- maybe.indent(!isOneLine);
40
+ maybe.indent(manyLines);
44
41
 
45
42
  if (property.isObjectMethod()) {
46
43
  traverse(property);
@@ -56,11 +53,27 @@ module.exports.ObjectExpression = (path, {traverse, write, maybe, indent}) => {
56
53
  traverse(property.get('value'));
57
54
  }
58
55
 
59
- maybe.write(!isOneLine, ',\n');
56
+ maybe.write(manyLines, ',\n');
60
57
  }
61
58
 
62
59
  indent.dec();
63
- maybe.indent(!isOneLine);
60
+
61
+ maybe.indent(manyLines);
64
62
  write('}');
65
63
  maybe.write(parens, ')');
66
64
  };
65
+ function isOneLine(path) {
66
+ const isCall = path.parentPath.isCallExpression();
67
+ const {length} = path.get('properties');
68
+
69
+ if (isCoupleLines(path))
70
+ return false;
71
+
72
+ if (!length)
73
+ return true;
74
+
75
+ if (isCall && length < 2)
76
+ return true;
77
+
78
+ return isForOf(path);
79
+ }
@@ -3,12 +3,15 @@
3
3
  module.exports.UnaryExpression = unaryExpressions;
4
4
  module.exports.UpdateExpression = unaryExpressions;
5
5
 
6
- function unaryExpressions(path, {traverse, write}) {
6
+ const isWord = (a) => /delete|typeof/.test(a);
7
+
8
+ function unaryExpressions(path, {traverse, write, maybe}) {
7
9
  const {prefix, operator} = path.node;
8
10
 
9
11
  if (prefix)
10
12
  write(operator);
11
13
 
14
+ maybe.write(isWord(operator), ' ');
12
15
  traverse(path.get('argument'));
13
16
 
14
17
  if (!prefix)
@@ -1,4 +1,11 @@
1
1
  'use strict';
2
2
 
3
+ module.exports.isFirst = (path) => path.node === path.parentPath.node.body[0];
3
4
  module.exports.isPrevBody = (path) => path.getPrevSibling().isBlockStatement();
4
5
  module.exports.isNext = (path) => path.getNextSibling().node;
6
+ module.exports.isCoupleLines = (path) => {
7
+ const start = path.node?.loc?.start.line;
8
+ const end = path.node?.loc?.end.line;
9
+
10
+ return end > start;
11
+ };
@@ -5,7 +5,8 @@ const {TemplateLiteral} = require('./template-literal');
5
5
  module.exports = {
6
6
  TemplateLiteral,
7
7
  NumericLiteral(path, {write}) {
8
- write(path.node.value);
8
+ const {extra, raw} = path.node;
9
+ write(raw || extra.raw);
9
10
  },
10
11
  BooleanLiteral(path, {write}) {
11
12
  write(path.node.value);
@@ -1,25 +1,40 @@
1
1
  'use strict';
2
2
 
3
- const WATER_MARK = '__putout_newline';
3
+ const WATER_MARK_BEFORE = '__putout_newline_before';
4
+ const WATER_MARK_AFTER = '__putout_newline_after';
4
5
 
5
- module.exports.mark = mark;
6
+ module.exports.markBefore = markBefore;
7
+ module.exports.markAfter = markAfter;
6
8
 
7
- function mark(path) {
8
- path[WATER_MARK] = true;
9
+ function markBefore(path) {
10
+ path[WATER_MARK_BEFORE] = true;
9
11
  }
10
12
 
11
- module.exports.isMarked = isMarked;
13
+ function markAfter(path) {
14
+ path[WATER_MARK_AFTER] = true;
15
+ }
16
+
17
+ module.exports.isMarkedBefore = isMarkedBefore;
18
+ module.exports.isMarkedAfter = isMarkedAfter;
19
+
20
+ function isMarkedBefore(path) {
21
+ return path[WATER_MARK_BEFORE];
22
+ }
12
23
 
13
- function isMarked(path) {
14
- return path[WATER_MARK];
24
+ function isMarkedAfter(path) {
25
+ return path[WATER_MARK_AFTER];
15
26
  }
16
27
 
17
28
  module.exports.hasPrevNewline = (path) => {
18
- return isMarked(path.getPrevSibling());
29
+ return isMarkedAfter(path.getPrevSibling());
19
30
  };
20
31
 
21
- module.exports.maybeMark = (a, path) => a && mark(path);
32
+ module.exports.maybeMarkAfter = (a, path) => a && markAfter(path);
33
+
34
+ module.exports.isMarkedParentBefore = (path) => {
35
+ return isMarkedBefore(path.parentPath);
36
+ };
22
37
 
23
- module.exports.isMarkedParent = (path) => {
24
- return isMarked(path.parentPath);
38
+ module.exports.isMarkedPrevAfter = (path) => {
39
+ return isMarkedAfter(path.getPrevSibling());
25
40
  };
@@ -1,10 +1,16 @@
1
1
  'use strict';
2
2
 
3
- const {mark} = require('../mark');
3
+ const {
4
+ markBefore,
5
+ markAfter,
6
+ isMarkedPrevAfter,
7
+ } = require('../mark');
8
+ const {isNext} = require('../is');
9
+
4
10
  module.exports.ExpressionStatement = (path, {write, indent, traverse}) => {
5
- if (isCoupleLinesExpression(path) && !isFirst(path) && shouldAddNewLine(path)) {
11
+ if (isCoupleLinesExpression(path) && !isFirst(path) && shouldAddNewLine(path) && !isMarkedPrevAfter(path)) {
6
12
  write.linebreak();
7
- mark(path);
13
+ markBefore(path);
8
14
  }
9
15
 
10
16
  const expressionPath = path.get('expression');
@@ -14,8 +20,10 @@ module.exports.ExpressionStatement = (path, {write, indent, traverse}) => {
14
20
  write(';');
15
21
  write.newline();
16
22
 
17
- if (isStrictMode(path))
23
+ if (isStrictMode(path) || isCoupleLinesExpression(path) && isNext(path)) {
18
24
  write.newline();
25
+ markAfter(path);
26
+ }
19
27
  };
20
28
 
21
29
  function isCoupleLinesExpression(path) {
@@ -1,17 +1,10 @@
1
1
  'use strict';
2
2
 
3
- const {isMarked} = require('../mark');
4
- const isPrevNewLine = (path) => {
5
- const prev = path.getPrevSibling();
6
-
7
- if (!prev.node)
8
- return true;
9
-
10
- return isMarked(prev);
11
- };
3
+ const {isMarkedPrevAfter} = require('../mark');
4
+ const {isFirst} = require('../is');
12
5
 
13
6
  module.exports.ForOfStatement = (path, {write, indent, traverse}) => {
14
- if (!isPrevNewLine(path)) {
7
+ if (!isFirst(path) && !isMarkedPrevAfter(path)) {
15
8
  write.indent();
16
9
  write.newline();
17
10
  }
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const {hasPrevNewline} = require('../mark');
4
+ const {isFirst} = require('../is');
4
5
 
5
6
  module.exports.IfStatement = (path, {write, indent, traverse, incIndent, decIndent}) => {
6
7
  if (!isFirst(path) && !hasPrevNewline(path)) {
@@ -36,7 +37,3 @@ module.exports.IfStatement = (path, {write, indent, traverse, incIndent, decInde
36
37
  write('\n');
37
38
  };
38
39
 
39
- function isFirst(path) {
40
- return path.node === path.parentPath.node.body[0];
41
- }
42
-
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const {isNext} = require('../is');
3
+ const {isNext, isCoupleLines} = require('../is');
4
4
  const isNextAssign = (path) => {
5
5
  const nextPath = path.getNextSibling();
6
6
 
@@ -27,21 +27,25 @@ module.exports.VariableDeclaration = (path, {write, maybe, maybeIndent, traverse
27
27
  traverse(initPath);
28
28
  maybe.write(isParentBlock, ';\n');
29
29
 
30
- const is = isNext(path) && isCoupleLinesExpression(path) && !isNextAssign(path);
30
+ const is = isNext(path) && isCoupleLines(path) && !isNextAssign(path);
31
31
 
32
32
  maybe.indent(is);
33
33
  maybe.write(is, '\n');
34
- maybe.mark(is, path);
34
+ maybe.markAfter(is, path);
35
35
  };
36
- function isCoupleLinesExpression(path) {
37
- const start = path.node?.loc?.start.line;
38
- const end = path.node?.loc?.end.line;
39
-
40
- return end > start;
41
- }
42
36
 
43
37
  function shouldAddNewLine(path) {
44
38
  const prevPath = path.getPrevSibling();
39
+
40
+ if (prevPath.isStatement() && !prevPath.isExpressionStatement() && !prevPath.isBlockStatement())
41
+ return false;
42
+
43
+ if (prevPath.isExpressionStatement() && prevPath.get('expression').isStringLiteral())
44
+ return false;
45
+
46
+ if (isCoupleLines(path))
47
+ return true;
48
+
45
49
  const nextPath = path.getNextSibling();
46
50
  const nextNextPath = nextPath.getNextSibling();
47
51
 
@@ -50,12 +54,6 @@ function shouldAddNewLine(path) {
50
54
  if (!twoNext)
51
55
  return false;
52
56
 
53
- if (prevPath.isVariableDeclaration() || prevPath.isExpressionStatement() && prevPath.get('expression').isStringLiteral())
54
- return false;
55
-
56
- if (prevPath.isIfStatement())
57
- return false;
58
-
59
57
  return true;
60
58
  }
61
59
 
@@ -6,7 +6,11 @@ const statements = require('./statements');
6
6
  const literals = require('./literals');
7
7
  const {TYPES} = require('../types');
8
8
  const {createDebug} = require('./debug');
9
- const {maybeMark} = require('./mark');
9
+ const {
10
+ maybeMarkAfter,
11
+ maybeMarkBefore,
12
+ } = require('./mark');
13
+ const {parseComments} = require('./comments');
10
14
 
11
15
  const {assign} = Object;
12
16
 
@@ -28,11 +32,12 @@ module.exports.tokenize = (ast) => {
28
32
 
29
33
  const maybeWrite = (a, b) => a && write(b);
30
34
  const maybeIndent = (a) => a && indent();
35
+ const maybeIndentInc = (a) => a && indent.inc();
36
+ const maybeIndentDec = (a) => a && indent.dec();
31
37
 
32
38
  let i = 0;
33
39
  const incIndent = () => ++i;
34
40
  const decIndent = () => --i;
35
-
36
41
  const indent = () => {
37
42
  tokens.push({
38
43
  type: TYPES.INDENT,
@@ -68,9 +73,15 @@ module.exports.tokenize = (ast) => {
68
73
  const maybe = {
69
74
  write: maybeWrite,
70
75
  indent: maybeIndent,
71
- mark: maybeMark,
76
+ markBefore: maybeMarkBefore,
77
+ markAfter: maybeMarkAfter,
72
78
  };
73
79
 
80
+ assign(maybe.indent, {
81
+ inc: maybeIndentInc,
82
+ dec: maybeIndentDec,
83
+ });
84
+
74
85
  const printer = {
75
86
  incIndent,
76
87
  decIndent,
@@ -101,6 +112,7 @@ module.exports.tokenize = (ast) => {
101
112
  if (!currentTraverse)
102
113
  throw Error(`Node type '${type}' is not supported yet: '${path}'`);
103
114
 
115
+ parseComments(path, printer);
104
116
  currentTraverse(path, printer);
105
117
  debug(path.type);
106
118
  }
package/package.json CHANGED
@@ -1,14 +1,11 @@
1
1
  {
2
2
  "name": "@putout/printer",
3
- "version": "1.3.1",
3
+ "version": "1.4.1",
4
4
  "type": "commonjs",
5
5
  "author": "coderaiser <mnemonic.enemy@gmail.com> (https://github.com/coderaiser)",
6
6
  "description": "Easiest possible opinionated Babel AST printer made with ❤️ to use in 🐊Putout",
7
7
  "homepage": "https://github.com/putoutjs/printer#readme",
8
8
  "main": "./lib/printer.js",
9
- "release": false,
10
- "tag": false,
11
- "changelog": false,
12
9
  "exports": {
13
10
  ".": "./lib/printer.js"
14
11
  },
@@ -25,11 +22,13 @@
25
22
  "lint:fresh": "madrun lint:fresh",
26
23
  "fix:lint": "madrun fix:lint",
27
24
  "coverage": "madrun coverage",
25
+ "coverage:html": "madrun coverage:html",
28
26
  "report": "madrun report"
29
27
  },
30
28
  "dependencies": {
31
29
  "@babel/parser": "^7.19.0",
32
30
  "@babel/traverse": "^7.21.2",
31
+ "@babel/types": "^7.21.3",
33
32
  "just-snake-case": "^3.2.0"
34
33
  },
35
34
  "keywords": [
@@ -46,6 +45,7 @@
46
45
  "eslint": "^8.0.1",
47
46
  "eslint-plugin-n": "^15.2.4",
48
47
  "eslint-plugin-putout": "^17.0.0",
48
+ "estree-to-babel": "^5.0.1",
49
49
  "just-kebab-case": "^4.2.0",
50
50
  "lerna": "^6.0.1",
51
51
  "madrun": "^9.0.0",