1 | "use strict";
|
2 |
|
3 | const {
|
4 | hasNewline,
|
5 | skipEverythingButNewLine,
|
6 | skipNewline,
|
7 | isNextLineEmpty: _isNextLineEmpty,
|
8 | isPreviousLineEmpty: _isPreviousLineEmpty,
|
9 | getNextNonSpaceNonCommentCharacterIndex: _getNextNonSpaceNonCommentCharacterIndex,
|
10 | } = require("prettier").util;
|
11 |
|
12 | const prettierVersion = require("prettier").version;
|
13 |
|
14 | function lookupIfPrettier2(options, prop) {
|
15 | return parseInt(prettierVersion[0]) > 1 ? options[prop] : options;
|
16 | }
|
17 |
|
18 | function isPreviousLineEmpty(text, node, options) {
|
19 | return _isPreviousLineEmpty(
|
20 | text,
|
21 | node,
|
22 | lookupIfPrettier2(options, "locStart")
|
23 | );
|
24 | }
|
25 |
|
26 | function isNextLineEmpty(text, node, options) {
|
27 | return _isNextLineEmpty(text, node, lookupIfPrettier2(options, "locEnd"));
|
28 | }
|
29 |
|
30 | function getNextNonSpaceNonCommentCharacterIndex(text, node, options) {
|
31 | return _getNextNonSpaceNonCommentCharacterIndex(
|
32 | text,
|
33 | node,
|
34 | lookupIfPrettier2(options, "locEnd")
|
35 | );
|
36 | }
|
37 |
|
38 | function printNumber(rawNumber) {
|
39 | return (
|
40 | rawNumber
|
41 | .toLowerCase()
|
42 |
|
43 | .replace(/^([+-]?[\d.]+e)(?:\+|(-))?0*(\d)/, "$1$2$3")
|
44 |
|
45 | .replace(/^([+-]?[\d.]+)e[+-]?0+$/, "$1")
|
46 |
|
47 | .replace(/^([+-])?\./, "$10.")
|
48 |
|
49 | .replace(/(\.\d+?)0+(?=e|$)/, "$1")
|
50 |
|
51 | .replace(/\.(?=e)/, "")
|
52 | );
|
53 | }
|
54 |
|
55 |
|
56 | const PRECEDENCE = {};
|
57 | [
|
58 | ["or"],
|
59 | ["xor"],
|
60 | ["and"],
|
61 | [
|
62 | "=",
|
63 | "+=",
|
64 | "-=",
|
65 | "*=",
|
66 | "**=",
|
67 | "/=",
|
68 | ".=",
|
69 | "%=",
|
70 | "&=",
|
71 | "|=",
|
72 | "^=",
|
73 | "<<=",
|
74 | ">>=",
|
75 | ],
|
76 | ["??"],
|
77 | ["||"],
|
78 | ["&&"],
|
79 | ["|"],
|
80 | ["^"],
|
81 | ["&"],
|
82 | ["==", "===", "!=", "!==", "<>", "<=>"],
|
83 | ["<", ">", "<=", ">="],
|
84 | [">>", "<<"],
|
85 | ["+", "-", "."],
|
86 | ["*", "/", "%"],
|
87 | ["!"],
|
88 | ["instanceof"],
|
89 | ["++", "--", "~"],
|
90 | ["**"],
|
91 | ].forEach((tier, i) => {
|
92 | tier.forEach((op) => {
|
93 | PRECEDENCE[op] = i;
|
94 | });
|
95 | });
|
96 |
|
97 | function getPrecedence(op) {
|
98 | return PRECEDENCE[op];
|
99 | }
|
100 |
|
101 | const equalityOperators = ["==", "!=", "===", "!==", "<>", "<=>"];
|
102 | const multiplicativeOperators = ["*", "/", "%"];
|
103 | const bitshiftOperators = [">>", "<<"];
|
104 |
|
105 | function isBitwiseOperator(operator) {
|
106 | return (
|
107 | !!bitshiftOperators[operator] ||
|
108 | operator === "|" ||
|
109 | operator === "^" ||
|
110 | operator === "&"
|
111 | );
|
112 | }
|
113 |
|
114 | function shouldFlatten(parentOp, nodeOp) {
|
115 | if (getPrecedence(nodeOp) !== getPrecedence(parentOp)) {
|
116 | return false;
|
117 | }
|
118 |
|
119 |
|
120 |
|
121 | if (parentOp === "**") {
|
122 | return false;
|
123 | }
|
124 |
|
125 |
|
126 | if (
|
127 | equalityOperators.includes(parentOp) &&
|
128 | equalityOperators.includes(nodeOp)
|
129 | ) {
|
130 | return false;
|
131 | }
|
132 |
|
133 |
|
134 | if (
|
135 | (nodeOp === "%" && multiplicativeOperators.includes(parentOp)) ||
|
136 | (parentOp === "%" && multiplicativeOperators.includes(nodeOp))
|
137 | ) {
|
138 | return false;
|
139 | }
|
140 |
|
141 |
|
142 |
|
143 | if (
|
144 | nodeOp !== parentOp &&
|
145 | multiplicativeOperators.includes(nodeOp) &&
|
146 | multiplicativeOperators.includes(parentOp)
|
147 | ) {
|
148 | return false;
|
149 | }
|
150 |
|
151 |
|
152 | if (
|
153 | bitshiftOperators.includes(parentOp) &&
|
154 | bitshiftOperators.includes(nodeOp)
|
155 | ) {
|
156 | return false;
|
157 | }
|
158 |
|
159 | return true;
|
160 | }
|
161 |
|
162 | function nodeHasStatement(node) {
|
163 | return [
|
164 | "block",
|
165 | "program",
|
166 | "namespace",
|
167 | "class",
|
168 | "interface",
|
169 | "trait",
|
170 | "traituse",
|
171 | "declare",
|
172 | ].includes(node.kind);
|
173 | }
|
174 |
|
175 | function getBodyFirstChild({ body }) {
|
176 | if (!body) {
|
177 | return null;
|
178 | }
|
179 | if (body.kind === "block") {
|
180 | body = body.children;
|
181 | }
|
182 | return body[0];
|
183 | }
|
184 |
|
185 | function getNodeListProperty(node) {
|
186 | const body = node.children || node.body || node.adaptations;
|
187 | return Array.isArray(body) ? body : null;
|
188 | }
|
189 |
|
190 | function getParentNodeListProperty(path) {
|
191 | const parent = path.getParentNode();
|
192 | if (!parent) {
|
193 | return null;
|
194 | }
|
195 | return getNodeListProperty(parent);
|
196 | }
|
197 |
|
198 | function getLast(arr) {
|
199 | if (arr.length > 0) {
|
200 | return arr[arr.length - 1];
|
201 | }
|
202 | return null;
|
203 | }
|
204 |
|
205 | function getPenultimate(arr) {
|
206 | if (arr.length > 1) {
|
207 | return arr[arr.length - 2];
|
208 | }
|
209 | return null;
|
210 | }
|
211 |
|
212 | function isLastStatement(path) {
|
213 | const body = getParentNodeListProperty(path);
|
214 | if (!body) {
|
215 | return true;
|
216 | }
|
217 | const node = path.getValue();
|
218 | return body[body.length - 1] === node;
|
219 | }
|
220 |
|
221 | function isFirstChildrenInlineNode(path) {
|
222 | const node = path.getValue();
|
223 |
|
224 | if (node.kind === "program") {
|
225 | const children = getNodeListProperty(node);
|
226 |
|
227 | if (!children || children.length === 0) {
|
228 | return false;
|
229 | }
|
230 |
|
231 | return children[0].kind === "inline";
|
232 | }
|
233 |
|
234 | if (node.kind === "switch") {
|
235 | if (!node.body) {
|
236 | return false;
|
237 | }
|
238 |
|
239 | const children = getNodeListProperty(node.body);
|
240 |
|
241 | if (children.length === 0) {
|
242 | return false;
|
243 | }
|
244 |
|
245 | const [firstCase] = children;
|
246 |
|
247 | if (!firstCase.body) {
|
248 | return false;
|
249 | }
|
250 |
|
251 | const firstCaseChildren = getNodeListProperty(firstCase.body);
|
252 |
|
253 | if (firstCaseChildren.length === 0) {
|
254 | return false;
|
255 | }
|
256 |
|
257 | return firstCaseChildren[0].kind === "inline";
|
258 | }
|
259 |
|
260 | const firstChild = getBodyFirstChild(node);
|
261 |
|
262 | if (!firstChild) {
|
263 | return false;
|
264 | }
|
265 |
|
266 | return firstChild.kind === "inline";
|
267 | }
|
268 |
|
269 | function isDocNode(node) {
|
270 | return (
|
271 | node.kind === "nowdoc" ||
|
272 | (node.kind === "encapsed" && node.type === "heredoc")
|
273 | );
|
274 | }
|
275 |
|
276 |
|
277 |
|
278 |
|
279 |
|
280 | function docShouldHaveTrailingNewline(path, recurse = 0) {
|
281 | const node = path.getNode(recurse);
|
282 | const parent = path.getNode(recurse + 1);
|
283 | const parentParent = path.getNode(recurse + 2);
|
284 |
|
285 | if (!parent) {
|
286 | return false;
|
287 | }
|
288 |
|
289 | if (
|
290 | (parentParent &&
|
291 | ["call", "new", "echo"].includes(parentParent.kind) &&
|
292 | !["call", "array"].includes(parent.kind)) ||
|
293 | parent.kind === "parameter"
|
294 | ) {
|
295 | const lastIndex = parentParent.arguments.length - 1;
|
296 | const index = parentParent.arguments.indexOf(parent);
|
297 |
|
298 | return index !== lastIndex;
|
299 | }
|
300 |
|
301 | if (parentParent && parentParent.kind === "for") {
|
302 | const initIndex = parentParent.init.indexOf(parent);
|
303 |
|
304 | if (initIndex !== -1) {
|
305 | return initIndex !== parentParent.init.length - 1;
|
306 | }
|
307 |
|
308 | const testIndex = parentParent.test.indexOf(parent);
|
309 |
|
310 | if (testIndex !== -1) {
|
311 | return testIndex !== parentParent.test.length - 1;
|
312 | }
|
313 |
|
314 | const incrementIndex = parentParent.increment.indexOf(parent);
|
315 |
|
316 | if (incrementIndex !== -1) {
|
317 | return incrementIndex !== parentParent.increment.length - 1;
|
318 | }
|
319 | }
|
320 |
|
321 | if (parent.kind === "bin") {
|
322 | return (
|
323 | parent.left === node || docShouldHaveTrailingNewline(path, recurse + 1)
|
324 | );
|
325 | }
|
326 |
|
327 | if (parent.kind === "case" && parent.test === node) {
|
328 | return true;
|
329 | }
|
330 |
|
331 | if (parent.kind === "staticvariable") {
|
332 | const lastIndex = parentParent.variables.length - 1;
|
333 | const index = parentParent.variables.indexOf(parent);
|
334 |
|
335 | return index !== lastIndex;
|
336 | }
|
337 |
|
338 | if (parent.kind === "entry") {
|
339 | if (parent.key === node) {
|
340 | return true;
|
341 | }
|
342 |
|
343 | const lastIndex = parentParent.items.length - 1;
|
344 | const index = parentParent.items.indexOf(parent);
|
345 |
|
346 | return index !== lastIndex;
|
347 | }
|
348 |
|
349 | if (["call", "new"].includes(parent.kind)) {
|
350 | const lastIndex = parent.arguments.length - 1;
|
351 | const index = parent.arguments.indexOf(node);
|
352 |
|
353 | return index !== lastIndex;
|
354 | }
|
355 |
|
356 | if (parent.kind === "echo") {
|
357 | const lastIndex = parent.expressions.length - 1;
|
358 | const index = parent.expressions.indexOf(node);
|
359 |
|
360 | return index !== lastIndex;
|
361 | }
|
362 |
|
363 | if (parent.kind === "array") {
|
364 | const lastIndex = parent.items.length - 1;
|
365 | const index = parent.items.indexOf(node);
|
366 |
|
367 | return index !== lastIndex;
|
368 | }
|
369 |
|
370 | if (parent.kind === "retif") {
|
371 | return docShouldHaveTrailingNewline(path, recurse + 1);
|
372 | }
|
373 |
|
374 | return false;
|
375 | }
|
376 |
|
377 | function lineShouldEndWithSemicolon(path) {
|
378 | const node = path.getValue();
|
379 | const parentNode = path.getParentNode();
|
380 | if (!parentNode) {
|
381 | return false;
|
382 | }
|
383 |
|
384 |
|
385 | if (
|
386 | ["for", "foreach", "while", "do", "if", "switch"].includes(
|
387 | parentNode.kind
|
388 | ) &&
|
389 | node.kind !== "block" &&
|
390 | node.kind !== "if" &&
|
391 | (parentNode.body === node || parentNode.alternate === node)
|
392 | ) {
|
393 | return true;
|
394 | }
|
395 | if (!nodeHasStatement(parentNode)) {
|
396 | return false;
|
397 | }
|
398 | if (node.kind === "echo" && node.shortForm) {
|
399 | return false;
|
400 | }
|
401 | if (node.kind === "traituse") {
|
402 | return !node.adaptations;
|
403 | }
|
404 | if (node.kind === "method" && node.isAbstract) {
|
405 | return true;
|
406 | }
|
407 | if (node.kind === "method") {
|
408 | const parent = path.getParentNode();
|
409 | if (parent && parent.kind === "interface") {
|
410 | return true;
|
411 | }
|
412 | }
|
413 | return [
|
414 | "expressionstatement",
|
415 | "do",
|
416 | "usegroup",
|
417 | "classconstant",
|
418 | "propertystatement",
|
419 | "traitprecedence",
|
420 | "traitalias",
|
421 | "goto",
|
422 | "constantstatement",
|
423 | "global",
|
424 | "static",
|
425 | "echo",
|
426 | "unset",
|
427 | "return",
|
428 | "break",
|
429 | "continue",
|
430 | "throw",
|
431 | ].includes(node.kind);
|
432 | }
|
433 |
|
434 | function fileShouldEndWithHardline(path) {
|
435 | const node = path.getValue();
|
436 | const isProgramNode = node.kind === "program";
|
437 | const lastNode = node.children && getLast(node.children);
|
438 |
|
439 | if (!isProgramNode) {
|
440 | return false;
|
441 | }
|
442 |
|
443 | if (lastNode && ["halt", "inline"].includes(lastNode.kind)) {
|
444 | return false;
|
445 | }
|
446 |
|
447 | if (
|
448 | lastNode &&
|
449 | (lastNode.kind === "declare" || lastNode.kind === "namespace")
|
450 | ) {
|
451 | const lastNestedNode =
|
452 | lastNode.children.length > 0 && getLast(lastNode.children);
|
453 |
|
454 | if (lastNestedNode && ["halt", "inline"].includes(lastNestedNode.kind)) {
|
455 | return false;
|
456 | }
|
457 | }
|
458 |
|
459 | return true;
|
460 | }
|
461 |
|
462 | function maybeStripLeadingSlashFromUse(name) {
|
463 | const nameWithoutLeadingSlash = name.replace(/^\\/, "");
|
464 | if (nameWithoutLeadingSlash.indexOf("\\") !== -1) {
|
465 | return nameWithoutLeadingSlash;
|
466 | }
|
467 | return name;
|
468 | }
|
469 |
|
470 | function hasDanglingComments(node) {
|
471 | return (
|
472 | node.comments &&
|
473 | node.comments.some((comment) => !comment.leading && !comment.trailing)
|
474 | );
|
475 | }
|
476 |
|
477 | function hasLeadingComment(node) {
|
478 | return node.comments && node.comments.some((comment) => comment.leading);
|
479 | }
|
480 |
|
481 | function hasTrailingComment(node) {
|
482 | return node.comments && node.comments.some((comment) => comment.trailing);
|
483 | }
|
484 |
|
485 | function isLookupNode(node) {
|
486 | return (
|
487 | node.kind === "propertylookup" ||
|
488 | node.kind === "staticlookup" ||
|
489 | node.kind === "offsetlookup"
|
490 | );
|
491 | }
|
492 |
|
493 | function shouldPrintHardLineAfterStartInControlStructure(path) {
|
494 | const node = path.getValue();
|
495 |
|
496 | if (["try", "catch"].includes(node.kind)) {
|
497 | return false;
|
498 | }
|
499 |
|
500 | return isFirstChildrenInlineNode(path);
|
501 | }
|
502 |
|
503 | function shouldPrintHardLineBeforeEndInControlStructure(path) {
|
504 | const node = path.getValue();
|
505 |
|
506 | if (["try", "catch"].includes(node.kind)) {
|
507 | return true;
|
508 | }
|
509 |
|
510 | if (node.kind === "switch") {
|
511 | const children = getNodeListProperty(node.body);
|
512 |
|
513 | if (children.length === 0) {
|
514 | return true;
|
515 | }
|
516 |
|
517 | const lastCase = getLast(children);
|
518 |
|
519 | if (!lastCase.body) {
|
520 | return true;
|
521 | }
|
522 |
|
523 | const childrenInCase = getNodeListProperty(lastCase.body);
|
524 |
|
525 | if (childrenInCase.length === 0) {
|
526 | return true;
|
527 | }
|
528 |
|
529 | return childrenInCase[0].kind !== "inline";
|
530 | }
|
531 |
|
532 | return !isFirstChildrenInlineNode(path);
|
533 | }
|
534 |
|
535 | function getAlignment(text) {
|
536 | const lines = text.split("\n");
|
537 | const lastLine = lines.pop();
|
538 |
|
539 | return lastLine.length - lastLine.trimLeft().length + 1;
|
540 | }
|
541 |
|
542 | function getNextNode(path, node) {
|
543 | const parent = path.getParentNode();
|
544 | const children = getNodeListProperty(parent);
|
545 |
|
546 | if (!children) {
|
547 | return null;
|
548 | }
|
549 |
|
550 | const index = children.indexOf(node);
|
551 |
|
552 | if (index === -1) {
|
553 | return null;
|
554 | }
|
555 |
|
556 | return parent.children[index + 1];
|
557 | }
|
558 |
|
559 | function isProgramLikeNode(node) {
|
560 | return ["program", "declare", "namespace"].includes(node.kind);
|
561 | }
|
562 |
|
563 | function isReferenceLikeNode(node) {
|
564 | return [
|
565 | "name",
|
566 | "parentreference",
|
567 | "selfreference",
|
568 | "staticreference",
|
569 | ].includes(node.kind);
|
570 | }
|
571 |
|
572 |
|
573 |
|
574 | function getNodeKindIncludingLogical(node) {
|
575 | if (node.kind === "bin" && ["||", "&&"].includes(node.type)) {
|
576 | return "logical";
|
577 | }
|
578 |
|
579 | return node.kind;
|
580 | }
|
581 |
|
582 |
|
583 |
|
584 |
|
585 |
|
586 |
|
587 |
|
588 |
|
589 |
|
590 |
|
591 |
|
592 | function useDoubleQuote(node, options) {
|
593 | if (node.isDoubleQuote && options.singleQuote) {
|
594 | const rawValue = node.raw.slice(node.raw[0] === "b" ? 2 : 1, -1);
|
595 |
|
596 | return rawValue.match(
|
597 | /\\([$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3}|u{([0-9a-fA-F]+)})|\r?\n|'|"/
|
598 | );
|
599 | }
|
600 |
|
601 | return node.isDoubleQuote;
|
602 | }
|
603 |
|
604 | function hasEmptyBody(path, name = "body") {
|
605 | const node = path.getValue();
|
606 |
|
607 | return (
|
608 | node[name] &&
|
609 | node[name].children &&
|
610 | node[name].children.length === 0 &&
|
611 | (!node[name].comments || node[name].comments.length === 0)
|
612 | );
|
613 | }
|
614 |
|
615 | function isNextLineEmptyAfterNamespace(text, node, locStart) {
|
616 | let idx = locStart(node);
|
617 | idx = skipEverythingButNewLine(text, idx);
|
618 | idx = skipNewline(text, idx);
|
619 | return hasNewline(text, idx);
|
620 | }
|
621 |
|
622 | function shouldPrintHardlineBeforeTrailingComma(lastElem) {
|
623 | if (
|
624 | lastElem.kind === "nowdoc" ||
|
625 | (lastElem.kind === "encapsed" && lastElem.type === "heredoc")
|
626 | ) {
|
627 | return true;
|
628 | }
|
629 |
|
630 | if (
|
631 | lastElem.kind === "entry" &&
|
632 | (lastElem.value.kind === "nowdoc" ||
|
633 | (lastElem.value.kind === "encapsed" && lastElem.value.type === "heredoc"))
|
634 | ) {
|
635 | return true;
|
636 | }
|
637 |
|
638 | return false;
|
639 | }
|
640 |
|
641 | function getAncestorCounter(path, typeOrTypes) {
|
642 | const types = [].concat(typeOrTypes);
|
643 | let counter = -1;
|
644 | let ancestorNode;
|
645 | while ((ancestorNode = path.getParentNode(++counter))) {
|
646 | if (types.indexOf(ancestorNode.kind) !== -1) {
|
647 | return counter;
|
648 | }
|
649 | }
|
650 | return -1;
|
651 | }
|
652 |
|
653 | function getAncestorNode(path, typeOrTypes) {
|
654 | const counter = getAncestorCounter(path, typeOrTypes);
|
655 | return counter === -1 ? null : path.getParentNode(counter);
|
656 | }
|
657 |
|
658 | const magicMethods = [
|
659 | "__construct",
|
660 | "__destruct",
|
661 | "__call",
|
662 | "__callStatic",
|
663 | "__get",
|
664 | "__set",
|
665 | "__isset",
|
666 | "__unset",
|
667 | "__sleep",
|
668 | "__wakeup",
|
669 | "__toString",
|
670 | "__invoke",
|
671 | "__set_state",
|
672 | "__clone",
|
673 | "__debugInfo",
|
674 | ];
|
675 | const MagicMethodsMap = magicMethods.reduce((map, obj) => {
|
676 | map[obj.toLowerCase()] = obj;
|
677 |
|
678 | return map;
|
679 | }, {});
|
680 |
|
681 | function normalizeMagicMethodName(name) {
|
682 | const loweredName = name.toLowerCase();
|
683 |
|
684 | if (MagicMethodsMap[loweredName]) {
|
685 | return MagicMethodsMap[loweredName];
|
686 | }
|
687 |
|
688 | return name;
|
689 | }
|
690 |
|
691 | module.exports = {
|
692 | printNumber,
|
693 | getPrecedence,
|
694 | isBitwiseOperator,
|
695 | shouldFlatten,
|
696 | nodeHasStatement,
|
697 | getNodeListProperty,
|
698 | getParentNodeListProperty,
|
699 | getLast,
|
700 | getPenultimate,
|
701 | isLastStatement,
|
702 | getBodyFirstChild,
|
703 | lineShouldEndWithSemicolon,
|
704 | fileShouldEndWithHardline,
|
705 | maybeStripLeadingSlashFromUse,
|
706 | hasDanglingComments,
|
707 | hasLeadingComment,
|
708 | hasTrailingComment,
|
709 | docShouldHaveTrailingNewline,
|
710 | isLookupNode,
|
711 | isFirstChildrenInlineNode,
|
712 | shouldPrintHardLineAfterStartInControlStructure,
|
713 | shouldPrintHardLineBeforeEndInControlStructure,
|
714 | getAlignment,
|
715 | isProgramLikeNode,
|
716 | isReferenceLikeNode,
|
717 | getNodeKindIncludingLogical,
|
718 | useDoubleQuote,
|
719 | hasEmptyBody,
|
720 | isNextLineEmptyAfterNamespace,
|
721 | shouldPrintHardlineBeforeTrailingComma,
|
722 | isDocNode,
|
723 | getAncestorNode,
|
724 | getNextNode,
|
725 | normalizeMagicMethodName,
|
726 | isPreviousLineEmpty,
|
727 | isNextLineEmpty,
|
728 | getNextNonSpaceNonCommentCharacterIndex,
|
729 | };
|