From b47cd502ad87859d83a555a735a2889d65588d51 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Mon, 3 Mar 2025 09:07:40 +0800 Subject: [PATCH 1/7] Add title to patch release changelog (#17197) --- scripts/changelog-for-patch.js | 9 ++++++++- scripts/draft-blog-post.js | 23 +---------------------- scripts/utils/changelog.js | 22 ++++++++++++++++++++++ 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/scripts/changelog-for-patch.js b/scripts/changelog-for-patch.js index eca2d3319..eaa472593 100644 --- a/scripts/changelog-for-patch.js +++ b/scripts/changelog-for-patch.js @@ -4,6 +4,7 @@ import path from "node:path"; import minimist from "minimist"; import semver from "semver"; import { + categories, changelogUnreleasedDirPath, changelogUnreleasedDirs, getEntries, @@ -15,7 +16,13 @@ const { previousVersion, newVersion } = parseArgv(); const entries = changelogUnreleasedDirs.flatMap((dir) => { const dirPath = path.join(changelogUnreleasedDirPath, dir.name); - return getEntries(dirPath); + const { title } = categories.find((category) => category.dir === dir.name); + + return getEntries(dirPath).map((entry) => { + const content = + entry.content.slice(0, 4) + ` ${title}:` + entry.content.slice(4); + return { ...entry, content }; + }); }); console.log( diff --git a/scripts/draft-blog-post.js b/scripts/draft-blog-post.js index 1695477a3..1791b9061 100644 --- a/scripts/draft-blog-post.js +++ b/scripts/draft-blog-post.js @@ -6,6 +6,7 @@ import createEsmUtils from "esm-utils"; import fg from "fast-glob"; import semver from "semver"; import { + categories, changelogUnreleasedDirPath, changelogUnreleasedDirs, getEntries, @@ -36,28 +37,6 @@ const postFile = path.join( `${new Date().toISOString().replace(/T.+/u, "")}-${nextVersion}.md`, ); -const categories = [ - { dir: "javascript", title: "JavaScript" }, - { dir: "typescript", title: "TypeScript" }, - { dir: "flow", title: "Flow" }, - { dir: "json", title: "JSON" }, - { dir: "css", title: "CSS" }, - { dir: "scss", title: "SCSS" }, - { dir: "less", title: "Less" }, - { dir: "html", title: "HTML" }, - { dir: "vue", title: "Vue" }, - { dir: "angular", title: "Angular" }, - { dir: "lwc", title: "LWC" }, - { dir: "handlebars", title: "Ember / Handlebars" }, - { dir: "graphql", title: "GraphQL" }, - { dir: "markdown", title: "Markdown" }, - { dir: "mdx", title: "MDX" }, - { dir: "yaml", title: "YAML" }, - { dir: "api", title: "API" }, - { dir: "cli", title: "CLI" }, - { dir: "misc", title: "Miscellaneous" }, -]; - const categoriesByDir = new Map( categories.map((category) => [category.dir, category]), ); diff --git a/scripts/utils/changelog.js b/scripts/utils/changelog.js index 3699c02c3..39008cbf8 100644 --- a/scripts/utils/changelog.js +++ b/scripts/utils/changelog.js @@ -10,6 +10,28 @@ export const changelogUnreleasedDirPath = path.join( "../../changelog_unreleased", ); +export const categories = [ + { dir: "javascript", title: "JavaScript" }, + { dir: "typescript", title: "TypeScript" }, + { dir: "flow", title: "Flow" }, + { dir: "json", title: "JSON" }, + { dir: "css", title: "CSS" }, + { dir: "scss", title: "SCSS" }, + { dir: "less", title: "Less" }, + { dir: "html", title: "HTML" }, + { dir: "vue", title: "Vue" }, + { dir: "angular", title: "Angular" }, + { dir: "lwc", title: "LWC" }, + { dir: "handlebars", title: "Ember / Handlebars" }, + { dir: "graphql", title: "GraphQL" }, + { dir: "markdown", title: "Markdown" }, + { dir: "mdx", title: "MDX" }, + { dir: "yaml", title: "YAML" }, + { dir: "api", title: "API" }, + { dir: "cli", title: "CLI" }, + { dir: "misc", title: "Miscellaneous" }, +]; + export const changelogUnreleasedDirs = fs .readdirSync(changelogUnreleasedDirPath, { withFileTypes: true, From fa95c97eb30dbcd86e352d4178c9b366bb073b97 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Mon, 3 Mar 2025 09:08:06 +0800 Subject: [PATCH 2/7] Add `mergeBlogPost` step to release script (#17199) --- scripts/release/run.js | 6 +++++- scripts/release/steps/index.js | 1 + scripts/release/steps/merge-blog-post.js | 25 ++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 scripts/release/steps/merge-blog-post.js diff --git a/scripts/release/run.js b/scripts/release/run.js index 78488fda5..17ef88529 100644 --- a/scripts/release/run.js +++ b/scripts/release/run.js @@ -56,13 +56,17 @@ for (let step of [ }, params.manual ? steps.publishToNpm : steps.waitForBotRelease, steps.showInstructionsAfterNpmPublish, + { + name: "Merge release notes PR", + process: steps.mergeBlogPost, + }, steps.updateDependentsCount, - steps.bumpPrettier, { name: "Cleaning changelog", process: steps.cleanChangelog, skip: params.dry || params.next, }, + steps.bumpPrettier, steps.postPublishSteps, ]) { if (typeof step === "function") { diff --git a/scripts/release/steps/index.js b/scripts/release/steps/index.js index df103e975..5a8368903 100644 --- a/scripts/release/steps/index.js +++ b/scripts/release/steps/index.js @@ -5,6 +5,7 @@ export { default as cleanChangelog } from "./clean-changelog.js"; export { default as generateBundles } from "./generate-bundles.js"; export { default as installDependencies } from "./install-dependencies.js"; export { default as lintFiles } from "./lint-files.js"; +export { default as mergeBlogPost } from "./merge-blog-post.js"; export { default as postPublishSteps } from "./post-publish-steps.js"; export { default as publishToNpm } from "./publish-to-npm.js"; export { default as pushToGit } from "./push-to-git.js"; diff --git a/scripts/release/steps/merge-blog-post.js b/scripts/release/steps/merge-blog-post.js new file mode 100644 index 000000000..beb1a153e --- /dev/null +++ b/scripts/release/steps/merge-blog-post.js @@ -0,0 +1,25 @@ +import styleText from "node-style-text"; +import semver from "semver"; +import { runGit, waitForEnter } from "../utils.js"; + +export default async function mergeBlogPost({ + dry, + repo, + version, + previousVersion, +}) { + if (dry) { + return; + } + + if (semver.diff(version, previousVersion) === "patch") { + return; + } + + console.log( + styleText.yellow.bold("Please merge the release notes PR if exits."), + ); + + await waitForEnter(); + await runGit(["pull", "--repo", repo]); +} From a2e560e951b44e4b51b0c082da37a5ce396fadd2 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Mon, 3 Mar 2025 09:09:29 +0800 Subject: [PATCH 3/7] Fix missing parentheses in `ConditionalTypeAnnotation` (#17196) --- changelog_unreleased/flow/17196.md | 16 ++++++++ src/language-js/comments/handle-comments.js | 10 ++--- src/language-js/needs-parens.js | 39 ++++++++----------- src/language-js/print/ternary.js | 8 ++-- src/language-js/print/type-annotation.js | 5 +-- src/language-js/utils/index.js | 10 ++++- .../__snapshots__/format.test.js.snap | 26 +++++++++++++ .../parentheses/format.test.js | 5 +++ .../conditional-types/parentheses/union.js | 2 + 9 files changed, 83 insertions(+), 38 deletions(-) create mode 100644 changelog_unreleased/flow/17196.md create mode 100644 tests/format/flow/conditional-types/parentheses/__snapshots__/format.test.js.snap create mode 100644 tests/format/flow/conditional-types/parentheses/format.test.js create mode 100644 tests/format/flow/conditional-types/parentheses/union.js diff --git a/changelog_unreleased/flow/17196.md b/changelog_unreleased/flow/17196.md new file mode 100644 index 000000000..ec6ea8f61 --- /dev/null +++ b/changelog_unreleased/flow/17196.md @@ -0,0 +1,16 @@ +#### Fix missing parentheses in `ConditionalTypeAnnotation` (#17196 by @fisker) + + +```jsx +// Input +type T = 'a' | ('b' extends U ? 'c' : empty); +type T = 'a' & ('b' extends U ? 'c' : empty); + +// Prettier stable +type T = "a" | "b" extends U ? "c" : empty; +type T = "a" & "b" extends U ? "c" : empty; + +// Prettier main +type T = "a" | ("b" extends U ? "c" : empty); +type T = "a" & ("b" extends U ? "c" : empty); +``` diff --git a/src/language-js/comments/handle-comments.js b/src/language-js/comments/handle-comments.js index 45036f3c3..cabe00221 100644 --- a/src/language-js/comments/handle-comments.js +++ b/src/language-js/comments/handle-comments.js @@ -15,6 +15,7 @@ import { getFunctionParameters, isCallExpression, isCallLikeExpression, + isConditionalType, isIntersectionType, isLineComment, isMemberExpression, @@ -360,8 +361,7 @@ function handleNestedConditionalExpressionComments({ const enclosingIsCond = enclosingNode?.type === "ConditionalExpression" || - enclosingNode?.type === "ConditionalTypeAnnotation" || - enclosingNode?.type === "TSConditionalType"; + isConditionalType(enclosingNode); if (!enclosingIsCond) { return false; @@ -369,8 +369,7 @@ function handleNestedConditionalExpressionComments({ const followingIsCond = followingNode?.type === "ConditionalExpression" || - followingNode?.type === "ConditionalTypeAnnotation" || - followingNode?.type === "TSConditionalType"; + isConditionalType(followingNode); if (followingIsCond) { addDanglingComment(enclosingNode, comment); @@ -394,8 +393,7 @@ function handleConditionalExpressionComments({ if ( (!precedingNode || !isSameLineAsPrecedingNode) && (enclosingNode?.type === "ConditionalExpression" || - enclosingNode?.type === "ConditionalTypeAnnotation" || - enclosingNode?.type === "TSConditionalType") && + isConditionalType(enclosingNode)) && followingNode ) { if ( diff --git a/src/language-js/needs-parens.js b/src/language-js/needs-parens.js index efa809e69..3c9ed1ac9 100644 --- a/src/language-js/needs-parens.js +++ b/src/language-js/needs-parens.js @@ -10,10 +10,13 @@ import { isBinaryCastExpression, isBitwiseOperator, isCallExpression, + isConditionalType, + isIntersectionType, isMemberExpression, isNullishCoalescing, isObjectOrRecordExpression, isObjectProperty, + isUnionType, shouldFlatten, startsWithNoLookaheadToken, } from "./utils/index.js"; @@ -531,11 +534,20 @@ function needsParens(path, options) { // fallthrough case "TSConditionalType": case "TSConstructorType": - if (key === "extendsType" && parent.type === "TSConditionalType") { - if (node.type === "TSConditionalType") { - return true; - } + case "ConditionalTypeAnnotation": + if ( + key === "extendsType" && + isConditionalType(node) && + parent.type === node.type + ) { + return true; + } + if (key === "checkType" && isConditionalType(parent)) { + return true; + } + + if (key === "extendsType" && parent.type === "TSConditionalType") { let { typeAnnotation } = node.returnType || node.typeAnnotation; if ( @@ -553,15 +565,11 @@ function needsParens(path, options) { } } - if (key === "checkType" && parent.type === "TSConditionalType") { - return true; - } // fallthrough case "TSUnionType": case "TSIntersectionType": if ( - (parent.type === "TSUnionType" || - parent.type === "TSIntersectionType") && + (isUnionType(parent) || isIntersectionType(parent)) && parent.types.length > 1 && (!node.types || node.types.length > 1) ) { @@ -712,19 +720,6 @@ function needsParens(path, options) { ); } - case "ConditionalTypeAnnotation": - if ( - key === "extendsType" && - parent.type === "ConditionalTypeAnnotation" && - node.type === "ConditionalTypeAnnotation" - ) { - return true; - } - - if (key === "checkType" && parent.type === "ConditionalTypeAnnotation") { - return true; - } - // fallthrough case "OptionalIndexedAccessType": return key === "objectType" && parent.type === "IndexedAccessType"; diff --git a/src/language-js/print/ternary.js b/src/language-js/print/ternary.js index c9480904d..7dadade51 100644 --- a/src/language-js/print/ternary.js +++ b/src/language-js/print/ternary.js @@ -18,6 +18,7 @@ import { hasComment, isBinaryCastExpression, isCallExpression, + isConditionalType, isJsxElement, isLoneShortArgument, isMemberExpression, @@ -153,9 +154,7 @@ function printTernary(path, options, print, args) { const { node } = path; const isConditionalExpression = node.type === "ConditionalExpression"; - const isTSConditional = - node.type === "TSConditionalType" || - node.type === "ConditionalTypeAnnotation"; // For Flow. + const isTSConditional = isConditionalType(node); const consequentNodePropertyName = isConditionalExpression ? "consequent" : "trueType"; @@ -322,8 +321,7 @@ function printTernary(path, options, print, args) { " ", "extends", " ", - node.extendsType.type === "TSConditionalType" || - node.extendsType.type === "ConditionalTypeAnnotation" || + isConditionalType(node.extendsType) || node.extendsType.type === "TSMappedType" ? print("extendsType") : group(wrapInParens(print("extendsType"))), diff --git a/src/language-js/print/type-annotation.js b/src/language-js/print/type-annotation.js index 9e5df2b15..89d6edd53 100644 --- a/src/language-js/print/type-annotation.js +++ b/src/language-js/print/type-annotation.js @@ -15,6 +15,7 @@ import { createTypeCheckFunction, hasComment, hasLeadingOwnLineComment, + isConditionalType, isFlowObjectTypePropertyAFunction, isObjectType, isSimpleType, @@ -174,9 +175,7 @@ function printUnionType(path, options, print) { // If there's a leading comment, the parent is doing the indentation const shouldIndent = parent.type !== "TypeParameterInstantiation" && - (parent.type !== "TSConditionalType" || !options.experimentalTernaries) && - (parent.type !== "ConditionalTypeAnnotation" || - !options.experimentalTernaries) && + (!isConditionalType(parent) || !options.experimentalTernaries) && parent.type !== "TSTypeParameterInstantiation" && parent.type !== "GenericTypeAnnotation" && parent.type !== "TSTypeReference" && diff --git a/src/language-js/utils/index.js b/src/language-js/utils/index.js index e403a722c..7800f4f93 100644 --- a/src/language-js/utils/index.js +++ b/src/language-js/utils/index.js @@ -1074,13 +1074,18 @@ const isBinaryCastExpression = createTypeCheckFunction([ ]); const isUnionType = createTypeCheckFunction([ - "UnionTypeAnnotation", "TSUnionType", + "UnionTypeAnnotation", ]); const isIntersectionType = createTypeCheckFunction([ - "IntersectionTypeAnnotation", "TSIntersectionType", + "IntersectionTypeAnnotation", +]); + +const isConditionalType = createTypeCheckFunction([ + "TSConditionalType", + "ConditionalTypeAnnotation", ]); export { @@ -1106,6 +1111,7 @@ export { isBitwiseOperator, isCallExpression, isCallLikeExpression, + isConditionalType, isExportDeclaration, isFlowObjectTypePropertyAFunction, isFunctionCompositionArgs, diff --git a/tests/format/flow/conditional-types/parentheses/__snapshots__/format.test.js.snap b/tests/format/flow/conditional-types/parentheses/__snapshots__/format.test.js.snap new file mode 100644 index 000000000..53054e5d8 --- /dev/null +++ b/tests/format/flow/conditional-types/parentheses/__snapshots__/format.test.js.snap @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`union.js [babel-flow] format 1`] = ` +"Unexpected token, expected ")" (1:24) +> 1 | type T = 'a' | ('b' extends U ? 'c' : empty); + | ^ + 2 | type T = 'a' & ('b' extends U ? 'c' : empty); + 3 | +Cause: Unexpected token, expected ")" (1:23)" +`; + +exports[`union.js format 1`] = ` +====================================options===================================== +parsers: ["flow", "typescript"] +printWidth: 80 + | printWidth +=====================================input====================================== +type T = 'a' | ('b' extends U ? 'c' : empty); +type T = 'a' & ('b' extends U ? 'c' : empty); + +=====================================output===================================== +type T = "a" | ("b" extends U ? "c" : empty); +type T = "a" & ("b" extends U ? "c" : empty); + +================================================================================ +`; diff --git a/tests/format/flow/conditional-types/parentheses/format.test.js b/tests/format/flow/conditional-types/parentheses/format.test.js new file mode 100644 index 000000000..909340f13 --- /dev/null +++ b/tests/format/flow/conditional-types/parentheses/format.test.js @@ -0,0 +1,5 @@ +runFormatTest(import.meta, ["flow", "typescript"], { + errors: { + "babel-flow": true, + }, +}); diff --git a/tests/format/flow/conditional-types/parentheses/union.js b/tests/format/flow/conditional-types/parentheses/union.js new file mode 100644 index 000000000..b9aafa1e8 --- /dev/null +++ b/tests/format/flow/conditional-types/parentheses/union.js @@ -0,0 +1,2 @@ +type T = 'a' | ('b' extends U ? 'c' : empty); +type T = 'a' & ('b' extends U ? 'c' : empty); From 4ff5dc540fa09b25fb0c02709d227af5ca53cfbf Mon Sep 17 00:00:00 2001 From: fisker Date: Mon, 3 Mar 2025 09:17:10 +0800 Subject: [PATCH 4/7] Release 3.5.3 --- .github/ISSUE_TEMPLATE/formatting.md | 2 +- .github/ISSUE_TEMPLATE/integration.md | 2 +- CHANGELOG.md | 21 +++++++++++ package.json | 2 +- .../versioned_docs/version-stable/browser.md | 36 +++++++++---------- 5 files changed, 42 insertions(+), 21 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/formatting.md b/.github/ISSUE_TEMPLATE/formatting.md index e6d75cc1b..178328dff 100644 --- a/.github/ISSUE_TEMPLATE/formatting.md +++ b/.github/ISSUE_TEMPLATE/formatting.md @@ -26,7 +26,7 @@ Don't fill the form below manually! Let a program create a report for you: --> -**Prettier 3.5.2** +**Prettier 3.5.3** [Playground link](https://prettier.io/playground/#.....) ```sh diff --git a/.github/ISSUE_TEMPLATE/integration.md b/.github/ISSUE_TEMPLATE/integration.md index 54447f5a4..8d760bec5 100644 --- a/.github/ISSUE_TEMPLATE/integration.md +++ b/.github/ISSUE_TEMPLATE/integration.md @@ -20,7 +20,7 @@ BEFORE SUBMITTING AN ISSUE: **Environments:** -- Prettier Version: 3.5.2 +- Prettier Version: 3.5.3 - Running Prettier via: - Runtime: - Operating System: diff --git a/CHANGELOG.md b/CHANGELOG.md index fe4b58121..bf58066dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +# 3.5.3 + +[diff](https://github.com/prettier/prettier/compare/3.5.2...3.5.3) + +#### Flow: Fix missing parentheses in `ConditionalTypeAnnotation` ([#17196](https://github.com/prettier/prettier/pull/17196) by [@fisker](https://github.com/fisker)) + + +```jsx +// Input +type T = 'a' | ('b' extends U ? 'c' : empty); +type T = 'a' & ('b' extends U ? 'c' : empty); + +// Prettier 3.5.2 +type T = "a" | "b" extends U ? "c" : empty; +type T = "a" & "b" extends U ? "c" : empty; + +// Prettier 3.5.3 +type T = "a" | ("b" extends U ? "c" : empty); +type T = "a" & ("b" extends U ? "c" : empty); +``` + # 3.5.2 [diff](https://github.com/prettier/prettier/compare/3.5.1...3.5.2) diff --git a/package.json b/package.json index f4a53afe8..7f6fcb5f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prettier", - "version": "3.6.0-dev", + "version": "3.5.3", "description": "Prettier is an opinionated code formatter", "bin": "./bin/prettier.cjs", "repository": "prettier/prettier", diff --git a/website/versioned_docs/version-stable/browser.md b/website/versioned_docs/version-stable/browser.md index 8836d7d17..e3eba4fee 100644 --- a/website/versioned_docs/version-stable/browser.md +++ b/website/versioned_docs/version-stable/browser.md @@ -21,7 +21,7 @@ Required options: - **[`parser`](options.md#parser) (or [`filepath`](options.md#file-path))**: One of these options has to be specified for Prettier to know which parser to use. -- **`plugins`**: Unlike the `format` function from the [Node.js-based API](api.md#prettierformatsource-options), this function doesn’t load plugins automatically. The `plugins` option is required because all the parsers included in the Prettier package come as plugins (for reasons of file size). These plugins are files in [https://unpkg.com/browse/prettier@3.5.2/plugins](https://unpkg.com/browse/prettier@3.5.2/plugins). Note that `estree` plugin should be loaded when printing JavaScript, TypeScript, Flow, or JSON. +- **`plugins`**: Unlike the `format` function from the [Node.js-based API](api.md#prettierformatsource-options), this function doesn’t load plugins automatically. The `plugins` option is required because all the parsers included in the Prettier package come as plugins (for reasons of file size). These plugins are files in [https://unpkg.com/browse/prettier@3.5.3/plugins](https://unpkg.com/browse/prettier@3.5.3/plugins). Note that `estree` plugin should be loaded when printing JavaScript, TypeScript, Flow, or JSON. You need to load the ones that you’re going to use and pass them to `prettier.format` using the `plugins` option. @@ -32,8 +32,8 @@ See below for examples. ### Global ```html - - + +