use std::io;

use bstr::ByteSlice;

/// The default buffer capacity that we use for the line buffer.
pub(crate) const DEFAULT_BUFFER_CAPACITY: usize = 64 * (1 << 10); // 64 KB

/// The behavior of a searcher in the face of long lines and big contexts.
///
/// When searching data incrementally using a fixed size buffer, this controls
/// the amount of *additional* memory to allocate beyond the size of the buffer
/// to accommodate lines (which may include the lines in a context window, when
/// enabled) that do not fit in the buffer.
///
/// The default is to eagerly allocate without a limit.
#[derive(Clone, Copy, Debug)]
pub(crate) enum BufferAllocation {
    /// Attempt to expand the size of the buffer until either at least the next
    /// line fits into memory or until all available memory is exhausted.
    ///
    /// This is the default.
    Eager,
    /// Limit the amount of additional memory allocated to the given size. If
    /// a line is found that requires more memory than is allowed here, then
    /// stop reading and return an error.
    Error(usize),
}

impl Default for BufferAllocation {
    fn default() -> BufferAllocation {
        BufferAllocation::Eager
    }
}

/// Create a new error to be used when a configured allocation limit has been
/// reached.
pub(crate) fn alloc_error(limit: usize) -> io::Error {
    let msg = format!("configured allocation limit ({}) exceeded", limit);
    io::Error::new(io::ErrorKind::Other, msg)
}

/// The behavior of binary detection in the line buffer.
///
/// Binary detection is the process of _heuristically_ identifying whether a
/// given chunk of data is binary or not, and then taking an action based on
/// the result of that heuristic. The motivation behind detecting binary data
/// is that binary data often indicates data that is undesirable to search
/// using textual patterns. Of course, there are many cases in which this isn't
/// true, which is why binary detection is disabled by default.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum BinaryDetection {
    /// No binary detection is performed. Data reported by the line buffer may
    /// contain arbitrary bytes.
    None,
    /// The given byte is searched in all contents read by the line buffer. If
    /// it occurs, then the data is considered binary and the line buffer acts
    /// as if it reached EOF. The line buffer guarantees that this byte will
    /// never be observable by callers.
    Quit(u8),
    /// The given byte is searched in all contents read by the line buffer. If
    /// it occurs, then it is replaced by the line terminator. The line buffer
    /// guarantees that this byte will never be observable by callers.
    Convert(u8),
}

impl Default for BinaryDetection {
    fn default() -> BinaryDetection {
        BinaryDetection::None
    }
}

impl BinaryDetection {
    /// Returns true if and only if the detection heuristic demands that
    /// the line buffer stop read data once binary data is observed.
    fn is_quit(&self) -> bool {
        match *self {
            BinaryDetection::Quit(_) => true,
            _ => false,
        }
    }
}

/// The configuration of a buffer. This contains options that are fixed once
/// a buffer has been constructed.
#[derive(Clone, Copy, Debug)]
struct Config {
    /// The number of bytes to attempt to read at a time.
    capacity: usize,
    /// The line terminator.
    lineterm: u8,
    /// The behavior for handling long lines.
    buffer_alloc: BufferAllocation,
    /// When set, the presence of the given byte indicates binary content.
    binary: BinaryDetection,
}

impl Default for Config {
    fn default() -> Config {
        Config {
            capacity: DEFAULT_BUFFER_CAPACITY,
            lineterm: b'\n',
            buffer_alloc: BufferAllocation::default(),
            binary: BinaryDetection::default(),
        }
    }
}

/// A builder for constructing line buffers.
#[derive(Clone, Debug, Default)]
pub(crate) struct LineBufferBuilder {
    config: Config,
}

impl LineBufferBuilder {
    /// Create a new builder for a buffer.
    pub(crate) fn new() -> LineBufferBuilder {
        LineBufferBuilder { config: Config::default() }
    }

    /// Create a new line buffer from this builder's configuration.
    pub(crate) fn build(&self) -> LineBuffer {
        LineBuffer {
            config: self.config,
            buf: vec![0; self.config.capacity],
            pos: 0,
            last_lineterm: 0,
            end: 0,
            absolute_byte_offset: 0,
            binary_byte_offset: None,
        }
    }

    /// Set the default capacity to use for a buffer.
    ///
    /// In general, the capacity of a buffer corresponds to the amount of data
    /// to hold in memory, and the size of the reads to make to the underlying
    /// reader.
    ///
    /// This is set to a reasonable default and probably shouldn't be changed
    /// unless there's a specific reason to do so.
    pub(crate) fn capacity(
        &mut self,
        capacity: usize,
    ) -> &mut LineBufferBuilder {
        self.config.capacity = capacity;
        self
    }

    /// Set the line terminator for the buffer.
    ///
    /// Every buffer has a line terminator, and this line terminator is used
    /// to determine how to roll the buffer forward. For example, when a read
    /// to the buffer's underlying reader occurs, the end of the data that is
    /// read is likely to correspond to an incomplete line. As a line buffer,
    /// callers should not access this data since it is incomplete. The line
    /// terminator is how the line buffer determines the part of the read that
    /// is incomplete.
    ///
    /// By default, this is set to `b'\n'`.
    pub(crate) fn line_terminator(
        &mut self,
        lineterm: u8,
    ) -> &mut LineBufferBuilder {
        self.config.lineterm = lineterm;
        self
    }

    /// Set the maximum amount of additional memory to allocate for long lines.
    ///
    /// In order to enable line oriented search, a fundamental requirement is
    /// that, at a minimum, each line must be able to fit into memory. This
    /// setting controls how big that line is allowed to be. By default, this
    /// is set to `BufferAllocation::Eager`, which means a line buffer will
    /// attempt to allocate as much memory as possible to fit a line, and will
    /// only be limited by available memory.
    ///
    /// Note that this setting only applies to the amount of *additional*
    /// memory to allocate, beyond the capacity of the buffer. That means that
    /// a value of `0` is sensible, and in particular, will guarantee that a
    /// line buffer will never allocate additional memory beyond its initial
    /// capacity.
    pub(crate) fn buffer_alloc(
        &mut self,
        behavior: BufferAllocation,
    ) -> &mut LineBufferBuilder {
        self.config.buffer_alloc = behavior;
        self
    }

    /// Whether to enable binary detection or not. Depending on the setting,
    /// this can either cause the line buffer to report EOF early or it can
    /// cause the line buffer to clean the data.
    ///
    /// By default, this is disabled. In general, binary detection should be
    /// viewed as an imperfect heuristic.
    pub(crate) fn binary_detection(
        &mut self,
        detection: BinaryDetection,
    ) -> &mut LineBufferBuilder {
        self.config.binary = detection;
        self
    }
}

/// A line buffer reader efficiently reads a line oriented buffer from an
/// arbitrary reader.
#[derive(Debug)]
pub(crate) struct LineBufferReader<'b, R> {
    rdr: R,
    line_buffer: &'b mut LineBuffer,
}

impl<'b, R: io::Read> LineBufferReader<'b, R> {
    /// Create a new buffered reader that reads from `rdr` and uses the given
    /// `line_buffer` as an intermediate buffer.
    ///
    /// This does not change the binary detection behavior of the given line
    /// buffer.
    pub(crate) fn new(
        rdr: R,
        line_buffer: &'b mut LineBuffer,
    ) -> LineBufferReader<'b, R> {
        line_buffer.clear();
        LineBufferReader { rdr, line_buffer }
    }

    /// The absolute byte offset which corresponds to the starting offsets
    /// of the data returned by `buffer` relative to the beginning of the
    /// underlying reader's contents. As such, this offset does not generally
    /// correspond to an offset in memory. It is typically used for reporting
    /// purposes. It can also be used for counting the number of bytes that
    /// have been searched.
    pub(crate) fn absolute_byte_offset(&self) -> u64 {
        self.line_buffer.absolute_byte_offset()
    }

    /// If binary data was detected, then this returns the absolute byte offset
    /// at which binary data was initially found.
    pub(crate) fn binary_byte_offset(&self) -> Option<u64> {
        self.line_buffer.binary_byte_offset()
    }

    /// Fill the contents of this buffer by discarding the part of the buffer
    /// that has been consumed. The free space created by discarding the
    /// consumed part of the buffer is then filled with new data from the
    /// reader.
    ///
    /// If EOF is reached, then `false` is returned. Otherwise, `true` is
    /// returned. (Note that if this line buffer's binary detection is set to
    /// `Quit`, then the presence of binary data will cause this buffer to
    /// behave as if it had seen EOF at the first occurrence of binary data.)
    ///
    /// This forwards any errors returned by the underlying reader, and will
    /// also return an error if the buffer must be expanded past its allocation
    /// limit, as governed by the buffer allocation strategy.
    pub(crate) fn fill(&mut self) -> Result<bool, io::Error> {
        self.line_buffer.fill(&mut self.rdr)
    }

    /// Return the contents of this buffer.
    pub(crate) fn buffer(&self) -> &[u8] {
        self.line_buffer.buffer()
    }

    /// Return the buffer as a BStr, used for convenient equality checking
    /// in tests only.
    #[cfg(test)]
    fn bstr(&self) -> &bstr::BStr {
        self.buffer().as_bstr()
    }

    /// Consume the number of bytes provided. This must be less than or equal
    /// to the number of bytes returned by `buffer`.
    pub(crate) fn consume(&mut self, amt: usize) {
        self.line_buffer.consume(amt);
    }

    /// Consumes the remainder of the buffer. Subsequent calls to `buffer` are
    /// guaranteed to return an empty slice until the buffer is refilled.
    ///
    /// This is a convenience function for `consume(buffer.len())`.
    #[cfg(test)]
    fn consume_all(&mut self) {
        self.line_buffer.consume_all();
    }
}

/// A line buffer manages a (typically fixed) buffer for holding lines.
///
/// Callers should create line buffers sparingly and reuse them when possible.
/// Line buffers cannot be used directly, but instead must be used via the
/// LineBufferReader.
#[derive(Clone, Debug)]
pub(crate) struct LineBuffer {
    /// The configuration of this buffer.
    config: Config,
    /// The primary buffer with which to hold data.
    buf: Vec<u8>,
    /// The current position of this buffer. This is always a valid sliceable
    /// index into `buf`, and its maximum value is the length of `buf`.
    pos: usize,
    /// The end position of searchable content in this buffer. This is either
    /// set to just after the final line terminator in the buffer, or to just
    /// after the end of the last byte emitted by the reader when the reader
    /// has been exhausted.
    last_lineterm: usize,
    /// The end position of the buffer. This is always greater than or equal to
    /// last_lineterm. The bytes between last_lineterm and end, if any, always
    /// correspond to a partial line.
    end: usize,
    /// The absolute byte offset corresponding to `pos`. This is most typically
    /// not a valid index into addressable memory, but rather, an offset that
    /// is relative to all data that passes through a line buffer (since
    /// construction or since the last time `clear` was called).
    ///
    /// When the line buffer reaches EOF, this is set to the position just
    /// after the last byte read from the underlying reader. That is, it
    /// becomes the total count of bytes that have been read.
    absolute_byte_offset: u64,
    /// If binary data was found, this records the absolute byte offset at
    /// which it was first detected.
    binary_byte_offset: Option<u64>,
}

impl LineBuffer {
    /// Set the binary detection method used on this line buffer.
    ///
    /// This permits dynamically changing the binary detection strategy on
    /// an existing line buffer without needing to create a new one.
    pub(crate) fn set_binary_detection(&mut self, binary: BinaryDetection) {
        self.config.binary = binary;
    }

    /// Reset this buffer, such that it can be used with a new reader.
    fn clear(&mut self) {
        self.pos = 0;
        self.last_lineterm = 0;
        self.end = 0;
        self.absolute_byte_offset = 0;
        self.binary_byte_offset = None;
    }

    /// The absolute byte offset which corresponds to the starting offsets
    /// of the data returned by `buffer` relative to the beginning of the
    /// reader's contents. As such, this offset does not generally correspond
    /// to an offset in memory. It is typically used for reporting purposes,
    /// particularly in error messages.
    ///
    /// This is reset to `0` when `clear` is called.
    fn absolute_byte_offset(&self) -> u64 {
        self.absolute_byte_offset
    }

    /// If binary data was detected, then this returns the absolute byte offset
    /// at which binary data was initially found.
    fn binary_byte_offset(&self) -> Option<u64> {
        self.binary_byte_offset
    }

    /// Return the contents of this buffer.
    fn buffer(&self) -> &[u8] {
        &self.buf[self.pos..self.last_lineterm]
    }

    /// Return the contents of the free space beyond the end of the buffer as
    /// a mutable slice.
    fn free_buffer(&mut self) -> &mut [u8] {
        &mut self.buf[self.end..]
    }

    /// Consume the number of bytes provided. This must be less than or equal
    /// to the number of bytes returned by `buffer`.
    fn consume(&mut self, amt: usize) {
        assert!(amt <= self.buffer().len());
        self.pos += amt;
        self.absolute_byte_offset += amt as u64;
    }

    /// Consumes the remainder of the buffer. Subsequent calls to `buffer` are
    /// guaranteed to return an empty slice until the buffer is refilled.
    ///
    /// This is a convenience function for `consume(buffer.len())`.
    #[cfg(test)]
    fn consume_all(&mut self) {
        let amt = self.buffer().len();
        self.consume(amt);
    }

    /// Fill the contents of this buffer by discarding the part of the buffer
    /// that has been consumed. The free space created by discarding the
    /// consumed part of the buffer is then filled with new data from the given
    /// reader.
    ///
    /// Callers should provide the same reader to this line buffer in
    /// subsequent calls to fill. A different reader can only be used
    /// immediately following a call to `clear`.
    ///
    /// If EOF is reached, then `false` is returned. Otherwise, `true` is
    /// returned. (Note that if this line buffer's binary detection is set to
    /// `Quit`, then the presence of binary data will cause this buffer to
    /// behave as if it had seen EOF.)
    ///
    /// This forwards any errors returned by `rdr`, and will also return an
    /// error if the buffer must be expanded past its allocation limit, as
    /// governed by the buffer allocation strategy.
    fn fill<R: io::Read>(&mut self, mut rdr: R) -> Result<bool, io::Error> {
        // If the binary detection heuristic tells us to quit once binary data
        // has been observed, then we no longer read new data and reach EOF
        // once the current buffer has been consumed.
        if self.config.binary.is_quit() && self.binary_byte_offset.is_some() {
            return Ok(!self.buffer().is_empty());
        }

        self.roll();
        assert_eq!(self.pos, 0);
        loop {
            self.ensure_capacity()?;
            let readlen = rdr.read(self.free_buffer().as_bytes_mut())?;
            if readlen == 0 {
                // We're only done reading for good once the caller has
                // consumed everything.
                self.last_lineterm = self.end;
                return Ok(!self.buffer().is_empty());
            }

            // Get a mutable view into the bytes we've just read. These are
            // the bytes that we do binary detection on, and also the bytes we
            // search to find the last line terminator. We need a mutable slice
            // in the case of binary conversion.
            let oldend = self.end;
            self.end += readlen;
            let newbytes = &mut self.buf[oldend..self.end];

            // Binary detection.
            match self.config.binary {
                BinaryDetection::None => {} // nothing to do
                BinaryDetection::Quit(byte) => {
                    if let Some(i) = newbytes.find_byte(byte) {
                        self.end = oldend + i;
                        self.last_lineterm = self.end;
                        self.binary_byte_offset =
                            Some(self.absolute_byte_offset + self.end as u64);
                        // If the first byte in our buffer is a binary byte,
                        // then our buffer is empty and we should report as
                        // such to the caller.
                        return Ok(self.pos < self.end);
                    }
                }
                BinaryDetection::Convert(byte) => {
                    if let Some(i) =
                        replace_bytes(newbytes, byte, self.config.lineterm)
                    {
                        // Record only the first binary offset.
                        if self.binary_byte_offset.is_none() {
                            self.binary_byte_offset = Some(
                                self.absolute_byte_offset
                                    + (oldend + i) as u64,
                            );
                        }
                    }
                }
            }

            // Update our `last_lineterm` positions if we read one.
            if let Some(i) = newbytes.rfind_byte(self.config.lineterm) {
                self.last_lineterm = oldend + i + 1;
                return Ok(true);
            }
            // At this point, if we couldn't find a line terminator, then we
            // don't have a complete line. Therefore, we try to read more!
        }
    }

    /// Roll the unconsumed parts of the buffer to the front.
    ///
    /// This operation is idempotent.
    ///
    /// After rolling, `last_lineterm` and `end` point to the same location,
    /// and `pos` is always set to `0`.
    fn roll(&mut self) {
        if self.pos == self.end {
            self.pos = 0;
            self.last_lineterm = 0;
            self.end = 0;
            return;
        }

        let roll_len = self.end - self.pos;
        self.buf.copy_within(self.pos..self.end, 0);
        self.pos = 0;
        self.last_lineterm = roll_len;
        self.end = roll_len;
    }

    /// Ensures that the internal buffer has a non-zero amount of free space
    /// in which to read more data. If there is no free space, then more is
    /// allocated. If the allocation must exceed the configured limit, then
    /// this returns an error.
    fn ensure_capacity(&mut self) -> Result<(), io::Error> {
        if !self.free_buffer().is_empty() {
            return Ok(());
        }
        // `len` is used for computing the next allocation size. The capacity
        // is permitted to start at `0`, so we make sure it's at least `1`.
        let len = std::cmp::max(1, self.buf.len());
        let additional = match self.config.buffer_alloc {
            BufferAllocation::Eager => len * 2,
            BufferAllocation::Error(limit) => {
                let used = self.buf.len() - self.config.capacity;
                let n = std::cmp::min(len * 2, limit - used);
                if n == 0 {
                    return Err(alloc_error(self.config.capacity + limit));
                }
                n
            }
        };
        assert!(additional > 0);
        let newlen = self.buf.len() + additional;
        self.buf.resize(newlen, 0);
        assert!(!self.free_buffer().is_empty());
        Ok(())
    }
}

/// Replaces `src` with `replacement` in bytes, and return the offset of the
/// first replacement, if one exists.
fn replace_bytes(
    mut bytes: &mut [u8],
    src: u8,
    replacement: u8,
) -> Option<usize> {
    if src == replacement {
        return None;
    }
    let first_pos = bytes.find_byte(src)?;
    bytes[first_pos] = replacement;
    bytes = &mut bytes[first_pos + 1..];
    while let Some(i) = bytes.find_byte(src) {
        bytes[i] = replacement;
        bytes = &mut bytes[i + 1..];

        // To search for adjacent `src` bytes we use a different strategy.
        // Since binary data tends to have long runs of NUL terminators,
        // it is faster to compare one-byte-at-a-time than to stop and start
        // memchr (through `find_byte`) for every byte in a sequence.
        while bytes.get(0) == Some(&src) {
            bytes[0] = replacement;
            bytes = &mut bytes[1..];
        }
    }
    Some(first_pos)
}

#[cfg(test)]
mod tests {
    use bstr::ByteVec;

    use super::*;

    const SHERLOCK: &'static str = "\
For the Doctor Watsons of this world, as opposed to the Sherlock
Holmeses, success in the province of detective work must always
be, to a very large extent, the result of luck. Sherlock Holmes
can extract a clew from a wisp of straw or a flake of cigar ash;
but Doctor Watson has to have it taken out for him and dusted,
and exhibited clearly, with a label attached.\
";

    fn s(slice: &str) -> String {
        slice.to_string()
    }

    fn replace_str(
        slice: &str,
        src: u8,
        replacement: u8,
    ) -> (String, Option<usize>) {
        let mut dst = Vec::from(slice);
        let result = replace_bytes(&mut dst, src, replacement);
        (dst.into_string().unwrap(), result)
    }

    #[test]
    fn replace() {
        assert_eq!(replace_str("", b'b', b'z'), (s(""), None));
        assert_eq!(replace_str("a", b'a', b'a'), (s("a"), None));
        assert_eq!(replace_str("a", b'b', b'z'), (s("a"), None));
        assert_eq!(replace_str("abc", b'b', b'z'), (s("azc"), Some(1)));
        assert_eq!(replace_str("abb", b'b', b'z'), (s("azz"), Some(1)));
        assert_eq!(replace_str("aba", b'a', b'z'), (s("zbz"), Some(0)));
        assert_eq!(replace_str("bbb", b'b', b'z'), (s("zzz"), Some(0)));
        assert_eq!(replace_str("bac", b'b', b'z'), (s("zac"), Some(0)));
    }

    #[test]
    fn buffer_basics1() {
        let bytes = "homer\nlisa\nmaggie";
        let mut linebuf = LineBufferBuilder::new().build();
        let mut rdr = LineBufferReader::new(bytes.as_bytes(), &mut linebuf);

        assert!(rdr.buffer().is_empty());

        assert!(rdr.fill().unwrap());
        assert_eq!(rdr.bstr(), "homer\nlisa\n");
        assert_eq!(rdr.absolute_byte_offset(), 0);
        rdr.consume(5);
        assert_eq!(rdr.absolute_byte_offset(), 5);
        rdr.consume_all();
        assert_eq!(rdr.absolute_byte_offset(), 11);

        assert!(rdr.fill().unwrap());
        assert_eq!(rdr.bstr(), "maggie");
        rdr.consume_all();

        assert!(!rdr.fill().unwrap());
        assert_eq!(rdr.absolute_byte_offset(), bytes.len() as u64);
        assert_eq!(rdr.binary_byte_offset(), None);
    }

    #[test]
    fn buffer_basics2() {
        let bytes = "homer\nlisa\nmaggie\n";
        let mut linebuf = LineBufferBuilder::new().build();
        let mut rdr = LineBufferReader::new(bytes.as_bytes(), &mut linebuf);

        assert!(rdr.fill().unwrap());
        assert_eq!(rdr.bstr(), "homer\nlisa\nmaggie\n");
        rdr.consume_all();

        assert!(!rdr.fill().unwrap());
        assert_eq!(rdr.absolute_byte_offset(), bytes.len() as u64);
        assert_eq!(rdr.binary_byte_offset(), None);
    }

    #[test]
    fn buffer_basics3() {
        let bytes = "\n";
        let mut linebuf = LineBufferBuilder::new().build();
        let mut rdr = LineBufferReader::new(bytes.as_bytes(), &mut linebuf);

        assert!(rdr.fill().unwrap());
        assert_eq!(rdr.bstr(), "\n");
        rdr.consume_all();

        assert!(!rdr.fill().unwrap());
        assert_eq!(rdr.absolute_byte_offset(), bytes.len() as u64);
        assert_eq!(rdr.binary_byte_offset(), None);
    }

    #[test]
    fn buffer_basics4() {
        let bytes = "\n\n";
        let mut linebuf = LineBufferBuilder::new().build();
        let mut rdr = LineBufferReader::new(bytes.as_bytes(), &mut linebuf);

        assert!(rdr.fill().unwrap());
        assert_eq!(rdr.bstr(), "\n\n");
        rdr.consume_all();

        assert!(!rdr.fill().unwrap());
        assert_eq!(rdr.absolute_byte_offset(), bytes.len() as u64);
        assert_eq!(rdr.binary_byte_offset(), None);
    }

    #[test]
    fn buffer_empty() {
        let bytes = "";
        let mut linebuf = LineBufferBuilder::new().build();
        let mut rdr = LineBufferReader::new(bytes.as_bytes(), &mut linebuf);

        assert!(!rdr.fill().unwrap());
        assert_eq!(rdr.absolute_byte_offset(), bytes.len() as u64);
        assert_eq!(rdr.binary_byte_offset(), None);
    }

    #[test]
    fn buffer_zero_capacity() {
        let bytes = "homer\nlisa\nmaggie";
        let mut linebuf = LineBufferBuilder::new().capacity(0).build();
        let mut rdr = LineBufferReader::new(bytes.as_bytes(), &mut linebuf);

        while rdr.fill().unwrap() {
            rdr.consume_all();
        }
        assert_eq!(rdr.absolute_byte_offset(), bytes.len() as u64);
        assert_eq!(rdr.binary_byte_offset(), None);
    }

    #[test]
    fn buffer_small_capacity() {
        let bytes = "homer\nlisa\nmaggie";
        let mut linebuf = LineBufferBuilder::new().capacity(1).build();
        let mut rdr = LineBufferReader::new(bytes.as_bytes(), &mut linebuf);

        let mut got = vec![];
        while rdr.fill().unwrap() {
            got.push_str(rdr.buffer());
            rdr.consume_all();
        }
        assert_eq!(bytes, got.as_bstr());
        assert_eq!(rdr.absolute_byte_offset(), bytes.len() as u64);
        assert_eq!(rdr.binary_byte_offset(), None);
    }

    #[test]
    fn buffer_limited_capacity1() {
        let bytes = "homer\nlisa\nmaggie";
        let mut linebuf = LineBufferBuilder::new()
            .capacity(1)
            .buffer_alloc(BufferAllocation::Error(5))
            .build();
        let mut rdr = LineBufferReader::new(bytes.as_bytes(), &mut linebuf);

        assert!(rdr.fill().unwrap());
        assert_eq!(rdr.bstr(), "homer\n");
        rdr.consume_all();

        assert!(rdr.fill().unwrap());
        assert_eq!(rdr.bstr(), "lisa\n");
        rdr.consume_all();

        // This returns an error because while we have just enough room to
        // store maggie in the buffer, we *don't* have enough room to read one
        // more byte, so we don't know whether we're at EOF or not, and
        // therefore must give up.
        assert!(rdr.fill().is_err());

        // We can mush on though!
        assert_eq!(rdr.bstr(), "m");
        rdr.consume_all();

        assert!(rdr.fill().unwrap());
        assert_eq!(rdr.bstr(), "aggie");
        rdr.consume_all();

        assert!(!rdr.fill().unwrap());
    }

    #[test]
    fn buffer_limited_capacity2() {
        let bytes = "homer\nlisa\nmaggie";
        let mut linebuf = LineBufferBuilder::new()
            .capacity(1)
            .buffer_alloc(BufferAllocation::Error(6))
            .build();
        let mut rdr = LineBufferReader::new(bytes.as_bytes(), &mut linebuf);

        assert!(rdr.fill().unwrap());
        assert_eq!(rdr.bstr(), "homer\n");
        rdr.consume_all();

        assert!(rdr.fill().unwrap());
        assert_eq!(rdr.bstr(), "lisa\n");
        rdr.consume_all();

        // We have just enough space.
        assert!(rdr.fill().unwrap());
        assert_eq!(rdr.bstr(), "maggie");
        rdr.consume_all();

        assert!(!rdr.fill().unwrap());
    }

    #[test]
    fn buffer_limited_capacity3() {
        let bytes = "homer\nlisa\nmaggie";
        let mut linebuf = LineBufferBuilder::new()
            .capacity(1)
            .buffer_alloc(BufferAllocation::Error(0))
            .build();
        let mut rdr = LineBufferReader::new(bytes.as_bytes(), &mut linebuf);

        assert!(rdr.fill().is_err());
        assert_eq!(rdr.bstr(), "");
    }

    #[test]
    fn buffer_binary_none() {
        let bytes = "homer\nli\x00sa\nmaggie\n";
        let mut linebuf = LineBufferBuilder::new().build();
        let mut rdr = LineBufferReader::new(bytes.as_bytes(), &mut linebuf);

        assert!(rdr.buffer().is_empty());

        assert!(rdr.fill().unwrap());
        assert_eq!(rdr.bstr(), "homer\nli\x00sa\nmaggie\n");
        rdr.consume_all();

        assert!(!rdr.fill().unwrap());
        assert_eq!(rdr.absolute_byte_offset(), bytes.len() as u64);
        assert_eq!(rdr.binary_byte_offset(), None);
    }

    #[test]
    fn buffer_binary_quit1() {
        let bytes = "homer\nli\x00sa\nmaggie\n";
        let mut linebuf = LineBufferBuilder::new()
            .binary_detection(BinaryDetection::Quit(b'\x00'))
            .build();
        let mut rdr = LineBufferReader::new(bytes.as_bytes(), &mut linebuf);

        assert!(rdr.buffer().is_empty());

        assert!(rdr.fill().unwrap());
        assert_eq!(rdr.bstr(), "homer\nli");
        rdr.consume_all();

        assert!(!rdr.fill().unwrap());
        assert_eq!(rdr.absolute_byte_offset(), 8);
        assert_eq!(rdr.binary_byte_offset(), Some(8));
    }

    #[test]
    fn buffer_binary_quit2() {
        let bytes = "\x00homer\nlisa\nmaggie\n";
        let mut linebuf = LineBufferBuilder::new()
            .binary_detection(BinaryDetection::Quit(b'\x00'))
            .build();
        let mut rdr = LineBufferReader::new(bytes.as_bytes(), &mut linebuf);

        assert!(!rdr.fill().unwrap());
        assert_eq!(rdr.bstr(), "");
        assert_eq!(rdr.absolute_byte_offset(), 0);
        assert_eq!(rdr.binary_byte_offset(), Some(0));
    }

    #[test]
    fn buffer_binary_quit3() {
        let bytes = "homer\nlisa\nmaggie\n\x00";
        let mut linebuf = LineBufferBuilder::new()
            .binary_detection(BinaryDetection::Quit(b'\x00'))
            .build();
        let mut rdr = LineBufferReader::new(bytes.as_bytes(), &mut linebuf);

        assert!(rdr.buffer().is_empty());

        assert!(rdr.fill().unwrap());
        assert_eq!(rdr.bstr(), "homer\nlisa\nmaggie\n");
        rdr.consume_all();

        assert!(!rdr.fill().unwrap());
        assert_eq!(rdr.absolute_byte_offset(), bytes.len() as u64 - 1);
        assert_eq!(rdr.binary_byte_offset(), Some(bytes.len() as u64 - 1));
    }

    #[test]
    fn buffer_binary_quit4() {
        let bytes = "homer\nlisa\nmaggie\x00\n";
        let mut linebuf = LineBufferBuilder::new()
            .binary_detection(BinaryDetection::Quit(b'\x00'))
            .build();
        let mut rdr = LineBufferReader::new(bytes.as_bytes(), &mut linebuf);

        assert!(rdr.buffer().is_empty());

        assert!(rdr.fill().unwrap());
        assert_eq!(rdr.bstr(), "homer\nlisa\nmaggie");
        rdr.consume_all();

        assert!(!rdr.fill().unwrap());
        assert_eq!(rdr.absolute_byte_offset(), bytes.len() as u64 - 2);
        assert_eq!(rdr.binary_byte_offset(), Some(bytes.len() as u64 - 2));
    }

    #[test]
    fn buffer_binary_quit5() {
        let mut linebuf = LineBufferBuilder::new()
            .binary_detection(BinaryDetection::Quit(b'u'))
            .build();
        let mut rdr = LineBufferReader::new(SHERLOCK.as_bytes(), &mut linebuf);

        assert!(rdr.buffer().is_empty());

        assert!(rdr.fill().unwrap());
        assert_eq!(
            rdr.bstr(),
            "\
For the Doctor Watsons of this world, as opposed to the Sherlock
Holmeses, s\
"
        );
        rdr.consume_all();

        assert!(!rdr.fill().unwrap());
        assert_eq!(rdr.absolute_byte_offset(), 76);
        assert_eq!(rdr.binary_byte_offset(), Some(76));
        assert_eq!(SHERLOCK.as_bytes()[76], b'u');
    }

    #[test]
    fn buffer_binary_convert1() {
        let bytes = "homer\nli\x00sa\nmaggie\n";
        let mut linebuf = LineBufferBuilder::new()
            .binary_detection(BinaryDetection::Convert(b'\x00'))
            .build();
        let mut rdr = LineBufferReader::new(bytes.as_bytes(), &mut linebuf);

        assert!(rdr.buffer().is_empty());

        assert!(rdr.fill().unwrap());
        assert_eq!(rdr.bstr(), "homer\nli\nsa\nmaggie\n");
        rdr.consume_all();

        assert!(!rdr.fill().unwrap());
        assert_eq!(rdr.absolute_byte_offset(), bytes.len() as u64);
        assert_eq!(rdr.binary_byte_offset(), Some(8));
    }

    #[test]
    fn buffer_binary_convert2() {
        let bytes = "\x00homer\nlisa\nmaggie\n";
        let mut linebuf = LineBufferBuilder::new()
            .binary_detection(BinaryDetection::Convert(b'\x00'))
            .build();
        let mut rdr = LineBufferReader::new(bytes.as_bytes(), &mut linebuf);

        assert!(rdr.buffer().is_empty());

        assert!(rdr.fill().unwrap());
        assert_eq!(rdr.bstr(), "\nhomer\nlisa\nmaggie\n");
        rdr.consume_all();

        assert!(!rdr.fill().unwrap());
        assert_eq!(rdr.absolute_byte_offset(), bytes.len() as u64);
        assert_eq!(rdr.binary_byte_offset(), Some(0));
    }

    #[test]
    fn buffer_binary_convert3() {
        let bytes = "homer\nlisa\nmaggie\n\x00";
        let mut linebuf = LineBufferBuilder::new()
            .binary_detection(BinaryDetection::Convert(b'\x00'))
            .build();
        let mut rdr = LineBufferReader::new(bytes.as_bytes(), &mut linebuf);

        assert!(rdr.buffer().is_empty());

        assert!(rdr.fill().unwrap());
        assert_eq!(rdr.bstr(), "homer\nlisa\nmaggie\n\n");
        rdr.consume_all();

        assert!(!rdr.fill().unwrap());
        assert_eq!(rdr.absolute_byte_offset(), bytes.len() as u64);
        assert_eq!(rdr.binary_byte_offset(), Some(bytes.len() as u64 - 1));
    }

    #[test]
    fn buffer_binary_convert4() {
        let bytes = "homer\nlisa\nmaggie\x00\n";
        let mut linebuf = LineBufferBuilder::new()
            .binary_detection(BinaryDetection::Convert(b'\x00'))
            .build();
        let mut rdr = LineBufferReader::new(bytes.as_bytes(), &mut linebuf);

        assert!(rdr.buffer().is_empty());

        assert!(rdr.fill().unwrap());
        assert_eq!(rdr.bstr(), "homer\nlisa\nmaggie\n\n");
        rdr.consume_all();

        assert!(!rdr.fill().unwrap());
        assert_eq!(rdr.absolute_byte_offset(), bytes.len() as u64);
        assert_eq!(rdr.binary_byte_offset(), Some(bytes.len() as u64 - 2));
    }
}


import { expect, test } from 'vitest'
import { getColor, page } from '~utils'

test('import from .ts', async () => {
  await expect.poll(() => page.textContent('.ts')).toMatch('[success]')
})

test('import from .js', async () => {
  await expect.poll(() => page.textContent('.js')).toMatch('[success]')
})

test('import using # prefixed path', async () => {
  await expect.poll(() => page.textContent('.hash')).toMatch('[success]')
})

test('fallback works', async () => {
  await expect.poll(() => page.textContent('.fallback')).toMatch('[success]')
})

test('nested tsconfig.json & references / include works', async () => {
  await expect.poll(() => page.textContent('.nested-a')).toMatch('[success]')
  await expect.poll(() => page.textContent('.nested-b')).toMatch('[success]')
})

test('import.meta.glob resolves tsconfig paths', async () => {
  await expect
    .poll(() => page.textContent('.glob-eager'))
    .toBe('[success] glob-a [success] glob-b')
})

test('css @import resolves tsconfig paths', async () => {
  await expect.poll(() => getColor('.tsconfig-paths-css')).toBe('darkcyan')
})

test('sass @use resolves tsconfig paths', async () => {
  await expect.poll(() => getColor('.tsconfig-paths-scss')).toBe('seagreen')
})

// `resolve.tsconfigPaths` is not supported inside `.less` files. The aliased
// `@import (optional) '@/less-imported.less'` cannot resolve (even with
// `**/*.less` in `include`), so it is skipped and the color stays `navy`.
test('less @import does not resolve tsconfig paths (unsupported)', async () => {
  await expect.poll(() => getColor('.tsconfig-paths-less')).toBe('navy')
})


import path from 'node:path'
import picomatch from 'picomatch'
import { globSync } from 'tinyglobby'
import type { ResolvedConfig } from '../config'
import { escapeRegex, getNpmPackageName } from '../utils'
import { resolvePackageData } from '../packages'
import { slash } from '../../shared/utils'
import type { Environment } from '../environment'
import { createBackCompatIdResolver } from '../idResolver'

export function createOptimizeDepsIncludeResolver(
  environment: Environment,
): (id: string) => Promise<string | undefined> {
  const topLevelConfig = environment.getTopLevelConfig()
  const resolve = createBackCompatIdResolver(topLevelConfig, {
    asSrc: false,
    scan: true,
    packageCache: new Map(),
  })

  return async (id: string) => {
    const lastArrowIndex = id.lastIndexOf('>')
    if (lastArrowIndex === -1) {
      return await resolve(environment, id, undefined)
    }
    // split nested selected id by last '>', for example:
    // 'foo > bar > baz' => 'foo > bar' & 'baz'
    const nestedRoot = id.substring(0, lastArrowIndex).trim()
    const nestedPath = id.substring(lastArrowIndex + 1).trim()
    const basedir = nestedResolveBasedir(
      nestedRoot,
      topLevelConfig.root,
      topLevelConfig.resolve.preserveSymlinks,
    )
    return await resolve(
      environment,
      nestedPath,
      path.resolve(basedir, 'package.json'),
    )
  }
}

/**
 * Expand the glob syntax in `optimizeDeps.include` to proper import paths
 */
export function expandGlobIds(id: string, config: ResolvedConfig): string[] {
  const pkgName = getNpmPackageName(id)
  if (!pkgName) return []

  const pkgData = resolvePackageData(
    pkgName,
    config.root,
    config.resolve.preserveSymlinks,
    config.packageCache,
  )
  if (!pkgData) return []

  const pattern = '.' + id.slice(pkgName.length)
  const exports = pkgData.data.exports

  // if package has exports field, get all possible export paths and apply
  // glob on them with picomatch
  if (exports) {
    if (typeof exports === 'string' || Array.isArray(exports)) {
      return [pkgName]
    }

    const possibleExportPaths: string[] = []
    for (const key in exports) {
      if (key[0] === '.') {
        if (key.includes('*')) {
          // "./glob/*": {
          //   "browser": "./dist/glob/*-browser/*.js", <-- get this one
          //   "default": "./dist/glob/*/*.js"
          // }
          // NOTE: theoretically the "default" condition could map to a different
          // set of files, but that complicates the resolve logic, so we assume
          // all conditions map to the same set of files, and get the first one.
          const exportsValue = getFirstExportStringValue(exports[key])
          if (!exportsValue) continue

          // "./dist/glob/*-browser/*.js" => "./dist/glob/**/*-browser/**/*.js"
          // NOTE: in some cases, this could expand to consecutive /**/*/**/* etc
          // but it's fine since `tinyglobby` handles it the same.
          const exportValuePattern = exportsValue.replace(/\*/g, '**/*')
          // "./dist/glob/*-browser/*.js" => /dist\/glob\/(.*)-browser\/(.*)\.js/
          const exportsValueGlobRe = new RegExp(
            exportsValue.split('*').map(escapeRegex).join('(.*)'),
          )

          possibleExportPaths.push(
            ...globSync(exportValuePattern, {
              cwd: pkgData.dir,
              expandDirectories: false,
              ignore: ['node_modules'],
            })
              .map((filePath) => {
                // `tinyglobby` returns paths as they are formatted by the underlying `fdir`.
                // Both `globSync("./some-dir/**/*")` and `globSync("./**/*")` result in
                // `"some-dir/somefile"` being returned, so we ensure the correct prefix manually.
                if (exportsValue.startsWith('./')) {
                  filePath = './' + filePath
                }

                // "./glob/*": "./dist/glob/*-browser/*.js"
                // `filePath`: "./dist/glob/foo-browser/foo.js"
                // we need to revert the file path back to the export key by
                // matching value regex and replacing the capture groups to the key
                const matched = exportsValueGlobRe.exec(slash(filePath))
                // `matched`: [..., 'foo', 'foo']
                if (matched) {
                  let allGlobSame = matched.length === 2
                  // exports key can only have one *, so for >=2 matched groups,
                  // make sure they have the same value
                  if (!allGlobSame) {
                    // assume true, if one group is different, set false and break
                    allGlobSame = true
                    for (let i = 2; i < matched.length; i++) {
                      if (matched[i] !== matched[i - 1]) {
                        allGlobSame = false
                        break
                      }
                    }
                  }
                  if (allGlobSame) {
                    return key.replace('*', matched[1]).slice(2)
                  }
                }
                return ''
              })
              .filter(Boolean),
          )
        } else {
          // null export value means the subpath is intentionally private/blocked
          // https://nodejs.org/api/packages.html#subpath-patterns
          if (exports[key] == null) continue
          possibleExportPaths.push(key.slice(2))
        }
      }
    }

    const isMatch = picomatch(pattern)
    const matched = possibleExportPaths
      .filter((p) => isMatch(p))
      .map((match) => path.posix.join(pkgName, match))
    matched.unshift(pkgName)
    return matched
  } else {
    // for packages without exports, we can do a simple glob
    const matched = globSync(pattern, {
      cwd: pkgData.dir,
      expandDirectories: false,
      ignore: ['node_modules'],
    }).map((match) => path.posix.join(pkgName, slash(match)))
    matched.unshift(pkgName)
    return matched
  }
}

function getFirstExportStringValue(
  obj: string | string[] | Record<string, any>,
): string | undefined {
  if (typeof obj === 'string') {
    return obj
  } else if (Array.isArray(obj)) {
    return obj[0]
  } else {
    for (const key in obj) {
      return getFirstExportStringValue(obj[key])
    }
  }
}

/**
 * Continuously resolve the basedir of packages separated by '>'
 */
function nestedResolveBasedir(
  id: string,
  basedir: string,
  preserveSymlinks = false,
) {
  const pkgs = id.split('>').map((pkg) => pkg.trim())
  for (const pkg of pkgs) {
    basedir = resolvePackageData(pkg, basedir, preserveSymlinks)?.dir || basedir
  }
  return basedir
}


/**
 * This is a development only file for testing types.
 */
import type { Plugin as RolldownPlugin } from 'rolldown'
import type { Equal, ExpectExtends, ExpectTrue } from '@type-challenges/utils'
import type { Plugin as RollupPlugin } from 'rollup'
import type { Plugin, PluginContextExtension, PluginOption } from '../plugin'
import type { ROLLUP_HOOKS } from '../constants'
import type {
  GetHookContextMap,
  NonNeverKeys,
  RollupPluginHooks,
} from '../typeUtils'

type EnvironmentPluginHooksContext = GetHookContextMap<Plugin>
type EnvironmentPluginHooksContextMatched = {
  [K in keyof EnvironmentPluginHooksContext]: EnvironmentPluginHooksContext[K] extends PluginContextExtension
    ? never
    : false
}

type HooksMissingExtension = NonNeverKeys<EnvironmentPluginHooksContextMatched>
type HooksMissingInConstants = Exclude<
  RollupPluginHooks,
  (typeof ROLLUP_HOOKS)[number]
>

export type cases = [
  // Ensure environment plugin hooks are superset of rollup plugin hooks
  ExpectTrue<ExpectExtends<RolldownPlugin, Plugin>>,

  // Ensure all Rollup hooks have Vite's plugin context extension
  ExpectTrue<Equal<HooksMissingExtension, never>>,

  // Ensure the `ROLLUP_HOOKS` constant is up-to-date
  ExpectTrue<Equal<HooksMissingInConstants, never>>,

  // Ensure all Vite plugins, Rolldown plugins, and Rollup plugins can be assigned to `plugins` option
  ExpectTrue<ExpectExtends<PluginOption, RolldownPlugin>>,
  ExpectTrue<ExpectExtends<PluginOption, RollupPlugin>>,
  ExpectTrue<ExpectExtends<PluginOption, Plugin>>,
]

export {}


export function createIsBuiltin(
  builtins: (string | RegExp)[],
): (id: string) => boolean {
  const plainBuiltinsSet = new Set(
    builtins.filter((builtin) => typeof builtin === 'string'),
  )
  const regexBuiltins = builtins.filter(
    (builtin) => typeof builtin !== 'string',
  )

  return (id: string) =>
    plainBuiltinsSet.has(id) || regexBuiltins.some((regexp) => regexp.test(id))
}


//===- FuzzerRandom.h - Internal header for the Fuzzer ----------*- C++ -* ===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// fuzzer::Random
//===----------------------------------------------------------------------===//

#ifndef LLVM_FUZZER_RANDOM_H
#define LLVM_FUZZER_RANDOM_H

#include <random>

namespace fuzzer {
class Random {
 public:
  Random(unsigned int seed) : R(seed) {}
  size_t Rand() { return R(); }
  size_t RandBool() { return Rand() % 2; }
  size_t operator()(size_t n) { return n ? Rand() % n : 0; }
  intptr_t operator()(intptr_t From, intptr_t To) {
    assert(From < To);
    intptr_t RangeSize = To - From + 1;
    return operator()(RangeSize) + From;
  }
  std::mt19937 &Get_mt19937() { return R; }
 private:
  std::mt19937 R;
};

}  // namespace fuzzer

#endif  // LLVM_FUZZER_RANDOM_H


# 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).


// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.

// Simple test for a fuzzer. The fuzzer must find the empty string.
#include <cstdint>
#include <cstddef>

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
  static volatile int Zero = 0;
  if (!Size)
    while(!Zero)
      ;
  return 0;
}


import { describe, expect, test } from 'vitest'
import { findAssetFile, isBuild } from '~utils'

describe.runIf(isBuild)('css lib entry', () => {
  test('remove useless js sourcemap', async () => {
    expect(findAssetFile('linked.js.map', 'lib-entry', './')).toBeUndefined()
  })
})


# Vulnerability Disclosure

If you think you have found a potential security vulnerability in
requests, please open a [draft Security Advisory](https://github.com/psf/requests/security/advisories/new)
via GitHub. We will coordinate verification and next steps through
that secure medium.

If English is not your first language, please try to describe the
problem and its impact to the best of your ability. For greater detail,
please use your native language and we will try our best to translate it
using online services.

Please also include the code you used to find the problem and the
shortest amount of code necessary to reproduce it.

Please do not disclose this to anyone else. We will retrieve a CVE
identifier if necessary and give you full credit under whatever name or
alias you provide. We will only request an identifier when we have a fix
and can publish it in a release.

We will respect your privacy and will only publicize your involvement if
you grant us permission.

## Process

This following information discusses the process the requests project
follows in response to vulnerability disclosures. If you are disclosing
a vulnerability, this section of the documentation lets you know how we
will respond to your disclosure.

### Timeline

When you report an issue, one of the project members will respond to you
within two days *at the outside*. In most cases responses will be
faster, usually within 12 hours. This initial response will at the very
least confirm receipt of the report.

If we were able to rapidly reproduce the issue, the initial response
will also contain confirmation of the issue. If we are not, we will
often ask for more information about the reproduction scenario.

Our goal is to have a fix for any vulnerability released within two
weeks of the initial disclosure. This may potentially involve shipping
an interim release that simply disables function while a more mature fix
can be prepared, but will in the vast majority of cases mean shipping a
complete release as soon as possible.

Throughout the fix process we will keep you up to speed with how the fix
is progressing. Once the fix is prepared, we will notify you that we
believe we have a fix. Often we will ask you to confirm the fix resolves
the problem in your environment, especially if we are not confident of
our reproduction scenario.

At this point, we will prepare for the release. We will obtain a CVE
number if one is required, providing you with full credit for the
discovery. We will also decide on a planned release date, and let you
know when it is. This release date will *always* be on a weekday.

At this point we will reach out to our major downstream packagers to
notify them of an impending security-related patch so they can make
arrangements. In addition, these packagers will be provided with the
intended patch ahead of time, to ensure that they are able to promptly
release their downstream packages. Currently the list of people we
actively contact *ahead of a public release* is:

-   Python Maintenance Team, Red Hat (python-maint@redhat.com)
-   Daniele Tricoli, Debian (@eriol)

We will notify these individuals at least a week ahead of our planned
release date to ensure that they have sufficient time to prepare. If you
believe you should be on this list, please let one of the maintainers
know at one of the email addresses at the top of this article.

On release day, we will push the patch to our public repository, along
with an updated changelog that describes the issue and credits you. We
will then issue a PyPI release containing the patch.

At this point, we will publicise the release. This will involve mails to
mailing lists, Tweets, and all other communication mechanisms available
to the core team.

We will also explicitly mention which commits contain the fix to make it
easier for other distributors and users to easily patch their own
versions of requests if upgrading is not an option.


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

using json = nlohmann::json;

namespace ns
{
enum TaskState
{
    TS_STOPPED,
    TS_RUNNING,
    TS_COMPLETED,
    TS_INVALID = -1
};

NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(TaskState,
{
    { TS_INVALID, nullptr },
    { TS_STOPPED, "stopped" },
    { TS_RUNNING, "running" },
    { TS_COMPLETED, "completed" }
})

enum class Color
{
    red, green, blue, unknown
};

NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(Color,
{
    { Color::unknown, "unknown" }, { Color::red, "red" },
    { Color::green, "green" }, { Color::blue, "blue" }
})
} // namespace ns

int main()
{
    // serialization
    json j_stopped = ns::TS_STOPPED;
    json j_red = ns::Color::red;
    std::cout << "ns::TS_STOPPED -> " << j_stopped
              << ", ns::Color::red -> " << j_red << std::endl;

    // deserialization
    json j_running = "running";
    json j_blue = "blue";
    auto running = j_running.get<ns::TaskState>();
    auto blue = j_blue.get<ns::Color>();
    std::cout << j_running << " -> " << running
              << ", " << j_blue << " -> " << static_cast<int>(blue) << std::endl;

}


import path from 'node:path'
import { describe, expect, test } from 'vitest'
import {
  browserLogs,
  editFile,
  findAssetFile,
  getBg,
  getColor,
  isBuild,
  isBundled,
  isBundledDev,
  isServe,
  listAssets,
  notifyRebuildComplete,
  page,
  readFile,
  readManifest,
  serverLogs,
  viteTestUrl,
  watcher,
} from '~utils'

const assetMatch = isBundled
  ? /\/foo\/bar\/assets\/asset-[-\w]{8}\.png/
  : '/foo/bar/nested/asset.png'

const encodedAssetMatch = isBundled
  ? /\/foo\/bar\/assets\/asset_small_-[-\w]{8}\.png/
  : '/foo/bar/nested/asset[small].png'

const iconMatch = `/foo/bar/icon.png`

const fetchPath = (p: string) => {
  return fetch(path.posix.join(viteTestUrl, p), {
    headers: { Accept: 'text/html,*/*' },
  })
}

// bundled dev turns a `?url` CSS import into a data URI, and it skips the CSS
// pipeline while doing so. A `url()` inside that CSS keeps its original
// relative path, which then points nowhere and gives a 404.
// This is a real bug (vitejs/vite#22863), so the test must stay skipped here.
// It will pass again once the bug is fixed, with no change to the test.
test.skipIf(isBundledDev)('should have no 404s', () => {
  browserLogs.forEach((msg) => {
    expect(msg).not.toMatch('404')
  })
})

test.runIf(isBuild)(
  'should not warn about VITE_ASSET tokens in image-set',
  async () => {
    expect(serverLogs).toStrictEqual(
      expect.not.arrayContaining([
        expect.stringMatching(/VITE_ASSET__.*?didn't resolve at build time/),
      ]),
    )
  },
)

test('should get a 404 when using incorrect case', async () => {
  expect((await fetchPath('icon.png')).headers.get('Content-Type')).toBe(
    'image/png',
  )
  // fallback to index.html
  const iconPngResult = await fetchPath('ICON.png')
  expect(iconPngResult.headers.get('Content-Type')).toBe('text/html')
  expect(iconPngResult.status).toBe(200)

  expect((await fetchPath('bar')).headers.get('Content-Type')).toBe('')
  // fallback to index.html
  const barResult = await fetchPath('BAR')
  expect(barResult.headers.get('Content-Type')).toContain('text/html')
  expect(barResult.status).toBe(200)
})

test('should fallback to index.html when accessing non-existent html file', async () => {
  expect((await fetchPath('doesnt-exist.html')).status).toBe(200)
})

describe.runIf(isServe)('outside base', () => {
  test('should get a 404 with html', async () => {
    const res = await fetch(new URL('/baz', viteTestUrl), {
      headers: { Accept: 'text/html,*/*' },
    })
    expect(res.status).toBe(404)
    expect(res.headers.get('Content-Type')).toBe('text/html')
  })

  test('should get a 404 with text', async () => {
    const res = await fetch(new URL('/baz', viteTestUrl))
    expect(res.status).toBe(404)
    expect(res.headers.get('Content-Type')).toBe('text/plain')
  })
})

describe('injected scripts', () => {
  test('@vite/client', async () => {
    const hasClient = await page.$(
      'script[type="module"][src="/foo/bar/@vite/client"]',
    )
    if (isBundled) {
      expect(hasClient).toBeFalsy()
    } else {
      expect(hasClient).toBeTruthy()
    }
  })

  test('html-proxy', async () => {
    const hasHtmlProxy = await page.$(
      'script[type="module"][src^="/foo/bar/index.html?html-proxy"]',
    )
    if (isBundled) {
      expect(hasHtmlProxy).toBeFalsy()
    } else {
      expect(hasHtmlProxy).toBeTruthy()
    }
  })
})

describe('raw references from /public', () => {
  test('load raw js from /public', async () => {
    expect(await page.textContent('.raw-js')).toMatch('[success]')
  })

  test('load raw css from /public', async () => {
    expect(await getColor('.raw-css')).toBe('red')
  })
})

test('import-expression from simple script', async () => {
  expect(await page.textContent('.import-expression')).toMatch(
    '[success][success]',
  )
})

describe('asset imports from js', () => {
  test('relative', async () => {
    expect(await page.textContent('.asset-import-relative')).toMatch(assetMatch)
  })

  test('absolute', async () => {
    expect(await page.textContent('.asset-import-absolute')).toMatch(assetMatch)
  })

  test('from /public', async () => {
    expect(await page.textContent('.public-import')).toMatch(iconMatch)
  })

  test('from /public (json)', async () => {
    expect(await page.textContent('.public-json-import')).toMatch(
      '/foo/bar/foo.json',
    )
    expect(await page.textContent('.public-json-import-content'))
      .toMatchInlineSnapshot(`
        "{
          "foo": "bar"
        }
        "
      `)
  })

  test('from /public (js)', async () => {
    expect(await page.textContent('.public-js-import')).toMatch(
      '/foo/bar/raw.js',
    )
    expect(await page.textContent('.public-js-import-content'))
      .toMatchInlineSnapshot(`
        "document.querySelector('.raw-js').textContent =
          '[success] Raw js from /public loaded'
        "
      `)
    expect(await page.textContent('.public-js-import-content-type')).toMatch(
      'text/javascript',
    )
  })

  test('from /public (ts)', async () => {
    expect(await page.textContent('.public-ts-import')).toMatch(
      '/foo/bar/raw.ts',
    )
    expect(await page.textContent('.public-ts-import-content'))
      .toMatchInlineSnapshot(`
      "export default function other() {
        return 1 + 2
      }
      "
    `)
    // NOTE: users should configure the mime type for .ts files for preview server
    if (isServe) {
      expect(await page.textContent('.public-ts-import-content-type')).toMatch(
        'text/javascript',
      )
    }
  })

  test('from /public (mts)', async () => {
    expect(await page.textContent('.public-mts-import')).toMatch(
      '/foo/bar/raw.mts',
    )
    expect(await page.textContent('.public-mts-import-content'))
      .toMatchInlineSnapshot(`
      "export default function foobar() {
        return 1 + 2
      }
      "
    `)
    // NOTE: users should configure the mime type for .ts files for preview server
    if (isServe) {
      expect(await page.textContent('.public-mts-import-content-type')).toMatch(
        'text/javascript',
      )
    }
  })
})

describe('css url() references', () => {
  test('fonts', async () => {
    expect(
      await page.evaluate(() => {
        return (document as any).fonts.check('700 32px Inter')
      }),
    ).toBe(true)
  })

  test('relative', async () => {
    expect(await getBg('.css-url-relative')).toMatch(assetMatch)
  })

  test('encoded', async () => {
    expect(await getBg('.css-url-encoded')).toMatch(encodedAssetMatch)
  })

  test('image-set relative', async () => {
    const imageSet = await getBg('.css-image-set-relative')
    imageSet.split(', ').forEach((s) => {
      expect(s).toMatch(assetMatch)
    })
  })

  test('image-set without the url() call', async () => {
    const imageSet = await getBg('.css-image-set-without-url-call')
    imageSet.split(', ').forEach((s) => {
      expect(s).toMatch(assetMatch)
    })
  })

  test('image-set with var', async () => {
    const imageSet = await getBg('.css-image-set-with-var')
    imageSet.split(', ').forEach((s) => {
      expect(s).toMatch(assetMatch)
    })
  })

  test('image-set with mix', async () => {
    const imageSet = await getBg('.css-image-set-mix-url-var')
    imageSet.split(', ').forEach((s) => {
      expect(s).toMatch(assetMatch)
    })
  })

  test('image-set with base64', async () => {
    const imageSet = await getBg('.css-image-set-base64')
    expect(imageSet).toContain('image-set(url("data:image/png;base64,')
  })

  test('image-set with gradient', async () => {
    const imageSet = await getBg('.css-image-set-gradient')
    expect(imageSet).toContain('image-set(url("data:image/png;base64,')
  })

  test('image-set with multiple descriptor', async () => {
    const imageSet = await getBg('.css-image-set-multiple-descriptor')
    imageSet.split(', ').forEach((s) => {
      expect(s).toMatch(assetMatch)
    })
  })

  test('image-set with multiple descriptor as inline style', async () => {
    const imageSet = await getBg(
      '.css-image-set-multiple-descriptor-inline-style',
    )
    imageSet.split(', ').forEach((s) => {
      expect(s).toMatch(assetMatch)
    })
  })

  test('image-set and url exist at the same time.', async () => {
    const imageSet = await getBg('.image-set-and-url-exsiting-at-same-time')
    expect(imageSet).toMatch(assetMatch)
  })

  test('relative in @import', async () => {
    expect(await getBg('.css-url-relative-at-imported')).toMatch(assetMatch)
  })

  test('absolute', async () => {
    expect(await getBg('.css-url-absolute')).toMatch(assetMatch)
  })

  test('from /public', async () => {
    expect(await getBg('.css-url-public')).toMatch(iconMatch)
  })

  test('base64 inline', async () => {
    const match = isBundled
      ? `data:image/png;base64`
      : `/foo/bar/nested/icon.png`
    expect(await getBg('.css-url-base64-inline')).toMatch(match)
    expect(await getBg('.css-url-quotes-base64-inline')).toMatch(match)
  })

  test('no base64 inline for icon and manifest links', async () => {
    const iconEl = await page.$(`link.ico`)
    const href = await iconEl.getAttribute('href')
    expect(href).toMatch(
      isBundled ? /\/foo\/bar\/assets\/favicon-[-\w]{8}\.ico/ : 'favicon.ico',
    )

    const manifestEl = await page.$(`link[rel="manifest"]`)
    const manifestHref = await manifestEl.getAttribute('href')
    expect(manifestHref).toMatch(
      isBundled
        ? /\/foo\/bar\/assets\/manifest-[-\w]{8}\.json/
        : 'manifest.json',
    )
  })

  test('multiple urls on the same line', async () => {
    const bg = await getBg('.css-url-same-line')
    expect(bg).toMatch(assetMatch)
    expect(bg).toMatch(iconMatch)
  })

  test('aliased', async () => {
    const bg = await getBg('.css-url-aliased')
    expect(bg).toMatch(assetMatch)
  })

  test('preinlined SVG', async () => {
    expect(await getBg('.css-url-preinlined-svg')).toMatch(
      /data:image\/svg\+xml,.+/,
    )
  })

  test.runIf(isBuild)('generated paths in CSS', () => {
    const css = findAssetFile(/index-[-\w]{8}\.css$/, 'foo')

    // preserve postfix query/hash
    expect(css).toMatch(`woff2?#iefix`)

    // generate non-relative base for public path in CSS
    expect(css).not.toMatch(`../icon.png`)
  })

  test('url() with svg', async () => {
    const bg = await getBg('.css-url-svg')
    expect(bg).toMatch(/data:image\/svg\+xml,.+/)
    expect(bg).toContain('blue')
    expect(bg).not.toContain('red')

    if (isServe) {
      editFile('nested/fragment-bg-hmr.svg', (code) =>
        code.replace('fill="blue"', 'fill="red"'),
      )
      await expect.poll(() => getBg('.css-url-svg')).toMatch('red')
    }
  })

  test('image-set() with svg', async () => {
    expect(await getBg('.css-image-set-svg')).toMatch(/data:image\/svg\+xml,.+/)
  })

  // bundled dev turns the `?url` CSS into a data URI, while build always
  // writes a CSS file. The svg inside that CSS is therefore never processed:
  // it is not inlined, and it does not get the base prefix.
  // Same cause as '?url import on css' below: the CSS pipeline never runs on a
  // `?url` import (vitejs/vite#22863)
  test.skipIf(isBundledDev)('url() with svg in .css?url', async () => {
    const bg = await getBg('.css-url-svg-in-url')
    expect(bg).toMatch(/data:image\/svg\+xml,.+/)
    expect(bg).toContain('blue')
    expect(bg).not.toContain('red')

    if (isServe) {
      editFile('nested/fragment-bg-hmr2.svg', (code) =>
        code.replace('fill="blue"', 'fill="red"'),
      )
      await expect.poll(() => getBg('.css-url-svg')).toMatch('red')
    }
  })

  test.runIf(isServe)('non inlined url() HMR', async () => {
    const bg = await getBg('.css-url-non-inline-hmr')
    editFile('nested/donuts-large.svg', (code) =>
      code.replace('fill="blue"', 'fill="red"'),
    )
    await expect.poll(() => getBg('.css-url-non-inline-hmr')).not.toBe(bg)
  })
})

describe('image', () => {
  test('src', async () => {
    const img = await page.$('.img-src')
    const src = await img.getAttribute('src')
    expect(src).toMatch(
      isBundled
        ? /\/foo\/bar\/assets\/html-only-asset-[-\w]{8}\.jpg/
        : /\/foo\/bar\/nested\/html-only-asset.jpg/,
    )
  })

  test('src inline', async () => {
    const img = await page.$('.img-src-inline')
    const src = await img.getAttribute('src')
    expect(src).toMatch(
      isBundled
        ? /^data:image\/svg\+xml,%3csvg/
        : /\/foo\/bar\/nested\/inlined.svg/,
    )
  })

  test('srcset', async () => {
    const img = await page.$('.img-src-set')
    const srcset = await img.getAttribute('srcset')
    srcset.split(', ').forEach((s) => {
      expect(s).toMatch(
        isBundled
          ? /\/foo\/bar\/assets\/asset-[-\w]{8}\.png \dx/
          : /\/foo\/bar\/nested\/asset.png \dx/,
      )
    })
  })

  test('srcset (public)', async () => {
    const img = await page.$('.img-src-set-public')
    const srcset = await img.getAttribute('srcset')
    srcset.split(', ').forEach((s) => {
      expect(s).toMatch(/\/foo\/bar\/icon\.png \dx/)
    })
  })

  test('srcset (mixed)', async () => {
    const img = await page.$('.img-src-set-mixed')
    const srcset = await img.getAttribute('srcset')
    const srcs = srcset.split(', ')
    expect(srcs[1]).toMatch(
      isBundled
        ? /\/foo\/bar\/assets\/asset-[-\w]{8}\.png \dx/
        : /\/foo\/bar\/nested\/asset.png \dx/,
    )
  })
})

describe('meta', () => {
  test('og image', async () => {
    const meta = await page.$('.meta-og-image')
    const content = await meta.getAttribute('content')
    expect(content).toMatch(
      isBundled
        ? /\/foo\/bar\/assets\/asset-\w{8}\.png/
        : /\/foo\/bar\/nested\/asset.png/,
    )
  })
})

describe('svg fragments', () => {
  // 404 is checked already, so here we just ensure the urls end with #fragment
  // bundled dev drops the #fragment postfix from hashed asset URLs (vitejs/vite#23028)
  test.skipIf(isBundledDev)('img url', async () => {
    const img = await page.$('.svg-frag-img')
    expect(await img.getAttribute('src')).toMatch(/svg#icon-clock-view$/)
  })

  // bundled dev: #fragment dropped (see 'img url')
  test.skipIf(isBundledDev)('via css url()', async () => {
    expect(await getBg('.icon')).toMatch(/svg#icon-clock-view"\)$/)
  })

  test('from js import', async () => {
    const img = await page.$('.svg-frag-import')
    expect(await img.getAttribute('src')).toMatch(
      // Assert trimmed (data URI starts with < and ends with >)
      /^data:image\/svg\+xml,%3c.*%3e#icon-heart-view$/,
    )
  })

  // bundled dev: #fragment dropped (see 'img url')
  test.skipIf(isBundledDev)('url with an alias', async () => {
    expect(await getBg('.icon-clock-alias')).toMatch(
      /\.svg#icon-clock-view"\)$/,
    )
  })
})

test('Unknown extension assets import', async () => {
  expect(await page.textContent('.unknown-ext')).toMatch(
    isBundled ? 'data:application/octet-stream;' : '/nested/foo.unknown',
  )
})

test('Unknown extension assets import with ?inline', async () => {
  expect(await page.textContent('.unknown-ext-inline')).toMatch(
    'data:application/octet-stream;',
  )
})

test('Asset matched by a relative path in assetsInclude import', async () => {
  expect(await page.textContent('.relative-path-assets-include')).toMatch(
    isBundled
      ? 'data:application/octet-stream;'
      : '/nested/relative-path.custom',
  )
})

test('Asset matched by a relative path in assetsInclude import with ?inline', async () => {
  expect(
    await page.textContent('.relative-path-assets-include-inline'),
  ).toMatch('data:application/octet-stream;')
})

test('?raw import', async () => {
  expect(await page.textContent('.raw')).toMatch('SVG')
  expect(await page.textContent('.raw-html')).toBe('
partial
\n')

  if (isBuild) return
  editFile('nested/partial.html', (code) =>
    code.replace('
partial
', '
partial updated
'),
  )
  await expect
    .poll(() => page.textContent('.raw-html'))
    .toBe('
partial updated
\n')

  // bundled dev logs `playground-temp/assets/nested/...` where dev logs the URL
  // path `/nested/...`. This is a gap on the vite side, not in rolldown
  // (vitejs/vite#23028). Two causes:
  // - the server never passes `cwd` to rolldown (bundledDev.ts), so module ids
  //   start from process.cwd() and show where the project sits on disk
  // - bundledDevHmrClient.ts logs those ids as they are, without turning them
  //   into URL paths first
  // Fix both, then remove this guard so the check runs in bundled dev too.
  if (!isBundled) {
    expect(browserLogs).toStrictEqual(
      expect.arrayContaining([
        expect.stringContaining('hot updated: /nested/partial.html?raw via'),
      ]),
    )
  }
})

test('?no-inline svg import', async () => {
  expect(await page.textContent('.no-inline-svg')).toMatch(
    isBundled
      ? /\/foo\/bar\/assets\/fragment-[-\w]{8}\.svg/
      : '/foo/bar/nested/fragment.svg?no-inline',
  )
})

// bundled dev drops the ?query postfix from hashed asset URLs (build keeps ?foo=bar) (vitejs/vite#23028)
test.skipIf(isBundledDev)(
  '?no-inline svg import -- multiple postfix',
  async () => {
    expect(await page.textContent('.no-inline-svg-mp')).toMatch(
      isBundled
        ? /\/foo\/bar\/assets\/fragment-[-\w]{8}\.svg\?foo=bar/
        : '/foo/bar/nested/fragment.svg?no-inline&foo=bar',
    )
  },
)

test('?inline png import', async () => {
  expect(await page.textContent('.inline-png')).toMatch(
    /^data:image\/png;base64,/,
  )
})

test('?inline public png import', async () => {
  expect(await page.textContent('.inline-public-png')).toMatch(
    /^data:image\/png;base64,/,
  )
})

test('?inline public json import', async () => {
  expect(await page.textContent('.inline-public-json')).toMatch(
    /^data:application\/json;base64,/,
  )
})

test('?url import', async () => {
  const src = readFile('foo.js')
  expect(await page.textContent('.url')).toMatch(
    isBundled
      ? `data:text/javascript;base64,${Buffer.from(src).toString('base64')}`
      : `/foo/bar/foo.js`,
  )
})

// bundled dev turns the `?url` CSS into a data URI, while build always writes
// a CSS file (vitejs/vite#22863).
// After the fix, bundled dev returns the same URL shape as build. Then remove
// the skip and change `isBuild` below to `isBundled`.
test.skipIf(isBundledDev)('?url import on css', async () => {
  const txt = await page.textContent('.url-css')
  expect(txt).toMatch(
    isBuild
      ? /\/foo\/bar\/assets\/icons-[-\w]{8}\.css/
      : '/foo/bar/css/icons.css',
  )
})

describe('unicode url', () => {
  test('from js import', async () => {
    const src = readFile('テスト-測試-white space.js')
    expect(await page.textContent('.unicode-url')).toMatch(
      isBundled
        ? `data:text/javascript;base64,${Buffer.from(src).toString('base64')}`
        : encodeURI(`/foo/bar/テスト-測試-white space.js`),
    )
  })
})

describe.runIf(isBuild)('encodeURI', () => {
  test('img src with encodeURI', async () => {
    const img = await page.$('.encodeURI')
    expect(await img.getAttribute('src')).toMatch(/^data:image\/png;base64,/)
  })
})

test('new URL(..., import.meta.url)', async () => {
  const imgMatch = isBundled
    ? /\/foo\/bar\/assets\/img-[-\w]{8}\.png/
    : '/foo/bar/import-meta-url/img.png'

  expect(await page.textContent('.import-meta-url')).toMatch(imgMatch)
  if (isServe) {
    const loadPromise = page.waitForEvent('load')
    const newContent = readFile('import-meta-url/img-update.png', null)
    let oldContent: Buffer
    editFile('import-meta-url/img.png', null, (_oldContent) => {
      oldContent = _oldContent
      return newContent
    })
    await loadPromise // expect reload
    await expect
      .poll(() => page.textContent('.import-meta-url'))
      .toMatch(imgMatch)

    const loadPromise2 = page.waitForEvent('load')
    editFile('import-meta-url/img.png', null, (_) => oldContent)
    await loadPromise2 // expect reload
    await expect
      .poll(() => page.textContent('.import-meta-url'))
      .toMatch(imgMatch)
  }
})

test('new URL("@/...", import.meta.url)', async () => {
  expect(await page.textContent('.import-meta-url-dep')).toMatch(assetMatch)
})

test('new URL("/...", import.meta.url)', async () => {
  expect(await page.textContent('.import-meta-url-base-path')).toMatch(
    iconMatch,
  )
})

test('new URL("data:...", import.meta.url)', async () => {
  const img = await page.$('.import-meta-url-data-uri-img')
  expect(await img.getAttribute('src')).toMatch(/^data:image\/png;base64,/)
  expect(await page.textContent('.import-meta-url-data-uri')).toMatch(
    /^data:image\/png;base64,/,
  )
})

test('new URL(..., import.meta.url) without extension', async () => {
  expect(await page.textContent('.import-meta-url-without-extension')).toMatch(
    isBundled ? 'data:text/javascript' : 'nested/test.js',
  )
  expect(
    await page.textContent('.import-meta-url-content-without-extension'),
  ).toContain('export default class')
})

test('new URL(`${dynamic}`, import.meta.url)', async () => {
  expect(await page.textContent('.dynamic-import-meta-url-1')).toMatch(
    isBundled ? 'data:image/png;base64' : '/foo/bar/nested/icon.png',
  )
  expect(await page.textContent('.dynamic-import-meta-url-2')).toMatch(
    assetMatch,
  )
  expect(await page.textContent('.dynamic-import-meta-url-js')).toMatch(
    isBundled ? 'data:text/javascript;base64' : '/foo/bar/nested/test.js',
  )
})

// bundled dev: ?abc postfix dropped (see '?no-inline svg import -- multiple postfix')
test.skipIf(isBundledDev)(
  'new URL(`./${dynamic}?abc`, import.meta.url)',
  async () => {
    expect(await page.textContent('.dynamic-import-meta-url-1-query')).toMatch(
      isBundled ? 'data:image/png;base64' : '/foo/bar/nested/icon.png?abc',
    )
    expect(await page.textContent('.dynamic-import-meta-url-2-query')).toMatch(
      isBundled
        ? /\/foo\/bar\/assets\/asset-[-\w]{8}\.png\?abc/
        : '/foo/bar/nested/asset.png?abc',
    )
  },
)

// bundled dev: ?abc postfix dropped (see '?no-inline svg import -- multiple postfix')
test.skipIf(isBundledDev)(
  'new URL(`./${1 === 0 ? static : dynamic}?abc`, import.meta.url)',
  async () => {
    expect(
      await page.textContent('.dynamic-import-meta-url-1-ternary'),
    ).toMatch(
      isBundled ? 'data:image/png;base64' : '/foo/bar/nested/icon.png?abc',
    )
    expect(
      await page.textContent('.dynamic-import-meta-url-2-ternary'),
    ).toMatch(
      isBundled
        ? /\/foo\/bar\/assets\/asset-[-\w]{8}\.png\?abc/
        : '/foo/bar/nested/asset.png?abc',
    )
  },
)

test("new URL(/* @vite-ignore */ 'non-existent', import.meta.url)", async () => {
  // the inlined script tag is extracted in a separate file
  const importMetaUrl = new URL(
    isBundled ? '/foo/bar/assets/index.js' : '/foo/bar/index.html',
    page.url(),
  )
  expect(await page.textContent('.non-existent-import-meta-url')).toMatch(
    new URL('non-existent', importMetaUrl).pathname,
  )
  expect(serverLogs).not.toContainEqual(
    expect.stringContaining("doesn't exist at build time"),
  )
})

test('new URL(..., import.meta.url) (multiline)', async () => {
  const assetMatch = isBundled
    ? /\/foo\/bar\/assets\/asset-[-\w]{8}\.png/
    : '/foo/bar/nested/asset.png'

  expect(await page.textContent('.import-meta-url-multiline')).toMatch(
    assetMatch,
  )
})

test.runIf(isBuild)('manifest', async () => {
  const manifest = readManifest('foo')
  const entry = manifest['index.html']

  for (const file of listAssets('foo')) {
    if (file.endsWith('.css')) {
      // ignore icons-*.css and css-url-url-*.css as it's imported with ?url
      if (file.includes('icons-') || file.includes('css-url-url-')) continue
      expect(entry.css).toContain(`assets/${file}`)
    } else if (!file.endsWith('.js')) {
      expect(entry.assets).toContain(`assets/${file}`)
    }
  }
})

describe.runIf(isBuild)('css and assets in css in build watch', () => {
  test('css will not be lost and css does not contain undefined', async () => {
    editFile('index.html', (code) => code.replace('Assets', 'assets2'))
    await notifyRebuildComplete(watcher)
    const cssFile = findAssetFile(/index-[-\w]+\.css$/, 'foo')
    expect(cssFile).not.toBe('')
    expect(cssFile).not.toMatch(/undefined/)
  })

  test('old file is removed when the content changes', async () => {
    await expect.poll(() => page.textContent('.update-content')).toBe('hello')

    const oldMainJsFiles = listAssets('foo').filter((f) =>
      /index-[-\w]+\.js$/.test(f),
    )
    expect(oldMainJsFiles.length).toBe(1)
    const oldMainJsFile = oldMainJsFiles[0]

    editFile('asset/update.js', (code) => code.replace('hello', 'world2'))
    await notifyRebuildComplete(watcher)
    await page.reload()
    await expect.poll(() => page.textContent('.update-content')).toBe('world2')

    const newMainJsFiles = listAssets('foo').filter((f) =>
      /index-[-\w]+\.js$/.test(f),
    )
    expect(newMainJsFiles).not.toContain(oldMainJsFile)
    expect(newMainJsFiles.length).toBe(1)
  })

  test('import module.css', async () => {
    expect(await getColor('#foo')).toBe('red')
    editFile('css/foo.module.css', (code) => code.replace('red', 'blue'))
    await notifyRebuildComplete(watcher)
    await page.reload()
    expect(await getColor('#foo')).toBe('blue')
  })

  test('import with raw query', async () => {
    expect(await page.textContent('.raw-query')).toBe('foo')
    editFile('static/foo.txt', (code) => code.replace('foo', 'zoo2'))
    await notifyRebuildComplete(watcher)
    await page.reload()
    expect(await page.textContent('.raw-query')).toBe('zoo2')
  })
})

test('inline style test', async () => {
  expect(await getBg('.inline-style')).toMatch(assetMatch)
  expect(await getBg('.style-url-assets')).toMatch(assetMatch)
})

if (!isBuild) {
  // bundled dev: editing a CSS file imported by an inline


# Element Access

There are many ways elements in a JSON value can be accessed:

- unchecked access via [`operator[]`](unchecked_access.md)
- checked access via [`at`](checked_access.md)
- access with default value via [`value`](default_value.md)
- iterators
- JSON pointers


---
title: 5th anniversary of the Vite Team Creation
layout: home
theme: dark
description: "Vite: The Documentary re-premiere. Join the ecosystem this March 19th at 3 PM UTC to remember the story we wrote together. And participate in the live stage to discuss our past, present, and future. We'll be waiting for you!"
head:
  - - meta
    - property: og:type
      content: website
  - - meta
    - property: og:title
      content: 5th anniversary of the Vite Team Creation
  - - meta
    - property: og:description
      content: "Vite: The Documentary re-premiere. Join the ecosystem this March 19th at 3 PM UTC to remember the story we wrote together. And participate in the live stage to discuss our past, present, and future. We'll be waiting for you!"
  - - meta
    - property: og:type
      content: website
  - - meta
    - property: og:image
      content: https://vite.dev/og-image-5-anniversary-live.webp
  - - meta
    - property: og:url
      content: https://vite.dev/live
  - - meta
    - name: twitter:card
      content: summary_large_image
---

<script setup>
import Live from './.vitepress/theme/live/Layout.vue'
</script>

<Live />


# <small>nlohmann::basic_json::</small>unflatten

```cpp
basic_json unflatten() const;
```

The function restores the arbitrary nesting of a JSON value that has been flattened before using the
[`flatten()`](flatten.md) function. The JSON value must meet certain constraints:

1. The value must be an object.
2. The keys must be JSON pointers (see [RFC 6901](https://tools.ietf.org/html/rfc6901))
3. The mapped values must be primitive JSON types.

## Return value

the original JSON from a flattened version

## Exception safety

Strong exception safety: if an exception occurs, the original value stays intact.

## Exceptions

The function can throw the following exceptions:

- Throws [`type_error.314`](../../home/exceptions.md#jsonexceptiontype_error314) if value is not an object
- Throws [`type_error.315`](../../home/exceptions.md#jsonexceptiontype_error315) if object values are not primitive
- Throws [`type_error.313`](../../home/exceptions.md#jsonexceptiontype_error313) if a key (JSON pointer) leads to a
  conflicting nesting; example: `"invalid value to unflatten"`
- Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in a key is not a
  number; example: `"array index 'one' is not a number"`

## Complexity

Linear in the size of the JSON value.

## Notes

Empty objects and arrays are flattened by [`flatten()`](flatten.md) to `#!json null` values and cannot unflattened to
their original type. Apart from this example, for a JSON value `j`, the following is always true:
`#!cpp j == j.flatten().unflatten()`.

## Examples

??? example

    The following code shows how a flattened JSON object is unflattened into the original nested JSON object.

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

    Output:

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

## See also

- [flatten](flatten.md) the reverse function

## Version history

- Added in version 2.0.0.


// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.

// This test should not be instrumented.
#include <cstdint>
#include <cstddef>

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
  return 0;
}


Glob import

Normal

Eager

node_modules

Raw

Property access

Relative raw

Side effect

Tree shake Eager CSS

Should be orange

Should be orange

Escape relative glob

Escape alias glob

Escape literal parenthesis

Subpath imports

Subpath imports (sub dir)

#alias imports

In package

Base

Follow symlinks

Follow symlinks same reference

Alias exclusion

Array Pattern with Exclusions

Array Pattern with Sibling Directories Sharing a Prefix

Transform visibility

Case Sensitive Match (default: true)

Case Sensitive Match (false)

Absolute base outside root


This directory contains updated benchmarks as of 2022-12-16. They were captured
via the benchsuite script at `benchsuite/benchsuite` from the root of this
repository. The command that was run:

    $ ./benchsuite \
          --dir /dev/shm/benchsuite \
          --raw runs/2022-12-16-archlinux-duff/raw.csv \
          | tee runs/2022-12-16-archlinux-duff/summary

The versions of each tool are as follows:

    $ rg --version
    ripgrep 13.0.0 (rev 87c4a2b4b1)
    -SIMD -AVX (compiled)
    +SIMD +AVX (runtime)

    $ grep -V
    grep (GNU grep) 3.8

    $ ag -V
    ag version 2.2.0

    Features:
      +jit +lzma +zlib

    $ git --version
    git version 2.39.0

    $ ugrep --version
    ugrep 3.9.2 x86_64-pc-linux-gnu +avx2 +pcre2jit +zlib +bzip2 +lzma +lz4 +zstd
    License BSD-3-Clause: <https://opensource.org/licenses/BSD-3-Clause>
    Written by Robert van Engelen and others: <https://github.com/Genivia/ugrep>

The version of ripgrep used was compiled from source on commit 7f23cd63:

    $ cargo build --release --features 'pcre2'

This was run on a machine with an Intel i9-12900K with 128GB of memory.


export interface DefineImportMetadata {
  /**
   * Imported names before being transformed to `ssrImportKey`
   *
   * import foo, { bar as baz, qux } from 'hello'
   * => ['default', 'bar', 'qux']
   *
   * import * as namespace from 'world
   * => undefined
   */
  importedNames?: string[]
}

export interface SSRImportMetadata extends DefineImportMetadata {
  isDynamicImport?: boolean
}

/**
 * Vite converts `import { } from 'foo'` to `const _ = __vite_ssr_import__('foo')`.
 * Top-level imports and dynamic imports work slightly differently in Node.js.
 * This function normalizes the differences so it matches prod behaviour.
 */
export function analyzeImportedModDifference(
  mod: any,
  rawId: string,
  moduleType: string | undefined,
  metadata?: SSRImportMetadata,
): void {
  // No normalization needed if the user already dynamic imports this module
  if (metadata?.isDynamicImport) return

  // If the user named imports a specifier that can't be analyzed, error.
  // If the module doesn't import anything explicitly, e.g. `import 'foo'` or
  // `import * as foo from 'foo'`, we can skip.
  if (metadata?.importedNames?.length) {
    const missingBindings = metadata.importedNames.filter((s) => !(s in mod))
    if (missingBindings.length) {
      const lastBinding = missingBindings[missingBindings.length - 1]

      // For invalid named exports only, similar to how Node.js errors for top-level imports.
      // But since we transform as dynamic imports, we need to emulate the error manually.
      if (moduleType === 'module') {
        throw new SyntaxError(
          `[vite] The requested module '${rawId}' does not provide an export named '${lastBinding}'`,
        )
      } else {
        // For non-ESM, named imports is done via static analysis with cjs-module-lexer in Node.js.
        // Copied from Node.js
        throw new SyntaxError(`\
[vite] Named export '${lastBinding}' not found. The requested module '${rawId}' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from '${rawId}';
const {${missingBindings.join(', ')}} = pkg;
`)
      }
    }
  }
}


import type * as http from 'node:http'
import * as httpProxy from 'http-proxy-3'
import colors from 'picocolors'
import type { Connect } from '#dep-types/connect'
import { createDebugger } from '../../utils'
import type { CommonServerOptions, ResolvedConfig } from '../..'
import type { HttpServer } from '..'

const debug = createDebugger('vite:proxy')

export interface ProxyOptions extends httpProxy.ServerOptions {
  /**
   * rewrite path
   */
  rewrite?: (path: string) => string
  /**
   * configure the proxy server (e.g. listen to events)
   */
  configure?: (proxy: httpProxy.ProxyServer, options: ProxyOptions) => void
  /**
   * webpack-dev-server style bypass function
   */
  bypass?: (
    req: http.IncomingMessage,
    /** undefined for WebSocket upgrade requests */
    res: http.ServerResponse | undefined,
    options: ProxyOptions,
  ) =>
    | void
    | null
    | undefined
    | false
    | string
    | Promise<void | null | undefined | boolean | string>
  /**
   * rewrite the Origin header of a WebSocket request to match the target
   *
   * **Exercise caution as rewriting the Origin can leave the proxying open to [CSRF attacks](https://owasp.org/www-community/attacks/csrf).**
   */
  rewriteWsOrigin?: boolean | undefined
}

const rewriteOriginHeader = (
  proxyReq: http.ClientRequest,
  options: ProxyOptions,
  config: ResolvedConfig,
) => {
  // Browsers may send Origin headers even with same-origin
  // requests. It is common for WebSocket servers to check the Origin
  // header, so if rewriteWsOrigin is true we change the Origin to match
  // the target URL.
  if (options.rewriteWsOrigin) {
    const { target } = options

    if (proxyReq.headersSent) {
      config.logger.warn(
        colors.yellow(
          `Unable to rewrite Origin header as headers are already sent.`,
        ),
      )
      return
    }

    if (proxyReq.getHeader('origin') && target) {
      const changedOrigin =
        typeof target === 'object'
          ? `${target.protocol ?? 'http:'}//${target.host}`
          : target

      proxyReq.setHeader('origin', changedOrigin)
    }
  }
}

export function proxyMiddleware(
  httpServer: HttpServer | null,
  options: NonNullable<CommonServerOptions['proxy']>,
  config: ResolvedConfig,
): Connect.NextHandleFunction {
  // lazy require only when proxy is used
  const proxies: Record<string, [httpProxy.ProxyServer, ProxyOptions]> = {}

  Object.keys(options).forEach((context) => {
    let opts = options[context]
    if (!opts) {
      return
    }
    if (typeof opts === 'string') {
      opts = { target: opts, changeOrigin: true }
    }
    const proxy = httpProxy.createProxyServer(opts)

    if (opts.configure) {
      opts.configure(proxy, opts)
    }

    proxy.on('error', (err, _req, res) => {
      // When it is ws proxy, res is net.Socket
      if ('req' in res) {
        config.logger.error(
          `${colors.red(`http proxy error: ${res.req.url}`)}\n${err.stack}`,
          {
            timestamp: true,
            error: err,
          },
        )
        if (!res.headersSent && !res.writableEnded) {
          res
            .writeHead(502, {
              'Content-Type': 'text/plain',
            })
            .end()
        }
      } else {
        config.logger.error(`${colors.red(`ws proxy error:`)}\n${err.stack}`, {
          timestamp: true,
          error: err,
        })
        res.end()
      }
    })

    proxy.on('proxyReqWs', (proxyReq, _req, socket, options) => {
      rewriteOriginHeader(proxyReq, options, config)

      socket.on('error', (err) => {
        config.logger.error(
          `${colors.red(`ws proxy socket error:`)}\n${err.stack}`,
          {
            timestamp: true,
            error: err,
          },
        )
      })
    })

    // clone before saving because http-proxy mutates the options
    proxies[context] = [proxy, { ...opts }]
  })

  if (httpServer) {
    httpServer.on('upgrade', async (req, socket, head) => {
      const url = req.url!
      for (const context in proxies) {
        if (doesProxyContextMatchUrl(context, url)) {
          const [proxy, opts] = proxies[context]
          if (
            opts.ws ||
            opts.target?.toString().startsWith('ws:') ||
            opts.target?.toString().startsWith('wss:')
          ) {
            if (opts.bypass) {
              try {
                const bypassResult = await opts.bypass(req, undefined, opts)
                if (typeof bypassResult === 'string') {
                  debug?.(`bypass: ${req.url} -> ${bypassResult}`)
                  req.url = bypassResult
                  return
                }
                if (bypassResult === false) {
                  debug?.(`bypass: ${req.url} -> 404`)
                  socket.end('HTTP/1.1 404 Not Found\r\n\r\n', '')
                  return
                }
              } catch (err) {
                config.logger.error(
                  `${colors.red(`ws proxy bypass error:`)}\n${err.stack}`,
                  {
                    timestamp: true,
                    error: err,
                  },
                )
                return
              }
            }

            if (opts.rewrite) {
              req.url = opts.rewrite(url)
            }
            debug?.(`${req.url} -> ws ${opts.target}`)
            proxy.ws(req, socket, head)
            return
          }
        }
      }
    })
  }

  // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
  return async function viteProxyMiddleware(req, res, next) {
    const url = req.url!
    for (const context in proxies) {
      if (doesProxyContextMatchUrl(context, url)) {
        const [proxy, opts] = proxies[context]
        const options: httpProxy.ServerOptions = {}

        if (opts.bypass) {
          try {
            const bypassResult = await opts.bypass(req, res, opts)
            if (typeof bypassResult === 'string') {
              debug?.(`bypass: ${req.url} -> ${bypassResult}`)
              req.url = bypassResult
              if (res.writableEnded) {
                return
              }
              return next()
            }
            if (bypassResult === false) {
              debug?.(`bypass: ${req.url} -> 404`)
              res.statusCode = 404
              return res.end()
            }
          } catch (e) {
            debug?.(`bypass: ${req.url} -> ${e}`)
            return next(e)
          }
        }

        debug?.(`${req.url} -> ${opts.target || opts.forward}`)
        if (opts.rewrite) {
          req.url = opts.rewrite(req.url!)
        }
        proxy.web(req, res, options)
        return
      }
    }
    next()
  }
}

function doesProxyContextMatchUrl(context: string, url: string): boolean {
  return (
    (context[0] === '^' && new RegExp(context).test(url)) ||
    url.startsWith(context)
  )
}


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

#pragma once

#include <type_traits>

#include <nlohmann/detail/meta/void_t.hpp>

NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{

// https://en.cppreference.com/w/cpp/experimental/is_detected
struct nonesuch
{
    nonesuch() = delete;
    ~nonesuch() = delete;
    nonesuch(nonesuch const&) = delete;
    nonesuch(nonesuch const&&) = delete;
    void operator=(nonesuch const&) = delete;
    void operator=(nonesuch&&) = delete;
};

template<class Default,
         class AlwaysVoid,
         template<class...> class Op,
         class... Args>
struct detector
{
    using value_t = std::false_type;
    using type = Default;
};

template<class Default, template<class...> class Op, class... Args>
struct detector<Default, void_t<Op<Args...>>, Op, Args...>
{
    using value_t = std::true_type;
    using type = Op<Args...>;
};

template<template<class...> class Op, class... Args>
using is_detected = typename detector<nonesuch, void, Op, Args...>::value_t;

template<template<class...> class Op, class... Args>
struct is_detected_lazy : is_detected<Op, Args...> { };

template<template<class...> class Op, class... Args>
using detected_t = typename detector<nonesuch, void, Op, Args...>::type;

template<class Default, template<class...> class Op, class... Args>
using detected_or = detector<Default, void, Op, Args...>;

template<class Default, template<class...> class Op, class... Args>
using detected_or_t = typename detected_or<Default, Op, Args...>::type;

template<class Expected, template<class...> class Op, class... Args>
using is_detected_exact = std::is_same<Expected, detected_t<Op, Args...>>;

template<class To, template<class...> class Op, class... Args>
using is_detected_convertible =
    std::is_convertible<detected_t<Op, Args...>, To>;

}  // namespace detail
NLOHMANN_JSON_NAMESPACE_END