Repository: postcss/postcss-import
Pull request #533 (2023-07-20 09:45:53)

add c8 test coverage
File                    | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
------------------------|---------|----------|---------|---------|-------------------
All files               |   99.48 |    99.26 |     100 |   99.48 |                   
 postcss-import         |     100 |      100 |     100 |     100 |                   
  index.js              |     100 |      100 |     100 |     100 |                   
 postcss-import/lib     |   98.87 |    98.49 |     100 |   98.87 |                   
  assign-layer-names.js |     100 |      100 |     100 |     100 |                   
  data-url.js           |     100 |      100 |     100 |     100 |                   
  join-layer.js         |     100 |      100 |     100 |     100 |                   
  join-media.js         |     100 |      100 |     100 |     100 |                   
  load-content.js       |     100 |      100 |     100 |     100 |                   
  parse-statements.js   |   97.67 |    96.66 |     100 |   97.67 | 151-152,165-166   
  process-content.js    |     100 |      100 |     100 |     100 |                   
  resolve-id.js         |     100 |      100 |     100 |     100 |      


Patch:
diff --git a/index.js b/index.js
index d324a7e..cc1237d 100755
--- a/index.js
+++ b/index.js
@@ -3,14 +3,12 @@
 const path = require("path")
 
 // internal tooling
-const joinMedia = require("./lib/join-media")
-const joinLayer = require("./lib/join-layer")
-const resolveId = require("./lib/resolve-id")
+const applyMedia = require("./lib/apply-media")
+const applyRaws = require("./lib/apply-raws")
+const applyStyles = require("./lib/apply-styles")
 const loadContent = require("./lib/load-content")
-const processContent = require("./lib/process-content")
-const parseStatements = require("./lib/parse-statements")
-const assignLayerNames = require("./lib/assign-layer-names")
-const dataURL = require("./lib/data-url")
+const parseStyles = require("./lib/parse-styles")
+const resolveId = require("./lib/resolve-id")
 
 function AtImport(options) {
   options = {
@@ -36,7 +34,7 @@ function AtImport(options) {
 
   return {
     postcssPlugin: "postcss-import",
-    Once(styles, { result, atRule, postcss }) {
+    async Once(styles, { result, atRule, postcss }) {
       const state = {
         importedFiles: {},
         hashFiles: {},
@@ -57,360 +55,19 @@ function AtImport(options) {
         throw new Error("nameLayer option must be a function")
       }
 
-      return parseStyles(result, styles, options, state, [], []).then(
-        bundle => {
-          applyRaws(bundle)
-          applyMedia(bundle)
-          applyStyles(bundle, styles)
-        }
+      const bundle = await parseStyles(
+        result,
+        styles,
+        options,
+        state,
+        [],
+        [],
+        postcss
       )
 
-      function applyRaws(bundle) {
-        bundle.forEach((stmt, index) => {
-          if (index === 0) return
-
-          if (stmt.parent) {
-            const { before } = stmt.parent.node.raws
-            if (stmt.type === "nodes") stmt.nodes[0].raws.before = before
-            else stmt.node.raws.before = before
-          } else if (stmt.type === "nodes") {
-            stmt.nodes[0].raws.before = stmt.nodes[0].raws.before || "\n"
-          }
-        })
-      }
-
-      function applyMedia(bundle) {
-        bundle.forEach(stmt => {
-          if (
-            (!stmt.media.length && !stmt.layer.length) ||
-            stmt.type === "charset"
-          ) {
-            return
-          }
-
-          if (stmt.layer.length > 1) {
-            assignLayerNames(stmt.layer, stmt.node, state, options)
-          }
-
-          if (stmt.type === "import") {
-            const parts = [stmt.fullUri]
-
-            const media = stmt.media.join(", ")
-
-            if (stmt.layer.length) {
-              const layerName = stmt.layer.join(".")
-
-              let layerParams = "layer"
-              if (layerName) {
-                layerParams = `layer(${layerName})`
-              }
-
-              parts.push(layerParams)
-            }
-
-            if (media) {
-              parts.push(media)
-            }
-
-            stmt.node.params = parts.join(" ")
-          } else if (stmt.type === "media") {
-            if (stmt.layer.length) {
-              const layerNode = atRule({
-                name: "layer",
-                params: stmt.layer.join("."),
-                source: stmt.node.source,
-              })
-
-              if (stmt.parentMedia?.length) {
-                const mediaNode = atRule({
-                  name: "media",
-                  params: stmt.parentMedia.join(", "),
-                  source: stmt.node.source,
-                })
-
-                mediaNode.append(layerNode)
-                layerNode.append(stmt.node)
-                stmt.node = mediaNode
-              } else {
-                layerNode.append(stmt.node)
-                stmt.node = layerNode
-              }
-            } else {
-              stmt.node.params = stmt.media.join(", ")
-            }
-          } else {
-            const { nodes } = stmt
-            const { parent } = nodes[0]
-
-            let outerAtRule
-            let innerAtRule
-            if (stmt.media.length && stmt.layer.length) {
-              const mediaNode = atRule({
-                name: "media",
-                params: stmt.media.join(", "),
-                source: parent.source,
-              })
-
-              const layerNode = atRule({
-                name: "layer",
-                params: stmt.layer.join("."),
-                source: parent.source,
-              })
-
-              mediaNode.append(layerNode)
-              innerAtRule = layerNode
-              outerAtRule = mediaNode
-            } else if (stmt.media.length) {
-              const mediaNode = atRule({
-                name: "media",
-                params: stmt.media.join(", "),
-                source: parent.source,
-              })
-
-              innerAtRule = mediaNode
-              outerAtRule = mediaNode
-            } else if (stmt.layer.length) {
-              const layerNode = atRule({
-                name: "layer",
-                params: stmt.layer.join("."),
-                source: parent.source,
-              })
-
-              innerAtRule = layerNode
-              outerAtRule = layerNode
-            }
-
-            parent.insertBefore(nodes[0], outerAtRule)
-
-            // remove nodes
-            nodes.forEach(node => {
-              node.parent = undefined
-            })
-
-            // better output
-            nodes[0].raws.before = nodes[0].raws.before || "\n"
-
-            // wrap new rules with media query and/or layer at rule
-            innerAtRule.append(nodes)
-
-            stmt.type = "media"
-            stmt.node = outerAtRule
-            delete stmt.nodes
-          }
-        })
-      }
-
-      function applyStyles(bundle, styles) {
-        styles.nodes = []
-
-        // Strip additional statements.
-        bundle.forEach(stmt => {
-          if (["charset", "import", "media"].includes(stmt.type)) {
-            stmt.node.parent = undefined
-            styles.append(stmt.node)
-          } else if (stmt.type === "nodes") {
-            stmt.nodes.forEach(node => {
-              node.parent = undefined
-              styles.append(node)
-            })
-          }
-        })
-      }
-
-      function parseStyles(result, styles, options, state, media, layer) {
-        const statements = parseStatements(result, styles)
-
-        return Promise.resolve(statements)
-          .then(stmts => {
-            // process each statement in series
-            return stmts.reduce((promise, stmt) => {
-              return promise.then(() => {
-                stmt.media = joinMedia(media, stmt.media || [])
-                stmt.parentMedia = media
-                stmt.layer = joinLayer(layer, stmt.layer || [])
-
-                // skip protocol base uri (protocol://url) or protocol-relative
-                if (
-                  stmt.type !== "import" ||
-                  /^(?:[a-z]+:)?\/\//i.test(stmt.uri)
-                ) {
-                  return
-                }
-
-                if (options.filter && !options.filter(stmt.uri)) {
-                  // rejected by filter
-                  return
-                }
-
-                return resolveImportId(result, stmt, options, state)
-              })
-            }, Promise.resolve())
-          })
-          .then(() => {
-            let charset
-            const imports = []
-            const bundle = []
-
-            function handleCharset(stmt) {
-              if (!charset) charset = stmt
-              // charsets aren't case-sensitive, so convert to lower case to compare
-              else if (
-                stmt.node.params.toLowerCase() !==
-                charset.node.params.toLowerCase()
-              ) {
-                throw new Error(
-                  `Incompatable @charset statements:
-  ${stmt.node.params} specified in ${stmt.node.source.input.file}
-  ${charset.node.params} specified in ${charset.node.source.input.file}`
-                )
-              }
-            }
-
-            // squash statements and their children
-            statements.forEach(stmt => {
-              if (stmt.type === "charset") handleCharset(stmt)
-              else if (stmt.type === "import") {
-                if (stmt.children) {
-                  stmt.children.forEach((child, index) => {
-                    if (child.type === "import") imports.push(child)
-                    else if (child.type === "charset") handleCharset(child)
-                    else bundle.push(child)
-                    // For better output
-                    if (index === 0) child.parent = stmt
-                  })
-                } else imports.push(stmt)
-              } else if (stmt.type === "media" || stmt.type === "nodes") {
-                bundle.push(stmt)
-              }
-            })
-
-            return charset
-              ? [charset, ...imports.concat(bundle)]
-              : imports.concat(bundle)
-          })
-      }
-
-      function resolveImportId(result, stmt, options, state) {
-        if (dataURL.isValid(stmt.uri)) {
-          return loadImportContent(result, stmt, stmt.uri, options, state).then(
-            result => {
-              stmt.children = result
-            }
-          )
-        }
-
-        const atRule = stmt.node
-        let sourceFile
-        if (atRule.source?.input?.file) {
-          sourceFile = atRule.source.input.file
-        }
-        const base = sourceFile
-          ? path.dirname(atRule.source.input.file)
-          : options.root
-
-        return Promise.resolve(options.resolve(stmt.uri, base, options))
-          .then(paths => {
-            if (!Array.isArray(paths)) paths = [paths]
-            // Ensure that each path is absolute:
-            return Promise.all(
-              paths.map(file => {
-                return !path.isAbsolute(file)
-                  ? resolveId(file, base, options)
-                  : file
-              })
-            )
-          })
-          .then(resolved => {
-            // Add dependency messages:
-            resolved.forEach(file => {
-              result.messages.push({
-                type: "dependency",
-                plugin: "postcss-import",
-                file,
-                parent: sourceFile,
-              })
-            })
-
-            return Promise.all(
-              resolved.map(file => {
-                return loadImportContent(result, stmt, file, options, state)
-              })
-            )
-          })
-          .then(result => {
-            // Merge loaded statements
-            stmt.children = result.reduce((result, statements) => {
-              return statements ? result.concat(statements) : result
-            }, [])
-          })
-      }
-
-      function loadImportContent(result, stmt, filename, options, state) {
-        const atRule = stmt.node
-        const { media, layer } = stmt
-
-        assignLayerNames(layer, atRule, state, options)
-
-        if (options.skipDuplicates) {
-          // skip files already imported at the same scope
-          if (state.importedFiles[filename]?.[media]?.[layer]) {
-            return
-          }
-
-          // save imported files to skip them next time
-          if (!state.importedFiles[filename]) {
-            state.importedFiles[filename] = {}
-          }
-          if (!state.importedFiles[filename][media]) {
-            state.importedFiles[filename][media] = {}
-          }
-          state.importedFiles[filename][media][layer] = true
-        }
-
-        return Promise.resolve(options.load(filename, options)).then(
-          content => {
-            if (content.trim() === "") {
-              result.warn(`${filename} is empty`, { node: atRule })
-              return
-            }
-
-            // skip previous imported files not containing @import rules
-            if (state.hashFiles[content]?.[media]?.[layer]) {
-              return
-            }
-
-            return processContent(
-              result,
-              content,
-              filename,
-              options,
-              postcss
-            ).then(importedResult => {
-              const styles = importedResult.root
-              result.messages = result.messages.concat(importedResult.messages)
-
-              if (options.skipDuplicates) {
-                const hasImport = styles.some(child => {
-                  return child.type === "atrule" && child.name === "import"
-                })
-                if (!hasImport) {
-                  // save hash files to skip them next time
-                  if (!state.hashFiles[content]) {
-                    state.hashFiles[content] = {}
-                  }
-                  if (!state.hashFiles[content][media]) {
-                    state.hashFiles[content][media] = {}
-                  }
-                  state.hashFiles[content][media][layer] = true
-                }
-              }
-
-              // recursion: import @import from imported file
-              return parseStyles(result, styles, options, state, media, layer)
-            })
-          }
-        )
-      }
+      applyRaws(bundle)
+      applyMedia(bundle, options, state, atRule)
+      applyStyles(bundle, styles)
     },
   }
 }
diff --git a/lib/apply-media.js b/lib/apply-media.js
new file mode 100644
index 0000000..08f5f1e
--- /dev/null
+++ b/lib/apply-media.js
@@ -0,0 +1,113 @@
+"use strict"
+
+const assignLayerNames = require("./assign-layer-names")
+
+module.exports = function applyMedia(bundle, options, state, atRule) {
+  bundle.forEach(stmt => {
+    if ((!stmt.media.length && !stmt.layer.length) || stmt.type === "charset") {
+      return
+    }
+
+    if (stmt.layer.length > 1) {
+      assignLayerNames(stmt.layer, stmt.node, state, options)
+    }
+
+    if (stmt.type === "import") {
+      const parts = [stmt.fullUri]
+
+      const media = stmt.media.join(", ")
+
+      if (stmt.layer.length) {
+        const layerName = stmt.layer.join(".")
+
+        let layerParams = "layer"
+        if (layerName) {
+          layerParams = `layer(${layerName})`
+        }
+
+        parts.push(layerParams)
+      }
+
+      if (media) {
+        parts.push(media)
+      }
+
+      stmt.node.params = parts.join(" ")
+    } else if (stmt.type === "media") {
+      if (stmt.layer.length) {
+        const layerNode = atRule({
+          name: "layer",
+          params: stmt.layer.join("."),
+          source: stmt.node.source,
+        })
+
+        if (stmt.parentMedia?.length) {
+          const mediaNode = atRule({
+            name: "media",
+            params: stmt.parentMedia.join(", "),
+            source: stmt.node.source,
+          })
+
+          mediaNode.append(layerNode)
+          layerNode.append(stmt.node)
+          stmt.node = mediaNode
+        } else {
+          layerNode.append(stmt.node)
+          delete stmt.node
+          stmt.nodes = [layerNode]
+          stmt.type = "nodes"
+        }
+      } else {
+        stmt.node.params = stmt.media.join(", ")
+      }
+    } else {
+      const { nodes } = stmt
+      const { parent } = nodes[0]
+
+      const atRules = []
+
+      if (stmt.media.length) {
+        const mediaNode = atRule({
+          name: "media",
+          params: stmt.media.join(", "),
+          source: parent.source,
+        })
+
+        atRules.push(mediaNode)
+      }
+
+      if (stmt.layer.length) {
+        const layerNode = atRule({
+          name: "layer",
+          params: stmt.layer.join("."),
+          source: parent.source,
+        })
+
+        atRules.push(layerNode)
+      }
+
+      const outerAtRule = atRules.shift()
+      const innerAtRule = atRules.reduce((previous, next) => {
+        previous.append(next)
+        return next
+      }, outerAtRule)
+
+      parent.insertBefore(nodes[0], outerAtRule)
+
+      // remove nodes
+      nodes.forEach(node => {
+        node.parent = undefined
+      })
+
+      // better output
+      nodes[0].raws.before = nodes[0].raws.before || "\n"
+
+      // wrap new rules with media query and/or layer at rule
+      innerAtRule.append(nodes)
+
+      stmt.type = "media"
+      stmt.node = outerAtRule
+      delete stmt.nodes
+    }
+  })
+}
diff --git a/lib/apply-raws.js b/lib/apply-raws.js
new file mode 100644
index 0000000..12caaa8
--- /dev/null
+++ b/lib/apply-raws.js
@@ -0,0 +1,15 @@
+"use strict"
+
+module.exports = function applyRaws(bundle) {
+  bundle.forEach((stmt, index) => {
+    if (index === 0) return
+
+    if (stmt.parent) {
+      const { before } = stmt.parent.node.raws
+      if (stmt.type === "nodes") stmt.nodes[0].raws.before = before
+      else stmt.node.raws.before = before
+    } else if (stmt.type === "nodes") {
+      stmt.nodes[0].raws.before = stmt.nodes[0].raws.before || "\n"
+    }
+  })
+}
diff --git a/lib/apply-styles.js b/lib/apply-styles.js
new file mode 100644
index 0000000..2ed2c69
--- /dev/null
+++ b/lib/apply-styles.js
@@ -0,0 +1,18 @@
+"use strict"
+
+module.exports = function applyStyles(bundle, styles) {
+  styles.nodes = []
+
+  // Strip additional statements.
+  bundle.forEach(stmt => {
+    if (["charset", "import", "media"].includes(stmt.type)) {
+      stmt.node.parent = undefined
+      styles.append(stmt.node)
+    } else if (stmt.type === "nodes") {
+      stmt.nodes.forEach(node => {
+        node.parent = undefined
+        styles.append(node)
+      })
+    }
+  })
+}
diff --git a/lib/assign-layer-names.js b/lib/assign-layer-names.js
index 18cfcc7..5e0bef4 100644
--- a/lib/assign-layer-names.js
+++ b/lib/assign-layer-names.js
@@ -1,6 +1,6 @@
 "use strict"
 
-module.exports = function (layer, node, state, options) {
+module.exports = function assignLayerNames(layer, node, state, options) {
   layer.forEach((layerPart, i) => {
     if (layerPart.trim() === "") {
       if (options.nameLayer) {
diff --git a/lib/join-layer.js b/lib/join-layer.js
index 9d91519..579c5f0 100644
--- a/lib/join-layer.js
+++ b/lib/join-layer.js
@@ -1,6 +1,6 @@
 "use strict"
 
-module.exports = function (parentLayer, childLayer) {
+module.exports = function joinLayer(parentLayer, childLayer) {
   if (!parentLayer.length && childLayer.length) return childLayer
   if (parentLayer.length && !childLayer.length) return parentLayer
   if (!parentLayer.length && !childLayer.length) return []
diff --git a/lib/join-media.js b/lib/join-media.js
index fcaaecd..0eff3c9 100644
--- a/lib/join-media.js
+++ b/lib/join-media.js
@@ -2,7 +2,7 @@
 
 const startsWithKeywordRegexp = /^(all|not|only|print|screen)/i
 
-module.exports = function (parentMedia, childMedia) {
+module.exports = function joinMedia(parentMedia, childMedia) {
   if (!parentMedia.length && childMedia.length) return childMedia
   if (parentMedia.length && !childMedia.length) return parentMedia
   if (!parentMedia.length && !childMedia.length) return []
diff --git a/lib/load-content.js b/lib/load-content.js
index c10b57e..d138afd 100644
--- a/lib/load-content.js
+++ b/lib/load-content.js
@@ -3,7 +3,7 @@
 const readCache = require("read-cache")
 const dataURL = require("./data-url")
 
-module.exports = filename => {
+module.exports = function loadContent(filename) {
   if (dataURL.isValid(filename)) {
     return dataURL.contents(filename)
   }
diff --git a/lib/parse-statements.js b/lib/parse-statements.js
index 0c94e5a..aa66e5b 100644
--- a/lib/parse-statements.js
+++ b/lib/parse-statements.js
@@ -20,7 +20,7 @@ function split(params, start) {
   return list
 }
 
-module.exports = function (result, styles) {
+module.exports = function parseStatements(result, styles) {
   const statements = []
   let nodes = []
 
@@ -113,59 +113,97 @@ function parseImport(result, atRule) {
   const params = valueParser(atRule.params).nodes
   const stmt = {
     type: "import",
+    uri: "",
+    fullUri: "",
     node: atRule,
     media: [],
     layer: [],
+    supports: [],
   }
 
-  // prettier-ignore
-  if (
-    !params.length ||
-    (
-      params[0].type !== "string" ||
-      !params[0].value
-    ) &&
-    (
-      params[0].type !== "function" ||
-      params[0].value !== "url" ||
-      !params[0].nodes.length ||
-      !params[0].nodes[0].value
-    )
-  ) {
-    return result.warn(`Unable to find uri in '${  atRule.toString()  }'`, {
-      node: atRule,
-    })
-  }
+  for (let i = 0; i < params.length; i++) {
+    const node = params[i]
 
-  if (params[0].type === "string") stmt.uri = params[0].value
-  else stmt.uri = params[0].nodes[0].value
-  stmt.fullUri = stringify(params[0])
+    if (node.type === "space" || node.type === "comment") continue
 
-  let remainder = params
-  if (remainder.length > 2) {
-    if (
-      (remainder[2].type === "word" || remainder[2].type === "function") &&
-      remainder[2].value === "layer"
-    ) {
-      if (remainder[1].type !== "space") {
-        return result.warn("Invalid import layer statement", { node: atRule })
+    if (node.type === "string") {
+      if (stmt.uri) {
+        return result.warn(`Multiple url's in '${atRule.toString()}'`, {
+          node: atRule,
+        })
+      }
+
+      if (!node.value) {
+        return result.warn(`Unable to find uri in '${atRule.toString()}'`, {
+          node: atRule,
+        })
+      }
+
+      stmt.uri = node.value
+      stmt.fullUri = stringify(node)
+      continue
+    }
+
+    if (node.type === "function" && /^url$/i.test(node.value)) {
+      if (stmt.uri) {
+        return result.warn(`Multiple url's in '${atRule.toString()}'`, {
+          node: atRule,
+        })
+      }
+
+      if (!node.nodes?.[0]?.value) {
+        return result.warn(`Unable to find uri in '${atRule.toString()}'`, {
+          node: atRule,
+        })
       }
 
-      if (remainder[2].nodes) {
-        stmt.layer = [stringify(remainder[2].nodes)]
+      stmt.uri = node.nodes[0].value
+      stmt.fullUri = stringify(node)
+      continue
+    }
+
+    if (!stmt.uri) {
+      return result.warn(`Unable to find uri in '${atRule.toString()}'`, {
+        node: atRule,
+      })
+    }
+
+    if (
+      (node.type === "word" || node.type === "function") &&
+      /^layer$/i.test(node.value)
+    ) {
+      if (node.nodes) {
+        stmt.layer = [stringify(node.nodes)]
       } else {
         stmt.layer = [""]
       }
-      remainder = remainder.slice(2)
+
+      continue
     }
-  }
 
-  if (remainder.length > 2) {
-    if (remainder[1].type !== "space") {
-      return result.warn("Invalid import media statement", { node: atRule })
+    if (node.type === "function" && /^supports$/i.test(node.value)) {
+      stmt.supports = [stringify(node.nodes)]
+
+      continue
     }
 
-    stmt.media = split(remainder, 2)
+    stmt.media = split(params, i)
+    break
+  }
+
+  if (!stmt.uri) {
+    return result.warn(`Unable to find uri in '${atRule.toString()}'`, {
+      node: atRule,
+    })
+  }
+
+  if (stmt.supports.length) {
+    return result.warn(
+      `Supports conditions are not implemented at this time.`,
+      {
+        node: atRule,
+      }
+    )
   }
 
   return stmt
diff --git a/lib/parse-styles.js b/lib/parse-styles.js
new file mode 100644
index 0000000..50b74eb
--- /dev/null
+++ b/lib/parse-styles.js
@@ -0,0 +1,207 @@
+"use strict"
+
+const path = require("path")
+
+const assignLayerNames = require("./assign-layer-names")
+const dataURL = require("./data-url")
+const joinLayer = require("./join-layer")
+const joinMedia = require("./join-media")
+const parseStatements = require("./parse-statements")
+const processContent = require("./process-content")
+const resolveId = require("./resolve-id")
+
+async function parseStyles(
+  result,
+  styles,
+  options,
+  state,
+  media,
+  layer,
+  postcss
+) {
+  const statements = parseStatements(result, styles)
+
+  for (const stmt of statements) {
+    stmt.media = joinMedia(media, stmt.media || [])
+    stmt.parentMedia = media
+    stmt.layer = joinLayer(layer, stmt.layer || [])
+
+    // skip protocol base uri (protocol://url) or protocol-relative
+    if (stmt.type !== "import" || /^(?:[a-z]+:)?\/\//i.test(stmt.uri)) {
+      continue
+    }
+
+    if (options.filter && !options.filter(stmt.uri)) {
+      // rejected by filter
+      continue
+    }
+
+    await resolveImportId(result, stmt, options, state, postcss)
+  }
+
+  let charset
+  const imports = []
+  const bundle = []
+
+  function handleCharset(stmt) {
+    if (!charset) charset = stmt
+    // charsets aren't case-sensitive, so convert to lower case to compare
+    else if (
+      stmt.node.params.toLowerCase() !== charset.node.params.toLowerCase()
+    ) {
+      throw new Error(
+        `Incompatable @charset statements:
+  ${stmt.node.params} specified in ${stmt.node.source.input.file}
+  ${charset.node.params} specified in ${charset.node.source.input.file}`
+      )
+    }
+  }
+
+  // squash statements and their children
+  statements.forEach(stmt => {
+    if (stmt.type === "charset") handleCharset(stmt)
+    else if (stmt.type === "import") {
+      if (stmt.children) {
+        stmt.children.forEach((child, index) => {
+          if (child.type === "import") imports.push(child)
+          else if (child.type === "charset") handleCharset(child)
+          else bundle.push(child)
+          // For better output
+          if (index === 0) child.parent = stmt
+        })
+      } else imports.push(stmt)
+    } else if (stmt.type === "media" || stmt.type === "nodes") {
+      bundle.push(stmt)
+    }
+  })
+
+  return charset ? [charset, ...imports.concat(bundle)] : imports.concat(bundle)
+}
+
+async function resolveImportId(result, stmt, options, state, postcss) {
+  if (dataURL.isValid(stmt.uri)) {
+    // eslint-disable-next-line require-atomic-updates
+    stmt.children = await loadImportContent(
+      result,
+      stmt,
+      stmt.uri,
+      options,
+      state,
+      postcss
+    )
+
+    return
+  }
+
+  const atRule = stmt.node
+  let sourceFile
+  if (atRule.source?.input?.file) {
+    sourceFile = atRule.source.input.file
+  }
+  const base = sourceFile
+    ? path.dirname(atRule.source.input.file)
+    : options.root
+
+  const paths = [await options.resolve(stmt.uri, base, options)].flat()
+
+  // Ensure that each path is absolute:
+  const resolved = await Promise.all(
+    paths.map(file => {
+      return !path.isAbsolute(file) ? resolveId(file, base, options) : file
+    })
+  )
+
+  // Add dependency messages:
+  resolved.forEach(file => {
+    result.messages.push({
+      type: "dependency",
+      plugin: "postcss-import",
+      file,
+      parent: sourceFile,
+    })
+  })
+
+  const importedContent = await Promise.all(
+    resolved.map(file => {
+      return loadImportContent(result, stmt, file, options, state, postcss)
+    })
+  )
+
+  // Merge loaded statements
+  // eslint-disable-next-line require-atomic-updates
+  stmt.children = importedContent.flat().filter(x => !!x)
+}
+
+async function loadImportContent(
+  result,
+  stmt,
+  filename,
+  options,
+  state,
+  postcss
+) {
+  const atRule = stmt.node
+  const { media, layer } = stmt
+
+  assignLayerNames(layer, atRule, state, options)
+
+  if (options.skipDuplicates) {
+    // skip files already imported at the same scope
+    if (state.importedFiles[filename]?.[media]?.[layer]) {
+      return
+    }
+
+    // save imported files to skip them next time
+    if (!state.importedFiles[filename]) {
+      state.importedFiles[filename] = {}
+    }
+    if (!state.importedFiles[filename][media]) {
+      state.importedFiles[filename][media] = {}
+    }
+    state.importedFiles[filename][media][layer] = true
+  }
+
+  const content = await options.load(filename, options)
+
+  if (content.trim() === "") {
+    result.warn(`${filename} is empty`, { node: atRule })
+    return
+  }
+
+  // skip previous imported files not containing @import rules
+  if (state.hashFiles[content]?.[media]?.[layer]) {
+    return
+  }
+
+  const importedResult = await processContent(
+    result,
+    content,
+    filename,
+    options,
+    postcss
+  )
+
+  const styles = importedResult.root
+  result.messages = result.messages.concat(importedResult.messages)
+
+  if (options.skipDuplicates) {
+    const hasImport = styles.some(child => {
+      return child.type === "atrule" && child.name === "import"
+    })
+    if (!hasImport) {
+      // save hash files to skip them next time
+      if (!state.hashFiles[content]) {
+        state.hashFiles[content] = {}
+      }
+      if (!state.hashFiles[content][media]) {
+        state.hashFiles[content][media] = {}
+      }
+      state.hashFiles[content][media][layer] = true
+    }
+  }
+
+  // recursion: import @import from imported file
+  return parseStyles(result, styles, options, state, media, layer, postcss)
+}
+
+module.exports = parseStyles
diff --git a/lib/process-content.js b/lib/process-content.js
index ec413e0..e0c2ac8 100644
--- a/lib/process-content.js
+++ b/lib/process-content.js
@@ -21,6 +21,7 @@ module.exports = function processContent(
   // SugarSS support:
   if (ext === ".sss") {
     if (!sugarss) {
+      /* c8 ignore next 3 */
       try {
         sugarss = require("sugarss")
       } catch {} // Ignore
diff --git a/lib/resolve-id.js b/lib/resolve-id.js
index ffef034..d6d1668 100644
--- a/lib/resolve-id.js
+++ b/lib/resolve-id.js
@@ -11,7 +11,7 @@ function resolveModule(id, opts) {
   })
 }
 
-module.exports = function (id, base, options) {
+module.exports = function resolveId(id, base, options) {
   const paths = options.path
 
   const resolveOpts = {
diff --git a/package.json b/package.json
index a81ea5f..11d8e96 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
   },
   "devDependencies": {
     "ava": "^5.0.0",
+    "c8": "^8.0.0",
     "eslint": "^8.2.0",
     "eslint-config-problems": "^7.0.0",
     "eslint-plugin-prettier": "^4.0.0",
@@ -42,7 +43,7 @@
     "ci": "eslint . && ava",
     "lint": "eslint . --fix",
     "pretest": "npm run lint",
-    "test": "ava"
+    "test": "c8 ava"
   },
   "eslintConfig": {
     "extends": "eslint-config-problems",


Test patch:
diff --git a/test/lint.js b/test/lint.js
index 71a7c3b..743b1b9 100644
--- a/test/lint.js
+++ b/test/lint.js
@@ -144,12 +144,14 @@ test("should warn on invalid url", t => {
       @import url();
       @import url('');
       @import url("");
+      @import layer url("");
+      @import supports(foo: bar) url("");
       `,
       { from: undefined }
     )
     .then(result => {
       const warnings = result.warnings()
-      t.is(warnings.length, 7)
+      t.is(warnings.length, 9)
       t.is(warnings[0].text, `Unable to find uri in '@import foo-bar'`)
       t.is(warnings[1].text, `Unable to find uri in '@import '`)
       t.is(warnings[2].text, `Unable to find uri in '@import '''`)
@@ -157,6 +159,65 @@ test("should warn on invalid url", t => {
       t.is(warnings[4].text, `Unable to find uri in '@import url()'`)
       t.is(warnings[5].text, `Unable to find uri in '@import url('')'`)
       t.is(warnings[6].text, `Unable to find uri in '@import url("")'`)
+      t.is(warnings[7].text, `Unable to find uri in '@import layer url("")'`)
+      t.is(
+        warnings[8].text,
+        `Unable to find uri in '@import supports(foo: bar) url("")'`
+      )
+    })
+})
+
+test("should warn on duplicate url's", t => {
+  return processor
+    .process(
+      `
+      @import 'foo' "bar";
+      @import "foo" url(bar);
+      @import url(foo) "bar";
+      @import url('foo') url(bar);
+      @import url('foo') layer url(bar);
+      @import url('foo') layer(foo) "bar";
+      @import url('foo') supports(foo: bar) url(bar);
+      `,
+      { from: undefined }
+    )
+    .then(result => {
+      const warnings = result.warnings()
+      t.is(warnings.length, 7)
+      t.is(warnings[0].text, `Multiple url's in '@import 'foo' "bar"'`)
+      t.is(warnings[1].text, `Multiple url's in '@import "foo" url(bar)'`)
+      t.is(warnings[2].text, `Multiple url's in '@import url(foo) "bar"'`)
+      t.is(warnings[3].text, `Multiple url's in '@import url('foo') url(bar)'`)
+      t.is(
+        warnings[4].text,
+        `Multiple url's in '@import url('foo') layer url(bar)'`
+      )
+      t.is(
+        warnings[5].text,
+        `Multiple url's in '@import url('foo') layer(foo) "bar"'`
+      )
+      t.is(
+        warnings[6].text,
+        `Multiple url's in '@import url('foo') supports(foo: bar) url(bar)'`
+      )
+    })
+})
+
+test("should warn on unimplemented features", t => {
+  return processor
+    .process(
+      `
+      @import url('foo') supports(foo: bar);
+      `,
+      { from: undefined }
+    )
+    .then(result => {
+      const warnings = result.warnings()
+      t.is(warnings.length, 1)
+      t.is(
+        warnings[0].text,
+        `Supports conditions are not implemented at this time.`
+      )
     })
 })