395f0d931498321bb08d7559bb9e643083ec8518
1 // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 // Distributed under an MIT license: https://codemirror.net/LICENSE
5 if (typeof exports
== "object" && typeof module
== "object") // CommonJS
6 mod(require("../../lib/codemirror"));
7 else if (typeof define
== "function" && define
.amd
) // AMD
8 define(["../../lib/codemirror"], mod
);
9 else // Plain browser env
11 })(function(CodeMirror
) {
13 var GUTTER_ID
= "CodeMirror-lint-markers";
15 function showTooltip(cm
, e
, content
) {
16 var tt
= document
.createElement("div");
17 tt
.className
= "CodeMirror-lint-tooltip cm-s-" + cm
.options
.theme
;
18 tt
.appendChild(content
.cloneNode(true));
19 if (cm
.state
.lint
.options
.selfContain
)
20 cm
.getWrapperElement().appendChild(tt
);
22 document
.body
.appendChild(tt
);
24 function position(e
) {
25 if (!tt
.parentNode
) return CodeMirror
.off(document
, "mousemove", position
);
26 tt
.style
.top
= Math
.max(0, e
.clientY
- tt
.offsetHeight
- 5) + "px";
27 tt
.style
.left
= (e
.clientX
+ 5) + "px";
29 CodeMirror
.on(document
, "mousemove", position
);
31 if (tt
.style
.opacity
!= null) tt
.style
.opacity
= 1;
35 if (elt
.parentNode
) elt
.parentNode
.removeChild(elt
);
37 function hideTooltip(tt
) {
38 if (!tt
.parentNode
) return;
39 if (tt
.style
.opacity
== null) rm(tt
);
41 setTimeout(function() { rm(tt
); }, 600);
44 function showTooltipFor(cm
, e
, content
, node
) {
45 var tooltip
= showTooltip(cm
, e
, content
);
47 CodeMirror
.off(node
, "mouseout", hide
);
48 if (tooltip
) { hideTooltip(tooltip
); tooltip
= null; }
50 var poll
= setInterval(function() {
51 if (tooltip
) for (var n
= node
;; n
= n
.parentNode
) {
52 if (n
&& n
.nodeType
== 11) n
= n
.host
;
53 if (n
== document
.body
) return;
54 if (!n
) { hide(); break; }
56 if (!tooltip
) return clearInterval(poll
);
58 CodeMirror
.on(node
, "mouseout", hide
);
61 function LintState(cm
, options
, hasGutter
) {
63 this.options
= options
;
65 this.hasGutter
= hasGutter
;
66 this.onMouseOver = function(e
) { onMouseOver(cm
, e
); };
70 function parseOptions(_cm
, options
) {
71 if (options
instanceof Function
) return {getAnnotations
: options
};
72 if (!options
|| options
=== true) options
= {};
76 function clearMarks(cm
) {
77 var state
= cm
.state
.lint
;
78 if (state
.hasGutter
) cm
.clearGutter(GUTTER_ID
);
79 for (var i
= 0; i
< state
.marked
.length
; ++i
)
80 state
.marked
[i
].clear();
81 state
.marked
.length
= 0;
84 function makeMarker(cm
, labels
, severity
, multiple
, tooltips
) {
85 var marker
= document
.createElement("div"), inner
= marker
;
86 marker
.className
= "CodeMirror-lint-marker CodeMirror-lint-marker-" + severity
;
88 inner
= marker
.appendChild(document
.createElement("div"));
89 inner
.className
= "CodeMirror-lint-marker CodeMirror-lint-marker-multiple";
92 if (tooltips
!= false) CodeMirror
.on(inner
, "mouseover", function(e
) {
93 showTooltipFor(cm
, e
, labels
, inner
);
99 function getMaxSeverity(a
, b
) {
100 if (a
== "error") return a
;
104 function groupByLine(annotations
) {
106 for (var i
= 0; i
< annotations
.length
; ++i
) {
107 var ann
= annotations
[i
], line
= ann
.from.line
;
108 (lines
[line
] || (lines
[line
] = [])).push(ann
);
113 function annotationTooltip(ann
) {
114 var severity
= ann
.severity
;
115 if (!severity
) severity
= "error";
116 var tip
= document
.createElement("div");
117 tip
.className
= "CodeMirror-lint-message CodeMirror-lint-message-" + severity
;
118 if (typeof ann
.messageHTML
!= 'undefined') {
119 tip
.innerHTML
= ann
.messageHTML
;
121 tip
.appendChild(document
.createTextNode(ann
.message
));
126 function lintAsync(cm
, getAnnotations
, passOptions
) {
127 var state
= cm
.state
.lint
128 var id
= ++state
.waitingFor
131 cm
.off("change", abort
)
133 cm
.on("change", abort
)
134 getAnnotations(cm
.getValue(), function(annotations
, arg2
) {
135 cm
.off("change", abort
)
136 if (state
.waitingFor
!= id
) return
137 if (arg2
&& annotations
instanceof CodeMirror
) annotations
= arg2
138 cm
.operation(function() {updateLinting(cm
, annotations
)})
142 function startLinting(cm
) {
143 var state
= cm
.state
.lint
, options
= state
.options
;
145 * Passing rules in `options` property prevents JSHint (and other linters) from complaining
146 * about unrecognized rules like `onUpdateLinting`, `delay`, `lintOnChange`, etc.
148 var passOptions
= options
.options
|| options
;
149 var getAnnotations
= options
.getAnnotations
|| cm
.getHelper(CodeMirror
.Pos(0, 0), "lint");
150 if (!getAnnotations
) return;
151 if (options
.async
|| getAnnotations
.async
) {
152 lintAsync(cm
, getAnnotations
, passOptions
)
154 var annotations
= getAnnotations(cm
.getValue(), passOptions
, cm
);
155 if (!annotations
) return;
156 if (annotations
.then
) annotations
.then(function(issues
) {
157 cm
.operation(function() {updateLinting(cm
, issues
)})
159 else cm
.operation(function() {updateLinting(cm
, annotations
)})
163 function updateLinting(cm
, annotationsNotSorted
) {
165 var state
= cm
.state
.lint
, options
= state
.options
;
167 var annotations
= groupByLine(annotationsNotSorted
);
169 for (var line
= 0; line
< annotations
.length
; ++line
) {
170 var anns
= annotations
[line
];
173 // filter out duplicate messages
175 anns
= anns
.filter(function(item
) { return message
.indexOf(item
.message
) > -1 ? false : message
.push(item
.message
) });
177 var maxSeverity
= null;
178 var tipLabel
= state
.hasGutter
&& document
.createDocumentFragment();
180 for (var i
= 0; i
< anns
.length
; ++i
) {
182 var severity
= ann
.severity
;
183 if (!severity
) severity
= "error";
184 maxSeverity
= getMaxSeverity(maxSeverity
, severity
);
186 if (options
.formatAnnotation
) ann
= options
.formatAnnotation(ann
);
187 if (state
.hasGutter
) tipLabel
.appendChild(annotationTooltip(ann
));
189 if (ann
.to
) state
.marked
.push(cm
.markText(ann
.from, ann
.to
, {
190 className
: "CodeMirror-lint-mark CodeMirror-lint-mark-" + severity
,
194 // use original annotations[line] to show multiple messages
196 cm
.setGutterMarker(line
, GUTTER_ID
, makeMarker(cm
, tipLabel
, maxSeverity
, annotations
[line
].length
> 1,
197 state
.options
.tooltips
));
199 if (options
.onUpdateLinting
) options
.onUpdateLinting(annotationsNotSorted
, annotations
, cm
);
202 function onChange(cm
) {
203 var state
= cm
.state
.lint
;
205 clearTimeout(state
.timeout
);
206 state
.timeout
= setTimeout(function(){startLinting(cm
);}, state
.options
.delay
|| 500);
209 function popupTooltips(cm
, annotations
, e
) {
210 var target
= e
.target
|| e
.srcElement
;
211 var tooltip
= document
.createDocumentFragment();
212 for (var i
= 0; i
< annotations
.length
; i
++) {
213 var ann
= annotations
[i
];
214 tooltip
.appendChild(annotationTooltip(ann
));
216 showTooltipFor(cm
, e
, tooltip
, target
);
219 function onMouseOver(cm
, e
) {
220 var target
= e
.target
|| e
.srcElement
;
221 if (!/\bCodeMirror-lint-mark-/.test(target
.className
)) return;
222 var box
= target
.getBoundingClientRect(), x
= (box
.left
+ box
.right
) / 2, y
= (box
.top
+ box
.bottom
) / 2;
223 var spans
= cm
.findMarksAt(cm
.coordsChar({left
: x
, top
: y
}, "client"));
225 var annotations
= [];
226 for (var i
= 0; i
< spans
.length
; ++i
) {
227 var ann
= spans
[i
].__annotation
;
228 if (ann
) annotations
.push(ann
);
230 if (annotations
.length
) popupTooltips(cm
, annotations
, e
);
233 CodeMirror
.defineOption("lint", false, function(cm
, val
, old
) {
234 if (old
&& old
!= CodeMirror
.Init
) {
236 if (cm
.state
.lint
.options
.lintOnChange
!== false)
237 cm
.off("change", onChange
);
238 CodeMirror
.off(cm
.getWrapperElement(), "mouseover", cm
.state
.lint
.onMouseOver
);
239 clearTimeout(cm
.state
.lint
.timeout
);
240 delete cm
.state
.lint
;
244 var gutters
= cm
.getOption("gutters"), hasLintGutter
= false;
245 for (var i
= 0; i
< gutters
.length
; ++i
) if (gutters
[i
] == GUTTER_ID
) hasLintGutter
= true;
246 var state
= cm
.state
.lint
= new LintState(cm
, parseOptions(cm
, val
), hasLintGutter
);
247 if (state
.options
.lintOnChange
!== false)
248 cm
.on("change", onChange
);
249 if (state
.options
.tooltips
!= false && state
.options
.tooltips
!= "gutter")
250 CodeMirror
.on(cm
.getWrapperElement(), "mouseover", state
.onMouseOver
);
256 CodeMirror
.defineExtension("performLint", function() {
257 if (this.state
.lint
) startLinting(this);