Repository: prettier/prettier (JavaScript)
Date: 2024-08-12 22:53:37+00:00
Task type: Feature

Issue:
Any thoughts on making `getPreferredQuote` part of the public `util` API?
Since `makeString` is already public and many languages support strings declared with single and double quotes, providing this functionality would benefit many plugins.



Issue discussion:
I guess it's acceptable. [implementation](https://github.com/prettier/prettier/blob/main/src/utils/get-preferred-quote.js)
It sounds good to me

Solution patch:
diff --git a/changelog_unreleased/api/16567.md b/changelog_unreleased/api/16567.md
new file mode 100644
index 000000000000..8147d50c0df9
--- /dev/null
+++ b/changelog_unreleased/api/16567.md
@@ -0,0 +1,34 @@
+#### Make `getPreferredQuote` public (#16567 by @sosukesuzuki)
+
+This change makes the internal `getPreferredQuote` function a part of the public API.
+
+In languages like JavaScript, both single quotes and double quotes can be used for string literals. Prettier determines the quote to enclose a string literal based on the number of quotes within the string and the value of the [`singleQuote`](https://prettier.io/docs/en/options#quotes) option. For more details, please refer to the [Rationale](https://prettier.io/docs/en/rationale.html#strings) page.
+
+The `getPreferredQuote` function determines the appropriate quote to enclose a string literal and has the following interface:
+
+```ts
+type Quote = '"' | "'";
+function getPreferredQuote(
+  text: string,
+  preferredQuoteOrPreferSingleQuote: Quote | boolean,
+): Quote;
+```
+
+Here are some examples of how to use it:
+
+<!-- prettier-ignore -->
+```jsx
+import * as prettier from "prettier";
+
+const SINGLE_QUOTE = `'`;
+const DOUBLE_QUOTE = `"`;
+
+console.log(prettier.util.getPreferredQuote(`Hello World Test`, SINGLE_QUOTE)); // '
+console.log(prettier.util.getPreferredQuote(`Hello World Test`, DOUBLE_QUOTE)); // "
+console.log(prettier.util.getPreferredQuote(`'Hello' "World" 'Test'`, SINGLE_QUOTE)); // "
+console.log(prettier.util.getPreferredQuote(`"Hello" 'World' "Test"`, DOUBLE_QUOTE)); // '
+console.log(prettier.util.getPreferredQuote(`"Hello" "World" "Test"`, SINGLE_QUOTE)); // '
+console.log(prettier.util.getPreferredQuote(`'Hello' 'World' 'Test'`, DOUBLE_QUOTE)); // "
+```
+
+Making this function public will benefit plugin developers. Since the function is relatively short, you can find more details in the [implementation](https://github.com/prettier/prettier/blob/509a0cc10f12cbfa5304737965f99284449aca88/src/utils/get-preferred-quote.js).
diff --git a/docs/plugins.md b/docs/plugins.md
index bfa039eabe43..9bd503f835de 100644
--- a/docs/plugins.md
+++ b/docs/plugins.md
@@ -609,6 +609,11 @@ function getNextNonSpaceNonCommentCharacterIndex(
 function isNextLineEmpty(text: string, startIndex: number): boolean;
 
 function isPreviousLineEmpty(text: string, startIndex: number): boolean;
+
+function getPreferredQuote(
+  text: string,
+  preferredQuoteOrPreferSingleQuote: Quote | boolean,
+): Quote;
 ```
 
 ### Tutorials
diff --git a/src/index.d.ts b/src/index.d.ts
index c4b6e29d08c2..8e4df1647f36 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -938,4 +938,9 @@ export namespace util {
   function addDanglingComment(node: any, comment: any, marker: any): void;
 
   function addTrailingComment(node: any, comment: any): void;
+
+  function getPreferredQuote(
+    text: string,
+    preferredQuoteOrPreferSingleQuote: Quote | boolean,
+  ): Quote;
 }
diff --git a/src/utils/get-preferred-quote.js b/src/utils/get-preferred-quote.js
index 720095eb3b74..ec1a85ff7c34 100644
--- a/src/utils/get-preferred-quote.js
+++ b/src/utils/get-preferred-quote.js
@@ -7,11 +7,11 @@ const DOUBLE_QUOTE = '"';
 
 /**
  *
- * @param {string} rawContent
+ * @param {string} text
  * @param {Quote | boolean} preferredQuoteOrPreferSingleQuote
  * @returns {Quote}
  */
-function getPreferredQuote(rawContent, preferredQuoteOrPreferSingleQuote) {
+function getPreferredQuote(text, preferredQuoteOrPreferSingleQuote) {
   const preferred =
     preferredQuoteOrPreferSingleQuote === true ||
     preferredQuoteOrPreferSingleQuote === SINGLE_QUOTE
@@ -21,7 +21,7 @@ function getPreferredQuote(rawContent, preferredQuoteOrPreferSingleQuote) {
 
   let preferredQuoteCount = 0;
   let alternateQuoteCount = 0;
-  for (const character of rawContent) {
+  for (const character of text) {
     if (character === preferred) {
       preferredQuoteCount++;
     } else if (character === alternate) {
diff --git a/src/utils/public.js b/src/utils/public.js
index 9bf12ace3711..44ae192dc807 100644
--- a/src/utils/public.js
+++ b/src/utils/public.js
@@ -91,6 +91,7 @@ export { default as getAlignmentSize } from "./get-alignment-size.js";
 export { default as getIndentSize } from "./get-indent-size.js";
 export { default as getMaxContinuousCount } from "./get-max-continuous-count.js";
 export { default as getNextNonSpaceNonCommentCharacter } from "./get-next-non-space-non-comment-character.js";
+export { default as getPreferredQuote } from "./get-preferred-quote.js";
 export { default as getStringWidth } from "./get-string-width.js";
 export { default as hasNewline } from "./has-newline.js";
 export { default as hasNewlineInRange } from "./has-newline-in-range.js";


Test patch:
diff --git a/tests/integration/__tests__/util-shared.js b/tests/integration/__tests__/util-shared.js
index 2140db10725e..4fdf3ab37f3b 100644
--- a/tests/integration/__tests__/util-shared.js
+++ b/tests/integration/__tests__/util-shared.js
@@ -26,6 +26,7 @@ test("shared util has correct structure", () => {
     "function",
   );
   expect(typeof sharedUtil.makeString).toBe("function");
+  expect(typeof sharedUtil.getPreferredQuote).toBe("function");
 });
 
 test("sharedUtil.getMaxContinuousCount", () => {
@@ -229,3 +230,30 @@ test("sharedUtil.makeString", () => {
     ),
   ).toBe(`${SINGLE_QUOTE}a${SINGLE_QUOTE}`);
 });
+
+test("sharedUtil.getPreferredQuote", () => {
+  const { getPreferredQuote } = sharedUtil;
+  const DOUBLE_QUOTE = '"';
+  const SINGLE_QUOTE = "'";
+
+  expect(getPreferredQuote(``, true)).toBe(SINGLE_QUOTE);
+  expect(getPreferredQuote(``, false)).toBe(DOUBLE_QUOTE);
+  expect(getPreferredQuote(``, DOUBLE_QUOTE)).toBe(DOUBLE_QUOTE);
+  expect(getPreferredQuote(``, SINGLE_QUOTE)).toBe(SINGLE_QUOTE);
+  expect(getPreferredQuote(`'Hello' "World" 'Test'`, SINGLE_QUOTE)).toBe(
+    DOUBLE_QUOTE,
+  );
+  expect(getPreferredQuote(`"Hello" 'World' "Test"`, DOUBLE_QUOTE)).toBe(
+    SINGLE_QUOTE,
+  );
+  expect(getPreferredQuote(`"Hello" "World" "Test"`, SINGLE_QUOTE)).toBe(
+    SINGLE_QUOTE,
+  );
+  expect(getPreferredQuote(`'Hello' 'World' 'Test'`, DOUBLE_QUOTE)).toBe(
+    DOUBLE_QUOTE,
+  );
+  expect(getPreferredQuote(`'Hello' "World"`, SINGLE_QUOTE)).toBe(SINGLE_QUOTE);
+  expect(getPreferredQuote(`"Hello" 'World'`, DOUBLE_QUOTE)).toBe(DOUBLE_QUOTE);
+  expect(getPreferredQuote(`Hello World`, true)).toBe(SINGLE_QUOTE);
+  expect(getPreferredQuote(`Hello World`, false)).toBe(DOUBLE_QUOTE);
+});