@prettier/plugin-php
Advanced tools
Comparing version 0.0.0-development to 0.1.0
{ | ||
"name": "@prettier/plugin-php", | ||
"version": "0.0.0-development", | ||
"version": "0.1.0", | ||
"description": "Prettier PHP Plugin", | ||
@@ -13,4 +13,3 @@ "repository": "prettier/prettier-php", | ||
"dependencies": { | ||
"php-parser": "2.2.0", | ||
"prettier": "prettier/prettier#b449a526ce0f16703cdf5b4b22731b2a94775473" | ||
"php-parser": "glayzzle/php-parser#a8f10d8c9aacf8e90b283229f8bed76363b99fcf" | ||
}, | ||
@@ -23,5 +22,9 @@ "devDependencies": { | ||
"eslint-plugin-prettier": "2.4.0", | ||
"prettier": "prettier/prettier", | ||
"jest": "21.1.0", | ||
"jest-runner-eslint": "0.3.0" | ||
}, | ||
"peerDependencies": { | ||
"prettier": "^1.11.1" | ||
}, | ||
"scripts": { | ||
@@ -28,0 +31,0 @@ "test": "jest", |
@@ -37,19 +37,22 @@ <p align="center"> | ||
Please note that this plugin is under active development, and might not be ready to run on production code yet. | ||
Please note that this plugin is currently in alpha stage and still under active development. We encourage everyone to try it and give feedback, but we don't recommend it for production use yet. | ||
## Contributing | ||
## Install | ||
If you're interested in contributing to the development of Prettier for PHP, you can follow the [CONTRIBUTING guide from Prettier](https://github.com/prettier/prettier/blob/master/CONTRIBUTING.md), as it all applies to this repository too. | ||
You'll need a current development version of prettier, because the plugin depends on currently unreleased features. | ||
To test it out on a PHP file: | ||
yarn: | ||
* Clone this repository. | ||
* Run `yarn`. | ||
* Create a file called `test.php`. | ||
* Run `yarn prettier test.php` to check the output. | ||
```bash | ||
yarn add --dev prettier/prettier @prettier/plugin-php | ||
# or globally | ||
yarn global add prettier/prettier @prettier/plugin-php | ||
``` | ||
## Install | ||
npm: | ||
```bash | ||
yarn add --dev --exact prettier @prettier/plugin-php | ||
npm install --save-dev prettier/prettier @prettier/plugin-php | ||
# or globally | ||
npm install --global prettier/prettier @prettier/plugin-php | ||
``` | ||
@@ -59,6 +62,35 @@ | ||
If you installed prettier as a local dependency, you can add prettier as a script in your `package.json`, | ||
```json | ||
"scripts": { | ||
"prettier": "prettier" | ||
} | ||
``` | ||
and then run it via | ||
```bash | ||
prettier --write "**/*.php" | ||
yarn run prettier path/to/file.php --write | ||
# or | ||
npm run prettier path/to/file.php --write | ||
``` | ||
If you installed globally, run | ||
```bash | ||
prettier path/to/file.php --write | ||
``` | ||
## Contributing | ||
If you're interested in contributing to the development of Prettier for PHP, you can follow the [CONTRIBUTING guide from Prettier](https://github.com/prettier/prettier/blob/master/CONTRIBUTING.md), as it all applies to this repository too. | ||
To test it out on a PHP file: | ||
* Clone this repository. | ||
* Run `yarn`. | ||
* Create a file called `test.php`. | ||
* Run `yarn prettier test.php` to check the output. | ||
## Maintainers | ||
@@ -73,3 +105,3 @@ | ||
</br> | ||
Mike | ||
Mike Grip | ||
</a> | ||
@@ -84,4 +116,11 @@ </td> | ||
</td> | ||
<td align="center"> | ||
<a href="https://github.com/evilebottnawi"> | ||
<img width="150" height="150" src="https://github.com/evilebottnawi.png?v=3&s=150"> | ||
</br> | ||
Evilebot Tnawi | ||
</a> | ||
</td> | ||
</tr> | ||
<tbody> | ||
</table> |
266
src/index.js
@@ -5,2 +5,6 @@ "use strict"; | ||
const print = require("./printer"); | ||
const clean = require("./clean"); | ||
const options = require("./options"); | ||
const comments = require("./comments"); | ||
const { join, hardline } = require("prettier").doc.builders; | ||
@@ -32,6 +36,38 @@ const languages = [ | ||
const loc = function(prop) { | ||
return function(node) { | ||
return node.loc && node.loc[prop] && node.loc[prop].offset; | ||
}; | ||
// for anonymous classes, we need to keep track of the parent "new" node. | ||
// this is because in a case like this: | ||
// $test = new class($arg1, $arg2) extends TestClass {}; | ||
// we actually want to pretend that the class node starts after the "new" | ||
// node's arguments, since there is logic in prettier that assumes child nodes | ||
// will never overlap each other (ie the "new" node has the arguments, and class | ||
// as children, and we can't have them overlap) | ||
const anonymousClassesNewNodes = []; | ||
const loc = prop => node => { | ||
if ( | ||
node.kind === "new" && | ||
node.what.kind === "class" && | ||
node.what.isAnonymous && | ||
node.arguments.length > 0 && | ||
!anonymousClassesNewNodes.includes(node) | ||
) { | ||
anonymousClassesNewNodes.push(node); | ||
} | ||
if (prop === "start" && node.kind === "class" && node.isAnonymous) { | ||
const parentNewNode = anonymousClassesNewNodes.find( | ||
newNode => newNode.what === node | ||
); | ||
if (parentNewNode && parentNewNode.arguments.length > 0) { | ||
const lastArgumentNode = | ||
parentNewNode.arguments[parentNewNode.arguments.length - 1]; | ||
// if this is an anonymous class node with a parent "new" node, use the | ||
// location of the last argument in the new node as the fake start location | ||
// for this class node | ||
return ( | ||
(lastArgumentNode.loc && | ||
lastArgumentNode.loc.end && | ||
lastArgumentNode.loc.end.offset) + 1 | ||
); | ||
} | ||
} | ||
return node.loc && node.loc[prop] && node.loc[prop].offset; | ||
}; | ||
@@ -50,3 +86,217 @@ | ||
php: { | ||
print, | ||
massageAstNode: clean, | ||
// @TODO: determine if it makes sense to abstract this into a "getChildNodes" util function | ||
getCommentChildNodes(node) { | ||
const nodeMap = [ | ||
{ | ||
kinds: ["constant", "property", "classconstant"], | ||
children: { listNodes: [], nodes: ["value"] } | ||
}, | ||
{ | ||
kinds: ["return"], | ||
children: { listNodes: [], nodes: ["expr"] } | ||
}, | ||
{ | ||
kinds: ["static", "array", "usegroup"], | ||
children: { listNodes: ["items"], nodes: [] } | ||
}, | ||
{ | ||
kinds: ["entry"], | ||
children: { listNodes: [], nodes: ["key", "value"] } | ||
}, | ||
{ | ||
kinds: ["assign"], | ||
children: { listNodes: [], nodes: ["left", "right"] } | ||
}, | ||
{ | ||
kinds: ["if"], | ||
children: { listNodes: [], nodes: ["body", "alternate", "test"] } | ||
}, | ||
{ | ||
kinds: ["block", "program", "namespace", "declare"], | ||
children: { listNodes: ["children"], nodes: [] } | ||
}, | ||
{ | ||
kinds: ["foreach"], | ||
children: { | ||
listNodes: [], | ||
nodes: ["key", "source", "value", "body"] | ||
} | ||
}, | ||
{ | ||
kinds: ["try"], | ||
children: { | ||
listNodes: ["catches"], | ||
nodes: ["always", "body"] | ||
} | ||
}, | ||
{ | ||
kinds: ["call", "new"], | ||
children: { listNodes: ["arguments"], nodes: ["what"] } | ||
}, | ||
{ | ||
kinds: ["offsetlookup", "staticlookup"], | ||
children: { listNodes: [], nodes: ["what", "offset"] } | ||
}, | ||
{ | ||
kinds: ["catch"], | ||
children: { | ||
listNodes: ["what"], | ||
nodes: ["variable", "body"] | ||
} | ||
}, | ||
{ | ||
kinds: ["for", "while", "do"], | ||
children: { listNodes: [], nodes: ["body"] } | ||
}, | ||
{ | ||
kinds: ["trait", "class"], | ||
children: { listNodes: ["implements", "body"], nodes: ["extends"] } | ||
}, | ||
{ | ||
kinds: ["interface"], | ||
children: { listNodes: ["extends", "body"], nodes: [] } | ||
}, | ||
{ | ||
kinds: ["function", "method", "closure"], | ||
children: { listNodes: ["arguments", "uses"], nodes: ["body"] } | ||
}, | ||
{ | ||
kinds: ["switch", "case"], | ||
children: { listNodes: [], nodes: ["test", "body"] } | ||
}, | ||
{ | ||
kinds: ["bin"], | ||
children: { listNodes: [], nodes: ["left", "right"] } | ||
}, | ||
{ | ||
kinds: ["parameter", "yieldfrom"], | ||
children: { listNodes: [], nodes: ["value"] } | ||
}, | ||
{ | ||
kinds: ["retif"], | ||
children: { listNodes: [], nodes: ["trueExpr", "falseExpr", "test"] } | ||
}, | ||
{ | ||
kinds: ["echo"], | ||
children: { listNodes: ["arguments"], nodes: [] } | ||
}, | ||
{ | ||
kinds: ["parenthesis"], | ||
children: { listNodes: [], nodes: ["inner"] } | ||
}, | ||
{ | ||
kinds: ["include"], | ||
children: { listNodes: [], nodes: ["target"] } | ||
}, | ||
{ | ||
kinds: ["throw", "clone", "pre", "post", "unary", "cast"], | ||
children: { listNodes: [], nodes: ["what"] } | ||
}, | ||
{ | ||
kinds: ["list", "isset", "unset", "empty"], | ||
children: { listNodes: ["arguments"], nodes: [] } | ||
}, | ||
{ | ||
kinds: ["global"], | ||
children: { listNodes: ["items"], nodes: [] } | ||
}, | ||
{ | ||
kinds: ["print"], | ||
children: { listNodes: [], nodes: ["arguments"] } | ||
}, | ||
{ | ||
kinds: ["eval"], | ||
children: { listNodes: [], nodes: ["source"] } | ||
}, | ||
{ | ||
kinds: ["yield"], | ||
children: { listNodes: [], nodes: ["key", "value"] } | ||
}, | ||
{ | ||
kinds: ["traituse"], | ||
children: { listNodes: ["traits", "adaptations"], nodes: [] } | ||
}, | ||
{ | ||
kinds: ["traitprecedence"], | ||
children: { listNodes: [], nodes: ["trait", "instead"] } | ||
}, | ||
{ | ||
kinds: ["traitalias"], | ||
children: { listNodes: [], nodes: ["trait"] } | ||
}, | ||
{ | ||
kinds: ["silent"], | ||
children: { listNodes: [], nodes: ["expr"] } | ||
}, | ||
{ | ||
kinds: ["exit"], | ||
children: { listNodes: [], nodes: ["status"] } | ||
} | ||
]; | ||
const children = nodeMap.reduce((currentChildren, map) => { | ||
if (map.kinds.includes(node.kind)) { | ||
return [ | ||
...map.children.listNodes.reduce((accumulator, listNode) => { | ||
const childValue = node[listNode]; | ||
return childValue ? [...accumulator, ...childValue] : accumulator; | ||
}, []), | ||
...map.children.nodes.reduce((accumulator, childNode) => { | ||
const childValue = node[childNode]; | ||
return childValue ? [...accumulator, childValue] : accumulator; | ||
}, []) | ||
]; | ||
} | ||
return currentChildren; | ||
}, false); | ||
return children !== false ? children : []; | ||
}, | ||
canAttachComment(node) { | ||
return ( | ||
node.kind && node.kind !== "commentblock" && node.kind !== "commentline" | ||
); | ||
}, | ||
handleComments: { | ||
ownLine: comments.handleOwnLineComment, | ||
endOfLine: comments.handleEndOfLineComment, | ||
remaining: comments.handleRemainingComment | ||
}, | ||
printComment(commentPath) { | ||
const comment = commentPath.getValue(); | ||
switch (comment.kind) { | ||
case "commentblock": { | ||
// for now, don't touch single line block comments | ||
if (!comment.value.includes("\n")) { | ||
return comment.value; | ||
} | ||
const lines = comment.value.split("\n"); | ||
// if this is a block comment, handle indentation | ||
if ( | ||
lines | ||
.slice(1, lines.length - 1) | ||
.every(line => line.trim()[0] === "*") | ||
) { | ||
return join( | ||
hardline, | ||
lines.map( | ||
(line, index) => | ||
(index > 0 ? " " : "") + | ||
(index < lines.length - 1 ? line.trim() : line.trimLeft()) | ||
) | ||
); | ||
} | ||
// otherwise we can't be sure about indentation, so just print as is | ||
return comment.value; | ||
} | ||
case "commentline": { | ||
return comment.value.trimRight(); | ||
} | ||
default: | ||
throw new Error(`Not a comment: ${JSON.stringify(comment)}`); | ||
} | ||
} | ||
} | ||
@@ -58,3 +308,7 @@ }; | ||
printers, | ||
parsers | ||
parsers, | ||
options, | ||
defaultOptions: { | ||
tabWidth: 4 | ||
} | ||
}; |
1504
src/printer.js
"use strict"; | ||
const docBuilders = require("prettier").doc.builders; | ||
const util = require("prettier").util; | ||
const { | ||
concat, | ||
join, | ||
line, | ||
lineSuffix, | ||
lineSuffixBoundary, | ||
group, | ||
conditionalGroup, | ||
indent, | ||
dedent, | ||
ifBreak, | ||
hardline, | ||
softline | ||
} = require("prettier").doc.builders; | ||
const { willBreak } = require("prettier").doc.utils; | ||
const { makeString, isNextLineEmpty } = require("prettier").util; | ||
const comments = require("./comments"); | ||
const concat = docBuilders.concat; | ||
const join = docBuilders.join; | ||
const line = docBuilders.line; | ||
const group = docBuilders.group; | ||
const indent = docBuilders.indent; | ||
const hardline = docBuilders.hardline; | ||
const softline = docBuilders.softline; | ||
const { | ||
getNodeListProperty, | ||
getLast, | ||
isControlStructureNode, | ||
isFirstNodeInParentProgramNode, | ||
isFirstNodeInParentNode, | ||
isPrevNodeInline, | ||
isNextNodeInline, | ||
isLastStatement, | ||
lineShouldHaveStartPHPTag, | ||
lineShouldEndWithSemicolon, | ||
lineShouldHaveEndPHPTag, | ||
printNumber, | ||
shouldFlatten, | ||
shouldRemoveLines, | ||
removeNewlines, | ||
maybeStripLeadingSlashFromUse, | ||
fileShouldEndWithHardline, | ||
hasDanglingComments, | ||
docShouldHaveTrailingNewline | ||
} = require("./util"); | ||
// polyfill for node 4 | ||
function includes(array, val) { | ||
return array.indexOf(val) !== -1; | ||
function shouldPrintComma(options) { | ||
switch (options.trailingComma) { | ||
case "all": | ||
return true; | ||
// fallthrough | ||
case "none": | ||
default: | ||
return false; | ||
} | ||
} | ||
function genericPrint(path, options, print) { | ||
const n = path.getValue(); | ||
if (!n) { | ||
const node = path.getValue(); | ||
if (!node) { | ||
return ""; | ||
} else if (typeof n === "string") { | ||
return n; | ||
} else if (typeof node === "string") { | ||
return node; | ||
} | ||
return printNode(path, options, print); | ||
} | ||
function lineShouldEndWithSemicolon(path) { | ||
const node = path.getValue(); | ||
const semiColonWhitelist = [ | ||
"assign", | ||
"return", | ||
"break", | ||
"call", | ||
"pre", | ||
"post", | ||
"bin", | ||
"unary", | ||
"yield", | ||
"yieldfrom", | ||
"echo", | ||
"list", | ||
"print", | ||
"isset", | ||
"unset", | ||
"empty", | ||
"traitprecedence", | ||
"traitalias", | ||
"constant", | ||
"classconstant", | ||
"exit", | ||
"global", | ||
"static", | ||
"include", | ||
"goto", | ||
"throw" | ||
]; | ||
if (node.kind === "traituse") { | ||
return !node.adaptations; | ||
const isProgramNode = node.kind === "program"; | ||
if (isProgramNode && node.children.length === 0) { | ||
return concat([ | ||
"<?php", | ||
hardline, | ||
comments.printDanglingComments(path, options, /* sameIndent */ true) | ||
]); | ||
} | ||
return includes(semiColonWhitelist, node.kind); | ||
const printed = printNode(path, options, print); | ||
if (node.kind === "inline") { | ||
const parentNode = path.getParentNode(); | ||
return concat([ | ||
parentNode && | ||
parentNode.kind === "declare" && | ||
parentNode.children.indexOf(node) === 0 | ||
? "?>" | ||
: "", | ||
printed | ||
]); | ||
} | ||
if (node.kind === "block") { | ||
const nodeBody = getNodeListProperty(node); | ||
if (nodeBody.length !== 0) { | ||
return concat([ | ||
nodeBody[0].kind === "inline" ? "?>" : "", | ||
printed, | ||
nodeBody[nodeBody.length - 1].kind === "inline" ? "<?php" : "" | ||
]); | ||
} | ||
} | ||
return concat([ | ||
lineShouldHaveStartPHPTag(path) | ||
? concat([ | ||
"<?php", | ||
isFirstNodeInParentProgramNode(path) || | ||
(!isNextNodeInline(path) && !isControlStructureNode(node)) | ||
? hardline | ||
: " " | ||
]) | ||
: "", | ||
shouldRemoveLines(path) ? removeNewlines(printed) : printed, | ||
lineShouldEndWithSemicolon(path) ? ";" : "", | ||
lineShouldHaveEndPHPTag(path) | ||
? concat([ | ||
isFirstNodeInParentNode(path) || !isPrevNodeInline(path) | ||
? hardline | ||
: " ", | ||
"?>" | ||
]) | ||
: "" | ||
]); | ||
} | ||
function isLastStatement(path) { | ||
const parent = path.getParentNode(); | ||
if (!parent) { | ||
return true; | ||
// special layout for "chained" function calls, i.e. | ||
// $foo = a() | ||
// ->b() | ||
// ->c(); | ||
function printMemberChain(path, options, print) { | ||
// First step: Linearize and reorder the AST. | ||
// | ||
// Example: | ||
// a()->b->c()->d() | ||
// has the AST structure | ||
// Call (PropertyLookup d ( | ||
// Call (PropertyLookup c ( | ||
// PropertyLookup b ( | ||
// Call (Identifier a) | ||
// ) | ||
// )) | ||
// )) | ||
// and we transform it into (notice the reversed order) | ||
// [Identifier a, Call, PropertyLookup b, PropertyLookup c, Call, | ||
// PropertyLookup d, Call] | ||
const printedNodes = []; | ||
function traverse(path) { | ||
const node = path.getValue(); | ||
if (node.kind === "call") { | ||
printedNodes.unshift({ | ||
node, | ||
printed: concat([printArgumentsList(path, options, print)]) | ||
}); | ||
path.call(what => traverse(what), "what"); | ||
} else if (node.kind === "propertylookup") { | ||
printedNodes.unshift({ | ||
node, | ||
printed: concat([ | ||
"->", | ||
wrapPropertyLookup(node, path.call(print, "offset")) | ||
]) | ||
}); | ||
path.call(what => traverse(what), "what"); | ||
} else { | ||
printedNodes.unshift({ | ||
node, | ||
printed: path.call(print) | ||
}); | ||
} | ||
} | ||
const node = path.getValue(); | ||
const body = parent.children; | ||
return body && body[body.length - 1] === node; | ||
} | ||
traverse(path); | ||
function printLines(path, options, print) { | ||
const text = options.originalText; | ||
const printed = path.map(stmtPath => { | ||
const stmt = stmtPath.getValue(); | ||
const parts = []; | ||
parts.push(print(stmtPath)); | ||
if (lineShouldEndWithSemicolon(stmtPath)) { | ||
parts.push(";"); | ||
// create groups from list of nodes, i.e. | ||
// [Identifier a, Call, PropertyLookup b, PropertyLookup c, Call, | ||
// PropertyLookup d, Call] | ||
// will be grouped as | ||
// [ | ||
// [Identifier a, Call], | ||
// [PropertyLookup b, PropertyLookup c, Call], | ||
// [PropertyLookup d, Call] | ||
// ] | ||
// so that we can print it as | ||
// a() | ||
// ->b->c() | ||
// ->d() | ||
const groups = []; | ||
let currentGroup = []; | ||
let hasSeenCall = false; | ||
printedNodes.forEach(printedNode => { | ||
if (hasSeenCall && printedNode.node.kind === "propertylookup") { | ||
groups.push(currentGroup); | ||
currentGroup = [printedNode]; | ||
hasSeenCall = false; | ||
} else { | ||
currentGroup.push(printedNode); | ||
} | ||
if ( | ||
util.isNextLineEmpty(text, stmt, options) && | ||
!isLastStatement(stmtPath) | ||
) { | ||
parts.push(hardline); | ||
if (printedNode.node.kind === "call") { | ||
hasSeenCall = true; | ||
} | ||
return concat(parts); | ||
}); | ||
return join(hardline, printed); | ||
groups.push(currentGroup); | ||
function printGroup(printedGroup) { | ||
return concat(printedGroup.map(tuple => tuple.printed)); | ||
} | ||
function printIndentedGroup(groups) { | ||
if (groups.length === 0) { | ||
return ""; | ||
} | ||
return indent( | ||
group(concat([hardline, join(hardline, groups.map(printGroup))])) | ||
); | ||
} | ||
const printedGroups = groups.map(printGroup); | ||
if (groups.length <= 2) { | ||
return group(concat(printedGroups)); | ||
} | ||
const expanded = concat([ | ||
printGroup(groups[0]), | ||
printIndentedGroup(groups.slice(1)) | ||
]); | ||
return expanded; | ||
} | ||
function printArgumentsList(path, options, print, argumentsKey = "arguments") { | ||
const args = path.getValue()[argumentsKey]; | ||
const printed = path | ||
.map(print, argumentsKey) | ||
.map(argument => group(argument)); | ||
if (printed.length === 0) { | ||
return "()"; | ||
} | ||
const breakingIndex = printed.findIndex(willBreak); | ||
const breakingInTheMiddle = | ||
printed.length > 2 && | ||
breakingIndex !== 0 && | ||
breakingIndex !== printed.length - 1; | ||
const shouldGroupLast = getLast(args).kind === "array"; | ||
const shouldGroupFirst = !shouldGroupLast && args[0].kind === "array"; | ||
const shortForm = concat(["(", join(", ", printed), ")"]); | ||
const mediumForm = shouldGroupFirst | ||
? concat([ | ||
"(", | ||
group(printed[0], { shouldBreak: true }), | ||
printed.length > 1 ? ", " : "", | ||
join(concat([",", line]), printed.slice(1)), | ||
")" | ||
]) | ||
: concat([ | ||
"(", | ||
join(concat([",", line]), printed.slice(0, -1)), | ||
printed.length > 1 ? ", " : "", | ||
group(getLast(printed), { shouldBreak: true }), | ||
")" | ||
]); | ||
const longForm = group( | ||
concat([ | ||
"(", | ||
indent(concat([softline, join(concat([",", line]), printed)])), | ||
softline, | ||
")" | ||
]) | ||
); | ||
const formsToConsider = [ | ||
!breakingInTheMiddle ? shortForm : null, | ||
shouldGroupFirst || shouldGroupLast ? mediumForm : null, | ||
longForm | ||
]; | ||
return conditionalGroup(formsToConsider.filter(Boolean)); | ||
} | ||
function wrapPropertyLookup(node, doc) { | ||
const addCurly = | ||
node.kind === "propertylookup" && | ||
(node.offset.kind !== "constref" || typeof node.offset.name !== "string"); | ||
return addCurly ? concat(["{", doc, "}"]) : doc; | ||
} | ||
function isPropertyLookupChain(node) { | ||
if (node.kind !== "propertylookup") { | ||
return false; | ||
} | ||
if (node.what.kind === "variable") { | ||
return true; | ||
} | ||
return isPropertyLookupChain(node.what); | ||
} | ||
// so this is a bit hacky, but for anonymous classes, there's a chance that an | ||
// assumption core to prettier will break - that child nodes will not overlap. In | ||
// this case, if we have something like this: | ||
// $test = new class($arg1, $arg2) extends TestClass {}; | ||
// we end up with a parent "new" node, which has children "arguments", and "what" | ||
// the "what" is a "class" node, but it overlaps with the "arguments". To solve this, | ||
// we use this variable to store off the printed arguments when the "new" node is printing, | ||
// so that the "class" node can then access them later | ||
let anonymousClassesNewArguments = null; | ||
const expressionKinds = [ | ||
@@ -117,3 +315,4 @@ "array", | ||
"nowdoc", | ||
"encapsed" | ||
"encapsed", | ||
"variadic" | ||
]; | ||
@@ -125,3 +324,4 @@ function printExpression(path, options, print) { | ||
switch (node.kind) { | ||
case "propertylookup": | ||
case "propertylookup": { | ||
const canBreak = path.stack.indexOf("left") === -1; | ||
return group( | ||
@@ -131,5 +331,11 @@ concat([ | ||
"->", | ||
indent(concat([softline, path.call(print, "offset")])) | ||
indent( | ||
concat([ | ||
canBreak ? softline : "", | ||
wrapPropertyLookup(node, path.call(print, "offset")) | ||
]) | ||
) | ||
]) | ||
); | ||
} | ||
case "staticlookup": | ||
@@ -141,3 +347,4 @@ return concat([ | ||
]); | ||
case "offsetlookup": | ||
case "offsetlookup": { | ||
const isOffsetNumberNode = node.offset && node.offset.kind === "number"; | ||
return group( | ||
@@ -147,13 +354,24 @@ concat([ | ||
"[", | ||
softline, | ||
node.offset ? path.call(print, "offset") : "", | ||
softline, | ||
node.offset | ||
? group( | ||
concat([ | ||
indent( | ||
concat([ | ||
isOffsetNumberNode ? "" : softline, | ||
path.call(print, "offset") | ||
]) | ||
), | ||
isOffsetNumberNode ? "" : softline | ||
]) | ||
) | ||
: "", | ||
"]" | ||
]) | ||
); | ||
} | ||
default: | ||
return "Have not implemented lookup kind " + node.kind + " yet."; | ||
return `Have not implemented lookup kind ${node.kind} yet.`; | ||
} | ||
} | ||
if (includes(lookupKinds, node.kind)) { | ||
if (lookupKinds.includes(node.kind)) { | ||
return printLookup(node); | ||
@@ -169,14 +387,72 @@ } | ||
return concat([path.call(print, "what"), node.type + node.type]); | ||
case "bin": | ||
return concat([ | ||
path.call(print, "left"), | ||
" ", | ||
node.type, | ||
" ", | ||
path.call(print, "right") | ||
case "bin": { | ||
// general idea here is that we are continually checking nested | ||
// binary expressions to see if we should be starting a new group | ||
// or not (based on operator precedence) | ||
const printBinaryExpression = ( | ||
path, | ||
print, | ||
options, | ||
isNested = false | ||
) => { | ||
const node = path.getValue(); | ||
if (node.kind !== "bin") { | ||
return path.call(print); | ||
} | ||
const shouldGroupLeft = !( | ||
node.left.kind === "bin" && shouldFlatten(node.type, node.left.type) | ||
); | ||
const printedLeft = path.call( | ||
left => printBinaryExpression(left, print, options, true), | ||
"left" | ||
); | ||
const shouldGroupRight = !( | ||
node.right.kind === "bin" && | ||
shouldFlatten(node.type, node.right.type) | ||
); | ||
const printedRight = path.call( | ||
right => printBinaryExpression(right, print, options, true), | ||
"right" | ||
); | ||
const printedExpression = concat([ | ||
shouldGroupLeft ? group(printedLeft) : printedLeft, | ||
" ", | ||
node.type, | ||
line, | ||
shouldGroupRight ? group(printedRight) : printedRight | ||
]); | ||
return isNested ? printedExpression : group(printedExpression); | ||
}; | ||
return printBinaryExpression(path, print, options); | ||
} | ||
case "parenthesis": { | ||
const parentNode = path.getParentNode(); | ||
if (parentNode && parentNode.kind === "parenthesis") { | ||
return path.call(print, "inner"); | ||
} | ||
const shouldPrintParenthesis = | ||
parentNode.kind !== "print" && | ||
parentNode.kind !== "echo" && | ||
parentNode.kind !== "include"; | ||
const shouldBeInlined = | ||
node.inner.kind === "new" || node.inner.kind === "clone"; | ||
const dangling = comments.printDanglingComments(path, options, true); | ||
const printedInner = concat([ | ||
shouldBeInlined || !shouldPrintParenthesis ? "" : softline, | ||
dangling ? concat([dangling, hardline]) : "", | ||
path.call(print, "inner") | ||
]); | ||
case "parenthesis": | ||
return group( | ||
concat(["(", softline, path.call(print, "inner"), softline, ")"]) | ||
concat([ | ||
shouldPrintParenthesis ? "(" : "", | ||
shouldBeInlined ? printedInner : indent(printedInner), | ||
shouldPrintParenthesis | ||
? concat([shouldBeInlined ? "" : softline, ")"]) | ||
: "" | ||
]) | ||
); | ||
} | ||
case "unary": | ||
@@ -187,6 +463,6 @@ return concat([node.type, path.call(print, "what")]); | ||
default: | ||
return "Have not implemented operation kind " + node.kind + " yet."; | ||
return `Have not implemented operation kind ${node.kind} yet.`; | ||
} | ||
} | ||
if (includes(operationKinds, node.kind)) { | ||
if (operationKinds.includes(node.kind)) { | ||
return printOperation(node); | ||
@@ -207,3 +483,3 @@ } | ||
if (node.type === "heredoc") { | ||
return opening ? "<<<" + node.label : node.label; | ||
return opening ? `<<<${node.label}` : node.label; | ||
} | ||
@@ -217,3 +493,3 @@ const quotes = { | ||
} | ||
return "Unimplemented encapsed type " + node.type; | ||
return `Unimplemented encapsed type ${node.type}`; | ||
} | ||
@@ -225,2 +501,5 @@ | ||
case "string": { | ||
// @TODO: need resolve https://github.com/glayzzle/php-parser/issues/101 | ||
// @TODO: need resolve https://github.com/glayzzle/php-parser/issues/123 | ||
// @TODO: need resolve https://github.com/glayzzle/php-parser/issues/124 | ||
// @TODO: for now just reusing double/single quote preference from doc. could eventually | ||
@@ -230,6 +509,14 @@ // use setting from options. need to figure out how this works w/ complex strings and interpolation | ||
const quote = node.isDoubleQuote ? '"' : "'"; | ||
return quote + node.value + quote; | ||
let stringValue = node.raw; | ||
// we need to strip out the quotes from the raw value | ||
if (['"', "'"].includes(stringValue[0])) { | ||
stringValue = stringValue.substr(1); | ||
} | ||
if (['"', "'"].includes(stringValue[stringValue.length - 1])) { | ||
stringValue = stringValue.substr(0, stringValue.length - 1); | ||
} | ||
return makeString(stringValue, quote, false); | ||
} | ||
case "number": | ||
return node.value; | ||
return printNumber(node.value); | ||
case "encapsed": | ||
@@ -241,19 +528,32 @@ if (node.type === "offset") { | ||
getEncapsedQuotes(node), | ||
node.type === "heredoc" ? hardline : "", | ||
// Respect `indent` for `heredoc` nodes | ||
node.type === "heredoc" ? "\n" : "", | ||
concat( | ||
path.map(valuePath => { | ||
// might need to figure out better way to do this. don't want | ||
// to send through printNode() because value is a string and | ||
// we don't want the quotes | ||
const node = valuePath.getValue(); | ||
return node.kind === "string" ? node.value : print(valuePath); | ||
if (node.kind === "string") { | ||
return node.raw; | ||
} else if (node.kind === "variable") { | ||
if (typeof node.name === "object") { | ||
return concat(["${", path.call(print, "name"), "}"]); | ||
} else if (node.curly) { | ||
return `{$${node.name}}`; | ||
} | ||
return print(valuePath); | ||
} | ||
return concat(["{", print(valuePath), "}"]); | ||
}, "value") | ||
), | ||
getEncapsedQuotes(node, { opening: false }) | ||
getEncapsedQuotes(node, { opening: false }), | ||
node.type === "heredoc" && docShouldHaveTrailingNewline(path) | ||
? hardline | ||
: "" | ||
]); | ||
case "inline": | ||
return node.value; | ||
return node.raw; | ||
case "magic": | ||
return node.value; | ||
// for magic constant we prefer upper case | ||
return node.value.toUpperCase(); | ||
case "nowdoc": | ||
// Respect `indent` for `nowdoc` nodes | ||
return concat([ | ||
@@ -263,12 +563,13 @@ "<<<'", | ||
"'", | ||
hardline, | ||
"\n", | ||
node.value, | ||
hardline, | ||
node.label | ||
"\n", | ||
node.label, | ||
docShouldHaveTrailingNewline(path) ? "\n" : "" | ||
]); | ||
default: | ||
return "Have not implemented literal kind " + node.kind + " yet."; | ||
return `Have not implemented literal kind ${node.kind} yet.`; | ||
} | ||
} | ||
if (includes(literalKinds, node.kind)) { | ||
if (literalKinds.includes(node.kind)) { | ||
return printLiteral(node); | ||
@@ -283,3 +584,3 @@ } | ||
node.curly ? "{" : "", | ||
node.name, | ||
path.call(print, "name"), | ||
node.curly ? "}" : "" | ||
@@ -292,6 +593,21 @@ ]); | ||
return node.name; | ||
case "array": | ||
case "array": { | ||
const open = node.shortForm ? "[" : "array("; | ||
const close = node.shortForm ? "]" : ")"; | ||
if (node.items.length === 0) { | ||
if (!hasDanglingComments(node)) { | ||
return concat([open, close]); | ||
} | ||
return group( | ||
concat([ | ||
open, | ||
comments.printDanglingComments(path, options), | ||
softline, | ||
close | ||
]) | ||
); | ||
} | ||
return group( | ||
concat([ | ||
node.shortForm ? "[" : "array(", | ||
open, | ||
indent( | ||
@@ -303,6 +619,9 @@ concat([ | ||
), | ||
ifBreak(shouldPrintComma(options) ? "," : ""), | ||
comments.printDanglingComments(path, options, true), | ||
softline, | ||
node.shortForm ? "]" : ")" | ||
close | ||
]) | ||
); | ||
} | ||
case "yield": | ||
@@ -316,4 +635,6 @@ return concat([ | ||
return concat(["yield from ", path.call(print, "value")]); | ||
case "variadic": | ||
return concat(["...", path.call(print, "what")]); | ||
default: | ||
return "Have not implemented expression kind " + node.kind + " yet."; | ||
return `Have not implemented expression kind ${node.kind} yet.`; | ||
} | ||
@@ -369,2 +690,20 @@ } | ||
]; | ||
function printLines(path, options, print, childrenAttribute = "children") { | ||
return concat( | ||
path.map(childPath => { | ||
const canPrintBlankLine = | ||
!isLastStatement(childPath) && | ||
childPath.getValue().kind !== "inline" && | ||
!isNextNodeInline(childPath); | ||
return concat([ | ||
print(childPath), | ||
canPrintBlankLine ? hardline : "", | ||
canPrintBlankLine && | ||
isNextLineEmpty(options.originalText, childPath.getValue(), options) | ||
? hardline | ||
: "" | ||
]); | ||
}, childrenAttribute) | ||
); | ||
} | ||
function printStatement(path, options, print) { | ||
@@ -376,32 +715,32 @@ const node = path.getValue(); | ||
case "block": | ||
return path.call( | ||
childrenPath => printLines(childrenPath, options, print), | ||
"children" | ||
); | ||
return concat([ | ||
printLines(path, options, print), | ||
comments.printDanglingComments(path, options, true) | ||
]); | ||
case "program": { | ||
return concat([ | ||
"<?php", | ||
hardline, | ||
path.call(childrenPath => { | ||
return printLines(childrenPath, options, print); | ||
}, "children") | ||
printLines(path, options, print), | ||
fileShouldEndWithHardline(path) ? hardline : "" | ||
]); | ||
} | ||
case "namespace": | ||
case "namespace": { | ||
const printed = printLines(path, options, print); | ||
const hasName = node.name && typeof node.name === "string"; | ||
return concat([ | ||
"namespace ", | ||
node.name, | ||
";", | ||
concat( | ||
path.map( | ||
usegroup => concat([hardline, print(usegroup), ";"]), | ||
"children" | ||
) | ||
) | ||
hasName ? node.name : "", | ||
node.withBrackets ? concat([" ", "{"]) : ";", | ||
// don't know why we need 2 line breaks here, but 1 doesn't work | ||
node.children.length > 0 && !node.withBrackets | ||
? concat([hardline, hardline]) | ||
: "", | ||
node.withBrackets ? indent(concat([hardline, printed])) : printed, | ||
node.withBrackets ? concat([hardline, "}"]) : "" | ||
]); | ||
} | ||
default: | ||
return "Have not implemented block kind " + node.kind + " yet."; | ||
return `Have not implemented block kind ${node.kind} yet.`; | ||
} | ||
} | ||
if (includes(blockKinds, node.kind)) { | ||
if (blockKinds.includes(node.kind)) { | ||
return printBlock(path, options, print); | ||
@@ -413,6 +752,27 @@ } | ||
switch (node.kind) { | ||
case "echo": | ||
return concat(["echo ", join(", ", path.map(print, "arguments"))]); | ||
case "print": | ||
return concat(["print ", path.call(print, "arguments")]); | ||
case "echo": { | ||
const printedArguments = path.map(argumentPath => { | ||
const node = argumentPath.getValue(); | ||
return node.kind === "bin" | ||
? print(argumentPath) | ||
: dedent(print(argumentPath)); | ||
}, "arguments"); | ||
return indent( | ||
group( | ||
concat([ | ||
"echo ", | ||
group(join(concat([",", line]), printedArguments)) | ||
]) | ||
) | ||
); | ||
} | ||
case "print": { | ||
const printedArguments = path.call(print, "arguments"); | ||
return concat([ | ||
"print ", | ||
node.arguments.kind === "bin" | ||
? indent(printedArguments) | ||
: printedArguments | ||
]); | ||
} | ||
case "list": | ||
@@ -437,6 +797,6 @@ case "isset": | ||
default: | ||
return "Have not implemented sys kind " + node.kind + " yet."; | ||
return `Have not implemented sys kind ${node.kind} yet.`; | ||
} | ||
} | ||
if (includes(sysKinds, node.kind)) { | ||
if (sysKinds.includes(node.kind)) { | ||
return printSys(node); | ||
@@ -457,21 +817,108 @@ } | ||
function printDeclaration(node) { | ||
function printDeclarationBlock({ | ||
declaration, | ||
argumentsList = [], | ||
returnTypeContents = "", | ||
bodyContents = "" | ||
}) { | ||
const isClassLikeNode = ["class", "interface", "trait"].includes( | ||
node.kind | ||
); | ||
const printedDeclaration = group(declaration); | ||
const printedSignature = !isClassLikeNode | ||
? group( | ||
concat([ | ||
"(", | ||
argumentsList.length | ||
? concat([ | ||
indent( | ||
concat([ | ||
softline, | ||
join(concat([",", line]), argumentsList) | ||
]) | ||
), | ||
softline | ||
]) | ||
: "", | ||
")", | ||
returnTypeContents ? concat([": ", returnTypeContents]) : "" | ||
]) | ||
) | ||
: ""; | ||
const printedBody = bodyContents | ||
? concat([ | ||
"{", | ||
indent(concat([hardline, bodyContents])), | ||
comments.printDanglingComments(path, options, true), | ||
hardline, | ||
"}" | ||
]) | ||
: ""; | ||
return concat([ | ||
group( | ||
concat([ | ||
printedDeclaration, | ||
printedSignature, | ||
// see https://github.com/prettier/plugin-php/issues/107 | ||
// options.openingBraceNewLine ? hardline : " ", | ||
// Hack, we need `invertLine` command here, as `line`, but have versa vice logic | ||
bodyContents | ||
? node.kind === "function" || node.kind === "method" | ||
? ifBreak(" ", concat([lineSuffix(""), lineSuffixBoundary])) | ||
: hardline | ||
: "" | ||
]) | ||
), | ||
printedBody | ||
]); | ||
} | ||
switch (node.kind) { | ||
case "class": | ||
return concat([ | ||
group( | ||
case "class": { | ||
const classPrefixes = [ | ||
...(node.isFinal ? ["final"] : []), | ||
...(node.isAbstract ? ["abstract"] : []) | ||
]; | ||
const parentNode = path.getParentNode(); | ||
// if this is an anonymous class, we need to check if the parent was a | ||
// "new" node. if it was, we need to get the arguments from that node | ||
// ex: $test = new class($arg1, $arg2) extends TestClass {}; | ||
const anonymousArguments = | ||
node.isAnonymous && | ||
parentNode.kind === "new" && | ||
parentNode.arguments.length > 0 | ||
? anonymousClassesNewArguments | ||
: ""; | ||
return printDeclarationBlock({ | ||
declaration: concat([ | ||
classPrefixes.join(" "), | ||
classPrefixes.length > 0 ? " " : "", | ||
concat([ | ||
node.isAbstract ? "abstract " : "", | ||
node.isFinal ? "final " : "", | ||
"class ", | ||
node.name, | ||
"class", | ||
anonymousArguments, | ||
node.name ? concat([" ", node.name]) : "" | ||
]), | ||
group( | ||
indent( | ||
concat([ | ||
node.extends | ||
? concat([line, "extends ", path.call(print, "extends")]) | ||
? group( | ||
concat([line, "extends ", path.call(print, "extends")]) | ||
) | ||
: "", | ||
line, | ||
node.implements | ||
? concat([ | ||
line, | ||
"implements ", | ||
join(", ", path.map(print, "implements")) | ||
"implements", | ||
group( | ||
indent( | ||
concat([ | ||
line, | ||
join( | ||
concat([",", line]), | ||
path.map(print, "implements") | ||
) | ||
]) | ||
) | ||
) | ||
]) | ||
@@ -481,79 +928,42 @@ : "" | ||
) | ||
]) | ||
), | ||
hardline, | ||
"{", | ||
hardline, | ||
indent( | ||
concat([ | ||
hardline, | ||
path.call(bodyPath => { | ||
return printLines(bodyPath, options, print); | ||
}, "body") | ||
]) | ||
), | ||
hardline, | ||
"}" | ||
]); | ||
) | ||
]), | ||
bodyContents: printLines(path, options, print, "body") | ||
}); | ||
} | ||
case "function": | ||
return concat([ | ||
group(concat(["function ", node.name, "("])), | ||
group( | ||
concat([ | ||
indent( | ||
join( | ||
", ", | ||
path.map( | ||
argument => concat([softline, print(argument)]), | ||
"arguments" | ||
) | ||
) | ||
), | ||
softline | ||
]) | ||
), | ||
group(") {"), | ||
indent(concat([hardline, path.call(print, "body")])), | ||
concat([hardline, "}"]) | ||
]); | ||
case "method": | ||
return concat([ | ||
group( | ||
concat([ | ||
node.isFinal ? "final " : "", | ||
node.visibility, | ||
node.isStatic ? " static" : "", | ||
" function ", | ||
node.name, | ||
"(" | ||
]) | ||
), | ||
group( | ||
concat([ | ||
indent( | ||
join( | ||
", ", | ||
path.map( | ||
argument => concat([softline, print(argument)]), | ||
"arguments" | ||
) | ||
) | ||
), | ||
softline | ||
]) | ||
), | ||
")", | ||
node.body | ||
? concat([ | ||
hardline, | ||
"{", | ||
indent(concat([hardline, path.call(print, "body")])), | ||
hardline, | ||
"}" | ||
]) | ||
: "" | ||
]); | ||
return printDeclarationBlock({ | ||
declaration: concat(["function ", node.byref ? "&" : "", node.name]), | ||
argumentsList: path.map(print, "arguments"), | ||
returnTypeContents: node.type | ||
? concat([node.nullable ? "?" : "", path.call(print, "type")]) | ||
: "", | ||
bodyContents: node.body ? path.call(print, "body") : "" | ||
}); | ||
case "method": { | ||
const methodPrefixes = [ | ||
...(node.isFinal ? ["final"] : []), | ||
...(node.isAbstract ? ["abstract"] : []), | ||
...(node.visibility ? [node.visibility] : []), | ||
...(node.isStatic ? ["static"] : []) | ||
]; | ||
return printDeclarationBlock({ | ||
declaration: concat([ | ||
methodPrefixes.join(" "), | ||
methodPrefixes.length > 0 ? " " : "", | ||
"function ", | ||
node.byref ? "&" : "", | ||
node.name | ||
]), | ||
argumentsList: path.map(print, "arguments"), | ||
returnTypeContents: node.type | ||
? concat([node.nullable ? "?" : "", path.call(print, "type")]) | ||
: "", | ||
bodyContents: node.body ? concat([path.call(print, "body")]) : "" | ||
}); | ||
} | ||
case "parameter": { | ||
const name = concat([ | ||
node.type ? path.call(print, "type") + " " : "", | ||
node.nullable ? "?" : "", | ||
node.type ? concat([path.call(print, "type"), " "]) : "", | ||
node.variadic ? "..." : "", | ||
@@ -577,9 +987,7 @@ node.byref ? "&" : "", | ||
concat([ | ||
node.visibility, | ||
node.isStatic ? " static " : "", | ||
" $", | ||
node.visibility ? concat([node.visibility, " "]) : "", | ||
node.isStatic ? "static " : "", | ||
"$", | ||
node.name, | ||
node.value | ||
? indent(concat([line, "= ", path.call(print, "value")])) | ||
: "", | ||
node.value ? concat([" = ", path.call(print, "value")]) : "", | ||
";" | ||
@@ -589,88 +997,115 @@ ]) | ||
case "interface": | ||
return concat([ | ||
group( | ||
concat([ | ||
concat(["interface ", node.name]), | ||
node.extends | ||
? indent( | ||
concat([ | ||
line, | ||
"extends ", | ||
join(", ", path.map(print, "extends")) | ||
]) | ||
) | ||
: "", | ||
" {" | ||
]) | ||
), | ||
indent( | ||
concat( | ||
path.map(element => { | ||
return concat([hardline, print(element), ";"]); | ||
}, "body") | ||
) | ||
), | ||
hardline, | ||
"}" | ||
]); | ||
return printDeclarationBlock({ | ||
declaration: concat([ | ||
concat(["interface ", node.name]), | ||
node.extends | ||
? indent( | ||
concat([ | ||
line, | ||
"extends ", | ||
join(", ", path.map(print, "extends")) | ||
]) | ||
) | ||
: "" | ||
]), | ||
bodyContents: printLines(path, options, print, "body") | ||
}); | ||
case "trait": | ||
return printDeclarationBlock({ | ||
declaration: concat([ | ||
concat(["trait ", node.name]), | ||
node.extends | ||
? indent(concat([line, "extends ", path.call(print, "extends")])) | ||
: "", | ||
node.implements | ||
? indent( | ||
concat([ | ||
line, | ||
"implements ", | ||
join(", ", path.map(print, "implements")) | ||
]) | ||
) | ||
: "" | ||
]), | ||
bodyContents: printLines(path, options, print, "body") | ||
}); | ||
case "constant": | ||
case "classconstant": | ||
return concat([ | ||
group( | ||
concat([ | ||
concat(["trait ", node.name]), | ||
node.extends | ||
? indent( | ||
concat([line, "extends ", path.call(print, "extends")]) | ||
) | ||
: "", | ||
node.implements | ||
? indent( | ||
concat([ | ||
line, | ||
"implements ", | ||
join(", ", path.map(print, "implements")) | ||
]) | ||
) | ||
: "", | ||
" {" | ||
]) | ||
), | ||
indent( | ||
concat( | ||
path.map(element => concat([hardline, print(element)]), "body") | ||
) | ||
), | ||
hardline, | ||
"}" | ||
node.visibility ? concat([node.visibility, " "]) : "", | ||
"const ", | ||
node.name, | ||
" = ", | ||
path.call(print, "value") | ||
]); | ||
case "constant": | ||
case "classconstant": | ||
return concat(["const ", node.name, " = ", path.call(print, "value")]); | ||
default: | ||
return "Have not implmented declaration kind " + node.kind + " yet."; | ||
return `Have not implmented declaration kind ${node.kind} yet.`; | ||
} | ||
} | ||
if (includes(declarationKinds, node.kind)) { | ||
if (declarationKinds.includes(node.kind)) { | ||
return printDeclaration(node); | ||
} | ||
function printBodyControlStructure(path, bodyProperty = "body") { | ||
const node = path.getValue(); | ||
if (!node[bodyProperty]) { | ||
return ";"; | ||
} | ||
const printedBody = path.call(print, bodyProperty); | ||
return concat([ | ||
node.shortForm ? ":" : " {", | ||
indent( | ||
concat([ | ||
comments.printDanglingComments(path, options, true), | ||
node[bodyProperty].kind !== "block" || | ||
(node[bodyProperty].children && | ||
node[bodyProperty].children.length > 0) || | ||
(node[bodyProperty].comments && | ||
node[bodyProperty].comments.length > 0) | ||
? concat([hardline, printedBody]) | ||
: "" | ||
]) | ||
), | ||
node.kind === "if" && bodyProperty === "body" | ||
? "" | ||
: concat([ | ||
hardline, | ||
node.shortForm ? concat(["end", node.kind, ";"]) : "}" | ||
]) | ||
]); | ||
} | ||
switch (node.kind) { | ||
case "assign": | ||
return join(concat([" ", node.operator, " "]), [ | ||
path.call(print, "left"), | ||
path.call(print, "right") | ||
]); | ||
case "assign": { | ||
const canBreak = | ||
["bin", "number", "string"].includes(node.right.kind) || | ||
(node.right.kind === "retif" && node.right.trueExpr === null) || | ||
isPropertyLookupChain(node.right); | ||
return group( | ||
concat([ | ||
path.call(print, "left"), | ||
" ", | ||
node.operator, | ||
canBreak | ||
? indent(concat([line, path.call(print, "right")])) | ||
: concat([" ", path.call(print, "right")]) | ||
]) | ||
); | ||
} | ||
case "if": { | ||
const handleIfAlternate = alternate => { | ||
if (!alternate) { | ||
return "}"; | ||
return node.shortForm ? "endif;" : "}"; | ||
} | ||
if (alternate.kind === "if") { | ||
return concat(["} else", path.call(print, "alternate")]); | ||
return concat([ | ||
node.shortForm ? "" : "} ", | ||
"else", | ||
path.call(print, "alternate") | ||
]); | ||
} | ||
return concat([ | ||
"} else {", | ||
indent(concat([line, path.call(print, "alternate")])), | ||
line, | ||
"}" | ||
node.shortForm ? "" : "} ", | ||
"else", | ||
printBodyControlStructure(path, "alternate") | ||
]); | ||
@@ -680,17 +1115,4 @@ }; | ||
group( | ||
concat(["if (", softline, path.call(print, "test"), softline, ") {"]) | ||
), | ||
indent(concat([line, path.call(print, "body")])), | ||
line, | ||
handleIfAlternate(node.alternate) | ||
]); | ||
} | ||
case "do": | ||
return concat([ | ||
"do {", | ||
indent(concat([line, path.call(print, "body")])), | ||
line, | ||
group( | ||
concat([ | ||
"} while (", | ||
"if (", | ||
group( | ||
@@ -702,23 +1124,44 @@ concat([ | ||
), | ||
");" | ||
")", | ||
printBodyControlStructure(path) | ||
]) | ||
) | ||
), | ||
hardline, | ||
handleIfAlternate(node.alternate) | ||
]); | ||
case "while": | ||
} | ||
case "do": | ||
return concat([ | ||
"do", | ||
printBodyControlStructure(path), | ||
" while (", | ||
group( | ||
concat([ | ||
"while (", | ||
softline, | ||
path.call(print, "test"), | ||
softline, | ||
") {" | ||
indent(concat([softline, path.call(print, "test")])), | ||
softline | ||
]) | ||
), | ||
indent(concat([line, path.call(print, "body")])), | ||
line, | ||
"}" | ||
");" | ||
]); | ||
case "while": | ||
return group( | ||
concat([ | ||
"while (", | ||
group( | ||
concat([ | ||
indent(concat([softline, path.call(print, "test")])), | ||
softline | ||
]) | ||
), | ||
")", | ||
printBodyControlStructure(path) | ||
]) | ||
); | ||
case "for": { | ||
const parts = [ | ||
const body = printBodyControlStructure(path); | ||
if (!node.init.length && !node.test.length && !node.increment.length) { | ||
return concat([group(concat(["for (;;)", body]))]); | ||
} | ||
return concat([ | ||
"for (", | ||
@@ -731,14 +1174,10 @@ group( | ||
group( | ||
concat([ | ||
join(concat([",", line]), path.map(print, "init")), | ||
";" | ||
]) | ||
concat([join(concat([",", line]), path.map(print, "init"))]) | ||
), | ||
";", | ||
line, | ||
group( | ||
concat([ | ||
join(concat([",", line]), path.map(print, "test")), | ||
";" | ||
]) | ||
concat([join(concat([",", line]), path.map(print, "test"))]) | ||
), | ||
";", | ||
line, | ||
@@ -748,13 +1187,8 @@ group(join(concat([",", line]), path.map(print, "increment"))) | ||
), | ||
softline, | ||
node.body ? ") {" : ");" | ||
softline | ||
]) | ||
) | ||
]; | ||
if (node.body) { | ||
parts.push( | ||
concat([indent(concat([line, path.call(print, "body")])), line, "}"]) | ||
); | ||
} | ||
return concat(parts); | ||
), | ||
")", | ||
body | ||
]); | ||
} | ||
@@ -770,19 +1204,19 @@ case "foreach": | ||
path.call(print, "source"), | ||
" as", | ||
line, | ||
"as ", | ||
node.key | ||
? join(" => ", [ | ||
path.call(print, "key"), | ||
path.call(print, "value") | ||
]) | ||
? indent( | ||
join(concat([" =>", line]), [ | ||
path.call(print, "key"), | ||
path.call(print, "value") | ||
]) | ||
) | ||
: path.call(print, "value") | ||
]) | ||
), | ||
softline, | ||
") {" | ||
softline | ||
]) | ||
), | ||
indent(concat([line, path.call(print, "body")])), | ||
line, | ||
"}" | ||
")", | ||
printBodyControlStructure(path) | ||
]); | ||
@@ -794,115 +1228,102 @@ case "switch": | ||
"switch (", | ||
indent(concat([softline, path.call(print, "test")])), | ||
softline, | ||
path.call(print, "test"), | ||
softline, | ||
") {" | ||
")" | ||
]) | ||
), | ||
indent( | ||
concat( | ||
path.map( | ||
caseChild => concat([line, print(caseChild)]), | ||
"body", | ||
"children" | ||
) | ||
) | ||
), | ||
line, | ||
"}" | ||
printBodyControlStructure(path) | ||
]); | ||
case "call": | ||
case "call": { | ||
// chain: Call (PropertyLookup (Call (PropertyLookup (...)))) | ||
if (node.what.kind === "propertylookup") { | ||
return printMemberChain(path, options, print); | ||
} | ||
return concat([ | ||
path.call(print, "what"), | ||
printArgumentsList(path, options, print) | ||
]); | ||
} | ||
case "usegroup": | ||
return group( | ||
concat([ | ||
path.call(print, "what"), | ||
concat([ | ||
"(", | ||
indent( | ||
concat([ | ||
softline, | ||
join(concat([",", line]), path.map(print, "arguments")) | ||
]) | ||
), | ||
softline, | ||
")" | ||
]) | ||
"use ", | ||
node.type ? concat([node.type, " "]) : "", | ||
indent( | ||
concat([ | ||
node.name | ||
? concat([ | ||
maybeStripLeadingSlashFromUse(node.name), | ||
"\\{", | ||
softline | ||
]) | ||
: "", | ||
join( | ||
concat([",", line]), | ||
path.map(item => concat([print(item)]), "items") | ||
) | ||
]) | ||
), | ||
node.name ? concat([softline, "}"]) : "", | ||
";" | ||
]) | ||
); | ||
case "usegroup": | ||
return group( | ||
indent( | ||
concat([ | ||
"use ", | ||
node.type ? concat([node.type, " "]) : "", | ||
node.name ? concat([node.name, "\\{"]) : "", | ||
join( | ||
concat([",", line]), | ||
path.map(item => concat([print(item)]), "items") | ||
), | ||
node.name ? "}" : "" | ||
]) | ||
) | ||
); | ||
case "useitem": | ||
return node.alias ? concat([node.name, " as ", node.alias]) : node.name; | ||
return concat([ | ||
node.type ? concat([node.type, " "]) : "", | ||
maybeStripLeadingSlashFromUse(node.name), | ||
node.alias ? concat([" as ", node.alias]) : "" | ||
]); | ||
case "closure": | ||
return concat([ | ||
"function (", | ||
group( | ||
concat([ | ||
indent( | ||
join( | ||
", ", | ||
path.map( | ||
argument => concat([softline, print(argument)]), | ||
"arguments" | ||
) | ||
) | ||
), | ||
softline | ||
]) | ||
), | ||
"function ", | ||
printArgumentsList(path, options, print), | ||
node.uses && node.uses.length > 0 | ||
? group( | ||
concat([ | ||
") use (", | ||
indent( | ||
join( | ||
", ", | ||
path.map(use => { | ||
return concat([softline, print(use)]); | ||
}, "uses") | ||
) | ||
), | ||
softline | ||
" use ", | ||
printArgumentsList(path, options, print, "uses") | ||
]) | ||
) | ||
: "", | ||
group(") {"), | ||
" {", | ||
indent(concat([hardline, path.call(print, "body")])), | ||
concat([hardline, "}"]) | ||
]); | ||
case "retif": | ||
case "retif": { | ||
const parent = path.getParentNode(); | ||
const canBreak = !node.trueExpr; | ||
const forceNoIndent = parent.kind === "retif" || canBreak; | ||
const printedExpr = concat([ | ||
line, | ||
"?", | ||
node.trueExpr ? concat([" ", path.call(print, "trueExpr"), line]) : "", | ||
": ", | ||
path.call(print, "falseExpr") | ||
]); | ||
return group( | ||
concat([ | ||
path.call(print, "test"), | ||
indent( | ||
concat([ | ||
line, | ||
"?", | ||
node.trueExpr | ||
? concat([" ", path.call(print, "trueExpr"), line]) | ||
: "", | ||
": ", | ||
path.call(print, "falseExpr") | ||
]) | ||
) | ||
forceNoIndent ? printedExpr : indent(printedExpr) | ||
]) | ||
); | ||
} | ||
case "exit": | ||
return concat(["exit(", path.call(print, "status"), ")"]); | ||
return group( | ||
concat([ | ||
node.useDie ? "die" : "exit", | ||
"(", | ||
node.status | ||
? concat([ | ||
indent(concat([softline, path.call(print, "status")])), | ||
softline | ||
]) | ||
: comments.printDanglingComments(path, options), | ||
")" | ||
]) | ||
); | ||
case "clone": | ||
return concat(["clone ", path.call(print, "what")]); | ||
case "declare": { | ||
const printDeclareArguments = function(path) { | ||
const directive = Object.keys(path.getValue().what)[0]; | ||
const printDeclareArguments = path => { | ||
const [directive] = Object.keys(path.getValue().what); | ||
return concat([directive, "=", path.call(print, "what", directive)]); | ||
@@ -916,6 +1337,3 @@ }; | ||
hardline, | ||
path.call( | ||
childrenPath => printLines(childrenPath, options, print), | ||
"children" | ||
), | ||
concat(path.map(print, "children")), | ||
hardline, | ||
@@ -929,11 +1347,3 @@ "enddeclare;" | ||
") {", | ||
indent( | ||
concat([ | ||
hardline, | ||
path.call( | ||
childrenPath => printLines(childrenPath, options, print), | ||
"children" | ||
) | ||
]) | ||
), | ||
indent(concat([hardline, concat(path.map(print, "children"))])), | ||
hardline, | ||
@@ -948,6 +1358,3 @@ "}" | ||
hardline, | ||
path.call( | ||
childrenPath => printLines(childrenPath, options, print), | ||
"children" | ||
) | ||
concat(path.map(print, "children")) | ||
]); | ||
@@ -960,3 +1367,3 @@ } | ||
indent( | ||
concat([line, join(concat([",", line]), path.map(print, "items"))]) | ||
concat([" ", join(concat([",", line]), path.map(print, "items"))]) | ||
) | ||
@@ -971,3 +1378,3 @@ ]) | ||
concat([ | ||
line, | ||
" ", | ||
join( | ||
@@ -1001,3 +1408,10 @@ concat([",", line]), | ||
return concat(["goto ", node.label]); | ||
case "new": | ||
case "new": { | ||
// if the child node is an anonymous class, we need to store the arguments | ||
// so the child class node can access them later | ||
const isAnonymousClassNode = | ||
node.what && node.what.kind === "class" && node.what.isAnonymous; | ||
if (isAnonymousClassNode && node.arguments.length > 0) { | ||
anonymousClassesNewArguments = printArgumentsList(path, options, print); | ||
} | ||
return group( | ||
@@ -1007,20 +1421,16 @@ concat([ | ||
path.call(print, "what"), | ||
"(", | ||
indent( | ||
join( | ||
", ", | ||
path.map( | ||
argument => concat([softline, print(argument)]), | ||
"arguments" | ||
) | ||
) | ||
), | ||
softline, | ||
")" | ||
isAnonymousClassNode ? "" : printArgumentsList(path, options, print) | ||
]) | ||
); | ||
} | ||
case "try": | ||
return concat([ | ||
"try {", | ||
indent(concat([hardline, path.call(print, "body")])), | ||
indent( | ||
concat([ | ||
hardline, | ||
path.call(print, "body"), | ||
comments.printDanglingComments(path, options, true) | ||
]) | ||
), | ||
hardline, | ||
@@ -1051,3 +1461,9 @@ "}", | ||
" {", | ||
indent(concat([hardline, path.call(print, "body")])), | ||
indent( | ||
concat([ | ||
hardline, | ||
path.call(print, "body"), | ||
comments.printDanglingComments(path, options, true) | ||
]) | ||
), | ||
hardline, | ||
@@ -1061,7 +1477,14 @@ "}" | ||
case "halt": | ||
return concat(["__halt_compiler();", node.after]); | ||
//@TODO: leaving eval until we figure out encapsed https://github.com/prettier/prettier-php/pull/2 | ||
return concat(["__halt_compiler();", node.after.replace(/\n$/, "")]); | ||
case "eval": | ||
return group( | ||
concat([ | ||
"eval(", | ||
indent(concat([softline, path.call(print, "source")])), | ||
softline, | ||
")" | ||
]) | ||
); | ||
default: | ||
return "Have not implemented statement kind " + node.kind + " yet."; | ||
return `Have not implemented statement kind ${node.kind} yet.`; | ||
} | ||
@@ -1072,11 +1495,12 @@ } | ||
const node = path.getValue(); | ||
if (includes(expressionKinds, node.kind)) { | ||
if (expressionKinds.includes(node.kind)) { | ||
return printExpression(path, options, print); | ||
} | ||
if (includes(statementKinds, node.kind)) { | ||
if (statementKinds.includes(node.kind)) { | ||
return printStatement(path, options, print); | ||
} | ||
switch (node.kind) { | ||
case "identifier": | ||
case "identifier": { | ||
// this is a hack until https://github.com/glayzzle/php-parser/issues/113 is resolved | ||
// for reserved words we prefer lowercase case | ||
if (node.name === "\\array") { | ||
@@ -1087,3 +1511,19 @@ return "array"; | ||
} | ||
return node.name; | ||
const lowerCasedName = node.name.toLowerCase(); | ||
const isLowerCase = | ||
[ | ||
"int", | ||
"float", | ||
"bool", | ||
"string", | ||
"null", | ||
"void", | ||
"iterable", | ||
"object", | ||
"self" | ||
].indexOf(lowerCasedName) !== -1; | ||
return isLowerCase ? lowerCasedName : node.name; | ||
} | ||
case "case": | ||
@@ -1094,36 +1534,66 @@ return concat([ | ||
: "default:", | ||
indent(concat([line, path.call(print, "body")])) | ||
node.body | ||
? node.body.children && node.body.children.length | ||
? indent(concat([hardline, path.call(print, "body")])) | ||
: ";" | ||
: "" | ||
]); | ||
case "break": | ||
if (node.level) { | ||
while (node.level.kind == "parenthesis") { | ||
node.level = node.level.inner; | ||
} | ||
if (node.level.kind == "number" && node.level.value != 1) { | ||
return concat(["break ", path.call(print, "level")]); | ||
} | ||
return "break"; | ||
} | ||
return "break"; | ||
case "return": | ||
case "continue": | ||
if (node.level) { | ||
while (node.level.kind == "parenthesis") { | ||
node.level = node.level.inner; | ||
} | ||
if (node.level.kind == "number" && node.level.value != 1) { | ||
return concat(["continue ", path.call(print, "level")]); | ||
} | ||
return "continue"; | ||
} | ||
return "continue"; | ||
case "return": { | ||
const parts = []; | ||
parts.push("return"); | ||
if (node.expr) { | ||
return concat(["return ", path.call(print, "expr")]); | ||
const printedExpr = path.call(print, "expr"); | ||
if (node.expr.kind === "bin") { | ||
parts.push( | ||
group( | ||
concat([ | ||
ifBreak(" (", " "), | ||
indent(concat([softline, printedExpr])), | ||
softline, | ||
ifBreak(")") | ||
]) | ||
) | ||
); | ||
} else { | ||
parts.push(" ", printedExpr); | ||
} | ||
} | ||
return "return"; | ||
case "doc": | ||
return node.isDoc | ||
? concat([ | ||
"/**", | ||
// we use the number of lines to determine if this is a single or | ||
// multi line docblock | ||
node.lines.length > 1 | ||
? concat( | ||
node.lines.map( | ||
(comment, index) => | ||
index != 0 && index != node.lines.length - 1 | ||
? concat([hardline, " * ", comment]) | ||
: "" | ||
) | ||
) | ||
: concat([" ", node.lines[0]], " "), | ||
node.lines.length > 1 ? hardline : "", | ||
" */" | ||
]) | ||
: join(hardline, node.lines.map(comment => concat(["// ", comment]))); | ||
case "entry": | ||
return concat(parts); | ||
} | ||
case "entry": { | ||
const dangling = comments.printDanglingComments( | ||
path, | ||
options, | ||
/* sameLine */ true | ||
); | ||
const printedComments = dangling ? concat([hardline, dangling]) : ""; | ||
return concat([ | ||
node.key ? concat([path.call(print, "key"), " => "]) : "", | ||
path.call(print, "value") | ||
path.call(print, "value"), | ||
printedComments | ||
]); | ||
} | ||
case "traituse": | ||
@@ -1140,7 +1610,3 @@ return group( | ||
line, | ||
path.call( | ||
adaptationsPath => | ||
printLines(adaptationsPath, options, print), | ||
"adaptations" | ||
) | ||
printLines(path, options, print, "adaptations") | ||
]) | ||
@@ -1165,7 +1631,9 @@ ), | ||
path.call(print, "trait"), | ||
"::", | ||
node.method, | ||
node.trait ? "::" : "", | ||
node.method || "", | ||
" as ", | ||
node.visibility ? concat([node.visibility, " "]) : "", | ||
node.as | ||
join(" ", [ | ||
...(node.visibility ? [node.visibility] : []), | ||
...(node.as ? [node.as] : []) | ||
]) | ||
]); | ||
@@ -1176,3 +1644,3 @@ case "label": | ||
default: | ||
return "Have not implemented kind " + node.kind + " yet."; | ||
return `Have not implemented kind ${node.kind} yet.`; | ||
} | ||
@@ -1179,0 +1647,0 @@ } |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
GitHub dependency
Supply chain riskContains a dependency which resolves to a GitHub URL. Dependencies fetched from GitHub specifiers are not immutable can be used to inject untrusted code or reduce the likelihood of a reproducible install.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
GitHub dependency
Supply chain riskContains a dependency which resolves to a GitHub URL. Dependencies fetched from GitHub specifiers are not immutable can be used to inject untrusted code or reduce the likelihood of a reproducible install.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
77988
10
2537
123
8
3
+ Addedprettier@1.19.1(transitive)
- Removedprettier@prettier/prettier#b449a526ce0f16703cdf5b4b22731b2a94775473
- Removedphp-parser@2.2.0(transitive)
Updatedphp-parser@glayzzle/php-parser#a8f10d8c9aacf8e90b283229f8bed76363b99fcf