Repository: facebook/stylex (JavaScript)

Issue:
Don't wrap `content: counters(...)`  in quotes.
StyleX wraps values passed to `content` in quotes for you. However, this is sometimes problematic because it is also legal to use the `counters()` function instead of a string literal.

The compiler should be updated to detect this function, (and any other such valid value) and not wrap it in quotes. It would better to err on the side not putting quotes than putting quotes too often because it will always be possible to put the quotes in manually.


## Where to change the code

The relevant for this is the `transformValue` function in `traansform-value.js` inside of `@stylexjs/shared`


Discussion: Hey @nmn , I’d love to work on this issue as my second PR alongside my work on the playground. I have a few questions to help me get started:

**- Where to Start in the Codebase:** Could you point me to the files where content wrapping is handled? Knowing which files contain the core logic for handling values passed to content would save me some time and give me a better idea of how the current process works.
**- Pattern Matching for CSS Functions:** Since we need to detect specific CSS functions like counters() and avoid quoting them, is there any existing pattern-matching or parsing utility in the codebase that I can leverage? If not, is there a preferred way to add such detection?
**- List of Functions to Avoid Quoting:** Besides counters(), are there other functions or cases that should skip quoting? Would it be useful to create a list of functions to ignore, or is there a more dynamic way you’d prefer handling this?
**- Testing Requirements:** What kind of tests should I focus on for this fix? I want to make sure I cover both the expected cases (like counters()) and potential edge cases (other values, potential manual quotes, etc.).
**- Error Handling:** Do we already have error handling in place for parsing issues here, or should I add it if needed? I’d like to make sure we don’t break anything unintentionally by leaving some values unwrapped.
@p0nch000 I added a comment about which file would need to be updated to handle this.

- We don't have a strong opinion about how you parse the value for now. We use `postcss-value-parser` for some use-cases. (we are currently investing in a new parser, but that won't be ready for a while)
- You can see the MDN page for the all the types of values that can be used: https://developer.mozilla.org/en-US/docs/Web/CSS/content
  - Note: you can also have strings alongside other values separated by a space.
- Testing requirements: The function in question is a simple function that takes and returns a string. You can define some unit tests for various input values and show how it automatically adds quotes around values when that makes sense.
- Error Handling: When in doubt, just leave the value unchanged. Don't throw errors.
Ok ill give  it a try, thank you!



Fix (patch):
diff --git a/packages/shared/src/transform-value.js b/packages/shared/src/transform-value.js
index adc4d6166..c40b028aa 100644
--- a/packages/shared/src/transform-value.js
+++ b/packages/shared/src/transform-value.js
@@ -24,8 +24,6 @@ export default function transformValue(
       ? String(Math.round(rawValue * 10000) / 10000) + getNumberSuffix(key)
       : rawValue;
 
-  // content is one of the values that needs to wrapped in quotes.
-  // Users may write `''` without thinking about it, so we fix that.
   if (
     (key === 'content' ||
       key === 'hyphenateCharacter' ||
@@ -33,17 +31,42 @@ export default function transformValue(
     typeof value === 'string'
   ) {
     const val = value.trim();
-    if (val.match(/^attr\([a-zA-Z0-9-]+\)$/)) {
+
+    const cssContentFunctions = [
+      'attr(',
+      'counter(',
+      'counters(',
+      'url(',
+      'linear-gradient(',
+      'image-set(',
+    ];
+
+    const cssContentKeywords = new Set([
+      'normal',
+      'none',
+      'open-quote',
+      'close-quote',
+      'no-open-quote',
+      'no-close-quote',
+      'inherit',
+      'initial',
+      'revert',
+      'revert-layer',
+      'unset',
+    ]);
+
+    const isCssFunction = cssContentFunctions.some((func) =>
+      val.includes(func),
+    );
+    const isKeyword = cssContentKeywords.has(val);
+    const hasMatchingQuotes =
+      (val.match(/"/g)?.length ?? 0) >= 2 ||
+      (val.match(/'/g)?.length ?? 0) >= 2;
+
+    if (isCssFunction || isKeyword || hasMatchingQuotes) {
       return val;
     }
-    if (
-      !(
-        (val.startsWith('"') && val.endsWith('"')) ||
-        (val.startsWith("'") && val.endsWith("'"))
-      )
-    ) {
-      return `"${val}"`;
-    }
+    return `"${val}"`;
   }
 
   return normalizeValue(value, key, options);