Skip to content

Commit

Permalink
Upgrade package
Browse files Browse the repository at this point in the history
  • Loading branch information
niksy committed Oct 8, 2020
1 parent 13dc8c9 commit 25b1ebf
Show file tree
Hide file tree
Showing 23 changed files with 583 additions and 461 deletions.
9 changes: 5 additions & 4 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"root": true,
"extends": [
"niksy",
"niksy/next"
]
"extends": ["niksy", "niksy/next", "prettier"],
"plugins": ["prettier"],
"rules": {
"prettier/prettier": 1
}
}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ npm-debug.log
coverage/
index.cjs.js
index.esm.js
index.cjs.js.map
index.esm.js.map
5 changes: 5 additions & 0 deletions .huskyrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"hooks": {
"pre-commit": "lint-staged"
}
}
8 changes: 8 additions & 0 deletions .lintstagedrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"*.js": ["eslint --fix", "git add"],
"*.md": ["prettier --ignore-path .gitignore --write", "git add"],
".!(npm)*rc": [
"prettier --ignore-path .gitignore --parser json --write",
"git add"
]
}
19 changes: 19 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"printWidth": 80,
"tabWidth": 4,
"useTabs": true,
"semi": true,
"singleQuote": true,
"quoteProps": "preserve",
"jsxSingleQuote": true,
"trailingComma": "none",
"bracketSpacing": true,
"jsxBracketSameLine": false,
"arrowParens": "always",
"requirePragma": false,
"insertPragma": false,
"proseWrap": "always",
"htmlWhitespaceSensitivity": "css",
"vueIndentScriptAndStyle": false,
"endOfLine": "lf"
}
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
language: node_js
node_js:
- '8'
- '12'
44 changes: 29 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,31 @@ Query PostCSS AST with CSS selectors.

Supported selectors are:

* [Type selectors][mdn-type-selector]: `rule`, `atrule`, `decl`, `comment`
* [Universal selector](mdn-universal-selector): `*`
* [Attribute selectors][mdn-attribute-selector]: `[attr=value]`, `[attr=value]`, `[attr~=value]`, `[attr|=value]`, `[attr^=value]`, `[attr$=value]`, `[attr*=value]`
* [Descendant combinator][mdn-descendant-combinator]: `rule decl`
* [Child combinator][mdn-child-combinator]: `atrule > rule`
* [Adjacent sibling combinator][mdn-adjacent-sibling-combinator]: `rule + rule`
* [General sibling combinator][mdn-general-sibling-combinator]: `rule ~ rule`
* Child pseudo classes ([`:first-child`][mdn-first-child], [`:last-child`][mdn-last-child], [`:nth-child`][mdn-nth-child], [`:nth-last-child`][mdn-nth-last-child], [`:only-child`][mdn-only-child]): `rule:first-child`
* Type pseudo classes ([`:first-of-type`][mdn-first-of-type], [`:last-of-type`][mdn-last-of-type], [`:nth-of-type`][mdn-nth-of-type], [`:nth-last-of-type`][mdn-nth-last-of-type], [`:only-of-type`][mdn-only-of-type]): `rule:first-of-type`
* [Empty nodes][mdn-empty]: `rule:empty`
* [Matches][mdn-matches]: `:matches(rule, atrule)`
* [Negation][mdn-not]: `:not(atrule)`
- [Type selectors][mdn-type-selector]: `rule`, `atrule`, `decl`, `comment`
- [Universal selector](mdn-universal-selector): `*`
- [Attribute selectors][mdn-attribute-selector]: `[attr=value]`,
`[attr=value]`, `[attr~=value]`, `[attr|=value]`, `[attr^=value]`,
`[attr$=value]`, `[attr*=value]`
- [Descendant combinator][mdn-descendant-combinator]: `rule decl`
- [Child combinator][mdn-child-combinator]: `atrule > rule`
- [Adjacent sibling combinator][mdn-adjacent-sibling-combinator]:
`rule + rule`
- [General sibling combinator][mdn-general-sibling-combinator]: `rule ~ rule`
- Child pseudo classes ([`:first-child`][mdn-first-child],
[`:last-child`][mdn-last-child], [`:nth-child`][mdn-nth-child],
[`:nth-last-child`][mdn-nth-last-child], [`:only-child`][mdn-only-child]):
`rule:first-child`
- Type pseudo classes ([`:first-of-type`][mdn-first-of-type],
[`:last-of-type`][mdn-last-of-type], [`:nth-of-type`][mdn-nth-of-type],
[`:nth-last-of-type`][mdn-nth-last-of-type],
[`:only-of-type`][mdn-only-of-type]): `rule:first-of-type`
- [Empty nodes][mdn-empty]: `rule:empty`
- [Matches][mdn-matches]: `:matches(rule, atrule)`
- [Negation][mdn-not]: `:not(atrule)`

In addition to standard selectors, there are also custom selectors:

* Attribute selector with regular expression: `[attr="/^value$/i"]`
- Attribute selector with regular expression: `[attr="/^value$/i"]`

## Install

Expand All @@ -31,7 +40,8 @@ npm install postcss-query-ast --save

## Usage

Querying AST from following CSS will give us only `body` rule with `jackie` ID attribute.
Querying AST from following CSS will give us only `body` rule with `jackie` ID
attribute.

```css
body {
Expand All @@ -50,7 +60,7 @@ a {
```js
import queryAst from 'postcss-query-ast';

queryAst('rule[selector="body#jackie"]').then(( ast ) => {
queryAst('rule[selector="body#jackie"]').then((ast) => {
/* [ Rule {
raws: { before: '\n\n', between: ' ', semicolon: true, after: '\n' },
type: 'rule',
Expand Down Expand Up @@ -94,6 +104,8 @@ PostCSS AST.

MIT © [Ivan Nikolić](http://ivannikolic.com)

<!-- prettier-ignore-start -->

[ci]: https://travis-ci.com/niksy/postcss-query-ast
[ci-img]: https://travis-ci.com/niksy/postcss-query-ast.svg?branch=master
[mdn-type-selector]: https://developer.mozilla.org/en-US/docs/Web/CSS/Type_selectors
Expand All @@ -116,3 +128,5 @@ MIT © [Ivan Nikolić](http://ivannikolic.com)
[mdn-empty]: https://developer.mozilla.org/en-US/docs/Web/CSS/:empty
[mdn-matches]: https://developer.mozilla.org/en-US/docs/Web/CSS/:matches
[mdn-not]: https://developer.mozilla.org/en-US/docs/Web/CSS/:not

<!-- prettier-ignore-end -->
37 changes: 18 additions & 19 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,46 @@ import getTag from './lib/node/tag';
import getAttribute from './lib/node/attribute';
import getPseudo from './lib/node/pseudo';

function processSelectors ( selectors, ast ) {

function processSelectors(selectors, ast) {
const nodes = selectors
.map(( rootSelector ) => (
rootSelector
.reduce(( astContainer, selector ) => (
.map((rootSelector) =>
rootSelector.reduce(
(astContainer, selector) =>
astContainer
.map(( node ) => {

switch ( selector.type ) {

.map((node) => {
switch (selector.type) {
case 'combinator':
return getCombinator(node, selector);

case 'attribute':
return getAttribute(node, selector);

case 'pseudo':
return getPseudo(node, selector, processSelectors);
return getPseudo(
node,
selector,
processSelectors
);

case 'tag':
case 'universal':
default:
return getTag(node, selector);

}

})
.reduce(( arr, result ) => [ ...arr, ...result ], [])
.filter(( result ) => result !== null)
), [ast])
))
.reduce(( arr, result ) => [ ...arr, ...result ], []);
.reduce((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 ) => {
export default async (query, postcssAst) => {
const selectorAst = await getSelectorAst(query);
return processSelectors(selectorAst, postcssAst);
};
53 changes: 29 additions & 24 deletions lib/node/attribute.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
const REGEX_REGEX = /^\/(.+)\/([gimuy]+)?$/; // eslint-disable-line unicorn/no-unsafe-regex

function isRegexMatched ( regex, flags, node, selector ) {
function isRegexMatched(regex, flags, node, selector) {
return new RegExp(regex, flags).test(node[selector.attribute]);
}

function whitespaceSeparatedWordTest ( selector ) {
return ( value ) => {
if ( selector.insensitive ) {
function whitespaceSeparatedWordTest(selector) {
return (value) => {
if (selector.insensitive) {
return value.toLowerCase() === selector.value.toLowerCase();
}
return value === selector.value;
};
}

function getRegexByOperator ( operator, value ) {

switch ( operator ) {
function getRegexByOperator(operator, value) {
switch (operator) {
case '|=':
return `^${value}-?`;
case '^=':
Expand All @@ -26,17 +25,16 @@ function getRegexByOperator ( operator, value ) {
default:
return `${value}`;
}

}

export default ( node, selector ) => {

export default (node, selector) => {
const result = [];
const hasSelectorAttribute = selector.attribute in node;

if (
hasSelectorAttribute &&
(typeof selector.operator === 'undefined' || typeof selector.value === 'undefined')
(typeof selector.operator === 'undefined' ||
typeof selector.value === 'undefined')
) {
result.push(node);
}
Expand All @@ -47,38 +45,45 @@ export default ( node, selector ) => {
selector.quoted &&
REGEX_REGEX.test(selector.value)
) {
const [ , regex, flags = '' ] = selector.value.match(REGEX_REGEX);
if ( isRegexMatched(regex, flags, node, selector) ) {
const [, regex, flags = ''] = selector.value.match(REGEX_REGEX);
if (isRegexMatched(regex, flags, node, selector)) {
result.push(node);
}
}

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

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

if ( hasSelectorAttribute && [ '|=', '^=', '$=', '*=' ].includes(selector.operator) ) {

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

if ( regex !== null && isRegexMatched(regex, flags, node, selector) ) {
if (regex !== null && isRegexMatched(regex, flags, node, selector)) {
result.push(node);
}

}

return result;

};
34 changes: 15 additions & 19 deletions lib/node/combinator.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import Container from 'postcss/lib/container';
import PassthroughContainer from '../passthrough-container';

function isValidNode ( node, selector ) {
function isValidNode(node, selector) {
return (
selector &&
(
(selector.type === 'tag' && selector.value === node.type) ||
selector.type === 'universal'
)
((selector.type === 'tag' && selector.value === node.type) ||
selector.type === 'universal')
);
}

export default ( node, selector ) => {

export default (node, selector) => {
const container = new PassthroughContainer();
const result = [];

Expand All @@ -21,40 +18,39 @@ export default ( node, selector ) => {
const nextSelector = selector.next();
const isNodeContainer = node instanceof Container;

if ( selector.value === ' ' && isNodeContainer ) {
node.walk(( resolvedNode ) => {
if (selector.value === ' ' && isNodeContainer) {
node.walk((resolvedNode) => {
container.append(resolvedNode);
});
result.push(container);
}

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

if ( selector.value === '~' ) {
node.parent.each(( resolvedNode, index ) => {
if ( index > nodeIndex && isValidNode(resolvedNode, nextSelector) ) {
if (selector.value === '~') {
node.parent.each((resolvedNode, index) => {
if (index > nodeIndex && isValidNode(resolvedNode, nextSelector)) {
container.append(resolvedNode);
}
});
result.push(container);
}

if ( selector.value === '>' && isNodeContainer ) {
node.each(( resolvedNode ) => {
if ( nextSelector && isValidNode(resolvedNode, nextSelector) ) {
if (selector.value === '>' && isNodeContainer) {
node.each((resolvedNode) => {
if (nextSelector && isValidNode(resolvedNode, nextSelector)) {
container.append(resolvedNode);
}
});
result.push(container);
}

return result;

};
Loading

0 comments on commit 25b1ebf

Please sign in to comment.