@putout/printer 18.7.9 → 18.8.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 CHANGED
@@ -1,3 +1,19 @@
1
+ 2026.03.19, v18.8.1
2
+
3
+ feature:
4
+ - cbc16cf6 @putout/printer: ObjectPattern: isBreaklineBeforeProperty
5
+ - a78cf04e @putout/printer: ObjectPattern: isCommaAfterProperty
6
+ - c5b21af0 @putout/printer: ObjectPattern: simplify
7
+ - 394ff5e4 @putout/printer: ObjectPattern: calculateLongAssignPattern: simplify
8
+ - 42c8dfd4 @putout/printer: ObjectPattern: isIndentBeforeProperty
9
+
10
+ 2026.03.18, v18.8.0
11
+
12
+ feature:
13
+ - cc719056 @putout/printer: ArrayExpression: newline
14
+ - 53cdda91 @putout/printer: maxElementsInOneLine: 3
15
+ - e5d67901 @putout/printer: ObjectPattern: isPrevAssignObject: simplify
16
+
1
17
  2026.03.18, v18.7.9
2
18
 
3
19
  feature:
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  isCoupleLines,
3
3
  isIdentifierAndIdentifier,
4
- isStringAndIdentifier,
5
4
  } from '#is';
6
5
  import {createTypeChecker} from '#type-checker';
7
6
  import {isInsideOneElementArray} from './before-if.js';
@@ -10,6 +9,5 @@ export const afterIf = createTypeChecker([
10
9
  ['-: parentPath -> !ArrayExpression'],
11
10
  ['-: parentPath ->', isCoupleLines],
12
11
  ['+', isIdentifierAndIdentifier],
13
- ['-: -> !', isStringAndIdentifier],
14
12
  ['+', isInsideOneElementArray],
15
13
  ]);
@@ -3,7 +3,6 @@ import {
3
3
  isCoupleLines,
4
4
  isIdentifierAndIdentifier,
5
5
  isInsideArray,
6
- isStringAndIdentifier,
7
6
  } from '#is';
8
7
 
9
8
  export const isInsideOneElementArray = ({parentPath}) => {
@@ -11,14 +10,9 @@ export const isInsideOneElementArray = ({parentPath}) => {
11
10
  return elements.length === 1;
12
11
  };
13
12
 
14
- const isStringAndIdentifierInsideOneElementArray = createTypeChecker([
15
- ['-: -> !', isStringAndIdentifier],
16
- ['+', isInsideOneElementArray],
17
- ]);
18
-
19
13
  export const beforeIf = createTypeChecker([
20
14
  ['-: -> !', isInsideArray],
21
15
  ['-: parentPath ->', isCoupleLines],
22
16
  ['+', isIdentifierAndIdentifier],
23
- ['+', isStringAndIdentifierInsideOneElementArray],
17
+ ['+', isInsideOneElementArray],
24
18
  ]);
@@ -109,7 +109,6 @@ const isLastElementObjectExpression = ({node}) => isObjectExpression(node.elemen
109
109
  export const isSecondIndent = createTypeChecker([
110
110
  ['-: -> !', isMultilineOption],
111
111
  ['+', isNeedsToHideIndentOption],
112
- ['-: ->', isArrayInsideArray],
113
112
  ['+: -> !', isLastElementObjectExpression],
114
113
  ]);
115
114
 
@@ -15,6 +15,20 @@ import {
15
15
  import {isIncreaseIndent} from './indent.js';
16
16
  import {isMultilineOption} from './is.js';
17
17
 
18
+ const {
19
+ isObjectExpression,
20
+ isObjectProperty,
21
+ isCallExpression,
22
+ isAwaitExpression,
23
+ isBooleanLiteral,
24
+ isNullLiteral,
25
+ isStringLiteral,
26
+ isSpreadElement,
27
+ isIdentifier,
28
+ isNumericLiteral,
29
+ isArrayExpression,
30
+ } = types;
31
+
18
32
  const isParentProperty = (path) => path.find(isObjectProperty);
19
33
 
20
34
  const isNumbersArray = createTypeChecker([
@@ -70,19 +84,6 @@ const isShortTwoSimplesInsideCall = (path, {maxElementsInOneLine}) => {
70
84
  return length < maxElementsInOneLine;
71
85
  };
72
86
 
73
- const {
74
- isObjectExpression,
75
- isObjectProperty,
76
- isCallExpression,
77
- isAwaitExpression,
78
- isBooleanLiteral,
79
- isNullLiteral,
80
- isStringLiteral,
81
- isSpreadElement,
82
- isIdentifier,
83
- isNumericLiteral,
84
- } = types;
85
-
86
87
  const {round} = Math;
87
88
 
88
89
  const isOneSpread = (path) => {
@@ -138,12 +139,13 @@ const isSiblingIsArray = (path) => {
138
139
 
139
140
  const isMoreThenMaxLiteralLength = (path, {maxElementLengthInOneLine}) => {
140
141
  const {elements} = path.node;
141
- const [first] = elements;
142
142
 
143
- if (!isStringLiteral(first))
144
- return false;
143
+ for (const element of elements.filter(isStringLiteral)) {
144
+ if (element.value.length >= maxElementLengthInOneLine)
145
+ return true;
146
+ }
145
147
 
146
- return first.value.length > maxElementLengthInOneLine;
148
+ return false;
147
149
  };
148
150
 
149
151
  const isMoreThenMaxIdentifierLength = (path, {maxElementLengthInOneLine}) => {
@@ -197,8 +199,49 @@ const isBodyWithOneElement = createTypeChecker([
197
199
  ['+', isBody],
198
200
  ]);
199
201
 
202
+ const isLengthLessTheMax = (length, {maxElementsInOneLine}) => length <= maxElementsInOneLine;
203
+
204
+ const isAllStrings = (path) => {
205
+ const {elements} = path.node;
206
+ const {length} = elements.filter(isStringLiteral);
207
+
208
+ return elements.length === length;
209
+ };
210
+
211
+ const isTupleInsideArray = createTypeChecker([
212
+ ['-: parentPath -> !ArrayExpression'],
213
+ ['-: -> !', isAllStrings],
214
+ ['-', isMoreThenMaxLiteralLength],
215
+ ['+: node.elements.length', isLengthLessTheMax],
216
+ ]);
217
+
218
+ const isMixedTuples = (path, {maxElementLengthInOneLine}) => {
219
+ const elements = path.get('elements');
220
+
221
+ const arrayElements = elements.filter(isArrayExpression);
222
+ const {length} = arrayElements;
223
+
224
+ if (!length)
225
+ return false;
226
+
227
+ if (isObjectProperty(path.parentPath))
228
+ return false;
229
+
230
+ for (const arrayElement of arrayElements) {
231
+ if (isMoreThenMaxLiteralLength(arrayElement, {maxElementLengthInOneLine}))
232
+ return false;
233
+
234
+ if (isAllStrings(arrayElement))
235
+ return true;
236
+ }
237
+
238
+ return false;
239
+ };
240
+
200
241
  export const isMultiLine = createTypeChecker([
201
242
  ['-: node.elements.length -> -'],
243
+ ['-', isTupleInsideArray],
244
+ ['+', isMixedTuples],
202
245
  ['+', isBodyWithOneElement],
203
246
  ['+', isMoreThenMaxElementLengthInOneLine],
204
247
  ['+', isElementsMoreThenMaxWithFirstString],
@@ -207,7 +250,6 @@ export const isMultiLine = createTypeChecker([
207
250
  ['-', isOneSimple],
208
251
  ['-', isOneSpread],
209
252
  ['-', isIdentifierAndIdentifier],
210
- ['-', isCallInsideArrow],
211
253
  ['-', isIncreaseIndent],
212
254
  ['-', isInsideLoop],
213
255
  ['-', isBooleanAndSimple],
@@ -268,32 +310,20 @@ function isInsideLoop(path) {
268
310
  return path.parentPath.isForOfStatement();
269
311
  }
270
312
 
271
- function tooLong(path) {
313
+ function tooLong(path, {maxElementLengthInOneLine}) {
272
314
  const elements = path.get('elements');
273
315
 
274
316
  if (elements.length < 2)
275
317
  return false;
276
318
 
277
319
  for (const el of path.get('elements')) {
278
- if (el.isStringLiteral() && el.node.value.length > 4)
320
+ if (el.isStringLiteral() && el.node.value.length > maxElementLengthInOneLine)
279
321
  return true;
280
322
  }
281
323
 
282
324
  return false;
283
325
  }
284
326
 
285
- function isCallInsideArrow(path) {
286
- const {parentPath} = path;
287
-
288
- if (!parentPath.isCallExpression())
289
- return false;
290
-
291
- if (!parentPath.parentPath.isFunction())
292
- return false;
293
-
294
- return path.node.elements.length < 4;
295
- }
296
-
297
327
  function isNumbers(elements) {
298
328
  for (const element of elements) {
299
329
  if (isNumericLiteral(element))
@@ -1,5 +1,9 @@
1
1
  import {types} from '@putout/babel';
2
2
  import {createTypeChecker} from '#type-checker';
3
+ import {
4
+ isFirstArgOfCall,
5
+ isLastArgInCall,
6
+ } from '#is';
3
7
  import {chain} from '../chain.js';
4
8
  import {checkCallsCount} from './check-calls-count.js';
5
9
 
@@ -18,26 +22,6 @@ const {
18
22
  isCallExpression,
19
23
  } = types;
20
24
 
21
- const isPathFirstArg = ({node, parentPath}) => {
22
- const [first] = parentPath.node.arguments;
23
- return node === first;
24
- };
25
-
26
- function isPathLastArg({node, parentPath}) {
27
- const last = parentPath.node.arguments.at(-1);
28
- return node === last;
29
- }
30
-
31
- const isFirstArgOfCall = createTypeChecker([
32
- ['-: parentPath -> !CallExpression'],
33
- ['+', isPathFirstArg],
34
- ]);
35
-
36
- const isLastArgInCall = createTypeChecker([
37
- ['-: parentPath -> !CallExpression'],
38
- ['+', isPathLastArg],
39
- ]);
40
-
41
25
  const callWithRoot = (fn) => (a, {root}) => fn(root);
42
26
 
43
27
  const isExcludedFromChain = createTypeChecker([
@@ -1,41 +1,20 @@
1
- import {types} from '@putout/babel';
1
+ import {createTypeChecker} from '#type-checker';
2
+ import {isNextAssignObject} from './is.js';
2
3
 
3
- const {
4
- isAssignmentPattern,
5
- isArrayExpression,
6
- isObjectExpression,
7
- isIdentifier,
8
- } = types;
9
-
10
- export const calculateAssigns = (property, semantics) => {
11
- const currentAssign = isLongAssignPattern(property, semantics);
12
-
13
- const {right} = property.node.value;
14
- const isArrayOrObjectRight = isArrayExpression(right) || isComplexObject(right);
15
- const complexAssign = currentAssign && isArrayOrObjectRight;
16
-
17
- return {
18
- complexAssign,
19
- };
4
+ const isMoreThenMaxPropertiesLengthInOneLineOption = (a, {semantics}) => {
5
+ const {maxPropertiesLengthInOneLine} = semantics;
6
+ return a > maxPropertiesLengthInOneLine;
20
7
  };
21
8
 
22
- export function isLongAssignPattern(path, semantics) {
23
- const {key, value} = path.node;
24
-
25
- if (!isAssignmentPattern(value))
26
- return false;
27
-
28
- if (!isIdentifier(key))
29
- return true;
30
-
31
- const {maxPropertiesLengthInOneLine} = semantics;
32
-
33
- return key.name.length > maxPropertiesLengthInOneLine;
34
- }
9
+ const isCoupleOption = (a, {couple}) => couple;
10
+
11
+ const isNextAssignAndCurrentNotAssign = createTypeChecker([
12
+ ['-: ->', isCoupleOption],
13
+ ['+: node.value -> AssignmentPattern'],
14
+ ['+: -> !', isNextAssignObject],
15
+ ]);
35
16
 
36
- function isComplexObject(node) {
37
- if (!isObjectExpression(node))
38
- return false;
39
-
40
- return node.properties.length;
41
- }
17
+ export const isBreaklineBeforeProperty = createTypeChecker([
18
+ ['-: ->', isNextAssignAndCurrentNotAssign],
19
+ ['+: node.key.name.length -> !', isMoreThenMaxPropertiesLengthInOneLineOption],
20
+ ]);
@@ -1,5 +1,11 @@
1
1
  import {types} from '@putout/babel';
2
2
  import {createTypeChecker} from '#type-checker';
3
+ import {callWithNext} from '#is';
4
+ import {
5
+ hasOptionIs,
6
+ isNextAssignObject,
7
+ isPrevAssignObject,
8
+ } from './is.js';
3
9
 
4
10
  const {
5
11
  isForOfStatement,
@@ -18,11 +24,24 @@ export function isPrevAssign(path) {
18
24
  return isAssignmentPattern(prev.node.value);
19
25
  }
20
26
 
27
+ const hasNode = (path) => path.node;
28
+
21
29
  export const isCommaAfterProperty = createTypeChecker([
22
30
  ['+', isCoupleOption],
31
+ ['+', hasOptionIs],
32
+ ['+', isNextAssignObject],
33
+ ['+', isPrevAssignObject],
34
+ ['+', callWithNext(hasNode)],
23
35
  ['-: key -> -'],
24
36
  ['-', isPrevAssign],
25
37
  ['-: parentPath', isInsideForOf],
26
38
  ['+: node.value.right -> ObjectExpression'],
27
39
  ]);
28
40
 
41
+ export const isNewlineAfterComma = createTypeChecker([
42
+ ['+', isCoupleOption],
43
+ ['-: key -> -'],
44
+ ['-', isPrevAssign],
45
+ ['-: parentPath', isInsideForOf],
46
+ ['+: node.value.right -> ObjectExpression'],
47
+ ]);
@@ -0,0 +1,12 @@
1
+ import {createTypeChecker} from '#type-checker';
2
+ import {
3
+ hasOptionIs,
4
+ isInsideFn,
5
+ isPrevAssignObject,
6
+ } from './is.js';
7
+
8
+ export const isIndentBeforeProperty = createTypeChecker([
9
+ ['-', isInsideFn],
10
+ ['+', isPrevAssignObject],
11
+ ['+', hasOptionIs],
12
+ ]);
@@ -1,15 +1,21 @@
1
1
  import {types} from '@putout/babel';
2
- import {isCoupleLines, exists} from '#is';
2
+ import {createTypeChecker} from '#type-checker';
3
+ import {
4
+ isCoupleLines,
5
+ exists,
6
+ callWithPrev,
7
+ callWithNext,
8
+ } from '#is';
3
9
  import {hasAssign} from './has.js';
4
10
 
5
11
  const {
6
- isObjectExpression,
7
- isAssignmentPattern,
8
12
  isFunction,
9
13
  isVariableDeclarator,
10
14
  isObjectProperty,
11
15
  } = types;
12
16
 
17
+ export const hasOptionIs = (a, {is}) => is;
18
+
13
19
  export const isInsideFn = (path) => {
14
20
  if (isFunction(path.parentPath))
15
21
  return true;
@@ -37,27 +43,13 @@ export const isCoupleProperties = ({path, valuePath, property}) => {
37
43
  return !isObjectProperty(parentPath);
38
44
  };
39
45
 
40
- export function isPrevAssignObject(path) {
41
- const prev = path.getPrevSibling();
42
-
43
- if (!isAssignmentPattern(prev.node.value))
44
- return false;
45
-
46
- const {right} = prev.node.value;
47
-
48
- return isObjectExpression(right);
49
- }
46
+ export const isPrevAssignObject = callWithPrev(createTypeChecker([
47
+ '-: node.value -> !AssignmentPattern',
48
+ '+: node.value.right -> ObjectExpression',
49
+ ]));
50
50
 
51
- export function isNextAssignObject(path) {
52
- const next = path.getNextSibling();
53
-
54
- if (!next.node)
55
- return false;
56
-
57
- if (!isAssignmentPattern(next.node.value))
58
- return false;
59
-
60
- const {right} = next.node.value;
61
-
62
- return isObjectExpression(right);
63
- }
51
+ export const isNextAssignObject = callWithNext(createTypeChecker([
52
+ '-: node -> -',
53
+ '-: node.value -> !AssignmentPattern',
54
+ '+: node.value.right -> ObjectExpression',
55
+ ]));
@@ -1,17 +1,18 @@
1
1
  import {wrongShorthand} from './wrong-shorthand.js';
2
2
  import {maybeTypeAnnotation} from '../../maybe/maybe-type-annotation.js';
3
3
  import {printKey} from '../object-expression/print-key.js';
4
- import {
5
- calculateAssigns,
6
- isLongAssignPattern,
7
- } from './calculate-long-assign-pattern.js';
4
+ import {isBreaklineBeforeProperty} from './calculate-long-assign-pattern.js';
8
5
  import {printLeadingComments} from './comments.js';
9
6
  import {shouldAddNewline} from './should-add-new-line.js';
10
7
  import {
11
8
  hasAssignObject,
12
9
  hasObjectPattern,
13
10
  } from './has.js';
14
- import {isCommaAfterProperty} from './comma.js';
11
+ import {
12
+ isCommaAfterProperty,
13
+ isNewlineAfterComma,
14
+ } from './comma.js';
15
+ import {isIndentBeforeProperty} from './indent.js';
15
16
  import {
16
17
  isCoupleProperties,
17
18
  isIndent,
@@ -51,16 +52,19 @@ export const ObjectPattern = {
51
52
  maybe.print.newline(is && notInsideFn);
52
53
 
53
54
  for (const [i, property] of properties.entries()) {
55
+ const prevAssignObject = i && isPrevAssignObject(property);
56
+
57
+ if (isIndentBeforeProperty(property, {is}))
58
+ indent();
59
+
54
60
  if (property.isRestElement()) {
55
61
  const couple = is || hasObject;
56
62
 
57
- maybe.indent(couple);
58
63
  print(property);
59
64
  maybe.print.newline(couple);
60
65
  continue;
61
66
  }
62
67
 
63
- const prevAssignObject = i && isPrevAssignObject(property);
64
68
  const nextAssignObject = isNextAssignObject(property);
65
69
 
66
70
  const valuePath = property.get('value');
@@ -74,11 +78,7 @@ export const ObjectPattern = {
74
78
  valuePath,
75
79
  });
76
80
 
77
- maybe.indent((prevAssignObject || is) && notInsideFn);
78
-
79
- maybe.print.breakline(couple && !isLongAssignPattern(property, semantics));
80
-
81
- if (!isAssign && nextAssignObject)
81
+ if (isBreaklineBeforeProperty(property, {couple, semantics}))
82
82
  print.breakline();
83
83
 
84
84
  printLeadingComments(property, {
@@ -92,33 +92,28 @@ export const ObjectPattern = {
92
92
  print(valuePath);
93
93
  } else if (isAssign) {
94
94
  print(valuePath);
95
-
96
- if (isCommaAfterProperty(property, {couple})) {
97
- print(',');
98
- print.newline();
99
- continue;
100
- }
101
95
  }
102
96
 
103
- if (!isAssign && nextAssignObject && notInsideFn) {
97
+ if (isCommaAfterProperty(property, {is, couple}))
104
98
  print(',');
105
- print.breakline();
99
+
100
+ if (isNewlineAfterComma(property, {couple})) {
101
+ print.newline();
106
102
  continue;
107
103
  }
108
104
 
109
- const {complexAssign} = calculateAssigns(property, semantics);
105
+ if (!isAssign && nextAssignObject && notInsideFn) {
106
+ print.breakline();
107
+ continue;
108
+ }
110
109
 
111
- if (!complexAssign && (is || hasObject || prevAssignObject && notInsideFn)) {
112
- print(',');
110
+ if (is || hasObject || prevAssignObject && notInsideFn) {
113
111
  print.newline();
114
-
115
112
  continue;
116
113
  }
117
114
 
118
- if (i < n && !(isAssign && couple)) {
119
- print(',');
115
+ if (i < n && !(isAssign && couple))
120
116
  print.space();
121
- }
122
117
  }
123
118
 
124
119
  indent.dec();
@@ -128,4 +123,3 @@ export const ObjectPattern = {
128
123
  print('}');
129
124
  }),
130
125
  };
131
-
@@ -1,4 +1,5 @@
1
1
  import {types} from '@putout/babel';
2
+ import {createTypeChecker} from '#type-checker';
2
3
 
3
4
  const {
4
5
  isStringLiteral,
@@ -202,3 +203,23 @@ export const hasTrailingComment = (path) => {
202
203
  export const hasLeadingComment = (path) => path.node?.leadingComments?.length;
203
204
 
204
205
  export const noTrailingComment = (path) => !path.node.trailingComments?.length;
206
+
207
+ const isPathFirstArg = ({node, parentPath}) => {
208
+ const [first] = parentPath.node.arguments;
209
+ return node === first;
210
+ };
211
+
212
+ function isPathLastArg({node, parentPath}) {
213
+ const last = parentPath.node.arguments.at(-1);
214
+ return node === last;
215
+ }
216
+
217
+ export const isFirstArgOfCall = createTypeChecker([
218
+ ['-: parentPath -> !CallExpression'],
219
+ ['+', isPathFirstArg],
220
+ ]);
221
+
222
+ export const isLastArgInCall = createTypeChecker([
223
+ ['-: parentPath -> !CallExpression'],
224
+ ['+', isPathLastArg],
225
+ ]);
@@ -32,7 +32,7 @@ const initSemantics = (format, semantics = {}) => ({
32
32
  maxPropertiesInOneLine: 2,
33
33
  maxPropertiesLengthInOneLine: 15,
34
34
  maxSpecifiersInOneLine: 2,
35
- maxElementsInOneLine: 5,
35
+ maxElementsInOneLine: 3,
36
36
  maxElementLengthInOneLine: 15,
37
37
  maxLogicalsInOneLine: 3,
38
38
  maxVariablesInOneLine: 4,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@putout/printer",
3
- "version": "18.7.9",
3
+ "version": "18.8.1",
4
4
  "type": "module",
5
5
  "author": "coderaiser <mnemonic.enemy@gmail.com> (https://github.com/coderaiser)",
6
6
  "description": "Simplest possible opinionated Babel AST printer for 🐊Putout",