mac npm-packages

This commit is contained in:
onvia
2023-09-06 09:51:55 +08:00
parent 037e598d81
commit ad27fa6bae
441 changed files with 255551 additions and 263062 deletions

View File

@@ -1,12 +1,9 @@
# PostCSS [![Gitter][chat-img]][chat]
# PostCSS
<img align="right" width="95" height="95"
alt="Philosophers stone, logo of PostCSS"
src="https://postcss.org/logo.svg">
[chat-img]: https://img.shields.io/badge/Gitter-Join_the_PostCSS_chat-brightgreen.svg
[chat]: https://gitter.im/postcss/postcss
PostCSS is a tool for transforming styles with JS plugins.
These plugins can lint your CSS, support variables and mixins,
transpile future CSS syntax, inline images, and more.
@@ -20,10 +17,8 @@ PostCSS takes a CSS file and provides an API to analyze and modify its rules
This API can then be used by [plugins] to do a lot of useful things,
e.g., to find errors automatically, or to insert vendor prefixes.
**Support / Discussion:** [Gitter](https://gitter.im/postcss/postcss)<br>
**Twitter account:** [@postcss](https://twitter.com/postcss)<br>
**VK.com page:** [postcss](https://vk.com/postcss)<br>
**中文翻译**: [`docs/README-cn.md`](./docs/README-cn.md)
**Twitter account:** [@postcss](https://twitter.com/postcss)<br>
**中文翻译**: [`docs/README-cn.md`](./docs/README-cn.md)
For PostCSS commercial support (consulting, improving the front-end culture
of your company, PostCSS plugins), contact [Evil Martians]

View File

@@ -1,48 +1,53 @@
import Container, { ContainerProps } from './container.js'
interface AtRuleRaws extends Record<string, unknown> {
/**
* The space symbols before the node. It also stores `*`
* and `_` symbols before the declaration (IE hack).
*/
before?: string
declare namespace AtRule {
export interface AtRuleRaws extends Record<string, unknown> {
/**
* The space symbols after the last child of the node to the end of the node.
*/
after?: string
/**
* The space symbols after the last child of the node to the end of the node.
*/
after?: string
/**
* The space between the at-rule name and its parameters.
*/
afterName?: string
/**
* The space between the at-rule name and its parameters.
*/
afterName?: string
/**
* The space symbols before the node. It also stores `*`
* and `_` symbols before the declaration (IE hack).
*/
before?: string
/**
* The symbols between the last parameter and `{` for rules.
*/
between?: string
/**
* The symbols between the last parameter and `{` for rules.
*/
between?: string
/**
* Contains `true` if the last child has an (optional) semicolon.
*/
semicolon?: boolean
/**
* The rules selector with comments.
*/
params?: {
raw: string
value: string
}
/**
* The rules selector with comments.
*/
params?: {
value: string
raw: string
/**
* Contains `true` if the last child has an (optional) semicolon.
*/
semicolon?: boolean
}
}
export interface AtRuleProps extends ContainerProps {
/** Name of the at-rule. */
name: string
/** Parameters following the name of the at-rule. */
params?: string | number
/** Information used to generate byte-to-byte equal node string as it was in the origin input. */
raws?: AtRuleRaws
export interface AtRuleProps extends ContainerProps {
/** Name of the at-rule. */
name: string
/** Parameters following the name of the at-rule. */
params?: number | string
/** Information used to generate byte-to-byte equal node string as it was in the origin input. */
raws?: AtRuleRaws
}
// eslint-disable-next-line @typescript-eslint/no-use-before-define
export { AtRule_ as default }
}
/**
@@ -56,7 +61,7 @@ export interface AtRuleProps extends ContainerProps {
* }
* ```
*
* If its followed in the CSS by a {} block, this node will have
* If its followed in the CSS by a `{}` block, this node will have
* a nodes property representing its children.
*
* ```js
@@ -70,11 +75,7 @@ export interface AtRuleProps extends ContainerProps {
* media.nodes //=> []
* ```
*/
export default class AtRule extends Container {
type: 'atrule'
parent: Container | undefined
raws: AtRuleRaws
declare class AtRule_ extends Container {
/**
* The at-rules name immediately follows the `@`.
*
@@ -85,10 +86,9 @@ export default class AtRule extends Container {
* ```
*/
name: string
/**
* The at-rules parameters, the values that follow the at-rules name
* but precede any {} block.
* but precede any `{}` block.
*
* ```js
* const root = postcss.parse('@media print, screen {}')
@@ -97,10 +97,19 @@ export default class AtRule extends Container {
* ```
*/
params: string
parent: Container | undefined
constructor(defaults?: AtRuleProps)
assign(overrides: object | AtRuleProps): this
clone(overrides?: Partial<AtRuleProps>): this
cloneBefore(overrides?: Partial<AtRuleProps>): this
cloneAfter(overrides?: Partial<AtRuleProps>): this
raws: AtRule.AtRuleRaws
type: 'atrule'
constructor(defaults?: AtRule.AtRuleProps)
assign(overrides: AtRule.AtRuleProps | object): this
clone(overrides?: Partial<AtRule.AtRuleProps>): AtRule
cloneAfter(overrides?: Partial<AtRule.AtRuleProps>): AtRule
cloneBefore(overrides?: Partial<AtRule.AtRuleProps>): AtRule
}
declare class AtRule extends AtRule_ {}
export = AtRule

View File

@@ -1,56 +1,67 @@
import Container from './container.js'
import Node, { NodeProps } from './node.js'
interface CommentRaws extends Record<string, unknown> {
/**
* The space symbols before the node.
*/
before?: string
declare namespace Comment {
export interface CommentRaws extends Record<string, unknown> {
/**
* The space symbols before the node.
*/
before?: string
/**
* The space symbols between `/*` and the comments text.
*/
left?: string
/**
* The space symbols between `/*` and the comments text.
*/
left?: string
/**
* The space symbols between the comments text.
*/
right?: string
}
/**
* The space symbols between the comments text.
*/
right?: string
}
export interface CommentProps extends NodeProps {
/** Content of the comment. */
text: string
/** Information used to generate byte-to-byte equal node string as it was in the origin input. */
raws?: CommentRaws
export interface CommentProps extends NodeProps {
/** Information used to generate byte-to-byte equal node string as it was in the origin input. */
raws?: CommentRaws
/** Content of the comment. */
text: string
}
// eslint-disable-next-line @typescript-eslint/no-use-before-define
export { Comment_ as default }
}
/**
* Represents a comment between declarations or statements (rule and at-rules).
* It represents a class that handles
* [CSS comments](https://developer.mozilla.org/en-US/docs/Web/CSS/Comments)
*
* ```js
* Once (root, { Comment }) {
* let note = new Comment({ text: 'Note: …' })
* const note = new Comment({ text: 'Note: …' })
* root.append(note)
* }
* ```
*
* Comments inside selectors, at-rule parameters, or declaration values
* will be stored in the `raws` properties explained above.
* Remember that CSS comments inside selectors, at-rule parameters,
* or declaration values will be stored in the `raws` properties
* explained above.
*/
export default class Comment extends Node {
type: 'comment'
declare class Comment_ extends Node {
parent: Container | undefined
raws: CommentRaws
raws: Comment.CommentRaws
/**
* The comment's text.
*/
text: string
constructor(defaults?: CommentProps)
assign(overrides: object | CommentProps): this
clone(overrides?: Partial<CommentProps>): this
cloneBefore(overrides?: Partial<CommentProps>): this
cloneAfter(overrides?: Partial<CommentProps>): this
type: 'comment'
constructor(defaults?: Comment.CommentProps)
assign(overrides: Comment.CommentProps | object): this
clone(overrides?: Partial<Comment.CommentProps>): Comment
cloneAfter(overrides?: Partial<Comment.CommentProps>): Comment
cloneBefore(overrides?: Partial<Comment.CommentProps>): Comment
}
declare class Comment extends Comment_ {}
export = Comment

View File

@@ -1,23 +1,28 @@
import Node, { ChildNode, NodeProps, ChildProps } from './node.js'
import Declaration from './declaration.js'
import Comment from './comment.js'
import AtRule from './at-rule.js'
import Comment from './comment.js'
import Declaration from './declaration.js'
import Node, { ChildNode, ChildProps, NodeProps } from './node.js'
import Rule from './rule.js'
interface ValueOptions {
/**
* An array of property names.
*/
props?: string[]
declare namespace Container {
export interface ValueOptions {
/**
* String thats used to narrow down values and speed up the regexp search.
*/
fast?: string
/**
* String thats used to narrow down values and speed up the regexp search.
*/
fast?: string
}
/**
* An array of property names.
*/
props?: string[]
}
export interface ContainerProps extends NodeProps {
nodes?: (ChildNode | ChildProps)[]
export interface ContainerProps extends NodeProps {
nodes?: (ChildNode | ChildProps)[]
}
// eslint-disable-next-line @typescript-eslint/no-use-before-define
export { Container_ as default }
}
/**
@@ -27,9 +32,7 @@ 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<
Child extends Node = ChildNode
> extends Node {
declare abstract class Container_<Child extends Node = ChildNode> extends Node {
/**
* An array containing the containers children.
*
@@ -43,22 +46,33 @@ export default abstract class Container<
nodes: Child[]
/**
* The containers first child.
* Inserts new nodes to the end of the container.
*
* ```js
* rule.first === rules.nodes[0]
* 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.
*/
get first(): Child | undefined
append(
...nodes: (ChildProps | ChildProps[] | Node | Node[] | string | string[])[]
): this
/**
* The containers last child.
*
* ```js
* rule.last === rule.nodes[rule.nodes.length - 1]
* ```
*/
get last(): Child | undefined
assign(overrides: Container.ContainerProps | object): this
clone(overrides?: Partial<Container.ContainerProps>): Container<Child>
cloneAfter(overrides?: Partial<Container.ContainerProps>): Container<Child>
cloneBefore(overrides?: Partial<Container.ContainerProps>): Container<Child>
/**
* Iterates through the containers immediate children,
@@ -97,6 +111,207 @@ export default abstract class Container<
callback: (node: Child, index: number) => false | void
): false | undefined
/**
* Returns `true` if callback returns `true`
* for all of the containers children.
*
* ```js
* const noPrefixes = rule.every(i => i.prop[0] !== '-')
* ```
*
* @param condition Iterator returns true or false.
* @return Is every child pass condition.
*/
every(
condition: (node: Child, index: number, nodes: Child[]) => boolean
): boolean
/**
* The containers first child.
*
* ```js
* rule.first === rules.nodes[0]
* ```
*/
get first(): Child | undefined
/**
* 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.
*/
index(child: Child | number): number
/**
* Insert new node after old node within the container.
*
* @param oldNode Child or childs index.
* @param newNode New node.
* @return This node for methods chain.
*/
insertAfter(
oldNode: Child | number,
newNode: Child | Child[] | ChildProps | ChildProps[] | string | string[]
): this
/**
* Traverses the containers descendant nodes, calling callback
* for each comment node.
*
* Like `Container#each`, this method is safe
* to use if you are mutating arrays during iteration.
*
* ```js
* root.walkComments(comment => {
* comment.remove()
* })
* ```
*
* @param callback Iterator receives each node and index.
* @return Returns `false` if iteration was broke.
*/
/**
* Insert new node before old node within the container.
*
* ```js
* rule.insertBefore(decl, decl.clone({ prop: '-webkit-' + decl.prop }))
* ```
*
* @param oldNode Child or childs index.
* @param newNode New node.
* @return This node for methods chain.
*/
insertBefore(
oldNode: Child | number,
newNode: Child | Child[] | ChildProps | ChildProps[] | string | string[]
): this
/**
* The containers last child.
*
* ```js
* rule.last === rule.nodes[rule.nodes.length - 1]
* ```
*/
get last(): Child | undefined
/**
* 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[] | Node | Node[] | string | string[])[]
): 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.
*/
push(child: Child): this
/**
* Removes all children from the container
* and cleans their parent properties.
*
* ```js
* rule.removeAll()
* rule.nodes.length //=> 0
* ```
*
* @return This node for methods chain.
*/
removeAll(): 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 childs index.
* @return This node for methods chain.
*/
removeChild(child: Child | number): this
replaceValues(
pattern: RegExp | string,
replaced: { (substring: string, ...args: any[]): string } | string
): this
/**
* Passes all declaration values within the container that match pattern
* through callback, replacing those values with the returned result
* of callback.
*
* This method is useful if you are using a custom unit or function
* and need to iterate through all values.
*
* ```js
* root.replaceValues(/\d+rem/, { fast: 'rem' }, string => {
* return 15 * parseInt(string) + 'px'
* })
* ```
*
* @param pattern Replace pattern.
* @param {object} opts Options to speed up the search.
* @param callback String to replace pattern or callback
* that returns a new value. The callback
* will receive the same arguments
* as those passed to a function parameter
* of `String#replace`.
* @return This node for methods chain.
*/
replaceValues(
pattern: RegExp | string,
options: Container.ValueOptions,
replaced: { (substring: string, ...args: any[]): string } | string
): this
/**
* Returns `true` if callback returns `true` for (at least) one
* of the containers children.
*
* ```js
* const hasPrefix = rule.some(i => i.prop[0] === '-')
* ```
*
* @param condition Iterator returns true or false.
* @return Is some child pass condition.
*/
some(
condition: (node: Child, index: number, nodes: Child[]) => boolean
): boolean
/**
* Traverses the containers descendant nodes, calling callback
* for each node.
@@ -119,6 +334,51 @@ export default abstract class Container<
walk(
callback: (node: ChildNode, index: number) => false | void
): false | undefined
/**
* Traverses the containers descendant nodes, calling callback
* for each at-rule node.
*
* If you pass a filter, iteration will only happen over at-rules
* that have matching names.
*
* Like `Container#each`, this method is safe
* to use if you are mutating arrays during iteration.
*
* ```js
* root.walkAtRules(rule => {
* if (isOld(rule.name)) rule.remove()
* })
*
* let first = false
* root.walkAtRules('charset', rule => {
* if (!first) {
* first = true
* } else {
* rule.remove()
* }
* })
* ```
*
* @param name String or regular expression to filter at-rules by name.
* @param callback Iterator receives each node and index.
* @return Returns `false` if iteration was broke.
*/
walkAtRules(
nameFilter: RegExp | string,
callback: (atRule: AtRule, index: number) => false | void
): false | undefined
walkAtRules(
callback: (atRule: AtRule, index: number) => false | void
): false | undefined
walkComments(
callback: (comment: Comment, indexed: number) => false | void
): false | undefined
walkComments(
callback: (comment: Comment, indexed: number) => false | void
): false | undefined
/**
* Traverses the containers descendant nodes, calling callback
@@ -150,13 +410,12 @@ export default abstract class Container<
* @return Returns `false` if iteration was broke.
*/
walkDecls(
propFilter: string | RegExp,
propFilter: RegExp | string,
callback: (decl: Declaration, index: number) => false | void
): false | undefined
walkDecls(
callback: (decl: Declaration, index: number) => false | void
): false | undefined
/**
* Traverses the containers descendant nodes, calling callback
* for each rule node.
@@ -180,263 +439,14 @@ export default abstract class Container<
* @return Returns `false` if iteration was broke.
*/
walkRules(
selectorFilter: string | RegExp,
selectorFilter: RegExp | string,
callback: (rule: Rule, index: number) => false | void
): false | undefined
walkRules(
callback: (rule: Rule, index: number) => false | void
): false | undefined
/**
* Traverses the containers descendant nodes, calling callback
* for each at-rule node.
*
* If you pass a filter, iteration will only happen over at-rules
* that have matching names.
*
* Like `Container#each`, this method is safe
* to use if you are mutating arrays during iteration.
*
* ```js
* root.walkAtRules(rule => {
* if (isOld(rule.name)) rule.remove()
* })
*
* let first = false
* root.walkAtRules('charset', rule => {
* if (!first) {
* first = true
* } else {
* rule.remove()
* }
* })
* ```
*
* @param name String or regular expression to filter at-rules by name.
* @param callback Iterator receives each node and index.
* @return Returns `false` if iteration was broke.
*/
walkAtRules(
nameFilter: string | RegExp,
callback: (atRule: AtRule, index: number) => false | void
): false | undefined
walkAtRules(
callback: (atRule: AtRule, index: number) => false | void
): false | undefined
/**
* Traverses the containers descendant nodes, calling callback
* for each comment node.
*
* Like `Container#each`, this method is safe
* to use if you are mutating arrays during iteration.
*
* ```js
* root.walkComments(comment => {
* comment.remove()
* })
* ```
*
* @param callback Iterator receives each node and index.
* @return Returns `false` if iteration was broke.
*/
walkComments(
callback: (comment: Comment, indexed: number) => false | void
): false | undefined
walkComments(
callback: (comment: Comment, indexed: 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: (Node | Node[] | ChildProps | ChildProps[] | string | string[])[]
): 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: (Node | Node[] | ChildProps | ChildProps[] | string | string[])[]
): 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.
*/
push(child: Child): this
/**
* Insert new node before old node within the container.
*
* ```js
* rule.insertBefore(decl, decl.clone({ prop: '-webkit-' + decl.prop }))
* ```
*
* @param oldNode Child or childs index.
* @param newNode New node.
* @return This node for methods chain.
*/
insertBefore(
oldNode: Child | number,
newNode: Child | ChildProps | string | Child[] | ChildProps[] | string[]
): this
/**
* Insert new node after old node within the container.
*
* @param oldNode Child or childs index.
* @param newNode New node.
* @return This node for methods chain.
*/
insertAfter(
oldNode: Child | number,
newNode: Child | ChildProps | string | Child[] | ChildProps[] | string[]
): 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 childs index.
* @return This node for methods chain.
*/
removeChild(child: Child | number): this
/**
* Removes all children from the container
* and cleans their parent properties.
*
* ```js
* rule.removeAll()
* rule.nodes.length //=> 0
* ```
*
* @return This node for methods chain.
*/
removeAll(): this
/**
* Passes all declaration values within the container that match pattern
* through callback, replacing those values with the returned result
* of callback.
*
* This method is useful if you are using a custom unit or function
* and need to iterate through all values.
*
* ```js
* root.replaceValues(/\d+rem/, { fast: 'rem' }, string => {
* return 15 * parseInt(string) + 'px'
* })
* ```
*
* @param pattern Replace pattern.
* @param {object} opts Options to speed up the search.
* @param callback String to replace pattern or callback
* that returns a new value. The callback
* will receive the same arguments
* as those passed to a function parameter
* of `String#replace`.
* @return This node for methods chain.
*/
replaceValues(
pattern: string | RegExp,
options: ValueOptions,
replaced: string | { (substring: string, ...args: any[]): string }
): this
replaceValues(
pattern: string | RegExp,
replaced: string | { (substring: string, ...args: any[]): string }
): this
/**
* Returns `true` if callback returns `true`
* for all of the containers children.
*
* ```js
* const noPrefixes = rule.every(i => i.prop[0] !== '-')
* ```
*
* @param condition Iterator returns true or false.
* @return Is every child pass condition.
*/
every(
condition: (node: Child, index: number, nodes: Child[]) => boolean
): boolean
/**
* Returns `true` if callback returns `true` for (at least) one
* of the containers children.
*
* ```js
* const hasPrefix = rule.some(i => i.prop[0] === '-')
* ```
*
* @param condition Iterator returns true or false.
* @return Is some child pass condition.
*/
some(
condition: (node: Child, index: number, nodes: Child[]) => 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.
*/
index(child: Child | number): number
}
declare class Container<Child extends Node = ChildNode> extends Container_<Child> {}
export = Container

View File

@@ -25,12 +25,24 @@ function markDirtyUp(node) {
}
class Container extends Node {
push(child) {
child.parent = this
this.proxyOf.nodes.push(child)
append(...children) {
for (let child of children) {
let nodes = this.normalize(child, this.last)
for (let node of nodes) this.proxyOf.nodes.push(node)
}
this.markDirty()
return this
}
cleanRaws(keepBetween) {
super.cleanRaws(keepBetween)
if (this.nodes) {
for (let node of this.nodes) node.cleanRaws(keepBetween)
}
}
each(callback) {
if (!this.proxyOf.nodes) return undefined
let iterator = this.getIterator()
@@ -48,151 +60,80 @@ class Container extends Node {
return result
}
walk(callback) {
return this.each((child, i) => {
let result
try {
result = callback(child, i)
} catch (e) {
throw child.addToError(e)
}
if (result !== false && child.walk) {
result = child.walk(callback)
}
return result
})
every(condition) {
return this.nodes.every(condition)
}
walkDecls(prop, callback) {
if (!callback) {
callback = prop
return this.walk((child, i) => {
if (child.type === 'decl') {
return callback(child, i)
get first() {
if (!this.proxyOf.nodes) return undefined
return this.proxyOf.nodes[0]
}
getIterator() {
if (!this.lastEach) this.lastEach = 0
if (!this.indexes) this.indexes = {}
this.lastEach += 1
let iterator = this.lastEach
this.indexes[iterator] = 0
return iterator
}
getProxyProcessor() {
return {
get(node, prop) {
if (prop === 'proxyOf') {
return node
} else if (!node[prop]) {
return node[prop]
} else if (
prop === 'each' ||
(typeof prop === 'string' && prop.startsWith('walk'))
) {
return (...args) => {
return node[prop](
...args.map(i => {
if (typeof i === 'function') {
return (child, index) => i(child.toProxy(), index)
} else {
return i
}
})
)
}
} else if (prop === 'every' || prop === 'some') {
return cb => {
return node[prop]((child, ...other) =>
cb(child.toProxy(), ...other)
)
}
} else if (prop === 'root') {
return () => node.root().toProxy()
} else if (prop === 'nodes') {
return node.nodes.map(i => i.toProxy())
} else if (prop === 'first' || prop === 'last') {
return node[prop].toProxy()
} else {
return node[prop]
}
})
}
if (prop instanceof RegExp) {
return this.walk((child, i) => {
if (child.type === 'decl' && prop.test(child.prop)) {
return callback(child, i)
},
set(node, prop, value) {
if (node[prop] === value) return true
node[prop] = value
if (prop === 'name' || prop === 'params' || prop === 'selector') {
node.markDirty()
}
})
}
return this.walk((child, i) => {
if (child.type === 'decl' && child.prop === prop) {
return callback(child, i)
}
})
}
walkRules(selector, callback) {
if (!callback) {
callback = selector
return this.walk((child, i) => {
if (child.type === 'rule') {
return callback(child, i)
}
})
}
if (selector instanceof RegExp) {
return this.walk((child, i) => {
if (child.type === 'rule' && selector.test(child.selector)) {
return callback(child, i)
}
})
}
return this.walk((child, i) => {
if (child.type === 'rule' && child.selector === selector) {
return callback(child, i)
}
})
}
walkAtRules(name, callback) {
if (!callback) {
callback = name
return this.walk((child, i) => {
if (child.type === 'atrule') {
return callback(child, i)
}
})
}
if (name instanceof RegExp) {
return this.walk((child, i) => {
if (child.type === 'atrule' && name.test(child.name)) {
return callback(child, i)
}
})
}
return this.walk((child, i) => {
if (child.type === 'atrule' && child.name === name) {
return callback(child, i)
}
})
}
walkComments(callback) {
return this.walk((child, i) => {
if (child.type === 'comment') {
return callback(child, i)
}
})
}
append(...children) {
for (let child of children) {
let nodes = this.normalize(child, this.last)
for (let node of nodes) this.proxyOf.nodes.push(node)
}
this.markDirty()
return this
}
prepend(...children) {
children = children.reverse()
for (let child of children) {
let nodes = this.normalize(child, this.first, 'prepend').reverse()
for (let node of nodes) this.proxyOf.nodes.unshift(node)
for (let id in this.indexes) {
this.indexes[id] = this.indexes[id] + nodes.length
return true
}
}
this.markDirty()
return this
}
cleanRaws(keepBetween) {
super.cleanRaws(keepBetween)
if (this.nodes) {
for (let node of this.nodes) node.cleanRaws(keepBetween)
}
}
insertBefore(exist, add) {
let existIndex = this.index(exist)
let type = existIndex === 0 ? 'prepend' : false
let nodes = this.normalize(add, this.proxyOf.nodes[existIndex], type).reverse()
existIndex = this.index(exist)
for (let node of nodes) this.proxyOf.nodes.splice(existIndex, 0, node)
let index
for (let id in this.indexes) {
index = this.indexes[id]
if (existIndex <= index) {
this.indexes[id] = index + nodes.length
}
}
this.markDirty()
return this
index(child) {
if (typeof child === 'number') return child
if (child.proxyOf) child = child.proxyOf
return this.proxyOf.nodes.indexOf(child)
}
insertAfter(exist, add) {
@@ -214,16 +155,18 @@ class Container extends Node {
return this
}
removeChild(child) {
child = this.index(child)
this.proxyOf.nodes[child].parent = undefined
this.proxyOf.nodes.splice(child, 1)
insertBefore(exist, add) {
let existIndex = this.index(exist)
let type = existIndex === 0 ? 'prepend' : false
let nodes = this.normalize(add, this.proxyOf.nodes[existIndex], type).reverse()
existIndex = this.index(exist)
for (let node of nodes) this.proxyOf.nodes.splice(existIndex, 0, node)
let index
for (let id in this.indexes) {
index = this.indexes[id]
if (index >= child) {
this.indexes[id] = index - 1
if (existIndex <= index) {
this.indexes[id] = index + nodes.length
}
}
@@ -232,52 +175,6 @@ class Container extends Node {
return this
}
removeAll() {
for (let node of this.proxyOf.nodes) node.parent = undefined
this.proxyOf.nodes = []
this.markDirty()
return this
}
replaceValues(pattern, opts, callback) {
if (!callback) {
callback = opts
opts = {}
}
this.walkDecls(decl => {
if (opts.props && !opts.props.includes(decl.prop)) return
if (opts.fast && !decl.value.includes(opts.fast)) return
decl.value = decl.value.replace(pattern, callback)
})
this.markDirty()
return this
}
every(condition) {
return this.nodes.every(condition)
}
some(condition) {
return this.nodes.some(condition)
}
index(child) {
if (typeof child === 'number') return child
if (child.proxyOf) child = child.proxyOf
return this.proxyOf.nodes.indexOf(child)
}
get first() {
if (!this.proxyOf.nodes) return undefined
return this.proxyOf.nodes[0]
}
get last() {
if (!this.proxyOf.nodes) return undefined
return this.proxyOf.nodes[this.proxyOf.nodes.length - 1]
@@ -333,65 +230,168 @@ class Container extends Node {
return processed
}
getProxyProcessor() {
return {
set(node, prop, value) {
if (node[prop] === value) return true
node[prop] = value
if (prop === 'name' || prop === 'params' || prop === 'selector') {
node.markDirty()
}
return true
},
get(node, prop) {
if (prop === 'proxyOf') {
return node
} else if (!node[prop]) {
return node[prop]
} else if (
prop === 'each' ||
(typeof prop === 'string' && prop.startsWith('walk'))
) {
return (...args) => {
return node[prop](
...args.map(i => {
if (typeof i === 'function') {
return (child, index) => i(child.toProxy(), index)
} else {
return i
}
})
)
}
} else if (prop === 'every' || prop === 'some') {
return cb => {
return node[prop]((child, ...other) =>
cb(child.toProxy(), ...other)
)
}
} else if (prop === 'root') {
return () => node.root().toProxy()
} else if (prop === 'nodes') {
return node.nodes.map(i => i.toProxy())
} else if (prop === 'first' || prop === 'last') {
return node[prop].toProxy()
} else {
return node[prop]
}
prepend(...children) {
children = children.reverse()
for (let child of children) {
let nodes = this.normalize(child, this.first, 'prepend').reverse()
for (let node of nodes) this.proxyOf.nodes.unshift(node)
for (let id in this.indexes) {
this.indexes[id] = this.indexes[id] + nodes.length
}
}
this.markDirty()
return this
}
getIterator() {
if (!this.lastEach) this.lastEach = 0
if (!this.indexes) this.indexes = {}
push(child) {
child.parent = this
this.proxyOf.nodes.push(child)
return this
}
this.lastEach += 1
let iterator = this.lastEach
this.indexes[iterator] = 0
removeAll() {
for (let node of this.proxyOf.nodes) node.parent = undefined
this.proxyOf.nodes = []
return iterator
this.markDirty()
return this
}
removeChild(child) {
child = this.index(child)
this.proxyOf.nodes[child].parent = undefined
this.proxyOf.nodes.splice(child, 1)
let index
for (let id in this.indexes) {
index = this.indexes[id]
if (index >= child) {
this.indexes[id] = index - 1
}
}
this.markDirty()
return this
}
replaceValues(pattern, opts, callback) {
if (!callback) {
callback = opts
opts = {}
}
this.walkDecls(decl => {
if (opts.props && !opts.props.includes(decl.prop)) return
if (opts.fast && !decl.value.includes(opts.fast)) return
decl.value = decl.value.replace(pattern, callback)
})
this.markDirty()
return this
}
some(condition) {
return this.nodes.some(condition)
}
walk(callback) {
return this.each((child, i) => {
let result
try {
result = callback(child, i)
} catch (e) {
throw child.addToError(e)
}
if (result !== false && child.walk) {
result = child.walk(callback)
}
return result
})
}
walkAtRules(name, callback) {
if (!callback) {
callback = name
return this.walk((child, i) => {
if (child.type === 'atrule') {
return callback(child, i)
}
})
}
if (name instanceof RegExp) {
return this.walk((child, i) => {
if (child.type === 'atrule' && name.test(child.name)) {
return callback(child, i)
}
})
}
return this.walk((child, i) => {
if (child.type === 'atrule' && child.name === name) {
return callback(child, i)
}
})
}
walkComments(callback) {
return this.walk((child, i) => {
if (child.type === 'comment') {
return callback(child, i)
}
})
}
walkDecls(prop, callback) {
if (!callback) {
callback = prop
return this.walk((child, i) => {
if (child.type === 'decl') {
return callback(child, i)
}
})
}
if (prop instanceof RegExp) {
return this.walk((child, i) => {
if (child.type === 'decl' && prop.test(child.prop)) {
return callback(child, i)
}
})
}
return this.walk((child, i) => {
if (child.type === 'decl' && child.prop === prop) {
return callback(child, i)
}
})
}
walkRules(selector, callback) {
if (!callback) {
callback = selector
return this.walk((child, i) => {
if (child.type === 'rule') {
return callback(child, i)
}
})
}
if (selector instanceof RegExp) {
return this.walk((child, i) => {
if (child.type === 'rule' && selector.test(child.selector)) {
return callback(child, i)
}
})
}
return this.walk((child, i) => {
if (child.type === 'rule' && child.selector === selector) {
return callback(child, i)
}
})
}
}

View File

@@ -1,18 +1,23 @@
import { FilePosition } from './input.js'
/**
* A position that is part of a range.
*/
export interface RangePosition {
declare namespace CssSyntaxError {
/**
* The line number in the input.
* A position that is part of a range.
*/
line: number
export interface RangePosition {
/**
* The column number in the input.
*/
column: number
/**
* The column number in the input.
*/
column: number
/**
* The line number in the input.
*/
line: number
}
// eslint-disable-next-line @typescript-eslint/no-use-before-define
export { CssSyntaxError_ as default }
}
/**
@@ -44,89 +49,7 @@ export interface RangePosition {
* }
* ```
*/
export default class CssSyntaxError {
/**
* Instantiates a CSS syntax error. Can be instantiated for a single position
* or for a range.
* @param message Error message.
* @param lineOrStartPos If for a single position, the line number, or if for
* a range, the inclusive start position of the error.
* @param columnOrEndPos If for a single position, the column number, or if for
* a range, the exclusive end position of the error.
* @param source Source code of the broken file.
* @param file Absolute path to the broken file.
* @param plugin PostCSS plugin name, if error came from plugin.
*/
constructor(
message: string,
lineOrStartPos?: number | RangePosition,
columnOrEndPos?: number | RangePosition,
source?: string,
file?: string,
plugin?: string
)
stack: string
/**
* Always equal to `'CssSyntaxError'`. You should always check error type
* by `error.name === 'CssSyntaxError'`
* instead of `error instanceof CssSyntaxError`,
* because npm could have several PostCSS versions.
*
* ```js
* if (error.name === 'CssSyntaxError') {
* error //=> CssSyntaxError
* }
* ```
*/
name: 'CssSyntaxError'
/**
* Error message.
*
* ```js
* error.message //=> 'Unclosed block'
* ```
*/
reason: string
/**
* Full error text in the GNU error format
* with plugin, file, line and column.
*
* ```js
* error.message //=> 'a.css:1:1: Unclosed block'
* ```
*/
message: string
/**
* Absolute path to the broken file.
*
* ```js
* error.file //=> 'a.sass'
* error.input.file //=> 'a.css'
* ```
*
* PostCSS will use the input source map to detect the original location.
* If you need the position in the PostCSS input, use `error.input.file`.
*/
file?: string
/**
* Source line of the error.
*
* ```js
* error.line //=> 2
* error.input.line //=> 4
* ```
*
* PostCSS will use the input source map to detect the original location.
* If you need the position in the PostCSS input, use `error.input.line`.
*/
line?: number
declare class CssSyntaxError_ {
/**
* Source column of the error.
*
@@ -140,20 +63,6 @@ export default class CssSyntaxError {
*/
column?: number
/**
* Source line of the error's end, exclusive. Provided if the error pertains
* to a range.
*
* ```js
* error.endLine //=> 3
* error.input.endLine //=> 4
* ```
*
* PostCSS will use the input source map to detect the original location.
* If you need the position in the PostCSS input, use `error.input.endLine`.
*/
endLine?: number
/**
* Source column of the error's end, exclusive. Provided if the error pertains
* to a range.
@@ -169,23 +78,31 @@ export default class CssSyntaxError {
endColumn?: number
/**
* Source code of the broken file.
* Source line of the error's end, exclusive. Provided if the error pertains
* to a range.
*
* ```js
* error.source //=> 'a { b {} }'
* error.input.source //=> 'a b { }'
* error.endLine //=> 3
* error.input.endLine //=> 4
* ```
*
* PostCSS will use the input source map to detect the original location.
* If you need the position in the PostCSS input, use `error.input.endLine`.
*/
source?: string
endLine?: number
/**
* Plugin name, if error came from plugin.
* Absolute path to the broken file.
*
* ```js
* error.plugin //=> 'postcss-vars'
* error.file //=> 'a.sass'
* error.input.file //=> 'a.css'
* ```
*
* PostCSS will use the input source map to detect the original location.
* If you need the position in the PostCSS input, use `error.input.file`.
*/
plugin?: string
file?: string
/**
* Input object with PostCSS internal information
@@ -202,17 +119,92 @@ export default class CssSyntaxError {
input?: FilePosition
/**
* Returns error position, message and source code of the broken part.
* Source line of the error.
*
* ```js
* error.toString() //=> "CssSyntaxError: app.css:1:1: Unclosed block
* // > 1 | a {
* // | ^"
* error.line //=> 2
* error.input.line //=> 4
* ```
*
* @return Error position, message and source code.
* PostCSS will use the input source map to detect the original location.
* If you need the position in the PostCSS input, use `error.input.line`.
*/
toString(): string
line?: number
/**
* Full error text in the GNU error format
* with plugin, file, line and column.
*
* ```js
* error.message //=> 'a.css:1:1: Unclosed block'
* ```
*/
message: string
/**
* Always equal to `'CssSyntaxError'`. You should always check error type
* by `error.name === 'CssSyntaxError'`
* instead of `error instanceof CssSyntaxError`,
* because npm could have several PostCSS versions.
*
* ```js
* if (error.name === 'CssSyntaxError') {
* error //=> CssSyntaxError
* }
* ```
*/
name: 'CssSyntaxError'
/**
* Plugin name, if error came from plugin.
*
* ```js
* error.plugin //=> 'postcss-vars'
* ```
*/
plugin?: string
/**
* Error message.
*
* ```js
* error.message //=> 'Unclosed block'
* ```
*/
reason: string
/**
* Source code of the broken file.
*
* ```js
* error.source //=> 'a { b {} }'
* error.input.source //=> 'a b { }'
* ```
*/
source?: string
stack: string
/**
* Instantiates a CSS syntax error. Can be instantiated for a single position
* or for a range.
* @param message Error message.
* @param lineOrStartPos If for a single position, the line number, or if for
* a range, the inclusive start position of the error.
* @param columnOrEndPos If for a single position, the column number, or if for
* a range, the exclusive end position of the error.
* @param source Source code of the broken file.
* @param file Absolute path to the broken file.
* @param plugin PostCSS plugin name, if error came from plugin.
*/
constructor(
message: string,
lineOrStartPos?: CssSyntaxError.RangePosition | number,
columnOrEndPos?: CssSyntaxError.RangePosition | number,
source?: string,
file?: string,
plugin?: string
)
/**
* Returns a few lines of CSS source that caused the error.
@@ -236,4 +228,21 @@ export default class CssSyntaxError {
* @return Few lines of CSS source that caused the error.
*/
showSourceCode(color?: boolean): string
/**
* Returns error position, message and source code of the broken part.
*
* ```js
* error.toString() //=> "CssSyntaxError: app.css:1:1: Unclosed block
* // > 1 | a {
* // | ^"
* ```
*
* @return Error position, message and source code.
*/
toString(): string
}
declare class CssSyntaxError extends CssSyntaxError_ {}
export = CssSyntaxError

View File

@@ -64,7 +64,7 @@ class CssSyntaxError extends Error {
let mark, aside
if (color) {
let { bold, red, gray } = pico.createColors(true)
let { bold, gray, red } = pico.createColors(true)
mark = text => bold(red(text))
aside = text => gray(text)
} else {

View File

@@ -1,124 +1,148 @@
import Container from './container.js'
import Node from './node.js'
interface DeclarationRaws extends Record<string, unknown> {
/**
* The space symbols before the node. It also stores `*`
* and `_` symbols before the declaration (IE hack).
*/
before?: string
declare namespace Declaration {
export interface DeclarationRaws extends Record<string, unknown> {
/**
* The space symbols before the node. It also stores `*`
* and `_` symbols before the declaration (IE hack).
*/
before?: string
/**
* The symbols between the property and value for declarations.
*/
between?: string
/**
* The symbols between the property and value for declarations.
*/
between?: string
/**
* The content of the important statement, if it is not just `!important`.
*/
important?: string
/**
* The content of the important statement, if it is not just `!important`.
*/
important?: string
/**
* Declaration value with comments.
*/
value?: {
value: string
raw: string
/**
* Declaration value with comments.
*/
value?: {
raw: string
value: string
}
}
}
export interface DeclarationProps {
/** Name of the declaration. */
prop: string
/** Value of the declaration. */
value: string
/** Whether the declaration has an `!important` annotation. */
important?: boolean
/** Information used to generate byte-to-byte equal node string as it was in the origin input. */
raws?: DeclarationRaws
export interface DeclarationProps {
/** Whether the declaration has an `!important` annotation. */
important?: boolean
/** Name of the declaration. */
prop: string
/** Information used to generate byte-to-byte equal node string as it was in the origin input. */
raws?: DeclarationRaws
/** Value of the declaration. */
value: string
}
// eslint-disable-next-line @typescript-eslint/no-use-before-define
export { Declaration_ as default }
}
/**
* Represents a CSS declaration.
* It represents a class that handles
* [CSS declarations](https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax#css_declarations)
*
* ```js
* Once (root, { Declaration }) {
* let color = new Declaration({ prop: 'color', value: 'black' })
* const color = new Declaration({ prop: 'color', value: 'black' })
* root.append(color)
* }
* ```
*
* ```js
* const root = postcss.parse('a { color: black }')
* const decl = root.first.first
* const decl = root.first?.first
*
* decl.type //=> 'decl'
* decl.toString() //=> ' color: black'
* ```
*/
export default class Declaration extends Node {
type: 'decl'
parent: Container | undefined
raws: DeclarationRaws
declare class Declaration_ extends Node {
/**
* The declaration's property name.
* It represents a specificity of the declaration.
*
* ```js
* const root = postcss.parse('a { color: black }')
* const decl = root.first.first
* decl.prop //=> 'color'
* ```
*/
prop: string
/**
* The declarations value.
*
* This value will be cleaned of comments. If the source value contained
* comments, those comments will be available in the `raws` property.
* If you have not changed the value, the result of `decl.toString()`
* will include the original raws value (comments and all).
*
* ```js
* const root = postcss.parse('a { color: black }')
* const decl = root.first.first
* decl.value //=> 'black'
* ```
*/
value: string
/**
* `true` if the declaration has an `!important` annotation.
* If true, the CSS declaration will have an
* [important](https://developer.mozilla.org/en-US/docs/Web/CSS/important)
* specifier.
*
* ```js
* const root = postcss.parse('a { color: black !important; color: red }')
*
* root.first.first.important //=> true
* root.first.last.important //=> undefined
* ```
*/
important: boolean
parent: Container | undefined
/**
* `true` if declaration is declaration of CSS Custom Property
* or Sass variable.
* The property name for a CSS declaration.
*
* ```js
* const root = postcss.parse('a { color: black }')
* const decl = root.first.first
*
* decl.prop //=> 'color'
* ```
*/
prop: string
raws: Declaration.DeclarationRaws
type: 'decl'
/**
* The property value for a CSS declaration.
*
* Any CSS comments inside the value string will be filtered out.
* CSS comments present in the source value will be available in
* the `raws` property.
*
* Assigning new `value` would ignore the comments in `raws`
* property while compiling node to string.
*
* ```js
* const root = postcss.parse('a { color: black }')
* const decl = root.first.first
*
* decl.value //=> 'black'
* ```
*/
value: string
/**
* It represents a getter that returns `true` if a declaration starts with
* `--` or `$`, which are used to declare variables in CSS and SASS/SCSS.
*
* ```js
* const root = postcss.parse(':root { --one: 1 }')
* let one = root.first.first
* const one = root.first.first
*
* one.variable //=> true
* ```
*
* ```js
* const root = postcss.parse('$one: 1')
* let one = root.first
* const one = root.first
*
* one.variable //=> true
* ```
*/
variable: boolean
constructor(defaults?: DeclarationProps)
assign(overrides: object | DeclarationProps): this
clone(overrides?: Partial<DeclarationProps>): this
cloneBefore(overrides?: Partial<DeclarationProps>): this
cloneAfter(overrides?: Partial<DeclarationProps>): this
constructor(defaults?: Declaration.DeclarationProps)
assign(overrides: Declaration.DeclarationProps | object): this
clone(overrides?: Partial<Declaration.DeclarationProps>): Declaration
cloneAfter(overrides?: Partial<Declaration.DeclarationProps>): Declaration
cloneBefore(overrides?: Partial<Declaration.DeclarationProps>): Declaration
}
declare class Declaration extends Declaration_ {}
export = Declaration

View File

@@ -1,23 +1,25 @@
import Container, { ContainerProps } from './container.js'
import { ProcessOptions } from './postcss.js'
import Result from './result.js'
import Root, { RootProps } from './root.js'
import Root from './root.js'
export interface DocumentProps extends ContainerProps {
nodes?: Root[]
declare namespace Document {
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>
/**
* 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>
}
// eslint-disable-next-line @typescript-eslint/no-use-before-define
export { Document_ as default }
}
type ChildNode = Root
type ChildProps = RootProps
/**
* Represents a file and contains all its parsed nodes.
*
@@ -32,11 +34,16 @@ type ChildProps = RootProps
* document.nodes.length //=> 2
* ```
*/
export default class Document extends Container<Root> {
type: 'document'
declare class Document_ extends Container<Root> {
parent: undefined
type: 'document'
constructor(defaults?: DocumentProps)
constructor(defaults?: Document.DocumentProps)
assign(overrides: Document.DocumentProps | object): this
clone(overrides?: Partial<Document.DocumentProps>): Document
cloneAfter(overrides?: Partial<Document.DocumentProps>): Document
cloneBefore(overrides?: Partial<Document.DocumentProps>): Document
/**
* Returns a `Result` instance representing the documents CSS roots.
@@ -55,3 +62,7 @@ export default class Document extends Container<Root> {
*/
toResult(options?: ProcessOptions): Result
}
declare class Document extends Document_ {}
export = Document

View File

@@ -1,5 +1,9 @@
import { JSONHydrator } from './postcss.js'
declare const fromJSON: JSONHydrator
interface FromJSON extends JSONHydrator {
default: FromJSON
}
export default fromJSON
declare const fromJSON: FromJSON
export = fromJSON

View File

@@ -1,41 +1,46 @@
import { ProcessOptions } from './postcss.js'
import { CssSyntaxError, ProcessOptions } from './postcss.js'
import PreviousMap from './previous-map.js'
export interface FilePosition {
/**
* URL for the source file.
*/
url: string
declare namespace Input {
export interface FilePosition {
/**
* Column of inclusive start position in source file.
*/
column: number
/**
* Absolute path to the source file.
*/
file?: string
/**
* Column of exclusive end position in source file.
*/
endColumn?: number
/**
* Line of inclusive start position in source file.
*/
line: number
/**
* Line of exclusive end position in source file.
*/
endLine?: number
/**
* Column of inclusive start position in source file.
*/
column: number
/**
* Absolute path to the source file.
*/
file?: string
/**
* Line of exclusive end position in source file.
*/
endLine?: number
/**
* Line of inclusive start position in source file.
*/
line: number
/**
* Column of exclusive end position in source file.
*/
endColumn?: number
/**
* Source code.
*/
source?: string
/**
* Source code.
*/
source?: string
/**
* URL for the source file.
*/
url: string
}
// eslint-disable-next-line @typescript-eslint/no-use-before-define
export { Input_ as default }
}
/**
@@ -46,7 +51,7 @@ export interface FilePosition {
* const input = root.source.input
* ```
*/
export default class Input {
declare class Input_ {
/**
* Input CSS source.
*
@@ -57,16 +62,6 @@ export default class Input {
*/
css: string
/**
* The input source map passed from a compilation step before PostCSS
* (for example, from Sass compiler).
*
* ```js
* root.source.input.map.consumer().sources //=> ['a.sass']
* ```
*/
map: PreviousMap
/**
* The absolute path to the CSS source file defined
* with the `from` option.
@@ -78,6 +73,11 @@ export default class Input {
*/
file?: string
/**
* The flag to indicate whether or not the source code has Unicode BOM.
*/
hasBOM: boolean
/**
* The unique ID of the CSS source. It will be created if `from` option
* is not provided (because PostCSS does not know the file path).
@@ -91,9 +91,14 @@ export default class Input {
id?: string
/**
* The flag to indicate whether or not the source code has Unicode BOM.
* The input source map passed from a compilation step before PostCSS
* (for example, from Sass compiler).
*
* ```js
* root.source.input.map.consumer().sources //=> ['a.sass']
* ```
*/
hasBOM: boolean
map: PreviousMap
/**
* @param css Input CSS source.
@@ -101,6 +106,43 @@ export default class Input {
*/
constructor(css: string, opts?: ProcessOptions)
error(
message: string,
start:
| {
column: number
line: number
}
| {
offset: number
},
end:
| {
column: number
line: number
}
| {
offset: number
},
opts?: { plugin?: CssSyntaxError['plugin'] }
): CssSyntaxError
/**
* Returns `CssSyntaxError` with information about the error and its position.
*/
error(
message: string,
line: number,
column: number,
opts?: { plugin?: CssSyntaxError['plugin'] }
): CssSyntaxError
error(
message: string,
offset: number,
opts?: { plugin?: CssSyntaxError['plugin'] }
): CssSyntaxError
/**
* The CSS source identifier. Contains `Input#file` if the user
* set the `from` option, or `Input#id` if they did not.
@@ -114,7 +156,12 @@ export default class Input {
* ```
*/
get from(): string
/**
* Converts source offset to line and column.
*
* @param offset Source offset.
*/
fromOffset(offset: number): { col: number; line: number } | null
/**
* Reads the input source map and returns a symbol position
* in the input source (e.g., in a Sass file that was compiled
@@ -139,12 +186,9 @@ export default class Input {
column: number,
endLine?: number,
endColumn?: number
): FilePosition | false
/**
* Converts source offset to line and column.
*
* @param offset Source offset.
*/
fromOffset(offset: number): { line: number; col: number } | null
): false | Input.FilePosition
}
declare class Input extends Input_ {}
export = Input

View File

@@ -2,7 +2,7 @@
let { SourceMapConsumer, SourceMapGenerator } = require('source-map-js')
let { fileURLToPath, pathToFileURL } = require('url')
let { resolve, isAbsolute } = require('path')
let { isAbsolute, resolve } = require('path')
let { nanoid } = require('nanoid/non-secure')
let terminalHighlight = require('./terminal-highlight')
@@ -60,6 +60,74 @@ class Input {
if (this.map) this.map.file = this.from
}
error(message, line, column, opts = {}) {
let result, endLine, endColumn
if (line && typeof line === 'object') {
let start = line
let end = column
if (typeof start.offset === 'number') {
let pos = this.fromOffset(start.offset)
line = pos.line
column = pos.col
} else {
line = start.line
column = start.column
}
if (typeof end.offset === 'number') {
let pos = this.fromOffset(end.offset)
endLine = pos.line
endColumn = pos.col
} else {
endLine = end.line
endColumn = end.column
}
} else if (!column) {
let pos = this.fromOffset(line)
line = pos.line
column = pos.col
}
let origin = this.origin(line, column, endLine, endColumn)
if (origin) {
result = new CssSyntaxError(
message,
origin.endLine === undefined
? origin.line
: { column: origin.column, line: origin.line },
origin.endLine === undefined
? origin.column
: { column: origin.endColumn, line: origin.endLine },
origin.source,
origin.file,
opts.plugin
)
} else {
result = new CssSyntaxError(
message,
endLine === undefined ? line : { column, line },
endLine === undefined ? column : { column: endColumn, line: endLine },
this.css,
this.file,
opts.plugin
)
}
result.input = { column, endColumn, endLine, line, source: this.css }
if (this.file) {
if (pathToFileURL) {
result.input.url = pathToFileURL(this.file).toString()
}
result.input.file = this.file
}
return result
}
get from() {
return this.file || this.id
}
fromOffset(offset) {
let lastLine, lineToIndex
if (!this[fromOffsetCache]) {
@@ -97,85 +165,28 @@ class Input {
}
}
return {
line: min + 1,
col: offset - lineToIndex[min] + 1
col: offset - lineToIndex[min] + 1,
line: min + 1
}
}
error(message, line, column, opts = {}) {
let result, endLine, endColumn
if (line && typeof line === 'object') {
let start = line
let end = column
if (typeof line.offset === 'number') {
let pos = this.fromOffset(start.offset)
line = pos.line
column = pos.col
} else {
line = start.line
column = start.column
}
if (typeof end.offset === 'number') {
let pos = this.fromOffset(end.offset)
endLine = pos.line
endColumn = pos.col
} else {
endLine = end.line
endColumn = end.column
}
} else if (!column) {
let pos = this.fromOffset(line)
line = pos.line
column = pos.col
mapResolve(file) {
if (/^\w+:\/\//.test(file)) {
return file
}
let origin = this.origin(line, column, endLine, endColumn)
if (origin) {
result = new CssSyntaxError(
message,
origin.endLine === undefined
? origin.line
: { line: origin.line, column: origin.column },
origin.endLine === undefined
? origin.column
: { line: origin.endLine, column: origin.endColumn },
origin.source,
origin.file,
opts.plugin
)
} else {
result = new CssSyntaxError(
message,
endLine === undefined ? line : { line, column },
endLine === undefined ? column : { line: endLine, column: endColumn },
this.css,
this.file,
opts.plugin
)
}
result.input = { line, column, endLine, endColumn, source: this.css }
if (this.file) {
if (pathToFileURL) {
result.input.url = pathToFileURL(this.file).toString()
}
result.input.file = this.file
}
return result
return resolve(this.map.consumer().sourceRoot || this.map.root || '.', file)
}
origin(line, column, endLine, endColumn) {
if (!this.map) return false
let consumer = this.map.consumer()
let from = consumer.originalPositionFor({ line, column })
let from = consumer.originalPositionFor({ column, line })
if (!from.source) return false
let to
if (typeof endLine === 'number') {
to = consumer.originalPositionFor({ line: endLine, column: endColumn })
to = consumer.originalPositionFor({ column: endColumn, line: endLine })
}
let fromUrl
@@ -190,11 +201,11 @@ class Input {
}
let result = {
url: fromUrl.toString(),
line: from.line,
column: from.column,
endColumn: to && to.column,
endLine: to && to.line,
endColumn: to && to.column
line: from.line,
url: fromUrl.toString()
}
if (fromUrl.protocol === 'file:') {
@@ -212,17 +223,6 @@ class Input {
return result
}
mapResolve(file) {
if (/^\w+:\/\//.test(file)) {
return file
}
return resolve(this.map.consumer().sourceRoot || this.map.root || '.', file)
}
get from() {
return this.file || this.id
}
toJSON() {
let json = {}
for (let name of ['hasBOM', 'css', 'file', 'id']) {

View File

@@ -1,8 +1,14 @@
import Result, { Message, ResultOptions } from './result.js'
import Document from './document.js'
import { SourceMap } from './postcss.js'
import Processor from './processor.js'
import Warning from './warning.js'
import Result, { Message, ResultOptions } from './result.js'
import Root from './root.js'
import Warning from './warning.js'
declare namespace LazyResult {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
export { LazyResult_ as default }
}
/**
* A Promise proxy for the result of PostCSS transformations.
@@ -13,22 +19,9 @@ import Root from './root.js'
* const lazy = postcss([autoprefixer]).process(css)
* ```
*/
export default class LazyResult implements PromiseLike<Result> {
/**
* Processes input CSS through synchronous and asynchronous plugins
* and calls `onFulfilled` with a Result instance. If a plugin throws
* an error, the `onRejected` callback will be executed.
*
* It implements standard Promise API.
*
* ```js
* postcss([autoprefixer]).process(css, { from: cssPath }).then(result => {
* console.log(result.css)
* })
* ```
*/
then: Promise<Result>['then']
declare class LazyResult_<RootNode = Document | Root>
implements PromiseLike<Result<RootNode>>
{
/**
* Processes input CSS through synchronous and asynchronous plugins
* and calls onRejected for each error thrown in any plugin.
@@ -43,7 +36,7 @@ export default class LazyResult implements PromiseLike<Result> {
* })
* ```
*/
catch: Promise<Result>['catch']
catch: Promise<Result<RootNode>>['catch']
/**
* Processes input CSS through synchronous and asynchronous plugins
@@ -57,7 +50,22 @@ export default class LazyResult implements PromiseLike<Result> {
* })
* ```
*/
finally: Promise<Result>['finally']
finally: Promise<Result<RootNode>>['finally']
/**
* Processes input CSS through synchronous and asynchronous plugins
* and calls `onFulfilled` with a Result instance. If a plugin throws
* an error, the `onRejected` callback will be executed.
*
* It implements standard Promise API.
*
* ```js
* postcss([autoprefixer]).process(css, { from: cssPath }).then(result => {
* console.log(result.css)
* })
* ```
*/
then: Promise<Result<RootNode>>['then']
/**
* @param processor Processor used for this transformation.
@@ -67,33 +75,11 @@ export default class LazyResult implements PromiseLike<Result> {
constructor(processor: Processor, css: string, opts: ResultOptions)
/**
* Returns the default string description of an object.
* Required to implement the Promise interface.
*/
get [Symbol.toStringTag](): string
/**
* Returns a `Processor` instance, which will be used
* for CSS transformations.
*/
get processor(): Processor
/**
* Options from the `Processor#process` call.
*/
get opts(): ResultOptions
/**
* Processes input CSS through synchronous plugins, converts `Root`
* to a CSS string and returns `Result#css`.
* Run plugin in async way and return `Result`.
*
* This property will only work with synchronous plugins.
* If the processor contains any asynchronous plugins
* it will throw an error.
*
* PostCSS runners should always use `LazyResult#then`.
* @return Result with output content.
*/
get css(): string
async(): Promise<Result<RootNode>>
/**
* An alias for the `css` property. Use it with syntaxes
@@ -107,6 +93,18 @@ export default class LazyResult implements PromiseLike<Result> {
*/
get content(): string
/**
* Processes input CSS through synchronous plugins, converts `Root`
* to a CSS string and returns `Result#css`.
*
* This property will only work with synchronous plugins.
* If the processor contains any asynchronous plugins
* it will throw an error.
*
* PostCSS runners should always use `LazyResult#then`.
*/
get css(): string
/**
* Processes input CSS through synchronous plugins
* and returns `Result#map`.
@@ -119,17 +117,6 @@ export default class LazyResult implements PromiseLike<Result> {
*/
get map(): SourceMap
/**
* Processes input CSS through synchronous plugins
* and returns `Result#root`.
*
* This property will only work with synchronous plugins. If the processor
* contains any asynchronous plugins it will throw an error.
*
* PostCSS runners should always use `LazyResult#then`.
*/
get root(): Root
/**
* Processes input CSS through synchronous plugins
* and returns `Result#messages`.
@@ -142,12 +129,39 @@ export default class LazyResult implements PromiseLike<Result> {
get messages(): Message[]
/**
* Processes input CSS through synchronous plugins
* and calls `Result#warnings`.
*
* @return Warnings from plugins.
* Options from the `Processor#process` call.
*/
warnings(): Warning[]
get opts(): ResultOptions
/**
* Returns a `Processor` instance, which will be used
* for CSS transformations.
*/
get processor(): Processor
/**
* Processes input CSS through synchronous plugins
* and returns `Result#root`.
*
* This property will only work with synchronous plugins. If the processor
* contains any asynchronous plugins it will throw an error.
*
* PostCSS runners should always use `LazyResult#then`.
*/
get root(): RootNode
/**
* Returns the default string description of an object.
* Required to implement the Promise interface.
*/
get [Symbol.toStringTag](): string
/**
* Run plugin in sync way and return `Result`.
*
* @return Result with output content.
*/
sync(): Result<RootNode>
/**
* Alias for the `LazyResult#css` property.
@@ -161,16 +175,16 @@ export default class LazyResult implements PromiseLike<Result> {
toString(): string
/**
* Run plugin in sync way and return `Result`.
* Processes input CSS through synchronous plugins
* and calls `Result#warnings`.
*
* @return Result with output content.
* @return Warnings from plugins.
*/
sync(): Result
/**
* Run plugin in async way and return `Result`.
*
* @return Result with output content.
*/
async(): Promise<Result>
warnings(): Warning[]
}
declare class LazyResult<
RootNode = Document | Root
> extends LazyResult_<RootNode> {}
export = LazyResult

View File

@@ -11,37 +11,37 @@ let parse = require('./parse')
let Root = require('./root')
const TYPE_TO_CLASS_NAME = {
atrule: 'AtRule',
comment: 'Comment',
decl: 'Declaration',
document: 'Document',
root: 'Root',
atrule: 'AtRule',
rule: 'Rule',
decl: 'Declaration',
comment: 'Comment'
rule: 'Rule'
}
const PLUGIN_PROPS = {
AtRule: true,
AtRuleExit: true,
Comment: true,
CommentExit: true,
Declaration: true,
DeclarationExit: true,
Document: true,
DocumentExit: true,
Once: true,
OnceExit: true,
postcssPlugin: true,
prepare: true,
Once: true,
Document: true,
Root: true,
Declaration: true,
Rule: true,
AtRule: true,
Comment: true,
DeclarationExit: true,
RuleExit: true,
AtRuleExit: true,
CommentExit: true,
RootExit: true,
DocumentExit: true,
OnceExit: true
Rule: true,
RuleExit: true
}
const NOT_VISITORS = {
Once: true,
postcssPlugin: true,
prepare: true,
Once: true
prepare: true
}
const CHILDREN = 0
@@ -87,12 +87,12 @@ function toStack(node) {
}
return {
node,
events,
eventIndex: 0,
visitors: [],
events,
iterator: 0,
node,
visitorIndex: 0,
iterator: 0
visitors: []
}
}
@@ -143,7 +143,7 @@ class LazyResult {
}
this.result = new Result(processor, root, opts)
this.helpers = { ...postcss, result: this.result, postcss }
this.helpers = { ...postcss, postcss, result: this.result }
this.plugins = this.processor.plugins.map(plugin => {
if (typeof plugin === 'object' && plugin.prepare) {
return { ...plugin, ...plugin.prepare(this.result) }
@@ -153,67 +153,6 @@ class LazyResult {
})
}
get [Symbol.toStringTag]() {
return 'LazyResult'
}
get processor() {
return this.result.processor
}
get opts() {
return this.result.opts
}
get css() {
return this.stringify().css
}
get content() {
return this.stringify().content
}
get map() {
return this.stringify().map
}
get root() {
return this.sync().root
}
get messages() {
return this.sync().messages
}
warnings() {
return this.sync().warnings()
}
toString() {
return this.css
}
then(onFulfilled, onRejected) {
if (process.env.NODE_ENV !== 'production') {
if (!('from' in this.opts)) {
warnOnce(
'Without `from` option PostCSS could generate wrong source map ' +
'and will not find Browserslist config. Set it to CSS file path ' +
'or to `undefined` to prevent this warning.'
)
}
}
return this.async().then(onFulfilled, onRejected)
}
catch(onRejected) {
return this.async().catch(onRejected)
}
finally(onFinally) {
return this.async().then(onFinally, onFinally)
}
async() {
if (this.error) return Promise.reject(this.error)
if (this.processed) return Promise.resolve(this.result)
@@ -223,124 +162,20 @@ class LazyResult {
return this.processing
}
sync() {
if (this.error) throw this.error
if (this.processed) return this.result
this.processed = true
if (this.processing) {
throw this.getAsyncError()
}
for (let plugin of this.plugins) {
let promise = this.runOnRoot(plugin)
if (isPromise(promise)) {
throw this.getAsyncError()
}
}
this.prepareVisitors()
if (this.hasListener) {
let root = this.result.root
while (!root[isClean]) {
root[isClean] = true
this.walkSync(root)
}
if (this.listeners.OnceExit) {
if (root.type === 'document') {
for (let subRoot of root.nodes) {
this.visitSync(this.listeners.OnceExit, subRoot)
}
} else {
this.visitSync(this.listeners.OnceExit, root)
}
}
}
return this.result
catch(onRejected) {
return this.async().catch(onRejected)
}
stringify() {
if (this.error) throw this.error
if (this.stringified) return this.result
this.stringified = true
this.sync()
let opts = this.result.opts
let str = stringify
if (opts.syntax) str = opts.syntax.stringify
if (opts.stringifier) str = opts.stringifier
if (str.stringify) str = str.stringify
let map = new MapGenerator(str, this.result.root, this.result.opts)
let data = map.generate()
this.result.css = data[0]
this.result.map = data[1]
return this.result
get content() {
return this.stringify().content
}
walkSync(node) {
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)
})
}
} else {
let visitors = this.listeners[event]
if (visitors) {
if (this.visitSync(visitors, node.toProxy())) return
}
}
}
get css() {
return this.stringify().css
}
visitSync(visitors, node) {
for (let [plugin, visitor] of visitors) {
this.result.lastPlugin = plugin
let promise
try {
promise = visitor(node, this.helpers)
} catch (e) {
throw this.handleError(e, node.proxyOf)
}
if (node.type !== 'root' && node.type !== 'document' && !node.parent) {
return true
}
if (isPromise(promise)) {
throw this.getAsyncError()
}
}
}
runOnRoot(plugin) {
this.result.lastPlugin = plugin
try {
if (typeof plugin === 'object' && plugin.Once) {
if (this.result.root.type === 'document') {
let roots = this.result.root.nodes.map(root =>
plugin.Once(root, this.helpers)
)
if (isPromise(roots[0])) {
return Promise.all(roots)
}
return roots
}
return plugin.Once(this.result.root, this.helpers)
} else if (typeof plugin === 'function') {
return plugin(this.result.root, this.result)
}
} catch (error) {
throw this.handleError(error)
}
finally(onFinally) {
return this.async().then(onFinally, onFinally)
}
getAsyncError() {
@@ -386,6 +221,64 @@ class LazyResult {
return error
}
get map() {
return this.stringify().map
}
get messages() {
return this.sync().messages
}
get opts() {
return this.result.opts
}
prepareVisitors() {
this.listeners = {}
let add = (plugin, type, cb) => {
if (!this.listeners[type]) this.listeners[type] = []
this.listeners[type].push([plugin, cb])
}
for (let plugin of this.plugins) {
if (typeof plugin === 'object') {
for (let event in plugin) {
if (!PLUGIN_PROPS[event] && /^[A-Z]/.test(event)) {
throw new Error(
`Unknown event ${event} in ${plugin.postcssPlugin}. ` +
`Try to update PostCSS (${this.processor.version} now).`
)
}
if (!NOT_VISITORS[event]) {
if (typeof plugin[event] === 'object') {
for (let filter in plugin[event]) {
if (filter === '*') {
add(plugin, event, plugin[event][filter])
} else {
add(
plugin,
event + '-' + filter.toLowerCase(),
plugin[event][filter]
)
}
}
} else if (typeof plugin[event] === 'function') {
add(plugin, event, plugin[event])
}
}
}
}
}
this.hasListener = Object.keys(this.listeners).length > 0
}
get processor() {
return this.result.processor
}
get root() {
return this.sync().root
}
async runAsync() {
this.plugin = 0
for (let i = 0; i < this.plugins.length; i++) {
@@ -443,42 +336,126 @@ class LazyResult {
return this.stringify()
}
prepareVisitors() {
this.listeners = {}
let add = (plugin, type, cb) => {
if (!this.listeners[type]) this.listeners[type] = []
this.listeners[type].push([plugin, cb])
runOnRoot(plugin) {
this.result.lastPlugin = plugin
try {
if (typeof plugin === 'object' && plugin.Once) {
if (this.result.root.type === 'document') {
let roots = this.result.root.nodes.map(root =>
plugin.Once(root, this.helpers)
)
if (isPromise(roots[0])) {
return Promise.all(roots)
}
return roots
}
return plugin.Once(this.result.root, this.helpers)
} else if (typeof plugin === 'function') {
return plugin(this.result.root, this.result)
}
} catch (error) {
throw this.handleError(error)
}
}
stringify() {
if (this.error) throw this.error
if (this.stringified) return this.result
this.stringified = true
this.sync()
let opts = this.result.opts
let str = stringify
if (opts.syntax) str = opts.syntax.stringify
if (opts.stringifier) str = opts.stringifier
if (str.stringify) str = str.stringify
let map = new MapGenerator(str, this.result.root, this.result.opts)
let data = map.generate()
this.result.css = data[0]
this.result.map = data[1]
return this.result
}
get [Symbol.toStringTag]() {
return 'LazyResult'
}
sync() {
if (this.error) throw this.error
if (this.processed) return this.result
this.processed = true
if (this.processing) {
throw this.getAsyncError()
}
for (let plugin of this.plugins) {
if (typeof plugin === 'object') {
for (let event in plugin) {
if (!PLUGIN_PROPS[event] && /^[A-Z]/.test(event)) {
throw new Error(
`Unknown event ${event} in ${plugin.postcssPlugin}. ` +
`Try to update PostCSS (${this.processor.version} now).`
)
}
if (!NOT_VISITORS[event]) {
if (typeof plugin[event] === 'object') {
for (let filter in plugin[event]) {
if (filter === '*') {
add(plugin, event, plugin[event][filter])
} else {
add(
plugin,
event + '-' + filter.toLowerCase(),
plugin[event][filter]
)
}
}
} else if (typeof plugin[event] === 'function') {
add(plugin, event, plugin[event])
}
let promise = this.runOnRoot(plugin)
if (isPromise(promise)) {
throw this.getAsyncError()
}
}
this.prepareVisitors()
if (this.hasListener) {
let root = this.result.root
while (!root[isClean]) {
root[isClean] = true
this.walkSync(root)
}
if (this.listeners.OnceExit) {
if (root.type === 'document') {
for (let subRoot of root.nodes) {
this.visitSync(this.listeners.OnceExit, subRoot)
}
} else {
this.visitSync(this.listeners.OnceExit, root)
}
}
}
this.hasListener = Object.keys(this.listeners).length > 0
return this.result
}
then(onFulfilled, onRejected) {
if (process.env.NODE_ENV !== 'production') {
if (!('from' in this.opts)) {
warnOnce(
'Without `from` option PostCSS could generate wrong source map ' +
'and will not find Browserslist config. Set it to CSS file path ' +
'or to `undefined` to prevent this warning.'
)
}
}
return this.async().then(onFulfilled, onRejected)
}
toString() {
return this.css
}
visitSync(visitors, node) {
for (let [plugin, visitor] of visitors) {
this.result.lastPlugin = plugin
let promise
try {
promise = visitor(node, this.helpers)
} catch (e) {
throw this.handleError(e, node.proxyOf)
}
if (node.type !== 'root' && node.type !== 'document' && !node.parent) {
return true
}
if (isPromise(promise)) {
throw this.getAsyncError()
}
}
}
visitTick(stack) {
@@ -537,6 +514,29 @@ class LazyResult {
}
stack.pop()
}
walkSync(node) {
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)
})
}
} else {
let visitors = this.listeners[event]
if (visitors) {
if (this.visitSync(visitors, node.toProxy())) return
}
}
}
}
warnings() {
return this.sync().warnings()
}
}
LazyResult.registerPostcss = dependant => {

View File

@@ -1,51 +1,57 @@
export type List = {
/**
* Safely splits values.
*
* ```js
* Once (root, { list }) {
* list.split('1px calc(10% + 1px)', [' ', '\n', '\t']) //=> ['1px', 'calc(10% + 1px)']
* }
* ```
*
* @param string separated values.
* @param separators array of separators.
* @param last boolean indicator.
* @return Split values.
*/
split(string: string, separators: string[], last: boolean): string[]
/**
* Safely splits space-separated values (such as those for `background`,
* `border-radius`, and other shorthand properties).
*
* ```js
* Once (root, { list }) {
* list.space('1px calc(10% + 1px)') //=> ['1px', 'calc(10% + 1px)']
* }
* ```
*
* @param str Space-separated values.
* @return Split values.
*/
space(str: string): string[]
declare namespace list {
type List = {
/**
* Safely splits comma-separated values (such as those for `transition-*`
* and `background` properties).
*
* ```js
* Once (root, { list }) {
* list.comma('black, linear-gradient(white, black)')
* //=> ['black', 'linear-gradient(white, black)']
* }
* ```
*
* @param str Comma-separated values.
* @return Split values.
*/
comma(str: string): string[]
/**
* Safely splits comma-separated values (such as those for `transition-*`
* and `background` properties).
*
* ```js
* Once (root, { list }) {
* list.comma('black, linear-gradient(white, black)')
* //=> ['black', 'linear-gradient(white, black)']
* }
* ```
*
* @param str Comma-separated values.
* @return Split values.
*/
comma(str: string): string[]
default: List
/**
* Safely splits space-separated values (such as those for `background`,
* `border-radius`, and other shorthand properties).
*
* ```js
* Once (root, { list }) {
* list.space('1px calc(10% + 1px)') //=> ['1px', 'calc(10% + 1px)']
* }
* ```
*
* @param str Space-separated values.
* @return Split values.
*/
space(str: string): string[]
/**
* Safely splits values.
*
* ```js
* Once (root, { list }) {
* list.split('1px calc(10% + 1px)', [' ', '\n', '\t']) //=> ['1px', 'calc(10% + 1px)']
* }
* ```
*
* @param string separated values.
* @param separators array of separators.
* @param last boolean indicator.
* @return Split values.
*/
split(string: string, separators: string[], last: boolean): string[]
}
}
declare const list: List
// eslint-disable-next-line @typescript-eslint/no-redeclare
declare const list: list.List
export default list
export = list

View File

@@ -1,6 +1,15 @@
'use strict'
let list = {
comma(string) {
return list.split(string, [','], true)
},
space(string) {
let spaces = [' ', '\n', '\t']
return list.split(string, spaces)
},
split(string, separators, last) {
let array = []
let current = ''
@@ -42,15 +51,6 @@ let list = {
if (last || current !== '') array.push(current.trim())
return array
},
space(string) {
let spaces = [' ', '\n', '\t']
return list.split(string, spaces)
},
comma(string) {
return list.split(string, [','], true)
}
}

View File

@@ -1,7 +1,7 @@
'use strict'
let { SourceMapConsumer, SourceMapGenerator } = require('source-map-js')
let { dirname, resolve, relative, sep } = require('path')
let { dirname, relative, resolve, sep } = require('path')
let { pathToFileURL } = require('url')
let Input = require('./input')
@@ -19,140 +19,6 @@ class MapGenerator {
this.usesFileUrls = !this.mapOpts.from && this.mapOpts.absolute
}
isMap() {
if (typeof this.opts.map !== 'undefined') {
return !!this.opts.map
}
return this.previous().length > 0
}
previous() {
if (!this.previousMaps) {
this.previousMaps = []
if (this.root) {
this.root.walk(node => {
if (node.source && node.source.input.map) {
let map = node.source.input.map
if (!this.previousMaps.includes(map)) {
this.previousMaps.push(map)
}
}
})
} else {
let input = new Input(this.css, this.opts)
if (input.map) this.previousMaps.push(input.map)
}
}
return this.previousMaps
}
isInline() {
if (typeof this.mapOpts.inline !== 'undefined') {
return this.mapOpts.inline
}
let annotation = this.mapOpts.annotation
if (typeof annotation !== 'undefined' && annotation !== true) {
return false
}
if (this.previous().length) {
return this.previous().some(i => i.inline)
}
return true
}
isSourcesContent() {
if (typeof this.mapOpts.sourcesContent !== 'undefined') {
return this.mapOpts.sourcesContent
}
if (this.previous().length) {
return this.previous().some(i => i.withContent())
}
return true
}
clearAnnotation() {
if (this.mapOpts.annotation === false) return
if (this.root) {
let node
for (let i = this.root.nodes.length - 1; i >= 0; i--) {
node = this.root.nodes[i]
if (node.type !== 'comment') continue
if (node.text.indexOf('# sourceMappingURL=') === 0) {
this.root.removeChild(i)
}
}
} else if (this.css) {
this.css = this.css.replace(/(\n)?\/\*#[\S\s]*?\*\/$/gm, '')
}
}
setSourcesContent() {
let already = {}
if (this.root) {
this.root.walk(node => {
if (node.source) {
let from = node.source.input.from
if (from && !already[from]) {
already[from] = true
let fromUrl = this.usesFileUrls
? this.toFileUrl(from)
: this.toUrl(this.path(from))
this.map.setSourceContent(fromUrl, node.source.input.css)
}
}
})
} else if (this.css) {
let from = this.opts.from
? this.toUrl(this.path(this.opts.from))
: '<no source>'
this.map.setSourceContent(from, this.css)
}
}
applyPrevMaps() {
for (let prev of this.previous()) {
let from = this.toUrl(this.path(prev.file))
let root = prev.root || dirname(prev.file)
let map
if (this.mapOpts.sourcesContent === false) {
map = new SourceMapConsumer(prev.text)
if (map.sourcesContent) {
map.sourcesContent = map.sourcesContent.map(() => null)
}
} else {
map = prev.consumer()
}
this.map.applySourceMap(map, from, this.toUrl(this.path(root)))
}
}
isAnnotation() {
if (this.isInline()) {
return true
}
if (typeof this.mapOpts.annotation !== 'undefined') {
return this.mapOpts.annotation
}
if (this.previous().length) {
return this.previous().some(i => i.annotation)
}
return true
}
toBase64(str) {
if (Buffer) {
return Buffer.from(str).toString('base64')
} else {
return window.btoa(unescape(encodeURIComponent(str)))
}
}
addAnnotation() {
let content
@@ -172,13 +38,52 @@ class MapGenerator {
this.css += eol + '/*# sourceMappingURL=' + content + ' */'
}
outputFile() {
if (this.opts.to) {
return this.path(this.opts.to)
} else if (this.opts.from) {
return this.path(this.opts.from)
applyPrevMaps() {
for (let prev of this.previous()) {
let from = this.toUrl(this.path(prev.file))
let root = prev.root || dirname(prev.file)
let map
if (this.mapOpts.sourcesContent === false) {
map = new SourceMapConsumer(prev.text)
if (map.sourcesContent) {
map.sourcesContent = map.sourcesContent.map(() => null)
}
} else {
map = prev.consumer()
}
this.map.applySourceMap(map, from, this.toUrl(this.path(root)))
}
}
clearAnnotation() {
if (this.mapOpts.annotation === false) return
if (this.root) {
let node
for (let i = this.root.nodes.length - 1; i >= 0; i--) {
node = this.root.nodes[i]
if (node.type !== 'comment') continue
if (node.text.indexOf('# sourceMappingURL=') === 0) {
this.root.removeChild(i)
}
}
} else if (this.css) {
this.css = this.css.replace(/(\n)?\/\*#[\S\s]*?\*\/$/gm, '')
}
}
generate() {
this.clearAnnotation()
if (pathAvailable && sourceMapAvailable && this.isMap()) {
return this.generateMap()
} else {
return 'to.css'
let result = ''
this.stringify(this.root, i => {
result += i
})
return [result]
}
}
@@ -192,11 +97,11 @@ class MapGenerator {
} else {
this.map = new SourceMapGenerator({ file: this.outputFile() })
this.map.addMapping({
generated: { column: 0, line: 1 },
original: { column: 0, line: 1 },
source: this.opts.from
? this.toUrl(this.path(this.opts.from))
: '<no source>',
generated: { line: 1, column: 0 },
original: { line: 1, column: 0 }
: '<no source>'
})
}
@@ -211,48 +116,6 @@ class MapGenerator {
}
}
path(file) {
if (file.indexOf('<') === 0) return file
if (/^\w+:\/\//.test(file)) return file
if (this.mapOpts.absolute) return file
let from = this.opts.to ? dirname(this.opts.to) : '.'
if (typeof this.mapOpts.annotation === 'string') {
from = dirname(resolve(from, this.mapOpts.annotation))
}
file = relative(from, file)
return file
}
toUrl(path) {
if (sep === '\\') {
path = path.replace(/\\/g, '/')
}
return encodeURI(path).replace(/[#?]/g, encodeURIComponent)
}
toFileUrl(path) {
if (pathToFileURL) {
return pathToFileURL(path).toString()
} else {
throw new Error(
'`map.absolute` option is not available in this PostCSS build'
)
}
}
sourcePath(node) {
if (this.mapOpts.from) {
return this.toUrl(this.mapOpts.from)
} else if (this.usesFileUrls) {
return this.toFileUrl(node.source.input.from)
} else {
return this.toUrl(this.path(node.source.input.from))
}
}
generateString() {
this.css = ''
this.map = new SourceMapGenerator({ file: this.outputFile() })
@@ -262,9 +125,9 @@ class MapGenerator {
let noSource = '<no source>'
let mapping = {
source: '',
generated: { line: 0, column: 0 },
original: { line: 0, column: 0 }
generated: { column: 0, line: 0 },
original: { column: 0, line: 0 },
source: ''
}
let lines, last
@@ -321,17 +184,154 @@ class MapGenerator {
})
}
generate() {
this.clearAnnotation()
if (pathAvailable && sourceMapAvailable && this.isMap()) {
return this.generateMap()
} else {
let result = ''
this.stringify(this.root, i => {
result += i
})
return [result]
isAnnotation() {
if (this.isInline()) {
return true
}
if (typeof this.mapOpts.annotation !== 'undefined') {
return this.mapOpts.annotation
}
if (this.previous().length) {
return this.previous().some(i => i.annotation)
}
return true
}
isInline() {
if (typeof this.mapOpts.inline !== 'undefined') {
return this.mapOpts.inline
}
let annotation = this.mapOpts.annotation
if (typeof annotation !== 'undefined' && annotation !== true) {
return false
}
if (this.previous().length) {
return this.previous().some(i => i.inline)
}
return true
}
isMap() {
if (typeof this.opts.map !== 'undefined') {
return !!this.opts.map
}
return this.previous().length > 0
}
isSourcesContent() {
if (typeof this.mapOpts.sourcesContent !== 'undefined') {
return this.mapOpts.sourcesContent
}
if (this.previous().length) {
return this.previous().some(i => i.withContent())
}
return true
}
outputFile() {
if (this.opts.to) {
return this.path(this.opts.to)
} else if (this.opts.from) {
return this.path(this.opts.from)
} else {
return 'to.css'
}
}
path(file) {
if (file.indexOf('<') === 0) return file
if (/^\w+:\/\//.test(file)) return file
if (this.mapOpts.absolute) return file
let from = this.opts.to ? dirname(this.opts.to) : '.'
if (typeof this.mapOpts.annotation === 'string') {
from = dirname(resolve(from, this.mapOpts.annotation))
}
file = relative(from, file)
return file
}
previous() {
if (!this.previousMaps) {
this.previousMaps = []
if (this.root) {
this.root.walk(node => {
if (node.source && node.source.input.map) {
let map = node.source.input.map
if (!this.previousMaps.includes(map)) {
this.previousMaps.push(map)
}
}
})
} else {
let input = new Input(this.css, this.opts)
if (input.map) this.previousMaps.push(input.map)
}
}
return this.previousMaps
}
setSourcesContent() {
let already = {}
if (this.root) {
this.root.walk(node => {
if (node.source) {
let from = node.source.input.from
if (from && !already[from]) {
already[from] = true
let fromUrl = this.usesFileUrls
? this.toFileUrl(from)
: this.toUrl(this.path(from))
this.map.setSourceContent(fromUrl, node.source.input.css)
}
}
})
} else if (this.css) {
let from = this.opts.from
? this.toUrl(this.path(this.opts.from))
: '<no source>'
this.map.setSourceContent(from, this.css)
}
}
sourcePath(node) {
if (this.mapOpts.from) {
return this.toUrl(this.mapOpts.from)
} else if (this.usesFileUrls) {
return this.toFileUrl(node.source.input.from)
} else {
return this.toUrl(this.path(node.source.input.from))
}
}
toBase64(str) {
if (Buffer) {
return Buffer.from(str).toString('base64')
} else {
return window.btoa(unescape(encodeURIComponent(str)))
}
}
toFileUrl(path) {
if (pathToFileURL) {
return pathToFileURL(path).toString()
} else {
throw new Error(
'`map.absolute` option is not available in this PostCSS build'
)
}
}
toUrl(path) {
if (sep === '\\') {
path = path.replace(/\\/g, '/')
}
return encodeURI(path).replace(/[#?]/g, encodeURIComponent)
}
}

View File

@@ -1,9 +1,14 @@
import Result, { Message, ResultOptions } from './result.js'
import LazyResult from './lazy-result.js'
import { SourceMap } from './postcss.js'
import Processor from './processor.js'
import Warning from './warning.js'
import Result, { Message, ResultOptions } from './result.js'
import Root from './root.js'
import LazyResult from './lazy-result.js'
import Warning from './warning.js'
declare namespace NoWorkResult {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
export { NoWorkResult_ as default }
}
/**
* A Promise proxy for the result of PostCSS transformations.
@@ -17,21 +22,25 @@ import LazyResult from './lazy-result.js'
* let root = noWorkResult.root // now css is parsed because we accessed the root
* ```
*/
export default class NoWorkResult implements LazyResult {
then: Promise<Result>['then']
catch: Promise<Result>['catch']
finally: Promise<Result>['finally']
declare class NoWorkResult_ implements LazyResult<Root> {
catch: Promise<Result<Root>>['catch']
finally: Promise<Result<Root>>['finally']
then: Promise<Result<Root>>['then']
constructor(processor: Processor, css: string, opts: ResultOptions)
get [Symbol.toStringTag](): string
get processor(): Processor
get opts(): ResultOptions
get css(): string
async(): Promise<Result<Root>>
get content(): string
get css(): string
get map(): SourceMap
get root(): Root
get messages(): Message[]
warnings(): Warning[]
get opts(): ResultOptions
get processor(): Processor
get root(): Root
get [Symbol.toStringTag](): string
sync(): Result<Root>
toString(): string
sync(): Result
async(): Promise<Result>
warnings(): Warning[]
}
declare class NoWorkResult extends NoWorkResult_ {}
export = NoWorkResult

View File

@@ -40,30 +40,43 @@ class NoWorkResult {
}
}
get [Symbol.toStringTag]() {
return 'NoWorkResult'
async() {
if (this.error) return Promise.reject(this.error)
return Promise.resolve(this.result)
}
get processor() {
return this.result.processor
}
get opts() {
return this.result.opts
}
get css() {
return this.result.css
catch(onRejected) {
return this.async().catch(onRejected)
}
get content() {
return this.result.css
}
get css() {
return this.result.css
}
finally(onFinally) {
return this.async().then(onFinally, onFinally)
}
get map() {
return this.result.map
}
get messages() {
return []
}
get opts() {
return this.result.opts
}
get processor() {
return this.result.processor
}
get root() {
if (this._root) {
return this._root
@@ -86,16 +99,13 @@ class NoWorkResult {
}
}
get messages() {
return []
get [Symbol.toStringTag]() {
return 'NoWorkResult'
}
warnings() {
return []
}
toString() {
return this._css
sync() {
if (this.error) throw this.error
return this.result
}
then(onFulfilled, onRejected) {
@@ -112,22 +122,12 @@ class NoWorkResult {
return this.async().then(onFulfilled, onRejected)
}
catch(onRejected) {
return this.async().catch(onRejected)
toString() {
return this._css
}
finally(onFinally) {
return this.async().then(onFinally, onFinally)
}
async() {
if (this.error) return Promise.reject(this.error)
return Promise.resolve(this.result)
}
sync() {
if (this.error) throw this.error
return this.result
warnings() {
return []
}
}

View File

@@ -1,166 +1,147 @@
import Declaration, { DeclarationProps } from './declaration.js'
import AtRule = require('./at-rule.js')
import { AtRuleProps } from './at-rule.js'
import Comment, { CommentProps } from './comment.js'
import Container from './container.js'
import CssSyntaxError from './css-syntax-error.js'
import Declaration, { DeclarationProps } from './declaration.js'
import Document from './document.js'
import Input from './input.js'
import { Stringifier, Syntax } from './postcss.js'
import AtRule, { AtRuleProps } from './at-rule.js'
import Result from './result.js'
import Root from './root.js'
import Rule, { RuleProps } from './rule.js'
import Warning, { WarningOptions } from './warning.js'
import CssSyntaxError from './css-syntax-error.js'
import Result from './result.js'
import Input from './input.js'
import Root from './root.js'
import Document from './document.js'
import Container from './container.js'
export type ChildNode = AtRule | Rule | Declaration | Comment
declare namespace Node {
export type ChildNode = AtRule.default | Comment | Declaration | Rule
export type AnyNode = AtRule | Rule | Declaration | Comment | Root | Document
export type AnyNode =
| AtRule.default
| Comment
| Declaration
| Document
| Root
| Rule
export type ChildProps =
| AtRuleProps
| RuleProps
| DeclarationProps
| CommentProps
export type ChildProps =
| AtRuleProps
| CommentProps
| DeclarationProps
| RuleProps
export interface Position {
/**
* Source offset in file. It starts from 0.
*/
offset: number
export interface Position {
/**
* Source line in file. In contrast to `offset` it starts from 1.
*/
column: number
/**
* Source column in file.
*/
line: number
/**
* Source offset in file. It starts from 0.
*/
offset: number
}
export interface Range {
/**
* End position, exclusive.
*/
end: Position
/**
* Start position, inclusive.
*/
start: Position
}
/**
* Source line in file. In contrast to `offset` it starts from 1.
* Source represents an interface for the {@link Node.source} property.
*/
column: number
export interface Source {
/**
* The inclusive ending position for the source
* code of a node.
*/
end?: Position
/**
* The source file from where a node has originated.
*/
input: Input
/**
* The inclusive starting position for the source
* code of a node.
*/
start?: Position
}
/**
* Source column in file.
* Interface represents an interface for an object received
* as parameter by Node class constructor.
*/
line: number
}
export interface NodeProps {
source?: Source
}
export interface Range {
/**
* Start position, inclusive.
*/
start: Position
export interface NodeErrorOptions {
/**
* An ending index inside a node's string that should be highlighted as
* source of error.
*/
endIndex?: number
/**
* An index inside a node's string that should be highlighted as source
* of error.
*/
index?: number
/**
* Plugin name that created this error. PostCSS will set it automatically.
*/
plugin?: string
/**
* A word inside a node's string, that should be highlighted as source
* of error.
*/
word?: string
}
/**
* End position, exclusive.
*/
end: Position
}
export interface Source {
/**
* The file source of the node.
*/
input: Input
/**
* The inclusive starting position of the nodes source.
*/
start?: Position
/**
* The inclusive ending position of the node's source.
*/
end?: Position
}
export interface NodeProps {
source?: Source
}
interface NodeErrorOptions {
/**
* Plugin name that created this error. PostCSS will set it automatically.
*/
plugin?: string
/**
* A word inside a node's string, that should be highlighted as source
* of error.
*/
word?: string
/**
* An index inside a node's string that should be highlighted as source
* of error.
*/
index?: number
/**
* An ending index inside a node's string that should be highlighted as
* source of error.
*/
endIndex?: number
// eslint-disable-next-line @typescript-eslint/no-shadow
class Node extends Node_ {}
export { Node as default }
}
/**
* All node classes inherit the following common methods.
* It represents an abstract class that handles common
* methods for other CSS abstract syntax tree nodes.
*
* You should not extend this classes to create AST for selector or value
* parser.
* Any node that represents CSS selector or value should
* not extend the `Node` class.
*/
export default abstract class Node {
declare abstract class Node_ {
/**
* tring representing the nodes type. Possible values are `root`, `atrule`,
* `rule`, `decl`, or `comment`.
* It represents parent of the current node.
*
* ```js
* new Declaration({ prop: 'color', value: 'black' }).type //=> 'decl'
* root.nodes[0].parent === root //=> true
* ```
*/
type: string
parent: Container | Document | undefined
/**
* The nodes parent node.
* It represents unnecessary whitespace and characters present
* in the css source code.
*
* ```js
* root.nodes[0].parent === root
* ```
*/
parent: Document | Container | undefined
/**
* The input source of the node.
*
* The property is used in source map generation.
*
* If you create a node manually (e.g., with `postcss.decl()`),
* that node will not have a `source` property and will be absent
* from the source map. For this reason, the plugin developer should
* consider cloning nodes to create new ones (in which case the new nodes
* source will reference the original, cloned node) or setting
* the `source` property manually.
*
* ```js
* decl.source.input.from //=> '/home/ai/a.sass'
* decl.source.start //=> { line: 10, column: 2 }
* decl.source.end //=> { line: 10, column: 12 }
* ```
*
* ```js
* // Bad
* const prefixed = postcss.decl({
* prop: '-moz-' + decl.prop,
* value: decl.value
* })
*
* // Good
* const prefixed = decl.clone({ prop: '-moz-' + decl.prop })
* ```
*
* ```js
* if (atrule.name === 'add-link') {
* const rule = postcss.rule({ selector: 'a', source: atrule.source })
* atrule.parent.insertBefore(atrule, rule)
* }
* ```
*/
source?: Source
/**
* Information to generate byte-to-byte equal node string as it was
* in the origin input.
*
* Every parser saves its own properties,
* but the default CSS parser uses:
* The properties of the raws object are decided by parser,
* the default parser uses the following properties:
*
* * `before`: the space symbols before the node. It also stores `*`
* and `_` symbols before the declaration (IE hack).
@@ -175,13 +156,11 @@ export default abstract class Node {
* * `left`: the space symbols between `/*` and the comments text.
* * `right`: the space symbols between the comments text
* and <code>*&#47;</code>.
* * `important`: the content of the important statement,
* - `important`: the content of the important statement,
* if it is not just `!important`.
*
* PostCSS cleans selectors, declaration values and at-rule parameters
* from comments and extra spaces, but it stores origin content in raws
* properties. As such, if you dont change a declarations value,
* PostCSS will use the raw value with comments.
* PostCSS filters out the comments inside selectors, declaration values
* and at-rule parameters but it stores the origin content in raws.
*
* ```js
* const root = postcss.parse('a {\n color:black\n}')
@@ -191,101 +170,127 @@ export default abstract class Node {
raws: any
/**
* @param defaults Value for node properties.
* It represents information related to origin of a node and is required
* for generating source maps.
*
* The nodes that are created manually using the public APIs
* provided by PostCSS will have `source` undefined and
* will be absent in the source map.
*
* For this reason, the plugin developer should consider
* duplicating nodes as the duplicate node will have the
* same source as the original node by default or assign
* source to a node created manually.
*
* ```js
* decl.source.input.from //=> '/home/ai/source.css'
* decl.source.start //=> { line: 10, column: 2 }
* decl.source.end //=> { line: 10, column: 12 }
* ```
*
* ```js
* // Incorrect method, source not specified!
* const prefixed = postcss.decl({
* prop: '-moz-' + decl.prop,
* value: decl.value
* })
*
* // Correct method, source is inherited when duplicating.
* const prefixed = decl.clone({
* prop: '-moz-' + decl.prop
* })
* ```
*
* ```js
* if (atrule.name === 'add-link') {
* const rule = postcss.rule({
* selector: 'a',
* source: atrule.source
* })
*
* atrule.parent.insertBefore(atrule, rule)
* }
* ```
*/
source?: Node.Source
/**
* It represents type of a node in
* an abstract syntax tree.
*
* A type of node helps in identification of a node
* and perform operation based on it's type.
*
* ```js
* const declaration = new Declaration({
* prop: 'color',
* value: 'black'
* })
*
* declaration.type //=> 'decl'
* ```
*/
type: string
constructor(defaults?: object)
/**
* Returns a `CssSyntaxError` instance containing the original position
* of the node in the source, showing line and column numbers and also
* a small excerpt to facilitate debugging.
* Insert new node after current node to current nodes parent.
*
* If present, an input source map will be used to get the original position
* of the source, even from a previous compilation step
* (e.g., from Sass compilation).
*
* This method produces very useful error messages.
* Just alias for `node.parent.insertAfter(node, add)`.
*
* ```js
* if (!variables[name]) {
* throw decl.error(`Unknown variable ${name}`, { word: name })
* // CssSyntaxError: postcss-vars:a.sass:4:3: Unknown variable $black
* // color: $black
* // a
* // ^
* // background: white
* }
* decl.after('color: black')
* ```
*
* @param message Error description.
* @param opts Options.
*
* @return Error object to throw it.
* @param newNode New node.
* @return This node for methods chain.
*/
error(message: string, options?: NodeErrorOptions): CssSyntaxError
after(newNode: Node | Node.ChildProps | Node[] | string): this
/**
* This method is provided as a convenience wrapper for `Result#warn`.
*
* ```js
* Declaration: {
* bad: (decl, { result }) => {
* decl.warn(result, 'Deprecated property bad')
* }
* }
* ```
*
* @param result The `Result` instance that will receive the warning.
* @param text Warning message.
* @param opts Warning Options.
*
* @return Created warning object.
*/
warn(result: Result, text: string, opts?: WarningOptions): Warning
/**
* Removes the node from its parent and cleans the parent properties
* from the node and its children.
*
* ```js
* if (decl.prop.match(/^-webkit-/)) {
* decl.remove()
* }
* ```
*
* @return Node to make calls chain.
*/
remove(): this
/**
* Returns a CSS string representing the node.
*
* ```js
* new Rule({ selector: 'a' }).toString() //=> "a {}"
* ```
*
* @param stringifier A syntax to use in string generation.
* @return CSS string of this node.
*/
toString(stringifier?: Stringifier | Syntax): string
/**
* Assigns properties to the current node.
* It assigns properties to an existing node instance.
*
* ```js
* decl.assign({ prop: 'word-wrap', value: 'break-word' })
* ```
*
* @param overrides New properties to override the node.
* @return Current node to methods chain.
*
* @return `this` for method chaining.
*/
assign(overrides: object): this
/**
* Returns an exact clone of the node.
* Insert new node before current node to current nodes parent.
*
* The resulting cloned node and its (cloned) children will retain
* code style properties.
* Just alias for `node.parent.insertBefore(node, add)`.
*
* ```js
* decl.before('content: ""')
* ```
*
* @param newNode New node.
* @return This node for methods chain.
*/
before(newNode: Node | Node.ChildProps | Node[] | string): this
/**
* Clear the code style properties for the node and its children.
*
* ```js
* node.raws.before //=> ' '
* node.cleanRaws()
* node.raws.before //=> undefined
* ```
*
* @param keepBetween Keep the `raws.between` symbols.
*/
cleanRaws(keepBetween?: boolean): void
/**
* It creates clone of an existing node, which includes all the properties
* and their values, that includes `raws` but not `type`.
*
* ```js
* decl.raws.before //=> "\n "
@@ -295,9 +300,19 @@ export default abstract class Node {
* ```
*
* @param overrides New properties to override in the clone.
* @return Clone of the node.
*
* @return Duplicate of the node instance.
*/
clone(overrides?: object): this
clone(overrides?: object): Node
/**
* Shortcut to clone the node and insert the resulting cloned node
* after the current node.
*
* @param overrides New properties to override in the clone.
* @return New node.
*/
cloneAfter(overrides?: object): Node
/**
* Shortcut to clone the node and insert the resulting cloned node
@@ -311,34 +326,43 @@ export default abstract class Node {
*
* @return New node
*/
cloneBefore(overrides?: object): this
cloneBefore(overrides?: object): Node
/**
* Shortcut to clone the node and insert the resulting cloned node
* after the current node.
* It creates an instance of the class `CssSyntaxError` and parameters passed
* to this method are assigned to the error instance.
*
* @param overrides New properties to override in the clone.
* @return New node.
*/
cloneAfter(overrides?: object): this
/**
* Inserts node(s) before the current node and removes the current node.
* The error instance will have description for the
* error, original position of the node in the
* source, showing line and column number.
*
* If any previous map is present, it would be used
* to get original position of the source.
*
* The Previous Map here is referred to the source map
* generated by previous compilation, example: Less,
* Stylus and Sass.
*
* This method returns the error instance instead of
* throwing it.
*
* ```js
* AtRule: {
* mixin: atrule => {
* atrule.replaceWith(mixinRules[atrule.params])
* }
* if (!variables[name]) {
* throw decl.error(`Unknown variable ${name}`, { word: name })
* // CssSyntaxError: postcss-vars:a.sass:4:3: Unknown variable $black
* // color: $black
* // a
* // ^
* // background: white
* }
* ```
*
* @param nodes Mode(s) to replace current one.
* @return Current node to methods chain.
* @param message Description for the error instance.
* @param options Options for the error instance.
*
* @return Error instance is returned.
*/
replaceWith(
...nodes: (ChildNode | ChildProps | ChildNode[] | ChildProps[])[]
): this
error(message: string, options?: Node.NodeErrorOptions): CssSyntaxError
/**
* Returns the next child of the nodes parent.
@@ -355,7 +379,23 @@ export default abstract class Node {
*
* @return Next node.
*/
next(): ChildNode | undefined
next(): Node.ChildNode | undefined
/**
* Get the position for a word or an index inside the node.
*
* @param opts Options.
* @return Position.
*/
positionBy(opts?: Pick<WarningOptions, 'index' | 'word'>): Node.Position
/**
* Convert string index to line/column.
*
* @param index The symbol number in the nodes string.
* @return Symbol position in file.
*/
positionInside(index: number): Node.Position
/**
* Returns the previous child of the nodes parent.
@@ -370,49 +410,21 @@ export default abstract class Node {
*
* @return Previous node.
*/
prev(): ChildNode | undefined
prev(): Node.ChildNode | undefined
/**
* Insert new node before current node to current nodes parent.
* Get the range for a word or start and end index inside the node.
* The start index is inclusive; the end index is exclusive.
*
* Just alias for `node.parent.insertBefore(node, add)`.
*
* ```js
* decl.before('content: ""')
* ```
*
* @param newNode New node.
* @return This node for methods chain.
* @param opts Options.
* @return Range.
*/
before(newNode: Node | ChildProps | string | Node[]): this
rangeBy(
opts?: Pick<WarningOptions, 'endIndex' | 'index' | 'word'>
): Node.Range
/**
* Insert new node after current node to current nodes parent.
*
* Just alias for `node.parent.insertAfter(node, add)`.
*
* ```js
* decl.after('color: black')
* ```
*
* @param newNode New node.
* @return This node for methods chain.
*/
after(newNode: Node | ChildProps | string | Node[]): this
/**
* Finds the Root instance of the nodes tree.
*
* ```js
* root.nodes[0].nodes[0].root() === root
* ```
*
* @return Root parent.
*/
root(): Root
/**
* Returns a `Node#raws` value. If the node is missing
* Returns a `raws` value. If the node is missing
* the code style property (because the node was manually built or cloned),
* PostCSS will try to autodetect the code style property by looking
* at other nodes in the tree.
@@ -432,17 +444,51 @@ export default abstract class Node {
raw(prop: string, defaultType?: string): string
/**
* Clear the code style properties for the node and its children.
* It removes the node from its parent and deletes its parent property.
*
* ```js
* node.raws.before //=> ' '
* node.cleanRaws()
* node.raws.before //=> undefined
* if (decl.prop.match(/^-webkit-/)) {
* decl.remove()
* }
* ```
*
* @param keepBetween Keep the `raws.between` symbols.
* @return `this` for method chaining.
*/
cleanRaws(keepBetween?: boolean): void
remove(): this
/**
* Inserts node(s) before the current node and removes the current node.
*
* ```js
* AtRule: {
* mixin: atrule => {
* atrule.replaceWith(mixinRules[atrule.params])
* }
* }
* ```
*
* @param nodes Mode(s) to replace current one.
* @return Current node to methods chain.
*/
replaceWith(
...nodes: (
| Node.ChildNode
| Node.ChildNode[]
| Node.ChildProps
| Node.ChildProps[]
)[]
): this
/**
* Finds the Root instance of the nodes tree.
*
* ```js
* root.nodes[0].nodes[0].root() === root
* ```
*
* @return Root parent.
*/
root(): Root
/**
* Fix circular links on `JSON.stringify()`.
@@ -452,27 +498,39 @@ export default abstract class Node {
toJSON(): object
/**
* Convert string index to line/column.
* It compiles the node to browser readable cascading style sheets string
* depending on it's type.
*
* @param index The symbol number in the nodes string.
* @return Symbol position in file.
* ```js
* new Rule({ selector: 'a' }).toString() //=> "a {}"
* ```
*
* @param stringifier A syntax to use in string generation.
* @return CSS string of this node.
*/
positionInside(index: number): Position
toString(stringifier?: Stringifier | Syntax): string
/**
* Get the position for a word or an index inside the node.
* It is a wrapper for {@link Result#warn}, providing convenient
* way of generating warnings.
*
* @param opts Options.
* @return Position.
*/
positionBy(opts?: Pick<WarningOptions, 'word' | 'index'>): Position
/**
* Get the range for a word or start and end index inside the node.
* The start index is inclusive; the end index is exclusive.
* ```js
* Declaration: {
* bad: (decl, { result }) => {
* decl.warn(result, 'Deprecated property: bad')
* }
* }
* ```
*
* @param opts Options.
* @return Range.
* @param result The `Result` instance that will receive the warning.
* @param message Description for the warning.
* @param options Options for the warning.
*
* @return `Warning` instance is returned
*/
rangeBy(opts?: Pick<WarningOptions, 'word' | 'index' | 'endIndex'>): Range
warn(result: Result, message: string, options?: WarningOptions): Warning
}
declare class Node extends Node_ { }
export = Node

View File

@@ -54,42 +54,23 @@ class Node {
}
}
error(message, opts = {}) {
if (this.source) {
let { start, end } = this.rangeBy(opts)
return this.source.input.error(
message,
{ line: start.line, column: start.column },
{ line: end.line, column: end.column },
opts
addToError(error) {
error.postcssNode = this
if (error.stack && this.source && /\n\s{4}at /.test(error.stack)) {
let s = this.source
error.stack = error.stack.replace(
/\n\s{4}at /,
`$&${s.input.from}:${s.start.line}:${s.start.column}$&`
)
}
return new CssSyntaxError(message)
return error
}
warn(result, text, opts) {
let data = { node: this }
for (let i in opts) data[i] = opts[i]
return result.warn(text, data)
}
remove() {
if (this.parent) {
this.parent.removeChild(this)
}
this.parent = undefined
after(add) {
this.parent.insertAfter(this, add)
return this
}
toString(stringifier = stringify) {
if (stringifier.stringify) stringifier = stringifier.stringify
let result = ''
stringifier(this, i => {
result += i
})
return result
}
assign(overrides = {}) {
for (let name in overrides) {
this[name] = overrides[name]
@@ -97,6 +78,17 @@ class Node {
return this
}
before(add) {
this.parent.insertBefore(this, add)
return this
}
cleanRaws(keepBetween) {
delete this.raws.before
delete this.raws.after
if (!keepBetween) delete this.raws.between
}
clone(overrides = {}) {
let cloned = cloneNode(this)
for (let name in overrides) {
@@ -105,16 +97,182 @@ class Node {
return cloned
}
cloneAfter(overrides = {}) {
let cloned = this.clone(overrides)
this.parent.insertAfter(this, cloned)
return cloned
}
cloneBefore(overrides = {}) {
let cloned = this.clone(overrides)
this.parent.insertBefore(this, cloned)
return cloned
}
cloneAfter(overrides = {}) {
let cloned = this.clone(overrides)
this.parent.insertAfter(this, cloned)
return cloned
error(message, opts = {}) {
if (this.source) {
let { end, start } = this.rangeBy(opts)
return this.source.input.error(
message,
{ column: start.column, line: start.line },
{ column: end.column, line: end.line },
opts
)
}
return new CssSyntaxError(message)
}
getProxyProcessor() {
return {
get(node, prop) {
if (prop === 'proxyOf') {
return node
} else if (prop === 'root') {
return () => node.root().toProxy()
} else {
return node[prop]
}
},
set(node, prop, value) {
if (node[prop] === value) return true
node[prop] = value
if (
prop === 'prop' ||
prop === 'value' ||
prop === 'name' ||
prop === 'params' ||
prop === 'important' ||
/* c8 ignore next */
prop === 'text'
) {
node.markDirty()
}
return true
}
}
}
markDirty() {
if (this[isClean]) {
this[isClean] = false
let next = this
while ((next = next.parent)) {
next[isClean] = false
}
}
}
next() {
if (!this.parent) return undefined
let index = this.parent.index(this)
return this.parent.nodes[index + 1]
}
positionBy(opts, stringRepresentation) {
let pos = this.source.start
if (opts.index) {
pos = this.positionInside(opts.index, stringRepresentation)
} else if (opts.word) {
stringRepresentation = this.toString()
let index = stringRepresentation.indexOf(opts.word)
if (index !== -1) pos = this.positionInside(index, stringRepresentation)
}
return pos
}
positionInside(index, stringRepresentation) {
let string = stringRepresentation || this.toString()
let column = this.source.start.column
let line = this.source.start.line
for (let i = 0; i < index; i++) {
if (string[i] === '\n') {
column = 1
line += 1
} else {
column += 1
}
}
return { column, line }
}
prev() {
if (!this.parent) return undefined
let index = this.parent.index(this)
return this.parent.nodes[index - 1]
}
get proxyOf() {
return this
}
rangeBy(opts) {
let start = {
column: this.source.start.column,
line: this.source.start.line
}
let end = this.source.end
? {
column: this.source.end.column + 1,
line: this.source.end.line
}
: {
column: start.column + 1,
line: start.line
}
if (opts.word) {
let stringRepresentation = this.toString()
let index = stringRepresentation.indexOf(opts.word)
if (index !== -1) {
start = this.positionInside(index, stringRepresentation)
end = this.positionInside(index + opts.word.length, stringRepresentation)
}
} else {
if (opts.start) {
start = {
column: opts.start.column,
line: opts.start.line
}
} else if (opts.index) {
start = this.positionInside(opts.index)
}
if (opts.end) {
end = {
column: opts.end.column,
line: opts.end.line
}
} else if (opts.endIndex) {
end = this.positionInside(opts.endIndex)
} else if (opts.index) {
end = this.positionInside(opts.index + 1)
}
}
if (
end.line < start.line ||
(end.line === start.line && end.column <= start.column)
) {
end = { column: start.column + 1, line: start.line }
}
return { end, start }
}
raw(prop, defaultType) {
let str = new Stringifier()
return str.raw(this, prop, defaultType)
}
remove() {
if (this.parent) {
this.parent.removeChild(this)
}
this.parent = undefined
return this
}
replaceWith(...nodes) {
@@ -140,28 +298,6 @@ class Node {
return this
}
next() {
if (!this.parent) return undefined
let index = this.parent.index(this)
return this.parent.nodes[index + 1]
}
prev() {
if (!this.parent) return undefined
let index = this.parent.index(this)
return this.parent.nodes[index - 1]
}
before(add) {
this.parent.insertBefore(this, add)
return this
}
after(add) {
this.parent.insertAfter(this, add)
return this
}
root() {
let result = this
while (result.parent && result.parent.type !== 'document') {
@@ -170,17 +306,6 @@ class Node {
return result
}
raw(prop, defaultType) {
let str = new Stringifier()
return str.raw(this, prop, defaultType)
}
cleanRaws(keepBetween) {
delete this.raws.before
delete this.raws.after
if (!keepBetween) delete this.raws.between
}
toJSON(_, inputs) {
let fixed = {}
let emitInputs = inputs == null
@@ -213,9 +338,9 @@ class Node {
inputsNextIndex++
}
fixed[name] = {
end: value.end,
inputId,
start: value.start,
end: value.end
start: value.start
}
} else {
fixed[name] = value
@@ -229,118 +354,6 @@ class Node {
return fixed
}
positionInside(index) {
let string = this.toString()
let column = this.source.start.column
let line = this.source.start.line
for (let i = 0; i < index; i++) {
if (string[i] === '\n') {
column = 1
line += 1
} else {
column += 1
}
}
return { line, column }
}
positionBy(opts) {
let pos = this.source.start
if (opts.index) {
pos = this.positionInside(opts.index)
} else if (opts.word) {
let index = this.toString().indexOf(opts.word)
if (index !== -1) pos = this.positionInside(index)
}
return pos
}
rangeBy(opts) {
let start = {
line: this.source.start.line,
column: this.source.start.column
}
let end = this.source.end
? {
line: this.source.end.line,
column: this.source.end.column + 1
}
: {
line: start.line,
column: start.column + 1
}
if (opts.word) {
let index = this.toString().indexOf(opts.word)
if (index !== -1) {
start = this.positionInside(index)
end = this.positionInside(index + opts.word.length)
}
} else {
if (opts.start) {
start = {
line: opts.start.line,
column: opts.start.column
}
} else if (opts.index) {
start = this.positionInside(opts.index)
}
if (opts.end) {
end = {
line: opts.end.line,
column: opts.end.column
}
} else if (opts.endIndex) {
end = this.positionInside(opts.endIndex)
} else if (opts.index) {
end = this.positionInside(opts.index + 1)
}
}
if (
end.line < start.line ||
(end.line === start.line && end.column <= start.column)
) {
end = { line: start.line, column: start.column + 1 }
}
return { start, end }
}
getProxyProcessor() {
return {
set(node, prop, value) {
if (node[prop] === value) return true
node[prop] = value
if (
prop === 'prop' ||
prop === 'value' ||
prop === 'name' ||
prop === 'params' ||
prop === 'important' ||
/* c8 ignore next */
prop === 'text'
) {
node.markDirty()
}
return true
},
get(node, prop) {
if (prop === 'proxyOf') {
return node
} else if (prop === 'root') {
return () => node.root().toProxy()
} else {
return node[prop]
}
}
}
}
toProxy() {
if (!this.proxyCache) {
this.proxyCache = new Proxy(this, this.getProxyProcessor())
@@ -348,30 +361,19 @@ class Node {
return this.proxyCache
}
addToError(error) {
error.postcssNode = this
if (error.stack && this.source && /\n\s{4}at /.test(error.stack)) {
let s = this.source
error.stack = error.stack.replace(
/\n\s{4}at /,
`$&${s.input.from}:${s.start.line}:${s.start.column}$&`
)
}
return error
toString(stringifier = stringify) {
if (stringifier.stringify) stringifier = stringifier.stringify
let result = ''
stringifier(this, i => {
result += i
})
return result
}
markDirty() {
if (this[isClean]) {
this[isClean] = false
let next = this
while ((next = next.parent)) {
next[isClean] = false
}
}
}
get proxyOf() {
return this
warn(result, text, opts) {
let data = { node: this }
for (let i in opts) data[i] = opts[i]
return result.warn(text, data)
}
}

View File

@@ -1,5 +1,9 @@
import { Parser } from './postcss.js'
declare const parse: Parser
interface Parse extends Parser {
default: Parse
}
export default parse
declare const parse: Parse
export = parse

View File

@@ -31,55 +31,150 @@ class Parser {
this.customProperty = false
this.createTokenizer()
this.root.source = { input, start: { offset: 0, line: 1, column: 1 } }
this.root.source = { input, start: { column: 1, line: 1, offset: 0 } }
}
createTokenizer() {
this.tokenizer = tokenizer(this.input)
}
atrule(token) {
let node = new AtRule()
node.name = token[1].slice(1)
if (node.name === '') {
this.unnamedAtrule(node, token)
}
this.init(node, token[2])
let type
let prev
let shift
let last = false
let open = false
let params = []
let brackets = []
parse() {
let token
while (!this.tokenizer.endOfFile()) {
token = this.tokenizer.nextToken()
type = token[0]
switch (token[0]) {
case 'space':
this.spaces += token[1]
if (type === '(' || type === '[') {
brackets.push(type === '(' ? ')' : ']')
} else if (type === '{' && brackets.length > 0) {
brackets.push('}')
} else if (type === brackets[brackets.length - 1]) {
brackets.pop()
}
if (brackets.length === 0) {
if (type === ';') {
node.source.end = this.getPosition(token[2])
node.source.end.offset++
this.semicolon = true
break
case ';':
this.freeSemicolon(token)
} else if (type === '{') {
open = true
break
case '}':
} else if (type === '}') {
if (params.length > 0) {
shift = params.length - 1
prev = params[shift]
while (prev && prev[0] === 'space') {
prev = params[--shift]
}
if (prev) {
node.source.end = this.getPosition(prev[3] || prev[2])
node.source.end.offset++
}
}
this.end(token)
break
} else {
params.push(token)
}
} else {
params.push(token)
}
case 'comment':
this.comment(token)
break
case 'at-word':
this.atrule(token)
break
case '{':
this.emptyRule(token)
break
default:
this.other(token)
break
if (this.tokenizer.endOfFile()) {
last = true
break
}
}
this.endFile()
node.raws.between = this.spacesAndCommentsFromEnd(params)
if (params.length) {
node.raws.afterName = this.spacesAndCommentsFromStart(params)
this.raw(node, 'params', params)
if (last) {
token = params[params.length - 1]
node.source.end = this.getPosition(token[3] || token[2])
node.source.end.offset++
this.spaces = node.raws.between
node.raws.between = ''
}
} else {
node.raws.afterName = ''
node.params = ''
}
if (open) {
node.nodes = []
this.current = node
}
}
checkMissedSemicolon(tokens) {
let colon = this.colon(tokens)
if (colon === false) return
let founded = 0
let token
for (let j = colon - 1; j >= 0; j--) {
token = tokens[j]
if (token[0] !== 'space') {
founded += 1
if (founded === 2) break
}
}
// If the token is a word, e.g. `!important`, `red` or any other valid property's value.
// Then we need to return the colon after that word token. [3] is the "end" colon of that word.
// And because we need it after that one we do +1 to get the next one.
throw this.input.error(
'Missed semicolon',
token[0] === 'word' ? token[3] + 1 : token[2]
)
}
colon(tokens) {
let brackets = 0
let token, type, prev
for (let [i, element] of tokens.entries()) {
token = element
type = token[0]
if (type === '(') {
brackets += 1
}
if (type === ')') {
brackets -= 1
}
if (brackets === 0 && type === ':') {
if (!prev) {
this.doubleColon(token)
} else if (prev[0] === 'word' && prev[1] === 'progid') {
continue
} else {
return i
}
}
prev = token
}
return false
}
comment(token) {
let node = new Comment()
this.init(node, token[2])
node.source.end = this.getPosition(token[3] || token[2])
node.source.end.offset++
let text = token[1].slice(2, -2)
if (/^\s*$/.test(text)) {
@@ -94,86 +189,8 @@ class Parser {
}
}
emptyRule(token) {
let node = new Rule()
this.init(node, token[2])
node.selector = ''
node.raws.between = ''
this.current = node
}
other(start) {
let end = false
let type = null
let colon = false
let bracket = null
let brackets = []
let customProperty = start[1].startsWith('--')
let tokens = []
let token = start
while (token) {
type = token[0]
tokens.push(token)
if (type === '(' || type === '[') {
if (!bracket) bracket = token
brackets.push(type === '(' ? ')' : ']')
} else if (customProperty && colon && type === '{') {
if (!bracket) bracket = token
brackets.push('}')
} else if (brackets.length === 0) {
if (type === ';') {
if (colon) {
this.decl(tokens, customProperty)
return
} else {
break
}
} else if (type === '{') {
this.rule(tokens)
return
} else if (type === '}') {
this.tokenizer.back(tokens.pop())
end = true
break
} else if (type === ':') {
colon = true
}
} else if (type === brackets[brackets.length - 1]) {
brackets.pop()
if (brackets.length === 0) bracket = null
}
token = this.tokenizer.nextToken()
}
if (this.tokenizer.endOfFile()) end = true
if (brackets.length > 0) this.unclosedBracket(bracket)
if (end && colon) {
if (!customProperty) {
while (tokens.length) {
token = tokens[tokens.length - 1][0]
if (token !== 'space' && token !== 'comment') break
this.tokenizer.back(tokens.pop())
}
}
this.decl(tokens, customProperty)
} else {
this.unknownWord(tokens)
}
}
rule(tokens) {
tokens.pop()
let node = new Rule()
this.init(node, tokens[0][2])
node.raws.between = this.spacesAndCommentsFromEnd(tokens)
this.raw(node, 'selector', tokens)
this.current = node
createTokenizer() {
this.tokenizer = tokenizer(this.input)
}
decl(tokens, customProperty) {
@@ -189,6 +206,7 @@ class Parser {
node.source.end = this.getPosition(
last[3] || last[2] || findLastWithPosition(tokens)
)
node.source.end.offset++
while (tokens[0][0] !== 'word') {
if (tokens.length === 1) this.unknownWord(tokens)
@@ -280,87 +298,20 @@ class Parser {
}
}
atrule(token) {
let node = new AtRule()
node.name = token[1].slice(1)
if (node.name === '') {
this.unnamedAtrule(node, token)
}
doubleColon(token) {
throw this.input.error(
'Double colon',
{ offset: token[2] },
{ offset: token[2] + token[1].length }
)
}
emptyRule(token) {
let node = new Rule()
this.init(node, token[2])
let type
let prev
let shift
let last = false
let open = false
let params = []
let brackets = []
while (!this.tokenizer.endOfFile()) {
token = this.tokenizer.nextToken()
type = token[0]
if (type === '(' || type === '[') {
brackets.push(type === '(' ? ')' : ']')
} else if (type === '{' && brackets.length > 0) {
brackets.push('}')
} else if (type === brackets[brackets.length - 1]) {
brackets.pop()
}
if (brackets.length === 0) {
if (type === ';') {
node.source.end = this.getPosition(token[2])
this.semicolon = true
break
} else if (type === '{') {
open = true
break
} else if (type === '}') {
if (params.length > 0) {
shift = params.length - 1
prev = params[shift]
while (prev && prev[0] === 'space') {
prev = params[--shift]
}
if (prev) {
node.source.end = this.getPosition(prev[3] || prev[2])
}
}
this.end(token)
break
} else {
params.push(token)
}
} else {
params.push(token)
}
if (this.tokenizer.endOfFile()) {
last = true
break
}
}
node.raws.between = this.spacesAndCommentsFromEnd(params)
if (params.length) {
node.raws.afterName = this.spacesAndCommentsFromStart(params)
this.raw(node, 'params', params)
if (last) {
token = params[params.length - 1]
node.source.end = this.getPosition(token[3] || token[2])
this.spaces = node.raws.between
node.raws.between = ''
}
} else {
node.raws.afterName = ''
node.params = ''
}
if (open) {
node.nodes = []
this.current = node
}
node.selector = ''
node.raws.between = ''
this.current = node
}
end(token) {
@@ -374,6 +325,7 @@ class Parser {
if (this.current.parent) {
this.current.source.end = this.getPosition(token[2])
this.current.source.end.offset++
this.current = this.current.parent
} else {
this.unexpectedClose(token)
@@ -386,6 +338,7 @@ class Parser {
this.current.raws.semicolon = this.semicolon
}
this.current.raws.after = (this.current.raws.after || '') + this.spaces
this.root.source.end = this.getPosition(this.tokenizer.position())
}
freeSemicolon(token) {
@@ -404,23 +357,128 @@ class Parser {
getPosition(offset) {
let pos = this.input.fromOffset(offset)
return {
offset,
column: pos.col,
line: pos.line,
column: pos.col
offset
}
}
init(node, offset) {
this.current.push(node)
node.source = {
start: this.getPosition(offset),
input: this.input
input: this.input,
start: this.getPosition(offset)
}
node.raws.before = this.spaces
this.spaces = ''
if (node.type !== 'comment') this.semicolon = false
}
other(start) {
let end = false
let type = null
let colon = false
let bracket = null
let brackets = []
let customProperty = start[1].startsWith('--')
let tokens = []
let token = start
while (token) {
type = token[0]
tokens.push(token)
if (type === '(' || type === '[') {
if (!bracket) bracket = token
brackets.push(type === '(' ? ')' : ']')
} else if (customProperty && colon && type === '{') {
if (!bracket) bracket = token
brackets.push('}')
} else if (brackets.length === 0) {
if (type === ';') {
if (colon) {
this.decl(tokens, customProperty)
return
} else {
break
}
} else if (type === '{') {
this.rule(tokens)
return
} else if (type === '}') {
this.tokenizer.back(tokens.pop())
end = true
break
} else if (type === ':') {
colon = true
}
} else if (type === brackets[brackets.length - 1]) {
brackets.pop()
if (brackets.length === 0) bracket = null
}
token = this.tokenizer.nextToken()
}
if (this.tokenizer.endOfFile()) end = true
if (brackets.length > 0) this.unclosedBracket(bracket)
if (end && colon) {
if (!customProperty) {
while (tokens.length) {
token = tokens[tokens.length - 1][0]
if (token !== 'space' && token !== 'comment') break
this.tokenizer.back(tokens.pop())
}
}
this.decl(tokens, customProperty)
} else {
this.unknownWord(tokens)
}
}
parse() {
let token
while (!this.tokenizer.endOfFile()) {
token = this.tokenizer.nextToken()
switch (token[0]) {
case 'space':
this.spaces += token[1]
break
case ';':
this.freeSemicolon(token)
break
case '}':
this.end(token)
break
case 'comment':
this.comment(token)
break
case 'at-word':
this.atrule(token)
break
case '{':
this.emptyRule(token)
break
default:
this.other(token)
break
}
}
this.endFile()
}
precheckMissedSemicolon(/* tokens */) {
// Hook for Safe Parser
}
raw(node, prop, tokens, customProperty) {
let token, type
let length = tokens.length
@@ -451,11 +509,22 @@ class Parser {
}
if (!clean) {
let raw = tokens.reduce((all, i) => all + i[1], '')
node.raws[prop] = { value, raw }
node.raws[prop] = { raw, value }
}
node[prop] = value
}
rule(tokens) {
tokens.pop()
let node = new Rule()
this.init(node, tokens[0][2])
node.raws.between = this.spacesAndCommentsFromEnd(tokens)
this.raw(node, 'selector', tokens)
this.current = node
}
spacesAndCommentsFromEnd(tokens) {
let lastTokenType
let spaces = ''
@@ -467,6 +536,8 @@ class Parser {
return spaces
}
// Errors
spacesAndCommentsFromStart(tokens) {
let next
let spaces = ''
@@ -498,36 +569,11 @@ class Parser {
return result
}
colon(tokens) {
let brackets = 0
let token, type, prev
for (let [i, element] of tokens.entries()) {
token = element
type = token[0]
if (type === '(') {
brackets += 1
}
if (type === ')') {
brackets -= 1
}
if (brackets === 0 && type === ':') {
if (!prev) {
this.doubleColon(token)
} else if (prev[0] === 'word' && prev[1] === 'progid') {
continue
} else {
return i
}
}
prev = token
}
return false
unclosedBlock() {
let pos = this.current.source.start
throw this.input.error('Unclosed block', pos.line, pos.column)
}
// Errors
unclosedBracket(bracket) {
throw this.input.error(
'Unclosed bracket',
@@ -536,14 +582,6 @@ class Parser {
)
}
unknownWord(tokens) {
throw this.input.error(
'Unknown word',
{ offset: tokens[0][2] },
{ offset: tokens[0][2] + tokens[0][1].length }
)
}
unexpectedClose(token) {
throw this.input.error(
'Unexpected }',
@@ -552,16 +590,11 @@ class Parser {
)
}
unclosedBlock() {
let pos = this.current.source.start
throw this.input.error('Unclosed block', pos.line, pos.column)
}
doubleColon(token) {
unknownWord(tokens) {
throw this.input.error(
'Double colon',
{ offset: token[2] },
{ offset: token[2] + token[1].length }
'Unknown word',
{ offset: tokens[0][2] },
{ offset: tokens[0][2] + tokens[0][1].length }
)
}
@@ -572,32 +605,6 @@ class Parser {
{ offset: token[2] + token[1].length }
)
}
precheckMissedSemicolon(/* tokens */) {
// Hook for Safe Parser
}
checkMissedSemicolon(tokens) {
let colon = this.colon(tokens)
if (colon === false) return
let founded = 0
let token
for (let j = colon - 1; j >= 0; j--) {
token = tokens[j]
if (token[0] !== 'space') {
founded += 1
if (founded === 2) break
}
}
// If the token is a word, e.g. `!important`, `red` or any other valid property's value.
// Then we need to return the colon after that word token. [3] is the "end" colon of that word.
// And because we need it after that one we do +1 to get the next one.
throw this.input.error(
'Missed semicolon',
token[0] === 'word' ? token[3] + 1 : token[2]
)
}
}
module.exports = Parser

View File

@@ -0,0 +1,72 @@
export {
// postcss function / namespace
default,
// Value exports from postcss.mjs
stringify,
fromJSON,
// @ts-expect-error This value exists, but its untyped.
plugin,
parse,
list,
document,
comment,
atRule,
rule,
decl,
root,
CssSyntaxError,
Declaration,
Container,
Processor,
Document,
Comment,
Warning,
AtRule,
Result,
Input,
Rule,
Root,
Node,
// Type-only exports
AcceptedPlugin,
AnyNode,
AtRuleProps,
Builder,
ChildNode,
ChildProps,
CommentProps,
ContainerProps,
DeclarationProps,
DocumentProps,
FilePosition,
Helpers,
JSONHydrator,
Message,
NodeErrorOptions,
NodeProps,
OldPlugin,
Parser,
Plugin,
PluginCreator,
Position,
Postcss,
ProcessOptions,
RootProps,
RuleProps,
Source,
SourceMap,
SourceMapOptions,
Stringifier,
Syntax,
TransformCallback,
Transformer,
WarningOptions,
// This is a class, but its not re-exported. Thats why its exported as type-only here.
type LazyResult,
} from './postcss.js'

View File

@@ -1,87 +1,92 @@
import { SourceMapGenerator, RawSourceMap } from 'source-map-js'
import { RawSourceMap, SourceMapGenerator } from 'source-map-js'
import AtRule, { AtRuleProps } from './at-rule.js'
import Comment, { CommentProps } from './comment.js'
import Container, { ContainerProps } from './container.js'
import CssSyntaxError from './css-syntax-error.js'
import Declaration, { DeclarationProps } from './declaration.js'
import Document, { DocumentProps } from './document.js'
import Input, { FilePosition } from './input.js'
import LazyResult from './lazy-result.js'
import list from './list.js'
import Node, {
Position,
Source,
AnyNode,
ChildNode,
ChildProps,
NodeErrorOptions,
NodeProps,
ChildProps,
AnyNode
Position,
Source
} from './node.js'
import Declaration, { DeclarationProps } from './declaration.js'
import Container, { ContainerProps } from './container.js'
import Document, { DocumentProps } from './document.js'
import Warning, { WarningOptions } from './warning.js'
import Comment, { CommentProps } from './comment.js'
import AtRule, { AtRuleProps } from './at-rule.js'
import Input, { FilePosition } from './input.js'
import Processor from './processor.js'
import Result, { Message } from './result.js'
import Root, { RootProps } from './root.js'
import Rule, { RuleProps } from './rule.js'
import CssSyntaxError from './css-syntax-error.js'
import list, { List } from './list.js'
import LazyResult from './lazy-result.js'
import Processor from './processor.js'
export {
NodeErrorOptions,
DeclarationProps,
CssSyntaxError,
ContainerProps,
WarningOptions,
DocumentProps,
FilePosition,
CommentProps,
AtRuleProps,
Declaration,
ChildProps,
LazyResult,
ChildNode,
NodeProps,
Processor,
RuleProps,
RootProps,
Container,
Position,
Document,
AnyNode,
Warning,
Message,
Comment,
Source,
AtRule,
Result,
Input,
Node,
list,
Rule,
Root
}
export type SourceMap = SourceMapGenerator & {
toJSON(): RawSourceMap
}
export type Helpers = { result: Result; postcss: Postcss } & Postcss
import Warning, { WarningOptions } from './warning.js'
type DocumentProcessor = (
document: Document,
helper: Helpers
helper: postcss.Helpers
) => Promise<void> | void
type RootProcessor = (root: Root, helper: Helpers) => Promise<void> | void
type RootProcessor = (root: Root, helper: postcss.Helpers) => Promise<void> | void
type DeclarationProcessor = (
decl: Declaration,
helper: Helpers
helper: postcss.Helpers
) => Promise<void> | void
type RuleProcessor = (rule: Rule, helper: Helpers) => Promise<void> | void
type AtRuleProcessor = (atRule: AtRule, helper: Helpers) => Promise<void> | void
type RuleProcessor = (rule: Rule, helper: postcss.Helpers) => Promise<void> | void
type AtRuleProcessor = (atRule: AtRule, helper: postcss.Helpers) => Promise<void> | void
type CommentProcessor = (
comment: Comment,
helper: Helpers
helper: postcss.Helpers
) => Promise<void> | void
interface Processors {
/**
* Will be called on all`AtRule` nodes.
*
* Will be called again on node or children changes.
*/
AtRule?: { [name: string]: AtRuleProcessor } | AtRuleProcessor
/**
* Will be called on all `AtRule` nodes, when all children will be processed.
*
* Will be called again on node or children changes.
*/
AtRuleExit?: { [name: string]: AtRuleProcessor } | AtRuleProcessor
/**
* Will be called on all `Comment` nodes.
*
* Will be called again on node or children changes.
*/
Comment?: CommentProcessor
/**
* Will be called on all `Comment` nodes after listeners
* for `Comment` event.
*
* Will be called again on node or children changes.
*/
CommentExit?: CommentProcessor
/**
* Will be called on all `Declaration` nodes after listeners
* for `Declaration` event.
*
* Will be called again on node or children changes.
*/
Declaration?: { [prop: string]: DeclarationProcessor } | DeclarationProcessor
/**
* Will be called on all `Declaration` nodes.
*
* Will be called again on node or children changes.
*/
DeclarationExit?:
| { [prop: string]: DeclarationProcessor }
| DeclarationProcessor
/**
* Will be called on `Document` node.
*
@@ -120,23 +125,6 @@ interface Processors {
*/
RootExit?: RootProcessor
/**
* Will be called on all `Declaration` nodes after listeners
* for `Declaration` event.
*
* Will be called again on node or children changes.
*/
Declaration?: DeclarationProcessor | { [prop: string]: DeclarationProcessor }
/**
* Will be called on all `Declaration` nodes.
*
* Will be called again on node or children changes.
*/
DeclarationExit?:
| DeclarationProcessor
| { [prop: string]: DeclarationProcessor }
/**
* Will be called on all `Rule` nodes.
*
@@ -150,223 +138,212 @@ interface Processors {
* Will be called again on node or children changes.
*/
RuleExit?: RuleProcessor
/**
* Will be called on all`AtRule` nodes.
*
* Will be called again on node or children changes.
*/
AtRule?: AtRuleProcessor | { [name: string]: AtRuleProcessor }
/**
* Will be called on all `AtRule` nodes, when all children will be processed.
*
* Will be called again on node or children changes.
*/
AtRuleExit?: AtRuleProcessor | { [name: string]: AtRuleProcessor }
/**
* Will be called on all `Comment` nodes.
*
* Will be called again on node or children changes.
*/
Comment?: CommentProcessor
/**
* Will be called on all `Comment` nodes after listeners
* for `Comment` event.
*
* Will be called again on node or children changes.
*/
CommentExit?: CommentProcessor
/**
* Will be called when all other listeners processed the document.
*
* This listener will not be called again.
*/
Exit?: RootProcessor
}
export interface Plugin extends Processors {
postcssPlugin: string
prepare?: (result: Result) => Processors
}
declare namespace postcss {
export {
AnyNode,
AtRule,
AtRuleProps,
ChildNode,
ChildProps,
Comment,
CommentProps,
Container,
ContainerProps,
CssSyntaxError,
Declaration,
DeclarationProps,
Document,
DocumentProps,
FilePosition,
Input,
LazyResult,
list,
Message,
Node,
NodeErrorOptions,
NodeProps,
Position,
Processor,
Result,
Root,
RootProps,
Rule,
RuleProps,
Source,
Warning,
WarningOptions
}
export interface PluginCreator<PluginOptions> {
(opts?: PluginOptions): Plugin | Processor
postcss: true
}
export type SourceMap = SourceMapGenerator & {
toJSON(): RawSourceMap
}
export interface Transformer extends TransformCallback {
postcssPlugin: string
postcssVersion: string
}
export type Helpers = { postcss: Postcss; result: Result } & Postcss
export interface TransformCallback {
(root: Root, result: Result): Promise<void> | void
}
export interface Plugin extends Processors {
postcssPlugin: string
prepare?: (result: Result) => Processors
}
export interface OldPlugin<T> extends Transformer {
(opts?: T): Transformer
postcss: Transformer
}
export interface PluginCreator<PluginOptions> {
(opts?: PluginOptions): Plugin | Processor
postcss: true
}
export type AcceptedPlugin =
| Plugin
| PluginCreator<any>
| OldPlugin<any>
| TransformCallback
| {
postcss: TransformCallback | Processor
}
| Processor
export interface Transformer extends TransformCallback {
postcssPlugin: string
postcssVersion: string
}
export interface Parser<RootNode = Root | Document> {
(
css: string | { toString(): string },
opts?: Pick<ProcessOptions, 'map' | 'from'>
): RootNode
}
export interface TransformCallback {
(root: Root, result: Result): Promise<void> | void
}
export interface Builder {
(part: string, node?: AnyNode, type?: 'start' | 'end'): void
}
export interface OldPlugin<T> extends Transformer {
(opts?: T): Transformer
postcss: Transformer
}
export interface Stringifier {
(node: AnyNode, builder: Builder): void
}
export type AcceptedPlugin =
| {
postcss: Processor | TransformCallback
}
| OldPlugin<any>
| Plugin
| PluginCreator<any>
| Processor
| TransformCallback
export interface JSONHydrator {
(data: object[]): Node[]
(data: object): Node
}
export interface Parser<RootNode = Document | Root> {
(
css: { toString(): string } | string,
opts?: Pick<ProcessOptions, 'from' | 'map'>
): RootNode
}
export interface Syntax {
/**
* Function to generate AST by string.
*/
parse?: Parser
export interface Builder {
(part: string, node?: AnyNode, type?: 'end' | 'start'): void
}
/**
* Class to generate string by AST.
*/
stringify?: Stringifier
}
export interface Stringifier {
(node: AnyNode, builder: Builder): void
}
export interface SourceMapOptions {
/**
* Indicates that the source map should be embedded in the output CSS
* as a Base64-encoded comment. By default, it is `true`.
* But if all previous maps are external, not inline, PostCSS will not embed
* the map even if you do not set this option.
*
* If you have an inline source map, the result.map property will be empty,
* as the source map will be contained within the text of `result.css`.
*/
inline?: boolean
export interface JSONHydrator {
(data: object): Node
(data: object[]): Node[]
}
/**
* Source map content from a previous processing step (e.g., Sass).
*
* PostCSS will try to read the previous source map
* automatically (based on comments within the source CSS), but you can use
* this option to identify it manually.
*
* If desired, you can omit the previous map with prev: `false`.
*/
prev?: string | boolean | object | ((file: string) => string)
export interface Syntax<RootNode = Document | Root> {
/**
* Function to generate AST by string.
*/
parse?: Parser<RootNode>
/**
* Indicates that PostCSS should set the origin content (e.g., Sass source)
* of the source map. By default, it is true. But if all previous maps do not
* contain sources content, PostCSS will also leave it out even if you
* do not set this option.
*/
sourcesContent?: boolean
/**
* Class to generate string by AST.
*/
stringify?: Stringifier
}
/**
* Indicates that PostCSS should add annotation comments to the CSS.
* By default, PostCSS will always add a comment with a path
* to the source map. PostCSS will not add annotations to CSS files
* that do not contain any comments.
*
* By default, PostCSS presumes that you want to save the source map as
* `opts.to + '.map'` and will use this path in the annotation comment.
* A different path can be set by providing a string value for annotation.
*
* If you have set `inline: true`, annotation cannot be disabled.
*/
annotation?: string | boolean | ((file: string, root: Root) => string)
export interface SourceMapOptions {
/**
* Use absolute path in generated source map.
*/
absolute?: boolean
/**
* Override `from` in maps sources.
*/
from?: string
/**
* Indicates that PostCSS should add annotation comments to the CSS.
* By default, PostCSS will always add a comment with a path
* to the source map. PostCSS will not add annotations to CSS files
* that do not contain any comments.
*
* By default, PostCSS presumes that you want to save the source map as
* `opts.to + '.map'` and will use this path in the annotation comment.
* A different path can be set by providing a string value for annotation.
*
* If you have set `inline: true`, annotation cannot be disabled.
*/
annotation?: ((file: string, root: Root) => string) | boolean | string
/**
* Use absolute path in generated source map.
*/
absolute?: boolean
}
/**
* Override `from` in maps sources.
*/
from?: string
export interface ProcessOptions {
/**
* The path of the CSS source file. You should always set `from`,
* because it is used in source map generation and syntax error messages.
*/
from?: string
/**
* Indicates that the source map should be embedded in the output CSS
* as a Base64-encoded comment. By default, it is `true`.
* But if all previous maps are external, not inline, PostCSS will not embed
* the map even if you do not set this option.
*
* If you have an inline source map, the result.map property will be empty,
* as the source map will be contained within the text of `result.css`.
*/
inline?: boolean
/**
* The path where you'll put the output CSS file. You should always set `to`
* to generate correct source maps.
*/
to?: string
/**
* Source map content from a previous processing step (e.g., Sass).
*
* PostCSS will try to read the previous source map
* automatically (based on comments within the source CSS), but you can use
* this option to identify it manually.
*
* If desired, you can omit the previous map with prev: `false`.
*/
prev?: ((file: string) => string) | boolean | object | string
/**
* Function to generate AST by string.
*/
parser?: Syntax | Parser
/**
* Indicates that PostCSS should set the origin content (e.g., Sass source)
* of the source map. By default, it is true. But if all previous maps do not
* contain sources content, PostCSS will also leave it out even if you
* do not set this option.
*/
sourcesContent?: boolean
}
/**
* Class to generate string by AST.
*/
stringifier?: Syntax | Stringifier
export interface ProcessOptions<RootNode = Document | Root> {
/**
* The path of the CSS source file. You should always set `from`,
* because it is used in source map generation and syntax error messages.
*/
from?: string
/**
* Object with parse and stringify.
*/
syntax?: Syntax
/**
* Source map options
*/
map?: boolean | SourceMapOptions
/**
* Source map options
*/
map?: SourceMapOptions | boolean
}
/**
* Function to generate AST by string.
*/
parser?: Parser<RootNode> | Syntax<RootNode>
export interface Postcss {
/**
* Create a new `Processor` instance that will apply `plugins`
* as CSS processors.
*
* ```js
* let postcss = require('postcss')
*
* postcss(plugins).process(css, { from, to }).then(result => {
* console.log(result.css)
* })
* ```
*
* @param plugins PostCSS plugins.
* @return Processor to process multiple CSS.
*/
(plugins?: AcceptedPlugin[]): Processor
(...plugins: AcceptedPlugin[]): Processor
/**
* Class to generate string by AST.
*/
stringifier?: Stringifier | Syntax<RootNode>
/**
* Object with parse and stringify.
*/
syntax?: Syntax<RootNode>
/**
* The path where you'll put the output CSS file. You should always set `to`
* to generate correct source maps.
*/
to?: string
}
export type Postcss = typeof postcss
/**
* Default function to convert a node tree into a CSS string.
*/
stringify: Stringifier
export let stringify: Stringifier
/**
* Parses source css and returns a new `Root` or `Document` node,
@@ -379,7 +356,7 @@ export interface Postcss {
* root1.append(root2).toResult().css
* ```
*/
parse: Parser<Root>
export let parse: Parser<Root>
/**
* Rehydrate a JSON AST (from `Node#toJSON`) back into the AST classes.
@@ -390,12 +367,7 @@ export interface Postcss {
* const root2 = postcss.fromJSON(json)
* ```
*/
fromJSON: JSONHydrator
/**
* Contains the `list` module.
*/
list: List
export let fromJSON: JSONHydrator
/**
* Creates a new `Comment` node.
@@ -403,7 +375,7 @@ export interface Postcss {
* @param defaults Properties for the new node.
* @return New comment node
*/
comment(defaults?: CommentProps): Comment
export function comment(defaults?: CommentProps): Comment
/**
* Creates a new `AtRule` node.
@@ -411,7 +383,7 @@ export interface Postcss {
* @param defaults Properties for the new node.
* @return New at-rule node.
*/
atRule(defaults?: AtRuleProps): AtRule
export function atRule(defaults?: AtRuleProps): AtRule
/**
* Creates a new `Declaration` node.
@@ -419,7 +391,7 @@ export interface Postcss {
* @param defaults Properties for the new node.
* @return New declaration node.
*/
decl(defaults?: DeclarationProps): Declaration
export function decl(defaults?: DeclarationProps): Declaration
/**
* Creates a new `Rule` node.
@@ -427,7 +399,7 @@ export interface Postcss {
* @param default Properties for the new node.
* @return New rule node.
*/
rule(defaults?: RuleProps): Rule
export function rule(defaults?: RuleProps): Rule
/**
* Creates a new `Root` node.
@@ -435,7 +407,7 @@ export interface Postcss {
* @param defaults Properties for the new node.
* @return New root node.
*/
root(defaults?: RootProps): Root
export function root(defaults?: RootProps): Root
/**
* Creates a new `Document` node.
@@ -443,31 +415,27 @@ export interface Postcss {
* @param defaults Properties for the new node.
* @return New document node.
*/
document(defaults?: DocumentProps): Document
export function document(defaults?: DocumentProps): Document
CssSyntaxError: typeof CssSyntaxError
Declaration: typeof Declaration
Container: typeof Container
Comment: typeof Comment
Warning: typeof Warning
AtRule: typeof AtRule
Result: typeof Result
Input: typeof Input
Rule: typeof Rule
Root: typeof Root
Node: typeof Node
export { postcss as default }
}
export const stringify: Stringifier
export const parse: Parser<Root>
export const fromJSON: JSONHydrator
/**
* Create a new `Processor` instance that will apply `plugins`
* as CSS processors.
*
* ```js
* let postcss = require('postcss')
*
* postcss(plugins).process(css, { from, to }).then(result => {
* console.log(result.css)
* })
* ```
*
* @param plugins PostCSS plugins.
* @return Processor to process multiple CSS.
*/
declare function postcss(plugins?: postcss.AcceptedPlugin[]): Processor
declare function postcss(...plugins: postcss.AcceptedPlugin[]): Processor
export const comment: Postcss['comment']
export const atRule: Postcss['atRule']
export const decl: Postcss['decl']
export const rule: Postcss['rule']
export const root: Postcss['root']
declare const postcss: Postcss
export default postcss
export = postcss

View File

@@ -2,6 +2,11 @@ import { SourceMapConsumer } from 'source-map-js'
import { ProcessOptions } from './postcss.js'
declare namespace PreviousMap {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
export { PreviousMap_ as default }
}
/**
* Source map information from input CSS.
* For example, source map after Sass compiler.
@@ -14,38 +19,38 @@ import { ProcessOptions } from './postcss.js'
* root.input.map //=> PreviousMap
* ```
*/
export default class PreviousMap {
/**
* Was source map inlined by data-uri to input CSS.
*/
inline: boolean
declare class PreviousMap_ {
/**
* `sourceMappingURL` content.
*/
annotation?: string
/**
* Source map file content.
*/
text?: string
/**
* The directory with source map file, if source map is in separated file.
*/
root?: string
/**
* The CSS source identifier. Contains `Input#file` if the user
* set the `from` option, or `Input#id` if they did not.
*/
file?: string
/**
* Was source map inlined by data-uri to input CSS.
*/
inline: boolean
/**
* Path to source map file.
*/
mapFile?: string
/**
* The directory with source map file, if source map is in separated file.
*/
root?: string
/**
* Source map file content.
*/
text?: string
/**
* @param css Input CSS source.
* @param opts Process options.
@@ -70,3 +75,7 @@ export default class PreviousMap {
*/
withContent(): boolean
}
declare class PreviousMap extends PreviousMap_ {}
export = PreviousMap

View File

@@ -35,36 +35,6 @@ class PreviousMap {
return this.consumerCache
}
withContent() {
return !!(
this.consumer().sourcesContent &&
this.consumer().sourcesContent.length > 0
)
}
startWith(string, start) {
if (!string) return false
return string.substr(0, start.length) === start
}
getAnnotationURL(sourceMapString) {
return sourceMapString.replace(/^\/\*\s*# sourceMappingURL=/, '').trim()
}
loadAnnotation(css) {
let comments = css.match(/\/\*\s*# sourceMappingURL=/gm)
if (!comments) return
// sourceMappingURLs from comments, strings, etc.
let start = css.lastIndexOf(comments.pop())
let end = css.indexOf('*/', start)
if (start > -1 && end > -1) {
// Locate the last sourceMappingURL to avoid pickin
this.annotation = this.getAnnotationURL(css.substring(start, end))
}
}
decodeInline(text) {
let baseCharsetUri = /^data:application\/json;charset=utf-?8;base64,/
let baseUri = /^data:application\/json;base64,/
@@ -83,6 +53,33 @@ class PreviousMap {
throw new Error('Unsupported source map encoding ' + encoding)
}
getAnnotationURL(sourceMapString) {
return sourceMapString.replace(/^\/\*\s*# sourceMappingURL=/, '').trim()
}
isMap(map) {
if (typeof map !== 'object') return false
return (
typeof map.mappings === 'string' ||
typeof map._mappings === 'string' ||
Array.isArray(map.sections)
)
}
loadAnnotation(css) {
let comments = css.match(/\/\*\s*# sourceMappingURL=/gm)
if (!comments) return
// sourceMappingURLs from comments, strings, etc.
let start = css.lastIndexOf(comments.pop())
let end = css.indexOf('*/', start)
if (start > -1 && end > -1) {
// Locate the last sourceMappingURL to avoid pickin
this.annotation = this.getAnnotationURL(css.substring(start, end))
}
}
loadFile(path) {
this.root = dirname(path)
if (existsSync(path)) {
@@ -128,12 +125,15 @@ class PreviousMap {
}
}
isMap(map) {
if (typeof map !== 'object') return false
return (
typeof map.mappings === 'string' ||
typeof map._mappings === 'string' ||
Array.isArray(map.sections)
startWith(string, start) {
if (!string) return false
return string.substr(0, start.length) === start
}
withContent() {
return !!(
this.consumer().sourcesContent &&
this.consumer().sourcesContent.length > 0
)
}
}

View File

@@ -1,14 +1,20 @@
import Document from './document.js'
import LazyResult from './lazy-result.js'
import NoWorkResult from './no-work-result.js'
import {
AcceptedPlugin,
Plugin,
ProcessOptions,
Transformer,
TransformCallback
TransformCallback,
Transformer
} from './postcss.js'
import LazyResult from './lazy-result.js'
import Result from './result.js'
import Root from './root.js'
import NoWorkResult from './no-work-result.js'
declare namespace Processor {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
export { Processor_ as default }
}
/**
* Contains plugins to process CSS. Create one `Processor` instance,
@@ -20,7 +26,17 @@ import NoWorkResult from './no-work-result.js'
* processor.process(css2).then(result => console.log(result.css))
* ```
*/
export default class Processor {
declare class Processor_ {
/**
* Plugins added to this processor.
*
* ```js
* const processor = postcss([autoprefixer, postcssNested])
* processor.plugins.length //=> 2
* ```
*/
plugins: (Plugin | TransformCallback | Transformer)[]
/**
* Current PostCSS version.
*
@@ -32,21 +48,38 @@ export default class Processor {
*/
version: string
/**
* Plugins added to this processor.
*
* ```js
* const processor = postcss([autoprefixer, postcssNested])
* processor.plugins.length //=> 2
* ```
*/
plugins: (Plugin | Transformer | TransformCallback)[]
/**
* @param plugins PostCSS plugins
*/
constructor(plugins?: AcceptedPlugin[])
/**
* Parses source CSS and returns a `LazyResult` Promise proxy.
* Because some plugins can be asynchronous it doesnt make
* any transformations. Transformations will be applied
* in the `LazyResult` methods.
*
* ```js
* processor.process(css, { from: 'a.css', to: 'a.out.css' })
* .then(result => {
* console.log(result.css)
* })
* ```
*
* @param css String with input CSS or any object with a `toString()` method,
* like a Buffer. Optionally, send a `Result` instance
* and the processor will take the `Root` from it.
* @param opts Options.
* @return Promise proxy.
*/
process(
css: { toString(): string } | LazyResult | Result | Root | string
): LazyResult | NoWorkResult
process<RootNode extends Document | Root = Root>(
css: { toString(): string } | LazyResult | Result | Root | string,
options: ProcessOptions<RootNode>
): LazyResult<RootNode>
/**
* Adds a plugin to be used as a CSS processor.
*
@@ -54,7 +87,7 @@ export default class Processor {
* * A plugin in `Plugin` format.
* * A plugin creator function with `pluginCreator.postcss = true`.
* PostCSS will call this function without argument to get plugin.
* * A function. PostCSS will pass the function a @{link Root}
* * A function. PostCSS will pass the function a {@link Root}
* as the first argument and current `Result` instance
* as the second.
* * Another `Processor` instance. PostCSS will copy plugins
@@ -75,28 +108,8 @@ export default class Processor {
* @return Current processor to make methods chain.
*/
use(plugin: AcceptedPlugin): this
/**
* Parses source CSS and returns a `LazyResult` Promise proxy.
* Because some plugins can be asynchronous it doesnt make
* any transformations. Transformations will be applied
* in the `LazyResult` methods.
*
* ```js
* processor.process(css, { from: 'a.css', to: 'a.out.css' })
* .then(result => {
* console.log(result.css)
* })
* ```
*
* @param css String with input CSS or any object with a `toString()` method,
* like a Buffer. Optionally, senda `Result` instance
* and the processor will take the `Root` from it.
* @param opts Options.
* @return Promise proxy.
*/
process(
css: string | { toString(): string } | Result | LazyResult | Root,
options?: ProcessOptions
): LazyResult | NoWorkResult
}
declare class Processor extends Processor_ {}
export = Processor

View File

@@ -7,28 +7,10 @@ let Root = require('./root')
class Processor {
constructor(plugins = []) {
this.version = '8.4.20'
this.version = '8.4.29'
this.plugins = this.normalize(plugins)
}
use(plugin) {
this.plugins = this.plugins.concat(this.normalize([plugin]))
return this
}
process(css, opts = {}) {
if (
this.plugins.length === 0 &&
typeof opts.parser === 'undefined' &&
typeof opts.stringifier === 'undefined' &&
typeof opts.syntax === 'undefined'
) {
return new NoWorkResult(this, css, opts)
} else {
return new LazyResult(this, css, opts)
}
}
normalize(plugins) {
let normalized = []
for (let i of plugins) {
@@ -58,6 +40,24 @@ class Processor {
}
return normalized
}
process(css, opts = {}) {
if (
this.plugins.length === 0 &&
typeof opts.parser === 'undefined' &&
typeof opts.stringifier === 'undefined' &&
typeof opts.syntax === 'undefined'
) {
return new NoWorkResult(this, css, opts)
} else {
return new LazyResult(this, css, opts)
}
}
use(plugin) {
this.plugins = this.plugins.concat(this.normalize([plugin]))
return this
}
}
module.exports = Processor

View File

@@ -1,41 +1,47 @@
import {
ProcessOptions,
Plugin,
SourceMap,
TransformCallback,
Root,
Document,
Node,
Plugin,
ProcessOptions,
Root,
SourceMap,
TransformCallback,
Warning,
WarningOptions
} from './postcss.js'
import Processor from './processor.js'
export interface Message {
/**
* Message type.
*/
type: string
declare namespace Result {
export interface Message {
[others: string]: any
/**
* Source PostCSS plugin name.
*/
plugin?: string
/**
* Source PostCSS plugin name.
*/
plugin?: string
[others: string]: any
}
/**
* Message type.
*/
type: string
}
export interface ResultOptions extends ProcessOptions {
/**
* The CSS node that was the source of the warning.
*/
node?: Node
export interface ResultOptions extends ProcessOptions {
/**
* The CSS node that was the source of the warning.
*/
node?: Node
/**
* Name of plugin that created this warning. `Result#warn` will fill it
* automatically with `Plugin#postcssPlugin` value.
*/
plugin?: string
/**
* Name of plugin that created this warning. `Result#warn` will fill it
* automatically with `Plugin#postcssPlugin` value.
*/
plugin?: string
}
// eslint-disable-next-line @typescript-eslint/no-use-before-define
export { Result_ as default }
}
/**
@@ -54,19 +60,36 @@ export interface ResultOptions extends ProcessOptions {
* const result2 = postcss.parse(css).toResult()
* ```
*/
export default class Result {
declare class Result_<RootNode = Document | Root> {
/**
* The Processor instance used for this transformation.
* A CSS string representing of `Result#root`.
*
* ```js
* for (const plugin of result.processor.plugins) {
* if (plugin.postcssPlugin === 'postcss-bad') {
* throw 'postcss-good is incompatible with postcss-bad'
* }
* })
* postcss.parse('a{}').toResult().css //=> "a{}"
* ```
*/
processor: Processor
css: string
/**
* Last runned PostCSS plugin.
*/
lastPlugin: Plugin | TransformCallback
/**
* An instance of `SourceMapGenerator` class from the `source-map` library,
* representing changes to the `Result#root` instance.
*
* ```js
* result.map.toJSON() //=> { version: 3, file: 'a.css', … }
* ```
*
* ```js
* if (result.map) {
* fs.writeFileSync(result.opts.to + '.map', result.map.toString())
* }
* ```
*/
map: SourceMap
/**
* Contains messages from plugins (e.g., warnings or custom messages).
@@ -86,16 +109,7 @@ export default class Result {
* }
* ```
*/
messages: Message[]
/**
* Root node after all transformations.
*
* ```js
* root.toResult().root === root
* ```
*/
root: Root | Document
messages: Result.Message[]
/**
* Options from the `Processor#process` or `Root#toResult` call
@@ -105,44 +119,36 @@ export default class Result {
* root.toResult(opts).opts === opts
* ```
*/
opts: ResultOptions
opts: Result.ResultOptions
/**
* A CSS string representing of `Result#root`.
* The Processor instance used for this transformation.
*
* ```js
* postcss.parse('a{}').toResult().css //=> "a{}"
* for (const plugin of result.processor.plugins) {
* if (plugin.postcssPlugin === 'postcss-bad') {
* throw 'postcss-good is incompatible with postcss-bad'
* }
* })
* ```
*/
css: string
processor: Processor
/**
* An instance of `SourceMapGenerator` class from the `source-map` library,
* representing changes to the `Result#root` instance.
* Root node after all transformations.
*
* ```js
* result.map.toJSON() //=> { version: 3, file: 'a.css', … }
* ```
*
* ```js
* if (result.map) {
* fs.writeFileSync(result.opts.to + '.map', result.map.toString())
* }
* root.toResult().root === root
* ```
*/
map: SourceMap
/**
* Last runned PostCSS plugin.
*/
lastPlugin: Plugin | TransformCallback
root: RootNode
/**
* @param processor Processor used for this transformation.
* @param root Root node after all transformations.
* @param opts Options from the `Processor#process` or `Root#toResult`.
*/
constructor(processor: Processor, root: Root | Document, opts: ResultOptions)
constructor(processor: Processor, root: RootNode, opts: Result.ResultOptions)
/**
* An alias for the `Result#css` property.
@@ -194,3 +200,7 @@ export default class Result {
*/
warnings(): Warning[]
}
declare class Result<RootNode = Document | Root> extends Result_<RootNode> {}
export = Result

View File

@@ -12,6 +12,10 @@ class Result {
this.map = undefined
}
get content() {
return this.css
}
toString() {
return this.css
}
@@ -32,10 +36,6 @@ class Result {
warnings() {
return this.messages.filter(i => i.type === 'warning')
}
get content() {
return this.css
}
}
module.exports = Result

View File

@@ -3,40 +3,45 @@ import Document from './document.js'
import { ProcessOptions } from './postcss.js'
import Result from './result.js'
interface RootRaws extends Record<string, any> {
/**
* The space symbols after the last child to the end of file.
*/
after?: string
declare namespace Root {
export interface RootRaws extends Record<string, any> {
/**
* The space symbols after the last child to the end of file.
*/
after?: string
/**
* Non-CSS code before `Root`, when `Root` is inside `Document`.
*
* **Experimental:** some aspects of this node could change within minor
* or patch version releases.
*/
codeBefore?: string
/**
* Non-CSS code after `Root`, when `Root` is inside `Document`.
*
* **Experimental:** some aspects of this node could change within minor
* or patch version releases.
*/
codeAfter?: string
/**
* Non-CSS code after `Root`, when `Root` is inside `Document`.
*
* **Experimental:** some aspects of this node could change within minor
* or patch version releases.
*/
codeAfter?: string
/**
* Non-CSS code before `Root`, when `Root` is inside `Document`.
*
* **Experimental:** some aspects of this node could change within minor
* or patch version releases.
*/
codeBefore?: string
/**
* Is the last child has an (optional) semicolon.
*/
semicolon?: boolean
}
/**
* Is the last child has an (optional) semicolon.
*/
semicolon?: boolean
}
export interface RootProps extends ContainerProps {
/**
* Information used to generate byte-to-byte equal node string
* as it was in the origin input.
* */
raws?: RootRaws
export interface RootProps extends ContainerProps {
/**
* Information used to generate byte-to-byte equal node string
* as it was in the origin input.
* */
raws?: RootRaws
}
// eslint-disable-next-line @typescript-eslint/no-use-before-define
export { Root_ as default }
}
/**
@@ -48,10 +53,17 @@ export interface RootProps extends ContainerProps {
* root.nodes.length //=> 2
* ```
*/
export default class Root extends Container {
type: 'root'
declare class Root_ extends Container {
parent: Document | undefined
raws: RootRaws
raws: Root.RootRaws
type: 'root'
constructor(defaults?: Root.RootProps)
assign(overrides: object | Root.RootProps): this
clone(overrides?: Partial<Root.RootProps>): Root
cloneAfter(overrides?: Partial<Root.RootProps>): Root
cloneBefore(overrides?: Partial<Root.RootProps>): Root
/**
* Returns a `Result` instance representing the roots CSS.
@@ -67,7 +79,8 @@ export default class Root extends Container {
* @return Result with current roots CSS.
*/
toResult(options?: ProcessOptions): Result
constructor(defaults?: RootProps)
assign(overrides: object | RootProps): this
}
declare class Root extends Root_ {}
export = Root

View File

@@ -11,16 +11,6 @@ class Root extends Container {
if (!this.nodes) this.nodes = []
}
removeChild(child, ignore) {
let index = this.index(child)
if (!ignore && index === 0 && this.nodes.length > 1) {
this.nodes[1].raws.before = this.nodes[index].raws.before
}
return super.removeChild(child)
}
normalize(child, sample, type) {
let nodes = super.normalize(child)
@@ -41,6 +31,16 @@ class Root extends Container {
return nodes
}
removeChild(child, ignore) {
let index = this.index(child)
if (!ignore && index === 0 && this.nodes.length > 1) {
this.nodes[1].raws.before = this.nodes[index].raws.before
}
return super.removeChild(child)
}
toResult(opts = {}) {
let lazy = new LazyResult(new Processor(), this, opts)
return lazy.stringify()

View File

@@ -1,48 +1,53 @@
import Container, { ContainerProps } from './container.js'
interface RuleRaws extends Record<string, unknown> {
/**
* The space symbols before the node. It also stores `*`
* and `_` symbols before the declaration (IE hack).
*/
before?: string
declare namespace Rule {
export interface RuleRaws extends Record<string, unknown> {
/**
* The space symbols after the last child of the node to the end of the node.
*/
after?: string
/**
* The space symbols after the last child of the node to the end of the node.
*/
after?: string
/**
* The space symbols before the node. It also stores `*`
* and `_` symbols before the declaration (IE hack).
*/
before?: string
/**
* The symbols between the selector and `{` for rules.
*/
between?: string
/**
* The symbols between the selector and `{` for rules.
*/
between?: string
/**
* Contains `true` if the last child has an (optional) semicolon.
*/
semicolon?: boolean
/**
* Contains `true` if there is semicolon after rule.
*/
ownSemicolon?: string
/**
* Contains `true` if there is semicolon after rule.
*/
ownSemicolon?: string
/**
* The rules selector with comments.
*/
selector?: {
raw: string
value: string
}
/**
* The rules selector with comments.
*/
selector?: {
value: string
raw: string
/**
* Contains `true` if the last child has an (optional) semicolon.
*/
semicolon?: boolean
}
}
export interface RuleProps extends ContainerProps {
/** Selector or selectors of the rule. */
selector?: string
/** Selectors of the rule represented as an array of strings. */
selectors?: string[]
/** Information used to generate byte-to-byte equal node string as it was in the origin input. */
raws?: RuleRaws
export interface RuleProps extends ContainerProps {
/** Information used to generate byte-to-byte equal node string as it was in the origin input. */
raws?: RuleRaws
/** Selector or selectors of the rule. */
selector?: string
/** Selectors of the rule represented as an array of strings. */
selectors?: string[]
}
// eslint-disable-next-line @typescript-eslint/no-use-before-define
export { Rule_ as default }
}
/**
@@ -63,11 +68,9 @@ export interface RuleProps extends ContainerProps {
* rule.toString() //=> 'a{}'
* ```
*/
export default class Rule extends Container {
type: 'rule'
declare class Rule_ extends Container {
parent: Container | undefined
raws: RuleRaws
raws: Rule.RuleRaws
/**
* The rules full selector represented as a string.
*
@@ -96,9 +99,15 @@ export default class Rule extends Container {
*/
selectors: string[]
constructor(defaults?: RuleProps)
assign(overrides: object | RuleProps): this
clone(overrides?: Partial<RuleProps>): this
cloneBefore(overrides?: Partial<RuleProps>): this
cloneAfter(overrides?: Partial<RuleProps>): this
type: 'rule'
constructor(defaults?: Rule.RuleProps)
assign(overrides: object | Rule.RuleProps): this
clone(overrides?: Partial<Rule.RuleProps>): Rule
cloneAfter(overrides?: Partial<Rule.RuleProps>): Rule
cloneBefore(overrides?: Partial<Rule.RuleProps>): Rule
}
declare class Rule extends Rule_ {}
export = Rule

View File

@@ -1,37 +1,46 @@
import {
AnyNode,
AtRule,
Builder,
Comment,
Container,
Declaration,
Document,
Root,
Comment,
Declaration,
Builder,
AnyNode,
Rule,
AtRule,
Container
Rule
} from './postcss.js'
export default class Stringifier {
declare namespace Stringifier {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
export { Stringifier_ as default }
}
declare class Stringifier_ {
builder: Builder
constructor(builder: Builder)
stringify(node: AnyNode, semicolon?: boolean): void
document(node: Document): void
root(node: Root): void
atrule(node: AtRule, semicolon?: boolean): void
beforeAfter(node: AnyNode, detect: 'after' | 'before'): string
block(node: AnyNode, start: string): void
body(node: Container): void
comment(node: Comment): void
decl(node: Declaration, semicolon?: boolean): void
rule(node: Rule): void
atrule(node: AtRule, semicolon?: boolean): void
body(node: Container): void
block(node: AnyNode, start: string): void
raw(node: AnyNode, own: string | null, detect?: string): string
rawSemicolon(root: Root): boolean | undefined
rawEmptyBody(root: Root): string | undefined
rawIndent(root: Root): string | undefined
document(node: Document): void
raw(node: AnyNode, own: null | string, detect?: string): string
rawBeforeClose(root: Root): string | undefined
rawBeforeComment(root: Root, node: Comment): string | undefined
rawBeforeDecl(root: Root, node: Declaration): string | undefined
rawBeforeRule(root: Root): string | undefined
rawBeforeClose(root: Root): string | undefined
rawBeforeOpen(root: Root): string | undefined
rawBeforeRule(root: Root): string | undefined
rawColon(root: Root): string | undefined
beforeAfter(node: AnyNode, detect: 'before' | 'after'): string
rawEmptyBody(root: Root): string | undefined
rawIndent(root: Root): string | undefined
rawSemicolon(root: Root): boolean | undefined
rawValue(node: AnyNode, prop: string): string
root(node: Root): void
rule(node: Rule): void
stringify(node: AnyNode, semicolon?: boolean): void
}
declare class Stringifier extends Stringifier_ {}
export = Stringifier

View File

@@ -1,17 +1,17 @@
'use strict'
const DEFAULT_RAW = {
colon: ': ',
indent: ' ',
beforeDecl: '\n',
beforeRule: '\n',
beforeOpen: ' ',
after: '\n',
beforeClose: '\n',
beforeComment: '\n',
after: '\n',
emptyBody: '',
beforeDecl: '\n',
beforeOpen: ' ',
beforeRule: '\n',
colon: ': ',
commentLeft: ' ',
commentRight: ' ',
emptyBody: '',
indent: ' ',
semicolon: false
}
@@ -24,54 +24,6 @@ class Stringifier {
this.builder = builder
}
stringify(node, semicolon) {
/* c8 ignore start */
if (!this[node.type]) {
throw new Error(
'Unknown AST node type ' +
node.type +
'. ' +
'Maybe you need to change PostCSS stringifier.'
)
}
/* c8 ignore stop */
this[node.type](node, semicolon)
}
document(node) {
this.body(node)
}
root(node) {
this.body(node)
if (node.raws.after) this.builder(node.raws.after)
}
comment(node) {
let left = this.raw(node, 'left', 'commentLeft')
let right = this.raw(node, 'right', 'commentRight')
this.builder('/*' + left + node.text + right + '*/', node)
}
decl(node, semicolon) {
let between = this.raw(node, 'between', 'colon')
let string = node.prop + between + this.rawValue(node, 'value')
if (node.important) {
string += node.raws.important || ' !important'
}
if (semicolon) string += ';'
this.builder(string, node)
}
rule(node) {
this.block(node, this.rawValue(node, 'selector'))
if (node.raws.ownSemicolon) {
this.builder(node.raws.ownSemicolon, node, 'end')
}
}
atrule(node, semicolon) {
let name = '@' + node.name
let params = node.params ? this.rawValue(node, 'params') : ''
@@ -90,20 +42,33 @@ class Stringifier {
}
}
body(node) {
let last = node.nodes.length - 1
while (last > 0) {
if (node.nodes[last].type !== 'comment') break
last -= 1
beforeAfter(node, detect) {
let value
if (node.type === 'decl') {
value = this.raw(node, null, 'beforeDecl')
} else if (node.type === 'comment') {
value = this.raw(node, null, 'beforeComment')
} else if (detect === 'before') {
value = this.raw(node, null, 'beforeRule')
} else {
value = this.raw(node, null, 'beforeClose')
}
let semicolon = this.raw(node, 'semicolon')
for (let i = 0; i < node.nodes.length; i++) {
let child = node.nodes[i]
let before = this.raw(child, 'before')
if (before) this.builder(before)
this.stringify(child, last !== i || semicolon)
let buf = node.parent
let depth = 0
while (buf && buf.type !== 'root') {
depth += 1
buf = buf.parent
}
if (value.includes('\n')) {
let indent = this.raw(node, null, 'indent')
if (indent.length) {
for (let step = 0; step < depth; step++) value += indent
}
}
return value
}
block(node, start) {
@@ -122,6 +87,44 @@ class Stringifier {
this.builder('}', node, 'end')
}
body(node) {
let last = node.nodes.length - 1
while (last > 0) {
if (node.nodes[last].type !== 'comment') break
last -= 1
}
let semicolon = this.raw(node, 'semicolon')
for (let i = 0; i < node.nodes.length; i++) {
let child = node.nodes[i]
let before = this.raw(child, 'before')
if (before) this.builder(before)
this.stringify(child, last !== i || semicolon)
}
}
comment(node) {
let left = this.raw(node, 'left', 'commentLeft')
let right = this.raw(node, 'right', 'commentRight')
this.builder('/*' + left + node.text + right + '*/', node)
}
decl(node, semicolon) {
let between = this.raw(node, 'between', 'colon')
let string = node.prop + between + this.rawValue(node, 'value')
if (node.important) {
string += node.raws.important || ' !important'
}
if (semicolon) string += ';'
this.builder(string, node)
}
document(node) {
this.body(node)
}
raw(node, own, detect) {
let value
if (!detect) detect = own
@@ -176,42 +179,20 @@ class Stringifier {
return value
}
rawSemicolon(root) {
rawBeforeClose(root) {
let value
root.walk(i => {
if (i.nodes && i.nodes.length && i.last.type === 'decl') {
value = i.raws.semicolon
if (typeof value !== 'undefined') return false
}
})
return value
}
rawEmptyBody(root) {
let value
root.walk(i => {
if (i.nodes && i.nodes.length === 0) {
value = i.raws.after
if (typeof value !== 'undefined') return false
}
})
return value
}
rawIndent(root) {
if (root.raws.indent) return root.raws.indent
let value
root.walk(i => {
let p = i.parent
if (p && p !== root && p.parent && p.parent === root) {
if (typeof i.raws.before !== 'undefined') {
let parts = i.raws.before.split('\n')
value = parts[parts.length - 1]
value = value.replace(/\S/g, '')
if (i.nodes && i.nodes.length > 0) {
if (typeof i.raws.after !== 'undefined') {
value = i.raws.after
if (value.includes('\n')) {
value = value.replace(/[^\n]+$/, '')
}
return false
}
}
})
if (value) value = value.replace(/\S/g, '')
return value
}
@@ -253,6 +234,17 @@ class Stringifier {
return value
}
rawBeforeOpen(root) {
let value
root.walk(i => {
if (i.type !== 'decl') {
value = i.raws.between
if (typeof value !== 'undefined') return false
}
})
return value
}
rawBeforeRule(root) {
let value
root.walk(i => {
@@ -270,34 +262,6 @@ class Stringifier {
return value
}
rawBeforeClose(root) {
let value
root.walk(i => {
if (i.nodes && i.nodes.length > 0) {
if (typeof i.raws.after !== 'undefined') {
value = i.raws.after
if (value.includes('\n')) {
value = value.replace(/[^\n]+$/, '')
}
return false
}
}
})
if (value) value = value.replace(/\S/g, '')
return value
}
rawBeforeOpen(root) {
let value
root.walk(i => {
if (i.type !== 'decl') {
value = i.raws.between
if (typeof value !== 'undefined') return false
}
})
return value
}
rawColon(root) {
let value
root.walkDecls(i => {
@@ -309,32 +273,42 @@ class Stringifier {
return value
}
beforeAfter(node, detect) {
rawEmptyBody(root) {
let value
if (node.type === 'decl') {
value = this.raw(node, null, 'beforeDecl')
} else if (node.type === 'comment') {
value = this.raw(node, null, 'beforeComment')
} else if (detect === 'before') {
value = this.raw(node, null, 'beforeRule')
} else {
value = this.raw(node, null, 'beforeClose')
}
let buf = node.parent
let depth = 0
while (buf && buf.type !== 'root') {
depth += 1
buf = buf.parent
}
if (value.includes('\n')) {
let indent = this.raw(node, null, 'indent')
if (indent.length) {
for (let step = 0; step < depth; step++) value += indent
root.walk(i => {
if (i.nodes && i.nodes.length === 0) {
value = i.raws.after
if (typeof value !== 'undefined') return false
}
}
})
return value
}
rawIndent(root) {
if (root.raws.indent) return root.raws.indent
let value
root.walk(i => {
let p = i.parent
if (p && p !== root && p.parent && p.parent === root) {
if (typeof i.raws.before !== 'undefined') {
let parts = i.raws.before.split('\n')
value = parts[parts.length - 1]
value = value.replace(/\S/g, '')
return false
}
}
})
return value
}
rawSemicolon(root) {
let value
root.walk(i => {
if (i.nodes && i.nodes.length && i.last.type === 'decl') {
value = i.raws.semicolon
if (typeof value !== 'undefined') return false
}
})
return value
}
@@ -347,6 +321,32 @@ class Stringifier {
return value
}
root(node) {
this.body(node)
if (node.raws.after) this.builder(node.raws.after)
}
rule(node) {
this.block(node, this.rawValue(node, 'selector'))
if (node.raws.ownSemicolon) {
this.builder(node.raws.ownSemicolon, node, 'end')
}
}
stringify(node, semicolon) {
/* c8 ignore start */
if (!this[node.type]) {
throw new Error(
'Unknown AST node type ' +
node.type +
'. ' +
'Maybe you need to change PostCSS stringifier.'
)
}
/* c8 ignore stop */
this[node.type](node, semicolon)
}
}
module.exports = Stringifier

View File

@@ -1,5 +1,9 @@
import { Stringifier } from './postcss.js'
declare const stringify: Stringifier
interface Stringify extends Stringifier {
default: Stringify
}
export default stringify
declare const stringify: Stringify
export = stringify

View File

@@ -11,21 +11,21 @@ function registerInput(dependant) {
}
const HIGHLIGHT_THEME = {
'brackets': pico.cyan,
'at-word': pico.cyan,
'comment': pico.gray,
'string': pico.green,
'class': pico.yellow,
'hash': pico.magenta,
'call': pico.cyan,
';': pico.yellow,
':': pico.yellow,
'(': pico.cyan,
')': pico.cyan,
'{': pico.yellow,
'}': pico.yellow,
'[': pico.yellow,
']': pico.yellow,
':': pico.yellow,
';': pico.yellow
'{': pico.yellow,
'}': pico.yellow,
'at-word': pico.cyan,
'brackets': pico.cyan,
'call': pico.cyan,
'class': pico.yellow,
'comment': pico.gray,
'hash': pico.magenta,
'string': pico.green
}
function getTokenType([type, value], processor) {

View File

@@ -259,8 +259,8 @@ module.exports = function tokenizer(input, options = {}) {
return {
back,
nextToken,
endOfFile,
nextToken,
position
}
}

View File

@@ -1,42 +1,47 @@
import { RangePosition } from './css-syntax-error.js'
import Node from './node.js'
export interface WarningOptions {
/**
* CSS node that caused the warning.
*/
node?: Node
declare namespace Warning {
export interface WarningOptions {
/**
* End position, exclusive, in CSS node string that caused the warning.
*/
end?: RangePosition
/**
* Word in CSS source that caused the warning.
*/
word?: string
/**
* End index, exclusive, in CSS node string that caused the warning.
*/
endIndex?: number
/**
* Start index, inclusive, in CSS node string that caused the warning.
*/
index?: number
/**
* Start index, inclusive, in CSS node string that caused the warning.
*/
index?: number
/**
* End index, exclusive, in CSS node string that caused the warning.
*/
endIndex?: number
/**
* CSS node that caused the warning.
*/
node?: Node
/**
* Start position, inclusive, in CSS node string that caused the warning.
*/
start?: RangePosition
/**
* Name of the plugin that created this warning. `Result#warn` fills
* this property automatically.
*/
plugin?: string
/**
* End position, exclusive, in CSS node string that caused the warning.
*/
end?: RangePosition
/**
* Start position, inclusive, in CSS node string that caused the warning.
*/
start?: RangePosition
/**
* Name of the plugin that created this warning. `Result#warn` fills
* this property automatically.
*/
plugin?: string
/**
* Word in CSS source that caused the warning.
*/
word?: string
}
// eslint-disable-next-line @typescript-eslint/no-use-before-define
export { Warning_ as default }
}
/**
@@ -48,21 +53,51 @@ export interface WarningOptions {
* }
* ```
*/
export default class Warning {
declare class Warning_ {
/**
* Type to filter warnings from `Result#messages`.
* Always equal to `"warning"`.
*/
type: 'warning'
/**
* The warning message.
* Column for inclusive start position in the input file with this warnings source.
*
* ```js
* warning.text //=> 'Try to avoid !important'
* warning.column //=> 6
* ```
*/
text: string
column: number
/**
* Column for exclusive end position in the input file with this warnings source.
*
* ```js
* warning.endColumn //=> 4
* ```
*/
endColumn?: number
/**
* Line for exclusive end position in the input file with this warnings source.
*
* ```js
* warning.endLine //=> 6
* ```
*/
endLine?: number
/**
* Line for inclusive start position in the input file with this warnings source.
*
* ```js
* warning.line //=> 5
* ```
*/
line: number
/**
* Contains the CSS node that caused the warning.
*
* ```js
* warning.node.toString() //=> 'color: white !important'
* ```
*/
node: Node
/**
* The name of the plugin that created this warning.
@@ -75,55 +110,25 @@ export default class Warning {
plugin: string
/**
* Contains the CSS node that caused the warning.
* The warning message.
*
* ```js
* warning.node.toString() //=> 'color: white !important'
* warning.text //=> 'Try to avoid !important'
* ```
*/
node: Node
text: string
/**
* Line for inclusive start position in the input file with this warnings source.
*
* ```js
* warning.line //=> 5
* ```
* Type to filter warnings from `Result#messages`.
* Always equal to `"warning"`.
*/
line: number
/**
* Column for inclusive start position in the input file with this warnings source.
*
* ```js
* warning.column //=> 6
* ```
*/
column: number
/**
* Line for exclusive end position in the input file with this warnings source.
*
* ```js
* warning.endLine //=> 6
* ```
*/
endLine?: number
/**
* Column for exclusive end position in the input file with this warnings source.
*
* ```js
* warning.endColumn //=> 4
* ```
*/
endColumn?: number
type: 'warning'
/**
* @param text Warning message.
* @param opts Warning options.
*/
constructor(text: string, opts?: WarningOptions)
constructor(text: string, opts?: Warning.WarningOptions)
/**
* Returns a warning position and message.
@@ -136,3 +141,7 @@ export default class Warning {
*/
toString(): string
}
declare class Warning extends Warning_ {}
export = Warning

View File

@@ -19,8 +19,8 @@ class Warning {
toString() {
if (this.node) {
return this.node.error(this.text, {
plugin: this.plugin,
index: this.index,
plugin: this.plugin,
word: this.word
}).message
}

View File

@@ -1,6 +1,6 @@
{
"name": "postcss",
"version": "8.4.20",
"version": "8.4.29",
"description": "Tool for transforming styles with JS plugins",
"engines": {
"node": "^10 || ^12 || >=14"
@@ -8,8 +8,7 @@
"exports": {
".": {
"require": "./lib/postcss.js",
"import": "./lib/postcss.mjs",
"types": "./lib/postcss.d.ts"
"import": "./lib/postcss.mjs"
},
"./lib/at-rule": "./lib/at-rule.js",
"./lib/comment": "./lib/comment.js",
@@ -61,6 +60,10 @@
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"author": "Andrey Sitnik <andrey@sitnik.ru>",
@@ -71,7 +74,7 @@
"url": "https://github.com/postcss/postcss/issues"
},
"dependencies": {
"nanoid": "^3.3.4",
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},