Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 27 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,17 +176,33 @@ await prettier.format(YOUR_CODE, {

Prettier for PHP supports the following options. We recommend that all users set the `phpVersion` option.

| Name | Default | Description |
| ------------------ | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `phpVersion` | `"7.0"` | Allows specifying the PHP version you're using. If you're using PHP 7.1 or later, setting this option will make use of modern language features in the printed output. If you're using PHP lower than 7.0, you'll have to set this option or Prettier will generate incompatible code. |
| `printWidth` | `80` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#print-width)) |
| `tabWidth` | `4` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#tab-width)), The default is `4` based on the `PSR-2` coding standard. |
| `useTabs` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#tabs)) |
| `singleQuote` | `false` | If set to `"true"`, strings that use double quotes but do not rely on the features they add, will be reformatted. Example: `"foo" -> 'foo'`, `"foo $bar" -> "foo $bar"`. |
| `trailingCommaPHP` | `true` | If set to `true`, trailing commas will be added wherever possible. <br> If set to `false`, no trailing commas are printed. |
| `braceStyle` | `"per-cs"` | If set to `"per-cs"`, prettier will move open brace for code blocks (classes, functions and methods) onto new line. <br> If set to `"1tbs"`, prettier will move open brace for code blocks (classes, functions and methods) onto same line. |
| `requirePragma` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#require-pragma)) |
| `insertPragma` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#insert-pragma)) |
| Name | Default | Description |
| ------------------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `phpVersion` | `"auto"` \* | Allows specifying the PHP version you're using. (See Notes Below) |
| `printWidth` | `80` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#print-width)) |
| `tabWidth` | `4` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#tab-width)), The default is `4` based on the `PSR-2` coding standard. |
| `useTabs` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#tabs)) |
| `singleQuote` | `false` | If set to `"true"`, strings that use double quotes but do not rely on the features they add, will be reformatted. Example: `"foo" -> 'foo'`, `"foo $bar" -> "foo $bar"`. |
| `trailingCommaPHP` | `true` | If set to `true`, trailing commas will be added wherever possible. <br> If set to `false`, no trailing commas are printed. |
| `braceStyle` | `"per-cs"` | If set to `"per-cs"`, prettier will move open brace for code blocks (classes, functions and methods) onto new line. <br> If set to `"1tbs"`, prettier will move open brace for code blocks (classes, functions and methods) onto same line. |
| `requirePragma` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#require-pragma)) |
| `insertPragma` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#insert-pragma)) |

### \* `phpVersion` Configuration Notes :

The default setting `auto`, attempts to auto-detect your supported php versions from a `composer.json` with in the
current directory or any parent directory, the plugin will use a minimum supported php version from
`{"require":{"php":"..."}}` to set the phpVersion. If not found or not able to be parsed, it will default to latest
supported PHP version.

You set the `phpVersion` to `composer` and this will only use the `composer.json` file to determine the php
version, prettier will crash if the PHP cannot be determined.

You can also set the `phpVersion` to a specific version, such as `7.4`, `8.0`, `8.1`, `8.2`, or `8.3`.

**Please Note:** If the phpVersion is not set correctly for your environment, this plugin will produce code that could
be incompatible with your PHP runtime. For example, if you are using PHP 7.4, but the plugin is set to PHP 8.3, it will
produce code that uses features not available in PHP 7.4.

## Ignoring code

Expand Down
2 changes: 1 addition & 1 deletion jest.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default {
transform: {},
setupFiles: ["<rootDir>/tests_config/run_spec.mjs"],
// Matches `.js` file to prevent files use `.js` extension by mistake, https://github.com/prettier/plugin-php/pull/2247#discussion_r1331847801
testRegex: "jsfmt\\.spec\\.m?js$|__tests__/.*\\.m?js$",
testRegex: ".*\\.spec\\.m?js$",
snapshotSerializers: ["jest-snapshot-serializer-raw"],
globals: {
STANDALONE: RUN_STANDALONE_TESTS,
Expand Down
9 changes: 2 additions & 7 deletions src/needs-parens.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import {
getPrecedence,
shouldFlatten,
isBitwiseOperator,
isMinVersion,
} from "./util.mjs";
import { getPrecedence, shouldFlatten, isBitwiseOperator } from "./util.mjs";

function needsParens(path, options) {
const { parent } = path;
Expand Down Expand Up @@ -135,7 +130,7 @@ function needsParens(path, options) {
case "new": {
const requiresParens =
node.kind === "clone" ||
(node.kind === "new" && !isMinVersion(options.phpVersion, "8.4"));
(node.kind === "new" && options.phpVersion < 8.4);
switch (parent.kind) {
case "propertylookup":
case "nullsafepropertylookup":
Expand Down
130 changes: 112 additions & 18 deletions src/options.mjs
Original file line number Diff line number Diff line change
@@ -1,30 +1,124 @@
import fs from "fs";
import path from "path";

const CATEGORY_PHP = "PHP";

// prettier-ignore
const SUPPORTED_PHP_VERSIONS = [
5.0, 5.1, 5.2, 5.3, 5.4, 5.5, 5.6,
7.0, 7.1, 7.2, 7.3, 7.4,
8.0, 8.1, 8.2, 8.3, 8.4,
];

export const LATEST_SUPPORTED_PHP_VERSION = Math.max(...SUPPORTED_PHP_VERSIONS);

let getComposerError = "";

/**
* Detect the minimum PHP version from the composer.json file
* @return {number|null} The PHP version to use in the composer.json file, null when not found
*/
function getComposerPhpVersion() {
// Try to find composer.json
const currentDir = process.cwd();
let composerPath = null;

const directComposerPath = path.join(currentDir, "composer.json");
if (fs.existsSync(directComposerPath)) {
composerPath = directComposerPath;
}

if (!composerPath) {
let searchDir = path.dirname(currentDir);
while (searchDir !== path.parse(searchDir).root) {
const potentialComposerPath = path.join(searchDir, "composer.json");
if (fs.existsSync(potentialComposerPath)) {
composerPath = potentialComposerPath;
break;
}
searchDir = path.dirname(searchDir);
}
}

if (composerPath) {
try {
const fileContent = fs.readFileSync(composerPath, "utf8");
const composerJson = JSON.parse(fileContent);

if (composerJson.require && composerJson.require.php) {
// Check for a wildcard pattern like "7.*"
const wildcardMatch = composerJson.require.php.match(
/^(?:[^0-9]*)?([0-9]+)\.\*/
);
if (wildcardMatch) {
return parseFloat(`${wildcardMatch[1]}.0`);
}

// Extract version from composer semver format
const versionMatch = composerJson.require.php.match(
/^(?:[^0-9]*)?([0-9]+)\.([0-9]+)/
);

if (versionMatch) {
return parseFloat(`${versionMatch[1]}.${versionMatch[2]}`);
} else {
getComposerError = `Could not decode PHP version (${composerJson.require.php}})`;
return null;
}
}
} catch (e) {
getComposerError = `Error reading composer.json: ${e.message}`;
}
} else {
getComposerError = "Could not find composer.json";
}

return null;
}

export { getComposerPhpVersion };

/**
* Resolve the PHP version to a number based on the provided options.
*
*/
export function resolvePhpVersion(options) {
if (!options) {
return;
}
if (options.phpVersion === "auto") {
options.phpVersion =
getComposerPhpVersion() ?? LATEST_SUPPORTED_PHP_VERSION;
} else if (options.phpVersion === "composer") {
const v = getComposerPhpVersion();
if (v === null) {
throw new Error(
`Could not determine PHP version from composer; ${getComposerError}`
);
}
options.phpVersion = v;
} else {
options.phpVersion = parseFloat(options.phpVersion);
}
}

export default {
phpVersion: {
since: "0.13.0",
category: CATEGORY_PHP,
type: "choice",
default: "7.0",
default: "auto",
description: "Minimum target PHP version.",
choices: [
{ value: "5.0" },
{ value: "5.1" },
{ value: "5.2" },
{ value: "5.3" },
{ value: "5.4" },
{ value: "5.5" },
{ value: "5.6" },
{ value: "7.0" },
{ value: "7.1" },
{ value: "7.2" },
{ value: "7.3" },
{ value: "7.4" },
{ value: "8.0" },
{ value: "8.1" },
{ value: "8.2" },
{ value: "8.3" },
{ value: "8.4" },
...SUPPORTED_PHP_VERSIONS.map((v) => ({ value: v.toFixed(1) })),
{
value: "composer",
description: "Use the PHP version defined in composer.json",
},
{
value: "auto",
description: `Try composer.json, else latest PHP Version (${LATEST_SUPPORTED_PHP_VERSION})`,
},
],
},
trailingCommaPHP: {
Expand Down
10 changes: 4 additions & 6 deletions src/parser.mjs
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
import engine from "php-parser";
import options from "./options.mjs";
import { LATEST_SUPPORTED_PHP_VERSION } from "./options.mjs";
import { resolvePhpVersion } from "./options.mjs";

function parse(text, opts) {
const inMarkdown = opts && opts.parentParser === "markdown";

if (!text && inMarkdown) {
return "";
}
resolvePhpVersion(opts);

// Todo https://github.com/glayzzle/php-parser/issues/170
text = text.replace(/\?>\n<\?/g, "?>\n___PSEUDO_INLINE_PLACEHOLDER___<?");

const latestSupportedPhpVersion = Math.max(
...options.phpVersion.choices.map((c) => parseFloat(c.value))
);

// initialize a new parser instance
const parser = new engine({
parser: {
extractDoc: true,
version: `${latestSupportedPhpVersion}`,
version: `${LATEST_SUPPORTED_PHP_VERSION}`,
},
ast: {
withPositions: true,
Expand Down
35 changes: 18 additions & 17 deletions src/printer.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import {
isReferenceLikeNode,
normalizeMagicMethodName,
isSimpleCallArgument,
isMinVersion,
} from "./util.mjs";

const {
Expand Down Expand Up @@ -66,12 +65,19 @@ const {
isPreviousLineEmpty,
} = prettierUtil;

/**
* Determine if we should print a trailing comma based on the config & php version
*
* @param options {object} Prettier Options
* @param requiredVersion {number}
* @returns {boolean}
*/
function shouldPrintComma(options, requiredVersion) {
if (!options.trailingCommaPHP) {
return false;
}

return isMinVersion(options.phpVersion, requiredVersion);
return options.phpVersion >= requiredVersion;
}

function shouldPrintHardlineForOpenBrace(options) {
Expand Down Expand Up @@ -612,9 +618,9 @@ function printArgumentsList(path, options, print, argumentsKey = "arguments") {
const lastArg = getLast(args);

const maybeTrailingComma =
(shouldPrintComma(options, "7.3") &&
(shouldPrintComma(options, 7.3) &&
["call", "new", "unset", "isset"].includes(node.kind)) ||
(shouldPrintComma(options, "8.0") &&
(shouldPrintComma(options, 8.0) &&
["function", "closure", "method", "arrowfunc", "attribute"].includes(
node.kind
))
Expand Down Expand Up @@ -1186,7 +1192,7 @@ function printAttrs(path, options, print, { inline = false } = {}) {
allAttrs.push(
group([
indent(attrGroup),
ifBreak(shouldPrintComma(options, "8.0") ? "," : ""),
ifBreak(shouldPrintComma(options, 8.0) ? "," : ""),
softline,
"]",
inline ? ifBreak(softline, " ") : "",
Expand Down Expand Up @@ -1658,11 +1664,7 @@ function printNode(path, options, print) {
join([",", line], path.map(print, "items")),
]),
node.name
? [
ifBreak(shouldPrintComma(options, "7.2") ? "," : ""),
softline,
"}",
]
? [ifBreak(shouldPrintComma(options, 7.2) ? "," : ""), softline, "}"]
: "",
]);
case "useitem":
Expand Down Expand Up @@ -2356,9 +2358,8 @@ function printNode(path, options, print) {
case "list":
case "array": {
const useShortForm =
(node.kind === "array" && isMinVersion(options.phpVersion, "5.4")) ||
(node.kind === "list" &&
(node.shortForm || isMinVersion(options.phpVersion, "7.1")));
(node.kind === "array" && options.phpVersion >= 5.4) ||
(node.kind === "list" && (node.shortForm || options.phpVersion >= 7.1));
const open = useShortForm ? "[" : [node.kind, "("];
const close = useShortForm ? "]" : ")";

Expand Down Expand Up @@ -2408,7 +2409,7 @@ function printNode(path, options, print) {
indent([softline, printArrayItems(path, options, print)]),
needsForcedTrailingComma ? "," : "",
ifBreak(
!needsForcedTrailingComma && shouldPrintComma(options, "5.0")
!needsForcedTrailingComma && shouldPrintComma(options, 5.0)
? [
lastElem && shouldPrintHardlineBeforeTrailingComma(lastElem)
? hardline
Expand Down Expand Up @@ -2675,7 +2676,7 @@ function printNode(path, options, print) {
if (parent.kind === "encapsedpart") {
const parentParent = path.grandparent;
let closingTagIndentation = 0;
const flexible = isMinVersion(options.phpVersion, "7.3");
const flexible = options.phpVersion >= 7.3;
let linebreak = literalline;
if (parentParent.type === "heredoc") {
linebreak = flexible ? hardline : literalline;
Expand Down Expand Up @@ -2744,7 +2745,7 @@ function printNode(path, options, print) {
case "string":
case "shell":
case "heredoc": {
const flexible = isMinVersion(options.phpVersion, "7.3");
const flexible = options.phpVersion >= 7.3;
const linebreak = flexible ? hardline : literalline;
return [
getEncapsedQuotes(node),
Expand All @@ -2769,7 +2770,7 @@ function printNode(path, options, print) {
case "magic":
return node.value;
case "nowdoc": {
const flexible = isMinVersion(options.phpVersion, "7.3");
const flexible = options.phpVersion >= 7.3;
const linebreak = flexible ? hardline : literalline;
return [
"<<<'",
Expand Down
5 changes: 0 additions & 5 deletions src/util.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -709,10 +709,6 @@ function memoize(fn) {
};
}

function isMinVersion(actualVersion, requiredVersion) {
return parseFloat(actualVersion) >= parseFloat(requiredVersion);
}

export {
printNumber,
getPrecedence,
Expand Down Expand Up @@ -744,5 +740,4 @@ export {
normalizeMagicMethodName,
isSimpleCallArgument,
memoize,
isMinVersion,
};
Loading
Loading