# JavaScript API

Vite's JavaScript APIs are fully typed, and it's recommended to use TypeScript or enable JS type checking in VS Code to leverage the intellisense and validation.

## `createServer`

**Type Signature:**

```ts
async function createServer(inlineConfig?: InlineConfig): Promise<ViteDevServer>
```

**Example Usage:**

```ts twoslash
import { createServer } from 'vite'

const server = await createServer({
  // any valid user config options, plus `mode` and `configFile`
  configFile: false,
  root: import.meta.dirname,
  server: {
    port: 1337,
  },
})
await server.listen()

server.printUrls()
server.bindCLIShortcuts({ print: true })
```

::: tip NOTE
When using `createServer` and `build` in the same Node.js process, both functions rely on `process.env.NODE_ENV` to work properly, which also depends on the `mode` config option. To prevent conflicting behavior, set `process.env.NODE_ENV` or the `mode` of the two APIs to `development`. Otherwise, you can spawn a child process to run the APIs separately.
:::

::: tip NOTE
When using [middleware mode](/config/server-options.html#server-middlewaremode) combined with [proxy config for WebSocket](/config/server-options.html#server-proxy), the parent http server should be provided in `middlewareMode` to bind the proxy correctly.

<details>
<summary>Example</summary>

```ts twoslash
import http from 'http'
import { createServer } from 'vite'

const parentServer = http.createServer() // or express, koa, etc.

const vite = await createServer({
  server: {
    // Enable middleware mode
    middlewareMode: {
      // Provide the parent http server for proxy WebSocket
      server: parentServer,
    },
    proxy: {
      '/ws': {
        target: 'ws://localhost:3000',
        // Proxying WebSocket
        ws: true,
      },
    },
  },
})

// @noErrors: 2339
parentServer.use(vite.middlewares)
```

</details>
:::

## `InlineConfig`

The `InlineConfig` interface extends `UserConfig` with additional properties:

- `configFile`: specify config file to use. If not set, Vite will try to automatically resolve one from project root. Set to `false` to disable auto resolving.

## `ResolvedConfig`

The `ResolvedConfig` interface has all the same properties of a `UserConfig`, except most properties are resolved and non-undefined. It also contains utilities like:

- `config.assetsInclude`: A function to check if an `id` is considered an asset.
- `config.logger`: Vite's internal logger object.

## `ViteDevServer`

```ts
interface ViteDevServer {
  /**
   * The resolved Vite config object.
   */
  config: ResolvedConfig
  /**
   * A connect app instance
   * - Can be used to attach custom middlewares to the dev server.
   * - Can also be used as the handler function of a custom http server
   *   or as a middleware in any connect-style Node.js frameworks.
   *
   * https://github.com/senchalabs/connect#use-middleware
   */
  middlewares: Connect.Server
  /**
   * Native Node http server instance.
   * Will be null in middleware mode.
   */
  httpServer: http.Server | null
  /**
   * Chokidar watcher instance. If `config.server.watch` is set to `null`,
   * it will not watch any files and calling `add` or `unwatch` will have no effect.
   * https://github.com/paulmillr/chokidar/tree/3.6.0#api
   */
  watcher: FSWatcher
  /**
   * WebSocket server with `send(payload)` method.
   */
  ws: WebSocketServer
  /**
   * Rollup plugin container that can run plugin hooks on a given file.
   */
  pluginContainer: PluginContainer
  /**
   * Module graph that tracks the import relationships, url to file mapping
   * and hmr state.
   */
  moduleGraph: ModuleGraph
  /**
   * The resolved urls Vite prints on the CLI (URL-encoded). Returns `null`
   * in middleware mode or if the server is not listening on any port.
   */
  resolvedUrls: ResolvedServerUrls | null
  /**
   * Programmatically resolve, load and transform a URL and get the result
   * without going through the http request pipeline.
   */
  transformRequest(
    url: string,
    options?: TransformOptions,
  ): Promise<TransformResult | null>
  /**
   * Apply Vite built-in HTML transforms and any plugin HTML transforms.
   */
  transformIndexHtml(
    url: string,
    html: string,
    originalUrl?: string,
  ): Promise<string>
  /**
   * Load a given URL as an instantiated module for SSR.
   */
  ssrLoadModule(
    url: string,
    options?: { fixStacktrace?: boolean },
  ): Promise<Record<string, any>>
  /**
   * Fix ssr error stacktrace.
   */
  ssrFixStacktrace(e: Error): void
  /**
   * Triggers HMR for a module in the module graph. You can use the `server.moduleGraph`
   * API to retrieve the module to be reloaded. If `hmr` is false, this is a no-op.
   */
  reloadModule(module: ModuleNode): Promise<void>
  /**
   * Start the server.
   */
  listen(port?: number, isRestart?: boolean): Promise<ViteDevServer>
  /**
   * Restart the server.
   *
   * @param forceOptimize - force the optimizer to re-bundle, same as --force cli flag
   */
  restart(forceOptimize?: boolean): Promise<void>
  /**
   * Stop the server.
   */
  close(): Promise<void>
  /**
   * Bind CLI shortcuts
   */
  bindCLIShortcuts(options?: BindCLIShortcutsOptions<ViteDevServer>): void
  /**
   * Calling `await server.waitForRequestsIdle(id)` will wait until all static imports
   * are processed. If called from a load or transform plugin hook, the id needs to be
   * passed as a parameter to avoid deadlocks. Calling this function after the first
   * static imports section of the module graph has been processed will resolve immediately.
   * @experimental
   */
  waitForRequestsIdle: (ignoredId?: string) => Promise<void>
}
```

:::info
`waitForRequestsIdle` is meant to be used as an escape hatch to improve DX for features that can't be implemented following the on-demand nature of the Vite dev server. It can be used during startup by tools like Tailwind to delay generating the app CSS classes until the app code has been seen, avoiding flashes of style changes. When this function is used in a load or transform hook, and the default HTTP1 server is used, one of the six http channels will be blocked until the server processes all static imports. Vite's dependency optimizer currently uses this function to avoid full-page reloads on missing dependencies by delaying loading of pre-bundled dependencies until all imported dependencies have been collected from static imported sources. Vite may switch to a different strategy in a future major release, setting `optimizeDeps.holdUntilCrawlEnd: false` by default to avoid the performance hit in large applications during cold start.
:::

## `build`

**Type Signature:**

```ts
async function build(
  inlineConfig?: InlineConfig,
): Promise<RolldownOutput | RolldownOutput[] | RolldownWatcher>
```

**Example Usage:**

```ts twoslash [vite.config.js]
import path from 'node:path'
import { build } from 'vite'

await build({
  root: path.resolve(import.meta.dirname, './project'),
  base: '/foo/',
  build: {
    rolldownOptions: {
      // ...
    },
  },
})
```

## `preview`

**Type Signature:**

```ts
async function preview(inlineConfig?: InlineConfig): Promise<PreviewServer>
```

**Example Usage:**

```ts twoslash
import { preview } from 'vite'

const previewServer = await preview({
  // any valid user config options, plus `mode` and `configFile`
  preview: {
    port: 8080,
    open: true,
  },
})

previewServer.printUrls()
previewServer.bindCLIShortcuts({ print: true })
```

## `PreviewServer`

```ts
interface PreviewServer {
  /**
   * The resolved vite config object
   */
  config: ResolvedConfig
  /**
   * A connect app instance.
   * - Can be used to attach custom middlewares to the preview server.
   * - Can also be used as the handler function of a custom http server
   *   or as a middleware in any connect-style Node.js frameworks
   *
   * https://github.com/senchalabs/connect#use-middleware
   */
  middlewares: Connect.Server
  /**
   * native Node http server instance
   */
  httpServer: http.Server
  /**
   * The resolved urls Vite prints on the CLI (URL-encoded). Returns `null`
   * if the server is not listening on any port.
   */
  resolvedUrls: ResolvedServerUrls | null
  /**
   * Print server urls
   */
  printUrls(): void
  /**
   * Bind CLI shortcuts
   */
  bindCLIShortcuts(options?: BindCLIShortcutsOptions<PreviewServer>): void
}
```

## `resolveConfig`

**Type Signature:**

```ts
async function resolveConfig(
  inlineConfig: InlineConfig,
  command: 'build' | 'serve',
  defaultMode = 'development',
  defaultNodeEnv = 'development',
  isPreview = false,
): Promise<ResolvedConfig>
```

The `command` value is `serve` in dev and preview, and `build` in build.

## `mergeConfig`

**Type Signature:**

```ts
function mergeConfig(
  defaults: Record<string, any>,
  overrides: Record<string, any>,
  isRoot = true,
): Record<string, any>
```

Deeply merge two Vite configs. `isRoot` represents the level within the Vite config which is being merged. For example, set `false` if you're merging two `build` options.

Note that `null` and `undefined` values in `overrides` are skipped and not merged. If you need to explicitly clear a value from `defaults`, modify the result of `mergeConfig` directly.

::: tip NOTE
`mergeConfig` accepts only config in object form. If you have a config in callback form, you should call it before passing into `mergeConfig`.

You can use the `defineConfig` helper to merge a config in callback form with another config:

```ts twoslash
import {
  defineConfig,
  mergeConfig,
  type UserConfigFnObject,
  type UserConfig,
} from 'vite'
declare const configAsCallback: UserConfigFnObject
declare const configAsObject: UserConfig

// ---cut---
export default defineConfig((configEnv) =>
  mergeConfig(configAsCallback(configEnv), configAsObject),
)
```

:::

## `searchForWorkspaceRoot`

**Type Signature:**

```ts
function searchForWorkspaceRoot(
  current: string,
  root = searchForPackageRoot(current),
): string
```

**Related:** [server.fs.allow](/config/server-options.md#server-fs-allow)

Search for the root of the potential workspace if it meets the following conditions, otherwise it would fallback to `root`:

- contains `workspaces` field in `package.json`
- contains one of the following file
  - `lerna.json`
  - `pnpm-workspace.yaml`

## `loadEnv`

**Type Signature:**

```ts
function loadEnv(
  mode: string,
  envDir: string,
  prefixes: string | string[] = 'VITE_',
): Record<string, string>
```

**Related:** [`.env` Files](./env-and-mode.md#env-files)

Load `.env` files within the `envDir` and merge them with the matching variables already present in `process.env`. By default, only env variables prefixed with `VITE_` are loaded, unless `prefixes` is changed.

## `normalizePath`

**Type Signature:**

```ts
function normalizePath(id: string): string
```

**Related:** [Path Normalization](./api-plugin.md#path-normalization)

Normalizes a path to interoperate between Vite plugins.

## `transformWithOxc`

**Type Signature:**

```ts
async function transformWithOxc(
  code: string,
  filename: string,
  options?: OxcTransformOptions,
  inMap?: object,
): Promise<Omit<OxcTransformResult, 'errors'> & { warnings: string[] }>
```

Transform JavaScript or TypeScript with [Oxc Transformer](https://oxc.rs/docs/guide/usage/transformer). Useful for plugins that prefer matching Vite's internal Oxc Transformer transform.

## `transformWithEsbuild`

**Type Signature:**

```ts
async function transformWithEsbuild(
  code: string,
  filename: string,
  options?: EsbuildTransformOptions,
  inMap?: object,
): Promise<ESBuildTransformResult>
```

**Deprecated:** Use `transformWithOxc` instead.

Transform JavaScript or TypeScript with esbuild. Useful for plugins that prefer matching Vite's internal esbuild transform.

## `loadConfigFromFile`

**Type Signature:**

```ts
async function loadConfigFromFile(
  configEnv: ConfigEnv,
  configFile?: string,
  configRoot: string = process.cwd(),
  logLevel?: LogLevel,
  customLogger?: Logger,
): Promise<{
  path: string
  config: UserConfig
  dependencies: string[]
} | null>
```

Load a Vite config file manually with Rolldown.

## `preprocessCSS`

- **Experimental:** [Give Feedback](https://github.com/vitejs/vite/discussions/13815)

**Type Signature:**

```ts
async function preprocessCSS(
  code: string,
  filename: string,
  config: ResolvedConfig,
): Promise<PreprocessCSSResult>

interface PreprocessCSSResult {
  code: string
  map?: SourceMapInput
  modules?: Record<string, string>
  deps?: Set<string>
}
```

Pre-processes `.css`, `.scss`, `.sass`, `.less`, `.styl` and `.stylus` files to plain CSS so it can be used in browsers or parsed by other tools. Similar to the [built-in CSS pre-processing support](/guide/features#css-pre-processors), the corresponding pre-processor must be installed if used.

The pre-processor used is inferred from the `filename` extension. If the `filename` ends with `.module.{ext}`, it is inferred as a [CSS module](https://github.com/css-modules/css-modules) and the returned result will include a `modules` object mapping the original class names to the transformed ones.

Note that pre-processing will not resolve URLs in `url()` or `image-set()`.

## `version`

**Type:** `string`

The current version of Vite as a string (e.g. `"8.0.0"`).

## `rolldownVersion`

**Type:** `string`

The version of Rolldown used by Vite as a string (e.g. `"1.0.0"`). A re-export of [`VERSION`](https://rolldown.rs/reference/Variable.VERSION) from `rolldown`.

## `esbuildVersion`

**Type:** `string`

Only kept for backward compatibility.

## `rollupVersion`

**Type:** `string`

Only kept for backward compatibility.


import type { ModuleInfo } from 'rolldown'
import { monotonicDateNow } from '../utils'
import type { TransformResult } from './transformRequest'
import type {
  EnvironmentModuleGraph,
  EnvironmentModuleNode,
  ResolvedUrl,
} from './moduleGraph'

/**
 * Backward compatible ModuleNode and ModuleGraph with mixed nodes from both the client and ssr environments
 * It would be good to take the types names for the new EnvironmentModuleNode and EnvironmentModuleGraph but we can't
 * do that at this point without breaking to much code in the ecosystem.
 * We are going to deprecate these types and we can try to use them back in the future.
 */

// same default value of "moduleInfo.meta" as in Rollup
const EMPTY_OBJECT = Object.freeze({})

export class ModuleNode {
  _moduleGraph: ModuleGraph
  _clientModule: EnvironmentModuleNode | undefined
  _ssrModule: EnvironmentModuleNode | undefined
  constructor(
    moduleGraph: ModuleGraph,
    clientModule?: EnvironmentModuleNode,
    ssrModule?: EnvironmentModuleNode,
  ) {
    this._moduleGraph = moduleGraph
    this._clientModule = clientModule
    this._ssrModule = ssrModule
  }
  _get<T extends keyof EnvironmentModuleNode>(
    prop: T,
  ): EnvironmentModuleNode[T] {
    return (this._clientModule?.[prop] ?? this._ssrModule?.[prop])!
  }
  _set<T extends keyof EnvironmentModuleNode>(
    prop: T,
    value: EnvironmentModuleNode[T],
  ): void {
    if (this._clientModule) {
      this._clientModule[prop] = value
    }
    if (this._ssrModule) {
      this._ssrModule[prop] = value
    }
  }

  _wrapModuleSet(
    prop: ModuleSetNames,
    module: EnvironmentModuleNode | undefined,
  ): Set<ModuleNode> {
    if (!module) {
      return new Set()
    }
    return createBackwardCompatibleModuleSet(this._moduleGraph, prop, module)
  }
  _getModuleSetUnion(prop: 'importedModules' | 'importers'): Set<ModuleNode> {
    // A good approximation to the previous logic that returned the union of
    // the importedModules and importers from both the browser and server
    const importedModules = new Set<ModuleNode>()
    const ids = new Set<string>()
    if (this._clientModule) {
      for (const mod of this._clientModule[prop]) {
        if (mod.id) ids.add(mod.id)
        importedModules.add(
          this._moduleGraph.getBackwardCompatibleModuleNode(mod),
        )
      }
    }
    if (this._ssrModule) {
      for (const mod of this._ssrModule[prop]) {
        if (mod.id && !ids.has(mod.id)) {
          importedModules.add(
            this._moduleGraph.getBackwardCompatibleModuleNode(mod),
          )
        }
      }
    }
    return importedModules
  }
  _getModuleInfoUnion(prop: 'info'): ModuleInfo | undefined {
    const _clientValue = this._clientModule?.[prop]
    const _ssrValue = this._ssrModule?.[prop]

    if (_clientValue == null && _ssrValue == null) return undefined

    return new Proxy({} as any, {
      get: (_, key: string) => {
        // `meta` refers to `ModuleInfo.meta` so we refer to `this.meta` to
        // handle the object union between client and ssr
        if (key === 'meta') {
          return this.meta || EMPTY_OBJECT
        }
        if (_clientValue) {
          if (key in _clientValue) {
            return _clientValue[key as keyof ModuleInfo]
          }
        }
        if (_ssrValue) {
          if (key in _ssrValue) {
            return _ssrValue[key as keyof ModuleInfo]
          }
        }
      },
    })
  }
  _getModuleObjectUnion(prop: 'meta'): Record<string, any> | undefined {
    const _clientValue = this._clientModule?.[prop]
    const _ssrValue = this._ssrModule?.[prop]

    if (_clientValue == null && _ssrValue == null) return undefined

    const info: Record<string, any> = {}
    if (_ssrValue) {
      Object.assign(info, _ssrValue)
    }
    if (_clientValue) {
      Object.assign(info, _clientValue)
    }
    return info
  }

  get url(): string {
    return this._get('url')
  }
  set url(value: string) {
    this._set('url', value)
  }
  get id(): string | null {
    return this._get('id')
  }
  set id(value: string | null) {
    this._set('id', value)
  }
  get file(): string | null {
    return this._get('file')
  }
  set file(value: string | null) {
    this._set('file', value)
  }
  get type(): 'js' | 'css' | 'asset' {
    return this._get('type')
  }
  // `info` needs special care as it's defined as a proxy in `pluginContainer`,
  // so we also merge it as a proxy too
  get info(): ModuleInfo | undefined {
    return this._getModuleInfoUnion('info')
  }
  get meta(): Record<string, any> | undefined {
    return this._getModuleObjectUnion('meta')
  }
  get importers(): Set<ModuleNode> {
    return this._getModuleSetUnion('importers')
  }
  get clientImportedModules(): Set<ModuleNode> {
    return this._wrapModuleSet('importedModules', this._clientModule)
  }
  get ssrImportedModules(): Set<ModuleNode> {
    return this._wrapModuleSet('importedModules', this._ssrModule)
  }
  get importedModules(): Set<ModuleNode> {
    return this._getModuleSetUnion('importedModules')
  }
  get acceptedHmrDeps(): Set<ModuleNode> {
    return this._wrapModuleSet('acceptedHmrDeps', this._clientModule)
  }
  get acceptedHmrExports(): Set<string> | null {
    return this._clientModule?.acceptedHmrExports ?? null
  }
  get importedBindings(): Map<string, Set<string>> | null {
    return this._clientModule?.importedBindings ?? null
  }
  get isSelfAccepting(): boolean | undefined {
    return this._clientModule?.isSelfAccepting
  }
  get transformResult(): TransformResult | null {
    return this._clientModule?.transformResult ?? null
  }
  set transformResult(value: TransformResult | null) {
    if (this._clientModule) {
      this._clientModule.transformResult = value
    }
  }
  get ssrTransformResult(): TransformResult | null {
    return this._ssrModule?.transformResult ?? null
  }
  set ssrTransformResult(value: TransformResult | null) {
    if (this._ssrModule) {
      this._ssrModule.transformResult = value
    }
  }
  get ssrModule(): Record<string, any> | null {
    return this._ssrModule?.ssrModule ?? null
  }
  get ssrError(): Error | null {
    return this._ssrModule?.ssrError ?? null
  }
  get lastHMRTimestamp(): number {
    return Math.max(
      this._clientModule?.lastHMRTimestamp ?? 0,
      this._ssrModule?.lastHMRTimestamp ?? 0,
    )
  }
  set lastHMRTimestamp(value: number) {
    if (this._clientModule) {
      this._clientModule.lastHMRTimestamp = value
    }
    if (this._ssrModule) {
      this._ssrModule.lastHMRTimestamp = value
    }
  }
  get lastInvalidationTimestamp(): number {
    return Math.max(
      this._clientModule?.lastInvalidationTimestamp ?? 0,
      this._ssrModule?.lastInvalidationTimestamp ?? 0,
    )
  }
  get invalidationState(): TransformResult | 'HARD_INVALIDATED' | undefined {
    return this._clientModule?.invalidationState
  }
  get ssrInvalidationState(): TransformResult | 'HARD_INVALIDATED' | undefined {
    return this._ssrModule?.invalidationState
  }
}

function mapIterator<T, K = T>(
  iterable: IterableIterator<T>,
  transform: (value: T) => K,
): IterableIterator<K> {
  return {
    [Symbol.iterator](): IterableIterator<K> {
      return this
    },
    next(): IteratorResult<K> {
      const r = iterable.next()
      return r.done
        ? r
        : {
            value: transform(r.value),
            done: false,
          }
    },
  }
}

export class ModuleGraph {
  /** @internal */
  _moduleGraphs: {
    client: () => EnvironmentModuleGraph
    ssr: () => EnvironmentModuleGraph
  }

  /** @internal */
  get _client(): EnvironmentModuleGraph {
    return this._moduleGraphs.client()
  }

  /** @internal */
  get _ssr(): EnvironmentModuleGraph {
    return this._moduleGraphs.ssr()
  }

  urlToModuleMap: Map<string, ModuleNode>
  idToModuleMap: Map<string, ModuleNode>
  etagToModuleMap: Map<string, ModuleNode>

  fileToModulesMap: Map<string, Set<ModuleNode>>

  private moduleNodeCache = new DualWeakMap<
    EnvironmentModuleNode,
    EnvironmentModuleNode,
    ModuleNode
  >()

  constructor(moduleGraphs: {
    client: () => EnvironmentModuleGraph
    ssr: () => EnvironmentModuleGraph
  }) {
    this._moduleGraphs = moduleGraphs

    const getModuleMapUnion =
      (prop: 'urlToModuleMap' | 'idToModuleMap') => () => {
        // A good approximation to the previous logic that returned the union of
        // the importedModules and importers from both the browser and server
        if (this._ssr[prop].size === 0) {
          return this._client[prop]
        }
        const map = new Map(this._client[prop])
        for (const [key, module] of this._ssr[prop]) {
          if (!map.has(key)) {
            map.set(key, module)
          }
        }
        return map
      }

    this.urlToModuleMap = createBackwardCompatibleModuleMap(
      this,
      'urlToModuleMap',
      getModuleMapUnion('urlToModuleMap'),
    )
    this.idToModuleMap = createBackwardCompatibleModuleMap(
      this,
      'idToModuleMap',
      getModuleMapUnion('idToModuleMap'),
    )
    this.etagToModuleMap = createBackwardCompatibleModuleMap(
      this,
      'etagToModuleMap',
      () => this._client.etagToModuleMap,
    )
    this.fileToModulesMap = createBackwardCompatibleFileToModulesMap(this)
  }

  getModuleById(id: string): ModuleNode | undefined {
    const clientModule = this._client.getModuleById(id)
    const ssrModule = this._ssr.getModuleById(id)
    if (!clientModule && !ssrModule) {
      return
    }
    return this.getBackwardCompatibleModuleNodeDual(clientModule, ssrModule)
  }

  async getModuleByUrl(
    url: string,
    _ssr?: boolean,
  ): Promise<ModuleNode | undefined> {
    // In the mixed graph, the ssr flag was used to resolve the id.
    const [clientModule, ssrModule] = await Promise.all([
      this._client.getModuleByUrl(url),
      this._ssr.getModuleByUrl(url),
    ])
    if (!clientModule && !ssrModule) {
      return
    }
    return this.getBackwardCompatibleModuleNodeDual(clientModule, ssrModule)
  }

  getModulesByFile(file: string): Set<ModuleNode> | undefined {
    // Until Vite 5.1.x, the moduleGraph contained modules from both the browser and server
    // We maintain backwards compatibility by returning a Set of module proxies assuming
    // that the modules for a certain file are the same in both the browser and server
    const clientModules = this._client.getModulesByFile(file)
    const ssrModules = this._ssr.getModulesByFile(file)
    if (!clientModules && !ssrModules) {
      return undefined
    }
    const result = new Set<ModuleNode>()
    if (clientModules) {
      for (const mod of clientModules) {
        result.add(this.getBackwardCompatibleBrowserModuleNode(mod)!)
      }
    }
    if (ssrModules) {
      for (const mod of ssrModules) {
        if (mod.id == null || !this._client.getModuleById(mod.id)) {
          result.add(this.getBackwardCompatibleServerModuleNode(mod)!)
        }
      }
    }
    return result
  }

  onFileChange(file: string): void {
    this._client.onFileChange(file)
    this._ssr.onFileChange(file)
  }

  onFileDelete(file: string): void {
    this._client.onFileDelete(file)
    this._ssr.onFileDelete(file)
  }

  /** @internal */
  _getModuleGraph(environment: string): EnvironmentModuleGraph {
    switch (environment) {
      case 'client':
        return this._client
      case 'ssr':
        return this._ssr
      default:
        throw new Error(`Invalid module node environment ${environment}`)
    }
  }

  invalidateModule(
    mod: ModuleNode,
    seen: Set<ModuleNode> = new Set(),
    timestamp: number = monotonicDateNow(),
    isHmr: boolean = false,
    /** @internal */
    softInvalidate = false,
  ): void {
    if (mod._clientModule) {
      this._client.invalidateModule(
        mod._clientModule,
        new Set(
          [...seen].map((mod) => mod._clientModule).filter(Boolean),
        ) as Set<EnvironmentModuleNode>,
        timestamp,
        isHmr,
        softInvalidate,
      )
    }
    if (mod._ssrModule) {
      // TODO: Maybe this isn't needed?
      this._ssr.invalidateModule(
        mod._ssrModule,
        new Set(
          [...seen].map((mod) => mod._ssrModule).filter(Boolean),
        ) as Set<EnvironmentModuleNode>,
        timestamp,
        isHmr,
        softInvalidate,
      )
    }
  }

  invalidateAll(): void {
    this._client.invalidateAll()
    this._ssr.invalidateAll()
  }

  /* TODO: It seems there isn't usage of this method in the ecosystem
     Waiting to check if we really need this for backwards compatibility
  async updateModuleInfo(
    module: ModuleNode,
    importedModules: Set<string | ModuleNode>,
    importedBindings: Map<string, Set<string>> | null,
    acceptedModules: Set<string | ModuleNode>,
    acceptedExports: Set<string> | null,
    isSelfAccepting: boolean,
    ssr?: boolean,
    staticImportedUrls?: Set<string>, // internal
  ): Promise<Set<ModuleNode> | undefined> {
    // Not implemented
  }
  */

  async ensureEntryFromUrl(
    rawUrl: string,
    ssr?: boolean,
    setIsSelfAccepting = true,
  ): Promise<ModuleNode> {
    const module = await (ssr ? this._ssr : this._client).ensureEntryFromUrl(
      rawUrl,
      setIsSelfAccepting,
    )
    return this.getBackwardCompatibleModuleNode(module)!
  }

  createFileOnlyEntry(file: string): ModuleNode {
    const clientModule = this._client.createFileOnlyEntry(file)
    const ssrModule = this._ssr.createFileOnlyEntry(file)
    return this.getBackwardCompatibleModuleNodeDual(clientModule, ssrModule)!
  }

  async resolveUrl(url: string, ssr?: boolean): Promise<ResolvedUrl> {
    return ssr ? this._ssr.resolveUrl(url) : this._client.resolveUrl(url)
  }

  updateModuleTransformResult(
    mod: ModuleNode,
    result: TransformResult | null,
    ssr?: boolean,
  ): void {
    const environment = ssr ? 'ssr' : 'client'
    this._getModuleGraph(environment).updateModuleTransformResult(
      (environment === 'client' ? mod._clientModule : mod._ssrModule)!,
      result,
    )
  }

  getModuleByEtag(etag: string): ModuleNode | undefined {
    const mod = this._client.etagToModuleMap.get(etag)
    return mod && this.getBackwardCompatibleBrowserModuleNode(mod)
  }

  getBackwardCompatibleBrowserModuleNode(
    clientModule: EnvironmentModuleNode,
  ): ModuleNode {
    return this.getBackwardCompatibleModuleNodeDual(
      clientModule,
      clientModule.id ? this._ssr.getModuleById(clientModule.id) : undefined,
    )
  }

  getBackwardCompatibleServerModuleNode(
    ssrModule: EnvironmentModuleNode,
  ): ModuleNode {
    return this.getBackwardCompatibleModuleNodeDual(
      ssrModule.id ? this._client.getModuleById(ssrModule.id) : undefined,
      ssrModule,
    )
  }

  getBackwardCompatibleModuleNode(mod: EnvironmentModuleNode): ModuleNode {
    return mod.environment === 'client'
      ? this.getBackwardCompatibleBrowserModuleNode(mod)
      : this.getBackwardCompatibleServerModuleNode(mod)
  }

  getBackwardCompatibleModuleNodeDual(
    clientModule?: EnvironmentModuleNode,
    ssrModule?: EnvironmentModuleNode,
  ): ModuleNode {
    const cached = this.moduleNodeCache.get(clientModule, ssrModule)
    if (cached) {
      return cached
    }

    const moduleNode = new ModuleNode(this, clientModule, ssrModule)
    this.moduleNodeCache.set(clientModule, ssrModule, moduleNode)
    return moduleNode
  }
}

class DualWeakMap<K1 extends WeakKey, K2 extends WeakKey, V> {
  private map = new WeakMap<K1 | object, WeakMap<K2 | object, V>>()
  private undefinedKey = {}

  get(key1: K1 | undefined, key2: K2 | undefined): V | undefined {
    const k1 = key1 ?? this.undefinedKey
    const k2 = key2 ?? this.undefinedKey
    return this.map.get(k1)?.get(k2)
  }

  set(key1: K1 | undefined, key2: K2 | undefined, value: V): void {
    const k1 = key1 ?? this.undefinedKey
    const k2 = key2 ?? this.undefinedKey
    if (!this.map.has(k1)) {
      this.map.set(k1, new Map<K2, V>())
    }

    const m = this.map.get(k1)!
    m.set(k2, value)
  }
}

type ModuleSetNames = 'acceptedHmrDeps' | 'importedModules'

function createBackwardCompatibleModuleSet(
  moduleGraph: ModuleGraph,
  prop: ModuleSetNames,
  module: EnvironmentModuleNode,
): Set<ModuleNode> {
  return {
    [Symbol.iterator]() {
      return this.keys()
    },
    has(key) {
      if (!key.id) {
        return false
      }
      const keyModule = moduleGraph
        ._getModuleGraph(module.environment)
        .getModuleById(key.id)
      return keyModule !== undefined && module[prop].has(keyModule)
    },
    values() {
      return this.keys()
    },
    keys() {
      return mapIterator(module[prop].keys(), (mod) =>
        moduleGraph.getBackwardCompatibleModuleNode(mod),
      )
    },
    get size() {
      return module[prop].size
    },
    forEach(callback, thisArg) {
      return module[prop].forEach((mod) => {
        const backwardCompatibleMod =
          moduleGraph.getBackwardCompatibleModuleNode(mod)
        callback.call(
          thisArg,
          backwardCompatibleMod,
          backwardCompatibleMod,
          this,
        )
      })
    },
    // There are several methods missing. We can implement them if downstream
    // projects are relying on them: add, clear, delete, difference, intersection,
    // sDisjointFrom, isSubsetOf, isSupersetOf, symmetricDifference, union
  } as Set<ModuleNode>
}

function createBackwardCompatibleModuleMap(
  moduleGraph: ModuleGraph,
  prop: 'urlToModuleMap' | 'idToModuleMap' | 'etagToModuleMap',
  getModuleMap: () => Map<string, EnvironmentModuleNode>,
): Map<string, ModuleNode> {
  return {
    [Symbol.iterator]() {
      return this.entries()
    },
    get(key) {
      const clientModule = moduleGraph._client[prop].get(key)
      const ssrModule = moduleGraph._ssr[prop].get(key)
      if (!clientModule && !ssrModule) {
        return
      }
      return moduleGraph.getBackwardCompatibleModuleNodeDual(
        clientModule,
        ssrModule,
      )
    },
    set(key, mod) {
      const clientModule = mod._clientModule
      if (clientModule) {
        moduleGraph._client[prop].set(key, clientModule)
      }
      const ssrModule = mod._ssrModule
      if (ssrModule) {
        moduleGraph._ssr[prop].set(key, ssrModule)
      }
    },
    keys() {
      return getModuleMap().keys()
    },
    values() {
      return mapIterator(getModuleMap().values(), (mod) =>
        moduleGraph.getBackwardCompatibleModuleNode(mod),
      )
    },
    entries() {
      return mapIterator(getModuleMap().entries(), ([key, mod]) => [
        key,
        moduleGraph.getBackwardCompatibleModuleNode(mod),
      ])
    },
    get size() {
      return getModuleMap().size
    },
    forEach(callback, thisArg) {
      return getModuleMap().forEach((mod, key) => {
        const backwardCompatibleMod =
          moduleGraph.getBackwardCompatibleModuleNode(mod)
        callback.call(thisArg, backwardCompatibleMod, key, this)
      })
    },
  } as Map<string, ModuleNode>
}

function createBackwardCompatibleFileToModulesMap(
  moduleGraph: ModuleGraph,
): Map<string, Set<ModuleNode>> {
  const getFileToModulesMap = (): Map<string, Set<EnvironmentModuleNode>> => {
    // A good approximation to the previous logic that returned the union of
    // the importedModules and importers from both the browser and server
    if (!moduleGraph._ssr.fileToModulesMap.size) {
      return moduleGraph._client.fileToModulesMap
    }
    const map = new Map(moduleGraph._client.fileToModulesMap)
    for (const [key, modules] of moduleGraph._ssr.fileToModulesMap) {
      const modulesSet = map.get(key)
      if (!modulesSet) {
        map.set(key, modules)
      } else {
        for (const ssrModule of modules) {
          let hasModule = false
          for (const clientModule of modulesSet) {
            hasModule ||= clientModule.id === ssrModule.id
            if (hasModule) {
              break
            }
          }
          if (!hasModule) {
            modulesSet.add(ssrModule)
          }
        }
      }
    }
    return map
  }
  const getBackwardCompatibleModules = (
    modules: Set<EnvironmentModuleNode>,
  ): Set<ModuleNode> =>
    new Set(
      [...modules].map((mod) =>
        moduleGraph.getBackwardCompatibleModuleNode(mod),
      ),
    )

  return {
    [Symbol.iterator]() {
      return this.entries()
    },
    get(key) {
      const clientModules = moduleGraph._client.fileToModulesMap.get(key)
      const ssrModules = moduleGraph._ssr.fileToModulesMap.get(key)
      if (!clientModules && !ssrModules) {
        return
      }
      const modules = clientModules ?? new Set<EnvironmentModuleNode>()
      if (ssrModules) {
        for (const ssrModule of ssrModules) {
          if (ssrModule.id) {
            let found = false
            for (const mod of modules) {
              found ||= mod.id === ssrModule.id
              if (found) {
                break
              }
            }
            if (!found) {
              modules.add(ssrModule)
            }
          }
        }
      }
      return getBackwardCompatibleModules(modules)
    },
    keys() {
      return getFileToModulesMap().keys()
    },
    values() {
      return mapIterator(
        getFileToModulesMap().values(),
        getBackwardCompatibleModules,
      )
    },
    entries() {
      return mapIterator(getFileToModulesMap().entries(), ([key, modules]) => [
        key,
        getBackwardCompatibleModules(modules),
      ])
    },
    get size() {
      return getFileToModulesMap().size
    },
    forEach(callback, thisArg) {
      return getFileToModulesMap().forEach((modules, key) => {
        callback.call(thisArg, getBackwardCompatibleModules(modules), key, this)
      })
    },
  } as Map<string, Set<ModuleNode>>
}


# Architecture

!!! info

    This page is still under construction. Its goal is to provide a high-level overview of the library's architecture.
    This should help new contributors to get an idea of the used concepts and where to make changes.

## Overview

The main structure is class [nlohmann::basic_json](../api/basic_json/index.md).

- public API
- container interface
- iterators

## Template specializations

- describe template parameters of `basic_json`
- [`json`](../api/json.md)
- [`ordered_json`](../api/ordered_json.md) via [`ordered_map`](../api/ordered_map.md)

## Value storage

Values are stored as a tagged union of [value_t](../api/basic_json/value_t.md) and json_value.

```cpp
/// the type of the current element
value_t m_type = value_t::null;

/// the value of the current element
json_value m_value = {};
```

with

```cpp
enum class value_t : std::uint8_t
{
    null,             ///< null value
    object,           ///< object (unordered set of name/value pairs)
    array,            ///< array (ordered collection of values)
    string,           ///< string value
    boolean,          ///< boolean value
    number_integer,   ///< number value (signed integer)
    number_unsigned,  ///< number value (unsigned integer)
    number_float,     ///< number value (floating-point)
    binary,           ///< binary array (ordered collection of bytes)
    discarded         ///< discarded by the parser callback function
};

union json_value {
  /// object (stored with pointer to save storage)
  object_t *object;
  /// array (stored with pointer to save storage)
  array_t *array;
  /// string (stored with pointer to save storage)
  string_t *string;
  /// binary (stored with pointer to save storage)
  binary_t *binary;
  /// boolean
  boolean_t boolean;
  /// number (integer)
  number_integer_t number_integer;
  /// number (unsigned integer)
  number_unsigned_t number_unsigned;
  /// number (floating-point)
  number_float_t number_float;
};
```

## Parsing inputs (deserialization)

Input is read via **input adapters** that abstract a source with a common interface:

```cpp
/// read a single character
std::char_traits<char>::int_type get_character() noexcept;

/// read multiple characters to a destination buffer and
/// returns the number of characters successfully read
template<class T>
std::size_t get_elements(T* dest, std::size_t count = 1);
```

List examples of input adapters.

## SAX Interface

TODO

## Writing outputs (serialization)

Output is written via **output adapters**:

```cpp
template<typename T>
void write_character(CharType c);

template<typename CharType>
void write_characters(const CharType* s, std::size_t length);
```

List examples of output adapters.

## Value conversion

```cpp
template<class T>
void to_json(basic_json& j, const T& t);

template<class T>
void from_json(const basic_json& j, T& t);
```

## Additional features

- JSON Pointers
- Binary formats
- Custom base class
- Conversion macros

## Details namespace

- C++ feature backports


# `this.environment` in Hooks

::: tip Feedback
Give us feedback at [Environment API feedback discussion](https://github.com/vitejs/vite/discussions/16358)
:::

Before Vite 6, only two environments were available: `client` and `ssr`. A single `options.ssr` plugin hook argument in `resolveId`, `load` and `transform` allowed plugin authors to differentiate between these two environments when processing modules in plugin hooks. In Vite 6, a Vite application can define any number of named environments as needed. We're introducing `this.environment` in the plugin context to interact with the environment of the current module in hooks.

Affected scope: `Vite Plugin Authors`

::: warning Future Deprecation
`this.environment` was introduced in `v6.0`. The deprecation of `options.ssr` is planned for a future major. At that point we'll start recommending migrating your plugins to use the new API. To identify your usage, set `future.removePluginHookSsrArgument` to `"warn"` in your vite config.
:::

## Motivation

`this.environment` not only allow the plugin hook implementation to know the current environment name, it also gives access to the environment config options, module graph information, and transform pipeline (`environment.config`, `environment.moduleGraph`, `environment.transformRequest()`). Having the environment instance available in the context allows plugin authors to avoid the dependency of the whole dev server (typically cached at startup through the `configureServer` hook).

## Migration Guide

For the existing plugin to do a quick migration, replace the `options.ssr` argument with `this.environment.config.consumer === 'server'` in the `resolveId`, `load` and `transform` hooks:

```ts
import { Plugin } from 'vite'

export function myPlugin(): Plugin {
  return {
    name: 'my-plugin',
    resolveId(id, importer, options) {
      const isSSR = options.ssr // [!code --]
      const isSSR = this.environment.config.consumer === 'server' // [!code ++]

      if (isSSR) {
        // SSR specific logic
      } else {
        // Client specific logic
      }
    },
  }
}
```

For a more robust long term implementation, the plugin hook should handle for [multiple environments](/guide/api-environment-plugins.html#accessing-the-current-environment-in-hooks) using fine-grained environment options instead of relying on the environment name.


//     __ _____ _____ _____
//  __|  |   __|     |   | |  JSON for Modern C++ (supporting code)
// |  |  |__   |  |  | | | |  version 3.12.0
// |_____|_____|_____|_|___|  https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT

#include "doctest_compatibility.h"

#include <nlohmann/json_v3_10_5.hpp>
using nlohmann::json;
using nlohmann::ordered_json;

TEST_CASE("use library v3.10.5 without inline namespace")
{
    json j;
    j[ordered_json::json_pointer("/root")] = json::object();
    // In v3.10.5 mixing json_pointers of different basic_json types
    // results in implicit string conversion
    CHECK(j.dump() == "{\"/root\":{}}");
}


Banner:	fuzz

Directory:	fuzz-testing/out

Generated on:	Sun Oct  2 08:51:02 CEST 2016


//     __ _____ _____ _____
//  __|  |   __|     |   | |  JSON for Modern C++ (supporting code)
// |  |  |__   |  |  | | | |  version 3.12.0
// |_____|_____|_____|_|___|  https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT

#include "doctest_compatibility.h"

// for some reason including this after the json header leads to linker errors with VS 2017...
#include <locale>

#include <nlohmann/json.hpp>
using nlohmann::json;

#include <fstream>
#include <sstream>
#include <iostream>
#include <iomanip>
#include "make_test_data_available.hpp"

// this test suite uses static variables with non-trivial destructors
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")

namespace
{
extern size_t calls;
size_t calls = 0;

void check_utf8dump(bool success_expected, int byte1, int byte2, int byte3, int byte4);

void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3 = -1, int byte4 = -1)
{
    static std::string json_string;
    json_string.clear();

    CAPTURE(byte1)
    CAPTURE(byte2)
    CAPTURE(byte3)
    CAPTURE(byte4)

    json_string += std::string(1, static_cast<char>(byte1));

    if (byte2 != -1)
    {
        json_string += std::string(1, static_cast<char>(byte2));
    }

    if (byte3 != -1)
    {
        json_string += std::string(1, static_cast<char>(byte3));
    }

    if (byte4 != -1)
    {
        json_string += std::string(1, static_cast<char>(byte4));
    }

    CAPTURE(json_string)

    // store the string in a JSON value
    static json j;
    static json j2;
    j = json_string;
    j2 = "abc" + json_string + "xyz";

    static std::string s_ignored;
    static std::string s_ignored2;
    static std::string s_ignored_ascii;
    static std::string s_ignored2_ascii;
    static std::string s_replaced;
    static std::string s_replaced2;
    static std::string s_replaced_ascii;
    static std::string s_replaced2_ascii;

    // dumping with ignore/replace must not throw in any case
    s_ignored = j.dump(-1, ' ', false, json::error_handler_t::ignore);
    s_ignored2 = j2.dump(-1, ' ', false, json::error_handler_t::ignore);
    s_ignored_ascii = j.dump(-1, ' ', true, json::error_handler_t::ignore);
    s_ignored2_ascii = j2.dump(-1, ' ', true, json::error_handler_t::ignore);
    s_replaced = j.dump(-1, ' ', false, json::error_handler_t::replace);
    s_replaced2 = j2.dump(-1, ' ', false, json::error_handler_t::replace);
    s_replaced_ascii = j.dump(-1, ' ', true, json::error_handler_t::replace);
    s_replaced2_ascii = j2.dump(-1, ' ', true, json::error_handler_t::replace);

    if (success_expected)
    {
        static std::string s_strict;
        // strict mode must not throw if success is expected
        s_strict = j.dump();
        // all dumps should agree on the string
        CHECK(s_strict == s_ignored);
        CHECK(s_strict == s_replaced);
    }
    else
    {
        // strict mode must throw if success is not expected
        CHECK_THROWS_AS(j.dump(), json::type_error&);
        // ignore and replace must create different dumps
        CHECK(s_ignored != s_replaced);

        // check that replace string contains a replacement character
        CHECK(s_replaced.find("\xEF\xBF\xBD") != std::string::npos);
    }

    // check that prefix and suffix are preserved
    CHECK(s_ignored2.substr(1, 3) == "abc");
    CHECK(s_ignored2.substr(s_ignored2.size() - 4, 3) == "xyz");
    CHECK(s_ignored2_ascii.substr(1, 3) == "abc");
    CHECK(s_ignored2_ascii.substr(s_ignored2_ascii.size() - 4, 3) == "xyz");
    CHECK(s_replaced2.substr(1, 3) == "abc");
    CHECK(s_replaced2.substr(s_replaced2.size() - 4, 3) == "xyz");
    CHECK(s_replaced2_ascii.substr(1, 3) == "abc");
    CHECK(s_replaced2_ascii.substr(s_replaced2_ascii.size() - 4, 3) == "xyz");
}

void check_utf8string(bool success_expected, int byte1, int byte2, int byte3, int byte4);

// create and check a JSON string with up to four UTF-8 bytes
void check_utf8string(bool success_expected, int byte1, int byte2 = -1, int byte3 = -1, int byte4 = -1)
{
    if (++calls % 100000 == 0)
    {
        std::cout << calls << " of 1246225 UTF-8 strings checked" << std::endl; // NOLINT(performance-avoid-endl)
    }

    static std::string json_string;
    json_string = "\"";

    CAPTURE(byte1)
    json_string += std::string(1, static_cast<char>(byte1));

    if (byte2 != -1)
    {
        CAPTURE(byte2)
        json_string += std::string(1, static_cast<char>(byte2));
    }

    if (byte3 != -1)
    {
        CAPTURE(byte3)
        json_string += std::string(1, static_cast<char>(byte3));
    }

    if (byte4 != -1)
    {
        CAPTURE(byte4)
        json_string += std::string(1, static_cast<char>(byte4));
    }

    json_string += "\"";

    CAPTURE(json_string)

    json _;
    if (success_expected)
    {
        CHECK_NOTHROW(_ = json::parse(json_string));
    }
    else
    {
        CHECK_THROWS_AS(_ = json::parse(json_string), json::parse_error&);
    }
}
} // namespace

TEST_CASE("Unicode (5/5)" * doctest::skip())
{
    SECTION("RFC 3629")
    {
        /*
        RFC 3629 describes in Sect. 4 the syntax of UTF-8 byte sequences as
        follows:

            A UTF-8 string is a sequence of octets representing a sequence of UCS
            characters.  An octet sequence is valid UTF-8 only if it matches the
            following syntax, which is derived from the rules for encoding UTF-8
            and is expressed in the ABNF of [RFC2234].

            UTF8-octets = *( UTF8-char )
            UTF8-char   = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
            UTF8-1      = %x00-7F
            UTF8-2      = %xC2-DF UTF8-tail
            UTF8-3      = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
                          %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
            UTF8-4      = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
                          %xF4 %x80-8F 2( UTF8-tail )
            UTF8-tail   = %x80-BF
        */

        SECTION("UTF8-4 (xF4 x80-8F UTF8-tail UTF8-tail)")
        {
            SECTION("well-formed")
            {
                for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
                {
                    for (int byte2 = 0x80; byte2 <= 0x8F; ++byte2)
                    {
                        for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
                        {
                            for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
                            {
                                check_utf8string(true, byte1, byte2, byte3, byte4);
                                check_utf8dump(true, byte1, byte2, byte3, byte4);
                            }
                        }
                    }
                }
            }

            SECTION("ill-formed: missing second byte")
            {
                for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
                {
                    check_utf8string(false, byte1);
                    check_utf8dump(false, byte1);
                }
            }

            SECTION("ill-formed: missing third byte")
            {
                for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
                {
                    for (int byte2 = 0x80; byte2 <= 0x8F; ++byte2)
                    {
                        check_utf8string(false, byte1, byte2);
                        check_utf8dump(false, byte1, byte2);
                    }
                }
            }

            SECTION("ill-formed: missing fourth byte")
            {
                for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
                {
                    for (int byte2 = 0x80; byte2 <= 0x8F; ++byte2)
                    {
                        for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
                        {
                            check_utf8string(false, byte1, byte2, byte3);
                            check_utf8dump(false, byte1, byte2, byte3);
                        }
                    }
                }
            }

            SECTION("ill-formed: wrong second byte")
            {
                for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
                {
                    for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
                    {
                        // skip correct second byte
                        if (0x80 <= byte2 && byte2 <= 0x8F)
                        {
                            continue;
                        }

                        for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
                        {
                            for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
                            {
                                check_utf8string(false, byte1, byte2, byte3, byte4);
                                check_utf8dump(false, byte1, byte2, byte3, byte4);
                            }
                        }
                    }
                }
            }

            SECTION("ill-formed: wrong third byte")
            {
                for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
                {
                    for (int byte2 = 0x80; byte2 <= 0x8F; ++byte2)
                    {
                        for (int byte3 = 0x00; byte3 <= 0xFF; ++byte3)
                        {
                            // skip correct third byte
                            if (0x80 <= byte3 && byte3 <= 0xBF)
                            {
                                continue;
                            }

                            for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
                            {
                                check_utf8string(false, byte1, byte2, byte3, byte4);
                                check_utf8dump(false, byte1, byte2, byte3, byte4);
                            }
                        }
                    }
                }
            }

            SECTION("ill-formed: wrong fourth byte")
            {
                for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
                {
                    for (int byte2 = 0x80; byte2 <= 0x8F; ++byte2)
                    {
                        for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
                        {
                            for (int byte4 = 0x00; byte4 <= 0xFF; ++byte4)
                            {
                                // skip correct fourth byte
                                if (0x80 <= byte3 && byte3 <= 0xBF)
                                {
                                    continue;
                                }

                                check_utf8string(false, byte1, byte2, byte3, byte4);
                                check_utf8dump(false, byte1, byte2, byte3, byte4);
                            }
                        }
                    }
                }
            }
        }
    }
}

DOCTEST_CLANG_SUPPRESS_WARNING_POP


# <small>nlohmann::basic_json::</small>operator ValueType

```cpp
template<typename ValueType>
JSON_EXPLICIT operator ValueType() const;
```

Implicit type conversion between the JSON value and a compatible value. The call is realized by calling
[`get()`](get.md). See [Notes](#notes) for the meaning of `JSON_EXPLICIT`.

## Template parameters

`ValueType`
:   the value type to return

## Return value

copy of the JSON value, converted to `ValueType`

## Exceptions

Depends on what `json_serializer<ValueType>` `from_json()` method throws

## Complexity

Linear in the size of the JSON value.

## Notes

!!! note "Definition of `JSON_EXPLICIT`"

    By default `JSON_EXPLICIT` is defined to the empty string, so the signature is:

    ```cpp
    template<typename ValueType>
    operator ValueType() const;
    ```

    If [`JSON_USE_IMPLICIT_CONVERSIONS`](../macros/json_use_implicit_conversions.md) is set to `0`,
    `JSON_EXPLICIT` is defined to `#!cpp explicit`:

    ```cpp
    template<typename ValueType>
    explicit operator ValueType() const;
    ```

    That is, implicit conversions can be switched off by defining
    [`JSON_USE_IMPLICIT_CONVERSIONS`](../macros/json_use_implicit_conversions.md) to `0`.

!!! info "Future behavior change"

    Implicit conversions will be switched off by default in the next major release of the library. That is,
    `JSON_EXPLICIT` will be set to `#!cpp explicit` by default.

    You can prepare existing code by already defining
    [`JSON_USE_IMPLICIT_CONVERSIONS`](../macros/json_use_implicit_conversions.md) to `0` and replace any implicit
    conversions with calls to [`get`](../basic_json/get.md).

## Examples

??? example

    The example below shows several conversions from JSON values to other types. There are a few things to note: (1)
    Floating-point numbers can be converted to integers, (2) A JSON array can be converted to a standard
    `std::vector<short>`, (3) A JSON object can be converted to C++ associative containers such as
    `std::unordered_map<std::string, json>`.

    ```cpp
    --8<-- "examples/operator__ValueType.cpp"
    ```

    Output:

    ```json
    --8<-- "examples/operator__ValueType.output"
    ```

## See also

- [get](get.md) get a value (explicit conversion)
- [Converting values](../../features/conversions.md) - the type conversions article

## Version history

- Since version 1.0.0.
- Macros `JSON_EXPLICIT`/[`JSON_USE_IMPLICIT_CONVERSIONS`](../macros/json_use_implicit_conversions.md) added
  in version 3.9.0.


import { rerender } from './runtime'

export const value = 'child'

if (import.meta.hot) {
  if (globalThis.__vite_file_delete_restore_child_pruned) {
    log(
      `file-delete-restore/child.js hot data after prune: ${import.meta.hot.data.persisted}`,
    )
    delete globalThis.__vite_file_delete_restore_child_pruned
  }
  import.meta.hot.data.persisted = true

  import.meta.hot.accept((newMod) => {
    if (!newMod) return

    rerender({ child: newMod.value })
  })

  import.meta.hot.dispose(() => {
    log('file-delete-restore/child.js is disposed')
  })

  import.meta.hot.prune(() => {
    log('file-delete-restore/child.js is pruned')
    globalThis.__vite_file_delete_restore_child_pruned = true
  })
}


#include <iostream>
#include <nlohmann/json.hpp>

// define a byte container based on std::vector
using byte_container_with_subtype = nlohmann::byte_container_with_subtype<std::vector<std::uint8_t>>;

int main()
{
    std::vector<std::uint8_t> bytes = {{0xca, 0xfe, 0xba, 0xbe}};

    // create container
    auto c1 = byte_container_with_subtype(bytes);

    // create container with subtype
    auto c2 = byte_container_with_subtype(bytes, 42);

    std::cout << "c1.subtype() = " << c1.subtype()
              << "\nc2.subtype() = " << c2.subtype() << std::endl;

    // in case no subtype is set, return special value
    assert(c1.subtype() == static_cast<byte_container_with_subtype::subtype_type>(-1));
}


export const component1 = /* html */ `

component1

`

import.meta.hot?.accept((mod) => {
  document.querySelectorAll('[data-id="component1"]').forEach((d) => {
    d.outerHTML = mod.component1
  })
})


//! Deserialization for adjacently tagged enums:
//!
//! ```ignore
//! #[serde(tag = "...", content = "...")]
//! enum Enum {}
//! ```

use crate::de::enum_;
use crate::de::enum_untagged;
use crate::de::{field_i, Parameters};
use crate::fragment::{Fragment, Match};
use crate::internals::ast::{Style, Variant};
use crate::internals::attr;
use crate::private;
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;

/// Generates `Deserialize::deserialize` body for an `enum Enum {...}` with `#[serde(tag, content)]` attributes
pub(super) fn deserialize(
    params: &Parameters,
    variants: &[Variant],
    cattrs: &attr::Container,
    tag: &str,
    content: &str,
) -> Fragment {
    let this_type = &params.this_type;
    let this_value = &params.this_value;
    let (de_impl_generics, de_ty_generics, ty_generics, where_clause) =
        params.generics_with_de_lifetime();
    let delife = params.borrowed.de_lifetime();

    let (variants_stmt, variant_visitor) = enum_::prepare_enum_variant_enum(variants);

    let mut variant_arms = Vec::new();
    for (i, variant) in variants.iter().enumerate() {
        if variant.attrs.skip_deserializing() {
            continue;
        }
        let variant_index = field_i(i);

        let block = Match(enum_untagged::deserialize_variant(params, variant, cattrs));

        variant_arms.push(quote! {
            __Field::#variant_index => #block
        });
    }

    let rust_name = params.type_name();
    let expecting = format!("adjacently tagged enum {}", rust_name);
    let expecting = cattrs.expecting().unwrap_or(&expecting);
    let type_name = cattrs.name().deserialize_name();
    let deny_unknown_fields = cattrs.deny_unknown_fields();

    // If unknown fields are allowed, we pick the visitor that can step over
    // those. Otherwise we pick the visitor that fails on unknown keys.
    let field_visitor_ty = if deny_unknown_fields {
        quote! { _serde::#private::de::TagOrContentFieldVisitor }
    } else {
        quote! { _serde::#private::de::TagContentOtherFieldVisitor }
    };

    let mut missing_content = quote! {
        _serde::#private::Err(<__A::Error as _serde::de::Error>::missing_field(#content))
    };
    let mut missing_content_fallthrough = quote!();
    let mut missing_content_arms = Vec::new();
    for (i, variant) in variants.iter().enumerate() {
        if variant.attrs.skip_deserializing() {
            continue;
        }
        let variant_index = field_i(i);
        let variant_ident = &variant.ident;

        let arm = match variant.style {
            Style::Unit => quote! {
                _serde::#private::Ok(#this_value::#variant_ident)
            },
            Style::Newtype if variant.attrs.deserialize_with().is_none() => {
                let span = variant.original.span();
                let func = quote_spanned!(span=> _serde::#private::de::missing_field);
                quote! {
                    #func(#content).map(#this_value::#variant_ident)
                }
            }
            _ => {
                missing_content_fallthrough = quote!(_ => #missing_content);
                continue;
            }
        };
        missing_content_arms.push(quote! {
            __Field::#variant_index => #arm,
        });
    }

    if !missing_content_arms.is_empty() {
        missing_content = quote! {
            match __field {
                #(#missing_content_arms)*
                #missing_content_fallthrough
            }
        };
    }

    // Advance the map by one key, returning early in case of error.
    let next_key = quote! {
        _serde::de::MapAccess::next_key_seed(&mut __map, #field_visitor_ty {
            tag: #tag,
            content: #content,
        })?
    };

    let variant_from_map = quote! {
        _serde::de::MapAccess::next_value_seed(&mut __map, _serde::#private::de::AdjacentlyTaggedEnumVariantSeed::<__Field> {
            enum_name: #rust_name,
            variants: VARIANTS,
            fields_enum: _serde::#private::PhantomData
        })?
    };

    // When allowing unknown fields, we want to transparently step through keys
    // we don't care about until we find `tag`, `content`, or run out of keys.
    let next_relevant_key = if deny_unknown_fields {
        next_key
    } else {
        quote!({
            let mut __rk : _serde::#private::Option<_serde::#private::de::TagOrContentField> = _serde::#private::None;
            while let _serde::#private::Some(__k) = #next_key {
                match __k {
                    _serde::#private::de::TagContentOtherField::Other => {
                        let _ = _serde::de::MapAccess::next_value::<_serde::de::IgnoredAny>(&mut __map)?;
                        continue;
                    },
                    _serde::#private::de::TagContentOtherField::Tag => {
                        __rk = _serde::#private::Some(_serde::#private::de::TagOrContentField::Tag);
                        break;
                    }
                    _serde::#private::de::TagContentOtherField::Content => {
                        __rk = _serde::#private::Some(_serde::#private::de::TagOrContentField::Content);
                        break;
                    }
                }
            }

            __rk
        })
    };

    // Step through remaining keys, looking for duplicates of previously-seen
    // keys. When unknown fields are denied, any key that isn't a duplicate will
    // at this point immediately produce an error.
    let visit_remaining_keys = quote! {
        match #next_relevant_key {
            _serde::#private::Some(_serde::#private::de::TagOrContentField::Tag) => {
                _serde::#private::Err(<__A::Error as _serde::de::Error>::duplicate_field(#tag))
            }
            _serde::#private::Some(_serde::#private::de::TagOrContentField::Content) => {
                _serde::#private::Err(<__A::Error as _serde::de::Error>::duplicate_field(#content))
            }
            _serde::#private::None => _serde::#private::Ok(__ret),
        }
    };

    let finish_content_then_tag = if variant_arms.is_empty() {
        quote! {
            match #variant_from_map {}
        }
    } else {
        quote! {
            let __seed = __Seed {
                variant: #variant_from_map,
                marker: _serde::#private::PhantomData,
                lifetime: _serde::#private::PhantomData,
            };
            let __deserializer = _serde::#private::de::ContentDeserializer::<__A::Error>::new(__content);
            let __ret = _serde::de::DeserializeSeed::deserialize(__seed, __deserializer)?;
            // Visit remaining keys, looking for duplicates.
            #visit_remaining_keys
        }
    };

    quote_block! {
        #variant_visitor

        #variants_stmt

        #[doc(hidden)]
        struct __Seed #de_impl_generics #where_clause {
            variant: __Field,
            marker: _serde::#private::PhantomData<#this_type #ty_generics>,
            lifetime: _serde::#private::PhantomData<&#delife ()>,
        }

        #[automatically_derived]
        impl #de_impl_generics _serde::de::DeserializeSeed<#delife> for __Seed #de_ty_generics #where_clause {
            type Value = #this_type #ty_generics;

            fn deserialize<__D>(self, __deserializer: __D) -> _serde::#private::Result<Self::Value, __D::Error>
            where
                __D: _serde::Deserializer<#delife>,
            {
                match self.variant {
                    #(#variant_arms)*
                }
            }
        }

        #[doc(hidden)]
        struct __Visitor #de_impl_generics #where_clause {
            marker: _serde::#private::PhantomData<#this_type #ty_generics>,
            lifetime: _serde::#private::PhantomData<&#delife ()>,
        }

        #[automatically_derived]
        impl #de_impl_generics _serde::de::Visitor<#delife> for __Visitor #de_ty_generics #where_clause {
            type Value = #this_type #ty_generics;

            fn expecting(&self, __formatter: &mut _serde::#private::Formatter) -> _serde::#private::fmt::Result {
                _serde::#private::Formatter::write_str(__formatter, #expecting)
            }

            fn visit_map<__A>(self, mut __map: __A) -> _serde::#private::Result<Self::Value, __A::Error>
            where
                __A: _serde::de::MapAccess<#delife>,
            {
                // Visit the first relevant key.
                match #next_relevant_key {
                    // First key is the tag.
                    _serde::#private::Some(_serde::#private::de::TagOrContentField::Tag) => {
                        // Parse the tag.
                        let __field = #variant_from_map;
                        // Visit the second key.
                        match #next_relevant_key {
                            // Second key is a duplicate of the tag.
                            _serde::#private::Some(_serde::#private::de::TagOrContentField::Tag) => {
                                _serde::#private::Err(<__A::Error as _serde::de::Error>::duplicate_field(#tag))
                            }
                            // Second key is the content.
                            _serde::#private::Some(_serde::#private::de::TagOrContentField::Content) => {
                                let __ret = _serde::de::MapAccess::next_value_seed(&mut __map,
                                    __Seed {
                                        variant: __field,
                                        marker: _serde::#private::PhantomData,
                                        lifetime: _serde::#private::PhantomData,
                                    })?;
                                // Visit remaining keys, looking for duplicates.
                                #visit_remaining_keys
                            }
                            // There is no second key; might be okay if the we have a unit variant.
                            _serde::#private::None => #missing_content
                        }
                    }
                    // First key is the content.
                    _serde::#private::Some(_serde::#private::de::TagOrContentField::Content) => {
                        // Buffer up the content.
                        let __content = _serde::de::MapAccess::next_value_seed(&mut __map, _serde::#private::de::ContentVisitor::new())?;
                        // Visit the second key.
                        match #next_relevant_key {
                            // Second key is the tag.
                            _serde::#private::Some(_serde::#private::de::TagOrContentField::Tag) => {
                                #finish_content_then_tag
                            }
                            // Second key is a duplicate of the content.
                            _serde::#private::Some(_serde::#private::de::TagOrContentField::Content) => {
                                _serde::#private::Err(<__A::Error as _serde::de::Error>::duplicate_field(#content))
                            }
                            // There is no second key.
                            _serde::#private::None => {
                                _serde::#private::Err(<__A::Error as _serde::de::Error>::missing_field(#tag))
                            }
                        }
                    }
                    // There is no first key.
                    _serde::#private::None => {
                        _serde::#private::Err(<__A::Error as _serde::de::Error>::missing_field(#tag))
                    }
                }
            }

            fn visit_seq<__A>(self, mut __seq: __A) -> _serde::#private::Result<Self::Value, __A::Error>
            where
                __A: _serde::de::SeqAccess<#delife>,
            {
                // Visit the first element - the tag.
                match _serde::de::SeqAccess::next_element(&mut __seq) {
                    _serde::#private::Ok(_serde::#private::Some(__variant)) => {
                        // Visit the second element - the content.
                        match _serde::de::SeqAccess::next_element_seed(
                            &mut __seq,
                            __Seed {
                                variant: __variant,
                                marker: _serde::#private::PhantomData,
                                lifetime: _serde::#private::PhantomData,
                            },
                        ) {
                            _serde::#private::Ok(_serde::#private::Some(__ret)) => _serde::#private::Ok(__ret),
                            // There is no second element.
                            _serde::#private::Ok(_serde::#private::None) => {
                                _serde::#private::Err(_serde::de::Error::invalid_length(1, &self))
                            }
                            _serde::#private::Err(__err) => _serde::#private::Err(__err),
                        }
                    }
                    // There is no first element.
                    _serde::#private::Ok(_serde::#private::None) => {
                        _serde::#private::Err(_serde::de::Error::invalid_length(0, &self))
                    }
                    _serde::#private::Err(__err) => _serde::#private::Err(__err),
                }
            }
        }

        #[doc(hidden)]
        const FIELDS: &'static [&'static str] = &[#tag, #content];
        _serde::Deserializer::deserialize_struct(
            __deserializer,
            #type_name,
            FIELDS,
            __Visitor {
                marker: _serde::#private::PhantomData::<#this_type #ty_generics>,
                lifetime: _serde::#private::PhantomData,
            },
        )
    }
}


import path from 'node:path'
import legacy from '@vitejs/plugin-legacy'
import { defineConfig } from 'vite'

export default defineConfig(({ isPreview }) => ({
  input: {
    index: path.resolve(import.meta.dirname, 'modern-target.html'),
  },
  base: !isPreview ? './' : '/modern-target/',
  plugins: [
    legacy({
      modernPolyfills: ['es.array.at'],
      // a browser without optional catch binding
      modernTargets: ['chrome >= 64'],
      renderLegacyChunks: false,
    }),
  ],
  build: {
    outDir: 'dist/modern-target',
  },
}))


    <picture>
      <source media="(prefers-color-scheme: dark)" srcset="https://vite.dev/vite-light.svg">
      <source media="(prefers-color-scheme: light)" srcset="https://vite.dev/vite-dark.svg">

    </picture>

# Vite ⚡

> Next Generation Frontend Tooling

- 💡 Instant Server Start
- ⚡ Lightning Fast HMR
- 🛠 Rich Features
- 📦 Optimized Build
- 🔩 Universal Plugin Interface
- 🔑 Fully Typed APIs

Vite (French word for "quick", pronounced [`/viːt/`](https://cdn.jsdelivr.net/gh/vitejs/vite@main/docs/public/vite.mp3), like "veet") is a build tool that aims to provide a faster and leaner development experience for modern web projects. It consists of two major parts:

- A dev server that provides [rich feature enhancements](https://vite.dev/guide/features) over [native ES modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules), for example extremely fast [Hot Module Replacement (HMR)](https://vite.dev/guide/features#hot-module-replacement).

- A build command that bundles your code with [Rolldown](https://rolldown.rs), pre-configured to output highly optimized static assets for production.

In addition, Vite is highly extensible via its [Plugin API](https://vite.dev/guide/api-plugin.html) and [JavaScript API](https://vite.dev/guide/api-javascript.html) with full typing support.

[Read the Docs to Learn More](https://vite.dev).

## Packages

| Package                                         | Version (click for changelogs)                                                                                |
| ----------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------- |
| [vite](packages/vite)                           | [![vite version](https://img.shields.io/npm/v/vite.svg?label=%20)](packages/vite/CHANGELOG.md)                                    |
| [@vitejs/plugin-legacy](packages/plugin-legacy) | [![plugin-legacy version](https://img.shields.io/npm/v/@vitejs/plugin-legacy.svg?label=%20)](packages/plugin-legacy/CHANGELOG.md) |
| [create-vite](packages/create-vite)             | [![create-vite version](https://img.shields.io/npm/v/create-vite.svg?label=%20)](packages/create-vite/CHANGELOG.md)               |

## Contribution

See [Contributing Guide](CONTRIBUTING.md).

## License

[MIT](LICENSE).

## Sponsors


#![allow(clippy::trivially_copy_pass_by_ref, dead_code)]

use serde_derive::Deserialize;

macro_rules! declare_in_macro {
    ($with:literal) => {
        #[derive(Deserialize)]
        pub struct S(
            #[serde(with = $with)]
            #[allow(dead_code)]
            i32,
        );
    };
}

declare_in_macro!("with");

mod with {
    use serde::Deserializer;

    pub fn deserialize<'de, D>(_: D) -> Result<i32, D::Error>
    where
        D: Deserializer<'de>,
    {
        unimplemented!()
    }
}


// Formatting library for C++ - ranges tests
//
// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors
// All rights reserved.
//
// For the license information refer to format.h.

#include "fmt/ranges.h"

#include <array>
#include <list>
#include <map>
#include <queue>
#include <stack>
#include <string>
#include <utility>
#include <vector>

#if FMT_CPLUSPLUS > 201703L && FMT_HAS_INCLUDE(<ranges>)
#  include <ranges>
#endif

#include "fmt/format.h"
#include "gtest/gtest.h"

#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 601
#  define FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
#endif

#ifdef FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
TEST(ranges_test, format_array) {
  int arr[] = {1, 2, 3, 5, 7, 11};
  EXPECT_EQ(fmt::format("{}", arr), "[1, 2, 3, 5, 7, 11]");
}

TEST(ranges_test, format_2d_array) {
  int arr[][2] = {{1, 2}, {3, 5}, {7, 11}};
  EXPECT_EQ(fmt::format("{}", arr), "[[1, 2], [3, 5], [7, 11]]");
}

TEST(ranges_test, format_array_of_literals) {
  const char* arr[] = {"1234", "abcd"};
  EXPECT_EQ(fmt::format("{}", arr), "[\"1234\", \"abcd\"]");
  EXPECT_EQ(fmt::format("{:n}", arr), "\"1234\", \"abcd\"");
  EXPECT_EQ(fmt::format("{:n:}", arr), "1234, abcd");
}
#endif  // FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY

struct unformattable {};

TEST(ranges_test, format_vector) {
  auto v = std::vector<int>{1, 2, 3, 5, 7, 11};
  EXPECT_EQ(fmt::format("{}", v), "[1, 2, 3, 5, 7, 11]");
  EXPECT_EQ(fmt::format("{::#x}", v), "[0x1, 0x2, 0x3, 0x5, 0x7, 0xb]");
  EXPECT_EQ(fmt::format("{:n:#x}", v), "0x1, 0x2, 0x3, 0x5, 0x7, 0xb");

  auto vc = std::vector<char>{'a', 'b', 'c'};
  auto vec = std::vector<char>{'a', '\n', '\t'};
  auto vvc = std::vector<std::vector<char>>{vc, vc};
  EXPECT_EQ(fmt::format("{}", vc), "['a', 'b', 'c']");
  EXPECT_EQ(fmt::format("{:s}", vc), "\"abc\"");
  EXPECT_EQ(fmt::format("{:?s}", vec), "\"a\\n\\t\"");
  EXPECT_EQ(fmt::format("{:s}", vec), "\"a\n\t\"");
  EXPECT_EQ(fmt::format("{::s}", vvc), "[\"abc\", \"abc\"]");
  EXPECT_EQ(fmt::format("{}", vvc), "[['a', 'b', 'c'], ['a', 'b', 'c']]");
  EXPECT_EQ(fmt::format("{:n}", vvc), "['a', 'b', 'c'], ['a', 'b', 'c']");
  EXPECT_EQ(fmt::format("{:n:n}", vvc), "'a', 'b', 'c', 'a', 'b', 'c'");
  EXPECT_EQ(fmt::format("{:n:n:}", vvc), "a, b, c, a, b, c");

  EXPECT_FALSE(fmt::is_formattable<unformattable>::value);
  EXPECT_FALSE(fmt::is_formattable<std::vector<unformattable>>::value);
}

TEST(ranges_test, format_nested_vector) {
  auto v = std::vector<std::vector<int>>{{1, 2}, {3, 5}, {7, 11}};
  EXPECT_EQ(fmt::format("{}", v), "[[1, 2], [3, 5], [7, 11]]");
  EXPECT_EQ(fmt::format("{:::#x}", v), "[[0x1, 0x2], [0x3, 0x5], [0x7, 0xb]]");
  EXPECT_EQ(fmt::format("{:n:n:#x}", v), "0x1, 0x2, 0x3, 0x5, 0x7, 0xb");
}

TEST(ranges_test, to_string_vector) {
  auto v = std::vector<std::string>{"a", "b", "c"};
  EXPECT_EQ(fmt::to_string(v), "[\"a\", \"b\", \"c\"]");
}

TEST(ranges_test, format_map) {
  auto m = std::map<std::string, int>{{"one", 1}, {"two", 2}};
  EXPECT_EQ(fmt::format("{}", m), "{\"one\": 1, \"two\": 2}");
  EXPECT_EQ(fmt::format("{:n}", m), "\"one\": 1, \"two\": 2");

  EXPECT_FALSE((fmt::is_formattable<std::map<int, unformattable>>::value));
}

struct test_map_value {};

FMT_BEGIN_NAMESPACE
template <> struct formatter<test_map_value> : formatter<string_view> {
  auto format(test_map_value, format_context& ctx) const
      -> format_context::iterator {
    return formatter<string_view>::format("foo", ctx);
  }
};

template <typename K>
struct formatter<std::pair<K, test_map_value>> : formatter<string_view> {
  auto format(std::pair<K, test_map_value>, format_context& ctx) const
      -> format_context::iterator {
    return ctx.out();
  }
};

template <typename K>
struct is_tuple_formattable<std::pair<K, test_map_value>, char>
    : std::false_type {};

FMT_END_NAMESPACE

TEST(ranges_test, format_map_custom_pair) {
  EXPECT_EQ(fmt::format("{}", std::map<int, test_map_value>{{42, {}}}),
            "{42: \"foo\"}");
}

TEST(ranges_test, format_set) {
  EXPECT_EQ(fmt::format("{}", std::set<std::string>{"one", "two"}),
            "{\"one\", \"two\"}");
}

// Models std::flat_set close enough to test if no ambiguous lookup of a
// formatter happens due to the flat_set type matching is_set and
// is_container_adaptor.
template <typename T> class flat_set {
 public:
  using key_type = T;
  using container_type = std::vector<T>;

  using iterator = typename std::vector<T>::iterator;
  using const_iterator = typename std::vector<T>::const_iterator;

  template <typename... Ts>
  explicit flat_set(Ts&&... args) : c{std::forward<Ts>(args)...} {}

  auto begin() -> iterator { return c.begin(); }
  auto end() -> iterator { return c.end(); }

  auto begin() const -> const_iterator { return c.begin(); }
  auto end() const -> const_iterator { return c.end(); }

 private:
  std::vector<T> c;
};

TEST(ranges_test, format_flat_set) {
  EXPECT_EQ(fmt::format("{}", flat_set<std::string>{"one", "two"}),
            "{\"one\", \"two\"}");
  EXPECT_FALSE(fmt::is_formattable<flat_set<unformattable>>::value);
}

namespace adl {
struct box {
  int value;
};

auto begin(const box& b) -> const int* { return &b.value; }
auto end(const box& b) -> const int* { return &b.value + 1; }
}  // namespace adl

TEST(ranges_test, format_adl_begin_end) {
  auto b = adl::box{42};
  EXPECT_EQ(fmt::format("{}", b), "[42]");
}

TEST(ranges_test, format_pair) {
  auto p = std::pair<int, float>(42, 1.5f);
  EXPECT_EQ(fmt::format("{}", p), "(42, 1.5)");
  EXPECT_EQ(fmt::format("{:}", p), "(42, 1.5)");
  EXPECT_EQ(fmt::format("{:n}", p), "421.5");
}

TEST(ranges_test, format_tuple) {
  auto t =
      std::tuple<int, float, std::string, char>(42, 1.5f, "this is tuple", 'i');
  EXPECT_EQ(fmt::format("{}", t), "(42, 1.5, \"this is tuple\", 'i')");
  EXPECT_EQ(fmt::format("{:n}", t), "421.5\"this is tuple\"'i'");

  EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()");

  EXPECT_TRUE((fmt::is_formattable<std::tuple<>>::value));
  EXPECT_FALSE((fmt::is_formattable<std::tuple<unformattable>>::value));
  EXPECT_FALSE((fmt::is_formattable<std::tuple<unformattable, int>>::value));
  EXPECT_FALSE((fmt::is_formattable<std::tuple<int, unformattable>>::value));
  EXPECT_FALSE(
      (fmt::is_formattable<std::tuple<unformattable, unformattable>>::value));
  EXPECT_TRUE((fmt::is_formattable<std::tuple<int, float>>::value));
}

struct not_default_formattable {};
struct bad_format {};

FMT_BEGIN_NAMESPACE
template <> struct formatter<not_default_formattable> {
  auto parse(format_parse_context&) -> const char* { throw bad_format(); }
  auto format(not_default_formattable, format_context& ctx)
      -> format_context::iterator {
    return ctx.out();
  }
};
FMT_END_NAMESPACE

TEST(ranges_test, tuple_parse_calls_element_parse) {
  auto f = fmt::formatter<std::tuple<not_default_formattable>>();
  auto ctx = fmt::format_parse_context("");
  EXPECT_THROW(f.parse(ctx), bad_format);
}

struct tuple_like {
  int i;
  std::string str;

  template <size_t N>
  auto get() const noexcept -> fmt::enable_if_t<N == 0, int> {
    return i;
  }
  template <size_t N>
  auto get() const noexcept -> fmt::enable_if_t<N == 1, fmt::string_view> {
    return str;
  }
};

template <size_t N>
auto get(const tuple_like& t) noexcept -> decltype(t.get<N>()) {
  return t.get<N>();
}

// https://github.com/llvm/llvm-project/issues/39218
FMT_PRAGMA_CLANG(diagnostic ignored "-Wmismatched-tags")

namespace std {
template <>
struct tuple_size<tuple_like> : public std::integral_constant<size_t, 2> {};

template <size_t N> struct tuple_element<N, tuple_like> {
  using type = decltype(std::declval<tuple_like>().get<N>());
};
}  // namespace std

TEST(ranges_test, format_struct) {
  auto t = tuple_like{42, "foo"};
  EXPECT_EQ(fmt::format("{}", t), "(42, \"foo\")");
}

TEST(ranges_test, format_to) {
  char buf[10];
  auto end = fmt::format_to(buf, "{}", std::vector<int>{1, 2, 3});
  *end = '\0';
  EXPECT_STREQ(buf, "[1, 2, 3]");
}

template <typename Char> struct path_like {
  auto begin() const -> const path_like*;
  auto end() const -> const path_like*;

  operator std::basic_string<Char>() const;
};

TEST(ranges_test, disabled_range_formatting_of_path) {
  // Range formatting of path is disabled because of infinite recursion
  // (path element is itself a path).
  EXPECT_EQ((fmt::range_format_kind<path_like<char>, char>::value),
            fmt::range_format::disabled);
  EXPECT_EQ((fmt::range_format_kind<path_like<wchar_t>, char>::value),
            fmt::range_format::disabled);
}

template <typename T> struct optional_like {
  auto begin() const -> const T*;
  auto end() const -> const T*;

  bool has_value() const noexcept;
  T& value() &;
  const T& value() const&;
  T&& value() &&;
  const T&& value() const&&;
};

TEST(ranges_test, disabled_range_formatting_of_optional) {
  // (C++26) Has a range support for std::optional.
  EXPECT_EQ((fmt::range_format_kind<optional_like<int>, char>::value),
            fmt::range_format::disabled);
}

struct vector_string : std::vector<char> {
  using base = std::vector<char>;
  using base::base;
};
struct vector_debug_string : std::vector<char> {
  using base = std::vector<char>;
  using base::base;
};
FMT_BEGIN_NAMESPACE
template <>
struct range_format_kind<vector_string, char>
    : std::integral_constant<range_format, range_format::string> {};
template <>
struct range_format_kind<vector_debug_string, char>
    : std::integral_constant<range_format, range_format::debug_string> {};
FMT_END_NAMESPACE

TEST(ranges_test, range_format_string) {
  const vector_string v{'f', 'o', 'o'};
  EXPECT_EQ(fmt::format("{}", v), "foo");
}

TEST(ranges_test, range_format_debug_string) {
  const vector_debug_string v{'f', 'o', 'o'};
  EXPECT_EQ(fmt::format("{}", v), "\"foo\"");
}

// A range that provides non-const only begin()/end() to test fmt::join
// handles that.
//
// Some ranges (e.g. those produced by range-v3's views::filter()) can cache
// information during iteration so they only provide non-const begin()/end().
template <typename T> class non_const_only_range {
 private:
  std::vector<T> vec;

 public:
  using const_iterator = typename ::std::vector<T>::const_iterator;

  template <typename... Args>
  explicit non_const_only_range(Args&&... args)
      : vec(std::forward<Args>(args)...) {}

  auto begin() -> const_iterator { return vec.begin(); }
  auto end() -> const_iterator { return vec.end(); }
};

template <typename T> class noncopyable_range {
 private:
  std::vector<T> vec;

 public:
  using iterator = typename ::std::vector<T>::iterator;

  template <typename... Args>
  explicit noncopyable_range(Args&&... args)
      : vec(std::forward<Args>(args)...) {}

  noncopyable_range(const noncopyable_range&) = delete;
  noncopyable_range(noncopyable_range&) = delete;

  auto begin() -> iterator { return vec.begin(); }
  auto end() -> iterator { return vec.end(); }
};

TEST(ranges_test, range) {
  auto&& w = noncopyable_range<int>(3u, 0);
  EXPECT_EQ(fmt::format("{}", w), "[0, 0, 0]");
  EXPECT_EQ(fmt::format("{}", noncopyable_range<int>(3u, 0)), "[0, 0, 0]");

  auto x = non_const_only_range<int>(3u, 0);
  EXPECT_EQ(fmt::format("{}", x), "[0, 0, 0]");
  EXPECT_EQ(fmt::format("{}", non_const_only_range<int>(3u, 0)), "[0, 0, 0]");

  auto y = std::vector<int>(3u, 0);
  EXPECT_EQ(fmt::format("{}", y), "[0, 0, 0]");
  EXPECT_EQ(fmt::format("{}", std::vector<int>(3u, 0)), "[0, 0, 0]");

  const auto z = std::vector<int>(3u, 0);
  EXPECT_EQ(fmt::format("{}", z), "[0, 0, 0]");
}

enum test_enum { foo, bar };
auto format_as(test_enum e) -> int { return e; }

TEST(ranges_test, enum_range) {
  auto v = std::vector<test_enum>{test_enum::foo};
  EXPECT_EQ(fmt::format("{}", v), "[0]");
}

#if !FMT_MSC_VERSION
TEST(ranges_test, unformattable_range) {
  EXPECT_FALSE((fmt::is_formattable<std::vector<unformattable>, char>::value));
}
#endif

TEST(ranges_test, join) {
  using fmt::join;
  int v1[3] = {1, 2, 3};
  auto v2 = std::vector<float>();
  v2.push_back(1.2f);
  v2.push_back(3.4f);
  void* v3[2] = {&v1[0], &v1[1]};

  EXPECT_EQ(fmt::format("({})", join(v1, v1 + 3, ", ")), "(1, 2, 3)");
  EXPECT_EQ(fmt::format("({})", join(v1, v1 + 1, ", ")), "(1)");
  EXPECT_EQ(fmt::format("({})", join(v1, v1, ", ")), "()");
  EXPECT_EQ(fmt::format("({:03})", join(v1, v1 + 3, ", ")), "(001, 002, 003)");
  EXPECT_EQ("(+01.20, +03.40)",
            fmt::format("({:+06.2f})", join(v2.begin(), v2.end(), ", ")));

  EXPECT_EQ(fmt::format("{0:{1}}", join(v1, v1 + 3, ", "), 1), "1, 2, 3");

  EXPECT_EQ(fmt::format("{}, {}", v3[0], v3[1]),
            fmt::format("{}", join(v3, v3 + 2, ", ")));

  EXPECT_EQ(fmt::format("({})", join(v1, ", ")), "(1, 2, 3)");
  EXPECT_EQ(fmt::format("({:+06.2f})", join(v2, ", ")), "(+01.20, +03.40)");

  auto v4 = std::vector<test_enum>{foo, bar, foo};
  EXPECT_EQ(fmt::format("{}", join(v4, " ")), "0 1 0");
}

TEST(ranges_test, join_tuple) {
  // Value tuple args.
  auto t1 = std::tuple<char, int, float>('a', 1, 2.0f);
  EXPECT_EQ(fmt::format("({})", fmt::join(t1, ", ")), "(a, 1, 2)");

  // Testing lvalue tuple args.
  int x = 4;
  auto t2 = std::tuple<char, int&>('b', x);
  EXPECT_EQ(fmt::format("{}", fmt::join(t2, " + ")), "b + 4");

  // Empty tuple.
  auto t3 = std::tuple<>();
  EXPECT_EQ(fmt::format("{}", fmt::join(t3, "|")), "");

  // Single element tuple.
  auto t4 = std::tuple<float>(4.0f);
  EXPECT_EQ(fmt::format("{}", fmt::join(t4, "/")), "4");

  // Tuple-like.
  auto t5 = tuple_like{42, "foo"};
  EXPECT_EQ(fmt::format("{}", fmt::join(t5, ", ")), "42, foo");

#if FMT_TUPLE_JOIN_SPECIFIERS
  // Specs applied to each element.
  auto t5 = std::tuple<int, int, long>(-3, 100, 1);
  EXPECT_EQ(fmt::format("{:+03}", fmt::join(t5, ", ")), "-03, +100, +01");

  auto t6 = std::tuple<float, double, long double>(3, 3.14, 3.1415);
  EXPECT_EQ(fmt::format("{:5.5f}", fmt::join(t6, ", ")),
            "3.00000, 3.14000, 3.14150");

  // Testing lvalue tuple args.
  int y = -1;
  auto t7 = std::tuple<int, int&, const int&>(3, y, y);
  EXPECT_EQ(fmt::format("{:03}", fmt::join(t7, ", ")), "003, -01, -01");
#endif
}

struct zstring_sentinel {};

bool operator==(const char* p, zstring_sentinel) { return *p == '\0'; }
bool operator!=(const char* p, zstring_sentinel) { return *p != '\0'; }

struct zstring {
  const char* p;
  auto begin() const -> const char* { return p; }
  auto end() const -> zstring_sentinel { return {}; }
};

#ifdef __cpp_lib_ranges
struct cpp20_only_range {
  struct iterator {
    int val = 0;

    using value_type = int;
    using difference_type = std::ptrdiff_t;
    using iterator_concept = std::input_iterator_tag;

    iterator() = default;
    iterator(int i) : val(i) {}
    auto operator*() const -> int { return val; }
    auto operator++() -> iterator& {
      ++val;
      return *this;
    }
    void operator++(int) { ++*this; }
    auto operator==(const iterator& rhs) const -> bool {
      return val == rhs.val;
    }
  };

  int lo;
  int hi;

  auto begin() const -> iterator { return iterator(lo); }
  auto end() const -> iterator { return iterator(hi); }
};

static_assert(std::input_iterator<cpp20_only_range::iterator>);
#endif

TEST(ranges_test, join_sentinel) {
  auto hello = zstring{"hello"};
  EXPECT_EQ(fmt::format("{}", hello), "['h', 'e', 'l', 'l', 'o']");
  EXPECT_EQ(fmt::format("{::}", hello), "[h, e, l, l, o]");
  EXPECT_EQ(fmt::format("{}", fmt::join(hello, "_")), "h_e_l_l_o");
}

TEST(ranges_test, join_range) {
  auto&& w = noncopyable_range<int>(3u, 0);
  EXPECT_EQ(fmt::format("{}", fmt::join(w, ",")), "0,0,0");
  EXPECT_EQ(fmt::format("{}", fmt::join(noncopyable_range<int>(3u, 0), ",")),
            "0,0,0");

  auto x = non_const_only_range<int>(3u, 0);
  EXPECT_EQ(fmt::format("{}", fmt::join(x, ",")), "0,0,0");
  EXPECT_EQ(fmt::format("{}", fmt::join(non_const_only_range<int>(3u, 0), ",")),
            "0,0,0");

  auto y = std::vector<int>(3u, 0);
  EXPECT_EQ(fmt::format("{}", fmt::join(y, ",")), "0,0,0");
  EXPECT_EQ(fmt::format("{}", fmt::join(std::vector<int>(3u, 0), ",")),
            "0,0,0");

  const auto z = std::vector<int>(3u, 0);
  EXPECT_EQ(fmt::format("{}", fmt::join(z, ",")), "0,0,0");

#ifdef __cpp_lib_ranges
  EXPECT_EQ(fmt::format("{}", cpp20_only_range{.lo = 0, .hi = 5}),
            "[0, 1, 2, 3, 4]");
  EXPECT_EQ(
      fmt::format("{}", fmt::join(cpp20_only_range{.lo = 0, .hi = 5}, ",")),
      "0,1,2,3,4");
#endif
}

namespace adl {
struct vec {
  int n[2] = {42, 43};
};

auto begin(const vec& v) -> const int* { return v.n; }
auto end(const vec& v) -> const int* { return v.n + 2; }
}  // namespace adl

TEST(ranges_test, format_join_adl_begin_end) {
  EXPECT_EQ(fmt::format("{}", fmt::join(adl::vec(), "/")), "42/43");
}

#if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 202207L
TEST(ranges_test, nested_ranges) {
  auto l = std::list{1, 2, 3};
  auto r = std::views::iota(0, 3) | std::views::transform([&l](auto i) {
             return std::views::take(std::ranges::subrange(l), i);
           }) |
           std::views::transform(std::views::reverse);
  EXPECT_EQ(fmt::format("{}", r), "[[], [1], [2, 1]]");
}
#endif

TEST(ranges_test, is_printable) {
  using fmt::detail::is_printable;
  EXPECT_TRUE(is_printable(0x0323));
  EXPECT_FALSE(is_printable(0x0378));
  EXPECT_FALSE(is_printable(0x110000));
}

TEST(ranges_test, escape) {
  using vec = std::vector<std::string>;
  EXPECT_EQ(fmt::format("{}", vec{"\n\r\t\"\\"}), "[\"\\n\\r\\t\\\"\\\\\"]");
  EXPECT_EQ(fmt::format("{}", vec{"\x07"}), "[\"\\x07\"]");
  EXPECT_EQ(fmt::format("{}", vec{"\x7f"}), "[\"\\x7f\"]");
  EXPECT_EQ(fmt::format("{}", vec{"n\xcc\x83"}), "[\"n\xcc\x83\"]");

  if (fmt::detail::use_utf8) {
    EXPECT_EQ(fmt::format("{}", vec{"\xcd\xb8"}), "[\"\\u0378\"]");
    // Unassigned Unicode code points.
    EXPECT_EQ(fmt::format("{}", vec{"\xf0\xaa\x9b\x9e"}), "[\"\\U0002a6de\"]");
    // Broken utf-8.
    EXPECT_EQ(fmt::format("{}", vec{"\xf4\x8f\xbf\xc0"}),
              "[\"\\xf4\\x8f\\xbf\\xc0\"]");
    EXPECT_EQ(fmt::format("{}", vec{"\xf0\x28"}), "[\"\\xf0(\"]");
    EXPECT_EQ(fmt::format("{}", vec{"\xe1\x28"}), "[\"\\xe1(\"]");
    EXPECT_EQ(fmt::format("{}", vec{std::string("\xf0\x28\0\0anything", 12)}),
              "[\"\\xf0(\\x00\\x00anything\"]");

    // Correct utf-8.
    EXPECT_EQ(fmt::format("{}", vec{"🦄"}), "[\"🦄\"]");
  }

  EXPECT_EQ(fmt::format("{}", std::vector<std::vector<char>>{{'x'}}),
            "[['x']]");

// Disabled due to a clang 17 bug: https://github.com/fmtlib/fmt/issues/4144.
#if FMT_CLANG_VERSION >= 1800
  EXPECT_EQ(fmt::format("{}", std::tuple<std::vector<char>>{{'x'}}), "(['x'])");
#endif
}

template <typename R> struct fmt_ref_view {
  R* r;

  auto begin() const -> decltype(r->begin()) { return r->begin(); }
  auto end() const -> decltype(r->end()) { return r->end(); }
};

TEST(ranges_test, range_of_range_of_mixed_const) {
  auto v = std::vector<std::vector<int>>{{1, 2, 3}, {4, 5}};
  EXPECT_EQ(fmt::format("{}", v), "[[1, 2, 3], [4, 5]]");

  auto r = fmt_ref_view<decltype(v)>{&v};
  EXPECT_EQ(fmt::format("{}", r), "[[1, 2, 3], [4, 5]]");
}

TEST(ranges_test, vector_char) {
  EXPECT_EQ(fmt::format("{}", std::vector<char>{'a', 'b'}), "['a', 'b']");
}

TEST(ranges_test, container_adaptor) {
  {
    using T = std::nullptr_t;
    static_assert(fmt::is_container_adaptor<std::stack<T>>::value, "");
    static_assert(fmt::is_container_adaptor<std::queue<T>>::value, "");
    static_assert(fmt::is_container_adaptor<std::priority_queue<T>>::value, "");
    static_assert(!fmt::is_container_adaptor<std::vector<T>>::value, "");
  }

  {
    auto s = std::stack<int>();
    s.push(1);
    s.push(2);
    EXPECT_EQ(fmt::format("{}", s), "[1, 2]");
    EXPECT_EQ(fmt::format("{}", const_cast<const decltype(s)&>(s)), "[1, 2]");
  }

  {
    auto q = std::queue<int>();
    q.push(1);
    q.push(2);
    EXPECT_EQ(fmt::format("{}", q), "[1, 2]");
  }

  {
    auto q = std::priority_queue<int>();
    q.push(3);
    q.push(1);
    q.push(2);
    q.push(4);
    EXPECT_EQ(fmt::format("{}", q), "[4, 3, 2, 1]");
  }

  {
    auto s = std::stack<char, std::string>();
    s.push('a');
    s.push('b');
    // See https://cplusplus.github.io/LWG/issue3881.
    EXPECT_EQ(fmt::format("{}", s), "['a', 'b']");
  }

  {
    struct my_container_adaptor {
      using value_type = int;
      using container_type = std::vector<value_type>;
      void push(const value_type& v) { c.push_back(v); }

     protected:
      container_type c;
    };

    auto m = my_container_adaptor();
    m.push(1);
    m.push(2);
    EXPECT_EQ(fmt::format("{}", m), "[1, 2]");
  }

  EXPECT_FALSE(fmt::is_formattable<std::stack<unformattable>>::value);
}

struct tieable {
  int a = 3;
  double b = 0.42;
};

auto format_as(const tieable& t) -> std::tuple<int, double> {
  return std::tie(t.a, t.b);
}

TEST(ranges_test, format_as_tie) {
  EXPECT_EQ(fmt::format("{}", tieable()), "(3, 0.42)");
}

struct lvalue_qualified_begin_end {
  int arr[5] = {1, 2, 3, 4, 5};

  auto begin() & -> const int* { return arr; }
  auto end() & -> const int* { return arr + 5; }
};

TEST(ranges_test, lvalue_qualified_begin_end) {
  EXPECT_EQ(fmt::format("{}", lvalue_qualified_begin_end{}), "[1, 2, 3, 4, 5]");
}

#if !defined(__cpp_lib_ranges) || __cpp_lib_ranges <= 202106L
#  define ENABLE_STD_VIEWS_TESTS 0
#elif FMT_CLANG_VERSION
#  if FMT_CLANG_VERSION > 1500
#    define ENABLE_STD_VIEWS_TESTS 1
#  else
#    define ENABLE_STD_VIEWS_TESTS 0
#  endif
#else
#  define ENABLE_STD_VIEWS_TESTS 1
#endif

#if ENABLE_STD_VIEWS_TESTS
TEST(ranges_test, input_range_join) {
  auto iss = std::istringstream("1 2 3 4 5");
  auto view = std::views::istream<std::string>(iss);
  EXPECT_EQ("1, 2, 3, 4, 5",
            fmt::format("{}", fmt::join(view.begin(), view.end(), ", ")));
}

TEST(ranges_test, input_range_join_overload) {
  auto iss = std::istringstream("1 2 3 4 5");
  EXPECT_EQ(
      "1.2.3.4.5",
      fmt::format("{}", fmt::join(std::views::istream<std::string>(iss), ".")));
}

namespace views_filter_view_test {
struct codec_mask {
  static constexpr auto codecs = std::array{0, 1, 2, 3};
  int except = 0;
};

// A named functor instead of a lambda to avoid -Wsubobject-linkage: a lambda
// gives the filter_view type internal linkage, which propagates to the
// formatter base class via format_as.
struct not_equal {
  int value;
  bool operator()(int c) const { return c != value; }
};

auto format_as(codec_mask mask) {
  return codec_mask::codecs |
         std::views::filter(not_equal{mask.except});
}
}  // namespace views_filter_view_test

TEST(ranges_test, format_as_with_ranges_mutable_begin_end) {
  using namespace views_filter_view_test;
  {
    auto make_filter_view = []() {
      return codec_mask::codecs |
             std::views::filter([](auto c) { return c != 2; });
    };
    auto r = make_filter_view();
    EXPECT_EQ("[0, 1, 3]", fmt::format("{}", r));
    EXPECT_EQ("[0, 1, 3]", fmt::format("{}", make_filter_view()));
  }

  {
    auto mask = codec_mask{2};
    const auto const_mask = codec_mask{2};

    EXPECT_EQ("[0, 1, 3]", fmt::format("{}", mask));
    EXPECT_EQ("[0, 1, 3]", fmt::format("{}", const_mask));
    EXPECT_EQ("[0, 1, 3]", fmt::format("{}", codec_mask{2}));
  }
}

#endif

TEST(ranges_test, std_istream_iterator_join) {
  auto&& iss = std::istringstream("1 2 3 4 5");
  auto first = std::istream_iterator<int>(iss);
  auto last = std::istream_iterator<int>();
  EXPECT_EQ("1, 2, 3, 4, 5", fmt::format("{}", fmt::join(first, last, ", ")));
}

// Mirrors C++20 std::ranges::basic_istream_view::iterator.
struct noncopyable_istream_iterator : std::istream_iterator<int> {
  using base = std::istream_iterator<int>;
  explicit noncopyable_istream_iterator(std::istringstream& iss) : base{iss} {}
  noncopyable_istream_iterator(const noncopyable_istream_iterator&) = delete;
  noncopyable_istream_iterator(noncopyable_istream_iterator&&) = default;
};
static_assert(!std::is_copy_constructible<noncopyable_istream_iterator>::value,
              "");

TEST(ranges_test, movable_only_istream_iter_join) {
  auto&& iss = std::istringstream("1 2 3 4 5");
  auto first = noncopyable_istream_iterator(iss);
  auto last = std::istream_iterator<int>();
  EXPECT_EQ("1, 2, 3, 4, 5",
            fmt::format("{}", fmt::join(std::move(first), last, ", ")));
}

struct movable_iter_range {
  std::istringstream iss{"1 2 3 4 5"};
  noncopyable_istream_iterator begin() {
    return noncopyable_istream_iterator{iss};
  }
  std::istream_iterator<int> end() { return {}; }
};

TEST(ranges_test, movable_only_istream_iter_join2) {
  EXPECT_EQ("[1, 2, 3, 4, 5]", fmt::format("{}", movable_iter_range{}));
}

struct not_range {
  void begin() const {}
  void end() const {}
};
static_assert(!fmt::is_formattable<not_range>{}, "");

struct test_adaptor {
  using container_type = std::vector<int>;
  std::vector<int> c = {1, 2, 3};
};

namespace fmt {
template <> struct is_container_adaptor<test_adaptor> : std::false_type {};

template <> struct formatter<test_adaptor> : formatter<string_view> {
  auto format(const test_adaptor&, format_context& ctx) const
      -> format_context::iterator {
    return formatter<string_view>::format("test", ctx);
  }
};
}  // namespace fmt

TEST(ranges_test, container_adaptor_opt_out) {
  EXPECT_EQ(fmt::format("{}", test_adaptor()), "test");
}