Skip to content

Commit

Permalink
fix(postcss-reduce-idents): handle uppercase
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-akait committed Jan 31, 2019
1 parent 8d48058 commit 9109c3a
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 17 deletions.
82 changes: 80 additions & 2 deletions packages/postcss-reduce-idents/src/__tests__/index.js
Expand Up @@ -13,33 +13,67 @@ test(
'@keyframes a{0%{color:#fff}to{color:#000}}.one{animation-name:a}'
);

test(
'should rename keyframes (uppercase)',
processCSS,
'@KEYFRAMES whiteToBlack{0%{color:#fff}to{color:#000}}.one{ANIMATION-NAME:whiteToBlack}',
'@KEYFRAMES a{0%{color:#fff}to{color:#000}}.one{ANIMATION-NAME:a}'
);

test(
'should rename multiple keyframes',
processCSS,
'@keyframes whiteToBlack{0%{color:#fff}to{color:#000}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.one{animation-name:whiteToBlack}.two{animation-name:fadeOut}',
'@keyframes a{0%{color:#fff}to{color:#000}}@keyframes b{0%{opacity:1}to{opacity:0}}.one{animation-name:a}.two{animation-name:b}',
);

test(
'should rename multiple keyframes (uppercase)',
processCSS,
'@KEYFRAMES whiteToBlack{0%{color:#fff}to{color:#000}}@KEYFRAMES fadeOut{0%{opacity:1}to{opacity:0}}.one{animation-name:whiteToBlack}.two{animation-name:fadeOut}',
'@KEYFRAMES a{0%{color:#fff}to{color:#000}}@KEYFRAMES b{0%{opacity:1}to{opacity:0}}.one{animation-name:a}.two{animation-name:b}',
);

test(
'should reuse the same animation name for vendor prefixed keyframes',
processCSS,
'@-webkit-keyframes whiteToBlack{0%{color:#fff}to{color:#000}}@keyframes whiteToBlack{0%{color:#fff}to{color:#000}}div{-webkit-animation-name:whiteToBlack;animation-name:whiteToBlack}',
'@-webkit-keyframes a{0%{color:#fff}to{color:#000}}@keyframes a{0%{color:#fff}to{color:#000}}div{-webkit-animation-name:a;animation-name:a}'
);

test(
'should reuse the same animation name for vendor prefixed keyframes',
processCSS,
'@-WEBKIT-KEYFRAMES whiteToBlack{0%{color:#fff}to{color:#000}}@KEYFRAMES whiteToBlack{0%{color:#fff}to{color:#000}}div{-webkit-animation-name:whiteToBlack;animation-name:whiteToBlack}',
'@-WEBKIT-KEYFRAMES a{0%{color:#fff}to{color:#000}}@KEYFRAMES a{0%{color:#fff}to{color:#000}}div{-webkit-animation-name:a;animation-name:a}'
);

test(
'should support multiple animations',
processCSS,
'@keyframes one{0%{transform:rotate(0deg)}to{transform:rotate(360deg)}}@keyframes two{0%{border-width:0;opacity:0}}.loader{animation:one 1250ms infinite linear, two .3s ease-out both}',
'@keyframes a{0%{transform:rotate(0deg)}to{transform:rotate(360deg)}}@keyframes b{0%{border-width:0;opacity:0}}.loader{animation:a 1250ms infinite linear, b .3s ease-out both}'
);

test(
'should support multiple animations (uppercase)',
processCSS,
'@KEYFRAMES one{0%{transform:rotate(0deg)}to{transform:rotate(360deg)}}@KEYFRAMES two{0%{border-width:0;opacity:0}}.loader{animation:one 1250ms infinite linear, two .3s ease-out both}',
'@KEYFRAMES a{0%{transform:rotate(0deg)}to{transform:rotate(360deg)}}@KEYFRAMES b{0%{border-width:0;opacity:0}}.loader{animation:a 1250ms infinite linear, b .3s ease-out both}'
);

test(
'should not touch animation names that are not defined in the file',
passthroughCSS,
'.one{animation-name:fadeInUp}'
);

test(
'should not touch animation names that are not defined in the file (uppercase)',
passthroughCSS,
'.one{ANIMATION-NAME:fadeInUp}'
);

test(
'should not touch keyframes that are not referenced in the file',
passthroughCSS,
Expand All @@ -59,6 +93,13 @@ test(
'@counter-style a{system:extends decimal;suffix:"> "}ol{list-style:a}'
);

test(
'should rename counter styles (uppercase)',
processCSS,
'@COUNTER-STYLE custom{system:extends decimal;suffix:"> "}ol{LIST-STYLE:custom}',
'@COUNTER-STYLE a{system:extends decimal;suffix:"> "}ol{LIST-STYLE:a}'
);

test(
'should rename multiple counter styles & be aware of extensions',
processCSS,
Expand All @@ -85,6 +126,13 @@ test(
'body{counter-reset:a}h3:before{counter-increment:a;content:"Section" counter(a) ": "}'
);

test(
'should rename counters (uppercase)',
processCSS,
'body{COUNTER-RESET:section}h3:before{COUNTER-INCREMENT:section;CONTENT:"Section" counter(section) ": "}',
'body{COUNTER-RESET:a}h3:before{COUNTER-INCREMENT:a;CONTENT:"Section" counter(a) ": "}'
);

test(
'should rename counters (2)',
processCSS,
Expand All @@ -99,6 +147,13 @@ test(
'li{counter-increment:a}li::marker{content:"(" counters(a,".") ")"}'
);

test(
'should rename counters (3) (uppercase)',
processCSS,
'li{counter-increment:list-item}li::marker{content:"(" COUNTERS(list-item,".") ")"}',
'li{counter-increment:a}li::marker{content:"(" COUNTERS(a,".") ")"}'
);

test(
'should rename multiple counters',
processCSS,
Expand Down Expand Up @@ -192,6 +247,25 @@ test(
].join('')
);

test(
'should rename grid-template-areas and grid-area (uppercase)',
processCSS,
[
'body{GRID-TEMPLATE-AREAS:"head head" \n"nav main"\n"nav foot";}',
'header { GRID-AREA: head }',
'nav{GRID-AREA:nav}',
'main{GRID-AREA:main}',
'footer{GRID-AREA:foot}',
].join(''),
[
'body{GRID-TEMPLATE-AREAS:"a a" "b c" "b d";}',
'header { GRID-AREA: a }',
'nav{GRID-AREA:b}',
'main{GRID-AREA:c}',
'footer{GRID-AREA:d}',
].join('')
);

test(
'should rename grid-template short syntax',
processCSS,
Expand Down Expand Up @@ -254,10 +328,14 @@ test('should not generate same ident when plugin instance is reused', t => {
const instance = postcss(plugin);
return Promise.all([
instance.process('@keyframes whiteToBlack{0%{color:#fff}to{color:#000}}.one{animation-name:whiteToBlack}'),
instance.process('@KEYFRAMES whiteToBlack{0%{color:#fff}to{color:#000}}.one{animation-name:whiteToBlack}'),
instance.process('@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.two{animation-name:fadeOut}'),
]).then(([result1, result2]) => {
instance.process('@KEYFRAMES fadeOut{0%{opacity:1}to{opacity:0}}.two{animation-name:fadeOut}'),
]).then(([result1, result2, result3, result4]) => {
t.is(result1.css, '@keyframes a{0%{color:#fff}to{color:#000}}.one{animation-name:a}');
t.is(result2.css, '@keyframes b{0%{opacity:1}to{opacity:0}}.two{animation-name:b}');
t.is(result2.css, '@KEYFRAMES a{0%{color:#fff}to{color:#000}}.one{animation-name:a}');
t.is(result3.css, '@keyframes b{0%{opacity:1}to{opacity:0}}.two{animation-name:b}');
t.is(result4.css, '@KEYFRAMES b{0%{opacity:1}to{opacity:0}}.two{animation-name:b}');
});
});

Expand Down
1 change: 1 addition & 0 deletions packages/postcss-reduce-idents/src/index.js
Expand Up @@ -14,6 +14,7 @@ export default postcss.plugin('postcss-reduce-idents', ({
encoder = encode,
} = {}) => {
const reducers = [];

counter && reducers.push(counterReducer());
counterStyle && reducers.push(counterStyleReducer());
keyframes && reducers.push(keyframesReducer());
Expand Down
1 change: 1 addition & 0 deletions packages/postcss-reduce-idents/src/lib/cache.js
Expand Up @@ -2,6 +2,7 @@ export default function (value, encoder, cache) {
if (cache[value]) {
return;
}

cache[value] = {
ident: encoder(value, Object.keys(cache).length),
count: 0,
Expand Down
10 changes: 7 additions & 3 deletions packages/postcss-reduce-idents/src/lib/counter-style.js
Expand Up @@ -25,14 +25,15 @@ export default function () {

if (
type === 'atrule' &&
/counter-style/.test(name) &&
RESERVED_KEYWORDS.indexOf(node.params) === -1
/counter-style/i.test(name) &&
RESERVED_KEYWORDS.indexOf(node.params.toLowerCase()) === -1
) {
addToCache(node.params, encoder, cache);

atRules.push(node);
}

if (type === 'decl' && /(list-style|system)/.test(prop)) {
if (type === 'decl' && /(list-style|system)/i.test(prop)) {
decls.push(node);
}
},
Expand All @@ -43,13 +44,16 @@ export default function () {
decl.value = valueParser(decl.value).walk(node => {
if (node.type === 'word' && node.value in cache) {
cache[node.value].count++;

node.value = cache[node.value].ident;
}
}).toString();
});

// Iterate each at rule and change their name if references to them have been found
atRules.forEach(rule => {
const cached = cache[rule.params];

if (cached && cached.count > 0) {
rule.params = cached.ident;
}
Expand Down
18 changes: 14 additions & 4 deletions packages/postcss-reduce-idents/src/lib/counter.js
Expand Up @@ -19,46 +19,56 @@ export default function () {
return;
}

if (/counter-(reset|increment)/.test(prop)) {
if (/counter-(reset|increment)/i.test(prop)) {
node.value = valueParser(node.value).walk(child => {
if (
child.type === 'word' &&
!isNum(child) &&
RESERVED_KEYWORDS.indexOf(child.value) === -1
RESERVED_KEYWORDS.indexOf(child.value.toLowerCase()) === -1
) {
addToCache(child.value, encoder, cache);

child.value = cache[child.value].ident;
}
});

declOneCache.push(node);
} else if (/content/.test(prop)) {
} else if (/content/i.test(prop)) {
declTwoCache.push(node);
}
},

transform () {
declTwoCache.forEach(decl => {
decl.value = valueParser(decl.value).walk(node => {
const {type, value} = node;
const {type} = node;

const value = node.value.toLowerCase();

if (type === 'function' && (value === 'counter' || value === 'counters')) {
walk(node.nodes, child => {
if (child.type === 'word' && child.value in cache) {
cache[child.value].count++;

child.value = cache[child.value].ident;
}
});
}

if (type === 'space') {
node.value = ' ';
}

return false;
}).toString();
});

declOneCache.forEach(decl => {
decl.value = decl.value.walk(node => {
if (node.type === 'word' && !isNum(node)) {
Object.keys(cache).forEach(key => {
const cached = cache[key];

if (cached.ident === node.value && !cached.count) {
node.value = key;
}
Expand Down
2 changes: 2 additions & 0 deletions packages/postcss-reduce-idents/src/lib/encode.js
Expand Up @@ -4,6 +4,7 @@ export default function encode (val, num) {
let character = num % base;
let result = characters[character];
let remainder = Math.floor(num / base);

if (remainder) {
base = 64;
characters = characters + '0123456789-_';
Expand All @@ -13,5 +14,6 @@ export default function encode (val, num) {
result = result + characters[character];
}
}

return result;
};
14 changes: 9 additions & 5 deletions packages/postcss-reduce-idents/src/lib/grid-template.js
Expand Up @@ -16,45 +16,49 @@ export default function () {
return;
}

if (/(grid-template|grid-template-areas)/.test(node.prop)) {
if (/(grid-template|grid-template-areas)/i.test(node.prop)) {
valueParser(node.value).walk(child => {
if (child.type === 'string') {
child.value.split(/\s+/).forEach(word => {
if (/\.+/.test(word)) { // reduce empty zones to a single `.`
node.value = node.value.replace(word, ".");
} else if (word && RESERVED_KEYWORDS.indexOf(word) === -1) {
} else if (word && RESERVED_KEYWORDS.indexOf(word.toLowerCase()) === -1) {
addToCache(word, encoder, cache);
}
});
}
});

declCache.push(node);
} else if (node.prop === 'grid-area') {
} else if (node.prop.toLowerCase() === 'grid-area') {
valueParser(node.value).walk(child => {
if (child.type === 'word' && RESERVED_KEYWORDS.indexOf(child.value) === -1) {
addToCache(child.value, encoder, cache);
}
});

declCache.push(node);
}
},

transform () {
declCache.forEach(decl => {
decl.value = valueParser(decl.value).walk(node => {
if (/(grid-template|grid-template-areas)/.test(decl.prop)) {
if (/(grid-template|grid-template-areas)/i.test(decl.prop)) {
node.value.split(/\s+/).forEach(word => {
if (word in cache) {
node.value = node.value.replace(word, cache[word].ident);
}
});
node.value = node.value.replace(/\s+/g, " "); // merge white-spaces
}
if (decl.prop === 'grid-area' && !isNum(node)) {

if (decl.prop.toLowerCase() === 'grid-area' && !isNum(node)) {
if (node.value in cache) {
node.value = cache[node.value].ident;
}
}

return false;
}).toString();
});
Expand Down
9 changes: 6 additions & 3 deletions packages/postcss-reduce-idents/src/lib/keyframes.js
Expand Up @@ -16,27 +16,29 @@ export default function () {

if (
type === 'atrule' &&
/keyframes/.test(name) &&
RESERVED_KEYWORDS.indexOf(node.params) === -1
/keyframes/i.test(name) &&
RESERVED_KEYWORDS.indexOf(node.params.toLowerCase()) === -1
) {
addToCache(node.params, encoder, cache);
atRules.push(node);
}

if (type === 'decl' && /animation/.test(prop)) {
if (type === 'decl' && /animation/i.test(prop)) {
decls.push(node);
}
},

transform () {
let referenced = [];

// Iterate each property and change their names
decls.forEach(decl => {
decl.value = valueParser(decl.value).walk(node => {
if (node.type === 'word' && node.value in cache) {
if (!~referenced.indexOf(node.value)) {
referenced.push(node.value);
}

cache[node.value].count++;
node.value = cache[node.value].ident;
}
Expand All @@ -46,6 +48,7 @@ export default function () {
// Iterate each at rule and change their name if references to them have been found
atRules.forEach(rule => {
const cached = cache[rule.params];

if (cached && cached.count > 0 && !!~referenced.indexOf(rule.params)) {
rule.params = cached.ident;
}
Expand Down

0 comments on commit 9109c3a

Please sign in to comment.