From 3505aa37bdddf307a47a4e1db2425e9fe328cc21 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 10 May 2016 17:07:30 +0200 Subject: [PATCH] fixes #5919: [html] Angular 2 directives break HTML syntax highlighting --- src/vs/languages/html/common/html.ts | 38 ++++++++++++------- .../languages/html/test/common/html.test.ts | 34 ++++++++++++++--- 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/src/vs/languages/html/common/html.ts b/src/vs/languages/html/common/html.ts index f1b379f8a29..9dff02bf856 100644 --- a/src/vs/languages/html/common/html.ts +++ b/src/vs/languages/html/common/html.ts @@ -85,10 +85,14 @@ export class State extends AbstractState { return false; } - private nextName(stream:Modes.IStream):string { + private nextElementName(stream:Modes.IStream):string { return stream.advanceIfRegExp(/^[_:\w][_:\w-.\d]*/).toLowerCase(); } + private nextAttributeName(stream:Modes.IStream):string { + return stream.advanceIfRegExp(/^[^\s"'>/=\x00-\x0F\x7F\x80-\x9F]*/).toLowerCase(); + } + public tokenize(stream:Modes.IStream) : Modes.ITokenizationResult { switch(this.kind){ @@ -133,7 +137,7 @@ export class State extends AbstractState { break; case States.OpeningEndTag: - var tagName = this.nextName(stream); + var tagName = this.nextElementName(stream); if (tagName.length > 0){ return { type: State.escapeTagName(tagName), @@ -149,7 +153,7 @@ export class State extends AbstractState { } case States.OpeningStartTag: - this.lastTagName = this.nextName(stream); + this.lastTagName = this.nextElementName(stream); if (this.lastTagName.length > 0) { this.lastAttributeName = null; if ('script' === this.lastTagName || 'style' === this.lastTagName) { @@ -165,18 +169,22 @@ export class State extends AbstractState { case States.WithinTag: if (stream.skipWhitespace2() || stream.eos()) { + this.lastAttributeName = ''; // remember that we have seen a whitespace return { type: '' }; } else { - var name:string = this.nextName(stream); - if (name.length > 0) { - this.lastAttributeName = name; - this.kind = States.AttributeName; - return { type: htmlTokenTypes.ATTRIB_NAME }; - - } else if (stream.advanceIfString2('/>')) { - this.kind = States.Content; - return { type: htmlTokenTypes.DELIM_START, dontMergeWithPrev: true }; - } if (stream.advanceIfCharCode2('>'.charCodeAt(0))) { + if (this.lastAttributeName === '') { + var name = this.nextAttributeName(stream); + if (name.length > 0) { + this.lastAttributeName = name; + this.kind = States.AttributeName; + return { type: htmlTokenTypes.ATTRIB_NAME }; + } + } + if (stream.advanceIfString2('/>')) { + this.kind = States.Content; + return { type: htmlTokenTypes.DELIM_START, dontMergeWithPrev: true }; + } + if (stream.advanceIfCharCode2('>'.charCodeAt(0))) { if (tagsEmbeddingContent.indexOf(this.lastTagName) !== -1) { this.kind = States.WithinEmbeddedContent; return { type: htmlTokenTypes.DELIM_START, dontMergeWithPrev: true }; @@ -200,6 +208,7 @@ export class State extends AbstractState { return { type: htmlTokenTypes.DELIM_ASSIGN }; } else { this.kind = States.WithinTag; + this.lastAttributeName = ''; return this.tokenize(stream); // no advance yet - jump to WithinTag } @@ -231,6 +240,7 @@ export class State extends AbstractState { this.kind = States.WithinTag; this.attributeValue = ''; this.attributeValueQuote = ''; + this.lastAttributeName = null; } else { var part = stream.next(); this.attributeValue += part; @@ -241,6 +251,7 @@ export class State extends AbstractState { let attributeValue = stream.advanceIfRegExp(/^[^\s"'`=<>]+/); if (attributeValue.length > 0) { this.kind = States.WithinTag; + this.lastAttributeName = null; return { type: htmlTokenTypes.ATTRIB_VALUE }; } var ch = stream.peek(); @@ -251,6 +262,7 @@ export class State extends AbstractState { return { type: htmlTokenTypes.ATTRIB_VALUE }; } else { this.kind = States.WithinTag; + this.lastAttributeName = null; return this.tokenize(stream); // no advance yet - jump to WithinTag } } diff --git a/src/vs/languages/html/test/common/html.test.ts b/src/vs/languages/html/test/common/html.test.ts index 1fc0e908554..815d37c56d2 100644 --- a/src/vs/languages/html/test/common/html.test.ts +++ b/src/vs/languages/html/test/common/html.test.ts @@ -466,7 +466,7 @@ suite('Colorizing - HTML', () => { ]); }); - test('Tag with empty atrributes', () => { + test('Tag with empty attributes', () => { modesUtil.assertTokenization(tokenizationSupport, [{ line: '', tokens: [ @@ -580,7 +580,7 @@ suite('Colorizing - HTML', () => { ]); }); - test('Tag with Invalid Attribute Name', () => { + test('Tag with Interesting Attribute Name', () => { modesUtil.assertTokenization(tokenizationSupport, [{ line: '', tokens: [ @@ -588,14 +588,38 @@ suite('Colorizing - HTML', () => { { startIndex:1, type: getTag('abc') }, { startIndex:4, type: '' }, { startIndex:5, type: ATTRIB_NAME }, - { startIndex:8, type: '' }, - { startIndex:13, type: ATTRIB_NAME }, - { startIndex:16, type: '' }, + { startIndex:11, type: DELIM_ASSIGN }, + { startIndex:12, type: ATTRIB_VALUE }, { startIndex:17, type: DELIM_START } ]} ]); }); + test('Tag with Angular Attribute Name', () => { + modesUtil.assertTokenization(tokenizationSupport, [{ + line: '', + tokens: [ + { startIndex:0, type: DELIM_START }, + { startIndex:1, type: getTag('abc') }, + { startIndex:4, type: '' }, + { startIndex:5, type: ATTRIB_NAME }, + { startIndex:13, type: '' }, + { startIndex:14, type: ATTRIB_NAME }, + { startIndex:21, type: DELIM_ASSIGN }, + { startIndex:22, type: ATTRIB_VALUE }, + { startIndex:27, type: '' }, + { startIndex:28, type: ATTRIB_NAME }, + { startIndex:35, type: DELIM_ASSIGN }, + { startIndex:36, type: ATTRIB_VALUE }, + { startIndex:50, type: '' }, + { startIndex:51, type: ATTRIB_NAME }, + { startIndex:56, type: DELIM_ASSIGN }, + { startIndex:57, type: ATTRIB_VALUE }, + { startIndex:72, type: DELIM_START } + ]} + ]); + }); + test('Tag with Invalid Attribute Value', () => { modesUtil.assertTokenization(tokenizationSupport, [{ line: '