X Tutup
Skip to content

Commit d388c0a

Browse files
committed
feat(HtmlParser): enforce only void & foreign elts can be self closed
BREAKING CHANGE: `<whatever />` used to be expanded to `<whatever></whatever>`. The parser now follows the HTML5 spec more closely. Only void and foreign elements can be self closed. Closes #5591
1 parent 5660446 commit d388c0a

File tree

5 files changed

+45
-10
lines changed

5 files changed

+45
-10
lines changed

modules/angular2/src/compiler/html_parser.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@ class TreeBuilder {
134134
if (this.peek.type === HtmlTokenType.TAG_OPEN_END_VOID) {
135135
this._advance();
136136
selfClosing = true;
137+
if (namespacePrefix(fullName) == null && !getHtmlTagDefinition(fullName).isVoid) {
138+
this.errors.push(HtmlTreeError.create(
139+
fullName, startTagToken.sourceSpan.start,
140+
`Only void and foreign elements can be self closed "${startTagToken.parts[1]}"`));
141+
}
137142
} else if (this.peek.type === HtmlTokenType.TAG_OPEN_END) {
138143
this._advance();
139144
selfClosing = false;

modules/angular2/test/compiler/html_parser_spec.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,16 @@ export function main() {
147147
expect(humanizeDom(parser.parse('<DiV><P></p></dIv>', 'TestComp')))
148148
.toEqual([[HtmlElementAst, 'DiV', 0], [HtmlElementAst, 'P', 1]]);
149149
});
150+
151+
it('should support self closing void elements', () => {
152+
expect(humanizeDom(parser.parse('<input />', 'TestComp')))
153+
.toEqual([[HtmlElementAst, 'input', 0]]);
154+
});
155+
156+
it('should support self closing foreign elements', () => {
157+
expect(humanizeDom(parser.parse('<math />', 'TestComp')))
158+
.toEqual([[HtmlElementAst, '@math:math', 0]]);
159+
});
150160
});
151161

152162
describe('attributes', () => {
@@ -175,8 +185,8 @@ export function main() {
175185
});
176186

177187
it('should support mamespace', () => {
178-
expect(humanizeDom(parser.parse('<use xlink:href="Port" />', 'TestComp')))
179-
.toEqual([[HtmlElementAst, 'use', 0], [HtmlAttrAst, '@xlink:href', 'Port']]);
188+
expect(humanizeDom(parser.parse('<svg:use xlink:href="Port" />', 'TestComp')))
189+
.toEqual([[HtmlElementAst, '@svg:use', 0], [HtmlAttrAst, '@xlink:href', 'Port']]);
180190
});
181191
});
182192

@@ -216,6 +226,22 @@ export function main() {
216226
.toEqual([['input', 'Void elements do not have end tags "input"', '0:7']]);
217227
});
218228

229+
it('should report self closing html element', () => {
230+
let errors = parser.parse('<p />', 'TestComp').errors;
231+
expect(errors.length).toEqual(1);
232+
expect(humanizeErrors(errors))
233+
.toEqual([['p', 'Only void and foreign elements can be self closed "p"', '0:0']]);
234+
});
235+
236+
it('should report self closing custom element', () => {
237+
let errors = parser.parse('<my-cmp />', 'TestComp').errors;
238+
expect(errors.length).toEqual(1);
239+
expect(humanizeErrors(errors))
240+
.toEqual([
241+
['my-cmp', 'Only void and foreign elements can be self closed "my-cmp"', '0:0']
242+
]);
243+
});
244+
219245
it('should also report lexer errors', () => {
220246
let errors = parser.parse('<!-err--><div></p></div>', 'TestComp').errors;
221247
expect(errors.length).toEqual(2);

modules/angular2/test/compiler/template_parser_spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -728,8 +728,8 @@ Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp@0:5 ("<div [
728728
type: new CompileTypeMetadata({name: 'DirB'}),
729729
template: new CompileTemplateMetadata({ngContentSelectors: []})
730730
});
731-
expect(() => parse('<div/>', [dirB, dirA])).toThrowError(`Template parse errors:
732-
More than one component: DirB,DirA ("[ERROR ->]<div/>"): TestComp@0:0`);
731+
expect(() => parse('<div>', [dirB, dirA])).toThrowError(`Template parse errors:
732+
More than one component: DirB,DirA ("[ERROR ->]<div>"): TestComp@0:0`);
733733
});
734734

735735
it('should not allow components or element bindings nor dom events on explicit embedded templates',

modules/angular2/test/core/linker/integration_spec.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -578,10 +578,12 @@ export function main() {
578578
inject(
579579
[TestComponentBuilder, AsyncTestCompleter],
580580
(tcb: TestComponentBuilder, async) => {
581-
tcb.overrideView(MyComp, new ViewMetadata({
582-
template: '<p><child-cmp var-alice/><child-cmp var-bob/></p>',
583-
directives: [ChildComp]
584-
}))
581+
tcb.overrideView(
582+
MyComp, new ViewMetadata({
583+
template:
584+
'<p><child-cmp var-alice></child-cmp><child-cmp var-bob></child-cmp></p>',
585+
directives: [ChildComp]
586+
}))
585587

586588
.createAsync(MyComp)
587589
.then((fixture) => {

modules_dart/transform/test/transform/template_compiler/all_tests.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,8 @@ void allTests() {
352352
});
353353

354354
it('should include platform directives.', () async {
355-
fooComponentMeta.template = new CompileTemplateMetadata(template: '<bar/>');
355+
fooComponentMeta.template = new CompileTemplateMetadata(
356+
template: '<bar></bar>');
356357
final viewAnnotation = new AnnotationModel()
357358
..name = 'View'
358359
..isView = true;
@@ -370,7 +371,8 @@ void allTests() {
370371
});
371372

372373
it('should include platform directives when it it a list.', () async {
373-
fooComponentMeta.template = new CompileTemplateMetadata(template: '<bar/>');
374+
fooComponentMeta.template = new CompileTemplateMetadata(
375+
template: '<bar></bar>');
374376
final viewAnnotation = new AnnotationModel()
375377
..name = 'View'
376378
..isView = true;

0 commit comments

Comments
 (0)
X Tutup