Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Document node and related API (#1498) #1582

Merged
merged 12 commits into from May 19, 2021
2 changes: 1 addition & 1 deletion lib/container.js
Expand Up @@ -310,7 +310,7 @@ class Container extends Node {
for (let i of nodes) {
if (i.parent) i.parent.removeChild(i, 'ignore')
}
} else if (nodes.type === 'root') {
} else if (nodes.type === 'root' && this.type !== 'document') {
nodes = nodes.nodes.slice(0)
for (let i of nodes) {
if (i.parent) i.parent.removeChild(i, 'ignore')
Expand Down
264 changes: 264 additions & 0 deletions lib/document.d.ts
@@ -0,0 +1,264 @@
import Container, { ContainerProps } from './container.js'
import { ProcessOptions } from './postcss.js'
import Result from './result.js'
import Root, { RootProps } from './root.js'

export interface DocumentProps extends ContainerProps {
nodes?: Root[]
}

type ChildNode = Root
type ChildProps = RootProps

/**
* Represents a file and contains all its parsed nodes.
*
* ```js
* const document = postcss.parse('<html><style>a{color:black}</style><style>b{z-index:2}</style>')
ai marked this conversation as resolved.
Show resolved Hide resolved
* document.type //=> 'document'
* document.nodes.length //=> 2
* ```
*/
export default class Document extends Container {
type: 'document'
parent: undefined

constructor(defaults?: DocumentProps)

/**
* Returns a `Result` instance representing the document’s CSS roots.
*
* ```js
* const root1 = postcss.parse(css1, { from: 'a.css' })
* const root2 = postcss.parse(css2, { from: 'b.css' })
* const document = postcss.document()
* document.append(root1)
* document.append(root2)
* const result = document.toResult({ to: 'all.css', map: true })
* ```
*
* @param opts Options.
* @return Result with current document’s CSS.
*/
toResult(options?: ProcessOptions): Result

/**
* An array containing the container’s children.
*
* ```js
* const root = postcss.parse('a { color: black }')
* root.nodes.length //=> 1
* root.nodes[0].selector //=> 'a'
* root.nodes[0].nodes[0].prop //=> 'color'
* ```
*/
// @ts-expect-error
nodes: Root[]
hudochenkov marked this conversation as resolved.
Show resolved Hide resolved

/**
* The container’s first child.
*
* ```js
* rule.first === rules.nodes[0]
* ```
*/
// @ts-expect-error
get first(): ChildNode | undefined
hudochenkov marked this conversation as resolved.
Show resolved Hide resolved

/**
* The container’s last child.
*
* ```js
* rule.last === rule.nodes[rule.nodes.length - 1]
* ```
*/
// @ts-expect-error
get last(): ChildNode | undefined

/**
* Iterates through the container’s immediate children,
* calling `callback` for each child.
*
* Returning `false` in the callback will break iteration.
*
* This method only iterates through the container’s immediate children.
* If you need to recursively iterate through all the container’s descendant
* nodes, use `Container#walk`.
*
* Unlike the for `{}`-cycle or `Array#forEach` this iterator is safe
* if you are mutating the array of child nodes during iteration.
* PostCSS will adjust the current index to match the mutations.
*
* ```js
* const root = postcss.parse('a { color: black; z-index: 1 }')
* const rule = root.first
*
* for (const decl of rule.nodes) {
* decl.cloneBefore({ prop: '-webkit-' + decl.prop })
* // Cycle will be infinite, because cloneBefore moves the current node
* // to the next index
* }
*
* rule.each(decl => {
* decl.cloneBefore({ prop: '-webkit-' + decl.prop })
* // Will be executed only for color and z-index
* })
* ```
*
* @param callback Iterator receives each node and index.
* @return Returns `false` if iteration was broke.
*/
// @ts-expect-error
each(
callback: (node: ChildNode, index: number) => false | void
): false | undefined

/**
* Inserts new nodes to the end of the container.
*
* ```js
* const decl1 = new Declaration({ prop: 'color', value: 'black' })
* const decl2 = new Declaration({ prop: 'background-color', value: 'white' })
* rule.append(decl1, decl2)
*
* root.append({ name: 'charset', params: '"UTF-8"' }) // at-rule
* root.append({ selector: 'a' }) // rule
* rule.append({ prop: 'color', value: 'black' }) // declaration
* rule.append({ text: 'Comment' }) // comment
*
* root.append('a {}')
* root.first.append('color: black; z-index: 1')
* ```
*
* @param nodes New nodes.
* @return This node for methods chain.
*/
append(...nodes: (ChildProps | ChildProps[])[]): this

/**
* Inserts new nodes to the start of the container.
*
* ```js
* const decl1 = new Declaration({ prop: 'color', value: 'black' })
* const decl2 = new Declaration({ prop: 'background-color', value: 'white' })
* rule.prepend(decl1, decl2)
*
* root.append({ name: 'charset', params: '"UTF-8"' }) // at-rule
* root.append({ selector: 'a' }) // rule
* rule.append({ prop: 'color', value: 'black' }) // declaration
* rule.append({ text: 'Comment' }) // comment
*
* root.append('a {}')
* root.first.append('color: black; z-index: 1')
* ```
*
* @param nodes New nodes.
* @return This node for methods chain.
*/
prepend(...nodes: (ChildProps | ChildProps[])[]): this

/**
* Add child to the end of the node.
*
* ```js
* rule.push(new Declaration({ prop: 'color', value: 'black' }))
* ```
*
* @param child New node.
* @return This node for methods chain.
*/
// @ts-expect-error
push(child: ChildNode): this

/**
* Insert new node before old node within the container.
*
* ```js
* rule.insertBefore(decl, decl.clone({ prop: '-webkit-' + decl.prop }))
* ```
*
* @param oldNode Child or child’s index.
* @param newNode New node.
* @return This node for methods chain.
*/
// @ts-expect-error
insertBefore(
oldNode: ChildNode | number,
newNode: ChildNode | ChildProps | ChildNode[] | ChildProps[]
): this

/**
* Insert new node after old node within the container.
*
* @param oldNode Child or child’s index.
* @param newNode New node.
* @return This node for methods chain.
*/
// @ts-expect-error
insertAfter(
oldNode: ChildNode | number,
newNode: ChildNode | ChildProps | ChildNode[] | ChildProps[]
): this

/**
* Removes node from the container and cleans the parent properties
* from the node and its children.
*
* ```js
* rule.nodes.length //=> 5
* rule.removeChild(decl)
* rule.nodes.length //=> 4
* decl.parent //=> undefined
* ```
*
* @param child Child or child’s index.
* @return This node for methods chain.
*/
// @ts-expect-error
removeChild(child: ChildNode | number): this

/**
* Returns `true` if callback returns `true`
* for all of the container’s children.
*
* ```js
* const noPrefixes = rule.every(i => i.prop[0] !== '-')
* ```
*
* @param condition Iterator returns true or false.
* @return Is every child pass condition.
*/
// @ts-expect-error
every(
condition: (node: ChildNode, index: number, nodes: ChildNode[]) => boolean
): boolean

/**
* Returns `true` if callback returns `true` for (at least) one
* of the container’s children.
*
* ```js
* const hasPrefix = rule.some(i => i.prop[0] === '-')
* ```
*
* @param condition Iterator returns true or false.
* @return Is some child pass condition.
*/
// @ts-expect-error
some(
condition: (node: ChildNode, index: number, nodes: ChildNode[]) => boolean
): boolean

/**
* Returns a `child`’s index within the `Container#nodes` array.
*
* ```js
* rule.index( rule.nodes[2] ) //=> 2
* ```
*
* @param child Child of the current container.
* @return Child index.
*/
// @ts-expect-error
index(child: ChildNode | number): number
}
33 changes: 33 additions & 0 deletions lib/document.js
@@ -0,0 +1,33 @@
'use strict'

let Container = require('./container')

let LazyResult, Processor

class Document extends Container {
ai marked this conversation as resolved.
Show resolved Hide resolved
constructor(defaults) {
// type needs to be passed to super, otherwise child roots won't be normalized correctly
super({ type: 'document', ...defaults })

if (!this.nodes) {
this.nodes = []
}
}

toResult(opts = {}) {
let lazy = new LazyResult(new Processor(), this, opts)

return lazy.stringify()
}
}

Document.registerLazyResult = dependant => {
LazyResult = dependant
}

Document.registerProcessor = dependant => {
Processor = dependant
}

module.exports = Document
Document.default = Document