Skip to content

Commit 2c70cca

Browse files
committed
refactor(*): separate jqLite/compile/sce camelCasing logic
jqLite needs camelCase for it's css method; it should only convert one dash followed by a lowercase letter to an uppercase one; it shouldn't touch underscores, colons or collapse multiple dashes into one. This is behavior of jQuery 3 as well. This commit separates jqLite camelCasing from the $compile one (and $sce but that's an internal-only use). The $compile version should behave as before. Also, jqLite's css camelCasing logic was put in a separate function and refactored: now the properties starting from an uppercase letter are used by default (i.e. Webkit, not webkit) and the only exception is for the -ms- prefix that is converted to ms, not Ms. This makes the logic clearer as we're just always changing a dash followed by a lowercase letter by an uppercase one; this is also how it works in jQuery. Ref angular#15126 Fix angular#7744
1 parent 4f44e01 commit 2c70cca

File tree

6 files changed

+159
-29
lines changed

6 files changed

+159
-29
lines changed

src/.eslintrc.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@
123123
"BOOLEAN_ATTR": false,
124124
"ALIASED_ATTR": false,
125125
"jqNextId": false,
126-
"camelCase": false,
126+
"fnCamelCaseReplace": false,
127127
"jqLitePatchJQueryRemove": false,
128128
"JQLite": false,
129129
"jqLiteClone": false,

src/jqLite.js

+20-11
Original file line numberDiff line numberDiff line change
@@ -136,22 +136,31 @@ JQLite._data = function(node) {
136136
function jqNextId() { return ++jqId; }
137137

138138

139-
var SPECIAL_CHARS_REGEXP = /([:\-_]+(.))/g;
140-
var MOZ_HACK_REGEXP = /^moz([A-Z])/;
139+
var DASH_LOWERCASE_REGEXP = /-([a-z])/g;
140+
var MS_HACK_REGEXP = /^-ms-/;
141141
var MOUSE_EVENT_MAP = { mouseleave: 'mouseout', mouseenter: 'mouseover' };
142142
var jqLiteMinErr = minErr('jqLite');
143143

144144
/**
145-
* Converts snake_case to camelCase.
146-
* Also there is special case for Moz prefix starting with upper case letter.
145+
* Converts kebab-case to camelCase.
146+
* There is also a special case for the ms prefix starting with a lowercase letter.
147147
* @param name Name to normalize
148148
*/
149-
function camelCase(name) {
150-
return name.
151-
replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
152-
return offset ? letter.toUpperCase() : letter;
153-
}).
154-
replace(MOZ_HACK_REGEXP, 'Moz$1');
149+
function cssKebabToCamel(name) {
150+
return kebabToCamel(name.replace(MS_HACK_REGEXP, 'ms-'));
151+
}
152+
153+
function fnCamelCaseReplace(all, letter) {
154+
return letter.toUpperCase();
155+
}
156+
157+
/**
158+
* Converts kebab-case to camelCase.
159+
* @param name Name to normalize
160+
*/
161+
function kebabToCamel(name) {
162+
return name
163+
.replace(DASH_LOWERCASE_REGEXP, fnCamelCaseReplace);
155164
}
156165

157166
var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/;
@@ -628,7 +637,7 @@ forEach({
628637
hasClass: jqLiteHasClass,
629638

630639
css: function(element, name, value) {
631-
name = camelCase(name);
640+
name = cssKebabToCamel(name);
632641

633642
if (isDefined(value)) {
634643
element.style[name] = value;

src/ng/compile.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -3590,12 +3590,16 @@ SimpleChange.prototype.isFirstChange = function() { return this.previousValue ==
35903590

35913591

35923592
var PREFIX_REGEXP = /^((?:x|data)[:\-_])/i;
3593+
var SPECIAL_CHARS_REGEXP = /[:\-_]+(.)/g;
3594+
35933595
/**
35943596
* Converts all accepted directives format into proper directive name.
35953597
* @param name Name to normalize
35963598
*/
35973599
function directiveNormalize(name) {
3598-
return camelCase(name.replace(PREFIX_REGEXP, ''));
3600+
return name
3601+
.replace(PREFIX_REGEXP, '')
3602+
.replace(SPECIAL_CHARS_REGEXP, fnCamelCaseReplace);
35993603
}
36003604

36013605
/**

src/ng/sce.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ var SCE_CONTEXTS = {
2727

2828
// Helper functions follow.
2929

30+
var UNDERSCORE_LOWERCASE_REGEXP = /_([a-z])/g;
31+
32+
function snakeToCamel(name) {
33+
return name
34+
.replace(UNDERSCORE_LOWERCASE_REGEXP, fnCamelCaseReplace);
35+
}
36+
3037
function adjustMatcher(matcher) {
3138
if (matcher === 'self') {
3239
return matcher;
@@ -1054,13 +1061,13 @@ function $SceProvider() {
10541061

10551062
forEach(SCE_CONTEXTS, function(enumValue, name) {
10561063
var lName = lowercase(name);
1057-
sce[camelCase('parse_as_' + lName)] = function(expr) {
1064+
sce[snakeToCamel('parse_as_' + lName)] = function(expr) {
10581065
return parse(enumValue, expr);
10591066
};
1060-
sce[camelCase('get_trusted_' + lName)] = function(value) {
1067+
sce[snakeToCamel('get_trusted_' + lName)] = function(value) {
10611068
return getTrusted(enumValue, value);
10621069
};
1063-
sce[camelCase('trust_as_' + lName)] = function(value) {
1070+
sce[snakeToCamel('trust_as_' + lName)] = function(value) {
10641071
return trustAs(enumValue, value);
10651072
};
10661073
});

test/.eslintrc.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@
118118
/* jqLite.js */
119119
"BOOLEAN_ATTR": false,
120120
"jqNextId": false,
121-
"camelCase": false,
121+
"kebabToCamel": false,
122+
"fnCamelCaseReplace": false,
122123
"jqLitePatchJQueryRemove": false,
123124
"JQLite": false,
124125
"jqLiteClone": false,

test/jqLiteSpec.js

+121-12
Original file line numberDiff line numberDiff line change
@@ -1055,6 +1055,105 @@ describe('jqLite', function() {
10551055
expect(jqA.css('z-index')).toBeOneOf('7', 7);
10561056
expect(jqA.css('zIndex')).toBeOneOf('7', 7);
10571057
});
1058+
1059+
it('should leave non-dashed strings alone', function() {
1060+
var jqA = jqLite(a);
1061+
1062+
jqA.css('foo', 'foo');
1063+
jqA.css('fooBar', 'bar');
1064+
1065+
expect(a.style.foo).toBe('foo');
1066+
expect(a.style.fooBar).toBe('bar');
1067+
});
1068+
1069+
it('should covert dash-separated strings to camelCase', function() {
1070+
var jqA = jqLite(a);
1071+
1072+
jqA.css('foo-bar', 'foo');
1073+
jqA.css('foo-bar-baz', 'bar');
1074+
jqA.css('foo:bar_baz', 'baz');
1075+
1076+
expect(a.style.fooBar).toBe('foo');
1077+
expect(a.style.fooBarBaz).toBe('bar');
1078+
expect(a.style['foo:bar_baz']).toBe('baz');
1079+
});
1080+
1081+
it('should covert leading dashes followed by a lowercase letter', function() {
1082+
var jqA = jqLite(a);
1083+
1084+
jqA.css('-foo-bar', 'foo');
1085+
1086+
expect(a.style.FooBar).toBe('foo');
1087+
});
1088+
1089+
it('should not convert slashes followed by a non-letter', function() {
1090+
var jqA = jqLite(a);
1091+
1092+
jqA.css('foo-42- -a-B', 'foo');
1093+
1094+
expect(a.style['foo-42- A-B']).toBe('foo');
1095+
});
1096+
1097+
it('should covert the -ms- prefix to ms instead of Ms', function() {
1098+
var jqA = jqLite(a);
1099+
1100+
jqA.css('-ms-foo-bar', 'foo');
1101+
jqA.css('-moz-foo-bar', 'bar');
1102+
jqA.css('-webkit-foo-bar', 'baz');
1103+
1104+
expect(a.style.msFooBar).toBe('foo');
1105+
expect(a.style.MozFooBar).toBe('bar');
1106+
expect(a.style.WebkitFooBar).toBe('baz');
1107+
});
1108+
1109+
it('should not collapse sequences of dashes', function() {
1110+
var jqA = jqLite(a);
1111+
1112+
jqA.css('foo---bar-baz--qaz', 'foo');
1113+
1114+
expect(a.style['foo--BarBaz-Qaz']).toBe('foo');
1115+
});
1116+
1117+
1118+
it('should read vendor prefixes with the special -ms- exception', function() {
1119+
// jQuery uses getComputedStyle() in a css getter so these tests would fail there.
1120+
if (!_jqLiteMode) return;
1121+
1122+
var jqA = jqLite(a);
1123+
1124+
a.style.WebkitFooBar = 'webkit-uppercase';
1125+
a.style.webkitFooBar = 'webkit-lowercase';
1126+
1127+
a.style.MozFooBaz = 'moz-uppercase';
1128+
a.style.mozFooBaz = 'moz-lowercase';
1129+
1130+
a.style.MsFooQaz = 'ms-uppercase';
1131+
a.style.msFooQaz = 'ms-lowercase';
1132+
1133+
expect(jqA.css('-webkit-foo-bar')).toBe('webkit-uppercase');
1134+
expect(jqA.css('-moz-foo-baz')).toBe('moz-uppercase');
1135+
expect(jqA.css('-ms-foo-qaz')).toBe('ms-lowercase');
1136+
});
1137+
1138+
it('should write vendor prefixes with the special -ms- exception', function() {
1139+
// jQuery uses getComputedStyle() in a css getter so these tests would fail there.
1140+
if (!_jqLiteMode) return;
1141+
1142+
var jqA = jqLite(a);
1143+
1144+
jqA.css('-webkit-foo-bar', 'webkit');
1145+
jqA.css('-moz-foo-baz', 'moz');
1146+
jqA.css('-ms-foo-qaz', 'ms');
1147+
1148+
expect(a.style.WebkitFooBar).toBe('webkit');
1149+
expect(a.style.webkitFooBar).not.toBeDefined();
1150+
1151+
expect(a.style.MozFooBaz).toBe('moz');
1152+
expect(a.style.mozFooBaz).not.toBeDefined();
1153+
1154+
expect(a.style.MsFooQaz).not.toBeDefined();
1155+
expect(a.style.msFooQaz).toBe('ms');
1156+
});
10581157
});
10591158

10601159

@@ -2267,25 +2366,35 @@ describe('jqLite', function() {
22672366
});
22682367

22692368

2270-
describe('camelCase', function() {
2369+
describe('kebabToCamel', function() {
22712370
it('should leave non-dashed strings alone', function() {
2272-
expect(camelCase('foo')).toBe('foo');
2273-
expect(camelCase('')).toBe('');
2274-
expect(camelCase('fooBar')).toBe('fooBar');
2371+
expect(kebabToCamel('foo')).toBe('foo');
2372+
expect(kebabToCamel('')).toBe('');
2373+
expect(kebabToCamel('fooBar')).toBe('fooBar');
22752374
});
22762375

2277-
22782376
it('should covert dash-separated strings to camelCase', function() {
2279-
expect(camelCase('foo-bar')).toBe('fooBar');
2280-
expect(camelCase('foo-bar-baz')).toBe('fooBarBaz');
2281-
expect(camelCase('foo:bar_baz')).toBe('fooBarBaz');
2377+
expect(kebabToCamel('foo-bar')).toBe('fooBar');
2378+
expect(kebabToCamel('foo-bar-baz')).toBe('fooBarBaz');
2379+
expect(kebabToCamel('foo:bar_baz')).toBe('foo:bar_baz');
2380+
});
2381+
2382+
it('should covert leading dashes followed by a lowercase letter', function() {
2383+
expect(kebabToCamel('-foo-bar')).toBe('FooBar');
22822384
});
22832385

2386+
it('should not convert slashes followed by a non-letter', function() {
2387+
expect(kebabToCamel('foo-42- -a-B')).toBe('foo-42- A-B');
2388+
});
2389+
2390+
it('should not covert browser specific css properties in a special way', function() {
2391+
expect(kebabToCamel('-ms-foo-bar')).toBe('MsFooBar');
2392+
expect(kebabToCamel('-moz-foo-bar')).toBe('MozFooBar');
2393+
expect(kebabToCamel('-webkit-foo-bar')).toBe('WebkitFooBar');
2394+
});
22842395

2285-
it('should covert browser specific css properties', function() {
2286-
expect(camelCase('-moz-foo-bar')).toBe('MozFooBar');
2287-
expect(camelCase('-webkit-foo-bar')).toBe('webkitFooBar');
2288-
expect(camelCase('-webkit-foo-bar')).toBe('webkitFooBar');
2396+
it('should not collapse sequences of dashes', function() {
2397+
expect(kebabToCamel('foo---bar-baz--qaz')).toBe('foo--BarBaz-Qaz');
22892398
});
22902399
});
22912400

0 commit comments

Comments
 (0)