diff --git a/lodash.js b/lodash.js index 5659d268c..f52b48391 100644 --- a/lodash.js +++ b/lodash.js @@ -187,7 +187,8 @@ rsBreakRange = rsMathOpRange + rsNonCharRange + rsQuoteRange + rsSpaceRange; /** Used to compose unicode capture groups. */ - var rsAstral = '[' + rsAstralRange + ']', + var rsApos = "['\u2019]", + rsAstral = '[' + rsAstralRange + ']', rsBreak = '[' + rsBreakRange + ']', rsCombo = '[' + rsComboMarksRange + rsComboSymbolsRange + ']', rsDigits = '\\d+', @@ -205,6 +206,8 @@ /** Used to compose unicode regexes. */ var rsLowerMisc = '(?:' + rsLower + '|' + rsMisc + ')', rsUpperMisc = '(?:' + rsUpper + '|' + rsMisc + ')', + rsOptLowerContr = '(?:' + rsApos + '(?:d|ll|m|re|s|t|ve))?', + rsOptUpperContr = '(?:' + rsApos + '(?:D|LL|M|RE|S|T|VE))?', reOptMod = rsModifier + '?', rsOptVar = '[' + rsVarRange + ']?', rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*', @@ -212,6 +215,9 @@ rsEmoji = '(?:' + [rsDingbat, rsRegional, rsSurrPair].join('|') + ')' + rsSeq, rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')'; + /** Used to match apostrophes. */ + var reApos = RegExp(rsApos, 'g'); + /** * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols). @@ -223,10 +229,10 @@ /** Used to match complex or compound words. */ var reComplexWord = RegExp([ - rsUpper + '?' + rsLower + '+(?=' + [rsBreak, rsUpper, '$'].join('|') + ')', - rsUpperMisc + '+(?=' + [rsBreak, rsUpper + rsLowerMisc, '$'].join('|') + ')', - rsUpper + '?' + rsLowerMisc + '+', - rsUpper + '+', + rsUpper + '?' + rsLower + '+' + rsOptLowerContr + '(?=' + [rsBreak, rsUpper, '$'].join('|') + ')', + rsUpperMisc + '+' + rsOptUpperContr + '(?=' + [rsBreak, rsUpper + rsLowerMisc, '$'].join('|') + ')', + rsUpper + '?' + rsLowerMisc + '+' + rsOptLowerContr, + rsUpper + '+' + rsOptUpperContr, rsDigits, rsEmoji ].join('|'), 'g'); @@ -4388,7 +4394,7 @@ */ function createCompounder(callback) { return function(string) { - return arrayReduce(words(deburr(string)), callback, ''); + return arrayReduce(words(deburr(string).replace(reApos, '')), callback, ''); }; } diff --git a/test/test.js b/test/test.js index c647fcd65..a3f8f8940 100644 --- a/test/test.js +++ b/test/test.js @@ -2134,7 +2134,32 @@ assert.deepEqual(actual, lodashStable.map(burredLetters, alwaysTrue)); }); - QUnit.test('`_.' + methodName + '` should trim latin-1 mathematical operators', function(assert) { + QUnit.test('`_.' + methodName + '` should remove contraction apostrophes', function(assert) { + assert.expect(2); + + var postfixes = ['d', 'll', 'm', 're', 's', 't', 've']; + + lodashStable.each(["'", '\u2019'], function(apos) { + var actual = lodashStable.map(postfixes, function(postfix) { + return func('a b' + apos + postfix + ' c'); + }); + + var expected = lodashStable.map(postfixes, function(postfix) { + switch (caseName) { + case 'camel': return 'aB' + postfix + 'C'; + case 'kebab': return 'a-b' + postfix + '-c'; + case 'lower': return 'a b' + postfix + ' c'; + case 'snake': return 'a_b' + postfix + '_c'; + case 'start': return 'A B' + postfix + ' C'; + case 'upper': return 'A B' + postfix.toUpperCase() + ' C'; + } + }); + + assert.deepEqual(actual, expected); + }); + }); + + QUnit.test('`_.' + methodName + '` should remove latin-1 mathematical operators', function(assert) { assert.expect(1); var actual = lodashStable.map(['\xd7', '\xf7'], func); @@ -2176,7 +2201,7 @@ QUnit.test('should get the original value after cycling through all case methods', function(assert) { assert.expect(1); - var funcs = [_.camelCase, _.kebabCase, _.snakeCase, _.startCase, _.camelCase]; + var funcs = [_.camelCase, _.kebabCase, _.lowerCase, _.snakeCase, _.startCase, _.lowerCase, _.camelCase]; var actual = lodashStable.reduce(funcs, function(result, func) { return func(result);