Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
codemirror5/addon/lint/lint.js /
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
291 lines (258 sloc)
9.75 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // CodeMirror, copyright (c) by Marijn Haverbeke and others | |
| // Distributed under an MIT license: https://codemirror.net/5/LICENSE | |
| (function(mod) { | |
| if (typeof exports == "object" && typeof module == "object") // CommonJS | |
| mod(require("../../lib/codemirror")); | |
| else if (typeof define == "function" && define.amd) // AMD | |
| define(["../../lib/codemirror"], mod); | |
| else // Plain browser env | |
| mod(CodeMirror); | |
| })(function(CodeMirror) { | |
| "use strict"; | |
| var GUTTER_ID = "CodeMirror-lint-markers"; | |
| var LINT_LINE_ID = "CodeMirror-lint-line-"; | |
| function showTooltip(cm, e, content) { | |
| var tt = document.createElement("div"); | |
| tt.className = "CodeMirror-lint-tooltip cm-s-" + cm.options.theme; | |
| tt.appendChild(content.cloneNode(true)); | |
| if (cm.state.lint.options.selfContain) | |
| cm.getWrapperElement().appendChild(tt); | |
| else | |
| document.body.appendChild(tt); | |
| function position(e) { | |
| if (!tt.parentNode) return CodeMirror.off(document, "mousemove", position); | |
| tt.style.top = Math.max(0, e.clientY - tt.offsetHeight - 5) + "px"; | |
| tt.style.left = (e.clientX + 5) + "px"; | |
| } | |
| CodeMirror.on(document, "mousemove", position); | |
| position(e); | |
| if (tt.style.opacity != null) tt.style.opacity = 1; | |
| return tt; | |
| } | |
| function rm(elt) { | |
| if (elt.parentNode) elt.parentNode.removeChild(elt); | |
| } | |
| function hideTooltip(tt) { | |
| if (!tt.parentNode) return; | |
| if (tt.style.opacity == null) rm(tt); | |
| tt.style.opacity = 0; | |
| setTimeout(function() { rm(tt); }, 600); | |
| } | |
| function showTooltipFor(cm, e, content, node) { | |
| var tooltip = showTooltip(cm, e, content); | |
| function hide() { | |
| CodeMirror.off(node, "mouseout", hide); | |
| if (tooltip) { hideTooltip(tooltip); tooltip = null; } | |
| } | |
| var poll = setInterval(function() { | |
| if (tooltip) for (var n = node;; n = n.parentNode) { | |
| if (n && n.nodeType == 11) n = n.host; | |
| if (n == document.body) return; | |
| if (!n) { hide(); break; } | |
| } | |
| if (!tooltip) return clearInterval(poll); | |
| }, 400); | |
| CodeMirror.on(node, "mouseout", hide); | |
| } | |
| function LintState(cm, conf, hasGutter) { | |
| this.marked = []; | |
| if (conf instanceof Function) conf = {getAnnotations: conf}; | |
| if (!conf || conf === true) conf = {}; | |
| this.options = {}; | |
| this.linterOptions = conf.options || {}; | |
| for (var prop in defaults) this.options[prop] = defaults[prop]; | |
| for (var prop in conf) { | |
| if (defaults.hasOwnProperty(prop)) { | |
| if (conf[prop] != null) this.options[prop] = conf[prop]; | |
| } else if (!conf.options) { | |
| this.linterOptions[prop] = conf[prop]; | |
| } | |
| } | |
| this.timeout = null; | |
| this.hasGutter = hasGutter; | |
| this.onMouseOver = function(e) { onMouseOver(cm, e); }; | |
| this.waitingFor = 0 | |
| } | |
| var defaults = { | |
| highlightLines: false, | |
| tooltips: true, | |
| delay: 500, | |
| lintOnChange: true, | |
| getAnnotations: null, | |
| async: false, | |
| selfContain: null, | |
| formatAnnotation: null, | |
| onUpdateLinting: null | |
| } | |
| function clearMarks(cm) { | |
| var state = cm.state.lint; | |
| if (state.hasGutter) cm.clearGutter(GUTTER_ID); | |
| if (state.options.highlightLines) clearErrorLines(cm); | |
| for (var i = 0; i < state.marked.length; ++i) | |
| state.marked[i].clear(); | |
| state.marked.length = 0; | |
| } | |
| function clearErrorLines(cm) { | |
| cm.eachLine(function(line) { | |
| var has = line.wrapClass && /\bCodeMirror-lint-line-\w+\b/.exec(line.wrapClass); | |
| if (has) cm.removeLineClass(line, "wrap", has[0]); | |
| }) | |
| } | |
| function makeMarker(cm, labels, severity, multiple, tooltips) { | |
| var marker = document.createElement("div"), inner = marker; | |
| marker.className = "CodeMirror-lint-marker CodeMirror-lint-marker-" + severity; | |
| if (multiple) { | |
| inner = marker.appendChild(document.createElement("div")); | |
| inner.className = "CodeMirror-lint-marker CodeMirror-lint-marker-multiple"; | |
| } | |
| if (tooltips != false) CodeMirror.on(inner, "mouseover", function(e) { | |
| showTooltipFor(cm, e, labels, inner); | |
| }); | |
| return marker; | |
| } | |
| function getMaxSeverity(a, b) { | |
| if (a == "error") return a; | |
| else return b; | |
| } | |
| function groupByLine(annotations) { | |
| var lines = []; | |
| for (var i = 0; i < annotations.length; ++i) { | |
| var ann = annotations[i], line = ann.from.line; | |
| (lines[line] || (lines[line] = [])).push(ann); | |
| } | |
| return lines; | |
| } | |
| function annotationTooltip(ann) { | |
| var severity = ann.severity; | |
| if (!severity) severity = "error"; | |
| var tip = document.createElement("div"); | |
| tip.className = "CodeMirror-lint-message CodeMirror-lint-message-" + severity; | |
| if (typeof ann.messageHTML != 'undefined') { | |
| tip.innerHTML = ann.messageHTML; | |
| } else { | |
| tip.appendChild(document.createTextNode(ann.message)); | |
| } | |
| return tip; | |
| } | |
| function lintAsync(cm, getAnnotations) { | |
| var state = cm.state.lint | |
| var id = ++state.waitingFor | |
| function abort() { | |
| id = -1 | |
| cm.off("change", abort) | |
| } | |
| cm.on("change", abort) | |
| getAnnotations(cm.getValue(), function(annotations, arg2) { | |
| cm.off("change", abort) | |
| if (state.waitingFor != id) return | |
| if (arg2 && annotations instanceof CodeMirror) annotations = arg2 | |
| cm.operation(function() {updateLinting(cm, annotations)}) | |
| }, state.linterOptions, cm); | |
| } | |
| function startLinting(cm) { | |
| var state = cm.state.lint; | |
| if (!state) return; | |
| var options = state.options; | |
| /* | |
| * Passing rules in `options` property prevents JSHint (and other linters) from complaining | |
| * about unrecognized rules like `onUpdateLinting`, `delay`, `lintOnChange`, etc. | |
| */ | |
| var getAnnotations = options.getAnnotations || cm.getHelper(CodeMirror.Pos(0, 0), "lint"); | |
| if (!getAnnotations) return; | |
| if (options.async || getAnnotations.async) { | |
| lintAsync(cm, getAnnotations) | |
| } else { | |
| var annotations = getAnnotations(cm.getValue(), state.linterOptions, cm); | |
| if (!annotations) return; | |
| if (annotations.then) annotations.then(function(issues) { | |
| cm.operation(function() {updateLinting(cm, issues)}) | |
| }); | |
| else cm.operation(function() {updateLinting(cm, annotations)}) | |
| } | |
| } | |
| function updateLinting(cm, annotationsNotSorted) { | |
| var state = cm.state.lint; | |
| if (!state) return; | |
| var options = state.options; | |
| clearMarks(cm); | |
| var annotations = groupByLine(annotationsNotSorted); | |
| for (var line = 0; line < annotations.length; ++line) { | |
| var anns = annotations[line]; | |
| if (!anns) continue; | |
| // filter out duplicate messages | |
| var message = []; | |
| anns = anns.filter(function(item) { return message.indexOf(item.message) > -1 ? false : message.push(item.message) }); | |
| var maxSeverity = null; | |
| var tipLabel = state.hasGutter && document.createDocumentFragment(); | |
| for (var i = 0; i < anns.length; ++i) { | |
| var ann = anns[i]; | |
| var severity = ann.severity; | |
| if (!severity) severity = "error"; | |
| maxSeverity = getMaxSeverity(maxSeverity, severity); | |
| if (options.formatAnnotation) ann = options.formatAnnotation(ann); | |
| if (state.hasGutter) tipLabel.appendChild(annotationTooltip(ann)); | |
| if (ann.to) state.marked.push(cm.markText(ann.from, ann.to, { | |
| className: "CodeMirror-lint-mark CodeMirror-lint-mark-" + severity, | |
| __annotation: ann | |
| })); | |
| } | |
| // use original annotations[line] to show multiple messages | |
| if (state.hasGutter) | |
| cm.setGutterMarker(line, GUTTER_ID, makeMarker(cm, tipLabel, maxSeverity, annotations[line].length > 1, | |
| options.tooltips)); | |
| if (options.highlightLines) | |
| cm.addLineClass(line, "wrap", LINT_LINE_ID + maxSeverity); | |
| } | |
| if (options.onUpdateLinting) options.onUpdateLinting(annotationsNotSorted, annotations, cm); | |
| } | |
| function onChange(cm) { | |
| var state = cm.state.lint; | |
| if (!state) return; | |
| clearTimeout(state.timeout); | |
| state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay); | |
| } | |
| function popupTooltips(cm, annotations, e) { | |
| var target = e.target || e.srcElement; | |
| var tooltip = document.createDocumentFragment(); | |
| for (var i = 0; i < annotations.length; i++) { | |
| var ann = annotations[i]; | |
| tooltip.appendChild(annotationTooltip(ann)); | |
| } | |
| showTooltipFor(cm, e, tooltip, target); | |
| } | |
| function onMouseOver(cm, e) { | |
| var target = e.target || e.srcElement; | |
| if (!/\bCodeMirror-lint-mark-/.test(target.className)) return; | |
| var box = target.getBoundingClientRect(), x = (box.left + box.right) / 2, y = (box.top + box.bottom) / 2; | |
| var spans = cm.findMarksAt(cm.coordsChar({left: x, top: y}, "client")); | |
| var annotations = []; | |
| for (var i = 0; i < spans.length; ++i) { | |
| var ann = spans[i].__annotation; | |
| if (ann) annotations.push(ann); | |
| } | |
| if (annotations.length) popupTooltips(cm, annotations, e); | |
| } | |
| CodeMirror.defineOption("lint", false, function(cm, val, old) { | |
| if (old && old != CodeMirror.Init) { | |
| clearMarks(cm); | |
| if (cm.state.lint.options.lintOnChange !== false) | |
| cm.off("change", onChange); | |
| CodeMirror.off(cm.getWrapperElement(), "mouseover", cm.state.lint.onMouseOver); | |
| clearTimeout(cm.state.lint.timeout); | |
| delete cm.state.lint; | |
| } | |
| if (val) { | |
| var gutters = cm.getOption("gutters"), hasLintGutter = false; | |
| for (var i = 0; i < gutters.length; ++i) if (gutters[i] == GUTTER_ID) hasLintGutter = true; | |
| var state = cm.state.lint = new LintState(cm, val, hasLintGutter); | |
| if (state.options.lintOnChange) | |
| cm.on("change", onChange); | |
| if (state.options.tooltips != false && state.options.tooltips != "gutter") | |
| CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver); | |
| startLinting(cm); | |
| } | |
| }); | |
| CodeMirror.defineExtension("performLint", function() { | |
| startLinting(this); | |
| }); | |
| }); |