postcss/import/parse.js

/* eslint consistent-return: 0 */
/** @module */
import postcss from 'postcss'

/**
 * @typedef {object} ImportMeta
 * @prop {AtRule} node The at-rule node parsed by PostCSS.
 * @prop {string} value The specified value.
 */

/**
 * Marpit PostCSS import parse plugin.
 *
 * Parse `@import` and `@import-theme` rules that specify a plain string.
 *
 * The `@import` rule for Marpit theme follows CSS spec. It must precede all
 * other statements. (excepted `@charset`)
 *
 * When you are using CSS preprocessors like Sass, `@import` would resolve path
 * in compiling and would be lost definition. So you can use `@import-theme`
 * rule alternatively.
 *
 * A specification of `@import-theme` has a bit different from `@import`. You
 * can place `@import-theme` rule at any in the CSS root, and the content of
 * imported theme will always append to the beginning of CSS.
 *
 * @alias module:postcss/import/parse
 */
const plugin = postcss.plugin(
  'marpit-postcss-import-parse',
  () => (css, ret) => {
    const imports = { import: [], importTheme: [] }
    let allowImport = true

    css.walk(node => {
      if (node.type === 'atrule') {
        const push = target => {
          const [quote] = node.params
          if (quote !== '"' && quote !== "'") return

          const splitedValue = node.params.slice(1).split(quote)
          let value = ''

          splitedValue.every(v => {
            if (v.endsWith('\\')) {
              value = `${value}${v.slice(0, -1)}${quote}`
              return true
            }
            value = `${value}${v}`
            return false
          })

          node.marpitImportParse = value
          target.push({ node, value })
        }

        if (allowImport) {
          if (node.name === 'import') {
            push(imports.import)
          } else if (node.name !== 'charset') {
            allowImport = false
          }
        }

        if (node.name === 'import-theme' && node.parent.type === 'root') {
          push(imports.importTheme)
        }
      } else if (node.type !== 'comment') {
        allowImport = false
      }
    })

    ret.marpitImport = [...imports.importTheme, ...imports.import]
  }
)

export default plugin