+81
-34
@@ -11,14 +11,21 @@ 'use strict' | ||
| function cleanSource(nodes) { | ||
| return nodes.map(i => { | ||
| if (i.nodes) i.nodes = cleanSource(i.nodes) | ||
| delete i.source | ||
| return i | ||
| }) | ||
| let stack = nodes.slice() | ||
| while (stack.length > 0) { | ||
| let node = stack.pop() | ||
| delete node.source | ||
| if (node.nodes) { | ||
| node.nodes = node.nodes.slice() | ||
| for (let i of node.nodes) stack.push(i) | ||
| } | ||
| } | ||
| return nodes.slice() | ||
| } | ||
| function markTreeDirty(node) { | ||
| node[isClean] = false | ||
| if (node.proxyOf.nodes) { | ||
| for (let i of node.proxyOf.nodes) { | ||
| markTreeDirty(i) | ||
| let stack = [node] | ||
| while (stack.length > 0) { | ||
| let next = stack.pop() | ||
| next[isClean] = false | ||
| if (next.proxyOf.nodes) { | ||
| for (let i of next.proxyOf.nodes) stack.push(i) | ||
| } | ||
@@ -51,5 +58,14 @@ } | ||
| cleanRaws(keepBetween) { | ||
| super.cleanRaws(keepBetween) | ||
| if (this.nodes) { | ||
| for (let node of this.nodes) node.cleanRaws(keepBetween) | ||
| let stack = [this] | ||
| while (stack.length > 0) { | ||
| let node = stack.pop() | ||
| if (node !== this && node.cleanRaws !== Container.prototype.cleanRaws) { | ||
| // Subclass with own logic; let it handle its subtree | ||
| node.cleanRaws(keepBetween) | ||
| continue | ||
| } | ||
| Node.prototype.cleanRaws.call(node, keepBetween) | ||
| if (node.nodes) { | ||
| for (let child of node.nodes) stack.push(child) | ||
| } | ||
| } | ||
@@ -314,15 +330,44 @@ } | ||
| walk(callback) { | ||
| return this.each((child, i) => { | ||
| if (!this.proxyOf.nodes) return undefined | ||
| // An explicit stack instead of recursive `each()` calls to survive | ||
| // deeply nested trees. Each frame keeps a live `indexes` slot, so | ||
| // insertion and removal during the walk behave like `each()`: the | ||
| // slot stays at the current child until its subtree is finished. | ||
| let stack = [{ iterator: this.getIterator(), node: this.proxyOf }] | ||
| while (stack.length > 0) { | ||
| let { iterator, node } = stack[stack.length - 1] | ||
| let index = node.indexes[iterator] | ||
| if (index >= node.proxyOf.nodes.length) { | ||
| delete node.indexes[iterator] | ||
| stack.pop() | ||
| let parent = stack[stack.length - 1] | ||
| // Finish the parent’s step for the child subtree we just left | ||
| if (parent) parent.node.indexes[parent.iterator] += 1 | ||
| continue | ||
| } | ||
| let child = node.proxyOf.nodes[index] | ||
| let result | ||
| try { | ||
| result = callback(child, i) | ||
| result = callback(child, index) | ||
| } catch (e) { | ||
| throw child.addToError(e) | ||
| } | ||
| if (result !== false && child.walk) { | ||
| result = child.walk(callback) | ||
| if (result === false) { | ||
| for (let opened of stack) { | ||
| delete opened.node.indexes[opened.iterator] | ||
| } | ||
| return false | ||
| } | ||
| if (child.walk && child.proxyOf.nodes) { | ||
| stack.push({ iterator: child.getIterator(), node: child }) | ||
| } else { | ||
| node.indexes[iterator] += 1 | ||
| } | ||
| } | ||
| return result | ||
| }) | ||
| return undefined | ||
| } | ||
@@ -430,22 +475,24 @@ | ||
| Container.rebuild = node => { | ||
| if (node.type === 'atrule') { | ||
| Object.setPrototypeOf(node, AtRule.prototype) | ||
| } else if (node.type === 'rule') { | ||
| Object.setPrototypeOf(node, Rule.prototype) | ||
| } else if (node.type === 'decl') { | ||
| Object.setPrototypeOf(node, Declaration.prototype) | ||
| } else if (node.type === 'comment') { | ||
| Object.setPrototypeOf(node, Comment.prototype) | ||
| } else if (node.type === 'root') { | ||
| Object.setPrototypeOf(node, Root.prototype) | ||
| } | ||
| let stack = [node] | ||
| while (stack.length > 0) { | ||
| let next = stack.pop() | ||
| if (next.type === 'atrule') { | ||
| Object.setPrototypeOf(next, AtRule.prototype) | ||
| } else if (next.type === 'rule') { | ||
| Object.setPrototypeOf(next, Rule.prototype) | ||
| } else if (next.type === 'decl') { | ||
| Object.setPrototypeOf(next, Declaration.prototype) | ||
| } else if (next.type === 'comment') { | ||
| Object.setPrototypeOf(next, Comment.prototype) | ||
| } else if (next.type === 'root') { | ||
| Object.setPrototypeOf(next, Root.prototype) | ||
| } | ||
| node[my] = true | ||
| next[my] = true | ||
| if (node.nodes) { | ||
| node.nodes.forEach(child => { | ||
| Container.rebuild(child) | ||
| }) | ||
| if (next.nodes) { | ||
| for (let child of next.nodes) stack.push(child) | ||
| } | ||
| } | ||
| } | ||
| /* c8 ignore stop */ |
+65
-26
@@ -11,27 +11,20 @@ 'use strict' | ||
| function fromJSON(json, inputs) { | ||
| if (Array.isArray(json)) return json.map(n => fromJSON(n)) | ||
| let { inputs: ownInputs, ...defaults } = json | ||
| if (ownInputs) { | ||
| inputs = [] | ||
| for (let input of ownInputs) { | ||
| let inputHydrated = { ...input, __proto__: Input.prototype } | ||
| if (inputHydrated.map) { | ||
| inputHydrated.map = { | ||
| ...inputHydrated.map, | ||
| __proto__: PreviousMap.prototype | ||
| } | ||
| function hydrateInputs(json, inputs) { | ||
| if (!json.inputs) return inputs | ||
| return json.inputs.map(input => { | ||
| let inputHydrated = { ...input, __proto__: Input.prototype } | ||
| if (inputHydrated.map) { | ||
| inputHydrated.map = { | ||
| ...inputHydrated.map, | ||
| __proto__: PreviousMap.prototype | ||
| } | ||
| inputs.push(inputHydrated) | ||
| } | ||
| } | ||
| // Rehydrate children separately and attach them after construction. | ||
| // Passing them through the container constructor would re-run insertion | ||
| // spacing normalization and overwrite each child's own `raws.before`. | ||
| let nodes | ||
| if (defaults.nodes) { | ||
| nodes = json.nodes.map(n => fromJSON(n, inputs)) | ||
| delete defaults.nodes | ||
| } | ||
| return inputHydrated | ||
| }) | ||
| } | ||
| function constructNode(json, inputs, children) { | ||
| let defaults = { ...json } | ||
| delete defaults.inputs | ||
| delete defaults.nodes | ||
| if (defaults.source) { | ||
@@ -60,5 +53,8 @@ let { inputId, ...source } = defaults.source | ||
| if (nodes) { | ||
| node.nodes = nodes | ||
| for (let child of nodes) child.parent = node | ||
| // Rehydrated children are attached after construction. Passing them | ||
| // through the container constructor would re-run insertion spacing | ||
| // normalization and overwrite each child's own `raws.before`. | ||
| if (children) { | ||
| node.nodes = children | ||
| for (let child of children) child.parent = node | ||
| } | ||
@@ -69,3 +65,46 @@ | ||
| function fromJSON(json, inputs) { | ||
| if (Array.isArray(json)) return json.map(n => fromJSON(n)) | ||
| // An explicit stack instead of recursive calls to survive deeply | ||
| // nested trees. Children are rehydrated before their parent node | ||
| // is constructed. | ||
| let result | ||
| let stack = [ | ||
| { childIndex: 0, children: [], inputs: hydrateInputs(json, inputs), json } | ||
| ] | ||
| while (stack.length > 0) { | ||
| let frame = stack[stack.length - 1] | ||
| let jsonNodes = frame.json.nodes | ||
| if (jsonNodes && frame.childIndex < jsonNodes.length) { | ||
| let childJson = jsonNodes[frame.childIndex] | ||
| frame.childIndex += 1 | ||
| stack.push({ | ||
| childIndex: 0, | ||
| children: [], | ||
| inputs: hydrateInputs(childJson, frame.inputs), | ||
| json: childJson | ||
| }) | ||
| continue | ||
| } | ||
| stack.pop() | ||
| let node = constructNode( | ||
| frame.json, | ||
| frame.inputs, | ||
| jsonNodes ? frame.children : undefined | ||
| ) | ||
| if (stack.length > 0) { | ||
| stack[stack.length - 1].children.push(node) | ||
| } else { | ||
| result = node | ||
| } | ||
| } | ||
| return result | ||
| } | ||
| module.exports = fromJSON | ||
| fromJSON.default = fromJSON |
+7
-1
@@ -214,6 +214,12 @@ 'use strict' | ||
| if (typeof endLine === 'number') { | ||
| to = consumer.originalPositionFor({ | ||
| let toPosition = consumer.originalPositionFor({ | ||
| column: endColumn - 1, | ||
| line: endLine | ||
| }) | ||
| // The source map may not have a mapping that covers the end position | ||
| // (`originalPositionFor()` then returns `null` for `line`/`column` | ||
| // instead of omitting them). Treat that the same as not requesting | ||
| // an end position at all, so `endLine`/`endColumn` stay a consistent | ||
| // `undefined` pair instead of a mix of `null` and a bogus number. | ||
| if (toPosition.source) to = toPosition | ||
| } | ||
@@ -220,0 +226,0 @@ |
+55
-13
@@ -100,4 +100,10 @@ 'use strict' | ||
| function cleanMarks(node) { | ||
| node[isClean] = false | ||
| if (node.nodes) node.nodes.forEach(i => cleanMarks(i)) | ||
| let stack = [node] | ||
| while (stack.length > 0) { | ||
| let next = stack.pop() | ||
| next[isClean] = false | ||
| if (next.nodes) { | ||
| for (let i of next.nodes) stack.push(i) | ||
| } | ||
| } | ||
| return node | ||
@@ -533,17 +539,53 @@ } | ||
| walkSync(node) { | ||
| // An explicit stack like in async `visitTick()` to survive deeply | ||
| // nested trees. Unlike `visitTick()`, nodes are marked clean only | ||
| // on entering, so a node dirtied by its own visitors is revisited | ||
| // on the next pass. | ||
| node[isClean] = true | ||
| let events = getEvents(node) | ||
| for (let event of events) { | ||
| if (event === CHILDREN) { | ||
| if (node.nodes) { | ||
| node.each(child => { | ||
| if (!child[isClean]) this.walkSync(child) | ||
| }) | ||
| let stack = [{ eventIndex: 0, events: getEvents(node), iterator: 0, node }] | ||
| while (stack.length > 0) { | ||
| let visit = stack[stack.length - 1] | ||
| let visitNode = visit.node | ||
| if (visit.iterator !== 0) { | ||
| let iterator = visit.iterator | ||
| let child | ||
| let descended = false | ||
| while ((child = visitNode.nodes[visitNode.indexes[iterator]])) { | ||
| visitNode.indexes[iterator] += 1 | ||
| if (!child[isClean]) { | ||
| child[isClean] = true | ||
| stack.push({ | ||
| eventIndex: 0, | ||
| events: getEvents(child), | ||
| iterator: 0, | ||
| node: child | ||
| }) | ||
| descended = true | ||
| break | ||
| } | ||
| } | ||
| } else { | ||
| let visitors = this.listeners[event] | ||
| if (visitors) { | ||
| if (this.visitSync(visitors, node.toProxy())) return | ||
| if (descended) continue | ||
| visit.iterator = 0 | ||
| delete visitNode.indexes[iterator] | ||
| } | ||
| if (visit.eventIndex < visit.events.length) { | ||
| let event = visit.events[visit.eventIndex] | ||
| visit.eventIndex += 1 | ||
| if (event === CHILDREN) { | ||
| if (visitNode.nodes && visitNode.nodes.length) { | ||
| visit.iterator = visitNode.getIterator() | ||
| } | ||
| } else { | ||
| let visitors = this.listeners[event] | ||
| if (visitors) { | ||
| if (this.visitSync(visitors, visitNode.toProxy())) stack.pop() | ||
| } | ||
| } | ||
| continue | ||
| } | ||
| stack.pop() | ||
| } | ||
@@ -550,0 +592,0 @@ } |
+87
-49
@@ -10,21 +10,37 @@ 'use strict' | ||
| let cloned = new obj.constructor() | ||
| // An explicit stack instead of recursive calls to survive deeply | ||
| // nested trees. Each entry is [source, its clone, clone's parent]. | ||
| let stack = [[obj, cloned, parent]] | ||
| for (let i in obj) { | ||
| if (!Object.prototype.hasOwnProperty.call(obj, i)) { | ||
| /* c8 ignore next 2 */ | ||
| continue | ||
| } | ||
| if (i === 'proxyCache') continue | ||
| let value = obj[i] | ||
| let type = typeof value | ||
| while (stack.length > 0) { | ||
| let [source, target, targetParent] = stack.pop() | ||
| for (let i in source) { | ||
| if (!Object.prototype.hasOwnProperty.call(source, i)) { | ||
| /* c8 ignore next 2 */ | ||
| continue | ||
| } | ||
| if (i === 'proxyCache') continue | ||
| let value = source[i] | ||
| let type = typeof value | ||
| if (i === 'parent' && type === 'object') { | ||
| if (parent) cloned[i] = parent | ||
| } else if (i === 'source') { | ||
| cloned[i] = value | ||
| } else if (Array.isArray(value)) { | ||
| cloned[i] = value.map(j => cloneNode(j, cloned)) | ||
| } else { | ||
| if (type === 'object' && value !== null) value = cloneNode(value) | ||
| cloned[i] = value | ||
| if (i === 'parent' && type === 'object') { | ||
| if (targetParent) target[i] = targetParent | ||
| } else if (i === 'source') { | ||
| target[i] = value | ||
| } else if (Array.isArray(value)) { | ||
| let children = [] | ||
| target[i] = children | ||
| for (let j of value) { | ||
| let childClone = new j.constructor() | ||
| children.push(childClone) | ||
| stack.push([j, childClone, target]) | ||
| } | ||
| } else { | ||
| if (type === 'object' && value !== null) { | ||
| let valueClone = new value.constructor() | ||
| stack.push([value, valueClone, undefined]) | ||
| value = valueClone | ||
| } | ||
| target[i] = value | ||
| } | ||
| } | ||
@@ -73,3 +89,4 @@ } | ||
| for (let name in defaults) { | ||
| for (let name of Object.keys(defaults)) { | ||
| if (name === '__proto__') continue | ||
| if (name === 'nodes') { | ||
@@ -386,43 +403,64 @@ this.nodes = [] | ||
| toJSON(_, inputs) { | ||
| let fixed = {} | ||
| let emitInputs = inputs == null | ||
| inputs = inputs || new Map() | ||
| let inputsNextIndex = 0 | ||
| for (let name in this) { | ||
| if (!Object.prototype.hasOwnProperty.call(this, name)) { | ||
| /* c8 ignore next 2 */ | ||
| continue | ||
| } | ||
| if (name === 'parent' || name === 'proxyCache') continue | ||
| let value = this[name] | ||
| // A worklist instead of recursive `toJSON()` calls to survive deeply | ||
| // nested trees. Each entry converts one node and writes the result | ||
| // into the already converted parent by [holder, key]. | ||
| let holderOfRoot = [] | ||
| let queue = [[this, holderOfRoot, 0]] | ||
| if (Array.isArray(value)) { | ||
| fixed[name] = value.map(i => { | ||
| if (typeof i === 'object' && i.toJSON) { | ||
| return i.toJSON(null, inputs) | ||
| for (let step = 0; step < queue.length; step++) { | ||
| let [node, holder, key] = queue[step] | ||
| let fixed = {} | ||
| holder[key] = fixed | ||
| for (let name in node) { | ||
| if (!Object.prototype.hasOwnProperty.call(node, name)) { | ||
| /* c8 ignore next 2 */ | ||
| continue | ||
| } | ||
| if (name === 'parent' || name === 'proxyCache') continue | ||
| let value = node[name] | ||
| if (Array.isArray(value)) { | ||
| let fixedArray = [] | ||
| fixed[name] = fixedArray | ||
| for (let i = 0; i < value.length; i++) { | ||
| let item = value[i] | ||
| if (typeof item === 'object' && item.toJSON) { | ||
| if (item.toJSON === Node.prototype.toJSON) { | ||
| queue.push([item, fixedArray, i]) | ||
| } else { | ||
| fixedArray[i] = item.toJSON(null, inputs) | ||
| } | ||
| } else { | ||
| fixedArray[i] = item | ||
| } | ||
| } | ||
| } else if (typeof value === 'object' && value.toJSON) { | ||
| if (value.toJSON === Node.prototype.toJSON) { | ||
| queue.push([value, fixed, name]) | ||
| } else { | ||
| return i | ||
| fixed[name] = value.toJSON(null, inputs) | ||
| } | ||
| }) | ||
| } else if (typeof value === 'object' && value.toJSON) { | ||
| fixed[name] = value.toJSON(null, inputs) | ||
| } else if (name === 'source') { | ||
| if (value == null) continue | ||
| let inputId = inputs.get(value.input) | ||
| if (inputId == null) { | ||
| inputId = inputsNextIndex | ||
| inputs.set(value.input, inputsNextIndex) | ||
| inputsNextIndex++ | ||
| } else if (name === 'source') { | ||
| if (value == null) continue | ||
| let inputId = inputs.get(value.input) | ||
| if (inputId == null) { | ||
| inputId = inputs.size | ||
| inputs.set(value.input, inputId) | ||
| } | ||
| fixed[name] = { | ||
| end: value.end, | ||
| inputId, | ||
| start: value.start | ||
| } | ||
| } else { | ||
| fixed[name] = value | ||
| } | ||
| fixed[name] = { | ||
| end: value.end, | ||
| inputId, | ||
| start: value.start | ||
| } | ||
| } else { | ||
| fixed[name] = value | ||
| } | ||
| } | ||
| let fixed = holderOfRoot[0] | ||
| if (emitInputs) { | ||
@@ -429,0 +467,0 @@ fixed.inputs = [...inputs.keys()].map(input => input.toJSON()) |
+1
-1
@@ -10,3 +10,3 @@ 'use strict' | ||
| constructor(plugins = []) { | ||
| this.version = '8.5.16' | ||
| this.version = '8.5.17' | ||
| this.plugins = this.normalize(plugins) | ||
@@ -13,0 +13,0 @@ } |
+90
-25
@@ -34,2 +34,58 @@ 'use strict' | ||
| function atruleStart(str, node) { | ||
| let name = '@' + node.name | ||
| let params = node.params ? str.rawValue(node, 'params') : '' | ||
| if (typeof node.raws.afterName !== 'undefined') { | ||
| name += node.raws.afterName | ||
| } else if (params) { | ||
| name += ' ' | ||
| } | ||
| return name + params | ||
| } | ||
| function pushBody(str, stack, node) { | ||
| let nodes = node.nodes | ||
| let last = nodes.length - 1 | ||
| while (last > 0) { | ||
| if (nodes[last].type !== 'comment') break | ||
| last -= 1 | ||
| } | ||
| let semicolon = str.raw(node, 'semicolon') | ||
| let isDocument = node.type === 'document' | ||
| for (let i = nodes.length - 1; i >= 0; i--) { | ||
| stack.push({ | ||
| document: isDocument, | ||
| node: nodes[i], | ||
| semicolon: last !== i || semicolon | ||
| }) | ||
| } | ||
| } | ||
| function pushBlock(str, stack, node, start) { | ||
| let between = str.raw(node, 'between', 'beforeOpen') | ||
| str.builder(escapeHTMLInCSS(start + between) + '{', node, 'start') | ||
| let hasNodes = node.nodes && node.nodes.length | ||
| let close = () => { | ||
| let after = hasNodes | ||
| ? str.raw(node, 'after') | ||
| : str.raw(node, 'after', 'emptyBody') | ||
| if (after) str.builder(escapeHTMLInCSS(after)) | ||
| str.builder('}', node, 'end') | ||
| if (node.type === 'rule' && node.raws.ownSemicolon) { | ||
| str.builder(escapeHTMLInCSS(node.raws.ownSemicolon), node, 'end') | ||
| } | ||
| } | ||
| if (hasNodes) { | ||
| stack.push(close) | ||
| pushBody(str, stack, node) | ||
| } else { | ||
| close() | ||
| } | ||
| } | ||
| class Stringifier { | ||
@@ -41,17 +97,8 @@ constructor(builder) { | ||
| atrule(node, semicolon) { | ||
| let raws = node.raws | ||
| let name = '@' + node.name | ||
| let params = node.params ? this.rawValue(node, 'params') : '' | ||
| if (typeof raws.afterName !== 'undefined') { | ||
| name += raws.afterName | ||
| } else if (params) { | ||
| name += ' ' | ||
| } | ||
| let start = atruleStart(this, node) | ||
| if (node.nodes) { | ||
| this.block(node, name + params) | ||
| this.block(node, start) | ||
| } else { | ||
| let end = (raws.between || '') + (semicolon ? ';' : '') | ||
| this.builder(escapeHTMLInCSS(name + params + end), node) | ||
| let end = (node.raws.between || '') + (semicolon ? ';' : '') | ||
| this.builder(escapeHTMLInCSS(start + end), node) | ||
| } | ||
@@ -106,16 +153,34 @@ } | ||
| body(node) { | ||
| let nodes = node.nodes | ||
| let last = nodes.length - 1 | ||
| while (last > 0) { | ||
| if (nodes[last].type !== 'comment') break | ||
| last -= 1 | ||
| } | ||
| // Rules and at-rules are expanded into an explicit stack instead of | ||
| // recursive `stringify()` calls to survive deeply nested trees. | ||
| // If a subclass changes the traversal methods, its children go | ||
| // through `stringify()` to keep the override in charge. | ||
| let proto = Stringifier.prototype | ||
| let expandable = ['atrule', 'block', 'body', 'rule', 'stringify'].every( | ||
| method => this[method] === proto[method] | ||
| ) | ||
| let semicolon = this.raw(node, 'semicolon') | ||
| let isDocument = node.type === 'document' | ||
| for (let i = 0; i < nodes.length; i++) { | ||
| let child = nodes[i] | ||
| let stack = [] | ||
| pushBody(this, stack, node) | ||
| while (stack.length > 0) { | ||
| let entry = stack.pop() | ||
| if (typeof entry === 'function') { | ||
| entry() | ||
| continue | ||
| } | ||
| let child = entry.node | ||
| let before = this.raw(child, 'before') | ||
| if (before) this.builder(isDocument ? before : escapeHTMLInCSS(before)) | ||
| this.stringify(child, last !== i || semicolon) | ||
| if (before) { | ||
| this.builder(entry.document ? before : escapeHTMLInCSS(before)) | ||
| } | ||
| if (expandable && child.type === 'rule') { | ||
| pushBlock(this, stack, child, this.rawValue(child, 'selector')) | ||
| } else if (expandable && child.type === 'atrule' && child.nodes) { | ||
| pushBlock(this, stack, child, atruleStart(this, child)) | ||
| } else { | ||
| this.stringify(child, entry.semicolon) | ||
| } | ||
| } | ||
@@ -122,0 +187,0 @@ } |
+1
-1
| { | ||
| "name": "postcss", | ||
| "version": "8.5.16", | ||
| "version": "8.5.17", | ||
| "description": "Tool for transforming styles with JS plugins", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
214074
3.73%7301
2.99%