A childless (statement) at-rule that is the last non-comment child but is
followed by comment siblings was stringified without its terminating
semicolon. On re-parse the trailing comments were folded into the
at-rule prelude and dropped, so building such a tree with append() or
insertBefore() silently lost the comment nodes.
Emit the semicolon when a childless at-rule still has following siblings
so the output round-trips.
When the requested end position has no mapping in the source map,
originalPositionFor() returns an object with line/column set to null
rather than omitting them. endLine picked that null up correctly, but
endColumn was computed as `to && to.column + 1`, which due to operator
precedence is `to && (to.column + 1)`, turning null into 1. So callers
could get back { endColumn: 1, endLine: null }, which doesn't match
how every other unmapped case in this function behaves (undefined for
both). Looks like the +1 fix in #2036 covered the mapped case but not
this one.
Guard `to` the same way `from` already is, so an unmapped end position
just stays undefined.
The container constructor cloned every real Node passed in `nodes`, so a
freshly created (parent-less) node was adopted as a copy and the caller's
original reference never had its `parent` set — later operating on it threw
`Cannot read properties of undefined`. The clone was added only to keep the
source tree intact when moving nodes between roots, so it's skipped when the
node has no parent. Nodes that already belong to another tree are still
cloned, preserving the existing behavior. Closes#1987.
fromJSON() attached child nodes through the container constructor, which
runs them back through append()/normalize(). For root-level children that
re-applies insertion spacing normalization and overwrites each node's own
raws.before with the previous sibling's, so toJSON() -> fromJSON() was not
lossless: stylesheets with non-uniform blank lines between top-level rules
came back with their spacing flattened.
Rehydrate children separately and attach them directly, keeping the raws
exactly as serialized.
When calling Node#rangeBy() (and therefore Node#error() and
Node#warn()) with index 0, the resulting range covered the whole node
instead of a single character at offset 0.
The end-of-range branch used a truthy check on opts.index, so an index
of 0 fell through and left the end position at the node's end. This is
the same off-by-zero issue that was previously fixed for endIndex; the
index branches were missed. Switch both index checks to a numeric type
check, matching the existing endIndex handling.
index 0 now produces a single-character range, consistent with every
other index value.
positionBy()'s default branch returned this.source.start verbatim, so for
custom syntaxes whose source.start lacks an offset (e.g. postcss-html) it
returned a position without offset, violating the Position type which
declares offset as required. rangeBy()/positionInside() were normalized to
always compute offset via sourceOffset() (#2033, issue #2029) but
positionBy() was left behind. Mirror the rangeBy() pattern.
Co-authored-by: oss69U-prep <prep@local>
Co-authored-by: Hanzo Dev <[email protected]>
Cache node.raws references locally in hot methods (atrule, block, body,
comment, decl) to avoid repeated property chain resolution. In decl(),
inline the common rawValue() path when node.raws.value exists. In raw(),
cache root.rawCache in a local variable. In body(), short-circuit
raw(child, 'before') when child.raws.before is already defined. In
block() and comment(), inline the raws check to skip raw() call overhead
when the value is already present.
When opts.map is undefined and the root has no previous source map,
skip MapGenerator construction entirely and stringify directly. This
avoids 3 Map allocations, a clearAnnotation AST walk, and a previous()
AST walk per file.