diff --git a/src/evented-tokenizer.ts b/src/evented-tokenizer.ts index 80d0b4b..58b71de 100644 --- a/src/evented-tokenizer.ts +++ b/src/evented-tokenizer.ts @@ -267,17 +267,17 @@ export default class EventedTokenizer { let char = this.consume(); if (isSpace(char)) { - this.transitionTo(TokenizerState.beforeAttributeName); - this.tagNameBuffer = ''; + this.delegate.reportSyntaxError('closing tag must only contain tagname'); } else if (char === '/') { - this.transitionTo(TokenizerState.selfClosingStartTag); - this.tagNameBuffer = ''; + this.delegate.reportSyntaxError('closing tag cannot be self-closing'); } else if (char === '>') { this.delegate.finishTag(); this.transitionTo(TokenizerState.beforeData); this.tagNameBuffer = ''; } else { - this.appendToTagName(char); + if (!this.delegate.current().syntaxError && !isSpace(char)) { + this.appendToTagName(char); + } } }, @@ -480,13 +480,17 @@ export default class EventedTokenizer { }, endTagOpen() { - let char = this.consume(); + let char = this.peek(); if (char === '@' || char === ':' || isAlpha(char)) { + this.consume(); this.transitionTo(TokenizerState.endTagName); this.tagNameBuffer = ''; this.delegate.beginEndTag(); this.appendToTagName(char); + } else { + this.transitionTo(TokenizerState.endTagName); + this.delegate.beginEndTag(); } } }; diff --git a/src/types.ts b/src/types.ts index 631a96d..4221f3d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -64,6 +64,7 @@ export interface TokenMap { } export interface TokenizerDelegate { + current(): Token; reset(): void; finishData(): void; tagOpen(): void; diff --git a/tests/tokenizer-tests.ts b/tests/tokenizer-tests.ts index 98538f1..9fda089 100644 --- a/tests/tokenizer-tests.ts +++ b/tests/tokenizer-tests.ts @@ -31,9 +31,54 @@ QUnit.test('A simple closing tag', function(assert) { assert.deepEqual(tokens, [endTag('div')]); }); -QUnit.test('A simple closing tag with trailing spaces', function(assert) { +QUnit.test('A closing tag cannot contain trailing spaces', function(assert) { let tokens = tokenize('</div \t\n>'); - assert.deepEqual(tokens, [endTag('div')]); + let output = [withSyntaxError( + 'closing tag must only contain tagname', + endTag('div') + )]; + + assert.deepEqual(tokens, output); +}); + +QUnit.test('A closing tag cannot contain leading spaces', function(assert) { + let tokens = tokenize('</ div>'); + let output = [withSyntaxError( + 'closing tag must only contain tagname', + endTag('') + )]; + + assert.deepEqual(tokens, output); +}); + +QUnit.test('A closing tag cannot contain an attribute', function(assert) { + let tokens = tokenize('</div foo="bar">'); + let output = [withSyntaxError( + 'closing tag must only contain tagname', + endTag('div') + )]; + + assert.deepEqual(tokens, output); +}); + +QUnit.test('A closing tag cannot contain multiple attributes', function(assert) { + let tokens = tokenize('</div foo="bar" foo="baz">'); + let output = [withSyntaxError( + 'closing tag must only contain tagname', + endTag('div') + )]; + + assert.deepEqual(tokens, output); +}); + +QUnit.test('A closing tag cannot be self-closing', function(assert) { + let tokens = tokenize('</div/>'); + let output = [withSyntaxError( + 'closing tag cannot be self-closing', + endTag('div') + )]; + + assert.deepEqual(tokens, output); }); QUnit.test('A pair of hyphenated tags', function(assert) {