diff --git a/changelog_unreleased/typescript/17251.md b/changelog_unreleased/typescript/17251.md new file mode 100644 index 000000000..44ec50883 --- /dev/null +++ b/changelog_unreleased/typescript/17251.md @@ -0,0 +1,31 @@ +#### Fix linebreak in member chain (#17251 by @fisker) + + +```tsx +// Input +model = types + .model({ something: mxSomething }) + .volatile<| "a" | "b">(() => [ + annularThingamabobWrapper, + octahedralWeevilService, + cantileveredRhubarbProcessor, + annularPlatypusGenerator, + ]); + +// Prettier stable +model = types + .model({ something: mxSomething }) + .volatile< + "a" | "b" + >(() => [annularThingamabobWrapper, octahedralWeevilService, cantileveredRhubarbProcessor, annularPlatypusGenerator]); + +// Prettier main +model = types + .model({ something: mxSomething }) + .volatile<"a" | "b">(() => [ + annularThingamabobWrapper, + octahedralWeevilService, + cantileveredRhubarbProcessor, + annularPlatypusGenerator, + ]); +``` diff --git a/src/document/printer/printer.js b/src/document/printer/printer.js index 0e4059dab..54a69e87d 100644 --- a/src/document/printer/printer.js +++ b/src/document/printer/printer.js @@ -302,7 +302,7 @@ function printDocToString(doc, options) { index < doc.expandedStates.length + 1; index++ ) { - if (index >= doc.expandedStates.length) { + if (index >= doc.expandedStates.length - 1) { commands.push({ indent, mode: MODE_BREAK, diff --git a/tests/config/run-format-test.js b/tests/config/run-format-test.js index 8afb9a248..ca766e6ec 100644 --- a/tests/config/run-format-test.js +++ b/tests/config/run-format-test.js @@ -48,6 +48,10 @@ const unstableTests = new Map( "typescript/import-type/long-module-name/long-module-name4.ts", // Unstable due to lack of indent information "js/multiparser-comments/comment-inside.js", + [ + "typescript/method-chain/object/issue-17239.ts", + (options) => options.objectWrap !== "collapse", + ], ].map((fixture) => { const [file, isUnstable = () => true] = Array.isArray(fixture) ? fixture diff --git a/tests/format/flow/method-chain/18136-flow/18136.js b/tests/format/flow/method-chain/18136-flow/18136.js new file mode 100644 index 000000000..6afe8d967 --- /dev/null +++ b/tests/format/flow/method-chain/18136-flow/18136.js @@ -0,0 +1,4 @@ +const defaultColorDecoratorsEnablement = accessor.get(IConfigurationService) + .getValue<"auto" | "always" | "never">( + "longlonglonglonglonglonglonglonglong", + ); diff --git a/tests/format/flow/method-chain/18136-flow/__snapshots__/format.test.js.snap b/tests/format/flow/method-chain/18136-flow/__snapshots__/format.test.js.snap new file mode 100644 index 000000000..60383d14c --- /dev/null +++ b/tests/format/flow/method-chain/18136-flow/__snapshots__/format.test.js.snap @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`18136.js format 1`] = ` +====================================options===================================== +parsers: ["flow"] +printWidth: 80 + | printWidth +=====================================input====================================== +const defaultColorDecoratorsEnablement = accessor.get(IConfigurationService) + .getValue<"auto" | "always" | "never">( + "longlonglonglonglonglonglonglonglong", + ); + +=====================================output===================================== +const defaultColorDecoratorsEnablement = accessor + .get(IConfigurationService) + .getValue<"auto" | "always" | "never">( + "longlonglonglonglonglonglonglonglong", + ); + +================================================================================ +`; diff --git a/tests/format/flow/method-chain/18136-flow/format.test.js b/tests/format/flow/method-chain/18136-flow/format.test.js new file mode 100644 index 000000000..55c339883 --- /dev/null +++ b/tests/format/flow/method-chain/18136-flow/format.test.js @@ -0,0 +1 @@ +runFormatTest(import.meta, ["flow"]); diff --git a/tests/format/typescript/method-chain/18136-typescript/18136.ts b/tests/format/typescript/method-chain/18136-typescript/18136.ts new file mode 100644 index 000000000..6afe8d967 --- /dev/null +++ b/tests/format/typescript/method-chain/18136-typescript/18136.ts @@ -0,0 +1,4 @@ +const defaultColorDecoratorsEnablement = accessor.get(IConfigurationService) + .getValue<"auto" | "always" | "never">( + "longlonglonglonglonglonglonglonglong", + ); diff --git a/tests/format/typescript/method-chain/18136-typescript/__snapshots__/format.test.js.snap b/tests/format/typescript/method-chain/18136-typescript/__snapshots__/format.test.js.snap new file mode 100644 index 000000000..745543d86 --- /dev/null +++ b/tests/format/typescript/method-chain/18136-typescript/__snapshots__/format.test.js.snap @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`18136.ts format 1`] = ` +====================================options===================================== +parsers: ["typescript"] +printWidth: 80 + | printWidth +=====================================input====================================== +const defaultColorDecoratorsEnablement = accessor.get(IConfigurationService) + .getValue<"auto" | "always" | "never">( + "longlonglonglonglonglonglonglonglong", + ); + +=====================================output===================================== +const defaultColorDecoratorsEnablement = accessor + .get(IConfigurationService) + .getValue<"auto" | "always" | "never">( + "longlonglonglonglonglonglonglonglong", + ); + +================================================================================ +`; diff --git a/tests/format/typescript/method-chain/18136-typescript/format.test.js b/tests/format/typescript/method-chain/18136-typescript/format.test.js new file mode 100644 index 000000000..116dcbd78 --- /dev/null +++ b/tests/format/typescript/method-chain/18136-typescript/format.test.js @@ -0,0 +1 @@ +runFormatTest(import.meta, ["typescript"]); diff --git a/tests/format/typescript/method-chain/__snapshots__/format.test.js.snap b/tests/format/typescript/method-chain/__snapshots__/format.test.js.snap index 5796c94f2..e6e705b78 100644 --- a/tests/format/typescript/method-chain/__snapshots__/format.test.js.snap +++ b/tests/format/typescript/method-chain/__snapshots__/format.test.js.snap @@ -35,3 +35,240 @@ this.firebase ================================================================================ `; + +exports[`issue-17236.ts format 1`] = ` +====================================options===================================== +parsers: ["typescript", "flow"] +printWidth: 80 + | printWidth +=====================================input====================================== +lumalOnetaxesResponse + .map< + [string, LumalOnetax[]] + >((lumalOnetaxesForZandoni) => [ + lumalOnetaxesForZandoni.cimosey, + lumalOnetaxesForZandoni.lumalOnetaxes + .map((response) => this.mapToLumalOnetax(response)) + .filter(({ name, dapy }, i) => this.filterLumalOnetax(name, dapy, i, rivano)) + ]) + .filter(([cimosey], i) => this.filterCimosey(cimosey, rivano, i)); + +lumalOnetaxesResponse + .map((lumalOnetaxesForZandoni) => [ + lumalOnetaxesForZandoni.cimosey, + lumalOnetaxesForZandoni.lumalOnetaxes + .map((response) => this.mapToLumalOnetax(response)) + .filter(({ name, dapy }, i) => this.filterLumalOnetax(name, dapy, i, rivano)) + ]) + .filter(([cimosey], i) => this.filterCimosey(cimosey, rivano, i)); + +=====================================output===================================== +lumalOnetaxesResponse + .map<[string, LumalOnetax[]]>((lumalOnetaxesForZandoni) => [ + lumalOnetaxesForZandoni.cimosey, + lumalOnetaxesForZandoni.lumalOnetaxes + .map((response) => this.mapToLumalOnetax(response)) + .filter(({ name, dapy }, i) => + this.filterLumalOnetax(name, dapy, i, rivano), + ), + ]) + .filter(([cimosey], i) => this.filterCimosey(cimosey, rivano, i)); + +lumalOnetaxesResponse + .map((lumalOnetaxesForZandoni) => [ + lumalOnetaxesForZandoni.cimosey, + lumalOnetaxesForZandoni.lumalOnetaxes + .map((response) => this.mapToLumalOnetax(response)) + .filter(({ name, dapy }, i) => + this.filterLumalOnetax(name, dapy, i, rivano), + ), + ]) + .filter(([cimosey], i) => this.filterCimosey(cimosey, rivano, i)); + +================================================================================ +`; + +exports[`issue-17239.ts format 1`] = ` +====================================options===================================== +parsers: ["typescript", "flow"] +printWidth: 80 + | printWidth +=====================================input====================================== +model = types + .model({ something: mxSomething }) + .volatile<[foo]>((self) => ([ loading, false, savingStatus, "idle", undoDisabled, false, aiFocused, false, online, true ])); + +=====================================output===================================== +model = types + .model({ something: mxSomething }) + .volatile<[foo]>((self) => [ + loading, + false, + savingStatus, + "idle", + undoDisabled, + false, + aiFocused, + false, + online, + true, + ]); + +================================================================================ +`; + +exports[`issue-18136-2.ts format 1`] = ` +====================================options===================================== +parsers: ["typescript", "flow"] +printWidth: 80 + | printWidth +=====================================input====================================== +{ + const x = async (xxxxxxxx: string): Promise => { + await ( + await (await xxxx.xxxx.x(xxxxxxxxxxxxxxx))?.xxxxxxxxxxxx() + ) + ?.xxxxxxx(xxxxxxxx) + .xxxxxxxx(); + }; +} + +=====================================output===================================== +{ + const x = async (xxxxxxxx: string): Promise => { + await ( + await (await xxxx.xxxx.x(xxxxxxxxxxxxxxx))?.xxxxxxxxxxxx() + ) + ?.xxxxxxx(xxxxxxxx) + .xxxxxxxx(); + }; +} + +================================================================================ +`; + +exports[`realworld-cases.ts format 1`] = ` +====================================options===================================== +parsers: ["typescript", "flow"] +printWidth: 80 + | printWidth +=====================================input====================================== +// https://github.com/mdn/content/blob/923adb616baa87402ca965ebd18a73380cc84d27/files/en-us/mozilla/add-ons/webextensions/api/browsersettings/tlsversionrestrictionconfig/index.md?plain=1#L25 +browser.browserSettings.tlsVersionRestrictionConfig.maximum + .get({}) + .then((result) => { + console.log(\`Highest TLS version supported: \${result}\`); + }); + +// https://github.com/mdn/content/blob/923adb616baa87402ca965ebd18a73380cc84d27/files/en-us/web/api/animation/pause/index.md?plain=1#L38 +const nommingCake = document + .getElementById("eat-me_sprite") + .animate( + [{ transform: "translateY(0)" }, { transform: "translateY(-80%)" }], + { + fill: "forwards", + easing: "steps(4, end)", + duration: aliceChange.effect.timing.duration / 2, + }, + ); + +// https://github.com/mdn/content/blob/923adb616baa87402ca965ebd18a73380cc84d27/files/en-us/web/api/element/fullscreenchange_event/index.md?plain=1#L75 +document + .getElementById("toggle-fullscreen") + .addEventListener("click", (event) => { + if (document.fullscreenElement) { + // exitFullscreen is only available on the Document object. + document.exitFullscreen(); + } else { + el.requestFullscreen(); + } + }); + +// https://github.com/mdn/content/blob/923adb616baa87402ca965ebd18a73380cc84d27/files/en-us/web/api/htmlelement/focus/index.md?plain=1#L98 +document + .getElementById("focusButtonVisibleIndication") + .addEventListener("click", () => { + document.getElementById("myButton").focus({ focusVisible: true }); + }); + +function replacePlaceholders(quasisDoc, expressionDocs) { + const newDoc = mapDoc(cleanDoc(quasisDoc), (doc) => { + // When we have multiple placeholders in one line, like: + // \${Child}\${Child2}:not(:first-child) + return doc + .split(/@prettier-placeholder-(\\d+)-id/u) + .map((component, idx) => { + // The placeholder is always at odd indices + if (idx % 2 === 0) { + return replaceEndOfLine(component); + } + + // The component will always be a number at odd index + replaceCounter++; + return expressionDocs[component]; + }); + }); + return expressionDocs.length === replaceCounter ? newDoc : null; +} + +=====================================output===================================== +// https://github.com/mdn/content/blob/923adb616baa87402ca965ebd18a73380cc84d27/files/en-us/mozilla/add-ons/webextensions/api/browsersettings/tlsversionrestrictionconfig/index.md?plain=1#L25 +browser.browserSettings.tlsVersionRestrictionConfig.maximum + .get({}) + .then((result) => { + console.log(\`Highest TLS version supported: \${result}\`); + }); + +// https://github.com/mdn/content/blob/923adb616baa87402ca965ebd18a73380cc84d27/files/en-us/web/api/animation/pause/index.md?plain=1#L38 +const nommingCake = document + .getElementById("eat-me_sprite") + .animate( + [{ transform: "translateY(0)" }, { transform: "translateY(-80%)" }], + { + fill: "forwards", + easing: "steps(4, end)", + duration: aliceChange.effect.timing.duration / 2, + }, + ); + +// https://github.com/mdn/content/blob/923adb616baa87402ca965ebd18a73380cc84d27/files/en-us/web/api/element/fullscreenchange_event/index.md?plain=1#L75 +document + .getElementById("toggle-fullscreen") + .addEventListener("click", (event) => { + if (document.fullscreenElement) { + // exitFullscreen is only available on the Document object. + document.exitFullscreen(); + } else { + el.requestFullscreen(); + } + }); + +// https://github.com/mdn/content/blob/923adb616baa87402ca965ebd18a73380cc84d27/files/en-us/web/api/htmlelement/focus/index.md?plain=1#L98 +document + .getElementById("focusButtonVisibleIndication") + .addEventListener("click", () => { + document.getElementById("myButton").focus({ focusVisible: true }); + }); + +function replacePlaceholders(quasisDoc, expressionDocs) { + const newDoc = mapDoc(cleanDoc(quasisDoc), (doc) => { + // When we have multiple placeholders in one line, like: + // \${Child}\${Child2}:not(:first-child) + return doc + .split(/@prettier-placeholder-(\\d+)-id/u) + .map((component, idx) => { + // The placeholder is always at odd indices + if (idx % 2 === 0) { + return replaceEndOfLine(component); + } + + // The component will always be a number at odd index + replaceCounter++; + return expressionDocs[component]; + }); + }); + return expressionDocs.length === replaceCounter ? newDoc : null; +} + +================================================================================ +`; diff --git a/tests/format/typescript/method-chain/issue-17236.ts b/tests/format/typescript/method-chain/issue-17236.ts new file mode 100644 index 000000000..b373cc53e --- /dev/null +++ b/tests/format/typescript/method-chain/issue-17236.ts @@ -0,0 +1,19 @@ +lumalOnetaxesResponse + .map< + [string, LumalOnetax[]] + >((lumalOnetaxesForZandoni) => [ + lumalOnetaxesForZandoni.cimosey, + lumalOnetaxesForZandoni.lumalOnetaxes + .map((response) => this.mapToLumalOnetax(response)) + .filter(({ name, dapy }, i) => this.filterLumalOnetax(name, dapy, i, rivano)) + ]) + .filter(([cimosey], i) => this.filterCimosey(cimosey, rivano, i)); + +lumalOnetaxesResponse + .map((lumalOnetaxesForZandoni) => [ + lumalOnetaxesForZandoni.cimosey, + lumalOnetaxesForZandoni.lumalOnetaxes + .map((response) => this.mapToLumalOnetax(response)) + .filter(({ name, dapy }, i) => this.filterLumalOnetax(name, dapy, i, rivano)) + ]) + .filter(([cimosey], i) => this.filterCimosey(cimosey, rivano, i)); diff --git a/tests/format/typescript/method-chain/issue-17239.ts b/tests/format/typescript/method-chain/issue-17239.ts new file mode 100644 index 000000000..89c7ff916 --- /dev/null +++ b/tests/format/typescript/method-chain/issue-17239.ts @@ -0,0 +1,3 @@ +model = types + .model({ something: mxSomething }) + .volatile<[foo]>((self) => ([ loading, false, savingStatus, "idle", undoDisabled, false, aiFocused, false, online, true ])); diff --git a/tests/format/typescript/method-chain/issue-18136-2.ts b/tests/format/typescript/method-chain/issue-18136-2.ts new file mode 100644 index 000000000..60e8a9cb5 --- /dev/null +++ b/tests/format/typescript/method-chain/issue-18136-2.ts @@ -0,0 +1,9 @@ +{ + const x = async (xxxxxxxx: string): Promise => { + await ( + await (await xxxx.xxxx.x(xxxxxxxxxxxxxxx))?.xxxxxxxxxxxx() + ) + ?.xxxxxxx(xxxxxxxx) + .xxxxxxxx(); + }; +} diff --git a/tests/format/typescript/method-chain/object/__snapshots__/format.test.js.snap b/tests/format/typescript/method-chain/object/__snapshots__/format.test.js.snap new file mode 100644 index 000000000..d5d44f5e4 --- /dev/null +++ b/tests/format/typescript/method-chain/object/__snapshots__/format.test.js.snap @@ -0,0 +1,50 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`issue-17239.ts - {"objectWrap":"collapse"} format 1`] = ` +====================================options===================================== +objectWrap: "collapse" +parsers: ["typescript", "flow"] +printWidth: 80 + | printWidth +=====================================input====================================== +model = types + .model({ something: mxSomething }) + .volatile<[foo]>((self) => ({ loading: false, savingStatus: "idle", undoDisabled: false, aiFocused: false, online: true })); + +=====================================output===================================== +model = types + .model({ something: mxSomething }) + .volatile<[foo]>((self) => ({ + loading: false, + savingStatus: "idle", + undoDisabled: false, + aiFocused: false, + online: true, + })); + +================================================================================ +`; + +exports[`issue-17239.ts format 1`] = ` +====================================options===================================== +parsers: ["typescript", "flow"] +printWidth: 80 + | printWidth +=====================================input====================================== +model = types + .model({ something: mxSomething }) + .volatile<[foo]>((self) => ({ loading: false, savingStatus: "idle", undoDisabled: false, aiFocused: false, online: true })); + +=====================================output===================================== +model = types + .model({ something: mxSomething }) + .volatile<[foo]>((self) => ({ + loading: false, + savingStatus: "idle", + undoDisabled: false, + aiFocused: false, + online: true, + })); + +================================================================================ +`; diff --git a/tests/format/typescript/method-chain/object/format.test.js b/tests/format/typescript/method-chain/object/format.test.js new file mode 100644 index 000000000..3896c79ff --- /dev/null +++ b/tests/format/typescript/method-chain/object/format.test.js @@ -0,0 +1,2 @@ +runFormatTest(import.meta, ["typescript", "flow"]); +runFormatTest(import.meta, ["typescript", "flow"], { objectWrap: "collapse" }); diff --git a/tests/format/typescript/method-chain/object/issue-17239.ts b/tests/format/typescript/method-chain/object/issue-17239.ts new file mode 100644 index 000000000..b67268612 --- /dev/null +++ b/tests/format/typescript/method-chain/object/issue-17239.ts @@ -0,0 +1,3 @@ +model = types + .model({ something: mxSomething }) + .volatile<[foo]>((self) => ({ loading: false, savingStatus: "idle", undoDisabled: false, aiFocused: false, online: true })); diff --git a/tests/format/typescript/method-chain/realworld-cases.ts b/tests/format/typescript/method-chain/realworld-cases.ts new file mode 100644 index 000000000..6e213427a --- /dev/null +++ b/tests/format/typescript/method-chain/realworld-cases.ts @@ -0,0 +1,57 @@ +// https://github.com/mdn/content/blob/923adb616baa87402ca965ebd18a73380cc84d27/files/en-us/mozilla/add-ons/webextensions/api/browsersettings/tlsversionrestrictionconfig/index.md?plain=1#L25 +browser.browserSettings.tlsVersionRestrictionConfig.maximum + .get({}) + .then((result) => { + console.log(`Highest TLS version supported: ${result}`); + }); + +// https://github.com/mdn/content/blob/923adb616baa87402ca965ebd18a73380cc84d27/files/en-us/web/api/animation/pause/index.md?plain=1#L38 +const nommingCake = document + .getElementById("eat-me_sprite") + .animate( + [{ transform: "translateY(0)" }, { transform: "translateY(-80%)" }], + { + fill: "forwards", + easing: "steps(4, end)", + duration: aliceChange.effect.timing.duration / 2, + }, + ); + +// https://github.com/mdn/content/blob/923adb616baa87402ca965ebd18a73380cc84d27/files/en-us/web/api/element/fullscreenchange_event/index.md?plain=1#L75 +document + .getElementById("toggle-fullscreen") + .addEventListener("click", (event) => { + if (document.fullscreenElement) { + // exitFullscreen is only available on the Document object. + document.exitFullscreen(); + } else { + el.requestFullscreen(); + } + }); + +// https://github.com/mdn/content/blob/923adb616baa87402ca965ebd18a73380cc84d27/files/en-us/web/api/htmlelement/focus/index.md?plain=1#L98 +document + .getElementById("focusButtonVisibleIndication") + .addEventListener("click", () => { + document.getElementById("myButton").focus({ focusVisible: true }); + }); + +function replacePlaceholders(quasisDoc, expressionDocs) { + const newDoc = mapDoc(cleanDoc(quasisDoc), (doc) => { + // When we have multiple placeholders in one line, like: + // ${Child}${Child2}:not(:first-child) + return doc + .split(/@prettier-placeholder-(\d+)-id/u) + .map((component, idx) => { + // The placeholder is always at odd indices + if (idx % 2 === 0) { + return replaceEndOfLine(component); + } + + // The component will always be a number at odd index + replaceCounter++; + return expressionDocs[component]; + }); + }); + return expressionDocs.length === replaceCounter ? newDoc : null; +}