@@ -8,7 +8,10 @@ import {
88 CompletionItemKind ,
99 CompletionItemProvider ,
1010 CompletionList ,
11+ Diagnostic ,
12+ DiagnosticSeverity ,
1113 Disposable ,
14+ languages ,
1215 Position ,
1316 ProviderResult ,
1417 Range ,
@@ -24,17 +27,24 @@ export class ClassCompletionItemProvider implements CompletionItemProvider, Disp
2427 readonly cache = new Map < string , Map < string , CompletionItem > > ( ) ;
2528 readonly extends = new Map < string , Set < string > > ( ) ;
2629 readonly watchers = new Map < string , Disposable > ( ) ;
30+ readonly collection = languages . createDiagnosticCollection ( "vscode-html-css" ) ;
2731 readonly isRemote = / ^ h t t p s ? : \/ \/ / i;
2832 readonly canComplete = / ( i d | c l a s s | c l a s s N a m e ) \s * = \s * ( " | ' ) (?: (? ! \2) .) * $ / si;
2933 readonly findLinkRel = / r e l \s * = \s * ( " | ' ) ( (?: (? ! \1) .) + ) \1/ si;
3034 readonly findLinkHref = / h r e f \s * = \s * ( " | ' ) ( (?: (? ! \1) .) + ) \1/ si;
3135 readonly findExtended = / (?: { { <| { % \s * e x t e n d s | @ e x t e n d s \s * \( ) \s * ( " | ' ) ? ( [ . / A - Z a - z _ 0 - 9 \\ \- ] + ) \1\s * (?: \) | % } | } } ) / i;
3236
33- dispose ( ) {
34- for ( const watcher of this . watchers . values ( ) ) {
35- watcher . dispose ( ) ;
36- }
37+ constructor ( ) {
38+ let debounce : NodeJS . Timeout ;
3739
40+ this . watchers . set ( "changed" , workspace . onDidChangeTextDocument ( e => {
41+ clearTimeout ( debounce ) ;
42+ debounce = setTimeout ( ( ) => this . validate ( e . document ) , 1000 ) ;
43+ } ) ) ;
44+ }
45+
46+ dispose ( ) {
47+ this . watchers . forEach ( v => v . dispose ( ) ) ;
3848 this . cache . clear ( ) ;
3949 this . extends . clear ( ) ;
4050 this . watchers . clear ( ) ;
@@ -283,4 +293,59 @@ export class ClassCompletionItemProvider implements CompletionItemProvider, Disp
283293 }
284294 } ) ;
285295 }
296+
297+ validate ( document : TextDocument ) {
298+ const uri = document . uri ;
299+ const text = document . getText ( ) ;
300+
301+ this . findAll ( uri , text ) . then ( sets => {
302+ const ids = new Set < string > ( ) ;
303+ const classes = new Set < string > ( ) ;
304+
305+ sets . forEach ( set => set . forEach ( key => this . getItems ( key ) ?. forEach ( ( v , k ) => {
306+ if ( v . kind === CompletionItemKind . Value ) {
307+ ids . add ( k ) ;
308+ } else {
309+ classes . add ( k ) ;
310+ }
311+ } ) ) ) ;
312+
313+ const diagnostics : Diagnostic [ ] = [ ] ;
314+ const findAttribute = / ( i d | c l a s s | c l a s s N a m e ) \s * = \s * ( " | ' ) ( .+ ?) \2/ gsi;
315+
316+ let attribute ;
317+
318+ while ( ( attribute = findAttribute . exec ( text ) ) !== null ) {
319+ const offset = findAttribute . lastIndex
320+ - attribute [ 3 ] . length
321+ + attribute [ 3 ] . indexOf ( attribute [ 2 ] ) ;
322+
323+ const findValue = / ( [ ^ \s ] + ) / gi;
324+
325+ let value ;
326+
327+ while ( ( value = findValue . exec ( attribute [ 3 ] ) ) !== null ) {
328+ const anchor = findValue . lastIndex + offset ;
329+ const end = document . positionAt ( anchor ) ;
330+ const start = document . positionAt ( anchor - value [ 1 ] . length ) ;
331+
332+ if ( attribute [ 1 ] === "id" ) {
333+ if ( ! ids . has ( value [ 1 ] ) ) {
334+ diagnostics . push ( new Diagnostic ( new Range ( start , end ) ,
335+ `CSS id selector '${ value [ 1 ] } ' not found.` ,
336+ DiagnosticSeverity . Warning ) ) ;
337+ }
338+ } else {
339+ if ( ! classes . has ( value [ 1 ] ) ) {
340+ diagnostics . push ( new Diagnostic ( new Range ( start , end ) ,
341+ `CSS class selector '${ value [ 1 ] } ' not found.` ,
342+ DiagnosticSeverity . Warning ) ) ;
343+ }
344+ }
345+ }
346+ }
347+
348+ this . collection . set ( uri , diagnostics ) ;
349+ } ) ;
350+ }
286351}
0 commit comments