@putout/printer 17.12.0 → 18.0.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/ChangeLog CHANGED
@@ -1,3 +1,22 @@
1
+ 2026.03.04, v18.0.0
2
+
3
+ feature:
4
+ - 1aaf942 @putout/printer: ArrayExpression: newline: is more then max element length in one line
5
+ - a0d465b @putout/printer: maxElementLengthInOneLine: 10 -> 15
6
+ - eea22ae @putout/printer: BlockStatement: newline: simplify
7
+ - c9c9f34 @putout/printer: BlockStatement: isMethodOrArrow: simplify
8
+ - 010780c @putout/printer: private-imports: #type-checker
9
+ - 9a4b938 BlockStatements: apply type-checker
10
+ - 9357d36 type-checker: simplify
11
+
12
+ 2026.03.04, v17.13.0
13
+
14
+ feature:
15
+ - 4af358d @putout/printer: type-checker: expression
16
+ - e3da216 @putout/printer: type-checker: improve dsl
17
+ - 93986ce @putout/printer: type checker: add
18
+ - 6a86e86 @putout/printer: type-checker: add
19
+
1
20
  2026.03.03, v17.12.0
2
21
 
3
22
  feature:
package/README.md CHANGED
@@ -97,7 +97,7 @@ print(ast, {
97
97
  comments: true,
98
98
  maxSpecifiersInOneLine: 2,
99
99
  maxElementsInOneLine: 3,
100
- maxElementLengthInOneLine: 10,
100
+ maxElementLengthInOneLine: 15,
101
101
  maxLogicalsInOneLine: 3,
102
102
  maxVariablesInOneLine: 4,
103
103
  maxTypesInOneLine: 3,
@@ -1,5 +1,6 @@
1
1
  import {isSimple} from '@putout/operate';
2
2
  import {types} from '@putout/babel';
3
+ import {createTypeChecker} from '#type-checker';
3
4
  import {
4
5
  isStringAndMember,
5
6
  isStringAndIdentifier,
@@ -8,6 +9,7 @@ import {
8
9
  isStringAndArray,
9
10
  isIdentifierAndIdentifier,
10
11
  isSimpleAndNotEmptyObject,
12
+ isInsideCall,
11
13
  } from '#is';
12
14
 
13
15
  const {
@@ -57,6 +59,27 @@ const isSiblingIsArray = (path) => {
57
59
  .isArrayExpression();
58
60
  };
59
61
 
62
+ const isEmptyArray = (path) => !path.node.elements.length;
63
+ const isAllLiterals = (path) => {
64
+ const {elements} = path.node;
65
+ const literals = elements.filter(isStringLiteral);
66
+
67
+ return literals.length !== elements.length;
68
+ };
69
+
70
+ const isMoreThenMaxLiteralLength = (path, {maxElementLengthInOneLine}) => {
71
+ const [first] = path.node.elements;
72
+
73
+ return first.value.length > maxElementLengthInOneLine;
74
+ };
75
+
76
+ const isMoreThenMaxElementLengthInOneLine = createTypeChecker([
77
+ ['-', isEmptyArray],
78
+ ['-: -> !', isInsideCall],
79
+ ['-', isAllLiterals],
80
+ ['+', isMoreThenMaxLiteralLength],
81
+ ]);
82
+
60
83
  function isMaxElementLengthInOneLine(elements, maxElementLengthInOneLine) {
61
84
  if (elements.length > 1)
62
85
  return false;
@@ -75,6 +98,9 @@ export const isMultiLine = (path, {elements, maxElementsInOneLine, maxElementLen
75
98
  if (isMaxElementLengthInOneLine(elements, maxElementLengthInOneLine))
76
99
  return ONE_LINE;
77
100
 
101
+ if (isMoreThenMaxElementLengthInOneLine(path, {elements, maxElementLengthInOneLine}))
102
+ return MULTI_LINE;
103
+
78
104
  if (elements.length > maxElementsInOneLine && isStringLiteral(first))
79
105
  return MULTI_LINE;
80
106
 
@@ -194,9 +220,6 @@ function isOneSimple(path) {
194
220
 
195
221
  const [first] = elements;
196
222
 
197
- if (first.isIdentifier() && first.node.name.length < 15)
198
- return true;
199
-
200
223
  if (first.isStringLiteral() && first.node.value.length > 10)
201
224
  return false;
202
225
 
@@ -288,12 +311,10 @@ export function isIncreaseIndent(path) {
288
311
  return isStringAndObject(elements);
289
312
  }
290
313
 
291
- function isInsideCallLoop(path) {
292
- if (!path.parentPath.isCallExpression())
293
- return false;
294
-
295
- return path.parentPath.parentPath.isForOfStatement();
296
- }
314
+ const isInsideCallLoop = createTypeChecker([
315
+ ['-: -> !', isInsideCall],
316
+ '+: parentPath.parentPath -> ForOfStatement',
317
+ ]);
297
318
 
298
319
  const isStringAndObject = (elements) => {
299
320
  const first = elements.at(0);
@@ -308,3 +329,4 @@ export const isCurrentNewLine = (path) => {
308
329
 
309
330
  return !path.isObjectExpression();
310
331
  };
332
+
@@ -38,4 +38,3 @@ export const AssignmentExpression = maybeParens({
38
38
 
39
39
  AssignmentExpression.printLeadingCommentLine = printLeadingCommentLine;
40
40
  AssignmentExpression.printLeadingCommentBlock = printLeadingCommentBlock;
41
-
@@ -1,22 +1,18 @@
1
1
  import {types} from '@putout/babel';
2
- import {isNext} from '#is';
2
+ import {
3
+ hasBody,
4
+ isInsideTSModuleBlock,
5
+ isNext,
6
+ } from '#is';
3
7
  import {markAfter} from '#mark';
4
8
  import {maybeDeclare} from '../../maybe/maybe-declare.js';
5
9
  import {parseComments} from '../../comment/comment.js';
6
10
  import {maybeDecorators} from '../../maybe/maybe-decorators.js';
7
11
 
8
- const isInsideTSModuleBlock = (path) => {
9
- return isTSModuleBlock(path.parentPath.parentPath);
10
- };
11
-
12
- const {
13
- isFunction,
14
- isTSModuleBlock,
15
- } = types;
12
+ const {isFunction} = types;
16
13
 
17
14
  const isInsideExport = ({parentPath}) => parentPath.isExportDeclaration();
18
15
  const isFunctionLike = (path) => isFunction(path.parentPath.parentPath);
19
- const hasBody = ({node}) => node.body.body.length;
20
16
 
21
17
  const classVisitor = maybeDecorators((path, printer, semantics) => {
22
18
  const {
@@ -1,21 +1,36 @@
1
1
  import {types} from '@putout/babel';
2
- import {isNext, isNextParent} from '#is';
3
2
  import {printParams} from '#print-params';
4
3
  import {markAfter} from '#mark';
5
-
6
- const not = (fn) => (...a) => !fn(...a);
7
- const notInsideExportDefaultWithBody = not(isInsideExportDefaultWithBody);
4
+ import {
5
+ isInsideTSModuleBlock,
6
+ isNext,
7
+ isNextParent,
8
+ } from '#is';
9
+ import {createTypeChecker} from '#type-checker';
8
10
 
9
11
  const {
10
12
  isAssignmentExpression,
11
- isTSModuleBlock,
12
- isBlockStatement,
13
13
  isExportNamedDeclaration,
14
14
  isExpressionStatement,
15
15
  isFunctionDeclaration,
16
- isExportDefaultDeclaration,
17
16
  } = types;
18
17
 
18
+ const hasFnBody = ({node}) => node.body.body.length;
19
+
20
+ const isInsideExportDefaultWithBody = createTypeChecker([
21
+ '-: parentPath -> !ExportDefaultDeclaration',
22
+ ['+', hasFnBody],
23
+ ]);
24
+
25
+ const isInsideBlockLike = createTypeChecker([
26
+ ['+', isInsideTSModuleBlock],
27
+ '-: parentPath -> !BlockStatement',
28
+ ['+: -> !', hasFnBody],
29
+ ]);
30
+
31
+ const not = (fn) => (...a) => !fn(...a);
32
+ const notInsideExportDefaultWithBody = not(isInsideExportDefaultWithBody);
33
+
19
34
  const isInsideNamedExport = ({parentPath}) => isExportNamedDeclaration(parentPath);
20
35
 
21
36
  export const FunctionDeclaration = {
@@ -74,22 +89,3 @@ const isNextAssign = (path) => {
74
89
 
75
90
  return isAssignmentExpression(next.node.expression);
76
91
  };
77
-
78
- function isInsideBlockLike(path) {
79
- const {parentPath} = path;
80
-
81
- if (isTSModuleBlock(parentPath.parentPath))
82
- return true;
83
-
84
- if (!isBlockStatement(parentPath))
85
- return false;
86
-
87
- return !path.node.body.body.length;
88
- }
89
-
90
- function isInsideExportDefaultWithBody(path) {
91
- if (!isExportDefaultDeclaration(path.parentPath))
92
- return false;
93
-
94
- return path.node.body.body.length;
95
- }
@@ -8,6 +8,7 @@ import {
8
8
  noLeadingComment,
9
9
  hasLeadingComment,
10
10
  exists,
11
+ isInsideCall,
11
12
  } from '#is';
12
13
  import {parseComments} from '../../comment/comment.js';
13
14
  import {isInsideTuple} from './is-inside-tuple.js';
@@ -37,8 +38,6 @@ const isMemberExpressionCallee = ({parentPath}) => {
37
38
  return isLooksLikeChain(callee);
38
39
  };
39
40
 
40
- const isInsideCall = ({parentPath}) => parentPath.isCallExpression();
41
-
42
41
  function isInsideNestedArrayCall({parentPath}) {
43
42
  if (!isArrayExpression(parentPath))
44
43
  return false;
@@ -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,
@@ -12,11 +13,26 @@ const {
12
13
  isObjectExpression,
13
14
  isLabeledStatement,
14
15
  isTryStatement,
16
+ isProgram,
17
+ isBlockStatement,
18
+ isTSModuleBlock,
15
19
  } = types;
16
20
 
17
- export const isParentProgram = (path) => path.parentPath?.isProgram();
18
- export const isParentBlock = (path) => path.parentPath.isBlockStatement();
19
- export const isInsideBlockLike = (path) => /^(Program|BlockStatement|TSModuleBlock|SwitchCase)$/.test(path.parentPath.type);
21
+ export const isParentProgram = (path) => isProgram(path.parentPath);
22
+ export const isParentBlock = (path) => isBlockStatement(path.parentPath);
23
+
24
+ export const isParentBlockLike = createTypeChecker('path.parentPath', [
25
+ 'Program',
26
+ 'BlockStatement',
27
+ 'TSModuleBlock',
28
+ 'SwitchCase',
29
+ ]);
30
+
31
+ export const isInsideTSModuleBlock = ({parentPath}) => {
32
+ return isTSModuleBlock(parentPath?.parentPath);
33
+ };
34
+
35
+ export const isInsideCall = ({parentPath}) => parentPath.isCallExpression();
20
36
 
21
37
  export const isNext = (path) => {
22
38
  const next = path.getNextSibling();
@@ -27,6 +43,10 @@ export const isNext = (path) => {
27
43
  return !next.isEmptyStatement();
28
44
  };
29
45
 
46
+ export const hasBody = ({node}) => node.body.body.length;
47
+
48
+ export const hasEmptyBody = (path) => !path.node.body.length;
49
+
30
50
  export const isNextTry = (path) => {
31
51
  return isTryStatement(path.getNextSibling());
32
52
  };
@@ -178,4 +198,3 @@ export const hasLeadingComment = (path) => path.node?.leadingComments?.length;
178
198
 
179
199
  export const noTrailingComment = (path) => !path.node.trailingComments?.length;
180
200
  export const noLeadingComment = (path) => !path.node.leadingComments?.length;
181
-
@@ -33,7 +33,7 @@ const initSemantics = (format, semantics = {}) => ({
33
33
  maxPropertiesLengthInOneLine: 15,
34
34
  maxSpecifiersInOneLine: 2,
35
35
  maxElementsInOneLine: 5,
36
- maxElementLengthInOneLine: 10,
36
+ maxElementLengthInOneLine: 15,
37
37
  maxLogicalsInOneLine: 3,
38
38
  maxVariablesInOneLine: 4,
39
39
  maxTypesInOneLine: 3,
@@ -0,0 +1,98 @@
1
+ import {types} from '@putout/babel';
2
+ import {createTypeChecker} from '#type-checker';
3
+ import {
4
+ isNext,
5
+ isParentProgram,
6
+ isLast,
7
+ exists,
8
+ satisfy,
9
+ isParentBlock,
10
+ } from '#is';
11
+ import {insideIfWithNoBody} from './inside-if-with-no-body.js';
12
+
13
+ const parentIfWithoutElse = ({parentPath}) => {
14
+ if (!parentPath.isIfStatement())
15
+ return false;
16
+
17
+ return !parentPath.node.alternate;
18
+ };
19
+
20
+ const {
21
+ isFunctionDeclaration,
22
+ isExportDeclaration,
23
+ isDoWhileStatement,
24
+ } = types;
25
+
26
+ const isTopLevelWithNoNext = (path) => {
27
+ if (isNext(path))
28
+ return false;
29
+
30
+ return !isNext(path.parentPath) && isParentProgram(path.parentPath);
31
+ };
32
+
33
+ const isInsideDoWhile = ({parentPath}) => isDoWhileStatement(parentPath);
34
+ const isMethodOrArrow = createTypeChecker(['ArrowFunctionExpression', 'ObjectMethod']);
35
+ const isInsideFn = (path) => path.find(isMethodOrArrow);
36
+
37
+ const isInsideIfWithoutElseInsideFn = createTypeChecker([
38
+ ['-: -> !', parentIfWithoutElse],
39
+ ['+', isInsideFn],
40
+ ]);
41
+
42
+ const isEmptyBodyNoNext = (path) => {
43
+ const {parentPath} = path;
44
+ return parentPath.isStatement() && !path.node.body.length && !isNext(parentPath);
45
+ };
46
+
47
+ const isLooksLikeInsideFn = ({parentPath}) => {
48
+ return /FunctionExpression/.test(parentPath.type);
49
+ };
50
+
51
+ const isNoNewline = satisfy([
52
+ isInsideDoWhile,
53
+ isTopLevelWithNoNext,
54
+ insideIfWithNoBody,
55
+ ]);
56
+
57
+ export const shouldAddNewlineAfter = createTypeChecker([
58
+ ['+', isParentBlock],
59
+ ['-', isNoNewline],
60
+ ['+', isInsideIfWithoutElseInsideFn],
61
+ ['-', isEmptyBodyNoNext],
62
+ ['-', isTry],
63
+ ['-', isLooksLikeInsideFn],
64
+ ['-', isLast],
65
+ ['-', isExportFunction],
66
+ ['+: -> !', isNextIfAlternate],
67
+ ]);
68
+
69
+ function isExportFunction(path) {
70
+ if (!isFunctionDeclaration(path.parentPath))
71
+ return false;
72
+
73
+ if (!isExportDeclaration(path.parentPath?.parentPath))
74
+ return false;
75
+
76
+ return !isNext(path.parentPath?.parentPath);
77
+ }
78
+
79
+ function isNextIfAlternate(path) {
80
+ const {parentPath} = path;
81
+
82
+ if (!parentPath.isIfStatement())
83
+ return false;
84
+
85
+ const alternate = parentPath.get('alternate');
86
+
87
+ if (path === alternate)
88
+ return false;
89
+
90
+ return exists(alternate);
91
+ }
92
+
93
+ function isTry({parentPath}) {
94
+ if (parentPath.isTryStatement())
95
+ return true;
96
+
97
+ return parentPath.parentPath?.isTryStatement();
98
+ }
@@ -1,30 +1,18 @@
1
1
  import {types} from '@putout/babel';
2
2
  import {markAfter} from '#mark';
3
- import {
4
- isNext,
5
- isParentProgram,
6
- isLast,
7
- exists,
8
- satisfy,
9
- isParentBlock,
10
- } from '#is';
3
+ import {isNext} from '#is';
11
4
  import {parseComments} from '../../comment/comment.js';
12
- import {insideIfWithNoBody} from './inside-if-with-no-body.js';
13
5
  import {getDirectives} from './get-directives.js';
14
6
  import {isCallInsideChain} from './is-call-inside-chain.js';
7
+ import {shouldAddNewlineAfter} from './block-statement-newline.js';
15
8
 
16
9
  const {
17
- isArrowFunctionExpression,
18
10
  isObjectMethod,
19
- isFunctionDeclaration,
20
- isExportDeclaration,
21
- isDoWhileStatement,
22
11
  isArrayExpression,
23
12
  } = types;
24
13
 
25
14
  const isFirstStatement = (path) => path.node.body[0];
26
15
  const isFirstDirective = (path) => path.node.directives?.[0];
27
- const isMethodOrArrow = (path) => isArrowFunctionExpression(path) || isObjectMethod(path);
28
16
 
29
17
  const isInsideArrayTupleOfThree = (path) => {
30
18
  const {parentPath} = path.parentPath;
@@ -37,13 +25,6 @@ const isInsideArrayTupleOfThree = (path) => {
37
25
  return length === 3;
38
26
  };
39
27
 
40
- const parentIfWithoutElse = ({parentPath}) => {
41
- if (!parentPath.isIfStatement())
42
- return false;
43
-
44
- return !parentPath.node.alternate;
45
- };
46
-
47
28
  export const BlockStatement = {
48
29
  print(path, printer, semantics) {
49
30
  const {trailingComma} = semantics;
@@ -102,93 +83,3 @@ export const BlockStatement = {
102
83
  markAfter(path.parentPath);
103
84
  },
104
85
  };
105
-
106
- const isTopLevelWithNoNext = (path) => {
107
- if (isNext(path))
108
- return false;
109
-
110
- return !isNext(path.parentPath) && isParentProgram(path.parentPath);
111
- };
112
-
113
- const isInsideIfWithoutElseInsideFn = (path) => {
114
- return parentIfWithoutElse(path) && path.find(isMethodOrArrow);
115
- };
116
-
117
- const isEmptyBodyNoNext = (path) => {
118
- const {parentPath} = path;
119
- return parentPath.isStatement() && !path.node.body.length && !isNext(parentPath);
120
- };
121
-
122
- const isLooksLikeInsideFn = ({parentPath}) => {
123
- return /FunctionExpression/.test(parentPath.type);
124
- };
125
-
126
- const NEWLINE = true;
127
- const NO_NEWLINE = false;
128
-
129
- const isInsideDoWhile = ({parentPath}) => isDoWhileStatement(parentPath);
130
-
131
- const isNoNewline = satisfy([
132
- isInsideDoWhile,
133
- isTopLevelWithNoNext,
134
- insideIfWithNoBody,
135
- ]);
136
-
137
- function shouldAddNewlineAfter(path) {
138
- if (isParentBlock(path))
139
- return NEWLINE;
140
-
141
- if (isNoNewline(path))
142
- return NO_NEWLINE;
143
-
144
- if (isInsideIfWithoutElseInsideFn(path))
145
- return NEWLINE;
146
-
147
- if (isEmptyBodyNoNext(path))
148
- return NO_NEWLINE;
149
-
150
- if (isTry(path))
151
- return NO_NEWLINE;
152
-
153
- if (isLooksLikeInsideFn(path))
154
- return NO_NEWLINE;
155
-
156
- if (isLast(path))
157
- return NO_NEWLINE;
158
-
159
- if (isExportFunction(path))
160
- return NO_NEWLINE;
161
-
162
- return !isNextIfAlternate(path);
163
- }
164
-
165
- function isExportFunction(path) {
166
- if (!isFunctionDeclaration(path.parentPath))
167
- return false;
168
-
169
- if (!isExportDeclaration(path.parentPath?.parentPath))
170
- return false;
171
-
172
- return !isNext(path.parentPath?.parentPath);
173
- }
174
-
175
- function isNextIfAlternate(path) {
176
- const {parentPath} = path;
177
-
178
- if (!parentPath.isIfStatement())
179
- return false;
180
-
181
- const alternate = parentPath.get('alternate');
182
-
183
- if (path === alternate)
184
- return false;
185
-
186
- return exists(alternate);
187
- }
188
-
189
- function isTry({parentPath}) {
190
- if (parentPath.isTryStatement())
191
- return true;
192
-
193
- return parentPath.parentPath?.isTryStatement();
194
- }
@@ -1,10 +1,11 @@
1
1
  import {types} from '@putout/babel';
2
+ import {markAfter} from '#mark';
2
3
  import {
3
4
  exists,
4
5
  isNext,
5
6
  isInsideIf,
7
+ hasEmptyBody,
6
8
  } from '#is';
7
- import {markAfter} from '#mark';
8
9
  import {
9
10
  printTrailingCommentBlock,
10
11
  printTrailingCommentLine,
@@ -42,8 +43,6 @@ const isInsideNestedBody = ({parentPath}) => {
42
43
  return parentPath.parentPath.type === 'BlockStatement';
43
44
  };
44
45
 
45
- const isEmptyBody = (path) => !path.node.body.length;
46
-
47
46
  const isLastEmptyInsideBody = (path) => {
48
47
  const {parentPath} = path;
49
48
 
@@ -85,7 +84,7 @@ export const IfStatement = {
85
84
  print(consequent);
86
85
 
87
86
  if (isInsideIf(path.parentPath) || isInsideNestedBody(path))
88
- maybe.print.newline(isEmptyBody(consequent));
87
+ maybe.print.newline(hasEmptyBody(consequent));
89
88
  } else {
90
89
  const is = !isEmptyConsequent(path);
91
90
 
@@ -1,12 +1,13 @@
1
1
  import {types} from '@putout/babel';
2
2
  import {hasPrevNewline} from '#mark';
3
+ import {createTypeChecker} from '#type-checker';
3
4
  import {
4
5
  isNext,
5
6
  isCoupleLines,
6
7
  isNewlineBetweenSiblings,
7
8
  exists,
8
9
  noTrailingComment,
9
- isInsideBlockLike,
10
+ isParentBlockLike,
10
11
  } from '#is';
11
12
  import {maybeSpaceAfterKeyword} from './maybe-space-after-keyword.js';
12
13
  import {isConcatenation} from '../../expressions/binary-expression/concatenate.js';
@@ -16,11 +17,17 @@ import {maybeDeclare} from '../../maybe/maybe-declare.js';
16
17
  const {isExportDeclaration} = types;
17
18
 
18
19
  const isParentTSModuleBlock = (path) => path.parentPath.isTSModuleBlock();
19
- const isParentBlock = (path) => /Program|BlockStatement|Export|LabeledStatement/.test(path.parentPath.type);
20
20
  const isParentSwitchCase = (path) => path.parentPath.isSwitchCase();
21
21
  const isFirstInSwitch = (path) => path.parentPath.get('consequent.0') === path;
22
22
  const isParentIf = (path) => path.parentPath.isIfStatement();
23
23
 
24
+ const isParentBlock = createTypeChecker('path.parentPath', [
25
+ 'Program',
26
+ 'BlockStatement',
27
+ 'ExportNamedDeclaration',
28
+ 'LabeledStatement',
29
+ ]);
30
+
24
31
  export const VariableDeclaration = {
25
32
  beforeIf: shouldAddNewlineBefore,
26
33
  before(path, {print}) {
@@ -29,7 +36,7 @@ export const VariableDeclaration = {
29
36
  print: maybeDeclare((path, {maybe, store, write, traverse, print, indent}, semantics) => {
30
37
  const {maxVariablesInOneLine} = semantics;
31
38
 
32
- maybe.indent(isInsideBlockLike(path));
39
+ maybe.indent(isParentBlockLike(path));
33
40
 
34
41
  write(path.node.kind);
35
42
  maybeSpaceAfterKeyword(path, {
@@ -0,0 +1,24 @@
1
+ const isFn = (a) => typeof a === 'function';
2
+ const isString = (a) => typeof a === 'string';
3
+
4
+ export const equal = (not, a, b) => {
5
+ if (!isString(b))
6
+ return false;
7
+
8
+ if (not)
9
+ return a !== b;
10
+
11
+ return a === b;
12
+ };
13
+
14
+ export const maybeCall = (fn, not, a, options) => {
15
+ if (!isFn(fn))
16
+ return false;
17
+
18
+ const result = fn(a, options);
19
+
20
+ if (not)
21
+ return !result;
22
+
23
+ return result;
24
+ };
@@ -0,0 +1,17 @@
1
+ export function createTuple(typeName) {
2
+ const afterSplit = typeName.split(' ');
3
+ const typeWithNot = afterSplit.pop();
4
+ const array = typeWithNot.split('!');
5
+ const operation = afterSplit.join(' ');
6
+
7
+ if (array.length === 1)
8
+ return [
9
+ `${operation} `,
10
+ typeWithNot,
11
+ ];
12
+
13
+ return [
14
+ `${operation} !`,
15
+ array[1],
16
+ ];
17
+ }
@@ -0,0 +1,51 @@
1
+ import {isArray} from '@putout/compare/lib/is.js';
2
+ import {createTuple} from './create-tuple.js';
3
+
4
+ const isString = (a) => typeof a === 'string';
5
+
6
+ export const parseOperation = (operation) => {
7
+ const [result, command] = operation.split(':');
8
+ const parsedResult = parseResult(result);
9
+
10
+ if (!command)
11
+ return [parsedResult, '', ''];
12
+
13
+ const [selector, not] = command.split(' -> ');
14
+
15
+ return [parsedResult, selector.trim(), not];
16
+ };
17
+
18
+ const parseResult = (a) => a === '+';
19
+
20
+ export function parseTypeNames(typeNames) {
21
+ const tuples = [];
22
+
23
+ if (isArray(typeNames)) {
24
+ for (const typeName of typeNames) {
25
+ if (isArray(typeName)) {
26
+ tuples.push(typeName);
27
+ continue;
28
+ }
29
+
30
+ if (isString(typeName) && typeName.includes(' -> ')) {
31
+ const tuple = createTuple(typeName);
32
+ tuples.push(tuple);
33
+ continue;
34
+ }
35
+
36
+ tuples.push(['+', typeName]);
37
+ }
38
+
39
+ return tuples;
40
+ }
41
+
42
+ for (const typeName of typeNames.include) {
43
+ tuples.push(['+', typeName]);
44
+ }
45
+
46
+ for (const typeName of typeNames.exclude) {
47
+ tuples.push(['-', typeName]);
48
+ }
49
+
50
+ return tuples;
51
+ }
@@ -0,0 +1,40 @@
1
+ import {jessy} from 'jessy';
2
+ import {parseOperation, parseTypeNames} from './parsers.js';
3
+ import {equal, maybeCall} from './comparators.js';
4
+
5
+ export const createTypeChecker = (deepness, typeNames) => {
6
+ if (!typeNames) {
7
+ typeNames = deepness;
8
+ deepness = '';
9
+ }
10
+
11
+ const tuples = parseTypeNames(typeNames);
12
+
13
+ return (path, options) => {
14
+ let i = deepness.split('.').length;
15
+
16
+ while (--i)
17
+ path = path?.parentPath;
18
+
19
+ if (!path)
20
+ return false;
21
+
22
+ for (const [operation, typeName] of tuples) {
23
+ const [result, selector, not] = parseOperation(operation);
24
+ let currentPath = path;
25
+
26
+ if (selector)
27
+ currentPath = jessy(selector, path);
28
+
29
+ const {type} = currentPath;
30
+
31
+ if (equal(not, type, typeName))
32
+ return result;
33
+
34
+ if (maybeCall(typeName, not, currentPath, options))
35
+ return result;
36
+ }
37
+
38
+ return false;
39
+ };
40
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@putout/printer",
3
- "version": "17.12.0",
3
+ "version": "18.0.0",
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",
@@ -38,6 +38,7 @@
38
38
  "@putout/operate": "^15.0.0",
39
39
  "@putout/operator-json": "^3.1.0",
40
40
  "fullstore": "^4.0.0",
41
+ "jessy": "^5.0.0",
41
42
  "just-snake-case": "^3.2.0",
42
43
  "parse-import-specifiers": "^1.0.1",
43
44
  "rendy": "^5.0.0"
@@ -61,7 +62,8 @@
61
62
  "#maybe-parens": "./lib/tokenize/maybe/maybe-parens.js",
62
63
  "#print-params": "./lib/tokenize/expressions/function/params.js",
63
64
  "#import-attributes": "./lib/tokenize/statements/import-declaration/import-attribute.js",
64
- "#types": "./lib/types.js"
65
+ "#types": "./lib/types.js",
66
+ "#type-checker": "./lib/tokenize/type-checker/type-checker.js"
65
67
  },
66
68
  "devDependencies": {
67
69
  "@babel/parser": "^7.28.5",