ronantakizawa/codereview-bench huggingface dataset:
Repository: tailwindlabs/tailwindcss
File: integrations/upgrade/index.test.ts (TypeScript)

Code before:
          content: ['./src/**/*.{html,js}'],
        }
      `,
      'src/index.html': html`
        <h1>🤠👋</h1>
        <div class="!flex sm:!block bg-gradient-to-t bg-[--my-red]"></div>
      `,
      'src/input.css': css`
        @tailwind base;
        @tailwind components;
        @tailwind utilities;
      `,
    },
  },
  async ({ exec, fs }) => {
    await exec('npx @tailwindcss/upgrade')

    expect(await fs.dumpFiles('./src/**/*.{css,html}')).toMatchInlineSnapshot(`
      "
      --- ./src/index.html ---
      <h1>🤠👋</h1>
      <div class="flex! sm:block! bg-linear-to-t bg-[var(--my-red)]"></div>

      --- ./src/input.css ---
      @import 'tailwindcss';
      @config "../tailwind.config.js";"
    `)

    let packageJsonContent = await fs.read('package.json')
    let packageJson = JSON.parse(packageJsonContent)
    expect(packageJson.dependencies).toMatchObject({
      tailwindcss: expect.stringContaining('4.0.0'),
    })
  },
)

test(
  `upgrades a v3 project with prefixes to v4`,
  {
    fs: {
      'package.json': json`
        {
          "dependencies": {
            "@tailwindcss/upgrade": "workspace:^"
          }
        }
      `,
      'tailwind.config.js': js`
        /** @type {import('tailwindcss').Config} */
        module.exports = {
          content: ['./src/**/*.{html,js}'],

Review comment (suggestion):
```suggestion
      @config "../tailwind.config.js";
```

Code after:
          content: ['./src/**/*.{html,js}'],
        }
      `,
      'src/index.html': html`
        <h1>🤠👋</h1>
        <div class="!flex sm:!block bg-gradient-to-t bg-[--my-red]"></div>
      `,
      'src/input.css': css`
        @tailwind base;
        @tailwind components;
        @tailwind utilities;
      `,
    },
  },
  async ({ exec, fs }) => {
    await exec('npx @tailwindcss/upgrade')

    expect(await fs.dumpFiles('./src/**/*.{css,html}')).toMatchInlineSnapshot(`
      "
      --- ./src/index.html ---
      <h1>🤠👋</h1>
      <div class="flex! sm:block! bg-linear-to-t bg-[var(--my-red)]"></div>

      --- ./src/input.css ---
      @import 'tailwindcss';
      @config "../tailwind.config.js";
      "
    `)

    let packageJsonContent = await fs.read('package.json')
    let packageJson = JSON.parse(packageJsonContent)
    expect(packageJson.dependencies).toMatchObject({
      tailwindcss: expect.stringContaining('4.0.0'),
    })
  },
)

test(
  `upgrades a v3 project with prefixes to v4`,
  {
    fs: {
      'package.json': json`
        {
          "dependencies": {
            "@tailwindcss/upgrade": "workspace:^"
          }
        }
      `,
      'tailwind.config.js': js`
        /** @type {import('tailwindcss').Config} */
        module.exports = {
Repository: tailwindlabs/tailwindcss
File: packages/@tailwindcss-upgrade/src/template/prepare-config.ts (TypeScript)

Code before:
    if (!relative.startsWith('.')) {
      relative = './' + relative
    }

    let userConfig = await createResolvedUserConfig(fullConfigPath)

    let newPrefix = userConfig.prefix ? migratePrefix(userConfig.prefix) : null
    let input = css`
      @import 'tailwindcss' ${newPrefix ? `prefix(${newPrefix})` : ''};
      @config './${relative}';
    `

    let [compiler, designSystem] = await Promise.all([
      compile(input, { base: __dirname, onDependency: () => {} }),
      __unstable__loadDesignSystem(input, { base: __dirname }),
    ])

    return {
      designSystem,
      globs: compiler.globs,
      userConfig,
      newPrefix,
      configFilePath: fullConfigPath,
    }
  } catch (e: any) {
    console.error(e)
    error('Could not load the configuration file: ' + e.message)
    process.exit(1)
  }
}

async function createResolvedUserConfig(fullConfigPath: string): Promise<Config> {
  let [noopDesignSystem, unresolvedUserConfig] = await Promise.all([
    __unstable__loadDesignSystem(
      css`
        @import 'tailwindcss';
      `,
      { base: __dirname },
    ),
    loadModule(fullConfigPath, __dirname, () => {}).then((result) => result.module) as Config,
  ])

  return resolveConfig(noopDesignSystem, [
    { base: dirname(fullConfigPath), config: unresolvedUserConfig },
  ]) as any
}

const DEFAULT_CONFIG_FILES = [
  './tailwind.config.js',
  './tailwind.config.cjs',
  './tailwind.config.mjs',

Review comment (suggestion):
feels weird to me to use both `console.error` _and_ `error`

Code after:
    if (!relative.startsWith('.')) {
      relative = './' + relative
    }

    let userConfig = await createResolvedUserConfig(fullConfigPath)

    let newPrefix = userConfig.prefix ? migratePrefix(userConfig.prefix) : null
    let input = css`
      @import 'tailwindcss' ${newPrefix ? `prefix(${newPrefix})` : ''};
      @config './${relative}';
    `

    let [compiler, designSystem] = await Promise.all([
      compile(input, { base: __dirname, onDependency: () => {} }),
      __unstable__loadDesignSystem(input, { base: __dirname }),
    ])

    return {
      designSystem,
      globs: compiler.globs,
      userConfig,
      newPrefix,
      configFilePath: fullConfigPath,
    }
  } catch (e: any) {
    error('Could not load the configuration file: ' + e.message)
    process.exit(1)
  }
}

async function createResolvedUserConfig(fullConfigPath: string): Promise<Config> {
  let [noopDesignSystem, unresolvedUserConfig] = await Promise.all([
    __unstable__loadDesignSystem(
      css`
        @import 'tailwindcss';
      `,
      { base: __dirname },
    ),
    loadModule(fullConfigPath, __dirname, () => {}).then((result) => result.module) as Config,
  ])

  return resolveConfig(noopDesignSystem, [
    { base: dirname(fullConfigPath), config: unresolvedUserConfig },
  ]) as any
}

const DEFAULT_CONFIG_FILES = [
  './tailwind.config.js',
  './tailwind.config.cjs',
  './tailwind.config.mjs',
  './tailwind.config.ts',
Repository: tailwindlabs/tailwindcss
File: integrations/upgrade/js-config.test.ts (TypeScript)

Code before:
    expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(`
      "
      --- src/input.css ---
      @import 'tailwindcss';
      @config '../tailwind.config.ts';
      "
    `)

    expect(await fs.dumpFiles('tailwind.config.ts')).toMatchInlineSnapshot(`
      "
      --- tailwind.config.ts ---
      import { type Config } from 'tailwindcss'
      import typographyStyles from './typography'

      export default {
        theme: {
          typography: typographyStyles,
        },
      } satisfies Config
      "
    `)
  },
)

test(
  'does not upgrade JS config files with static plugins',
  {
    fs: {
      'package.json': json`
        {
          "dependencies": {
            "@tailwindcss/typography": "^0.5.15",
            "@tailwindcss/upgrade": "workspace:^"
          }
        }
      `,
      'tailwind.config.ts': ts`
        import { type Config } from 'tailwindcss'
        import typography from '@tailwindcss/typography'
        import customPlugin from './custom-plugin'

        export default {
          plugins: [typography, customPlugin],
        } satisfies Config
      `,
      'custom-plugin.js': ts`
        export default function ({ addVariant }) {
          addVariant('inverted', '@media (inverted-colors: inverted)')
          addVariant('hocus', ['&:focus', '&:hover'])
        }
      `,

Review comment (suggestion):
Maybe just "plugins" instead of "static plugins"? I don't think we support any type of plugins right now.

Code after:
    expect(await fs.dumpFiles('tailwind.config.ts')).toMatchInlineSnapshot(`
      "
      --- tailwind.config.ts ---
      import { type Config } from 'tailwindcss'

      export default {
        theme: {
          typography: {
            DEFAULT: {
              css: {
                '--tw-prose-body': 'red',
                color: 'var(--tw-prose-body)',
              },
            },
          },
        },
      } satisfies Config
      "
    `)
  },
)

test(
  'does not upgrade JS config files with plugins',
  {
    fs: {
      'package.json': json`
        {
          "dependencies": {
            "@tailwindcss/typography": "^0.5.15",
            "@tailwindcss/upgrade": "workspace:^"
          }
        }
      `,
      'tailwind.config.ts': ts`
        import { type Config } from 'tailwindcss'
        import typography from '@tailwindcss/typography'
        import customPlugin from './custom-plugin'

        export default {
          plugins: [typography, customPlugin],
        } satisfies Config
      `,
      'custom-plugin.js': ts`
        export default function ({ addVariant }) {
          addVariant('inverted', '@media (inverted-colors: inverted)')
          addVariant('hocus', ['&:focus', '&:hover'])
        }
      `,
      'src/input.css': css`
        @tailwind base;
Repository: tailwindlabs/tailwindcss
File: packages/tailwindcss/src/compat/config/resolve-config.test.ts (TypeScript)

Code before:
      },
      base: '/root',
    },
  ])

  expect(config).toMatchObject({
    theme: {
      colors: {
        red: 'red',
        green: 'green',
      },
      accentColor: {
        red: 'red',
        green: 'green',
      },
      placeholderColor: {
        primary: 'green',
        secondary: 'green',
      },
      caretColor: {
        primary: {
          '100': '#dcfce7',
          '200': '#bbf7d0',
          '300': '#86efac',
          '400': '#4ade80',
          '50': '#f0fdf4',
          '500': '#22c55e',
          '600': '#16a34a',
          '700': '#15803d',
          '800': '#166534',
          '900': '#14532d',
          '950': '#052e16',
        },
      },
    },
  })
})


Review comment (suggestion):
```suggestion
          '50': '#f0fdf4',
          '100': '#dcfce7',
          '200': '#bbf7d0',
          '300': '#86efac',
          '400': '#4ade80',
```

Code after:
      },
      base: '/root',
    },
  ])

  expect(config).toMatchObject({
    theme: {
      colors: {
        red: 'red',
        green: 'green',
      },
      accentColor: {
        red: 'red',
        green: 'green',
      },
      placeholderColor: {
        primary: 'green',
        secondary: 'green',
      },
      caretColor: {
        primary: {
          '50': '#f0fdf4',
          '100': '#dcfce7',
          '200': '#bbf7d0',
          '300': '#86efac',
          '400': '#4ade80',
          '500': '#22c55e',
          '600': '#16a34a',
          '700': '#15803d',
          '800': '#166534',
          '900': '#14532d',
          '950': '#052e16',
        },
      },
    },
  })
})
Repository: tailwindlabs/tailwindcss
File: packages/tailwindcss/src/compat/config/resolve-config.test.ts (TypeScript)

Code before:
      colors: {
        red: 'red',
        green: 'green',
        blue: 'blue',
      },
      accentColor: {
        red: 'red',
        green: 'green',
        blue: 'blue',
      },
      backgroundColor: {
        red: 'red',
        green: 'green',
        blue: 'blue',
      },
      caretColor: {
        red: 'red',
        green: 'green',
        blue: 'blue',
      },
    },
  })
})

test('theme keys can read from the CSS theme', () => {
  let originalWarn = console.warn
  try {
    console.warn = vi.fn()
    let theme = new Theme()
    theme.add('--color-green', 'green')

    let design = buildDesignSystem(theme)

    let config = resolveConfig(design, [
      {
        config: {
          theme: {
            colors: ({ theme }) => ({
              // Reads from the --color-* namespace
              ...theme('color'),
              red: 'red',
            }),
            accentColor: ({ theme }) => ({
              // Reads from the --color-* namespace through `colors`
              ...theme('colors'),
            }),
            placeholderColor: ({ theme }) => ({
              // Reads from the --color-* namespace through `colors`
              primary: theme('colors.green'),

              // Reads from the --color-* namespace directly

Review comment (suggestion):
I'd probably just mock `console` for the entire test file — it'll help cleanup the test and makes adding / testing additional warnings in the future simpler:

```ts
  const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined);

  afterAll(() => warn.mockReset());
```

```ts
    expect(warn).toHaveBeenCalledWith(
       'Using the plugin object parameter as the theme function is deprecated. Please use the `theme` property instead.',
     )
```

Code after:
      colors: {
        red: 'red',
        green: 'green',
        blue: 'blue',
      },
      accentColor: {
        red: 'red',
        green: 'green',
        blue: 'blue',
      },
      backgroundColor: {
        red: 'red',
        green: 'green',
        blue: 'blue',
      },
      caretColor: {
        red: 'red',
        green: 'green',
        blue: 'blue',
      },
    },
  })
})

test('theme keys can read from the CSS theme', ({ onTestFinished }) => {
  let warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
  onTestFinished(() => {
    warn.mockReset()
  })

  let theme = new Theme()
  theme.add('--color-green', 'green')

  let design = buildDesignSystem(theme)

  let config = resolveConfig(design, [
    {
      config: {
        theme: {
          colors: ({ theme }) => ({
            // Reads from the --color-* namespace
            ...theme('color'),
            red: 'red',
          }),
          accentColor: ({ theme }) => ({
            // Reads from the --color-* namespace through `colors`
            ...theme('colors'),
          }),
          placeholderColor: ({ theme }) => ({
            // Reads from the --color-* namespace through `colors`
            primary: theme('colors.green'),
Repository: tailwindlabs/tailwindcss
File: packages/tailwindcss/src/theme.ts (TypeScript)

Code before:
    return (this.getOptions(key) & ThemeOptions.DEFAULT) === ThemeOptions.DEFAULT
  }

  getOptions(key: string) {
    return this.values.get(key)?.options ?? ThemeOptions.NONE
  }

  entries() {
    if (!this.prefix) return this.values.entries()

    return Array.from(this.values, (entry) => {
      entry[0] = this.#prefixKey(entry[0])
      return entry
    })
  }

  #prefixKey(key: string) {
    if (!this.prefix) return key
    return `--${this.prefix}-${key.slice(2)}`
  }

  clearNamespace(namespace: string, clearOptions: ThemeOptions) {
    for (let key of this.values.keys()) {
      if (key.startsWith(namespace)) {
        if (clearOptions !== ThemeOptions.NONE) {
          let options = this.getOptions(key)
          if ((options & clearOptions) !== clearOptions) {
            continue
          }
        }
        this.values.delete(key)
      }
    }
  }

  #resolveKey(candidateValue: string | null, themeKeys: ThemeKey[]): string | null {
    for (let key of themeKeys) {
      let themeKey =
        candidateValue !== null ? escape(`${key}-${candidateValue.replaceAll('.', '_')}`) : key

      if (this.values.has(themeKey)) {
        return themeKey
      }
    }

    return null
  }

  #var(themeKey: string) {
    if (!this.values.has(themeKey)) {
      return null

Review comment (suggestion):
Made this public so we can selectively only clean `@theme default` values... See the test case as to why I think this is necessary.

Code after:
    return null
  }

  hasDefault(key: string): boolean {
    return (this.getOptions(key) & ThemeOptions.DEFAULT) === ThemeOptions.DEFAULT
  }

  getOptions(key: string) {
    return this.values.get(key)?.options ?? ThemeOptions.NONE
  }

  entries() {
    if (!this.prefix) return this.values.entries()

    return Array.from(this.values, (entry) => {
      entry[0] = this.#prefixKey(entry[0])
      return entry
    })
  }

  #prefixKey(key: string) {
    if (!this.prefix) return key
    return `--${this.prefix}-${key.slice(2)}`
  }

  clearNamespace(namespace: string, clearOptions: ThemeOptions) {
    for (let key of this.values.keys()) {
      if (key.startsWith(namespace)) {
        if (clearOptions !== ThemeOptions.NONE) {
          let options = this.getOptions(key)
          if ((options & clearOptions) !== clearOptions) {
            continue
          }
        }
        this.values.delete(key)
      }
    }
  }

  #resolveKey(candidateValue: string | null, themeKeys: ThemeKey[]): string | null {
    for (let key of themeKeys) {
      let themeKey =
        candidateValue !== null ? escape(`${key}-${candidateValue.replaceAll('.', '_')}`) : key

      if (this.values.has(themeKey)) {
        return themeKey
      }
    }

    return null
  }
Repository: tailwindlabs/tailwindcss
File: packages/@tailwindcss-upgrade/src/migrate-js-config.ts (TypeScript)

Code before:
  let theme = unresolvedConfig.theme
  if (theme && typeof theme === 'object') {
    if (theme.extend && !onlyAllowedThemeValues(theme.extend)) return false
    let { extend: _extend, ...themeCopy } = theme
    if (!onlyAllowedThemeValues(themeCopy)) return false
  }

  return true
}

const DEFAULT_THEME_KEYS = [
  ...Object.keys(defaultTheme),
  // Used by @tailwindcss/container-queries
  'containers',
]
function onlyAllowedThemeValues(theme: ThemeConfig): boolean {
  for (let key of Object.keys(theme)) {
    if (!DEFAULT_THEME_KEYS.includes(key)) {
      return false
    }
  }

  if ('screens' in theme && typeof theme.screens === 'object' && theme.screens !== null) {
    for (let screen of Object.values(theme.screens)) {
      if (typeof screen === 'object' && screen !== null && ('max' in screen || 'raw' in screen))
        return false
    }
  }
  return true
}

function keyframesToCss(keyframes: Record<string, unknown>): string {
  let ast: AstNode[] = []
  applyKeyframesToAst(ast, { theme: { keyframes } })
  return toCss(ast).trim() + '\n'
}


Review comment (suggestion):
```suggestion
      if (typeof screen === 'object' && screen !== null && ('max' in screen || 'raw' in screen)) {
        return false
      }
```

Code after:
  let theme = unresolvedConfig.theme
  if (theme && typeof theme === 'object') {
    if (theme.extend && !onlyAllowedThemeValues(theme.extend)) return false
    let { extend: _extend, ...themeCopy } = theme
    if (!onlyAllowedThemeValues(themeCopy)) return false
  }

  return true
}

const DEFAULT_THEME_KEYS = [
  ...Object.keys(defaultTheme),
  // Used by @tailwindcss/container-queries
  'containers',
]
function onlyAllowedThemeValues(theme: ThemeConfig): boolean {
  for (let key of Object.keys(theme)) {
    if (!DEFAULT_THEME_KEYS.includes(key)) {
      return false
    }
  }

  if ('screens' in theme && typeof theme.screens === 'object' && theme.screens !== null) {
    for (let screen of Object.values(theme.screens)) {
      if (typeof screen === 'object' && screen !== null && ('max' in screen || 'raw' in screen)) {
        return false
      }
    }
  }
  return true
}

function keyframesToCss(keyframes: Record<string, unknown>): string {
  let ast: AstNode[] = []
  applyKeyframesToAst(ast, { theme: { keyframes } })
  return toCss(ast).trim() + '\n'
}
Repository: tailwindlabs/tailwindcss
File: packages/tailwindcss/src/variants.test.ts (TypeScript)

Code before:
    .peer-data-\\[foo\\=bar\\ baz\\]\\/parent-name\\:flex:is(:where(.peer\\/parent-name)[data-foo="bar baz"] ~ *) {
      display: flex;
    }

    .peer-data-\\[foo\\$\\=\\'bar\\'_i\\]\\/parent-name\\:flex:is(:where(.peer\\/parent-name)[data-foo$="bar" i] ~ *) {
      display: flex;
    }

    .peer-data-\\[foo\\$\\=bar_baz_i\\]\\/parent-name\\:flex:is(:where(.peer\\/parent-name)[data-foo$="bar baz" i] ~ *) {
      display: flex;
    }

    .data-disabled\\:flex[data-disabled] {
      display: flex;
    }

    .data-\\[potato\\=salad\\]\\:flex[data-potato="salad"] {
      display: flex;
    }

    .data-\\[potato_\\=_\\"salad\\"\\]\\:flex[data-potato="salad"] {
      display: flex;
    }

    .data-\\[potato_\\^\\=_\\"salad\\"\\]\\:flex[data-potato^="salad"] {
      display: flex;
    }

    .data-\\[foo\\=1\\]\\:flex[data-foo="1"] {
      display: flex;
    }

    .data-\\[foo\\=bar_baz\\]\\:flex[data-foo="bar baz"] {
      display: flex;
    }

    .data-\\[foo\\$\\=\\'bar\\'_i\\]\\:flex[data-foo$="bar" i] {
      display: flex;
    }

    .data-\\[foo\\$\\=bar_baz_i\\]\\:flex[data-foo$="bar baz" i] {
      display: flex;
    }"
  `)
  expect(
    await run([
      'data-[foo_^_=_"bar"]:flex', // Can't have spaces between `^` and `=`
      'data-disabled/foo:flex',
      'data-[potato=salad]/foo:flex',
    ]),
  ).toEqual('')

Review comment (suggestion):
Is it lightningcss that did remove the spaces again here or did we? I feel like if the argument is "we want to support arbitrary CSS because CSS would also allow that", we should also make sure we output the spaces again in the resulting CSS. Conceptually we just pass-through even though we do auto-quote where we have to.

Code after:

    .peer-data-\\[foo\\=bar\\ baz\\]\\/parent-name\\:flex:is(:where(.peer\\/parent-name)[data-foo="bar baz"] ~ *) {
      display: flex;
    }

    .peer-data-\\[foo\\$\\=\\'bar\\'_i\\]\\/parent-name\\:flex:is(:where(.peer\\/parent-name)[data-foo$="bar" i] ~ *) {
      display: flex;
    }

    .peer-data-\\[foo\\$\\=bar_baz_i\\]\\/parent-name\\:flex:is(:where(.peer\\/parent-name)[data-foo$="bar baz" i] ~ *) {
      display: flex;
    }

    .data-disabled\\:flex[data-disabled] {
      display: flex;
    }

    .data-\\[potato\\=salad\\]\\:flex[data-potato="salad"] {
      display: flex;
    }

    .data-\\[potato_\\=_\\"salad\\"\\]\\:flex[data-potato="salad"] {
      display: flex;
    }

    .data-\\[potato_\\^\\=_\\"salad\\"\\]\\:flex[data-potato^="salad"] {
      display: flex;
    }

    .data-\\[potato\\=\\"\\^_\\=\\"\\]\\:flex[data-potato="^ ="] {
      display: flex;
    }

    .data-\\[foo\\=1\\]\\:flex[data-foo="1"] {
      display: flex;
    }

    .data-\\[foo\\=bar_baz\\]\\:flex[data-foo="bar baz"] {
      display: flex;
    }

    .data-\\[foo\\$\\=\\'bar\\'_i\\]\\:flex[data-foo$="bar" i] {
      display: flex;
    }

    .data-\\[foo\\$\\=bar_baz_i\\]\\:flex[data-foo$="bar baz" i] {
      display: flex;
    }"
  `)
  expect(
    await run([