1+ import * as vscode from "vscode" ;
2+ import * as htmlService from "vscode-html-languageservice" ;
3+ import { ITagData } from "../interface/tagData"
4+ import { CustomElement } from "../class/customElement" ;
5+ import { HtmlTagTemplate } from "../interface/IHtmlTemplate" ;
6+ import { extractHtmlTagWithAttr } from "../utils/extractHtmlTagWithAttr"
7+ import { createPositions } from "../utils/createPosition" ;
8+ import { createVscodeDiagnostic } from "../utils/createVscodeDiagnostic" ;
9+ import { IHtmlValidator } from "../interface/IHtmlValidator" ;
10+
11+ type Parse5 = typeof import ( "parse5" , { with : { "resolution-mode" : "import" } } ) ;
12+ const htmlLanguageService = htmlService . getDefaultHTMLDataProvider ( ) ;
13+ const customHtmlElements = new CustomElement ( ) ;
14+
15+ export class HtmlValidator implements IHtmlValidator {
16+ private validTagNames : Set < string > ;
17+ private ps : Parse5 ;
18+ private document : vscode . TextDocument ;
19+ private diagnosticCollection : vscode . Diagnostic [ ] ;
20+ private errorCollection : vscode . DiagnosticCollection
21+
22+ constructor (
23+ ps : Parse5 ,
24+ document : vscode . TextDocument ,
25+ diagnosticCollection : vscode . Diagnostic [ ]
26+ ) {
27+ this . ps = ps ;
28+ this . document = document ;
29+ this . diagnosticCollection = diagnosticCollection ;
30+ this . validTagNames = new Set ( htmlLanguageService . provideTags ( ) . map ( t => t . name ) ) ;
31+ this . errorCollection = vscode . languages . createDiagnosticCollection ( "myExtension" ) ;
32+ }
33+
34+ validate ( htmlTemplateArray : Array < { htmlTemplate : HtmlTagTemplate } > ) : void {
35+ for ( const template of htmlTemplateArray ) {
36+ this . validateTemplate ( template ) ;
37+ }
38+ this . errorCollection . set ( this . document . uri , this . diagnosticCollection ) ;
39+ }
40+
41+ private validateTemplate ( template : { htmlTemplate : HtmlTagTemplate } ) : void {
42+ const parsedHtml = this . ps . parseFragment (
43+ template . htmlTemplate . content ,
44+ { sourceCodeLocationInfo : true }
45+ ) ;
46+
47+ for ( const node of parsedHtml . childNodes ) {
48+ this . validateNode ( node , template ) ;
49+ }
50+ }
51+
52+ private validateNode ( node : any , template : { htmlTemplate : HtmlTagTemplate } ) : void {
53+ const tagData = extractHtmlTagWithAttr ( node ) ;
54+
55+ if ( this . isValidTag ( tagData . name ) ) {
56+ this . validateAttributes ( tagData , template ) ;
57+ } else if ( tagData . name && tagData . name !== "#text" ) {
58+ this . addInvalidTagDiagnostic ( tagData , template ) ;
59+ }
60+ }
61+
62+ private isValidTag ( tagName : string ) : boolean {
63+ return this . validTagNames . has ( tagName ) ;
64+ }
65+
66+ private validateAttributes (
67+ tagData : ITagData ,
68+ template : { htmlTemplate : HtmlTagTemplate }
69+ ) : void {
70+ for ( const attribute of tagData . attributes ) {
71+ if ( ! this . isValidAttribute ( attribute . name , tagData . name ) ) {
72+ this . addInvalidAttributeDiagnostic ( attribute , tagData . name , template ) ;
73+ }
74+ }
75+ }
76+
77+ private isValidAttribute ( attributeName : string , tagName : string ) : boolean {
78+ if ( attributeName === "#text" ) return true ;
79+
80+ const validAttributeNames = new Set (
81+ htmlLanguageService . provideAttributes ( tagName ) . map ( t => t . name )
82+ ) ;
83+
84+ const customValidAttributeNames = new Set (
85+ customHtmlElements . provideAttributes ( tagName ) . map ( t => t . name )
86+ ) ;
87+
88+ return validAttributeNames . has ( attributeName ) ||
89+ customValidAttributeNames . has ( attributeName ) ;
90+ }
91+
92+ private addInvalidAttributeDiagnostic (
93+ attribute : { name : string ; startOffset : number ; endOffset : number } ,
94+ tagName : string ,
95+ template : { htmlTemplate : HtmlTagTemplate }
96+ ) : void {
97+ const positions = createPositions (
98+ template . htmlTemplate . tag ,
99+ template . htmlTemplate . pos ,
100+ attribute . startOffset ,
101+ attribute . endOffset
102+ ) ;
103+
104+ this . diagnosticCollection . push (
105+ createVscodeDiagnostic (
106+ positions . globalStartOffset ,
107+ positions . globalEndOffset ,
108+ `Attribute ${ attribute . name } is unknown or not valid for ${ tagName } .` ,
109+ vscode . DiagnosticSeverity . Warning ,
110+ this . document
111+ )
112+ ) ;
113+ }
114+
115+ private addInvalidTagDiagnostic (
116+ tagData : ITagData ,
117+ template : { htmlTemplate : HtmlTagTemplate }
118+ ) : void {
119+ const positions = createPositions (
120+ template . htmlTemplate . tag ,
121+ template . htmlTemplate . pos ,
122+ tagData . startOffset ,
123+ tagData . endOffset
124+ ) ;
125+
126+ this . diagnosticCollection . push (
127+ createVscodeDiagnostic (
128+ positions . globalStartOffset ,
129+ positions . globalEndOffset ,
130+ `Tag ${ tagData . name } is unknown. Check for typos.` ,
131+ vscode . DiagnosticSeverity . Warning ,
132+ this . document
133+ )
134+ ) ;
135+ }
136+ }
0 commit comments