Skip to content

Commit

Permalink
Add types
Browse files Browse the repository at this point in the history
  • Loading branch information
niksy committed Oct 25, 2021
1 parent 26da684 commit f331330
Show file tree
Hide file tree
Showing 21 changed files with 739 additions and 671 deletions.
18 changes: 18 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"presets": [
[
"@babel/preset-env",
{
"modules": false,
"targets": {
"node": "12"
}
}
]
],
"env": {
"test": {
"plugins": ["babel-plugin-istanbul"]
}
}
}
5 changes: 5 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
"eslint-config-prettier"
],
"plugins": ["eslint-plugin-prettier"],
"settings": {
"jsdoc": {
"preferredTypes": ["postcss", "selectorParser"]
}
},
"rules": {
"prettier/prettier": 1
},
Expand Down
7 changes: 3 additions & 4 deletions .nycrc
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
{
"statements": 80,
"lines": 0,
"reporter": [
"html",
"text"
]
"reporter": ["html", "text"],
"sourceMap": false,
"instrument": false
}
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased][]

### Added

- TypeScript types

## [2.0.0][] - 2020-10-08

### Added
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,19 @@ const postcssAst = [];

### queryAst(query, ast)

Returns: `Promise`
Returns: `Promise<(Root | Rule | AtRule | Declaration | Comment)[]>`

Queries PostCSS with CSS selector.

#### query

Type: `String`
Type: `string`

CSS selector.

#### ast

Type: `PostCSS.Node`
Type: `Root`

PostCSS AST.

Expand Down
76 changes: 61 additions & 15 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,46 @@
/* eslint-disable import/no-namespace */

import * as postcss from 'postcss';
import * as selectorParser from 'postcss-selector-parser';
import getSelectorAst from './lib/selector-ast';
import getCombinator from './lib/node/combinator';
import getTag from './lib/node/tag';
import getAttribute from './lib/node/attribute';
import getPseudo from './lib/node/pseudo';
import PassthroughContainer from './lib/passthrough-container';

/**
* @typedef {(
* selectors: selectorParser.Container|selectorParser.Root,
* ast: postcss.Root|postcss.ChildNode|PassthroughContainer
* ) => (postcss.Root|postcss.ChildNode|PassthroughContainer)[]} ProcessSelectors
*/

/**
* @type {ProcessSelectors}
*/
function processSelectors(selectors, ast) {
const nodes = selectors
.map((rootSelector) =>
rootSelector.reduce(
(astContainer, selector) =>
astContainer
.map((rootSelector) => {
if (!selectorParser.isSelector(rootSelector)) {
return [];
}
return rootSelector.reduce(
(astContainer, selector) => {
return astContainer
.map((node) => {
if (
node instanceof postcss.Root ||
node instanceof PassthroughContainer
) {
switch (selector.type) {
case 'tag':
case 'universal':
return getTag(node, selector);
default:
return [];
}
}
switch (selector.type) {
case 'combinator':
return getCombinator(node, selector);
Expand All @@ -24,26 +54,42 @@ function processSelectors(selectors, ast) {
selector,
processSelectors
);

case 'tag':
case 'universal':
default:
return getTag(node, selector);
return [];
}
})
.reduce((array, result) => [...array, ...result], [])
.filter((result) => result !== null),
.reduce(
(
/** @type {(postcss.ChildNode|PassthroughContainer)[]}*/ array,
result
) => [...array, ...result],
[]
)
.filter((result) => result !== null);
},
[ast]
)
)
);
})
.reduce((array, result) => [...array, ...result], []);

const uniqueNodes = [...new Set(nodes)];

return uniqueNodes;
}

export default async (query, postcssAst) => {
/**
* Queries PostCSS with CSS selector.
*
* @param {string} query CSS selector.
* @param {postcss.Root} ast PostCSS AST.
*/
export default async function (query, ast) {
const selectorAst = await getSelectorAst(query);
return processSelectors(selectorAst, postcssAst);
};
const result = [];
for (const node of processSelectors(selectorAst, ast)) {
if (!(node instanceof PassthroughContainer)) {
result.push(node);
}
}
return result;
}
81 changes: 59 additions & 22 deletions lib/node/attribute.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,44 @@
/**
* @typedef {import('postcss').ChildNode} postcss.ChildNode
* @typedef {import('postcss-selector-parser').Attribute} selectorParser.Attribute
* @typedef {import('postcss-selector-parser').Node} selectorParser.Node
*/

const REGEX_REGEX = /^\/(.+)\/([gimuy]+)?$/; // eslint-disable-line unicorn/no-unsafe-regex

/**
* @param {string} regex
* @param {string} flags
* @param {postcss.ChildNode} node
* @param {selectorParser.Attribute} selector
*/
function isRegexMatched(regex, flags, node, selector) {
return new RegExp(regex, flags).test(node[selector.attribute]);
/** @type {string} */
// @ts-ignore
const composedAttribute = node[selector.attribute];

return new RegExp(regex, flags).test(composedAttribute);
}

/**
* @param {selectorParser.Attribute} selector
*/
function whitespaceSeparatedWordTest(selector) {
/**
* @param {string} value
*/
return (value) => {
if (selector.insensitive) {
return value.toLowerCase() === selector.value.toLowerCase();
return value.toLowerCase() === selector.value?.toLowerCase();
}
return value === selector.value;
};
}

/**
* @param {string} operator
* @param {string} value
*/
function getRegexByOperator(operator, value) {
switch (operator) {
case '|=':
Expand All @@ -27,7 +53,12 @@ function getRegexByOperator(operator, value) {
}
}

/**
* @param {postcss.ChildNode} node
* @param {selectorParser.Attribute} selector
*/
export default (node, selector) => {
/** @type {postcss.ChildNode[]} */
const result = [];
const hasSelectorAttribute = selector.attribute in node;

Expand All @@ -43,42 +74,48 @@ export default (node, selector) => {
hasSelectorAttribute &&
selector.operator === '=' &&
selector.quoted &&
typeof selector.value !== 'undefined' &&
REGEX_REGEX.test(selector.value)
) {
const [, regex, flags = ''] = selector.value.match(REGEX_REGEX);
const [, regex, flags = ''] = selector.value.match(REGEX_REGEX) ?? [];
if (isRegexMatched(regex, flags, node, selector)) {
result.push(node);
}
}

if (hasSelectorAttribute && selector.operator === '=') {
if (
(node[selector.attribute] === selector.value &&
!selector.insensitive) ||
(node[selector.attribute].toLowerCase() ===
selector.value.toLowerCase() &&
selector.insensitive)
) {
result.push(node);
}
/** @type {string} */
// @ts-ignore
const composedAttribute = node[selector.attribute];

if (
hasSelectorAttribute &&
selector.operator === '=' &&
((composedAttribute === selector.value && !selector.insensitive) ||
(composedAttribute.toLowerCase() ===
selector.value?.toLowerCase() &&
selector.insensitive))
) {
result.push(node);
}

if (hasSelectorAttribute && selector.operator === '~=') {
if (
node[selector.attribute]
.split(' ')
.some(whitespaceSeparatedWordTest(selector))
) {
result.push(node);
}
if (
hasSelectorAttribute &&
selector.operator === '~=' &&
composedAttribute.split(' ').some(whitespaceSeparatedWordTest(selector))
) {
result.push(node);
}

if (
hasSelectorAttribute &&
typeof selector.operator !== 'undefined' &&
['|=', '^=', '$=', '*='].includes(selector.operator)
) {
const flags = selector.insensitive ? 'i' : '';
const regex = getRegexByOperator(selector.operator, selector.value);
const regex = getRegexByOperator(
selector.operator,
selector.value ?? ''
);

if (regex !== null && isRegexMatched(regex, flags, node, selector)) {
result.push(node);
Expand Down
33 changes: 26 additions & 7 deletions lib/node/combinator.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import Container from 'postcss/lib/container';
import PassthroughContainer from '../passthrough-container';

/**
* @typedef {import('postcss').ChildNode} postcss.ChildNode
* @typedef {import('postcss-selector-parser').Combinator} selectorParser.Combinator
* @typedef {import('postcss-selector-parser').Node} selectorParser.Node
*/

/**
* @param {postcss.ChildNode} node
* @param {selectorParser.Node} selector
*/
function isValidNode(node, selector) {
return (
selector &&
Expand All @@ -9,11 +19,15 @@ function isValidNode(node, selector) {
);
}

/**
* @param {postcss.ChildNode} node
* @param {selectorParser.Combinator} selector
*/
export default (node, selector) => {
const container = new PassthroughContainer();
const result = [];

const nodeIndex = node.parent.index(node);
const nodeIndex = node.parent?.index(node);
const nextNode = node.next();
const nextSelector = selector.next();
const isNodeContainer = node instanceof Container;
Expand All @@ -26,17 +40,22 @@ export default (node, selector) => {
}

if (selector.value === '+') {
if (typeof nextNode !== 'undefined') {
if (isValidNode(nextNode, nextSelector)) {
container.append(nextNode);
}
if (
typeof nextNode !== 'undefined' &&
isValidNode(nextNode, nextSelector)
) {
container.append(nextNode);
}
result.push(container);
}

if (selector.value === '~') {
node.parent.each((resolvedNode, index) => {
if (index > nodeIndex && isValidNode(resolvedNode, nextSelector)) {
node.parent?.each((resolvedNode, index) => {
if (
typeof nodeIndex !== 'undefined' &&
index > nodeIndex &&
isValidNode(resolvedNode, nextSelector)
) {
container.append(resolvedNode);
}
});
Expand Down

0 comments on commit f331330

Please sign in to comment.