├── .gitignore ├── README.md ├── deno ├── CHANGELOG.md ├── LICENSE ├── README.md ├── lib │ ├── at-rule.d.ts │ ├── at-rule.js │ ├── comment.d.ts │ ├── comment.js │ ├── container.d.ts │ ├── container.js │ ├── css-syntax-error.d.ts │ ├── css-syntax-error.js │ ├── declaration.d.ts │ ├── declaration.js │ ├── deps.js │ ├── document.d.ts │ ├── document.js │ ├── fromJSON.d.ts │ ├── fromJSON.js │ ├── input.d.ts │ ├── input.js │ ├── lazy-result.d.ts │ ├── lazy-result.js │ ├── list.d.ts │ ├── list.js │ ├── map-generator.js │ ├── no-work-result.d.ts │ ├── no-work-result.js │ ├── node.d.ts │ ├── node.js │ ├── parse.d.ts │ ├── parse.js │ ├── parser.js │ ├── postcss.d.ts │ ├── postcss.js │ ├── previous-map.d.ts │ ├── previous-map.js │ ├── processor.d.ts │ ├── processor.js │ ├── result.d.ts │ ├── result.js │ ├── root.d.ts │ ├── root.js │ ├── rule.d.ts │ ├── rule.js │ ├── source_map.ts │ ├── stringifier.d.ts │ ├── stringifier.js │ ├── stringify.d.ts │ ├── stringify.js │ ├── symbols.js │ ├── terminal-highlight.js │ ├── tokenize.js │ ├── warn-once.js │ ├── warning.d.ts │ └── warning.js ├── mod.js └── test │ ├── at-rule.test.js │ ├── comment.test.js │ ├── container.test.js │ ├── declaration.test.js │ ├── deps.js │ ├── lazy-result.test.js │ ├── list.test.js │ ├── node.test.js │ ├── result.test.js │ ├── root.test.ts │ ├── rule.test.ts │ ├── tokenize.test.js │ └── warning.test.js ├── deps.js ├── run.sh ├── source_map.ts ├── test ├── at-rule.test.js ├── comment.test.js ├── container.test.js ├── declaration.test.js ├── deps.js ├── lazy-result.test.js ├── list.test.js ├── node.test.js ├── result.test.js ├── root.test.ts ├── rule.test.ts ├── tokenize.test.js └── warning.test.js └── to_deno.js /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | postcss 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PostCSS for Deno 2 | 3 | Scripts to transform the source code of PostCSS for Deno compatibility. 4 | 5 | ```sh 6 | sh run.sh 7 | ``` 8 | 9 | To import Postcss in your Deno project: 10 | 11 | ```js 12 | import postcss from "https://deno.land/x/postcss/mod.js"; 13 | import autoprefixer from "https://deno.land/x/postcss_autoprefixer/mod.js"; 14 | 15 | const result = await postcss([autoprefixer]).process(css); 16 | ``` 17 | -------------------------------------------------------------------------------- /deno/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2013 Andrey Sitnik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /deno/lib/at-rule.d.ts: -------------------------------------------------------------------------------- 1 | import Container, { ContainerProps } from "./container.js"; 2 | 3 | interface AtRuleRaws extends Record { 4 | /** 5 | * The space symbols before the node. It also stores `*` 6 | * and `_` symbols before the declaration (IE hack). 7 | */ 8 | before?: string; 9 | 10 | /** 11 | * The space symbols after the last child of the node to the end of the node. 12 | */ 13 | after?: string; 14 | 15 | /** 16 | * The space between the at-rule name and its parameters. 17 | */ 18 | afterName?: string; 19 | 20 | /** 21 | * The symbols between the last parameter and `{` for rules. 22 | */ 23 | between?: string; 24 | 25 | /** 26 | * Contains `true` if the last child has an (optional) semicolon. 27 | */ 28 | semicolon?: boolean; 29 | 30 | /** 31 | * The rule’s selector with comments. 32 | */ 33 | params?: { 34 | value: string; 35 | raw: string; 36 | }; 37 | } 38 | 39 | export interface AtRuleProps extends ContainerProps { 40 | /** Name of the at-rule. */ 41 | name: string; 42 | /** Parameters following the name of the at-rule. */ 43 | params?: string | number; 44 | /** Information used to generate byte-to-byte equal node string as it was in the origin input. */ 45 | raws?: AtRuleRaws; 46 | } 47 | 48 | /** 49 | * Represents an at-rule. 50 | * 51 | * ```js 52 | * Once (root, { AtRule }) { 53 | * let media = new AtRule({ name: 'media', params: 'print' }) 54 | * media.append(…) 55 | * root.append(media) 56 | * } 57 | * ``` 58 | * 59 | * If it’s followed in the CSS by a {} block, this node will have 60 | * a nodes property representing its children. 61 | * 62 | * ```js 63 | * const root = postcss.parse('@charset "UTF-8"; @media print {}') 64 | * 65 | * const charset = root.first 66 | * charset.type //=> 'atrule' 67 | * charset.nodes //=> undefined 68 | * 69 | * const media = root.last 70 | * media.nodes //=> [] 71 | * ``` 72 | */ 73 | export default class AtRule extends Container { 74 | type: "atrule"; 75 | parent: Container | undefined; 76 | raws: AtRuleRaws; 77 | 78 | /** 79 | * The at-rule’s name immediately follows the `@`. 80 | * 81 | * ```js 82 | * const root = postcss.parse('@media print {}') 83 | * media.name //=> 'media' 84 | * const media = root.first 85 | * ``` 86 | */ 87 | name: string; 88 | 89 | /** 90 | * The at-rule’s parameters, the values that follow the at-rule’s name 91 | * but precede any {} block. 92 | * 93 | * ```js 94 | * const root = postcss.parse('@media print, screen {}') 95 | * const media = root.first 96 | * media.params //=> 'print, screen' 97 | * ``` 98 | */ 99 | params: string; 100 | 101 | constructor(defaults?: AtRuleProps); 102 | assign(overrides: object | AtRuleProps): this; 103 | clone(overrides?: Partial): this; 104 | cloneBefore(overrides?: Partial): this; 105 | cloneAfter(overrides?: Partial): this; 106 | } 107 | -------------------------------------------------------------------------------- /deno/lib/at-rule.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import Container from "./container.js"; 4 | 5 | class AtRule extends Container { 6 | constructor(defaults) { 7 | super(defaults); 8 | this.type = "atrule"; 9 | } 10 | 11 | append(...children) { 12 | if (!this.proxyOf.nodes) this.nodes = []; 13 | return super.append(...children); 14 | } 15 | 16 | prepend(...children) { 17 | if (!this.proxyOf.nodes) this.nodes = []; 18 | return super.prepend(...children); 19 | } 20 | } 21 | 22 | export default AtRule; 23 | 24 | AtRule.default = AtRule; 25 | 26 | Container.registerAtRule(AtRule); 27 | -------------------------------------------------------------------------------- /deno/lib/comment.d.ts: -------------------------------------------------------------------------------- 1 | import Container from "./container.js"; 2 | import Node, { NodeProps } from "./node.js"; 3 | 4 | interface CommentRaws extends Record { 5 | /** 6 | * The space symbols before the node. 7 | */ 8 | before?: string; 9 | 10 | /** 11 | * The space symbols between `/*` and the comment’s text. 12 | */ 13 | left?: string; 14 | 15 | /** 16 | * The space symbols between the comment’s text. 17 | */ 18 | right?: string; 19 | } 20 | 21 | export interface CommentProps extends NodeProps { 22 | /** Content of the comment. */ 23 | text: string; 24 | /** Information used to generate byte-to-byte equal node string as it was in the origin input. */ 25 | raws?: CommentRaws; 26 | } 27 | 28 | /** 29 | * Represents a comment between declarations or statements (rule and at-rules). 30 | * 31 | * ```js 32 | * Once (root, { Comment }) { 33 | * let note = new Comment({ text: 'Note: …' }) 34 | * root.append(note) 35 | * } 36 | * ``` 37 | * 38 | * Comments inside selectors, at-rule parameters, or declaration values 39 | * will be stored in the `raws` properties explained above. 40 | */ 41 | export default class Comment extends Node { 42 | type: "comment"; 43 | parent: Container | undefined; 44 | raws: CommentRaws; 45 | 46 | /** 47 | * The comment's text. 48 | */ 49 | text: string; 50 | 51 | constructor(defaults?: CommentProps); 52 | assign(overrides: object | CommentProps): this; 53 | clone(overrides?: Partial): this; 54 | cloneBefore(overrides?: Partial): this; 55 | cloneAfter(overrides?: Partial): this; 56 | } 57 | -------------------------------------------------------------------------------- /deno/lib/comment.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import Node from "./node.js"; 4 | 5 | class Comment extends Node { 6 | constructor(defaults) { 7 | super(defaults); 8 | this.type = "comment"; 9 | } 10 | } 11 | 12 | export default Comment; 13 | 14 | Comment.default = Comment; 15 | -------------------------------------------------------------------------------- /deno/lib/container.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { isClean, my } from "./symbols.js"; 4 | import Declaration from "./declaration.js"; 5 | import Comment from "./comment.js"; 6 | import Node from "./node.js"; 7 | 8 | let parse, Rule, AtRule, Root; 9 | 10 | function cleanSource(nodes) { 11 | return nodes.map((i) => { 12 | if (i.nodes) i.nodes = cleanSource(i.nodes); 13 | delete i.source; 14 | return i; 15 | }); 16 | } 17 | 18 | function markDirtyUp(node) { 19 | node[isClean] = false; 20 | if (node.proxyOf.nodes) { 21 | for (let i of node.proxyOf.nodes) { 22 | markDirtyUp(i); 23 | } 24 | } 25 | } 26 | 27 | class Container extends Node { 28 | push(child) { 29 | child.parent = this; 30 | this.proxyOf.nodes.push(child); 31 | return this; 32 | } 33 | 34 | each(callback) { 35 | if (!this.proxyOf.nodes) return undefined; 36 | let iterator = this.getIterator(); 37 | 38 | let index, result; 39 | while (this.indexes[iterator] < this.proxyOf.nodes.length) { 40 | index = this.indexes[iterator]; 41 | result = callback(this.proxyOf.nodes[index], index); 42 | if (result === false) break; 43 | 44 | this.indexes[iterator] += 1; 45 | } 46 | 47 | delete this.indexes[iterator]; 48 | return result; 49 | } 50 | 51 | walk(callback) { 52 | return this.each((child, i) => { 53 | let result; 54 | try { 55 | result = callback(child, i); 56 | } catch (e) { 57 | throw child.addToError(e); 58 | } 59 | if (result !== false && child.walk) { 60 | result = child.walk(callback); 61 | } 62 | 63 | return result; 64 | }); 65 | } 66 | 67 | walkDecls(prop, callback) { 68 | if (!callback) { 69 | callback = prop; 70 | return this.walk((child, i) => { 71 | if (child.type === "decl") { 72 | return callback(child, i); 73 | } 74 | }); 75 | } 76 | if (prop instanceof RegExp) { 77 | return this.walk((child, i) => { 78 | if (child.type === "decl" && prop.test(child.prop)) { 79 | return callback(child, i); 80 | } 81 | }); 82 | } 83 | return this.walk((child, i) => { 84 | if (child.type === "decl" && child.prop === prop) { 85 | return callback(child, i); 86 | } 87 | }); 88 | } 89 | 90 | walkRules(selector, callback) { 91 | if (!callback) { 92 | callback = selector; 93 | 94 | return this.walk((child, i) => { 95 | if (child.type === "rule") { 96 | return callback(child, i); 97 | } 98 | }); 99 | } 100 | if (selector instanceof RegExp) { 101 | return this.walk((child, i) => { 102 | if (child.type === "rule" && selector.test(child.selector)) { 103 | return callback(child, i); 104 | } 105 | }); 106 | } 107 | return this.walk((child, i) => { 108 | if (child.type === "rule" && child.selector === selector) { 109 | return callback(child, i); 110 | } 111 | }); 112 | } 113 | 114 | walkAtRules(name, callback) { 115 | if (!callback) { 116 | callback = name; 117 | return this.walk((child, i) => { 118 | if (child.type === "atrule") { 119 | return callback(child, i); 120 | } 121 | }); 122 | } 123 | if (name instanceof RegExp) { 124 | return this.walk((child, i) => { 125 | if (child.type === "atrule" && name.test(child.name)) { 126 | return callback(child, i); 127 | } 128 | }); 129 | } 130 | return this.walk((child, i) => { 131 | if (child.type === "atrule" && child.name === name) { 132 | return callback(child, i); 133 | } 134 | }); 135 | } 136 | 137 | walkComments(callback) { 138 | return this.walk((child, i) => { 139 | if (child.type === "comment") { 140 | return callback(child, i); 141 | } 142 | }); 143 | } 144 | 145 | append(...children) { 146 | for (let child of children) { 147 | let nodes = this.normalize(child, this.last); 148 | for (let node of nodes) this.proxyOf.nodes.push(node); 149 | } 150 | 151 | this.markDirty(); 152 | 153 | return this; 154 | } 155 | 156 | prepend(...children) { 157 | children = children.reverse(); 158 | for (let child of children) { 159 | let nodes = this.normalize(child, this.first, "prepend").reverse(); 160 | for (let node of nodes) this.proxyOf.nodes.unshift(node); 161 | for (let id in this.indexes) { 162 | this.indexes[id] = this.indexes[id] + nodes.length; 163 | } 164 | } 165 | 166 | this.markDirty(); 167 | 168 | return this; 169 | } 170 | 171 | cleanRaws(keepBetween) { 172 | super.cleanRaws(keepBetween); 173 | if (this.nodes) { 174 | for (let node of this.nodes) node.cleanRaws(keepBetween); 175 | } 176 | } 177 | 178 | insertBefore(exist, add) { 179 | let existIndex = this.index(exist); 180 | let type = exist === 0 ? "prepend" : false; 181 | let nodes = this.normalize(add, this.proxyOf.nodes[existIndex], type) 182 | .reverse(); 183 | existIndex = this.index(exist); 184 | for (let node of nodes) this.proxyOf.nodes.splice(existIndex, 0, node); 185 | 186 | let index; 187 | for (let id in this.indexes) { 188 | index = this.indexes[id]; 189 | if (existIndex <= index) { 190 | this.indexes[id] = index + nodes.length; 191 | } 192 | } 193 | 194 | this.markDirty(); 195 | 196 | return this; 197 | } 198 | 199 | insertAfter(exist, add) { 200 | let existIndex = this.index(exist); 201 | let nodes = this.normalize(add, this.proxyOf.nodes[existIndex]).reverse(); 202 | existIndex = this.index(exist); 203 | for (let node of nodes) this.proxyOf.nodes.splice(existIndex + 1, 0, node); 204 | 205 | let index; 206 | for (let id in this.indexes) { 207 | index = this.indexes[id]; 208 | if (existIndex < index) { 209 | this.indexes[id] = index + nodes.length; 210 | } 211 | } 212 | 213 | this.markDirty(); 214 | 215 | return this; 216 | } 217 | 218 | removeChild(child) { 219 | child = this.index(child); 220 | this.proxyOf.nodes[child].parent = undefined; 221 | this.proxyOf.nodes.splice(child, 1); 222 | 223 | let index; 224 | for (let id in this.indexes) { 225 | index = this.indexes[id]; 226 | if (index >= child) { 227 | this.indexes[id] = index - 1; 228 | } 229 | } 230 | 231 | this.markDirty(); 232 | 233 | return this; 234 | } 235 | 236 | removeAll() { 237 | for (let node of this.proxyOf.nodes) node.parent = undefined; 238 | this.proxyOf.nodes = []; 239 | 240 | this.markDirty(); 241 | 242 | return this; 243 | } 244 | 245 | replaceValues(pattern, opts, callback) { 246 | if (!callback) { 247 | callback = opts; 248 | opts = {}; 249 | } 250 | 251 | this.walkDecls((decl) => { 252 | if (opts.props && !opts.props.includes(decl.prop)) return; 253 | if (opts.fast && !decl.value.includes(opts.fast)) return; 254 | 255 | decl.value = decl.value.replace(pattern, callback); 256 | }); 257 | 258 | this.markDirty(); 259 | 260 | return this; 261 | } 262 | 263 | every(condition) { 264 | return this.nodes.every(condition); 265 | } 266 | 267 | some(condition) { 268 | return this.nodes.some(condition); 269 | } 270 | 271 | index(child) { 272 | if (typeof child === "number") return child; 273 | if (child.proxyOf) child = child.proxyOf; 274 | return this.proxyOf.nodes.indexOf(child); 275 | } 276 | 277 | get first() { 278 | if (!this.proxyOf.nodes) return undefined; 279 | return this.proxyOf.nodes[0]; 280 | } 281 | 282 | get last() { 283 | if (!this.proxyOf.nodes) return undefined; 284 | return this.proxyOf.nodes[this.proxyOf.nodes.length - 1]; 285 | } 286 | 287 | normalize(nodes, sample) { 288 | if (typeof nodes === "string") { 289 | nodes = cleanSource(parse(nodes).nodes); 290 | } else if (Array.isArray(nodes)) { 291 | nodes = nodes.slice(0); 292 | for (let i of nodes) { 293 | if (i.parent) i.parent.removeChild(i, "ignore"); 294 | } 295 | } else if (nodes.type === "root" && this.type !== "document") { 296 | nodes = nodes.nodes.slice(0); 297 | for (let i of nodes) { 298 | if (i.parent) i.parent.removeChild(i, "ignore"); 299 | } 300 | } else if (nodes.type) { 301 | nodes = [nodes]; 302 | } else if (nodes.prop) { 303 | if (typeof nodes.value === "undefined") { 304 | throw new Error("Value field is missed in node creation"); 305 | } else if (typeof nodes.value !== "string") { 306 | nodes.value = String(nodes.value); 307 | } 308 | nodes = [new Declaration(nodes)]; 309 | } else if (nodes.selector) { 310 | nodes = [new Rule(nodes)]; 311 | } else if (nodes.name) { 312 | nodes = [new AtRule(nodes)]; 313 | } else if (nodes.text) { 314 | nodes = [new Comment(nodes)]; 315 | } else { 316 | throw new Error("Unknown node type in node creation"); 317 | } 318 | 319 | let processed = nodes.map((i) => { 320 | /* c8 ignore next */ 321 | if (!i[my]) Container.rebuild(i); 322 | i = i.proxyOf; 323 | if (i.parent) i.parent.removeChild(i); 324 | if (i[isClean]) markDirtyUp(i); 325 | if (typeof i.raws.before === "undefined") { 326 | if (sample && typeof sample.raws.before !== "undefined") { 327 | i.raws.before = sample.raws.before.replace(/\S/g, ""); 328 | } 329 | } 330 | i.parent = this.proxyOf; 331 | return i; 332 | }); 333 | 334 | return processed; 335 | } 336 | 337 | getProxyProcessor() { 338 | return { 339 | set(node, prop, value) { 340 | if (node[prop] === value) return true; 341 | node[prop] = value; 342 | if (prop === "name" || prop === "params" || prop === "selector") { 343 | node.markDirty(); 344 | } 345 | return true; 346 | }, 347 | 348 | get(node, prop) { 349 | if (prop === "proxyOf") { 350 | return node; 351 | } else if (!node[prop]) { 352 | return node[prop]; 353 | } else if ( 354 | prop === "each" || 355 | (typeof prop === "string" && prop.startsWith("walk")) 356 | ) { 357 | return (...args) => { 358 | return node[prop]( 359 | ...args.map((i) => { 360 | if (typeof i === "function") { 361 | return (child, index) => i(child.toProxy(), index); 362 | } else { 363 | return i; 364 | } 365 | }), 366 | ); 367 | }; 368 | } else if (prop === "every" || prop === "some") { 369 | return (cb) => { 370 | return node[prop]((child, ...other) => 371 | cb(child.toProxy(), ...other) 372 | ); 373 | }; 374 | } else if (prop === "root") { 375 | return () => node.root().toProxy(); 376 | } else if (prop === "nodes") { 377 | return node.nodes.map((i) => i.toProxy()); 378 | } else if (prop === "first" || prop === "last") { 379 | return node[prop].toProxy(); 380 | } else { 381 | return node[prop]; 382 | } 383 | }, 384 | }; 385 | } 386 | 387 | getIterator() { 388 | if (!this.lastEach) this.lastEach = 0; 389 | if (!this.indexes) this.indexes = {}; 390 | 391 | this.lastEach += 1; 392 | let iterator = this.lastEach; 393 | this.indexes[iterator] = 0; 394 | 395 | return iterator; 396 | } 397 | } 398 | 399 | Container.registerParse = (dependant) => { 400 | parse = dependant; 401 | }; 402 | 403 | Container.registerRule = (dependant) => { 404 | Rule = dependant; 405 | }; 406 | 407 | Container.registerAtRule = (dependant) => { 408 | AtRule = dependant; 409 | }; 410 | 411 | Container.registerRoot = (dependant) => { 412 | Root = dependant; 413 | }; 414 | 415 | export default Container; 416 | 417 | Container.default = Container; 418 | 419 | /* c8 ignore start */ 420 | Container.rebuild = (node) => { 421 | if (node.type === "atrule") { 422 | Object.setPrototypeOf(node, AtRule.prototype); 423 | } else if (node.type === "rule") { 424 | Object.setPrototypeOf(node, Rule.prototype); 425 | } else if (node.type === "decl") { 426 | Object.setPrototypeOf(node, Declaration.prototype); 427 | } else if (node.type === "comment") { 428 | Object.setPrototypeOf(node, Comment.prototype); 429 | } else if (node.type === "root") { 430 | Object.setPrototypeOf(node, Root.prototype); 431 | } 432 | 433 | node[my] = true; 434 | 435 | if (node.nodes) { 436 | node.nodes.forEach((child) => { 437 | Container.rebuild(child); 438 | }); 439 | } 440 | }; 441 | /* c8 ignore stop */ 442 | -------------------------------------------------------------------------------- /deno/lib/css-syntax-error.d.ts: -------------------------------------------------------------------------------- 1 | import { FilePosition } from "./input.js"; 2 | 3 | /** 4 | * A position that is part of a range. 5 | */ 6 | export interface RangePosition { 7 | /** 8 | * The line number in the input. 9 | */ 10 | line: number; 11 | 12 | /** 13 | * The column number in the input. 14 | */ 15 | column: number; 16 | } 17 | 18 | /** 19 | * The CSS parser throws this error for broken CSS. 20 | * 21 | * Custom parsers can throw this error for broken custom syntax using 22 | * the `Node#error` method. 23 | * 24 | * PostCSS will use the input source map to detect the original error location. 25 | * If you wrote a Sass file, compiled it to CSS and then parsed it with PostCSS, 26 | * PostCSS will show the original position in the Sass file. 27 | * 28 | * If you need the position in the PostCSS input 29 | * (e.g., to debug the previous compiler), use `error.input.file`. 30 | * 31 | * ```js 32 | * // Raising error from plugin 33 | * throw node.error('Unknown variable', { plugin: 'postcss-vars' }) 34 | * ``` 35 | * 36 | * ```js 37 | * // Catching and checking syntax error 38 | * try { 39 | * postcss.parse('a{') 40 | * } catch (error) { 41 | * if (error.name === 'CssSyntaxError') { 42 | * error //=> CssSyntaxError 43 | * } 44 | * } 45 | * ``` 46 | */ 47 | export default class CssSyntaxError { 48 | /** 49 | * Instantiates a CSS syntax error. Can be instantiated for a single position 50 | * or for a range. 51 | * @param message Error message. 52 | * @param lineOrStartPos If for a single position, the line number, or if for 53 | * a range, the inclusive start position of the error. 54 | * @param columnOrEndPos If for a single position, the column number, or if for 55 | * a range, the exclusive end position of the error. 56 | * @param source Source code of the broken file. 57 | * @param file Absolute path to the broken file. 58 | * @param plugin PostCSS plugin name, if error came from plugin. 59 | */ 60 | constructor( 61 | message: string, 62 | lineOrStartPos?: number | RangePosition, 63 | columnOrEndPos?: number | RangePosition, 64 | source?: string, 65 | file?: string, 66 | plugin?: string, 67 | ); 68 | 69 | stack: string; 70 | 71 | /** 72 | * Always equal to `'CssSyntaxError'`. You should always check error type 73 | * by `error.name === 'CssSyntaxError'` 74 | * instead of `error instanceof CssSyntaxError`, 75 | * because npm could have several PostCSS versions. 76 | * 77 | * ```js 78 | * if (error.name === 'CssSyntaxError') { 79 | * error //=> CssSyntaxError 80 | * } 81 | * ``` 82 | */ 83 | name: "CssSyntaxError"; 84 | 85 | /** 86 | * Error message. 87 | * 88 | * ```js 89 | * error.message //=> 'Unclosed block' 90 | * ``` 91 | */ 92 | reason: string; 93 | 94 | /** 95 | * Full error text in the GNU error format 96 | * with plugin, file, line and column. 97 | * 98 | * ```js 99 | * error.message //=> 'a.css:1:1: Unclosed block' 100 | * ``` 101 | */ 102 | message: string; 103 | 104 | /** 105 | * Absolute path to the broken file. 106 | * 107 | * ```js 108 | * error.file //=> 'a.sass' 109 | * error.input.file //=> 'a.css' 110 | * ``` 111 | * 112 | * PostCSS will use the input source map to detect the original location. 113 | * If you need the position in the PostCSS input, use `error.input.file`. 114 | */ 115 | file?: string; 116 | 117 | /** 118 | * Source line of the error. 119 | * 120 | * ```js 121 | * error.line //=> 2 122 | * error.input.line //=> 4 123 | * ``` 124 | * 125 | * PostCSS will use the input source map to detect the original location. 126 | * If you need the position in the PostCSS input, use `error.input.line`. 127 | */ 128 | line?: number; 129 | 130 | /** 131 | * Source column of the error. 132 | * 133 | * ```js 134 | * error.column //=> 1 135 | * error.input.column //=> 4 136 | * ``` 137 | * 138 | * PostCSS will use the input source map to detect the original location. 139 | * If you need the position in the PostCSS input, use `error.input.column`. 140 | */ 141 | column?: number; 142 | 143 | /** 144 | * Source line of the error's end, exclusive. Provided if the error pertains 145 | * to a range. 146 | * 147 | * ```js 148 | * error.endLine //=> 3 149 | * error.input.endLine //=> 4 150 | * ``` 151 | * 152 | * PostCSS will use the input source map to detect the original location. 153 | * If you need the position in the PostCSS input, use `error.input.endLine`. 154 | */ 155 | endLine?: number; 156 | 157 | /** 158 | * Source column of the error's end, exclusive. Provided if the error pertains 159 | * to a range. 160 | * 161 | * ```js 162 | * error.endColumn //=> 1 163 | * error.input.endColumn //=> 4 164 | * ``` 165 | * 166 | * PostCSS will use the input source map to detect the original location. 167 | * If you need the position in the PostCSS input, use `error.input.endColumn`. 168 | */ 169 | endColumn?: number; 170 | 171 | /** 172 | * Source code of the broken file. 173 | * 174 | * ```js 175 | * error.source //=> 'a { b {} }' 176 | * error.input.source //=> 'a b { }' 177 | * ``` 178 | */ 179 | source?: string; 180 | 181 | /** 182 | * Plugin name, if error came from plugin. 183 | * 184 | * ```js 185 | * error.plugin //=> 'postcss-vars' 186 | * ``` 187 | */ 188 | plugin?: string; 189 | 190 | /** 191 | * Input object with PostCSS internal information 192 | * about input file. If input has source map 193 | * from previous tool, PostCSS will use origin 194 | * (for example, Sass) source. You can use this 195 | * object to get PostCSS input source. 196 | * 197 | * ```js 198 | * error.input.file //=> 'a.css' 199 | * error.file //=> 'a.sass' 200 | * ``` 201 | */ 202 | input?: FilePosition; 203 | 204 | /** 205 | * Returns error position, message and source code of the broken part. 206 | * 207 | * ```js 208 | * error.toString() //=> "CssSyntaxError: app.css:1:1: Unclosed block 209 | * // > 1 | a { 210 | * // | ^" 211 | * ``` 212 | * 213 | * @return Error position, message and source code. 214 | */ 215 | toString(): string; 216 | 217 | /** 218 | * Returns a few lines of CSS source that caused the error. 219 | * 220 | * If the CSS has an input source map without `sourceContent`, 221 | * this method will return an empty string. 222 | * 223 | * ```js 224 | * error.showSourceCode() //=> " 4 | } 225 | * // 5 | a { 226 | * // > 6 | bad 227 | * // | ^ 228 | * // 7 | } 229 | * // 8 | b {" 230 | * ``` 231 | * 232 | * @param color Whether arrow will be colored red by terminal 233 | * color codes. By default, PostCSS will detect 234 | * color support by `process.stdout.isTTY` 235 | * and `Deno.env.get("NODE_DISABLE_COLORS")`. 236 | * @return Few lines of CSS source that caused the error. 237 | */ 238 | showSourceCode(color?: boolean): string; 239 | } 240 | -------------------------------------------------------------------------------- /deno/lib/css-syntax-error.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { pico } from "./deps.js"; 4 | import terminalHighlight from "./terminal-highlight.js"; 5 | 6 | class CssSyntaxError extends Error { 7 | constructor(message, line, column, source, file, plugin) { 8 | super(message); 9 | this.name = "CssSyntaxError"; 10 | this.reason = message; 11 | 12 | if (file) { 13 | this.file = file; 14 | } 15 | if (source) { 16 | this.source = source; 17 | } 18 | if (plugin) { 19 | this.plugin = plugin; 20 | } 21 | if (typeof line !== "undefined" && typeof column !== "undefined") { 22 | if (typeof line === "number") { 23 | this.line = line; 24 | this.column = column; 25 | } else { 26 | this.line = line.line; 27 | this.column = line.column; 28 | this.endLine = column.line; 29 | this.endColumn = column.column; 30 | } 31 | } 32 | 33 | this.setMessage(); 34 | 35 | if (Error.captureStackTrace) { 36 | Error.captureStackTrace(this, CssSyntaxError); 37 | } 38 | } 39 | 40 | setMessage() { 41 | this.message = this.plugin ? this.plugin + ": " : ""; 42 | this.message += this.file ? this.file : ""; 43 | if (typeof this.line !== "undefined") { 44 | this.message += ":" + this.line + ":" + this.column; 45 | } 46 | this.message += ": " + this.reason; 47 | } 48 | 49 | showSourceCode(color) { 50 | if (!this.source) return ""; 51 | 52 | let css = this.source; 53 | if (color == null) color = pico.isColorSupported; 54 | if (terminalHighlight) { 55 | if (color) css = terminalHighlight(css); 56 | } 57 | 58 | let lines = css.split(/\r?\n/); 59 | let start = Math.max(this.line - 3, 0); 60 | let end = Math.min(this.line + 2, lines.length); 61 | 62 | let maxWidth = String(end).length; 63 | 64 | let mark, aside; 65 | if (color) { 66 | let { bold, red, gray } = pico.createColors(true); 67 | mark = (text) => bold(red(text)); 68 | aside = (text) => gray(text); 69 | } else { 70 | mark = aside = (str) => str; 71 | } 72 | 73 | return lines 74 | .slice(start, end) 75 | .map((line, index) => { 76 | let number = start + 1 + index; 77 | let gutter = " " + (" " + number).slice(-maxWidth) + " | "; 78 | if (number === this.line) { 79 | let spacing = aside(gutter.replace(/\d/g, " ")) + 80 | line.slice(0, this.column - 1).replace(/[^\t]/g, " "); 81 | return mark(">") + aside(gutter) + line + "\n " + spacing + mark("^"); 82 | } 83 | return " " + aside(gutter) + line; 84 | }) 85 | .join("\n"); 86 | } 87 | 88 | toString() { 89 | let code = this.showSourceCode(); 90 | if (code) { 91 | code = "\n\n" + code + "\n"; 92 | } 93 | return this.name + ": " + this.message + code; 94 | } 95 | } 96 | 97 | export default CssSyntaxError; 98 | 99 | CssSyntaxError.default = CssSyntaxError; 100 | -------------------------------------------------------------------------------- /deno/lib/declaration.d.ts: -------------------------------------------------------------------------------- 1 | import Container from "./container.js"; 2 | import Node from "./node.js"; 3 | 4 | interface DeclarationRaws extends Record { 5 | /** 6 | * The space symbols before the node. It also stores `*` 7 | * and `_` symbols before the declaration (IE hack). 8 | */ 9 | before?: string; 10 | 11 | /** 12 | * The symbols between the property and value for declarations. 13 | */ 14 | between?: string; 15 | 16 | /** 17 | * The content of the important statement, if it is not just `!important`. 18 | */ 19 | important?: string; 20 | 21 | /** 22 | * Declaration value with comments. 23 | */ 24 | value?: { 25 | value: string; 26 | raw: string; 27 | }; 28 | } 29 | 30 | export interface DeclarationProps { 31 | /** Name of the declaration. */ 32 | prop: string; 33 | /** Value of the declaration. */ 34 | value: string; 35 | /** Whether the declaration has an `!important` annotation. */ 36 | important?: boolean; 37 | /** Information used to generate byte-to-byte equal node string as it was in the origin input. */ 38 | raws?: DeclarationRaws; 39 | } 40 | 41 | /** 42 | * Represents a CSS declaration. 43 | * 44 | * ```js 45 | * Once (root, { Declaration }) { 46 | * let color = new Declaration({ prop: 'color', value: 'black' }) 47 | * root.append(color) 48 | * } 49 | * ``` 50 | * 51 | * ```js 52 | * const root = postcss.parse('a { color: black }') 53 | * const decl = root.first.first 54 | * decl.type //=> 'decl' 55 | * decl.toString() //=> ' color: black' 56 | * ``` 57 | */ 58 | export default class Declaration extends Node { 59 | type: "decl"; 60 | parent: Container | undefined; 61 | raws: DeclarationRaws; 62 | 63 | /** 64 | * The declaration's property name. 65 | * 66 | * ```js 67 | * const root = postcss.parse('a { color: black }') 68 | * const decl = root.first.first 69 | * decl.prop //=> 'color' 70 | * ``` 71 | */ 72 | prop: string; 73 | 74 | /** 75 | * The declaration’s value. 76 | * 77 | * This value will be cleaned of comments. If the source value contained 78 | * comments, those comments will be available in the `raws` property. 79 | * If you have not changed the value, the result of `decl.toString()` 80 | * will include the original raws value (comments and all). 81 | * 82 | * ```js 83 | * const root = postcss.parse('a { color: black }') 84 | * const decl = root.first.first 85 | * decl.value //=> 'black' 86 | * ``` 87 | */ 88 | value: string; 89 | 90 | /** 91 | * `true` if the declaration has an `!important` annotation. 92 | * 93 | * ```js 94 | * const root = postcss.parse('a { color: black !important; color: red }') 95 | * root.first.first.important //=> true 96 | * root.first.last.important //=> undefined 97 | * ``` 98 | */ 99 | important: boolean; 100 | 101 | /** 102 | * `true` if declaration is declaration of CSS Custom Property 103 | * or Sass variable. 104 | * 105 | * ```js 106 | * const root = postcss.parse(':root { --one: 1 }') 107 | * let one = root.first.first 108 | * one.variable //=> true 109 | * ``` 110 | * 111 | * ```js 112 | * const root = postcss.parse('$one: 1') 113 | * let one = root.first 114 | * one.variable //=> true 115 | * ``` 116 | */ 117 | variable: boolean; 118 | 119 | constructor(defaults?: DeclarationProps); 120 | assign(overrides: object | DeclarationProps): this; 121 | clone(overrides?: Partial): this; 122 | cloneBefore(overrides?: Partial): this; 123 | cloneAfter(overrides?: Partial): this; 124 | } 125 | -------------------------------------------------------------------------------- /deno/lib/declaration.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import Node from "./node.js"; 4 | 5 | class Declaration extends Node { 6 | constructor(defaults) { 7 | if ( 8 | defaults && 9 | typeof defaults.value !== "undefined" && 10 | typeof defaults.value !== "string" 11 | ) { 12 | defaults = { ...defaults, value: String(defaults.value) }; 13 | } 14 | super(defaults); 15 | this.type = "decl"; 16 | } 17 | 18 | get variable() { 19 | return this.prop.startsWith("--") || this.prop[0] === "$"; 20 | } 21 | } 22 | 23 | export default Declaration; 24 | 25 | Declaration.default = Declaration; 26 | -------------------------------------------------------------------------------- /deno/lib/deps.js: -------------------------------------------------------------------------------- 1 | export { 2 | fileURLToPath, 3 | pathToFileURL, 4 | } from "https://deno.land/std@0.159.0/node/url.ts"; 5 | 6 | export { 7 | existsSync, 8 | readFileSync, 9 | } from "https://deno.land/std@0.159.0/node/fs.ts"; 10 | 11 | export { 12 | basename, 13 | dirname, 14 | isAbsolute, 15 | join, 16 | relative, 17 | resolve, 18 | sep, 19 | } from "https://deno.land/std@0.159.0/node/path.ts"; 20 | 21 | export { Buffer } from "https://deno.land/std@0.159.0/node/buffer.ts"; 22 | export { nanoid } from "https://deno.land/x/nanoid@v3.0.0/nanoid.ts"; 23 | 24 | import { 25 | bold, 26 | cyan, 27 | getColorEnabled, 28 | gray, 29 | green, 30 | magenta, 31 | red, 32 | yellow, 33 | } from "https://deno.land/std@0.159.0/fmt/colors.ts"; 34 | 35 | export const pico = { 36 | isColorSupported: getColorEnabled(), 37 | createColors: () => ({ bold, red, gray }), 38 | cyan, 39 | gray, 40 | green, 41 | yellow, 42 | magenta, 43 | }; 44 | -------------------------------------------------------------------------------- /deno/lib/document.d.ts: -------------------------------------------------------------------------------- 1 | import Container, { ContainerProps } from "./container.js"; 2 | import { ProcessOptions } from "./postcss.js"; 3 | import Result from "./result.js"; 4 | import Root, { RootProps } from "./root.js"; 5 | 6 | export interface DocumentProps extends ContainerProps { 7 | nodes?: Root[]; 8 | 9 | /** 10 | * Information to generate byte-to-byte equal node string as it was 11 | * in the origin input. 12 | * 13 | * Every parser saves its own properties. 14 | */ 15 | raws?: Record; 16 | } 17 | 18 | type ChildNode = Root; 19 | type ChildProps = RootProps; 20 | 21 | /** 22 | * Represents a file and contains all its parsed nodes. 23 | * 24 | * **Experimental:** some aspects of this node could change within minor 25 | * or patch version releases. 26 | * 27 | * ```js 28 | * const document = htmlParser( 29 | * '' 30 | * ) 31 | * document.type //=> 'document' 32 | * document.nodes.length //=> 2 33 | * ``` 34 | */ 35 | export default class Document extends Container { 36 | type: "document"; 37 | parent: undefined; 38 | 39 | constructor(defaults?: DocumentProps); 40 | 41 | /** 42 | * Returns a `Result` instance representing the document’s CSS roots. 43 | * 44 | * ```js 45 | * const root1 = postcss.parse(css1, { from: 'a.css' }) 46 | * const root2 = postcss.parse(css2, { from: 'b.css' }) 47 | * const document = postcss.document() 48 | * document.append(root1) 49 | * document.append(root2) 50 | * const result = document.toResult({ to: 'all.css', map: true }) 51 | * ``` 52 | * 53 | * @param opts Options. 54 | * @return Result with current document’s CSS. 55 | */ 56 | toResult(options?: ProcessOptions): Result; 57 | } 58 | -------------------------------------------------------------------------------- /deno/lib/document.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import Container from "./container.js"; 4 | 5 | let LazyResult, Processor; 6 | 7 | class Document extends Container { 8 | constructor(defaults) { 9 | // type needs to be passed to super, otherwise child roots won't be normalized correctly 10 | super({ type: "document", ...defaults }); 11 | 12 | if (!this.nodes) { 13 | this.nodes = []; 14 | } 15 | } 16 | 17 | toResult(opts = {}) { 18 | let lazy = new LazyResult(new Processor(), this, opts); 19 | 20 | return lazy.stringify(); 21 | } 22 | } 23 | 24 | Document.registerLazyResult = (dependant) => { 25 | LazyResult = dependant; 26 | }; 27 | 28 | Document.registerProcessor = (dependant) => { 29 | Processor = dependant; 30 | }; 31 | 32 | export default Document; 33 | 34 | Document.default = Document; 35 | -------------------------------------------------------------------------------- /deno/lib/fromJSON.d.ts: -------------------------------------------------------------------------------- 1 | import { JSONHydrator } from "./postcss.js"; 2 | 3 | declare const fromJSON: JSONHydrator; 4 | 5 | export default fromJSON; 6 | -------------------------------------------------------------------------------- /deno/lib/fromJSON.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import Declaration from "./declaration.js"; 4 | import PreviousMap from "./previous-map.js"; 5 | import Comment from "./comment.js"; 6 | import AtRule from "./at-rule.js"; 7 | import Input from "./input.js"; 8 | import Root from "./root.js"; 9 | import Rule from "./rule.js"; 10 | 11 | function fromJSON(json, inputs) { 12 | if (Array.isArray(json)) return json.map((n) => fromJSON(n)); 13 | 14 | let { inputs: ownInputs, ...defaults } = json; 15 | if (ownInputs) { 16 | inputs = []; 17 | for (let input of ownInputs) { 18 | let inputHydrated = { ...input, __proto__: Input.prototype }; 19 | if (inputHydrated.map) { 20 | inputHydrated.map = { 21 | ...inputHydrated.map, 22 | __proto__: PreviousMap.prototype, 23 | }; 24 | } 25 | inputs.push(inputHydrated); 26 | } 27 | } 28 | if (defaults.nodes) { 29 | defaults.nodes = json.nodes.map((n) => fromJSON(n, inputs)); 30 | } 31 | if (defaults.source) { 32 | let { inputId, ...source } = defaults.source; 33 | defaults.source = source; 34 | if (inputId != null) { 35 | defaults.source.input = inputs[inputId]; 36 | } 37 | } 38 | if (defaults.type === "root") { 39 | return new Root(defaults); 40 | } else if (defaults.type === "decl") { 41 | return new Declaration(defaults); 42 | } else if (defaults.type === "rule") { 43 | return new Rule(defaults); 44 | } else if (defaults.type === "comment") { 45 | return new Comment(defaults); 46 | } else if (defaults.type === "atrule") { 47 | return new AtRule(defaults); 48 | } else { 49 | throw new Error("Unknown node type: " + json.type); 50 | } 51 | } 52 | 53 | export default fromJSON; 54 | 55 | fromJSON.default = fromJSON; 56 | -------------------------------------------------------------------------------- /deno/lib/input.d.ts: -------------------------------------------------------------------------------- 1 | import { ProcessOptions } from "./postcss.js"; 2 | import PreviousMap from "./previous-map.js"; 3 | 4 | export interface FilePosition { 5 | /** 6 | * URL for the source file. 7 | */ 8 | url: string; 9 | 10 | /** 11 | * Absolute path to the source file. 12 | */ 13 | file?: string; 14 | 15 | /** 16 | * Line of inclusive start position in source file. 17 | */ 18 | line: number; 19 | 20 | /** 21 | * Column of inclusive start position in source file. 22 | */ 23 | column: number; 24 | 25 | /** 26 | * Line of exclusive end position in source file. 27 | */ 28 | endLine?: number; 29 | 30 | /** 31 | * Column of exclusive end position in source file. 32 | */ 33 | endColumn?: number; 34 | 35 | /** 36 | * Source code. 37 | */ 38 | source?: string; 39 | } 40 | 41 | /** 42 | * Represents the source CSS. 43 | * 44 | * ```js 45 | * const root = postcss.parse(css, { from: file }) 46 | * const input = root.source.input 47 | * ``` 48 | */ 49 | export default class Input { 50 | /** 51 | * Input CSS source. 52 | * 53 | * ```js 54 | * const input = postcss.parse('a{}', { from: file }).input 55 | * input.css //=> "a{}" 56 | * ``` 57 | */ 58 | css: string; 59 | 60 | /** 61 | * The input source map passed from a compilation step before PostCSS 62 | * (for example, from Sass compiler). 63 | * 64 | * ```js 65 | * root.source.input.map.consumer().sources //=> ['a.sass'] 66 | * ``` 67 | */ 68 | map: PreviousMap; 69 | 70 | /** 71 | * The absolute path to the CSS source file defined 72 | * with the `from` option. 73 | * 74 | * ```js 75 | * const root = postcss.parse(css, { from: 'a.css' }) 76 | * root.source.input.file //=> '/home/ai/a.css' 77 | * ``` 78 | */ 79 | file?: string; 80 | 81 | /** 82 | * The unique ID of the CSS source. It will be created if `from` option 83 | * is not provided (because PostCSS does not know the file path). 84 | * 85 | * ```js 86 | * const root = postcss.parse(css) 87 | * root.source.input.file //=> undefined 88 | * root.source.input.id //=> "" 89 | * ``` 90 | */ 91 | id?: string; 92 | 93 | /** 94 | * The flag to indicate whether or not the source code has Unicode BOM. 95 | */ 96 | hasBOM: boolean; 97 | 98 | /** 99 | * @param css Input CSS source. 100 | * @param opts Process options. 101 | */ 102 | constructor(css: string, opts?: ProcessOptions); 103 | 104 | /** 105 | * The CSS source identifier. Contains `Input#file` if the user 106 | * set the `from` option, or `Input#id` if they did not. 107 | * 108 | * ```js 109 | * const root = postcss.parse(css, { from: 'a.css' }) 110 | * root.source.input.from //=> "/home/ai/a.css" 111 | * 112 | * const root = postcss.parse(css) 113 | * root.source.input.from //=> "" 114 | * ``` 115 | */ 116 | get from(): string; 117 | 118 | /** 119 | * Reads the input source map and returns a symbol position 120 | * in the input source (e.g., in a Sass file that was compiled 121 | * to CSS before being passed to PostCSS). Optionally takes an 122 | * end position, exclusive. 123 | * 124 | * ```js 125 | * root.source.input.origin(1, 1) //=> { file: 'a.css', line: 3, column: 1 } 126 | * root.source.input.origin(1, 1, 1, 4) 127 | * //=> { file: 'a.css', line: 3, column: 1, endLine: 3, endColumn: 4 } 128 | * ``` 129 | * 130 | * @param line Line for inclusive start position in input CSS. 131 | * @param column Column for inclusive start position in input CSS. 132 | * @param endLine Line for exclusive end position in input CSS. 133 | * @param endColumn Column for exclusive end position in input CSS. 134 | * 135 | * @return Position in input source. 136 | */ 137 | origin( 138 | line: number, 139 | column: number, 140 | endLine?: number, 141 | endColumn?: number, 142 | ): FilePosition | false; 143 | 144 | /** 145 | * Converts source offset to line and column. 146 | * 147 | * @param offset Source offset. 148 | */ 149 | fromOffset(offset: number): { line: number; col: number } | null; 150 | } 151 | -------------------------------------------------------------------------------- /deno/lib/input.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { SourceMapConsumer, SourceMapGenerator } from "./source_map.ts"; 4 | import { fileURLToPath, pathToFileURL } from "./deps.js"; 5 | import { isAbsolute, resolve } from "./deps.js"; 6 | import { nanoid } from "./deps.js"; 7 | import terminalHighlight from "./terminal-highlight.js"; 8 | import CssSyntaxError from "./css-syntax-error.js"; 9 | import PreviousMap from "./previous-map.js"; 10 | 11 | let fromOffsetCache = Symbol("fromOffsetCache"); 12 | 13 | let sourceMapAvailable = Boolean(SourceMapConsumer && SourceMapGenerator); 14 | let pathAvailable = Boolean(resolve && isAbsolute); 15 | 16 | class Input { 17 | constructor(css, opts = {}) { 18 | if ( 19 | css === null || 20 | typeof css === "undefined" || 21 | (typeof css === "object" && !css.toString) 22 | ) { 23 | throw new Error(`PostCSS received ${css} instead of CSS string`); 24 | } 25 | 26 | this.css = css.toString(); 27 | 28 | if (this.css[0] === "\uFEFF" || this.css[0] === "\uFFFE") { 29 | this.hasBOM = true; 30 | this.css = this.css.slice(1); 31 | } else { 32 | this.hasBOM = false; 33 | } 34 | 35 | if (opts.from) { 36 | if ( 37 | !pathAvailable || 38 | /^\w+:\/\//.test(opts.from) || 39 | isAbsolute(opts.from) 40 | ) { 41 | this.file = opts.from; 42 | } else { 43 | this.file = resolve(opts.from); 44 | } 45 | } 46 | 47 | if (pathAvailable && sourceMapAvailable) { 48 | let map = new PreviousMap(this.css, opts); 49 | if (map.text) { 50 | this.map = map; 51 | let file = map.consumer().file; 52 | if (!this.file && file) this.file = this.mapResolve(file); 53 | } 54 | } 55 | 56 | if (!this.file) { 57 | this.id = ""; 58 | } 59 | if (this.map) this.map.file = this.from; 60 | } 61 | 62 | fromOffset(offset) { 63 | let lastLine, lineToIndex; 64 | if (!this[fromOffsetCache]) { 65 | let lines = this.css.split("\n"); 66 | lineToIndex = new Array(lines.length); 67 | let prevIndex = 0; 68 | 69 | for (let i = 0, l = lines.length; i < l; i++) { 70 | lineToIndex[i] = prevIndex; 71 | prevIndex += lines[i].length + 1; 72 | } 73 | 74 | this[fromOffsetCache] = lineToIndex; 75 | } else { 76 | lineToIndex = this[fromOffsetCache]; 77 | } 78 | lastLine = lineToIndex[lineToIndex.length - 1]; 79 | 80 | let min = 0; 81 | if (offset >= lastLine) { 82 | min = lineToIndex.length - 1; 83 | } else { 84 | let max = lineToIndex.length - 2; 85 | let mid; 86 | while (min < max) { 87 | mid = min + ((max - min) >> 1); 88 | if (offset < lineToIndex[mid]) { 89 | max = mid - 1; 90 | } else if (offset >= lineToIndex[mid + 1]) { 91 | min = mid + 1; 92 | } else { 93 | min = mid; 94 | break; 95 | } 96 | } 97 | } 98 | return { 99 | line: min + 1, 100 | col: offset - lineToIndex[min] + 1, 101 | }; 102 | } 103 | 104 | error(message, line, column, opts = {}) { 105 | let result, endLine, endColumn; 106 | 107 | if (line && typeof line === "object") { 108 | let start = line; 109 | let end = column; 110 | if (typeof line.offset === "number") { 111 | let pos = this.fromOffset(start.offset); 112 | line = pos.line; 113 | column = pos.col; 114 | } else { 115 | line = start.line; 116 | column = start.column; 117 | } 118 | if (typeof end.offset === "number") { 119 | let pos = this.fromOffset(end.offset); 120 | endLine = pos.line; 121 | endColumn = pos.col; 122 | } else { 123 | endLine = end.line; 124 | endColumn = end.column; 125 | } 126 | } else if (!column) { 127 | let pos = this.fromOffset(line); 128 | line = pos.line; 129 | column = pos.col; 130 | } 131 | 132 | let origin = this.origin(line, column, endLine, endColumn); 133 | if (origin) { 134 | result = new CssSyntaxError( 135 | message, 136 | origin.endLine === undefined 137 | ? origin.line 138 | : { line: origin.line, column: origin.column }, 139 | origin.endLine === undefined 140 | ? origin.column 141 | : { line: origin.endLine, column: origin.endColumn }, 142 | origin.source, 143 | origin.file, 144 | opts.plugin, 145 | ); 146 | } else { 147 | result = new CssSyntaxError( 148 | message, 149 | endLine === undefined ? line : { line, column }, 150 | endLine === undefined ? column : { line: endLine, column: endColumn }, 151 | this.css, 152 | this.file, 153 | opts.plugin, 154 | ); 155 | } 156 | 157 | result.input = { line, column, endLine, endColumn, source: this.css }; 158 | if (this.file) { 159 | if (pathToFileURL) { 160 | result.input.url = pathToFileURL(this.file).toString(); 161 | } 162 | result.input.file = this.file; 163 | } 164 | 165 | return result; 166 | } 167 | 168 | origin(line, column, endLine, endColumn) { 169 | if (!this.map) return false; 170 | let consumer = this.map.consumer(); 171 | 172 | let from = consumer.originalPositionFor({ line, column }); 173 | if (!from.source) return false; 174 | 175 | let to; 176 | if (typeof endLine === "number") { 177 | to = consumer.originalPositionFor({ line: endLine, column: endColumn }); 178 | } 179 | 180 | let fromUrl; 181 | 182 | if (isAbsolute(from.source)) { 183 | fromUrl = pathToFileURL(from.source); 184 | } else { 185 | fromUrl = new URL( 186 | from.source, 187 | this.map.consumer().sourceRoot || pathToFileURL(this.map.mapFile), 188 | ); 189 | } 190 | 191 | let result = { 192 | url: fromUrl.toString(), 193 | line: from.line, 194 | column: from.column, 195 | endLine: to && to.line, 196 | endColumn: to && to.column, 197 | }; 198 | 199 | if (fromUrl.protocol === "file:") { 200 | if (fileURLToPath) { 201 | result.file = fileURLToPath(fromUrl); 202 | } else { 203 | /* c8 ignore next 2 */ 204 | throw new Error( 205 | `file: protocol is not available in this PostCSS build`, 206 | ); 207 | } 208 | } 209 | 210 | let source = consumer.sourceContentFor(from.source); 211 | if (source) result.source = source; 212 | 213 | return result; 214 | } 215 | 216 | mapResolve(file) { 217 | if (/^\w+:\/\//.test(file)) { 218 | return file; 219 | } 220 | return resolve( 221 | this.map.consumer().sourceRoot || this.map.root || ".", 222 | file, 223 | ); 224 | } 225 | 226 | get from() { 227 | return this.file || this.id; 228 | } 229 | 230 | toJSON() { 231 | let json = {}; 232 | for (let name of ["hasBOM", "css", "file", "id"]) { 233 | if (this[name] != null) { 234 | json[name] = this[name]; 235 | } 236 | } 237 | if (this.map) { 238 | json.map = { ...this.map }; 239 | if (json.map.consumerCache) { 240 | json.map.consumerCache = undefined; 241 | } 242 | } 243 | return json; 244 | } 245 | } 246 | 247 | export default Input; 248 | 249 | Input.default = Input; 250 | 251 | if (terminalHighlight && terminalHighlight.registerInput) { 252 | terminalHighlight.registerInput(Input); 253 | } 254 | -------------------------------------------------------------------------------- /deno/lib/lazy-result.d.ts: -------------------------------------------------------------------------------- 1 | import Result, { Message, ResultOptions } from "./result.js"; 2 | import { SourceMap } from "./postcss.js"; 3 | import Processor from "./processor.js"; 4 | import Warning from "./warning.js"; 5 | import Root from "./root.js"; 6 | 7 | /** 8 | * A Promise proxy for the result of PostCSS transformations. 9 | * 10 | * A `LazyResult` instance is returned by `Processor#process`. 11 | * 12 | * ```js 13 | * const lazy = postcss([autoprefixer]).process(css) 14 | * ``` 15 | */ 16 | export default class LazyResult implements PromiseLike { 17 | /** 18 | * Processes input CSS through synchronous and asynchronous plugins 19 | * and calls `onFulfilled` with a Result instance. If a plugin throws 20 | * an error, the `onRejected` callback will be executed. 21 | * 22 | * It implements standard Promise API. 23 | * 24 | * ```js 25 | * postcss([autoprefixer]).process(css, { from: cssPath }).then(result => { 26 | * console.log(result.css) 27 | * }) 28 | * ``` 29 | */ 30 | then: Promise["then"]; 31 | 32 | /** 33 | * Processes input CSS through synchronous and asynchronous plugins 34 | * and calls onRejected for each error thrown in any plugin. 35 | * 36 | * It implements standard Promise API. 37 | * 38 | * ```js 39 | * postcss([autoprefixer]).process(css).then(result => { 40 | * console.log(result.css) 41 | * }).catch(error => { 42 | * console.error(error) 43 | * }) 44 | * ``` 45 | */ 46 | catch: Promise["catch"]; 47 | 48 | /** 49 | * Processes input CSS through synchronous and asynchronous plugins 50 | * and calls onFinally on any error or when all plugins will finish work. 51 | * 52 | * It implements standard Promise API. 53 | * 54 | * ```js 55 | * postcss([autoprefixer]).process(css).finally(() => { 56 | * console.log('processing ended') 57 | * }) 58 | * ``` 59 | */ 60 | finally: Promise["finally"]; 61 | 62 | /** 63 | * @param processor Processor used for this transformation. 64 | * @param css CSS to parse and transform. 65 | * @param opts Options from the `Processor#process` or `Root#toResult`. 66 | */ 67 | constructor(processor: Processor, css: string, opts: ResultOptions); 68 | 69 | /** 70 | * Returns the default string description of an object. 71 | * Required to implement the Promise interface. 72 | */ 73 | get [Symbol.toStringTag](): string; 74 | 75 | /** 76 | * Returns a `Processor` instance, which will be used 77 | * for CSS transformations. 78 | */ 79 | get processor(): Processor; 80 | 81 | /** 82 | * Options from the `Processor#process` call. 83 | */ 84 | get opts(): ResultOptions; 85 | 86 | /** 87 | * Processes input CSS through synchronous plugins, converts `Root` 88 | * to a CSS string and returns `Result#css`. 89 | * 90 | * This property will only work with synchronous plugins. 91 | * If the processor contains any asynchronous plugins 92 | * it will throw an error. 93 | * 94 | * PostCSS runners should always use `LazyResult#then`. 95 | */ 96 | get css(): string; 97 | 98 | /** 99 | * An alias for the `css` property. Use it with syntaxes 100 | * that generate non-CSS output. 101 | * 102 | * This property will only work with synchronous plugins. 103 | * If the processor contains any asynchronous plugins 104 | * it will throw an error. 105 | * 106 | * PostCSS runners should always use `LazyResult#then`. 107 | */ 108 | get content(): string; 109 | 110 | /** 111 | * Processes input CSS through synchronous plugins 112 | * and returns `Result#map`. 113 | * 114 | * This property will only work with synchronous plugins. 115 | * If the processor contains any asynchronous plugins 116 | * it will throw an error. 117 | * 118 | * PostCSS runners should always use `LazyResult#then`. 119 | */ 120 | get map(): SourceMap; 121 | 122 | /** 123 | * Processes input CSS through synchronous plugins 124 | * and returns `Result#root`. 125 | * 126 | * This property will only work with synchronous plugins. If the processor 127 | * contains any asynchronous plugins it will throw an error. 128 | * 129 | * PostCSS runners should always use `LazyResult#then`. 130 | */ 131 | get root(): Root; 132 | 133 | /** 134 | * Processes input CSS through synchronous plugins 135 | * and returns `Result#messages`. 136 | * 137 | * This property will only work with synchronous plugins. If the processor 138 | * contains any asynchronous plugins it will throw an error. 139 | * 140 | * PostCSS runners should always use `LazyResult#then`. 141 | */ 142 | get messages(): Message[]; 143 | 144 | /** 145 | * Processes input CSS through synchronous plugins 146 | * and calls `Result#warnings`. 147 | * 148 | * @return Warnings from plugins. 149 | */ 150 | warnings(): Warning[]; 151 | 152 | /** 153 | * Alias for the `LazyResult#css` property. 154 | * 155 | * ```js 156 | * lazy + '' === lazy.css 157 | * ``` 158 | * 159 | * @return Output CSS. 160 | */ 161 | toString(): string; 162 | 163 | /** 164 | * Run plugin in sync way and return `Result`. 165 | * 166 | * @return Result with output content. 167 | */ 168 | sync(): Result; 169 | 170 | /** 171 | * Run plugin in async way and return `Result`. 172 | * 173 | * @return Result with output content. 174 | */ 175 | async(): Promise; 176 | } 177 | -------------------------------------------------------------------------------- /deno/lib/list.d.ts: -------------------------------------------------------------------------------- 1 | export type List = { 2 | /** 3 | * Safely splits values. 4 | * 5 | * ```js 6 | * Once (root, { list }) { 7 | * list.split('1px calc(10% + 1px)', [' ', '\n', '\t']) //=> ['1px', 'calc(10% + 1px)'] 8 | * } 9 | * ``` 10 | * 11 | * @param string separated values. 12 | * @param separators array of separators. 13 | * @param last boolean indicator. 14 | * @return Split values. 15 | */ 16 | split(string: string, separators: string[], last: boolean): string[]; 17 | /** 18 | * Safely splits space-separated values (such as those for `background`, 19 | * `border-radius`, and other shorthand properties). 20 | * 21 | * ```js 22 | * Once (root, { list }) { 23 | * list.space('1px calc(10% + 1px)') //=> ['1px', 'calc(10% + 1px)'] 24 | * } 25 | * ``` 26 | * 27 | * @param str Space-separated values. 28 | * @return Split values. 29 | */ 30 | space(str: string): string[]; 31 | 32 | /** 33 | * Safely splits comma-separated values (such as those for `transition-*` 34 | * and `background` properties). 35 | * 36 | * ```js 37 | * Once (root, { list }) { 38 | * list.comma('black, linear-gradient(white, black)') 39 | * //=> ['black', 'linear-gradient(white, black)'] 40 | * } 41 | * ``` 42 | * 43 | * @param str Comma-separated values. 44 | * @return Split values. 45 | */ 46 | comma(str: string): string[]; 47 | }; 48 | 49 | declare const list: List; 50 | 51 | export default list; 52 | -------------------------------------------------------------------------------- /deno/lib/list.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | let list = { 4 | split(string, separators, last) { 5 | let array = []; 6 | let current = ""; 7 | let split = false; 8 | 9 | let func = 0; 10 | let inQuote = false; 11 | let prevQuote = ""; 12 | let escape = false; 13 | 14 | for (let letter of string) { 15 | if (escape) { 16 | escape = false; 17 | } else if (letter === "\\") { 18 | escape = true; 19 | } else if (inQuote) { 20 | if (letter === prevQuote) { 21 | inQuote = false; 22 | } 23 | } else if (letter === '"' || letter === "'") { 24 | inQuote = true; 25 | prevQuote = letter; 26 | } else if (letter === "(") { 27 | func += 1; 28 | } else if (letter === ")") { 29 | if (func > 0) func -= 1; 30 | } else if (func === 0) { 31 | if (separators.includes(letter)) split = true; 32 | } 33 | 34 | if (split) { 35 | if (current !== "") array.push(current.trim()); 36 | current = ""; 37 | split = false; 38 | } else { 39 | current += letter; 40 | } 41 | } 42 | 43 | if (last || current !== "") array.push(current.trim()); 44 | return array; 45 | }, 46 | 47 | space(string) { 48 | let spaces = [" ", "\n", "\t"]; 49 | return list.split(string, spaces); 50 | }, 51 | 52 | comma(string) { 53 | return list.split(string, [","], true); 54 | }, 55 | }; 56 | 57 | export default list; 58 | 59 | list.default = list; 60 | -------------------------------------------------------------------------------- /deno/lib/map-generator.js: -------------------------------------------------------------------------------- 1 | import { Buffer } from "./deps.js"; 2 | import { SourceMapConsumer, SourceMapGenerator } from "./source_map.ts"; 3 | import { dirname, relative, resolve, sep } from "./deps.js"; 4 | import { pathToFileURL } from "./deps.js"; 5 | import Input from "./input.js"; 6 | 7 | let sourceMapAvailable = Boolean(SourceMapConsumer && SourceMapGenerator); 8 | let pathAvailable = Boolean(dirname && resolve && relative && sep); 9 | 10 | class MapGenerator { 11 | constructor(stringify, root, opts, cssString) { 12 | this.stringify = stringify; 13 | this.mapOpts = opts.map || {}; 14 | this.root = root; 15 | this.opts = opts; 16 | this.css = cssString; 17 | this.usesFileUrls = !this.mapOpts.from && this.mapOpts.absolute; 18 | } 19 | 20 | isMap() { 21 | if (typeof this.opts.map !== "undefined") { 22 | return !!this.opts.map; 23 | } 24 | return this.previous().length > 0; 25 | } 26 | 27 | previous() { 28 | if (!this.previousMaps) { 29 | this.previousMaps = []; 30 | if (this.root) { 31 | this.root.walk((node) => { 32 | if (node.source && node.source.input.map) { 33 | let map = node.source.input.map; 34 | if (!this.previousMaps.includes(map)) { 35 | this.previousMaps.push(map); 36 | } 37 | } 38 | }); 39 | } else { 40 | let input = new Input(this.css, this.opts); 41 | if (input.map) this.previousMaps.push(input.map); 42 | } 43 | } 44 | 45 | return this.previousMaps; 46 | } 47 | 48 | isInline() { 49 | if (typeof this.mapOpts.inline !== "undefined") { 50 | return this.mapOpts.inline; 51 | } 52 | 53 | let annotation = this.mapOpts.annotation; 54 | if (typeof annotation !== "undefined" && annotation !== true) { 55 | return false; 56 | } 57 | 58 | if (this.previous().length) { 59 | return this.previous().some((i) => i.inline); 60 | } 61 | return true; 62 | } 63 | 64 | isSourcesContent() { 65 | if (typeof this.mapOpts.sourcesContent !== "undefined") { 66 | return this.mapOpts.sourcesContent; 67 | } 68 | if (this.previous().length) { 69 | return this.previous().some((i) => i.withContent()); 70 | } 71 | return true; 72 | } 73 | 74 | clearAnnotation() { 75 | if (this.mapOpts.annotation === false) return; 76 | 77 | if (this.root) { 78 | let node; 79 | for (let i = this.root.nodes.length - 1; i >= 0; i--) { 80 | node = this.root.nodes[i]; 81 | if (node.type !== "comment") continue; 82 | if (node.text.indexOf("# sourceMappingURL=") === 0) { 83 | this.root.removeChild(i); 84 | } 85 | } 86 | } else if (this.css) { 87 | this.css = this.css.replace(/(\n)?\/\*#[\S\s]*?\*\/$/gm, ""); 88 | } 89 | } 90 | 91 | setSourcesContent() { 92 | let already = {}; 93 | if (this.root) { 94 | this.root.walk((node) => { 95 | if (node.source) { 96 | let from = node.source.input.from; 97 | if (from && !already[from]) { 98 | already[from] = true; 99 | let fromUrl = this.usesFileUrls 100 | ? this.toFileUrl(from) 101 | : this.toUrl(this.path(from)); 102 | this.map.setSourceContent(fromUrl, node.source.input.css); 103 | } 104 | } 105 | }); 106 | } else if (this.css) { 107 | let from = this.opts.from 108 | ? this.toUrl(this.path(this.opts.from)) 109 | : ""; 110 | this.map.setSourceContent(from, this.css); 111 | } 112 | } 113 | 114 | applyPrevMaps() { 115 | for (let prev of this.previous()) { 116 | let from = this.toUrl(this.path(prev.file)); 117 | let root = prev.root || dirname(prev.file); 118 | let map; 119 | 120 | if (this.mapOpts.sourcesContent === false) { 121 | map = new SourceMapConsumer(prev.text); 122 | if (map.sourcesContent) { 123 | map.sourcesContent = map.sourcesContent.map(() => null); 124 | } 125 | } else { 126 | map = prev.consumer(); 127 | } 128 | 129 | this.map.applySourceMap(map, from, this.toUrl(this.path(root))); 130 | } 131 | } 132 | 133 | isAnnotation() { 134 | if (this.isInline()) { 135 | return true; 136 | } 137 | if (typeof this.mapOpts.annotation !== "undefined") { 138 | return this.mapOpts.annotation; 139 | } 140 | if (this.previous().length) { 141 | return this.previous().some((i) => i.annotation); 142 | } 143 | return true; 144 | } 145 | 146 | toBase64(str) { 147 | if (Buffer) { 148 | return Buffer.from(str).toString("base64"); 149 | } else { 150 | return window.btoa(unescape(encodeURIComponent(str))); 151 | } 152 | } 153 | 154 | addAnnotation() { 155 | let content; 156 | 157 | if (this.isInline()) { 158 | content = "data:application/json;base64," + 159 | this.toBase64(this.map.toString()); 160 | } else if (typeof this.mapOpts.annotation === "string") { 161 | content = this.mapOpts.annotation; 162 | } else if (typeof this.mapOpts.annotation === "function") { 163 | content = this.mapOpts.annotation(this.opts.to, this.root); 164 | } else { 165 | content = this.outputFile() + ".map"; 166 | } 167 | let eol = "\n"; 168 | if (this.css.includes("\r\n")) eol = "\r\n"; 169 | 170 | this.css += eol + "/*# sourceMappingURL=" + content + " */"; 171 | } 172 | 173 | outputFile() { 174 | if (this.opts.to) { 175 | return this.path(this.opts.to); 176 | } else if (this.opts.from) { 177 | return this.path(this.opts.from); 178 | } else { 179 | return "to.css"; 180 | } 181 | } 182 | 183 | generateMap() { 184 | if (this.root) { 185 | this.generateString(); 186 | } else if (this.previous().length === 1) { 187 | let prev = this.previous()[0].consumer(); 188 | prev.file = this.outputFile(); 189 | this.map = SourceMapGenerator.fromSourceMap(prev); 190 | } else { 191 | this.map = new SourceMapGenerator({ file: this.outputFile() }); 192 | this.map.addMapping({ 193 | source: this.opts.from 194 | ? this.toUrl(this.path(this.opts.from)) 195 | : "", 196 | generated: { line: 1, column: 0 }, 197 | original: { line: 1, column: 0 }, 198 | }); 199 | } 200 | 201 | if (this.isSourcesContent()) this.setSourcesContent(); 202 | if (this.root && this.previous().length > 0) this.applyPrevMaps(); 203 | if (this.isAnnotation()) this.addAnnotation(); 204 | 205 | if (this.isInline()) { 206 | return [this.css]; 207 | } else { 208 | return [this.css, this.map]; 209 | } 210 | } 211 | 212 | path(file) { 213 | if (file.indexOf("<") === 0) return file; 214 | if (/^\w+:\/\//.test(file)) return file; 215 | if (this.mapOpts.absolute) return file; 216 | 217 | let from = this.opts.to ? dirname(this.opts.to) : "."; 218 | 219 | if (typeof this.mapOpts.annotation === "string") { 220 | from = dirname(resolve(from, this.mapOpts.annotation)); 221 | } 222 | 223 | file = relative(from, file); 224 | return file; 225 | } 226 | 227 | toUrl(path) { 228 | if (sep === "\\") { 229 | path = path.replace(/\\/g, "/"); 230 | } 231 | return encodeURI(path).replace(/[#?]/g, encodeURIComponent); 232 | } 233 | 234 | toFileUrl(path) { 235 | if (pathToFileURL) { 236 | return pathToFileURL(path).toString(); 237 | } else { 238 | throw new Error( 239 | "`map.absolute` option is not available in this PostCSS build", 240 | ); 241 | } 242 | } 243 | 244 | sourcePath(node) { 245 | if (this.mapOpts.from) { 246 | return this.toUrl(this.mapOpts.from); 247 | } else if (this.usesFileUrls) { 248 | return this.toFileUrl(node.source.input.from); 249 | } else { 250 | return this.toUrl(this.path(node.source.input.from)); 251 | } 252 | } 253 | 254 | generateString() { 255 | this.css = ""; 256 | this.map = new SourceMapGenerator({ file: this.outputFile() }); 257 | 258 | let line = 1; 259 | let column = 1; 260 | 261 | let noSource = ""; 262 | let mapping = { 263 | source: "", 264 | generated: { line: 0, column: 0 }, 265 | original: { line: 0, column: 0 }, 266 | }; 267 | 268 | let lines, last; 269 | this.stringify(this.root, (str, node, type) => { 270 | this.css += str; 271 | 272 | if (node && type !== "end") { 273 | mapping.generated.line = line; 274 | mapping.generated.column = column - 1; 275 | if (node.source && node.source.start) { 276 | mapping.source = this.sourcePath(node); 277 | mapping.original.line = node.source.start.line; 278 | mapping.original.column = node.source.start.column - 1; 279 | this.map.addMapping(mapping); 280 | } else { 281 | mapping.source = noSource; 282 | mapping.original.line = 1; 283 | mapping.original.column = 0; 284 | this.map.addMapping(mapping); 285 | } 286 | } 287 | 288 | lines = str.match(/\n/g); 289 | if (lines) { 290 | line += lines.length; 291 | last = str.lastIndexOf("\n"); 292 | column = str.length - last; 293 | } else { 294 | column += str.length; 295 | } 296 | 297 | if (node && type !== "start") { 298 | let p = node.parent || { raws: {} }; 299 | if (node.type !== "decl" || node !== p.last || p.raws.semicolon) { 300 | if (node.source && node.source.end) { 301 | mapping.source = this.sourcePath(node); 302 | mapping.original.line = node.source.end.line; 303 | mapping.original.column = node.source.end.column - 1; 304 | mapping.generated.line = line; 305 | mapping.generated.column = column - 2; 306 | this.map.addMapping(mapping); 307 | } else { 308 | mapping.source = noSource; 309 | mapping.original.line = 1; 310 | mapping.original.column = 0; 311 | mapping.generated.line = line; 312 | mapping.generated.column = column - 1; 313 | this.map.addMapping(mapping); 314 | } 315 | } 316 | } 317 | }); 318 | } 319 | 320 | generate() { 321 | this.clearAnnotation(); 322 | if (pathAvailable && sourceMapAvailable && this.isMap()) { 323 | return this.generateMap(); 324 | } else { 325 | let result = ""; 326 | this.stringify(this.root, (i) => { 327 | result += i; 328 | }); 329 | return [result]; 330 | } 331 | } 332 | } 333 | 334 | export default MapGenerator; 335 | -------------------------------------------------------------------------------- /deno/lib/no-work-result.d.ts: -------------------------------------------------------------------------------- 1 | import Result, { Message, ResultOptions } from "./result.js"; 2 | import { SourceMap } from "./postcss.js"; 3 | import Processor from "./processor.js"; 4 | import Warning from "./warning.js"; 5 | import Root from "./root.js"; 6 | import LazyResult from "./lazy-result.js"; 7 | 8 | /** 9 | * A Promise proxy for the result of PostCSS transformations. 10 | * This lazy result instance doesn't parse css unless `NoWorkResult#root` or `Result#root` 11 | * are accessed. See the example below for details. 12 | * A `NoWork` instance is returned by `Processor#process` ONLY when no plugins defined. 13 | * 14 | * ```js 15 | * const noWorkResult = postcss().process(css) // No plugins are defined. 16 | * // CSS is not parsed 17 | * let root = noWorkResult.root // now css is parsed because we accessed the root 18 | * ``` 19 | */ 20 | export default class NoWorkResult implements LazyResult { 21 | then: Promise["then"]; 22 | catch: Promise["catch"]; 23 | finally: Promise["finally"]; 24 | constructor(processor: Processor, css: string, opts: ResultOptions); 25 | get [Symbol.toStringTag](): string; 26 | get processor(): Processor; 27 | get opts(): ResultOptions; 28 | get css(): string; 29 | get content(): string; 30 | get map(): SourceMap; 31 | get root(): Root; 32 | get messages(): Message[]; 33 | warnings(): Warning[]; 34 | toString(): string; 35 | sync(): Result; 36 | async(): Promise; 37 | } 38 | -------------------------------------------------------------------------------- /deno/lib/no-work-result.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import MapGenerator from "./map-generator.js"; 4 | import stringify from "./stringify.js"; 5 | import warnOnce from "./warn-once.js"; 6 | import parse from "./parse.js"; 7 | import Result from "./result.js"; 8 | 9 | class NoWorkResult { 10 | constructor(processor, css, opts) { 11 | css = css.toString(); 12 | this.stringified = false; 13 | 14 | this._processor = processor; 15 | this._css = css; 16 | this._opts = opts; 17 | this._map = undefined; 18 | let root; 19 | 20 | let str = stringify; 21 | this.result = new Result(this._processor, root, this._opts); 22 | this.result.css = css; 23 | 24 | let self = this; 25 | Object.defineProperty(this.result, "root", { 26 | get() { 27 | return self.root; 28 | }, 29 | }); 30 | 31 | let map = new MapGenerator(str, root, this._opts, css); 32 | if (map.isMap()) { 33 | let [generatedCSS, generatedMap] = map.generate(); 34 | if (generatedCSS) { 35 | this.result.css = generatedCSS; 36 | } 37 | if (generatedMap) { 38 | this.result.map = generatedMap; 39 | } 40 | } 41 | } 42 | 43 | get [Symbol.toStringTag]() { 44 | return "NoWorkResult"; 45 | } 46 | 47 | get processor() { 48 | return this.result.processor; 49 | } 50 | 51 | get opts() { 52 | return this.result.opts; 53 | } 54 | 55 | get css() { 56 | return this.result.css; 57 | } 58 | 59 | get content() { 60 | return this.result.css; 61 | } 62 | 63 | get map() { 64 | return this.result.map; 65 | } 66 | 67 | get root() { 68 | if (this._root) { 69 | return this._root; 70 | } 71 | 72 | let root; 73 | let parser = parse; 74 | 75 | try { 76 | root = parser(this._css, this._opts); 77 | } catch (error) { 78 | this.error = error; 79 | } 80 | 81 | if (this.error) { 82 | throw this.error; 83 | } else { 84 | this._root = root; 85 | return root; 86 | } 87 | } 88 | 89 | get messages() { 90 | return []; 91 | } 92 | 93 | warnings() { 94 | return []; 95 | } 96 | 97 | toString() { 98 | return this._css; 99 | } 100 | 101 | then(onFulfilled, onRejected) { 102 | if (Deno.env.get("DENO_ENV") !== "production") { 103 | if (!("from" in this._opts)) { 104 | warnOnce( 105 | "Without `from` option PostCSS could generate wrong source map " + 106 | "and will not find Browserslist config. Set it to CSS file path " + 107 | "or to `undefined` to prevent this warning.", 108 | ); 109 | } 110 | } 111 | 112 | return this.async().then(onFulfilled, onRejected); 113 | } 114 | 115 | catch(onRejected) { 116 | return this.async().catch(onRejected); 117 | } 118 | 119 | finally(onFinally) { 120 | return this.async().then(onFinally, onFinally); 121 | } 122 | 123 | async() { 124 | if (this.error) return Promise.reject(this.error); 125 | return Promise.resolve(this.result); 126 | } 127 | 128 | sync() { 129 | if (this.error) throw this.error; 130 | return this.result; 131 | } 132 | } 133 | 134 | export default NoWorkResult; 135 | 136 | NoWorkResult.default = NoWorkResult; 137 | -------------------------------------------------------------------------------- /deno/lib/node.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { isClean, my } from "./symbols.js"; 4 | import CssSyntaxError from "./css-syntax-error.js"; 5 | import Stringifier from "./stringifier.js"; 6 | import stringify from "./stringify.js"; 7 | 8 | function cloneNode(obj, parent) { 9 | let cloned = new obj.constructor(); 10 | 11 | for (let i in obj) { 12 | if (!Object.prototype.hasOwnProperty.call(obj, i)) { 13 | /* c8 ignore next 2 */ 14 | continue; 15 | } 16 | if (i === "proxyCache") continue; 17 | let value = obj[i]; 18 | let type = typeof value; 19 | 20 | if (i === "parent" && type === "object") { 21 | if (parent) cloned[i] = parent; 22 | } else if (i === "source") { 23 | cloned[i] = value; 24 | } else if (Array.isArray(value)) { 25 | cloned[i] = value.map((j) => cloneNode(j, cloned)); 26 | } else { 27 | if (type === "object" && value !== null) value = cloneNode(value); 28 | cloned[i] = value; 29 | } 30 | } 31 | 32 | return cloned; 33 | } 34 | 35 | class Node { 36 | constructor(defaults = {}) { 37 | this.raws = {}; 38 | this[isClean] = false; 39 | this[my] = true; 40 | 41 | for (let name in defaults) { 42 | if (name === "nodes") { 43 | this.nodes = []; 44 | for (let node of defaults[name]) { 45 | if (typeof node.clone === "function") { 46 | this.append(node.clone()); 47 | } else { 48 | this.append(node); 49 | } 50 | } 51 | } else { 52 | this[name] = defaults[name]; 53 | } 54 | } 55 | } 56 | 57 | error(message, opts = {}) { 58 | if (this.source) { 59 | let { start, end } = this.rangeBy(opts); 60 | return this.source.input.error( 61 | message, 62 | { line: start.line, column: start.column }, 63 | { line: end.line, column: end.column }, 64 | opts, 65 | ); 66 | } 67 | return new CssSyntaxError(message); 68 | } 69 | 70 | warn(result, text, opts) { 71 | let data = { node: this }; 72 | for (let i in opts) data[i] = opts[i]; 73 | return result.warn(text, data); 74 | } 75 | 76 | remove() { 77 | if (this.parent) { 78 | this.parent.removeChild(this); 79 | } 80 | this.parent = undefined; 81 | return this; 82 | } 83 | 84 | toString(stringifier = stringify) { 85 | if (stringifier.stringify) stringifier = stringifier.stringify; 86 | let result = ""; 87 | stringifier(this, (i) => { 88 | result += i; 89 | }); 90 | return result; 91 | } 92 | 93 | assign(overrides = {}) { 94 | for (let name in overrides) { 95 | this[name] = overrides[name]; 96 | } 97 | return this; 98 | } 99 | 100 | clone(overrides = {}) { 101 | let cloned = cloneNode(this); 102 | for (let name in overrides) { 103 | cloned[name] = overrides[name]; 104 | } 105 | return cloned; 106 | } 107 | 108 | cloneBefore(overrides = {}) { 109 | let cloned = this.clone(overrides); 110 | this.parent.insertBefore(this, cloned); 111 | return cloned; 112 | } 113 | 114 | cloneAfter(overrides = {}) { 115 | let cloned = this.clone(overrides); 116 | this.parent.insertAfter(this, cloned); 117 | return cloned; 118 | } 119 | 120 | replaceWith(...nodes) { 121 | if (this.parent) { 122 | let bookmark = this; 123 | let foundSelf = false; 124 | for (let node of nodes) { 125 | if (node === this) { 126 | foundSelf = true; 127 | } else if (foundSelf) { 128 | this.parent.insertAfter(bookmark, node); 129 | bookmark = node; 130 | } else { 131 | this.parent.insertBefore(bookmark, node); 132 | } 133 | } 134 | 135 | if (!foundSelf) { 136 | this.remove(); 137 | } 138 | } 139 | 140 | return this; 141 | } 142 | 143 | next() { 144 | if (!this.parent) return undefined; 145 | let index = this.parent.index(this); 146 | return this.parent.nodes[index + 1]; 147 | } 148 | 149 | prev() { 150 | if (!this.parent) return undefined; 151 | let index = this.parent.index(this); 152 | return this.parent.nodes[index - 1]; 153 | } 154 | 155 | before(add) { 156 | this.parent.insertBefore(this, add); 157 | return this; 158 | } 159 | 160 | after(add) { 161 | this.parent.insertAfter(this, add); 162 | return this; 163 | } 164 | 165 | root() { 166 | let result = this; 167 | while (result.parent && result.parent.type !== "document") { 168 | result = result.parent; 169 | } 170 | return result; 171 | } 172 | 173 | raw(prop, defaultType) { 174 | let str = new Stringifier(); 175 | return str.raw(this, prop, defaultType); 176 | } 177 | 178 | cleanRaws(keepBetween) { 179 | delete this.raws.before; 180 | delete this.raws.after; 181 | if (!keepBetween) delete this.raws.between; 182 | } 183 | 184 | toJSON(_, inputs) { 185 | let fixed = {}; 186 | let emitInputs = inputs == null; 187 | inputs = inputs || new Map(); 188 | let inputsNextIndex = 0; 189 | 190 | for (let name in this) { 191 | if (!Object.prototype.hasOwnProperty.call(this, name)) { 192 | /* c8 ignore next 2 */ 193 | continue; 194 | } 195 | if (name === "parent" || name === "proxyCache") continue; 196 | let value = this[name]; 197 | 198 | if (Array.isArray(value)) { 199 | fixed[name] = value.map((i) => { 200 | if (typeof i === "object" && i.toJSON) { 201 | return i.toJSON(null, inputs); 202 | } else { 203 | return i; 204 | } 205 | }); 206 | } else if (typeof value === "object" && value.toJSON) { 207 | fixed[name] = value.toJSON(null, inputs); 208 | } else if (name === "source") { 209 | let inputId = inputs.get(value.input); 210 | if (inputId == null) { 211 | inputId = inputsNextIndex; 212 | inputs.set(value.input, inputsNextIndex); 213 | inputsNextIndex++; 214 | } 215 | fixed[name] = { 216 | inputId, 217 | start: value.start, 218 | end: value.end, 219 | }; 220 | } else { 221 | fixed[name] = value; 222 | } 223 | } 224 | 225 | if (emitInputs) { 226 | fixed.inputs = [...inputs.keys()].map((input) => input.toJSON()); 227 | } 228 | 229 | return fixed; 230 | } 231 | 232 | positionInside(index) { 233 | let string = this.toString(); 234 | let column = this.source.start.column; 235 | let line = this.source.start.line; 236 | 237 | for (let i = 0; i < index; i++) { 238 | if (string[i] === "\n") { 239 | column = 1; 240 | line += 1; 241 | } else { 242 | column += 1; 243 | } 244 | } 245 | 246 | return { line, column }; 247 | } 248 | 249 | positionBy(opts) { 250 | let pos = this.source.start; 251 | if (opts.index) { 252 | pos = this.positionInside(opts.index); 253 | } else if (opts.word) { 254 | let index = this.toString().indexOf(opts.word); 255 | if (index !== -1) pos = this.positionInside(index); 256 | } 257 | return pos; 258 | } 259 | 260 | rangeBy(opts) { 261 | let start = { 262 | line: this.source.start.line, 263 | column: this.source.start.column, 264 | }; 265 | let end = this.source.end 266 | ? { 267 | line: this.source.end.line, 268 | column: this.source.end.column + 1, 269 | } 270 | : { 271 | line: start.line, 272 | column: start.column + 1, 273 | }; 274 | 275 | if (opts.word) { 276 | let index = this.toString().indexOf(opts.word); 277 | if (index !== -1) { 278 | start = this.positionInside(index); 279 | end = this.positionInside(index + opts.word.length); 280 | } 281 | } else { 282 | if (opts.start) { 283 | start = { 284 | line: opts.start.line, 285 | column: opts.start.column, 286 | }; 287 | } else if (opts.index) { 288 | start = this.positionInside(opts.index); 289 | } 290 | 291 | if (opts.end) { 292 | end = { 293 | line: opts.end.line, 294 | column: opts.end.column, 295 | }; 296 | } else if (opts.endIndex) { 297 | end = this.positionInside(opts.endIndex); 298 | } else if (opts.index) { 299 | end = this.positionInside(opts.index + 1); 300 | } 301 | } 302 | 303 | if ( 304 | end.line < start.line || 305 | (end.line === start.line && end.column <= start.column) 306 | ) { 307 | end = { line: start.line, column: start.column + 1 }; 308 | } 309 | 310 | return { start, end }; 311 | } 312 | 313 | getProxyProcessor() { 314 | return { 315 | set(node, prop, value) { 316 | if (node[prop] === value) return true; 317 | node[prop] = value; 318 | if ( 319 | prop === "prop" || 320 | prop === "value" || 321 | prop === "name" || 322 | prop === "params" || 323 | prop === "important" || 324 | /* c8 ignore next */ 325 | prop === "text" 326 | ) { 327 | node.markDirty(); 328 | } 329 | return true; 330 | }, 331 | 332 | get(node, prop) { 333 | if (prop === "proxyOf") { 334 | return node; 335 | } else if (prop === "root") { 336 | return () => node.root().toProxy(); 337 | } else { 338 | return node[prop]; 339 | } 340 | }, 341 | }; 342 | } 343 | 344 | toProxy() { 345 | if (!this.proxyCache) { 346 | this.proxyCache = new Proxy(this, this.getProxyProcessor()); 347 | } 348 | return this.proxyCache; 349 | } 350 | 351 | addToError(error) { 352 | error.postcssNode = this; 353 | if (error.stack && this.source && /\n\s{4}at /.test(error.stack)) { 354 | let s = this.source; 355 | error.stack = error.stack.replace( 356 | /\n\s{4}at /, 357 | `$&${s.input.from}:${s.start.line}:${s.start.column}$&`, 358 | ); 359 | } 360 | return error; 361 | } 362 | 363 | markDirty() { 364 | if (this[isClean]) { 365 | this[isClean] = false; 366 | let next = this; 367 | while ((next = next.parent)) { 368 | next[isClean] = false; 369 | } 370 | } 371 | } 372 | 373 | get proxyOf() { 374 | return this; 375 | } 376 | } 377 | 378 | export default Node; 379 | 380 | Node.default = Node; 381 | -------------------------------------------------------------------------------- /deno/lib/parse.d.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "./postcss.js"; 2 | 3 | declare const parse: Parser; 4 | 5 | export default parse; 6 | -------------------------------------------------------------------------------- /deno/lib/parse.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import Container from "./container.js"; 4 | import Parser from "./parser.js"; 5 | import Input from "./input.js"; 6 | 7 | function parse(css, opts) { 8 | let input = new Input(css, opts); 9 | let parser = new Parser(input); 10 | try { 11 | parser.parse(); 12 | } catch (e) { 13 | if (Deno.env.get("DENO_ENV") !== "production") { 14 | if (e.name === "CssSyntaxError" && opts && opts.from) { 15 | if (/\.scss$/i.test(opts.from)) { 16 | e.message += "\nYou tried to parse SCSS with " + 17 | "the standard CSS parser; " + 18 | "try again with the postcss-scss parser"; 19 | } else if (/\.sass/i.test(opts.from)) { 20 | e.message += "\nYou tried to parse Sass with " + 21 | "the standard CSS parser; " + 22 | "try again with the postcss-sass parser"; 23 | } else if (/\.less$/i.test(opts.from)) { 24 | e.message += "\nYou tried to parse Less with " + 25 | "the standard CSS parser; " + 26 | "try again with the postcss-less parser"; 27 | } 28 | } 29 | } 30 | throw e; 31 | } 32 | 33 | return parser.root; 34 | } 35 | 36 | export default parse; 37 | 38 | parse.default = parse; 39 | 40 | Container.registerParse(parse); 41 | -------------------------------------------------------------------------------- /deno/lib/postcss.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import CssSyntaxError from "./css-syntax-error.js"; 4 | import Declaration from "./declaration.js"; 5 | import LazyResult from "./lazy-result.js"; 6 | import Container from "./container.js"; 7 | import Processor from "./processor.js"; 8 | import stringify from "./stringify.js"; 9 | import fromJSON from "./fromJSON.js"; 10 | import Document from "./document.js"; 11 | import Warning from "./warning.js"; 12 | import Comment from "./comment.js"; 13 | import AtRule from "./at-rule.js"; 14 | import Result from "./result.js"; 15 | import Input from "./input.js"; 16 | import parse from "./parse.js"; 17 | import list from "./list.js"; 18 | import Rule from "./rule.js"; 19 | import Root from "./root.js"; 20 | import Node from "./node.js"; 21 | 22 | function postcss(...plugins) { 23 | if (plugins.length === 1 && Array.isArray(plugins[0])) { 24 | plugins = plugins[0]; 25 | } 26 | return new Processor(plugins); 27 | } 28 | 29 | postcss.plugin = function plugin(name, initializer) { 30 | let warningPrinted = false; 31 | function creator(...args) { 32 | // eslint-disable-next-line no-console 33 | if (console && console.warn && !warningPrinted) { 34 | warningPrinted = true; 35 | // eslint-disable-next-line no-console 36 | console.warn( 37 | name + 38 | ": postcss.plugin was deprecated. Migration guide:\n" + 39 | "https://evilmartians.com/chronicles/postcss-8-plugin-migration", 40 | ); 41 | if (Deno.env.get("LANG") && Deno.env.get("LANG").startsWith("cn")) { 42 | /* c8 ignore next 7 */ 43 | // eslint-disable-next-line no-console 44 | console.warn( 45 | name + 46 | ": 里面 postcss.plugin 被弃用. 迁移指南:\n" + 47 | "https://www.w3ctech.com/topic/2226", 48 | ); 49 | } 50 | } 51 | let transformer = initializer(...args); 52 | transformer.postcssPlugin = name; 53 | transformer.postcssVersion = new Processor().version; 54 | return transformer; 55 | } 56 | 57 | let cache; 58 | Object.defineProperty(creator, "postcss", { 59 | get() { 60 | if (!cache) cache = creator(); 61 | return cache; 62 | }, 63 | }); 64 | 65 | creator.process = function (css, processOpts, pluginOpts) { 66 | return postcss([creator(pluginOpts)]).process(css, processOpts); 67 | }; 68 | 69 | return creator; 70 | }; 71 | 72 | postcss.stringify = stringify; 73 | postcss.parse = parse; 74 | postcss.fromJSON = fromJSON; 75 | postcss.list = list; 76 | 77 | postcss.comment = (defaults) => new Comment(defaults); 78 | postcss.atRule = (defaults) => new AtRule(defaults); 79 | postcss.decl = (defaults) => new Declaration(defaults); 80 | postcss.rule = (defaults) => new Rule(defaults); 81 | postcss.root = (defaults) => new Root(defaults); 82 | postcss.document = (defaults) => new Document(defaults); 83 | 84 | postcss.CssSyntaxError = CssSyntaxError; 85 | postcss.Declaration = Declaration; 86 | postcss.Container = Container; 87 | postcss.Processor = Processor; 88 | postcss.Document = Document; 89 | postcss.Comment = Comment; 90 | postcss.Warning = Warning; 91 | postcss.AtRule = AtRule; 92 | postcss.Result = Result; 93 | postcss.Input = Input; 94 | postcss.Rule = Rule; 95 | postcss.Root = Root; 96 | postcss.Node = Node; 97 | 98 | LazyResult.registerPostcss(postcss); 99 | 100 | export default postcss; 101 | 102 | postcss.default = postcss; 103 | -------------------------------------------------------------------------------- /deno/lib/previous-map.d.ts: -------------------------------------------------------------------------------- 1 | import { SourceMapConsumer } from "./source_map.ts"; 2 | import { ProcessOptions } from "./postcss.js"; 3 | 4 | /** 5 | * Source map information from input CSS. 6 | * For example, source map after Sass compiler. 7 | * 8 | * This class will automatically find source map in input CSS or in file system 9 | * near input file (according `from` option). 10 | * 11 | * ```js 12 | * const root = parse(css, { from: 'a.sass.css' }) 13 | * root.input.map //=> PreviousMap 14 | * ``` 15 | */ 16 | export default class PreviousMap { 17 | /** 18 | * Was source map inlined by data-uri to input CSS. 19 | */ 20 | inline: boolean; 21 | 22 | /** 23 | * `sourceMappingURL` content. 24 | */ 25 | annotation?: string; 26 | 27 | /** 28 | * Source map file content. 29 | */ 30 | text?: string; 31 | 32 | /** 33 | * The directory with source map file, if source map is in separated file. 34 | */ 35 | root?: string; 36 | 37 | /** 38 | * The CSS source identifier. Contains `Input#file` if the user 39 | * set the `from` option, or `Input#id` if they did not. 40 | */ 41 | file?: string; 42 | 43 | /** 44 | * Path to source map file. 45 | */ 46 | mapFile?: string; 47 | 48 | /** 49 | * @param css Input CSS source. 50 | * @param opts Process options. 51 | */ 52 | constructor(css: string, opts?: ProcessOptions); 53 | 54 | /** 55 | * Create a instance of `SourceMapGenerator` class 56 | * from the `source-map` library to work with source map information. 57 | * 58 | * It is lazy method, so it will create object only on first call 59 | * and then it will use cache. 60 | * 61 | * @return Object with source map information. 62 | */ 63 | consumer(): SourceMapConsumer; 64 | 65 | /** 66 | * Does source map contains `sourcesContent` with input source text. 67 | * 68 | * @return Is `sourcesContent` present. 69 | */ 70 | withContent(): boolean; 71 | } 72 | -------------------------------------------------------------------------------- /deno/lib/previous-map.js: -------------------------------------------------------------------------------- 1 | import { Buffer } from "./deps.js"; 2 | 3 | /// 4 | 5 | import { SourceMapConsumer, SourceMapGenerator } from "./source_map.ts"; 6 | import { existsSync, readFileSync } from "./deps.js"; 7 | import { dirname, join } from "./deps.js"; 8 | 9 | function fromBase64(str) { 10 | if (Buffer) { 11 | return Buffer.from(str, "base64").toString(); 12 | } else { 13 | /* c8 ignore next 2 */ 14 | return window.atob(str); 15 | } 16 | } 17 | 18 | class PreviousMap { 19 | constructor(css, opts) { 20 | if (opts.map === false) return; 21 | this.loadAnnotation(css); 22 | this.inline = this.startWith(this.annotation, "data:"); 23 | 24 | let prev = opts.map ? opts.map.prev : undefined; 25 | let text = this.loadMap(opts.from, prev); 26 | if (!this.mapFile && opts.from) { 27 | this.mapFile = opts.from; 28 | } 29 | if (this.mapFile) this.root = dirname(this.mapFile); 30 | if (text) this.text = text; 31 | } 32 | 33 | consumer() { 34 | if (!this.consumerCache) { 35 | this.consumerCache = new SourceMapConsumer(this.text); 36 | } 37 | return this.consumerCache; 38 | } 39 | 40 | withContent() { 41 | return !!( 42 | this.consumer().sourcesContent && 43 | this.consumer().sourcesContent.length > 0 44 | ); 45 | } 46 | 47 | startWith(string, start) { 48 | if (!string) return false; 49 | return string.substr(0, start.length) === start; 50 | } 51 | 52 | getAnnotationURL(sourceMapString) { 53 | return sourceMapString.replace(/^\/\*\s*# sourceMappingURL=/, "").trim(); 54 | } 55 | 56 | loadAnnotation(css) { 57 | let comments = css.match(/\/\*\s*# sourceMappingURL=/gm); 58 | if (!comments) return; 59 | 60 | // sourceMappingURLs from comments, strings, etc. 61 | let start = css.lastIndexOf(comments.pop()); 62 | let end = css.indexOf("*/", start); 63 | 64 | if (start > -1 && end > -1) { 65 | // Locate the last sourceMappingURL to avoid pickin 66 | this.annotation = this.getAnnotationURL(css.substring(start, end)); 67 | } 68 | } 69 | 70 | decodeInline(text) { 71 | let baseCharsetUri = /^data:application\/json;charset=utf-?8;base64,/; 72 | let baseUri = /^data:application\/json;base64,/; 73 | let charsetUri = /^data:application\/json;charset=utf-?8,/; 74 | let uri = /^data:application\/json,/; 75 | 76 | if (charsetUri.test(text) || uri.test(text)) { 77 | return decodeURIComponent(text.substr(RegExp.lastMatch.length)); 78 | } 79 | 80 | if (baseCharsetUri.test(text) || baseUri.test(text)) { 81 | return fromBase64(text.substr(RegExp.lastMatch.length)); 82 | } 83 | 84 | let encoding = text.match(/data:application\/json;([^,]+),/)[1]; 85 | throw new Error("Unsupported source map encoding " + encoding); 86 | } 87 | 88 | loadFile(path) { 89 | this.root = dirname(path); 90 | if (existsSync(path)) { 91 | this.mapFile = path; 92 | return readFileSync(path, "utf-8").toString().trim(); 93 | } 94 | } 95 | 96 | loadMap(file, prev) { 97 | if (prev === false) return false; 98 | 99 | if (prev) { 100 | if (typeof prev === "string") { 101 | return prev; 102 | } else if (typeof prev === "function") { 103 | let prevPath = prev(file); 104 | if (prevPath) { 105 | let map = this.loadFile(prevPath); 106 | if (!map) { 107 | throw new Error( 108 | "Unable to load previous source map: " + prevPath.toString(), 109 | ); 110 | } 111 | return map; 112 | } 113 | } else if (prev instanceof SourceMapConsumer) { 114 | return SourceMapGenerator.fromSourceMap(prev).toString(); 115 | } else if (prev instanceof SourceMapGenerator) { 116 | return prev.toString(); 117 | } else if (this.isMap(prev)) { 118 | return JSON.stringify(prev); 119 | } else { 120 | throw new Error( 121 | "Unsupported previous source map format: " + prev.toString(), 122 | ); 123 | } 124 | } else if (this.inline) { 125 | return this.decodeInline(this.annotation); 126 | } else if (this.annotation) { 127 | let map = this.annotation; 128 | if (file) map = join(dirname(file), map); 129 | return this.loadFile(map); 130 | } 131 | } 132 | 133 | isMap(map) { 134 | if (typeof map !== "object") return false; 135 | return ( 136 | typeof map.mappings === "string" || 137 | typeof map._mappings === "string" || 138 | Array.isArray(map.sections) 139 | ); 140 | } 141 | } 142 | 143 | export default PreviousMap; 144 | 145 | PreviousMap.default = PreviousMap; 146 | -------------------------------------------------------------------------------- /deno/lib/processor.d.ts: -------------------------------------------------------------------------------- 1 | import { Buffer } from "./deps.js"; 2 | import { 3 | AcceptedPlugin, 4 | Plugin, 5 | ProcessOptions, 6 | TransformCallback, 7 | Transformer, 8 | } from "./postcss.js"; 9 | import LazyResult from "./lazy-result.js"; 10 | import Result from "./result.js"; 11 | import Root from "./root.js"; 12 | import NoWorkResult from "./no-work-result.js"; 13 | 14 | /** 15 | * Contains plugins to process CSS. Create one `Processor` instance, 16 | * initialize its plugins, and then use that instance on numerous CSS files. 17 | * 18 | * ```js 19 | * const processor = postcss([autoprefixer, postcssNested]) 20 | * processor.process(css1).then(result => console.log(result.css)) 21 | * processor.process(css2).then(result => console.log(result.css)) 22 | * ``` 23 | */ 24 | export default class Processor { 25 | /** 26 | * Current PostCSS version. 27 | * 28 | * ```js 29 | * if (result.processor.version.split('.')[0] !== '6') { 30 | * throw new Error('This plugin works only with PostCSS 6') 31 | * } 32 | * ``` 33 | */ 34 | version: string; 35 | 36 | /** 37 | * Plugins added to this processor. 38 | * 39 | * ```js 40 | * const processor = postcss([autoprefixer, postcssNested]) 41 | * processor.plugins.length //=> 2 42 | * ``` 43 | */ 44 | plugins: (Plugin | Transformer | TransformCallback)[]; 45 | 46 | /** 47 | * @param plugins PostCSS plugins 48 | */ 49 | constructor(plugins?: AcceptedPlugin[]); 50 | 51 | /** 52 | * Adds a plugin to be used as a CSS processor. 53 | * 54 | * PostCSS plugin can be in 4 formats: 55 | * * A plugin in `Plugin` format. 56 | * * A plugin creator function with `pluginCreator.postcss = true`. 57 | * PostCSS will call this function without argument to get plugin. 58 | * * A function. PostCSS will pass the function a @{link Root} 59 | * as the first argument and current `Result` instance 60 | * as the second. 61 | * * Another `Processor` instance. PostCSS will copy plugins 62 | * from that instance into this one. 63 | * 64 | * Plugins can also be added by passing them as arguments when creating 65 | * a `postcss` instance (see [`postcss(plugins)`]). 66 | * 67 | * Asynchronous plugins should return a `Promise` instance. 68 | * 69 | * ```js 70 | * const processor = postcss() 71 | * .use(autoprefixer) 72 | * .use(postcssNested) 73 | * ``` 74 | * 75 | * @param plugin PostCSS plugin or `Processor` with plugins. 76 | * @return Current processor to make methods chain. 77 | */ 78 | use(plugin: AcceptedPlugin): this; 79 | 80 | /** 81 | * Parses source CSS and returns a `LazyResult` Promise proxy. 82 | * Because some plugins can be asynchronous it doesn’t make 83 | * any transformations. Transformations will be applied 84 | * in the `LazyResult` methods. 85 | * 86 | * ```js 87 | * processor.process(css, { from: 'a.css', to: 'a.out.css' }) 88 | * .then(result => { 89 | * console.log(result.css) 90 | * }) 91 | * ``` 92 | * 93 | * @param css String with input CSS or any object with a `toString()` method, 94 | * like a Buffer. Optionally, senda `Result` instance 95 | * and the processor will take the `Root` from it. 96 | * @param opts Options. 97 | * @return Promise proxy. 98 | */ 99 | process( 100 | css: string | { toString(): string } | Result | LazyResult | Root, 101 | options?: ProcessOptions, 102 | ): LazyResult | NoWorkResult; 103 | } 104 | -------------------------------------------------------------------------------- /deno/lib/processor.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import NoWorkResult from "./no-work-result.js"; 4 | import LazyResult from "./lazy-result.js"; 5 | import Document from "./document.js"; 6 | import Root from "./root.js"; 7 | 8 | class Processor { 9 | constructor(plugins = []) { 10 | this.version = "8.4.18"; 11 | this.plugins = this.normalize(plugins); 12 | } 13 | 14 | use(plugin) { 15 | this.plugins = this.plugins.concat(this.normalize([plugin])); 16 | return this; 17 | } 18 | 19 | process(css, opts = {}) { 20 | if ( 21 | this.plugins.length === 0 && 22 | typeof opts.parser === "undefined" && 23 | typeof opts.stringifier === "undefined" && 24 | typeof opts.syntax === "undefined" 25 | ) { 26 | return new NoWorkResult(this, css, opts); 27 | } else { 28 | return new LazyResult(this, css, opts); 29 | } 30 | } 31 | 32 | normalize(plugins) { 33 | let normalized = []; 34 | for (let i of plugins) { 35 | if (i.postcss === true) { 36 | i = i(); 37 | } else if (i.postcss) { 38 | i = i.postcss; 39 | } 40 | 41 | if (typeof i === "object" && Array.isArray(i.plugins)) { 42 | normalized = normalized.concat(i.plugins); 43 | } else if (typeof i === "object" && i.postcssPlugin) { 44 | normalized.push(i); 45 | } else if (typeof i === "function") { 46 | normalized.push(i); 47 | } else if (typeof i === "object" && (i.parse || i.stringify)) { 48 | if (Deno.env.get("DENO_ENV") !== "production") { 49 | throw new Error( 50 | "PostCSS syntaxes cannot be used as plugins. Instead, please use " + 51 | "one of the syntax/parser/stringifier options as outlined " + 52 | "in your PostCSS runner documentation.", 53 | ); 54 | } 55 | } else { 56 | throw new Error(i + " is not a PostCSS plugin"); 57 | } 58 | } 59 | return normalized; 60 | } 61 | } 62 | 63 | export default Processor; 64 | 65 | Processor.default = Processor; 66 | 67 | Root.registerProcessor(Processor); 68 | Document.registerProcessor(Processor); 69 | -------------------------------------------------------------------------------- /deno/lib/result.d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Document, 3 | Node, 4 | Plugin, 5 | ProcessOptions, 6 | Root, 7 | SourceMap, 8 | TransformCallback, 9 | Warning, 10 | WarningOptions, 11 | } from "./postcss.js"; 12 | import Processor from "./processor.js"; 13 | 14 | export interface Message { 15 | /** 16 | * Message type. 17 | */ 18 | type: string; 19 | 20 | /** 21 | * Source PostCSS plugin name. 22 | */ 23 | plugin?: string; 24 | 25 | [others: string]: any; 26 | } 27 | 28 | export interface ResultOptions extends ProcessOptions { 29 | /** 30 | * The CSS node that was the source of the warning. 31 | */ 32 | node?: Node; 33 | 34 | /** 35 | * Name of plugin that created this warning. `Result#warn` will fill it 36 | * automatically with `Plugin#postcssPlugin` value. 37 | */ 38 | plugin?: string; 39 | } 40 | 41 | /** 42 | * Provides the result of the PostCSS transformations. 43 | * 44 | * A Result instance is returned by `LazyResult#then` 45 | * or `Root#toResult` methods. 46 | * 47 | * ```js 48 | * postcss([autoprefixer]).process(css).then(result => { 49 | * console.log(result.css) 50 | * }) 51 | * ``` 52 | * 53 | * ```js 54 | * const result2 = postcss.parse(css).toResult() 55 | * ``` 56 | */ 57 | export default class Result { 58 | /** 59 | * The Processor instance used for this transformation. 60 | * 61 | * ```js 62 | * for (const plugin of result.processor.plugins) { 63 | * if (plugin.postcssPlugin === 'postcss-bad') { 64 | * throw 'postcss-good is incompatible with postcss-bad' 65 | * } 66 | * }) 67 | * ``` 68 | */ 69 | processor: Processor; 70 | 71 | /** 72 | * Contains messages from plugins (e.g., warnings or custom messages). 73 | * Each message should have type and plugin properties. 74 | * 75 | * ```js 76 | * AtRule: { 77 | * import: (atRule, { result }) { 78 | * const importedFile = parseImport(atRule) 79 | * result.messages.push({ 80 | * type: 'dependency', 81 | * plugin: 'postcss-import', 82 | * file: importedFile, 83 | * parent: result.opts.from 84 | * }) 85 | * } 86 | * } 87 | * ``` 88 | */ 89 | messages: Message[]; 90 | 91 | /** 92 | * Root node after all transformations. 93 | * 94 | * ```js 95 | * root.toResult().root === root 96 | * ``` 97 | */ 98 | root: Root | Document; 99 | 100 | /** 101 | * Options from the `Processor#process` or `Root#toResult` call 102 | * that produced this Result instance.] 103 | * 104 | * ```js 105 | * root.toResult(opts).opts === opts 106 | * ``` 107 | */ 108 | opts: ResultOptions; 109 | 110 | /** 111 | * A CSS string representing of `Result#root`. 112 | * 113 | * ```js 114 | * postcss.parse('a{}').toResult().css //=> "a{}" 115 | * ``` 116 | */ 117 | css: string; 118 | 119 | /** 120 | * An instance of `SourceMapGenerator` class from the `source-map` library, 121 | * representing changes to the `Result#root` instance. 122 | * 123 | * ```js 124 | * result.map.toJSON() //=> { version: 3, file: 'a.css', … } 125 | * ``` 126 | * 127 | * ```js 128 | * if (result.map) { 129 | * fs.writeFileSync(result.opts.to + '.map', result.map.toString()) 130 | * } 131 | * ``` 132 | */ 133 | map: SourceMap; 134 | 135 | /** 136 | * Last runned PostCSS plugin. 137 | */ 138 | lastPlugin: Plugin | TransformCallback; 139 | 140 | /** 141 | * @param processor Processor used for this transformation. 142 | * @param root Root node after all transformations. 143 | * @param opts Options from the `Processor#process` or `Root#toResult`. 144 | */ 145 | constructor(processor: Processor, root: Root | Document, opts: ResultOptions); 146 | 147 | /** 148 | * An alias for the `Result#css` property. 149 | * Use it with syntaxes that generate non-CSS output. 150 | * 151 | * ```js 152 | * result.css === result.content 153 | * ``` 154 | */ 155 | get content(): string; 156 | 157 | /** 158 | * Returns for `Result#css` content. 159 | * 160 | * ```js 161 | * result + '' === result.css 162 | * ``` 163 | * 164 | * @return String representing of `Result#root`. 165 | */ 166 | toString(): string; 167 | 168 | /** 169 | * Creates an instance of `Warning` and adds it to `Result#messages`. 170 | * 171 | * ```js 172 | * if (decl.important) { 173 | * result.warn('Avoid !important', { node: decl, word: '!important' }) 174 | * } 175 | * ``` 176 | * 177 | * @param text Warning message. 178 | * @param opts Warning options. 179 | * @return Created warning. 180 | */ 181 | warn(message: string, options?: WarningOptions): Warning; 182 | 183 | /** 184 | * Returns warnings from plugins. Filters `Warning` instances 185 | * from `Result#messages`. 186 | * 187 | * ```js 188 | * result.warnings().forEach(warn => { 189 | * console.warn(warn.toString()) 190 | * }) 191 | * ``` 192 | * 193 | * @return Warnings from plugins. 194 | */ 195 | warnings(): Warning[]; 196 | } 197 | -------------------------------------------------------------------------------- /deno/lib/result.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import Warning from "./warning.js"; 4 | 5 | class Result { 6 | constructor(processor, root, opts) { 7 | this.processor = processor; 8 | this.messages = []; 9 | this.root = root; 10 | this.opts = opts; 11 | this.css = undefined; 12 | this.map = undefined; 13 | } 14 | 15 | toString() { 16 | return this.css; 17 | } 18 | 19 | warn(text, opts = {}) { 20 | if (!opts.plugin) { 21 | if (this.lastPlugin && this.lastPlugin.postcssPlugin) { 22 | opts.plugin = this.lastPlugin.postcssPlugin; 23 | } 24 | } 25 | 26 | let warning = new Warning(text, opts); 27 | this.messages.push(warning); 28 | 29 | return warning; 30 | } 31 | 32 | warnings() { 33 | return this.messages.filter((i) => i.type === "warning"); 34 | } 35 | 36 | get content() { 37 | return this.css; 38 | } 39 | } 40 | 41 | export default Result; 42 | 43 | Result.default = Result; 44 | -------------------------------------------------------------------------------- /deno/lib/root.d.ts: -------------------------------------------------------------------------------- 1 | import Container, { ContainerProps } from "./container.js"; 2 | import Document from "./document.js"; 3 | import { ProcessOptions } from "./postcss.js"; 4 | import Result from "./result.js"; 5 | 6 | interface RootRaws extends Record { 7 | /** 8 | * The space symbols after the last child to the end of file. 9 | */ 10 | after?: string; 11 | 12 | /** 13 | * Non-CSS code before `Root`, when `Root` is inside `Document`. 14 | * 15 | * **Experimental:** some aspects of this node could change within minor 16 | * or patch version releases. 17 | */ 18 | codeBefore?: string; 19 | 20 | /** 21 | * Non-CSS code after `Root`, when `Root` is inside `Document`. 22 | * 23 | * **Experimental:** some aspects of this node could change within minor 24 | * or patch version releases. 25 | */ 26 | codeAfter?: string; 27 | 28 | /** 29 | * Is the last child has an (optional) semicolon. 30 | */ 31 | semicolon?: boolean; 32 | } 33 | 34 | export interface RootProps extends ContainerProps { 35 | /** 36 | * Information used to generate byte-to-byte equal node string 37 | * as it was in the origin input. 38 | */ 39 | raws?: RootRaws; 40 | } 41 | 42 | /** 43 | * Represents a CSS file and contains all its parsed nodes. 44 | * 45 | * ```js 46 | * const root = postcss.parse('a{color:black} b{z-index:2}') 47 | * root.type //=> 'root' 48 | * root.nodes.length //=> 2 49 | * ``` 50 | */ 51 | export default class Root extends Container { 52 | type: "root"; 53 | parent: Document | undefined; 54 | raws: RootRaws; 55 | 56 | /** 57 | * Returns a `Result` instance representing the root’s CSS. 58 | * 59 | * ```js 60 | * const root1 = postcss.parse(css1, { from: 'a.css' }) 61 | * const root2 = postcss.parse(css2, { from: 'b.css' }) 62 | * root1.append(root2) 63 | * const result = root1.toResult({ to: 'all.css', map: true }) 64 | * ``` 65 | * 66 | * @param opts Options. 67 | * @return Result with current root’s CSS. 68 | */ 69 | toResult(options?: ProcessOptions): Result; 70 | 71 | constructor(defaults?: RootProps); 72 | assign(overrides: object | RootProps): this; 73 | } 74 | -------------------------------------------------------------------------------- /deno/lib/root.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import Container from "./container.js"; 4 | 5 | let LazyResult, Processor; 6 | 7 | class Root extends Container { 8 | constructor(defaults) { 9 | super(defaults); 10 | this.type = "root"; 11 | if (!this.nodes) this.nodes = []; 12 | } 13 | 14 | removeChild(child, ignore) { 15 | let index = this.index(child); 16 | 17 | if (!ignore && index === 0 && this.nodes.length > 1) { 18 | this.nodes[1].raws.before = this.nodes[index].raws.before; 19 | } 20 | 21 | return super.removeChild(child); 22 | } 23 | 24 | normalize(child, sample, type) { 25 | let nodes = super.normalize(child); 26 | 27 | if (sample) { 28 | if (type === "prepend") { 29 | if (this.nodes.length > 1) { 30 | sample.raws.before = this.nodes[1].raws.before; 31 | } else { 32 | delete sample.raws.before; 33 | } 34 | } else if (this.first !== sample) { 35 | for (let node of nodes) { 36 | node.raws.before = sample.raws.before; 37 | } 38 | } 39 | } 40 | 41 | return nodes; 42 | } 43 | 44 | toResult(opts = {}) { 45 | let lazy = new LazyResult(new Processor(), this, opts); 46 | return lazy.stringify(); 47 | } 48 | } 49 | 50 | Root.registerLazyResult = (dependant) => { 51 | LazyResult = dependant; 52 | }; 53 | 54 | Root.registerProcessor = (dependant) => { 55 | Processor = dependant; 56 | }; 57 | 58 | export default Root; 59 | 60 | Root.default = Root; 61 | 62 | Container.registerRoot(Root); 63 | -------------------------------------------------------------------------------- /deno/lib/rule.d.ts: -------------------------------------------------------------------------------- 1 | import Container, { ContainerProps } from "./container.js"; 2 | 3 | interface RuleRaws extends Record { 4 | /** 5 | * The space symbols before the node. It also stores `*` 6 | * and `_` symbols before the declaration (IE hack). 7 | */ 8 | before?: string; 9 | 10 | /** 11 | * The space symbols after the last child of the node to the end of the node. 12 | */ 13 | after?: string; 14 | 15 | /** 16 | * The symbols between the selector and `{` for rules. 17 | */ 18 | between?: string; 19 | 20 | /** 21 | * Contains `true` if the last child has an (optional) semicolon. 22 | */ 23 | semicolon?: boolean; 24 | 25 | /** 26 | * Contains `true` if there is semicolon after rule. 27 | */ 28 | ownSemicolon?: string; 29 | 30 | /** 31 | * The rule’s selector with comments. 32 | */ 33 | selector?: { 34 | value: string; 35 | raw: string; 36 | }; 37 | } 38 | 39 | export interface RuleProps extends ContainerProps { 40 | /** Selector or selectors of the rule. */ 41 | selector?: string; 42 | /** Selectors of the rule represented as an array of strings. */ 43 | selectors?: string[]; 44 | /** Information used to generate byte-to-byte equal node string as it was in the origin input. */ 45 | raws?: RuleRaws; 46 | } 47 | 48 | /** 49 | * Represents a CSS rule: a selector followed by a declaration block. 50 | * 51 | * ```js 52 | * Once (root, { Rule }) { 53 | * let a = new Rule({ selector: 'a' }) 54 | * a.append(…) 55 | * root.append(a) 56 | * } 57 | * ``` 58 | * 59 | * ```js 60 | * const root = postcss.parse('a{}') 61 | * const rule = root.first 62 | * rule.type //=> 'rule' 63 | * rule.toString() //=> 'a{}' 64 | * ``` 65 | */ 66 | export default class Rule extends Container { 67 | type: "rule"; 68 | parent: Container | undefined; 69 | raws: RuleRaws; 70 | 71 | /** 72 | * The rule’s full selector represented as a string. 73 | * 74 | * ```js 75 | * const root = postcss.parse('a, b { }') 76 | * const rule = root.first 77 | * rule.selector //=> 'a, b' 78 | * ``` 79 | */ 80 | selector: string; 81 | 82 | /** 83 | * An array containing the rule’s individual selectors. 84 | * Groups of selectors are split at commas. 85 | * 86 | * ```js 87 | * const root = postcss.parse('a, b { }') 88 | * const rule = root.first 89 | * 90 | * rule.selector //=> 'a, b' 91 | * rule.selectors //=> ['a', 'b'] 92 | * 93 | * rule.selectors = ['a', 'strong'] 94 | * rule.selector //=> 'a, strong' 95 | * ``` 96 | */ 97 | selectors: string[]; 98 | 99 | constructor(defaults?: RuleProps); 100 | assign(overrides: object | RuleProps): this; 101 | clone(overrides?: Partial): this; 102 | cloneBefore(overrides?: Partial): this; 103 | cloneAfter(overrides?: Partial): this; 104 | } 105 | -------------------------------------------------------------------------------- /deno/lib/rule.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import Container from "./container.js"; 4 | import list from "./list.js"; 5 | 6 | class Rule extends Container { 7 | constructor(defaults) { 8 | super(defaults); 9 | this.type = "rule"; 10 | if (!this.nodes) this.nodes = []; 11 | } 12 | 13 | get selectors() { 14 | return list.comma(this.selector); 15 | } 16 | 17 | set selectors(values) { 18 | let match = this.selector ? this.selector.match(/,\s*/) : null; 19 | let sep = match ? match[0] : "," + this.raw("between", "beforeOpen"); 20 | this.selector = values.join(sep); 21 | } 22 | } 23 | 24 | export default Rule; 25 | 26 | Rule.default = Rule; 27 | 28 | Container.registerRule(Rule); 29 | -------------------------------------------------------------------------------- /deno/lib/source_map.ts: -------------------------------------------------------------------------------- 1 | // @deno-types="https://deno.land/x/source_map@0.6.2/source-map.d.ts" 2 | export * from "https://deno.land/x/source_map@0.6.2/mod.js"; 3 | -------------------------------------------------------------------------------- /deno/lib/stringifier.d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AnyNode, 3 | AtRule, 4 | Builder, 5 | Comment, 6 | Container, 7 | Declaration, 8 | Document, 9 | Root, 10 | Rule, 11 | } from "./postcss.js"; 12 | 13 | export default class Stringifier { 14 | builder: Builder; 15 | constructor(builder: Builder); 16 | stringify(node: AnyNode, semicolon?: boolean): void; 17 | document(node: Document): void; 18 | root(node: Root): void; 19 | comment(node: Comment): void; 20 | decl(node: Declaration, semicolon?: boolean): void; 21 | rule(node: Rule): void; 22 | atrule(node: AtRule, semicolon?: boolean): void; 23 | body(node: Container): void; 24 | block(node: AnyNode, start: string): void; 25 | raw(node: AnyNode, own: string | null, detect?: string): string; 26 | rawSemicolon(root: Root): boolean | undefined; 27 | rawEmptyBody(root: Root): string | undefined; 28 | rawIndent(root: Root): string | undefined; 29 | rawBeforeComment(root: Root, node: Comment): string | undefined; 30 | rawBeforeDecl(root: Root, node: Declaration): string | undefined; 31 | rawBeforeRule(root: Root): string | undefined; 32 | rawBeforeClose(root: Root): string | undefined; 33 | rawBeforeOpen(root: Root): string | undefined; 34 | rawColon(root: Root): string | undefined; 35 | beforeAfter(node: AnyNode, detect: "before" | "after"): string; 36 | rawValue(node: AnyNode, prop: string): string; 37 | } 38 | -------------------------------------------------------------------------------- /deno/lib/stringifier.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | const DEFAULT_RAW = { 4 | colon: ": ", 5 | indent: " ", 6 | beforeDecl: "\n", 7 | beforeRule: "\n", 8 | beforeOpen: " ", 9 | beforeClose: "\n", 10 | beforeComment: "\n", 11 | after: "\n", 12 | emptyBody: "", 13 | commentLeft: " ", 14 | commentRight: " ", 15 | semicolon: false, 16 | }; 17 | 18 | function capitalize(str) { 19 | return str[0].toUpperCase() + str.slice(1); 20 | } 21 | 22 | class Stringifier { 23 | constructor(builder) { 24 | this.builder = builder; 25 | } 26 | 27 | stringify(node, semicolon) { 28 | /* c8 ignore start */ 29 | if (!this[node.type]) { 30 | throw new Error( 31 | "Unknown AST node type " + 32 | node.type + 33 | ". " + 34 | "Maybe you need to change PostCSS stringifier.", 35 | ); 36 | } 37 | /* c8 ignore stop */ 38 | this[node.type](node, semicolon); 39 | } 40 | 41 | document(node) { 42 | this.body(node); 43 | } 44 | 45 | root(node) { 46 | this.body(node); 47 | if (node.raws.after) this.builder(node.raws.after); 48 | } 49 | 50 | comment(node) { 51 | let left = this.raw(node, "left", "commentLeft"); 52 | let right = this.raw(node, "right", "commentRight"); 53 | this.builder("/*" + left + node.text + right + "*/", node); 54 | } 55 | 56 | decl(node, semicolon) { 57 | let between = this.raw(node, "between", "colon"); 58 | let string = node.prop + between + this.rawValue(node, "value"); 59 | 60 | if (node.important) { 61 | string += node.raws.important || " !important"; 62 | } 63 | 64 | if (semicolon) string += ";"; 65 | this.builder(string, node); 66 | } 67 | 68 | rule(node) { 69 | this.block(node, this.rawValue(node, "selector")); 70 | if (node.raws.ownSemicolon) { 71 | this.builder(node.raws.ownSemicolon, node, "end"); 72 | } 73 | } 74 | 75 | atrule(node, semicolon) { 76 | let name = "@" + node.name; 77 | let params = node.params ? this.rawValue(node, "params") : ""; 78 | 79 | if (typeof node.raws.afterName !== "undefined") { 80 | name += node.raws.afterName; 81 | } else if (params) { 82 | name += " "; 83 | } 84 | 85 | if (node.nodes) { 86 | this.block(node, name + params); 87 | } else { 88 | let end = (node.raws.between || "") + (semicolon ? ";" : ""); 89 | this.builder(name + params + end, node); 90 | } 91 | } 92 | 93 | body(node) { 94 | let last = node.nodes.length - 1; 95 | while (last > 0) { 96 | if (node.nodes[last].type !== "comment") break; 97 | last -= 1; 98 | } 99 | 100 | let semicolon = this.raw(node, "semicolon"); 101 | for (let i = 0; i < node.nodes.length; i++) { 102 | let child = node.nodes[i]; 103 | let before = this.raw(child, "before"); 104 | if (before) this.builder(before); 105 | this.stringify(child, last !== i || semicolon); 106 | } 107 | } 108 | 109 | block(node, start) { 110 | let between = this.raw(node, "between", "beforeOpen"); 111 | this.builder(start + between + "{", node, "start"); 112 | 113 | let after; 114 | if (node.nodes && node.nodes.length) { 115 | this.body(node); 116 | after = this.raw(node, "after"); 117 | } else { 118 | after = this.raw(node, "after", "emptyBody"); 119 | } 120 | 121 | if (after) this.builder(after); 122 | this.builder("}", node, "end"); 123 | } 124 | 125 | raw(node, own, detect) { 126 | let value; 127 | if (!detect) detect = own; 128 | 129 | // Already had 130 | if (own) { 131 | value = node.raws[own]; 132 | if (typeof value !== "undefined") return value; 133 | } 134 | 135 | let parent = node.parent; 136 | 137 | if (detect === "before") { 138 | // Hack for first rule in CSS 139 | if (!parent || (parent.type === "root" && parent.first === node)) { 140 | return ""; 141 | } 142 | 143 | // `root` nodes in `document` should use only their own raws 144 | if (parent && parent.type === "document") { 145 | return ""; 146 | } 147 | } 148 | 149 | // Floating child without parent 150 | if (!parent) return DEFAULT_RAW[detect]; 151 | 152 | // Detect style by other nodes 153 | let root = node.root(); 154 | if (!root.rawCache) root.rawCache = {}; 155 | if (typeof root.rawCache[detect] !== "undefined") { 156 | return root.rawCache[detect]; 157 | } 158 | 159 | if (detect === "before" || detect === "after") { 160 | return this.beforeAfter(node, detect); 161 | } else { 162 | let method = "raw" + capitalize(detect); 163 | if (this[method]) { 164 | value = this[method](root, node); 165 | } else { 166 | root.walk((i) => { 167 | value = i.raws[own]; 168 | if (typeof value !== "undefined") return false; 169 | }); 170 | } 171 | } 172 | 173 | if (typeof value === "undefined") value = DEFAULT_RAW[detect]; 174 | 175 | root.rawCache[detect] = value; 176 | return value; 177 | } 178 | 179 | rawSemicolon(root) { 180 | let value; 181 | root.walk((i) => { 182 | if (i.nodes && i.nodes.length && i.last.type === "decl") { 183 | value = i.raws.semicolon; 184 | if (typeof value !== "undefined") return false; 185 | } 186 | }); 187 | return value; 188 | } 189 | 190 | rawEmptyBody(root) { 191 | let value; 192 | root.walk((i) => { 193 | if (i.nodes && i.nodes.length === 0) { 194 | value = i.raws.after; 195 | if (typeof value !== "undefined") return false; 196 | } 197 | }); 198 | return value; 199 | } 200 | 201 | rawIndent(root) { 202 | if (root.raws.indent) return root.raws.indent; 203 | let value; 204 | root.walk((i) => { 205 | let p = i.parent; 206 | if (p && p !== root && p.parent && p.parent === root) { 207 | if (typeof i.raws.before !== "undefined") { 208 | let parts = i.raws.before.split("\n"); 209 | value = parts[parts.length - 1]; 210 | value = value.replace(/\S/g, ""); 211 | return false; 212 | } 213 | } 214 | }); 215 | return value; 216 | } 217 | 218 | rawBeforeComment(root, node) { 219 | let value; 220 | root.walkComments((i) => { 221 | if (typeof i.raws.before !== "undefined") { 222 | value = i.raws.before; 223 | if (value.includes("\n")) { 224 | value = value.replace(/[^\n]+$/, ""); 225 | } 226 | return false; 227 | } 228 | }); 229 | if (typeof value === "undefined") { 230 | value = this.raw(node, null, "beforeDecl"); 231 | } else if (value) { 232 | value = value.replace(/\S/g, ""); 233 | } 234 | return value; 235 | } 236 | 237 | rawBeforeDecl(root, node) { 238 | let value; 239 | root.walkDecls((i) => { 240 | if (typeof i.raws.before !== "undefined") { 241 | value = i.raws.before; 242 | if (value.includes("\n")) { 243 | value = value.replace(/[^\n]+$/, ""); 244 | } 245 | return false; 246 | } 247 | }); 248 | if (typeof value === "undefined") { 249 | value = this.raw(node, null, "beforeRule"); 250 | } else if (value) { 251 | value = value.replace(/\S/g, ""); 252 | } 253 | return value; 254 | } 255 | 256 | rawBeforeRule(root) { 257 | let value; 258 | root.walk((i) => { 259 | if (i.nodes && (i.parent !== root || root.first !== i)) { 260 | if (typeof i.raws.before !== "undefined") { 261 | value = i.raws.before; 262 | if (value.includes("\n")) { 263 | value = value.replace(/[^\n]+$/, ""); 264 | } 265 | return false; 266 | } 267 | } 268 | }); 269 | if (value) value = value.replace(/\S/g, ""); 270 | return value; 271 | } 272 | 273 | rawBeforeClose(root) { 274 | let value; 275 | root.walk((i) => { 276 | if (i.nodes && i.nodes.length > 0) { 277 | if (typeof i.raws.after !== "undefined") { 278 | value = i.raws.after; 279 | if (value.includes("\n")) { 280 | value = value.replace(/[^\n]+$/, ""); 281 | } 282 | return false; 283 | } 284 | } 285 | }); 286 | if (value) value = value.replace(/\S/g, ""); 287 | return value; 288 | } 289 | 290 | rawBeforeOpen(root) { 291 | let value; 292 | root.walk((i) => { 293 | if (i.type !== "decl") { 294 | value = i.raws.between; 295 | if (typeof value !== "undefined") return false; 296 | } 297 | }); 298 | return value; 299 | } 300 | 301 | rawColon(root) { 302 | let value; 303 | root.walkDecls((i) => { 304 | if (typeof i.raws.between !== "undefined") { 305 | value = i.raws.between.replace(/[^\s:]/g, ""); 306 | return false; 307 | } 308 | }); 309 | return value; 310 | } 311 | 312 | beforeAfter(node, detect) { 313 | let value; 314 | if (node.type === "decl") { 315 | value = this.raw(node, null, "beforeDecl"); 316 | } else if (node.type === "comment") { 317 | value = this.raw(node, null, "beforeComment"); 318 | } else if (detect === "before") { 319 | value = this.raw(node, null, "beforeRule"); 320 | } else { 321 | value = this.raw(node, null, "beforeClose"); 322 | } 323 | 324 | let buf = node.parent; 325 | let depth = 0; 326 | while (buf && buf.type !== "root") { 327 | depth += 1; 328 | buf = buf.parent; 329 | } 330 | 331 | if (value.includes("\n")) { 332 | let indent = this.raw(node, null, "indent"); 333 | if (indent.length) { 334 | for (let step = 0; step < depth; step++) value += indent; 335 | } 336 | } 337 | 338 | return value; 339 | } 340 | 341 | rawValue(node, prop) { 342 | let value = node[prop]; 343 | let raw = node.raws[prop]; 344 | if (raw && raw.value === value) { 345 | return raw.raw; 346 | } 347 | 348 | return value; 349 | } 350 | } 351 | 352 | export default Stringifier; 353 | 354 | Stringifier.default = Stringifier; 355 | -------------------------------------------------------------------------------- /deno/lib/stringify.d.ts: -------------------------------------------------------------------------------- 1 | import { Stringifier } from "./postcss.js"; 2 | 3 | declare const stringify: Stringifier; 4 | 5 | export default stringify; 6 | -------------------------------------------------------------------------------- /deno/lib/stringify.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import Stringifier from "./stringifier.js"; 4 | 5 | function stringify(node, builder) { 6 | let str = new Stringifier(builder); 7 | str.stringify(node); 8 | } 9 | 10 | export default stringify; 11 | 12 | stringify.default = stringify; 13 | -------------------------------------------------------------------------------- /deno/lib/symbols.js: -------------------------------------------------------------------------------- 1 | export const isClean = Symbol("isClean"); 2 | 3 | export const my = Symbol("my"); 4 | -------------------------------------------------------------------------------- /deno/lib/terminal-highlight.js: -------------------------------------------------------------------------------- 1 | import { pico } from "./deps.js"; 2 | import tokenizer from "./tokenize.js"; 3 | 4 | let Input; 5 | 6 | function registerInput(dependant) { 7 | Input = dependant; 8 | } 9 | 10 | const HIGHLIGHT_THEME = { 11 | "brackets": pico.cyan, 12 | "at-word": pico.cyan, 13 | "comment": pico.gray, 14 | "string": pico.green, 15 | "class": pico.yellow, 16 | "hash": pico.magenta, 17 | "call": pico.cyan, 18 | "(": pico.cyan, 19 | ")": pico.cyan, 20 | "{": pico.yellow, 21 | "}": pico.yellow, 22 | "[": pico.yellow, 23 | "]": pico.yellow, 24 | ":": pico.yellow, 25 | ";": pico.yellow, 26 | }; 27 | 28 | function getTokenType([type, value], processor) { 29 | if (type === "word") { 30 | if (value[0] === ".") { 31 | return "class"; 32 | } 33 | if (value[0] === "#") { 34 | return "hash"; 35 | } 36 | } 37 | 38 | if (!processor.endOfFile()) { 39 | let next = processor.nextToken(); 40 | processor.back(next); 41 | if (next[0] === "brackets" || next[0] === "(") return "call"; 42 | } 43 | 44 | return type; 45 | } 46 | 47 | function terminalHighlight(css) { 48 | let processor = tokenizer(new Input(css), { ignoreErrors: true }); 49 | let result = ""; 50 | while (!processor.endOfFile()) { 51 | let token = processor.nextToken(); 52 | let color = HIGHLIGHT_THEME[getTokenType(token, processor)]; 53 | if (color) { 54 | result += token[1] 55 | .split(/\r?\n/) 56 | .map((i) => color(i)) 57 | .join("\n"); 58 | } else { 59 | result += token[1]; 60 | } 61 | } 62 | return result; 63 | } 64 | 65 | terminalHighlight.registerInput = registerInput; 66 | 67 | export default terminalHighlight; 68 | -------------------------------------------------------------------------------- /deno/lib/tokenize.js: -------------------------------------------------------------------------------- 1 | const SINGLE_QUOTE = "'".charCodeAt(0); 2 | const DOUBLE_QUOTE = '"'.charCodeAt(0); 3 | const BACKSLASH = "\\".charCodeAt(0); 4 | const SLASH = "/".charCodeAt(0); 5 | const NEWLINE = "\n".charCodeAt(0); 6 | const SPACE = " ".charCodeAt(0); 7 | const FEED = "\f".charCodeAt(0); 8 | const TAB = "\t".charCodeAt(0); 9 | const CR = "\r".charCodeAt(0); 10 | const OPEN_SQUARE = "[".charCodeAt(0); 11 | const CLOSE_SQUARE = "]".charCodeAt(0); 12 | const OPEN_PARENTHESES = "(".charCodeAt(0); 13 | const CLOSE_PARENTHESES = ")".charCodeAt(0); 14 | const OPEN_CURLY = "{".charCodeAt(0); 15 | const CLOSE_CURLY = "}".charCodeAt(0); 16 | const SEMICOLON = ";".charCodeAt(0); 17 | const ASTERISK = "*".charCodeAt(0); 18 | const COLON = ":".charCodeAt(0); 19 | const AT = "@".charCodeAt(0); 20 | 21 | const RE_AT_END = /[\t\n\f\r "#'()/;[\\\]{}]/g; 22 | const RE_WORD_END = /[\t\n\f\r !"#'():;@[\\\]{}]|\/(?=\*)/g; 23 | const RE_BAD_BRACKET = /.[\n"'(/\\]/; 24 | const RE_HEX_ESCAPE = /[\da-f]/i; 25 | 26 | export default function tokenizer(input, options = {}) { 27 | let css = input.css.valueOf(); 28 | let ignore = options.ignoreErrors; 29 | 30 | let code, next, quote, content, escape; 31 | let escaped, escapePos, prev, n, currentToken; 32 | 33 | let length = css.length; 34 | let pos = 0; 35 | let buffer = []; 36 | let returned = []; 37 | 38 | function position() { 39 | return pos; 40 | } 41 | 42 | function unclosed(what) { 43 | throw input.error("Unclosed " + what, pos); 44 | } 45 | 46 | function endOfFile() { 47 | return returned.length === 0 && pos >= length; 48 | } 49 | 50 | function nextToken(opts) { 51 | if (returned.length) return returned.pop(); 52 | if (pos >= length) return; 53 | 54 | let ignoreUnclosed = opts ? opts.ignoreUnclosed : false; 55 | 56 | code = css.charCodeAt(pos); 57 | 58 | switch (code) { 59 | case NEWLINE: 60 | case SPACE: 61 | case TAB: 62 | case CR: 63 | case FEED: { 64 | next = pos; 65 | do { 66 | next += 1; 67 | code = css.charCodeAt(next); 68 | } while ( 69 | code === SPACE || 70 | code === NEWLINE || 71 | code === TAB || 72 | code === CR || 73 | code === FEED 74 | ); 75 | 76 | currentToken = ["space", css.slice(pos, next)]; 77 | pos = next - 1; 78 | break; 79 | } 80 | 81 | case OPEN_SQUARE: 82 | case CLOSE_SQUARE: 83 | case OPEN_CURLY: 84 | case CLOSE_CURLY: 85 | case COLON: 86 | case SEMICOLON: 87 | case CLOSE_PARENTHESES: { 88 | let controlChar = String.fromCharCode(code); 89 | currentToken = [controlChar, controlChar, pos]; 90 | break; 91 | } 92 | 93 | case OPEN_PARENTHESES: { 94 | prev = buffer.length ? buffer.pop()[1] : ""; 95 | n = css.charCodeAt(pos + 1); 96 | if ( 97 | prev === "url" && 98 | n !== SINGLE_QUOTE && 99 | n !== DOUBLE_QUOTE && 100 | n !== SPACE && 101 | n !== NEWLINE && 102 | n !== TAB && 103 | n !== FEED && 104 | n !== CR 105 | ) { 106 | next = pos; 107 | do { 108 | escaped = false; 109 | next = css.indexOf(")", next + 1); 110 | if (next === -1) { 111 | if (ignore || ignoreUnclosed) { 112 | next = pos; 113 | break; 114 | } else { 115 | unclosed("bracket"); 116 | } 117 | } 118 | escapePos = next; 119 | while (css.charCodeAt(escapePos - 1) === BACKSLASH) { 120 | escapePos -= 1; 121 | escaped = !escaped; 122 | } 123 | } while (escaped); 124 | 125 | currentToken = ["brackets", css.slice(pos, next + 1), pos, next]; 126 | 127 | pos = next; 128 | } else { 129 | next = css.indexOf(")", pos + 1); 130 | content = css.slice(pos, next + 1); 131 | 132 | if (next === -1 || RE_BAD_BRACKET.test(content)) { 133 | currentToken = ["(", "(", pos]; 134 | } else { 135 | currentToken = ["brackets", content, pos, next]; 136 | pos = next; 137 | } 138 | } 139 | 140 | break; 141 | } 142 | 143 | case SINGLE_QUOTE: 144 | case DOUBLE_QUOTE: { 145 | quote = code === SINGLE_QUOTE ? "'" : '"'; 146 | next = pos; 147 | do { 148 | escaped = false; 149 | next = css.indexOf(quote, next + 1); 150 | if (next === -1) { 151 | if (ignore || ignoreUnclosed) { 152 | next = pos + 1; 153 | break; 154 | } else { 155 | unclosed("string"); 156 | } 157 | } 158 | escapePos = next; 159 | while (css.charCodeAt(escapePos - 1) === BACKSLASH) { 160 | escapePos -= 1; 161 | escaped = !escaped; 162 | } 163 | } while (escaped); 164 | 165 | currentToken = ["string", css.slice(pos, next + 1), pos, next]; 166 | pos = next; 167 | break; 168 | } 169 | 170 | case AT: { 171 | RE_AT_END.lastIndex = pos + 1; 172 | RE_AT_END.test(css); 173 | if (RE_AT_END.lastIndex === 0) { 174 | next = css.length - 1; 175 | } else { 176 | next = RE_AT_END.lastIndex - 2; 177 | } 178 | 179 | currentToken = ["at-word", css.slice(pos, next + 1), pos, next]; 180 | 181 | pos = next; 182 | break; 183 | } 184 | 185 | case BACKSLASH: { 186 | next = pos; 187 | escape = true; 188 | while (css.charCodeAt(next + 1) === BACKSLASH) { 189 | next += 1; 190 | escape = !escape; 191 | } 192 | code = css.charCodeAt(next + 1); 193 | if ( 194 | escape && 195 | code !== SLASH && 196 | code !== SPACE && 197 | code !== NEWLINE && 198 | code !== TAB && 199 | code !== CR && 200 | code !== FEED 201 | ) { 202 | next += 1; 203 | if (RE_HEX_ESCAPE.test(css.charAt(next))) { 204 | while (RE_HEX_ESCAPE.test(css.charAt(next + 1))) { 205 | next += 1; 206 | } 207 | if (css.charCodeAt(next + 1) === SPACE) { 208 | next += 1; 209 | } 210 | } 211 | } 212 | 213 | currentToken = ["word", css.slice(pos, next + 1), pos, next]; 214 | 215 | pos = next; 216 | break; 217 | } 218 | 219 | default: { 220 | if (code === SLASH && css.charCodeAt(pos + 1) === ASTERISK) { 221 | next = css.indexOf("*/", pos + 2) + 1; 222 | if (next === 0) { 223 | if (ignore || ignoreUnclosed) { 224 | next = css.length; 225 | } else { 226 | unclosed("comment"); 227 | } 228 | } 229 | 230 | currentToken = ["comment", css.slice(pos, next + 1), pos, next]; 231 | pos = next; 232 | } else { 233 | RE_WORD_END.lastIndex = pos + 1; 234 | RE_WORD_END.test(css); 235 | if (RE_WORD_END.lastIndex === 0) { 236 | next = css.length - 1; 237 | } else { 238 | next = RE_WORD_END.lastIndex - 2; 239 | } 240 | 241 | currentToken = ["word", css.slice(pos, next + 1), pos, next]; 242 | buffer.push(currentToken); 243 | pos = next; 244 | } 245 | 246 | break; 247 | } 248 | } 249 | 250 | pos++; 251 | return currentToken; 252 | } 253 | 254 | function back(token) { 255 | returned.push(token); 256 | } 257 | 258 | return { 259 | back, 260 | nextToken, 261 | endOfFile, 262 | position, 263 | }; 264 | } 265 | -------------------------------------------------------------------------------- /deno/lib/warn-once.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | let printed = {}; 4 | 5 | export default function warnOnce(message) { 6 | if (printed[message]) return; 7 | printed[message] = true; 8 | 9 | if (typeof console !== "undefined" && console.warn) { 10 | console.warn(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /deno/lib/warning.d.ts: -------------------------------------------------------------------------------- 1 | import { RangePosition } from "./css-syntax-error.js"; 2 | import Node from "./node.js"; 3 | 4 | export interface WarningOptions { 5 | /** 6 | * CSS node that caused the warning. 7 | */ 8 | node?: Node; 9 | 10 | /** 11 | * Word in CSS source that caused the warning. 12 | */ 13 | word?: string; 14 | 15 | /** 16 | * Start index, inclusive, in CSS node string that caused the warning. 17 | */ 18 | index?: number; 19 | 20 | /** 21 | * End index, exclusive, in CSS node string that caused the warning. 22 | */ 23 | endIndex?: number; 24 | 25 | /** 26 | * Start position, inclusive, in CSS node string that caused the warning. 27 | */ 28 | start?: RangePosition; 29 | 30 | /** 31 | * End position, exclusive, in CSS node string that caused the warning. 32 | */ 33 | end?: RangePosition; 34 | 35 | /** 36 | * Name of the plugin that created this warning. `Result#warn` fills 37 | * this property automatically. 38 | */ 39 | plugin?: string; 40 | } 41 | 42 | /** 43 | * Represents a plugin’s warning. It can be created using `Node#warn`. 44 | * 45 | * ```js 46 | * if (decl.important) { 47 | * decl.warn(result, 'Avoid !important', { word: '!important' }) 48 | * } 49 | * ``` 50 | */ 51 | export default class Warning { 52 | /** 53 | * Type to filter warnings from `Result#messages`. 54 | * Always equal to `"warning"`. 55 | */ 56 | type: "warning"; 57 | 58 | /** 59 | * The warning message. 60 | * 61 | * ```js 62 | * warning.text //=> 'Try to avoid !important' 63 | * ``` 64 | */ 65 | text: string; 66 | 67 | /** 68 | * The name of the plugin that created this warning. 69 | * When you call `Node#warn` it will fill this property automatically. 70 | * 71 | * ```js 72 | * warning.plugin //=> 'postcss-important' 73 | * ``` 74 | */ 75 | plugin: string; 76 | 77 | /** 78 | * Contains the CSS node that caused the warning. 79 | * 80 | * ```js 81 | * warning.node.toString() //=> 'color: white !important' 82 | * ``` 83 | */ 84 | node: Node; 85 | 86 | /** 87 | * Line for inclusive start position in the input file with this warning’s source. 88 | * 89 | * ```js 90 | * warning.line //=> 5 91 | * ``` 92 | */ 93 | line: number; 94 | 95 | /** 96 | * Column for inclusive start position in the input file with this warning’s source. 97 | * 98 | * ```js 99 | * warning.column //=> 6 100 | * ``` 101 | */ 102 | column: number; 103 | 104 | /** 105 | * Line for exclusive end position in the input file with this warning’s source. 106 | * 107 | * ```js 108 | * warning.endLine //=> 6 109 | * ``` 110 | */ 111 | endLine?: number; 112 | 113 | /** 114 | * Column for exclusive end position in the input file with this warning’s source. 115 | * 116 | * ```js 117 | * warning.endColumn //=> 4 118 | * ``` 119 | */ 120 | endColumn?: number; 121 | 122 | /** 123 | * @param text Warning message. 124 | * @param opts Warning options. 125 | */ 126 | constructor(text: string, opts?: WarningOptions); 127 | 128 | /** 129 | * Returns a warning position and message. 130 | * 131 | * ```js 132 | * warning.toString() //=> 'postcss-lint:a.css:10:14: Avoid !important' 133 | * ``` 134 | * 135 | * @return Warning position and message. 136 | */ 137 | toString(): string; 138 | } 139 | -------------------------------------------------------------------------------- /deno/lib/warning.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | class Warning { 4 | constructor(text, opts = {}) { 5 | this.type = "warning"; 6 | this.text = text; 7 | 8 | if (opts.node && opts.node.source) { 9 | let range = opts.node.rangeBy(opts); 10 | this.line = range.start.line; 11 | this.column = range.start.column; 12 | this.endLine = range.end.line; 13 | this.endColumn = range.end.column; 14 | } 15 | 16 | for (let opt in opts) this[opt] = opts[opt]; 17 | } 18 | 19 | toString() { 20 | if (this.node) { 21 | return this.node.error(this.text, { 22 | plugin: this.plugin, 23 | index: this.index, 24 | word: this.word, 25 | }).message; 26 | } 27 | 28 | if (this.plugin) { 29 | return this.plugin + ": " + this.text; 30 | } 31 | 32 | return this.text; 33 | } 34 | } 35 | 36 | export default Warning; 37 | 38 | Warning.default = Warning; 39 | -------------------------------------------------------------------------------- /deno/mod.js: -------------------------------------------------------------------------------- 1 | import postcss from "./lib/postcss.js"; 2 | 3 | export default postcss; 4 | 5 | export const stringify = postcss.stringify; 6 | 7 | export const fromJSON = postcss.fromJSON; 8 | export const plugin = postcss.plugin; 9 | export const parse = postcss.parse; 10 | export const list = postcss.list; 11 | 12 | export const document = postcss.document; 13 | export const comment = postcss.comment; 14 | export const atRule = postcss.atRule; 15 | export const rule = postcss.rule; 16 | export const decl = postcss.decl; 17 | export const root = postcss.root; 18 | 19 | export const CssSyntaxError = postcss.CssSyntaxError; 20 | export const Declaration = postcss.Declaration; 21 | export const Container = postcss.Container; 22 | export const Processor = postcss.Processor; 23 | export const Document = postcss.Document; 24 | export const Comment = postcss.Comment; 25 | export const Warning = postcss.Warning; 26 | export const AtRule = postcss.AtRule; 27 | export const Result = postcss.Result; 28 | export const Input = postcss.Input; 29 | export const Rule = postcss.Rule; 30 | export const Root = postcss.Root; 31 | export const Node = postcss.Node; 32 | -------------------------------------------------------------------------------- /deno/test/at-rule.test.js: -------------------------------------------------------------------------------- 1 | import { assert, assertEquals } from "./deps.js"; 2 | import { AtRule, parse } from "../mod.js"; 3 | 4 | Deno.test("initializes with properties", () => { 5 | const rule = new AtRule({ name: "encoding", params: '"utf-8"' }); 6 | 7 | assertEquals(rule.name, "encoding"); 8 | assertEquals(rule.params, '"utf-8"'); 9 | assertEquals(rule.toString(), '@encoding "utf-8"'); 10 | }); 11 | 12 | Deno.test("does not fall on childless at-rule", () => { 13 | const rule = new AtRule(); 14 | assert(typeof rule.each((i) => i) === "undefined"); 15 | }); 16 | 17 | Deno.test("creates nodes property on prepend()", () => { 18 | const rule = new AtRule(); 19 | assert(typeof rule.nodes === "undefined"); 20 | 21 | rule.prepend("color: black"); 22 | assert(rule.nodes.length === 1); 23 | }); 24 | 25 | Deno.test("creates nodes property on append()", () => { 26 | const rule = new AtRule(); 27 | assert(typeof rule.nodes === "undefined"); 28 | 29 | rule.append("color: black"); 30 | assert(rule.nodes.length === 1); 31 | }); 32 | 33 | Deno.test("inserts default spaces", () => { 34 | const rule = new AtRule({ name: "page", params: 1, nodes: [] }); 35 | assertEquals(rule.toString(), "@page 1 {}"); 36 | }); 37 | 38 | Deno.test("clone spaces from another at-rule", () => { 39 | const root = parse("@page{}a{}"); 40 | const rule = new AtRule({ name: "page", params: 1, nodes: [] }); 41 | root.append(rule); 42 | 43 | assertEquals(rule.toString(), "@page 1{}"); 44 | }); 45 | -------------------------------------------------------------------------------- /deno/test/comment.test.js: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "./deps.js"; 2 | import { Comment, parse } from "../mod.js"; 3 | 4 | Deno.test("toString() inserts default spaces", () => { 5 | const comment = new Comment({ text: "hi" }); 6 | assertEquals(comment.toString(), "/* hi */"); 7 | }); 8 | 9 | Deno.test("toString() clones spaces from another comment", () => { 10 | const root = parse("a{} /*hello*/"); 11 | const comment = new Comment({ text: "world" }); 12 | root.append(comment); 13 | 14 | assertEquals(root.toString(), "a{} /*hello*/ /*world*/"); 15 | }); 16 | -------------------------------------------------------------------------------- /deno/test/declaration.test.js: -------------------------------------------------------------------------------- 1 | import { assert, assertEquals } from "./deps.js"; 2 | import { Declaration, parse, Rule } from "../mod.js"; 3 | 4 | Deno.test("initializes with properties", () => { 5 | const decl = new Declaration({ prop: "color", value: "black" }); 6 | assertEquals(decl.prop, "color"); 7 | assertEquals(decl.value, "black"); 8 | }); 9 | 10 | Deno.test("returns boolean important", () => { 11 | const decl = new Declaration({ prop: "color", value: "black" }); 12 | decl.important = true; 13 | assertEquals(decl.toString(), "color: black !important"); 14 | }); 15 | 16 | Deno.test("inserts default spaces", () => { 17 | const decl = new Declaration({ prop: "color", value: "black" }); 18 | const rule = new Rule({ selector: "a" }); 19 | rule.append(decl); 20 | assertEquals(rule.toString(), "a {\n color: black\n}"); 21 | }); 22 | 23 | Deno.test("clones spaces from another declaration", () => { 24 | const root = parse("a{color:black}"); 25 | const rule = root.first; 26 | const decl = new Declaration({ prop: "margin", value: "0" }); 27 | rule.append(decl); 28 | assertEquals(root.toString(), "a{color:black;margin:0}"); 29 | }); 30 | 31 | Deno.test("converts value to string", () => { 32 | const decl = new Declaration({ prop: "color", value: 1 }); 33 | assertEquals(decl.value, "1"); 34 | }); 35 | 36 | Deno.test("detects variable declarations", () => { 37 | const prop = new Declaration({ prop: "--color", value: "black" }); 38 | assert(prop.variable === true); 39 | const sass = new Declaration({ prop: "$color", value: "black" }); 40 | assert(sass.variable === true); 41 | const decl = new Declaration({ prop: "color", value: "black" }); 42 | assert(decl.variable === false); 43 | }); 44 | -------------------------------------------------------------------------------- /deno/test/deps.js: -------------------------------------------------------------------------------- 1 | export * from "https://deno.land/std@0.98.0/testing/asserts.ts"; 2 | import "https://deno.land/std@0.98.0/node/global.ts"; 3 | -------------------------------------------------------------------------------- /deno/test/lazy-result.test.js: -------------------------------------------------------------------------------- 1 | import { assert, assertEquals } from "./deps.js"; 2 | import { SourceMapGenerator } from "../lib/source_map.ts"; 3 | import LazyResult from "../lib/lazy-result.js"; 4 | import Processor from "../lib/processor.js"; 5 | 6 | const processor = new Processor(); 7 | 8 | Deno.test("contains AST", () => { 9 | const result = new LazyResult(processor, "a {}", {}); 10 | assertEquals(result.root.type, "root"); 11 | }); 12 | 13 | Deno.test("will stringify css", () => { 14 | const result = new LazyResult(processor, "a {}", {}); 15 | assertEquals(result.css, "a {}"); 16 | }); 17 | 18 | Deno.test("stringifies css", () => { 19 | const result = new LazyResult(processor, "a {}", {}); 20 | assertEquals(`${result}`, result.css); 21 | }); 22 | 23 | Deno.test("has content alias for css", () => { 24 | const result = new LazyResult(processor, "a {}", {}); 25 | assertEquals(result.content, "a {}"); 26 | }); 27 | 28 | Deno.test("has map only if necessary", () => { 29 | const result1 = new LazyResult(processor, "", {}); 30 | assert(typeof result1.map === "undefined"); 31 | 32 | const result2 = new LazyResult(processor, "", {}); 33 | assert(typeof result2.map === "undefined"); 34 | 35 | const result3 = new LazyResult(processor, "", { map: { inline: false } }); 36 | assert(result3.map instanceof SourceMapGenerator); 37 | }); 38 | 39 | Deno.test("contains options", () => { 40 | const result = new LazyResult(processor, "a {}", { to: "a.css" }); 41 | assertEquals(result.opts, { to: "a.css" }); 42 | }); 43 | 44 | Deno.test("contains warnings", () => { 45 | const result = new LazyResult(processor, "a {}", {}); 46 | assertEquals(result.warnings(), []); 47 | }); 48 | 49 | Deno.test("contains messages", () => { 50 | const result = new LazyResult(processor, "a {}", {}); 51 | assertEquals(result.messages, []); 52 | }); 53 | -------------------------------------------------------------------------------- /deno/test/list.test.js: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "./deps.js"; 2 | import { list } from "../mod.js"; 3 | 4 | Deno.test("space() splits list by spaces", () => { 5 | assertEquals(list.space("a b"), ["a", "b"]); 6 | }); 7 | 8 | Deno.test("space() trims values", () => { 9 | assertEquals(list.space(" a b "), ["a", "b"]); 10 | }); 11 | 12 | Deno.test("space() checks quotes", () => { 13 | assertEquals(list.space('"a b\\"" \'\''), ['"a b\\""', "''"]); 14 | }); 15 | 16 | Deno.test("space() checks functions", () => { 17 | assertEquals(list.space("f( )) a( () )"), ["f( ))", "a( () )"]); 18 | }); 19 | 20 | Deno.test("space() works from variable", () => { 21 | const space = list.space; 22 | assertEquals(space("a b"), ["a", "b"]); 23 | }); 24 | 25 | Deno.test("comma() splits list by spaces", () => { 26 | assertEquals(list.comma("a, b"), ["a", "b"]); 27 | }); 28 | 29 | Deno.test("comma() adds last empty", () => { 30 | assertEquals(list.comma("a, b,"), ["a", "b", ""]); 31 | }); 32 | 33 | Deno.test("comma() checks quotes", () => { 34 | assertEquals(list.comma('"a,b\\"", \'\''), ['"a,b\\""', "''"]); 35 | }); 36 | 37 | Deno.test("comma() checks functions", () => { 38 | assertEquals(list.comma("f(,)), a(,(),)"), ["f(,))", "a(,(),)"]); 39 | }); 40 | 41 | Deno.test("comma() works from variable", () => { 42 | const comma = list.comma; 43 | assertEquals(comma("a, b"), ["a", "b"]); 44 | }); 45 | -------------------------------------------------------------------------------- /deno/test/node.test.js: -------------------------------------------------------------------------------- 1 | import { assert, assertEquals } from "./deps.js"; 2 | import { resolve } from "../lib/deps.js"; 3 | 4 | import postcss, { 5 | AtRule, 6 | CssSyntaxError, 7 | Declaration, 8 | parse, 9 | Root, 10 | Rule, 11 | } from "../mod.js"; 12 | 13 | function stringify(node, builder) { 14 | if (node.type === "rule") { 15 | return builder(node.selector); 16 | } 17 | } 18 | 19 | Deno.test("error() generates custom error", () => { 20 | const file = resolve("a.css"); 21 | const css = parse("a{}", { from: file }); 22 | const a = css.first; 23 | const error = a.error("Test"); 24 | assert(error instanceof CssSyntaxError); 25 | assertEquals(error.message, file + ":1:1: Test"); 26 | }); 27 | 28 | Deno.test("error() generates custom error for nodes without source", () => { 29 | const rule = new Rule({ selector: "a" }); 30 | const error = rule.error("Test"); 31 | assertEquals(error.message, ": Test"); 32 | }); 33 | 34 | Deno.test("error() highlights index", () => { 35 | const root = parse("a { b: c }"); 36 | const a = root.first; 37 | const b = a.first; 38 | const error = b.error("Bad semicolon", { index: 1 }); 39 | assertEquals( 40 | error.showSourceCode(false), 41 | "> 1 | a { b: c }\n" + " | ^", 42 | ); 43 | }); 44 | 45 | Deno.test("error() highlights word", () => { 46 | const root = parse("a { color: x red }"); 47 | const a = root.first; 48 | const color = a.first; 49 | const error = color.error("Wrong color", { word: "x" }); 50 | assertEquals( 51 | error.showSourceCode(false), 52 | "> 1 | a { color: x red }\n" + " | ^", 53 | ); 54 | }); 55 | 56 | Deno.test("error() highlights word in multiline string", () => { 57 | const root = parse("a { color: red\n x }"); 58 | const a = root.first; 59 | const color = a.first; 60 | const error = color.error("Wrong color", { word: "x" }); 61 | assertEquals( 62 | error.showSourceCode(false), 63 | " 1 | a { color: red\n" + "> 2 | x }\n" + " | ^", 64 | ); 65 | }); 66 | 67 | Deno.test("warn() attaches a warning to the result object", async () => { 68 | let warning; 69 | const warner = { 70 | postcssPlugin: "warner", 71 | Once(css, { result }) { 72 | warning = css.first?.warn(result, "FIRST!"); 73 | }, 74 | }; 75 | 76 | const result = await postcss([warner]).process("a{}", { from: undefined }); 77 | assertEquals(warning.type, "warning"); 78 | assertEquals(warning.text, "FIRST!"); 79 | assertEquals(warning.plugin, "warner"); 80 | assertEquals(result.warnings(), [warning]); 81 | }); 82 | 83 | Deno.test("warn() accepts options", () => { 84 | const warner = (css, result) => { 85 | css.first?.warn(result, "FIRST!", { index: 1 }); 86 | }; 87 | 88 | const result = postcss([warner]).process("a{}"); 89 | assert(result.warnings().length === 1); 90 | const warning = result.warnings()[0]; 91 | assertEquals(warning.index, 1); 92 | }); 93 | 94 | Deno.test("remove() removes node from parent", () => { 95 | const rule = new Rule({ selector: "a" }); 96 | const decl = new Declaration({ prop: "color", value: "black" }); 97 | rule.append(decl); 98 | 99 | decl.remove(); 100 | assert(rule.nodes.length === 0); 101 | assert(typeof decl.parent === "undefined"); 102 | }); 103 | 104 | Deno.test("replaceWith() inserts new node", () => { 105 | const rule = new Rule({ selector: "a" }); 106 | rule.append({ prop: "color", value: "black" }); 107 | rule.append({ prop: "width", value: "1px" }); 108 | rule.append({ prop: "height", value: "1px" }); 109 | 110 | const node = new Declaration({ prop: "min-width", value: "1px" }); 111 | const width = rule.nodes[1]; 112 | const result = width.replaceWith(node); 113 | 114 | assertEquals(result, width); 115 | assertEquals( 116 | rule.toString(), 117 | "a {\n" + 118 | " color: black;\n" + 119 | " min-width: 1px;\n" + 120 | " height: 1px\n" + 121 | "}", 122 | ); 123 | }); 124 | 125 | Deno.test("replaceWith() inserts new root", () => { 126 | const root = new Root(); 127 | root.append(new AtRule({ name: "import", params: '"a.css"' })); 128 | 129 | const a = new Root(); 130 | a.append(new Rule({ selector: "a" })); 131 | a.append(new Rule({ selector: "b" })); 132 | 133 | root.first?.replaceWith(a); 134 | assertEquals(root.toString(), "a {}\nb {}"); 135 | }); 136 | 137 | Deno.test("replaceWith() replaces node", () => { 138 | const css = parse("a{one:1;two:2}"); 139 | const a = css.first; 140 | const one = a.first; 141 | const result = one.replaceWith({ prop: "fix", value: "fixed" }); 142 | 143 | assertEquals(result.prop, "one"); 144 | assert(typeof result.parent === "undefined"); 145 | assertEquals(css.toString(), "a{fix:fixed;two:2}"); 146 | }); 147 | 148 | Deno.test("replaceWith() can include itself", () => { 149 | const css = parse("a{one:1;two:2}"); 150 | const a = css.first; 151 | const one = a.first; 152 | const beforeDecl = { prop: "fix1", value: "fixedOne" }; 153 | const afterDecl = { prop: "fix2", value: "fixedTwo" }; 154 | one.replaceWith(beforeDecl, one, afterDecl); 155 | 156 | assertEquals(css.toString(), "a{fix1:fixedOne;one:1;fix2:fixedTwo;two:2}"); 157 | }); 158 | 159 | Deno.test("toString() accepts custom stringifier", () => { 160 | assertEquals(new Rule({ selector: "a" }).toString(stringify), "a"); 161 | }); 162 | 163 | Deno.test("toString() accepts custom syntax", () => { 164 | assertEquals(new Rule({ selector: "a" }).toString({ stringify }), "a"); 165 | }); 166 | 167 | Deno.test("clone() clones nodes", () => { 168 | const rule = new Rule({ selector: "a" }); 169 | rule.append({ prop: "color", value: "/**/black" }); 170 | 171 | const clone = rule.clone(); 172 | 173 | assert(typeof clone.parent === "undefined"); 174 | 175 | assert(rule.first?.parent === rule); 176 | assert(clone.first?.parent === clone); 177 | 178 | clone.append({ prop: "z-index", value: "1" }); 179 | assert(rule.nodes.length === 1); 180 | }); 181 | 182 | Deno.test("clone() overrides properties", () => { 183 | const rule = new Rule({ selector: "a" }); 184 | const clone = rule.clone({ selector: "b" }); 185 | assertEquals(clone.selector, "b"); 186 | }); 187 | 188 | Deno.test("clone() keeps code style", () => { 189 | const css = parse("@page 1{a{color:black;}}"); 190 | assertEquals(css.clone().toString(), "@page 1{a{color:black;}}"); 191 | }); 192 | 193 | Deno.test("clone() works with null in raws", () => { 194 | const decl = new Declaration({ 195 | prop: "color", 196 | value: "black", 197 | raws: { value: null }, 198 | }); 199 | const clone = decl.clone(); 200 | assertEquals(Object.keys(clone.raws), ["value"]); 201 | }); 202 | 203 | Deno.test("cloneBefore() clones and insert before current node", () => { 204 | const rule = new Rule({ selector: "a", raws: { after: "" } }); 205 | rule.append({ prop: "z-index", value: "1", raws: { before: "" } }); 206 | 207 | const result = rule.first?.cloneBefore({ value: "2" }); 208 | 209 | assert(result === rule.first); 210 | assertEquals(rule.toString(), "a {z-index: 2;z-index: 1}"); 211 | }); 212 | 213 | Deno.test("cloneAfter() clones and insert after current node", () => { 214 | const rule = new Rule({ selector: "a", raws: { after: "" } }); 215 | rule.append({ prop: "z-index", value: "1", raws: { before: "" } }); 216 | 217 | const result = rule.first?.cloneAfter({ value: "2" }); 218 | 219 | assert(result === rule.last); 220 | assertEquals(rule.toString(), "a {z-index: 1;z-index: 2}"); 221 | }); 222 | 223 | Deno.test("before() insert before current node", () => { 224 | const rule = new Rule({ selector: "a", raws: { after: "" } }); 225 | rule.append({ prop: "z-index", value: "1", raws: { before: "" } }); 226 | 227 | const result = rule.first?.before("color: black"); 228 | 229 | assert(result === rule.last); 230 | assertEquals(rule.toString(), "a {color: black;z-index: 1}"); 231 | }); 232 | 233 | Deno.test("after() insert after current node", () => { 234 | const rule = new Rule({ selector: "a", raws: { after: "" } }); 235 | rule.append({ prop: "z-index", value: "1", raws: { before: "" } }); 236 | 237 | const result = rule.first?.after("color: black"); 238 | 239 | assert(result === rule.first); 240 | assertEquals(rule.toString(), "a {z-index: 1;color: black}"); 241 | }); 242 | 243 | Deno.test("next() returns next node", () => { 244 | const css = parse("a{one:1;two:2}"); 245 | const a = css.first; 246 | assert(a.first?.next() === a.last); 247 | assert(typeof a.last?.next() === "undefined"); 248 | }); 249 | 250 | Deno.test("next() returns undefined on no parent", () => { 251 | const css = parse(""); 252 | assert(typeof css.next() === "undefined"); 253 | }); 254 | 255 | Deno.test("prev() returns previous node", () => { 256 | const css = parse("a{one:1;two:2}"); 257 | const a = css.first; 258 | assert(a.last?.prev() === a.first); 259 | assert(typeof a.first?.prev() === "undefined"); 260 | }); 261 | 262 | Deno.test("prev() returns undefined on no parent", () => { 263 | const css = parse(""); 264 | assert(typeof css.prev() === "undefined"); 265 | }); 266 | 267 | Deno.test("toJSON() cleans parents inside", () => { 268 | const rule = new Rule({ selector: "a" }); 269 | rule.append({ prop: "color", value: "b" }); 270 | 271 | const json = rule.toJSON(); 272 | assert(typeof json.parent === "undefined"); 273 | assert(typeof json.nodes[0].parent === "undefined"); 274 | 275 | assertEquals( 276 | JSON.stringify(rule), 277 | '{"raws":{},"selector":"a","type":"rule","nodes":[{"raws":{},"prop":"color","value":"b","type":"decl"}],"inputs":[]}', 278 | ); 279 | }); 280 | 281 | Deno.test("toJSON() converts custom properties", () => { 282 | const root = new Root(); 283 | root._cache = [1]; 284 | root._hack = { 285 | toJSON() { 286 | return "hack"; 287 | }, 288 | }; 289 | 290 | assertEquals(root.toJSON(), { 291 | type: "root", 292 | nodes: [], 293 | raws: {}, 294 | inputs: [], 295 | _hack: "hack", 296 | _cache: [1], 297 | }); 298 | }); 299 | 300 | Deno.test("raw() has shortcut to stringifier", () => { 301 | const rule = new Rule({ selector: "a" }); 302 | assertEquals(rule.raw("before"), ""); 303 | }); 304 | 305 | Deno.test("root() returns root", () => { 306 | const css = parse("@page{a{color:black}}"); 307 | const page = css.first; 308 | const a = page.first; 309 | const color = a.first; 310 | assert(color.root() === css); 311 | }); 312 | 313 | Deno.test("root() returns parent of parents", () => { 314 | const rule = new Rule({ selector: "a" }); 315 | rule.append({ prop: "color", value: "black" }); 316 | assert(rule.first?.root() === rule); 317 | }); 318 | 319 | Deno.test("root() returns self on root", () => { 320 | const rule = new Rule({ selector: "a" }); 321 | assert(rule.root() === rule); 322 | }); 323 | 324 | Deno.test("cleanRaws() cleans style recursivelly", () => { 325 | const css = parse("@page{a{color:black}}"); 326 | css.cleanRaws(); 327 | 328 | assertEquals( 329 | css.toString(), 330 | "@page {\n a {\n color: black\n }\n}", 331 | ); 332 | const page = css.first; 333 | const a = page.first; 334 | const color = a.first; 335 | assert(typeof page.raws.before === "undefined"); 336 | assert(typeof color.raws.before === "undefined"); 337 | assert(typeof page.raws.between === "undefined"); 338 | assert(typeof color.raws.between === "undefined"); 339 | assert(typeof page.raws.after === "undefined"); 340 | }); 341 | 342 | Deno.test("cleanRaws() keeps between on request", () => { 343 | const css = parse("@page{a{color:black}}"); 344 | css.cleanRaws(true); 345 | 346 | assertEquals(css.toString(), "@page{\n a{\n color:black\n }\n}"); 347 | const page = css.first; 348 | const a = page.first; 349 | const color = a.first; 350 | assert(typeof page.raws.between !== "undefined"); 351 | assert(typeof color.raws.between !== "undefined"); 352 | assert(typeof page.raws.before === "undefined"); 353 | assert(typeof color.raws.before === "undefined"); 354 | assert(typeof page.raws.after === "undefined"); 355 | }); 356 | 357 | Deno.test("positionInside() returns position when node starts mid-line", () => { 358 | const css = parse("a { one: X }"); 359 | const a = css.first; 360 | const one = a.first; 361 | assertEquals(one.positionInside(6), { line: 1, column: 12 }); 362 | }); 363 | 364 | Deno.test("positionInside() returns position when before contains newline", () => { 365 | const css = parse("a {\n one: X}"); 366 | const a = css.first; 367 | const one = a.first; 368 | assertEquals(one.positionInside(6), { line: 2, column: 9 }); 369 | }); 370 | 371 | Deno.test("positionInside() returns position when node contains newlines", () => { 372 | const css = parse("a {\n\tone: 1\n\t\tX\n3}"); 373 | const a = css.first; 374 | const one = a.first; 375 | assertEquals(one.positionInside(10), { line: 3, column: 4 }); 376 | }); 377 | -------------------------------------------------------------------------------- /deno/test/result.test.js: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "./deps.js"; 2 | import postcss, { Result, Root, Warning } from "../mod.js"; 3 | import Processor from "../lib/processor.js"; 4 | 5 | const processor = new Processor(); 6 | const root = new Root(); 7 | 8 | Deno.test("stringifies", () => { 9 | const result = new Result(processor, root, {}); 10 | result.css = "a{}"; 11 | assertEquals(`${result}`, result.css); 12 | }); 13 | 14 | Deno.test("adds warning", () => { 15 | let warning; 16 | const plugin = { 17 | postcssPlugin: "test-plugin", 18 | Once(css, { result }) { 19 | warning = result.warn("test", { node: css.first }); 20 | }, 21 | }; 22 | const result = postcss([plugin]).process("a{}").sync(); 23 | 24 | assertEquals( 25 | warning, 26 | new Warning("test", { 27 | plugin: "test-plugin", 28 | node: result.root.first, 29 | }), 30 | ); 31 | 32 | assertEquals(result.messages, [warning]); 33 | }); 34 | 35 | Deno.test("allows to override plugin", () => { 36 | const plugin = { 37 | postcssPlugin: "test-plugin", 38 | Once(_css, { result }) { 39 | result.warn("test", { plugin: "test-plugin#one" }); 40 | }, 41 | }; 42 | const result = postcss([plugin]).process("a{}").sync(); 43 | 44 | assertEquals(result.messages[0].plugin, "test-plugin#one"); 45 | }); 46 | 47 | Deno.test("allows Root", () => { 48 | const css = postcss.parse("a{}"); 49 | const result = new Result(processor, css, {}); 50 | result.warn("TT", { node: css.first }); 51 | 52 | assertEquals(result.messages[0].toString(), ":1:1: TT"); 53 | }); 54 | 55 | Deno.test("returns only warnings", () => { 56 | const result = new Result(processor, root, {}); 57 | result.messages = [ 58 | { type: "warning", text: "a" }, 59 | { type: "custom" }, 60 | { type: "warning", text: "b" }, 61 | ]; 62 | assertEquals(result.warnings(), [ 63 | { type: "warning", text: "a" }, 64 | { type: "warning", text: "b" }, 65 | ]); 66 | }); 67 | -------------------------------------------------------------------------------- /deno/test/root.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, assertEquals, assertMatch } from "./deps.js"; 2 | import { parse, Result } from "../mod.js"; 3 | 4 | Deno.test("prepend() fixes spaces on insert before first", () => { 5 | const css = parse("a {} b {}"); 6 | css.prepend({ selector: "em" }); 7 | assertEquals(css.toString(), "em {} a {} b {}"); 8 | }); 9 | 10 | Deno.test("prepend() fixes spaces on multiple inserts before first", () => { 11 | const css = parse("a {} b {}"); 12 | css.prepend({ selector: "em" }, { selector: "strong" }); 13 | assertEquals(css.toString(), "em {} strong {} a {} b {}"); 14 | }); 15 | 16 | Deno.test("prepend() uses default spaces on only first", () => { 17 | const css = parse("a {}"); 18 | css.prepend({ selector: "em" }); 19 | assertEquals(css.toString(), "em {}\na {}"); 20 | }); 21 | 22 | Deno.test("append() sets new line between rules in multiline files", () => { 23 | const a = parse("a {}\n\na {}\n"); 24 | const b = parse("b {}\n"); 25 | assertEquals(a.append(b).toString(), "a {}\n\na {}\n\nb {}\n"); 26 | }); 27 | 28 | Deno.test("insertAfter() does not use before of first rule", () => { 29 | const css = parse("a{} b{}"); 30 | css.insertAfter(0, { selector: ".a" }); 31 | css.insertAfter(2, { selector: ".b" }); 32 | 33 | assert(typeof css.nodes[1].raws.before === "undefined"); 34 | assertEquals(css.nodes[3].raws.before, " "); 35 | assertEquals(css.toString(), "a{} .a{} b{} .b{}"); 36 | }); 37 | 38 | Deno.test("keeps spaces on moving root", () => { 39 | const css1 = parse("a{}\nb{}\n"); 40 | 41 | const css2 = parse(""); 42 | css2.append(css1); 43 | assertEquals(css2.toString(), "a{}\nb{}"); 44 | 45 | const css3 = parse("\n"); 46 | css3.append(css2.nodes); 47 | assertEquals(css3.toString(), "a{}\nb{}\n"); 48 | }); 49 | 50 | Deno.test("generates result with map", () => { 51 | const root = parse("a {}"); 52 | const result = root.toResult({ map: true }); 53 | 54 | assert(result instanceof Result); 55 | assertMatch(result.css, /a {}\n\/\*# sourceMappingURL=/); 56 | }); 57 | -------------------------------------------------------------------------------- /deno/test/rule.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "./deps.js"; 2 | import { parse, Rule } from "../mod.js"; 3 | 4 | Deno.test("initializes with properties", () => { 5 | const rule = new Rule({ selector: "a" }); 6 | assertEquals(rule.selector, "a"); 7 | }); 8 | 9 | Deno.test("returns array in selectors", () => { 10 | const rule = new Rule({ selector: "a,b" }); 11 | assertEquals(rule.selectors, ["a", "b"]); 12 | }); 13 | 14 | Deno.test("trims selectors", () => { 15 | const rule = new Rule({ selector: ".a\n, .b , .c" }); 16 | assertEquals(rule.selectors, [".a", ".b", ".c"]); 17 | }); 18 | 19 | Deno.test("is smart about selectors commas", () => { 20 | const rule = new Rule({ 21 | selector: "[foo='a, b'], a:-moz-any(:focus, [href*=','])", 22 | }); 23 | assertEquals(rule.selectors, [ 24 | "[foo='a, b']", 25 | "a:-moz-any(:focus, [href*=','])", 26 | ]); 27 | }); 28 | 29 | Deno.test("receive array in selectors", () => { 30 | const rule = new Rule({ selector: "i, b" }); 31 | rule.selectors = ["em", "strong"]; 32 | assertEquals(rule.selector, "em, strong"); 33 | }); 34 | 35 | Deno.test("saves separator in selectors", () => { 36 | const rule = new Rule({ selector: "i,\nb" }); 37 | rule.selectors = ["em", "strong"]; 38 | assertEquals(rule.selector, "em,\nstrong"); 39 | }); 40 | 41 | Deno.test("uses between to detect separator in selectors", () => { 42 | const rule = new Rule({ selector: "b", raws: { between: "" } }); 43 | rule.selectors = ["b", "strong"]; 44 | assertEquals(rule.selector, "b,strong"); 45 | }); 46 | 47 | Deno.test("uses space in separator be default in selectors", () => { 48 | const rule = new Rule({ selector: "b" }); 49 | rule.selectors = ["b", "strong"]; 50 | assertEquals(rule.selector, "b, strong"); 51 | }); 52 | 53 | Deno.test("selectors works in constructor", () => { 54 | const rule = new Rule({ selectors: ["a", "b"] }); 55 | assertEquals(rule.selector, "a, b"); 56 | }); 57 | 58 | Deno.test("inserts default spaces", () => { 59 | const rule = new Rule({ selector: "a" }); 60 | assertEquals(rule.toString(), "a {}"); 61 | rule.append({ prop: "color", value: "black" }); 62 | assertEquals(rule.toString(), "a {\n color: black\n}"); 63 | }); 64 | 65 | Deno.test("clones spaces from another rule", () => { 66 | const root = parse("b{\n }"); 67 | const rule = new Rule({ selector: "em" }); 68 | root.append(rule); 69 | assertEquals(root.toString(), "b{\n }\nem{\n }"); 70 | }); 71 | 72 | Deno.test("uses different spaces for empty rules", () => { 73 | const root = parse("a{}\nb{\n a:1\n}"); 74 | const rule = new Rule({ selector: "em" }); 75 | root.append(rule); 76 | assertEquals(root.toString(), "a{}\nb{\n a:1\n}\nem{}"); 77 | 78 | rule.append({ prop: "top", value: "0" }); 79 | assertEquals(root.toString(), "a{}\nb{\n a:1\n}\nem{\n top:0\n}"); 80 | }); 81 | -------------------------------------------------------------------------------- /deno/test/tokenize.test.js: -------------------------------------------------------------------------------- 1 | import { assertEquals, assertThrows } from "./deps.js"; 2 | import tokenizer from "../lib/tokenize.js"; 3 | import { Input } from "../mod.js"; 4 | 5 | function tokenize(css, opts) { 6 | const processor = tokenizer(new Input(css), opts); 7 | const tokens = []; 8 | while (!processor.endOfFile()) { 9 | tokens.push(processor.nextToken()); 10 | } 11 | return tokens; 12 | } 13 | 14 | function run(css, tokens, opts) { 15 | assertEquals(tokenize(css, opts), tokens); 16 | } 17 | 18 | Deno.test("tokenizes empty file", () => { 19 | run("", []); 20 | }); 21 | 22 | Deno.test("tokenizes space", () => { 23 | run("\r\n \f\t", [["space", "\r\n \f\t"]]); 24 | }); 25 | 26 | Deno.test("tokenizes word", () => { 27 | run("ab", [["word", "ab", 0, 1]]); 28 | }); 29 | 30 | Deno.test("splits word by !", () => { 31 | run("aa!bb", [ 32 | ["word", "aa", 0, 1], 33 | ["word", "!bb", 2, 4], 34 | ]); 35 | }); 36 | 37 | Deno.test("changes lines in spaces", () => { 38 | run("a \n b", [ 39 | ["word", "a", 0, 0], 40 | ["space", " \n "], 41 | ["word", "b", 4, 4], 42 | ]); 43 | }); 44 | 45 | Deno.test("tokenizes control chars", () => { 46 | run("{:;}", [ 47 | ["{", "{", 0], 48 | [":", ":", 1], 49 | [";", ";", 2], 50 | ["}", "}", 3], 51 | ]); 52 | }); 53 | 54 | Deno.test("escapes control symbols", () => { 55 | run('\\(\\{\\"\\@\\\\""', [ 56 | ["word", "\\(", 0, 1], 57 | ["word", "\\{", 2, 3], 58 | ["word", '\\"', 4, 5], 59 | ["word", "\\@", 6, 7], 60 | ["word", "\\\\", 8, 9], 61 | ["string", '""', 10, 11], 62 | ]); 63 | }); 64 | 65 | Deno.test("escapes backslash", () => { 66 | run("\\\\\\\\{", [ 67 | ["word", "\\\\\\\\", 0, 3], 68 | ["{", "{", 4], 69 | ]); 70 | }); 71 | 72 | Deno.test("tokenizes simple brackets", () => { 73 | run("(ab)", [["brackets", "(ab)", 0, 3]]); 74 | }); 75 | 76 | Deno.test("tokenizes square brackets", () => { 77 | run("a[bc]", [ 78 | ["word", "a", 0, 0], 79 | ["[", "[", 1], 80 | ["word", "bc", 2, 3], 81 | ["]", "]", 4], 82 | ]); 83 | }); 84 | 85 | Deno.test("tokenizes complicated brackets", () => { 86 | run('(())("")(/**/)(\\\\)(\n)(', [ 87 | ["(", "(", 0], 88 | ["brackets", "()", 1, 2], 89 | [")", ")", 3], 90 | ["(", "(", 4], 91 | ["string", '""', 5, 6], 92 | [")", ")", 7], 93 | ["(", "(", 8], 94 | ["comment", "/**/", 9, 12], 95 | [")", ")", 13], 96 | ["(", "(", 14], 97 | ["word", "\\\\", 15, 16], 98 | [")", ")", 17], 99 | ["(", "(", 18], 100 | ["space", "\n"], 101 | [")", ")", 20], 102 | ["(", "(", 21], 103 | ]); 104 | }); 105 | 106 | Deno.test("tokenizes string", () => { 107 | run('\'"\'"\\""', [ 108 | ["string", "'\"'", 0, 2], 109 | ["string", '"\\""', 3, 6], 110 | ]); 111 | }); 112 | 113 | Deno.test("tokenizes escaped string", () => { 114 | run('"\\\\"', [["string", '"\\\\"', 0, 3]]); 115 | }); 116 | 117 | Deno.test("changes lines in strings", () => { 118 | run('"\n\n""\n\n"', [ 119 | ["string", '"\n\n"', 0, 3], 120 | ["string", '"\n\n"', 4, 7], 121 | ]); 122 | }); 123 | 124 | Deno.test("tokenizes at-word", () => { 125 | run("@word ", [ 126 | ["at-word", "@word", 0, 4], 127 | ["space", " "], 128 | ]); 129 | }); 130 | 131 | Deno.test("tokenizes at-word end", () => { 132 | run('@one{@two()@three""@four;', [ 133 | ["at-word", "@one", 0, 3], 134 | ["{", "{", 4], 135 | ["at-word", "@two", 5, 8], 136 | ["brackets", "()", 9, 10], 137 | ["at-word", "@three", 11, 16], 138 | ["string", '""', 17, 18], 139 | ["at-word", "@four", 19, 23], 140 | [";", ";", 24], 141 | ]); 142 | }); 143 | 144 | Deno.test("tokenizes urls", () => { 145 | run("url(/*\\))", [ 146 | ["word", "url", 0, 2], 147 | ["brackets", "(/*\\))", 3, 8], 148 | ]); 149 | }); 150 | 151 | Deno.test("tokenizes quoted urls", () => { 152 | run('url(")")', [ 153 | ["word", "url", 0, 2], 154 | ["(", "(", 3], 155 | ["string", '")"', 4, 6], 156 | [")", ")", 7], 157 | ]); 158 | }); 159 | 160 | Deno.test("tokenizes at-symbol", () => { 161 | run("@", [["at-word", "@", 0, 0]]); 162 | }); 163 | 164 | Deno.test("tokenizes comment", () => { 165 | run("/* a\nb */", [["comment", "/* a\nb */", 0, 8]]); 166 | }); 167 | 168 | Deno.test("changes lines in comments", () => { 169 | run("a/* \n */b", [ 170 | ["word", "a", 0, 0], 171 | ["comment", "/* \n */", 1, 7], 172 | ["word", "b", 8, 8], 173 | ]); 174 | }); 175 | 176 | Deno.test("supports line feed", () => { 177 | run("a\fb", [ 178 | ["word", "a", 0, 0], 179 | ["space", "\f"], 180 | ["word", "b", 2, 2], 181 | ]); 182 | }); 183 | 184 | Deno.test("supports carriage return", () => { 185 | run("a\rb\r\nc", [ 186 | ["word", "a", 0, 0], 187 | ["space", "\r"], 188 | ["word", "b", 2, 2], 189 | ["space", "\r\n"], 190 | ["word", "c", 5, 5], 191 | ]); 192 | }); 193 | 194 | Deno.test("tokenizes CSS", () => { 195 | const css = "a {\n" + 196 | ' content: "a";\n' + 197 | " width: calc(1px;)\n" + 198 | " }\n" + 199 | "/* small screen */\n" + 200 | "@media screen {}"; 201 | run(css, [ 202 | ["word", "a", 0, 0], 203 | ["space", " "], 204 | ["{", "{", 2], 205 | ["space", "\n "], 206 | ["word", "content", 6, 12], 207 | [":", ":", 13], 208 | ["space", " "], 209 | ["string", '"a"', 15, 17], 210 | [";", ";", 18], 211 | ["space", "\n "], 212 | ["word", "width", 22, 26], 213 | [":", ":", 27], 214 | ["space", " "], 215 | ["word", "calc", 29, 32], 216 | ["brackets", "(1px;)", 33, 38], 217 | ["space", "\n "], 218 | ["}", "}", 42], 219 | ["space", "\n"], 220 | ["comment", "/* small screen */", 44, 61], 221 | ["space", "\n"], 222 | ["at-word", "@media", 63, 68], 223 | ["space", " "], 224 | ["word", "screen", 70, 75], 225 | ["space", " "], 226 | ["{", "{", 77], 227 | ["}", "}", 78], 228 | ]); 229 | }); 230 | 231 | Deno.test("throws error on unclosed string", () => { 232 | assertThrows(() => { 233 | tokenize(' "'); 234 | }); 235 | }); 236 | 237 | Deno.test("throws error on unclosed comment", () => { 238 | assertThrows(() => { 239 | tokenize(" /*"); 240 | }); 241 | }); 242 | 243 | Deno.test("throws error on unclosed url", () => { 244 | assertThrows(() => { 245 | tokenize("url("); 246 | }); 247 | }); 248 | 249 | Deno.test("ignores unclosing string on request", () => { 250 | run( 251 | ' "', 252 | [ 253 | ["space", " "], 254 | ["string", '"', 1, 2], 255 | ], 256 | { ignoreErrors: true }, 257 | ); 258 | }); 259 | 260 | Deno.test("ignores unclosing comment on request", () => { 261 | run( 262 | " /*", 263 | [ 264 | ["space", " "], 265 | ["comment", "/*", 1, 3], 266 | ], 267 | { ignoreErrors: true }, 268 | ); 269 | }); 270 | 271 | Deno.test("ignores unclosing function on request", () => { 272 | run( 273 | "url(", 274 | [ 275 | ["word", "url", 0, 2], 276 | ["brackets", "(", 3, 3], 277 | ], 278 | { ignoreErrors: true }, 279 | ); 280 | }); 281 | 282 | Deno.test("tokenizes hexadecimal escape", () => { 283 | run("\\0a \\09 \\z ", [ 284 | ["word", "\\0a ", 0, 3], 285 | ["word", "\\09 ", 4, 7], 286 | ["word", "\\z", 8, 9], 287 | ["space", " "], 288 | ]); 289 | }); 290 | 291 | Deno.test("ignore unclosed per token request", () => { 292 | function tokn(css, opts) { 293 | const processor = tokenizer(new Input(css), opts); 294 | const tokens = []; 295 | while (!processor.endOfFile()) { 296 | tokens.push(processor.nextToken({ ignoreUnclosed: true })); 297 | } 298 | return tokens; 299 | } 300 | 301 | const css = "How's it going ("; 302 | const tokens = tokn(css, {}); 303 | const expected = [ 304 | ["word", "How", 0, 2], 305 | ["string", "'s", 3, 4], 306 | ["space", " "], 307 | ["word", "it", 6, 7], 308 | ["space", " "], 309 | ["word", "going", 9, 13], 310 | ["space", " "], 311 | ["(", "(", 15], 312 | ]; 313 | 314 | assertEquals(tokens, expected); 315 | }); 316 | 317 | Deno.test("provides correct position", () => { 318 | const css = "Three tokens"; 319 | const processor = tokenizer(new Input(css)); 320 | assertEquals(processor.position(), 0); 321 | processor.nextToken(); 322 | assertEquals(processor.position(), 5); 323 | processor.nextToken(); 324 | assertEquals(processor.position(), 6); 325 | processor.nextToken(); 326 | assertEquals(processor.position(), 12); 327 | processor.nextToken(); 328 | assertEquals(processor.position(), 12); 329 | }); 330 | -------------------------------------------------------------------------------- /deno/test/warning.test.js: -------------------------------------------------------------------------------- 1 | import { assert, assertEquals } from "./deps.js"; 2 | import { resolve } from "../lib/deps.js"; 3 | import { decl, parse, Warning } from "../mod.js"; 4 | 5 | Deno.test("outputs simple warning", () => { 6 | const warning = new Warning("text"); 7 | assertEquals(warning.toString(), "text"); 8 | }); 9 | 10 | Deno.test("outputs warning with plugin", () => { 11 | const warning = new Warning("text", { plugin: "plugin" }); 12 | assertEquals(warning.toString(), "plugin: text"); 13 | }); 14 | 15 | Deno.test("outputs warning with position", () => { 16 | const root = parse("a{}"); 17 | const warning = new Warning("text", { node: root.first }); 18 | assertEquals(warning.toString(), ":1:1: text"); 19 | }); 20 | 21 | Deno.test("outputs warning with plugin and node", () => { 22 | const file = resolve("a.css"); 23 | const root = parse("a{}", { from: file }); 24 | const warning = new Warning("text", { 25 | plugin: "plugin", 26 | node: root.first, 27 | }); 28 | assertEquals(warning.toString(), `plugin: ${file}:1:1: text`); 29 | }); 30 | 31 | Deno.test("outputs warning with index", () => { 32 | const file = resolve("a.css"); 33 | const root = parse("@rule param {}", { from: file }); 34 | const warning = new Warning("text", { 35 | plugin: "plugin", 36 | node: root.first, 37 | index: 7, 38 | }); 39 | assertEquals(warning.toString(), `plugin: ${file}:1:8: text`); 40 | }); 41 | 42 | Deno.test("outputs warning with word", () => { 43 | const file = resolve("a.css"); 44 | const root = parse("@rule param {}", { from: file }); 45 | const warning = new Warning("text", { 46 | plugin: "plugin", 47 | node: root.first, 48 | word: "am", 49 | }); 50 | assertEquals(warning.toString(), `plugin: ${file}:1:10: text`); 51 | }); 52 | 53 | Deno.test("generates warning without source", () => { 54 | const node = decl({ prop: "color", value: "black" }); 55 | const warning = new Warning("text", { node }); 56 | assertEquals(warning.toString(), ": text"); 57 | }); 58 | 59 | Deno.test("has line and column is undefined by default", () => { 60 | const warning = new Warning("text"); 61 | assert(typeof warning.line === "undefined"); 62 | assert(typeof warning.column === "undefined"); 63 | }); 64 | 65 | Deno.test("gets position from node", () => { 66 | const root = parse("a{}"); 67 | const warning = new Warning("text", { node: root.first }); 68 | assertEquals(warning.line, 1); 69 | assertEquals(warning.column, 1); 70 | }); 71 | 72 | Deno.test("gets position from word", () => { 73 | const root = parse("a b{}"); 74 | const warning = new Warning("text", { node: root.first, word: "b" }); 75 | assertEquals(warning.line, 1); 76 | assertEquals(warning.column, 3); 77 | }); 78 | 79 | Deno.test("gets position from index", () => { 80 | const root = parse("a b{}"); 81 | const warning = new Warning("text", { node: root.first, index: 2 }); 82 | assertEquals(warning.line, 1); 83 | assertEquals(warning.column, 3); 84 | }); 85 | -------------------------------------------------------------------------------- /deps.js: -------------------------------------------------------------------------------- 1 | export { 2 | fileURLToPath, 3 | pathToFileURL, 4 | } from "https://deno.land/std@0.159.0/node/url.ts"; 5 | 6 | export { 7 | existsSync, 8 | readFileSync, 9 | } from "https://deno.land/std@0.159.0/node/fs.ts"; 10 | 11 | export { 12 | basename, 13 | dirname, 14 | isAbsolute, 15 | join, 16 | relative, 17 | resolve, 18 | sep, 19 | } from "https://deno.land/std@0.159.0/node/path.ts"; 20 | 21 | export { Buffer } from "https://deno.land/std@0.159.0/node/buffer.ts"; 22 | export { nanoid } from "https://deno.land/x/nanoid@v3.0.0/nanoid.ts"; 23 | 24 | import { 25 | bold, 26 | cyan, 27 | gray, 28 | green, 29 | magenta, 30 | red, 31 | yellow, 32 | getColorEnabled, 33 | } from "https://deno.land/std@0.159.0/fmt/colors.ts"; 34 | 35 | export const pico = { 36 | isColorSupported: getColorEnabled(), 37 | createColors: () => ({ bold, red, gray }), 38 | cyan, 39 | gray, 40 | green, 41 | yellow, 42 | magenta, 43 | } 44 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | # Clean old files 2 | rm -rf deno 3 | rm -rf postcss 4 | 5 | # Clone the repo 6 | git clone --depth 1 --branch main https://github.com/postcss/postcss.git 7 | 8 | # Run the script 9 | deno run --unstable --allow-read=. --allow-write=. to_deno.js 10 | 11 | # Autoformat the code 12 | deno fmt deno 13 | 14 | # Run the tests 15 | deno test --unstable --allow-read=. --allow-env=DENO_ENV deno/test 16 | -------------------------------------------------------------------------------- /source_map.ts: -------------------------------------------------------------------------------- 1 | // @deno-types="https://deno.land/x/source_map@0.6.2/source-map.d.ts" 2 | export * from "https://deno.land/x/source_map@0.6.2/mod.js"; 3 | -------------------------------------------------------------------------------- /test/at-rule.test.js: -------------------------------------------------------------------------------- 1 | import { assert, assertEquals } from "./deps.js"; 2 | import { AtRule, parse } from "../mod.js"; 3 | 4 | Deno.test("initializes with properties", () => { 5 | const rule = new AtRule({ name: "encoding", params: '"utf-8"' }); 6 | 7 | assertEquals(rule.name, "encoding"); 8 | assertEquals(rule.params, '"utf-8"'); 9 | assertEquals(rule.toString(), '@encoding "utf-8"'); 10 | }); 11 | 12 | Deno.test("does not fall on childless at-rule", () => { 13 | const rule = new AtRule(); 14 | assert(typeof rule.each((i) => i) === "undefined"); 15 | }); 16 | 17 | Deno.test("creates nodes property on prepend()", () => { 18 | const rule = new AtRule(); 19 | assert(typeof rule.nodes === "undefined"); 20 | 21 | rule.prepend("color: black"); 22 | assert(rule.nodes.length === 1); 23 | }); 24 | 25 | Deno.test("creates nodes property on append()", () => { 26 | const rule = new AtRule(); 27 | assert(typeof rule.nodes === "undefined"); 28 | 29 | rule.append("color: black"); 30 | assert(rule.nodes.length === 1); 31 | }); 32 | 33 | Deno.test("inserts default spaces", () => { 34 | const rule = new AtRule({ name: "page", params: 1, nodes: [] }); 35 | assertEquals(rule.toString(), "@page 1 {}"); 36 | }); 37 | 38 | Deno.test("clone spaces from another at-rule", () => { 39 | const root = parse("@page{}a{}"); 40 | const rule = new AtRule({ name: "page", params: 1, nodes: [] }); 41 | root.append(rule); 42 | 43 | assertEquals(rule.toString(), "@page 1{}"); 44 | }); 45 | -------------------------------------------------------------------------------- /test/comment.test.js: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "./deps.js"; 2 | import { Comment, parse } from "../mod.js"; 3 | 4 | Deno.test("toString() inserts default spaces", () => { 5 | const comment = new Comment({ text: "hi" }); 6 | assertEquals(comment.toString(), "/* hi */"); 7 | }); 8 | 9 | Deno.test("toString() clones spaces from another comment", () => { 10 | const root = parse("a{} /*hello*/"); 11 | const comment = new Comment({ text: "world" }); 12 | root.append(comment); 13 | 14 | assertEquals(root.toString(), "a{} /*hello*/ /*world*/"); 15 | }); 16 | -------------------------------------------------------------------------------- /test/declaration.test.js: -------------------------------------------------------------------------------- 1 | import { assert, assertEquals } from "./deps.js"; 2 | import { Declaration, parse, Rule } from "../mod.js"; 3 | 4 | Deno.test("initializes with properties", () => { 5 | const decl = new Declaration({ prop: "color", value: "black" }); 6 | assertEquals(decl.prop, "color"); 7 | assertEquals(decl.value, "black"); 8 | }); 9 | 10 | Deno.test("returns boolean important", () => { 11 | const decl = new Declaration({ prop: "color", value: "black" }); 12 | decl.important = true; 13 | assertEquals(decl.toString(), "color: black !important"); 14 | }); 15 | 16 | Deno.test("inserts default spaces", () => { 17 | const decl = new Declaration({ prop: "color", value: "black" }); 18 | const rule = new Rule({ selector: "a" }); 19 | rule.append(decl); 20 | assertEquals(rule.toString(), "a {\n color: black\n}"); 21 | }); 22 | 23 | Deno.test("clones spaces from another declaration", () => { 24 | const root = parse("a{color:black}"); 25 | const rule = root.first; 26 | const decl = new Declaration({ prop: "margin", value: "0" }); 27 | rule.append(decl); 28 | assertEquals(root.toString(), "a{color:black;margin:0}"); 29 | }); 30 | 31 | Deno.test("converts value to string", () => { 32 | const decl = new Declaration({ prop: "color", value: 1 }); 33 | assertEquals(decl.value, "1"); 34 | }); 35 | 36 | Deno.test("detects variable declarations", () => { 37 | const prop = new Declaration({ prop: "--color", value: "black" }); 38 | assert(prop.variable === true); 39 | const sass = new Declaration({ prop: "$color", value: "black" }); 40 | assert(sass.variable === true); 41 | const decl = new Declaration({ prop: "color", value: "black" }); 42 | assert(decl.variable === false); 43 | }); 44 | -------------------------------------------------------------------------------- /test/deps.js: -------------------------------------------------------------------------------- 1 | export * from "https://deno.land/std@0.98.0/testing/asserts.ts"; 2 | import "https://deno.land/std@0.98.0/node/global.ts"; 3 | -------------------------------------------------------------------------------- /test/lazy-result.test.js: -------------------------------------------------------------------------------- 1 | import { assert, assertEquals } from "./deps.js"; 2 | import { SourceMapGenerator } from "../lib/source_map.ts"; 3 | import LazyResult from "../lib/lazy-result.js"; 4 | import Processor from "../lib/processor.js"; 5 | 6 | const processor = new Processor(); 7 | 8 | Deno.test("contains AST", () => { 9 | const result = new LazyResult(processor, "a {}", {}); 10 | assertEquals(result.root.type, "root"); 11 | }); 12 | 13 | Deno.test("will stringify css", () => { 14 | const result = new LazyResult(processor, "a {}", {}); 15 | assertEquals(result.css, "a {}"); 16 | }); 17 | 18 | Deno.test("stringifies css", () => { 19 | const result = new LazyResult(processor, "a {}", {}); 20 | assertEquals(`${result}`, result.css); 21 | }); 22 | 23 | Deno.test("has content alias for css", () => { 24 | const result = new LazyResult(processor, "a {}", {}); 25 | assertEquals(result.content, "a {}"); 26 | }); 27 | 28 | Deno.test("has map only if necessary", () => { 29 | const result1 = new LazyResult(processor, "", {}); 30 | assert(typeof result1.map === "undefined"); 31 | 32 | const result2 = new LazyResult(processor, "", {}); 33 | assert(typeof result2.map === "undefined"); 34 | 35 | const result3 = new LazyResult(processor, "", { map: { inline: false } }); 36 | assert(result3.map instanceof SourceMapGenerator); 37 | }); 38 | 39 | Deno.test("contains options", () => { 40 | const result = new LazyResult(processor, "a {}", { to: "a.css" }); 41 | assertEquals(result.opts, { to: "a.css" }); 42 | }); 43 | 44 | Deno.test("contains warnings", () => { 45 | const result = new LazyResult(processor, "a {}", {}); 46 | assertEquals(result.warnings(), []); 47 | }); 48 | 49 | Deno.test("contains messages", () => { 50 | const result = new LazyResult(processor, "a {}", {}); 51 | assertEquals(result.messages, []); 52 | }); 53 | -------------------------------------------------------------------------------- /test/list.test.js: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "./deps.js"; 2 | import { list } from "../mod.js"; 3 | 4 | Deno.test("space() splits list by spaces", () => { 5 | assertEquals(list.space("a b"), ["a", "b"]); 6 | }); 7 | 8 | Deno.test("space() trims values", () => { 9 | assertEquals(list.space(" a b "), ["a", "b"]); 10 | }); 11 | 12 | Deno.test("space() checks quotes", () => { 13 | assertEquals(list.space('"a b\\"" \'\''), ['"a b\\""', "''"]); 14 | }); 15 | 16 | Deno.test("space() checks functions", () => { 17 | assertEquals(list.space("f( )) a( () )"), ["f( ))", "a( () )"]); 18 | }); 19 | 20 | Deno.test("space() works from variable", () => { 21 | const space = list.space; 22 | assertEquals(space("a b"), ["a", "b"]); 23 | }); 24 | 25 | Deno.test("comma() splits list by spaces", () => { 26 | assertEquals(list.comma("a, b"), ["a", "b"]); 27 | }); 28 | 29 | Deno.test("comma() adds last empty", () => { 30 | assertEquals(list.comma("a, b,"), ["a", "b", ""]); 31 | }); 32 | 33 | Deno.test("comma() checks quotes", () => { 34 | assertEquals(list.comma('"a,b\\"", \'\''), ['"a,b\\""', "''"]); 35 | }); 36 | 37 | Deno.test("comma() checks functions", () => { 38 | assertEquals(list.comma("f(,)), a(,(),)"), ["f(,))", "a(,(),)"]); 39 | }); 40 | 41 | Deno.test("comma() works from variable", () => { 42 | const comma = list.comma; 43 | assertEquals(comma("a, b"), ["a", "b"]); 44 | }); 45 | -------------------------------------------------------------------------------- /test/node.test.js: -------------------------------------------------------------------------------- 1 | import { assert, assertEquals } from "./deps.js"; 2 | import { resolve } from "../lib/deps.js"; 3 | 4 | import postcss, { 5 | AtRule, 6 | CssSyntaxError, 7 | Declaration, 8 | parse, 9 | Root, 10 | Rule, 11 | } from "../mod.js"; 12 | 13 | function stringify(node, builder) { 14 | if (node.type === "rule") { 15 | return builder(node.selector); 16 | } 17 | } 18 | 19 | Deno.test("error() generates custom error", () => { 20 | const file = resolve("a.css"); 21 | const css = parse("a{}", { from: file }); 22 | const a = css.first; 23 | const error = a.error("Test"); 24 | assert(error instanceof CssSyntaxError); 25 | assertEquals(error.message, file + ":1:1: Test"); 26 | }); 27 | 28 | Deno.test("error() generates custom error for nodes without source", () => { 29 | const rule = new Rule({ selector: "a" }); 30 | const error = rule.error("Test"); 31 | assertEquals(error.message, ": Test"); 32 | }); 33 | 34 | Deno.test("error() highlights index", () => { 35 | const root = parse("a { b: c }"); 36 | const a = root.first; 37 | const b = a.first; 38 | const error = b.error("Bad semicolon", { index: 1 }); 39 | assertEquals( 40 | error.showSourceCode(false), 41 | "> 1 | a { b: c }\n" + " | ^", 42 | ); 43 | }); 44 | 45 | Deno.test("error() highlights word", () => { 46 | const root = parse("a { color: x red }"); 47 | const a = root.first; 48 | const color = a.first; 49 | const error = color.error("Wrong color", { word: "x" }); 50 | assertEquals( 51 | error.showSourceCode(false), 52 | "> 1 | a { color: x red }\n" + " | ^", 53 | ); 54 | }); 55 | 56 | Deno.test("error() highlights word in multiline string", () => { 57 | const root = parse("a { color: red\n x }"); 58 | const a = root.first; 59 | const color = a.first; 60 | const error = color.error("Wrong color", { word: "x" }); 61 | assertEquals( 62 | error.showSourceCode(false), 63 | " 1 | a { color: red\n" + "> 2 | x }\n" + " | ^", 64 | ); 65 | }); 66 | 67 | Deno.test("warn() attaches a warning to the result object", async () => { 68 | let warning; 69 | const warner = { 70 | postcssPlugin: "warner", 71 | Once(css, { result }) { 72 | warning = css.first?.warn(result, "FIRST!"); 73 | }, 74 | }; 75 | 76 | const result = await postcss([warner]).process("a{}", { from: undefined }); 77 | assertEquals(warning.type, "warning"); 78 | assertEquals(warning.text, "FIRST!"); 79 | assertEquals(warning.plugin, "warner"); 80 | assertEquals(result.warnings(), [warning]); 81 | }); 82 | 83 | Deno.test("warn() accepts options", () => { 84 | const warner = (css, result) => { 85 | css.first?.warn(result, "FIRST!", { index: 1 }); 86 | }; 87 | 88 | const result = postcss([warner]).process("a{}"); 89 | assert(result.warnings().length === 1); 90 | const warning = result.warnings()[0]; 91 | assertEquals(warning.index, 1); 92 | }); 93 | 94 | Deno.test("remove() removes node from parent", () => { 95 | const rule = new Rule({ selector: "a" }); 96 | const decl = new Declaration({ prop: "color", value: "black" }); 97 | rule.append(decl); 98 | 99 | decl.remove(); 100 | assert(rule.nodes.length === 0); 101 | assert(typeof decl.parent === "undefined"); 102 | }); 103 | 104 | Deno.test("replaceWith() inserts new node", () => { 105 | const rule = new Rule({ selector: "a" }); 106 | rule.append({ prop: "color", value: "black" }); 107 | rule.append({ prop: "width", value: "1px" }); 108 | rule.append({ prop: "height", value: "1px" }); 109 | 110 | const node = new Declaration({ prop: "min-width", value: "1px" }); 111 | const width = rule.nodes[1]; 112 | const result = width.replaceWith(node); 113 | 114 | assertEquals(result, width); 115 | assertEquals( 116 | rule.toString(), 117 | "a {\n" + 118 | " color: black;\n" + 119 | " min-width: 1px;\n" + 120 | " height: 1px\n" + 121 | "}", 122 | ); 123 | }); 124 | 125 | Deno.test("replaceWith() inserts new root", () => { 126 | const root = new Root(); 127 | root.append(new AtRule({ name: "import", params: '"a.css"' })); 128 | 129 | const a = new Root(); 130 | a.append(new Rule({ selector: "a" })); 131 | a.append(new Rule({ selector: "b" })); 132 | 133 | root.first?.replaceWith(a); 134 | assertEquals(root.toString(), "a {}\nb {}"); 135 | }); 136 | 137 | Deno.test("replaceWith() replaces node", () => { 138 | const css = parse("a{one:1;two:2}"); 139 | const a = css.first; 140 | const one = a.first; 141 | const result = one.replaceWith({ prop: "fix", value: "fixed" }); 142 | 143 | assertEquals(result.prop, "one"); 144 | assert(typeof result.parent === "undefined"); 145 | assertEquals(css.toString(), "a{fix:fixed;two:2}"); 146 | }); 147 | 148 | Deno.test("replaceWith() can include itself", () => { 149 | const css = parse("a{one:1;two:2}"); 150 | const a = css.first; 151 | const one = a.first; 152 | const beforeDecl = { prop: "fix1", value: "fixedOne" }; 153 | const afterDecl = { prop: "fix2", value: "fixedTwo" }; 154 | one.replaceWith(beforeDecl, one, afterDecl); 155 | 156 | assertEquals(css.toString(), "a{fix1:fixedOne;one:1;fix2:fixedTwo;two:2}"); 157 | }); 158 | 159 | Deno.test("toString() accepts custom stringifier", () => { 160 | assertEquals(new Rule({ selector: "a" }).toString(stringify), "a"); 161 | }); 162 | 163 | Deno.test("toString() accepts custom syntax", () => { 164 | assertEquals(new Rule({ selector: "a" }).toString({ stringify }), "a"); 165 | }); 166 | 167 | Deno.test("clone() clones nodes", () => { 168 | const rule = new Rule({ selector: "a" }); 169 | rule.append({ prop: "color", value: "/**/black" }); 170 | 171 | const clone = rule.clone(); 172 | 173 | assert(typeof clone.parent === "undefined"); 174 | 175 | assert(rule.first?.parent === rule); 176 | assert(clone.first?.parent === clone); 177 | 178 | clone.append({ prop: "z-index", value: "1" }); 179 | assert(rule.nodes.length === 1); 180 | }); 181 | 182 | Deno.test("clone() overrides properties", () => { 183 | const rule = new Rule({ selector: "a" }); 184 | const clone = rule.clone({ selector: "b" }); 185 | assertEquals(clone.selector, "b"); 186 | }); 187 | 188 | Deno.test("clone() keeps code style", () => { 189 | const css = parse("@page 1{a{color:black;}}"); 190 | assertEquals(css.clone().toString(), "@page 1{a{color:black;}}"); 191 | }); 192 | 193 | Deno.test("clone() works with null in raws", () => { 194 | const decl = new Declaration({ 195 | prop: "color", 196 | value: "black", 197 | raws: { value: null }, 198 | }); 199 | const clone = decl.clone(); 200 | assertEquals(Object.keys(clone.raws), ["value"]); 201 | }); 202 | 203 | Deno.test("cloneBefore() clones and insert before current node", () => { 204 | const rule = new Rule({ selector: "a", raws: { after: "" } }); 205 | rule.append({ prop: "z-index", value: "1", raws: { before: "" } }); 206 | 207 | const result = rule.first?.cloneBefore({ value: "2" }); 208 | 209 | assert(result === rule.first); 210 | assertEquals(rule.toString(), "a {z-index: 2;z-index: 1}"); 211 | }); 212 | 213 | Deno.test("cloneAfter() clones and insert after current node", () => { 214 | const rule = new Rule({ selector: "a", raws: { after: "" } }); 215 | rule.append({ prop: "z-index", value: "1", raws: { before: "" } }); 216 | 217 | const result = rule.first?.cloneAfter({ value: "2" }); 218 | 219 | assert(result === rule.last); 220 | assertEquals(rule.toString(), "a {z-index: 1;z-index: 2}"); 221 | }); 222 | 223 | Deno.test("before() insert before current node", () => { 224 | const rule = new Rule({ selector: "a", raws: { after: "" } }); 225 | rule.append({ prop: "z-index", value: "1", raws: { before: "" } }); 226 | 227 | const result = rule.first?.before("color: black"); 228 | 229 | assert(result === rule.last); 230 | assertEquals(rule.toString(), "a {color: black;z-index: 1}"); 231 | }); 232 | 233 | Deno.test("after() insert after current node", () => { 234 | const rule = new Rule({ selector: "a", raws: { after: "" } }); 235 | rule.append({ prop: "z-index", value: "1", raws: { before: "" } }); 236 | 237 | const result = rule.first?.after("color: black"); 238 | 239 | assert(result === rule.first); 240 | assertEquals(rule.toString(), "a {z-index: 1;color: black}"); 241 | }); 242 | 243 | Deno.test("next() returns next node", () => { 244 | const css = parse("a{one:1;two:2}"); 245 | const a = css.first; 246 | assert(a.first?.next() === a.last); 247 | assert(typeof a.last?.next() === "undefined"); 248 | }); 249 | 250 | Deno.test("next() returns undefined on no parent", () => { 251 | const css = parse(""); 252 | assert(typeof css.next() === "undefined"); 253 | }); 254 | 255 | Deno.test("prev() returns previous node", () => { 256 | const css = parse("a{one:1;two:2}"); 257 | const a = css.first; 258 | assert(a.last?.prev() === a.first); 259 | assert(typeof a.first?.prev() === "undefined"); 260 | }); 261 | 262 | Deno.test("prev() returns undefined on no parent", () => { 263 | const css = parse(""); 264 | assert(typeof css.prev() === "undefined"); 265 | }); 266 | 267 | Deno.test("toJSON() cleans parents inside", () => { 268 | const rule = new Rule({ selector: "a" }); 269 | rule.append({ prop: "color", value: "b" }); 270 | 271 | const json = rule.toJSON(); 272 | assert(typeof json.parent === "undefined"); 273 | assert(typeof json.nodes[0].parent === "undefined"); 274 | 275 | assertEquals( 276 | JSON.stringify(rule), 277 | '{"raws":{},"selector":"a","type":"rule","nodes":[{"raws":{},"prop":"color","value":"b","type":"decl"}],"inputs":[]}', 278 | ); 279 | }); 280 | 281 | Deno.test("toJSON() converts custom properties", () => { 282 | const root = new Root(); 283 | root._cache = [1]; 284 | root._hack = { 285 | toJSON() { 286 | return "hack"; 287 | }, 288 | }; 289 | 290 | assertEquals(root.toJSON(), { 291 | type: "root", 292 | nodes: [], 293 | raws: {}, 294 | inputs: [], 295 | _hack: "hack", 296 | _cache: [1], 297 | }); 298 | }); 299 | 300 | Deno.test("raw() has shortcut to stringifier", () => { 301 | const rule = new Rule({ selector: "a" }); 302 | assertEquals(rule.raw("before"), ""); 303 | }); 304 | 305 | Deno.test("root() returns root", () => { 306 | const css = parse("@page{a{color:black}}"); 307 | const page = css.first; 308 | const a = page.first; 309 | const color = a.first; 310 | assert(color.root() === css); 311 | }); 312 | 313 | Deno.test("root() returns parent of parents", () => { 314 | const rule = new Rule({ selector: "a" }); 315 | rule.append({ prop: "color", value: "black" }); 316 | assert(rule.first?.root() === rule); 317 | }); 318 | 319 | Deno.test("root() returns self on root", () => { 320 | const rule = new Rule({ selector: "a" }); 321 | assert(rule.root() === rule); 322 | }); 323 | 324 | Deno.test("cleanRaws() cleans style recursivelly", () => { 325 | const css = parse("@page{a{color:black}}"); 326 | css.cleanRaws(); 327 | 328 | assertEquals( 329 | css.toString(), 330 | "@page {\n a {\n color: black\n }\n}", 331 | ); 332 | const page = css.first; 333 | const a = page.first; 334 | const color = a.first; 335 | assert(typeof page.raws.before === "undefined"); 336 | assert(typeof color.raws.before === "undefined"); 337 | assert(typeof page.raws.between === "undefined"); 338 | assert(typeof color.raws.between === "undefined"); 339 | assert(typeof page.raws.after === "undefined"); 340 | }); 341 | 342 | Deno.test("cleanRaws() keeps between on request", () => { 343 | const css = parse("@page{a{color:black}}"); 344 | css.cleanRaws(true); 345 | 346 | assertEquals(css.toString(), "@page{\n a{\n color:black\n }\n}"); 347 | const page = css.first; 348 | const a = page.first; 349 | const color = a.first; 350 | assert(typeof page.raws.between !== "undefined"); 351 | assert(typeof color.raws.between !== "undefined"); 352 | assert(typeof page.raws.before === "undefined"); 353 | assert(typeof color.raws.before === "undefined"); 354 | assert(typeof page.raws.after === "undefined"); 355 | }); 356 | 357 | Deno.test("positionInside() returns position when node starts mid-line", () => { 358 | const css = parse("a { one: X }"); 359 | const a = css.first; 360 | const one = a.first; 361 | assertEquals(one.positionInside(6), { line: 1, column: 12 }); 362 | }); 363 | 364 | Deno.test("positionInside() returns position when before contains newline", () => { 365 | const css = parse("a {\n one: X}"); 366 | const a = css.first; 367 | const one = a.first; 368 | assertEquals(one.positionInside(6), { line: 2, column: 9 }); 369 | }); 370 | 371 | Deno.test("positionInside() returns position when node contains newlines", () => { 372 | const css = parse("a {\n\tone: 1\n\t\tX\n3}"); 373 | const a = css.first; 374 | const one = a.first; 375 | assertEquals(one.positionInside(10), { line: 3, column: 4 }); 376 | }); 377 | -------------------------------------------------------------------------------- /test/result.test.js: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "./deps.js"; 2 | import postcss, { Result, Root, Warning } from "../mod.js"; 3 | import Processor from "../lib/processor.js"; 4 | 5 | const processor = new Processor(); 6 | const root = new Root(); 7 | 8 | Deno.test("stringifies", () => { 9 | const result = new Result(processor, root, {}); 10 | result.css = "a{}"; 11 | assertEquals(`${result}`, result.css); 12 | }); 13 | 14 | Deno.test("adds warning", () => { 15 | let warning; 16 | const plugin = { 17 | postcssPlugin: "test-plugin", 18 | Once(css, { result }) { 19 | warning = result.warn("test", { node: css.first }); 20 | }, 21 | }; 22 | const result = postcss([plugin]).process("a{}").sync(); 23 | 24 | assertEquals( 25 | warning, 26 | new Warning("test", { 27 | plugin: "test-plugin", 28 | node: result.root.first, 29 | }), 30 | ); 31 | 32 | assertEquals(result.messages, [warning]); 33 | }); 34 | 35 | Deno.test("allows to override plugin", () => { 36 | const plugin = { 37 | postcssPlugin: "test-plugin", 38 | Once(_css, { result }) { 39 | result.warn("test", { plugin: "test-plugin#one" }); 40 | }, 41 | }; 42 | const result = postcss([plugin]).process("a{}").sync(); 43 | 44 | assertEquals(result.messages[0].plugin, "test-plugin#one"); 45 | }); 46 | 47 | Deno.test("allows Root", () => { 48 | const css = postcss.parse("a{}"); 49 | const result = new Result(processor, css, {}); 50 | result.warn("TT", { node: css.first }); 51 | 52 | assertEquals(result.messages[0].toString(), ":1:1: TT"); 53 | }); 54 | 55 | Deno.test("returns only warnings", () => { 56 | const result = new Result(processor, root, {}); 57 | result.messages = [ 58 | { type: "warning", text: "a" }, 59 | { type: "custom" }, 60 | { type: "warning", text: "b" }, 61 | ]; 62 | assertEquals(result.warnings(), [ 63 | { type: "warning", text: "a" }, 64 | { type: "warning", text: "b" }, 65 | ]); 66 | }); 67 | -------------------------------------------------------------------------------- /test/root.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, assertEquals, assertMatch } from "./deps.js"; 2 | import { parse, Result } from "../mod.js"; 3 | 4 | Deno.test("prepend() fixes spaces on insert before first", () => { 5 | const css = parse("a {} b {}"); 6 | css.prepend({ selector: "em" }); 7 | assertEquals(css.toString(), "em {} a {} b {}"); 8 | }); 9 | 10 | Deno.test("prepend() fixes spaces on multiple inserts before first", () => { 11 | const css = parse("a {} b {}"); 12 | css.prepend({ selector: "em" }, { selector: "strong" }); 13 | assertEquals(css.toString(), "em {} strong {} a {} b {}"); 14 | }); 15 | 16 | Deno.test("prepend() uses default spaces on only first", () => { 17 | const css = parse("a {}"); 18 | css.prepend({ selector: "em" }); 19 | assertEquals(css.toString(), "em {}\na {}"); 20 | }); 21 | 22 | Deno.test("append() sets new line between rules in multiline files", () => { 23 | const a = parse("a {}\n\na {}\n"); 24 | const b = parse("b {}\n"); 25 | assertEquals(a.append(b).toString(), "a {}\n\na {}\n\nb {}\n"); 26 | }); 27 | 28 | Deno.test("insertAfter() does not use before of first rule", () => { 29 | const css = parse("a{} b{}"); 30 | css.insertAfter(0, { selector: ".a" }); 31 | css.insertAfter(2, { selector: ".b" }); 32 | 33 | assert(typeof css.nodes[1].raws.before === "undefined"); 34 | assertEquals(css.nodes[3].raws.before, " "); 35 | assertEquals(css.toString(), "a{} .a{} b{} .b{}"); 36 | }); 37 | 38 | Deno.test("keeps spaces on moving root", () => { 39 | const css1 = parse("a{}\nb{}\n"); 40 | 41 | const css2 = parse(""); 42 | css2.append(css1); 43 | assertEquals(css2.toString(), "a{}\nb{}"); 44 | 45 | const css3 = parse("\n"); 46 | css3.append(css2.nodes); 47 | assertEquals(css3.toString(), "a{}\nb{}\n"); 48 | }); 49 | 50 | Deno.test("generates result with map", () => { 51 | const root = parse("a {}"); 52 | const result = root.toResult({ map: true }); 53 | 54 | assert(result instanceof Result); 55 | assertMatch(result.css, /a {}\n\/\*# sourceMappingURL=/); 56 | }); 57 | -------------------------------------------------------------------------------- /test/rule.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "./deps.js"; 2 | import { parse, Rule } from "../mod.js"; 3 | 4 | Deno.test("initializes with properties", () => { 5 | const rule = new Rule({ selector: "a" }); 6 | assertEquals(rule.selector, "a"); 7 | }); 8 | 9 | Deno.test("returns array in selectors", () => { 10 | const rule = new Rule({ selector: "a,b" }); 11 | assertEquals(rule.selectors, ["a", "b"]); 12 | }); 13 | 14 | Deno.test("trims selectors", () => { 15 | const rule = new Rule({ selector: ".a\n, .b , .c" }); 16 | assertEquals(rule.selectors, [".a", ".b", ".c"]); 17 | }); 18 | 19 | Deno.test("is smart about selectors commas", () => { 20 | const rule = new Rule({ 21 | selector: "[foo='a, b'], a:-moz-any(:focus, [href*=','])", 22 | }); 23 | assertEquals(rule.selectors, [ 24 | "[foo='a, b']", 25 | "a:-moz-any(:focus, [href*=','])", 26 | ]); 27 | }); 28 | 29 | Deno.test("receive array in selectors", () => { 30 | const rule = new Rule({ selector: "i, b" }); 31 | rule.selectors = ["em", "strong"]; 32 | assertEquals(rule.selector, "em, strong"); 33 | }); 34 | 35 | Deno.test("saves separator in selectors", () => { 36 | const rule = new Rule({ selector: "i,\nb" }); 37 | rule.selectors = ["em", "strong"]; 38 | assertEquals(rule.selector, "em,\nstrong"); 39 | }); 40 | 41 | Deno.test("uses between to detect separator in selectors", () => { 42 | const rule = new Rule({ selector: "b", raws: { between: "" } }); 43 | rule.selectors = ["b", "strong"]; 44 | assertEquals(rule.selector, "b,strong"); 45 | }); 46 | 47 | Deno.test("uses space in separator be default in selectors", () => { 48 | const rule = new Rule({ selector: "b" }); 49 | rule.selectors = ["b", "strong"]; 50 | assertEquals(rule.selector, "b, strong"); 51 | }); 52 | 53 | Deno.test("selectors works in constructor", () => { 54 | const rule = new Rule({ selectors: ["a", "b"] }); 55 | assertEquals(rule.selector, "a, b"); 56 | }); 57 | 58 | Deno.test("inserts default spaces", () => { 59 | const rule = new Rule({ selector: "a" }); 60 | assertEquals(rule.toString(), "a {}"); 61 | rule.append({ prop: "color", value: "black" }); 62 | assertEquals(rule.toString(), "a {\n color: black\n}"); 63 | }); 64 | 65 | Deno.test("clones spaces from another rule", () => { 66 | const root = parse("b{\n }"); 67 | const rule = new Rule({ selector: "em" }); 68 | root.append(rule); 69 | assertEquals(root.toString(), "b{\n }\nem{\n }"); 70 | }); 71 | 72 | Deno.test("uses different spaces for empty rules", () => { 73 | const root = parse("a{}\nb{\n a:1\n}"); 74 | const rule = new Rule({ selector: "em" }); 75 | root.append(rule); 76 | assertEquals(root.toString(), "a{}\nb{\n a:1\n}\nem{}"); 77 | 78 | rule.append({ prop: "top", value: "0" }); 79 | assertEquals(root.toString(), "a{}\nb{\n a:1\n}\nem{\n top:0\n}"); 80 | }); 81 | -------------------------------------------------------------------------------- /test/tokenize.test.js: -------------------------------------------------------------------------------- 1 | import { assertEquals, assertThrows } from "./deps.js"; 2 | import tokenizer from "../lib/tokenize.js"; 3 | import { Input } from "../mod.js"; 4 | 5 | function tokenize(css, opts) { 6 | const processor = tokenizer(new Input(css), opts); 7 | const tokens = []; 8 | while (!processor.endOfFile()) { 9 | tokens.push(processor.nextToken()); 10 | } 11 | return tokens; 12 | } 13 | 14 | function run(css, tokens, opts) { 15 | assertEquals(tokenize(css, opts), tokens); 16 | } 17 | 18 | Deno.test("tokenizes empty file", () => { 19 | run("", []); 20 | }); 21 | 22 | Deno.test("tokenizes space", () => { 23 | run("\r\n \f\t", [["space", "\r\n \f\t"]]); 24 | }); 25 | 26 | Deno.test("tokenizes word", () => { 27 | run("ab", [["word", "ab", 0, 1]]); 28 | }); 29 | 30 | Deno.test("splits word by !", () => { 31 | run("aa!bb", [ 32 | ["word", "aa", 0, 1], 33 | ["word", "!bb", 2, 4], 34 | ]); 35 | }); 36 | 37 | Deno.test("changes lines in spaces", () => { 38 | run("a \n b", [ 39 | ["word", "a", 0, 0], 40 | ["space", " \n "], 41 | ["word", "b", 4, 4], 42 | ]); 43 | }); 44 | 45 | Deno.test("tokenizes control chars", () => { 46 | run("{:;}", [ 47 | ["{", "{", 0], 48 | [":", ":", 1], 49 | [";", ";", 2], 50 | ["}", "}", 3], 51 | ]); 52 | }); 53 | 54 | Deno.test("escapes control symbols", () => { 55 | run('\\(\\{\\"\\@\\\\""', [ 56 | ["word", "\\(", 0, 1], 57 | ["word", "\\{", 2, 3], 58 | ["word", '\\"', 4, 5], 59 | ["word", "\\@", 6, 7], 60 | ["word", "\\\\", 8, 9], 61 | ["string", '""', 10, 11], 62 | ]); 63 | }); 64 | 65 | Deno.test("escapes backslash", () => { 66 | run("\\\\\\\\{", [ 67 | ["word", "\\\\\\\\", 0, 3], 68 | ["{", "{", 4], 69 | ]); 70 | }); 71 | 72 | Deno.test("tokenizes simple brackets", () => { 73 | run("(ab)", [["brackets", "(ab)", 0, 3]]); 74 | }); 75 | 76 | Deno.test("tokenizes square brackets", () => { 77 | run("a[bc]", [ 78 | ["word", "a", 0, 0], 79 | ["[", "[", 1], 80 | ["word", "bc", 2, 3], 81 | ["]", "]", 4], 82 | ]); 83 | }); 84 | 85 | Deno.test("tokenizes complicated brackets", () => { 86 | run('(())("")(/**/)(\\\\)(\n)(', [ 87 | ["(", "(", 0], 88 | ["brackets", "()", 1, 2], 89 | [")", ")", 3], 90 | ["(", "(", 4], 91 | ["string", '""', 5, 6], 92 | [")", ")", 7], 93 | ["(", "(", 8], 94 | ["comment", "/**/", 9, 12], 95 | [")", ")", 13], 96 | ["(", "(", 14], 97 | ["word", "\\\\", 15, 16], 98 | [")", ")", 17], 99 | ["(", "(", 18], 100 | ["space", "\n"], 101 | [")", ")", 20], 102 | ["(", "(", 21], 103 | ]); 104 | }); 105 | 106 | Deno.test("tokenizes string", () => { 107 | run('\'"\'"\\""', [ 108 | ["string", "'\"'", 0, 2], 109 | ["string", '"\\""', 3, 6], 110 | ]); 111 | }); 112 | 113 | Deno.test("tokenizes escaped string", () => { 114 | run('"\\\\"', [["string", '"\\\\"', 0, 3]]); 115 | }); 116 | 117 | Deno.test("changes lines in strings", () => { 118 | run('"\n\n""\n\n"', [ 119 | ["string", '"\n\n"', 0, 3], 120 | ["string", '"\n\n"', 4, 7], 121 | ]); 122 | }); 123 | 124 | Deno.test("tokenizes at-word", () => { 125 | run("@word ", [ 126 | ["at-word", "@word", 0, 4], 127 | ["space", " "], 128 | ]); 129 | }); 130 | 131 | Deno.test("tokenizes at-word end", () => { 132 | run('@one{@two()@three""@four;', [ 133 | ["at-word", "@one", 0, 3], 134 | ["{", "{", 4], 135 | ["at-word", "@two", 5, 8], 136 | ["brackets", "()", 9, 10], 137 | ["at-word", "@three", 11, 16], 138 | ["string", '""', 17, 18], 139 | ["at-word", "@four", 19, 23], 140 | [";", ";", 24], 141 | ]); 142 | }); 143 | 144 | Deno.test("tokenizes urls", () => { 145 | run("url(/*\\))", [ 146 | ["word", "url", 0, 2], 147 | ["brackets", "(/*\\))", 3, 8], 148 | ]); 149 | }); 150 | 151 | Deno.test("tokenizes quoted urls", () => { 152 | run('url(")")', [ 153 | ["word", "url", 0, 2], 154 | ["(", "(", 3], 155 | ["string", '")"', 4, 6], 156 | [")", ")", 7], 157 | ]); 158 | }); 159 | 160 | Deno.test("tokenizes at-symbol", () => { 161 | run("@", [["at-word", "@", 0, 0]]); 162 | }); 163 | 164 | Deno.test("tokenizes comment", () => { 165 | run("/* a\nb */", [["comment", "/* a\nb */", 0, 8]]); 166 | }); 167 | 168 | Deno.test("changes lines in comments", () => { 169 | run("a/* \n */b", [ 170 | ["word", "a", 0, 0], 171 | ["comment", "/* \n */", 1, 7], 172 | ["word", "b", 8, 8], 173 | ]); 174 | }); 175 | 176 | Deno.test("supports line feed", () => { 177 | run("a\fb", [ 178 | ["word", "a", 0, 0], 179 | ["space", "\f"], 180 | ["word", "b", 2, 2], 181 | ]); 182 | }); 183 | 184 | Deno.test("supports carriage return", () => { 185 | run("a\rb\r\nc", [ 186 | ["word", "a", 0, 0], 187 | ["space", "\r"], 188 | ["word", "b", 2, 2], 189 | ["space", "\r\n"], 190 | ["word", "c", 5, 5], 191 | ]); 192 | }); 193 | 194 | Deno.test("tokenizes CSS", () => { 195 | const css = "a {\n" + 196 | ' content: "a";\n' + 197 | " width: calc(1px;)\n" + 198 | " }\n" + 199 | "/* small screen */\n" + 200 | "@media screen {}"; 201 | run(css, [ 202 | ["word", "a", 0, 0], 203 | ["space", " "], 204 | ["{", "{", 2], 205 | ["space", "\n "], 206 | ["word", "content", 6, 12], 207 | [":", ":", 13], 208 | ["space", " "], 209 | ["string", '"a"', 15, 17], 210 | [";", ";", 18], 211 | ["space", "\n "], 212 | ["word", "width", 22, 26], 213 | [":", ":", 27], 214 | ["space", " "], 215 | ["word", "calc", 29, 32], 216 | ["brackets", "(1px;)", 33, 38], 217 | ["space", "\n "], 218 | ["}", "}", 42], 219 | ["space", "\n"], 220 | ["comment", "/* small screen */", 44, 61], 221 | ["space", "\n"], 222 | ["at-word", "@media", 63, 68], 223 | ["space", " "], 224 | ["word", "screen", 70, 75], 225 | ["space", " "], 226 | ["{", "{", 77], 227 | ["}", "}", 78], 228 | ]); 229 | }); 230 | 231 | Deno.test("throws error on unclosed string", () => { 232 | assertThrows(() => { 233 | tokenize(' "'); 234 | }); 235 | }); 236 | 237 | Deno.test("throws error on unclosed comment", () => { 238 | assertThrows(() => { 239 | tokenize(" /*"); 240 | }); 241 | }); 242 | 243 | Deno.test("throws error on unclosed url", () => { 244 | assertThrows(() => { 245 | tokenize("url("); 246 | }); 247 | }); 248 | 249 | Deno.test("ignores unclosing string on request", () => { 250 | run( 251 | ' "', 252 | [ 253 | ["space", " "], 254 | ["string", '"', 1, 2], 255 | ], 256 | { ignoreErrors: true }, 257 | ); 258 | }); 259 | 260 | Deno.test("ignores unclosing comment on request", () => { 261 | run( 262 | " /*", 263 | [ 264 | ["space", " "], 265 | ["comment", "/*", 1, 3], 266 | ], 267 | { ignoreErrors: true }, 268 | ); 269 | }); 270 | 271 | Deno.test("ignores unclosing function on request", () => { 272 | run( 273 | "url(", 274 | [ 275 | ["word", "url", 0, 2], 276 | ["brackets", "(", 3, 3], 277 | ], 278 | { ignoreErrors: true }, 279 | ); 280 | }); 281 | 282 | Deno.test("tokenizes hexadecimal escape", () => { 283 | run("\\0a \\09 \\z ", [ 284 | ["word", "\\0a ", 0, 3], 285 | ["word", "\\09 ", 4, 7], 286 | ["word", "\\z", 8, 9], 287 | ["space", " "], 288 | ]); 289 | }); 290 | 291 | Deno.test("ignore unclosed per token request", () => { 292 | function tokn(css, opts) { 293 | const processor = tokenizer(new Input(css), opts); 294 | const tokens = []; 295 | while (!processor.endOfFile()) { 296 | tokens.push(processor.nextToken({ ignoreUnclosed: true })); 297 | } 298 | return tokens; 299 | } 300 | 301 | const css = "How's it going ("; 302 | const tokens = tokn(css, {}); 303 | const expected = [ 304 | ["word", "How", 0, 2], 305 | ["string", "'s", 3, 4], 306 | ["space", " "], 307 | ["word", "it", 6, 7], 308 | ["space", " "], 309 | ["word", "going", 9, 13], 310 | ["space", " "], 311 | ["(", "(", 15], 312 | ]; 313 | 314 | assertEquals(tokens, expected); 315 | }); 316 | 317 | Deno.test("provides correct position", () => { 318 | const css = "Three tokens"; 319 | const processor = tokenizer(new Input(css)); 320 | assertEquals(processor.position(), 0); 321 | processor.nextToken(); 322 | assertEquals(processor.position(), 5); 323 | processor.nextToken(); 324 | assertEquals(processor.position(), 6); 325 | processor.nextToken(); 326 | assertEquals(processor.position(), 12); 327 | processor.nextToken(); 328 | assertEquals(processor.position(), 12); 329 | }); 330 | -------------------------------------------------------------------------------- /test/warning.test.js: -------------------------------------------------------------------------------- 1 | import { assert, assertEquals } from "./deps.js"; 2 | import { resolve } from "../lib/deps.js"; 3 | import { decl, parse, Warning } from "../mod.js"; 4 | 5 | Deno.test("outputs simple warning", () => { 6 | const warning = new Warning("text"); 7 | assertEquals(warning.toString(), "text"); 8 | }); 9 | 10 | Deno.test("outputs warning with plugin", () => { 11 | const warning = new Warning("text", { plugin: "plugin" }); 12 | assertEquals(warning.toString(), "plugin: text"); 13 | }); 14 | 15 | Deno.test("outputs warning with position", () => { 16 | const root = parse("a{}"); 17 | const warning = new Warning("text", { node: root.first }); 18 | assertEquals(warning.toString(), ":1:1: text"); 19 | }); 20 | 21 | Deno.test("outputs warning with plugin and node", () => { 22 | const file = resolve("a.css"); 23 | const root = parse("a{}", { from: file }); 24 | const warning = new Warning("text", { 25 | plugin: "plugin", 26 | node: root.first, 27 | }); 28 | assertEquals(warning.toString(), `plugin: ${file}:1:1: text`); 29 | }); 30 | 31 | Deno.test("outputs warning with index", () => { 32 | const file = resolve("a.css"); 33 | const root = parse("@rule param {}", { from: file }); 34 | const warning = new Warning("text", { 35 | plugin: "plugin", 36 | node: root.first, 37 | index: 7, 38 | }); 39 | assertEquals(warning.toString(), `plugin: ${file}:1:8: text`); 40 | }); 41 | 42 | Deno.test("outputs warning with word", () => { 43 | const file = resolve("a.css"); 44 | const root = parse("@rule param {}", { from: file }); 45 | const warning = new Warning("text", { 46 | plugin: "plugin", 47 | node: root.first, 48 | word: "am", 49 | }); 50 | assertEquals(warning.toString(), `plugin: ${file}:1:10: text`); 51 | }); 52 | 53 | Deno.test("generates warning without source", () => { 54 | const node = decl({ prop: "color", value: "black" }); 55 | const warning = new Warning("text", { node }); 56 | assertEquals(warning.toString(), ": text"); 57 | }); 58 | 59 | Deno.test("has line and column is undefined by default", () => { 60 | const warning = new Warning("text"); 61 | assert(typeof warning.line === "undefined"); 62 | assert(typeof warning.column === "undefined"); 63 | }); 64 | 65 | Deno.test("gets position from node", () => { 66 | const root = parse("a{}"); 67 | const warning = new Warning("text", { node: root.first }); 68 | assertEquals(warning.line, 1); 69 | assertEquals(warning.column, 1); 70 | }); 71 | 72 | Deno.test("gets position from word", () => { 73 | const root = parse("a b{}"); 74 | const warning = new Warning("text", { node: root.first, word: "b" }); 75 | assertEquals(warning.line, 1); 76 | assertEquals(warning.column, 3); 77 | }); 78 | 79 | Deno.test("gets position from index", () => { 80 | const root = parse("a b{}"); 81 | const warning = new Warning("text", { node: root.first, index: 2 }); 82 | assertEquals(warning.line, 1); 83 | assertEquals(warning.column, 3); 84 | }); 85 | -------------------------------------------------------------------------------- /to_deno.js: -------------------------------------------------------------------------------- 1 | import { convert } from "https://deno.land/x/nodedeno@v0.2.8/mod.js"; 2 | 3 | // Convert the code 4 | await convert({ 5 | src: "postcss", 6 | input: ["lib"], 7 | output: "deno", 8 | transpile: false, 9 | modules: { 10 | "": "mod.js", 11 | "deps.js": "lib/deps.js", 12 | "source-map-js": "lib/source_map.ts", 13 | }, 14 | copy: { 15 | "source_map.ts": "lib/source_map.ts", 16 | "deps.js": "lib/deps.js", 17 | "test": "test", 18 | "postcss/README.md": "README.md", 19 | "postcss/CHANGELOG.md": "CHANGELOG.md", 20 | "postcss/LICENSE": "LICENSE", 21 | }, 22 | beforeConvert(_src, { replaceAll, rename }) { 23 | // Rename lib/postcss.mjs => mod.js 24 | rename( 25 | "lib/postcss.mjs", 26 | "mod.js", 27 | (code) => code.replace(`'./postcss.js'`, `"./lib/postcss.js"`), 28 | ); 29 | }, 30 | afterConvert(_src, { replaceAll }) { 31 | replaceAll((code) => 32 | code.replaceAll('Deno.env.get("NODE_ENV")', 'Deno.env.get("DENO_ENV")') 33 | ); 34 | }, 35 | }); 36 | --------------------------------------------------------------------------------