PageRenderTime 43ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/node_modules/postcss/lib/parser.js

https://gitlab.com/ahmad.jamal/sally
JavaScript | 565 lines | 482 code | 77 blank | 6 comment | 142 complexity | ecac7e590bfe33db7042c86636ec7d57 MD5 | raw file
  1. 'use strict'
  2. let Declaration = require('./declaration')
  3. let tokenizer = require('./tokenize')
  4. let Comment = require('./comment')
  5. let AtRule = require('./at-rule')
  6. let Root = require('./root')
  7. let Rule = require('./rule')
  8. class Parser {
  9. constructor(input) {
  10. this.input = input
  11. this.root = new Root()
  12. this.current = this.root
  13. this.spaces = ''
  14. this.semicolon = false
  15. this.customProperty = false
  16. this.createTokenizer()
  17. this.root.source = { input, start: { offset: 0, line: 1, column: 1 } }
  18. }
  19. createTokenizer() {
  20. this.tokenizer = tokenizer(this.input)
  21. }
  22. parse() {
  23. let token
  24. while (!this.tokenizer.endOfFile()) {
  25. token = this.tokenizer.nextToken()
  26. switch (token[0]) {
  27. case 'space':
  28. this.spaces += token[1]
  29. break
  30. case ';':
  31. this.freeSemicolon(token)
  32. break
  33. case '}':
  34. this.end(token)
  35. break
  36. case 'comment':
  37. this.comment(token)
  38. break
  39. case 'at-word':
  40. this.atrule(token)
  41. break
  42. case '{':
  43. this.emptyRule(token)
  44. break
  45. default:
  46. this.other(token)
  47. break
  48. }
  49. }
  50. this.endFile()
  51. }
  52. comment(token) {
  53. let node = new Comment()
  54. this.init(node, token[2])
  55. node.source.end = this.getPosition(token[3] || token[2])
  56. let text = token[1].slice(2, -2)
  57. if (/^\s*$/.test(text)) {
  58. node.text = ''
  59. node.raws.left = text
  60. node.raws.right = ''
  61. } else {
  62. let match = text.match(/^(\s*)([^]*\S)(\s*)$/)
  63. node.text = match[2]
  64. node.raws.left = match[1]
  65. node.raws.right = match[3]
  66. }
  67. }
  68. emptyRule(token) {
  69. let node = new Rule()
  70. this.init(node, token[2])
  71. node.selector = ''
  72. node.raws.between = ''
  73. this.current = node
  74. }
  75. other(start) {
  76. let end = false
  77. let type = null
  78. let colon = false
  79. let bracket = null
  80. let brackets = []
  81. let customProperty = start[1].startsWith('--')
  82. let tokens = []
  83. let token = start
  84. while (token) {
  85. type = token[0]
  86. tokens.push(token)
  87. if (type === '(' || type === '[') {
  88. if (!bracket) bracket = token
  89. brackets.push(type === '(' ? ')' : ']')
  90. } else if (customProperty && colon && type === '{') {
  91. if (!bracket) bracket = token
  92. brackets.push('}')
  93. } else if (brackets.length === 0) {
  94. if (type === ';') {
  95. if (colon) {
  96. this.decl(tokens, customProperty)
  97. return
  98. } else {
  99. break
  100. }
  101. } else if (type === '{') {
  102. this.rule(tokens)
  103. return
  104. } else if (type === '}') {
  105. this.tokenizer.back(tokens.pop())
  106. end = true
  107. break
  108. } else if (type === ':') {
  109. colon = true
  110. }
  111. } else if (type === brackets[brackets.length - 1]) {
  112. brackets.pop()
  113. if (brackets.length === 0) bracket = null
  114. }
  115. token = this.tokenizer.nextToken()
  116. }
  117. if (this.tokenizer.endOfFile()) end = true
  118. if (brackets.length > 0) this.unclosedBracket(bracket)
  119. if (end && colon) {
  120. while (tokens.length) {
  121. token = tokens[tokens.length - 1][0]
  122. if (token !== 'space' && token !== 'comment') break
  123. this.tokenizer.back(tokens.pop())
  124. }
  125. this.decl(tokens, customProperty)
  126. } else {
  127. this.unknownWord(tokens)
  128. }
  129. }
  130. rule(tokens) {
  131. tokens.pop()
  132. let node = new Rule()
  133. this.init(node, tokens[0][2])
  134. node.raws.between = this.spacesAndCommentsFromEnd(tokens)
  135. this.raw(node, 'selector', tokens)
  136. this.current = node
  137. }
  138. decl(tokens, customProperty) {
  139. let node = new Declaration()
  140. this.init(node, tokens[0][2])
  141. let last = tokens[tokens.length - 1]
  142. if (last[0] === ';') {
  143. this.semicolon = true
  144. tokens.pop()
  145. }
  146. node.source.end = this.getPosition(last[3] || last[2])
  147. while (tokens[0][0] !== 'word') {
  148. if (tokens.length === 1) this.unknownWord(tokens)
  149. node.raws.before += tokens.shift()[1]
  150. }
  151. node.source.start = this.getPosition(tokens[0][2])
  152. node.prop = ''
  153. while (tokens.length) {
  154. let type = tokens[0][0]
  155. if (type === ':' || type === 'space' || type === 'comment') {
  156. break
  157. }
  158. node.prop += tokens.shift()[1]
  159. }
  160. node.raws.between = ''
  161. let token
  162. while (tokens.length) {
  163. token = tokens.shift()
  164. if (token[0] === ':') {
  165. node.raws.between += token[1]
  166. break
  167. } else {
  168. if (token[0] === 'word' && /\w/.test(token[1])) {
  169. this.unknownWord([token])
  170. }
  171. node.raws.between += token[1]
  172. }
  173. }
  174. if (node.prop[0] === '_' || node.prop[0] === '*') {
  175. node.raws.before += node.prop[0]
  176. node.prop = node.prop.slice(1)
  177. }
  178. let firstSpaces = this.spacesAndCommentsFromStart(tokens)
  179. this.precheckMissedSemicolon(tokens)
  180. for (let i = tokens.length - 1; i >= 0; i--) {
  181. token = tokens[i]
  182. if (token[1].toLowerCase() === '!important') {
  183. node.important = true
  184. let string = this.stringFrom(tokens, i)
  185. string = this.spacesFromEnd(tokens) + string
  186. if (string !== ' !important') node.raws.important = string
  187. break
  188. } else if (token[1].toLowerCase() === 'important') {
  189. let cache = tokens.slice(0)
  190. let str = ''
  191. for (let j = i; j > 0; j--) {
  192. let type = cache[j][0]
  193. if (str.trim().indexOf('!') === 0 && type !== 'space') {
  194. break
  195. }
  196. str = cache.pop()[1] + str
  197. }
  198. if (str.trim().indexOf('!') === 0) {
  199. node.important = true
  200. node.raws.important = str
  201. tokens = cache
  202. }
  203. }
  204. if (token[0] !== 'space' && token[0] !== 'comment') {
  205. break
  206. }
  207. }
  208. let hasWord = tokens.some(i => i[0] !== 'space' && i[0] !== 'comment')
  209. this.raw(node, 'value', tokens)
  210. if (hasWord) {
  211. node.raws.between += firstSpaces
  212. } else {
  213. node.value = firstSpaces + node.value
  214. }
  215. if (node.value.includes(':') && !customProperty) {
  216. this.checkMissedSemicolon(tokens)
  217. }
  218. }
  219. atrule(token) {
  220. let node = new AtRule()
  221. node.name = token[1].slice(1)
  222. if (node.name === '') {
  223. this.unnamedAtrule(node, token)
  224. }
  225. this.init(node, token[2])
  226. let type
  227. let prev
  228. let shift
  229. let last = false
  230. let open = false
  231. let params = []
  232. let brackets = []
  233. while (!this.tokenizer.endOfFile()) {
  234. token = this.tokenizer.nextToken()
  235. type = token[0]
  236. if (type === '(' || type === '[') {
  237. brackets.push(type === '(' ? ')' : ']')
  238. } else if (type === '{' && brackets.length > 0) {
  239. brackets.push('}')
  240. } else if (type === brackets[brackets.length - 1]) {
  241. brackets.pop()
  242. }
  243. if (brackets.length === 0) {
  244. if (type === ';') {
  245. node.source.end = this.getPosition(token[2])
  246. this.semicolon = true
  247. break
  248. } else if (type === '{') {
  249. open = true
  250. break
  251. } else if (type === '}') {
  252. if (params.length > 0) {
  253. shift = params.length - 1
  254. prev = params[shift]
  255. while (prev && prev[0] === 'space') {
  256. prev = params[--shift]
  257. }
  258. if (prev) {
  259. node.source.end = this.getPosition(prev[3] || prev[2])
  260. }
  261. }
  262. this.end(token)
  263. break
  264. } else {
  265. params.push(token)
  266. }
  267. } else {
  268. params.push(token)
  269. }
  270. if (this.tokenizer.endOfFile()) {
  271. last = true
  272. break
  273. }
  274. }
  275. node.raws.between = this.spacesAndCommentsFromEnd(params)
  276. if (params.length) {
  277. node.raws.afterName = this.spacesAndCommentsFromStart(params)
  278. this.raw(node, 'params', params)
  279. if (last) {
  280. token = params[params.length - 1]
  281. node.source.end = this.getPosition(token[3] || token[2])
  282. this.spaces = node.raws.between
  283. node.raws.between = ''
  284. }
  285. } else {
  286. node.raws.afterName = ''
  287. node.params = ''
  288. }
  289. if (open) {
  290. node.nodes = []
  291. this.current = node
  292. }
  293. }
  294. end(token) {
  295. if (this.current.nodes && this.current.nodes.length) {
  296. this.current.raws.semicolon = this.semicolon
  297. }
  298. this.semicolon = false
  299. this.current.raws.after = (this.current.raws.after || '') + this.spaces
  300. this.spaces = ''
  301. if (this.current.parent) {
  302. this.current.source.end = this.getPosition(token[2])
  303. this.current = this.current.parent
  304. } else {
  305. this.unexpectedClose(token)
  306. }
  307. }
  308. endFile() {
  309. if (this.current.parent) this.unclosedBlock()
  310. if (this.current.nodes && this.current.nodes.length) {
  311. this.current.raws.semicolon = this.semicolon
  312. }
  313. this.current.raws.after = (this.current.raws.after || '') + this.spaces
  314. }
  315. freeSemicolon(token) {
  316. this.spaces += token[1]
  317. if (this.current.nodes) {
  318. let prev = this.current.nodes[this.current.nodes.length - 1]
  319. if (prev && prev.type === 'rule' && !prev.raws.ownSemicolon) {
  320. prev.raws.ownSemicolon = this.spaces
  321. this.spaces = ''
  322. }
  323. }
  324. }
  325. // Helpers
  326. getPosition(offset) {
  327. let pos = this.input.fromOffset(offset)
  328. return {
  329. offset,
  330. line: pos.line,
  331. column: pos.col
  332. }
  333. }
  334. init(node, offset) {
  335. this.current.push(node)
  336. node.source = {
  337. start: this.getPosition(offset),
  338. input: this.input
  339. }
  340. node.raws.before = this.spaces
  341. this.spaces = ''
  342. if (node.type !== 'comment') this.semicolon = false
  343. }
  344. raw(node, prop, tokens) {
  345. let token, type
  346. let length = tokens.length
  347. let value = ''
  348. let clean = true
  349. let next, prev
  350. let pattern = /^([#.|])?(\w)+/i
  351. for (let i = 0; i < length; i += 1) {
  352. token = tokens[i]
  353. type = token[0]
  354. if (type === 'comment' && node.type === 'rule') {
  355. prev = tokens[i - 1]
  356. next = tokens[i + 1]
  357. if (
  358. prev[0] !== 'space' &&
  359. next[0] !== 'space' &&
  360. pattern.test(prev[1]) &&
  361. pattern.test(next[1])
  362. ) {
  363. value += token[1]
  364. } else {
  365. clean = false
  366. }
  367. continue
  368. }
  369. if (type === 'comment' || (type === 'space' && i === length - 1)) {
  370. clean = false
  371. } else {
  372. value += token[1]
  373. }
  374. }
  375. if (!clean) {
  376. let raw = tokens.reduce((all, i) => all + i[1], '')
  377. node.raws[prop] = { value, raw }
  378. }
  379. node[prop] = value
  380. }
  381. spacesAndCommentsFromEnd(tokens) {
  382. let lastTokenType
  383. let spaces = ''
  384. while (tokens.length) {
  385. lastTokenType = tokens[tokens.length - 1][0]
  386. if (lastTokenType !== 'space' && lastTokenType !== 'comment') break
  387. spaces = tokens.pop()[1] + spaces
  388. }
  389. return spaces
  390. }
  391. spacesAndCommentsFromStart(tokens) {
  392. let next
  393. let spaces = ''
  394. while (tokens.length) {
  395. next = tokens[0][0]
  396. if (next !== 'space' && next !== 'comment') break
  397. spaces += tokens.shift()[1]
  398. }
  399. return spaces
  400. }
  401. spacesFromEnd(tokens) {
  402. let lastTokenType
  403. let spaces = ''
  404. while (tokens.length) {
  405. lastTokenType = tokens[tokens.length - 1][0]
  406. if (lastTokenType !== 'space') break
  407. spaces = tokens.pop()[1] + spaces
  408. }
  409. return spaces
  410. }
  411. stringFrom(tokens, from) {
  412. let result = ''
  413. for (let i = from; i < tokens.length; i++) {
  414. result += tokens[i][1]
  415. }
  416. tokens.splice(from, tokens.length - from)
  417. return result
  418. }
  419. colon(tokens) {
  420. let brackets = 0
  421. let token, type, prev
  422. for (let [i, element] of tokens.entries()) {
  423. token = element
  424. type = token[0]
  425. if (type === '(') {
  426. brackets += 1
  427. }
  428. if (type === ')') {
  429. brackets -= 1
  430. }
  431. if (brackets === 0 && type === ':') {
  432. if (!prev) {
  433. this.doubleColon(token)
  434. } else if (prev[0] === 'word' && prev[1] === 'progid') {
  435. continue
  436. } else {
  437. return i
  438. }
  439. }
  440. prev = token
  441. }
  442. return false
  443. }
  444. // Errors
  445. unclosedBracket(bracket) {
  446. throw this.input.error('Unclosed bracket', bracket[2])
  447. }
  448. unknownWord(tokens) {
  449. throw this.input.error('Unknown word', tokens[0][2])
  450. }
  451. unexpectedClose(token) {
  452. throw this.input.error('Unexpected }', token[2])
  453. }
  454. unclosedBlock() {
  455. let pos = this.current.source.start
  456. throw this.input.error('Unclosed block', pos.line, pos.column)
  457. }
  458. doubleColon(token) {
  459. throw this.input.error('Double colon', token[2])
  460. }
  461. unnamedAtrule(node, token) {
  462. throw this.input.error('At-rule without name', token[2])
  463. }
  464. precheckMissedSemicolon(/* tokens */) {
  465. // Hook for Safe Parser
  466. }
  467. checkMissedSemicolon(tokens) {
  468. let colon = this.colon(tokens)
  469. if (colon === false) return
  470. let founded = 0
  471. let token
  472. for (let j = colon - 1; j >= 0; j--) {
  473. token = tokens[j]
  474. if (token[0] !== 'space') {
  475. founded += 1
  476. if (founded === 2) break
  477. }
  478. }
  479. // If the token is a word, e.g. `!important`, `red` or any other valid property's value.
  480. // Then we need to return the colon after that word token. [3] is the "end" colon of that word.
  481. // And because we need it after that one we do +1 to get the next one.
  482. throw this.input.error(
  483. 'Missed semicolon',
  484. token[0] === 'word' ? token[3] + 1 : token[2]
  485. )
  486. }
  487. }
  488. module.exports = Parser