scss-parser.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. let { Comment } = require('postcss')
  2. let Parser = require('postcss/lib/parser')
  3. let NestedDeclaration = require('./nested-declaration')
  4. let scssTokenizer = require('./scss-tokenize')
  5. class ScssParser extends Parser {
  6. atrule(token) {
  7. let name = token[1]
  8. let prev = token
  9. while (!this.tokenizer.endOfFile()) {
  10. let next = this.tokenizer.nextToken()
  11. if (next[0] === 'word' && next[2] === prev[3] + 1) {
  12. name += next[1]
  13. prev = next
  14. } else {
  15. this.tokenizer.back(next)
  16. break
  17. }
  18. }
  19. super.atrule(['at-word', name, token[2], prev[3]])
  20. }
  21. comment(token) {
  22. if (token[4] === 'inline') {
  23. let node = new Comment()
  24. this.init(node, token[2])
  25. node.raws.inline = true
  26. let pos = this.input.fromOffset(token[3])
  27. node.source.end = {
  28. column: pos.col,
  29. line: pos.line,
  30. offset: token[3] + 1
  31. }
  32. let text = token[1].slice(2)
  33. if (/^\s*$/.test(text)) {
  34. node.text = ''
  35. node.raws.left = text
  36. node.raws.right = ''
  37. } else {
  38. let match = text.match(/^(\s*)([^]*\S)(\s*)$/)
  39. let fixed = match[2].replace(/(\*\/|\/\*)/g, '*//*')
  40. node.text = fixed
  41. node.raws.left = match[1]
  42. node.raws.right = match[3]
  43. node.raws.text = match[2]
  44. }
  45. } else {
  46. super.comment(token)
  47. }
  48. }
  49. createTokenizer() {
  50. this.tokenizer = scssTokenizer(this.input)
  51. }
  52. raw(node, prop, tokens, customProperty) {
  53. super.raw(node, prop, tokens, customProperty)
  54. if (node.raws[prop]) {
  55. let scss = node.raws[prop].raw
  56. node.raws[prop].raw = tokens.reduce((all, i) => {
  57. if (i[0] === 'comment' && i[4] === 'inline') {
  58. let text = i[1].slice(2).replace(/(\*\/|\/\*)/g, '*//*')
  59. return all + '/*' + text + '*/'
  60. } else {
  61. return all + i[1]
  62. }
  63. }, '')
  64. if (scss !== node.raws[prop].raw) {
  65. node.raws[prop].scss = scss
  66. }
  67. }
  68. }
  69. rule(tokens) {
  70. let withColon = false
  71. let brackets = 0
  72. let value = ''
  73. for (let i of tokens) {
  74. if (withColon) {
  75. if (i[0] !== 'comment' && i[0] !== '{') {
  76. value += i[1]
  77. }
  78. } else if (i[0] === 'space' && i[1].includes('\n')) {
  79. break
  80. } else if (i[0] === '(') {
  81. brackets += 1
  82. } else if (i[0] === ')') {
  83. brackets -= 1
  84. } else if (brackets === 0 && i[0] === ':') {
  85. withColon = true
  86. }
  87. }
  88. if (!withColon || value.trim() === '' || /^[#:A-Za-z-]/.test(value)) {
  89. super.rule(tokens)
  90. } else {
  91. tokens.pop()
  92. let node = new NestedDeclaration()
  93. this.init(node, tokens[0][2])
  94. let last
  95. for (let i = tokens.length - 1; i >= 0; i--) {
  96. if (tokens[i][0] !== 'space') {
  97. last = tokens[i]
  98. break
  99. }
  100. }
  101. if (last[3]) {
  102. let pos = this.input.fromOffset(last[3])
  103. node.source.end = {
  104. column: pos.col,
  105. line: pos.line,
  106. offset: last[3] + 1
  107. }
  108. } else {
  109. let pos = this.input.fromOffset(last[2])
  110. node.source.end = {
  111. column: pos.col,
  112. line: pos.line,
  113. offset: last[2] + 1
  114. }
  115. }
  116. while (tokens[0][0] !== 'word') {
  117. node.raws.before += tokens.shift()[1]
  118. }
  119. if (tokens[0][2]) {
  120. let pos = this.input.fromOffset(tokens[0][2])
  121. node.source.start = {
  122. column: pos.col,
  123. line: pos.line,
  124. offset: tokens[0][2]
  125. }
  126. }
  127. node.prop = ''
  128. while (tokens.length) {
  129. let type = tokens[0][0]
  130. if (type === ':' || type === 'space' || type === 'comment') {
  131. break
  132. }
  133. node.prop += tokens.shift()[1]
  134. }
  135. node.raws.between = ''
  136. let token
  137. while (tokens.length) {
  138. token = tokens.shift()
  139. if (token[0] === ':') {
  140. node.raws.between += token[1]
  141. break
  142. } else {
  143. node.raws.between += token[1]
  144. }
  145. }
  146. if (node.prop[0] === '_' || node.prop[0] === '*') {
  147. node.raws.before += node.prop[0]
  148. node.prop = node.prop.slice(1)
  149. }
  150. node.raws.between += this.spacesAndCommentsFromStart(tokens)
  151. this.precheckMissedSemicolon(tokens)
  152. for (let i = tokens.length - 1; i > 0; i--) {
  153. token = tokens[i]
  154. if (token[1] === '!important') {
  155. node.important = true
  156. let string = this.stringFrom(tokens, i)
  157. string = this.spacesFromEnd(tokens) + string
  158. if (string !== ' !important') {
  159. node.raws.important = string
  160. }
  161. break
  162. } else if (token[1] === 'important') {
  163. let cache = tokens.slice(0)
  164. let str = ''
  165. for (let j = i; j > 0; j--) {
  166. let type = cache[j][0]
  167. if (str.trim().indexOf('!') === 0 && type !== 'space') {
  168. break
  169. }
  170. str = cache.pop()[1] + str
  171. }
  172. if (str.trim().indexOf('!') === 0) {
  173. node.important = true
  174. node.raws.important = str
  175. tokens = cache
  176. }
  177. }
  178. if (token[0] !== 'space' && token[0] !== 'comment') {
  179. break
  180. }
  181. }
  182. this.raw(node, 'value', tokens)
  183. if (node.value.includes(':')) {
  184. this.checkMissedSemicolon(tokens)
  185. }
  186. this.current = node
  187. }
  188. }
  189. }
  190. module.exports = ScssParser