Skip to content

Commit 7b4d64b

Browse files
czoselcseufert
andauthored
feat!: bump default formatting version to composer version with fallback to latest supported PHP (#2434)
* feat!: bump default formatting version to 8.3 BREAKING CHANGE: If you didn't set the `phpVersion` option explicitly, the formatting will assume PHP 8.3 now (instead of 7.0). * added composer version detection * run all spec.mjs test files, not just jsfmt, remove __tests__ dir as not used * changed return to null and improved test coverage - added wildcard major versions - updated test for null return - added invalid json test case * refactor: renamed getComposerPhpVersion func to match config naming more closely * documentation updaste for PHP default version changes * reformat: ran prettier on README * set default phpVersion to auto and added composer setting * refactored versions after rebasing - automatically use latest version - phpVersion is always a number now - moved isMinVersion to simple number comparison * update versions to number * snapshot changes after bumping default version to 8.4 * reformat: prettier * fix default version * Update README.md to be clearer about default php version Co-authored-by: Christian Zosel <christian@zosel.ch> * Update README.md fix typo Co-authored-by: Christian Zosel <christian@zosel.ch> * Update src/options.mjs remove extra else Co-authored-by: Christian Zosel <christian@zosel.ch> * make not json example much clearer --------- Co-authored-by: Chris Seufert <chris@seufert.id.au> Co-authored-by: Chris Seufert <chris@modd.com.au>
1 parent 74906e9 commit 7b4d64b

File tree

55 files changed

+992
-654
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+992
-654
lines changed

README.md

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -176,17 +176,33 @@ await prettier.format(YOUR_CODE, {
176176

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

179-
| Name | Default | Description |
180-
| ------------------ | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
181-
| `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. |
182-
| `printWidth` | `80` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#print-width)) |
183-
| `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. |
184-
| `useTabs` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#tabs)) |
185-
| `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"`. |
186-
| `trailingCommaPHP` | `true` | If set to `true`, trailing commas will be added wherever possible. <br> If set to `false`, no trailing commas are printed. |
187-
| `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. |
188-
| `requirePragma` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#require-pragma)) |
189-
| `insertPragma` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#insert-pragma)) |
179+
| Name | Default | Description |
180+
| ------------------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
181+
| `phpVersion` | `"auto"` \* | Allows specifying the PHP version you're using. (See Notes Below) |
182+
| `printWidth` | `80` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#print-width)) |
183+
| `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. |
184+
| `useTabs` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#tabs)) |
185+
| `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"`. |
186+
| `trailingCommaPHP` | `true` | If set to `true`, trailing commas will be added wherever possible. <br> If set to `false`, no trailing commas are printed. |
187+
| `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. |
188+
| `requirePragma` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#require-pragma)) |
189+
| `insertPragma` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#insert-pragma)) |
190+
191+
### \* `phpVersion` Configuration Notes :
192+
193+
The default setting `auto`, attempts to auto-detect your supported php versions from a `composer.json` with in the
194+
current directory or any parent directory, the plugin will use a minimum supported php version from
195+
`{"require":{"php":"..."}}` to set the phpVersion. If not found or not able to be parsed, it will default to latest
196+
supported PHP version.
197+
198+
You set the `phpVersion` to `composer` and this will only use the `composer.json` file to determine the php
199+
version, prettier will crash if the PHP cannot be determined.
200+
201+
You can also set the `phpVersion` to a specific version, such as `7.4`, `8.0`, `8.1`, `8.2`, or `8.3`.
202+
203+
**Please Note:** If the phpVersion is not set correctly for your environment, this plugin will produce code that could
204+
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
205+
produce code that uses features not available in PHP 7.4.
190206

191207
## Ignoring code
192208

jest.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default {
1414
transform: {},
1515
setupFiles: ["<rootDir>/tests_config/run_spec.mjs"],
1616
// Matches `.js` file to prevent files use `.js` extension by mistake, https://github.com/prettier/plugin-php/pull/2247#discussion_r1331847801
17-
testRegex: "jsfmt\\.spec\\.m?js$|__tests__/.*\\.m?js$",
17+
testRegex: ".*\\.spec\\.m?js$",
1818
snapshotSerializers: ["jest-snapshot-serializer-raw"],
1919
globals: {
2020
STANDALONE: RUN_STANDALONE_TESTS,

src/needs-parens.mjs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
import {
2-
getPrecedence,
3-
shouldFlatten,
4-
isBitwiseOperator,
5-
isMinVersion,
6-
} from "./util.mjs";
1+
import { getPrecedence, shouldFlatten, isBitwiseOperator } from "./util.mjs";
72

83
function needsParens(path, options) {
94
const { parent } = path;
@@ -135,7 +130,7 @@ function needsParens(path, options) {
135130
case "new": {
136131
const requiresParens =
137132
node.kind === "clone" ||
138-
(node.kind === "new" && !isMinVersion(options.phpVersion, "8.4"));
133+
(node.kind === "new" && options.phpVersion < 8.4);
139134
switch (parent.kind) {
140135
case "propertylookup":
141136
case "nullsafepropertylookup":

src/options.mjs

Lines changed: 112 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,124 @@
1+
import fs from "fs";
2+
import path from "path";
3+
14
const CATEGORY_PHP = "PHP";
25

6+
// prettier-ignore
7+
const SUPPORTED_PHP_VERSIONS = [
8+
5.0, 5.1, 5.2, 5.3, 5.4, 5.5, 5.6,
9+
7.0, 7.1, 7.2, 7.3, 7.4,
10+
8.0, 8.1, 8.2, 8.3, 8.4,
11+
];
12+
13+
export const LATEST_SUPPORTED_PHP_VERSION = Math.max(...SUPPORTED_PHP_VERSIONS);
14+
15+
let getComposerError = "";
16+
17+
/**
18+
* Detect the minimum PHP version from the composer.json file
19+
* @return {number|null} The PHP version to use in the composer.json file, null when not found
20+
*/
21+
function getComposerPhpVersion() {
22+
// Try to find composer.json
23+
const currentDir = process.cwd();
24+
let composerPath = null;
25+
26+
const directComposerPath = path.join(currentDir, "composer.json");
27+
if (fs.existsSync(directComposerPath)) {
28+
composerPath = directComposerPath;
29+
}
30+
31+
if (!composerPath) {
32+
let searchDir = path.dirname(currentDir);
33+
while (searchDir !== path.parse(searchDir).root) {
34+
const potentialComposerPath = path.join(searchDir, "composer.json");
35+
if (fs.existsSync(potentialComposerPath)) {
36+
composerPath = potentialComposerPath;
37+
break;
38+
}
39+
searchDir = path.dirname(searchDir);
40+
}
41+
}
42+
43+
if (composerPath) {
44+
try {
45+
const fileContent = fs.readFileSync(composerPath, "utf8");
46+
const composerJson = JSON.parse(fileContent);
47+
48+
if (composerJson.require && composerJson.require.php) {
49+
// Check for a wildcard pattern like "7.*"
50+
const wildcardMatch = composerJson.require.php.match(
51+
/^(?:[^0-9]*)?([0-9]+)\.\*/
52+
);
53+
if (wildcardMatch) {
54+
return parseFloat(`${wildcardMatch[1]}.0`);
55+
}
56+
57+
// Extract version from composer semver format
58+
const versionMatch = composerJson.require.php.match(
59+
/^(?:[^0-9]*)?([0-9]+)\.([0-9]+)/
60+
);
61+
62+
if (versionMatch) {
63+
return parseFloat(`${versionMatch[1]}.${versionMatch[2]}`);
64+
} else {
65+
getComposerError = `Could not decode PHP version (${composerJson.require.php}})`;
66+
return null;
67+
}
68+
}
69+
} catch (e) {
70+
getComposerError = `Error reading composer.json: ${e.message}`;
71+
}
72+
} else {
73+
getComposerError = "Could not find composer.json";
74+
}
75+
76+
return null;
77+
}
78+
79+
export { getComposerPhpVersion };
80+
81+
/**
82+
* Resolve the PHP version to a number based on the provided options.
83+
*
84+
*/
85+
export function resolvePhpVersion(options) {
86+
if (!options) {
87+
return;
88+
}
89+
if (options.phpVersion === "auto") {
90+
options.phpVersion =
91+
getComposerPhpVersion() ?? LATEST_SUPPORTED_PHP_VERSION;
92+
} else if (options.phpVersion === "composer") {
93+
const v = getComposerPhpVersion();
94+
if (v === null) {
95+
throw new Error(
96+
`Could not determine PHP version from composer; ${getComposerError}`
97+
);
98+
}
99+
options.phpVersion = v;
100+
} else {
101+
options.phpVersion = parseFloat(options.phpVersion);
102+
}
103+
}
104+
3105
export default {
4106
phpVersion: {
5107
since: "0.13.0",
6108
category: CATEGORY_PHP,
7109
type: "choice",
8-
default: "7.0",
110+
default: "auto",
9111
description: "Minimum target PHP version.",
10112
choices: [
11-
{ value: "5.0" },
12-
{ value: "5.1" },
13-
{ value: "5.2" },
14-
{ value: "5.3" },
15-
{ value: "5.4" },
16-
{ value: "5.5" },
17-
{ value: "5.6" },
18-
{ value: "7.0" },
19-
{ value: "7.1" },
20-
{ value: "7.2" },
21-
{ value: "7.3" },
22-
{ value: "7.4" },
23-
{ value: "8.0" },
24-
{ value: "8.1" },
25-
{ value: "8.2" },
26-
{ value: "8.3" },
27-
{ value: "8.4" },
113+
...SUPPORTED_PHP_VERSIONS.map((v) => ({ value: v.toFixed(1) })),
114+
{
115+
value: "composer",
116+
description: "Use the PHP version defined in composer.json",
117+
},
118+
{
119+
value: "auto",
120+
description: `Try composer.json, else latest PHP Version (${LATEST_SUPPORTED_PHP_VERSION})`,
121+
},
28122
],
29123
},
30124
trailingCommaPHP: {

src/parser.mjs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,23 @@
11
import engine from "php-parser";
2-
import options from "./options.mjs";
2+
import { LATEST_SUPPORTED_PHP_VERSION } from "./options.mjs";
3+
import { resolvePhpVersion } from "./options.mjs";
34

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

78
if (!text && inMarkdown) {
89
return "";
910
}
11+
resolvePhpVersion(opts);
1012

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

14-
const latestSupportedPhpVersion = Math.max(
15-
...options.phpVersion.choices.map((c) => parseFloat(c.value))
16-
);
17-
1816
// initialize a new parser instance
1917
const parser = new engine({
2018
parser: {
2119
extractDoc: true,
22-
version: `${latestSupportedPhpVersion}`,
20+
version: `${LATEST_SUPPORTED_PHP_VERSION}`,
2321
},
2422
ast: {
2523
withPositions: true,

src/printer.mjs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ import {
3737
isReferenceLikeNode,
3838
normalizeMagicMethodName,
3939
isSimpleCallArgument,
40-
isMinVersion,
4140
} from "./util.mjs";
4241

4342
const {
@@ -66,12 +65,19 @@ const {
6665
isPreviousLineEmpty,
6766
} = prettierUtil;
6867

68+
/**
69+
* Determine if we should print a trailing comma based on the config & php version
70+
*
71+
* @param options {object} Prettier Options
72+
* @param requiredVersion {number}
73+
* @returns {boolean}
74+
*/
6975
function shouldPrintComma(options, requiredVersion) {
7076
if (!options.trailingCommaPHP) {
7177
return false;
7278
}
7379

74-
return isMinVersion(options.phpVersion, requiredVersion);
80+
return options.phpVersion >= requiredVersion;
7581
}
7682

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

614620
const maybeTrailingComma =
615-
(shouldPrintComma(options, "7.3") &&
621+
(shouldPrintComma(options, 7.3) &&
616622
["call", "new", "unset", "isset"].includes(node.kind)) ||
617-
(shouldPrintComma(options, "8.0") &&
623+
(shouldPrintComma(options, 8.0) &&
618624
["function", "closure", "method", "arrowfunc", "attribute"].includes(
619625
node.kind
620626
))
@@ -1186,7 +1192,7 @@ function printAttrs(path, options, print, { inline = false } = {}) {
11861192
allAttrs.push(
11871193
group([
11881194
indent(attrGroup),
1189-
ifBreak(shouldPrintComma(options, "8.0") ? "," : ""),
1195+
ifBreak(shouldPrintComma(options, 8.0) ? "," : ""),
11901196
softline,
11911197
"]",
11921198
inline ? ifBreak(softline, " ") : "",
@@ -1658,11 +1664,7 @@ function printNode(path, options, print) {
16581664
join([",", line], path.map(print, "items")),
16591665
]),
16601666
node.name
1661-
? [
1662-
ifBreak(shouldPrintComma(options, "7.2") ? "," : ""),
1663-
softline,
1664-
"}",
1665-
]
1667+
? [ifBreak(shouldPrintComma(options, 7.2) ? "," : ""), softline, "}"]
16661668
: "",
16671669
]);
16681670
case "useitem":
@@ -2356,9 +2358,8 @@ function printNode(path, options, print) {
23562358
case "list":
23572359
case "array": {
23582360
const useShortForm =
2359-
(node.kind === "array" && isMinVersion(options.phpVersion, "5.4")) ||
2360-
(node.kind === "list" &&
2361-
(node.shortForm || isMinVersion(options.phpVersion, "7.1")));
2361+
(node.kind === "array" && options.phpVersion >= 5.4) ||
2362+
(node.kind === "list" && (node.shortForm || options.phpVersion >= 7.1));
23622363
const open = useShortForm ? "[" : [node.kind, "("];
23632364
const close = useShortForm ? "]" : ")";
23642365

@@ -2408,7 +2409,7 @@ function printNode(path, options, print) {
24082409
indent([softline, printArrayItems(path, options, print)]),
24092410
needsForcedTrailingComma ? "," : "",
24102411
ifBreak(
2411-
!needsForcedTrailingComma && shouldPrintComma(options, "5.0")
2412+
!needsForcedTrailingComma && shouldPrintComma(options, 5.0)
24122413
? [
24132414
lastElem && shouldPrintHardlineBeforeTrailingComma(lastElem)
24142415
? hardline
@@ -2675,7 +2676,7 @@ function printNode(path, options, print) {
26752676
if (parent.kind === "encapsedpart") {
26762677
const parentParent = path.grandparent;
26772678
let closingTagIndentation = 0;
2678-
const flexible = isMinVersion(options.phpVersion, "7.3");
2679+
const flexible = options.phpVersion >= 7.3;
26792680
let linebreak = literalline;
26802681
if (parentParent.type === "heredoc") {
26812682
linebreak = flexible ? hardline : literalline;
@@ -2744,7 +2745,7 @@ function printNode(path, options, print) {
27442745
case "string":
27452746
case "shell":
27462747
case "heredoc": {
2747-
const flexible = isMinVersion(options.phpVersion, "7.3");
2748+
const flexible = options.phpVersion >= 7.3;
27482749
const linebreak = flexible ? hardline : literalline;
27492750
return [
27502751
getEncapsedQuotes(node),
@@ -2769,7 +2770,7 @@ function printNode(path, options, print) {
27692770
case "magic":
27702771
return node.value;
27712772
case "nowdoc": {
2772-
const flexible = isMinVersion(options.phpVersion, "7.3");
2773+
const flexible = options.phpVersion >= 7.3;
27732774
const linebreak = flexible ? hardline : literalline;
27742775
return [
27752776
"<<<'",

src/util.mjs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -709,10 +709,6 @@ function memoize(fn) {
709709
};
710710
}
711711

712-
function isMinVersion(actualVersion, requiredVersion) {
713-
return parseFloat(actualVersion) >= parseFloat(requiredVersion);
714-
}
715-
716712
export {
717713
printNumber,
718714
getPrecedence,
@@ -744,5 +740,4 @@ export {
744740
normalizeMagicMethodName,
745741
isSimpleCallArgument,
746742
memoize,
747-
isMinVersion,
748743
};

0 commit comments

Comments
 (0)