pax_global_header00006660000000000000000000000064150240213210014500gustar00rootroot0000000000000052 comment=91d6eb5c3d1ca8acb4e8e3926005acf2b066c211 postcss-8.5.6/000077500000000000000000000000001502402132100132165ustar00rootroot00000000000000postcss-8.5.6/.editorconfig000066400000000000000000000002231502402132100156700ustar00rootroot00000000000000root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true postcss-8.5.6/.github/000077500000000000000000000000001502402132100145565ustar00rootroot00000000000000postcss-8.5.6/.github/CONTRIBUTING.md000066400000000000000000000042421502402132100170110ustar00rootroot00000000000000# Contributing Guide to PostCSS If you want to contribute to PostCSS, there are a few things that you should be familiar with. ## Adding Your Plugin to the List If you created or found a plugin and want to add it to the PostCSS plugins list follow these simple steps: PR should not change plugins defined in README — it only contains favorite plugins moderated by the PostCSS author. Plugins submitted by the community are located in [`docs/plugins`]. * **Keep plugins ordered** Be sure that a plugin is not already present and find a suitable position for it in alphabetical order. However plugins with `postcss-` prefix should come first. * **Check spelling** Before submitting a PR make sure the spelling check is passing. To run the check use `npm test`. If it fails with an unknown word error, add it as a word to `.yaspellerrc` dictionary. * **Check PostCSS plugin guidelines** The suggested plugin should match plugin [guidelines]. - **Provide link to suggested plugin** Make sure your pull request description contains a link to the plugin you want to add. [`docs/plugins`]: https://github.com/postcss/postcss/blob/main/docs/plugins.md [guidelines]: https://github.com/postcss/postcss/blob/main/docs/guidelines/plugin.md ## TypeScript Declaration Improvements If you found a bug or want to add certain improvements to types declaration file: * **Check current TypeScript styling** Be sure that your changes match TypeScript styling rules defined in typings file. * We use classes for existing JS classes like `Stringifier`. * Namespaces used for separating functions related to the same subject. * Interfaces used for defining custom types. Make sure you read through declaration file writing [best practices] by the TypeScript team. [best practices]: https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html ## Core Development If you want to add new features or fix existing issues - **Become familiar with PostCSS architecture** For a gentle intro to PostCSS architecture look through our [guide]. [guide]: https://github.com/postcss/postcss/blob/main/docs/architecture.md postcss-8.5.6/.github/FUNDING.yml000066400000000000000000000000721502402132100163720ustar00rootroot00000000000000open_collective: postcss tidelift: npm/postcss github: ai postcss-8.5.6/.github/workflows/000077500000000000000000000000001502402132100166135ustar00rootroot00000000000000postcss-8.5.6/.github/workflows/release.yml000066400000000000000000000025511502402132100207610ustar00rootroot00000000000000name: Release on: push: tags: - '*' permissions: contents: write jobs: release: name: Release On Tag if: startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-latest steps: - name: Checkout the repository uses: actions/checkout@v4 - name: Extract the changelog id: changelog run: | TAG_NAME=${GITHUB_REF/refs\/tags\//} READ_SECTION=false CHANGELOG="" while IFS= read -r line; do if [[ "$line" =~ ^#+\ +(.*) ]]; then if [[ "${BASH_REMATCH[1]}" == "$TAG_NAME" ]]; then READ_SECTION=true elif [[ "$READ_SECTION" == true ]]; then break fi elif [[ "$READ_SECTION" == true ]]; then CHANGELOG+="$line"$'\n' fi done < "CHANGELOG.md" CHANGELOG=$(echo "$CHANGELOG" | awk '/./ {$1=$1;print}') echo "changelog_content<> $GITHUB_OUTPUT echo "$CHANGELOG" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - name: Create the release if: steps.changelog.outputs.changelog_content != '' uses: softprops/action-gh-release@v2 with: name: ${{ github.ref_name }} body: '${{ steps.changelog.outputs.changelog_content }}' draft: false prerelease: false postcss-8.5.6/.github/workflows/test.yml000066400000000000000000000050371502402132100203220ustar00rootroot00000000000000name: Test on: push: branches: - main pull_request: permissions: contents: read jobs: full: name: Node.js Latest Full runs-on: ubuntu-latest steps: - name: Checkout the repository uses: actions/checkout@v4 - name: Install pnpm uses: pnpm/action-setup@v4 with: version: 10 - name: Install Node.js uses: actions/setup-node@v4 with: node-version: 23 cache: pnpm - name: Install dependencies run: pnpm install --ignore-scripts - name: Run tests run: pnpm test short: runs-on: ubuntu-latest strategy: matrix: node-version: - 22 - 20 - 18 name: Node.js ${{ matrix.node-version }} Quick steps: - name: Checkout the repository uses: actions/checkout@v4 - name: Install pnpm uses: pnpm/action-setup@v4 with: version: 10 - name: Install Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: pnpm - name: Install dependencies run: pnpm install --ignore-scripts - name: Run unit tests run: pnpm run unit old: runs-on: ubuntu-latest strategy: matrix: node-version: - 16 - 14 - 12 - 10 name: Node.js ${{ matrix.node-version }} Quick steps: - name: Checkout the repository uses: actions/checkout@v4 - name: Install pnpm uses: pnpm/action-setup@v4 with: version: 3 env: ACTIONS_ALLOW_UNSECURE_COMMANDS: true - name: Install Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - name: Install dependencies run: pnpm install --ignore-scripts - name: Downgrade TypeScript run: pnpm install typescript@4 - name: Run unit tests run: pnpm run old windows: runs-on: windows-latest name: Windows Quick steps: - name: Checkout the repository uses: actions/checkout@v4 - name: Install pnpm uses: pnpm/action-setup@v4 with: version: 10 - name: Install Node.js LTS uses: actions/setup-node@v4 with: node-version: 22 cache: pnpm - name: Install dependencies run: pnpm install --ignore-scripts - name: Run unit tests run: pnpm run unit postcss-8.5.6/.gitignore000066400000000000000000000000771502402132100152120ustar00rootroot00000000000000node_modules/ coverage/ docs/api/index.html docs/api/assets/ postcss-8.5.6/.npmignore000066400000000000000000000000671502402132100152200ustar00rootroot00000000000000coverage/ test/ docs/ tsconfig.json eslint.config.mjs postcss-8.5.6/CHANGELOG.md000066400000000000000000000753311502402132100150400ustar00rootroot00000000000000# Change Log This project adheres to [Semantic Versioning](https://semver.org/). ## 8.5.6 * Fixed `ContainerWithChildren` type discriminating (by @Goodwine). ## 8.5.5 * Fixed `package.json`→`exports` compatibility with some tools (by @JounQin). ## 8.5.4 * Fixed Parcel compatibility issue (by @git-sumitchaudhary). ## 8.5.3 * Added more details to `Unknown word` error (by @hiepxanh). * Fixed types (by @romainmenke). * Fixed docs (by @catnipan). ## 8.5.2 * Fixed end position of rules with semicolon (by @romainmenke). ## 8.5.1 * Fixed backwards compatibility for complex cases (by @romainmenke). ## 8.5 “Duke Alloces” * Added `Input#document` for sources like CSS-in-JS or HTML (by @romainmenke). ## 8.4.49 * Fixed custom syntax without `source.offset` (by @romainmenke). ## 8.4.48 * Fixed position calculation in error/warnings methods (by @romainmenke). ## 8.4.47 * Removed debug code. ## 8.4.46 * Fixed `Cannot read properties of undefined (reading 'before')`. ## 8.4.45 * Removed unnecessary fix which could lead to infinite loop. ## 8.4.44 * Another way to fix `markClean is not a function` error. ## 8.4.43 * Fixed `markClean is not a function` error. ## 8.4.42 * Fixed CSS syntax error on long minified files (by @varpstar). ## 8.4.41 * Fixed types (by @nex3 and @querkmachine). * Cleaned up RegExps (by @bluwy). ## 8.4.40 * Moved to getter/setter in nodes types to help Sass team (by @nex3). ## 8.4.39 * Fixed `CssSyntaxError` types (by @romainmenke). ## 8.4.38 * Fixed `endIndex: 0` in errors and warnings (by @romainmenke). ## 8.4.37 * Fixed `original.column are not numbers` error in another case. ## 8.4.36 * Fixed `original.column are not numbers` error on broken previous source map. ## 8.4.35 * Avoid `!` in `node.parent.nodes` type. * Allow to pass `undefined` to node adding method to simplify types. ## 8.4.34 * Fixed `AtRule#nodes` type (by Tim Weißenfels). * Cleaned up code (by Dmitry Kirillov). ## 8.4.33 * Fixed `NoWorkResult` behavior difference with normal mode (by Romain Menke). * Fixed `NoWorkResult` usage conditions (by @ahmdammarr). ## 8.4.32 * Fixed `postcss().process()` types (by Andrew Ferreira). ## 8.4.31 * Fixed `\r` parsing to fix CVE-2023-44270. ## 8.4.30 * Improved source map performance (by Romain Menke). ## 8.4.29 * Fixed `Node#source.offset` (by Ido Rosenthal). * Fixed docs (by Christian Oliff). ## 8.4.28 * Fixed `Root.source.end` for better source map (by Romain Menke). * Fixed `Result.root` types when `process()` has no parser. ## 8.4.27 * Fixed `Container` clone methods types. ## 8.4.26 * Fixed clone methods types. ## 8.4.25 * Improve stringify performance (by Romain Menke). * Fixed docs (by @vikaskaliramna07). ## 8.4.24 * Fixed `Plugin` types. ## 8.4.23 * Fixed warnings in TypeDoc. ## 8.4.22 * Fixed TypeScript support with `node16` (by Remco Haszing). ## 8.4.21 * Fixed `Input#error` types (by Aleks Hudochenkov). ## 8.4.20 * Fixed source map generation for childless at-rules like `@layer`. ## 8.4.19 * Fixed whitespace preserving after AST transformations (by Romain Menke). ## 8.4.18 * Fixed an error on `absolute: true` with empty `sourceContent` (by Rene Haas). ## 8.4.17 * Fixed `Node.before()` unexpected behavior (by Romain Menke). * Added TOC to docs (by Mikhail Dedov). ## 8.4.16 * Fixed `Root` AST migration. ## 8.4.15 * Fixed AST normalization after using custom parser with old PostCSS AST. ## 8.4.14 * Print “old plugin API” warning only if plugin was used (by @zardoy). ## 8.4.13 * Fixed `append()` error after using `.parent` (by Jordan Pittman). ## 8.4.12 * Fixed `package.funding` to have same value between all PostCSS packages. ## 8.4.11 * Fixed `Declaration#raws.value` type. ## 8.4.10 * Fixed `package.funding` URL format. ## 8.4.9 * Fixed `package.funding` (by Álvaro Mondéjar). ## 8.4.8 * Fixed end position in empty Custom Properties. ## 8.4.7 * Fixed `Node#warn()` type (by Masafumi Koba). * Fixed comment removal in values after `,`. ## 8.4.6 * Prevented comment removing when it change meaning of CSS. * Fixed parsing space in last semicolon-less CSS Custom Properties. * Fixed comment cleaning in CSS Custom Properties with space. * Fixed throwing an error on `.root` access for plugin-less case. ## 8.4.5 * Fixed `raws` types to make object extendable (by James Garbutt). * Moved from Yarn 1 to pnpm. ## 8.4.4 * Fixed absolute path in source map on zero plugins mode. ## 8.4.3 * Fixed `this.css.replace is not a function` error. ## 8.4.2 * Fixed previous source map support in zero plugins mode. ## 8.4.1 * Fixed `Stringifier` types (by James Garbutt). ## 8.4 “President Camio” * Added ranges for errors and warnings (by Adaline Valentina Simonian). * Added `Stringifier` types (by James Garbutt). * Added `Processor` types. * Removed `PostCSS does nothing` warning by lazy parser (Bogdan Dolin). * Fixed `Node#walkRules()` types (by Aleks Hudochenkov). * Fixed types `Root` and `Document` in result values (by James Garbutt). * Reduced npm install size by 0.5 MB. * Moved tests from Jest to `uvu` (by Andrey Kim). * Fixed docs (by Paul Shryock). ## 8.3.11 * Remove debugging code. ## 8.3.10 * Fixed `Maximum call stack` issue of some source maps (by Yeting Li). ## 8.3.9 * Replaced `nanocolors` to `picocolors`. * Reduced package size. ## 8.3.8 * Update `nanocolors`. ## 8.3.7 * Replaced `colorette` to `nanocolors`. * Added bug field to `package.json` (by Christian Oliff). * Improved docs (by Andrew Bruce and Paul Shryock). ## 8.3.6 * Fixed column in `missed semicolon` error (by @Gusted). ## 8.3.5 * Fixed broken AST detection. ## 8.3.4 * Fixed broken AST detection. ## 8.3.3 * Fixed broken AST on `postcss` dependency duplication in custom parsers. ## 8.3.2 * Update changelog. ## 8.3.1 * Fixed false positives `PostCSS does nothing` warning on `syntax` option. ## 8.3 “Duke Murmur” * Added `Node#assign()` shortcut (by Jonathan Neal). * Added experimental `Document` node to AST (by Aleks Hudochenkov). * Moved to faster fork of `source-map` (by Valentin Semirulnik). ## 8.2.15 * Fixed `list` type definitions (by @n19htz). ## 8.2.14 * Removed `source-map` from client-side bundle (by Barak Igal). ## 8.2.13 * Fixed ReDoS vulnerabilities in source map parsing (by Yeting Li). ## 8.2.12 * Fixed `package.json` exports. ## 8.2.11 * Fixed `DEP0148` warning in Node.js 16. * Fixed docs (by @semiromid). ## 8.2.10 * Fixed ReDoS vulnerabilities in source map parsing. * Fixed webpack 5 support (by Barak Igal). * Fixed docs (by Roeland Moors). ## 8.2.9 * Exported `NodeErrorOptions` type (by Rouven Weßling). ## 8.2.8 * Fixed browser builds in webpack 4 (by Matt Jones). ## 8.2.7 * Fixed browser builds in webpack 5 (by Matt Jones). ## 8.2.6 * Fixed `Maximum call stack size exceeded` in `Node#toJSON`. * Fixed docs (by inokawa). ## 8.2.5 * Fixed escaped characters handling in `list.split` (by Natalie Weizenbaum). ## 8.2.4 * Added plugin name to `postcss.plugin()` warning (by Tom Williams). * Fixed docs (by Bill Columbia). ## 8.2.3 * Fixed `JSON.stringify(Node[])` support (by Niklas Mischkulnig). ## 8.2.2 * Fixed CSS-in-JS support (by James Garbutt). * Fixed plugin types (by Ludovico Fischer). * Fixed `Result#warn()` types. ## 8.2.1 * Fixed `Node#toJSON()` and `postcss.fromJSON()` (by Niklas Mischkulnig). ## 8.2 “Prince Orobas” * Added `Node#toJSON()` and `postcss.fromJSON()` (by Niklas Mischkulnig). ## 8.1.14 * Fixed parser performance regression. ## 8.1.13 * Fixed broken AST after moving nodes in visitor API. ## 8.1.12 * Fixed Autoprefixer regression. ## 8.1.11 * Added PostCSS update suggestion on unknown event in plugin. ## 8.1.10 * Fixed `LazyResult` type export (by Evan You). * Fixed `LazyResult` type compatibility with `Promise` (by Anton Kastritskiy). ## 8.1.9 * Reduced dependencies number (by Bogdan Chadkin). ## 8.1.8 * Fixed `LazyResult` type compatibility with `Promise` (by Ludovico Fischer). * Fixed HTTPS links in documentation. ## 8.1.7 * Fixed `import` support in TypeScript (by Remco Haszing). ## 8.1.6 * Reverted `package.exports` Node.js 15 fix. ## 8.1.5 * Fixed Node.js 15 warning (by 沈鸿飞). ## 8.1.4 * Fixed TypeScript definition (by Arthur Petrie). ## 8.1.3 * Added `package.types`. ## 8.1.2 * Fixed API docs (by Arthur Petrie). * Improved plugin guide (by Yunus Gaziev). * Prepared code base for Deno support (by Oscar Otero). ## 8.1.1 * Updated funding link. ## 8.1 “Duke Gemory” * Added `Once` and `OnceExit` events. * Fixed `Root` and `RootExit` events re-visiting. * Fixed node re-visiting on deep children changes. * Added docs for visitor API events. ## 8.0.9 * Replace prototype in PostCSS 7 nodes instead of recreating them. * Added missed `Transformer` to exported types (by Pierre-Marie Dartus). ## 8.0.8 * Fix `8.0.7` regression on PostCSS 7 nodes converting (by Adam Wathan). ## 8.0.7 * Fixed compatibility issue with mixin AST with PostCSS 7 and 8 nodes. * Added migration guide translation to Chinese to the warning. ## 8.0.6 * Fixed child adding methods in `Container`. ## 8.0.5 * Update changelog. ## 8.0.4 * Fixed `Cannot read property 'line' of null` error. * Fixed source map support for declarations. ## 8.0.3 * Fixed client-side bundling support. ## 8.0.2 * Fixed plugin packs support. ## 8.0.1 * Updated `Processor#version`. ## 8.0 “President Ose” * Removed support for Node.js 6.x, 8.x, 11.x, and 13.x versions. * Removed `postcss.vendor` helpers. * Deprecated `postcss.plugin()` API. * Treats `sourceMap.sources` as URL instead of file path. * Plugins and runners must have `postcss` in `peerDependencies`. * Prohibited to extend PostCSS AST classes. * Moved from JSDoc to TypeDoc. * Moved unknown source from counter to random IDs. * Added visitor API for plugins (by Alexey Bondarenko). * Added ES modules support. * Added named exports for public classes `const { Rule } = require('postcss)`. * Added `position.url` to `Node#origin()` result. * Added `opts.maps.absolute = true` option. * Added `opts.maps.annotation = (file, root) => url` option support. * Added `Node#source.offset` (by Ayaz Zaynutdinov). * Added `Declaration#variable`. * Added JSON source map support. * Added index source map support. * Added `Declaration#value` auto-converting to string. * Fixed parsing `{}` in at-rule parameters. * Fixed parsing empty Custom Properties. `--foo: ;` will have ` ` value. * Fixed building PostCSS with Rollup (by MapGrid). * Fixed TypeScript types. * Fixed source map relative paths. * Fixed calling `replaceWith` with input replaced node (by Joseph Kaptur). * Improved “Writing a PostCSS Plugin” docs (by Alexey Bondarenko). * Removed Babel from the project’s release process. * Removed docs from npm package. * Replaced `chalk` to `colorette`. ## 7.0.38 * Update `Processor#version`. ## 7.0.37 * Backport `chalk` to `nanocolors` migration. ## 7.0.36 * Backport ReDoS vulnerabilities from PostCSS 8. ## 7.0.35 * Add migration guide link to PostCSS 8 error text. ## 7.0.34 * Fix compatibility with `postcss-scss` 2. ## 7.0.33 * Add error message for PostCSS 8 plugins. ## 7.0.32 * Fix error message (by @admosity). ## 7.0.31 * Use only the latest source map annotation (by Emmanouil Zoumpoulakis). ## 7.0.30 * Fix TypeScript definition (by Natalie Weizenbaum). ## 7.0.29 * Update `Processor#version`. ## 7.0.28 * Fix TypeScript definition (by Natalie Weizenbaum). ## 7.0.27 * Fix TypeScript definition (by Natalie Weizenbaum). ## 7.0.26 * Fix TypeScript definition (by Natalie Weizenbaum). ## 7.0.25 * Fix absolute path support for Windows (by Tom Raviv). ## 7.0.24 * Fix TypeScript definition (by Keith Cirkel). ## 7.0.23 * Update `Processor#version`. ## 7.0.22 * Add funding link for `npm fund`. ## 7.0.21 * Revert passing `nodes` property to node constructor. ## 7.0.20 * Allow to pass PostCSS’s nodes in `nodes` property to node constructor. ## 7.0.19 * Fix passing `nodes` property to node constructor. ## 7.0.18 * Fix TypeScript type definitions (by Jan Buschtöns). ## 7.0.17 * Fix TypeScript type definitions (by Bob Matcuk and Jan Buschtöns). ## 7.0.16 * Revert Custom Properties fix until PostCSS 8.0. ## 7.0.15 * Fix Custom Properties support (by Ivan Solovev). ## 7.0.14 * Fix tokenizer for `postcss-less` (by Matt Lyons). ## 7.0.13 * Fix parsing regression in 7.0.12 for comments between property and value. ## 7.0.12 * Fix parsing broken CSS with two words in declaration property. ## 7.0.11 * Fix source maps on declaration semicolon (by Niklas Mischkulnig). ## 7.0.10 * Fix source maps (by Niklas Mischkulnig). ## 7.0.9 * Increase stringifing performance for non-raws AST. ## 7.0.8 * Fix TypeScript definitions (by Ankur Oberoi). * Use `support-colors` 6.0. ## 7.0.7 * Extend `Error` in `CssSyntaxError`. ## 7.0.6 * Fix parsing files with BOM (by Veniamin Krol). ## 7.0.5 * Reduce npm package size (by Gilad Peleg). ## 7.0.4 * Fix safe parser regression. ## 7.0.3 * Fix tokenizer extendability (by Andrew Powell). * Reduce npm package size. ## 7.0.2 * Fix warning text (by Rui Pedro M Lima). ## 7.0.1 * Fix JSDoc (by Steven Lambert). ## 7.0 “President Amy” * Remove Node.js 9 and Node.js 4 support. * Remove IE and “dead” browsers support for client-side Babel transpiling. * Add CSS position on error happened inside `walk()` (by Nikhil Gaba). * Add `LazyResult#finally` (by Igor Kamyshev). * Add warning on calling PostCSS without plugins and syntax options. * Reduce client-side size. ## 6.0.23 * Fix parsing nested at-rules without semicolon, params, and spaces. * Fix docs (by Kevin Schiffer and Pat Cavit). ## 6.0.22 * Fix `Node#prev` and `Node#next` on missed parent. ## 6.0.21 * Rename Chinese docs to fix `yarnpkg.com` issue. ## 6.0.20 * Better error message on `null` as input CSS. ## 6.0.19 * Fix TypeScript definitions for source maps (by Oleh Kuchuk). * Fix `source` field in TypeScript definitions (by Sylvain Pollet-Villard). ## 6.0.18 * Use primitive object in TypeScript definitions (by Sylvain Pollet-Villard). ## 6.0.17 * Fix parsing comment in selector between word tokens (by Oleh Kuchuk). ## 6.0.16 * Fix warning text (by Michael Keller). ## 6.0.15 * Add warning about missed `from` option on `process().then()` call. * Add IE 10 support. ## 6.0.14 * Fix TypeScript definitions (by Jed Mao). ## 6.0.13 * Fix TypeScript definitions for case of multiple PostCSS versions in `node_modules` (by Chris Eppstein). * Use `source-map` 0.6. ## 6.0.12 * Don’t copy `*` hack to declaration indent. ## 6.0.11 * Add upper case `!IMPORTANT` support. ## 6.0.10 * Reduce PostCSS size in webpack bundle. ## 6.0.9 * Improve error message for plugin with old PostCSS (by Igor Adamenko). ## 6.0.8 * Fix Node.js 4.2.2 support. ## 6.0.7 * Fix base64 decoding for old Node.js and browser. ## 6.0.6 * Fix `end` position in at-rule without semicolon (by Oleh Kuchuk). ## 6.0.5 * Move Babel config from `package.json` for `node_modules` compiling cases. ## 6.0.4 * Fix parsing `;;` after rules. * Use Chalk 2.0. ## 6.0.3 * Fix escape sequences parsing (by Oleh Kuchuk). * Added ability to force disable colors with an environment variable. * Improved color detection of some terminal apps. ## 6.0.2 * Keep `raws.before` on moving `Root` children to new `Root`. ## 6.0.1 * Fix parser extensibility to use it in Safe Parser. ## 6.0 “Marquis Orias” * Remove node.js 0.12 support. * Remove deprecated method from PostCSS 4. * Insert methods remove child from previous parent, instead of closing. * Insert methods and cloning doesn’t clean `raws` anymore. * Methods `moveTo`, `moveAfter`, `moveBefore` were deprecated. * Options was changed in `Plugin#process(css, processOptions, pluginOptions)`. * Add stream parser to reduce memory usage (by Oleh Kuchuk). * Add `before()`/`after()` shortcuts for `node.parent.insertBefore(node, x)`. * Add `Rule#raws.ownSemicolon` for semicolon after templates for `@apply`. * Use `babel-preset-env` to compile npm package. * Remove `js-base64` from dependencies (by Roman Dvornov). * Fix error message on single `:` in CSS. * Move tests to Jest. * Clean up test (by Gabriel Kalani). ## 5.2.18 * Fix TypeScript definitions for case of multiple PostCSS versions in `node_modules` (by Chris Eppstein). ## 5.2.17 * Add `postcss-sass` suggestion to syntax error on `.sass` input. ## 5.2.16 * Better error on wrong argument in node constructor. ## 5.2.15 * Fix TypeScript definitions (by bumbleblym). ## 5.2.14 * Fix browser bundle building in webpack (by janschoenherr). ## 5.2.13 * Do not add comment to important raws. * Fix JSDoc (by Dmitry Semigradsky). ## 5.2.12 * Fix typo in deprecation message (by Garet McKinley). ## 5.2.11 * Fix TypeScript definitions (by Jed Mao). ## 5.2.10 * Fix TypeScript definitions (by Jed Mao). ## 5.2.9 * Update TypeScript definitions (by Jed Mao). ## 5.2.8 * Fix error message (by Ben Briggs). ## 5.2.7 * Better error message on syntax object in plugins list. ## 5.2.6 * Fix `postcss.vendor` for values with spaces (by 刘祺). ## 5.2.5 * Better error message on unclosed string (by Ben Briggs). ## 5.2.4 * Improve terminal CSS syntax highlight (by Simon Lydell). ## 5.2.3 * Better color highlight in syntax error code frame. * Fix color highlight support in old systems. ## 5.2.2 * Update `Processor#version`. ## 5.2.1 * Fix source map path for CSS without `from` option (by Michele Locati). ## 5.2 “Duke Vapula” * Add syntax highlight to code frame in syntax error (by Andrey Popp). * Use Babel code frame style and size in syntax error. * Add `[` and `]` tokens to parse `[attr=;] {}` correctly. * Add `ignoreErrors` options to tokenizer (by Andrey Popp). * Fix error position on tab indent (by Simon Lydell). ## 5.1.2 * Suggests SCSS/Less parsers on parse errors depends on file extension. ## 5.1.1 * Fix TypeScript definitions (by Efremov Alexey). ## 5.1 “King and President Zagan” * Add URI in source map support (by Mark Finger). * Add `map.from` option (by Mark Finger). * Add `` mappings for nodes without source (by Bogdan Chadkin). * Add function value support to `map.prev` option (by Chris Montoro). * Add declaration value type check in shortcut creating (by 刘祺). * `Result#warn` now returns new created warning. * Don’t call plugin creator in `postcss.plugin` call. * Add source maps to PostCSS ES5 build. * Add JSDoc to PostCSS classes. * Clean npm package from unnecessary docs. ## 5.0.21 * Fix support with input source mao with `utf8` encoding name. ## 5.0.20 * Fix between raw value parsing (by David Clark). * Update TypeScript definitions (by Jed Mao). * Clean fake node.source after `append(string)`. ## 5.0.19 * Fix indent-based syntaxes support. ## 5.0.18 * Parse new lines according W3C CSS syntax specification. ## 5.0.17 * Fix options argument in `Node#warn` (by Ben Briggs). * Fix TypeScript definitions (by Jed Mao). ## 5.0.16 * Fix CSS syntax error position on unclosed quotes. ## 5.0.15 * Fix `Node#clone()` on `null` value somewhere in node. ## 5.0.14 * Allow to use PostCSS in webpack bundle without JSON loader. ## 5.0.13 * Fix `index` and `word` options in `Warning#toString` (by Bogdan Chadkin). * Fix input source content loading in errors. * Fix map options on using `LazyResult` as input CSS. * 100% test coverage. * Use Babel 6. ## 5.0.12 * Allow passing a previous map with no mappings (by Andreas Lind). ## 5.0.11 * Increase plugins performance by 1.5 times. ## 5.0.10 * Fix warning from nodes without source. ## 5.0.9 * Fix source map type detection (by @asan). ## 5.0.8 * Fixed a missed step in `5.0.7` that caused the module to be published as ES6 code. ## 5.0.7 * PostCSS now requires that node 0.12 is installed via the engines property in package.json (by Howard Zuo). ## 5.0.6 * Fix parsing nested at-rule without semicolon (by Matt Drake). * Trim `Declaration#value` (by Bogdan Chadkin). ## 5.0.5 * Fix multi-tokens property parsing (by Matt Drake). ## 5.0.4 * Fix start position in `Root#source`. * Fix source map annotation, when CSS uses `\r\n` (by Mohammad Younes). ## 5.0.3 * Fix `url()` parsing. * Fix using `selectors` in `Rule` constructor. * Add start source to `Root` node. ## 5.0.2 * Fix `remove(index)` to be compatible with 4.x plugin. ## 5.0.1 * Fix PostCSS 4.x plugins compatibility. * Fix type definition loading (by Jed Mao). ## 5.0 “President Valac” * Remove `safe` option. Move Safe Parser to separate project. * `Node#toString` does not include `before` for root nodes. * Remove plugin returning `Root` API. * Remove Promise polyfill for node.js 0.10. * Deprecate `eachInside`, `eachDecl`, `eachRule`, `eachAtRule` and `eachComment` in favor of `walk`, `walkDecls`, `walkRules`, `walkAtRules` and `walkComments` (by Jed Mao). * Deprecate `Container#remove` and `Node#removeSelf` in favor of `Container#removeChild` and `Node#remove` (by Ben Briggs). * Deprecate `Node#replace` in favor of `replaceWith` (by Ben Briggs). * Deprecate raw properties in favor of `Node#raws` object. * Deprecate `Node#style` in favor of `raw`. * Deprecate `CssSyntaxError#generated` in favor of `input`. * Deprecate `Node#cleanStyles` in favor of `cleanRaws`. * Deprecate `Root#prevMap` in favor of `Root.source.input.map`. * Add `syntax`, `parser` and `stringifier` options for Custom Syntaxes. * Add stringifier option to `Node#toString`. * Add `Result#content` alias for non-CSS syntaxes. * Add `plugin.process(css)` shortcut to every plugin function (by Ben Briggs). * Add multiple nodes support to insert methods (by Jonathan Neal). * Add `Node#warn` shortcut (by Ben Briggs). * Add `word` and `index` options to errors and warnings (by David Clark). * Add `line`, `column` properties to `Warning`. * Use `supports-color` library to detect color support in error output. * Add type definitions for TypeScript plugin developers (by Jed Mao). * `Rule#selectors` setter detects separators. * Add `postcss.stringify` method. * Throw descriptive errors for incorrectly formatted plugins. * Add docs to npm release. * Fix `url()` parsing. * Fix Windows support (by Jed Mao). ## 4.1.16 * Fix errors without stack trace. ## 4.1.15 * Allow asynchronous plugins to change processor plugins list (by Ben Briggs). ## 4.1.14 * Fix for plugins packs defined by `postcss.plugin`. ## 4.1.13 * Fix input inlined source maps with UTF-8 encoding. ## 4.1.12 * Update Promise polyfill. ## 4.1.11 * Fix error message on wrong plugin format. ## 4.1.10 * Fix Promise behavior on sync plugin errors. * Automatically fill `plugin` field in `CssSyntaxError`. * Fix warning message (by Ben Briggs). ## 4.1.9 * Speed up `node.clone()`. ## 4.1.8 * Accepts `Processor` instance in `postcss()` constructor too. ## 4.1.7 * Speed up `postcss.list` (by Bogdan Chadkin). ## 4.1.6 * Fix Promise behavior on parsing error. ## 4.1.5 * Parse at-words in declaration values. ## 4.1.4 * Fix Promise polyfill dependency (by Anton Yakushev and Matija Marohnić). ## 4.1.3 * Add Promise polyfill for node.js 0.10 and IE. ## 4.1.2 * List helpers can be accessed independently `var space = postcss.list.space`. ## 4.1.1 * Show deprecated message only once. ## 4.1 “Marquis Andras” * Asynchronous plugin support. * Add warnings from plugins and `Result#messages`. * Add `postcss.plugin()` to create plugins with a standard API. * Insert nodes by CSS string. * Show version warning message on error from an outdated plugin. * Send `Result` instance to plugins as the second argument. * Add `CssSyntaxError#plugin`. * Add `CssSyntaxError#showSourceCode()`. * Add `postcss.list` and `postcss.vendor` aliases. * Add `Processor#version`. * Parse wrong closing bracket. * Parse `!important` statement with spaces and comments inside (by Ben Briggs). * Throw an error on declaration without `prop` or `value` (by Philip Peterson). * Fix source map mappings position. * Add indexed source map support. * Always set `error.generated`. * Clean all source map annotation comments. ## 4.0.6 * Remove `babel` from released package dependencies (by Andres Suarez). ## 4.0.5 * Fix error message on double colon in declaration. ## 4.0.4 * Fix indent detection in some rare cases. ## 4.0.3 * Faster API with 6to5 Loose mode. * Fix indexed source maps support. ## 4.0.2 * Do not copy IE hacks to code style. ## 4.0.1 * Add `source.input` to `Root` too. ## 4.0 “Duke Flauros” * Rename `Container#childs` to `nodes`. * Rename `PostCSS#processors` to `plugins`. * Add `Node#replaceValues()` method. * Add `Node#moveTo()`, `moveBefore()` and `moveAfter()` methods. * Add `Node#cloneBefore()` and `cloneAfter()` shortcuts. * Add `Node#next()`, `prev()` and `root()` shortcuts. * Add `Node#replaceWith()` method. * Add `Node#error()` method. * Add `Container#removeAll()` method. * Add filter argument to `eachDecl()` and `eachAtRule()`. * Add `Node#source.input` and move `source.file` or `source.id` to `input`. * Change code indent, when node was moved. * Better fix code style on `Rule`, `AtRule` and `Comment` nodes changes. * Allow to create rules and at-rules by hash shortcut in append methods. * Add class name to CSS syntax error output. ## 3.0.7 * Fix IE filter parsing with multiple commands. * Safer way to consume PostCSS object as plugin (by Maxime Thirouin). ## 3.0.6 * Fix missing semicolon when comment comes after last declaration. * Fix Safe Mode declaration parsing on unclosed blocks. ## 3.0.5 * Fix parser to support difficult cases with backslash escape and brackets. * Add `CssSyntaxError#stack` (by Maxime Thirouin). ## 3.0.4 * Fix Safe Mode on unknown word before declaration. ## 3.0.3 * Increase tokenizer speed (by Roman Dvornov). ## 3.0.2 * Fix empty comment parsing. * Fix `Root#normalize` in some inserts. ## 3.0.1 * Fix Rhino JS runtime support. * Typo in deprecated warning (by Maxime Thirouin). ## 3.0 “Marquis Andrealphus” * New parser, which become the fastest ever CSS parser written in JavaScript. * Parser can now parse declarations and rules in one parent (like in `@page`) and nested declarations for plugins like `postcss-nested`. * Child nodes array is now in `childs` property, instead of `decls` and `rules`. * `map.inline` and `map.sourcesContent` options are now `true` by default. * Fix iterators (`each`, `insertAfter`) on children array changes. * Use previous source map to show origin source of CSS syntax error. * Use 6to5 ES6 compiler, instead of ES6 Transpiler. * Use code style for manually added rules from existing rules. * Use `from` option from previous source map `file` field. * Set `to` value to `from` if `to` option is missing. * Use better node source name when missing `from` option. * Show a syntax error when `;` is missed between declarations. * Allow to pass `PostCSS` instance or list of plugins to `use()` method. * Allow to pass `Result` instance to `process()` method. * Trim Unicode BOM on source maps parsing. * Parse at-rules without spaces like `@import"file"`. * Better previous `sourceMappingURL` annotation comment cleaning. * Do not remove previous `sourceMappingURL` comment on `map.annotation: false`. * Parse nameless at-rules in Safe Mode. * Fix source map generation for nodes without source. * Fix next child `before` if `Root` first child got removed. ## 2.2.6 * Fix map generation for nodes without source (by Josiah Savary). ## 2.2.5 * Fix source map with BOM marker support (by Mohammad Younes). * Fix source map paths (by Mohammad Younes). ## 2.2.4 * Fix `prepend()` on empty `Root`. ## 2.2.3 * Allow to use object shortcut in `use()` with functions like `autoprefixer`. ## 2.2.2 * Add shortcut to set processors in `use()` via object with `.postcss` property. ## 2.2.1 * Send `opts` from `Processor#process(css, opts)` to processors. ## 2.2 “Marquis Cimeies” * Use GNU style syntax error messages. * Add `Node#replace` method. * Add `CssSyntaxError#reason` property. ## 2.1.2 * Fix UTF-8 support in inline source map. * Fix source map `sourcesContent` if there is no `from` and `to` options. ## 2.1.1 * Allow to miss `to` and `from` options for inline source maps. * Add `Node#source.id` if file name is unknown. * Better detect splitter between rules in CSS concatenation tools. * Automatically clone node in insert methods. ## 2.1 “King Amdusias” * Change Traceur ES6 compiler to ES6 Transpiler. * Show broken CSS line in syntax error. ## 2.0 “King Belial” * Project was rewritten from CoffeeScript to ES6. * Add Safe Mode to works with live input or with hacks from legacy code. * More safer parser to pass all hacks from Browserhacks.com. * Use real properties instead of magic getter/setter for raw properties. ## 1.0 “Marquis Decarabia” * Save previous source map for each node to support CSS concatenation with multiple previous maps. * Add `map.sourcesContent` option to add origin content to `sourcesContent` inside map. * Allow to set different place of output map in annotation comment. * Allow to use arrays and `Root` in `Container#append` and same methods. * Add `Root#prevMap` with information about previous map. * Allow to use latest PostCSS from GitHub by npm. * `Result` now is lazy and it will generate output CSS only if you use `css` or `map` property. * Use separated `map.prev` option to set previous map. * Rename `inlineMap` option to `map.inline`. * Rename `mapAnnotation` option to `map.annotation`. * `Result#map` now return `SourceMapGenerator` object, instead of string. * Run previous map autodetect only if input CSS contains annotation comment. * Add `map: 'inline'` shortcut for `map: { inline: true }` option. * `Node#source.file` now will contains absolute path. * Clean `Declaration#between` style on node clone. ## 0.3.5 * Allow to use `Root` or `Result` as first argument in `process()`. * Save parsed AST to `Result#root`. ## 0.3.4 * Better space symbol detect to read UTF-8 BOM correctly. ## 0.3.3 * Remove source map hacks by using new Mozilla’s `source-map` (by Simon Lydell). ## 0.3.2 * Add URI encoding support for inline source maps. ## 0.3.1 * Fix relative paths from previous source map. * Safer space split in `Rule#selectors` (by Simon Lydell). ## 0.3 “Prince Seere” * Add `Comment` node for comments between declarations or rules. * Add source map annotation comment to output CSS. * Allow to inline source map to annotation comment by data:uri. * Fix source maps on Windows. * Fix source maps for subdirectory (by Dmitry Nikitenko and Simon Lydell). * Autodetect previous source map. * Add `first` and `last` shortcuts to container nodes. * Parse `!important` to separated property in `Declaration`. * Allow to break iteration by returning `false`. * Copy code style to new nodes. * Add `eachInside` method to recursively iterate all nodes. * Add `selectors` shortcut to get selectors array. * Add `toResult` method to `Rule` to simplify work with several input files. * Clean declaration’s `value`, rule’s `selector` and at-rule’s `params` by storing spaces in `between` property. ## 0.2 “Duke Dantalion” * Add source map support. * Add shortcuts to create nodes. * Method `process()` now returns object with `css` and `map` keys. * Origin CSS file option was renamed from `file` to `from`. * Rename `Node#remove()` method to `removeSelf()` to fix name conflict. * Node source was moved to `source` property with origin file and node end position. * You can set own CSS generate function. ## 0.1 “Count Andromalius” * Initial release. postcss-8.5.6/LICENSE000066400000000000000000000021071502402132100142230ustar00rootroot00000000000000The MIT License (MIT) Copyright 2013 Andrey Sitnik Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. postcss-8.5.6/README.md000066400000000000000000000427611502402132100145070ustar00rootroot00000000000000# PostCSS Philosopher’s stone, logo of PostCSS PostCSS is a tool for transforming styles with JS plugins. These plugins can lint your CSS, support variables and mixins, transpile future CSS syntax, inline images, and more. PostCSS is used by industry leaders including Wikipedia, Twitter, Alibaba, and JetBrains. The [Autoprefixer] and [Stylelint] PostCSS plugins are some of the most popular CSS tools. ---   Built by Evil Martians, go-to agency for developer tools. --- [Abstract Syntax Tree]: https://en.wikipedia.org/wiki/Abstract_syntax_tree [Evil Martians]: https://evilmartians.com/?utm_source=postcss [Autoprefixer]: https://github.com/postcss/autoprefixer [Stylelint]: https://stylelint.io/ [plugins]: https://github.com/postcss/postcss#plugins ## Sponsorship PostCSS needs your support. We are accepting donations [at Open Collective](https://opencollective.com/postcss/). Sponsored by Tailwind CSS       Sponsored by ThemeIsle ## Plugins PostCSS takes a CSS file and provides an API to analyze and modify its rules (by transforming them into an [Abstract Syntax Tree]). This API can then be used by [plugins] to do a lot of useful things, e.g., to find errors automatically, or to insert vendor prefixes. Currently, PostCSS has more than 200 plugins. You can find all of the plugins in the [plugins list]. Below is a list of our favorite plugins — the best demonstrations of what can be built on top of PostCSS. If you have any new ideas, [PostCSS plugin development] is really easy. [plugins list]: https://github.com/postcss/postcss/blob/main/docs/plugins.md ### Solve Global CSS Problem * [`postcss-use`] allows you to explicitly set PostCSS plugins within CSS and execute them only for the current file. * [`postcss-modules`] and [`react-css-modules`] automatically isolate selectors within components. * [`postcss-autoreset`] is an alternative to using a global reset that is better for isolatable components. * [`postcss-initial`] adds `all: initial` support, which resets all inherited styles. * [`cq-prolyfill`] adds container query support, allowing styles that respond to the width of the parent. ### Use Future CSS, Today * [`autoprefixer`] adds vendor prefixes, using data from Can I Use. * [`postcss-preset-env`] allows you to use future CSS features today. ### Better CSS Readability * [`postcss-nested`] unwraps nested rules the way Sass does. * [`postcss-sorting`] sorts the content of rules and at-rules. * [`postcss-utilities`] includes the most commonly used shortcuts and helpers. * [`short`] adds and extends numerous shorthand properties. ### Images and Fonts * [`postcss-url`] postcss plugin to rebase url(), inline or copy asset. * [`postcss-sprites`] generates image sprites. * [`font-magician`] generates all the `@font-face` rules needed in CSS. * [`postcss-inline-svg`] allows you to inline SVG and customize its styles. * [`postcss-write-svg`] allows you to write simple SVG directly in your CSS. * [`webp-in-css`] to use WebP image format in CSS background. * [`avif-in-css`] to use AVIF image format in CSS background. ### Linters * [`stylelint`] is a modular stylesheet linter. * [`stylefmt`] is a tool that automatically formats CSS according `stylelint` rules. * [`doiuse`] lints CSS for browser support, using data from Can I Use. * [`colorguard`] helps you maintain a consistent color palette. ### Other * [`cssnano`] is a modular CSS minifier. * [`lost`] is a feature-rich `calc()` grid system. * [`rtlcss`] mirrors styles for right-to-left locales. [PostCSS plugin development]: https://github.com/postcss/postcss/blob/main/docs/writing-a-plugin.md [`postcss-inline-svg`]: https://github.com/TrySound/postcss-inline-svg [`postcss-preset-env`]: https://github.com/csstools/postcss-plugins/tree/main/plugin-packs/postcss-preset-env [`react-css-modules`]: https://github.com/gajus/react-css-modules [`postcss-autoreset`]: https://github.com/maximkoretskiy/postcss-autoreset [`postcss-write-svg`]: https://github.com/csstools/postcss-write-svg [`postcss-utilities`]: https://github.com/ismamz/postcss-utilities [`postcss-initial`]: https://github.com/maximkoretskiy/postcss-initial [`postcss-sprites`]: https://github.com/2createStudio/postcss-sprites [`postcss-modules`]: https://github.com/outpunk/postcss-modules [`postcss-sorting`]: https://github.com/hudochenkov/postcss-sorting [`font-magician`]: https://github.com/csstools/postcss-font-magician [`autoprefixer`]: https://github.com/postcss/autoprefixer [`cq-prolyfill`]: https://github.com/ausi/cq-prolyfill [`postcss-url`]: https://github.com/postcss/postcss-url [`postcss-use`]: https://github.com/postcss/postcss-use [`css-modules`]: https://github.com/css-modules/css-modules [`webp-in-css`]: https://github.com/ai/webp-in-css [`avif-in-css`]: https://github.com/nucliweb/avif-in-css [`colorguard`]: https://github.com/SlexAxton/css-colorguard [`stylelint`]: https://github.com/stylelint/stylelint [`stylefmt`]: https://github.com/morishitter/stylefmt [`cssnano`]: https://cssnano.github.io/cssnano/ [`postcss-nested`]: https://github.com/postcss/postcss-nested [`doiuse`]: https://github.com/anandthakker/doiuse [`rtlcss`]: https://github.com/MohammadYounes/rtlcss [`short`]: https://github.com/csstools/postcss-short [`lost`]: https://github.com/peterramsing/lost ## Syntaxes PostCSS can transform styles in any syntax, not just CSS. If there is not yet support for your favorite syntax, you can write a parser and/or stringifier to extend PostCSS. * [`sugarss`] is a indent-based syntax like Sass or Stylus. * [`postcss-syntax`] switch syntax automatically by file extensions. * [`postcss-html`] parsing styles in `' * ) * document.type //=> 'document' * document.nodes.length //=> 2 * ``` */ declare class Document_ extends Container { nodes: Root[] parent: undefined type: 'document' constructor(defaults?: Document.DocumentProps) assign(overrides: Document.DocumentProps | object): this clone(overrides?: Partial): this cloneAfter(overrides?: Partial): this cloneBefore(overrides?: Partial): this /** * Returns a `Result` instance representing the document’s CSS roots. * * ```js * const root1 = postcss.parse(css1, { from: 'a.css' }) * const root2 = postcss.parse(css2, { from: 'b.css' }) * const document = postcss.document() * document.append(root1) * document.append(root2) * const result = document.toResult({ to: 'all.css', map: true }) * ``` * * @param opts Options. * @return Result with current document’s CSS. */ toResult(options?: ProcessOptions): Result } declare class Document extends Document_ {} export = Document postcss-8.5.6/lib/document.js000066400000000000000000000012161502402132100161400ustar00rootroot00000000000000'use strict' let Container = require('./container') let LazyResult, Processor class Document extends Container { constructor(defaults) { // type needs to be passed to super, otherwise child roots won't be normalized correctly super({ type: 'document', ...defaults }) if (!this.nodes) { this.nodes = [] } } toResult(opts = {}) { let lazy = new LazyResult(new Processor(), this, opts) return lazy.stringify() } } Document.registerLazyResult = dependant => { LazyResult = dependant } Document.registerProcessor = dependant => { Processor = dependant } module.exports = Document Document.default = Document postcss-8.5.6/lib/fromJSON.d.ts000066400000000000000000000002421502402132100162110ustar00rootroot00000000000000import { JSONHydrator } from './postcss.js' interface FromJSON extends JSONHydrator { default: FromJSON } declare const fromJSON: FromJSON export = fromJSON postcss-8.5.6/lib/fromJSON.js000066400000000000000000000027421502402132100157640ustar00rootroot00000000000000'use strict' let AtRule = require('./at-rule') let Comment = require('./comment') let Declaration = require('./declaration') let Input = require('./input') let PreviousMap = require('./previous-map') let Root = require('./root') let Rule = require('./rule') function fromJSON(json, inputs) { if (Array.isArray(json)) return json.map(n => fromJSON(n)) let { inputs: ownInputs, ...defaults } = json if (ownInputs) { inputs = [] for (let input of ownInputs) { let inputHydrated = { ...input, __proto__: Input.prototype } if (inputHydrated.map) { inputHydrated.map = { ...inputHydrated.map, __proto__: PreviousMap.prototype } } inputs.push(inputHydrated) } } if (defaults.nodes) { defaults.nodes = json.nodes.map(n => fromJSON(n, inputs)) } if (defaults.source) { let { inputId, ...source } = defaults.source defaults.source = source if (inputId != null) { defaults.source.input = inputs[inputId] } } if (defaults.type === 'root') { return new Root(defaults) } else if (defaults.type === 'decl') { return new Declaration(defaults) } else if (defaults.type === 'rule') { return new Rule(defaults) } else if (defaults.type === 'comment') { return new Comment(defaults) } else if (defaults.type === 'atrule') { return new AtRule(defaults) } else { throw new Error('Unknown node type: ' + json.type) } } module.exports = fromJSON fromJSON.default = fromJSON postcss-8.5.6/lib/input.d.ts000066400000000000000000000121141502402132100157140ustar00rootroot00000000000000import { CssSyntaxError, ProcessOptions } from './postcss.js' import PreviousMap from './previous-map.js' declare namespace Input { export interface FilePosition { /** * Column of inclusive start position in source file. */ column: number /** * Column of exclusive end position in source file. */ endColumn?: number /** * Line of exclusive end position in source file. */ endLine?: number /** * Offset of exclusive end position in source file. */ endOffset?: number /** * Absolute path to the source file. */ file?: string /** * Line of inclusive start position in source file. */ line: number /** * Offset of inclusive start position in source file. */ offset: number /** * Source code. */ source?: string /** * URL for the source file. */ url: string } // eslint-disable-next-line @typescript-eslint/no-use-before-define export { Input_ as default } } /** * Represents the source CSS. * * ```js * const root = postcss.parse(css, { from: file }) * const input = root.source.input * ``` */ declare class Input_ { /** * Input CSS source. * * ```js * const input = postcss.parse('a{}', { from: file }).input * input.css //=> "a{}" * ``` */ css: string /** * Input source with support for non-CSS documents. * * ```js * const input = postcss.parse('a{}', { from: file, document: '' }).input * input.document //=> "" * input.css //=> "a{}" * ``` */ document: string /** * The absolute path to the CSS source file defined * with the `from` option. * * ```js * const root = postcss.parse(css, { from: 'a.css' }) * root.source.input.file //=> '/home/ai/a.css' * ``` */ file?: string /** * The flag to indicate whether or not the source code has Unicode BOM. */ hasBOM: boolean /** * The unique ID of the CSS source. It will be created if `from` option * is not provided (because PostCSS does not know the file path). * * ```js * const root = postcss.parse(css) * root.source.input.file //=> undefined * root.source.input.id //=> "" * ``` */ id?: string /** * The input source map passed from a compilation step before PostCSS * (for example, from Sass compiler). * * ```js * root.source.input.map.consumer().sources //=> ['a.sass'] * ``` */ map: PreviousMap /** * The CSS source identifier. Contains `Input#file` if the user * set the `from` option, or `Input#id` if they did not. * * ```js * const root = postcss.parse(css, { from: 'a.css' }) * root.source.input.from //=> "/home/ai/a.css" * * const root = postcss.parse(css) * root.source.input.from //=> "" * ``` */ get from(): string /** * @param css Input CSS source. * @param opts Process options. */ constructor(css: string, opts?: ProcessOptions) /** * Returns `CssSyntaxError` with information about the error and its position. */ error( message: string, start: | { column: number line: number } | { offset: number }, end: | { column: number line: number } | { offset: number }, opts?: { plugin?: CssSyntaxError['plugin'] } ): CssSyntaxError error( message: string, line: number, column: number, opts?: { plugin?: CssSyntaxError['plugin'] } ): CssSyntaxError error( message: string, offset: number, opts?: { plugin?: CssSyntaxError['plugin'] } ): CssSyntaxError /** * Converts source line and column to offset. * * @param line Source line. * @param column Source column. * @return Source offset. */ fromLineAndColumn(line: number, column: number): number /** * Converts source offset to line and column. * * @param offset Source offset. */ fromOffset(offset: number): { col: number; line: number } | null /** * Reads the input source map and returns a symbol position * in the input source (e.g., in a Sass file that was compiled * to CSS before being passed to PostCSS). Optionally takes an * end position, exclusive. * * ```js * root.source.input.origin(1, 1) //=> { file: 'a.css', line: 3, column: 1 } * root.source.input.origin(1, 1, 1, 4) * //=> { file: 'a.css', line: 3, column: 1, endLine: 3, endColumn: 4 } * ``` * * @param line Line for inclusive start position in input CSS. * @param column Column for inclusive start position in input CSS. * @param endLine Line for exclusive end position in input CSS. * @param endColumn Column for exclusive end position in input CSS. * * @return Position in input source. */ origin( line: number, column: number, endLine?: number, endColumn?: number ): false | Input.FilePosition /** Converts this to a JSON-friendly object representation. */ toJSON(): object } declare class Input extends Input_ {} export = Input postcss-8.5.6/lib/input.js000066400000000000000000000151401502402132100154620ustar00rootroot00000000000000'use strict' let { nanoid } = require('nanoid/non-secure') let { isAbsolute, resolve } = require('path') let { SourceMapConsumer, SourceMapGenerator } = require('source-map-js') let { fileURLToPath, pathToFileURL } = require('url') let CssSyntaxError = require('./css-syntax-error') let PreviousMap = require('./previous-map') let terminalHighlight = require('./terminal-highlight') let lineToIndexCache = Symbol('lineToIndexCache') let sourceMapAvailable = Boolean(SourceMapConsumer && SourceMapGenerator) let pathAvailable = Boolean(resolve && isAbsolute) function getLineToIndex(input) { if (input[lineToIndexCache]) return input[lineToIndexCache] let lines = input.css.split('\n') let lineToIndex = new Array(lines.length) let prevIndex = 0 for (let i = 0, l = lines.length; i < l; i++) { lineToIndex[i] = prevIndex prevIndex += lines[i].length + 1 } input[lineToIndexCache] = lineToIndex return lineToIndex } class Input { get from() { return this.file || this.id } constructor(css, opts = {}) { if ( css === null || typeof css === 'undefined' || (typeof css === 'object' && !css.toString) ) { throw new Error(`PostCSS received ${css} instead of CSS string`) } this.css = css.toString() if (this.css[0] === '\uFEFF' || this.css[0] === '\uFFFE') { this.hasBOM = true this.css = this.css.slice(1) } else { this.hasBOM = false } this.document = this.css if (opts.document) this.document = opts.document.toString() if (opts.from) { if ( !pathAvailable || /^\w+:\/\//.test(opts.from) || isAbsolute(opts.from) ) { this.file = opts.from } else { this.file = resolve(opts.from) } } if (pathAvailable && sourceMapAvailable) { let map = new PreviousMap(this.css, opts) if (map.text) { this.map = map let file = map.consumer().file if (!this.file && file) this.file = this.mapResolve(file) } } if (!this.file) { this.id = '' } if (this.map) this.map.file = this.from } error(message, line, column, opts = {}) { let endColumn, endLine, endOffset, offset, result if (line && typeof line === 'object') { let start = line let end = column if (typeof start.offset === 'number') { offset = start.offset let pos = this.fromOffset(offset) line = pos.line column = pos.col } else { line = start.line column = start.column offset = this.fromLineAndColumn(line, column) } if (typeof end.offset === 'number') { endOffset = end.offset let pos = this.fromOffset(endOffset) endLine = pos.line endColumn = pos.col } else { endLine = end.line endColumn = end.column endOffset = this.fromLineAndColumn(end.line, end.column) } } else if (!column) { offset = line let pos = this.fromOffset(offset) line = pos.line column = pos.col } else { offset = this.fromLineAndColumn(line, column) } let origin = this.origin(line, column, endLine, endColumn) if (origin) { result = new CssSyntaxError( message, origin.endLine === undefined ? origin.line : { column: origin.column, line: origin.line }, origin.endLine === undefined ? origin.column : { column: origin.endColumn, line: origin.endLine }, origin.source, origin.file, opts.plugin ) } else { result = new CssSyntaxError( message, endLine === undefined ? line : { column, line }, endLine === undefined ? column : { column: endColumn, line: endLine }, this.css, this.file, opts.plugin ) } result.input = { column, endColumn, endLine, endOffset, line, offset, source: this.css } if (this.file) { if (pathToFileURL) { result.input.url = pathToFileURL(this.file).toString() } result.input.file = this.file } return result } fromLineAndColumn(line, column) { let lineToIndex = getLineToIndex(this) let index = lineToIndex[line - 1] return index + column - 1 } fromOffset(offset) { let lineToIndex = getLineToIndex(this) let lastLine = lineToIndex[lineToIndex.length - 1] let min = 0 if (offset >= lastLine) { min = lineToIndex.length - 1 } else { let max = lineToIndex.length - 2 let mid while (min < max) { mid = min + ((max - min) >> 1) if (offset < lineToIndex[mid]) { max = mid - 1 } else if (offset >= lineToIndex[mid + 1]) { min = mid + 1 } else { min = mid break } } } return { col: offset - lineToIndex[min] + 1, line: min + 1 } } mapResolve(file) { if (/^\w+:\/\//.test(file)) { return file } return resolve(this.map.consumer().sourceRoot || this.map.root || '.', file) } origin(line, column, endLine, endColumn) { if (!this.map) return false let consumer = this.map.consumer() let from = consumer.originalPositionFor({ column, line }) if (!from.source) return false let to if (typeof endLine === 'number') { to = consumer.originalPositionFor({ column: endColumn, line: endLine }) } let fromUrl if (isAbsolute(from.source)) { fromUrl = pathToFileURL(from.source) } else { fromUrl = new URL( from.source, this.map.consumer().sourceRoot || pathToFileURL(this.map.mapFile) ) } let result = { column: from.column, endColumn: to && to.column, endLine: to && to.line, line: from.line, url: fromUrl.toString() } if (fromUrl.protocol === 'file:') { if (fileURLToPath) { result.file = fileURLToPath(fromUrl) } else { /* c8 ignore next 2 */ throw new Error(`file: protocol is not available in this PostCSS build`) } } let source = consumer.sourceContentFor(from.source) if (source) result.source = source return result } toJSON() { let json = {} for (let name of ['hasBOM', 'css', 'file', 'id']) { if (this[name] != null) { json[name] = this[name] } } if (this.map) { json.map = { ...this.map } if (json.map.consumerCache) { json.map.consumerCache = undefined } } return json } } module.exports = Input Input.default = Input if (terminalHighlight && terminalHighlight.registerInput) { terminalHighlight.registerInput(Input) } postcss-8.5.6/lib/lazy-result.d.ts000066400000000000000000000116171502402132100170570ustar00rootroot00000000000000import Document from './document.js' import { SourceMap } from './postcss.js' import Processor from './processor.js' import Result, { Message, ResultOptions } from './result.js' import Root from './root.js' import Warning from './warning.js' declare namespace LazyResult { // eslint-disable-next-line @typescript-eslint/no-use-before-define export { LazyResult_ as default } } /** * A Promise proxy for the result of PostCSS transformations. * * A `LazyResult` instance is returned by `Processor#process`. * * ```js * const lazy = postcss([autoprefixer]).process(css) * ``` */ declare class LazyResult_ implements PromiseLike> { /** * Processes input CSS through synchronous and asynchronous plugins * and calls onRejected for each error thrown in any plugin. * * It implements standard Promise API. * * ```js * postcss([autoprefixer]).process(css).then(result => { * console.log(result.css) * }).catch(error => { * console.error(error) * }) * ``` */ catch: Promise>['catch'] /** * Processes input CSS through synchronous and asynchronous plugins * and calls onFinally on any error or when all plugins will finish work. * * It implements standard Promise API. * * ```js * postcss([autoprefixer]).process(css).finally(() => { * console.log('processing ended') * }) * ``` */ finally: Promise>['finally'] /** * Processes input CSS through synchronous and asynchronous plugins * and calls `onFulfilled` with a Result instance. If a plugin throws * an error, the `onRejected` callback will be executed. * * It implements standard Promise API. * * ```js * postcss([autoprefixer]).process(css, { from: cssPath }).then(result => { * console.log(result.css) * }) * ``` */ then: Promise>['then'] /** * An alias for the `css` property. Use it with syntaxes * that generate non-CSS output. * * This property will only work with synchronous plugins. * If the processor contains any asynchronous plugins * it will throw an error. * * PostCSS runners should always use `LazyResult#then`. */ get content(): string /** * Processes input CSS through synchronous plugins, converts `Root` * to a CSS string and returns `Result#css`. * * This property will only work with synchronous plugins. * If the processor contains any asynchronous plugins * it will throw an error. * * PostCSS runners should always use `LazyResult#then`. */ get css(): string /** * Processes input CSS through synchronous plugins * and returns `Result#map`. * * This property will only work with synchronous plugins. * If the processor contains any asynchronous plugins * it will throw an error. * * PostCSS runners should always use `LazyResult#then`. */ get map(): SourceMap /** * Processes input CSS through synchronous plugins * and returns `Result#messages`. * * This property will only work with synchronous plugins. If the processor * contains any asynchronous plugins it will throw an error. * * PostCSS runners should always use `LazyResult#then`. */ get messages(): Message[] /** * Options from the `Processor#process` call. */ get opts(): ResultOptions /** * Returns a `Processor` instance, which will be used * for CSS transformations. */ get processor(): Processor /** * Processes input CSS through synchronous plugins * and returns `Result#root`. * * This property will only work with synchronous plugins. If the processor * contains any asynchronous plugins it will throw an error. * * PostCSS runners should always use `LazyResult#then`. */ get root(): RootNode /** * Returns the default string description of an object. * Required to implement the Promise interface. */ get [Symbol.toStringTag](): string /** * @param processor Processor used for this transformation. * @param css CSS to parse and transform. * @param opts Options from the `Processor#process` or `Root#toResult`. */ constructor(processor: Processor, css: string, opts: ResultOptions) /** * Run plugin in async way and return `Result`. * * @return Result with output content. */ async(): Promise> /** * Run plugin in sync way and return `Result`. * * @return Result with output content. */ sync(): Result /** * Alias for the `LazyResult#css` property. * * ```js * lazy + '' === lazy.css * ``` * * @return Output CSS. */ toString(): string /** * Processes input CSS through synchronous plugins * and calls `Result#warnings`. * * @return Warnings from plugins. */ warnings(): Warning[] } declare class LazyResult< RootNode = Document | Root > extends LazyResult_ {} export = LazyResult postcss-8.5.6/lib/lazy-result.js000066400000000000000000000323721502402132100166240ustar00rootroot00000000000000'use strict' let Container = require('./container') let Document = require('./document') let MapGenerator = require('./map-generator') let parse = require('./parse') let Result = require('./result') let Root = require('./root') let stringify = require('./stringify') let { isClean, my } = require('./symbols') let warnOnce = require('./warn-once') const TYPE_TO_CLASS_NAME = { atrule: 'AtRule', comment: 'Comment', decl: 'Declaration', document: 'Document', root: 'Root', rule: 'Rule' } const PLUGIN_PROPS = { AtRule: true, AtRuleExit: true, Comment: true, CommentExit: true, Declaration: true, DeclarationExit: true, Document: true, DocumentExit: true, Once: true, OnceExit: true, postcssPlugin: true, prepare: true, Root: true, RootExit: true, Rule: true, RuleExit: true } const NOT_VISITORS = { Once: true, postcssPlugin: true, prepare: true } const CHILDREN = 0 function isPromise(obj) { return typeof obj === 'object' && typeof obj.then === 'function' } function getEvents(node) { let key = false let type = TYPE_TO_CLASS_NAME[node.type] if (node.type === 'decl') { key = node.prop.toLowerCase() } else if (node.type === 'atrule') { key = node.name.toLowerCase() } if (key && node.append) { return [ type, type + '-' + key, CHILDREN, type + 'Exit', type + 'Exit-' + key ] } else if (key) { return [type, type + '-' + key, type + 'Exit', type + 'Exit-' + key] } else if (node.append) { return [type, CHILDREN, type + 'Exit'] } else { return [type, type + 'Exit'] } } function toStack(node) { let events if (node.type === 'document') { events = ['Document', CHILDREN, 'DocumentExit'] } else if (node.type === 'root') { events = ['Root', CHILDREN, 'RootExit'] } else { events = getEvents(node) } return { eventIndex: 0, events, iterator: 0, node, visitorIndex: 0, visitors: [] } } function cleanMarks(node) { node[isClean] = false if (node.nodes) node.nodes.forEach(i => cleanMarks(i)) return node } let postcss = {} class LazyResult { get content() { return this.stringify().content } get css() { return this.stringify().css } get map() { return this.stringify().map } get messages() { return this.sync().messages } get opts() { return this.result.opts } get processor() { return this.result.processor } get root() { return this.sync().root } get [Symbol.toStringTag]() { return 'LazyResult' } constructor(processor, css, opts) { this.stringified = false this.processed = false let root if ( typeof css === 'object' && css !== null && (css.type === 'root' || css.type === 'document') ) { root = cleanMarks(css) } else if (css instanceof LazyResult || css instanceof Result) { root = cleanMarks(css.root) if (css.map) { if (typeof opts.map === 'undefined') opts.map = {} if (!opts.map.inline) opts.map.inline = false opts.map.prev = css.map } } else { let parser = parse if (opts.syntax) parser = opts.syntax.parse if (opts.parser) parser = opts.parser if (parser.parse) parser = parser.parse try { root = parser(css, opts) } catch (error) { this.processed = true this.error = error } if (root && !root[my]) { /* c8 ignore next 2 */ Container.rebuild(root) } } this.result = new Result(processor, root, opts) this.helpers = { ...postcss, postcss, result: this.result } this.plugins = this.processor.plugins.map(plugin => { if (typeof plugin === 'object' && plugin.prepare) { return { ...plugin, ...plugin.prepare(this.result) } } else { return plugin } }) } async() { if (this.error) return Promise.reject(this.error) if (this.processed) return Promise.resolve(this.result) if (!this.processing) { this.processing = this.runAsync() } return this.processing } catch(onRejected) { return this.async().catch(onRejected) } finally(onFinally) { return this.async().then(onFinally, onFinally) } getAsyncError() { throw new Error('Use process(css).then(cb) to work with async plugins') } handleError(error, node) { let plugin = this.result.lastPlugin try { if (node) node.addToError(error) this.error = error if (error.name === 'CssSyntaxError' && !error.plugin) { error.plugin = plugin.postcssPlugin error.setMessage() } else if (plugin.postcssVersion) { if (process.env.NODE_ENV !== 'production') { let pluginName = plugin.postcssPlugin let pluginVer = plugin.postcssVersion let runtimeVer = this.result.processor.version let a = pluginVer.split('.') let b = runtimeVer.split('.') if (a[0] !== b[0] || parseInt(a[1]) > parseInt(b[1])) { // eslint-disable-next-line no-console console.error( 'Unknown error from PostCSS plugin. Your current PostCSS ' + 'version is ' + runtimeVer + ', but ' + pluginName + ' uses ' + pluginVer + '. Perhaps this is the source of the error below.' ) } } } } catch (err) { /* c8 ignore next 3 */ // eslint-disable-next-line no-console if (console && console.error) console.error(err) } return error } prepareVisitors() { this.listeners = {} let add = (plugin, type, cb) => { if (!this.listeners[type]) this.listeners[type] = [] this.listeners[type].push([plugin, cb]) } for (let plugin of this.plugins) { if (typeof plugin === 'object') { for (let event in plugin) { if (!PLUGIN_PROPS[event] && /^[A-Z]/.test(event)) { throw new Error( `Unknown event ${event} in ${plugin.postcssPlugin}. ` + `Try to update PostCSS (${this.processor.version} now).` ) } if (!NOT_VISITORS[event]) { if (typeof plugin[event] === 'object') { for (let filter in plugin[event]) { if (filter === '*') { add(plugin, event, plugin[event][filter]) } else { add( plugin, event + '-' + filter.toLowerCase(), plugin[event][filter] ) } } } else if (typeof plugin[event] === 'function') { add(plugin, event, plugin[event]) } } } } } this.hasListener = Object.keys(this.listeners).length > 0 } async runAsync() { this.plugin = 0 for (let i = 0; i < this.plugins.length; i++) { let plugin = this.plugins[i] let promise = this.runOnRoot(plugin) if (isPromise(promise)) { try { await promise } catch (error) { throw this.handleError(error) } } } this.prepareVisitors() if (this.hasListener) { let root = this.result.root while (!root[isClean]) { root[isClean] = true let stack = [toStack(root)] while (stack.length > 0) { let promise = this.visitTick(stack) if (isPromise(promise)) { try { await promise } catch (e) { let node = stack[stack.length - 1].node throw this.handleError(e, node) } } } } if (this.listeners.OnceExit) { for (let [plugin, visitor] of this.listeners.OnceExit) { this.result.lastPlugin = plugin try { if (root.type === 'document') { let roots = root.nodes.map(subRoot => visitor(subRoot, this.helpers) ) await Promise.all(roots) } else { await visitor(root, this.helpers) } } catch (e) { throw this.handleError(e) } } } } this.processed = true return this.stringify() } runOnRoot(plugin) { this.result.lastPlugin = plugin try { if (typeof plugin === 'object' && plugin.Once) { if (this.result.root.type === 'document') { let roots = this.result.root.nodes.map(root => plugin.Once(root, this.helpers) ) if (isPromise(roots[0])) { return Promise.all(roots) } return roots } return plugin.Once(this.result.root, this.helpers) } else if (typeof plugin === 'function') { return plugin(this.result.root, this.result) } } catch (error) { throw this.handleError(error) } } stringify() { if (this.error) throw this.error if (this.stringified) return this.result this.stringified = true this.sync() let opts = this.result.opts let str = stringify if (opts.syntax) str = opts.syntax.stringify if (opts.stringifier) str = opts.stringifier if (str.stringify) str = str.stringify let map = new MapGenerator(str, this.result.root, this.result.opts) let data = map.generate() this.result.css = data[0] this.result.map = data[1] return this.result } sync() { if (this.error) throw this.error if (this.processed) return this.result this.processed = true if (this.processing) { throw this.getAsyncError() } for (let plugin of this.plugins) { let promise = this.runOnRoot(plugin) if (isPromise(promise)) { throw this.getAsyncError() } } this.prepareVisitors() if (this.hasListener) { let root = this.result.root while (!root[isClean]) { root[isClean] = true this.walkSync(root) } if (this.listeners.OnceExit) { if (root.type === 'document') { for (let subRoot of root.nodes) { this.visitSync(this.listeners.OnceExit, subRoot) } } else { this.visitSync(this.listeners.OnceExit, root) } } } return this.result } then(onFulfilled, onRejected) { if (process.env.NODE_ENV !== 'production') { if (!('from' in this.opts)) { warnOnce( 'Without `from` option PostCSS could generate wrong source map ' + 'and will not find Browserslist config. Set it to CSS file path ' + 'or to `undefined` to prevent this warning.' ) } } return this.async().then(onFulfilled, onRejected) } toString() { return this.css } visitSync(visitors, node) { for (let [plugin, visitor] of visitors) { this.result.lastPlugin = plugin let promise try { promise = visitor(node, this.helpers) } catch (e) { throw this.handleError(e, node.proxyOf) } if (node.type !== 'root' && node.type !== 'document' && !node.parent) { return true } if (isPromise(promise)) { throw this.getAsyncError() } } } visitTick(stack) { let visit = stack[stack.length - 1] let { node, visitors } = visit if (node.type !== 'root' && node.type !== 'document' && !node.parent) { stack.pop() return } if (visitors.length > 0 && visit.visitorIndex < visitors.length) { let [plugin, visitor] = visitors[visit.visitorIndex] visit.visitorIndex += 1 if (visit.visitorIndex === visitors.length) { visit.visitors = [] visit.visitorIndex = 0 } this.result.lastPlugin = plugin try { return visitor(node.toProxy(), this.helpers) } catch (e) { throw this.handleError(e, node) } } if (visit.iterator !== 0) { let iterator = visit.iterator let child while ((child = node.nodes[node.indexes[iterator]])) { node.indexes[iterator] += 1 if (!child[isClean]) { child[isClean] = true stack.push(toStack(child)) return } } visit.iterator = 0 delete node.indexes[iterator] } let events = visit.events while (visit.eventIndex < events.length) { let event = events[visit.eventIndex] visit.eventIndex += 1 if (event === CHILDREN) { if (node.nodes && node.nodes.length) { node[isClean] = true visit.iterator = node.getIterator() } return } else if (this.listeners[event]) { visit.visitors = this.listeners[event] return } } stack.pop() } walkSync(node) { node[isClean] = true let events = getEvents(node) for (let event of events) { if (event === CHILDREN) { if (node.nodes) { node.each(child => { if (!child[isClean]) this.walkSync(child) }) } } else { let visitors = this.listeners[event] if (visitors) { if (this.visitSync(visitors, node.toProxy())) return } } } } warnings() { return this.sync().warnings() } } LazyResult.registerPostcss = dependant => { postcss = dependant } module.exports = LazyResult LazyResult.default = LazyResult Root.registerLazyResult(LazyResult) Document.registerLazyResult(LazyResult) postcss-8.5.6/lib/list.d.ts000066400000000000000000000026221502402132100155330ustar00rootroot00000000000000declare namespace list { type List = { /** * Safely splits comma-separated values (such as those for `transition-*` * and `background` properties). * * ```js * Once (root, { list }) { * list.comma('black, linear-gradient(white, black)') * //=> ['black', 'linear-gradient(white, black)'] * } * ``` * * @param str Comma-separated values. * @return Split values. */ comma(str: string): string[] default: List /** * Safely splits space-separated values (such as those for `background`, * `border-radius`, and other shorthand properties). * * ```js * Once (root, { list }) { * list.space('1px calc(10% + 1px)') //=> ['1px', 'calc(10% + 1px)'] * } * ``` * * @param str Space-separated values. * @return Split values. */ space(str: string): string[] /** * Safely splits values. * * ```js * Once (root, { list }) { * list.split('1px calc(10% + 1px)', [' ', '\n', '\t']) //=> ['1px', 'calc(10% + 1px)'] * } * ``` * * @param string separated values. * @param separators array of separators. * @param last boolean indicator. * @return Split values. */ split( string: string, separators: readonly string[], last: boolean ): string[] } } declare const list: list.List export = list postcss-8.5.6/lib/list.js000066400000000000000000000023131502402132100152740ustar00rootroot00000000000000'use strict' let list = { comma(string) { return list.split(string, [','], true) }, space(string) { let spaces = [' ', '\n', '\t'] return list.split(string, spaces) }, split(string, separators, last) { let array = [] let current = '' let split = false let func = 0 let inQuote = false let prevQuote = '' let escape = false for (let letter of string) { if (escape) { escape = false } else if (letter === '\\') { escape = true } else if (inQuote) { if (letter === prevQuote) { inQuote = false } } else if (letter === '"' || letter === "'") { inQuote = true prevQuote = letter } else if (letter === '(') { func += 1 } else if (letter === ')') { if (func > 0) func -= 1 } else if (func === 0) { if (separators.includes(letter)) split = true } if (split) { if (current !== '') array.push(current.trim()) current = '' split = false } else { current += letter } } if (last || current !== '') array.push(current.trim()) return array } } module.exports = list list.default = list postcss-8.5.6/lib/map-generator.js000066400000000000000000000227711502402132100170740ustar00rootroot00000000000000'use strict' let { dirname, relative, resolve, sep } = require('path') let { SourceMapConsumer, SourceMapGenerator } = require('source-map-js') let { pathToFileURL } = require('url') let Input = require('./input') let sourceMapAvailable = Boolean(SourceMapConsumer && SourceMapGenerator) let pathAvailable = Boolean(dirname && resolve && relative && sep) class MapGenerator { constructor(stringify, root, opts, cssString) { this.stringify = stringify this.mapOpts = opts.map || {} this.root = root this.opts = opts this.css = cssString this.originalCSS = cssString this.usesFileUrls = !this.mapOpts.from && this.mapOpts.absolute this.memoizedFileURLs = new Map() this.memoizedPaths = new Map() this.memoizedURLs = new Map() } addAnnotation() { let content if (this.isInline()) { content = 'data:application/json;base64,' + this.toBase64(this.map.toString()) } else if (typeof this.mapOpts.annotation === 'string') { content = this.mapOpts.annotation } else if (typeof this.mapOpts.annotation === 'function') { content = this.mapOpts.annotation(this.opts.to, this.root) } else { content = this.outputFile() + '.map' } let eol = '\n' if (this.css.includes('\r\n')) eol = '\r\n' this.css += eol + '/*# sourceMappingURL=' + content + ' */' } applyPrevMaps() { for (let prev of this.previous()) { let from = this.toUrl(this.path(prev.file)) let root = prev.root || dirname(prev.file) let map if (this.mapOpts.sourcesContent === false) { map = new SourceMapConsumer(prev.text) if (map.sourcesContent) { map.sourcesContent = null } } else { map = prev.consumer() } this.map.applySourceMap(map, from, this.toUrl(this.path(root))) } } clearAnnotation() { if (this.mapOpts.annotation === false) return if (this.root) { let node for (let i = this.root.nodes.length - 1; i >= 0; i--) { node = this.root.nodes[i] if (node.type !== 'comment') continue if (node.text.startsWith('# sourceMappingURL=')) { this.root.removeChild(i) } } } else if (this.css) { this.css = this.css.replace(/\n*\/\*#[\S\s]*?\*\/$/gm, '') } } generate() { this.clearAnnotation() if (pathAvailable && sourceMapAvailable && this.isMap()) { return this.generateMap() } else { let result = '' this.stringify(this.root, i => { result += i }) return [result] } } generateMap() { if (this.root) { this.generateString() } else if (this.previous().length === 1) { let prev = this.previous()[0].consumer() prev.file = this.outputFile() this.map = SourceMapGenerator.fromSourceMap(prev, { ignoreInvalidMapping: true }) } else { this.map = new SourceMapGenerator({ file: this.outputFile(), ignoreInvalidMapping: true }) this.map.addMapping({ generated: { column: 0, line: 1 }, original: { column: 0, line: 1 }, source: this.opts.from ? this.toUrl(this.path(this.opts.from)) : '' }) } if (this.isSourcesContent()) this.setSourcesContent() if (this.root && this.previous().length > 0) this.applyPrevMaps() if (this.isAnnotation()) this.addAnnotation() if (this.isInline()) { return [this.css] } else { return [this.css, this.map] } } generateString() { this.css = '' this.map = new SourceMapGenerator({ file: this.outputFile(), ignoreInvalidMapping: true }) let line = 1 let column = 1 let noSource = '' let mapping = { generated: { column: 0, line: 0 }, original: { column: 0, line: 0 }, source: '' } let last, lines this.stringify(this.root, (str, node, type) => { this.css += str if (node && type !== 'end') { mapping.generated.line = line mapping.generated.column = column - 1 if (node.source && node.source.start) { mapping.source = this.sourcePath(node) mapping.original.line = node.source.start.line mapping.original.column = node.source.start.column - 1 this.map.addMapping(mapping) } else { mapping.source = noSource mapping.original.line = 1 mapping.original.column = 0 this.map.addMapping(mapping) } } lines = str.match(/\n/g) if (lines) { line += lines.length last = str.lastIndexOf('\n') column = str.length - last } else { column += str.length } if (node && type !== 'start') { let p = node.parent || { raws: {} } let childless = node.type === 'decl' || (node.type === 'atrule' && !node.nodes) if (!childless || node !== p.last || p.raws.semicolon) { if (node.source && node.source.end) { mapping.source = this.sourcePath(node) mapping.original.line = node.source.end.line mapping.original.column = node.source.end.column - 1 mapping.generated.line = line mapping.generated.column = column - 2 this.map.addMapping(mapping) } else { mapping.source = noSource mapping.original.line = 1 mapping.original.column = 0 mapping.generated.line = line mapping.generated.column = column - 1 this.map.addMapping(mapping) } } } }) } isAnnotation() { if (this.isInline()) { return true } if (typeof this.mapOpts.annotation !== 'undefined') { return this.mapOpts.annotation } if (this.previous().length) { return this.previous().some(i => i.annotation) } return true } isInline() { if (typeof this.mapOpts.inline !== 'undefined') { return this.mapOpts.inline } let annotation = this.mapOpts.annotation if (typeof annotation !== 'undefined' && annotation !== true) { return false } if (this.previous().length) { return this.previous().some(i => i.inline) } return true } isMap() { if (typeof this.opts.map !== 'undefined') { return !!this.opts.map } return this.previous().length > 0 } isSourcesContent() { if (typeof this.mapOpts.sourcesContent !== 'undefined') { return this.mapOpts.sourcesContent } if (this.previous().length) { return this.previous().some(i => i.withContent()) } return true } outputFile() { if (this.opts.to) { return this.path(this.opts.to) } else if (this.opts.from) { return this.path(this.opts.from) } else { return 'to.css' } } path(file) { if (this.mapOpts.absolute) return file if (file.charCodeAt(0) === 60 /* `<` */) return file if (/^\w+:\/\//.test(file)) return file let cached = this.memoizedPaths.get(file) if (cached) return cached let from = this.opts.to ? dirname(this.opts.to) : '.' if (typeof this.mapOpts.annotation === 'string') { from = dirname(resolve(from, this.mapOpts.annotation)) } let path = relative(from, file) this.memoizedPaths.set(file, path) return path } previous() { if (!this.previousMaps) { this.previousMaps = [] if (this.root) { this.root.walk(node => { if (node.source && node.source.input.map) { let map = node.source.input.map if (!this.previousMaps.includes(map)) { this.previousMaps.push(map) } } }) } else { let input = new Input(this.originalCSS, this.opts) if (input.map) this.previousMaps.push(input.map) } } return this.previousMaps } setSourcesContent() { let already = {} if (this.root) { this.root.walk(node => { if (node.source) { let from = node.source.input.from if (from && !already[from]) { already[from] = true let fromUrl = this.usesFileUrls ? this.toFileUrl(from) : this.toUrl(this.path(from)) this.map.setSourceContent(fromUrl, node.source.input.css) } } }) } else if (this.css) { let from = this.opts.from ? this.toUrl(this.path(this.opts.from)) : '' this.map.setSourceContent(from, this.css) } } sourcePath(node) { if (this.mapOpts.from) { return this.toUrl(this.mapOpts.from) } else if (this.usesFileUrls) { return this.toFileUrl(node.source.input.from) } else { return this.toUrl(this.path(node.source.input.from)) } } toBase64(str) { if (Buffer) { return Buffer.from(str).toString('base64') } else { return window.btoa(unescape(encodeURIComponent(str))) } } toFileUrl(path) { let cached = this.memoizedFileURLs.get(path) if (cached) return cached if (pathToFileURL) { let fileURL = pathToFileURL(path).toString() this.memoizedFileURLs.set(path, fileURL) return fileURL } else { throw new Error( '`map.absolute` option is not available in this PostCSS build' ) } } toUrl(path) { let cached = this.memoizedURLs.get(path) if (cached) return cached if (sep === '\\') { path = path.replace(/\\/g, '/') } let url = encodeURI(path).replace(/[#?]/g, encodeURIComponent) this.memoizedURLs.set(path, url) return url } } module.exports = MapGenerator postcss-8.5.6/lib/no-work-result.d.ts000066400000000000000000000030451502402132100174700ustar00rootroot00000000000000import LazyResult from './lazy-result.js' import { SourceMap } from './postcss.js' import Processor from './processor.js' import Result, { Message, ResultOptions } from './result.js' import Root from './root.js' import Warning from './warning.js' declare namespace NoWorkResult { // eslint-disable-next-line @typescript-eslint/no-use-before-define export { NoWorkResult_ as default } } /** * A Promise proxy for the result of PostCSS transformations. * This lazy result instance doesn't parse css unless `NoWorkResult#root` or `Result#root` * are accessed. See the example below for details. * A `NoWork` instance is returned by `Processor#process` ONLY when no plugins defined. * * ```js * const noWorkResult = postcss().process(css) // No plugins are defined. * // CSS is not parsed * let root = noWorkResult.root // now css is parsed because we accessed the root * ``` */ declare class NoWorkResult_ implements LazyResult { catch: Promise>['catch'] finally: Promise>['finally'] then: Promise>['then'] get content(): string get css(): string get map(): SourceMap get messages(): Message[] get opts(): ResultOptions get processor(): Processor get root(): Root get [Symbol.toStringTag](): string constructor(processor: Processor, css: string, opts: ResultOptions) async(): Promise> sync(): Result toString(): string warnings(): Warning[] } declare class NoWorkResult extends NoWorkResult_ {} export = NoWorkResult postcss-8.5.6/lib/no-work-result.js000066400000000000000000000051001502402132100172260ustar00rootroot00000000000000'use strict' let MapGenerator = require('./map-generator') let parse = require('./parse') const Result = require('./result') let stringify = require('./stringify') let warnOnce = require('./warn-once') class NoWorkResult { get content() { return this.result.css } get css() { return this.result.css } get map() { return this.result.map } get messages() { return [] } get opts() { return this.result.opts } get processor() { return this.result.processor } get root() { if (this._root) { return this._root } let root let parser = parse try { root = parser(this._css, this._opts) } catch (error) { this.error = error } if (this.error) { throw this.error } else { this._root = root return root } } get [Symbol.toStringTag]() { return 'NoWorkResult' } constructor(processor, css, opts) { css = css.toString() this.stringified = false this._processor = processor this._css = css this._opts = opts this._map = undefined let root let str = stringify this.result = new Result(this._processor, root, this._opts) this.result.css = css let self = this Object.defineProperty(this.result, 'root', { get() { return self.root } }) let map = new MapGenerator(str, root, this._opts, css) if (map.isMap()) { let [generatedCSS, generatedMap] = map.generate() if (generatedCSS) { this.result.css = generatedCSS } if (generatedMap) { this.result.map = generatedMap } } else { map.clearAnnotation() this.result.css = map.css } } async() { if (this.error) return Promise.reject(this.error) return Promise.resolve(this.result) } catch(onRejected) { return this.async().catch(onRejected) } finally(onFinally) { return this.async().then(onFinally, onFinally) } sync() { if (this.error) throw this.error return this.result } then(onFulfilled, onRejected) { if (process.env.NODE_ENV !== 'production') { if (!('from' in this._opts)) { warnOnce( 'Without `from` option PostCSS could generate wrong source map ' + 'and will not find Browserslist config. Set it to CSS file path ' + 'or to `undefined` to prevent this warning.' ) } } return this.async().then(onFulfilled, onRejected) } toString() { return this._css } warnings() { return [] } } module.exports = NoWorkResult NoWorkResult.default = NoWorkResult postcss-8.5.6/lib/node.d.ts000066400000000000000000000346411502402132100155130ustar00rootroot00000000000000import AtRule = require('./at-rule.js') import { AtRuleProps } from './at-rule.js' import Comment, { CommentProps } from './comment.js' import Container, { NewChild } from './container.js' import CssSyntaxError from './css-syntax-error.js' import Declaration, { DeclarationProps } from './declaration.js' import Document from './document.js' import Input from './input.js' import { Stringifier, Syntax } from './postcss.js' import Result from './result.js' import Root from './root.js' import Rule, { RuleProps } from './rule.js' import Warning, { WarningOptions } from './warning.js' declare namespace Node { export type ChildNode = AtRule.default | Comment | Declaration | Rule export type AnyNode = | AtRule.default | Comment | Declaration | Document | Root | Rule export type ChildProps = | AtRuleProps | CommentProps | DeclarationProps | RuleProps export interface Position { /** * Source line in file. In contrast to `offset` it starts from 1. */ column: number /** * Source column in file. */ line: number /** * Source offset in file. It starts from 0. */ offset: number } export interface Range { /** * End position, exclusive. */ end: Position /** * Start position, inclusive. */ start: Position } /** * Source represents an interface for the {@link Node.source} property. */ export interface Source { /** * The inclusive ending position for the source * code of a node. * * However, `end.offset` of a non `Root` node is the exclusive position. * See https://github.com/postcss/postcss/pull/1879 for details. * * ```js * const root = postcss.parse('a { color: black }') * const a = root.first * const color = a.first * * // The offset of `Root` node is the inclusive position * css.source.end // { line: 1, column: 19, offset: 18 } * * // The offset of non `Root` node is the exclusive position * a.source.end // { line: 1, column: 18, offset: 18 } * color.source.end // { line: 1, column: 16, offset: 16 } * ``` */ end?: Position /** * The source file from where a node has originated. */ input: Input /** * The inclusive starting position for the source * code of a node. */ start?: Position } /** * Interface represents an interface for an object received * as parameter by Node class constructor. */ export interface NodeProps { source?: Source } export interface NodeErrorOptions { /** * An ending index inside a node's string that should be highlighted as * source of error. */ endIndex?: number /** * An index inside a node's string that should be highlighted as source * of error. */ index?: number /** * Plugin name that created this error. PostCSS will set it automatically. */ plugin?: string /** * A word inside a node's string, that should be highlighted as source * of error. */ word?: string } // eslint-disable-next-line @typescript-eslint/no-shadow class Node extends Node_ {} export { Node as default } } /** * It represents an abstract class that handles common * methods for other CSS abstract syntax tree nodes. * * Any node that represents CSS selector or value should * not extend the `Node` class. */ declare abstract class Node_ { /** * It represents parent of the current node. * * ```js * root.nodes[0].parent === root //=> true * ``` */ parent: Container | Document | undefined /** * It represents unnecessary whitespace and characters present * in the css source code. * * Information to generate byte-to-byte equal node string as it was * in the origin input. * * The properties of the raws object are decided by parser, * the default parser uses the following properties: * * * `before`: the space symbols before the node. It also stores `*` * and `_` symbols before the declaration (IE hack). * * `after`: the space symbols after the last child of the node * to the end of the node. * * `between`: the symbols between the property and value * for declarations, selector and `{` for rules, or last parameter * and `{` for at-rules. * * `semicolon`: contains true if the last child has * an (optional) semicolon. * * `afterName`: the space between the at-rule name and its parameters. * * `left`: the space symbols between `/*` and the comment’s text. * * `right`: the space symbols between the comment’s text * and */. * - `important`: the content of the important statement, * if it is not just `!important`. * * PostCSS filters out the comments inside selectors, declaration values * and at-rule parameters but it stores the origin content in raws. * * ```js * const root = postcss.parse('a {\n color:black\n}') * root.first.first.raws //=> { before: '\n ', between: ':' } * ``` */ raws: any /** * It represents information related to origin of a node and is required * for generating source maps. * * The nodes that are created manually using the public APIs * provided by PostCSS will have `source` undefined and * will be absent in the source map. * * For this reason, the plugin developer should consider * duplicating nodes as the duplicate node will have the * same source as the original node by default or assign * source to a node created manually. * * ```js * decl.source.input.from //=> '/home/ai/source.css' * decl.source.start //=> { line: 10, column: 2 } * decl.source.end //=> { line: 10, column: 12 } * ``` * * ```js * // Incorrect method, source not specified! * const prefixed = postcss.decl({ * prop: '-moz-' + decl.prop, * value: decl.value * }) * * // Correct method, source is inherited when duplicating. * const prefixed = decl.clone({ * prop: '-moz-' + decl.prop * }) * ``` * * ```js * if (atrule.name === 'add-link') { * const rule = postcss.rule({ * selector: 'a', * source: atrule.source * }) * * atrule.parent.insertBefore(atrule, rule) * } * ``` */ source?: Node.Source /** * It represents type of a node in * an abstract syntax tree. * * A type of node helps in identification of a node * and perform operation based on it's type. * * ```js * const declaration = new Declaration({ * prop: 'color', * value: 'black' * }) * * declaration.type //=> 'decl' * ``` */ type: string constructor(defaults?: object) /** * Insert new node after current node to current node’s parent. * * Just alias for `node.parent.insertAfter(node, add)`. * * ```js * decl.after('color: black') * ``` * * @param newNode New node. * @return This node for methods chain. */ after( newNode: Node | Node.ChildProps | readonly Node[] | string | undefined ): this /** * It assigns properties to an existing node instance. * * ```js * decl.assign({ prop: 'word-wrap', value: 'break-word' }) * ``` * * @param overrides New properties to override the node. * * @return `this` for method chaining. */ assign(overrides: object): this /** * Insert new node before current node to current node’s parent. * * Just alias for `node.parent.insertBefore(node, add)`. * * ```js * decl.before('content: ""') * ``` * * @param newNode New node. * @return This node for methods chain. */ before( newNode: Node | Node.ChildProps | readonly Node[] | string | undefined ): this /** * Clear the code style properties for the node and its children. * * ```js * node.raws.before //=> ' ' * node.cleanRaws() * node.raws.before //=> undefined * ``` * * @param keepBetween Keep the `raws.between` symbols. */ cleanRaws(keepBetween?: boolean): void /** * It creates clone of an existing node, which includes all the properties * and their values, that includes `raws` but not `type`. * * ```js * decl.raws.before //=> "\n " * const cloned = decl.clone({ prop: '-moz-' + decl.prop }) * cloned.raws.before //=> "\n " * cloned.toString() //=> -moz-transform: scale(0) * ``` * * @param overrides New properties to override in the clone. * * @return Duplicate of the node instance. */ clone(overrides?: object): this /** * Shortcut to clone the node and insert the resulting cloned node * after the current node. * * @param overrides New properties to override in the clone. * @return New node. */ cloneAfter(overrides?: object): this /** * Shortcut to clone the node and insert the resulting cloned node * before the current node. * * ```js * decl.cloneBefore({ prop: '-moz-' + decl.prop }) * ``` * * @param overrides Mew properties to override in the clone. * * @return New node */ cloneBefore(overrides?: object): this /** * It creates an instance of the class `CssSyntaxError` and parameters passed * to this method are assigned to the error instance. * * The error instance will have description for the * error, original position of the node in the * source, showing line and column number. * * If any previous map is present, it would be used * to get original position of the source. * * The Previous Map here is referred to the source map * generated by previous compilation, example: Less, * Stylus and Sass. * * This method returns the error instance instead of * throwing it. * * ```js * if (!variables[name]) { * throw decl.error(`Unknown variable ${name}`, { word: name }) * // CssSyntaxError: postcss-vars:a.sass:4:3: Unknown variable $black * // color: $black * // a * // ^ * // background: white * } * ``` * * @param message Description for the error instance. * @param options Options for the error instance. * * @return Error instance is returned. */ error(message: string, options?: Node.NodeErrorOptions): CssSyntaxError /** * Returns the next child of the node’s parent. * Returns `undefined` if the current node is the last child. * * ```js * if (comment.text === 'delete next') { * const next = comment.next() * if (next) { * next.remove() * } * } * ``` * * @return Next node. */ next(): Node.ChildNode | undefined /** * Get the position for a word or an index inside the node. * * @param opts Options. * @return Position. */ positionBy(opts?: Pick): Node.Position /** * Convert string index to line/column. * * @param index The symbol number in the node’s string. * @return Symbol position in file. */ positionInside(index: number): Node.Position /** * Returns the previous child of the node’s parent. * Returns `undefined` if the current node is the first child. * * ```js * const annotation = decl.prev() * if (annotation.type === 'comment') { * readAnnotation(annotation.text) * } * ``` * * @return Previous node. */ prev(): Node.ChildNode | undefined /** * Get the range for a word or start and end index inside the node. * The start index is inclusive; the end index is exclusive. * * @param opts Options. * @return Range. */ rangeBy( opts?: Pick ): Node.Range /** * Returns a `raws` value. If the node is missing * the code style property (because the node was manually built or cloned), * PostCSS will try to autodetect the code style property by looking * at other nodes in the tree. * * ```js * const root = postcss.parse('a { background: white }') * root.nodes[0].append({ prop: 'color', value: 'black' }) * root.nodes[0].nodes[1].raws.before //=> undefined * root.nodes[0].nodes[1].raw('before') //=> ' ' * ``` * * @param prop Name of code style property. * @param defaultType Name of default value, it can be missed * if the value is the same as prop. * @return {string} Code style value. */ raw(prop: string, defaultType?: string): string /** * It removes the node from its parent and deletes its parent property. * * ```js * if (decl.prop.match(/^-webkit-/)) { * decl.remove() * } * ``` * * @return `this` for method chaining. */ remove(): this /** * Inserts node(s) before the current node and removes the current node. * * ```js * AtRule: { * mixin: atrule => { * atrule.replaceWith(mixinRules[atrule.params]) * } * } * ``` * * @param nodes Mode(s) to replace current one. * @return Current node to methods chain. */ replaceWith(...nodes: NewChild[]): this /** * Finds the Root instance of the node’s tree. * * ```js * root.nodes[0].nodes[0].root() === root * ``` * * @return Root parent. */ root(): Root /** * Fix circular links on `JSON.stringify()`. * * @return Cleaned object. */ toJSON(): object /** * It compiles the node to browser readable cascading style sheets string * depending on it's type. * * ```js * new Rule({ selector: 'a' }).toString() //=> "a {}" * ``` * * @param stringifier A syntax to use in string generation. * @return CSS string of this node. */ toString(stringifier?: Stringifier | Syntax): string /** * It is a wrapper for {@link Result#warn}, providing convenient * way of generating warnings. * * ```js * Declaration: { * bad: (decl, { result }) => { * decl.warn(result, 'Deprecated property: bad') * } * } * ``` * * @param result The `Result` instance that will receive the warning. * @param message Description for the warning. * @param options Options for the warning. * * @return `Warning` instance is returned */ warn(result: Result, message: string, options?: WarningOptions): Warning /** * If this node isn't already dirty, marks it and its ancestors as such. This * indicates to the LazyResult processor that the {@link Root} has been * modified by the current plugin and may need to be processed again by other * plugins. */ protected markDirty(): void } declare class Node extends Node_ {} export = Node postcss-8.5.6/lib/node.js000066400000000000000000000247371502402132100152640ustar00rootroot00000000000000'use strict' let CssSyntaxError = require('./css-syntax-error') let Stringifier = require('./stringifier') let stringify = require('./stringify') let { isClean, my } = require('./symbols') function cloneNode(obj, parent) { let cloned = new obj.constructor() for (let i in obj) { if (!Object.prototype.hasOwnProperty.call(obj, i)) { /* c8 ignore next 2 */ continue } if (i === 'proxyCache') continue let value = obj[i] let type = typeof value if (i === 'parent' && type === 'object') { if (parent) cloned[i] = parent } else if (i === 'source') { cloned[i] = value } else if (Array.isArray(value)) { cloned[i] = value.map(j => cloneNode(j, cloned)) } else { if (type === 'object' && value !== null) value = cloneNode(value) cloned[i] = value } } return cloned } function sourceOffset(inputCSS, position) { // Not all custom syntaxes support `offset` in `source.start` and `source.end` if (position && typeof position.offset !== 'undefined') { return position.offset } let column = 1 let line = 1 let offset = 0 for (let i = 0; i < inputCSS.length; i++) { if (line === position.line && column === position.column) { offset = i break } if (inputCSS[i] === '\n') { column = 1 line += 1 } else { column += 1 } } return offset } class Node { get proxyOf() { return this } constructor(defaults = {}) { this.raws = {} this[isClean] = false this[my] = true for (let name in defaults) { if (name === 'nodes') { this.nodes = [] for (let node of defaults[name]) { if (typeof node.clone === 'function') { this.append(node.clone()) } else { this.append(node) } } } else { this[name] = defaults[name] } } } addToError(error) { error.postcssNode = this if (error.stack && this.source && /\n\s{4}at /.test(error.stack)) { let s = this.source error.stack = error.stack.replace( /\n\s{4}at /, `$&${s.input.from}:${s.start.line}:${s.start.column}$&` ) } return error } after(add) { this.parent.insertAfter(this, add) return this } assign(overrides = {}) { for (let name in overrides) { this[name] = overrides[name] } return this } before(add) { this.parent.insertBefore(this, add) return this } cleanRaws(keepBetween) { delete this.raws.before delete this.raws.after if (!keepBetween) delete this.raws.between } clone(overrides = {}) { let cloned = cloneNode(this) for (let name in overrides) { cloned[name] = overrides[name] } return cloned } cloneAfter(overrides = {}) { let cloned = this.clone(overrides) this.parent.insertAfter(this, cloned) return cloned } cloneBefore(overrides = {}) { let cloned = this.clone(overrides) this.parent.insertBefore(this, cloned) return cloned } error(message, opts = {}) { if (this.source) { let { end, start } = this.rangeBy(opts) return this.source.input.error( message, { column: start.column, line: start.line }, { column: end.column, line: end.line }, opts ) } return new CssSyntaxError(message) } getProxyProcessor() { return { get(node, prop) { if (prop === 'proxyOf') { return node } else if (prop === 'root') { return () => node.root().toProxy() } else { return node[prop] } }, set(node, prop, value) { if (node[prop] === value) return true node[prop] = value if ( prop === 'prop' || prop === 'value' || prop === 'name' || prop === 'params' || prop === 'important' || /* c8 ignore next */ prop === 'text' ) { node.markDirty() } return true } } } /* c8 ignore next 3 */ markClean() { this[isClean] = true } markDirty() { if (this[isClean]) { this[isClean] = false let next = this while ((next = next.parent)) { next[isClean] = false } } } next() { if (!this.parent) return undefined let index = this.parent.index(this) return this.parent.nodes[index + 1] } positionBy(opts = {}) { let pos = this.source.start if (opts.index) { pos = this.positionInside(opts.index) } else if (opts.word) { let inputString = 'document' in this.source.input ? this.source.input.document : this.source.input.css let stringRepresentation = inputString.slice( sourceOffset(inputString, this.source.start), sourceOffset(inputString, this.source.end) ) let index = stringRepresentation.indexOf(opts.word) if (index !== -1) pos = this.positionInside(index) } return pos } positionInside(index) { let column = this.source.start.column let line = this.source.start.line let inputString = 'document' in this.source.input ? this.source.input.document : this.source.input.css let offset = sourceOffset(inputString, this.source.start) let end = offset + index for (let i = offset; i < end; i++) { if (inputString[i] === '\n') { column = 1 line += 1 } else { column += 1 } } return { column, line, offset: end } } prev() { if (!this.parent) return undefined let index = this.parent.index(this) return this.parent.nodes[index - 1] } rangeBy(opts = {}) { let inputString = 'document' in this.source.input ? this.source.input.document : this.source.input.css let start = { column: this.source.start.column, line: this.source.start.line, offset: sourceOffset(inputString, this.source.start) } let end = this.source.end ? { column: this.source.end.column + 1, line: this.source.end.line, offset: typeof this.source.end.offset === 'number' ? // `source.end.offset` is exclusive, so we don't need to add 1 this.source.end.offset : // Since line/column in this.source.end is inclusive, // the `sourceOffset(... , this.source.end)` returns an inclusive offset. // So, we add 1 to convert it to exclusive. sourceOffset(inputString, this.source.end) + 1 } : { column: start.column + 1, line: start.line, offset: start.offset + 1 } if (opts.word) { let stringRepresentation = inputString.slice( sourceOffset(inputString, this.source.start), sourceOffset(inputString, this.source.end) ) let index = stringRepresentation.indexOf(opts.word) if (index !== -1) { start = this.positionInside(index) end = this.positionInside(index + opts.word.length) } } else { if (opts.start) { start = { column: opts.start.column, line: opts.start.line, offset: sourceOffset(inputString, opts.start) } } else if (opts.index) { start = this.positionInside(opts.index) } if (opts.end) { end = { column: opts.end.column, line: opts.end.line, offset: sourceOffset(inputString, opts.end) } } else if (typeof opts.endIndex === 'number') { end = this.positionInside(opts.endIndex) } else if (opts.index) { end = this.positionInside(opts.index + 1) } } if ( end.line < start.line || (end.line === start.line && end.column <= start.column) ) { end = { column: start.column + 1, line: start.line, offset: start.offset + 1 } } return { end, start } } raw(prop, defaultType) { let str = new Stringifier() return str.raw(this, prop, defaultType) } remove() { if (this.parent) { this.parent.removeChild(this) } this.parent = undefined return this } replaceWith(...nodes) { if (this.parent) { let bookmark = this let foundSelf = false for (let node of nodes) { if (node === this) { foundSelf = true } else if (foundSelf) { this.parent.insertAfter(bookmark, node) bookmark = node } else { this.parent.insertBefore(bookmark, node) } } if (!foundSelf) { this.remove() } } return this } root() { let result = this while (result.parent && result.parent.type !== 'document') { result = result.parent } return result } toJSON(_, inputs) { let fixed = {} let emitInputs = inputs == null inputs = inputs || new Map() let inputsNextIndex = 0 for (let name in this) { if (!Object.prototype.hasOwnProperty.call(this, name)) { /* c8 ignore next 2 */ continue } if (name === 'parent' || name === 'proxyCache') continue let value = this[name] if (Array.isArray(value)) { fixed[name] = value.map(i => { if (typeof i === 'object' && i.toJSON) { return i.toJSON(null, inputs) } else { return i } }) } else if (typeof value === 'object' && value.toJSON) { fixed[name] = value.toJSON(null, inputs) } else if (name === 'source') { if (value == null) continue let inputId = inputs.get(value.input) if (inputId == null) { inputId = inputsNextIndex inputs.set(value.input, inputsNextIndex) inputsNextIndex++ } fixed[name] = { end: value.end, inputId, start: value.start } } else { fixed[name] = value } } if (emitInputs) { fixed.inputs = [...inputs.keys()].map(input => input.toJSON()) } return fixed } toProxy() { if (!this.proxyCache) { this.proxyCache = new Proxy(this, this.getProxyProcessor()) } return this.proxyCache } toString(stringifier = stringify) { if (stringifier.stringify) stringifier = stringifier.stringify let result = '' stringifier(this, i => { result += i }) return result } warn(result, text, opts = {}) { let data = { node: this } for (let i in opts) data[i] = opts[i] return result.warn(text, data) } } module.exports = Node Node.default = Node postcss-8.5.6/lib/parse.d.ts000066400000000000000000000002071502402132100156670ustar00rootroot00000000000000import { Parser } from './postcss.js' interface Parse extends Parser { default: Parse } declare const parse: Parse export = parse postcss-8.5.6/lib/parse.js000066400000000000000000000021731502402132100154370ustar00rootroot00000000000000'use strict' let Container = require('./container') let Input = require('./input') let Parser = require('./parser') function parse(css, opts) { let input = new Input(css, opts) let parser = new Parser(input) try { parser.parse() } catch (e) { if (process.env.NODE_ENV !== 'production') { if (e.name === 'CssSyntaxError' && opts && opts.from) { if (/\.scss$/i.test(opts.from)) { e.message += '\nYou tried to parse SCSS with ' + 'the standard CSS parser; ' + 'try again with the postcss-scss parser' } else if (/\.sass/i.test(opts.from)) { e.message += '\nYou tried to parse Sass with ' + 'the standard CSS parser; ' + 'try again with the postcss-sass parser' } else if (/\.less$/i.test(opts.from)) { e.message += '\nYou tried to parse Less with ' + 'the standard CSS parser; ' + 'try again with the postcss-less parser' } } } throw e } return parser.root } module.exports = parse parse.default = parse Container.registerParse(parse) postcss-8.5.6/lib/parser.js000066400000000000000000000350041502402132100156200ustar00rootroot00000000000000'use strict' let AtRule = require('./at-rule') let Comment = require('./comment') let Declaration = require('./declaration') let Root = require('./root') let Rule = require('./rule') let tokenizer = require('./tokenize') const SAFE_COMMENT_NEIGHBOR = { empty: true, space: true } function findLastWithPosition(tokens) { for (let i = tokens.length - 1; i >= 0; i--) { let token = tokens[i] let pos = token[3] || token[2] if (pos) return pos } } class Parser { constructor(input) { this.input = input this.root = new Root() this.current = this.root this.spaces = '' this.semicolon = false this.createTokenizer() this.root.source = { input, start: { column: 1, line: 1, offset: 0 } } } atrule(token) { let node = new AtRule() node.name = token[1].slice(1) if (node.name === '') { this.unnamedAtrule(node, token) } this.init(node, token[2]) let type let prev let shift let last = false let open = false let params = [] let brackets = [] while (!this.tokenizer.endOfFile()) { token = this.tokenizer.nextToken() type = token[0] if (type === '(' || type === '[') { brackets.push(type === '(' ? ')' : ']') } else if (type === '{' && brackets.length > 0) { brackets.push('}') } else if (type === brackets[brackets.length - 1]) { brackets.pop() } if (brackets.length === 0) { if (type === ';') { node.source.end = this.getPosition(token[2]) node.source.end.offset++ this.semicolon = true break } else if (type === '{') { open = true break } else if (type === '}') { if (params.length > 0) { shift = params.length - 1 prev = params[shift] while (prev && prev[0] === 'space') { prev = params[--shift] } if (prev) { node.source.end = this.getPosition(prev[3] || prev[2]) node.source.end.offset++ } } this.end(token) break } else { params.push(token) } } else { params.push(token) } if (this.tokenizer.endOfFile()) { last = true break } } node.raws.between = this.spacesAndCommentsFromEnd(params) if (params.length) { node.raws.afterName = this.spacesAndCommentsFromStart(params) this.raw(node, 'params', params) if (last) { token = params[params.length - 1] node.source.end = this.getPosition(token[3] || token[2]) node.source.end.offset++ this.spaces = node.raws.between node.raws.between = '' } } else { node.raws.afterName = '' node.params = '' } if (open) { node.nodes = [] this.current = node } } checkMissedSemicolon(tokens) { let colon = this.colon(tokens) if (colon === false) return let founded = 0 let token for (let j = colon - 1; j >= 0; j--) { token = tokens[j] if (token[0] !== 'space') { founded += 1 if (founded === 2) break } } // If the token is a word, e.g. `!important`, `red` or any other valid property's value. // Then we need to return the colon after that word token. [3] is the "end" colon of that word. // And because we need it after that one we do +1 to get the next one. throw this.input.error( 'Missed semicolon', token[0] === 'word' ? token[3] + 1 : token[2] ) } colon(tokens) { let brackets = 0 let prev, token, type for (let [i, element] of tokens.entries()) { token = element type = token[0] if (type === '(') { brackets += 1 } if (type === ')') { brackets -= 1 } if (brackets === 0 && type === ':') { if (!prev) { this.doubleColon(token) } else if (prev[0] === 'word' && prev[1] === 'progid') { continue } else { return i } } prev = token } return false } comment(token) { let node = new Comment() this.init(node, token[2]) node.source.end = this.getPosition(token[3] || token[2]) node.source.end.offset++ let text = token[1].slice(2, -2) if (/^\s*$/.test(text)) { node.text = '' node.raws.left = text node.raws.right = '' } else { let match = text.match(/^(\s*)([^]*\S)(\s*)$/) node.text = match[2] node.raws.left = match[1] node.raws.right = match[3] } } createTokenizer() { this.tokenizer = tokenizer(this.input) } decl(tokens, customProperty) { let node = new Declaration() this.init(node, tokens[0][2]) let last = tokens[tokens.length - 1] if (last[0] === ';') { this.semicolon = true tokens.pop() } node.source.end = this.getPosition( last[3] || last[2] || findLastWithPosition(tokens) ) node.source.end.offset++ while (tokens[0][0] !== 'word') { if (tokens.length === 1) this.unknownWord(tokens) node.raws.before += tokens.shift()[1] } node.source.start = this.getPosition(tokens[0][2]) node.prop = '' while (tokens.length) { let type = tokens[0][0] if (type === ':' || type === 'space' || type === 'comment') { break } node.prop += tokens.shift()[1] } node.raws.between = '' let token while (tokens.length) { token = tokens.shift() if (token[0] === ':') { node.raws.between += token[1] break } else { if (token[0] === 'word' && /\w/.test(token[1])) { this.unknownWord([token]) } node.raws.between += token[1] } } if (node.prop[0] === '_' || node.prop[0] === '*') { node.raws.before += node.prop[0] node.prop = node.prop.slice(1) } let firstSpaces = [] let next while (tokens.length) { next = tokens[0][0] if (next !== 'space' && next !== 'comment') break firstSpaces.push(tokens.shift()) } this.precheckMissedSemicolon(tokens) for (let i = tokens.length - 1; i >= 0; i--) { token = tokens[i] if (token[1].toLowerCase() === '!important') { node.important = true let string = this.stringFrom(tokens, i) string = this.spacesFromEnd(tokens) + string if (string !== ' !important') node.raws.important = string break } else if (token[1].toLowerCase() === 'important') { let cache = tokens.slice(0) let str = '' for (let j = i; j > 0; j--) { let type = cache[j][0] if (str.trim().startsWith('!') && type !== 'space') { break } str = cache.pop()[1] + str } if (str.trim().startsWith('!')) { node.important = true node.raws.important = str tokens = cache } } if (token[0] !== 'space' && token[0] !== 'comment') { break } } let hasWord = tokens.some(i => i[0] !== 'space' && i[0] !== 'comment') if (hasWord) { node.raws.between += firstSpaces.map(i => i[1]).join('') firstSpaces = [] } this.raw(node, 'value', firstSpaces.concat(tokens), customProperty) if (node.value.includes(':') && !customProperty) { this.checkMissedSemicolon(tokens) } } doubleColon(token) { throw this.input.error( 'Double colon', { offset: token[2] }, { offset: token[2] + token[1].length } ) } emptyRule(token) { let node = new Rule() this.init(node, token[2]) node.selector = '' node.raws.between = '' this.current = node } end(token) { if (this.current.nodes && this.current.nodes.length) { this.current.raws.semicolon = this.semicolon } this.semicolon = false this.current.raws.after = (this.current.raws.after || '') + this.spaces this.spaces = '' if (this.current.parent) { this.current.source.end = this.getPosition(token[2]) this.current.source.end.offset++ this.current = this.current.parent } else { this.unexpectedClose(token) } } endFile() { if (this.current.parent) this.unclosedBlock() if (this.current.nodes && this.current.nodes.length) { this.current.raws.semicolon = this.semicolon } this.current.raws.after = (this.current.raws.after || '') + this.spaces this.root.source.end = this.getPosition(this.tokenizer.position()) } freeSemicolon(token) { this.spaces += token[1] if (this.current.nodes) { let prev = this.current.nodes[this.current.nodes.length - 1] if (prev && prev.type === 'rule' && !prev.raws.ownSemicolon) { prev.raws.ownSemicolon = this.spaces this.spaces = '' prev.source.end = this.getPosition(token[2]) prev.source.end.offset += prev.raws.ownSemicolon.length } } } // Helpers getPosition(offset) { let pos = this.input.fromOffset(offset) return { column: pos.col, line: pos.line, offset } } init(node, offset) { this.current.push(node) node.source = { input: this.input, start: this.getPosition(offset) } node.raws.before = this.spaces this.spaces = '' if (node.type !== 'comment') this.semicolon = false } other(start) { let end = false let type = null let colon = false let bracket = null let brackets = [] let customProperty = start[1].startsWith('--') let tokens = [] let token = start while (token) { type = token[0] tokens.push(token) if (type === '(' || type === '[') { if (!bracket) bracket = token brackets.push(type === '(' ? ')' : ']') } else if (customProperty && colon && type === '{') { if (!bracket) bracket = token brackets.push('}') } else if (brackets.length === 0) { if (type === ';') { if (colon) { this.decl(tokens, customProperty) return } else { break } } else if (type === '{') { this.rule(tokens) return } else if (type === '}') { this.tokenizer.back(tokens.pop()) end = true break } else if (type === ':') { colon = true } } else if (type === brackets[brackets.length - 1]) { brackets.pop() if (brackets.length === 0) bracket = null } token = this.tokenizer.nextToken() } if (this.tokenizer.endOfFile()) end = true if (brackets.length > 0) this.unclosedBracket(bracket) if (end && colon) { if (!customProperty) { while (tokens.length) { token = tokens[tokens.length - 1][0] if (token !== 'space' && token !== 'comment') break this.tokenizer.back(tokens.pop()) } } this.decl(tokens, customProperty) } else { this.unknownWord(tokens) } } parse() { let token while (!this.tokenizer.endOfFile()) { token = this.tokenizer.nextToken() switch (token[0]) { case 'space': this.spaces += token[1] break case ';': this.freeSemicolon(token) break case '}': this.end(token) break case 'comment': this.comment(token) break case 'at-word': this.atrule(token) break case '{': this.emptyRule(token) break default: this.other(token) break } } this.endFile() } precheckMissedSemicolon(/* tokens */) { // Hook for Safe Parser } raw(node, prop, tokens, customProperty) { let token, type let length = tokens.length let value = '' let clean = true let next, prev for (let i = 0; i < length; i += 1) { token = tokens[i] type = token[0] if (type === 'space' && i === length - 1 && !customProperty) { clean = false } else if (type === 'comment') { prev = tokens[i - 1] ? tokens[i - 1][0] : 'empty' next = tokens[i + 1] ? tokens[i + 1][0] : 'empty' if (!SAFE_COMMENT_NEIGHBOR[prev] && !SAFE_COMMENT_NEIGHBOR[next]) { if (value.slice(-1) === ',') { clean = false } else { value += token[1] } } else { clean = false } } else { value += token[1] } } if (!clean) { let raw = tokens.reduce((all, i) => all + i[1], '') node.raws[prop] = { raw, value } } node[prop] = value } rule(tokens) { tokens.pop() let node = new Rule() this.init(node, tokens[0][2]) node.raws.between = this.spacesAndCommentsFromEnd(tokens) this.raw(node, 'selector', tokens) this.current = node } spacesAndCommentsFromEnd(tokens) { let lastTokenType let spaces = '' while (tokens.length) { lastTokenType = tokens[tokens.length - 1][0] if (lastTokenType !== 'space' && lastTokenType !== 'comment') break spaces = tokens.pop()[1] + spaces } return spaces } // Errors spacesAndCommentsFromStart(tokens) { let next let spaces = '' while (tokens.length) { next = tokens[0][0] if (next !== 'space' && next !== 'comment') break spaces += tokens.shift()[1] } return spaces } spacesFromEnd(tokens) { let lastTokenType let spaces = '' while (tokens.length) { lastTokenType = tokens[tokens.length - 1][0] if (lastTokenType !== 'space') break spaces = tokens.pop()[1] + spaces } return spaces } stringFrom(tokens, from) { let result = '' for (let i = from; i < tokens.length; i++) { result += tokens[i][1] } tokens.splice(from, tokens.length - from) return result } unclosedBlock() { let pos = this.current.source.start throw this.input.error('Unclosed block', pos.line, pos.column) } unclosedBracket(bracket) { throw this.input.error( 'Unclosed bracket', { offset: bracket[2] }, { offset: bracket[2] + 1 } ) } unexpectedClose(token) { throw this.input.error( 'Unexpected }', { offset: token[2] }, { offset: token[2] + 1 } ) } unknownWord(tokens) { throw this.input.error( 'Unknown word ' + tokens[0][1], { offset: tokens[0][2] }, { offset: tokens[0][2] + tokens[0][1].length } ) } unnamedAtrule(node, token) { throw this.input.error( 'At-rule without name', { offset: token[2] }, { offset: token[2] + token[1].length } ) } } module.exports = Parser postcss-8.5.6/lib/postcss.d.mts000066400000000000000000000020311502402132100164250ustar00rootroot00000000000000export { // Type-only exports AcceptedPlugin, AnyNode, atRule, AtRule, AtRuleProps, Builder, ChildNode, ChildProps, comment, Comment, CommentProps, Container, ContainerProps, CssSyntaxError, decl, Declaration, DeclarationProps, // postcss function / namespace default, document, Document, DocumentProps, FilePosition, fromJSON, Helpers, Input, JSONHydrator, // This is a class, but it’s not re-exported. That’s why it’s exported as type-only here. type LazyResult, list, Message, Node, NodeErrorOptions, NodeProps, OldPlugin, parse, Parser, // @ts-expect-error This value exists, but it’s untyped. plugin, Plugin, PluginCreator, Position, Postcss, ProcessOptions, Processor, Result, root, Root, RootProps, rule, Rule, RuleProps, Source, SourceMap, SourceMapOptions, Stringifier, // Value exports from postcss.mjs stringify, Syntax, TransformCallback, Transformer, Warning, WarningOptions } from './postcss.js' postcss-8.5.6/lib/postcss.d.ts000066400000000000000000000263051502402132100162620ustar00rootroot00000000000000import { RawSourceMap, SourceMapGenerator } from 'source-map-js' import AtRule, { AtRuleProps } from './at-rule.js' import Comment, { CommentProps } from './comment.js' import Container, { ContainerProps, NewChild } from './container.js' import CssSyntaxError from './css-syntax-error.js' import Declaration, { DeclarationProps } from './declaration.js' import Document, { DocumentProps } from './document.js' import Input, { FilePosition } from './input.js' import LazyResult from './lazy-result.js' import list from './list.js' import Node, { AnyNode, ChildNode, ChildProps, NodeErrorOptions, NodeProps, Position, Source } from './node.js' import Processor from './processor.js' import Result, { Message } from './result.js' import Root, { RootProps } from './root.js' import Rule, { RuleProps } from './rule.js' import Warning, { WarningOptions } from './warning.js' type DocumentProcessor = ( document: Document, helper: postcss.Helpers ) => Promise | void type RootProcessor = ( root: Root, helper: postcss.Helpers ) => Promise | void type DeclarationProcessor = ( decl: Declaration, helper: postcss.Helpers ) => Promise | void type RuleProcessor = ( rule: Rule, helper: postcss.Helpers ) => Promise | void type AtRuleProcessor = ( atRule: AtRule, helper: postcss.Helpers ) => Promise | void type CommentProcessor = ( comment: Comment, helper: postcss.Helpers ) => Promise | void interface Processors { /** * Will be called on all`AtRule` nodes. * * Will be called again on node or children changes. */ AtRule?: { [name: string]: AtRuleProcessor } | AtRuleProcessor /** * Will be called on all `AtRule` nodes, when all children will be processed. * * Will be called again on node or children changes. */ AtRuleExit?: { [name: string]: AtRuleProcessor } | AtRuleProcessor /** * Will be called on all `Comment` nodes. * * Will be called again on node or children changes. */ Comment?: CommentProcessor /** * Will be called on all `Comment` nodes after listeners * for `Comment` event. * * Will be called again on node or children changes. */ CommentExit?: CommentProcessor /** * Will be called on all `Declaration` nodes after listeners * for `Declaration` event. * * Will be called again on node or children changes. */ Declaration?: { [prop: string]: DeclarationProcessor } | DeclarationProcessor /** * Will be called on all `Declaration` nodes. * * Will be called again on node or children changes. */ DeclarationExit?: | { [prop: string]: DeclarationProcessor } | DeclarationProcessor /** * Will be called on `Document` node. * * Will be called again on children changes. */ Document?: DocumentProcessor /** * Will be called on `Document` node, when all children will be processed. * * Will be called again on children changes. */ DocumentExit?: DocumentProcessor /** * Will be called on `Root` node once. */ Once?: RootProcessor /** * Will be called on `Root` node once, when all children will be processed. */ OnceExit?: RootProcessor /** * Will be called on `Root` node. * * Will be called again on children changes. */ Root?: RootProcessor /** * Will be called on `Root` node, when all children will be processed. * * Will be called again on children changes. */ RootExit?: RootProcessor /** * Will be called on all `Rule` nodes. * * Will be called again on node or children changes. */ Rule?: RuleProcessor /** * Will be called on all `Rule` nodes, when all children will be processed. * * Will be called again on node or children changes. */ RuleExit?: RuleProcessor } declare namespace postcss { export { AnyNode, AtRule, AtRuleProps, ChildNode, ChildProps, Comment, CommentProps, Container, ContainerProps, CssSyntaxError, Declaration, DeclarationProps, Document, DocumentProps, FilePosition, Input, LazyResult, list, Message, NewChild, Node, NodeErrorOptions, NodeProps, Position, Processor, Result, Root, RootProps, Rule, RuleProps, Source, Warning, WarningOptions } export type SourceMap = { toJSON(): RawSourceMap } & SourceMapGenerator export type Helpers = { postcss: Postcss; result: Result } & Postcss export interface Plugin extends Processors { postcssPlugin: string prepare?: (result: Result) => Processors } export interface PluginCreator { (opts?: PluginOptions): Plugin | Processor postcss: true } export interface Transformer extends TransformCallback { postcssPlugin: string postcssVersion: string } export interface TransformCallback { (root: Root, result: Result): Promise | void } export interface OldPlugin extends Transformer { (opts?: T): Transformer postcss: Transformer } export type AcceptedPlugin = | { postcss: Processor | TransformCallback } | OldPlugin | Plugin | PluginCreator | Processor | TransformCallback export interface Parser { ( css: { toString(): string } | string, opts?: Pick ): RootNode } export interface Builder { (part: string, node?: AnyNode, type?: 'end' | 'start'): void } export interface Stringifier { (node: AnyNode, builder: Builder): void } export interface JSONHydrator { (data: object): Node (data: object[]): Node[] } export interface Syntax { /** * Function to generate AST by string. */ parse?: Parser /** * Class to generate string by AST. */ stringify?: Stringifier } export interface SourceMapOptions { /** * Use absolute path in generated source map. */ absolute?: boolean /** * Indicates that PostCSS should add annotation comments to the CSS. * By default, PostCSS will always add a comment with a path * to the source map. PostCSS will not add annotations to CSS files * that do not contain any comments. * * By default, PostCSS presumes that you want to save the source map as * `opts.to + '.map'` and will use this path in the annotation comment. * A different path can be set by providing a string value for annotation. * * If you have set `inline: true`, annotation cannot be disabled. */ annotation?: ((file: string, root: Root) => string) | boolean | string /** * Override `from` in map’s sources. */ from?: string /** * Indicates that the source map should be embedded in the output CSS * as a Base64-encoded comment. By default, it is `true`. * But if all previous maps are external, not inline, PostCSS will not embed * the map even if you do not set this option. * * If you have an inline source map, the result.map property will be empty, * as the source map will be contained within the text of `result.css`. */ inline?: boolean /** * Source map content from a previous processing step (e.g., Sass). * * PostCSS will try to read the previous source map * automatically (based on comments within the source CSS), but you can use * this option to identify it manually. * * If desired, you can omit the previous map with prev: `false`. */ prev?: ((file: string) => string) | boolean | object | string /** * Indicates that PostCSS should set the origin content (e.g., Sass source) * of the source map. By default, it is true. But if all previous maps do not * contain sources content, PostCSS will also leave it out even if you * do not set this option. */ sourcesContent?: boolean } export interface ProcessOptions { /** * Input file if it is not simple CSS file, but HTML with ' }) is(root.source?.input.css, 'a {} b {}') is(root.source?.input.document, '') let a = root.first as Rule // Offset the source location of `a` to mimic syntaxes like `postcss-html` a.source = { end: { column: 12, line: 1, offset: 12 }, input: a.source!.input, start: { column: 8, line: 1, offset: 7 } } equal(a.positionInside(0), { column: 8, line: 1, offset: 7 }) equal(a.positionInside(1), { column: 9, line: 1, offset: 8 }) }) test('positionBy() returns position', () => { let css = parse('a { one: X }') let a = css.first as Rule let one = a.first as Declaration equal(one.positionBy(), { column: 6, line: 1, offset: 5 }) equal(a.positionBy(), { column: 1, line: 1, offset: 0 }) }) test('positionBy() returns position after AST mutations', () => { let css = parse('a {\n\tone: 1;\n\ttwo: 2;}') let a = css.first as Rule let one = a.first as Declaration let two = one.next() as Declaration equal(a.positionBy(), { column: 1, line: 1, offset: 0 }) equal(two.positionBy(), { column: 2, line: 3, offset: 14 }) one.remove() equal(a.positionBy(), { column: 1, line: 1, offset: 0 }) equal(two.positionBy(), { column: 2, line: 3, offset: 14 }) }) test('positionBy() returns position', () => { let css = parse('a { one: X }') let a = css.first as Rule let one = a.first as Declaration equal(one.positionBy(), { column: 6, line: 1, offset: 5 }) equal(a.positionBy(), { column: 1, line: 1, offset: 0 }) }) test('positionBy() returns position after AST mutations', () => { let css = parse('a {\n\tone: 1;\n\ttwo: 2;}') let a = css.first as Rule let one = a.first as Declaration let two = one.next() as Declaration equal(a.positionBy(), { column: 1, line: 1, offset: 0 }) equal(two.positionBy(), { column: 2, line: 3, offset: 14 }) one.remove() equal(a.positionBy(), { column: 1, line: 1, offset: 0 }) equal(two.positionBy(), { column: 2, line: 3, offset: 14 }) }) test('positionBy() returns position for word', () => { let css = parse('a { one: X }') let a = css.first as Rule let one = a.first as Declaration equal(one.positionBy({ word: 'one' }), { column: 6, line: 1, offset: 5 }) equal(one.positionBy({ word: 'X' }), { column: 11, line: 1, offset: 10 }) equal(a.positionBy({ word: '}' }), { column: 14, line: 1, offset: 13 }) }) test('positionBy() returns position for word after AST mutations', () => { let css = parse('a {\n\tone: 1;\n\ttwo: 2;}') let a = css.first as Rule let one = a.first as Declaration let two = one.next() as Declaration equal(a.positionBy({ word: 'two' }), { column: 2, line: 3, offset: 14 }) equal(two.positionBy({ word: 'two' }), { column: 2, line: 3, offset: 14 }) one.remove() equal(a.positionBy({ word: 'two' }), { column: 2, line: 3, offset: 14 }) equal(two.positionBy({ word: 'two' }), { column: 2, line: 3, offset: 14 }) }) test('positionBy() returns position for index', () => { let css = parse('a { one: X }') let a = css.first as Rule let one = a.first as Declaration equal(one.positionBy({ index: 1 }), { column: 7, line: 1, offset: 6 }) }) test('positionBy() returns position for index after AST mutations', () => { let css = parse('a {\n\tone: 1;\n\ttwo: 2;}') let a = css.first as Rule let one = a.first as Declaration let two = one.next() as Declaration equal(a.positionBy({ index: 15 }), { column: 3, line: 3, offset: 15 }) equal(two.positionBy({ index: 1 }), { column: 3, line: 3, offset: 15 }) one.remove() equal(a.positionBy({ index: 15 }), { column: 3, line: 3, offset: 15 }) equal(two.positionBy({ index: 1 }), { column: 3, line: 3, offset: 15 }) }) test('positionBy() supports multi-root documents', () => { let root = parse('a {} b {}', { document: '' }) is(root.source?.input.css, 'a {} b {}') is(root.source?.input.document, '') let a = root.first as Rule // Offset the source location of `a` to mimic syntaxes like `postcss-html` a.source = { end: { column: 12, line: 1, offset: 12 }, input: a.source!.input, start: { column: 8, line: 1, offset: 7 } } // `offset` is present because the `0` index returns `source.start` equal(a.positionBy({ index: 0 }), { column: 8, line: 1, offset: 7 }) equal(a.positionBy({ index: 1 }), { column: 9, line: 1, offset: 8 }) equal(a.positionBy({ word: 'a' }), { column: 8, line: 1, offset: 7 }) }) test('rangeBy() returns range', () => { let css = parse('a { one: X }') let a = css.first as Rule let one = a.first as Declaration equal(one.rangeBy(), { end: { column: 12, line: 1, offset: 11 }, start: { column: 6, line: 1, offset: 5 } }) }) test('rangeBy() returns range when offsets are missing', () => { let css = parse('a { one: X }') let a = css.first as Rule let one = a.first as Declaration // @ts-expect-error Testing non-standard AST if (one.source?.start) delete one.source.start.offset // @ts-expect-error Testing non-standard AST if (one.source?.end) delete one.source.end.offset equal(one.rangeBy(), { end: { column: 12, line: 1, offset: 11 }, start: { column: 6, line: 1, offset: 5 } }) }) test('rangeBy() returns range for empty object even after AST mutations', () => { let css = parse('a {\n\tone: 1;\n\ttwo: 2;}') let a = css.first as Rule let one = a.first as Declaration let two = one.next() as Declaration equal(a.rangeBy(), { end: { column: 10, line: 3, offset: 22 }, start: { column: 1, line: 1, offset: 0 } }) equal(two.rangeBy(), { end: { column: 9, line: 3, offset: 21 }, start: { column: 2, line: 3, offset: 14 } }) one.remove() equal(a.rangeBy(), { end: { column: 10, line: 3, offset: 22 }, start: { column: 1, line: 1, offset: 0 } }) equal(two.rangeBy(), { end: { column: 9, line: 3, offset: 21 }, start: { column: 2, line: 3, offset: 14 } }) }) test('rangeBy() returns range', () => { let css = parse('a { one: X }') let a = css.first as Rule let one = a.first as Declaration equal(one.rangeBy(), { end: { column: 12, line: 1, offset: 11 }, start: { column: 6, line: 1, offset: 5 } }) }) test('rangeBy() returns range when offsets are missing', () => { let css = parse('a { one: X }') let a = css.first as Rule let one = a.first as Declaration // @ts-expect-error Testing non-standard AST if (one.source?.start) delete one.source.start.offset // @ts-expect-error Testing non-standard AST if (one.source?.end) delete one.source.end.offset equal(one.rangeBy(), { end: { column: 12, line: 1, offset: 11 }, start: { column: 6, line: 1, offset: 5 } }) }) test('rangeBy() returns range for empty object even after AST mutations', () => { let css = parse('a {\n\tone: 1;\n\ttwo: 2;}') let a = css.first as Rule let one = a.first as Declaration let two = one.next() as Declaration equal(a.rangeBy(), { end: { column: 10, line: 3, offset: 22 }, start: { column: 1, line: 1, offset: 0 } }) equal(two.rangeBy(), { end: { column: 9, line: 3, offset: 21 }, start: { column: 2, line: 3, offset: 14 } }) one.remove() equal(a.rangeBy(), { end: { column: 10, line: 3, offset: 22 }, start: { column: 1, line: 1, offset: 0 } }) equal(two.rangeBy(), { end: { column: 9, line: 3, offset: 21 }, start: { column: 2, line: 3, offset: 14 } }) }) test('rangeBy() returns range for word', () => { let css = parse('a { one: X }') let a = css.first as Rule let one = a.first as Declaration equal(one.rangeBy({ word: 'one' }), { end: { column: 9, line: 1, offset: 8 }, start: { column: 6, line: 1, offset: 5 } }) }) test('rangeBy() returns range for word when offsets are missing', () => { let css = parse('a { one: X }') let a = css.first as Rule let one = a.first as Declaration // @ts-expect-error Testing non-standard AST if (one.source?.start) delete one.source.start.offset // @ts-expect-error Testing non-standard AST if (one.source?.end) delete one.source.end.offset equal(one.rangeBy({ word: 'one' }), { end: { column: 9, line: 1, offset: 8 }, start: { column: 6, line: 1, offset: 5 } }) }) test('rangeBy() returns range for word even after AST mutations', () => { let css = parse('a {\n\tone: 1;\n\ttwo: 2;}') let a = css.first as Rule let one = a.first as Declaration let two = one.next() as Declaration equal(a.rangeBy({ word: 'two' }), { end: { column: 5, line: 3, offset: 17 }, start: { column: 2, line: 3, offset: 14 } }) equal(two.rangeBy({ word: 'two' }), { end: { column: 5, line: 3, offset: 17 }, start: { column: 2, line: 3, offset: 14 } }) one.remove() equal(a.rangeBy({ word: 'two' }), { end: { column: 5, line: 3, offset: 17 }, start: { column: 2, line: 3, offset: 14 } }) equal(two.rangeBy({ word: 'two' }), { end: { column: 5, line: 3, offset: 17 }, start: { column: 2, line: 3, offset: 14 } }) }) test('rangeBy() returns range for word even after AST mutations when offsets are missing', () => { let css = parse('a {\n\tone: 1;\n\ttwo: 2;}') let a = css.first as Rule let one = a.first as Declaration let two = one.next() as Declaration // @ts-expect-error Testing non-standard AST if (a.source?.start) delete a.source.start.offset // @ts-expect-error Testing non-standard AST if (a.source?.end) delete a.source.end.offset // @ts-expect-error Testing non-standard AST if (two.source?.start) delete two.source.start.offset // @ts-expect-error Testing non-standard AST if (two.source?.end) delete two.source.end.offset equal(a.rangeBy({ word: 'two' }), { end: { column: 5, line: 3, offset: 17, }, start: { column: 2, line: 3, offset: 14 } }) equal(two.rangeBy({ word: 'two' }), { end: { column: 5, line: 3, offset: 17, }, start: { column: 2, line: 3, offset: 14 } }) one.remove() equal(a.rangeBy({ word: 'two' }), { end: { column: 5, line: 3, offset: 17, }, start: { column: 2, line: 3, offset: 14 } }) equal(two.rangeBy({ word: 'two' }), { end: { column: 5, line: 3, offset: 17 }, start: { column: 2, line: 3, offset: 14 } }) }) test('rangeBy() returns range for start and end', () => { let css = parse('a { one: X }') let a = css.first as Rule let one = a.first as Declaration equal(one.rangeBy({ end: { column: 9, line: 1 }, start: { column: 7, line: 1 } }), { end: { column: 9, line: 1, offset: 8 }, start: { column: 7, line: 1, offset: 6 } }) }) test('rangeBy() returns range for start and end when offsets are missing', () => { let css = parse('a { one: X }') let a = css.first as Rule let one = a.first as Declaration // @ts-expect-error Testing non-standard AST if (one.source?.start) delete one.source.start.offset // @ts-expect-error Testing non-standard AST if (one.source?.end) delete one.source.end.offset equal(one.rangeBy({ end: { column: 9, line: 1 }, start: { column: 7, line: 1 } }), { end: { column: 9, line: 1, offset: 8 }, start: { column: 7, line: 1, offset: 6 } }) }) test('rangeBy() returns range for start and end after AST mutations', () => { let css = parse('a {\n\tone: 1;\n\ttwo: 2;}') let a = css.first as Rule let one = a.first as Declaration let two = one.next() as Declaration equal(a.rangeBy({ end: { column: 5, line: 3 }, start: { column: 3, line: 3 } }), { end: { column: 5, line: 3, offset: 17 }, start: { column: 3, line: 3, offset: 15 } }) equal(two.rangeBy({ end: { column: 5, line: 3 }, start: { column: 3, line: 3 } }), { end: { column: 5, line: 3, offset: 17 }, start: { column: 3, line: 3, offset: 15 } }) one.remove() equal(a.rangeBy({ end: { column: 5, line: 3 }, start: { column: 3, line: 3 } }), { end: { column: 5, line: 3, offset: 17 }, start: { column: 3, line: 3, offset: 15 } }) equal(two.rangeBy({ end: { column: 5, line: 3 }, start: { column: 3, line: 3 } }), { end: { column: 5, line: 3, offset: 17 }, start: { column: 3, line: 3, offset: 15 } }) }) test('rangeBy() returns range for index and endIndex', () => { let css = parse('a { one: X }') let a = css.first as Rule let one = a.first as Declaration equal(one.rangeBy({ endIndex: 3, index: 1 }), { end: { column: 9, line: 1, offset: 8 }, start: { column: 7, line: 1, offset: 6 } }) }) test('rangeBy() returns range for index and endIndex when offsets are missing', () => { let css = parse('a { one: X }') let a = css.first as Rule let one = a.first as Declaration // @ts-expect-error Testing non-standard AST if (one.source?.start) delete one.source.start.offset // @ts-expect-error Testing non-standard AST if (one.source?.end) delete one.source.end.offset equal(one.rangeBy({ endIndex: 3, index: 1 }), { end: { column: 9, line: 1, offset: 8 }, start: { column: 7, line: 1, offset: 6 } }) }) test('rangeBy() returns range for index and endIndex after AST mutations', () => { let css = parse('a {\n\tone: 1;\n\ttwo: 2;}') let a = css.first as Rule let one = a.first as Declaration let two = one.next() as Declaration equal(a.rangeBy({ endIndex: 17, index: 15 }), { end: { column: 5, line: 3, offset: 17 }, start: { column: 3, line: 3, offset: 15 } }) equal(two.rangeBy({ endIndex: 3, index: 1 }), { end: { column: 5, line: 3, offset: 17 }, start: { column: 3, line: 3, offset: 15 } }) one.remove() equal(a.rangeBy({ endIndex: 17, index: 15 }), { end: { column: 5, line: 3, offset: 17 }, start: { column: 3, line: 3, offset: 15 } }) equal(two.rangeBy({ endIndex: 3, index: 1 }), { end: { column: 5, line: 3, offset: 17 }, start: { column: 3, line: 3, offset: 15 } }) }) test('rangeBy() supports multi-root documents', () => { let root = parse('a {} b {}', { document: '' }) is(root.source?.input.css, 'a {} b {}') is(root.source?.input.document, '') let a = root.first as Rule // Offset the source location of `a` to mimic syntaxes like `postcss-html` a.source = { end: { column: 12, line: 1, offset: 12 }, input: a.source!.input, start: { column: 8, line: 1, offset: 7 } } equal(a.rangeBy({ endIndex: 1, index: 0 }), { end: { column: 9, line: 1, offset: 8 }, start: { column: 8, line: 1, offset: 7 } }) equal(a.rangeBy({ word: 'a' }), { end: { column: 9, line: 1, offset: 8 }, start: { column: 8, line: 1, offset: 7 } }) }) test.run() postcss-8.5.6/test/old-node.js000066400000000000000000000004721502402132100162370ustar00rootroot00000000000000// eslint-disable-next-line globalThis = Function('return this')() let Module = require('module') let originalRequire = Module.prototype.require Module.prototype.require = function (request) { if (request.startsWith('node:')) { request = request.slice(5) } return originalRequire.call(this, request) } postcss-8.5.6/test/parse.test.ts000077500000000000000000000144051502402132100166440ustar00rootroot00000000000000import { readFileSync } from 'fs' import { resolve } from 'path' import { eachTest, jsonify, testPath } from 'postcss-parser-tests' import { test } from 'uvu' import { equal, is, match, not, throws } from 'uvu/assert' import { AtRule, Declaration, parse, Root, Rule } from '../lib/postcss.js' test('works with file reads', () => { let stream = readFileSync(testPath('atrule-empty.css')) is(parse(stream) instanceof Root, true) }) eachTest((name, css, json) => { test(`parses ${name}`, () => { css = css.replace(/\r\n/g, '\n') let parsed = jsonify(parse(css, { from: name })) equal(parsed, json) }) }) test('parses UTF-8 BOM', () => { let css = parse('\uFEFF@host { a {\f} }') equal(css.nodes[0].raws.before, '') }) test('should has true at hasBOM property', () => { let css = parse('\uFEFF@host { a {\f} }') is(css.first?.source?.input.hasBOM, true) }) test('should has false at hasBOM property', () => { let css = parse('@host { a {\f} }') is(css.first?.source?.input.hasBOM, false) }) test('parses carrier return', () => { throws(() => { parse('@font-face{ font:(\r/*);} body { a: "a*/)} a{}"}') }, /:1:46: Unclosed string/) }) test('saves source file', () => { let css = parse('a {}', { from: 'a.css' }) is(css.first?.source?.input.css, 'a {}') is(css.first?.source?.input.file, resolve('a.css')) is(css.first?.source?.input.from, resolve('a.css')) }) test('keeps absolute path in source', () => { let css = parse('a {}', { from: 'http://example.com/a.css' }) is(css.first?.source?.input.file, 'http://example.com/a.css') is(css.first?.source?.input.from, 'http://example.com/a.css') }) test('saves source file on previous map', () => { let root1 = parse('a {}', { map: { inline: true } }) let css = root1.toResult({ map: { inline: true } }).css let root2 = parse(css) is(root2.first?.source?.input.file, resolve('to.css')) }) test('sets unique ID for file without name', () => { let css1 = parse('a {}') let css2 = parse('a {}') match(String(css1.first?.source?.input.id), /^$/) match(String(css1.first?.source?.input.from), /^$/) is.not(css2.first?.source?.input.id, css1.first?.source?.input.id) }) test('sets parent node', () => { let file = testPath('atrule-rules.css') let css = parse(readFileSync(file)) let support = css.first as AtRule let keyframes = support.first as AtRule let from = keyframes.first as Rule let decl = from.first as Declaration is(decl.parent, from) is(from.parent, keyframes) is(support.parent, css) is(keyframes.parent, support) }) test('ignores wrong close bracket', () => { let root = parse('a { p: ()) }') let a = root.first as Rule let decl = a.first as Declaration is(decl.value, '())') }) test('parses unofficial --mixins', () => { let root = parse(':root { --x { color: pink; }; }') let rule = root.first as Rule let prop = rule.first as Rule is(prop.selector, '--x') }) test('ignores symbols before declaration', () => { let root = parse('a { :one: 1 }') let a = root.first as Rule let prop = a.first as Declaration is(prop.raws.before, ' :') }) test('parses double semicolon after rule', () => { is(parse('a { };;').toString(), 'a { };;') }) test('parses a functional property', () => { let root = parse('a { b(c): d }') let a = root.first as Rule let b = a.first as Declaration is(b.prop, 'b(c)') }) test('parses a functional tagname', () => { let root = parse('a { b(c): d {} }') let a = root.first as Rule let b = a.first as Rule is(b.selector, 'b(c): d') }) test('throws on unclosed blocks', () => { throws(() => { parse('\na {\n') }, /:2:1: Unclosed block/) }) test('throws on unnecessary block close', () => { throws(() => { parse('a {\n} }') }, /:2:3: Unexpected }/) }) test('throws on unclosed comment', () => { throws(() => { parse('\n/*\n ') }, /:2:1: Unclosed comment/) }) test('throws on unclosed quote', () => { throws(() => { parse('\n"\n\na ') }, /:2:1: Unclosed string/) }) test('throws on unclosed bracket', () => { throws(() => { parse(':not(one() { }') }, /:1:5: Unclosed bracket/) }) test('throws on property without value', () => { throws(() => { parse('a { b;}') }, /:1:5: Unknown word/) throws(() => { parse('a { b b }') }, /:1:5: Unknown word/) throws(() => { parse('a { b(); }') }, /:1:5: Unknown word/) }) test('throws on nameless at-rule', () => { throws(() => { parse('@') }, /:1:1: At-rule without name/) }) test('throws on property without semicolon', () => { throws(() => { parse('a { one: filter(a:"") two: 2 }') }, /:1:21: Missed semicolon/) }) test('throws on double colon', () => { throws(() => { parse('a { one:: 1 }') }, /:1:9: Double colon/) }) test('do not throws on comment in between', () => { parse('a { b/* c */: 1 }') }) test('throws on two words in between', () => { throws(() => { parse('a { b c: 1 }') }, /:1:7: Unknown word/) }) test('throws on just colon', () => { throws(() => { parse(':') }, /:1:1: Unknown word/) throws(() => { parse(' : ') }, /:1:2: Unknown word/) }) test('does not suggest different parsers for CSS', () => { let error: any try { parse('a { one:: 1 }', { from: 'app.css' }) } catch (e) { error = e } not.match(error.message, /postcss-less|postcss-scss/) }) test('suggests postcss-scss for SCSS sources', () => { throws(() => { parse('a { #{var}: 1 }', { from: 'app.scss' }) }, /postcss-scss/) }) test('suggests postcss-sass for Sass sources', () => { throws(() => { parse('a\n #{var}: 1', { from: 'app.sass' }) }, /postcss-sass/) }) test('suggests postcss-less for Less sources', () => { throws(() => { parse('.@{my-selector} { }', { from: 'app.less' }) }, /postcss-less/) }) test('should give the correct column of missed semicolon with !important', () => { let error: any try { parse('a { \n color: red !important\n background-color: black;\n}') } catch (e) { error = e } match(error.message, /2:26: Missed semicolon/) }) test('should give the correct column of missed semicolon without !important', () => { let error: any try { parse('a { \n color: red\n background-color: black;\n}') } catch (e) { error = e } match(error.message, /2:15: Missed semicolon/) }) test.run() postcss-8.5.6/test/postcss.test.ts000077500000000000000000000114401502402132100172240ustar00rootroot00000000000000import { restoreAll, spyOn } from 'nanospy' import { test } from 'uvu' import { equal, is, match, throws, type } from 'uvu/assert' import postcss = require('../lib/postcss.js') import postcssDefault, { PluginCreator, Root } from '../lib/postcss.js' import Processor from '../lib/processor.js' test.after.each(() => { restoreAll() }) test('default matches module.exports', () => { is(postcss, postcssDefault) }) test('creates plugins list', () => { let processor = postcss() is(processor instanceof Processor, true) equal(processor.plugins, []) }) test('saves plugins list', () => { let a = (): void => {} let b = (): void => {} equal(postcss(a, b).plugins, [a, b]) }) test('saves plugins list as array', () => { let a = (): void => {} let b = (): void => {} equal(postcss([a, b]).plugins, [a, b]) }) test('takes plugin from other processor', () => { let a = (): void => {} let b = (): void => {} let c = (): void => {} let other = postcss([a, b]) equal(postcss([other, c]).plugins, [a, b, c]) }) test('takes plugins from a a plugin returning a processor', () => { let a = (): void => {} let b = (): void => {} let c = (): void => {} let other = postcss([a, b]) let meta = (() => other) as PluginCreator meta.postcss = true equal(postcss([other, c]).plugins, [a, b, c]) }) test('contains parser', () => { is(postcss.parse('').type, 'root') }) test('contains stringifier', () => { type(postcss.stringify, 'function') }) test('allows to build own CSS', () => { let root = postcss.root({ raws: { after: '\n' } }) let comment = postcss.comment({ text: 'Example' }) let media = postcss.atRule({ name: 'media', params: 'screen' }) let rule = postcss.rule({ selector: 'a' }) let decl = postcss.decl({ prop: 'color', value: 'black' }) root.append(comment) rule.append(decl) media.append(rule) root.append(media) is( root.toString(), '/* Example */\n' + '@media screen {\n' + ' a {\n' + ' color: black\n' + ' }\n' + '}\n' ) }) test('allows to build own CSS with Document', () => { let document = postcss.document() let root = postcss.root({ raws: { after: '\n' } }) let comment = postcss.comment({ text: 'Example' }) let media = postcss.atRule({ name: 'media', params: 'screen' }) let rule = postcss.rule({ selector: 'a' }) let decl = postcss.decl({ prop: 'color', value: 'black' }) root.append(comment) rule.append(decl) media.append(rule) root.append(media) document.append(root) is( document.toString(), '/* Example */\n' + '@media screen {\n' + ' a {\n' + ' color: black\n' + ' }\n' + '}\n' ) }) test('contains list module', () => { equal(postcss.list.space('a b'), ['a', 'b']) }) test('works with null', () => { throws(() => { // @ts-expect-error Testing invalid input postcss([() => {}]).process(null).css }, /PostCSS received null instead of CSS string/) }) test('has deprecated method to create plugins', () => { let warn = spyOn(console, 'warn', () => {}) let plugin = (postcss as any).plugin('test', (filter?: string) => { return (root: Root) => { root.walkDecls(filter ?? 'two', i => { i.remove() }) } }) equal(warn.callCount, 0) let func1: any = postcss(plugin).plugins[0] is(func1.postcssPlugin, 'test') match(func1.postcssVersion, /\d+.\d+.\d+/) equal(warn.callCount, 1) let func2: any = postcss(plugin()).plugins[0] equal(func2.postcssPlugin, func1.postcssPlugin) equal(func2.postcssVersion, func1.postcssVersion) let result1 = postcss(plugin('one')).process('a{ one: 1; two: 2 }') is(result1.css, 'a{ two: 2 }') let result2 = postcss(plugin).process('a{ one: 1; two: 2 }') is(result2.css, 'a{ one: 1 }') equal(warn.callCount, 1) match(warn.calls[0][0], /postcss\.plugin was deprecated/) }) test('creates a shortcut to process css', async () => { let warn = spyOn(console, 'warn', () => {}) let plugin = (postcss as any).plugin('test', (str?: string) => { return (root: Root) => { root.walkDecls(i => { i.value = str ?? 'bar' }) } }) let result1 = plugin.process('a{value:foo}') is(result1.css, 'a{value:bar}') let result2 = plugin.process('a{value:foo}', {}, 'baz') is(result2.css, 'a{value:baz}') let result = await plugin.process('a{value:foo}', { from: 'a' }, 'baz') equal(result.opts, { from: 'a' }) is(result.css, 'a{value:baz}') equal(warn.callCount, 1) }) test('does not call plugin constructor', () => { let warn = spyOn(console, 'warn', () => {}) let calls = 0 let plugin = (postcss as any).plugin('test', () => { calls += 1 return () => {} }) is(calls, 0) postcss(plugin).process('a{}') is(calls, 1) postcss(plugin()).process('a{}') is(calls, 2) equal(warn.callCount, 1) }) test.run() postcss-8.5.6/test/previous-map.test.ts000077500000000000000000000210331502402132100201540ustar00rootroot00000000000000import { existsSync, lstatSync, mkdirSync, readdirSync, rmdirSync, unlinkSync, writeFileSync } from 'fs' import { join } from 'path' import { SourceMapConsumer } from 'source-map-js' import { pathToFileURL } from 'url' import { test } from 'uvu' import { equal, is, match, not, throws, type } from 'uvu/assert' import { parse } from '../lib/postcss.js' let dir = join(__dirname, 'prevmap-fixtures') let mapObj = { file: null, mappings: '', names: [], sources: [], version: 3 } let map = JSON.stringify(mapObj) function deleteDir(path: string): void { if (existsSync(path)) { readdirSync(path).forEach(i => { let file = join(path, i) if (lstatSync(file).isDirectory()) { deleteDir(file) } else { unlinkSync(file) } }) rmdirSync(path) } } test.after.each(() => { deleteDir(dir) }) test('misses property if no map', () => { type(parse('a{}').source?.input.map, 'undefined') }) test('creates property if map present', () => { let root = parse('a{}', { map: { prev: map } }) is(root.source?.input.map.text, map) }) test('returns consumer', () => { let obj = parse('a{}', { map: { prev: map } }).source?.input.map.consumer() is(obj instanceof SourceMapConsumer, true) }) test('sets annotation property', () => { let mapOpts = { map: { prev: map } } let root1 = parse('a{}', mapOpts) type(root1.source?.input.map.annotation, 'undefined') let root2 = parse('a{}/*# sourceMappingURL=a.css.map */', mapOpts) is(root2.source?.input.map.annotation, 'a.css.map') }) test('checks previous sources content', () => { let map2: any = { file: 'b', mappings: '', names: [], sources: ['a'], version: 3 } let opts = { map: { prev: map2 } } is(parse('a{}', opts).source?.input.map.withContent(), false) map2.sourcesContent = ['a{}'] is(parse('a{}', opts).source?.input.map.withContent(), true) }) test('decodes base64 maps', () => { let b64 = Buffer.from(map).toString('base64') let css = 'a{}\n' + `/*# sourceMappingURL=data:application/json;base64,${b64} */` is(parse(css).source?.input.map.text, map) }) test('decodes base64 UTF-8 maps', () => { let b64 = Buffer.from(map).toString('base64') let css = 'a{}\n/*# sourceMappingURL=data:application/json;' + 'charset=utf-8;base64,' + b64 + ' */' is(parse(css).source?.input.map.text, map) }) test('accepts different name for base64 maps with UTF-8 encoding', () => { let b64 = Buffer.from(map).toString('base64') let css = 'a{}\n/*# sourceMappingURL=data:application/json;' + 'charset=utf8;base64,' + b64 + ' */' is(parse(css).source?.input.map.text, map) }) test('decodes URI maps', () => { let uri = 'data:application/json,' + decodeURI(map) let css = `a{}\n/*# sourceMappingURL=${uri} */` is(parse(css).source?.input.map.text, map) }) test('decodes URI UTF-8 maps', () => { let uri = decodeURI(map) let css = 'a{}\n/*# sourceMappingURL=data:application/json;' + 'charset=utf-8,' + uri + ' */' is(parse(css).source?.input.map.text, map) }) test('accepts different name for URI maps with UTF-8 encoding', () => { let uri = decodeURI(map) let css = 'a{}\n/*# sourceMappingURL=data:application/json;' + 'charset=utf8,' + uri + ' */' is(parse(css).source?.input.map.text, map) }) test('removes map on request', () => { let uri = 'data:application/json,' + decodeURI(map) let css = `a{}\n/*# sourceMappingURL=${uri} */` let input = parse(css, { map: { prev: false } }).source?.input type(input?.map, 'undefined') }) test('raises on unknown inline encoding', () => { let css = 'a { }\n/*# sourceMappingURL=data:application/json;' + 'md5,68b329da9893e34099c7d8ad5cb9c940*/' throws(() => { parse(css) }, 'Unsupported source map encoding md5') }) test('raises on unknown map format', () => { throws(() => { // @ts-expect-error Invalid input parse('a{}', { map: { prev: 1 } }) }, 'Unsupported previous source map format: 1') }) test('reads map from annotation', () => { let file = join(dir, 'a.map') mkdirSync(dir) writeFileSync(file, map) let root = parse('a{}\n/*# sourceMappingURL=a.map */', { from: file }) is(root.source?.input.map.text, map) is(root.source?.input.map.root, dir) }) test('reads only the last map from annotation', () => { let file = join(dir, 'c.map') mkdirSync(dir) writeFileSync(file, map) let root = parse( 'a{}' + '\n/*# sourceMappingURL=a.map */' + '\n/*# sourceMappingURL=b.map */' + '\n/*# sourceMappingURL=c.map */', { from: file } ) is(root.source?.input.map.text, map) is(root.source?.input.map.root, dir) }) test('sets unique name for inline map', () => { let map2 = { mappings: '', names: [], sources: ['a'], version: 3 } let opts = { map: { prev: map2 } } let file1 = parse('a{}', opts).source?.input.map.file let file2 = parse('a{}', opts).source?.input.map.file match(String(file1), /^$/) is.not(file1, file2) }) test('accepts an empty mappings string', () => { not.throws(() => { let emptyMap = { mappings: '', names: [], sources: [], version: 3 } parse('body{}', { map: { prev: emptyMap } }) }) }) test('accepts a function', () => { let css = 'body{}\n/*# sourceMappingURL=a.map */' let file = join(dir, 'previous-sourcemap-function.map') mkdirSync(dir) writeFileSync(file, map) let opts = { map: { prev: () => file } } let root = parse(css, opts) is(root.source?.input.map.text, map) is(root.source?.input.map.annotation, 'a.map') }) test('calls function with opts.from', () => { let css = 'body{}\n/*# sourceMappingURL=a.map */' let file = join(dir, 'previous-sourcemap-function.map') mkdirSync(dir) writeFileSync(file, map) parse(css, { from: 'a.css', map: { prev: from => { is(from, 'a.css') return file } } }) }) test('raises when function returns invalid path', () => { let css = 'body{}\n/*# sourceMappingURL=a.map */' let fakeMap = Number.MAX_SAFE_INTEGER.toString() + '.map' let fakePath = join(dir, fakeMap) let opts = { map: { prev: () => fakePath } } throws(() => { parse(css, opts) }, 'Unable to load previous source map: ' + fakePath) }) test('uses source map path as a root', () => { let from = join(dir, 'a.css') mkdirSync(dir) mkdirSync(join(dir, 'maps')) writeFileSync( join(dir, 'maps', 'a.map'), JSON.stringify({ file: 'test.css', mappings: 'AACA,CAAC,CACG,GAAG,CAAC;EACF,KAAK,EAAE,GAAI;CACZ', names: [], sources: ['../../test.scss'], version: 3 }) ) let root = parse( '* div {\n color: red;\n }\n/*# sourceMappingURL=maps/a.map */', { from } ) equal(root.source?.input.origin(1, 3, 1, 5), { column: 4, endColumn: 7, endLine: 3, file: join(dir, '..', 'test.scss'), line: 3, url: pathToFileURL(join(dir, '..', 'test.scss')).href }) }) test('uses current file path for source map', () => { let root = parse('a{b:1}', { from: join(__dirname, 'dir', 'subdir', 'a.css'), map: { prev: { file: 'test.css', mappings: 'AAAA,CAAC;EAAC,CAAC,EAAC,CAAC', names: [], sources: ['../test.scss'], version: 3 } } }) equal(root.source?.input.origin(1, 1), { column: 1, endColumn: undefined, endLine: undefined, file: join(__dirname, 'dir', 'test.scss'), line: 1, url: pathToFileURL(join(__dirname, 'dir', 'test.scss')).href }) }) test('works with non-file sources', () => { let root = parse('a{b:1}', { from: join(__dirname, 'dir', 'subdir', 'a.css'), map: { prev: { file: 'test.css', mappings: 'AAAA,CAAC;EAAC,CAAC,EAAC,CAAC', names: [], sources: ['http://example.com/test.scss'], version: 3 } } }) equal(root.source?.input.origin(1, 1), { column: 1, endColumn: undefined, endLine: undefined, line: 1, url: 'http://example.com/test.scss' }) }) test('works with index map', () => { let root = parse('body {\nwidth:100%;\n}', { from: join(__dirname, 'a.css'), map: { prev: { sections: [ { map: { mappings: 'AAAA;AACA;AACA;', sources: ['b.css'], sourcesContent: ['body {\nwidth:100%;\n}'], version: 3 }, offset: { column: 0, line: 0 } } ], version: 3 } } }) is((root as any).source.input.origin(1, 1).file, join(__dirname, 'b.css')) }) test.run() postcss-8.5.6/test/processor.test.ts000077500000000000000000000361421502402132100175530ustar00rootroot00000000000000import { delay } from 'nanodelay' import { restoreAll, spyOn } from 'nanospy' import { resolve as pathResolve } from 'path' import { test } from 'uvu' import { equal, instance, is, match, not, throws, type } from 'uvu/assert' import CssSyntaxError from '../lib/css-syntax-error.js' import LazyResult from '../lib/lazy-result.js' import NoWorkResult from '../lib/no-work-result.js' import postcss, { Document, Node, parse, Parser, Plugin, PluginCreator, Result, Root, Stringifier } from '../lib/postcss.js' import Processor from '../lib/processor.js' import Rule from '../lib/rule.js' test.after.each(() => { restoreAll() }) function prs(): Root { return new Root({ raws: { after: 'ok' } }) } function str(node: Node, builder: (s: string) => void): void { builder(`${node.raws.after}!`) } async function catchError(cb: () => Promise): Promise { try { await cb() } catch (e) { if (e instanceof Error) return e } throw new Error('Error was not thrown') } let beforeFix = new Processor([ (root: Root) => { root.walkRules(rule => { if (!rule.selector.match(/::(before|after)/)) return if (!rule.some(i => i.type === 'decl' && i.prop === 'content')) { rule.prepend({ prop: 'content', value: '""' }) } }) } ]) test('adds new plugins', () => { let a = (): void => {} let processor = new Processor() processor.use(a) equal(processor.plugins, [a]) }) test('adds new plugin by object', () => { let a = (): void => {} let processor = new Processor() processor.use({ postcss: a }) equal(processor.plugins, [a]) }) test('adds new plugin by object-function', () => { let a = (): void => {} let obj: any = () => {} obj.postcss = a let processor = new Processor() processor.use(obj) equal(processor.plugins, [a]) }) test('adds new processors of another postcss instance', () => { let a = (): void => {} let processor = new Processor() let other = new Processor([a]) processor.use(other) equal(processor.plugins, [a]) }) test('adds new processors from object', () => { let a = (): void => {} let processor = new Processor() let other = new Processor([a]) processor.use({ postcss: other }) equal(processor.plugins, [a]) }) test('returns itself', () => { let a = (): void => {} let b = (): void => {} let processor = new Processor() equal(processor.use(a).use(b).plugins, [a, b]) }) test('throws on wrong format', () => { let pr = new Processor() throws(() => { // @ts-expect-error Testing invalid API pr.use(1) }, /1 is not a PostCSS plugin/) }) test('processes CSS', () => { let result = beforeFix.process('a::before{top:0}') is(result.css, 'a::before{content:"";top:0}') }) test('processes parsed AST', () => { let root = parse('a::before{top:0}') let result = beforeFix.process(root) is(result.css, 'a::before{content:"";top:0}') }) test('processes previous result', () => { let result = new Processor([() => {}]).process('a::before{top:0}') result = beforeFix.process(result) is(result.css, 'a::before{content:"";top:0}') }) test('takes maps from previous result', () => { let one = new Processor([() => {}]).process('a{}', { from: 'a.css', map: { inline: false }, to: 'b.css' }) let two = new Processor([() => {}]).process(one, { to: 'c.css' }) equal(two.map.toJSON().sources, ['a.css']) }) test('inlines maps from previous result', () => { let one = new Processor([() => {}]).process('a{}', { from: 'a.css', map: { inline: false }, to: 'b.css' }) let two = new Processor([() => {}]).process(one, { map: { inline: true }, to: 'c.css' }) type(two.map, 'undefined') }) test('throws with file name', () => { let error: CssSyntaxError | undefined try { new Processor([() => {}]).process('a {', { from: 'a.css' }).css } catch (e) { if (e instanceof CssSyntaxError) { error = e } else { throw e } } is(error?.file, pathResolve('a.css')) match(String(error?.message), /a.css:1:1: Unclosed block$/) }) test('allows to replace Root', () => { let processor = new Processor([ (css, result) => { result.root = new Root() } ]) is(processor.process('a {}').css, '') }) test('returns LazyResult object', () => { let result = new Processor([() => {}]).process('a{}') is(result instanceof LazyResult, true) is(result.css, 'a{}') is(result.toString(), 'a{}') }) test('calls all plugins once', async () => { let calls = '' let a = (): void => { calls += 'a' } let b = (): void => { calls += 'b' } let result = new Processor([a, b]).process('', { from: undefined }) result.css result.map result.root await result is(calls, 'ab') }) test('parses, converts and stringifies CSS', () => { is( typeof new Processor([ (css: Root) => { equal(css instanceof Root, true) } ]).process('a {}').css, 'string' ) }) test('send result to plugins', () => { let processor = new Processor([() => {}]) processor .use((css, result) => { is(result instanceof Result, true) equal(result.processor, processor) equal(result.opts, { map: true }) equal(result.root, css) }) .process('a {}', { from: undefined, map: true }) }) test('accepts source map from PostCSS', () => { let one = new Processor([() => {}]).process('a{}', { from: 'a.css', map: { inline: false }, to: 'b.css' }) let two = new Processor([() => {}]).process(one.css, { from: 'b.css', map: { inline: false, prev: one.map }, to: 'c.css' }) equal(two.map.toJSON().sources, ['a.css']) }) test('supports async plugins', async () => { let starts = 0 let finish = 0 let async1 = (css: Root): Promise => new Promise(resolve => { starts += 1 setTimeout(() => { equal(starts, 1) css.append('a {}') finish += 1 resolve() }, 1) }) let async2 = (css: Root): Promise => new Promise(resolve => { equal(starts, 1) equal(finish, 1) starts += 1 setTimeout(() => { css.append('b {}') finish += 1 resolve() }, 1) }) let r = await new Processor([async1, async2]).process('', { from: 'a' }) is(starts, 2) is(finish, 2) is(r.css, 'a {}b {}') }) test('works async without plugins', async () => { let r = await new Processor([() => {}]).process('a {}', { from: 'a' }) is(r.css, 'a {}') }) test('runs async plugin only once', async () => { let calls = 0 let async = (): Promise => { return new Promise(resolve => { setTimeout(() => { calls += 1 resolve() }, 1) }) } let result = new Processor([async]).process('a {}', { from: undefined }) result.then(() => {}) await result await result is(calls, 1) }) test('supports async errors', async () => { let error = new Error('Async') let async = (): Promise => { return new Promise((resolve, reject) => { reject(error) }) } let result = new Processor([async]).process('', { from: undefined }) let err1 = await catchError(async () => await result) equal(err1, error) let err2: unknown result.catch((catched: unknown) => { err2 = catched }) await delay(10) equal(err2, error) }) test('supports sync errors in async mode', async () => { let error = new Error('Async') let async = (): void => { throw error } let err = await catchError(() => new Processor([async]).process('', { from: undefined }) ) equal(err, error) }) test('throws parse error in async', async () => { let err = await catchError(() => new Processor([() => {}]).process('a{', { from: undefined }) ) is(err.message, ':1:1: Unclosed block') }) test('throws error on sync method to async plugin', () => { let async = (): Promise => { return new Promise(resolve => { resolve() }) } throws(() => { new Processor([async]).process('a{}').css }, /async/) }) test('throws a sync call in async running', () => { let async = (): Promise => new Promise(resolve => setTimeout(resolve, 1)) let processor = new Processor([async]).process('a{}', { from: 'a.css' }) processor.async() throws(() => { processor.sync() }, /then/) }) test('remembers errors', async () => { let calls = 0 let plugin: Plugin = { Once() { calls += 1 throw new Error('test') }, postcssPlugin: 'plugin' } let processing = postcss([plugin]).process('a{}', { from: undefined }) throws(() => { processing.css }, 'test') throws(() => { processing.css }, 'test') throws(() => { processing.root }, 'test') let asyncError: any try { await processing } catch (e) { asyncError = e } is(asyncError.message, 'test') is(calls, 1) }) test('checks plugin compatibility', () => { let error = spyOn(console, 'error', () => {}) let warn = spyOn(console, 'warn', () => {}) let plugin = (postcss as any).plugin('test', () => { return () => { throw new Error('Er') } }) let func = plugin() equal(warn.callCount, 1) func.postcssVersion = '2.1.5' function processBy(version: string): void { let processor = new Processor([func]) processor.version = version processor.process('a{}').css } throws(() => { processBy('1.0.0') }, 'Er') equal(error.callCount, 1) equal(error.calls, [ [ 'Unknown error from PostCSS plugin. ' + 'Your current PostCSS version is 1.0.0, but test uses 2.1.5. ' + 'Perhaps this is the source of the error below.' ] ]) throws(() => { processBy('3.0.0') }, 'Er') equal(error.callCount, 2) throws(() => { processBy('2.0.0') }, 'Er') equal(error.callCount, 3) throws(() => { processBy('2.1.0') }, 'Er') equal(error.callCount, 3) }) test('sets last plugin to result', async () => { let plugin1 = (css: Root, result: Result): void => { equal(result.lastPlugin, plugin1) } let plugin2 = (css: Root, result: Result): void => { equal(result.lastPlugin, plugin2) } let processor = new Processor([plugin1, plugin2]) let result = await processor.process('a{}', { from: undefined }) equal(result.lastPlugin, plugin2) }) test('uses custom parsers', async () => { let processor = new Processor([]) let result = await processor.process('a{}', { from: undefined, parser: prs }) is(result.css, 'ok') }) test('uses custom parsers from object', async () => { let processor = new Processor([]) let syntax = { parse: prs, stringify: str } let result = await processor.process('a{}', { from: 'a', parser: syntax }) equal(result.css, 'ok') }) test('uses custom stringifier', async () => { let processor = new Processor([]) let result = await processor.process('a{}', { from: 'a', stringifier: str }) is(result.css, '!') }) test('uses custom stringifier from object', async () => { let processor = new Processor([]) let syntax = { parse: prs, stringify: str } let result = await processor.process('', { from: 'a', stringifier: syntax }) is(result.css, '!') }) test('uses custom stringifier with source maps', async () => { let processor = new Processor([]) let result = await processor.process('a{}', { from: undefined, map: true, stringifier: str }) match(result.css, /!\n\/\*# sourceMap/) }) test('uses custom syntax', async () => { let processor = new Processor([() => {}]) let result = await processor.process('a{}', { from: undefined, syntax: { parse: prs, stringify: str } }) is(result.css, 'ok!') }) test('contains PostCSS version', () => { match(new Processor().version, /\d+.\d+.\d+/) }) test('throws on syntax as plugin', () => { let processor = new Processor([() => {}]) throws(() => { processor.use({ // @ts-expect-error Testing invalid API parse() {} }) }, /syntax/) }) test('warns about missed from', async () => { let warn = spyOn(console, 'warn', () => {}) let processor = new Processor([() => {}]) processor.process('a{}').css equal(warn.calls, []) await processor.process('a{}') equal(warn.calls, [ [ 'Without `from` option PostCSS could generate wrong source map ' + 'and will not find Browserslist config. Set it to CSS file path ' + 'or to `undefined` to prevent this warning.' ] ]) }) test('returns NoWorkResult object', () => { let result = new Processor().process('a{}') instance(result, NoWorkResult) }) test('without plugins parses CSS only on root access', async () => { let noWorkResult = new Processor().process('a{}') let result = await noWorkResult // @ts-expect-error Testing private API type(noWorkResult._root, 'undefined') is(result.root.nodes.length, 1) // @ts-expect-error Testing private API not.type(noWorkResult._root, 'undefined') is(noWorkResult.root.nodes.length, 1) }) test('catches error with empty processor', async () => { let noWorkResult = new Processor().process('a {') try { noWorkResult.root } catch {} let err = await catchError(async () => await noWorkResult) noWorkResult.catch((e: unknown) => { instance(e, CssSyntaxError) }) instance(err, CssSyntaxError) }) test('throws an error on root access on no plugins mode', () => { throws(() => { postcss().process('// invalid', { from: 'a' }).root }, 'Unknown word') }) test('supports plugins returning processors', () => { let warn = spyOn(console, 'warn', () => {}) let a = (): void => {} let processor = new Processor() let other: any = (postcss as any).plugin('test', () => { return new Processor([a]) }) processor.use(other) equal(processor.plugins, [a]) equal(warn.callCount, 1) }) test('supports plugin creators returning processors', () => { let a = (): void => {} let processor = new Processor() let other = (() => { return new Processor([a]) }) as PluginCreator other.postcss = true processor.use(other) equal(processor.plugins, [a]) }) test('uses custom syntax for document', async () => { let customParser: Parser = () => { return new Document({ nodes: [ new Root({ nodes: [new Rule({ selector: 'a' })], raws: { after: '\n\n\n', codeBefore: '\n\n\n', codeBefore: '\n\n\n' ) }) test.run() postcss-8.5.6/test/result.test.ts000077500000000000000000000031351502402132100170460ustar00rootroot00000000000000import { test } from 'uvu' import { equal, is } from 'uvu/assert' import postcss, { Plugin, Result, Root, Warning } from '../lib/postcss.js' import Processor from '../lib/processor.js' let processor = new Processor() let root = new Root() test('stringifies', () => { let result = new Result(processor, root, {}) result.css = 'a{}' is(`${result}`, result.css) }) test('adds warning', () => { let warning let plugin: Plugin = { Once(css, { result }) { warning = result.warn('test', { node: css.first }) }, postcssPlugin: 'test-plugin' } let result = postcss([plugin]).process('a{}').sync() equal( warning, new Warning('test', { node: result.root.first, plugin: 'test-plugin' }) ) equal(result.messages, [warning]) }) test('allows to override plugin', () => { let plugin: Plugin = { Once(css, { result }) { result.warn('test', { plugin: 'test-plugin#one' }) }, postcssPlugin: 'test-plugin' } let result = postcss([plugin]).process('a{}').sync() is(result.messages[0].plugin, 'test-plugin#one') }) test('allows Root', () => { let css = postcss.parse('a{}') let result = new Result(processor, css, {}) result.warn('TT', { node: css.first }) is(result.messages[0].toString(), ':1:1: TT') }) test('returns only warnings', () => { let result = new Result(processor, root, {}) result.messages = [ { text: 'a', type: 'warning' }, { type: 'custom' }, { text: 'b', type: 'warning' } ] equal(result.warnings(), [ { text: 'a', type: 'warning' }, { text: 'b', type: 'warning' } ]) }) test.run() postcss-8.5.6/test/root.test.ts000077500000000000000000000034441502402132100165160ustar00rootroot00000000000000import { test } from 'uvu' import { is, match, type } from 'uvu/assert' import { parse, Result } from '../lib/postcss.js' test('prepend() fixes spaces on insert before first', () => { let css = parse('a {} b {}') css.prepend({ selector: 'em' }) is(css.toString(), 'em {} a {} b {}') }) test('prepend() fixes spaces on multiple inserts before first', () => { let css = parse('a {} b {}') css.prepend({ selector: 'em' }, { selector: 'strong' }) is(css.toString(), 'em {} strong {} a {} b {}') }) test('prepend() uses default spaces on only first', () => { let css = parse('a {}') css.prepend({ selector: 'em' }) is(css.toString(), 'em {}\na {}') }) test('append() sets new line between rules in multiline files', () => { let a = parse('a {}\n\na {}\n') let b = parse('b {}\n') is(a.append(b).toString(), 'a {}\n\na {}\n\nb {}\n') }) test('insertAfter() does not use before of first rule', () => { let css = parse('a{} b{}') css.insertAfter(0, { selector: '.a' }) css.insertAfter(2, { selector: '.b' }) type(css.nodes[1].raws.before, 'undefined') is(css.nodes[3].raws.before, ' ') is(css.toString(), 'a{} .a{} b{} .b{}') }) test('fixes spaces on removing first rule', () => { let css = parse('a{}\nb{}\n') if (!css.first) throw new Error('No nodes were parsed') css.first.remove() is(css.toString(), 'b{}\n') }) test('keeps spaces on moving root', () => { let css1 = parse('a{}\nb{}\n') let css2 = parse('') css2.append(css1) is(css2.toString(), 'a{}\nb{}') let css3 = parse('\n') css3.append(css2.nodes) is(css3.toString(), 'a{}\nb{}\n') }) test('generates result with map', () => { let root = parse('a {}') let result = root.toResult({ map: true }) is(result instanceof Result, true) match(result.css, /a {}\n\/\*# sourceMappingURL=/) }) test.run() postcss-8.5.6/test/rule.test.ts000077500000000000000000000043221502402132100164760ustar00rootroot00000000000000import { test } from 'uvu' import { equal, is } from 'uvu/assert' import { parse, Rule } from '../lib/postcss.js' test('initializes with properties', () => { let rule = new Rule({ selector: 'a' }) is(rule.selector, 'a') }) test('returns array in selectors', () => { let rule = new Rule({ selector: 'a,b' }) equal(rule.selectors, ['a', 'b']) }) test('trims selectors', () => { let rule = new Rule({ selector: '.a\n, .b , .c' }) equal(rule.selectors, ['.a', '.b', '.c']) }) test('is smart about selectors commas', () => { let rule = new Rule({ selector: "[foo='a, b'], a:-moz-any(:focus, [href*=','])" }) equal(rule.selectors, ["[foo='a, b']", "a:-moz-any(:focus, [href*=','])"]) }) test('receive array in selectors', () => { let rule = new Rule({ selector: 'i, b' }) rule.selectors = ['em', 'strong'] is(rule.selector, 'em, strong') }) test('saves separator in selectors', () => { let rule = new Rule({ selector: 'i,\nb' }) rule.selectors = ['em', 'strong'] is(rule.selector, 'em,\nstrong') }) test('uses between to detect separator in selectors', () => { let rule = new Rule({ raws: { between: '' }, selector: 'b' }) rule.selectors = ['b', 'strong'] is(rule.selector, 'b,strong') }) test('uses space in separator be default in selectors', () => { let rule = new Rule({ selector: 'b' }) rule.selectors = ['b', 'strong'] is(rule.selector, 'b, strong') }) test('selectors works in constructor', () => { let rule = new Rule({ selectors: ['a', 'b'] }) is(rule.selector, 'a, b') }) test('inserts default spaces', () => { let rule = new Rule({ selector: 'a' }) is(rule.toString(), 'a {}') rule.append({ prop: 'color', value: 'black' }) is(rule.toString(), 'a {\n color: black\n}') }) test('clones spaces from another rule', () => { let root = parse('b{\n }') let rule = new Rule({ selector: 'em' }) root.append(rule) is(root.toString(), 'b{\n }\nem{\n }') }) test('uses different spaces for empty rules', () => { let root = parse('a{}\nb{\n a:1\n}') let rule = new Rule({ selector: 'em' }) root.append(rule) is(root.toString(), 'a{}\nb{\n a:1\n}\nem{}') rule.append({ prop: 'top', value: '0' }) is(root.toString(), 'a{}\nb{\n a:1\n}\nem{\n top:0\n}') }) test.run() postcss-8.5.6/test/stringifier.test.js000077500000000000000000000164271502402132100200530ustar00rootroot00000000000000let { test } = require('uvu') let { is } = require('uvu/assert') let { AtRule, Declaration, Document, Node, parse, Root, Rule } = require('../lib/postcss') let Stringifier = require('../lib/stringifier') let str test.before.each(() => { str = new Stringifier() }) test('creates trimmed/raw property', () => { let b = new Node({ one: 'trim' }) b.raws.one = { raw: 'raw', value: 'trim' } is(str.rawValue(b, 'one'), 'raw') b.one = 'trim1' is(str.rawValue(b, 'one'), 'trim1') }) test('works without rawValue magic', () => { let b = new Node() b.one = '1' is(b.one, '1') is(str.rawValue(b, 'one'), '1') }) test('uses node raw', () => { let rule = new Rule({ raws: { between: '\n' }, selector: 'a' }) is(str.raw(rule, 'between', 'beforeOpen'), '\n') }) test('hacks before for nodes without parent', () => { let rule = new Rule({ selector: 'a' }) is(str.raw(rule, 'before'), '') }) test('hacks before for first node', () => { let root = new Root() root.append(new Rule({ selector: 'a' })) is(str.raw(root.first, 'before'), '') }) test('hacks before for first decl', () => { let decl = new Declaration({ prop: 'color', value: 'black' }) is(str.raw(decl, 'before'), '') let rule = new Rule({ selector: 'a' }) rule.append(decl) is(str.raw(decl, 'before'), '\n ') }) test('detects after raw', () => { let root = new Root() root.append({ raws: { after: ' ' }, selector: 'a' }) root.first.append({ prop: 'color', value: 'black' }) root.append({ selector: 'a' }) is(str.raw(root.last, 'after'), ' ') }) test('uses defaults without parent', () => { let rule = new Rule({ selector: 'a' }) is(str.raw(rule, 'between', 'beforeOpen'), ' ') }) test('uses defaults for unique node', () => { let root = new Root() root.append(new Rule({ selector: 'a' })) is(str.raw(root.first, 'between', 'beforeOpen'), ' ') }) test('clones raw from first node', () => { let root = new Root() root.append(new Rule({ raws: { between: '' }, selector: 'a' })) root.append(new Rule({ selector: 'b' })) is(str.raw(root.last, 'between', 'beforeOpen'), '') }) test('indents by default', () => { let root = new Root() root.append(new AtRule({ name: 'page' })) root.first.append(new Rule({ selector: 'a' })) root.first.first.append({ prop: 'color', value: 'black' }) is( root.toString(), '@page {\n' + ' a {\n' + ' color: black\n' + ' }\n' + '}' ) }) test('clones style', () => { let compress = parse('@page{ a{ } }') let spaces = parse('@page {\n a {\n }\n}') compress.first.first.append({ prop: 'color', value: 'black' }) is(compress.toString(), '@page{ a{ color: black } }') spaces.first.first.append({ prop: 'color', value: 'black' }) is(spaces.toString(), '@page {\n a {\n color: black\n }\n}') }) test('clones indent', () => { let root = parse('a{\n}') root.first.append({ text: 'a' }) root.first.append({ raws: { before: '\n\n ' }, text: 'b' }) is(root.toString(), 'a{\n\n /* a */\n\n /* b */\n}') }) test('clones declaration before for comment', () => { let root = parse('a{\n}') root.first.append({ text: 'a' }) root.first.append({ prop: 'a', raws: { before: '\n\n ' }, value: '1' }) is(root.toString(), 'a{\n\n /* a */\n\n a: 1\n}') }) test('clones indent by types', () => { let css = parse('a {\n *color: black\n}\n\nb {\n}') css.append(new Rule({ selector: 'em' })) css.last.append({ prop: 'z-index', value: '1' }) is(css.last.first.raw('before'), '\n ') }) test('ignores non-space symbols in indent cloning', () => { let css = parse('a {\n color: black\n}\n\nb {\n}') css.append(new Rule({ selector: 'em' })) css.last.append({ prop: 'z-index', value: '1' }) is(css.last.raw('before'), '\n\n') is(css.last.first.raw('before'), '\n ') }) test('clones indent by before and after', () => { let css = parse('@page{\n\n a{\n color: black}}') css.first.append(new Rule({ selector: 'b' })) css.first.last.append({ prop: 'z-index', value: '1' }) is(css.first.last.raw('before'), '\n\n ') is(css.first.last.raw('after'), '') }) test('clones semicolon only from rules with children', () => { let css = parse('a{}b{one:1;}') is(str.raw(css.first, 'semicolon'), true) }) test('clones only spaces in before', () => { let css = parse('a{*one:1}') css.first.append({ prop: 'two', value: '2' }) css.append({ name: 'keyframes', params: 'a' }) css.last.append({ selector: 'from' }) is(css.toString(), 'a{*one:1;two:2}\n@keyframes a{\nfrom{}}') }) test('clones only spaces in between', () => { let css = parse('a{one/**/:1}') css.first.append({ prop: 'two', value: '2' }) is(css.toString(), 'a{one/**/:1;two:2}') }) test('uses optional raws.indent', () => { let rule = new Rule({ raws: { indent: ' ' }, selector: 'a' }) rule.append({ prop: 'color', value: 'black' }) is(rule.toString(), 'a {\n color: black\n}') }) test('handles nested roots', () => { let root = new Root() let subRoot = new Root() subRoot.append(new AtRule({ name: 'foo' })) root.append(subRoot) is(root.toString(), '@foo') }) test('handles root', () => { let root = new Root() root.append(new AtRule({ name: 'foo' })) let s = root.toString() is(s, '@foo') }) test('handles root with after', () => { let root = new Root({ raws: { after: ' ' } }) root.append(new AtRule({ name: 'foo' })) let s = root.toString() is(s, '@foo ') }) test('pass nodes to document', () => { let root = new Root() let document = new Document({ nodes: [root] }) is(document.toString(), '') }) test('handles document with one root', () => { let root = new Root() root.append(new AtRule({ name: 'foo' })) let document = new Document() document.append(root) let s = document.toString() is(s, '@foo') }) test('handles document with one root and after raw', () => { let document = new Document() let root = new Root({ raws: { after: ' ' } }) root.append(new AtRule({ name: 'foo' })) document.append(root) let s = document.toString() is(s, '@foo ') }) test('handles document with one root and before and after', () => { let document = new Document() let root = new Root({ raws: { after: 'AFTER' } }) root.append(new AtRule({ name: 'foo' })) document.append(root) let s = document.toString() is(s, '@fooAFTER') }) test('handles document with three roots without raws', () => { let root1 = new Root() root1.append(new AtRule({ name: 'foo' })) let root2 = new Root() root2.append(new Rule({ selector: 'a' })) let root3 = new Root() root3.append(new Declaration({ prop: 'color', value: 'black' })) let document = new Document() document.append(root1) document.append(root2) document.append(root3) let s = document.toString() is(s, '@fooa {}color: black') }) test('handles document with three roots, with before and after raws', () => { let root1 = new Root({ raws: { after: 'AFTER_ONE' } }) root1.append(new Rule({ selector: 'a.one' })) let root2 = new Root({ raws: { after: 'AFTER_TWO' } }) root2.append(new Rule({ selector: 'a.two' })) let root3 = new Root({ raws: { after: 'AFTER_THREE' } }) root3.append(new Rule({ selector: 'a.three' })) let document = new Document() document.append(root1) document.append(root2) document.append(root3) let s = document.toString() is(s, 'a.one {}AFTER_ONEa.two {}AFTER_TWOa.three {}AFTER_THREE') }) test.run() postcss-8.5.6/test/stringify.test.ts000077500000000000000000000006161502402132100175470ustar00rootroot00000000000000import { eachTest } from 'postcss-parser-tests' import { test } from 'uvu' import { is } from 'uvu/assert' import { parse, stringify } from '../lib/postcss.js' eachTest((name, css) => { if (name === 'bom.css') return test(`stringifies ${name}`, () => { let root = parse(css) let result = '' stringify(root, i => { result += i }) is(result, css) }) }) test.run() postcss-8.5.6/test/tokenize.test.js000077500000000000000000000150101502402132100173410ustar00rootroot00000000000000let { test } = require('uvu') let { equal, is, throws } = require('uvu/assert') let { Input } = require('../lib/postcss') let tokenizer = require('../lib/tokenize') function tokenize(css, opts) { let processor = tokenizer(new Input(css), opts) let tokens = [] while (!processor.endOfFile()) { tokens.push(processor.nextToken()) } return tokens } function run(css, tokens, opts) { equal(tokenize(css, opts), tokens) } test('tokenizes empty file', () => { run('', []) }) test('tokenizes space', () => { run('\r\n \f\t', [['space', '\r\n \f\t']]) }) test('tokenizes word', () => { run('ab', [['word', 'ab', 0, 1]]) }) test('splits word by !', () => { run('aa!bb', [ ['word', 'aa', 0, 1], ['word', '!bb', 2, 4] ]) }) test('changes lines in spaces', () => { run('a \n b', [ ['word', 'a', 0, 0], ['space', ' \n '], ['word', 'b', 4, 4] ]) }) test('tokenizes control chars', () => { run('{:;}', [ ['{', '{', 0], [':', ':', 1], [';', ';', 2], ['}', '}', 3] ]) }) test('escapes control symbols', () => { run('\\(\\{\\"\\@\\\\""', [ ['word', '\\(', 0, 1], ['word', '\\{', 2, 3], ['word', '\\"', 4, 5], ['word', '\\@', 6, 7], ['word', '\\\\', 8, 9], ['string', '""', 10, 11] ]) }) test('escapes backslash', () => { run('\\\\\\\\{', [ ['word', '\\\\\\\\', 0, 3], ['{', '{', 4] ]) }) test('tokenizes simple brackets', () => { run('(ab)', [['brackets', '(ab)', 0, 3]]) }) test('tokenizes square brackets', () => { run('a[bc]', [ ['word', 'a', 0, 0], ['[', '[', 1], ['word', 'bc', 2, 3], [']', ']', 4] ]) }) test('tokenizes complicated brackets', () => { run('(())("")(/**/)(\\\\)(\n)(', [ ['(', '(', 0], ['brackets', '()', 1, 2], [')', ')', 3], ['(', '(', 4], ['string', '""', 5, 6], [')', ')', 7], ['(', '(', 8], ['comment', '/**/', 9, 12], [')', ')', 13], ['(', '(', 14], ['word', '\\\\', 15, 16], [')', ')', 17], ['(', '(', 18], ['space', '\n'], [')', ')', 20], ['(', '(', 21] ]) }) test('tokenizes string', () => { run('\'"\'"\\""', [ ['string', "'\"'", 0, 2], ['string', '"\\""', 3, 6] ]) }) test('tokenizes escaped string', () => { run('"\\\\"', [['string', '"\\\\"', 0, 3]]) }) test('changes lines in strings', () => { run('"\n\n""\n\n"', [ ['string', '"\n\n"', 0, 3], ['string', '"\n\n"', 4, 7] ]) }) test('tokenizes at-word', () => { run('@word ', [ ['at-word', '@word', 0, 4], ['space', ' '] ]) }) test('tokenizes at-word end', () => { run('@one{@two()@three""@four;', [ ['at-word', '@one', 0, 3], ['{', '{', 4], ['at-word', '@two', 5, 8], ['brackets', '()', 9, 10], ['at-word', '@three', 11, 16], ['string', '""', 17, 18], ['at-word', '@four', 19, 23], [';', ';', 24] ]) }) test('tokenizes urls', () => { run('url(/*\\))', [ ['word', 'url', 0, 2], ['brackets', '(/*\\))', 3, 8] ]) }) test('tokenizes quoted urls', () => { run('url(")")', [ ['word', 'url', 0, 2], ['(', '(', 3], ['string', '")"', 4, 6], [')', ')', 7] ]) }) test('tokenizes at-symbol', () => { run('@', [['at-word', '@', 0, 0]]) }) test('tokenizes comment', () => { run('/* a\nb */', [['comment', '/* a\nb */', 0, 8]]) }) test('changes lines in comments', () => { run('a/* \n */b', [ ['word', 'a', 0, 0], ['comment', '/* \n */', 1, 7], ['word', 'b', 8, 8] ]) }) test('supports line feed', () => { run('a\fb', [ ['word', 'a', 0, 0], ['space', '\f'], ['word', 'b', 2, 2] ]) }) test('supports carriage return', () => { run('a\rb\r\nc', [ ['word', 'a', 0, 0], ['space', '\r'], ['word', 'b', 2, 2], ['space', '\r\n'], ['word', 'c', 5, 5] ]) }) test('tokenizes CSS', () => { let css = 'a {\n' + ' content: "a";\n' + ' width: calc(1px;)\n' + ' }\n' + '/* small screen */\n' + '@media screen {}' run(css, [ ['word', 'a', 0, 0], ['space', ' '], ['{', '{', 2], ['space', '\n '], ['word', 'content', 6, 12], [':', ':', 13], ['space', ' '], ['string', '"a"', 15, 17], [';', ';', 18], ['space', '\n '], ['word', 'width', 22, 26], [':', ':', 27], ['space', ' '], ['word', 'calc', 29, 32], ['brackets', '(1px;)', 33, 38], ['space', '\n '], ['}', '}', 42], ['space', '\n'], ['comment', '/* small screen */', 44, 61], ['space', '\n'], ['at-word', '@media', 63, 68], ['space', ' '], ['word', 'screen', 70, 75], ['space', ' '], ['{', '{', 77], ['}', '}', 78] ]) }) test('throws error on unclosed string', () => { throws(() => { tokenize(' "') }, /:1:2: Unclosed string/) }) test('throws error on unclosed comment', () => { throws(() => { tokenize(' /*') }, /:1:2: Unclosed comment/) }) test('throws error on unclosed url', () => { throws(() => { tokenize('url(') }, /:1:4: Unclosed bracket/) }) test('ignores unclosing string on request', () => { run( ' "', [ ['space', ' '], ['string', '"', 1, 2] ], { ignoreErrors: true } ) }) test('ignores unclosing comment on request', () => { run( ' /*', [ ['space', ' '], ['comment', '/*', 1, 3] ], { ignoreErrors: true } ) }) test('ignores unclosing function on request', () => { run( 'url(', [ ['word', 'url', 0, 2], ['brackets', '(', 3, 3] ], { ignoreErrors: true } ) }) test('tokenizes hexadecimal escape', () => { run('\\0a \\09 \\z ', [ ['word', '\\0a ', 0, 3], ['word', '\\09 ', 4, 7], ['word', '\\z', 8, 9], ['space', ' '] ]) }) test('ignore unclosed per token request', () => { function token(css, opts) { let processor = tokenizer(new Input(css), opts) let tokens = [] while (!processor.endOfFile()) { tokens.push(processor.nextToken({ ignoreUnclosed: true })) } return tokens } let css = "How's it going (" let tokens = token(css, {}) let expected = [ ['word', 'How', 0, 2], ['string', "'s", 3, 4], ['space', ' '], ['word', 'it', 6, 7], ['space', ' '], ['word', 'going', 9, 13], ['space', ' '], ['(', '(', 15] ] equal(tokens, expected) }) test('provides correct position', () => { let css = 'Three tokens' let processor = tokenizer(new Input(css)) is(processor.position(), 0) processor.nextToken() is(processor.position(), 5) processor.nextToken() is(processor.position(), 6) processor.nextToken() is(processor.position(), 12) processor.nextToken() is(processor.position(), 12) }) test.run() postcss-8.5.6/test/types.ts000066400000000000000000000027231502402132100157150ustar00rootroot00000000000000import postcss, { Document, PluginCreator } from '../lib/postcss.js' import { RootRaws } from '../lib/root.js' const plugin: PluginCreator = prop => { return { Declaration: (decl, { Comment, result }) => { if (decl.prop === prop) { decl.warn(result, `${decl.prop} found in ${decl.parent?.nodes.length}`) decl.replaceWith(new Comment({ text: `${decl.prop} removed` })) } }, postcssPlugin: 'remover' } } plugin.postcss = true postcss([plugin]) .process('h1{color: black;}', { from: undefined }) .then(result => { console.log(result.root.parent) console.log(result.css) }) function parseMarkdown(): Document { return new Document() } let doc = postcss().process('a{}', { parser: parseMarkdown }).root console.log(doc.toString()) function parentCanNarrowType(): never | void { let atRule = postcss.parse('@a{b{}}').first if (atRule?.type !== 'atrule') return let rule = atRule.first if (rule?.type !== 'rule') return let parent = rule.parent switch (parent?.type) { case undefined: console.log('ok') break case 'atrule': console.log(parent.params) break case 'root': { let raws: RootRaws = parent.raws console.log(raws) } break case 'rule': console.log(rule.selector) break default: { let exhaustiveCheck: never = parent return exhaustiveCheck } } } parentCanNarrowType() export default plugin postcss-8.5.6/test/version.js000077500000000000000000000003621502402132100162240ustar00rootroot00000000000000#!/usr/bin/env node let Processor = require('../lib/processor') let pkg = require('../package') let instance = new Processor() if (pkg.version !== instance.version) { throw new Error('Version in Processor is not equal to package.json') } postcss-8.5.6/test/visitor.test.ts000077500000000000000000001136661502402132100172420ustar00rootroot00000000000000import { delay } from 'nanodelay' import { basename, resolve } from 'path' import { test } from 'uvu' import { equal, is, throws, type } from 'uvu/assert' import postcss, { AnyNode, AtRule, Container, Declaration, Helpers, Plugin, PluginCreator, Root, Rule } from '../lib/postcss.js' function hasAlready(parent: Container | undefined, selector: string): boolean { if (typeof parent === 'undefined') return false return ( parent.nodes?.some(i => { return i.type === 'rule' && i.selectors.includes(selector) }) ?? false ) } function addIndex(array: any[][]): any[][] { return array.map((i, index) => { return [index, ...i] }) } function buildVisitor(): [[string, string][], Plugin] { let visits: [string, string][] = [] let visitor: Plugin = { AtRule(i) { visits.push(['AtRule', i.name]) }, AtRuleExit(i) { visits.push(['AtRuleExit', i.name]) }, Comment(i) { visits.push(['Comment', i.text]) }, CommentExit(i) { visits.push(['CommentExit', i.text]) }, Declaration(i) { visits.push(['Declaration', i.prop + ': ' + i.value]) }, DeclarationExit(i) { visits.push(['DeclarationExit', i.prop + ': ' + i.value]) }, Document(i) { visits.push(['Document', `${i.nodes.length}`]) }, DocumentExit(i) { visits.push(['DocumentExit', `${i.nodes.length}`]) }, Once(i) { visits.push(['Once', `${i.nodes.length}`]) }, OnceExit(i) { visits.push(['OnceExit', `${i.nodes.length}`]) }, postcssPlugin: 'visitor', Root(i) { visits.push(['Root', `${i.nodes.length}`]) }, RootExit(i) { visits.push(['RootExit', `${i.nodes.length}`]) }, Rule(i) { visits.push(['Rule', i.selector]) }, RuleExit(i) { visits.push(['RuleExit', i.selector]) } } return [visits, visitor] } let replaceColorGreenClassic: Plugin = { Once(root) { root.walkDecls('color', decl => { decl.value = 'green' }) }, postcssPlugin: 'replace-color' } let willChangeVisitor: Plugin = { Declaration(node) { if (node.prop !== 'will-change') return if (!node.parent) return let already = node.parent.some(i => { return i.type === 'decl' && i.prop === 'backface-visibility' }) if (already) return node.cloneBefore({ prop: 'backface-visibility', value: 'hidden' }) }, postcssPlugin: 'will-change' } let addPropsVisitor: Plugin = { Declaration(node) { if (node.prop !== 'will-change') return node.root().walkDecls('color', decl => { if (!decl.parent) return let already = decl.parent.some(i => { return i.type === 'decl' && i.prop === 'will-change' }) if (already) return decl.cloneBefore({ prop: 'will-change', value: 'transform' }) }) }, postcssPlugin: 'add-props' } let replaceAllButRedToGreen: Plugin = { Declaration(node) { if (node.prop === 'color' && node.value !== 'red') { node.value = 'green' } }, postcssPlugin: 'replace-not-red-to-green' } let replaceGreenToRed: Plugin = { Declaration(node) { if (node.prop === 'color' && node.value === 'green') { node.value = 'red' } }, postcssPlugin: 'replace-green-to-red' } let replacePrintToMobile: Plugin = { AtRule(node) { if (node.params === '(print)') { node.params = '(mobile)' } }, postcssPlugin: 'replace-to-mobile' } let replaceScreenToPrint: Plugin = { AtRule(node) { if (node.params === '(screen)') { node.params = '(print)' } }, postcssPlugin: 'replace-to-print' } let postcssFocus: Plugin = { postcssPlugin: 'postcss-focus', Rule(rule) { if (rule.selector.includes(':hover')) { let focuses: string[] = [] rule.selectors.forEach(selector => { if (selector.includes(':hover')) { let replaced = selector.replace(/:hover/g, ':focus') if (!hasAlready(rule.parent, replaced)) { focuses.push(replaced) } } }) if (focuses.length) { rule.selectors = rule.selectors.concat(focuses) } } } } let hidden: Plugin = { Declaration(decl) { if (decl.prop !== 'display') return let value = decl.value let rule = decl.parent as Rule if (value.includes('disappear')) { decl.cloneBefore({ prop: 'display', value: 'none !important' }) decl.cloneBefore({ prop: 'visibility', value: 'hidden' }) decl.remove() } if (value.includes('hidden')) { let ruleSelectors = rule.selectors.map(i => { return `${i}.focusable:active,${i}.focusable:focus` }) let newRule = rule.cloneAfter({ selectors: ruleSelectors }).removeAll() newRule.append('display: table; position: static; clear: both;') decl.cloneBefore({ prop: 'position', value: 'absolute' }) decl.cloneBefore({ prop: 'width', value: '1px' }) decl.cloneBefore({ prop: 'height', value: '1px' }) decl.cloneBefore({ prop: 'margin', value: '-1px' }) decl.cloneBefore({ prop: 'padding', value: '0' }) decl.cloneBefore({ prop: 'border', value: '0' }) decl.cloneBefore({ prop: 'overflow', value: 'hidden' }) decl.cloneBefore({ prop: 'clip', value: 'rect(0 0 0 0)' }) decl.remove() } if (value.includes('invisible')) { decl.cloneBefore({ prop: 'visibility', value: 'hidden' }) decl.remove() } }, postcssPlugin: 'hidden' } function createPlugin(creator: () => Plugin): PluginCreator { let result = creator as PluginCreator result.postcss = true return result } let postcssAlias = createPlugin(() => { let aliases: any = {} return { Declaration(decl) { let value = aliases[decl.prop] if (value !== undefined) { decl.replaceWith({ important: decl.important, prop: value, value: decl.value }) } }, Once(root) { root.walkAtRules('alias', rule => { rule.walkDecls(decl => { aliases[decl.prop] = decl.value }) rule.remove() }) }, postcssPlugin: 'postcss-alias' } }) test('works classic plugin replace-color', async () => { let { css } = await postcss([replaceColorGreenClassic]).process( '.a{ color: red; } ' + '.b{ will-change: transform; }', { from: 'a.css' } ) is(css, '.a{ color: green; } ' + '.b{ will-change: transform; }') }) test('works visitor plugin will-change', () => { let { css } = postcss([willChangeVisitor]).process( '.foo { will-change: transform; }', { from: 'a.css' } ) is(css, '.foo { backface-visibility: hidden; will-change: transform; }') }) test('works visitor plugin add-prop', async () => { let { css } = await postcss([addPropsVisitor]).process( '.a{ color: red; } .b{ will-change: transform; }', { from: 'a.css' } ) is( css, '.a{ will-change: transform; color: red; } ' + '.b{ will-change: transform; }' ) }) test('works visitor plugin add-prop in document with single root', async () => { let document = postcss.document({ nodes: [postcss.parse('.a{ color: red; } .b{ will-change: transform; }')] }) let { css } = await postcss([addPropsVisitor]).process(document, { from: 'a.css' }) is( css, '.a{ will-change: transform; color: red; } ' + '.b{ will-change: transform; }' ) }) test('works visitor plugin add-prop in document with two roots', async () => { let document = postcss.document({ nodes: [ postcss.parse('.a{ color: red; }'), postcss.parse('.b{ will-change: transform; }') ] }) let { css } = await postcss([addPropsVisitor]).process(document, { from: 'a.css' }) is(css, '.a{ color: red; }' + '.b{ will-change: transform; }') }) test('works with at-rule params', () => { let { css } = postcss([replacePrintToMobile, replaceScreenToPrint]).process( '@media (screen) {}', { from: 'a.css' } ) is(css, '@media (mobile) {}') }) test('wraps node to proxies', () => { let proxy: any let root: Root | undefined postcss({ Once(node) { root = node }, postcssPlugin: 'proxyCatcher', Rule(node) { proxy = node } }).process('a{color:black}', { from: 'a.css' }).css if (!root) throw new Error('Nodes were not catched') let rule = root.first as Rule equal(proxy.proxyOf, rule) equal(proxy.root().proxyOf, rule.root()) equal(proxy.nodes[0].proxyOf, rule.first) equal(proxy.first.proxyOf, rule.first) type(proxy.unknown, 'undefined') is( proxy.some((decl: Declaration) => decl.prop === 'color'), true ) is( proxy.every((decl: Declaration) => decl.prop === 'color'), true ) let props: string[] = [] proxy.walkDecls((decl: Declaration) => props.push(decl.prop)) equal(props, ['color']) }) const cssThree = '.a{ color: red; } .b{ will-change: transform; }' const expectedThree = '.a{ ' + 'backface-visibility: hidden; ' + 'will-change: transform; ' + 'color: green; ' + '} ' + '.b{ backface-visibility: hidden; will-change: transform; }' test('work of three plug-ins; sequence 1', async () => { let { css } = await postcss([ replaceColorGreenClassic, willChangeVisitor, addPropsVisitor ]).process(cssThree, { from: 'a.css' }) is(css, expectedThree) }) test('work of three plug-ins; sequence 2', async () => { let { css } = await postcss([ addPropsVisitor, replaceColorGreenClassic, willChangeVisitor ]).process(cssThree, { from: 'a.css' }) is(css, expectedThree) }) const cssThreeDocument = postcss.document({ nodes: [ postcss.parse('.a{ color: red; }'), postcss.parse('.b{ will-change: transform; }') ] }) const expectedThreeDocument = '.a{ color: green; }' + '.b{ backface-visibility: hidden; will-change: transform; }' test('work of three plug-ins in a document; sequence 1', async () => { let { css } = await postcss([ replaceColorGreenClassic, willChangeVisitor, addPropsVisitor ]).process(cssThreeDocument, { from: 'a.css' }) is(css, expectedThreeDocument) }) test('work of three plug-ins in a document; sequence 2', async () => { let { css } = await postcss([ addPropsVisitor, replaceColorGreenClassic, willChangeVisitor ]).process(cssThreeDocument, { from: 'a.css' }) is(css, expectedThreeDocument) }) const cssThroughProps = '.a{color: yellow;}' const expectedThroughProps = '.a{color: red;}' test('change in node values through props; sequence 1', async () => { let { css } = await postcss([ replaceGreenToRed, replaceAllButRedToGreen ]).process(cssThroughProps, { from: 'a.css' }) is(css, expectedThroughProps) }) test('change in node values through props; sequence 2', async () => { let { css } = await postcss([ replaceAllButRedToGreen, replaceGreenToRed ]).process(cssThroughProps, { from: 'a.css' }) is(css, expectedThroughProps) }) test('works visitor plugin postcss-focus', async () => { let input = '*:focus { outline: 0; }.button:hover { background: red; }' let expected = '*:focus { outline: 0; }' + '.button:hover, .button:focus { background: red; }' let { css } = await postcss([postcssFocus]).process(input, { from: 'a.css' }) is(css, expected) }) test('works visitor plugin hidden', async () => { let input = 'h2{' + 'display: hidden;' + '}' let expected = 'h2{' + 'position: absolute;' + 'width: 1px;' + 'height: 1px;' + 'margin: -1px;' + 'padding: 0;' + 'border: 0;' + 'overflow: hidden;' + 'clip: rect(0 0 0 0);' + '}' + 'h2.focusable:active,' + 'h2.focusable:focus{' + 'display: table;' + 'position: static;' + 'clear: both;' + '}' let { css } = await postcss([hidden]).process(input, { from: 'a.css' }) is(css, expected) }) let cssFocusHidden = '*:focus { outline: 0; }' + '.button:hover { background: red; }' + 'h2:hover{' + 'display: hidden;' + '}' let expectedFocusHidden = '*:focus { outline: 0; }' + '.button:hover, .button:focus { background: red; }' + 'h2:hover,h2:focus{' + 'position: absolute;' + 'width: 1px;' + 'height: 1px;' + 'margin: -1px;' + 'padding: 0;' + 'border: 0;' + 'overflow: hidden;' + 'clip: rect(0 0 0 0);' + '}' + 'h2:hover.focusable:active,' + 'h2:hover.focusable:focus,' + 'h2:focus.focusable:active,' + 'h2:focus.focusable:focus{' + 'display: table;' + 'position: static;' + 'clear: both;' + '}' test('works visitor plugins postcss-focus and hidden; sequence 1', async () => { let { css } = await postcss([hidden, postcssFocus]).process(cssFocusHidden, { from: 'a.css' }) is(css, expectedFocusHidden) }) test('works visitor plugins postcss-focus and hidden; sequence 2', async () => { let { css } = await postcss([postcssFocus, hidden]).process(cssFocusHidden, { from: 'a.css' }) is(css, expectedFocusHidden) }) test('works visitor plugin postcss-alias', () => { let input = '@alias { fs: font-size; bg: background; }' + '.aliased { fs: 16px; bg: white; }' let expected = '.aliased { font-size: 16px; background: white; }' let { css } = postcss([postcssAlias]).process(input, { from: 'a.css' }) is(css, expected) }) test('adds plugin to error', () => { let broken: Plugin = { postcssPlugin: 'broken', Rule(rule) { throw rule.error('test') } } let error: any try { postcss([broken]).process('a{}', { from: 'broken.css' }).css } catch (e) { error = e } is(error.message, `broken: ${resolve('broken.css')}:1:1: test`) is(error.postcssNode.toString(), 'a{}') is(error.stack.includes('broken.css:1:1'), true) }) test('adds plugin to async error', async () => { let broken: Plugin = { postcssPlugin: 'broken', async Rule(rule) { await delay(1) throw rule.error('test') } } let error: any try { await postcss([broken]).process('a{}', { from: 'broken.css' }) } catch (e) { error = e } is(error.message, `broken: ${resolve('broken.css')}:1:1: test`) is(error.postcssNode.toString(), 'a{}') is(error.stack.includes('broken.css:1:1'), true) }) test('adds sync plugin to async error', async () => { let broken: Plugin = { postcssPlugin: 'broken', Rule(rule) { throw rule.error('test') } } let error: any try { await postcss([broken]).process('a{}', { from: 'broken.css' }) } catch (e) { error = e } is(error.message, `broken: ${resolve('broken.css')}:1:1: test`) is(error.postcssNode.toString(), 'a{}') is(error.stack.includes('broken.css:1:1'), true) }) test('adds node to error', () => { let broken: Plugin = { postcssPlugin: 'broken', Rule() { throw new Error('test') } } let error: any try { postcss([broken]).process('a{}', { from: 'broken.css' }).css } catch (e) { error = e } is(error.message, 'test') is(error.postcssNode.toString(), 'a{}') is(error.stack.includes('broken.css:1:1'), true) }) test('adds node to async error', async () => { let broken: Plugin = { postcssPlugin: 'broken', async Rule() { await delay(1) throw new Error('test') } } let error: any try { await postcss([broken]).process('a{}', { from: 'broken.css' }) } catch (e) { error = e } is(error.message, 'test') is(error.postcssNode.toString(), 'a{}') is(error.stack.includes('broken.css:1:1'), true) }) test('shows error on sync call async plugins', () => { let asyncPlugin: Plugin = { postcssPlugin: 'asyncPlugin', async Rule() {} } let error: any try { postcss([asyncPlugin]).process('a{}', { from: 'broken.css' }).css } catch (e) { error = e } is(error.message.includes('work with async plugins'), true) }) test('passes helpers', async () => { function check(node: AnyNode, helpers: Helpers): void { equal(helpers.result.messages, []) is(typeof helpers.postcss, 'function') is(helpers.comment().type, 'comment') is(new helpers.Comment().type, 'comment') equal(helpers.list, postcss.list) } let syncPlugin: Plugin = { Once: check, OnceExit: check, postcssPlugin: 'syncPlugin', Rule: check, RuleExit: check } let asyncPlugin: Plugin = { async Once(node, helpers) { await delay(1) check(node, helpers) }, async OnceExit(node, helpers) { await delay(1) check(node, helpers) }, postcssPlugin: 'syncPlugin', async Rule(node, helpers) { await delay(1) check(node, helpers) } } postcss([syncPlugin]).process('a{}', { from: 'a.css' }).css await postcss([asyncPlugin]).process('a{}', { from: 'a.css' }) }) test('passes helpers in a document', async () => { function check(node: AnyNode, helpers: Helpers): void { equal(helpers.result.messages, []) type(helpers.postcss, 'function') is(helpers.comment().type, 'comment') is(new helpers.Comment().type, 'comment') equal(helpers.list, postcss.list) } let syncPlugin: Plugin = { Once: check, OnceExit: check, postcssPlugin: 'syncPlugin', Rule: check, RuleExit: check } let asyncPlugin: Plugin = { async Once(node, helpers) { await delay(1) check(node, helpers) }, async OnceExit(node, helpers) { await delay(1) check(node, helpers) }, postcssPlugin: 'syncPlugin', async Rule(node, helpers) { await delay(1) check(node, helpers) } } postcss([syncPlugin]).process( postcss.document({ nodes: [postcss.parse('a{}')] }), { from: 'a.css' } ).css await postcss([asyncPlugin]).process( postcss.document({ nodes: [postcss.parse('a{}')] }), { from: 'a.css' } ) }) test('detects non-changed values', () => { let plugin: Plugin = { Declaration(decl) { decl.value = 'red' }, postcssPlugin: 'test' } is( postcss([plugin]).process('a{ color: black; background: white; }', { from: 'a.css' }).css, 'a{ color: red; background: red; }' ) }) test('allows runtime listeners', () => { let root = false let plugin: Plugin = { Declaration(decl) { decl.value = 'red' }, postcssPlugin: 'test', prepare(result) { return { Once() { root = true }, Rule(rule) { rule.selector = basename(result.opts.from ?? '') } } } } is( postcss([plugin]).process('a{ color: black }', { from: 'a.css' }).css, 'a.css{ color: red }' ) is(root, true) }) test('works correctly with nodes changes', () => { let plugin: Plugin = { postcssPlugin: 'test', Rule(rule) { if (!rule.some(i => i.type === 'decl' && i.prop === 'z-index')) { rule.prepend({ prop: 'z-index', value: '1' }) } } } is( postcss([plugin]).process('a{ color: black }', { from: 'a.css' }).css, 'a{ z-index: 1; color: black }' ) }) test('throws error on unknown plugin property', () => { let plugin: any = { NO: true, postcssPlugin: 'test' } throws(() => { postcss([plugin]).process('').css }, /Unknown event NO in test\. Try to update PostCSS \(\d/) }) test('unwraps nodes on inserting', () => { let moveNode: Plugin = { Declaration: { color: decl => { if (decl.parent?.type !== 'root') { decl.root().append(decl) } } }, postcssPlugin: 'moveNode' } let root = postcss([moveNode]).process('a{color:red}').root equal((root.last as any).proxyOf, root.last) }) let redToGreen: Plugin = { Declaration: { color: decl => { if (decl.value === 'red') { decl.value = 'green' } } }, postcssPlugin: 'redToGreen' } let greenToBlue: Plugin = { Declaration(decl) { if (decl.value === 'green') { decl.value = 'blue' } }, postcssPlugin: 'greenToBlue' } let fooToBar: Plugin = { postcssPlugin: 'fooToBar', Rule(rule) { if (rule.selector === '.foo') { rule.selectors = ['.bar'] } } } let mixins: Plugin = { postcssPlugin: 'mixin', prepare() { let mixin: AnyNode | undefined return { AtRule: { 'apply-mixin': atRule => { if (mixin) atRule.replaceWith(mixin) }, 'define-mixin': atRule => { if (atRule.first) mixin = atRule.first atRule.remove() } } } } } let insertFirst: Plugin = { AtRule: { 'insert-first': atRule => { let first = atRule.root().first if (first) atRule.replaceWith(first) } }, postcssPlugin: 'insertFirst' } for (let funcType of ['sync', 'async']) { test(`walks ${funcType} through tree`, async () => { let [visits, visitor] = buildVisitor() let processor = postcss([visitor]).process( `@media screen { body { /* comment */ background: white; padding: 10px; } a { color: blue; } }`, { from: 'a.css' } ) if (funcType === 'sync') { processor.css } else { await processor } equal( addIndex(visits), addIndex([ ['Once', '1'], ['Root', '1'], ['AtRule', 'media'], ['Rule', 'body'], ['Comment', 'comment'], ['CommentExit', 'comment'], ['Declaration', 'background: white'], ['DeclarationExit', 'background: white'], ['Declaration', 'padding: 10px'], ['DeclarationExit', 'padding: 10px'], ['RuleExit', 'body'], ['Rule', 'a'], ['Declaration', 'color: blue'], ['DeclarationExit', 'color: blue'], ['RuleExit', 'a'], ['AtRuleExit', 'media'], ['RootExit', '1'], ['OnceExit', '1'] ]) ) }) test(`walks ${funcType} through tree in a document`, async () => { let document = postcss.document({ nodes: [ postcss.parse(`@media screen { body { /* comment */ background: white; padding: 10px; } a { color: blue; } }`) ] }) let [visits, visitor] = buildVisitor() let processor = postcss([visitor]).process(document, { from: 'a.css' }) if (funcType === 'sync') { processor.css } else { await processor } equal( addIndex(visits), addIndex([ ['Once', '1'], ['Document', '1'], ['Root', '1'], ['AtRule', 'media'], ['Rule', 'body'], ['Comment', 'comment'], ['CommentExit', 'comment'], ['Declaration', 'background: white'], ['DeclarationExit', 'background: white'], ['Declaration', 'padding: 10px'], ['DeclarationExit', 'padding: 10px'], ['RuleExit', 'body'], ['Rule', 'a'], ['Declaration', 'color: blue'], ['DeclarationExit', 'color: blue'], ['RuleExit', 'a'], ['AtRuleExit', 'media'], ['RootExit', '1'], ['DocumentExit', '1'], ['OnceExit', '1'] ]) ) }) test(`walks ${funcType} during transformations`, async () => { let [visits, visitor] = buildVisitor() let result = postcss([ visitor, redToGreen, greenToBlue, mixins, fooToBar, insertFirst ]).process( `.first { color: red; } @define-mixin { b { color: red; } } a { color: red; } @media (screen) { @insert-first; } .foo { background: red; } @apply-mixin;`, { from: 'a.css' } ) let output if (funcType === 'sync') { output = result.css } else { output = (await result).css } is( output, `a { color: blue; } @media (screen) {.first { color: blue; } } .bar { background: red; } b { color: blue; }` ) equal( addIndex(visits), addIndex([ ['Once', '6'], ['Root', '6'], ['Rule', '.first'], ['Declaration', 'color: red'], ['DeclarationExit', 'color: green'], ['RuleExit', '.first'], ['AtRule', 'define-mixin'], ['Rule', 'a'], ['Declaration', 'color: red'], ['DeclarationExit', 'color: green'], ['RuleExit', 'a'], ['AtRule', 'media'], ['AtRule', 'insert-first'], ['AtRuleExit', 'media'], ['Rule', '.foo'], ['Declaration', 'background: red'], ['DeclarationExit', 'background: red'], ['RuleExit', '.bar'], ['AtRule', 'apply-mixin'], ['RootExit', '4'], ['Root', '4'], ['Rule', 'a'], ['Declaration', 'color: green'], ['DeclarationExit', 'color: blue'], ['RuleExit', 'a'], ['AtRule', 'media'], ['Rule', '.first'], ['Declaration', 'color: green'], ['DeclarationExit', 'color: blue'], ['RuleExit', '.first'], ['AtRuleExit', 'media'], ['Rule', 'b'], ['Declaration', 'color: red'], ['DeclarationExit', 'color: green'], ['RuleExit', 'b'], ['RootExit', '4'], ['Root', '4'], ['Rule', 'a'], ['Declaration', 'color: blue'], ['DeclarationExit', 'color: blue'], ['RuleExit', 'a'], ['AtRule', 'media'], ['Rule', '.first'], ['Declaration', 'color: blue'], ['DeclarationExit', 'color: blue'], ['RuleExit', '.first'], ['AtRuleExit', 'media'], ['Rule', 'b'], ['Declaration', 'color: green'], ['DeclarationExit', 'color: blue'], ['RuleExit', 'b'], ['RootExit', '4'], ['Root', '4'], ['Rule', 'b'], ['Declaration', 'color: blue'], ['DeclarationExit', 'color: blue'], ['RuleExit', 'b'], ['RootExit', '4'], ['OnceExit', '4'] ]) ) }) test(`walks ${funcType} during transformations in a document`, async () => { let document = postcss.document({ nodes: [ postcss.parse( `.first { color: red; } @define-mixin { b { color: red; } } a { color: red; } @media (screen) { @insert-first; } .foo { background: red; } @apply-mixin;` ) ] }) let [visits, visitor] = buildVisitor() let result = postcss([ visitor, redToGreen, greenToBlue, mixins, fooToBar, insertFirst ]).process(document, { from: 'a.css' }) let output if (funcType === 'sync') { output = result.css } else { output = (await result).css } is( output, `a { color: blue; } @media (screen) {.first { color: blue; } } .bar { background: red; } b { color: blue; }` ) equal( addIndex(visits), addIndex([ ['Once', '6'], ['Document', '1'], ['Root', '6'], ['Rule', '.first'], ['Declaration', 'color: red'], ['DeclarationExit', 'color: green'], ['RuleExit', '.first'], ['AtRule', 'define-mixin'], ['Rule', 'a'], ['Declaration', 'color: red'], ['DeclarationExit', 'color: green'], ['RuleExit', 'a'], ['AtRule', 'media'], ['AtRule', 'insert-first'], ['AtRuleExit', 'media'], ['Rule', '.foo'], ['Declaration', 'background: red'], ['DeclarationExit', 'background: red'], ['RuleExit', '.bar'], ['AtRule', 'apply-mixin'], ['RootExit', '4'], ['DocumentExit', '1'], ['Document', '1'], ['Root', '4'], ['Rule', 'a'], ['Declaration', 'color: green'], ['DeclarationExit', 'color: blue'], ['RuleExit', 'a'], ['AtRule', 'media'], ['Rule', '.first'], ['Declaration', 'color: green'], ['DeclarationExit', 'color: blue'], ['RuleExit', '.first'], ['AtRuleExit', 'media'], ['Rule', 'b'], ['Declaration', 'color: red'], ['DeclarationExit', 'color: green'], ['RuleExit', 'b'], ['RootExit', '4'], ['DocumentExit', '1'], ['Document', '1'], ['Root', '4'], ['Rule', 'a'], ['Declaration', 'color: blue'], ['DeclarationExit', 'color: blue'], ['RuleExit', 'a'], ['AtRule', 'media'], ['Rule', '.first'], ['Declaration', 'color: blue'], ['DeclarationExit', 'color: blue'], ['RuleExit', '.first'], ['AtRuleExit', 'media'], ['Rule', 'b'], ['Declaration', 'color: green'], ['DeclarationExit', 'color: blue'], ['RuleExit', 'b'], ['RootExit', '4'], ['DocumentExit', '1'], ['Document', '1'], ['Root', '4'], ['Rule', 'b'], ['Declaration', 'color: blue'], ['DeclarationExit', 'color: blue'], ['RuleExit', 'b'], ['RootExit', '4'], ['DocumentExit', '1'], ['OnceExit', '4'] ]) ) }) test(`has ${funcType} property and at-rule name filters`, async () => { let filteredDecls: string[] = [] let allDecls: string[] = [] let filteredAtRules: string[] = [] let allAtRules: string[] = [] let allExits: string[] = [] let filteredExits: string[] = [] let scanner: Plugin = { AtRule: { '*': atRule => { allAtRules.push(atRule.name) }, 'media': atRule => { filteredAtRules.push(atRule.name) } }, Declaration: { '*': decl => { allDecls.push(decl.prop) }, 'color': decl => { filteredDecls.push(decl.prop) } }, DeclarationExit: { '*': decl => { allExits.push(decl.prop) }, 'color': decl => { filteredExits.push(decl.prop) } }, postcssPlugin: 'test' } let result = postcss([scanner]).process( `@charset "UTF-8"; @media (screen) { COLOR: black; z-index: 1 }`, { from: 'a.css' } ) if (funcType === 'sync') { result.css } else { await result } equal(filteredDecls, ['COLOR']) equal(allDecls, ['COLOR', 'z-index']) equal(filteredExits, ['COLOR']) equal(allExits, ['COLOR', 'z-index']) equal(filteredAtRules, ['media']) equal(allAtRules, ['charset', 'media']) }) test(`has ${funcType} property and at-rule name filters in a document`, async () => { let filteredDecls: string[] = [] let allDecls: string[] = [] let filteredAtRules: string[] = [] let allAtRules: string[] = [] let allExits: string[] = [] let filteredExits: string[] = [] let scanner: Plugin = { AtRule: { '*': atRule => { allAtRules.push(atRule.name) }, 'media': atRule => { filteredAtRules.push(atRule.name) } }, Declaration: { '*': decl => { allDecls.push(decl.prop) }, 'color': decl => { filteredDecls.push(decl.prop) } }, DeclarationExit: { '*': decl => { allExits.push(decl.prop) }, 'color': decl => { filteredExits.push(decl.prop) } }, postcssPlugin: 'test' } let document = postcss.document({ nodes: [ postcss.parse( `@charset "UTF-8"; @media (screen) { COLOR: black; z-index: 1 }` ) ] }) let result = postcss([scanner]).process(document, { from: 'a.css' }) if (funcType === 'sync') { result.css } else { await result } equal(filteredDecls, ['COLOR']) equal(allDecls, ['COLOR', 'z-index']) equal(filteredExits, ['COLOR']) equal(allExits, ['COLOR', 'z-index']) equal(filteredAtRules, ['media']) equal(allAtRules, ['charset', 'media']) }) test(`has ${funcType} OnceExit listener`, async () => { let rootExit = 0 let OnceExit = 0 let plugin: Plugin = { OnceExit() { OnceExit += 1 }, postcssPlugin: 'test', RootExit() { rootExit += 1 }, Rule(rule) { rule.remove() } } let result = postcss([plugin]).process('a{}', { from: 'a.css' }) if (funcType === 'sync') { result.css } else { await result } is(rootExit, 2) is(OnceExit, 1) }) test(`has ${funcType} OnceExit listener in a document with one root`, async () => { let RootExit = 0 let OnceExit = 0 let DocumentExit = 0 let plugin: Plugin = { DocumentExit() { DocumentExit += 1 }, OnceExit() { OnceExit += 1 }, postcssPlugin: 'test', RootExit() { RootExit += 1 }, Rule(rule) { rule.remove() } } let document = postcss.document({ nodes: [postcss.parse('a{}')] }) let result = postcss([plugin]).process(document, { from: 'a.css' }) if (funcType === 'sync') { result.css } else { await result } is(RootExit, 2) is(DocumentExit, 2) is(OnceExit, 1) }) test(`has ${funcType} OnceExit listener in a document with two roots`, async () => { let RootExit = 0 let OnceExit = 0 let DocumentExit = 0 let plugin: Plugin = { DocumentExit() { DocumentExit += 1 }, OnceExit() { OnceExit += 1 }, postcssPlugin: 'test', RootExit() { RootExit += 1 }, Rule(rule) { rule.remove() } } let document = postcss.document({ nodes: [postcss.parse('a{}'), postcss.parse('b{}')] }) let result = postcss([plugin]).process(document, { from: 'a.css' }) if (funcType === 'sync') { result.css } else { await result } is(RootExit, 4) is(DocumentExit, 2) is(OnceExit, 2) // 2 roots === 2 OnceExit }) } test('throws error from async OnceExit', async () => { let plugin: Plugin = { OnceExit() { throw new Error('test Exit error') }, postcssPlugin: 'test' } let result = postcss([plugin]).process('a{ color: black }', { from: 'a.css' }) let error: any try { await result } catch (e) { error = e } is(error.message, 'test Exit error') }) test('rescan Root in another processor', () => { let [visits, visitor] = buildVisitor() let root = postcss([visitor]).process('a{z-index:1}', { from: 'a.css' }).root visits.splice(0, visits.length) postcss([visitor]).process(root, { from: 'a.css' }).root equal(visits, [ ['Once', '1'], ['Root', '1'], ['Rule', 'a'], ['Declaration', 'z-index: 1'], ['DeclarationExit', 'z-index: 1'], ['RuleExit', 'a'], ['RootExit', '1'], ['OnceExit', '1'] ]) }) test('rescan Root in another processor in a document', () => { let [visits, visitor] = buildVisitor() let root = postcss([visitor]).process('a{z-index:1}', { from: 'a.css' }).root let document = postcss.document({ nodes: [root] }) visits.splice(0, visits.length) postcss([visitor]).process(document, { from: 'a.css' }).root equal(visits, [ ['Once', '1'], ['Document', '1'], ['Root', '1'], ['Rule', 'a'], ['Declaration', 'z-index: 1'], ['DeclarationExit', 'z-index: 1'], ['RuleExit', 'a'], ['RootExit', '1'], ['DocumentExit', '1'], ['OnceExit', '1'] ]) }) test('marks cleaned nodes as dirty on moving', () => { let mover: Plugin = { postcssPlugin: 'mover', Rule(rule) { if (rule.selector === 'b') { let a = rule.prev() if (a) rule.append(a) } } } let [visits, visitor] = buildVisitor() postcss([mover, visitor]).process('a { color: black } b { }', { from: 'a.css' }).root equal(visits, [ ['Once', '2'], ['Root', '2'], ['Rule', 'a'], ['Declaration', 'color: black'], ['DeclarationExit', 'color: black'], ['RuleExit', 'a'], ['Rule', 'b'], ['Rule', 'a'], ['Declaration', 'color: black'], ['DeclarationExit', 'color: black'], ['RuleExit', 'a'], ['RuleExit', 'b'], ['RootExit', '1'], ['Root', '1'], ['Rule', 'b'], ['RuleExit', 'b'], ['RootExit', '1'], ['OnceExit', '1'] ]) }) test('marks cleaned nodes as dirty on moving in a document', () => { let mover: Plugin = { postcssPlugin: 'mover', Rule(rule) { if (rule.selector === 'b') { let a = rule.prev() if (a) rule.append(a) } } } let [visits, visitor] = buildVisitor() let document = postcss.document({ nodes: [postcss.parse('a { color: black } b { }')] }) postcss([mover, visitor]).process(document, { from: 'a.css' }).root equal(visits, [ ['Once', '2'], ['Document', '1'], ['Root', '2'], ['Rule', 'a'], ['Declaration', 'color: black'], ['DeclarationExit', 'color: black'], ['RuleExit', 'a'], ['Rule', 'b'], ['Rule', 'a'], ['Declaration', 'color: black'], ['DeclarationExit', 'color: black'], ['RuleExit', 'a'], ['RuleExit', 'b'], ['RootExit', '1'], ['DocumentExit', '1'], ['Document', '1'], ['Root', '1'], ['Rule', 'b'], ['RuleExit', 'b'], ['RootExit', '1'], ['DocumentExit', '1'], ['OnceExit', '1'] ]) }) test('append works after reassigning nodes through .parent', async () => { let plugin: Plugin = { OnceExit(root) { let firstNode = root.nodes[0] as AtRule let secondNode = root.nodes[1] as AtRule let rule2 = secondNode.nodes![0] rule2.parent!.nodes = rule2.parent!.nodes firstNode.append(...secondNode.nodes!) secondNode.remove() }, postcssPlugin: 'test', Rule(rule) { if ( !( rule.selector === '.nested' && rule.nodes.length === 1 && rule.nodes[0].type === 'atrule' ) ) { return } let atrule = rule.nodes[0] atrule.append(rule.clone({ nodes: [] }).append(...atrule.nodes!)) rule.after(atrule) rule.remove() } } let { css } = await postcss([plugin]).process( postcss.parse( `@media (min-width:640px) { .page { width: auto; } } ` + `.nested { @media (min-width:640px) { width: auto; } }` ), { from: 'whatever' } ) is( css, '@media (min-width:640px) { .page { width: auto; } .nested { width: auto } }' ) }) test.run() postcss-8.5.6/test/warning.test.ts000066400000000000000000000116041502402132100171720ustar00rootroot00000000000000import { resolve } from 'path' import { test } from 'uvu' import { is, type } from 'uvu/assert' import { decl, parse, Warning } from '../lib/postcss.js' test('outputs simple warning', () => { let warning = new Warning('text') is(warning.toString(), 'text') }) test('outputs warning with plugin', () => { let warning = new Warning('text', { plugin: 'plugin' }) is(warning.toString(), 'plugin: text') }) test('outputs warning with position', () => { let root = parse('a{}') let warning = new Warning('text', { node: root.first }) is(warning.toString(), ':1:1: text') }) test('outputs warning with plugin and node', () => { let file = resolve('a.css') let root = parse('a{}', { from: file }) let warning = new Warning('text', { node: root.first, plugin: 'plugin' }) is(warning.toString(), `plugin: ${file}:1:1: text`) }) test('outputs warning with index', () => { let file = resolve('a.css') let root = parse('@rule param {}', { from: file }) let warning = new Warning('text', { index: 7, node: root.first, plugin: 'plugin' }) is(warning.toString(), `plugin: ${file}:1:8: text`) }) test('outputs warning with word', () => { let file = resolve('a.css') let root = parse('@rule param {}', { from: file }) let warning = new Warning('text', { node: root.first, plugin: 'plugin', word: 'am' }) is(warning.toString(), `plugin: ${file}:1:10: text`) }) test('generates warning without source', () => { let node = decl({ prop: 'color', value: 'black' }) let warning = new Warning('text', { node }) is(warning.toString(), ': text') }) test('has line and column is undefined by default', () => { let warning = new Warning('text') type(warning.line, 'undefined') type(warning.column, 'undefined') type(warning.endLine, 'undefined') type(warning.endColumn, 'undefined') }) test('gets range from node', () => { let root = parse('a{}') let warning = new Warning('text', { node: root.first }) is(warning.line, 1) is(warning.column, 1) is(warning.endLine, 1) is(warning.endColumn, 4) }) test('gets range from node without end', () => { let root = parse('a{}') root.first!.source!.end = undefined let warning = new Warning('text', { node: root.first }) is(warning.line, 1) is(warning.column, 1) is(warning.endLine, 1) is(warning.endColumn, 2) }) test('gets range from node with endIndex 3', () => { let root = parse('a{}') let warning = new Warning('text', { endIndex: 3, index: 0, node: root.first }) is(warning.line, 1) is(warning.column, 1) is(warning.endLine, 1) is(warning.endColumn, 4) }) test('gets range from node with endIndex 0', () => { let root = parse('a{}') let warning = new Warning('text', { endIndex: 0, index: 0, node: root.first }) is(warning.line, 1) is(warning.column, 1) is(warning.endLine, 1) is(warning.endColumn, 2) }) test('gets range from word', () => { let root = parse('a b{}') let warning = new Warning('text', { node: root.first, word: 'b' }) is(warning.line, 1) is(warning.column, 3) is(warning.endLine, 1) is(warning.endColumn, 4) }) test('gets range from index', () => { let root = parse('a b{}') let warning = new Warning('text', { index: 2, node: root.first }) is(warning.line, 1) is(warning.column, 3) is(warning.endLine, 1) is(warning.endColumn, 4) }) test('gets range from index and endIndex', () => { let root = parse('a b{}') let warning = new Warning('text', { endIndex: 3, index: 2, node: root.first }) is(warning.line, 1) is(warning.column, 3) is(warning.endLine, 1) is(warning.endColumn, 4) }) test('gets range from start', () => { let root = parse('a b{}') let warning = new Warning('text', { node: root.first, start: { column: 3, line: 1 } }) is(warning.line, 1) is(warning.column, 3) is(warning.endLine, 1) is(warning.endColumn, 6) }) test('gets range from end', () => { let root = parse('a b{}') let warning = new Warning('text', { end: { column: 3, line: 1 }, node: root.first }) is(warning.line, 1) is(warning.column, 1) is(warning.endLine, 1) is(warning.endColumn, 3) }) test('gets range from start and end', () => { let root = parse('a b{}') let warning = new Warning('text', { end: { column: 4, line: 1 }, node: root.first, start: { column: 3, line: 1 } }) is(warning.line, 1) is(warning.column, 3) is(warning.endLine, 1) is(warning.endColumn, 4) }) test('always returns exclusive ends', () => { let root = parse('a b{}') let warning = new Warning('text', { endIndex: 1, index: 1, node: root.first }) is(warning.line, 1) is(warning.column, 2) is(warning.endLine, 1) is(warning.endColumn, 3) }) test('always returns valid ranges', () => { let root = parse('a b{}') let warning = new Warning('text', { endIndex: 1, index: 2, node: root.first }) is(warning.line, 1) is(warning.column, 3) is(warning.endLine, 1) is(warning.endColumn, 4) }) test.run() postcss-8.5.6/tsconfig.json000066400000000000000000000002561502402132100157300ustar00rootroot00000000000000{ "compilerOptions": { "lib": ["es2018"], "target": "es2018", "module": "commonjs", "strict": true, "noEmit": true }, "exclude": ["**/errors.ts"] }