ronantakizawa/codereview-bench huggingface dataset:
Repository: vitejs/vite
File: vitest.config.e2e.ts (TypeScript)

Code before:
      '~utils': resolve(__dirname, './playground/test-utils'),
    },
  },
  test: {
    include: ['./playground/**/*.spec.[tj]s'],
    setupFiles: ['./playground/vitestSetup.ts'],
    globalSetup: ['./playground/vitestGlobalSetup.ts'],
    testTimeout: timeout,
    hookTimeout: timeout,
    reporters: 'dot',
    deps: {
      // Prevent Vitest from running the workspace packages in Vite's SSR runtime
      moduleDirectories: ['node_modules', 'packages'],
    },
    expect: {
      poll: {
        timeout: 50 * (process.env.CI ? 200 : 50),
      },
    },
  },
  ssr: {
    resolve: {
      // FIXME: this is a bug in Vitest, resolving externalized modules should use externalConditions
      conditions: ['node', 'module-sync'],
    },
  },
  esbuild: {
    target: 'node20',
  },
  publicDir: false,
})


Review comment (suggestion):
@hi-ogawa It seems Vitest uses `resolve.conditions` for both externalized files and no-externalized files. Is it possible to configure Vitest to only use `module-sync` for externalized files? (since `module-sync` should not be used for no-externalized files)

Code after:
      '~utils': resolve(__dirname, './playground/test-utils'),
    },
  },
  test: {
    include: ['./playground/**/*.spec.[tj]s'],
    setupFiles: ['./playground/vitestSetup.ts'],
    globalSetup: ['./playground/vitestGlobalSetup.ts'],
    testTimeout: timeout,
    hookTimeout: timeout,
    reporters: 'dot',
    deps: {
      // Prevent Vitest from running the workspace packages in Vite's SSR runtime
      moduleDirectories: ['node_modules', 'packages'],
    },
    expect: {
      poll: {
        timeout: 50 * (process.env.CI ? 200 : 50),
      },
    },
  },
  esbuild: {
    target: 'node20',
  },
  publicDir: false,
})
Repository: vitejs/vite
File: playground/ssr-resolve/main.js (JavaScript)

Code before:
// no `exports` key, should resolve to entries/dir/index.js
import dirEntry from '@vitejs/test-entries/dir'
// no `exports` key, should resolve to entries/file.js
import fileEntry from '@vitejs/test-entries/file'
// has `exports` key, should resolve to pkg-exports/entry
import pkgExportsEntry from '@vitejs/test-resolve-pkg-exports/entry'
import deepFoo from '@vitejs/test-deep-import/foo'
import deepBar from '@vitejs/test-deep-import/bar'
import { used } from './util'

export default `
  entries/dir: ${dirEntry}
  entries/file: ${fileEntry}
  pkg-exports/entry: ${pkgExportsEntry}
  deep-import/foo: ${deepFoo}
  deep-import/bar: ${deepBar}
  util: ${used(['[success]'])}
`


Review comment (suggestion):
I commented out this test case as it wasn't working.
The output contains `@vitejs/test-deep-import/bar` as-is, but to make that work in Node, it needs to be rewritten to `@vitejs/test-deep-import/utils/bar.js` as Node does not read `package.json` for directory specifiers in ESM.


Code after:
// no `exports` key, should resolve to entries/dir/index.js
import dirEntry from '@vitejs/test-entries/dir'
// no `exports` key, should resolve to entries/file.js
import fileEntry from '@vitejs/test-entries/file'
// has `exports` key, should resolve to pkg-exports/entry
import pkgExportsEntry from '@vitejs/test-resolve-pkg-exports/entry'
import deepFoo from '@vitejs/test-deep-import/foo'
// import deepBar from '@vitejs/test-deep-import/bar'
import moduleSync from '@vitejs/test-module-sync'
import { used } from './util'

export default `
  entries/dir: ${dirEntry}
  entries/file: ${fileEntry}
  pkg-exports/entry: ${pkgExportsEntry}
  deep-import/foo: ${deepFoo}
  ${/* `deep-import/bar: ${deepBar}` */ ''}
  module-sync: ${moduleSync}
  util: ${used(['[success]'])}
`
Repository: vitejs/vite
File: packages/vite/src/node/ssr/runtime/__tests__/server-runtime.spec.ts (TypeScript)

Code before:
    server.environments.ssr.moduleGraph.invalidateAll()
    server.environments.ssr.hot.send({ type: 'full-reload' })
    await vi.waitFor(() => {
      const mod = runner.evaluatedModules.getModuleById('\0virtual:test')
      expect(mod?.exports.default).toBe('reloaded')
    })
  })

  it("the external module's ID and file are resolved correctly", async ({
    server,
    runner,
  }) => {
    await runner.import(
      posix.join(server.config.root, 'fixtures/import-external.ts'),
    )
    const moduleNode = runner.evaluatedModules.getModuleByUrl('tinyglobby')!
    console.log(moduleNode)
    const meta = moduleNode.meta as ExternalFetchResult
    if (process.platform === 'win32') {
      expect(meta.externalize).toMatch(/file:\/\/\/\w:\//) // file:///C:/
      expect(moduleNode.id).toMatch(/\w:\//) // C:/
      expect(moduleNode.file).toMatch(/\w:\//) // C:/
    } else {
      expect(meta.externalize).toMatch(/file:\/\/\//) // file:///
      expect(moduleNode.id).toMatch(/\//) // /
      expect(moduleNode.file).toMatch(/\//) // /
    }
  })
})


Review comment (suggestion):
```suggestion
      expect(meta.externalize).toMatch(/^file:\/\/\/\w:\//) // file:///C:/
      expect(moduleNode.id).toMatch(/^\w:\//) // C:/
      expect(moduleNode.file).toMatch(/^\w:\//) // C:/
    } else {
      expect(meta.externalize).toMatch(/^file:\/\/\//) // file:///
      expect(moduleNode.id).toMatch(/^\//) // /
      expect(moduleNode.file).toMatch(/^\//) // /
```
Let's add a `^` to make it a bit more strict.

Code after:
    server.environments.ssr.moduleGraph.invalidateAll()
    server.environments.ssr.hot.send({ type: 'full-reload' })
    await vi.waitFor(() => {
      const mod = runner.evaluatedModules.getModuleById('\0virtual:test')
      expect(mod?.exports.default).toBe('reloaded')
    })
  })

  it("the external module's ID and file are resolved correctly", async ({
    server,
    runner,
  }) => {
    await runner.import(
      posix.join(server.config.root, 'fixtures/import-external.ts'),
    )
    const moduleNode = runner.evaluatedModules.getModuleByUrl('tinyglobby')!
    const meta = moduleNode.meta as ExternalFetchResult
    if (process.platform === 'win32') {
      expect(meta.externalize).toMatch(/^file:\/\/\/\w:\//) // file:///C:/
      expect(moduleNode.id).toMatch(/^\w:\//) // C:/
      expect(moduleNode.file).toMatch(/^\w:\//) // C:/
    } else {
      expect(meta.externalize).toMatch(/^file:\/\/\//) // file:///
      expect(moduleNode.id).toMatch(/^\//) // /
      expect(moduleNode.file).toMatch(/^\//) // /
    }
  })
})
Repository: vitejs/vite
File: packages/create-vite/__tests__/cli.spec.ts (TypeScript)

Code before:
  createNonEmptyDir()
  const { stdout } = run(['.', '--overwrite', 'ignore'], { cwd: genPath })
  expect(stdout).not.toContain(`Current directory is not empty.`)
})

test('return help usage how to use create-vite', () => {
  const { stdout } = run(['--help'], { cwd: __dirname })
  const message = 'Usage: create-vite [OPTION]... [DIRECTORY]'
  expect(stdout).toContain(message)
})

test('return help usage how to use create-vite with -h alias', () => {
  const { stdout } = run(['--h'], { cwd: __dirname })
  const message = 'Usage: create-vite [OPTION]... [DIRECTORY]'
  expect(stdout).toContain(message)
})

test('shows immediate and agent options in help', () => {
  const { stdout } = run(['--help'], { cwd: __dirname })
  expect(stdout).toContain(
    '-i, --immediate            install dependencies and start dev',
  )
  expect(stdout).toContain(
    '-a, --agent AGENT          install dependencies via npm, yarn, or pnpm',
  )
})

test('accepts immediate flag and skips install prompt', () => {
  const { stdout } = run(
    [projectName, '--template', 'vue', '--immediate', 'false'],
    {
      cwd: __dirname,
    },
  )
  expect(stdout).not.toContain('Install and start now?')
  expect(stdout).toContain(`Scaffolding project in ${genPath}`)
})

test('accepts agent flag', () => {
  const { stdout } = run(
    [projectName, '--template', 'vue', '-i', 'false', '-a', 'npm'],
    {
      cwd: __dirname,
    },
  )
  expect(stdout).toContain(`Scaffolding project in ${genPath}`)
})


Review comment (suggestion):
```suggestion
test('shows help', () => {
  const { stdout } = run(['--help'], { cwd: __dirname })
  expect(stdout).toMatchInlineSnapshot()
})
```

Code after:
    ],
    {
      cwd: __dirname,
    },
  )
  const generatedFiles = fs.readdirSync(genPathWithSubfolder).sort()

  // Assertions
  expect(stdout).toContain(`Scaffolding project in ${genPathWithSubfolder}`)
  expect(templateFilesReact).toEqual(generatedFiles)
})

test('successfully scaffolds a project with subfolder based on react starter template with rolldown flag', () => {
  const { stdout } = run(
    [`subfolder/${projectName}`, '--template', 'react', '--rolldown'],
    {
      cwd: __dirname,
    },
  )
  const generatedFiles = fs.readdirSync(genPathWithSubfolder).sort()

  // Assertions
  expect(stdout).toContain(`Scaffolding project in ${genPathWithSubfolder}`)
  expect(templateFilesReact).toEqual(generatedFiles)
  const generatedPackageJson = fs.readFileSync(
    path.join(genPathWithSubfolder, 'package.json'),
    'utf-8',
  )
  expect(generatedPackageJson).toContain('rolldown-vite')
})

test('works with the -t alias', () => {
  const { stdout } = run(
    [
      projectName,
      '--interactive',
      '--no-immediate',
      '-t',
      'vue',
      '--no-rolldown',
    ],
    {
      cwd: __dirname,
    },
  )
  const generatedFiles = fs.readdirSync(genPath).sort()

  // Assertions
  expect(stdout).toContain(`Scaffolding project in ${genPath}`)
  expect(templateFiles).toEqual(generatedFiles)
})
Repository: vitejs/vite
File: packages/create-vite/src/index.ts (TypeScript)

Code before:
    const targetPath = path.join(root, renameFiles[file] ?? file)
    if (content) {
      fs.writeFileSync(targetPath, content)
    } else {
      copy(path.join(templateDir, file), targetPath)
    }
  }

  const files = fs.readdirSync(templateDir)
  for (const file of files.filter((f) => f !== 'package.json')) {
    write(file)
  }

  const pkg = JSON.parse(
    fs.readFileSync(path.join(templateDir, `package.json`), 'utf-8'),
  )

  pkg.name = packageName

  write('package.json', JSON.stringify(pkg, null, 2) + '\n')

  if (isReactSwc) {
    setupReactSwc(root, template.endsWith('-ts'))
  }

  if (immediate && agent) {
    install(root, agent)
    start(root, agent)
  } else {
    let doneMessage = ''
    const cdProjectName = path.relative(cwd, root)
    doneMessage += `Done. Now run:\n`
    if (root !== cwd) {
      doneMessage += `\n  cd ${
        cdProjectName.includes(' ') ? `"${cdProjectName}"` : cdProjectName
      }`
    }
    switch (pkgManager) {
      case 'yarn':
        doneMessage += '\n  yarn'
        doneMessage += '\n  yarn dev'
        break
      default:
        doneMessage += `\n  ${pkgManager} install`
        doneMessage += `\n  ${pkgManager} run dev`
        break
    }
    prompts.outro(doneMessage)
  }
}


Review comment (suggestion):
```suggestion
  if (immediate) {
    if (!agent) throw new Error()
```
`agent` should always exist here


Code after:
  if (customCommand) {
    const fullCustomCommand = getFullCustomCommand(customCommand, pkgInfo)

    const [command, ...args] = fullCustomCommand.split(' ')
    // we replace TARGET_DIR here because targetDir may include a space
    const replacedArgs = args.map((arg) =>
      arg.replace('TARGET_DIR', () => targetDir),
    )
    const { status } = spawn.sync(command, replacedArgs, {
      stdio: 'inherit',
    })
    process.exit(status ?? 0)
  }

  let useRolldownVite = argRolldown
  if (useRolldownVite === undefined) {
    if (interactive) {
      const rolldownViteValue = await prompts.select({
        message: 'Use rolldown-vite (Experimental)?:',
        options: [
          {
            label: 'Yes',
            value: true,
            hint: 'The future default Vite, which is powered by Rolldown',
          },
          { label: 'No', value: false },
        ],
        initialValue: false,
      })
      if (prompts.isCancel(rolldownViteValue)) return cancel()
      useRolldownVite = rolldownViteValue
    } else {
      useRolldownVite = false
    }
  }

  prompts.log.step(`Scaffolding project in ${root}...`)

  const templateDir = path.resolve(
    fileURLToPath(import.meta.url),
    '../..',
    `template-${template}`,
  )

  const write = (file: string, content?: string) => {
    const targetPath = path.join(root, renameFiles[file] ?? file)
    if (content) {
      fs.writeFileSync(targetPath, content)
    } else if (file === 'index.html') {
      const templatePath = path.join(templateDir, file)
      const templateContent = fs.readFileSync(templatePath, 'utf-8')
Repository: vitejs/vite
File: packages/create-vite/src/index.ts (TypeScript)

Code before:
const renameFiles: Record<string, string | undefined> = {
  _gitignore: '.gitignore',
}

const defaultTargetDir = 'vite-project'

function run(...params: Parameters<typeof spawn.sync>) {
  const { status, error } = spawn.sync(...params)

  if (status != null && status > 0) {
    process.exit(status)
  }

  if (error) {
    console.error(`\n${params.slice(0, -1).join(' ')} error!`)
    console.error(error)
    process.exit(1)
  }
}

function install(root: string, agent: string) {
  if (process.env._VITE_TEST_CLI) {
    console.log(`\nInstalling dependencies via ${agent}... (skipped in test)`)
    return
  }
  console.log(`\nInstalling dependencies via ${agent}...`)
  run(agent, agent === 'yarn' ? [] : ['install'], {
    stdio: 'inherit',
    cwd: root,
  })
}

function start(root: string, agent: string) {
  if (process.env._VITE_TEST_CLI) {
    console.log('\nStart dev server... (skipped in test)')
    return
  }
  console.log('\nStart dev server...')
  run(agent, agent === 'npm' ? ['run', 'dev'] : ['dev'], {
    stdio: 'inherit',
    cwd: root,
  })
}

async function init() {
  const argTargetDir = argv._[0]
    ? formatTargetDir(String(argv._[0]))
    : undefined
  const argTemplate = argv.template
  const argOverwrite = argv.overwrite
  const argImmediate = argv.immediate

Review comment (suggestion):
```suggestion
  if (process.env._VITE_TEST_CLI) {
    console.log(`\nInstalling dependencies with ${agent}... (skipped in test)`)
    return
  }
  console.log(`\nInstalling dependencies with ${agent}...`)
```

I think using `with` is a bit more natural here.

Code after:
)

const renameFiles: Record<string, string | undefined> = {
  _gitignore: '.gitignore',
}

const defaultTargetDir = 'vite-project'

function run(...params: Parameters<typeof spawn.sync>) {
  const { status, error } = spawn.sync(...params)
  if (status != null && status > 0) {
    process.exit(status)
  }

  if (error) {
    console.error(`\n${params.slice(0, -1).join(' ')} error!`)
    console.error(error)
    process.exit(1)
  }
}

function install(root: string, agent: string) {
  if (process.env._VITE_TEST_CLI) {
    prompts.log.step(
      `Installing dependencies with ${agent}... (skipped in test)`,
    )
    return
  }
  prompts.log.step(`Installing dependencies with ${agent}...`)
  run(agent, agent === 'yarn' ? [] : ['install'], {
    stdio: 'inherit',
    cwd: root,
  })
}

function start(root: string, agent: string) {
  if (process.env._VITE_TEST_CLI) {
    prompts.log.step('Starting dev server... (skipped in test)')
    return
  }
  prompts.log.step('Starting dev server...')
  run(agent, agent === 'npm' ? ['run', 'dev'] : ['dev'], {
    stdio: 'inherit',
    cwd: root,
  })
}

async function init() {
  const argTargetDir = argv._[0]
    ? formatTargetDir(String(argv._[0]))
    : undefined
Repository: vitejs/vite
File: packages/create-vite/src/index.ts (TypeScript)

Code before:

  if (error) {
    console.error(`\n${params.slice(0, -1).join(' ')} error!`)
    console.error(error)
    process.exit(1)
  }
}

function install(root: string, agent: string) {
  if (process.env._VITE_TEST_CLI) {
    console.log(`\nInstalling dependencies via ${agent}... (skipped in test)`)
    return
  }
  console.log(`\nInstalling dependencies via ${agent}...`)
  run(agent, agent === 'yarn' ? [] : ['install'], {
    stdio: 'inherit',
    cwd: root,
  })
}

function start(root: string, agent: string) {
  if (process.env._VITE_TEST_CLI) {
    console.log('\nStart dev server... (skipped in test)')
    return
  }
  console.log('\nStart dev server...')
  run(agent, agent === 'npm' ? ['run', 'dev'] : ['dev'], {
    stdio: 'inherit',
    cwd: root,
  })
}

async function init() {
  const argTargetDir = argv._[0]
    ? formatTargetDir(String(argv._[0]))
    : undefined
  const argTemplate = argv.template
  const argOverwrite = argv.overwrite
  const argImmediate = argv.immediate
  const argAgent = argv.agent

  const help = argv.help
  if (help) {
    console.log(helpMessage)
    return
  }

  const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent)
  const cancel = () => prompts.cancel('Operation cancelled')

  // 1. Get project name and target dir

Review comment (suggestion):
```suggestion
  if (process.env._VITE_TEST_CLI) {
    console.log('\nStarting dev server... (skipped in test)')
    return
  }
  console.log('\nStarting dev server...')
```

Code after:
  }

  if (error) {
    console.error(`\n${params.slice(0, -1).join(' ')} error!`)
    console.error(error)
    process.exit(1)
  }
}

function install(root: string, agent: string) {
  if (process.env._VITE_TEST_CLI) {
    prompts.log.step(
      `Installing dependencies with ${agent}... (skipped in test)`,
    )
    return
  }
  prompts.log.step(`Installing dependencies with ${agent}...`)
  run(agent, agent === 'yarn' ? [] : ['install'], {
    stdio: 'inherit',
    cwd: root,
  })
}

function start(root: string, agent: string) {
  if (process.env._VITE_TEST_CLI) {
    prompts.log.step('Starting dev server... (skipped in test)')
    return
  }
  prompts.log.step('Starting dev server...')
  run(agent, agent === 'npm' ? ['run', 'dev'] : ['dev'], {
    stdio: 'inherit',
    cwd: root,
  })
}

async function init() {
  const argTargetDir = argv._[0]
    ? formatTargetDir(String(argv._[0]))
    : undefined
  const argTemplate = argv.template
  const argOverwrite = argv.overwrite
  const argImmediate = argv.immediate
  const argRolldown = argv.rolldown
  const argInteractive = argv.interactive

  const help = argv.help
  if (help) {
    console.log(helpMessage)
    return
  }
Repository: vitejs/vite
File: packages/create-vite/src/index.ts (TypeScript)

Code before:
    } else {
      copy(path.join(templateDir, file), targetPath)
    }
  }

  const files = fs.readdirSync(templateDir)
  for (const file of files.filter((f) => f !== 'package.json')) {
    write(file)
  }

  const pkg = JSON.parse(
    fs.readFileSync(path.join(templateDir, `package.json`), 'utf-8'),
  )

  pkg.name = packageName

  write('package.json', JSON.stringify(pkg, null, 2) + '\n')

  if (isReactSwc) {
    setupReactSwc(root, template.endsWith('-ts'))
  }

  if (immediate) {
    if (!agent) throw new Error()
    install(root, agent)
    start(root, agent)
  } else {
    let doneMessage = ''
    const cdProjectName = path.relative(cwd, root)
    doneMessage += `Done. Now run:\n`
    if (root !== cwd) {
      doneMessage += `\n  cd ${
        cdProjectName.includes(' ') ? `"${cdProjectName}"` : cdProjectName
      }`
    }
    switch (pkgManager) {
      case 'yarn':
        doneMessage += '\n  yarn'
        doneMessage += '\n  yarn dev'
        break
      default:
        doneMessage += `\n  ${pkgManager} install`
        doneMessage += `\n  ${pkgManager} run dev`
        break
    }
    prompts.outro(doneMessage)
  }
}

function formatTargetDir(targetDir: string) {
  return targetDir.trim().replace(/\/+$/g, '')

Review comment (suggestion):
Something I'm just wondering, maybe we should `cd` into the project so that when the dev server is stopped the directory is on the project, which also makes it feel like we're just auto-executing the commands we printed in non-immediate mode. 

Code after:
        options: [
          {
            label: 'Yes',
            value: true,
            hint: 'The future default Vite, which is powered by Rolldown',
          },
          { label: 'No', value: false },
        ],
        initialValue: false,
      })
      if (prompts.isCancel(rolldownViteValue)) return cancel()
      useRolldownVite = rolldownViteValue
    } else {
      useRolldownVite = false
    }
  }

  prompts.log.step(`Scaffolding project in ${root}...`)

  const templateDir = path.resolve(
    fileURLToPath(import.meta.url),
    '../..',
    `template-${template}`,
  )

  const write = (file: string, content?: string) => {
    const targetPath = path.join(root, renameFiles[file] ?? file)
    if (content) {
      fs.writeFileSync(targetPath, content)
    } else if (file === 'index.html') {
      const templatePath = path.join(templateDir, file)
      const templateContent = fs.readFileSync(templatePath, 'utf-8')
      const updatedContent = templateContent.replace(
        /<title>.*?<\/title>/,
        `<title>${packageName}</title>`,
      )
      fs.writeFileSync(targetPath, updatedContent)
    } else {
      copy(path.join(templateDir, file), targetPath)
    }
  }

  const files = fs.readdirSync(templateDir)
  for (const file of files.filter((f) => f !== 'package.json')) {
    write(file)
  }

  const pkg = JSON.parse(
    fs.readFileSync(path.join(templateDir, `package.json`), 'utf-8'),
  )
Repository: vitejs/vite
File: packages/vite/src/node/plugins/html.ts (TypeScript)

Code before:
): node is DefaultTreeAdapterMap['element'] {
  return node.nodeName[0] !== '#'
}

function traverseNodes(
  node: DefaultTreeAdapterMap['node'],
  visitor: (node: DefaultTreeAdapterMap['node']) => void,
) {
  if (node.nodeName === 'template') {
    node = (node as DefaultTreeAdapterMap['template']).content
  }
  visitor(node)
  if (
    nodeIsElement(node) ||
    node.nodeName === '#document' ||
    node.nodeName === '#document-fragment'
  ) {
    node.childNodes.forEach((childNode) => traverseNodes(childNode, visitor))
  }
}

export async function traverseHtml(
  html: string,
  filePath: string,
  visitor: (node: DefaultTreeAdapterMap['node']) => void,
  warn: ResolvedConfig['logger']['warn'],
): Promise<void> {
  // lazy load compiler
  const { parse } = await import('parse5')
  const ast = parse(html, {
    scriptingEnabled: false, // parse inside <noscript>
    sourceCodeLocationInfo: true,
    onParseError: (e: ParserError) => {
      handleParseError(e, html, filePath, warn)
    },
  })
  traverseNodes(ast, visitor)
}

export function getScriptInfo(node: DefaultTreeAdapterMap['element']): {
  src: Token.Attribute | undefined
  srcSourceCodeLocation: Token.Location | undefined
  isModule: boolean
  isAsync: boolean
  isIgnored: boolean
} {
  let src: Token.Attribute | undefined
  let srcSourceCodeLocation: Token.Location | undefined
  let isModule = false
  let isAsync = false
  let isIgnored = false

Review comment (suggestion):
```suggestion
  warn: ResolvedConfig['logger']['warn'],
  visitor: (node: DefaultTreeAdapterMap['node']) => void,
```
I think this is better. `visitor` is normally long and that makes it easier to overlook the parameters after it.


Code after:
  ResolvedConfig,
  Map<string, boolean>
>()

export function nodeIsElement(
  node: DefaultTreeAdapterMap['node'],
): node is DefaultTreeAdapterMap['element'] {
  return node.nodeName[0] !== '#'
}

function traverseNodes(
  node: DefaultTreeAdapterMap['node'],
  visitor: (node: DefaultTreeAdapterMap['node']) => void,
) {
  if (node.nodeName === 'template') {
    node = (node as DefaultTreeAdapterMap['template']).content
  }
  visitor(node)
  if (
    nodeIsElement(node) ||
    node.nodeName === '#document' ||
    node.nodeName === '#document-fragment'
  ) {
    node.childNodes.forEach((childNode) => traverseNodes(childNode, visitor))
  }
}

type ParseWarnings = Partial<Record<ErrorCodes, string>>

export async function traverseHtml(
  html: string,
  filePath: string,
  warn: Logger['warn'],
  visitor: (node: DefaultTreeAdapterMap['node']) => void,
): Promise<void> {
  // lazy load compiler
  const { parse } = await import('parse5')
  const warnings: ParseWarnings = {}
  const ast = parse(html, {
    scriptingEnabled: false, // parse inside <noscript>
    sourceCodeLocationInfo: true,
    onParseError: (e: ParserError) => {
      handleParseError(e, html, filePath, warnings)
    },
  })
  traverseNodes(ast, visitor)

  for (const message of Object.values(warnings)) {
    warn(colors.yellow(`\n${message}`))
  }
}