parse-statements.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. "use strict"
  2. // external tooling
  3. const valueParser = require("postcss-value-parser")
  4. // extended tooling
  5. const { stringify } = valueParser
  6. function split(params, start) {
  7. const list = []
  8. const last = params.reduce((item, node, index) => {
  9. if (index < start) return ""
  10. if (node.type === "div" && node.value === ",") {
  11. list.push(item)
  12. return ""
  13. }
  14. return item + stringify(node)
  15. }, "")
  16. list.push(last)
  17. return list
  18. }
  19. module.exports = function (result, styles) {
  20. const statements = []
  21. let nodes = []
  22. styles.each(node => {
  23. let stmt
  24. if (node.type === "atrule") {
  25. if (node.name === "import") stmt = parseImport(result, node)
  26. else if (node.name === "media") stmt = parseMedia(result, node)
  27. else if (node.name === "charset") stmt = parseCharset(result, node)
  28. }
  29. if (stmt) {
  30. if (nodes.length) {
  31. statements.push({
  32. type: "nodes",
  33. nodes,
  34. media: [],
  35. layer: [],
  36. })
  37. nodes = []
  38. }
  39. statements.push(stmt)
  40. } else nodes.push(node)
  41. })
  42. if (nodes.length) {
  43. statements.push({
  44. type: "nodes",
  45. nodes,
  46. media: [],
  47. layer: [],
  48. })
  49. }
  50. return statements
  51. }
  52. function parseMedia(result, atRule) {
  53. const params = valueParser(atRule.params).nodes
  54. return {
  55. type: "media",
  56. node: atRule,
  57. media: split(params, 0),
  58. layer: [],
  59. }
  60. }
  61. function parseCharset(result, atRule) {
  62. if (atRule.prev()) {
  63. return result.warn("@charset must precede all other statements", {
  64. node: atRule,
  65. })
  66. }
  67. return {
  68. type: "charset",
  69. node: atRule,
  70. media: [],
  71. layer: [],
  72. }
  73. }
  74. function parseImport(result, atRule) {
  75. let prev = atRule.prev()
  76. if (prev) {
  77. do {
  78. if (
  79. prev.type !== "comment" &&
  80. (prev.type !== "atrule" ||
  81. (prev.name !== "import" &&
  82. prev.name !== "charset" &&
  83. !(prev.name === "layer" && !prev.nodes)))
  84. ) {
  85. return result.warn(
  86. "@import must precede all other statements (besides @charset or empty @layer)",
  87. { node: atRule }
  88. )
  89. }
  90. prev = prev.prev()
  91. } while (prev)
  92. }
  93. if (atRule.nodes) {
  94. return result.warn(
  95. "It looks like you didn't end your @import statement correctly. " +
  96. "Child nodes are attached to it.",
  97. { node: atRule }
  98. )
  99. }
  100. const params = valueParser(atRule.params).nodes
  101. const stmt = {
  102. type: "import",
  103. node: atRule,
  104. media: [],
  105. layer: [],
  106. }
  107. // prettier-ignore
  108. if (
  109. !params.length ||
  110. (
  111. params[0].type !== "string" ||
  112. !params[0].value
  113. ) &&
  114. (
  115. params[0].type !== "function" ||
  116. params[0].value !== "url" ||
  117. !params[0].nodes.length ||
  118. !params[0].nodes[0].value
  119. )
  120. ) {
  121. return result.warn(`Unable to find uri in '${ atRule.toString() }'`, {
  122. node: atRule,
  123. })
  124. }
  125. if (params[0].type === "string") stmt.uri = params[0].value
  126. else stmt.uri = params[0].nodes[0].value
  127. stmt.fullUri = stringify(params[0])
  128. let remainder = params
  129. if (remainder.length > 2) {
  130. if (
  131. (remainder[2].type === "word" || remainder[2].type === "function") &&
  132. remainder[2].value === "layer"
  133. ) {
  134. if (remainder[1].type !== "space") {
  135. return result.warn("Invalid import layer statement", { node: atRule })
  136. }
  137. if (remainder[2].nodes) {
  138. stmt.layer = [stringify(remainder[2].nodes)]
  139. } else {
  140. stmt.layer = [""]
  141. }
  142. remainder = remainder.slice(2)
  143. }
  144. }
  145. if (remainder.length > 2) {
  146. if (remainder[1].type !== "space") {
  147. return result.warn("Invalid import media statement", { node: atRule })
  148. }
  149. stmt.media = split(remainder, 2)
  150. }
  151. return stmt
  152. }