handle empty `class` attributes (#922)
authorAlex Lam S.L <alexlamsl@gmail.com>
Sun, 20 May 2018 18:18:49 +0000 (02:18 +0800)
committerGitHub <noreply@github.com>
Sun, 20 May 2018 18:18:49 +0000 (02:18 +0800)
fixes #921

src/htmlminifier.js
tests/minifier.js

index e382434..7550c72 100644 (file)
@@ -9,10 +9,7 @@ var UglifyJS = require('uglify-js');
 var utils = require('./utils');
 
 function trimWhitespace(str) {
-  if (typeof str !== 'string') {
-    return str;
-  }
-  return str.replace(/^[ \n\r\t\f]+/, '').replace(/[ \n\r\t\f]+$/, '');
+  return str && str.replace(/^[ \n\r\t\f]+/, '').replace(/[ \n\r\t\f]+$/, '');
 }
 
 function collapseWhitespaceAll(str) {
@@ -258,7 +255,7 @@ function isSrcset(attrName, tag) {
 }
 
 function cleanAttributeValue(tag, attrName, attrValue, options, attrs) {
-  if (attrValue && isEventAttribute(attrName, options)) {
+  if (isEventAttribute(attrName, options)) {
     attrValue = trimWhitespace(attrValue).replace(/^javascript:\s*/i, '');
     return options.minifyJS(attrValue, true);
   }
@@ -314,7 +311,7 @@ function cleanAttributeValue(tag, attrName, attrValue, options, attrs) {
       return (+numString).toString();
     });
   }
-  else if (attrValue && options.customAttrCollapse && options.customAttrCollapse.test(attrName)) {
+  else if (options.customAttrCollapse && options.customAttrCollapse.test(attrName)) {
     attrValue = attrValue.replace(/\n+|\r+|\s{2,}/g, '');
   }
   else if (tag === 'script' && attrName === 'type') {
@@ -522,7 +519,7 @@ function canTrimWhitespace(tag) {
 }
 
 function normalizeAttr(attr, attrs, tag, options) {
-  var attrName = options.caseSensitive ? attr.name : attr.name.toLowerCase(),
+  var attrName = options.name(attr.name),
       attrValue = attr.value;
 
   if (options.decodeEntities && attrValue) {
@@ -538,7 +535,9 @@ function normalizeAttr(attr, attrs, tag, options) {
     return;
   }
 
-  attrValue = cleanAttributeValue(tag, attrName, attrValue, options, attrs);
+  if (attrValue) {
+    attrValue = cleanAttributeValue(tag, attrName, attrValue, options, attrs);
+  }
 
   if (options.removeEmptyAttributes &&
       canDeleteEmptyAttribute(tag, attrName, attrValue, options)) {
@@ -615,6 +614,9 @@ function identity(value) {
 
 function processOptions(values) {
   var options = {
+    name: function(name) {
+      return name.toLowerCase();
+    },
     canCollapseWhitespace: canCollapseWhitespace,
     canTrimWhitespace: canTrimWhitespace,
     html5: true,
@@ -631,7 +633,12 @@ function processOptions(values) {
   };
   Object.keys(values).forEach(function(key) {
     var value = values[key];
-    if (key === 'log') {
+    if (key === 'caseSensitive') {
+      if (value) {
+        options.name = identity;
+      }
+    }
+    else if (key === 'log') {
       if (typeof value === 'function') {
         options.log = value;
       }
@@ -732,7 +739,7 @@ function createSortFns(value, options, uidIgnore, uidAttr) {
 
   function attrNames(attrs) {
     return attrs.map(function(attr) {
-      return options.caseSensitive ? attr.name : attr.name.toLowerCase();
+      return options.name(attr.name);
     });
   }
 
@@ -756,7 +763,7 @@ function createSortFns(value, options, uidIgnore, uidAttr) {
         }
         for (var i = 0, len = attrs.length; i < len; i++) {
           var attr = attrs[i];
-          if (classChain && (options.caseSensitive ? attr.name : attr.name.toLowerCase()) === 'class') {
+          if (classChain && attr.value && options.name(attr.name) === 'class') {
             classChain.add(trimWhitespace(attr.value).split(/[ \t\n\f\r]+/).filter(shouldSkipUIDs));
           }
           else if (options.processScripts && attr.name.toLowerCase() === 'type') {
@@ -946,16 +953,13 @@ function minify(value, options, partialMarkup) {
     html5: options.html5,
 
     start: function(tag, attrs, unary, unarySlash, autoGenerated) {
-      var lowerTag = tag.toLowerCase();
-
-      if (lowerTag === 'svg') {
+      if (tag.toLowerCase() === 'svg') {
         options = Object.create(options);
-        options.keepClosingSlash = true;
         options.caseSensitive = true;
+        options.keepClosingSlash = true;
+        options.name = identity;
       }
-
-      tag = options.caseSensitive ? tag : lowerTag;
-
+      tag = options.name(tag);
       currentTag = tag;
       charsPrevTag = tag;
       if (!inlineTextTags(tag)) {
@@ -1035,11 +1039,10 @@ function minify(value, options, partialMarkup) {
       }
     },
     end: function(tag, attrs, autoGenerated) {
-      var lowerTag = tag.toLowerCase();
-      if (lowerTag === 'svg') {
+      if (tag.toLowerCase() === 'svg') {
         options = Object.getPrototypeOf(options);
       }
-      tag = options.caseSensitive ? tag : lowerTag;
+      tag = options.name(tag);
 
       // check if current tag is in a whitespace stack
       if (options.collapseWhitespace) {
index 233d201..fe425f5 100644 (file)
@@ -3229,6 +3229,10 @@ QUnit.test('sort style classes', function(assert) {
     removeAttributeQuotes: true,
     sortClassName: true
   }), output);
+
+  input = '<div class></div>';
+  assert.equal(minify(input, { sortClassName: false }), input);
+  assert.equal(minify(input, { sortClassName: true }), input);
 });
 
 QUnit.test('decode entity characters', function(assert) {