From fd34926c1bb6ec96b2a03cbbf32c85b2dab0f220 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Wed, 11 May 2022 06:54:12 +0200 Subject: [PATCH 01/17] Getting plugin started --- package-lock.json | 22 +++ .../.gitignore | 6 + .../postcss-trigonometric-functions/.nvmrc | 1 + .../postcss-trigonometric-functions/.tape.mjs | 8 + .../CHANGELOG.md | 5 + .../INSTALL.md | 176 ++++++++++++++++++ .../LICENSE.md | 108 +++++++++++ .../postcss-trigonometric-functions/README.md | 97 ++++++++++ .../docs/README.md | 51 +++++ .../package.json | 87 +++++++++ .../src/asin.ts | 34 ++++ .../src/cos.ts | 28 +++ .../src/index.ts | 61 ++++++ .../src/sin.ts | 28 +++ .../src/tan.ts | 28 +++ .../src/utils.ts | 94 ++++++++++ .../test/_import.mjs | 6 + .../test/_require.cjs | 6 + .../test/basic.css | 82 ++++++++ .../test/basic.expect.css | 63 +++++++ .../test/examples/example.css | 7 + .../test/examples/example.expect.css | 7 + .../examples/example.preserve-true.expect.css | 8 + .../tsconfig.json | 9 + 24 files changed, 1022 insertions(+) create mode 100644 plugins/postcss-trigonometric-functions/.gitignore create mode 100644 plugins/postcss-trigonometric-functions/.nvmrc create mode 100644 plugins/postcss-trigonometric-functions/.tape.mjs create mode 100644 plugins/postcss-trigonometric-functions/CHANGELOG.md create mode 100644 plugins/postcss-trigonometric-functions/INSTALL.md create mode 100644 plugins/postcss-trigonometric-functions/LICENSE.md create mode 100644 plugins/postcss-trigonometric-functions/README.md create mode 100644 plugins/postcss-trigonometric-functions/docs/README.md create mode 100644 plugins/postcss-trigonometric-functions/package.json create mode 100644 plugins/postcss-trigonometric-functions/src/asin.ts create mode 100644 plugins/postcss-trigonometric-functions/src/cos.ts create mode 100644 plugins/postcss-trigonometric-functions/src/index.ts create mode 100644 plugins/postcss-trigonometric-functions/src/sin.ts create mode 100644 plugins/postcss-trigonometric-functions/src/tan.ts create mode 100644 plugins/postcss-trigonometric-functions/src/utils.ts create mode 100644 plugins/postcss-trigonometric-functions/test/_import.mjs create mode 100644 plugins/postcss-trigonometric-functions/test/_require.cjs create mode 100644 plugins/postcss-trigonometric-functions/test/basic.css create mode 100644 plugins/postcss-trigonometric-functions/test/basic.expect.css create mode 100644 plugins/postcss-trigonometric-functions/test/examples/example.css create mode 100644 plugins/postcss-trigonometric-functions/test/examples/example.expect.css create mode 100644 plugins/postcss-trigonometric-functions/test/examples/example.preserve-true.expect.css create mode 100644 plugins/postcss-trigonometric-functions/tsconfig.json diff --git a/package-lock.json b/package-lock.json index 122285017..da3ed720e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1808,6 +1808,10 @@ "resolved": "packages/postcss-tape", "link": true }, + "node_modules/@csstools/postcss-trigonometric-functions": { + "resolved": "plugins/postcss-trigonometric-functions", + "link": true + }, "node_modules/@csstools/postcss-unset-value": { "resolved": "plugins/postcss-unset-value", "link": true @@ -7221,6 +7225,20 @@ "postcss": "^8.3" } }, + "plugins/postcss-trigonometric-functions": { + "version": "1.0.0", + "license": "CC0-1.0", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, "plugins/postcss-unset-value": { "name": "@csstools/postcss-unset-value", "version": "1.0.0", @@ -8505,6 +8523,10 @@ "postcss-8.2": "npm:postcss@~8.2" } }, + "@csstools/postcss-trigonometric-functions": { + "version": "file:plugins/postcss-trigonometric-functions", + "requires": {} + }, "@csstools/postcss-unset-value": { "version": "file:plugins/postcss-unset-value", "requires": {} diff --git a/plugins/postcss-trigonometric-functions/.gitignore b/plugins/postcss-trigonometric-functions/.gitignore new file mode 100644 index 000000000..7172b04f1 --- /dev/null +++ b/plugins/postcss-trigonometric-functions/.gitignore @@ -0,0 +1,6 @@ +node_modules +package-lock.json +yarn.lock +*.result.css +*.result.css.map +dist/* diff --git a/plugins/postcss-trigonometric-functions/.nvmrc b/plugins/postcss-trigonometric-functions/.nvmrc new file mode 100644 index 000000000..f0b10f153 --- /dev/null +++ b/plugins/postcss-trigonometric-functions/.nvmrc @@ -0,0 +1 @@ +v16.13.1 diff --git a/plugins/postcss-trigonometric-functions/.tape.mjs b/plugins/postcss-trigonometric-functions/.tape.mjs new file mode 100644 index 000000000..cda74a20c --- /dev/null +++ b/plugins/postcss-trigonometric-functions/.tape.mjs @@ -0,0 +1,8 @@ +import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import plugin from '@csstools/postcss-trigonometric-functions'; + +postcssTape(plugin)({ + basic: { + message: "supports basic usage", + }, +}); diff --git a/plugins/postcss-trigonometric-functions/CHANGELOG.md b/plugins/postcss-trigonometric-functions/CHANGELOG.md new file mode 100644 index 000000000..4b356ffb0 --- /dev/null +++ b/plugins/postcss-trigonometric-functions/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changes to PostCSS Trigonometric Functions + +### 1.0.0 (Unreleased) + +- Initial version diff --git a/plugins/postcss-trigonometric-functions/INSTALL.md b/plugins/postcss-trigonometric-functions/INSTALL.md new file mode 100644 index 000000000..dab91f1a1 --- /dev/null +++ b/plugins/postcss-trigonometric-functions/INSTALL.md @@ -0,0 +1,176 @@ +# Installing PostCSS Trigonometric Functions + +[PostCSS Trigonometric Functions] runs in all Node environments, with special instructions for: + +| [Node](#node) | [PostCSS CLI](#postcss-cli) | [Webpack](#webpack) | [Create React App](#create-react-app) | [Gulp](#gulp) | [Grunt](#grunt) | +| --- | --- | --- | --- | --- | --- | + +## Node + +Add [PostCSS Trigonometric Functions] to your project: + +```bash +npm install postcss @csstools/postcss-trigonometric-functions --save-dev +``` + +Use it as a [PostCSS] plugin: + +```js +const postcss = require('postcss'); +const postcssBasePlugin = require('@csstools/postcss-trigonometric-functions'); + +postcss([ + postcssBasePlugin(/* pluginOptions */) +]).process(YOUR_CSS /*, processOptions */); +``` + +## PostCSS CLI + +Add [PostCSS CLI] to your project: + +```bash +npm install postcss-cli @csstools/postcss-trigonometric-functions --save-dev +``` + +Use [PostCSS Trigonometric Functions] in your `postcss.config.js` configuration file: + +```js +const postcssBasePlugin = require('@csstools/postcss-trigonometric-functions'); + +module.exports = { + plugins: [ + postcssBasePlugin(/* pluginOptions */) + ] +} +``` + +## Webpack + +_Webpack version 5_ + +Add [PostCSS Loader] to your project: + +```bash +npm install postcss-loader @csstools/postcss-trigonometric-functions --save-dev +``` + +Use [PostCSS Trigonometric Functions] in your Webpack configuration: + +```js +module.exports = { + module: { + rules: [ + { + test: /\.css$/i, + use: [ + "style-loader", + { + loader: "css-loader", + options: { importLoaders: 1 }, + }, + { + loader: "postcss-loader", + options: { + postcssOptions: { + plugins: [ + [ + "@csstools/postcss-trigonometric-functions", + { + // Options + }, + ], + ], + }, + }, + }, + ], + }, + ], + }, +}; +``` + +## Create React App + +Add [React App Rewired] and [React App Rewire PostCSS] to your project: + +```bash +npm install react-app-rewired react-app-rewire-postcss @csstools/postcss-trigonometric-functions --save-dev +``` + +Use [React App Rewire PostCSS] and [PostCSS Trigonometric Functions] in your +`config-overrides.js` file: + +```js +const reactAppRewirePostcss = require('react-app-rewire-postcss'); +const postcssBasePlugin = require('@csstools/postcss-trigonometric-functions'); + +module.exports = config => reactAppRewirePostcss(config, { + plugins: () => [ + postcssBasePlugin(/* pluginOptions */) + ] +}); +``` + +## Gulp + +Add [Gulp PostCSS] to your project: + +```bash +npm install gulp-postcss @csstools/postcss-trigonometric-functions --save-dev +``` + +Use [PostCSS Trigonometric Functions] in your Gulpfile: + +```js +const postcss = require('gulp-postcss'); +const postcssBasePlugin = require('@csstools/postcss-trigonometric-functions'); + +gulp.task('css', function () { + var plugins = [ + postcssBasePlugin(/* pluginOptions */) + ]; + + return gulp.src('./src/*.css') + .pipe(postcss(plugins)) + .pipe(gulp.dest('.')); +}); +``` + +## Grunt + +Add [Grunt PostCSS] to your project: + +```bash +npm install grunt-postcss @csstools/postcss-trigonometric-functions --save-dev +``` + +Use [PostCSS Trigonometric Functions] in your Gruntfile: + +```js +const postcssBasePlugin = require('@csstools/postcss-trigonometric-functions'); + +grunt.loadNpmTasks('grunt-postcss'); + +grunt.initConfig({ + postcss: { + options: { + processors: [ + postcssBasePlugin(/* pluginOptions */) + ] + }, + dist: { + src: '*.css' + } + } +}); +``` + +[Gulp PostCSS]: https://github.com/postcss/gulp-postcss +[Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss +[PostCSS]: https://github.com/postcss/postcss +[PostCSS CLI]: https://github.com/postcss/postcss-cli +[PostCSS Loader]: https://github.com/postcss/postcss-loader +[PostCSS Trigonometric Functions]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-trigonometric-functions +[React App Rewire PostCSS]: https://github.com/csstools/react-app-rewire-postcss +[React App Rewired]: https://github.com/timarney/react-app-rewired diff --git a/plugins/postcss-trigonometric-functions/LICENSE.md b/plugins/postcss-trigonometric-functions/LICENSE.md new file mode 100644 index 000000000..0bc1fa706 --- /dev/null +++ b/plugins/postcss-trigonometric-functions/LICENSE.md @@ -0,0 +1,108 @@ +# CC0 1.0 Universal + +## Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator and +subsequent owner(s) (each and all, an “owner”) of an original work of +authorship and/or a database (each, a “Work”). + +Certain owners wish to permanently relinquish those rights to a Work for the +purpose of contributing to a commons of creative, cultural and scientific works +(“Commons”) that the public can reliably and without fear of later claims of +infringement build upon, modify, incorporate in other works, reuse and +redistribute as freely as possible in any form whatsoever and for any purposes, +including without limitation commercial purposes. These owners may contribute +to the Commons to promote the ideal of a free culture and the further +production of creative, cultural and scientific works, or to gain reputation or +greater distribution for their Work in part through the use and efforts of +others. + +For these and/or other purposes and motivations, and without any expectation of +additional consideration or compensation, the person associating CC0 with a +Work (the “Affirmer”), to the extent that he or she is an owner of Copyright +and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and +publicly distribute the Work under its terms, with knowledge of his or her +Copyright and Related Rights in the Work and the meaning and intended legal +effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be + protected by copyright and related or neighboring rights (“Copyright and + Related Rights”). Copyright and Related Rights include, but are not limited + to, the following: + 1. the right to reproduce, adapt, distribute, perform, display, communicate, + and translate a Work; + 2. moral rights retained by the original author(s) and/or performer(s); + 3. publicity and privacy rights pertaining to a person’s image or likeness + depicted in a Work; + 4. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(i), below; + 5. rights protecting the extraction, dissemination, use and reuse of data in + a Work; + 6. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation thereof, + including any amended or successor version of such directive); and + 7. other similar, equivalent or corresponding rights throughout the world + based on applicable law or treaty, and any national implementations + thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, + applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and + unconditionally waives, abandons, and surrenders all of Affirmer’s Copyright + and Related Rights and associated claims and causes of action, whether now + known or unknown (including existing as well as future claims and causes of + action), in the Work (i) in all territories worldwide, (ii) for the maximum + duration provided by applicable law or treaty (including future time + extensions), (iii) in any current or future medium and for any number of + copies, and (iv) for any purpose whatsoever, including without limitation + commercial, advertising or promotional purposes (the “Waiver”). Affirmer + makes the Waiver for the benefit of each member of the public at large and + to the detriment of Affirmer’s heirs and successors, fully intending that + such Waiver shall not be subject to revocation, rescission, cancellation, + termination, or any other legal or equitable action to disrupt the quiet + enjoyment of the Work by the public as contemplated by Affirmer’s express + Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be + judged legally invalid or ineffective under applicable law, then the Waiver + shall be preserved to the maximum extent permitted taking into account + Affirmer’s express Statement of Purpose. In addition, to the extent the + Waiver is so judged Affirmer hereby grants to each affected person a + royalty-free, non transferable, non sublicensable, non exclusive, + irrevocable and unconditional license to exercise Affirmer’s Copyright and + Related Rights in the Work (i) in all territories worldwide, (ii) for the + maximum duration provided by applicable law or treaty (including future time + extensions), (iii) in any current or future medium and for any number of + copies, and (iv) for any purpose whatsoever, including without limitation + commercial, advertising or promotional purposes (the “License”). The License + shall be deemed effective as of the date CC0 was applied by Affirmer to the + Work. Should any part of the License for any reason be judged legally + invalid or ineffective under applicable law, such partial invalidity or + ineffectiveness shall not invalidate the remainder of the License, and in + such case Affirmer hereby affirms that he or she will not (i) exercise any + of his or her remaining Copyright and Related Rights in the Work or (ii) + assert any associated claims and causes of action with respect to the Work, + in either case contrary to Affirmer’s express Statement of Purpose. + +4. Limitations and Disclaimers. + 1. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + 2. Affirmer offers the Work as-is and makes no representations or warranties + of any kind concerning the Work, express, implied, statutory or + otherwise, including without limitation warranties of title, + merchantability, fitness for a particular purpose, non infringement, or + the absence of latent or other defects, accuracy, or the present or + absence of errors, whether or not discoverable, all to the greatest + extent permissible under applicable law. + 3. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person’s Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the Work. + 4. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to this + CC0 or use of the Work. + +For more information, please see +http://creativecommons.org/publicdomain/zero/1.0/. diff --git a/plugins/postcss-trigonometric-functions/README.md b/plugins/postcss-trigonometric-functions/README.md new file mode 100644 index 000000000..4a81de958 --- /dev/null +++ b/plugins/postcss-trigonometric-functions/README.md @@ -0,0 +1,97 @@ +# PostCSS Trigonometric Functions [PostCSS Logo][postcss] + +[npm version][npm-url] +[CSS Standard Status][css-url] +[Build Status][cli-url] +[Discord][discord] + +[PostCSS Trigonometric Functions] lets easily create new plugins following some [CSS Specification]. + +```pcss +.foo { + color: red; +} + +.baz { + color: green; +} + +/* becomes */ + +.foo { + color: blue; +} + +.baz { + color: green; +} +``` + +## Usage + +Add [PostCSS Trigonometric Functions] to your project: + +```bash +npm install postcss @csstools/postcss-trigonometric-functions --save-dev +``` + +Use it as a [PostCSS] plugin: + +```js +const postcss = require('postcss'); +const postcssBasePlugin = require('@csstools/postcss-trigonometric-functions'); + +postcss([ + postcssBasePlugin(/* pluginOptions */) +]).process(YOUR_CSS /*, processOptions */); +``` + +[PostCSS Trigonometric Functions] runs in all Node environments, with special +instructions for: + +| [Node](INSTALL.md#node) | [PostCSS CLI](INSTALL.md#postcss-cli) | [Webpack](INSTALL.md#webpack) | [Create React App](INSTALL.md#create-react-app) | [Gulp](INSTALL.md#gulp) | [Grunt](INSTALL.md#grunt) | +| --- | --- | --- | --- | --- | --- | + +## Options + +### preserve + +The `preserve` option determines whether the original notation +is preserved. By default, it is not preserved. + +```js +postcssBasePlugin({ preserve: true }) +``` + +```pcss +.foo { + color: red; +} + +.baz { + color: green; +} + +/* becomes */ + +.foo { + color: blue; + color: red; +} + +.baz { + color: green; +} +``` + +[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test +[css-url]: https://cssdb.org/#TODO +[discord]: https://discord.gg/bUadyRwkJS +[npm-url]: https://www.npmjs.com/package/@csstools/postcss-trigonometric-functions + +[Gulp PostCSS]: https://github.com/postcss/gulp-postcss +[Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss +[PostCSS]: https://github.com/postcss/postcss +[PostCSS Loader]: https://github.com/postcss/postcss-loader +[PostCSS Trigonometric Functions]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-trigonometric-functions +[CSS Specification]: #TODO diff --git a/plugins/postcss-trigonometric-functions/docs/README.md b/plugins/postcss-trigonometric-functions/docs/README.md new file mode 100644 index 000000000..3bc455892 --- /dev/null +++ b/plugins/postcss-trigonometric-functions/docs/README.md @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + +
+ +[] lets easily create new plugins following some [CSS Specification]. + +```pcss + + +/* becomes */ + + +``` + + + + + +## Options + +### preserve + +The `preserve` option determines whether the original notation +is preserved. By default, it is not preserved. + +```js +({ preserve: true }) +``` + +```pcss + + +/* becomes */ + + +``` + + +[CSS Specification]: diff --git a/plugins/postcss-trigonometric-functions/package.json b/plugins/postcss-trigonometric-functions/package.json new file mode 100644 index 000000000..566cef784 --- /dev/null +++ b/plugins/postcss-trigonometric-functions/package.json @@ -0,0 +1,87 @@ +{ + "name": "@csstools/postcss-trigonometric-functions", + "description": "TODO: Add description for Trigonometric Functions", + "version": "1.0.0", + "contributors": [ + { + "name": "Antonio Laguna", + "email": "antonio@laguna.es", + "url": "https://antonio.laguna.es" + }, + { + "name": "Romain Menke ", + "email": "romainmenke@gmail.com" + } + ], + "license": "CC0-1.0", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "main": "dist/index.cjs", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.cjs", + "default": "./dist/index.mjs" + } + }, + "files": [ + "CHANGELOG.md", + "LICENSE.md", + "README.md", + "dist" + ], + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "postcss": "^8.3" + }, + "scripts": { + "build": "rollup -c ../../rollup/default.js", + "clean": "node -e \"fs.rmSync('./dist', { recursive: true, force: true });\"", + "docs": "node ../../.github/bin/generate-docs/install.mjs && node ../../.github/bin/generate-docs/readme.mjs", + "lint": "npm run lint:eslint && npm run lint:package-json", + "lint:eslint": "eslint ./src --ext .js --ext .ts --ext .mjs --no-error-on-unmatched-pattern", + "lint:package-json": "node ../../.github/bin/format-package-json.mjs", + "prepublishOnly": "npm run clean && npm run build && npm run test", + "stryker": "stryker run --logLevel error", + "test": "node .tape.mjs && npm run test:exports", + "test:exports": "node ./test/_import.mjs && node ./test/_require.cjs", + "test:rewrite-expects": "REWRITE_EXPECTS=true node .tape.mjs" + }, + "repository": { + "type": "git", + "url": "https://github.com/csstools/postcss-plugins.git", + "directory": "plugins/postcss-trigonometric-functions" + }, + "keywords": [ + "postcss-plugin", + "trigonometric", + "css", + "sin", + "cos", + "tan", + "asin", + "atan", + "acos", + "acos2" + ], + "csstools": { + "cssdbId": "trigonometric-functions", + "exportName": "postcssTrigonometricFunctions", + "humanReadableName": "PostCSS Trigonometric Functions", + "specUrl": "https://www.w3.org/TR/css-values-4/#trig-funcs" + }, + "volta": { + "extends": "../../package.json" + }, + "homepage": "https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-trigonometric-functions#readme", + "bugs": "https://github.com/csstools/postcss-plugins/issues" +} diff --git a/plugins/postcss-trigonometric-functions/src/asin.ts b/plugins/postcss-trigonometric-functions/src/asin.ts new file mode 100644 index 000000000..acbc150b5 --- /dev/null +++ b/plugins/postcss-trigonometric-functions/src/asin.ts @@ -0,0 +1,34 @@ +import type { Declaration } from 'postcss'; +import valueParser from 'postcss-value-parser'; +import { formatResultingNumber, radToDeg, validateNode } from './utils'; + +const asinFunctionCheck = 'asin('; + +function transformAsinFunction(decl: Declaration): string | undefined { + const parsedValue = valueParser(decl.value); + + parsedValue.walk(node => { + if (node.type !== 'function' || node.value !== 'asin') { + return; + } + + const validated = validateNode(node, false); + + if (!validated) { + return; + } + + const [transformedNode, number] = validated; + let value: string | number = Math.asin(number); + + if (!Number.isNaN(value) && typeof value === 'number') { + value = `${formatResultingNumber(radToDeg(value), 2)}deg`; + } + + transformedNode.value = value + ''; + }); + + return parsedValue.toString(); +} + +export { asinFunctionCheck, transformAsinFunction }; diff --git a/plugins/postcss-trigonometric-functions/src/cos.ts b/plugins/postcss-trigonometric-functions/src/cos.ts new file mode 100644 index 000000000..489401589 --- /dev/null +++ b/plugins/postcss-trigonometric-functions/src/cos.ts @@ -0,0 +1,28 @@ +import type { Declaration } from 'postcss'; +import valueParser from 'postcss-value-parser'; +import { formatResultingNumber, validateNode } from './utils'; + +const cosFunctionCheck = 'cos('; + +function transformCosFunction(decl: Declaration): string | undefined { + const parsedValue = valueParser(decl.value); + + parsedValue.walk(node => { + if (node.type !== 'function' || node.value !== 'cos') { + return; + } + + const validated = validateNode(node); + + if (!validated) { + return; + } + + const [transformedNode, number] = validated; + transformedNode.value = formatResultingNumber(Math.cos(number), 5); + }); + + return parsedValue.toString(); +} + +export { cosFunctionCheck, transformCosFunction }; diff --git a/plugins/postcss-trigonometric-functions/src/index.ts b/plugins/postcss-trigonometric-functions/src/index.ts new file mode 100644 index 000000000..69fd21367 --- /dev/null +++ b/plugins/postcss-trigonometric-functions/src/index.ts @@ -0,0 +1,61 @@ +import type { PluginCreator } from 'postcss'; +import { sinFunctionCheck, transformSinFunction } from './sin'; +import { cosFunctionCheck, transformCosFunction } from './cos'; +import { tanFunctionCheck, transformTanFunction } from './tan'; +import { asinFunctionCheck, transformAsinFunction } from './asin'; + +type pluginOptions = { preserve?: boolean }; + +const Transformations = [ + { check: sinFunctionCheck, transform: transformSinFunction }, + { check: cosFunctionCheck, transform: transformCosFunction }, + { check: tanFunctionCheck, transform: transformTanFunction }, + { check: asinFunctionCheck, transform: transformAsinFunction }, +]; + +const creator: PluginCreator = (opts?: pluginOptions) => { + const options = Object.assign( + // Default options + { + preserve: false, + }, + // Provided options + opts, + ); + + return { + postcssPlugin: 'postcss-trigonometric-functions', + Declaration(decl) { + const transformations = Transformations.filter( + transformation => decl.value.includes(transformation.check), + ); + + if (!decl || transformations.length === 0) { + return; + } + + const newDeclaration = decl.clone(); + + transformations.forEach(transformation => { + const modValue = transformation.transform(newDeclaration); + + if (modValue) { + newDeclaration.value = modValue; + } + }); + + if (decl.value !== newDeclaration.value) { + if (options.preserve) { + decl.cloneBefore({ value: newDeclaration.value }); + } else { + decl.value = newDeclaration.value; + } + } + }, + }; +}; + +creator.postcss = true; + +export default creator; + diff --git a/plugins/postcss-trigonometric-functions/src/sin.ts b/plugins/postcss-trigonometric-functions/src/sin.ts new file mode 100644 index 000000000..53dad263f --- /dev/null +++ b/plugins/postcss-trigonometric-functions/src/sin.ts @@ -0,0 +1,28 @@ +import type { Declaration } from 'postcss'; +import valueParser from 'postcss-value-parser'; +import { formatResultingNumber, validateNode } from './utils'; + +const sinFunctionCheck = 'sin('; + +function transformSinFunction(decl: Declaration): string | undefined { + const parsedValue = valueParser(decl.value); + + parsedValue.walk(node => { + if (node.type !== 'function' || node.value !== 'sin') { + return; + } + + const validated = validateNode(node); + + if (!validated) { + return; + } + + const [transformedNode, number] = validated; + transformedNode.value = formatResultingNumber(Math.sin(number), 5); + }); + + return parsedValue.toString(); +} + +export { sinFunctionCheck, transformSinFunction }; diff --git a/plugins/postcss-trigonometric-functions/src/tan.ts b/plugins/postcss-trigonometric-functions/src/tan.ts new file mode 100644 index 000000000..18685f684 --- /dev/null +++ b/plugins/postcss-trigonometric-functions/src/tan.ts @@ -0,0 +1,28 @@ +import type { Declaration } from 'postcss'; +import valueParser from 'postcss-value-parser'; +import { formatResultingNumber, validateNode } from './utils'; + +const tanFunctionCheck = 'tan('; + +function transformTanFunction(decl: Declaration): string | undefined { + const parsedValue = valueParser(decl.value); + + parsedValue.walk(node => { + if (node.type !== 'function' || node.value !== 'tan') { + return; + } + + const validated = validateNode(node); + + if (!validated) { + return; + } + + const [transformedNode, number] = validated; + transformedNode.value = formatResultingNumber(Math.tan(number), 5); + }); + + return parsedValue.toString(); +} + +export { tanFunctionCheck, transformTanFunction }; diff --git a/plugins/postcss-trigonometric-functions/src/utils.ts b/plugins/postcss-trigonometric-functions/src/utils.ts new file mode 100644 index 000000000..3f8e8ecff --- /dev/null +++ b/plugins/postcss-trigonometric-functions/src/utils.ts @@ -0,0 +1,94 @@ +import type { FunctionNode, WordNode, Node } from 'postcss-value-parser'; +import valueParser from 'postcss-value-parser'; + +export function turnToRad(turn: number): number { + return turn * 2 * Math.PI; +} + +export function degToRad(deg: number): number { + return deg * (Math.PI / 180); +} + +export function gradToRad(grad: number): number { + return grad * (Math.PI / 200); +} + +export function radToDeg(rad: number): number { + return rad * ( 180 / Math.PI ); +} + +const toRad = { + turn: turnToRad, + deg: degToRad, + grad: gradToRad, +}; + +export function functionNodeToWordNode(fn: FunctionNode): WordNode { + delete fn.nodes; + const node = fn as Node; + node.type = 'word'; + + return node as WordNode; +} + +export function formatResultingNumber(number: number, decimals: number): string { + if (!Number.isNaN(number)) { + if (number > Number.MAX_SAFE_INTEGER) { + return 'infinity'; + } else if (number < Number.MIN_SAFE_INTEGER) { + return '-infinity'; + } + } + + return Number(number.toFixed(decimals)).toString(); +} + +export function validateNode( + node: FunctionNode, + parseUnit = true, + args = 1, +): [WordNode,number] | undefined { + const words = node.nodes.filter(childNode => childNode.type === 'word'); + + if (words.length !== args) { + return; + } + + const { value } = words[0]; + + const parsed = valueParser.unit(value); + let number; + + if (value.toLowerCase() === 'infinity' ) { + number = Infinity; + } else if (value.toLowerCase() === '-infinity') { + number = Infinity * -1; + } + + if (!parsed && !number) { + return; + } + + if (parsed) { + number = Number(parsed.number); + + if (parseUnit) { + if (parsed.unit && parsed.unit !== 'rad') { + if (toRad[parsed.unit]) { + number = toRad[parsed.unit](number); + } else { + return; + } + } + } else if (parsed.unit) { + return; + } + } + + return [ + functionNodeToWordNode(node), + number, + ]; +} + +export { toRad }; diff --git a/plugins/postcss-trigonometric-functions/test/_import.mjs b/plugins/postcss-trigonometric-functions/test/_import.mjs new file mode 100644 index 000000000..a4098fe21 --- /dev/null +++ b/plugins/postcss-trigonometric-functions/test/_import.mjs @@ -0,0 +1,6 @@ +import assert from 'assert'; +import plugin from '@csstools/postcss-trigonometric-functions'; +plugin(); + +assert.ok(plugin.postcss, 'should have "postcss flag"'); +assert.equal(typeof plugin, 'function', 'should return a function'); diff --git a/plugins/postcss-trigonometric-functions/test/_require.cjs b/plugins/postcss-trigonometric-functions/test/_require.cjs new file mode 100644 index 000000000..52fb12fd2 --- /dev/null +++ b/plugins/postcss-trigonometric-functions/test/_require.cjs @@ -0,0 +1,6 @@ +const assert = require('assert'); +const plugin = require('@csstools/postcss-trigonometric-functions'); +plugin(); + +assert.ok(plugin.postcss, 'should have "postcss flag"'); +assert.equal(typeof plugin, 'function', 'should return a function'); diff --git a/plugins/postcss-trigonometric-functions/test/basic.css b/plugins/postcss-trigonometric-functions/test/basic.css new file mode 100644 index 000000000..3aa39fd41 --- /dev/null +++ b/plugins/postcss-trigonometric-functions/test/basic.css @@ -0,0 +1,82 @@ +.sin { + order: sin(45deg); + order: sin(.125turn); + order: sin(50grad); + order: sin(0.785398rad); + order: sin(0.785398); + order: sin(-45deg); + order: sin(-.125turn); + order: sin(-50grad); + order: sin(-0.785398rad); + order: sin(-0.785398); + order: sin(0); + order: sin(sin(0.785398)); + order: sin(sin(sin(sin(0.785398)))); + order: sin(1px); + order: sin(foo); + order: sin(Infinity); + order: sin(InFiNiTy); + order: sin(-infinity); + order: sin(3.14159 / 4); +} + +.cos { + order: cos(45deg); + order: cos(.125turn); + order: cos(50grad); + order: cos(1rad); + order: cos(1); + order: cos(-45deg); + order: cos(-.125turn); + order: cos(-50grad); + order: cos(-0.785398rad); + order: cos(-0.785398); + order: cos(cos(0.785398)); + order: cos(cos(cos(cos(0.785398)))); + order: cos(1px); + order: cos(foo); + order: cos(Infinity); + order: cos(InFiNiTy); + order: cos(-infinity); + order: cos(3.14159 / 4); +} + +.tan { + order: tan(45deg); + order: tan(90deg); + order: tan(-90deg); + order: tan(.125turn); + order: tan(50grad); + order: tan(1rad); + order: tan(1); + order: tan(-45deg); + order: tan(-.125turn); + order: tan(-50grad); + order: tan(-0.785398rad); + order: tan(-0.785398); + order: tan(0); + order: tan(tan(0.785398)); + order: tan(tan(tan(tan(0.785398)))); + order: tan(1px); + order: tan(foo); + order: tan(Infinity); + order: tan(InFiNiTy); + order: tan(-infinity); + order: tan(3.14159 / 4); +} + +.asin { + order: asin(sin(45deg)); + order: asin(-1); + order: asin(-0.5); + order: asin(0); + order: asin(0.5); + order: asin(1); + order: asin(2); + order: asin(-20); + order: asin(45deg); + order: asin(1rad); + order: asin(foo); + order: asin(infinity); + order: asin(-infinity); +} diff --git a/plugins/postcss-trigonometric-functions/test/basic.expect.css b/plugins/postcss-trigonometric-functions/test/basic.expect.css new file mode 100644 index 000000000..d71b9d90f --- /dev/null +++ b/plugins/postcss-trigonometric-functions/test/basic.expect.css @@ -0,0 +1,63 @@ +.sin { + order: 0.70711; + order: 0.70711; + order: 0.70711; + order: 0.70711; + order: 0.70711; + order: -0.70711; + order: -0.70711; + order: -0.70711; + order: -0.70711; + order: -0.70711; + order: 0; + order: 0.64964; + order: 0.56868; + order: sin(1px); + order: sin(foo); + order: NaN; + order: NaN; + order: sin(3.14159 / 4); +} + +.cos { + order: 0.70711; + order: 0.70711; + order: 0.70711; + order: 0.5403; + order: 0.5403; + order: 0.70711; + order: 0.70711; + order: 0.70711; + order: 0.70711; + order: 0.70711; + order: 0.76024; + order: 0.74872; + order: cos(1px); + order: cos(foo); + order: NaN; + order: NaN; + order: cos(3.14159 / 4); +} + +.tan { + order: 1; + order: infinity; + order: -infinity; + order: 1; + order: 1; + order: 1.55741; + order: 1.55741; + order: -1; + order: -1; + order: -1; + order: -1; + order: -1; + order: 0; + order: 1.55741; + order: -0.84159; + order: tan(1px); + order: tan(foo); + order: NaN; + order: NaN; + order: tan(3.14159 / 4); +} diff --git a/plugins/postcss-trigonometric-functions/test/examples/example.css b/plugins/postcss-trigonometric-functions/test/examples/example.css new file mode 100644 index 000000000..181f83a54 --- /dev/null +++ b/plugins/postcss-trigonometric-functions/test/examples/example.css @@ -0,0 +1,7 @@ +.foo { + color: red; +} + +.baz { + color: green; +} diff --git a/plugins/postcss-trigonometric-functions/test/examples/example.expect.css b/plugins/postcss-trigonometric-functions/test/examples/example.expect.css new file mode 100644 index 000000000..9d738d5ac --- /dev/null +++ b/plugins/postcss-trigonometric-functions/test/examples/example.expect.css @@ -0,0 +1,7 @@ +.foo { + color: blue; +} + +.baz { + color: green; +} diff --git a/plugins/postcss-trigonometric-functions/test/examples/example.preserve-true.expect.css b/plugins/postcss-trigonometric-functions/test/examples/example.preserve-true.expect.css new file mode 100644 index 000000000..8b020470a --- /dev/null +++ b/plugins/postcss-trigonometric-functions/test/examples/example.preserve-true.expect.css @@ -0,0 +1,8 @@ +.foo { + color: blue; + color: red; +} + +.baz { + color: green; +} diff --git a/plugins/postcss-trigonometric-functions/tsconfig.json b/plugins/postcss-trigonometric-functions/tsconfig.json new file mode 100644 index 000000000..e0d06239c --- /dev/null +++ b/plugins/postcss-trigonometric-functions/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "declarationDir": "." + }, + "include": ["./src/**/*"], + "exclude": ["dist"], +} From b16ffcd23b08029e731ea70965cf12ad13d65cae Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Mon, 16 May 2022 07:54:28 +0200 Subject: [PATCH 02/17] Lots of work Plugin should mostly be stable but needs lots of tests around calculations and unexpected values just to be sure --- package-lock.json | 8 +- .../src/acos.ts | 34 +++ .../src/atan.ts | 34 +++ .../src/atan2.ts | 78 ++++++ .../src/index.ts | 6 + .../src/tan.ts | 19 +- .../src/utils.ts | 263 ++++++++++++++++-- .../test/basic.css | 159 ++++++++--- .../test/basic.expect.css | 134 ++++++--- .../test/temporal.css | 172 ++++++++++++ 10 files changed, 810 insertions(+), 97 deletions(-) create mode 100644 plugins/postcss-trigonometric-functions/src/acos.ts create mode 100644 plugins/postcss-trigonometric-functions/src/atan.ts create mode 100644 plugins/postcss-trigonometric-functions/src/atan2.ts create mode 100644 plugins/postcss-trigonometric-functions/test/temporal.css diff --git a/package-lock.json b/package-lock.json index 78cafab07..d9bcd377c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7363,8 +7363,12 @@ } }, "plugins/postcss-trigonometric-functions": { + "name": "@csstools/postcss-trigonometric-functions", "version": "1.0.0", "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, "engines": { "node": "^12 || ^14 || >=16" }, @@ -8673,7 +8677,9 @@ }, "@csstools/postcss-trigonometric-functions": { "version": "file:plugins/postcss-trigonometric-functions", - "requires": {} + "requires": { + "postcss-value-parser": "^4.2.0" + } }, "@csstools/postcss-unset-value": { "version": "file:plugins/postcss-unset-value", diff --git a/plugins/postcss-trigonometric-functions/src/acos.ts b/plugins/postcss-trigonometric-functions/src/acos.ts new file mode 100644 index 000000000..e1362e9fb --- /dev/null +++ b/plugins/postcss-trigonometric-functions/src/acos.ts @@ -0,0 +1,34 @@ +import type { Declaration } from 'postcss'; +import valueParser from 'postcss-value-parser'; +import { formatResultingNumber, radToDeg, validateNode } from './utils'; + +const acosFunctionCheck = 'acos('; + +function transformAcosFunction(decl: Declaration): string | undefined { + const parsedValue = valueParser(decl.value); + + parsedValue.walk(node => { + if (node.type !== 'function' || node.value !== 'acos') { + return; + } + + const validated = validateNode(node, false); + + if (!validated) { + return; + } + + const [transformedNode, number] = validated; + let value: string | number = Math.acos(number); + + if (!Number.isNaN(value) && typeof value === 'number') { + value = `${formatResultingNumber(radToDeg(value), 2)}deg`; + } + + transformedNode.value = value + ''; + }); + + return parsedValue.toString(); +} + +export { acosFunctionCheck, transformAcosFunction }; diff --git a/plugins/postcss-trigonometric-functions/src/atan.ts b/plugins/postcss-trigonometric-functions/src/atan.ts new file mode 100644 index 000000000..89b73ef86 --- /dev/null +++ b/plugins/postcss-trigonometric-functions/src/atan.ts @@ -0,0 +1,34 @@ +import type { Declaration } from 'postcss'; +import valueParser from 'postcss-value-parser'; +import { formatResultingNumber, radToDeg, validateNode } from './utils'; + +const atanFunctionCheck = 'atan('; + +function transformAtanFunction(decl: Declaration): string | undefined { + const parsedValue = valueParser(decl.value); + + parsedValue.walk(node => { + if (node.type !== 'function' || node.value !== 'atan') { + return; + } + + const validated = validateNode(node, false); + + if (!validated) { + return; + } + + const [transformedNode, number] = validated; + let value: string | number = Math.atan(number); + + if (!Number.isNaN(value) && typeof value === 'number') { + value = `${formatResultingNumber(radToDeg(value), 2)}deg`; + } + + transformedNode.value = value + ''; + }); + + return parsedValue.toString(); +} + +export { atanFunctionCheck, transformAtanFunction }; diff --git a/plugins/postcss-trigonometric-functions/src/atan2.ts b/plugins/postcss-trigonometric-functions/src/atan2.ts new file mode 100644 index 000000000..5c0448f61 --- /dev/null +++ b/plugins/postcss-trigonometric-functions/src/atan2.ts @@ -0,0 +1,78 @@ +import type { Declaration } from 'postcss'; +import valueParser from 'postcss-value-parser'; +import { + computeCalculation, + filterOnlyWords, + formatResultingNumber, + functionNodeToWordNode, + parseNumber, + radToDeg, +} from './utils'; + +const atan2FunctionCheck = 'atan2('; + +function transformAtan2Function(decl: Declaration): string | undefined { + const parsedValue = valueParser(decl.value); + + parsedValue.walk(node => { + if (node.type !== 'function' || node.value !== 'atan2') { + return; + } + + const commaIndex = node.nodes.findIndex( + childNode => childNode.type === 'div' && childNode.value === ',', + ); + + if (commaIndex < 0) { + return; + } + + let first = node.nodes.slice(0, commaIndex).filter(filterOnlyWords); + let second = node.nodes.slice(commaIndex + 1).filter(filterOnlyWords); + + if (first.length === 0 || second.length === 0) { + return; + } + + // Compute calculations first + if (first.length > 1) { + first = computeCalculation(first); + } + + if (second.length > 1) { + second = computeCalculation(second); + } + + if (first.length !== 1 || second.length !== 1) { + return; + } + + const firstParsed = parseNumber(first[0].value); + const secondParsed = parseNumber(second[0].value); + + if (!firstParsed || !secondParsed) { + return; + } + + // Units must match + if (firstParsed.unit !== secondParsed.unit) { + return; + } + + let value: string | number = Math.atan2( + firstParsed.number, + secondParsed.number, + ); + + if (!Number.isNaN(value) && typeof value === 'number') { + value = `${formatResultingNumber(radToDeg(value), 2)}deg`; + } + + const transformedNode = functionNodeToWordNode(node); + transformedNode.value = value + ''; + }); + + return parsedValue.toString(); +} + +export { atan2FunctionCheck, transformAtan2Function }; diff --git a/plugins/postcss-trigonometric-functions/src/index.ts b/plugins/postcss-trigonometric-functions/src/index.ts index 69fd21367..c257adbe0 100644 --- a/plugins/postcss-trigonometric-functions/src/index.ts +++ b/plugins/postcss-trigonometric-functions/src/index.ts @@ -3,6 +3,9 @@ import { sinFunctionCheck, transformSinFunction } from './sin'; import { cosFunctionCheck, transformCosFunction } from './cos'; import { tanFunctionCheck, transformTanFunction } from './tan'; import { asinFunctionCheck, transformAsinFunction } from './asin'; +import { acosFunctionCheck, transformAcosFunction } from './acos'; +import { atanFunctionCheck, transformAtanFunction } from './atan'; +import { atan2FunctionCheck, transformAtan2Function } from './atan2'; type pluginOptions = { preserve?: boolean }; @@ -11,6 +14,9 @@ const Transformations = [ { check: cosFunctionCheck, transform: transformCosFunction }, { check: tanFunctionCheck, transform: transformTanFunction }, { check: asinFunctionCheck, transform: transformAsinFunction }, + { check: acosFunctionCheck, transform: transformAcosFunction }, + { check: atanFunctionCheck, transform: transformAtanFunction }, + { check: atan2FunctionCheck, transform: transformAtan2Function }, ]; const creator: PluginCreator = (opts?: pluginOptions) => { diff --git a/plugins/postcss-trigonometric-functions/src/tan.ts b/plugins/postcss-trigonometric-functions/src/tan.ts index 18685f684..7cc802293 100644 --- a/plugins/postcss-trigonometric-functions/src/tan.ts +++ b/plugins/postcss-trigonometric-functions/src/tan.ts @@ -1,6 +1,6 @@ import type { Declaration } from 'postcss'; import valueParser from 'postcss-value-parser'; -import { formatResultingNumber, validateNode } from './utils'; +import { formatResultingNumber, gradToDeg, radToDeg, validateNode } from './utils'; const tanFunctionCheck = 'tan('; @@ -19,6 +19,23 @@ function transformTanFunction(decl: Declaration): string | undefined { } const [transformedNode, number] = validated; + + /* + Asymptotes treatment + + Given at this stage the number is in radians, convert to degrees which + should give enough precision with the given methods. + */ + const degrees = Number(formatResultingNumber(radToDeg(number), 2)); + const isNinetyMultiple = degrees % 90 === 0; + const timesNinety = degrees / 90; + const isAsymptote = isNinetyMultiple && timesNinety % 2 !== 0; + + if (isAsymptote) { + transformedNode.value = timesNinety > 0 ? 'infinity' : '-infinity'; + return; + } + transformedNode.value = formatResultingNumber(Math.tan(number), 5); }); diff --git a/plugins/postcss-trigonometric-functions/src/utils.ts b/plugins/postcss-trigonometric-functions/src/utils.ts index 3f8e8ecff..dce516a5e 100644 --- a/plugins/postcss-trigonometric-functions/src/utils.ts +++ b/plugins/postcss-trigonometric-functions/src/utils.ts @@ -1,5 +1,6 @@ import type { FunctionNode, WordNode, Node } from 'postcss-value-parser'; import valueParser from 'postcss-value-parser'; +import vm from 'node:vm'; export function turnToRad(turn: number): number { return turn * 2 * Math.PI; @@ -17,12 +18,187 @@ export function radToDeg(rad: number): number { return rad * ( 180 / Math.PI ); } +export function gradToDeg(grad: number): number { + return grad * 180 / 200; +} + +export function turnToDeg(turn: number): number { + return turn * 360; +} + const toRad = { turn: turnToRad, deg: degToRad, grad: gradToRad, }; +const toDeg = { + grad: gradToDeg, + turn: turnToDeg, + rad: radToDeg, +}; + +export function filterOnlyWords(node: Node): boolean { + return node.type === 'word'; +} + +const ALLOWED_OPERATIONS = [ + '+', + '-', + '*', + '/', +]; + +enum ExpressionPart { + Number, + Operation +} + +/** + * Try to compute a calculation from a Node. + * + * This validates that the calculation has a valid order which is: + * - `{Number} {Operation} {Number} ...` + * + * Only basic arithmetic operations are allowed, and it has to be separate words + * similarly to how CSS calc works: + * + * - `sin(3.14159 * 2)` -> is valid + * - `sin(3.14159*2)` -> is not valid + * + * + * @param {FunctionNode} nodes Nodes to be parsed + * @param {Boolean} ignoreUnit Whether units are ignored or converted to radians + * @return {FunctionNode} Returns the node, if it managed to calculate, it will + * simplify inner nodes. + * @see https://www.w3.org/TR/css-values-4/#trig-funcs + */ +export function computeCalculation(nodes: Node[], ignoreUnit = false) { + let isValid = true; + const expression = []; + const words = nodes.filter(filterOnlyWords); + let operationPart = ExpressionPart.Number; + let detectedUnit; + + const addToExpression = (part: string, type: ExpressionPart) => { + if (operationPart !== type) { + isValid = false; + return; + } + + expression.push(part); + operationPart = type === ExpressionPart.Number + ? ExpressionPart.Operation + : ExpressionPart.Number; + }; + + for (let i = 0, len = words.length; i < len && isValid; i++) { + const word = words[i]; + + if (ALLOWED_OPERATIONS.includes(word.value)) { + addToExpression(word.value, ExpressionPart.Operation); + continue; + } + + const parsed = valueParser.unit(word.value); + + // This could be an unsupported expression + if (!parsed) { + isValid = false; + break; + } + + if (ignoreUnit) { + if (!detectedUnit) { + detectedUnit = parsed.unit; + } + + // Can't calculate from different units when unit is ignored + if (detectedUnit !== parsed.unit) { + isValid = false; + break; + } + + addToExpression(word.value, ExpressionPart.Operation); + continue; + } + + // Is it unitless? Assume radians + if (!parsed.unit) { + addToExpression(word.value, ExpressionPart.Number); + continue; + } + + if (parsed.unit === 'rad') { + addToExpression(parsed.number, ExpressionPart.Number); + continue; + } + + if (typeof toRad[parsed.unit] === 'function') { + const number = toRad[parsed.unit](Number(parsed.number)); + + if (!Number.isNaN(number) && Number.isFinite(number)) { + addToExpression(number.toString(), ExpressionPart.Number); + continue; + } else { + isValid = false; + break; + } + } + + isValid = false; + } + + if (!isValid) { + return nodes; + } + + // For expressions to be valid they have to be either 3 + // or subsequent even numbers of elements + // {Number} {Operation} {Number} -> 3 + // {Number} {Operation} {Number} {Operation} {Number} -> 5 + // ... and so on + // Otherwise don't bother computing + if (expression.length % 2 === 0 || expression.length < 3) { + return nodes; + } + + let result; + + try { + const context = vm.createContext({ result: NaN }); + const calculation = new vm.Script(`result = ${expression.join(' ')}`); + calculation.runInContext(context); + + if (typeof context.result === 'number' && !Number.isNaN(context.result) && Number.isFinite(context.result)) { + result = context.result; + } + } catch(error) { + // Error silently + } + + if (typeof result !== 'undefined') { + let value = result.toString(); + + if (detectedUnit) { + value += detectedUnit; + } + + const sourceIndex = nodes[0].sourceIndex; + const sourceEndIndex = value.length; + + nodes.length = 0; + nodes.push({ + type: 'word', + value, + sourceIndex, + sourceEndIndex, + }); + } + + return nodes; +} + export function functionNodeToWordNode(fn: FunctionNode): WordNode { delete fn.nodes; const node = fn as Node; @@ -31,6 +207,20 @@ export function functionNodeToWordNode(fn: FunctionNode): WordNode { return node as WordNode; } +/** + * Formats a number that's intended to be put into CSS. + * + * Due to processing of Number(number.toFixed(decimals)) this will get + * rid of ending zeroes, usually helping with the rounding which is the + * intended effect. + * + * For example, converting 4.71238898038469 radians into deg leads to + * 270.000000000669786 which is going to result as 270 unless a + * precision of 10 is chosen. + * + * @param {Number} number Number to be formatted + * @param {Number} decimals Precision of decimals, CSS doesn't usually handle further than 5. + */ export function formatResultingNumber(number: number, decimals: number): string { if (!Number.isNaN(number)) { if (number > Number.MAX_SAFE_INTEGER) { @@ -43,46 +233,69 @@ export function formatResultingNumber(number: number, decimals: number): string return Number(number.toFixed(decimals)).toString(); } +export function parseNumber(value: string) { + let number; + let unit = ''; + + if (value.toLowerCase() === 'infinity' ) { + number = Infinity; + } else if (value.toLowerCase() === '-infinity') { + number = Infinity * -1; + } + + if (!number) { + const parsed = valueParser.unit(value); + + if (!parsed) { + return false; + } + + number = Number(parsed.number); + + if (!Number.isNaN(number)) { + unit = parsed.unit; + } + } + + return { + number, + unit, + }; +} + +type validateNodeReturn = [WordNode, number] | undefined; + export function validateNode( node: FunctionNode, parseUnit = true, - args = 1, -): [WordNode,number] | undefined { - const words = node.nodes.filter(childNode => childNode.type === 'word'); +): validateNodeReturn { + node.nodes = computeCalculation(node.nodes); + const words = node.nodes.filter(filterOnlyWords); - if (words.length !== args) { + if (words.length !== 1) { return; } const { value } = words[0]; - const parsed = valueParser.unit(value); - let number; - - if (value.toLowerCase() === 'infinity' ) { - number = Infinity; - } else if (value.toLowerCase() === '-infinity') { - number = Infinity * -1; - } + const parsed = parseNumber(value); - if (!parsed && !number) { + if (!parsed) { return; } - if (parsed) { - number = Number(parsed.number); + let number = parsed.number; - if (parseUnit) { - if (parsed.unit && parsed.unit !== 'rad') { - if (toRad[parsed.unit]) { - number = toRad[parsed.unit](number); - } else { - return; - } + if (parseUnit) { + if (parsed.unit && parsed.unit !== 'rad') { + if (toRad[parsed.unit]) { + number = toRad[parsed.unit](number); + } else { + return; } - } else if (parsed.unit) { - return; } + } else if (parsed.unit) { + return; } return [ @@ -91,4 +304,4 @@ export function validateNode( ]; } -export { toRad }; +export { toRad, toDeg }; diff --git a/plugins/postcss-trigonometric-functions/test/basic.css b/plugins/postcss-trigonometric-functions/test/basic.css index 3aa39fd41..629149352 100644 --- a/plugins/postcss-trigonometric-functions/test/basic.css +++ b/plugins/postcss-trigonometric-functions/test/basic.css @@ -1,14 +1,14 @@ .sin { - order: sin(45deg); - order: sin(.125turn); - order: sin(50grad); - order: sin(0.785398rad); - order: sin(0.785398); - order: sin(-45deg); - order: sin(-.125turn); - order: sin(-50grad); - order: sin(-0.785398rad); - order: sin(-0.785398); + order: sin(45deg); /* 45deg */ + order: sin(.125turn); /* 45deg */ + order: sin(50grad); /* 45deg */ + order: sin(0.785398rad); /* 45deg */ + order: sin(0.785398); /* 45deg */ + order: sin(-45deg); /* -45deg */ + order: sin(-.125turn); /* -45deg */ + order: sin(-50grad); /* -45deg */ + order: sin(-0.785398rad); /* -45deg */ + order: sin(-0.785398); /* -45deg */ order: sin(0); order: sin(sin(0.785398)); order: sin(sin(sin(sin(0.785398)))); @@ -17,20 +17,19 @@ order: sin(Infinity); order: sin(InFiNiTy); order: sin(-infinity); - order: sin(3.14159 / 4); } .cos { - order: cos(45deg); - order: cos(.125turn); - order: cos(50grad); - order: cos(1rad); - order: cos(1); - order: cos(-45deg); - order: cos(-.125turn); - order: cos(-50grad); - order: cos(-0.785398rad); - order: cos(-0.785398); + order: cos(45deg); /* 45deg */ + order: cos(.125turn); /* 45deg */ + order: cos(50grad); /* 45deg */ + order: cos(0.785398rad); /* 45deg */ + order: cos(0.785398); /* 45deg */ + order: cos(-45deg); /* -45deg */ + order: cos(-.125turn); /* -45deg */ + order: cos(-50grad); /* -45deg */ + order: cos(-0.785398rad); /* -45deg */ + order: cos(-0.785398); /* -45deg */ order: cos(cos(0.785398)); order: cos(cos(cos(cos(0.785398)))); order: cos(1px); @@ -38,35 +37,46 @@ order: cos(Infinity); order: cos(InFiNiTy); order: cos(-infinity); - order: cos(3.14159 / 4); } .tan { - order: tan(45deg); - order: tan(90deg); - order: tan(-90deg); - order: tan(.125turn); - order: tan(50grad); - order: tan(1rad); - order: tan(1); - order: tan(-45deg); - order: tan(-.125turn); - order: tan(-50grad); - order: tan(-0.785398rad); - order: tan(-0.785398); + order: tan(45deg); /* 45deg */ + order: tan(.125turn); /* 45deg */ + order: tan(50grad); /* 45deg */ + order: tan(0.785398rad); /* 45deg */ + order: tan(0.785398); /* 45deg */ + order: tan(-45deg); /* -45deg */ + order: tan(-.125turn); /* -45deg */ + order: tan(-50grad); /* -45deg */ + order: tan(-0.785398rad); /* -45deg */ + order: tan(-0.785398); /* -45deg */ order: tan(0); order: tan(tan(0.785398)); order: tan(tan(tan(tan(0.785398)))); + /* Asymptotes */ + order: tan(90deg); + order: tan(100grad); + order: tan(270deg); + order: tan(300grad); + order: tan(-270deg); + order: tan(-100grad); + order: tan(-450deg); + order: tan(-300grad); + /* Non Asymptotes */ + order: tan(180deg); + order: tan(200grad); + order: tan(360deg); + order: tan(400grad); order: tan(1px); order: tan(foo); order: tan(Infinity); order: tan(InFiNiTy); order: tan(-infinity); - order: tan(3.14159 / 4); } .asin { order: asin(sin(45deg)); + order: asin(sin(90deg)); order: asin(-1); order: asin(-0.5); order: asin(0); @@ -80,3 +90,82 @@ order: asin(infinity); order: asin(-infinity); } + +.acos { + order: acos(cos(45deg)); + order: acos(cos(90deg)); + order: acos(-1); + order: acos(-0.5); + order: acos(0); + order: acos(0.5); + order: acos(1); + order: acos(2); + order: acos(-20); + order: acos(45deg); + order: acos(1rad); + order: acos(foo); + order: acos(infinity); + order: acos(-infinity); +} + +.atan { + order: atan(infinity); + order: atan(-infinity); + order: atan(1); + order: atan(0); + order: atan(-0); + order: atan(tan(45deg)); /* 45deg */ + order: atan(tan(.125turn)); /* 45deg */ + order: atan(tan(50grad)); /* 45deg */ + order: atan(tan(0.785398rad)); /* 45deg */ + order: atan(tan(0.785398)); /* 45deg */ + order: atan(tan(-45deg)); /* -45deg */ + order: atan(tan(-.125turn)); /* -45deg */ + order: atan(tan(-50grad)); /* -45deg */ + order: atan(tan(-0.785398rad)); /* -45deg */ + order: atan(tan(-0.785398)); /* -45deg */ +} + +.atan2 { + order: atan2(-infinity, -infinity); + order: atan2(-infinity, -1); + order: atan2(-infinity, -0); + order: atan2(-infinity, 0); + order: atan2(-infinity, 1); + order: atan2(-infinity, infinity); + + order: atan2(-1, -infinity); + order: atan2(-1, -1); + order: atan2(-1, -0); + order: atan2(-1, 0); + order: atan2(-1, 1); + order: atan2(-1, infinity); + + order: atan2(-0, -infinity); + order: atan2(-0, -1); + order: atan2(-0, -0); + order: atan2(-0, 0); + order: atan2(-0, 1); + order: atan2(-0, infinity); + + order: atan2(0, -infinity); + order: atan2(0, -1); + order: atan2(0, -0); + order: atan2(0, 0); + order: atan2(0, 1); + order: atan2(0, infinity); + + order: atan2(1, -infinity); + order: atan2(1, -1); + order: atan2(1, -0); + order: atan2(1, 0); + order: atan2(1, 1); + order: atan2(1, infinity); + + order: atan2(infinity, -infinity); + order: atan2(infinity, -1); + order: atan2(infinity, -0); + order: atan2(infinity, 0); + order: atan2(infinity, 1); + order: atan2(infinity, infinity); +} diff --git a/plugins/postcss-trigonometric-functions/test/basic.expect.css b/plugins/postcss-trigonometric-functions/test/basic.expect.css index d71b9d90f..549d16c8b 100644 --- a/plugins/postcss-trigonometric-functions/test/basic.expect.css +++ b/plugins/postcss-trigonometric-functions/test/basic.expect.css @@ -1,14 +1,14 @@ .sin { - order: 0.70711; - order: 0.70711; - order: 0.70711; - order: 0.70711; - order: 0.70711; - order: -0.70711; - order: -0.70711; - order: -0.70711; - order: -0.70711; - order: -0.70711; + order: 0.70711; /* 45deg */ + order: 0.70711; /* 45deg */ + order: 0.70711; /* 45deg */ + order: 0.70711; /* 45deg */ + order: 0.70711; /* 45deg */ + order: -0.70711; /* -45deg */ + order: -0.70711; /* -45deg */ + order: -0.70711; /* -45deg */ + order: -0.70711; /* -45deg */ + order: -0.70711; /* -45deg */ order: 0; order: 0.64964; order: 0.56868; @@ -16,48 +16,112 @@ order: sin(foo); order: NaN; order: NaN; - order: sin(3.14159 / 4); + order: NaN; } .cos { - order: 0.70711; - order: 0.70711; - order: 0.70711; - order: 0.5403; - order: 0.5403; - order: 0.70711; - order: 0.70711; - order: 0.70711; - order: 0.70711; - order: 0.70711; + order: 0.70711; /* 45deg */ + order: 0.70711; /* 45deg */ + order: 0.70711; /* 45deg */ + order: 0.70711; /* 45deg */ + order: 0.70711; /* 45deg */ + order: 0.70711; /* -45deg */ + order: 0.70711; /* -45deg */ + order: 0.70711; /* -45deg */ + order: 0.70711; /* -45deg */ + order: 0.70711; /* -45deg */ order: 0.76024; order: 0.74872; order: cos(1px); order: cos(foo); order: NaN; order: NaN; - order: cos(3.14159 / 4); + order: NaN; } .tan { - order: 1; - order: infinity; - order: -infinity; - order: 1; - order: 1; - order: 1.55741; - order: 1.55741; - order: -1; - order: -1; - order: -1; - order: -1; - order: -1; + order: 1; /* 45deg */ + order: 1; /* 45deg */ + order: 1; /* 45deg */ + order: 1; /* 45deg */ + order: 1; /* 45deg */ + order: -1; /* -45deg */ + order: -1; /* -45deg */ + order: -1; /* -45deg */ + order: -1; /* -45deg */ + order: -1; /* -45deg */ order: 0; order: 1.55741; order: -0.84159; + /* Asymptotes */ + order: infinity; + order: infinity; + order: infinity; + order: infinity; + order: -infinity; + order: -infinity; + order: -infinity; + order: -infinity; + /* Non Asymptotes */ + order: 0; + order: 0; + order: 0; + order: 0; order: tan(1px); order: tan(foo); order: NaN; order: NaN; - order: tan(3.14159 / 4); + order: NaN; +} + +.asin { + order: 45deg; + order: 90deg; + order: -90deg; + order: -30deg; + order: 0deg; + order: 30deg; + order: 90deg; + order: NaN; + order: NaN; + order: asin(45deg); + order: asin(1rad); + order: asin(foo); + order: NaN; + order: NaN; +} + +.acos { + order: 45deg; + order: 90deg; + order: 180deg; + order: 120deg; + order: 90deg; + order: 60deg; + order: 0deg; + order: NaN; + order: NaN; + order: acos(45deg); + order: acos(1rad); + order: acos(foo); + order: NaN; + order: NaN; +} + +.atan { + order: 90deg; + order: -90deg; + order: 45deg; + order: 0deg; + order: 0deg; + order: 45deg; /* 45deg */ + order: 45deg; /* 45deg */ + order: 45deg; /* 45deg */ + order: 45deg; /* 45deg */ + order: 45deg; /* 45deg */ + order: -45deg; /* -45deg */ + order: -45deg; /* -45deg */ + order: -45deg; /* -45deg */ + order: -45deg; /* -45deg */ + order: -45deg; /* -45deg */ } diff --git a/plugins/postcss-trigonometric-functions/test/temporal.css b/plugins/postcss-trigonometric-functions/test/temporal.css new file mode 100644 index 000000000..d122b105e --- /dev/null +++ b/plugins/postcss-trigonometric-functions/test/temporal.css @@ -0,0 +1,172 @@ +.sin { + order: sin(45deg); /* 45deg */ + order: sin(.125turn); /* 45deg */ + order: sin(50grad); /* 45deg */ + order: sin(0.785398rad); /* 45deg */ + order: sin(0.785398); /* 45deg */ + order: sin(-45deg); /* -45deg */ + order: sin(-.125turn); /* -45deg */ + order: sin(-50grad); /* -45deg */ + order: sin(-0.785398rad); /* -45deg */ + order: sin(-0.785398); /* -45deg */ + order: sin(0); + order: sin(sin(0.785398)); + order: sin(sin(sin(sin(0.785398)))); + order: sin(1px); + order: sin(foo); + order: sin(Infinity); + order: sin(InFiNiTy); + order: sin(-infinity); +} + +.cos { + order: cos(45deg); /* 45deg */ + order: cos(.125turn); /* 45deg */ + order: cos(50grad); /* 45deg */ + order: cos(0.785398rad); /* 45deg */ + order: cos(0.785398); /* 45deg */ + order: cos(-45deg); /* -45deg */ + order: cos(-.125turn); /* -45deg */ + order: cos(-50grad); /* -45deg */ + order: cos(-0.785398rad); /* -45deg */ + order: cos(-0.785398); /* -45deg */ + order: cos(cos(0.785398)); + order: cos(cos(cos(cos(0.785398)))); + order: cos(1px); + order: cos(foo); + order: cos(Infinity); + order: cos(InFiNiTy); + order: cos(-infinity); +} + +.tan { + order: tan(45deg); /* 45deg */ + order: tan(.125turn); /* 45deg */ + order: tan(50grad); /* 45deg */ + order: tan(0.785398rad); /* 45deg */ + order: tan(0.785398); /* 45deg */ + order: tan(-45deg); /* -45deg */ + order: tan(-.125turn); /* -45deg */ + order: tan(-50grad); /* -45deg */ + order: tan(-0.785398rad); /* -45deg */ + order: tan(-0.785398); /* -45deg */ + order: tan(0); + order: tan(tan(0.785398)); + order: tan(tan(tan(tan(0.785398)))); + /* Asymptotes */ + order: tan(90deg); + order: tan(100grad); + order: tan(270deg); + order: tan(300grad); + order: tan(-270deg); + order: tan(-100grad); + order: tan(-450deg); + order: tan(-300grad); + /* Non Asymptotes */ + order: tan(180deg); + order: tan(200grad); + order: tan(360deg); + order: tan(400grad); + order: tan(1px); + order: tan(foo); + order: tan(Infinity); + order: tan(InFiNiTy); + order: tan(-infinity); +} + +.asin { + order: asin(sin(45deg)); + order: asin(sin(90deg)); + order: asin(-1); + order: asin(-0.5); + order: asin(0); + order: asin(0.5); + order: asin(1); + order: asin(2); + order: asin(-20); + order: asin(45deg); + order: asin(1rad); + order: asin(foo); + order: asin(infinity); + order: asin(-infinity); +} + +.acos { + order: acos(cos(45deg)); + order: acos(cos(90deg)); + order: acos(-1); + order: acos(-0.5); + order: acos(0); + order: acos(0.5); + order: acos(1); + order: acos(2); + order: acos(-20); + order: acos(45deg); + order: acos(1rad); + order: acos(foo); + order: acos(infinity); + order: acos(-infinity); +} + +.atan { + order: atan(infinity); + order: atan(-infinity); + order: atan(1); + order: atan(0); + order: atan(-0); + order: atan(tan(45deg)); /* 45deg */ + order: atan(tan(.125turn)); /* 45deg */ + order: atan(tan(50grad)); /* 45deg */ + order: atan(tan(0.785398rad)); /* 45deg */ + order: atan(tan(0.785398)); /* 45deg */ + order: atan(tan(-45deg)); /* -45deg */ + order: atan(tan(-.125turn)); /* -45deg */ + order: atan(tan(-50grad)); /* -45deg */ + order: atan(tan(-0.785398rad)); /* -45deg */ + order: atan(tan(-0.785398)); /* -45deg */ +} + +.atan2 { + order: atan2(-infinity, -infinity); + order: atan2(-infinity, -1); + order: atan2(-infinity, -0); + order: atan2(-infinity, 0); + order: atan2(-infinity, 1); + order: atan2(-infinity, infinity); + + order: atan2(-1, -infinity); + order: atan2(-1, -1); + order: atan2(-1, -0); + order: atan2(-1, 0); + order: atan2(-1, 1); + order: atan2(-1, infinity); + + order: atan2(-0, -infinity); + order: atan2(-0, -1); + order: atan2(-0, -0); + order: atan2(-0, 0); + order: atan2(-0, 1); + order: atan2(-0, infinity); + + order: atan2(0, -infinity); + order: atan2(0, -1); + order: atan2(0, -0); + order: atan2(0, 0); + order: atan2(0, 1); + order: atan2(0, infinity); + + order: atan2(1, -infinity); + order: atan2(1, -1); + order: atan2(1, -0); + order: atan2(1, 0); + order: atan2(1, 1); + order: atan2(1, infinity); + + order: atan2(infinity, -infinity); + order: atan2(infinity, -1); + order: atan2(infinity, -0); + order: atan2(infinity, 0); + order: atan2(infinity, 1); + order: atan2(infinity, infinity); +} + From 8a5e9427b8e04ae45f664a63580c6f2ae9ecb0d5 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Mon, 16 May 2022 18:44:39 +0200 Subject: [PATCH 03/17] Less strict filtering --- plugins/postcss-trigonometric-functions/src/utils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/postcss-trigonometric-functions/src/utils.ts b/plugins/postcss-trigonometric-functions/src/utils.ts index dce516a5e..ee33983ee 100644 --- a/plugins/postcss-trigonometric-functions/src/utils.ts +++ b/plugins/postcss-trigonometric-functions/src/utils.ts @@ -76,7 +76,9 @@ enum ExpressionPart { export function computeCalculation(nodes: Node[], ignoreUnit = false) { let isValid = true; const expression = []; - const words = nodes.filter(filterOnlyWords); + const filteredNodes = nodes.filter( + node => node.type === 'word' || ALLOWED_OPERATIONS.includes(node.value) + ); let operationPart = ExpressionPart.Number; let detectedUnit; From 6f2828ec6f2859416a3611ec47503a38f9bdbccb Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Mon, 16 May 2022 18:44:59 +0200 Subject: [PATCH 04/17] Handling pi and e --- .../src/utils.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/plugins/postcss-trigonometric-functions/src/utils.ts b/plugins/postcss-trigonometric-functions/src/utils.ts index ee33983ee..7c278520b 100644 --- a/plugins/postcss-trigonometric-functions/src/utils.ts +++ b/plugins/postcss-trigonometric-functions/src/utils.ts @@ -94,14 +94,24 @@ export function computeCalculation(nodes: Node[], ignoreUnit = false) { : ExpressionPart.Number; }; - for (let i = 0, len = words.length; i < len && isValid; i++) { - const word = words[i]; + for (let i = 0, len = filteredNodes.length; i < len && isValid; i++) { + const word = filteredNodes[i]; if (ALLOWED_OPERATIONS.includes(word.value)) { addToExpression(word.value, ExpressionPart.Operation); continue; } + if (word.value === 'pi') { + addToExpression(Math.PI.toString(), ExpressionPart.Number); + continue; + } + + if (word.value === 'e') { + addToExpression(Math.E.toString(), ExpressionPart.Number); + continue; + } + const parsed = valueParser.unit(word.value); // This could be an unsupported expression @@ -243,6 +253,10 @@ export function parseNumber(value: string) { number = Infinity; } else if (value.toLowerCase() === '-infinity') { number = Infinity * -1; + } else if (value === 'pi') { + number = Math.PI; + } else if (value === 'e') { + number = Math.E; } if (!number) { From 8b6c3e713958be21c0bec546f8a9ca4cd89a7170 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Mon, 16 May 2022 18:45:06 +0200 Subject: [PATCH 05/17] Better ordering --- plugins/postcss-trigonometric-functions/src/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/postcss-trigonometric-functions/src/index.ts b/plugins/postcss-trigonometric-functions/src/index.ts index c257adbe0..a5610d70c 100644 --- a/plugins/postcss-trigonometric-functions/src/index.ts +++ b/plugins/postcss-trigonometric-functions/src/index.ts @@ -10,13 +10,13 @@ import { atan2FunctionCheck, transformAtan2Function } from './atan2'; type pluginOptions = { preserve?: boolean }; const Transformations = [ - { check: sinFunctionCheck, transform: transformSinFunction }, - { check: cosFunctionCheck, transform: transformCosFunction }, - { check: tanFunctionCheck, transform: transformTanFunction }, { check: asinFunctionCheck, transform: transformAsinFunction }, { check: acosFunctionCheck, transform: transformAcosFunction }, { check: atanFunctionCheck, transform: transformAtanFunction }, { check: atan2FunctionCheck, transform: transformAtan2Function }, + { check: sinFunctionCheck, transform: transformSinFunction }, + { check: cosFunctionCheck, transform: transformCosFunction }, + { check: tanFunctionCheck, transform: transformTanFunction }, ]; const creator: PluginCreator = (opts?: pluginOptions) => { From 1437ea8c051f717abce88bd9a0609e5d7dd80c24 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Mon, 16 May 2022 18:45:14 +0200 Subject: [PATCH 06/17] Enabling WPT tests --- .../postcss-trigonometric-functions/.tape.mjs | 3 + .../test/basic.css | 4 + .../test/basic.expect.css | 48 +++++ .../test/temporal.css | 172 ------------------ .../test/wpt.css | 44 +++++ .../test/wpt.expect.css | 44 +++++ 6 files changed, 143 insertions(+), 172 deletions(-) delete mode 100644 plugins/postcss-trigonometric-functions/test/temporal.css create mode 100644 plugins/postcss-trigonometric-functions/test/wpt.css create mode 100644 plugins/postcss-trigonometric-functions/test/wpt.expect.css diff --git a/plugins/postcss-trigonometric-functions/.tape.mjs b/plugins/postcss-trigonometric-functions/.tape.mjs index cda74a20c..b675809b4 100644 --- a/plugins/postcss-trigonometric-functions/.tape.mjs +++ b/plugins/postcss-trigonometric-functions/.tape.mjs @@ -5,4 +5,7 @@ postcssTape(plugin)({ basic: { message: "supports basic usage", }, + wpt: { + message: "supports wpt cases", + }, }); diff --git a/plugins/postcss-trigonometric-functions/test/basic.css b/plugins/postcss-trigonometric-functions/test/basic.css index 629149352..085fa3457 100644 --- a/plugins/postcss-trigonometric-functions/test/basic.css +++ b/plugins/postcss-trigonometric-functions/test/basic.css @@ -17,6 +17,7 @@ order: sin(Infinity); order: sin(InFiNiTy); order: sin(-infinity); + order: sin(var(--foo)); } .cos { @@ -168,4 +169,7 @@ order: atan2(infinity, 0); order: atan2(infinity, 1); order: atan2(infinity, infinity); + + order: atan2(90, 15); + order: atan2(15, 90); } diff --git a/plugins/postcss-trigonometric-functions/test/basic.expect.css b/plugins/postcss-trigonometric-functions/test/basic.expect.css index 549d16c8b..320b61e34 100644 --- a/plugins/postcss-trigonometric-functions/test/basic.expect.css +++ b/plugins/postcss-trigonometric-functions/test/basic.expect.css @@ -17,6 +17,7 @@ order: NaN; order: NaN; order: NaN; + order: sin(var(--foo)); } .cos { @@ -125,3 +126,50 @@ order: -45deg; /* -45deg */ order: -45deg; /* -45deg */ } + +.atan2 { + order: -135deg; + order: -90deg; + order: -90deg; + order: -90deg; + order: -90deg; + order: -45deg; + + order: -180deg; + order: -135deg; + order: -90deg; + order: -90deg; + order: -45deg; + order: 0deg; + + order: -180deg; + order: -180deg; + order: -180deg; + order: 0deg; + order: 0deg; + order: 0deg; + + order: 180deg; + order: 180deg; + order: 180deg; + order: 0deg; + order: 0deg; + order: 0deg; + + order: 180deg; + order: 135deg; + order: 90deg; + order: 90deg; + order: 45deg; + order: 0deg; + + order: 135deg; + order: 90deg; + order: 90deg; + order: 90deg; + order: 90deg; + order: 45deg; + + order: 80.54deg; + order: 9.46deg; +} diff --git a/plugins/postcss-trigonometric-functions/test/temporal.css b/plugins/postcss-trigonometric-functions/test/temporal.css deleted file mode 100644 index d122b105e..000000000 --- a/plugins/postcss-trigonometric-functions/test/temporal.css +++ /dev/null @@ -1,172 +0,0 @@ -.sin { - order: sin(45deg); /* 45deg */ - order: sin(.125turn); /* 45deg */ - order: sin(50grad); /* 45deg */ - order: sin(0.785398rad); /* 45deg */ - order: sin(0.785398); /* 45deg */ - order: sin(-45deg); /* -45deg */ - order: sin(-.125turn); /* -45deg */ - order: sin(-50grad); /* -45deg */ - order: sin(-0.785398rad); /* -45deg */ - order: sin(-0.785398); /* -45deg */ - order: sin(0); - order: sin(sin(0.785398)); - order: sin(sin(sin(sin(0.785398)))); - order: sin(1px); - order: sin(foo); - order: sin(Infinity); - order: sin(InFiNiTy); - order: sin(-infinity); -} - -.cos { - order: cos(45deg); /* 45deg */ - order: cos(.125turn); /* 45deg */ - order: cos(50grad); /* 45deg */ - order: cos(0.785398rad); /* 45deg */ - order: cos(0.785398); /* 45deg */ - order: cos(-45deg); /* -45deg */ - order: cos(-.125turn); /* -45deg */ - order: cos(-50grad); /* -45deg */ - order: cos(-0.785398rad); /* -45deg */ - order: cos(-0.785398); /* -45deg */ - order: cos(cos(0.785398)); - order: cos(cos(cos(cos(0.785398)))); - order: cos(1px); - order: cos(foo); - order: cos(Infinity); - order: cos(InFiNiTy); - order: cos(-infinity); -} - -.tan { - order: tan(45deg); /* 45deg */ - order: tan(.125turn); /* 45deg */ - order: tan(50grad); /* 45deg */ - order: tan(0.785398rad); /* 45deg */ - order: tan(0.785398); /* 45deg */ - order: tan(-45deg); /* -45deg */ - order: tan(-.125turn); /* -45deg */ - order: tan(-50grad); /* -45deg */ - order: tan(-0.785398rad); /* -45deg */ - order: tan(-0.785398); /* -45deg */ - order: tan(0); - order: tan(tan(0.785398)); - order: tan(tan(tan(tan(0.785398)))); - /* Asymptotes */ - order: tan(90deg); - order: tan(100grad); - order: tan(270deg); - order: tan(300grad); - order: tan(-270deg); - order: tan(-100grad); - order: tan(-450deg); - order: tan(-300grad); - /* Non Asymptotes */ - order: tan(180deg); - order: tan(200grad); - order: tan(360deg); - order: tan(400grad); - order: tan(1px); - order: tan(foo); - order: tan(Infinity); - order: tan(InFiNiTy); - order: tan(-infinity); -} - -.asin { - order: asin(sin(45deg)); - order: asin(sin(90deg)); - order: asin(-1); - order: asin(-0.5); - order: asin(0); - order: asin(0.5); - order: asin(1); - order: asin(2); - order: asin(-20); - order: asin(45deg); - order: asin(1rad); - order: asin(foo); - order: asin(infinity); - order: asin(-infinity); -} - -.acos { - order: acos(cos(45deg)); - order: acos(cos(90deg)); - order: acos(-1); - order: acos(-0.5); - order: acos(0); - order: acos(0.5); - order: acos(1); - order: acos(2); - order: acos(-20); - order: acos(45deg); - order: acos(1rad); - order: acos(foo); - order: acos(infinity); - order: acos(-infinity); -} - -.atan { - order: atan(infinity); - order: atan(-infinity); - order: atan(1); - order: atan(0); - order: atan(-0); - order: atan(tan(45deg)); /* 45deg */ - order: atan(tan(.125turn)); /* 45deg */ - order: atan(tan(50grad)); /* 45deg */ - order: atan(tan(0.785398rad)); /* 45deg */ - order: atan(tan(0.785398)); /* 45deg */ - order: atan(tan(-45deg)); /* -45deg */ - order: atan(tan(-.125turn)); /* -45deg */ - order: atan(tan(-50grad)); /* -45deg */ - order: atan(tan(-0.785398rad)); /* -45deg */ - order: atan(tan(-0.785398)); /* -45deg */ -} - -.atan2 { - order: atan2(-infinity, -infinity); - order: atan2(-infinity, -1); - order: atan2(-infinity, -0); - order: atan2(-infinity, 0); - order: atan2(-infinity, 1); - order: atan2(-infinity, infinity); - - order: atan2(-1, -infinity); - order: atan2(-1, -1); - order: atan2(-1, -0); - order: atan2(-1, 0); - order: atan2(-1, 1); - order: atan2(-1, infinity); - - order: atan2(-0, -infinity); - order: atan2(-0, -1); - order: atan2(-0, -0); - order: atan2(-0, 0); - order: atan2(-0, 1); - order: atan2(-0, infinity); - - order: atan2(0, -infinity); - order: atan2(0, -1); - order: atan2(0, -0); - order: atan2(0, 0); - order: atan2(0, 1); - order: atan2(0, infinity); - - order: atan2(1, -infinity); - order: atan2(1, -1); - order: atan2(1, -0); - order: atan2(1, 0); - order: atan2(1, 1); - order: atan2(1, infinity); - - order: atan2(infinity, -infinity); - order: atan2(infinity, -1); - order: atan2(infinity, -0); - order: atan2(infinity, 0); - order: atan2(infinity, 1); - order: atan2(infinity, infinity); -} - diff --git a/plugins/postcss-trigonometric-functions/test/wpt.css b/plugins/postcss-trigonometric-functions/test/wpt.css new file mode 100644 index 000000000..c792a4c02 --- /dev/null +++ b/plugins/postcss-trigonometric-functions/test/wpt.css @@ -0,0 +1,44 @@ +/* https://wpt.fyi/results/css/css-values/acos-asin-atan-atan2-computed.html?label=experimental&label=master&aligned&q=sin */ +.test { + order: acos(1); /* Should be 0deg */ + order: atan(0); /* Should be 0deg */ + order: asin(0); /* Should be 0deg */ + order: atan2(0, 0); /* Should be 0deg */ + order: asin(sin(pi / 2)); /* Should be 90deg */ + order: acos(cos(pi - 3.14159265358979323846)); /* Should be 0deg */ + order: atan(e - 2.7182818284590452354); /* Should be 0deg */ + order: asin(sin(30deg + 1.0471967rad)); /* Should be 90deg */ + order: acos(cos(30deg - 0.523599rad)); /* Should be 0deg */ + order: asin(sin(3.14159 / 2 + 1 - 1)); /* Should be 90deg */ + order: asin(sin(100grad)); /* Should be 90deg */ + order: acos(cos(0 / 2 + 1 - 1)); /* Should be 0deg */ + order: atan(tan(30deg + 0.261799rad)); /* Should be 45deg */ + order: atan(tan(0.7853975rad)); /* Should be 45deg */ + order: atan(tan(3.14159 / 4 + 1 - 1)); /* Should be 45deg */ + order: asin(sin(0.25turn)); /* Should be 90deg */ + order: atan2(0,1); /* Should be 0deg */ + order: atan2(0,-1); /* Should be 180deg */ + order: atan2(1,-1); /* Should be 135deg */ + order: atan2(-1,1); /* Should be -45deg */ + order: cos(sin(acos(cos(pi)))); /* Should be 1 */ + order: sin(atan(tan(pi/2))); /* Should be 1 */ + order: atan2(1px, -1px); /* Should be 135deg */ + order: atan2(1cm, -1cm); /* Should be 135deg */ + order: atan2(1mm, -1mm); /* Should be 135deg */ + order: atan2(1Q, -1Q); /* Should be 135deg */ + order: atan2(1in, -1in); /* Should be 135deg */ + order: atan2(1pc, -1pc); /* Should be 135deg */ + order: atan2(1pt, -1pt); /* Should be 135deg */ + order: atan2(1em, -1em); /* Should be 135deg */ + order: atan2(1ex, -1ex); /* Should be 135deg */ + order: atan2(1ch, -1ch); /* Should be 135deg */ + order: atan2(1rem, -1rem); /* Should be 135deg */ + order: atan2(1vh, -1vh); /* Should be 135deg */ + order: atan2(1vw, -1vw); /* Should be 135deg */ + order: atan2(1deg, -1deg); /* Should be 135deg */ + order: atan2(1grad, -1grad); /* Should be 135deg */ + order: atan2(1turn, -1turn); /* Should be 135deg */ + order: atan2(1rad, -1rad); /* Should be 135deg */ + order: atan2(1s, -1s); /* Should be 135deg */ + order: atan2(1ms, -1ms); /* Should be 135deg */ +} diff --git a/plugins/postcss-trigonometric-functions/test/wpt.expect.css b/plugins/postcss-trigonometric-functions/test/wpt.expect.css new file mode 100644 index 000000000..253ae9267 --- /dev/null +++ b/plugins/postcss-trigonometric-functions/test/wpt.expect.css @@ -0,0 +1,44 @@ +/* https://wpt.fyi/results/css/css-values/acos-asin-atan-atan2-computed.html?label=experimental&label=master&aligned&q=sin */ +.test { + order: 0deg; /* Should be 0deg */ + order: 0deg; /* Should be 0deg */ + order: 0deg; /* Should be 0deg */ + order: 0deg; /* Should be 0deg */ + order: 90deg; /* Should be 90deg */ + order: 0deg; /* Should be 0deg */ + order: 0deg; /* Should be 0deg */ + order: 90deg; /* Should be 90deg */ + order: 0deg; /* Should be 0deg */ + order: 90deg; /* Should be 90deg */ + order: 90deg; /* Should be 90deg */ + order: 0deg; /* Should be 0deg */ + order: 45deg; /* Should be 45deg */ + order: 45deg; /* Should be 45deg */ + order: 45deg; /* Should be 45deg */ + order: 90deg; /* Should be 90deg */ + order: 0deg; /* Should be 0deg */ + order: 180deg; /* Should be 180deg */ + order: 135deg; /* Should be 135deg */ + order: -45deg; /* Should be -45deg */ + order: 1; /* Should be 1 */ + order: 1; /* Should be 1 */ + order: 135deg; /* Should be 135deg */ + order: 135deg; /* Should be 135deg */ + order: 135deg; /* Should be 135deg */ + order: 135deg; /* Should be 135deg */ + order: 135deg; /* Should be 135deg */ + order: 135deg; /* Should be 135deg */ + order: 135deg; /* Should be 135deg */ + order: 135deg; /* Should be 135deg */ + order: 135deg; /* Should be 135deg */ + order: 135deg; /* Should be 135deg */ + order: 135deg; /* Should be 135deg */ + order: 135deg; /* Should be 135deg */ + order: 135deg; /* Should be 135deg */ + order: 135deg; /* Should be 135deg */ + order: 135deg; /* Should be 135deg */ + order: 135deg; /* Should be 135deg */ + order: 135deg; /* Should be 135deg */ + order: 135deg; /* Should be 135deg */ + order: 135deg; /* Should be 135deg */ +} From 53a4571057f0163f8f41c3bbf19e1065d890357b Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Tue, 17 May 2022 06:51:45 +0200 Subject: [PATCH 07/17] Ignore units for `atan2` --- plugins/postcss-trigonometric-functions/src/atan2.ts | 4 ++-- plugins/postcss-trigonometric-functions/test/test.css | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 plugins/postcss-trigonometric-functions/test/test.css diff --git a/plugins/postcss-trigonometric-functions/src/atan2.ts b/plugins/postcss-trigonometric-functions/src/atan2.ts index 5c0448f61..01d1707a4 100644 --- a/plugins/postcss-trigonometric-functions/src/atan2.ts +++ b/plugins/postcss-trigonometric-functions/src/atan2.ts @@ -36,11 +36,11 @@ function transformAtan2Function(decl: Declaration): string | undefined { // Compute calculations first if (first.length > 1) { - first = computeCalculation(first); + first = computeCalculation(first, true); } if (second.length > 1) { - second = computeCalculation(second); + second = computeCalculation(second, true); } if (first.length !== 1 || second.length !== 1) { diff --git a/plugins/postcss-trigonometric-functions/test/test.css b/plugins/postcss-trigonometric-functions/test/test.css new file mode 100644 index 000000000..0060207d4 --- /dev/null +++ b/plugins/postcss-trigonometric-functions/test/test.css @@ -0,0 +1,3 @@ +.test { + order: tan(((1 + 2) * 3) * 1deg); +} From a5b6fb2a87e58fd0b51bec97dbd900ddd2677094 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Tue, 17 May 2022 06:51:56 +0200 Subject: [PATCH 08/17] Housekeeping --- plugins/postcss-trigonometric-functions/src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/postcss-trigonometric-functions/src/utils.ts b/plugins/postcss-trigonometric-functions/src/utils.ts index 7c278520b..da526d4d5 100644 --- a/plugins/postcss-trigonometric-functions/src/utils.ts +++ b/plugins/postcss-trigonometric-functions/src/utils.ts @@ -77,7 +77,7 @@ export function computeCalculation(nodes: Node[], ignoreUnit = false) { let isValid = true; const expression = []; const filteredNodes = nodes.filter( - node => node.type === 'word' || ALLOWED_OPERATIONS.includes(node.value) + node => node.type === 'word' || ALLOWED_OPERATIONS.includes(node.value), ); let operationPart = ExpressionPart.Number; let detectedUnit; From a230e80ea65a9d42c75a797f3372a7917ec50bdb Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Tue, 17 May 2022 06:52:43 +0200 Subject: [PATCH 09/17] Early exit --- plugins/postcss-trigonometric-functions/src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/postcss-trigonometric-functions/src/utils.ts b/plugins/postcss-trigonometric-functions/src/utils.ts index da526d4d5..7d32f00e5 100644 --- a/plugins/postcss-trigonometric-functions/src/utils.ts +++ b/plugins/postcss-trigonometric-functions/src/utils.ts @@ -288,7 +288,7 @@ export function validateNode( node.nodes = computeCalculation(node.nodes); const words = node.nodes.filter(filterOnlyWords); - if (words.length !== 1) { + if (node.nodes.length !== 1 || words.length !== 1) { return; } From a1cade27ebe6e952a66c8b1fcb6b76c51c5159db Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Tue, 17 May 2022 06:52:58 +0200 Subject: [PATCH 10/17] Wrong broken tests --- .../test/wpt.css | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/plugins/postcss-trigonometric-functions/test/wpt.css b/plugins/postcss-trigonometric-functions/test/wpt.css index c792a4c02..0466255fe 100644 --- a/plugins/postcss-trigonometric-functions/test/wpt.css +++ b/plugins/postcss-trigonometric-functions/test/wpt.css @@ -42,3 +42,67 @@ order: atan2(1s, -1s); /* Should be 135deg */ order: atan2(1ms, -1ms); /* Should be 135deg */ } + +/* https://wpt.fyi/results/css/css-values/acos-asin-atan-atan2-invalid.html?label=master&label=experimental&aligned&q=sin */ +.wrong { + order: asin()); + order: asin( )); + order: asin(,)); + order: asin(1dag)); + order: asin(1deg, )); + order: asin(, 1deg)); + order: asin(1deg + )); + order: asin(1deg - )); + order: asin(1deg * )); + order: asin(1deg / )); + order: asin(1deg 2deg)); + order: asin(1deg, , 2deg)); + order: acos()); + order: acos( )); + order: acos(,)); + order: acos(1dag)); + order: acos(1deg, )); + order: acos(, 1deg)); + order: acos(1deg + )); + order: acos(1deg - )); + order: acos(1deg * )); + order: acos(1deg / )); + order: acos(1deg 2deg)); + order: acos(1deg, , 2deg)); + order: atan()); + order: atan( )); + order: atan(,)); + order: atan(1dag)); + order: atan(1deg, )); + order: atan(, 1deg)); + order: atan(1deg + )); + order: atan(1deg - )); + order: atan(1deg * )); + order: atan(1deg / )); + order: atan(1deg 2deg)); + order: atan(1deg, , 2deg)); + order: asin(90px)); + order: asin(30deg + 1.0471967rad, 0)); + order: acos( 0 ,)); + order: acos( () 30deg - 0.523599rad )); + order: atan(45deg )); + order: atan(30deg, + 0.261799rad)); + order: atan2()); + order: atan2( )); + order: atan2(,)); + order: atan2(1dag)); + order: atan2(1deg, )); + order: atan2(, 1deg)); + order: atan2(1deg + )); + order: atan2(1deg - )); + order: atan2(1deg * )); + order: atan2(1deg / )); + order: atan2(1deg 2deg)); + order: atan2(1deg, , 2deg)); + order: atan2(90px)); + order: atan2(30deg + 1.0471967rad, 0)); + order: atan2( 0 ,)); + order: atan2( () 30deg - 0.523599rad )); + order: atan2(45deg )); + order: atan2(30deg, + 0.261799rad)); +} From 7f7edd08a5f70a6b508e50b9e3ae5d228fec01ff Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Fri, 20 May 2022 02:42:57 +0200 Subject: [PATCH 11/17] Accounting for wrong tests --- .../test/wpt.expect.css | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/plugins/postcss-trigonometric-functions/test/wpt.expect.css b/plugins/postcss-trigonometric-functions/test/wpt.expect.css index 253ae9267..a414c7d5f 100644 --- a/plugins/postcss-trigonometric-functions/test/wpt.expect.css +++ b/plugins/postcss-trigonometric-functions/test/wpt.expect.css @@ -42,3 +42,67 @@ order: 135deg; /* Should be 135deg */ order: 135deg; /* Should be 135deg */ } + +/* https://wpt.fyi/results/css/css-values/acos-asin-atan-atan2-invalid.html?label=master&label=experimental&aligned&q=sin */ +.wrong { + order: asin()); + order: asin( )); + order: asin(,)); + order: asin(1dag)); + order: asin(1deg, )); + order: asin(, 1deg)); + order: asin(1deg + )); + order: asin(1deg - )); + order: asin(1deg * )); + order: asin(1deg / )); + order: asin(1deg 2deg)); + order: asin(1deg, , 2deg)); + order: acos()); + order: acos( )); + order: acos(,)); + order: acos(1dag)); + order: acos(1deg, )); + order: acos(, 1deg)); + order: acos(1deg + )); + order: acos(1deg - )); + order: acos(1deg * )); + order: acos(1deg / )); + order: acos(1deg 2deg)); + order: acos(1deg, , 2deg)); + order: atan()); + order: atan( )); + order: atan(,)); + order: atan(1dag)); + order: atan(1deg, )); + order: atan(, 1deg)); + order: atan(1deg + )); + order: atan(1deg - )); + order: atan(1deg * )); + order: atan(1deg / )); + order: atan(1deg 2deg)); + order: atan(1deg, , 2deg)); + order: asin(90px)); + order: asin(30deg + 1.0471967rad, 0)); + order: acos( 0 ,)); + order: acos( () 30deg - 0.523599rad )); + order: atan(45deg )); + order: 38.15deg); + order: atan2()); + order: atan2( )); + order: atan2(,)); + order: atan2(1dag)); + order: atan2(1deg, )); + order: atan2(, 1deg)); + order: atan2(1deg + )); + order: atan2(1deg - )); + order: atan2(1deg * )); + order: atan2(1deg / )); + order: atan2(1deg 2deg)); + order: 26.57deg); + order: atan2(90px)); + order: atan2(30deg + 1.0471967rad, 0)); + order: atan2( 0 ,)); + order: atan2( () 30deg - 0.523599rad )); + order: atan2(45deg )); + order: atan2(30deg, + 0.261799rad)); +} From ab27e059c90c1246638556bbf5be379ea0e1abec Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Fri, 20 May 2022 02:43:18 +0200 Subject: [PATCH 12/17] Support for even more complex calculations --- .../src/utils.ts | 105 +++++++++++++++--- 1 file changed, 92 insertions(+), 13 deletions(-) diff --git a/plugins/postcss-trigonometric-functions/src/utils.ts b/plugins/postcss-trigonometric-functions/src/utils.ts index 7d32f00e5..9eed0fcea 100644 --- a/plugins/postcss-trigonometric-functions/src/utils.ts +++ b/plugins/postcss-trigonometric-functions/src/utils.ts @@ -54,6 +54,10 @@ enum ExpressionPart { Operation } +function isFiniteNumber(number: number) { + return !Number.isNaN(number) && Number.isFinite(number); +} + /** * Try to compute a calculation from a Node. * @@ -76,18 +80,63 @@ enum ExpressionPart { export function computeCalculation(nodes: Node[], ignoreUnit = false) { let isValid = true; const expression = []; + + // Compute functions first, this will go as deep as it needs to resolve parenthesis first + // recursion happening here if any parsed value isn't valid everything bails out + nodes.filter(node => node.type === 'function').forEach((functionNode: FunctionNode) => { + if (!isValid) { + return; + } + + if (functionNode.value !== '') { + isValid = false; + return; + } + + const clonedNodes = functionNode.nodes.slice(0); + const result = computeCalculation(clonedNodes, ignoreUnit); + const hasOneItem = result.length === 1; + const itemValue = Number(result[0]?.value || ''); + const isValidResult = hasOneItem && result[0].type === 'word' && !Number.isNaN(itemValue); + + if (!isValidResult) { + isValid = false; + return; + } + + functionNodeToWordNode(functionNode); + functionNode.value = result[0].value; + }); + + if (!isValid) { + return nodes; + } + const filteredNodes = nodes.filter( node => node.type === 'word' || ALLOWED_OPERATIONS.includes(node.value), ); let operationPart = ExpressionPart.Number; + const expressionUnits = []; let detectedUnit; - const addToExpression = (part: string, type: ExpressionPart) => { + const addToExpression = (part: string, type: ExpressionPart, unit?: string) => { if (operationPart !== type) { isValid = false; return; } + if (type === ExpressionPart.Number) { + const normalizedUnit = unit || ''; + + if (!expressionUnits.includes(normalizedUnit)) { + expressionUnits.push({ + number: part, + unit: normalizedUnit, + index: expression.length, + }); + } + } + expression.push(part); operationPart = type === ExpressionPart.Number ? ExpressionPart.Operation @@ -142,20 +191,13 @@ export function computeCalculation(nodes: Node[], ignoreUnit = false) { } if (parsed.unit === 'rad') { - addToExpression(parsed.number, ExpressionPart.Number); + addToExpression(parsed.number, ExpressionPart.Number, parsed.unit); continue; } if (typeof toRad[parsed.unit] === 'function') { - const number = toRad[parsed.unit](Number(parsed.number)); - - if (!Number.isNaN(number) && Number.isFinite(number)) { - addToExpression(number.toString(), ExpressionPart.Number); - continue; - } else { - isValid = false; - break; - } + addToExpression(parsed.number, ExpressionPart.Number, parsed.unit); + continue; } isValid = false; @@ -178,12 +220,49 @@ export function computeCalculation(nodes: Node[], ignoreUnit = false) { let result; try { + let expressionConversion = ''; + const differentUnits = new Set(expressionUnits.map(part => part.unit)); + + if (differentUnits.size > 1) { + // If there's no empty unit this means is computing operations + // such as 15deg + 0.25turn + // among different units At this stage we're certain they're + // degrees, but we need to convert them first to radians + if (!differentUnits.has('')) { + expressionUnits.forEach(part => { + if (part.unit !== 'rad') { + const converted = toRad[part.unit](Number(part.number)); + + if (isFiniteNumber(converted)) { + expression[part.index] = converted.toString(); + } else { + // Bail out + throw new Error(); + } + } + }); + } else if (differentUnits.size === 2) { + // Now we can only calculate if we don't have more than 2 units + // having empty unit and more than 1 is not calculable + [expressionConversion] = Array.from(differentUnits).filter(v => v !== ''); + } else { + // Bail out + throw new Error(); + } + } + const context = vm.createContext({ result: NaN }); const calculation = new vm.Script(`result = ${expression.join(' ')}`); calculation.runInContext(context); - if (typeof context.result === 'number' && !Number.isNaN(context.result) && Number.isFinite(context.result)) { - result = context.result; + if (typeof context.result === 'number' && isFiniteNumber(context.result)) { + if (expressionConversion) { + context.result = toRad[expressionConversion](context.result); + } + + if (isFiniteNumber(context.result)) { + result = context.result; + } } } catch(error) { // Error silently From a61409dbec6c1d5969a483f19e680244c91fab55 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Fri, 20 May 2022 02:52:40 +0200 Subject: [PATCH 13/17] More tests --- .../test/basic.css | 15 +++++++++++++++ .../test/basic.expect.css | 15 +++++++++++++++ .../postcss-trigonometric-functions/test/test.css | 7 +++++-- .../test/test.expect.css | 0 4 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 plugins/postcss-trigonometric-functions/test/test.expect.css diff --git a/plugins/postcss-trigonometric-functions/test/basic.css b/plugins/postcss-trigonometric-functions/test/basic.css index 085fa3457..63ddad0de 100644 --- a/plugins/postcss-trigonometric-functions/test/basic.css +++ b/plugins/postcss-trigonometric-functions/test/basic.css @@ -1,7 +1,10 @@ .sin { order: sin(45deg); /* 45deg */ + order: sin(15 * 3deg); /* 45deg */ order: sin(.125turn); /* 45deg */ + order: sin(1 / 8turn); /* 45deg */ order: sin(50grad); /* 45deg */ + order: sin(10 + 4 * 10grad); /* 45deg */ order: sin(0.785398rad); /* 45deg */ order: sin(0.785398); /* 45deg */ order: sin(-45deg); /* -45deg */ @@ -54,6 +57,8 @@ order: tan(0); order: tan(tan(0.785398)); order: tan(tan(tan(tan(0.785398)))); + order: tan(61deg); /* 61deg */ + order: tan(1 + 2 * 3 * 10deg); /* 61deg should be similar to above */ /* Asymptotes */ order: tan(90deg); order: tan(100grad); @@ -63,6 +68,10 @@ order: tan(-100grad); order: tan(-450deg); order: tan(-300grad); + /* 1 + 2 -> 3 */ + /* 3 * 3 -> 9 */ + /* 9 * 10deg -> 90deg */ + order: tan(((1 + 2) * 3) * 10deg); /* Is asymptotic */ /* Non Asymptotes */ order: tan(180deg); order: tan(200grad); @@ -173,3 +182,9 @@ order: atan2(90, 15); order: atan2(15, 90); } + +.complex-calculations { + order: sin(1deg + 3 + .25turn); /* Mixed units, should not calculate */ + order: sin((1deg + 3) + .25turn); /* Mixed units but separated, should calculate */ + order: sin(var(--foo) * 3deg); +} diff --git a/plugins/postcss-trigonometric-functions/test/basic.expect.css b/plugins/postcss-trigonometric-functions/test/basic.expect.css index 320b61e34..bb71613d2 100644 --- a/plugins/postcss-trigonometric-functions/test/basic.expect.css +++ b/plugins/postcss-trigonometric-functions/test/basic.expect.css @@ -4,6 +4,9 @@ order: 0.70711; /* 45deg */ order: 0.70711; /* 45deg */ order: 0.70711; /* 45deg */ + order: 0.70711; /* 45deg */ + order: 0.70711; /* 45deg */ + order: 0.70711; /* 45deg */ order: -0.70711; /* -45deg */ order: -0.70711; /* -45deg */ order: -0.70711; /* -45deg */ @@ -54,6 +57,8 @@ order: 0; order: 1.55741; order: -0.84159; + order: 1.80405; /* 61deg */ + order: 1.80405; /* 61deg should be similar to above */ /* Asymptotes */ order: infinity; order: infinity; @@ -63,6 +68,10 @@ order: -infinity; order: -infinity; order: -infinity; + /* 1 + 2 -> 3 */ + /* 3 * 3 -> 9 */ + /* 9 * 10deg -> 90deg */ + order: infinity; /* Is asymptotic */ /* Non Asymptotes */ order: 0; order: 0; @@ -173,3 +182,9 @@ order: 80.54deg; order: 9.46deg; } + +.complex-calculations { + order: sin(1deg + 3 + .25turn); /* Mixed units, should not calculate */ + order: 0.90533; /* Mixed units but separated, should calculate */ + order: sin(var(--foo) * 3deg); +} diff --git a/plugins/postcss-trigonometric-functions/test/test.css b/plugins/postcss-trigonometric-functions/test/test.css index 0060207d4..3dcc90ece 100644 --- a/plugins/postcss-trigonometric-functions/test/test.css +++ b/plugins/postcss-trigonometric-functions/test/test.css @@ -1,3 +1,6 @@ -.test { - order: tan(((1 + 2) * 3) * 1deg); +.calculations { + /* 1 + 2 -> 3 */ + /* 3 * 3 -> 9 */ + /* 9 * 10deg -> 90deg */ + order: tan(((1 + 2) * 3) * 10deg); /* Is asymptotic */ } diff --git a/plugins/postcss-trigonometric-functions/test/test.expect.css b/plugins/postcss-trigonometric-functions/test/test.expect.css new file mode 100644 index 000000000..e69de29bb From 8e0042b51adf28ce3d116da365668dd034919afb Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Fri, 20 May 2022 02:57:52 +0200 Subject: [PATCH 14/17] Linting --- .../package.json | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/plugins/postcss-trigonometric-functions/package.json b/plugins/postcss-trigonometric-functions/package.json index 566cef784..146fd3199 100644 --- a/plugins/postcss-trigonometric-functions/package.json +++ b/plugins/postcss-trigonometric-functions/package.json @@ -51,27 +51,28 @@ "lint:eslint": "eslint ./src --ext .js --ext .ts --ext .mjs --no-error-on-unmatched-pattern", "lint:package-json": "node ../../.github/bin/format-package-json.mjs", "prepublishOnly": "npm run clean && npm run build && npm run test", - "stryker": "stryker run --logLevel error", "test": "node .tape.mjs && npm run test:exports", "test:exports": "node ./test/_import.mjs && node ./test/_require.cjs", "test:rewrite-expects": "REWRITE_EXPECTS=true node .tape.mjs" }, + "homepage": "https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-trigonometric-functions#readme", "repository": { "type": "git", "url": "https://github.com/csstools/postcss-plugins.git", "directory": "plugins/postcss-trigonometric-functions" }, + "bugs": "https://github.com/csstools/postcss-plugins/issues", "keywords": [ - "postcss-plugin", - "trigonometric", + "acos", + "acos2", + "asin", + "atan", + "cos", "css", + "postcss-plugin", "sin", - "cos", "tan", - "asin", - "atan", - "acos", - "acos2" + "trigonometric" ], "csstools": { "cssdbId": "trigonometric-functions", @@ -81,7 +82,5 @@ }, "volta": { "extends": "../../package.json" - }, - "homepage": "https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-trigonometric-functions#readme", - "bugs": "https://github.com/csstools/postcss-plugins/issues" + } } From b9c207e8cdeb871ebbbb73eeb8ac241ba6ab2967 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Fri, 20 May 2022 08:55:16 +0200 Subject: [PATCH 15/17] Adding documentation --- .../docs/README.md | 2 +- .../postcss-trigonometric-functions/.tape.mjs | 15 + .../INSTALL.md | 20 +- .../postcss-trigonometric-functions/README.md | 128 ++++-- .../docs/README.md | 24 +- .../package.json | 6 +- .../test/basic.preserve-true.expect.css | 363 ++++++++++++++++++ .../test/examples/example.css | 20 +- .../test/examples/example.expect.css | 20 +- .../examples/example.preserve-true.expect.css | 36 +- .../test/test.css | 6 - .../test/test.expect.css | 0 12 files changed, 567 insertions(+), 73 deletions(-) create mode 100644 plugins/postcss-trigonometric-functions/test/basic.preserve-true.expect.css delete mode 100644 plugins/postcss-trigonometric-functions/test/test.css delete mode 100644 plugins/postcss-trigonometric-functions/test/test.expect.css diff --git a/plugins/postcss-stepped-value-functions/docs/README.md b/plugins/postcss-stepped-value-functions/docs/README.md index 4ddef9ee4..a486bfa7a 100644 --- a/plugins/postcss-stepped-value-functions/docs/README.md +++ b/plugins/postcss-stepped-value-functions/docs/README.md @@ -30,7 +30,7 @@ ## ⚠️ About custom properties -Given the dynamic nature of custom properties it's impossible to know what the variable value is which means the plugin can't compute a final value for the stylesheet. +Given the dynamic nature of custom properties it's impossible to know what the variable value is, which means the plugin can't compute a final value for the stylesheet. Because of that, any usage that contains a `var` is skipped. diff --git a/plugins/postcss-trigonometric-functions/.tape.mjs b/plugins/postcss-trigonometric-functions/.tape.mjs index b675809b4..6783d575c 100644 --- a/plugins/postcss-trigonometric-functions/.tape.mjs +++ b/plugins/postcss-trigonometric-functions/.tape.mjs @@ -5,6 +5,21 @@ postcssTape(plugin)({ basic: { message: "supports basic usage", }, + 'basic:preserve-true': { + message: 'supports { preserve: true } usage', + options: { + preserve: true + }, + }, + 'examples/example': { + message: 'minimal example', + }, + 'examples/example:preserve-true': { + message: 'minimal example', + options: { + preserve: true + } + }, wpt: { message: "supports wpt cases", }, diff --git a/plugins/postcss-trigonometric-functions/INSTALL.md b/plugins/postcss-trigonometric-functions/INSTALL.md index dab91f1a1..d07645d54 100644 --- a/plugins/postcss-trigonometric-functions/INSTALL.md +++ b/plugins/postcss-trigonometric-functions/INSTALL.md @@ -17,10 +17,10 @@ Use it as a [PostCSS] plugin: ```js const postcss = require('postcss'); -const postcssBasePlugin = require('@csstools/postcss-trigonometric-functions'); +const postcssTrigonometricFunctions = require('@csstools/postcss-trigonometric-functions'); postcss([ - postcssBasePlugin(/* pluginOptions */) + postcssTrigonometricFunctions(/* pluginOptions */) ]).process(YOUR_CSS /*, processOptions */); ``` @@ -35,11 +35,11 @@ npm install postcss-cli @csstools/postcss-trigonometric-functions --save-dev Use [PostCSS Trigonometric Functions] in your `postcss.config.js` configuration file: ```js -const postcssBasePlugin = require('@csstools/postcss-trigonometric-functions'); +const postcssTrigonometricFunctions = require('@csstools/postcss-trigonometric-functions'); module.exports = { plugins: [ - postcssBasePlugin(/* pluginOptions */) + postcssTrigonometricFunctions(/* pluginOptions */) ] } ``` @@ -103,11 +103,11 @@ Use [React App Rewire PostCSS] and [PostCSS Trigonometric Functions] in your ```js const reactAppRewirePostcss = require('react-app-rewire-postcss'); -const postcssBasePlugin = require('@csstools/postcss-trigonometric-functions'); +const postcssTrigonometricFunctions = require('@csstools/postcss-trigonometric-functions'); module.exports = config => reactAppRewirePostcss(config, { plugins: () => [ - postcssBasePlugin(/* pluginOptions */) + postcssTrigonometricFunctions(/* pluginOptions */) ] }); ``` @@ -124,11 +124,11 @@ Use [PostCSS Trigonometric Functions] in your Gulpfile: ```js const postcss = require('gulp-postcss'); -const postcssBasePlugin = require('@csstools/postcss-trigonometric-functions'); +const postcssTrigonometricFunctions = require('@csstools/postcss-trigonometric-functions'); gulp.task('css', function () { var plugins = [ - postcssBasePlugin(/* pluginOptions */) + postcssTrigonometricFunctions(/* pluginOptions */) ]; return gulp.src('./src/*.css') @@ -148,7 +148,7 @@ npm install grunt-postcss @csstools/postcss-trigonometric-functions --save-dev Use [PostCSS Trigonometric Functions] in your Gruntfile: ```js -const postcssBasePlugin = require('@csstools/postcss-trigonometric-functions'); +const postcssTrigonometricFunctions = require('@csstools/postcss-trigonometric-functions'); grunt.loadNpmTasks('grunt-postcss'); @@ -156,7 +156,7 @@ grunt.initConfig({ postcss: { options: { processors: [ - postcssBasePlugin(/* pluginOptions */) + postcssTrigonometricFunctions(/* pluginOptions */) ] }, dist: { diff --git a/plugins/postcss-trigonometric-functions/README.md b/plugins/postcss-trigonometric-functions/README.md index 4a81de958..5b5973d2b 100644 --- a/plugins/postcss-trigonometric-functions/README.md +++ b/plugins/postcss-trigonometric-functions/README.md @@ -1,29 +1,45 @@ # PostCSS Trigonometric Functions [PostCSS Logo][postcss] [npm version][npm-url] -[CSS Standard Status][css-url] +[CSS Standard Status][css-url] [Build Status][cli-url] [Discord][discord] -[PostCSS Trigonometric Functions] lets easily create new plugins following some [CSS Specification]. +[PostCSS Trigonometric Functions] lets you use `sin`, `cos`, `tan`, `asin`, `acos`, `atan` and `atan2` to be able to compute trigonometric relationships following the [CSS Values 4] specification. ```pcss -.foo { - color: red; -} - -.baz { - color: green; +.trigonometry { + line-height: sin(pi / 4); + line-height: cos(.125turn); + line-height: tan(50grad); + transform: rotate(asin(-1)); + transform: rotate(asin(sin(30deg + 1.0471967rad))); + transform: rotate(acos(-1)); + transform: rotate(acos(cos(0 / 2 + 1 - 1))); + transform: rotate(atan(infinity)); + transform: rotate(atan(e - 2.7182818284590452354)); + transform: rotate(atan2(-infinity,-infinity)); + transform: rotate(atan2(-infinity,infinity)); + transform: rotate(atan2(-infinity,infinity)); + transform: rotate(atan2(90, 15)); } /* becomes */ -.foo { - color: blue; -} - -.baz { - color: green; +.trigonometry { + line-height: 0.70711; + line-height: 0.70711; + line-height: 1; + transform: rotate(-90deg); + transform: rotate(90deg); + transform: rotate(180deg); + transform: rotate(0deg); + transform: rotate(90deg); + transform: rotate(0deg); + transform: rotate(-135deg); + transform: rotate(-45deg); + transform: rotate(-45deg); + transform: rotate(80.54deg); } ``` @@ -39,10 +55,10 @@ Use it as a [PostCSS] plugin: ```js const postcss = require('postcss'); -const postcssBasePlugin = require('@csstools/postcss-trigonometric-functions'); +const postcssTrigonometricFunctions = require('@csstools/postcss-trigonometric-functions'); postcss([ - postcssBasePlugin(/* pluginOptions */) + postcssTrigonometricFunctions(/* pluginOptions */) ]).process(YOUR_CSS /*, processOptions */); ``` @@ -52,6 +68,24 @@ instructions for: | [Node](INSTALL.md#node) | [PostCSS CLI](INSTALL.md#postcss-cli) | [Webpack](INSTALL.md#webpack) | [Create React App](INSTALL.md#create-react-app) | [Gulp](INSTALL.md#gulp) | [Grunt](INSTALL.md#grunt) | | --- | --- | --- | --- | --- | --- | +## ⚠️ About custom properties + +Given the dynamic nature of custom properties it's impossible to know what the variable value is, which means the plugin can't compute a final value for the stylesheet. + +Because of that, any usage that contains a `var` is skipped. + +## Units + +[PostCSS Trigonometric Functions] lets you use different special units that are within the spec and computed at run time to be able to calculate the result of the trigonometric function. + +The following units are supported: + +* `pi`: Computes to `Math.PI` which is `3.141592653589793` +* `e`: Computes to `Math.E` which is `2.718281828459045` +* `infinity`, `-infinity`: Compute to `Infinity` and `-Infinity` respectively. Note that the usage is case insensitive so `InFiNiTy` is a valid value. + +Some calculations (such as `sin(-infinity)`) might return `NaN` as per the spec. Given that `NaN` can't be replaced with a value that's useful to CSS it is left as is, as the result will be effectively ignored by the browser. + ## Options ### preserve @@ -60,32 +94,62 @@ The `preserve` option determines whether the original notation is preserved. By default, it is not preserved. ```js -postcssBasePlugin({ preserve: true }) +postcssTrigonometricFunctions({ preserve: true }) ``` ```pcss -.foo { - color: red; -} - -.baz { - color: green; +.trigonometry { + line-height: sin(pi / 4); + line-height: cos(.125turn); + line-height: tan(50grad); + transform: rotate(asin(-1)); + transform: rotate(asin(sin(30deg + 1.0471967rad))); + transform: rotate(acos(-1)); + transform: rotate(acos(cos(0 / 2 + 1 - 1))); + transform: rotate(atan(infinity)); + transform: rotate(atan(e - 2.7182818284590452354)); + transform: rotate(atan2(-infinity,-infinity)); + transform: rotate(atan2(-infinity,infinity)); + transform: rotate(atan2(-infinity,infinity)); + transform: rotate(atan2(90, 15)); } /* becomes */ -.foo { - color: blue; - color: red; -} - -.baz { - color: green; +.trigonometry { + line-height: 0.70711; + line-height: sin(pi / 4); + line-height: 0.70711; + line-height: cos(.125turn); + line-height: 1; + line-height: tan(50grad); + transform: rotate(-90deg); + transform: rotate(asin(-1)); + transform: rotate(90deg); + transform: rotate(asin(1)); + transform: rotate(asin(sin(30deg + 1.0471967rad))); + transform: rotate(180deg); + transform: rotate(acos(-1)); + transform: rotate(0deg); + transform: rotate(acos(1)); + transform: rotate(acos(cos(0 / 2 + 1 - 1))); + transform: rotate(90deg); + transform: rotate(atan(infinity)); + transform: rotate(0deg); + transform: rotate(atan(e - 2.7182818284590452354)); + transform: rotate(-135deg); + transform: rotate(atan2(-infinity,-infinity)); + transform: rotate(-45deg); + transform: rotate(atan2(-infinity,infinity)); + transform: rotate(-45deg); + transform: rotate(atan2(-infinity,infinity)); + transform: rotate(80.54deg); + transform: rotate(atan2(90, 15)); } ``` [cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test -[css-url]: https://cssdb.org/#TODO +[css-url]: https://cssdb.org/#trigonometric-functions [discord]: https://discord.gg/bUadyRwkJS [npm-url]: https://www.npmjs.com/package/@csstools/postcss-trigonometric-functions @@ -94,4 +158,4 @@ postcssBasePlugin({ preserve: true }) [PostCSS]: https://github.com/postcss/postcss [PostCSS Loader]: https://github.com/postcss/postcss-loader [PostCSS Trigonometric Functions]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-trigonometric-functions -[CSS Specification]: #TODO +[CSS Values 4]: https://www.w3.org/TR/css-values-4/#trig-funcs diff --git a/plugins/postcss-trigonometric-functions/docs/README.md b/plugins/postcss-trigonometric-functions/docs/README.md index 3bc455892..05a3bee28 100644 --- a/plugins/postcss-trigonometric-functions/docs/README.md +++ b/plugins/postcss-trigonometric-functions/docs/README.md @@ -14,7 +14,7 @@
-[] lets easily create new plugins following some [CSS Specification]. +[] lets you use `sin`, `cos`, `tan`, `asin`, `acos`, `atan` and `atan2` to be able to compute trigonometric relationships following the [CSS Values 4] specification. ```pcss @@ -28,6 +28,26 @@ +## ⚠️ About custom properties + +Given the dynamic nature of custom properties it's impossible to know what the variable value is, which means the plugin can't compute a final value for the stylesheet. + +Because of that, any usage that contains a `var` is skipped. + +## Units + +[] lets you use different special units that are within the spec and computed at run time to be able to calculate the result of the trigonometric function. + +The following units are supported: + +* `pi`: Computes to `Math.PI` which is `3.141592653589793` +* `e`: Computes to `Math.E` which is `2.718281828459045` +* `infinity`, `-infinity`: Compute to `Infinity` and `-Infinity` respectively. Note that the usage is case insensitive so `InFiNiTy` is a valid value. + +Some calculations (such as `sin(-infinity)`) might return `NaN` as per the spec. Given that `NaN` can't be replaced with a value that's useful to CSS it is left as is, as the result will be effectively ignored by the browser. + + + ## Options ### preserve @@ -48,4 +68,4 @@ is preserved. By default, it is not preserved. ``` -[CSS Specification]: +[CSS Values 4]: diff --git a/plugins/postcss-trigonometric-functions/package.json b/plugins/postcss-trigonometric-functions/package.json index 146fd3199..f28fb7395 100644 --- a/plugins/postcss-trigonometric-functions/package.json +++ b/plugins/postcss-trigonometric-functions/package.json @@ -1,6 +1,6 @@ { "name": "@csstools/postcss-trigonometric-functions", - "description": "TODO: Add description for Trigonometric Functions", + "description": "Use sin(), cos(), tan(), acos(), atan(), and atan2() to compute trigonometric relationships", "version": "1.0.0", "contributors": [ { @@ -19,7 +19,7 @@ "url": "https://opencollective.com/csstools" }, "engines": { - "node": "^12 || ^14 || >=16" + "node": "^14 || >=16" }, "main": "dist/index.cjs", "module": "dist/index.mjs", @@ -41,7 +41,7 @@ "postcss-value-parser": "^4.2.0" }, "peerDependencies": { - "postcss": "^8.3" + "postcss": "^8.4" }, "scripts": { "build": "rollup -c ../../rollup/default.js", diff --git a/plugins/postcss-trigonometric-functions/test/basic.preserve-true.expect.css b/plugins/postcss-trigonometric-functions/test/basic.preserve-true.expect.css new file mode 100644 index 000000000..54b142288 --- /dev/null +++ b/plugins/postcss-trigonometric-functions/test/basic.preserve-true.expect.css @@ -0,0 +1,363 @@ +.sin { + order: 0.70711; + order: sin(45deg); /* 45deg */ + order: 0.70711; + order: sin(15 * 3deg); /* 45deg */ + order: 0.70711; + order: sin(.125turn); /* 45deg */ + order: 0.70711; + order: sin(1 / 8turn); /* 45deg */ + order: 0.70711; + order: sin(50grad); /* 45deg */ + order: 0.70711; + order: sin(10 + 4 * 10grad); /* 45deg */ + order: 0.70711; + order: sin(0.785398rad); /* 45deg */ + order: 0.70711; + order: sin(0.785398); /* 45deg */ + order: -0.70711; + order: sin(-45deg); /* -45deg */ + order: -0.70711; + order: sin(-.125turn); /* -45deg */ + order: -0.70711; + order: sin(-50grad); /* -45deg */ + order: -0.70711; + order: sin(-0.785398rad); /* -45deg */ + order: -0.70711; + order: sin(-0.785398); /* -45deg */ + order: 0; + order: sin(0); + order: 0.64964; + order: sin(0.70711); + order: sin(sin(0.785398)); + order: 0.56868; + order: sin(0.6049); + order: sin(sin(0.64964)); + order: sin(sin(sin(0.70711))); + order: sin(sin(sin(sin(0.785398)))); + order: sin(1px); + order: sin(foo); + order: NaN; + order: sin(Infinity); + order: NaN; + order: sin(InFiNiTy); + order: NaN; + order: sin(-infinity); + order: sin(var(--foo)); +} + +.cos { + order: 0.70711; + order: cos(45deg); /* 45deg */ + order: 0.70711; + order: cos(.125turn); /* 45deg */ + order: 0.70711; + order: cos(50grad); /* 45deg */ + order: 0.70711; + order: cos(0.785398rad); /* 45deg */ + order: 0.70711; + order: cos(0.785398); /* 45deg */ + order: 0.70711; + order: cos(-45deg); /* -45deg */ + order: 0.70711; + order: cos(-.125turn); /* -45deg */ + order: 0.70711; + order: cos(-50grad); /* -45deg */ + order: 0.70711; + order: cos(-0.785398rad); /* -45deg */ + order: 0.70711; + order: cos(-0.785398); /* -45deg */ + order: 0.76024; + order: cos(0.70711); + order: cos(cos(0.785398)); + order: 0.74872; + order: cos(0.72467); + order: cos(cos(0.76024)); + order: cos(cos(cos(0.70711))); + order: cos(cos(cos(cos(0.785398)))); + order: cos(1px); + order: cos(foo); + order: NaN; + order: cos(Infinity); + order: NaN; + order: cos(InFiNiTy); + order: NaN; + order: cos(-infinity); +} + +.tan { + order: 1; + order: tan(45deg); /* 45deg */ + order: 1; + order: tan(.125turn); /* 45deg */ + order: 1; + order: tan(50grad); /* 45deg */ + order: 1; + order: tan(0.785398rad); /* 45deg */ + order: 1; + order: tan(0.785398); /* 45deg */ + order: -1; + order: tan(-45deg); /* -45deg */ + order: -1; + order: tan(-.125turn); /* -45deg */ + order: -1; + order: tan(-50grad); /* -45deg */ + order: -1; + order: tan(-0.785398rad); /* -45deg */ + order: -1; + order: tan(-0.785398); /* -45deg */ + order: 0; + order: tan(0); + order: 1.55741; + order: tan(1); + order: tan(tan(0.785398)); + order: -0.84159; + order: tan(74.69863); + order: tan(tan(1.55741)); + order: tan(tan(tan(1))); + order: tan(tan(tan(tan(0.785398)))); + order: 1.80405; + order: tan(61deg); /* 61deg */ + order: 1.80405; + order: tan(1 + 2 * 3 * 10deg); /* 61deg should be similar to above */ + /* Asymptotes */ + order: infinity; + order: tan(90deg); + order: infinity; + order: tan(100grad); + order: infinity; + order: tan(270deg); + order: infinity; + order: tan(300grad); + order: -infinity; + order: tan(-270deg); + order: -infinity; + order: tan(-100grad); + order: -infinity; + order: tan(-450deg); + order: -infinity; + order: tan(-300grad); + /* 1 + 2 -> 3 */ + /* 3 * 3 -> 9 */ + /* 9 * 10deg -> 90deg */ + order: infinity; + order: tan(((1 + 2) * 3) * 10deg); /* Is asymptotic */ + /* Non Asymptotes */ + order: 0; + order: tan(180deg); + order: 0; + order: tan(200grad); + order: 0; + order: tan(360deg); + order: 0; + order: tan(400grad); + order: tan(1px); + order: tan(foo); + order: NaN; + order: tan(Infinity); + order: NaN; + order: tan(InFiNiTy); + order: NaN; + order: tan(-infinity); +} + +.asin { + order: 45deg; + order: asin(0.70711); + order: asin(sin(45deg)); + order: 90deg; + order: asin(1); + order: asin(sin(90deg)); + order: -90deg; + order: asin(-1); + order: -30deg; + order: asin(-0.5); + order: 0deg; + order: asin(0); + order: 30deg; + order: asin(0.5); + order: 90deg; + order: asin(1); + order: NaN; + order: asin(2); + order: NaN; + order: asin(-20); + order: asin(45deg); + order: asin(1rad); + order: asin(foo); + order: NaN; + order: asin(infinity); + order: NaN; + order: asin(-infinity); +} + +.acos { + order: 45deg; + order: acos(0.70711); + order: acos(cos(45deg)); + order: 90deg; + order: acos(0); + order: acos(cos(90deg)); + order: 180deg; + order: acos(-1); + order: 120deg; + order: acos(-0.5); + order: 90deg; + order: acos(0); + order: 60deg; + order: acos(0.5); + order: 0deg; + order: acos(1); + order: NaN; + order: acos(2); + order: NaN; + order: acos(-20); + order: acos(45deg); + order: acos(1rad); + order: acos(foo); + order: NaN; + order: acos(infinity); + order: NaN; + order: acos(-infinity); +} + +.atan { + order: 90deg; + order: atan(infinity); + order: -90deg; + order: atan(-infinity); + order: 45deg; + order: atan(1); + order: 0deg; + order: atan(0); + order: 0deg; + order: atan(-0); + order: 45deg; + order: atan(1); + order: atan(tan(45deg)); /* 45deg */ + order: 45deg; + order: atan(1); + order: atan(tan(.125turn)); /* 45deg */ + order: 45deg; + order: atan(1); + order: atan(tan(50grad)); /* 45deg */ + order: 45deg; + order: atan(1); + order: atan(tan(0.785398rad)); /* 45deg */ + order: 45deg; + order: atan(1); + order: atan(tan(0.785398)); /* 45deg */ + order: -45deg; + order: atan(-1); + order: atan(tan(-45deg)); /* -45deg */ + order: -45deg; + order: atan(-1); + order: atan(tan(-.125turn)); /* -45deg */ + order: -45deg; + order: atan(-1); + order: atan(tan(-50grad)); /* -45deg */ + order: -45deg; + order: atan(-1); + order: atan(tan(-0.785398rad)); /* -45deg */ + order: -45deg; + order: atan(-1); + order: atan(tan(-0.785398)); /* -45deg */ +} + +.atan2 { + order: -135deg; + order: atan2(-infinity, -infinity); + order: -90deg; + order: atan2(-infinity, -1); + order: -90deg; + order: atan2(-infinity, -0); + order: -90deg; + order: atan2(-infinity, 0); + order: -90deg; + order: atan2(-infinity, 1); + order: -45deg; + order: atan2(-infinity, infinity); + + order: -180deg; + + order: atan2(-1, -infinity); + order: -135deg; + order: atan2(-1, -1); + order: -90deg; + order: atan2(-1, -0); + order: -90deg; + order: atan2(-1, 0); + order: -45deg; + order: atan2(-1, 1); + order: 0deg; + order: atan2(-1, infinity); + + order: -180deg; + + order: atan2(-0, -infinity); + order: -180deg; + order: atan2(-0, -1); + order: -180deg; + order: atan2(-0, -0); + order: 0deg; + order: atan2(-0, 0); + order: 0deg; + order: atan2(-0, 1); + order: 0deg; + order: atan2(-0, infinity); + + order: 180deg; + + order: atan2(0, -infinity); + order: 180deg; + order: atan2(0, -1); + order: 180deg; + order: atan2(0, -0); + order: 0deg; + order: atan2(0, 0); + order: 0deg; + order: atan2(0, 1); + order: 0deg; + order: atan2(0, infinity); + + order: 180deg; + + order: atan2(1, -infinity); + order: 135deg; + order: atan2(1, -1); + order: 90deg; + order: atan2(1, -0); + order: 90deg; + order: atan2(1, 0); + order: 45deg; + order: atan2(1, 1); + order: 0deg; + order: atan2(1, infinity); + + order: 135deg; + + order: atan2(infinity, -infinity); + order: 90deg; + order: atan2(infinity, -1); + order: 90deg; + order: atan2(infinity, -0); + order: 90deg; + order: atan2(infinity, 0); + order: 90deg; + order: atan2(infinity, 1); + order: 45deg; + order: atan2(infinity, infinity); + + order: 80.54deg; + + order: atan2(90, 15); + order: 9.46deg; + order: atan2(15, 90); +} + +.complex-calculations { + order: sin(1deg + 3 + .25turn); /* Mixed units, should not calculate */ + order: 0.90533; + order: sin((1deg + 3) + .25turn); /* Mixed units but separated, should calculate */ + order: sin(var(--foo) * 3deg); +} diff --git a/plugins/postcss-trigonometric-functions/test/examples/example.css b/plugins/postcss-trigonometric-functions/test/examples/example.css index 181f83a54..962618a56 100644 --- a/plugins/postcss-trigonometric-functions/test/examples/example.css +++ b/plugins/postcss-trigonometric-functions/test/examples/example.css @@ -1,7 +1,15 @@ -.foo { - color: red; -} - -.baz { - color: green; +.trigonometry { + line-height: sin(pi / 4); + line-height: cos(.125turn); + line-height: tan(50grad); + transform: rotate(asin(-1)); + transform: rotate(asin(sin(30deg + 1.0471967rad))); + transform: rotate(acos(-1)); + transform: rotate(acos(cos(0 / 2 + 1 - 1))); + transform: rotate(atan(infinity)); + transform: rotate(atan(e - 2.7182818284590452354)); + transform: rotate(atan2(-infinity,-infinity)); + transform: rotate(atan2(-infinity,infinity)); + transform: rotate(atan2(-infinity,infinity)); + transform: rotate(atan2(90, 15)); } diff --git a/plugins/postcss-trigonometric-functions/test/examples/example.expect.css b/plugins/postcss-trigonometric-functions/test/examples/example.expect.css index 9d738d5ac..bbc2903ab 100644 --- a/plugins/postcss-trigonometric-functions/test/examples/example.expect.css +++ b/plugins/postcss-trigonometric-functions/test/examples/example.expect.css @@ -1,7 +1,15 @@ -.foo { - color: blue; -} - -.baz { - color: green; +.trigonometry { + line-height: 0.70711; + line-height: 0.70711; + line-height: 1; + transform: rotate(-90deg); + transform: rotate(90deg); + transform: rotate(180deg); + transform: rotate(0deg); + transform: rotate(90deg); + transform: rotate(0deg); + transform: rotate(-135deg); + transform: rotate(-45deg); + transform: rotate(-45deg); + transform: rotate(80.54deg); } diff --git a/plugins/postcss-trigonometric-functions/test/examples/example.preserve-true.expect.css b/plugins/postcss-trigonometric-functions/test/examples/example.preserve-true.expect.css index 8b020470a..7010ba6a8 100644 --- a/plugins/postcss-trigonometric-functions/test/examples/example.preserve-true.expect.css +++ b/plugins/postcss-trigonometric-functions/test/examples/example.preserve-true.expect.css @@ -1,8 +1,30 @@ -.foo { - color: blue; - color: red; -} - -.baz { - color: green; +.trigonometry { + line-height: 0.70711; + line-height: sin(pi / 4); + line-height: 0.70711; + line-height: cos(.125turn); + line-height: 1; + line-height: tan(50grad); + transform: rotate(-90deg); + transform: rotate(asin(-1)); + transform: rotate(90deg); + transform: rotate(asin(1)); + transform: rotate(asin(sin(30deg + 1.0471967rad))); + transform: rotate(180deg); + transform: rotate(acos(-1)); + transform: rotate(0deg); + transform: rotate(acos(1)); + transform: rotate(acos(cos(0 / 2 + 1 - 1))); + transform: rotate(90deg); + transform: rotate(atan(infinity)); + transform: rotate(0deg); + transform: rotate(atan(e - 2.7182818284590452354)); + transform: rotate(-135deg); + transform: rotate(atan2(-infinity,-infinity)); + transform: rotate(-45deg); + transform: rotate(atan2(-infinity,infinity)); + transform: rotate(-45deg); + transform: rotate(atan2(-infinity,infinity)); + transform: rotate(80.54deg); + transform: rotate(atan2(90, 15)); } diff --git a/plugins/postcss-trigonometric-functions/test/test.css b/plugins/postcss-trigonometric-functions/test/test.css deleted file mode 100644 index 3dcc90ece..000000000 --- a/plugins/postcss-trigonometric-functions/test/test.css +++ /dev/null @@ -1,6 +0,0 @@ -.calculations { - /* 1 + 2 -> 3 */ - /* 3 * 3 -> 9 */ - /* 9 * 10deg -> 90deg */ - order: tan(((1 + 2) * 3) * 10deg); /* Is asymptotic */ -} diff --git a/plugins/postcss-trigonometric-functions/test/test.expect.css b/plugins/postcss-trigonometric-functions/test/test.expect.css deleted file mode 100644 index e69de29bb..000000000 From 186d542bbeb83a9505c9d7829daf38fa7135bc38 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Fri, 20 May 2022 09:14:00 +0200 Subject: [PATCH 16/17] Missing labeler --- .github/labeler.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/labeler.yml b/.github/labeler.yml index c511a6ae8..b8d4202e3 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -188,5 +188,9 @@ - plugins/postcss-stepped-value-functions/** - experimental/postcss-stepped-value-functions/** +"plugins/postcss-trigonometric-functions": + - plugins/postcss-trigonometric-functions/** + - experimental/postcss-trigonometric-functions/** + "sites/postcss-preset-env": - sites/postcss-preset-env/** From d7a075c3d07bdd145a1db096f4988f916490306c Mon Sep 17 00:00:00 2001 From: romainmenke Date: Fri, 20 May 2022 09:16:59 +0200 Subject: [PATCH 17/17] add tests for the same keywords but as non-functions or without args --- .../test/basic.css | 20 +++++++++++++++++++ .../test/basic.expect.css | 20 +++++++++++++++++++ .../test/basic.preserve-true.expect.css | 20 +++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/plugins/postcss-trigonometric-functions/test/basic.css b/plugins/postcss-trigonometric-functions/test/basic.css index 63ddad0de..18ed707e3 100644 --- a/plugins/postcss-trigonometric-functions/test/basic.css +++ b/plugins/postcss-trigonometric-functions/test/basic.css @@ -188,3 +188,23 @@ order: sin((1deg + 3) + .25turn); /* Mixed units but separated, should calculate */ order: sin(var(--foo) * 3deg); } + +.ignore-not-a-function { + order: sin; + order: cos; + order: tan; + order: asin; + order: acos; + order: atan; + order: atan2; +} + +.ignore-no-arguments { + order: sin(); + order: cos(); + order: tan(); + order: asin(); + order: acos(); + order: atan(); + order: atan2(); +} diff --git a/plugins/postcss-trigonometric-functions/test/basic.expect.css b/plugins/postcss-trigonometric-functions/test/basic.expect.css index bb71613d2..d2b8ddfd1 100644 --- a/plugins/postcss-trigonometric-functions/test/basic.expect.css +++ b/plugins/postcss-trigonometric-functions/test/basic.expect.css @@ -188,3 +188,23 @@ order: 0.90533; /* Mixed units but separated, should calculate */ order: sin(var(--foo) * 3deg); } + +.ignore-not-a-function { + order: sin; + order: cos; + order: tan; + order: asin; + order: acos; + order: atan; + order: atan2; +} + +.ignore-no-arguments { + order: sin(); + order: cos(); + order: tan(); + order: asin(); + order: acos(); + order: atan(); + order: atan2(); +} diff --git a/plugins/postcss-trigonometric-functions/test/basic.preserve-true.expect.css b/plugins/postcss-trigonometric-functions/test/basic.preserve-true.expect.css index 54b142288..9f2554cdd 100644 --- a/plugins/postcss-trigonometric-functions/test/basic.preserve-true.expect.css +++ b/plugins/postcss-trigonometric-functions/test/basic.preserve-true.expect.css @@ -361,3 +361,23 @@ order: sin((1deg + 3) + .25turn); /* Mixed units but separated, should calculate */ order: sin(var(--foo) * 3deg); } + +.ignore-not-a-function { + order: sin; + order: cos; + order: tan; + order: asin; + order: acos; + order: atan; + order: atan2; +} + +.ignore-no-arguments { + order: sin(); + order: cos(); + order: tan(); + order: asin(); + order: acos(); + order: atan(); + order: atan2(); +}