Skip to content

Commit 9f4b31a

Browse files
committed
Add detection of mismatched!#if-!#endif in linter
Related issue: uBlockOrigin/uBlock-issues#1712
1 parent ac81430 commit 9f4b31a

File tree

4 files changed

+107
-31
lines changed

4 files changed

+107
-31
lines changed

src/css/codemirror.css

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -289,16 +289,14 @@
289289
.CodeMirror-lintmarker > * {
290290
position: absolute;
291291
}
292-
.CodeMirror-lintmarker[data-lint="error"] {
292+
.CodeMirror-lintmarker[data-error="y"] {
293293
background-color: var(--sf-error-ink);
294294
}
295-
.CodeMirror-lintmarker[data-lint="error"] .msg {
296-
display: none;
297-
}
298-
.CodeMirror-lintmarker[data-lint="error"] .msg {
295+
.CodeMirror-lintmarker .msg {
299296
background-color: var(--surface-0);
300297
border: 1px solid var(--sf-error-ink);
301298
color: var(--ink-1);
299+
display: none;
302300
filter: drop-shadow(2px 2px 4px #0008);
303301
left: 100%;
304302
padding: var(--default-gap-xsmall);
@@ -311,6 +309,9 @@
311309
top: 15%;
312310
width: 70%;
313311
}
312+
.CodeMirror-lintmarker[data-error="y"] svg {
313+
display: none;
314+
}
314315
.CodeMirror-lintmarker[data-fold="start"] {
315316
fill: var(--cm-foldmarker-ink);
316317
}
@@ -320,7 +321,7 @@
320321
.CodeMirror-lintmarker[data-fold="end"] {
321322
fill: var(--border-2);
322323
}
323-
.CodeMirror-lintmarker[data-lint="error"]:hover > span,
324-
.CodeMirror-lintmarker[data-lint="error"] > span:hover {
324+
.CodeMirror-lintmarker[data-error="y"]:hover > span,
325+
.CodeMirror-lintmarker[data-error="y"] > span:hover {
325326
display: initial;
326327
}

src/js/codemirror/search.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ import { i18n$ } from '../i18n.js';
377377
if ( markers === null ) { return; }
378378
const marker = markers['CodeMirror-lintgutter'];
379379
if ( marker === undefined ) { return; }
380-
if ( marker.dataset.lint !== 'error' ) { return; }
380+
if ( marker.dataset.error !== 'y' ) { return; }
381381
const line = lineHandle.lineNo();
382382
if ( dir < 0 ) {
383383
found = line;

src/js/codemirror/ubo-static-filtering.js

Lines changed: 97 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -699,9 +699,12 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
699699
const includeset = new Set();
700700
let errorCount = 0;
701701

702+
const ifendifSet = new Set();
703+
let ifendifSetChanged = false;
704+
702705
const extractMarkerDetails = (doc, lineHandle) => {
703706
if ( astParser.isUnsupported() ) {
704-
return { value: 'error', msg: 'Unsupported filter syntax' };
707+
return { lint: 'error', msg: 'Unsupported filter syntax' };
705708
}
706709
if ( astParser.hasError() ) {
707710
let msg = 'Invalid filter';
@@ -739,23 +742,31 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
739742
}
740743
break;
741744
}
742-
return { value: 'error', msg };
745+
return { lint: 'error', msg };
743746
}
744747
if ( astParser.astType !== sfp.AST_TYPE_COMMENT ) { return; }
745748
if ( astParser.astTypeFlavor !== sfp.AST_TYPE_COMMENT_PREPARSER ) {
746749
if ( astParser.raw.startsWith('! <<<<<<<< ') === false ) { return; }
747750
for ( const include of includeset ) {
748751
if ( astParser.raw.endsWith(include) === false ) { continue; }
749752
includeset.delete(include);
750-
return { value: 'include-end' };
753+
return { lint: 'include-end' };
751754
}
752755
return;
753756
}
754757
if ( /^\s*!#if \S+/.test(astParser.raw) ) {
755-
return { value: 'if-start' };
758+
return {
759+
lint: 'if-start',
760+
data: {
761+
state: sfp.utils.preparser.evaluateExpr(
762+
astParser.getTypeString(sfp.NODE_TYPE_PREPARSE_DIRECTIVE_IF_VALUE),
763+
preparseDirectiveEnv
764+
) ? 'y' : 'n'
765+
}
766+
};
756767
}
757768
if ( /^\s*!#endif\b/.test(astParser.raw) ) {
758-
return { value: 'if-end' };
769+
return { lint: 'if-end' };
759770
}
760771
const match = /^\s*!#include\s*(\S+)/.exec(astParser.raw);
761772
if ( match === null ) { return; }
@@ -765,7 +776,7 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
765776
const includeToken = `/${match[1]}`;
766777
if ( nextLineHandle.text.endsWith(includeToken) === false ) { return; }
767778
includeset.add(includeToken);
768-
return { value: 'include-start' };
779+
return { lint: 'include-start' };
769780
};
770781

771782
const extractMarker = lineHandle => {
@@ -779,18 +790,19 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
779790
'error': {
780791
node: null,
781792
html: [
782-
'<div class="CodeMirror-lintmarker" data-lint="error">&nbsp;',
793+
'<div class="CodeMirror-lintmarker" data-lint="error" data-error="y">&nbsp;',
783794
'<span class="msg"></span>',
784795
'</div>',
785796
],
786797
},
787798
'if-start': {
788799
node: null,
789800
html: [
790-
'<div class="CodeMirror-lintmarker" data-lint="if" data-fold="start">&nbsp;',
801+
'<div class="CodeMirror-lintmarker" data-lint="if" data-fold="start" data-state="">&nbsp;',
791802
'<svg viewBox="0 0 100 100">',
792803
'<polygon points="0,0 100,0 50,100" />',
793804
'</svg>',
805+
'<span class="msg">Mismatched if-endif directive</span>',
794806
'</div>',
795807
],
796808
},
@@ -801,6 +813,7 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
801813
'<svg viewBox="0 0 100 100">',
802814
'<polygon points="50,0 100,100 0,100" />',
803815
'</svg>',
816+
'<span class="msg">Mismatched if-endif directive</span>',
804817
'</div>',
805818
],
806819
},
@@ -826,52 +839,111 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
826839
},
827840
};
828841

829-
const markerFromTemplate = which => {
830-
const template = markerTemplates[which];
842+
const markerFromTemplate = details => {
843+
const template = markerTemplates[details.lint];
831844
if ( template.node === null ) {
832845
const domParser = new DOMParser();
833846
const doc = domParser.parseFromString(template.html.join(''), 'text/html');
834847
template.node = document.adoptNode(qs$(doc, '.CodeMirror-lintmarker'));
835848
}
836-
return template.node.cloneNode(true);
849+
const node = template.node.cloneNode(true);
850+
if ( details.data instanceof Object ) {
851+
for ( const [ k, v ] of Object.entries(details.data) ) {
852+
node.dataset[k] = `${v}`;
853+
}
854+
}
855+
return node;
837856
};
838857

839858
const addMarker = (doc, lineHandle, marker, details) => {
840-
if ( marker !== null && marker.dataset.lint !== details.value ) {
859+
if ( marker && marker.dataset.lint !== details.lint ) {
841860
doc.setGutterMarker(lineHandle, 'CodeMirror-lintgutter', null);
842-
if ( marker.dataset.lint === 'error' ) {
861+
if ( marker.dataset.error === 'y' ) {
843862
errorCount -= 1;
844863
}
864+
if ( marker.dataset.lint === 'if' ) {
865+
ifendifSet.delete(lineHandle);
866+
ifendifSetChanged = true;
867+
}
845868
marker = null;
846869
}
847870
if ( marker === null ) {
848-
marker = markerFromTemplate(details.value);
871+
marker = markerFromTemplate(details);
849872
doc.setGutterMarker(lineHandle, 'CodeMirror-lintgutter', marker);
850-
if ( details.value === 'error' ) {
873+
if ( marker.dataset.error === 'y' ) {
851874
errorCount += 1;
852875
}
876+
if ( marker.dataset.lint === 'if' ) {
877+
ifendifSet.add(lineHandle);
878+
ifendifSetChanged = true;
879+
}
853880
}
881+
if ( typeof details.msg !== 'string' || details.msg === '' ) { return; }
854882
const msgElem = qs$(marker, '.msg');
855883
if ( msgElem === null ) { return; }
856-
msgElem.textContent = details.msg || '';
884+
msgElem.textContent = details.msg;
857885
};
858886

859887
const removeMarker = (doc, lineHandle, marker) => {
860888
doc.setGutterMarker(lineHandle, 'CodeMirror-lintgutter', null);
861-
if ( marker.dataset.lint === 'error' ) {
889+
if ( marker.dataset.error === 'y' ) {
862890
errorCount -= 1;
863891
}
892+
if ( marker.dataset.lint === 'if' ) {
893+
ifendifSet.delete(lineHandle);
894+
ifendifSetChanged = true;
895+
}
896+
};
897+
898+
// Analyze whether all if-endif are properly paired
899+
const processIfendifs = ( ) => {
900+
if ( ifendifSet.size === 0 ) { return; }
901+
if ( ifendifSetChanged !== true ) { return; }
902+
const sortFn = (a, b) => a.lineNo() - b.lineNo();
903+
const sorted = Array.from(ifendifSet).sort(sortFn);
904+
const bad = [];
905+
const stack = [];
906+
for ( const line of sorted ) {
907+
const marker = extractMarker(line);
908+
const fold = marker.dataset.fold;
909+
if ( fold === 'start' ) {
910+
stack.push(line);
911+
} else if ( fold === 'end' ) {
912+
if ( stack.length !== 0 ) {
913+
if ( marker.dataset.error === 'y' ) {
914+
marker.dataset.error = '';
915+
errorCount -= 1;
916+
}
917+
const ifstart = extractMarker(stack.pop());
918+
if ( ifstart.dataset.error === 'y' ) {
919+
ifstart.dataset.error = '';
920+
errorCount -= 1;
921+
}
922+
} else {
923+
bad.push(line);
924+
}
925+
}
926+
}
927+
bad.push(...stack);
928+
for ( const line of bad ) {
929+
const marker = extractMarker(line);
930+
marker.dataset.error = 'y';
931+
errorCount += 1;
932+
}
933+
ifendifSetChanged = false;
864934
};
865935

866936
const processDeletion = (doc, change) => {
867937
let { from, to } = change;
868938
doc.eachLine(from.line, to.line, lineHandle => {
869939
const marker = extractMarker(lineHandle);
870940
if ( marker === null ) { return; }
871-
if ( marker.dataset.lint === 'error' ) {
941+
if ( marker.dataset.error === 'y' ) {
942+
marker.dataset.error = '';
872943
errorCount -= 1;
873-
marker.dataset.lint = 'void';
874944
}
945+
ifendifSet.delete(lineHandle);
946+
ifendifSetChanged = true;
875947
});
876948
};
877949

@@ -881,10 +953,10 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
881953
astParser.parse(lineHandle.text);
882954
const markerDetails = extractMarkerDetails(doc, lineHandle);
883955
const marker = extractMarker(lineHandle);
884-
if ( markerDetails === undefined && marker !== null ) {
885-
removeMarker(doc, lineHandle, marker);
886-
} else if ( markerDetails !== undefined ) {
956+
if ( markerDetails !== undefined ) {
887957
addMarker(doc, lineHandle, marker, markerDetails);
958+
} else if ( marker !== null ) {
959+
removeMarker(doc, lineHandle, marker);
888960
}
889961
from += 1;
890962
if ( (from & 0x0F) !== 0 ) { return; }
@@ -910,6 +982,7 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
910982
return processChangesetAsync(doc);
911983
}
912984
includeset.clear();
985+
processIfendifs(doc);
913986
CodeMirror.signal(doc.getEditor(), 'linterDone', { errorCount });
914987
};
915988

@@ -922,13 +995,14 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
922995
};
923996

924997
const onChanges = (cm, changes) => {
998+
if ( changes.length === 0 ) { return; }
925999
const doc = cm.getDoc();
9261000
for ( const change of changes ) {
9271001
const from = change.from.line;
9281002
const to = from + change.text.length;
9291003
changeset.push({ from, to });
930-
processChangesetAsync(doc);
9311004
}
1005+
processChangesetAsync(doc);
9321006
};
9331007

9341008
const onBeforeChanges = (cm, change) => {

src/js/static-filtering-parser.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,6 +1224,7 @@ export class AstFilterParser {
12241224
? NODE_TYPE_PREPARSE_DIRECTIVE_IF_VALUE
12251225
: NODE_TYPE_PREPARSE_DIRECTIVE_VALUE;
12261226
const next = this.allocTypedNode(type, directiveEnd, parentEnd);
1227+
this.addNodeToRegister(type, next);
12271228
this.linkRight(head, next);
12281229
if ( type === NODE_TYPE_PREPARSE_DIRECTIVE_IF_VALUE ) {
12291230
const rawToken = this.getNodeString(next).trim();

0 commit comments

Comments
 (0)