summaryrefslogtreecommitdiff
path: root/node_modules/postcss/lib/lazy-result.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/postcss/lib/lazy-result.js')
-rw-r--r--node_modules/postcss/lib/lazy-result.js550
1 files changed, 550 insertions, 0 deletions
diff --git a/node_modules/postcss/lib/lazy-result.js b/node_modules/postcss/lib/lazy-result.js
new file mode 100644
index 0000000..126f40c
--- /dev/null
+++ b/node_modules/postcss/lib/lazy-result.js
@@ -0,0 +1,550 @@
+'use strict'
+
+let { isClean, my } = require('./symbols')
+let MapGenerator = require('./map-generator')
+let stringify = require('./stringify')
+let Container = require('./container')
+let Document = require('./document')
+let warnOnce = require('./warn-once')
+let Result = require('./result')
+let parse = require('./parse')
+let Root = require('./root')
+
+const TYPE_TO_CLASS_NAME = {
+ atrule: 'AtRule',
+ comment: 'Comment',
+ decl: 'Declaration',
+ document: 'Document',
+ root: 'Root',
+ 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,
+ Root: true,
+ RootExit: true,
+ Rule: true,
+ RuleExit: true
+}
+
+const NOT_VISITORS = {
+ Once: true,
+ postcssPlugin: true,
+ prepare: true
+}
+
+const CHILDREN = 0
+
+function isPromise(obj) {
+ return typeof obj === 'object' && typeof obj.then === 'function'
+}
+
+function getEvents(node) {
+ let key = false
+ let type = TYPE_TO_CLASS_NAME[node.type]
+ if (node.type === 'decl') {
+ key = node.prop.toLowerCase()
+ } else if (node.type === 'atrule') {
+ key = node.name.toLowerCase()
+ }
+
+ if (key && node.append) {
+ return [
+ type,
+ type + '-' + key,
+ CHILDREN,
+ type + 'Exit',
+ type + 'Exit-' + key
+ ]
+ } else if (key) {
+ return [type, type + '-' + key, type + 'Exit', type + 'Exit-' + key]
+ } else if (node.append) {
+ return [type, CHILDREN, type + 'Exit']
+ } else {
+ return [type, type + 'Exit']
+ }
+}
+
+function toStack(node) {
+ let events
+ if (node.type === 'document') {
+ events = ['Document', CHILDREN, 'DocumentExit']
+ } else if (node.type === 'root') {
+ events = ['Root', CHILDREN, 'RootExit']
+ } else {
+ events = getEvents(node)
+ }
+
+ return {
+ eventIndex: 0,
+ events,
+ iterator: 0,
+ node,
+ visitorIndex: 0,
+ visitors: []
+ }
+}
+
+function cleanMarks(node) {
+ node[isClean] = false
+ if (node.nodes) node.nodes.forEach(i => cleanMarks(i))
+ return node
+}
+
+let postcss = {}
+
+class LazyResult {
+ constructor(processor, css, opts) {
+ this.stringified = false
+ this.processed = false
+
+ let root
+ if (
+ typeof css === 'object' &&
+ css !== null &&
+ (css.type === 'root' || css.type === 'document')
+ ) {
+ root = cleanMarks(css)
+ } else if (css instanceof LazyResult || css instanceof Result) {
+ root = cleanMarks(css.root)
+ if (css.map) {
+ if (typeof opts.map === 'undefined') opts.map = {}
+ if (!opts.map.inline) opts.map.inline = false
+ opts.map.prev = css.map
+ }
+ } else {
+ let parser = parse
+ if (opts.syntax) parser = opts.syntax.parse
+ if (opts.parser) parser = opts.parser
+ if (parser.parse) parser = parser.parse
+
+ try {
+ root = parser(css, opts)
+ } catch (error) {
+ this.processed = true
+ this.error = error
+ }
+
+ if (root && !root[my]) {
+ /* c8 ignore next 2 */
+ Container.rebuild(root)
+ }
+ }
+
+ this.result = new Result(processor, root, opts)
+ 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) }
+ } else {
+ return plugin
+ }
+ })
+ }
+
+ async() {
+ if (this.error) return Promise.reject(this.error)
+ if (this.processed) return Promise.resolve(this.result)
+ if (!this.processing) {
+ this.processing = this.runAsync()
+ }
+ return this.processing
+ }
+
+ catch(onRejected) {
+ return this.async().catch(onRejected)
+ }
+
+ finally(onFinally) {
+ return this.async().then(onFinally, onFinally)
+ }
+
+ getAsyncError() {
+ throw new Error('Use process(css).then(cb) to work with async plugins')
+ }
+
+ handleError(error, node) {
+ let plugin = this.result.lastPlugin
+ try {
+ if (node) node.addToError(error)
+ this.error = error
+ if (error.name === 'CssSyntaxError' && !error.plugin) {
+ error.plugin = plugin.postcssPlugin
+ error.setMessage()
+ } else if (plugin.postcssVersion) {
+ if (process.env.NODE_ENV !== 'production') {
+ let pluginName = plugin.postcssPlugin
+ let pluginVer = plugin.postcssVersion
+ let runtimeVer = this.result.processor.version
+ let a = pluginVer.split('.')
+ let b = runtimeVer.split('.')
+
+ if (a[0] !== b[0] || parseInt(a[1]) > parseInt(b[1])) {
+ // eslint-disable-next-line no-console
+ console.error(
+ 'Unknown error from PostCSS plugin. Your current PostCSS ' +
+ 'version is ' +
+ runtimeVer +
+ ', but ' +
+ pluginName +
+ ' uses ' +
+ pluginVer +
+ '. Perhaps this is the source of the error below.'
+ )
+ }
+ }
+ }
+ } catch (err) {
+ /* c8 ignore next 3 */
+ // eslint-disable-next-line no-console
+ if (console && console.error) console.error(err)
+ }
+ return error
+ }
+
+ 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
+ }
+
+ async runAsync() {
+ this.plugin = 0
+ for (let i = 0; i < this.plugins.length; i++) {
+ let plugin = this.plugins[i]
+ let promise = this.runOnRoot(plugin)
+ if (isPromise(promise)) {
+ try {
+ await promise
+ } catch (error) {
+ throw this.handleError(error)
+ }
+ }
+ }
+
+ this.prepareVisitors()
+ if (this.hasListener) {
+ let root = this.result.root
+ while (!root[isClean]) {
+ root[isClean] = true
+ let stack = [toStack(root)]
+ while (stack.length > 0) {
+ let promise = this.visitTick(stack)
+ if (isPromise(promise)) {
+ try {
+ await promise
+ } catch (e) {
+ let node = stack[stack.length - 1].node
+ throw this.handleError(e, node)
+ }
+ }
+ }
+ }
+
+ if (this.listeners.OnceExit) {
+ for (let [plugin, visitor] of this.listeners.OnceExit) {
+ this.result.lastPlugin = plugin
+ try {
+ if (root.type === 'document') {
+ let roots = root.nodes.map(subRoot =>
+ visitor(subRoot, this.helpers)
+ )
+
+ await Promise.all(roots)
+ } else {
+ await visitor(root, this.helpers)
+ }
+ } catch (e) {
+ throw this.handleError(e)
+ }
+ }
+ }
+ }
+
+ this.processed = true
+ return this.stringify()
+ }
+
+ 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
+ }
+
+ 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
+ }
+
+ 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) {
+ let visit = stack[stack.length - 1]
+ let { node, visitors } = visit
+
+ if (node.type !== 'root' && node.type !== 'document' && !node.parent) {
+ stack.pop()
+ return
+ }
+
+ if (visitors.length > 0 && visit.visitorIndex < visitors.length) {
+ let [plugin, visitor] = visitors[visit.visitorIndex]
+ visit.visitorIndex += 1
+ if (visit.visitorIndex === visitors.length) {
+ visit.visitors = []
+ visit.visitorIndex = 0
+ }
+ this.result.lastPlugin = plugin
+ try {
+ return visitor(node.toProxy(), this.helpers)
+ } catch (e) {
+ throw this.handleError(e, node)
+ }
+ }
+
+ if (visit.iterator !== 0) {
+ let iterator = visit.iterator
+ let child
+ while ((child = node.nodes[node.indexes[iterator]])) {
+ node.indexes[iterator] += 1
+ if (!child[isClean]) {
+ child[isClean] = true
+ stack.push(toStack(child))
+ return
+ }
+ }
+ visit.iterator = 0
+ delete node.indexes[iterator]
+ }
+
+ let events = visit.events
+ while (visit.eventIndex < events.length) {
+ let event = events[visit.eventIndex]
+ visit.eventIndex += 1
+ if (event === CHILDREN) {
+ if (node.nodes && node.nodes.length) {
+ node[isClean] = true
+ visit.iterator = node.getIterator()
+ }
+ return
+ } else if (this.listeners[event]) {
+ visit.visitors = this.listeners[event]
+ return
+ }
+ }
+ 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()
+ }
+
+ get content() {
+ return this.stringify().content
+ }
+
+ get css() {
+ return this.stringify().css
+ }
+
+ get map() {
+ return this.stringify().map
+ }
+
+ get messages() {
+ return this.sync().messages
+ }
+
+ get opts() {
+ return this.result.opts
+ }
+
+ get processor() {
+ return this.result.processor
+ }
+
+ get root() {
+ return this.sync().root
+ }
+
+ get [Symbol.toStringTag]() {
+ return 'LazyResult'
+ }
+}
+
+LazyResult.registerPostcss = dependant => {
+ postcss = dependant
+}
+
+module.exports = LazyResult
+LazyResult.default = LazyResult
+
+Root.registerLazyResult(LazyResult)
+Document.registerLazyResult(LazyResult)