Skip to content

Commit

Permalink
CLean up code from types
Browse files Browse the repository at this point in the history
  • Loading branch information
ai committed Oct 6, 2022
1 parent cb93b65 commit 6c37bc9
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 140 deletions.
8 changes: 0 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
# Change Log
This project adheres to [Semantic Versioning](http://semver.org/).

## Upcoming...
* ... <!-- Add new lines here. -->
* fix: Correctly handle `with`/`without` parameters on `@at-root`
* feat: Add option `rootRuleName` to rename the custom `@at-root` rule
* fix: Errors when handling sibling `@at-root` rule blocks
* fix: Move all preceeding comments with rule
* fix: `@layer` blocks should also bubble

## 5.0.6
* Fixed custom at-rules nesting (by @bsak-shell).

Expand Down
138 changes: 26 additions & 112 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,10 @@
// @ts-check
const { Rule, AtRule } = require('postcss')
let parser = require('postcss-selector-parser')

/** @typedef {import('postcss').Container} Container */
/** @typedef {import('postcss').ChildNode} ChildNode */
/** @typedef {import('postcss').Comment} Comment */
/** @typedef {import('postcss').Declaration} Declaration */
/** @typedef {import('postcss').Rule} PostcssRule */
/** @typedef {typeof import('postcss').Rule} RuleConstructor */
/** @typedef {parser.Root} Root */
/** @typedef {parser.Node} Node */
/** @typedef {parser.Selector} Selector */
/** @typedef {Record<string, true>} RuleMap Simple lookup table for \@-rules */

/**
* Run a selector string through postcss-selector-parser
*
* @param {string} rawSelector
* @param {PostcssRule} [rule]
* @returns {Selector}
*/
function parse(rawSelector, rule) {
/** @type {Root | undefined} */
let nodes
try {
parser(parsed => {
Expand All @@ -34,54 +17,41 @@ function parse(rawSelector, rule) {
throw rule ? rule.error(e.message) : e
}
}
// Should be safe, but @ts-check can't deduce the side-effect
// triggered by `saver.processSync(str)`
return /** @type {Root} */ (nodes).at(0)
return nodes.at(0)
}

/**
* Replaces the "&" token in a node's selector with the parent selector
* similar to what SCSS does.
*
* Mutates the nodes list
*
* @param {Extract<Node, { nodes: Array }>} nodes
* @param {Selector} parent
* @returns {boolean} Indicating whether a replacement took place or not.
*/
function interpolateAmpInSelector(nodes, parent) {
let replaced = false
nodes.each(
/** @type {Node} */ node => {
if (node.type === 'nesting') {
let clonedParent = parent.clone({})
if (node.value !== '&') {
node.replaceWith(
parse(node.value.replace('&', clonedParent.toString()))
)
} else {
node.replaceWith(clonedParent)
}
nodes.each(node => {
if (node.type === 'nesting') {
let clonedParent = parent.clone({})
if (node.value !== '&') {
node.replaceWith(
parse(node.value.replace('&', clonedParent.toString()))
)
} else {
node.replaceWith(clonedParent)
}
replaced = true
} else if ('nodes' in node && node.nodes) {
if (interpolateAmpInSelector(node, parent)) {
replaced = true
} else if ('nodes' in node && node.nodes) {
if (interpolateAmpInSelector(node, parent)) {
replaced = true
}
}
}
)
})
return replaced
}

/**
* Combines parent and child selectors, in a SCSS-like way
*
* @param {PostcssRule} parent
* @param {PostcssRule} child
* @returns {Array<string>} An array of new, merged selectors
*/
function mergeSelectors(parent, child) {
/** @type {Array<string>} */
let merged = []
parent.selectors.forEach(sel => {
let parentNode = parse(sel, parent)
Expand All @@ -93,10 +63,8 @@ function mergeSelectors(parent, child) {
let node = parse(selector, child)
let replaced = interpolateAmpInSelector(node, parentNode)
if (!replaced) {
// NOTE: The type definitions for `postcss-selector-parser` seem to be
// badly outdated.
node.prepend(/** @type {any} */ (parser.combinator({ value: ' ' })))
node.prepend(/** @type {Selector} */ (parentNode.clone({})))
node.prepend(parser.combinator({ value: ' ' }))
node.prepend(parentNode.clone({}))
}
merged.push(node.toString())
})
Expand All @@ -106,10 +74,6 @@ function mergeSelectors(parent, child) {

/**
* Move a child and its preceeding comment(s) to after "after"
*
* @param {ChildNode} child
* @param {ChildNode} after
* @returns {ChildNode} updated "after" node
*/
function breakOut(child, after) {
let prev = child.prev()
Expand All @@ -122,17 +86,8 @@ function breakOut(child, after) {
return child
}

/**
* @param {RuleMap} bubble
*/
function createFnAtruleChilds(bubble) {
/**
* @param {PostcssRule} rule
* @param {AtRule} atrule
* @param {boolean} bubbling
*/
return function atruleChilds(rule, atrule, bubbling, mergeSels = bubbling) {
/** @type {Array<ChildNode>} */
let children = []
atrule.each(child => {
if (child.type === 'rule' && bubbling) {
Expand Down Expand Up @@ -161,11 +116,6 @@ function createFnAtruleChilds(bubble) {
}
}

/**
* @param {string} selector
* @param {Array<ChildNode>} declarations
* @param {ChildNode} after
*/
function pickDeclarations(selector, declarations, after) {
let parent = new Rule({
selector,
Expand All @@ -176,12 +126,7 @@ function pickDeclarations(selector, declarations, after) {
return parent
}

/**
* @param {Array<string>} defaults,
* @param {Array<string>} [custom]
*/
function atruleNames(defaults, custom) {
/** @type {RuleMap} */
let list = {}
for (let name of defaults) {
list[name] = true
Expand All @@ -194,13 +139,6 @@ function atruleNames(defaults, custom) {
return list
}

/** @typedef {{ type: 'basic', selector?: string, escapes?: never }} AtRootBParams */
/** @typedef {{ type: 'withrules', escapes: (rule: string) => boolean, selector?: never }} AtRootWParams */
/** @typedef {{ type: 'unknown', selector?: never, escapes?: never }} AtRootUParams */
/** @typedef {{ type: 'noop', selector?: never, escapes?: never }} AtRootNParams */
/** @typedef {AtRootBParams | AtRootWParams | AtRootNParams | AtRootUParams} AtRootParams */

/** @type {(params: string) => AtRootParams } */
function parseRootRuleParams(params) {
params = params.trim()
let braceBlock = params.match(/^\((.*)\)$/)
Expand All @@ -210,7 +148,6 @@ function parseRootRuleParams(params) {
let bits = braceBlock[1].match(/^(with(?:out)?):(.+)$/)
if (bits) {
let allowlist = bits[1] === 'with'
/** @type {RuleMap} */
let rules = Object.fromEntries(
bits[2]
.trim()
Expand All @@ -236,52 +173,39 @@ function parseRootRuleParams(params) {
return { type: 'unknown' }
}

/**
* @param {AtRule} leaf
* @returns {Array<AtRule>}
*/
function getAncestorRules(leaf) {
/** @type {Array<AtRule>} */
const lineage = []
/** @type {Container | ChildNode | Document | undefined} */
let lineage = []
let parent = leaf.parent

while (parent && parent instanceof AtRule) {
lineage.push(/** @type {AtRule} */ (parent))
lineage.push(parent)
parent = parent.parent
}
return lineage
}

/**
* @param {AtRule} rule
*/
function unwrapRootRule(rule) {
const escapes = rule[rootRuleEscapes]
let escapes = rule[rootRuleEscapes]

if (!escapes) {
rule.after(rule.nodes)
} else {
const nodes = rule.nodes
let nodes = rule.nodes

/** @type {AtRule | undefined} */
let topEscaped
let topEscapedIdx = -1
/** @type {AtRule | undefined} */
let breakoutLeaf
/** @type {AtRule | undefined} */
let breakoutRoot
/** @type {AtRule | undefined} */
let clone

const lineage = getAncestorRules(rule)
let lineage = getAncestorRules(rule)
lineage.forEach((parent, i) => {
if (escapes(parent.name)) {
topEscaped = parent
topEscapedIdx = i
breakoutRoot = clone
} else {
const oldClone = clone
let oldClone = clone
clone = parent.clone({ nodes: [] })
oldClone && clone.append(oldClone)
breakoutLeaf = breakoutLeaf || clone
Expand All @@ -293,20 +217,18 @@ function unwrapRootRule(rule) {
} else if (!breakoutRoot) {
topEscaped.after(nodes)
} else {
const leaf = /** @type {AtRule} */ (breakoutLeaf)
let leaf = breakoutLeaf
leaf.append(nodes)
topEscaped.after(breakoutRoot)
}

if (rule.next() && topEscaped) {
/** @type {AtRule | undefined} */
let restRoot
lineage.slice(0, topEscapedIdx + 1).forEach((parent, i, arr) => {
const oldRoot = restRoot
let oldRoot = restRoot
restRoot = parent.clone({ nodes: [] })
oldRoot && restRoot.append(oldRoot)

/** @type {Array<ChildNode>} */
let nextSibs = []
let _child = arr[i - 1] || rule
let next = _child.next()
Expand All @@ -326,12 +248,9 @@ function unwrapRootRule(rule) {
const rootRuleMergeSel = Symbol('rootRuleMergeSel')
const rootRuleEscapes = Symbol('rootRuleEscapes')

/**
* @param {AtRule} rule
*/
function normalizeRootRule(rule) {
let { params } = rule
const { type, selector, escapes } = parseRootRuleParams(params)
let { type, selector, escapes } = parseRootRuleParams(params)
if (type === 'unknown') {
throw rule.error(
`Unknown @${rule.name} parameter ${JSON.stringify(params)}`
Expand All @@ -348,9 +267,6 @@ function normalizeRootRule(rule) {

const hasRootRule = Symbol('hasRootRule')

// ---------------------------------------------------------------------------

/** @type {import('./').Nested} */
module.exports = (opts = {}) => {
let bubble = atruleNames(['media', 'supports', 'layer'], opts.bubble)
let atruleChilds = createFnAtruleChilds(bubble)
Expand Down Expand Up @@ -379,10 +295,8 @@ module.exports = (opts = {}) => {

Rule(rule) {
let unwrapped = false
/** @type {ChildNode} */
let after = rule
let copyDeclarations = false
/** @type {Array<ChildNode>} */
let declarations = []

rule.each(child => {
Expand Down
22 changes: 6 additions & 16 deletions index.test.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,20 @@
// @ts-check
let { equal, throws } = require('uvu/assert')
let { test } = require('uvu')
let postcss = require('postcss').default

let plugin = require('./')

/**
* @param {string} css
* @returns {string}
*/
function normalise(css) {
function normalize(css) {
return css
.replace(/([:;{}]|\*\/|\/\*)/g, ' $1 ')
.replace(/\s\s+/g, ' ')
.replace(/ ([;:])/g, '$1')
.trim()
}

/**
* @param {string} input
* @param {string} output
* @param {plugin.Options | undefined} [opts]
*/
function run(input, output, opts) {
let result = postcss([plugin(opts)]).process(input, { from: '/test.css' })
equal(normalise(result.css), normalise(output))
equal(normalize(result.css), normalize(output))
equal(result.warnings().length, 0)
}

Expand Down Expand Up @@ -694,7 +684,7 @@ test('works with other visitors', () => {
}
}
}
mixinPlugin.postcss = /** @type {const} */ (true)
mixinPlugin.postcss = true
let out = postcss([plugin, mixinPlugin]).process(css, {
from: undefined
}).css
Expand All @@ -713,7 +703,7 @@ test('works with other visitors #2', () => {
}
}
}
mixinPlugin.postcss = /** @type {const} */ (true)
mixinPlugin.postcss = true
let out = postcss([plugin, mixinPlugin]).process(css, {
from: undefined
}).css
Expand Down Expand Up @@ -772,14 +762,14 @@ test('third level dependencies #2', () => {
})

test('Name of at-root is configurable', () => {
const rootRuleName = '_foobar_'
let rootRuleName = '_foobar_'
run(`a { & {} @${rootRuleName} { b {} } }`, `a {} b {}`, {
rootRuleName
})
})

test('The rooRuleName option may start with "@"', () => {
const rootRuleName = '@_foobar_'
let rootRuleName = '@_foobar_'
run(`a { & {} ${rootRuleName} { b {} } }`, `a {} b {}`, {
rootRuleName
})
Expand Down
5 changes: 1 addition & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,7 @@
"trailingComma": "none"
},
"eslintConfig": {
"extends": "@logux/eslint-config",
"rules": {
"prefer-let/prefer-let": 0
}
"extends": "@logux/eslint-config"
},
"c8": {
"exclude": [
Expand Down

0 comments on commit 6c37bc9

Please sign in to comment.