diff --git a/lib/index.js b/lib/index.js index ddeaba5..42686af 100644 --- a/lib/index.js +++ b/lib/index.js @@ -3,21 +3,98 @@ import postcss from 'postcss'; const modDelim = process.env.REBEM_MOD_DELIM || '_'; const elemDelim = process.env.REBEM_ELEM_DELIM || '__'; +function buildSelector (ctx, mod) { + let selector = '.'; + + if (ctx.blockName) { + selector += ctx.blockName; + } + if (ctx.elemName) { + selector += elemDelim + ctx.elemName; + } + if (mod) { + selector += modDelim + mod; + } + + return selector; +} + export default postcss.plugin('rebem-css', () => (css) => { css.walkRules((rule) => { rule.selector = rule.selector - // :block(block) → .block - .replace(/:block\(([\w-]+)\)/g, '.$1') - // :elem(elem) → __elem - .replace(/:elem\(([\w-]+)\)/g, elemDelim + '$1') - // :mod(mod) → _mod - // :mod(mod val) → _mod_val - .replace(/:mod\(([\w-]+)\s?([\w-]+)?\)/g, (match, mod, val) => { - if (val) { - return modDelim + mod + modDelim + val; + .replace(/(:block\(.+)/g, (match, rawSelector) => { + + // ":block(b):mod(m v) div :block(b2)" -> [":block(b):mod(m v)", "div", "block(b2)"] + const groups = rawSelector.split(/\s+(?![\w\s->'",]+\))/gi); + + const re = /(:+)([\w-]+)(\((['",\w->\s]+)\))?/g; + const ctx = { blockName: false, elemName: false }; + + // Convert all groups to CSS selectors + // :block(b):mod(m v) -> .b_m_v + + const result = []; + + for (const group of groups) { + if (!group.match(re)) { + result.push(group); + continue; + } + + let selector = ''; + let mathes = null; + let requiredBuild = false; + + while ((mathes = re.exec(group)) !== null) { + const _spliter = 1; + const _tag = 2; + const _rawValue = 3; + const _value = 4; + const spliter = mathes[_spliter]; + const tag = mathes[_tag]; + const rawValue = mathes[_rawValue]; + const value = mathes[_value] ? + mathes[_value].replace(/([\(\)'"])/g, '').trim() : false; + + + if (tag === 'block') { + requiredBuild = true; + ctx.blockName = value; + ctx.elemName = false; + continue; + } + + if (tag === 'elem') { + requiredBuild = true; + ctx.elemName = value; + continue; + } + + if (tag === 'mod') { + requiredBuild = false; + const mod = value.replace(/(\s?->\s?|,\s?|\s+?)/g, modDelim); + + selector += buildSelector(ctx, mod); + continue; + } + + // For pseudo-classes + selector += requiredBuild ? buildSelector(ctx) : ''; + selector += spliter + tag; + if (rawValue) { + selector += rawValue; + } + + requiredBuild = false; + } + if (requiredBuild) { + selector += buildSelector(ctx); + } + + result.push(selector); } - return modDelim + mod; + return result.join(' '); }); }); }); diff --git a/package.json b/package.json index a682207..dc07db1 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,11 @@ "name": "rebem-css", "version": "0.2.0", "description": "BEM syntax for CSS", - "keywords": [ "rebem", "bem", "css" ], + "keywords": [ + "rebem", + "bem", + "css" + ], "homepage": "https://github.com/rebem/css", "repository": "rebem/css", "maintainers": [ @@ -10,23 +14,23 @@ "Denis Koltsov (https://github.com/mistadikay)" ], "main": "build/index.js", - "files": [ "build/" ], + "files": [ + "build/" + ], "dependencies": { "postcss": "5.0.x" }, "devDependencies": { - "start-babel-cli": "1.x.x", - "start-rebem-preset": "0.x.x", - - "babel-preset-es2015": "6.6.x", - "babel-plugin-add-module-exports": "0.1.x", - "babel-eslint": "5.0.x", + "babel-plugin-add-module-exports": "0.1.x", + "babel-preset-es2015": "6.6.x", + "eslint-config-rebem": "1.1.x", "eslint-plugin-babel": "3.1.x", - "eslint-config-rebem": "0.3.x", - + "estraverse-fb": "^1.3.1", + "husky": "0.11.x", "require-uncached": "1.0.x", - "husky": "0.11.x" + "start-babel-cli": "1.x.x", + "start-rebem-preset": "0.x.x" }, "scripts": { "start": "start-runner start-rebem-preset", diff --git a/readme.md b/readme.md index f5f3a89..83c55f9 100644 --- a/readme.md +++ b/readme.md @@ -24,6 +24,9 @@ It just replaces substrings in selectors: ```css :block(block):elem(elem) {} .block__elem {} + +:block(block):elem(elem) :elem(elem2) :block(block2):elem(elem) {} +.block__elem .block__elem2 .block2__elem {} ``` #### `:mod()` @@ -34,6 +37,9 @@ It just replaces substrings in selectors: :block(block):mod(mod val) {} .block_mod_val {} + +:block(block):mod(mod val):mod(mod2) {} +.block_mod_val.block_mod2 {} ``` ```css @@ -51,7 +57,9 @@ It's just a custom pseudo-classes, so you can use it with Less or any other CSS ```less :block(block) { &:mod(mod) { - + :elem(elem) { + + } } &:elem(elem) { diff --git a/test/lib/index.js b/test/lib/index.js index 46c9532..ecc3599 100644 --- a/test/lib/index.js +++ b/test/lib/index.js @@ -22,10 +22,38 @@ describe('plugin', () => { it('multiple blocks', () => { test( - ':block(block1) :block(block2)', + ':block(block1):elem(elem) :block(block2)', + '.block1__elem .block2' + ); + }); + + it('block name with double quotes', () => { + test( + ':block("block1") :block("block2")', + '.block1 .block2' + ); + }); + + it('block name with single quotes', () => { + test( + ':block(\'block1\') :block(\'block2\')', '.block1 .block2' ); }); + + it('with pseudo classes', () => { + test( + ':root :block(block1):mod(m v):hover::before :block(block1):nth-of-type(2)', + ':root .block1_m_v:hover::before .block1:nth-of-type(2)' + ); + }); + + it('with other tags', () => { + test( + ':block(block1) div :block(bl2) img', + '.block1 div .bl2 img' + ); + }); }); describe('elem', () => { @@ -42,6 +70,13 @@ describe('plugin', () => { '.block1__elem1 .block2__elem2' ); }); + + it('block multiple short elems', () => { + test( + ':block(block1):elem(elem1) :elem(elem2) :elem(elem3)', + '.block1__elem1 .block1__elem2 .block1__elem3' + ); + }); }); describe('mod', () => { @@ -53,6 +88,13 @@ describe('plugin', () => { ); }); + it('block multiple short mods', () => { + test( + ':block(block):mod(mod):mod(mod2)', + '.block_mod.block_mod2' + ); + }); + it('multiple blocks shorts mods', () => { test( ':block(block1):mod(mod1) :block(block2):mod(mod2)', @@ -60,6 +102,13 @@ describe('plugin', () => { ); }); + it('multiple blocks mods with delimeter "-" in value', () => { + test( + ':block(block):mod(mod val-1) :block(block):elem(icon)', + '.block_mod_val-1 .block__icon' + ); + }); + it('block mod', () => { test( ':block(block):mod(mod val)', @@ -67,12 +116,33 @@ describe('plugin', () => { ); }); + it('block mod with double quotes', () => { + test( + ':block("block1"):mod("mod", "val")', + '.block1_mod_val' + ); + }); + + it('block mod with single quotes', () => { + test( + ':block(\'block1\'):mod(\'mod\',\'val\')', + '.block1_mod_val' + ); + }); + it('multiple blocks mods', () => { test( ':block(block1):mod(mod1 val1) :block(block2):mod(mod2 val2)', '.block1_mod1_val1 .block2_mod2_val2' ); }); + + it('mod val with delimeter "->"', () => { + test( + ':block(block1):mod(mod1 -> val1) :block(block2):mod("mod2" -> "val2")', + '.block1_mod1_val1 .block2_mod2_val2' + ); + }); }); describe('elem', () => { @@ -82,6 +152,12 @@ describe('plugin', () => { '.block__elem_mod' ); }); + it('elem multiple short mods', () => { + test( + ':block(block):elem(elem):mod(mod):mod(mod2)', + '.block__elem_mod.block__elem_mod2' + ); + }); it('multiple elems short mods', () => { test(