AK-21/Graphite-Industrial-Intelligence
0
1'use strict'2 3// Escapes sequences that could break out of an HTML <style> context.4// Uses CSS unicode escaping (\3c = '<') which is valid CSS and parsed5// correctly by all compliant CSS consumers.6const STYLE_TAG = /(<)(\/?style\b)/gi7const COMMENT_OPEN = /(<)(!--)/g8 9function escapeHTMLInCSS(str) {10 if (typeof str !== 'string') return str11 if (!str.includes('<')) return str12 return str.replace(STYLE_TAG, '\\3c $2').replace(COMMENT_OPEN, '\\3c $2')13}14 15const DEFAULT_RAW = {16 after: '\n',17 beforeClose: '\n',18 beforeComment: '\n',19 beforeDecl: '\n',20 beforeOpen: ' ',21 beforeRule: '\n',22 colon: ': ',23 commentLeft: ' ',24 commentRight: ' ',25 emptyBody: '',26 indent: ' ',27 semicolon: false28}29 30function capitalize(str) {31 return str[0].toUpperCase() + str.slice(1)32}33 34class Stringifier {35 constructor(builder) {36 this.builder = builder37 }38 39 atrule(node, semicolon) {40 let raws = node.raws41 let name = '@' + node.name42 let params = node.params ? this.rawValue(node, 'params') : ''43 44 if (typeof raws.afterName !== 'undefined') {45 name += raws.afterName46 } else if (params) {47 name += ' '48 }49 50 if (node.nodes) {51 this.block(node, name + params)52 } else {53 let end = (raws.between || '') + (semicolon ? ';' : '')54 this.builder(escapeHTMLInCSS(name + params + end), node)55 }56 }57 58 beforeAfter(node, detect) {59 let value60 if (node.type === 'decl') {61 value = this.raw(node, null, 'beforeDecl')62 } else if (node.type === 'comment') {63 value = this.raw(node, null, 'beforeComment')64 } else if (detect === 'before') {65 value = this.raw(node, null, 'beforeRule')66 } else {67 value = this.raw(node, null, 'beforeClose')68 }69 70 let buf = node.parent71 let depth = 072 while (buf && buf.type !== 'root') {73 depth += 174 buf = buf.parent75 }76 77 if (value.includes('\n')) {78 let indent = this.raw(node, null, 'indent')79 if (indent.length) {80 for (let step = 0; step < depth; step++) value += indent81 }82 }83 84 return value85 }86 87 block(node, start) {88 let between = this.raw(node, 'between', 'beforeOpen')89 this.builder(escapeHTMLInCSS(start + between) + '{', node, 'start')90 91 let after92 if (node.nodes && node.nodes.length) {93 this.body(node)94 after = this.raw(node, 'after')95 } else {96 after = this.raw(node, 'after', 'emptyBody')97 }98 99 if (after) this.builder(escapeHTMLInCSS(after))100 this.builder('}', node, 'end')101 }102 103 body(node) {104 let nodes = node.nodes105 let last = nodes.length - 1106 while (last > 0) {107 if (nodes[last].type !== 'comment') break108 last -= 1109 }110 111 let semicolon = this.raw(node, 'semicolon')112 let isDocument = node.type === 'document'113 for (let i = 0; i < nodes.length; i++) {114 let child = nodes[i]115 let before = this.raw(child, 'before')116 if (before) this.builder(isDocument ? before : escapeHTMLInCSS(before))117 this.stringify(child, last !== i || semicolon)118 }119 }120 121 comment(node) {122 let left = this.raw(node, 'left', 'commentLeft')123 let right = this.raw(node, 'right', 'commentRight')124 this.builder(escapeHTMLInCSS('/*' + left + node.text + right + '*/'), node)125 }126 127 decl(node, semicolon) {128 let raws = node.raws129 let between = this.raw(node, 'between', 'colon')130 131 let string = node.prop + between + this.rawValue(node, 'value')132 133 if (node.important) {134 string += raws.important || ' !important'135 }136 137 if (semicolon) string += ';'138 this.builder(escapeHTMLInCSS(string), node)139 }140 141 document(node) {142 this.body(node)143 }144 145 raw(node, own, detect) {146 let value147 if (!detect) detect = own148 149 // Already had150 if (own) {151 value = node.raws[own]152 if (typeof value !== 'undefined') return value153 }154 155 let parent = node.parent156 157 if (detect === 'before') {158 // Hack for first rule in CSS159 if (!parent || (parent.type === 'root' && parent.first === node)) {160 return ''161 }162 163 // `root` nodes in `document` should use only their own raws164 if (parent && parent.type === 'document') {165 return ''166 }167 }168 169 // Floating child without parent170 if (!parent) return DEFAULT_RAW[detect]171 172 // Detect style by other nodes173 let root = node.root()174 let cache = root.rawCache || (root.rawCache = {})175 if (typeof cache[detect] !== 'undefined') {176 return cache[detect]177 }178 179 if (detect === 'before' || detect === 'after') {180 return this.beforeAfter(node, detect)181 } else {182 let method = 'raw' + capitalize(detect)183 if (this[method]) {184 value = this[method](root, node)185 } else {186 root.walk(i => {187 value = i.raws[own]188 if (typeof value !== 'undefined') return false189 })190 }191 }192 193 if (typeof value === 'undefined') value = DEFAULT_RAW[detect]194 195 cache[detect] = value196 return value197 }198 199 rawBeforeClose(root) {200 let value201 root.walk(i => {202 if (i.nodes && i.nodes.length > 0) {203 if (typeof i.raws.after !== 'undefined') {204 value = i.raws.after205 if (value.includes('\n')) {206 value = value.replace(/[^\n]+$/, '')207 }208 return false209 }210 }211 })212 if (value) value = value.replace(/\S/g, '')213 return value214 }215 216 rawBeforeComment(root, node) {217 let value218 root.walkComments(i => {219 if (typeof i.raws.before !== 'undefined') {220 value = i.raws.before221 if (value.includes('\n')) {222 value = value.replace(/[^\n]+$/, '')223 }224 return false225 }226 })227 if (typeof value === 'undefined') {228 value = this.raw(node, null, 'beforeDecl')229 } else if (value) {230 value = value.replace(/\S/g, '')231 }232 return value233 }234 235 rawBeforeDecl(root, node) {236 let value237 root.walkDecls(i => {238 if (typeof i.raws.before !== 'undefined') {239 value = i.raws.before240 if (value.includes('\n')) {241 value = value.replace(/[^\n]+$/, '')242 }243 return false244 }245 })246 if (typeof value === 'undefined') {247 value = this.raw(node, null, 'beforeRule')248 } else if (value) {249 value = value.replace(/\S/g, '')250 }251 return value252 }253 254 rawBeforeOpen(root) {255 let value256 root.walk(i => {257 if (i.type !== 'decl') {258 value = i.raws.between259 if (typeof value !== 'undefined') return false260 }261 })262 return value263 }264 265 rawBeforeRule(root) {266 let value267 root.walk(i => {268 if (i.nodes && (i.parent !== root || root.first !== i)) {269 if (typeof i.raws.before !== 'undefined') {270 value = i.raws.before271 if (value.includes('\n')) {272 value = value.replace(/[^\n]+$/, '')273 }274 return false275 }276 }277 })278 if (value) value = value.replace(/\S/g, '')279 return value280 }281 282 rawColon(root) {283 let value284 root.walkDecls(i => {285 if (typeof i.raws.between !== 'undefined') {286 value = i.raws.between.replace(/[^\s:]/g, '')287 return false288 }289 })290 return value291 }292 293 rawEmptyBody(root) {294 let value295 root.walk(i => {296 if (i.nodes && i.nodes.length === 0) {297 value = i.raws.after298 if (typeof value !== 'undefined') return false299 }300 })301 return value302 }303 304 rawIndent(root) {305 if (root.raws.indent) return root.raws.indent306 let value307 root.walk(i => {308 let p = i.parent309 if (p && p !== root && p.parent && p.parent === root) {310 if (typeof i.raws.before !== 'undefined') {311 let parts = i.raws.before.split('\n')312 value = parts[parts.length - 1]313 value = value.replace(/\S/g, '')314 return false315 }316 }317 })318 return value319 }320 321 rawSemicolon(root) {322 let value323 root.walk(i => {324 if (i.nodes && i.nodes.length && i.last.type === 'decl') {325 value = i.raws.semicolon326 if (typeof value !== 'undefined') return false327 }328 })329 return value330 }331 332 rawValue(node, prop) {333 let value = node[prop]334 let raw = node.raws[prop]335 if (raw && raw.value === value) {336 return raw.raw337 }338 339 return value340 }341 342 root(node) {343 this.body(node)344 if (node.raws.after) {345 let after = node.raws.after346 let isDocument = node.parent && node.parent.type === 'document'347 this.builder(isDocument ? after : escapeHTMLInCSS(after))348 }349 }350 351 rule(node) {352 this.block(node, this.rawValue(node, 'selector'))353 if (node.raws.ownSemicolon) {354 this.builder(escapeHTMLInCSS(node.raws.ownSemicolon), node, 'end')355 }356 }357 358 stringify(node, semicolon) {359 /* c8 ignore start */360 if (!this[node.type]) {361 throw new Error(362 'Unknown AST node type ' +363 node.type +364 '. ' +365 'Maybe you need to change PostCSS stringifier.'366 )367 }368 /* c8 ignore stop */369 this[node.type](node, semicolon)370 }371}372 373module.exports = Stringifier374Stringifier.default = Stringifier375 