From 2b301609d66969c105d92aa7d56d509e36bba672 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Fri, 9 Sep 2022 09:49:35 +0200 Subject: [PATCH] postcss-nesting : typescript --- plugins/postcss-nesting/CHANGELOG.md | 6 ++- plugins/postcss-nesting/README.md | 36 ++++++++-------- plugins/postcss-nesting/package.json | 2 +- plugins/postcss-nesting/src/index.js | 17 -------- plugins/postcss-nesting/src/index.ts | 26 +++++++++++ ...thin-atrule.js => atrule-within-atrule.ts} | 15 +++---- ...e-within-rule.js => atrule-within-rule.ts} | 11 +++-- .../{cleanup-parent.js => cleanup-parent.ts} | 4 +- .../src/lib/is-type-of-rule.ts | 13 ++++++ .../src/lib/{list.js => list.ts} | 10 ++--- .../lib/{merge-params.js => merge-params.ts} | 2 +- ...of-size-n.js => combinations-of-size-n.ts} | 8 ++-- ...or-order.js => compound-selector-order.ts} | 26 ++++++----- ...{merge-selectors.js => merge-selectors.ts} | 12 +++--- .../{specificity.js => specificity.ts} | 2 +- ...p-multiple-tag-selectors-with-is-pseudo.js | 22 ---------- ...ithin-rule.js => nest-rule-within-rule.ts} | 14 ++++-- plugins/postcss-nesting/src/lib/options.ts | 1 + ...ule-within-rule.js => rule-within-rule.ts} | 15 +++++-- ...parent.js => shift-nodes-before-parent.ts} | 6 +-- .../{valid-atrules.js => valid-atrules.ts} | 0 plugins/postcss-nesting/src/lib/walk-func.ts | 3 ++ plugins/postcss-nesting/src/lib/walk.js | 22 ---------- plugins/postcss-nesting/src/lib/walk.ts | 43 +++++++++++++++++++ plugins/postcss-nesting/stryker.conf.json | 19 -------- plugins/postcss-nesting/tsconfig.json | 9 ++++ 26 files changed, 195 insertions(+), 149 deletions(-) delete mode 100644 plugins/postcss-nesting/src/index.js create mode 100644 plugins/postcss-nesting/src/index.ts rename plugins/postcss-nesting/src/lib/{atrule-within-atrule.js => atrule-within-atrule.ts} (53%) rename plugins/postcss-nesting/src/lib/{atrule-within-rule.js => atrule-within-rule.ts} (59%) rename plugins/postcss-nesting/src/lib/{cleanup-parent.js => cleanup-parent.ts} (65%) create mode 100644 plugins/postcss-nesting/src/lib/is-type-of-rule.ts rename plugins/postcss-nesting/src/lib/{list.js => list.ts} (83%) rename plugins/postcss-nesting/src/lib/{merge-params.js => merge-params.ts} (71%) rename plugins/postcss-nesting/src/lib/merge-selectors/{combinations-of-size-n.js => combinations-of-size-n.ts} (89%) rename plugins/postcss-nesting/src/lib/merge-selectors/{compound-selector-order.js => compound-selector-order.ts} (72%) rename plugins/postcss-nesting/src/lib/merge-selectors/{merge-selectors.js => merge-selectors.ts} (93%) rename plugins/postcss-nesting/src/lib/merge-selectors/{specificity.js => specificity.ts} (92%) delete mode 100644 plugins/postcss-nesting/src/lib/merge-selectors/wrap-multiple-tag-selectors-with-is-pseudo.js rename plugins/postcss-nesting/src/lib/{nest-rule-within-rule.js => nest-rule-within-rule.ts} (64%) create mode 100644 plugins/postcss-nesting/src/lib/options.ts rename plugins/postcss-nesting/src/lib/{rule-within-rule.js => rule-within-rule.ts} (58%) rename plugins/postcss-nesting/src/lib/{shift-nodes-before-parent.js => shift-nodes-before-parent.ts} (79%) rename plugins/postcss-nesting/src/lib/{valid-atrules.js => valid-atrules.ts} (100%) create mode 100644 plugins/postcss-nesting/src/lib/walk-func.ts delete mode 100644 plugins/postcss-nesting/src/lib/walk.js create mode 100644 plugins/postcss-nesting/src/lib/walk.ts delete mode 100644 plugins/postcss-nesting/stryker.conf.json create mode 100644 plugins/postcss-nesting/tsconfig.json diff --git a/plugins/postcss-nesting/CHANGELOG.md b/plugins/postcss-nesting/CHANGELOG.md index ab8efebba..959aa06a8 100644 --- a/plugins/postcss-nesting/CHANGELOG.md +++ b/plugins/postcss-nesting/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to PostCSS Nesting +### Unreleased + +- Added: TypeScript typings + ### 10.1.10 (June 30, 2022) - Partially revert the changes to pseudo element selectors from 10.1.9. @@ -13,7 +17,7 @@ /* becomes */ -- .something_else > :is(.anything:::before) { /* 10.1.9 */ +- .something_else > :is(.anything::before) { /* 10.1.9 */ + .something_else > .anything::before { /* previous and restored behavior */ order: 1; } diff --git a/plugins/postcss-nesting/README.md b/plugins/postcss-nesting/README.md index 19c60d07e..17e43d15e 100644 --- a/plugins/postcss-nesting/README.md +++ b/plugins/postcss-nesting/README.md @@ -10,21 +10,31 @@ you might want to use [PostCSS Nested] instead. ```pcss a, b { - color: red; + color: red; - & c, & d { - color: white; - } + /* "&" comes first */ + & c, & d { + color: white; + } + + /* "&" comes later, requiring "@nest" */ + @nest e & { + color: yellow; + } } /* becomes */ a, b { - color: red; + color: red; } a c, a d, b c, b d { - color: white; + color: white; +} + +e a, e b { + color: yellow; } ``` @@ -36,15 +46,7 @@ Add [PostCSS Nesting] to your project: npm install postcss-nesting --save-dev ``` -Use [PostCSS Nesting] to process your CSS: - -```js -import postcssNesting from 'postcss-nesting'; - -postcssNesting.process(YOUR_CSS /*, processOptions, pluginOptions */); -``` - -Or use it as a [PostCSS] plugin: +Use [PostCSS Nesting] as a [PostCSS] plugin: ```js import postcss from 'postcss'; @@ -174,8 +176,8 @@ _writing the selector without nesting is advised here_ ### ⚠️ Spec disclaimer -The [CSS Nesting Module] spec states on nesting that "Declarations occuring after a nested rule are invalid and ignored.". -While we think it makes sense on browsers, enforcing this at the plugin level introduces several constrains that would +The [CSS Nesting Module] spec states on nesting that "Declarations occurring after a nested rule are invalid and ignored.". +While we think it makes sense on browsers, enforcing this at the plugin level introduces several constraints that would interfere with PostCSS' plugin nature such as with `@mixin` [css-img]: https://cssdb.org/images/badges/nesting-rules.svg diff --git a/plugins/postcss-nesting/package.json b/plugins/postcss-nesting/package.json index 0a61339f4..ebcbec6df 100644 --- a/plugins/postcss-nesting/package.json +++ b/plugins/postcss-nesting/package.json @@ -13,6 +13,7 @@ }, "main": "dist/index.cjs", "module": "dist/index.mjs", + "types": "dist/index.d.ts", "jsdelivr": "dist/index.mjs", "unpkg": "dist/index.mjs", "exports": { @@ -41,7 +42,6 @@ "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:deno": "deno run --unstable --allow-env --allow-read test/deno/test.js", "test:exports": "node ./test/_import.mjs && node ./test/_require.cjs", diff --git a/plugins/postcss-nesting/src/index.js b/plugins/postcss-nesting/src/index.js deleted file mode 100644 index 01d143fcb..000000000 --- a/plugins/postcss-nesting/src/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import walk from './lib/walk.js'; - -/** - * @param {{noIsPseudoSelector?: boolean}} opts - * @returns {import('postcss').Plugin} - */ -export default function postcssNesting(opts) { - const noIsPseudoSelector = Object(opts).noIsPseudoSelector || false; - return { - postcssPlugin: 'postcss-nesting', - Rule(rule) { - walk(rule, {noIsPseudoSelector: noIsPseudoSelector}); - }, - }; -} - -postcssNesting.postcss = true; diff --git a/plugins/postcss-nesting/src/index.ts b/plugins/postcss-nesting/src/index.ts new file mode 100644 index 000000000..fdbf828cc --- /dev/null +++ b/plugins/postcss-nesting/src/index.ts @@ -0,0 +1,26 @@ +import type { PluginCreator } from 'postcss'; +import walk from './lib/walk.js'; + +type pluginOptions = { noIsPseudoSelector?: boolean }; + +const creator: PluginCreator = (opts?: pluginOptions) => { + const options = Object.assign( + // Default options + { + noIsPseudoSelector: false, + }, + // Provided options + opts, + ); + + return { + postcssPlugin: 'postcss-nesting', + Rule(rule) { + walk(rule, options); + }, + }; +}; + +creator.postcss = true; + +export default creator; diff --git a/plugins/postcss-nesting/src/lib/atrule-within-atrule.js b/plugins/postcss-nesting/src/lib/atrule-within-atrule.ts similarity index 53% rename from plugins/postcss-nesting/src/lib/atrule-within-atrule.js rename to plugins/postcss-nesting/src/lib/atrule-within-atrule.ts index 87fc98a66..baea2b108 100644 --- a/plugins/postcss-nesting/src/lib/atrule-within-atrule.js +++ b/plugins/postcss-nesting/src/lib/atrule-within-atrule.ts @@ -1,16 +1,12 @@ +import type { AtRule } from 'postcss'; import cleanupParent from './cleanup-parent.js'; import mergeParams from './merge-params.js'; import shiftNodesBeforeParent from './shift-nodes-before-parent.js'; import validAtrules from './valid-atrules.js'; -/* - * DEPRECATED: In v7.0.0 these features will be removed as they are not part of - * the nesting proposal. - */ - -export default function transformAtruleWithinAtrule(node) { +export default function transformAtruleWithinAtrule(node: AtRule, parent: AtRule) { // move previous siblings and the node to before the parent - const parent = shiftNodesBeforeParent(node); + shiftNodesBeforeParent(node, parent); // update the params of the node to be merged with the parent node.params = mergeParams(parent.params, node.params); @@ -19,4 +15,7 @@ export default function transformAtruleWithinAtrule(node) { cleanupParent(parent); } -export const isAtruleWithinAtrule = (node) => node.type === 'atrule' && validAtrules.includes(node.name) && Object(node.parent).type === 'atrule' && node.name === node.parent.name; +export function isAtruleWithinAtrule(node: AtRule, parent: AtRule) { + return validAtrules.includes(node.name) && + node.name === parent.name; +} diff --git a/plugins/postcss-nesting/src/lib/atrule-within-rule.js b/plugins/postcss-nesting/src/lib/atrule-within-rule.ts similarity index 59% rename from plugins/postcss-nesting/src/lib/atrule-within-rule.js rename to plugins/postcss-nesting/src/lib/atrule-within-rule.ts index 31000322d..ec80c4bdd 100644 --- a/plugins/postcss-nesting/src/lib/atrule-within-rule.js +++ b/plugins/postcss-nesting/src/lib/atrule-within-rule.ts @@ -1,10 +1,13 @@ import cleanupParent from './cleanup-parent.js'; +import { options } from './options.js'; import shiftNodesBeforeParent from './shift-nodes-before-parent.js'; import validAtrules from './valid-atrules.js'; +import { walkFunc } from './walk-func.js'; +import type { AtRule, Rule } from 'postcss'; -export default function atruleWithinRule(node, walk, opts) { +export default function atruleWithinRule(node: AtRule, parent: Rule, walk: walkFunc, opts: options) { // move previous siblings and the node to before the parent - const parent = shiftNodesBeforeParent(node); + shiftNodesBeforeParent(node, parent); // clone the parent as a new rule with children appended to it const rule = parent.clone().removeAll().append(node.nodes); @@ -19,4 +22,6 @@ export default function atruleWithinRule(node, walk, opts) { walk(rule, opts); } -export const isAtruleWithinRule = (node) => node.type === 'atrule' && validAtrules.includes(node.name) && Object(node.parent).type === 'rule'; +export function isAtruleWithinRule(node: AtRule) { + return validAtrules.includes(node.name); +} diff --git a/plugins/postcss-nesting/src/lib/cleanup-parent.js b/plugins/postcss-nesting/src/lib/cleanup-parent.ts similarity index 65% rename from plugins/postcss-nesting/src/lib/cleanup-parent.js rename to plugins/postcss-nesting/src/lib/cleanup-parent.ts index 81eef048b..815b1dd1c 100644 --- a/plugins/postcss-nesting/src/lib/cleanup-parent.js +++ b/plugins/postcss-nesting/src/lib/cleanup-parent.ts @@ -1,4 +1,6 @@ -export default function cleanupParent(parent) { +import type { ChildNode, Container } from 'postcss'; + +export default function cleanupParent(parent: Container) { if (!parent.nodes.length) { parent.remove(); return; diff --git a/plugins/postcss-nesting/src/lib/is-type-of-rule.ts b/plugins/postcss-nesting/src/lib/is-type-of-rule.ts new file mode 100644 index 000000000..7cea7cf62 --- /dev/null +++ b/plugins/postcss-nesting/src/lib/is-type-of-rule.ts @@ -0,0 +1,13 @@ +import type { AtRule, Node, Rule } from 'postcss'; + +export function isAtRule(node?: Node): node is AtRule { + return node && node.type === 'atrule'; +} + +export function isNestRule(node?: Node): node is AtRule { + return node && isAtRule(node) && node.name === 'nest'; +} + +export function isRule(node?: Node): node is Rule { + return node && node.type === 'rule'; +} diff --git a/plugins/postcss-nesting/src/lib/list.js b/plugins/postcss-nesting/src/lib/list.ts similarity index 83% rename from plugins/postcss-nesting/src/lib/list.js rename to plugins/postcss-nesting/src/lib/list.ts index 47c0f6055..76d4861d6 100644 --- a/plugins/postcss-nesting/src/lib/list.js +++ b/plugins/postcss-nesting/src/lib/list.ts @@ -1,13 +1,13 @@ -export const comma = (string) => { - let array = []; +export function comma(string: string) { + const array: Array = []; let current = ''; let split = false; let func = 0; - let quote = false; + let quote: string|false = false; let escape = false; - for (let letter of string) { + for (const letter of string) { if (escape) { escape = false; } else if (letter === '\\') { @@ -43,4 +43,4 @@ export const comma = (string) => { array.push(current.trim()); return array; -}; +} diff --git a/plugins/postcss-nesting/src/lib/merge-params.js b/plugins/postcss-nesting/src/lib/merge-params.ts similarity index 71% rename from plugins/postcss-nesting/src/lib/merge-params.js rename to plugins/postcss-nesting/src/lib/merge-params.ts index 673ff3d66..5e97609b8 100644 --- a/plugins/postcss-nesting/src/lib/merge-params.js +++ b/plugins/postcss-nesting/src/lib/merge-params.ts @@ -1,6 +1,6 @@ import { comma } from './list.js'; -export default function mergeParams(fromParams, toParams) { +export default function mergeParams(fromParams: string, toParams: string) { return comma(fromParams) .map((params1) => comma(toParams) diff --git a/plugins/postcss-nesting/src/lib/merge-selectors/combinations-of-size-n.js b/plugins/postcss-nesting/src/lib/merge-selectors/combinations-of-size-n.ts similarity index 89% rename from plugins/postcss-nesting/src/lib/merge-selectors/combinations-of-size-n.js rename to plugins/postcss-nesting/src/lib/merge-selectors/combinations-of-size-n.ts index ca12628b2..74d7bc2b4 100644 --- a/plugins/postcss-nesting/src/lib/merge-selectors/combinations-of-size-n.js +++ b/plugins/postcss-nesting/src/lib/merge-selectors/combinations-of-size-n.ts @@ -1,4 +1,4 @@ -export function combinationsWithSizeN(set, n) { +export function combinationsWithSizeN(set: Array, n: number): Array> { // set is the list of parent selectors // n is the amount of `&` selectors in the current selector. // all combinations of values in the set with an array size of n must be generated to match the nesting selector behavior. @@ -37,17 +37,17 @@ export function combinationsWithSizeN(set, n) { throw new Error('Too many combinations when trying to resolve a nested selector with lists, reduce the complexity of your selectors'); } - const counters = []; + const counters: Array = []; for (let i = 0; i < n; i++) { counters[i] = 0; } - const result = []; + const result: Array> = []; // eslint-disable-next-line no-constant-condition while (true) { - const ss = []; + const ss : Array = []; for (let i = n-1; i >=0; i--) { let currentCounter = counters[i]; if (currentCounter >= set.length) { diff --git a/plugins/postcss-nesting/src/lib/merge-selectors/compound-selector-order.js b/plugins/postcss-nesting/src/lib/merge-selectors/compound-selector-order.ts similarity index 72% rename from plugins/postcss-nesting/src/lib/merge-selectors/compound-selector-order.js rename to plugins/postcss-nesting/src/lib/merge-selectors/compound-selector-order.ts index b26941672..3c9985d47 100644 --- a/plugins/postcss-nesting/src/lib/merge-selectors/compound-selector-order.js +++ b/plugins/postcss-nesting/src/lib/merge-selectors/compound-selector-order.ts @@ -1,14 +1,15 @@ import parser from 'postcss-selector-parser'; +import type { Container, Node, Pseudo } from 'postcss-selector-parser'; const isPseudo = parser.pseudo({ value: ':is' }); -export function sortCompoundSelectorsInsideComplexSelector(node) { +export function sortCompoundSelectorsInsideComplexSelector(node: Container) { if (!node || !node.nodes) { return; } - const compoundSelectors = []; - let currentCompoundSelector = []; + const compoundSelectors: Array> = []; + let currentCompoundSelector: Array = []; for (let i = 0; i < node.nodes.length; i++) { if (node.nodes[i].type === 'combinator') { // Push the current compound selector @@ -34,10 +35,13 @@ export function sortCompoundSelectorsInsideComplexSelector(node) { } if (node.nodes[i].type === 'tag' && currentCompoundSelector.find(x => x.type === 'tag')) { - const isPseudoClone = isPseudo.clone(); + const isPseudoClone = isPseudo.clone({}) as Pseudo; const child = node.nodes[i]; child.replaceWith(isPseudoClone); - isPseudoClone.append(child); + isPseudoClone.append(parser.selector({ + nodes: [child], + value: undefined, + })); } currentCompoundSelector.push(node.nodes[i]); @@ -50,18 +54,18 @@ export function sortCompoundSelectorsInsideComplexSelector(node) { const compoundSelector = compoundSelectors[i]; compoundSelector.sort((a, b) => { if (a.type === 'selector' && b.type === 'selector' && a.nodes.length && b.nodes.length) { - return selectorTypeOrder(a.nodes[0], a.nodes[0].type) - selectorTypeOrder(b.nodes[0], b.nodes[0].type); + return selectorTypeOrder(a.nodes[0]) - selectorTypeOrder(b.nodes[0]); } if (a.type === 'selector' && a.nodes.length) { - return selectorTypeOrder(a.nodes[0], a.nodes[0].type) - selectorTypeOrder(b, b.type); + return selectorTypeOrder(a.nodes[0]) - selectorTypeOrder(b); } if (b.type === 'selector' && b.nodes.length) { - return selectorTypeOrder(a, a.type) - selectorTypeOrder(b.nodes[0], b.nodes[0].type); + return selectorTypeOrder(a) - selectorTypeOrder(b.nodes[0]); } - return selectorTypeOrder(a, a.type) - selectorTypeOrder(b, b.type); + return selectorTypeOrder(a) - selectorTypeOrder(b); }); for (let j = 0; j < compoundSelector.length; j++) { @@ -75,12 +79,12 @@ export function sortCompoundSelectorsInsideComplexSelector(node) { } } -function selectorTypeOrder(selector, type) { +function selectorTypeOrder(selector: Node) { if (parser.isPseudoElement(selector)) { return selectorTypeOrderIndex.pseudoElement; } - return selectorTypeOrderIndex[type]; + return selectorTypeOrderIndex[selector.type]; } const selectorTypeOrderIndex = { diff --git a/plugins/postcss-nesting/src/lib/merge-selectors/merge-selectors.js b/plugins/postcss-nesting/src/lib/merge-selectors/merge-selectors.ts similarity index 93% rename from plugins/postcss-nesting/src/lib/merge-selectors/merge-selectors.js rename to plugins/postcss-nesting/src/lib/merge-selectors/merge-selectors.ts index c5738e4a8..d33ba2f78 100644 --- a/plugins/postcss-nesting/src/lib/merge-selectors/merge-selectors.js +++ b/plugins/postcss-nesting/src/lib/merge-selectors/merge-selectors.ts @@ -1,9 +1,11 @@ import parser from 'postcss-selector-parser'; +import type { Root, Nesting } from 'postcss-selector-parser'; import { combinationsWithSizeN } from './combinations-of-size-n'; import { sortCompoundSelectorsInsideComplexSelector } from './compound-selector-order'; import { nodesAreEquallySpecific } from './specificity'; +import { options } from '../options'; -export default function mergeSelectors(fromSelectors, toSelectors, opts) { +export default function mergeSelectors(fromSelectors: Array, toSelectors: Array, opts: options) { const fromListHasUniformSpecificity = nodesAreEquallySpecific(fromSelectors); let fromSelectorsAST = []; @@ -16,7 +18,7 @@ export default function mergeSelectors(fromSelectors, toSelectors, opts) { fromSelectorsAST = [parser().astSync(`:is(${fromSelectors.join(',')})`)]; } - let result = []; + const result = []; for (let x = 0; x < toSelectors.length; x++) { const toSelector = toSelectors[x]; @@ -141,7 +143,7 @@ export default function mergeSelectors(fromSelectors, toSelectors, opts) { if (opts.noIsPseudoSelector) { nesting.replaceWith(...(fromSelectorAST.clone().nodes)); } else { - nesting.replaceWith(...(fromSelectorWithIsAST.clone().nodes)); + nesting.replaceWith(...((fromSelectorWithIsAST.clone({}) as Root).nodes)); } if (parent) { @@ -215,7 +217,7 @@ function nestingIsFirstAndOnlyInSelectorWithEitherSpaceOrChildCombinator(selecto return true; } -function nestingIsNotInsideCompoundSelector(selector) { +function nestingIsNotInsideCompoundSelector(selector: Nesting) { if (isSimpleSelector(selector)) { return true; } @@ -225,7 +227,7 @@ function nestingIsNotInsideCompoundSelector(selector) { } for (let i = 0; i < selector.parent.nodes.length; i++) { - if (!selector.parent.nodes[i].type === 'nesting') { + if (selector.parent.nodes[i].type === 'nesting') { continue; } diff --git a/plugins/postcss-nesting/src/lib/merge-selectors/specificity.js b/plugins/postcss-nesting/src/lib/merge-selectors/specificity.ts similarity index 92% rename from plugins/postcss-nesting/src/lib/merge-selectors/specificity.js rename to plugins/postcss-nesting/src/lib/merge-selectors/specificity.ts index 97c73ca18..a855f8f96 100644 --- a/plugins/postcss-nesting/src/lib/merge-selectors/specificity.js +++ b/plugins/postcss-nesting/src/lib/merge-selectors/specificity.ts @@ -1,7 +1,7 @@ import parser from 'postcss-selector-parser'; import { selectorSpecificity } from '@csstools/selector-specificity'; -export function nodesAreEquallySpecific(nodes) { +export function nodesAreEquallySpecific(nodes: Array) { // Selector specificity is important when the parent selector is a list. // These cases should be resolved with `:is()` pseudo. // Since browser support for `:is()` is not great, we try to avoid it. diff --git a/plugins/postcss-nesting/src/lib/merge-selectors/wrap-multiple-tag-selectors-with-is-pseudo.js b/plugins/postcss-nesting/src/lib/merge-selectors/wrap-multiple-tag-selectors-with-is-pseudo.js deleted file mode 100644 index 53c5dfb97..000000000 --- a/plugins/postcss-nesting/src/lib/merge-selectors/wrap-multiple-tag-selectors-with-is-pseudo.js +++ /dev/null @@ -1,22 +0,0 @@ -import parser from 'postcss-selector-parser'; - -const isPseudo = parser.pseudo({ value: ':is' }); - -export function wrapMultipleTagSelectorsWithIsPseudo(node) { - // This is a fallback for broken selectors like: - // `h1h2` - // These selectors are also useless as `h1:is(h2)`. - // Wrapping with is only prevents accidentally forming other words which might have meaning. - - const tagNodes = node.nodes.filter((x) => { - return x.type === 'tag'; - }); - - if (tagNodes.length > 1) { - tagNodes.slice(1).forEach((child) => { - const isPseudoClone = isPseudo.clone(); - child.replaceWith(isPseudoClone); - isPseudoClone.append(child); - }); - } -} diff --git a/plugins/postcss-nesting/src/lib/nest-rule-within-rule.js b/plugins/postcss-nesting/src/lib/nest-rule-within-rule.ts similarity index 64% rename from plugins/postcss-nesting/src/lib/nest-rule-within-rule.js rename to plugins/postcss-nesting/src/lib/nest-rule-within-rule.ts index 0c4c95326..6a004e2fa 100644 --- a/plugins/postcss-nesting/src/lib/nest-rule-within-rule.js +++ b/plugins/postcss-nesting/src/lib/nest-rule-within-rule.ts @@ -2,10 +2,13 @@ import { comma } from './list.js'; import shiftNodesBeforeParent from './shift-nodes-before-parent.js'; import cleanupParent from './cleanup-parent.js'; import mergeSelectors from './merge-selectors/merge-selectors.js'; +import type { AtRule, Rule } from 'postcss'; +import { walkFunc } from './walk-func.js'; +import { options } from './options.js'; -export default function transformNestRuleWithinRule(node, walk, opts) { +export default function transformNestRuleWithinRule(node: AtRule, parent: Rule, walk: walkFunc, opts: options) { // move previous siblings and the node to before the parent - const parent = shiftNodesBeforeParent(node); + shiftNodesBeforeParent(node, parent); // clone the parent as a new rule with children appended to it const rule = parent.clone().removeAll().append(node.nodes); @@ -24,4 +27,9 @@ export default function transformNestRuleWithinRule(node, walk, opts) { walk(rule, opts); } -export const isNestRuleWithinRule = (node) => node.type === 'atrule' && node.name === 'nest' && Object(node.parent).type === 'rule' && comma(node.params).every((selector) => selector.split('&').length >= 2 && selector.indexOf('|') === -1); +export function isValidNestRuleWithinRule(node: AtRule) { + return comma(node.params).every((selector) => { + return selector.split('&').length >= 2 && + selector.indexOf('|') === -1; + }); +} diff --git a/plugins/postcss-nesting/src/lib/options.ts b/plugins/postcss-nesting/src/lib/options.ts new file mode 100644 index 000000000..00c9b3158 --- /dev/null +++ b/plugins/postcss-nesting/src/lib/options.ts @@ -0,0 +1 @@ +export type options = { noIsPseudoSelector: boolean } diff --git a/plugins/postcss-nesting/src/lib/rule-within-rule.js b/plugins/postcss-nesting/src/lib/rule-within-rule.ts similarity index 58% rename from plugins/postcss-nesting/src/lib/rule-within-rule.js rename to plugins/postcss-nesting/src/lib/rule-within-rule.ts index 5d1b2f821..96a97e0ef 100644 --- a/plugins/postcss-nesting/src/lib/rule-within-rule.js +++ b/plugins/postcss-nesting/src/lib/rule-within-rule.ts @@ -1,16 +1,18 @@ import shiftNodesBeforeParent from './shift-nodes-before-parent.js'; import cleanupParent from './cleanup-parent.js'; import mergeSelectors from './merge-selectors/merge-selectors.js'; +import type { Rule } from 'postcss'; +import { options } from './options.js'; -export default function transformRuleWithinRule(node, opts) { +export default function transformRuleWithinRule(node: Rule, parent: Rule, opts: options) { // move previous siblings and the node to before the parent - const parent = shiftNodesBeforeParent(node); + shiftNodesBeforeParent(node, parent); // update the selectors of the node to be merged with the parent node.selectors = mergeSelectors(parent.selectors, node.selectors, opts); // merge similar rules back together - const areSameRule = (node.type === 'rule' && parent.type === 'rule' && node.selector === parent.selector) || (node.type === 'atrule' && parent.type === 'atrule' && node.params === parent.params); + const areSameRule = (node.type === 'rule' && parent.type === 'rule' && node.selector === parent.selector); if (areSameRule) { node.append(...parent.nodes); @@ -20,4 +22,9 @@ export default function transformRuleWithinRule(node, opts) { cleanupParent(parent); } -export const isRuleWithinRule = (node) => node.type === 'rule' && Object(node.parent).type === 'rule' && node.selectors.every((selector) => selector.trim().indexOf('&') === 0 && selector.indexOf('|') === -1); +export function isValidRuleWithinRule(node: Rule) { + return node.selectors.every((selector) => { + return selector.trim().indexOf('&') === 0 && + selector.indexOf('|') === -1; + }); +} diff --git a/plugins/postcss-nesting/src/lib/shift-nodes-before-parent.js b/plugins/postcss-nesting/src/lib/shift-nodes-before-parent.ts similarity index 79% rename from plugins/postcss-nesting/src/lib/shift-nodes-before-parent.js rename to plugins/postcss-nesting/src/lib/shift-nodes-before-parent.ts index fd4ecd90e..9a07f3ca3 100644 --- a/plugins/postcss-nesting/src/lib/shift-nodes-before-parent.js +++ b/plugins/postcss-nesting/src/lib/shift-nodes-before-parent.ts @@ -1,7 +1,7 @@ +import type { ChildNode, Container } from 'postcss'; import cleanupParent from './cleanup-parent'; -export default function shiftNodesBeforeParent(node) { - const parent = node.parent; +export default function shiftNodesBeforeParent(node: ChildNode, parent: Container) { const index = parent.index(node); // conditionally move previous siblings into a clone of the parent @@ -14,6 +14,4 @@ export default function shiftNodesBeforeParent(node) { // move the current node before the parent (and after the conditional clone) parent.before(node); parent.raws.semicolon = true; /* nested rules end with "}" and do not have this flag set */ - - return parent; } diff --git a/plugins/postcss-nesting/src/lib/valid-atrules.js b/plugins/postcss-nesting/src/lib/valid-atrules.ts similarity index 100% rename from plugins/postcss-nesting/src/lib/valid-atrules.js rename to plugins/postcss-nesting/src/lib/valid-atrules.ts diff --git a/plugins/postcss-nesting/src/lib/walk-func.ts b/plugins/postcss-nesting/src/lib/walk-func.ts new file mode 100644 index 000000000..a801218d9 --- /dev/null +++ b/plugins/postcss-nesting/src/lib/walk-func.ts @@ -0,0 +1,3 @@ +import type { Container } from 'postcss'; + +export type walkFunc = (node: Container, opts: { noIsPseudoSelector: boolean }) => void; diff --git a/plugins/postcss-nesting/src/lib/walk.js b/plugins/postcss-nesting/src/lib/walk.js deleted file mode 100644 index b288e20d7..000000000 --- a/plugins/postcss-nesting/src/lib/walk.js +++ /dev/null @@ -1,22 +0,0 @@ -import transformRuleWithinRule, { isRuleWithinRule } from './rule-within-rule.js'; -import transformNestRuleWithinRule, { isNestRuleWithinRule } from './nest-rule-within-rule.js'; -import transformAtruleWithinRule, { isAtruleWithinRule } from './atrule-within-rule.js'; -import transformAtruleWithinAtrule, { isAtruleWithinAtrule } from './atrule-within-atrule.js'; - -export default function walk(node, opts) { - node.each((child) => { - if (isRuleWithinRule(child)) { - transformRuleWithinRule(child, opts); - } else if (isNestRuleWithinRule(child)) { - transformNestRuleWithinRule(child, walk, opts); - } else if (isAtruleWithinRule(child)) { - transformAtruleWithinRule(child, walk, opts); - } else if (isAtruleWithinAtrule(child)) { - transformAtruleWithinAtrule(child); - } - - if (Object(child.nodes).length) { - walk(child, opts); - } - }); -} diff --git a/plugins/postcss-nesting/src/lib/walk.ts b/plugins/postcss-nesting/src/lib/walk.ts new file mode 100644 index 000000000..54b4c4b56 --- /dev/null +++ b/plugins/postcss-nesting/src/lib/walk.ts @@ -0,0 +1,43 @@ +import transformRuleWithinRule, { isValidRuleWithinRule } from './rule-within-rule.js'; +import transformNestRuleWithinRule, { isValidNestRuleWithinRule } from './nest-rule-within-rule.js'; +import transformAtruleWithinRule, { isAtruleWithinRule } from './atrule-within-rule.js'; +import transformAtruleWithinAtrule, { isAtruleWithinAtrule } from './atrule-within-atrule.js'; +import type { Container } from 'postcss'; +import { options } from './options.js'; +import { isAtRule, isNestRule, isRule } from './is-type-of-rule.js'; + +export default function walk(node: Container, opts: options) { + node.each((child) => { + const parent = child.parent; + + if ( + isRule(child) && + isRule(parent) && + isValidRuleWithinRule(child) + ) { + transformRuleWithinRule(child, parent, opts); + } else if ( + isNestRule(child) && + isRule(parent) && + isValidNestRuleWithinRule(child) + ) { + transformNestRuleWithinRule(child, parent, walk, opts); + } else if ( + isAtRule(child) && + isRule(parent) && + isAtruleWithinRule(child) + ) { + transformAtruleWithinRule(child, parent, walk, opts); + } else if ( + isAtRule(child) && + isAtRule(parent) && + isAtruleWithinAtrule(child, parent) + ) { + transformAtruleWithinAtrule(child, parent); + } + + if ('nodes' in child && child.nodes.length) { + walk(child, opts); + } + }); +} diff --git a/plugins/postcss-nesting/stryker.conf.json b/plugins/postcss-nesting/stryker.conf.json deleted file mode 100644 index 9b81b1e02..000000000 --- a/plugins/postcss-nesting/stryker.conf.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "../../node_modules/@stryker-mutator/core/schema/stryker-schema.json", - "mutate": [ - "src/lib/**/*.js" - ], - "testRunner": "command", - "coverageAnalysis": "perTest", - "tempDirName": "../../.stryker-tmp", - "commandRunner": { - "command": "npm run test:tape" - }, - "buildCommand": "npm run build", - "thresholds": { - "high": 100, - "low": 100, - "break": 100 - }, - "inPlace": true -} diff --git a/plugins/postcss-nesting/tsconfig.json b/plugins/postcss-nesting/tsconfig.json new file mode 100644 index 000000000..e0d06239c --- /dev/null +++ b/plugins/postcss-nesting/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "declarationDir": "." + }, + "include": ["./src/**/*"], + "exclude": ["dist"], +}