diff options
Diffstat (limited to 'node_modules/csso/lib/compressor/compress')
13 files changed, 846 insertions, 0 deletions
diff --git a/node_modules/csso/lib/compressor/compress/Atrule.js b/node_modules/csso/lib/compressor/compress/Atrule.js new file mode 100644 index 00000000..c5410ab9 --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/Atrule.js @@ -0,0 +1,9 @@ +var resolveKeyword = require('../../utils/names.js').keyword; +var compressKeyframes = require('./atrule/keyframes.js'); + +module.exports = function(node) { + // compress @keyframe selectors + if (resolveKeyword(node.name).name === 'keyframes') { + compressKeyframes(node); + } +}; diff --git a/node_modules/csso/lib/compressor/compress/Attribute.js b/node_modules/csso/lib/compressor/compress/Attribute.js new file mode 100644 index 00000000..99cafdce --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/Attribute.js @@ -0,0 +1,33 @@ +// Can unquote attribute detection +// Adopted implementation of Mathias Bynens +// https://github.com/mathiasbynens/mothereff.in/blob/master/unquoted-attributes/eff.js +var escapesRx = /\\([0-9A-Fa-f]{1,6})[ \t\n\f\r]?|\\./g; +var blockUnquoteRx = /^(-?\d|--)|[\u0000-\u002c\u002e\u002f\u003A-\u0040\u005B-\u005E\u0060\u007B-\u009f]/; + +function canUnquote(value) { + if (value === '' || value === '-') { + return; + } + + // Escapes are valid, so replace them with a valid non-empty string + value = value.replace(escapesRx, 'a'); + + return !blockUnquoteRx.test(value); +} + +module.exports = function(node) { + var attrValue = node.value; + + if (!attrValue || attrValue.type !== 'String') { + return; + } + + var unquotedValue = attrValue.value.replace(/^(.)(.*)\1$/, '$2'); + if (canUnquote(unquotedValue)) { + node.value = { + type: 'Identifier', + info: attrValue.info, + name: unquotedValue + }; + } +}; diff --git a/node_modules/csso/lib/compressor/compress/Dimension.js b/node_modules/csso/lib/compressor/compress/Dimension.js new file mode 100644 index 00000000..e614ad08 --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/Dimension.js @@ -0,0 +1,54 @@ +var packNumber = require('./Number.js').pack; +var LENGTH_UNIT = { + // absolute length units + 'px': true, + 'mm': true, + 'cm': true, + 'in': true, + 'pt': true, + 'pc': true, + + // relative length units + 'em': true, + 'ex': true, + 'ch': true, + 'rem': true, + + // viewport-percentage lengths + 'vh': true, + 'vw': true, + 'vmin': true, + 'vmax': true, + 'vm': true +}; + +module.exports = function compressDimension(node, item) { + var value = packNumber(node.value); + + node.value = value; + + if (value === '0' && this.declaration) { + var unit = node.unit.toLowerCase(); + + // only length values can be compressed + if (!LENGTH_UNIT.hasOwnProperty(unit)) { + return; + } + + // issue #200: don't remove units in flex property as it could change value meaning + if (this.declaration.property.name === 'flex') { + return; + } + + // issue #222: don't remove units inside calc + if (this['function'] && this['function'].name === 'calc') { + return; + } + + item.data = { + type: 'Number', + info: node.info, + value: value + }; + } +}; diff --git a/node_modules/csso/lib/compressor/compress/Number.js b/node_modules/csso/lib/compressor/compress/Number.js new file mode 100644 index 00000000..8e17e118 --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/Number.js @@ -0,0 +1,22 @@ +function packNumber(value) { + // 100 -> '100' + // 00100 -> '100' + // +100 -> '100' + // -100 -> '-100' + // 0.123 -> '.123' + // 0.12300 -> '.123' + // 0.0 -> '' + // 0 -> '' + value = String(value).replace(/^(?:\+|(-))?0*(\d*)(?:\.0*|(\.\d*?)0*)?$/, '$1$2$3'); + + if (value.length === 0 || value === '-') { + value = '0'; + } + + return value; +}; + +module.exports = function(node) { + node.value = packNumber(node.value); +}; +module.exports.pack = packNumber; diff --git a/node_modules/csso/lib/compressor/compress/String.js b/node_modules/csso/lib/compressor/compress/String.js new file mode 100644 index 00000000..01a20a2c --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/String.js @@ -0,0 +1,12 @@ +module.exports = function(node) { + var value = node.value; + + // remove escaped \n, i.e. + // .a { content: "foo\ + // bar"} + // -> + // .a { content: "foobar" } + value = value.replace(/\\\n/g, ''); + + node.value = value; +}; diff --git a/node_modules/csso/lib/compressor/compress/Url.js b/node_modules/csso/lib/compressor/compress/Url.js new file mode 100644 index 00000000..040a3590 --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/Url.js @@ -0,0 +1,33 @@ +var UNICODE = '\\\\[0-9a-f]{1,6}(\\r\\n|[ \\n\\r\\t\\f])?'; +var ESCAPE = '(' + UNICODE + '|\\\\[^\\n\\r\\f0-9a-fA-F])'; +var NONPRINTABLE = '\u0000\u0008\u000b\u000e-\u001f\u007f'; +var SAFE_URL = new RegExp('^(' + ESCAPE + '|[^\"\'\\(\\)\\\\\\s' + NONPRINTABLE + '])*$', 'i'); + +module.exports = function(node) { + var value = node.value; + + if (value.type !== 'String') { + return; + } + + var quote = value.value[0]; + var url = value.value.substr(1, value.value.length - 2); + + // convert `\\` to `/` + url = url.replace(/\\\\/g, '/'); + + // remove quotes when safe + // https://www.w3.org/TR/css-syntax-3/#url-unquoted-diagram + if (SAFE_URL.test(url)) { + node.value = { + type: 'Raw', + info: node.value.info, + value: url + }; + } else { + // use double quotes if string has no double quotes + // otherwise use original quotes + // TODO: make better quote type selection + node.value.value = url.indexOf('"') === -1 ? '"' + url + '"' : quote + url + quote; + } +}; diff --git a/node_modules/csso/lib/compressor/compress/Value.js b/node_modules/csso/lib/compressor/compress/Value.js new file mode 100644 index 00000000..8196ba82 --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/Value.js @@ -0,0 +1,18 @@ +var resolveName = require('../../utils/names.js').property; +var handlers = { + 'font': require('./property/font.js'), + 'font-weight': require('./property/font-weight.js'), + 'background': require('./property/background.js') +}; + +module.exports = function compressValue(node) { + if (!this.declaration) { + return; + } + + var property = resolveName(this.declaration.property.name); + + if (handlers.hasOwnProperty(property.name)) { + handlers[property.name](node); + } +}; diff --git a/node_modules/csso/lib/compressor/compress/atrule/keyframes.js b/node_modules/csso/lib/compressor/compress/atrule/keyframes.js new file mode 100644 index 00000000..74eff2c6 --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/atrule/keyframes.js @@ -0,0 +1,21 @@ +module.exports = function(node) { + node.block.rules.each(function(ruleset) { + ruleset.selector.selectors.each(function(simpleselector) { + simpleselector.sequence.each(function(data, item) { + if (data.type === 'Percentage' && data.value === '100') { + item.data = { + type: 'Identifier', + info: data.info, + name: 'to' + }; + } else if (data.type === 'Identifier' && data.name === 'from') { + item.data = { + type: 'Percentage', + info: data.info, + value: '0' + }; + } + }); + }); + }); +}; diff --git a/node_modules/csso/lib/compressor/compress/color.js b/node_modules/csso/lib/compressor/compress/color.js new file mode 100644 index 00000000..cf2a3236 --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/color.js @@ -0,0 +1,489 @@ +var List = require('../../utils/list.js'); +var packNumber = require('./Number.js').pack; + +// http://www.w3.org/TR/css3-color/#svg-color +var NAME_TO_HEX = { + 'aliceblue': 'f0f8ff', + 'antiquewhite': 'faebd7', + 'aqua': '0ff', + 'aquamarine': '7fffd4', + 'azure': 'f0ffff', + 'beige': 'f5f5dc', + 'bisque': 'ffe4c4', + 'black': '000', + 'blanchedalmond': 'ffebcd', + 'blue': '00f', + 'blueviolet': '8a2be2', + 'brown': 'a52a2a', + 'burlywood': 'deb887', + 'cadetblue': '5f9ea0', + 'chartreuse': '7fff00', + 'chocolate': 'd2691e', + 'coral': 'ff7f50', + 'cornflowerblue': '6495ed', + 'cornsilk': 'fff8dc', + 'crimson': 'dc143c', + 'cyan': '0ff', + 'darkblue': '00008b', + 'darkcyan': '008b8b', + 'darkgoldenrod': 'b8860b', + 'darkgray': 'a9a9a9', + 'darkgrey': 'a9a9a9', + 'darkgreen': '006400', + 'darkkhaki': 'bdb76b', + 'darkmagenta': '8b008b', + 'darkolivegreen': '556b2f', + 'darkorange': 'ff8c00', + 'darkorchid': '9932cc', + 'darkred': '8b0000', + 'darksalmon': 'e9967a', + 'darkseagreen': '8fbc8f', + 'darkslateblue': '483d8b', + 'darkslategray': '2f4f4f', + 'darkslategrey': '2f4f4f', + 'darkturquoise': '00ced1', + 'darkviolet': '9400d3', + 'deeppink': 'ff1493', + 'deepskyblue': '00bfff', + 'dimgray': '696969', + 'dimgrey': '696969', + 'dodgerblue': '1e90ff', + 'firebrick': 'b22222', + 'floralwhite': 'fffaf0', + 'forestgreen': '228b22', + 'fuchsia': 'f0f', + 'gainsboro': 'dcdcdc', + 'ghostwhite': 'f8f8ff', + 'gold': 'ffd700', + 'goldenrod': 'daa520', + 'gray': '808080', + 'grey': '808080', + 'green': '008000', + 'greenyellow': 'adff2f', + 'honeydew': 'f0fff0', + 'hotpink': 'ff69b4', + 'indianred': 'cd5c5c', + 'indigo': '4b0082', + 'ivory': 'fffff0', + 'khaki': 'f0e68c', + 'lavender': 'e6e6fa', + 'lavenderblush': 'fff0f5', + 'lawngreen': '7cfc00', + 'lemonchiffon': 'fffacd', + 'lightblue': 'add8e6', + 'lightcoral': 'f08080', + 'lightcyan': 'e0ffff', + 'lightgoldenrodyellow': 'fafad2', + 'lightgray': 'd3d3d3', + 'lightgrey': 'd3d3d3', + 'lightgreen': '90ee90', + 'lightpink': 'ffb6c1', + 'lightsalmon': 'ffa07a', + 'lightseagreen': '20b2aa', + 'lightskyblue': '87cefa', + 'lightslategray': '789', + 'lightslategrey': '789', + 'lightsteelblue': 'b0c4de', + 'lightyellow': 'ffffe0', + 'lime': '0f0', + 'limegreen': '32cd32', + 'linen': 'faf0e6', + 'magenta': 'f0f', + 'maroon': '800000', + 'mediumaquamarine': '66cdaa', + 'mediumblue': '0000cd', + 'mediumorchid': 'ba55d3', + 'mediumpurple': '9370db', + 'mediumseagreen': '3cb371', + 'mediumslateblue': '7b68ee', + 'mediumspringgreen': '00fa9a', + 'mediumturquoise': '48d1cc', + 'mediumvioletred': 'c71585', + 'midnightblue': '191970', + 'mintcream': 'f5fffa', + 'mistyrose': 'ffe4e1', + 'moccasin': 'ffe4b5', + 'navajowhite': 'ffdead', + 'navy': '000080', + 'oldlace': 'fdf5e6', + 'olive': '808000', + 'olivedrab': '6b8e23', + 'orange': 'ffa500', + 'orangered': 'ff4500', + 'orchid': 'da70d6', + 'palegoldenrod': 'eee8aa', + 'palegreen': '98fb98', + 'paleturquoise': 'afeeee', + 'palevioletred': 'db7093', + 'papayawhip': 'ffefd5', + 'peachpuff': 'ffdab9', + 'peru': 'cd853f', + 'pink': 'ffc0cb', + 'plum': 'dda0dd', + 'powderblue': 'b0e0e6', + 'purple': '800080', + 'rebeccapurple': '639', + 'red': 'f00', + 'rosybrown': 'bc8f8f', + 'royalblue': '4169e1', + 'saddlebrown': '8b4513', + 'salmon': 'fa8072', + 'sandybrown': 'f4a460', + 'seagreen': '2e8b57', + 'seashell': 'fff5ee', + 'sienna': 'a0522d', + 'silver': 'c0c0c0', + 'skyblue': '87ceeb', + 'slateblue': '6a5acd', + 'slategray': '708090', + 'slategrey': '708090', + 'snow': 'fffafa', + 'springgreen': '00ff7f', + 'steelblue': '4682b4', + 'tan': 'd2b48c', + 'teal': '008080', + 'thistle': 'd8bfd8', + 'tomato': 'ff6347', + 'turquoise': '40e0d0', + 'violet': 'ee82ee', + 'wheat': 'f5deb3', + 'white': 'fff', + 'whitesmoke': 'f5f5f5', + 'yellow': 'ff0', + 'yellowgreen': '9acd32' +}; + +var HEX_TO_NAME = { + '800000': 'maroon', + '800080': 'purple', + '808000': 'olive', + '808080': 'gray', + '00ffff': 'cyan', + 'f0ffff': 'azure', + 'f5f5dc': 'beige', + 'ffe4c4': 'bisque', + '000000': 'black', + '0000ff': 'blue', + 'a52a2a': 'brown', + 'ff7f50': 'coral', + 'ffd700': 'gold', + '008000': 'green', + '4b0082': 'indigo', + 'fffff0': 'ivory', + 'f0e68c': 'khaki', + '00ff00': 'lime', + 'faf0e6': 'linen', + '000080': 'navy', + 'ffa500': 'orange', + 'da70d6': 'orchid', + 'cd853f': 'peru', + 'ffc0cb': 'pink', + 'dda0dd': 'plum', + 'f00': 'red', + 'ff0000': 'red', + 'fa8072': 'salmon', + 'a0522d': 'sienna', + 'c0c0c0': 'silver', + 'fffafa': 'snow', + 'd2b48c': 'tan', + '008080': 'teal', + 'ff6347': 'tomato', + 'ee82ee': 'violet', + 'f5deb3': 'wheat', + 'ffffff': 'white', + 'ffff00': 'yellow' +}; + +function hueToRgb(p, q, t) { + if (t < 0) { + t += 1; + } + if (t > 1) { + t -= 1; + } + if (t < 1 / 6) { + return p + (q - p) * 6 * t; + } + if (t < 1 / 2) { + return q; + } + if (t < 2 / 3) { + return p + (q - p) * (2 / 3 - t) * 6; + } + return p; +} + +function hslToRgb(h, s, l, a) { + var r; + var g; + var b; + + if (s == 0) { + r = g = b = l; // achromatic + } else { + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + + r = hueToRgb(p, q, h + 1 / 3); + g = hueToRgb(p, q, h); + b = hueToRgb(p, q, h - 1 / 3); + } + + return [ + Math.round(r * 255), + Math.round(g * 255), + Math.round(b * 255), + a + ]; +} + +function toHex(value) { + value = value.toString(16); + return value.length === 1 ? '0' + value : value; +} + +function parseFunctionArgs(functionArgs, count, rgb) { + var argument = functionArgs.head; + var args = []; + + while (argument !== null) { + var argumentPart = argument.data.sequence.head; + var wasValue = false; + + while (argumentPart !== null) { + var value = argumentPart.data; + var type = value.type; + + switch (type) { + case 'Number': + case 'Percentage': + if (wasValue) { + return; + } + + wasValue = true; + args.push({ + type: type, + value: Number(value.value) + }); + break; + + case 'Operator': + if (wasValue || value.value !== '+') { + return; + } + break; + + default: + // something we couldn't understand + return; + } + + argumentPart = argumentPart.next; + } + + argument = argument.next; + } + + if (args.length !== count) { + // invalid arguments count + // TODO: remove those tokens + return; + } + + if (args.length === 4) { + if (args[3].type !== 'Number') { + // 4th argument should be a number + // TODO: remove those tokens + return; + } + + args[3].type = 'Alpha'; + } + + if (rgb) { + if (args[0].type !== args[1].type || args[0].type !== args[2].type) { + // invalid color, numbers and percentage shouldn't be mixed + // TODO: remove those tokens + return; + } + } else { + if (args[0].type !== 'Number' || + args[1].type !== 'Percentage' || + args[2].type !== 'Percentage') { + // invalid color, for hsl values should be: number, percentage, percentage + // TODO: remove those tokens + return; + } + + args[0].type = 'Angle'; + } + + return args.map(function(arg) { + var value = Math.max(0, arg.value); + + switch (arg.type) { + case 'Number': + // fit value to [0..255] range + value = Math.min(value, 255); + break; + + case 'Percentage': + // convert 0..100% to value in [0..255] range + value = Math.min(value, 100) / 100; + + if (!rgb) { + return value; + } + + value = 255 * value; + break; + + case 'Angle': + // fit value to (-360..360) range + return (((value % 360) + 360) % 360) / 360; + + case 'Alpha': + // fit value to [0..1] range + return Math.min(value, 1); + } + + return Math.round(value); + }); +} + +function compressFunction(node, item, list) { + var functionName = node.name; + var args; + + if (functionName === 'rgba' || functionName === 'hsla') { + args = parseFunctionArgs(node.arguments, 4, functionName === 'rgba'); + + if (!args) { + // something went wrong + return; + } + + if (functionName === 'hsla') { + args = hslToRgb.apply(null, args); + node.name = 'rgba'; + } + + if (args[3] !== 1) { + // replace argument values for normalized/interpolated + node.arguments.each(function(argument) { + var item = argument.sequence.head; + + if (item.data.type === 'Operator') { + item = item.next; + } + + argument.sequence = new List([{ + type: 'Number', + info: item.data.info, + value: packNumber(args.shift()) + }]); + }); + + return; + } + + // otherwise convert to rgb, i.e. rgba(255, 0, 0, 1) -> rgb(255, 0, 0) + functionName = 'rgb'; + } + + if (functionName === 'hsl') { + args = args || parseFunctionArgs(node.arguments, 3, false); + + if (!args) { + // something went wrong + return; + } + + // convert to rgb + args = hslToRgb.apply(null, args); + functionName = 'rgb'; + } + + if (functionName === 'rgb') { + args = args || parseFunctionArgs(node.arguments, 3, true); + + if (!args) { + // something went wrong + return; + } + + // check if color is not at the end and not followed by space + var next = item.next; + if (next && next.data.type !== 'Space') { + list.insert(list.createItem({ + type: 'Space' + }), next); + } + + item.data = { + type: 'Hash', + info: node.info, + value: toHex(args[0]) + toHex(args[1]) + toHex(args[2]) + }; + + compressHex(item.data, item); + } +} + +function compressIdent(node, item) { + if (this.declaration === null) { + return; + } + + var color = node.name.toLowerCase(); + + if (NAME_TO_HEX.hasOwnProperty(color)) { + var hex = NAME_TO_HEX[color]; + + if (hex.length + 1 <= color.length) { + // replace for shorter hex value + item.data = { + type: 'Hash', + info: node.info, + value: hex + }; + } else { + // special case for consistent colors + if (color === 'grey') { + color = 'gray'; + } + + // just replace value for lower cased name + node.name = color; + } + } +} + +function compressHex(node, item) { + var color = node.value.toLowerCase(); + + // #112233 -> #123 + if (color.length === 6 && + color[0] === color[1] && + color[2] === color[3] && + color[4] === color[5]) { + color = color[0] + color[2] + color[4]; + } + + if (HEX_TO_NAME[color]) { + item.data = { + type: 'Identifier', + info: node.info, + name: HEX_TO_NAME[color] + }; + } else { + node.value = color; + } +} + +module.exports = { + compressFunction: compressFunction, + compressIdent: compressIdent, + compressHex: compressHex +}; diff --git a/node_modules/csso/lib/compressor/compress/index.js b/node_modules/csso/lib/compressor/compress/index.js new file mode 100644 index 00000000..ff6b0296 --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/index.js @@ -0,0 +1,22 @@ +var walk = require('../../utils/walk.js').all; +var handlers = { + Atrule: require('./Atrule.js'), + Attribute: require('./Attribute.js'), + Value: require('./Value.js'), + Dimension: require('./Dimension.js'), + Percentage: require('./Number.js'), + Number: require('./Number.js'), + String: require('./String.js'), + Url: require('./Url.js'), + Hash: require('./color.js').compressHex, + Identifier: require('./color.js').compressIdent, + Function: require('./color.js').compressFunction +}; + +module.exports = function(ast) { + walk(ast, function(node, item, list) { + if (handlers.hasOwnProperty(node.type)) { + handlers[node.type].call(this, node, item, list); + } + }); +}; diff --git a/node_modules/csso/lib/compressor/compress/property/background.js b/node_modules/csso/lib/compressor/compress/property/background.js new file mode 100644 index 00000000..210127a9 --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/property/background.js @@ -0,0 +1,66 @@ +var List = require('../../../utils/list.js'); + +module.exports = function compressBackground(node) { + function lastType() { + if (buffer.length) { + return buffer[buffer.length - 1].type; + } + } + + function flush() { + if (lastType() === 'Space') { + buffer.pop(); + } + + if (!buffer.length) { + buffer.unshift( + { + type: 'Number', + value: '0' + }, + { + type: 'Space' + }, + { + type: 'Number', + value: '0' + } + ); + } + + newValue.push.apply(newValue, buffer); + + buffer = []; + } + + var newValue = []; + var buffer = []; + + node.sequence.each(function(node) { + if (node.type === 'Operator' && node.value === ',') { + flush(); + newValue.push(node); + return; + } + + // remove defaults + if (node.type === 'Identifier') { + if (node.name === 'transparent' || + node.name === 'none' || + node.name === 'repeat' || + node.name === 'scroll') { + return; + } + } + + // don't add redundant spaces + if (node.type === 'Space' && (!buffer.length || lastType() === 'Space')) { + return; + } + + buffer.push(node); + }); + + flush(); + node.sequence = new List(newValue); +}; diff --git a/node_modules/csso/lib/compressor/compress/property/font-weight.js b/node_modules/csso/lib/compressor/compress/property/font-weight.js new file mode 100644 index 00000000..29122eb6 --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/property/font-weight.js @@ -0,0 +1,22 @@ +module.exports = function compressFontWeight(node) { + var value = node.sequence.head.data; + + if (value.type === 'Identifier') { + switch (value.name) { + case 'normal': + node.sequence.head.data = { + type: 'Number', + info: value.info, + value: '400' + }; + break; + case 'bold': + node.sequence.head.data = { + type: 'Number', + info: value.info, + value: '700' + }; + break; + } + } +}; diff --git a/node_modules/csso/lib/compressor/compress/property/font.js b/node_modules/csso/lib/compressor/compress/property/font.js new file mode 100644 index 00000000..783685f4 --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/property/font.js @@ -0,0 +1,45 @@ +module.exports = function compressFont(node) { + var list = node.sequence; + + list.eachRight(function(node, item) { + if (node.type === 'Identifier') { + if (node.name === 'bold') { + item.data = { + type: 'Number', + info: node.info, + value: '700' + }; + } else if (node.name === 'normal') { + var prev = item.prev; + + if (prev && prev.data.type === 'Operator' && prev.data.value === '/') { + this.remove(prev); + } + + this.remove(item); + } else if (node.name === 'medium') { + var next = item.next; + + if (!next || next.data.type !== 'Operator') { + this.remove(item); + } + } + } + }); + + // remove redundant spaces + list.each(function(node, item) { + if (node.type === 'Space') { + if (!item.prev || !item.next || item.next.data.type === 'Space') { + this.remove(item); + } + } + }); + + if (list.isEmpty()) { + list.insert(list.createItem({ + type: 'Identifier', + name: 'normal' + })); + } +}; |
