diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index c6c8b3621..000000000 --- a/.editorconfig +++ /dev/null @@ -1,9 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 2 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index 6eec078d6..000000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,68 +0,0 @@ -# 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 diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 91de44399..000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,3 +0,0 @@ -open_collective: postcss -tidelift: npm/postcss -github: ai diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index ab42bc140..000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: 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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - 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@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1 - with: - name: ${{ github.ref_name }} - body: '${{ steps.changelog.outputs.changelog_content }}' - draft: false - prerelease: false diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 942b7514d..000000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,102 +0,0 @@ -name: 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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - name: Install pnpm - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - with: - version: 10 - - name: Install Node.js - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 - with: - node-version: 25 - 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: - - 24 - - 22 - - 20 - - 18 - name: Node.js ${{ matrix.node-version }} Quick - steps: - - name: Checkout the repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - name: Install pnpm - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - with: - version: 10 - - name: Install Node.js ${{ matrix.node-version }} - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 - 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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - name: Install pnpm - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - with: - version: 3 - env: - ACTIONS_ALLOW_UNSECURE_COMMANDS: true - - name: Install Node.js ${{ matrix.node-version }} - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 - 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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - name: Install pnpm - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - with: - version: 10 - - name: Install Node.js LTS - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 - with: - node-version: 24 - cache: pnpm - - name: Install dependencies - run: pnpm install --ignore-scripts - - name: Run unit tests - run: pnpm run unit diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 3030054ee..000000000 --- a/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules/ - -coverage/ - -docs/api/index.html -docs/api/assets/ diff --git a/.npmignore b/.npmignore deleted file mode 100644 index d9ff01c3c..000000000 --- a/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -coverage/ -test/ - -docs/ -tsconfig.json -eslint.config.mjs diff --git a/AtRule.html b/AtRule.html new file mode 100644 index 000000000..0b6993575 --- /dev/null +++ b/AtRule.html @@ -0,0 +1,5 @@ + + +Redirecting to postcss.org/api + + diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index d4a0bc6f6..000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,1080 +0,0 @@ -# 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. diff --git a/CNAME b/CNAME new file mode 100644 index 000000000..1c6432a25 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +api.postcss.org diff --git a/Comment.html b/Comment.html new file mode 100644 index 000000000..9724ac44d --- /dev/null +++ b/Comment.html @@ -0,0 +1,5 @@ + + +Redirecting to postcss.org/api + + diff --git a/Container.html b/Container.html new file mode 100644 index 000000000..78a98fc79 --- /dev/null +++ b/Container.html @@ -0,0 +1,5 @@ + + +Redirecting to postcss.org/api + + diff --git a/CssSyntaxError.html b/CssSyntaxError.html new file mode 100644 index 000000000..53ed7d57c --- /dev/null +++ b/CssSyntaxError.html @@ -0,0 +1,5 @@ + + +Redirecting to postcss.org/api + + diff --git a/Declaration.html b/Declaration.html new file mode 100644 index 000000000..55740221d --- /dev/null +++ b/Declaration.html @@ -0,0 +1,5 @@ + + +Redirecting to postcss.org/api + + diff --git a/Input.html b/Input.html new file mode 100644 index 000000000..d534c68c7 --- /dev/null +++ b/Input.html @@ -0,0 +1,5 @@ + + +Redirecting to postcss.org/api + + diff --git a/LICENSE b/LICENSE deleted file mode 100644 index da057b456..000000000 --- a/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -The 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. diff --git a/LazyResult.html b/LazyResult.html new file mode 100644 index 000000000..660824a7f --- /dev/null +++ b/LazyResult.html @@ -0,0 +1,5 @@ + + +Redirecting to postcss.org/api + + diff --git a/Node.html b/Node.html new file mode 100644 index 000000000..471ceb1c4 --- /dev/null +++ b/Node.html @@ -0,0 +1,5 @@ + + +Redirecting to postcss.org/api + + diff --git a/PreviousMap.html b/PreviousMap.html new file mode 100644 index 000000000..7fe4ce0c7 --- /dev/null +++ b/PreviousMap.html @@ -0,0 +1,5 @@ + + +Redirecting to postcss.org/api + + diff --git a/Processor.html b/Processor.html new file mode 100644 index 000000000..c3ce39757 --- /dev/null +++ b/Processor.html @@ -0,0 +1,5 @@ + + +Redirecting to postcss.org/api + + diff --git a/README.md b/README.md deleted file mode 100644 index afc754afc..000000000 --- a/README.md +++ /dev/null @@ -1,505 +0,0 @@ -# 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 diff --git a/lib/document.js b/lib/document.js deleted file mode 100644 index 44689917f..000000000 --- a/lib/document.js +++ /dev/null @@ -1,33 +0,0 @@ -'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 diff --git a/lib/fromJSON.d.ts b/lib/fromJSON.d.ts deleted file mode 100644 index e1deedbd3..000000000 --- a/lib/fromJSON.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { JSONHydrator } from './postcss.js' - -interface FromJSON extends JSONHydrator { - default: FromJSON -} - -declare const fromJSON: FromJSON - -export = fromJSON diff --git a/lib/fromJSON.js b/lib/fromJSON.js deleted file mode 100644 index c9ac1a86c..000000000 --- a/lib/fromJSON.js +++ /dev/null @@ -1,54 +0,0 @@ -'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 diff --git a/lib/input.d.ts b/lib/input.d.ts deleted file mode 100644 index b788f55d1..000000000 --- a/lib/input.d.ts +++ /dev/null @@ -1,227 +0,0 @@ -import { 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 - } - - - 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 diff --git a/lib/input.js b/lib/input.js deleted file mode 100644 index bb0ccf53a..000000000 --- a/lib/input.js +++ /dev/null @@ -1,265 +0,0 @@ -'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) -} diff --git a/lib/lazy-result.d.ts b/lib/lazy-result.d.ts deleted file mode 100644 index 393ab030f..000000000 --- a/lib/lazy-result.d.ts +++ /dev/null @@ -1,190 +0,0 @@ -import 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 { - - 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 diff --git a/lib/lazy-result.js b/lib/lazy-result.js deleted file mode 100644 index 1ea52b87a..000000000 --- a/lib/lazy-result.js +++ /dev/null @@ -1,550 +0,0 @@ -'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) diff --git a/lib/list.d.ts b/lib/list.d.ts deleted file mode 100644 index e262ad3ff..000000000 --- a/lib/list.d.ts +++ /dev/null @@ -1,60 +0,0 @@ -declare 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 diff --git a/lib/list.js b/lib/list.js deleted file mode 100644 index 1b31f9809..000000000 --- a/lib/list.js +++ /dev/null @@ -1,58 +0,0 @@ -'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 diff --git a/lib/map-generator.js b/lib/map-generator.js deleted file mode 100644 index 89069d3e4..000000000 --- a/lib/map-generator.js +++ /dev/null @@ -1,368 +0,0 @@ -'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 diff --git a/lib/no-work-result.d.ts b/lib/no-work-result.d.ts deleted file mode 100644 index 747d705b6..000000000 --- a/lib/no-work-result.d.ts +++ /dev/null @@ -1,46 +0,0 @@ -import 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 { - - 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 diff --git a/lib/no-work-result.js b/lib/no-work-result.js deleted file mode 100644 index 092fdc36f..000000000 --- a/lib/no-work-result.js +++ /dev/null @@ -1,137 +0,0 @@ -'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 str = stringify - this.result = new Result(this._processor, undefined, this._opts) - this.result.css = css - - let self = this - Object.defineProperty(this.result, 'root', { - get() { - return self.root - } - }) - - let map = new MapGenerator(str, undefined, 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 diff --git a/lib/node.d.ts b/lib/node.d.ts deleted file mode 100644 index a48120e72..000000000 --- a/lib/node.d.ts +++ /dev/null @@ -1,556 +0,0 @@ -import 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 - } - - - 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 diff --git a/lib/node.js b/lib/node.js deleted file mode 100644 index b403b7136..000000000 --- a/lib/node.js +++ /dev/null @@ -1,449 +0,0 @@ -'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 diff --git a/lib/parse.d.ts b/lib/parse.d.ts deleted file mode 100644 index 4c943a4d6..000000000 --- a/lib/parse.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Parser } from './postcss.js' - -interface Parse extends Parser { - default: Parse -} - -declare const parse: Parse - -export = parse diff --git a/lib/parse.js b/lib/parse.js deleted file mode 100644 index 00a1037aa..000000000 --- a/lib/parse.js +++ /dev/null @@ -1,42 +0,0 @@ -'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) diff --git a/lib/parser.js b/lib/parser.js deleted file mode 100644 index b29ff5b2d..000000000 --- a/lib/parser.js +++ /dev/null @@ -1,611 +0,0 @@ -'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 (!text.trim()) { - 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 diff --git a/lib/postcss.d.mts b/lib/postcss.d.mts deleted file mode 100644 index d343f3cdf..000000000 --- a/lib/postcss.d.mts +++ /dev/null @@ -1,69 +0,0 @@ -export { - // 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' diff --git a/lib/postcss.d.ts b/lib/postcss.d.ts deleted file mode 100644 index c5e36052e..000000000 --- a/lib/postcss.d.ts +++ /dev/null @@ -1,458 +0,0 @@ -import { 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() diff --git a/test/old-node.js b/test/old-node.js deleted file mode 100644 index 89b6c8fc6..000000000 --- a/test/old-node.js +++ /dev/null @@ -1,12 +0,0 @@ -// 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) -} diff --git a/test/parse.test.ts b/test/parse.test.ts deleted file mode 100755 index 13de89851..000000000 --- a/test/parse.test.ts +++ /dev/null @@ -1,254 +0,0 @@ -import { 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() diff --git a/test/postcss.test.ts b/test/postcss.test.ts deleted file mode 100755 index fddd982fe..000000000 --- a/test/postcss.test.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { 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() diff --git a/test/previous-map.test.ts b/test/previous-map.test.ts deleted file mode 100755 index efda7ff92..000000000 --- a/test/previous-map.test.ts +++ /dev/null @@ -1,365 +0,0 @@ -import { - 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() diff --git a/test/processor.test.ts b/test/processor.test.ts deleted file mode 100755 index 74803073d..000000000 --- a/test/processor.test.ts +++ /dev/null @@ -1,639 +0,0 @@ -import { 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() diff --git a/test/result.test.ts b/test/result.test.ts deleted file mode 100755 index e163e1787..000000000 --- a/test/result.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { 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() diff --git a/test/root.test.ts b/test/root.test.ts deleted file mode 100755 index fdf312329..000000000 --- a/test/root.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { 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() diff --git a/test/rule.test.ts b/test/rule.test.ts deleted file mode 100755 index c2887a093..000000000 --- a/test/rule.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { 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() diff --git a/test/stringifier.test.js b/test/stringifier.test.js deleted file mode 100755 index 86ddf2b9c..000000000 --- a/test/stringifier.test.js +++ /dev/null @@ -1,288 +0,0 @@ -let { 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() diff --git a/test/stringify.test.ts b/test/stringify.test.ts deleted file mode 100755 index 784d15031..000000000 --- a/test/stringify.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { 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() diff --git a/test/tokenize.test.js b/test/tokenize.test.js deleted file mode 100755 index e285c7ceb..000000000 --- a/test/tokenize.test.js +++ /dev/null @@ -1,334 +0,0 @@ -let { 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() diff --git a/test/types.ts b/test/types.ts deleted file mode 100644 index 4f7b861d8..000000000 --- a/test/types.ts +++ /dev/null @@ -1,64 +0,0 @@ -import 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 diff --git a/test/version.js b/test/version.js deleted file mode 100755 index 0934db2c8..000000000 --- a/test/version.js +++ /dev/null @@ -1,9 +0,0 @@ -#!/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') -} diff --git a/test/visitor.test.ts b/test/visitor.test.ts deleted file mode 100755 index 72557ab25..000000000 --- a/test/visitor.test.ts +++ /dev/null @@ -1,1605 +0,0 @@ -import { 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() diff --git a/test/warning.test.ts b/test/warning.test.ts deleted file mode 100644 index 609b056aa..000000000 --- a/test/warning.test.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { 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() diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 808df78e8..000000000 --- a/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "lib": ["es2018"], - "target": "es2018", - "module": "commonjs", - "strict": true, - "noEmit": true - }, - "exclude": ["**/errors.ts"] -} diff --git a/vendor.html b/vendor.html new file mode 100644 index 000000000..5f8362bf2 --- /dev/null +++ b/vendor.html @@ -0,0 +1,5 @@ + + +Redirecting to postcss.org/api + +