Skip to content

Commit

Permalink
Merge pull request #1582 from hudochenkov/add-document-node-1498
Browse files Browse the repository at this point in the history
Add Document node and related API (#1498)
  • Loading branch information
ai committed May 19, 2021
2 parents e4ab6c6 + 8af26ab commit e09414a
Show file tree
Hide file tree
Showing 24 changed files with 953 additions and 59 deletions.
5 changes: 3 additions & 2 deletions docs/syntax.md
Expand Up @@ -39,7 +39,7 @@ A good example of a parser is [Safe Parser], which parses malformed/broken CSS.
Because there is no point to generate broken output, this package only provides
a parser.

The parser API is a function which receives a string & returns a [`Root`] node.
The parser API is a function which receives a string & returns a [`Root`] or [`Document`] node.
The second argument is a function which receives an object with PostCSS options.

```js
Expand All @@ -54,6 +54,7 @@ module.exports = function parse (css, opts) {

[Safe Parser]: https://github.com/postcss/postcss-safe-parser
[`Root`]: https://postcss.org/api/#root
[`Document`]: https://postcss.org/api/#document


### Main Theory
Expand Down Expand Up @@ -170,7 +171,7 @@ The Stringifier API is little bit more complicated, than the parser API.
PostCSS generates a source map, so a stringifier can’t just return a string.
It must link every substring with its source node.

A Stringifier is a function which receives [`Root`] node and builder callback.
A Stringifier is a function which receives [`Root`] or [`Document`] node and builder callback.
Then it calls builder with every node’s string and node instance.

```js
Expand Down
1 change: 1 addition & 0 deletions lib/at-rule.d.ts
Expand Up @@ -72,6 +72,7 @@ export interface AtRuleProps extends ContainerProps {
*/
export default class AtRule extends Container {
type: 'atrule'
parent: Container | undefined
raws: AtRuleRaws

/**
Expand Down
2 changes: 2 additions & 0 deletions lib/comment.d.ts
@@ -1,3 +1,4 @@
import Container from './container.js'
import Node, { NodeProps } from './node.js'

interface CommentRaws {
Expand Down Expand Up @@ -39,6 +40,7 @@ export interface CommentProps extends NodeProps {
*/
export default class Comment extends Node {
type: 'comment'
parent: Container | undefined
raws: CommentRaws

/**
Expand Down
42 changes: 16 additions & 26 deletions lib/container.d.ts
Expand Up @@ -27,7 +27,9 @@ export interface ContainerProps extends NodeProps {
* Note that all containers can store any content. If you write a rule inside
* a rule, PostCSS will parse it.
*/
export default abstract class Container extends Node {
export default abstract class Container<
Child extends Node = ChildNode
> extends Node {
/**
* An array containing the container’s children.
*
Expand All @@ -38,7 +40,7 @@ export default abstract class Container extends Node {
* root.nodes[0].nodes[0].prop //=> 'color'
* ```
*/
nodes: ChildNode[]
nodes: Child[]

/**
* The container’s first child.
Expand All @@ -47,7 +49,7 @@ export default abstract class Container extends Node {
* rule.first === rules.nodes[0]
* ```
*/
get first(): ChildNode | undefined
get first(): Child | undefined

/**
* The container’s last child.
Expand All @@ -56,7 +58,7 @@ export default abstract class Container extends Node {
* rule.last === rule.nodes[rule.nodes.length - 1]
* ```
*/
get last(): ChildNode | undefined
get last(): Child | undefined

/**
* Iterates through the container’s immediate children,
Expand Down Expand Up @@ -92,7 +94,7 @@ export default abstract class Container extends Node {
* @return Returns `false` if iteration was broke.
*/
each(
callback: (node: ChildNode, index: number) => false | void
callback: (node: Child, index: number) => false | void
): false | undefined

/**
Expand Down Expand Up @@ -304,7 +306,7 @@ export default abstract class Container extends Node {
* @param child New node.
* @return This node for methods chain.
*/
push(child: ChildNode): this
push(child: Child): this

/**
* Insert new node before old node within the container.
Expand All @@ -318,14 +320,8 @@ export default abstract class Container extends Node {
* @return This node for methods chain.
*/
insertBefore(
oldNode: ChildNode | number,
newNode:
| ChildNode
| ChildProps
| string
| ChildNode[]
| ChildProps[]
| string[]
oldNode: Child | number,
newNode: Child | ChildProps | string | Child[] | ChildProps[] | string[]
): this

/**
Expand All @@ -336,14 +332,8 @@ export default abstract class Container extends Node {
* @return This node for methods chain.
*/
insertAfter(
oldNode: ChildNode | number,
newNode:
| ChildNode
| ChildProps
| string
| ChildNode[]
| ChildProps[]
| string[]
oldNode: Child | number,
newNode: Child | ChildProps | string | Child[] | ChildProps[] | string[]
): this

/**
Expand All @@ -360,7 +350,7 @@ export default abstract class Container extends Node {
* @param child Child or child’s index.
* @return This node for methods chain.
*/
removeChild(child: ChildNode | number): this
removeChild(child: Child | number): this

/**
* Removes all children from the container
Expand Down Expand Up @@ -420,7 +410,7 @@ export default abstract class Container extends Node {
* @return Is every child pass condition.
*/
every(
condition: (node: ChildNode, index: number, nodes: ChildNode[]) => boolean
condition: (node: Child, index: number, nodes: Child[]) => boolean
): boolean

/**
Expand All @@ -435,7 +425,7 @@ export default abstract class Container extends Node {
* @return Is some child pass condition.
*/
some(
condition: (node: ChildNode, index: number, nodes: ChildNode[]) => boolean
condition: (node: Child, index: number, nodes: Child[]) => boolean
): boolean

/**
Expand All @@ -448,5 +438,5 @@ export default abstract class Container extends Node {
* @param child Child of the current container.
* @return Child index.
*/
index(child: ChildNode | number): number
index(child: Child | number): number
}
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
2 changes: 2 additions & 0 deletions lib/declaration.d.ts
@@ -1,3 +1,4 @@
import Container from './container.js'
import Node from './node.js'

interface DeclarationRaws {
Expand Down Expand Up @@ -56,6 +57,7 @@ export interface DeclarationProps {
*/
export default class Declaration extends Node {
type: 'decl'
parent: Container | undefined
raws: DeclarationRaws

/**
Expand Down
54 changes: 54 additions & 0 deletions lib/document.d.ts
@@ -0,0 +1,54 @@
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[]

/**
* Information to generate byte-to-byte equal node string as it was
* in the origin input.
*
* Every parser saves its own properties.
*/
raws?: Record<string, any>
}

type ChildNode = Root
type ChildProps = RootProps

/**
* Represents a file and contains all its parsed nodes.
*
* **Experimental:** some aspects of this node could change within minor or patch version releases.
*
* ```js
* const document = htmlParser('<html><style>a{color:black}</style><style>b{z-index:2}</style>')
* document.type //=> 'document'
* document.nodes.length //=> 2
* ```
*/
export default class Document extends Container<Root> {
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
}
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 {
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

0 comments on commit e09414a

Please sign in to comment.