diff --git a/src/ng/location.js b/src/ng/location.js index 18442888309..625b9297720 100644 --- a/src/ng/location.js +++ b/src/ng/location.js @@ -422,7 +422,7 @@ var locationPrototype = { } var match = PATH_MATCH.exec(url); - if (match[1] || url === '') this.path(decodeURI(match[1])); + if (match[1] || url === '') this.path((this.$$html5 ? decodeURI : decodeURIComponent)(match[1])); if (match[2] || match[1] || url === '') this.search(match[3] || ''); this.hash(match[5] || ''); diff --git a/src/ng/parse.js b/src/ng/parse.js index 9f8621440cf..91a1b363e6c 100644 --- a/src/ng/parse.js +++ b/src/ng/parse.js @@ -1644,11 +1644,26 @@ Parser.prototype = { constructor: Parser, parse: function(text) { - var ast = this.ast.ast(text); - var fn = this.astCompiler.compile(ast); - fn.literal = isLiteral(ast); - fn.constant = isConstant(ast); + var ast = this.getAst(text); + var fn = this.astCompiler.compile(ast.ast); + fn.literal = isLiteral(ast.ast); + fn.constant = isConstant(ast.ast); + fn.oneTime = ast.oneTime; return fn; + }, + + getAst: function(exp) { + var oneTime = false; + exp = exp.trim(); + + if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { + oneTime = true; + exp = exp.substring(2); + } + return { + ast: this.ast.ast(exp), + oneTime: oneTime + }; } }; @@ -1771,10 +1786,11 @@ function $ParseProvider() { isIdentifierStart: isFunction(identStart) && identStart, isIdentifierContinue: isFunction(identContinue) && identContinue }; + $parse.$$getAst = $$getAst; return $parse; function $parse(exp, interceptorFn) { - var parsedExpression, oneTime, cacheKey; + var parsedExpression, cacheKey; switch (typeof exp) { case 'string': @@ -1784,14 +1800,9 @@ function $ParseProvider() { parsedExpression = cache[cacheKey]; if (!parsedExpression) { - if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { - oneTime = true; - exp = exp.substring(2); - } var lexer = new Lexer($parseOptions); var parser = new Parser(lexer, $filter, $parseOptions); parsedExpression = parser.parse(exp); - parsedExpression.oneTime = !!oneTime; cache[cacheKey] = addWatchDelegate(parsedExpression); } @@ -1805,6 +1816,12 @@ function $ParseProvider() { } } + function $$getAst(exp) { + var lexer = new Lexer($parseOptions); + var parser = new Parser(lexer, $filter, $parseOptions); + return parser.getAst(exp).ast; + } + function expressionInputDirtyCheck(newValue, oldValueOfValue, compareObjectIdentity) { if (newValue == null || oldValueOfValue == null) { // null/undefined diff --git a/test/ng/locationSpec.js b/test/ng/locationSpec.js index dd48edbe4e5..af17a1e3db7 100644 --- a/test/ng/locationSpec.js +++ b/test/ng/locationSpec.js @@ -673,6 +673,20 @@ describe('$location', function() { locationUrl.search({'q': '4/5 6'}); expect(locationUrl.absUrl()).toEqual('http://host.com?q=4%2F5%206'); }); + + it('url() should decode non-component special characters in hashbang mode', function() { + var locationUrl = new LocationHashbangUrl('http://host.com', 'http://host.com'); + locationUrl.$$parse('http://host.com'); + locationUrl.url('/foo%3Abar'); + expect(locationUrl.path()).toEqual('/foo:bar'); + }); + + it('url() should not decode non-component special characters in html5 mode', function() { + var locationUrl = new LocationHtml5Url('http://host.com', 'http://host.com'); + locationUrl.$$parse('http://host.com'); + locationUrl.url('/foo%3Abar'); + expect(locationUrl.path()).toEqual('/foo%3Abar'); + }); }); }); diff --git a/test/ng/parseSpec.js b/test/ng/parseSpec.js index 7c5baad1630..5142bc22a7e 100644 --- a/test/ng/parseSpec.js +++ b/test/ng/parseSpec.js @@ -4245,4 +4245,48 @@ describe('parser', function() { }); }); }); + + describe('hidden/unsupported features', function() { + describe('$$getAst()', function() { + it('should be a method exposed on the `$parse` service', inject(function($parse) { + expect(isFunction($parse.$$getAst)).toBeTruthy(); + })); + + it('should accept a string expression argument and return the corresponding AST', inject(function($parse) { + var ast = $parse.$$getAst('foo.bar'); + expect(ast).toEqual({ + type: 'Program', + body: [ + { + type: 'ExpressionStatement', + expression: { + type: 'MemberExpression', + object: { type: 'Identifier', name: 'foo' }, + property: { type: 'Identifier', name: 'bar' }, + computed: false + } + } + ] + }); + })); + + it('should parse one time binding expressions', inject(function($parse) { + var ast = $parse.$$getAst('::foo.bar'); + expect(ast).toEqual({ + type: 'Program', + body: [ + { + type: 'ExpressionStatement', + expression: { + type: 'MemberExpression', + object: { type: 'Identifier', name: 'foo' }, + property: { type: 'Identifier', name: 'bar' }, + computed: false + } + } + ] + }); + })); + }); + }); });