blob: e91100a2073c79dd438bda885eaa49aad9a7b90f [file] [log] [blame]
// Copyright (c) 2020 Google LLC All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#![deny(missing_docs)]
use {
crate::{error::*, formatter::*, options::*, parser::*},
std::cell::{Ref, RefCell, RefMut},
std::cmp::Ordering,
std::rc::Rc,
};
/// Represents the parsed state of a given JSON5 document.
pub struct ParsedDocument {
/// The saved document input buffer, if constructed with from_string().
owned_buffer: Option<String>,
/// The input filename, if any.
filename: Option<String>,
/// The parsed document model represented as an array of zero or more objects to format.
pub content: Array,
}
impl ParsedDocument {
/// Parses the JSON5 document represented by `buffer`, and returns a parsed representation of
/// the document that can be formatted by
/// [Json5Format::to_utf8()](struct.Json5Format.html#method.to_utf8).
///
/// If a filename is also provided, any parsing errors will include the filename with the line
/// number and column where the error was encountered.
pub fn from_str(buffer: &str, filename: Option<String>) -> Result<Self, Error> {
let mut parser = Parser::new(&filename);
let content = parser.parse(&buffer)?;
Ok(Self { owned_buffer: None, filename, content })
}
/// Parses the JSON5 document represented by `buffer`, and returns a parsed representation of
/// the document that can be formatted by
/// [Json5Format::to_utf8()](struct.Json5Format.html#method.to_utf8).
///
/// The returned `ParsedDocument` object retains ownership of the input buffer, which can be
/// useful in situations where borrowing the buffer (via
/// [from_str()](struct.ParsedDocument.html#method.from_str) requires burdensome workarounds.
///
/// If a filename is also provided, any parsing errors will include the filename with the line
/// number and column where the error was encountered.
pub fn from_string(buffer: String, filename: Option<String>) -> Result<Self, Error> {
let mut parser = Parser::new(&filename);
let content = parser.parse(&buffer)?;
Ok(Self { owned_buffer: Some(buffer), filename, content })
}
/// Returns the filename, if provided when the object was created.
pub fn filename(&self) -> &Option<String> {
&self.filename
}
/// Borrows the input buffer owned by this object, if provided by calling
/// [from_string()](struct.ParsedDocument.html#method.from_string).
pub fn input_buffer(&self) -> &Option<String> {
&self.owned_buffer
}
}
/// Represents the variations of allowable comments.
#[derive(Debug, Clone)]
pub enum Comment {
/// Represents a comment read from a `/* */` pattern.
Block {
/// The content of the block comment, represented as a `String` for each line.
lines: Vec<String>,
/// `align` (if true) indicates that all comment `lines` started in a column after the
/// star's column in the opening `/*`. For each subsequent line in lines, the spaces from
/// column 0 to the star's column will be stripped, allowing the indent spaces to be
/// restored, during format, relative to the block's new horizontal position. Otherwise, the
/// original indentation will not be stripped, and the lines will be restored at their
/// original horizontal position. In either case, lines after the opening `/*` will retain
/// their original horizontal alignment, relative to one another.
align: bool,
},
/// Represents a comment read from a line starting with `//`.
Line(String),
/// Represents a blank line between data.
Break,
}
impl Comment {
/// Returns `true` if the `Comment` instance is a `Block` variant.
pub fn is_block(&self) -> bool {
match self {
Comment::Block { .. } => true,
_ => false,
}
}
/// Returns `true` if the `Comment` instance is a `Line` variant.
#[allow(dead_code)] // for API consistency and tests even though enum is currently not `pub`
pub fn is_line(&self) -> bool {
match self {
Comment::Line(..) => true,
_ => false,
}
}
/// Returns `true` if the `Comment` instance is a `Break` variant.
#[allow(dead_code)] // for API consistency and tests even though enum is currently not `pub`
pub fn is_break(&self) -> bool {
match self {
Comment::Break => true,
_ => false,
}
}
pub(crate) fn format<'a>(
&self,
formatter: &'a mut Formatter,
) -> Result<&'a mut Formatter, Error> {
match self {
Comment::Block { lines, align } => {
let len = lines.len();
for (index, line) in lines.iter().enumerate() {
let is_first = index == 0;
let is_last = index == len - 1;
if is_first {
formatter.append(&format!("/*{}", line))?;
} else if line.len() > 0 {
formatter.append(&format!("{}", line))?;
}
if !is_last {
if *align {
formatter.start_next_line()?;
} else {
formatter.append_newline()?;
}
}
}
formatter.append("*/")
}
Comment::Line(comment) => formatter.append(&format!("//{}", comment)),
Comment::Break => Ok(&mut *formatter), // inserts blank line only
}?;
formatter.start_next_line()
}
}
/// A struct containing all comments associated with a specific `Value`.
#[derive(Clone)]
pub struct Comments {
/// Comments applied to the associated value.
before_value: Vec<Comment>,
/// A line comment positioned after and on the same line as the last character of the value. The
/// comment may have multiple lines, if parsed as a contiguous group of line comments that are
/// all left-aligned with the initial line comment.
end_of_line_comment: Option<String>,
}
impl Comments {
/// Retrieves the comments immediately before an associated value.
pub fn before_value(&self) -> &Vec<Comment> {
&self.before_value
}
/// Injects text into the end-of-line comment.
pub fn append_end_of_line_comment(&mut self, comment: &str) -> Result<(), Error> {
let updated = match self.end_of_line_comment.take() {
None => comment.to_string(),
Some(current) => current + "\n" + comment,
};
self.end_of_line_comment = Some(updated);
Ok(())
}
/// Retrieves a reference to the end-of-line comment.
pub fn end_of_line(&self) -> &Option<String> {
&self.end_of_line_comment
}
}
/// A struct used for capturing comments at the end of an JSON5 array or object, which are not
/// associated to any of the Values contained in the array/object.
pub(crate) struct ContainedComments {
/// Parsed comments to be applied to the next Value, when reached.
/// If there are any pending comments after the last item, they are written after the last
/// item when formatting.
pending_comments: Vec<Comment>,
/// Immediately after capturing a Value (primitive or start of an object or array block),
/// this is set to the new Value. If a line comment is captured *before* capturing a
/// newline, the line comment is applied to the current_line_value.
current_line_value: Option<Rc<RefCell<Value>>>,
/// If an end-of-line comment is captured after capturing a Value, this saves the column of the
/// first character in the line comment. Successive line comments with the same start column
/// are considered continuations of the end-of-line comment.
end_of_line_comment_start_column: Option<usize>,
}
impl ContainedComments {
fn new() -> Self {
Self {
pending_comments: vec![],
current_line_value: None,
end_of_line_comment_start_column: None,
}
}
/// After parsing a value, if a newline is encountered before an end-of-line comment, the
/// current line no longer has the value.
fn on_newline(&mut self) -> Result<(), Error> {
if self.end_of_line_comment_start_column.is_none() {
self.current_line_value = None;
}
Ok(())
}
/// Adds a standalone line comment to this container, or adds an end_of_line_comment to the
/// current container's current value.
///
/// # Arguments
/// * `content`: the line comment content (including leading spaces)
/// * `start_column`: the column number of the first character of content. If this line
/// comment was immediately preceded by an end-of-line comment, and both line comments
/// have the same start_column, then this line comment is a continuation of the end-of-line
/// comment (on a new line). Formatting should retain the associated vertical alignment.
/// * `pending_new_line_comment_block` - If true and the comment is not an
/// end_of_line_comment, the container should insert a line_comment_break before inserting
/// the next line comment. This should only be true if this standalone line comment was
/// preceded by one or more standalone line comments and one or more blank lines.
/// (This flag is ignored if the comment is part of an end-of-line comment.)
///
/// # Returns
/// true if the line comment is standalone, that is, not an end_of_line_comment
fn add_line_comment(
&mut self,
content: &str,
start_column: usize,
pending_new_line_comment_block: bool,
) -> Result<bool, Error> {
if let Some(value_ref) = &mut self.current_line_value {
if start_column == *self.end_of_line_comment_start_column.get_or_insert(start_column) {
(*value_ref.borrow_mut()).comments_mut().append_end_of_line_comment(content)?;
return Ok(false); // the comment is (part of) an end-of-line comment
}
self.current_line_value = None;
}
if pending_new_line_comment_block {
self.pending_comments.push(Comment::Break);
}
self.pending_comments.push(Comment::Line(content.to_string()));
Ok(true)
}
/// Add a block comment, to be applied to the next contained value, or to the end of the current
/// container.
fn add_block_comment(&mut self, comment: Comment) -> Result<(), Error> {
self.current_line_value = None;
self.pending_comments.push(comment);
Ok(())
}
/// There are one or more line and/or block comments to be applied to the next contained value,
/// or to the end of the current container.
fn has_pending_comments(&self) -> bool {
self.pending_comments.len() > 0
}
/// When a value is encountered inside the current container, move all pending comments from the
/// container to the new value.
fn take_pending_comments(&mut self) -> Vec<Comment> {
self.pending_comments.drain(..).collect()
}
}
/// Represents the possible data types in a JSON5 object. Each variant has a field representing a
/// specialized struct representing the value's data, and a field for comments (possibly including a
/// line comment and comments appearing immediately before the value). For `Object` and `Array`,
/// comments appearing at the end of the the structure are encapsulated inside the appropriate
/// specialized struct.
pub enum Value {
/// Represents a non-recursive data type (string, bool, number, or "null") and its associated
/// comments.
Primitive {
/// The struct containing the associated value.
val: Primitive,
/// The associated comments.
comments: Comments,
},
/// Represents a JSON5 array and its associated comments.
Array {
/// The struct containing the associated value.
val: Array,
/// The comments associated with the array.
comments: Comments,
},
/// Represents a JSON5 object and its associated comments.
Object {
/// The struct containing the associated value.
val: Object,
/// The comments associated with the object.
comments: Comments,
},
}
impl Value {
/// Returns `true` for an `Array` variant.
pub fn is_array(&self) -> bool {
match self {
Value::Array { .. } => true,
_ => false,
}
}
/// Returns `true` for an `Object` variant.
pub fn is_object(&self) -> bool {
match self {
Value::Object { .. } => true,
_ => false,
}
}
/// Returns `true` for a `Primitive` variant.
pub fn is_primitive(&self) -> bool {
match self {
Value::Primitive { .. } => true,
_ => false,
}
}
/// Recursively formats the data inside a `Value`.
pub(crate) fn format<'a>(
&self,
formatter: &'a mut Formatter,
) -> Result<&'a mut Formatter, Error> {
use Value::*;
match self {
Primitive { val, .. } => val.format(formatter),
Array { val, .. } => val.format(formatter),
Object { val, .. } => val.format(formatter),
}
}
/// Retrieves an immutable reference to the `comments` attribute of any variant.
pub fn comments(&self) -> &Comments {
use Value::*;
match self {
Primitive { comments, .. } | Array { comments, .. } | Object { comments, .. } => {
comments
}
}
}
/// Returns a mutable reference to the `comments` attribute of any variant.
pub fn comments_mut(&mut self) -> &mut Comments {
use Value::*;
match self {
Primitive { comments, .. } | Array { comments, .. } | Object { comments, .. } => {
comments
}
}
}
/// Returns true if this value has any block, line, or end-of-line comment(s).
pub fn has_comments(&mut self) -> bool {
let comments = self.comments();
comments.before_value().len() > 0 || comments.end_of_line().is_some()
}
}
impl std::fmt::Debug for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use Value::*;
match self {
Primitive { val, .. } => val.fmt(f),
Array { val, .. } => val.fmt(f),
Object { val, .. } => val.fmt(f),
}
}
}
/// Represents a primitive value in a JSON5 object property or array item.
/// The parsed value is stored as a formatted string, retaining its original format,
/// and written to the formatted document just as it appeared.
pub struct Primitive {
value_string: String,
}
impl Primitive {
/// Instantiates a `Value::Array` with empty data and the provided comments.
pub(crate) fn new(value_string: String, comments: Vec<Comment>) -> Value {
Value::Primitive {
val: Primitive { value_string },
comments: Comments { before_value: comments, end_of_line_comment: None },
}
}
/// Returns the primitive value, as a formatted string.
#[inline]
pub fn as_str(&self) -> &str {
&self.value_string
}
fn format<'a>(&self, formatter: &'a mut Formatter) -> Result<&'a mut Formatter, Error> {
formatter.append(&self.value_string)
}
}
impl std::fmt::Debug for Primitive {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Primitive: {}", self.value_string)
}
}
/// An interface that represents the recursive nature of `Object` and `Array`.
pub(crate) trait Container {
/// Called by the `Parser` to add a parsed `Value` to the current `Container`.
fn add_value(&mut self, value: Rc<RefCell<Value>>, parser: &Parser<'_>) -> Result<(), Error>;
/// The parser encountered a comma, indicating the end of an element declaration. Since
/// commas are optional, close() also indicates the end of a value without a trailing comma.
fn end_value(&mut self, _parser: &Parser<'_>) -> Result<(), Error>;
/// The parser encountered a closing brace indicating the end of the container's declaration.
fn close(&mut self, _parser: &Parser<'_>) -> Result<(), Error>;
/// Formats the content of a container (inside the braces) in accordance with the JSON5 syntax
/// and given format options.
fn format_content<'a>(&self, formatter: &'a mut Formatter) -> Result<&'a mut Formatter, Error>;
/// Retrieves an immutable reference to the `contained_comments` attribute.
fn contained_comments(&self) -> &ContainedComments;
/// Retrieves a mutable reference to the `contained_comments` attribute.
fn contained_comments_mut(&mut self) -> &mut ContainedComments;
/// See `ContainedComments::on_newline`.
fn on_newline(&mut self) -> Result<(), Error> {
self.contained_comments_mut().on_newline()
}
/// See `ContainedComments::add_line_comment`.
fn add_line_comment(
&mut self,
content: &str,
start_column: usize,
pending_new_line_comment_block: bool,
) -> Result<bool, Error> {
self.contained_comments_mut().add_line_comment(
content,
start_column,
pending_new_line_comment_block,
)
}
/// See `ContainedComments::add_block_comment`.
fn add_block_comment(&mut self, comment: Comment) -> Result<(), Error> {
self.contained_comments_mut().add_block_comment(comment)
}
/// See `ContainedComments::has_pending_comments`.
fn has_pending_comments(&self) -> bool {
self.contained_comments().has_pending_comments()
}
/// See `ContainedComments::take_pending_comments`.
fn take_pending_comments(&mut self) -> Vec<Comment> {
self.contained_comments_mut().take_pending_comments()
}
}
/// Represents a JSON5 array of items. During parsing, this object's state changes, as comments and
/// items are encountered. Parsed comments are temporarily stored in contained_comments, to be
/// transferred to the next parsed item. After the last item, if any other comments are encountered,
/// those comments are retained in the contained_comments field, to be restored during formatting,
/// after writing the last item.
pub struct Array {
/// The array items.
items: Vec<Rc<RefCell<Value>>>,
/// Set to true when a value is encountered (parsed primitive, or sub-container in process)
/// and false when a comma or the array's closing brace is encountered. This supports
/// validating that each array item is separated by one and only one comma.
is_parsing_value: bool,
/// Manages parsed comments inside the array scope, which are either transferred to each array
/// item, or retained for placement after the last array item.
contained_comments: ContainedComments,
}
impl Array {
/// Instantiates a `Value::Array` with empty data and the provided comments.
pub(crate) fn new(comments: Vec<Comment>) -> Value {
Value::Array {
val: Array {
items: vec![],
is_parsing_value: false,
contained_comments: ContainedComments::new(),
},
comments: Comments { before_value: comments, end_of_line_comment: None },
}
}
/// Returns an iterator over the array items. Items must be dereferenced to access
/// the `Value`. For example:
///
/// ```
/// use json5format::*;
/// let parsed_document = ParsedDocument::from_str("{}", None)?;
/// for item in parsed_document.content.items() {
/// assert!(!(*item).is_primitive());
/// }
/// # Ok::<(),anyhow::Error>(())
/// ```
pub fn items(&self) -> impl Iterator<Item = Ref<'_, Value>> {
self.items.iter().map(|rc| rc.borrow())
}
/// As in `Array::items`, returns an iterator over the array items, but with mutable references.
#[inline]
pub fn items_mut(&mut self) -> impl Iterator<Item = RefMut<'_, Value>> {
self.items.iter_mut().map(|rc| rc.borrow_mut())
}
/// Returns a reference to the comments at the end of an array not associated with any values.
#[inline]
pub fn trailing_comments(&self) -> &Vec<Comment> {
&self.contained_comments.pending_comments
}
/// Returns a mutable reference to the comments at the end of an array not associated with any
/// values.
#[inline]
pub fn trailing_comments_mut(&mut self) -> &mut Vec<Comment> {
&mut self.contained_comments.pending_comments
}
/// Returns a cloned vector of item references in sorted order. The items owned by this Array
/// retain their original order.
fn sort_items(&self, options: &FormatOptions) -> Vec<Rc<RefCell<Value>>> {
let mut items = self.items.clone();
if options.sort_array_items {
items.sort_by(|left, right| {
let left: &Value = &*left.borrow();
let right: &Value = &*right.borrow();
if let Value::Primitive { val: left_primitive, .. } = left {
if let Value::Primitive { val: right_primitive, .. } = right {
let mut ordering = left_primitive
.value_string
.to_lowercase()
.cmp(&right_primitive.value_string.to_lowercase());
// If two values are case-insensitively equal, compare them again with
// case-sensitivity to ensure consistent re-ordering.
if ordering == Ordering::Equal {
ordering =
left_primitive.value_string.cmp(&right_primitive.value_string);
}
ordering
} else {
Ordering::Equal
}
} else {
Ordering::Equal
}
});
}
items
}
fn format<'a>(&self, formatter: &'a mut Formatter) -> Result<&'a mut Formatter, Error> {
formatter.format_container("[", "]", |formatter| self.format_content(formatter))
}
}
impl Container for Array {
fn add_value(&mut self, value: Rc<RefCell<Value>>, parser: &Parser<'_>) -> Result<(), Error> {
if self.is_parsing_value {
Err(parser.error("Array items must be separated by a comma"))
} else {
self.is_parsing_value = true;
self.contained_comments.current_line_value = Some(value.clone());
self.contained_comments.end_of_line_comment_start_column = None;
self.items.push(value);
Ok(())
}
}
fn end_value(&mut self, parser: &Parser<'_>) -> Result<(), Error> {
if self.is_parsing_value {
self.is_parsing_value = false;
Ok(())
} else {
Err(parser.error("Unexpected comma without a preceding array item value"))
}
}
fn close(&mut self, _parser: &Parser<'_>) -> Result<(), Error> {
Ok(())
}
fn format_content<'a>(&self, formatter: &'a mut Formatter) -> Result<&'a mut Formatter, Error> {
let sorted_items = self.sort_items(&formatter.options_in_scope());
let len = sorted_items.len();
for (index, item) in sorted_items.iter().enumerate() {
let is_first = index == 0;
let is_last = index == len - 1;
formatter.format_item(
item,
is_first,
is_last,
self.contained_comments.has_pending_comments(),
)?;
}
formatter.format_trailing_comments(&self.contained_comments.pending_comments)
}
fn contained_comments(&self) -> &ContainedComments {
&self.contained_comments
}
fn contained_comments_mut(&mut self) -> &mut ContainedComments {
&mut self.contained_comments
}
}
impl std::fmt::Debug for Array {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Array of {} item{}",
self.items.len(),
if self.items.len() == 1 { "" } else { "s" }
)
}
}
/// Represents a name-value pair for a field in a JSON5 object.
#[derive(Clone)]
pub struct Property {
/// An unquoted or quoted property name. If unquoted, the name must match the
/// UNQUOTED_PROPERTY_NAME_PATTERN.
pub(crate) name: String,
/// The property value.
pub(crate) value: Rc<RefCell<Value>>,
}
impl Property {
/// Returns a new instance of a `Property` with the name provided as a `String`
/// and value provided as indirection to a `Value`.
pub(crate) fn new(name: String, value: Rc<RefCell<Value>>) -> Self {
Property { name, value }
}
/// An unquoted or quoted property name. If unquoted, JSON5 property
/// names comply with the ECMAScript 5.1 `IdentifierName` requirements.
#[inline]
pub fn name(&self) -> &str {
return &self.name;
}
/// Returns a `Ref` to the property's value, which can be accessed by dereference,
/// for example: `(*some_prop.value()).is_primitive()`.
#[inline]
pub fn value(&self) -> Ref<'_, Value> {
return self.value.borrow();
}
/// Returns a `RefMut` to the property's value, which can be accessed by dereference,
/// for example: `(*some_prop.value()).is_primitive()`.
#[inline]
pub fn value_mut(&mut self) -> RefMut<'_, Value> {
return self.value.borrow_mut();
}
}
impl std::fmt::Debug for Property {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Property {}: {:?}", self.name, self.value)
}
}
/// A specialized struct to represent the data of JSON5 object, including any comments placed at
/// the end of the object.
pub struct Object {
/// Parsed property name to be applied to the next upcoming Value.
pending_property_name: Option<String>,
/// Properties of this object.
properties: Vec<Property>,
/// Set to true when a value is encountered (parsed primitive, or sub-container in process)
/// and false when a comma or the object's closing brace is encountered. This supports
/// validating that each property is separated by one and only one comma.
is_parsing_property: bool,
/// Manages parsed comments inside the object scope, which are either transferred to each object
/// item, or retained for placement after the last object item.
contained_comments: ContainedComments,
}
impl Object {
/// Instantiates a `Value::Object` with empty data and the provided comments.
pub(crate) fn new(comments: Vec<Comment>) -> Value {
Value::Object {
val: Object {
pending_property_name: None,
properties: vec![],
is_parsing_property: false,
contained_comments: ContainedComments::new(),
},
comments: Comments { before_value: comments, end_of_line_comment: None },
}
}
/// Retrieves an iterator from the `properties` field.
#[inline]
pub fn properties(&self) -> impl Iterator<Item = &Property> {
self.properties.iter()
}
/// Retrieves an iterator of mutable references from the `properties` field.
#[inline]
pub fn properties_mut(&mut self) -> impl Iterator<Item = &mut Property> {
self.properties.iter_mut()
}
/// Returns a reference to the comments at the end of an object not associated with any values.
#[inline]
pub fn trailing_comments(&self) -> &Vec<Comment> {
&self.contained_comments.pending_comments
}
/// Returns a mutable reference to the comments at the end of an object not associated with any
/// values.
#[inline]
pub fn trailing_comments_mut(&mut self) -> &mut Vec<Comment> {
&mut self.contained_comments.pending_comments
}
/// The given property name was parsed. Once it's value is also parsed, the property will be
/// added to this `Object`.
///
/// # Arguments
/// * name - the property name, possibly quoted
/// * parser - reference to the current state of the parser
pub(crate) fn set_pending_property(
&mut self,
name: String,
parser: &Parser<'_>,
) -> Result<(), Error> {
self.contained_comments.current_line_value = None;
if self.is_parsing_property {
Err(parser.error("Properties must be separated by a comma"))
} else {
self.is_parsing_property = true;
match &self.pending_property_name {
Some(property_name) => Err(Error::internal(
parser.location(),
format!(
"Unexpected property '{}' encountered before completing the previous \
property '{}'",
name, property_name
),
)),
None => {
self.pending_property_name = Some(name.to_string());
Ok(())
}
}
}
}
/// Returns true if a property name has been parsed, and the parser has not yet reached a value.
pub(crate) fn has_pending_property(&mut self) -> Result<bool, Error> {
Ok(self.pending_property_name.is_some())
}
/// Returns a cloned vector of property references in sorted order. The properties owned by
/// this Object retain their original order.
fn sort_properties(&self, options: &SubpathOptions) -> Vec<Property> {
let mut properties = self.properties.clone();
properties.sort_by(|left, right| {
options
.get_property_priority(&left.name)
.cmp(&options.get_property_priority(&right.name))
});
properties
}
fn format<'a>(&self, formatter: &'a mut Formatter) -> Result<&'a mut Formatter, Error> {
formatter.format_container("{", "}", |formatter| self.format_content(formatter))
}
}
impl Container for Object {
fn add_value(&mut self, value: Rc<RefCell<Value>>, parser: &Parser<'_>) -> Result<(), Error> {
match self.pending_property_name.take() {
Some(name) => {
self.contained_comments.current_line_value = Some(value.clone());
self.contained_comments.end_of_line_comment_start_column = None;
self.properties.push(Property::new(name, value));
Ok(())
}
None => Err(parser.error("Object values require property names")),
}
}
fn end_value(&mut self, parser: &Parser<'_>) -> Result<(), Error> {
match &self.pending_property_name {
Some(property_name) => Err(parser.error(format!(
"Property '{}' must have a value before the next comma-separated property",
property_name
))),
None => {
if self.is_parsing_property {
self.is_parsing_property = false;
Ok(())
} else {
Err(parser.error("Unexpected comma without a preceding property"))
}
}
}
}
fn close(&mut self, parser: &Parser<'_>) -> Result<(), Error> {
match &self.pending_property_name {
Some(property_name) => Err(parser.error(format!(
"Property '{}' must have a value before closing an object",
property_name
))),
None => Ok(()),
}
}
fn format_content<'a>(&self, formatter: &'a mut Formatter) -> Result<&'a mut Formatter, Error> {
let sorted_properties = match formatter.get_current_subpath_options() {
Some(options) => Some(self.sort_properties(&*options.borrow())),
None => None,
};
let properties = match &sorted_properties {
Some(sorted_properties) => &sorted_properties,
None => &self.properties,
};
let len = properties.len();
for (index, property) in properties.iter().enumerate() {
let is_first = index == 0;
let is_last = index == len - 1;
formatter.format_property(
&property,
is_first,
is_last,
self.contained_comments.has_pending_comments(),
)?;
}
formatter.format_trailing_comments(&self.contained_comments.pending_comments)
}
fn contained_comments(&self) -> &ContainedComments {
&self.contained_comments
}
fn contained_comments_mut(&mut self) -> &mut ContainedComments {
&mut self.contained_comments
}
}
impl std::fmt::Debug for Object {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Object of {} propert{}",
self.properties.len(),
if self.properties.len() == 1 { "y" } else { "ies" }
)
}
}