User:Kephir/gadgets/cksyntax.js

/***** * THIS SCRIPT IS OBSOLETE * * It has been replaced by an update to the CodeEditor extension. This page is kept for historical interest only. *****/ mw.loader.using(['ext.wikiEditor'], function { "use strict";

if ((wgAction !== 'edit') && (wgAction !== 'submit')) return;

var wpTextbox1 = document.getElementById('wpTextbox1');

if ((window.wgPageContentModel === 'javascript') || (window.wgPageContentModel === 'css') || (window.wgPageContentModel === 'lua')) { var editCheckboxes = document.getElementsByClassName('editCheckboxes')[0]; var label = document.createElement('label'); var inputStrip = document.createElement('input'); inputStrip.type = 'checkbox'; inputStrip.checked = (window.wgPageContentModel !== 'lua'); label.textContent = "Strip trailing whitespace when saving"; editCheckboxes.appendChild(inputStrip); editCheckboxes.appendChild(label); inputStrip.id = 'cksyntax-strip-whitespace'; label.setAttribute('for', inputStrip.id); }

if (window.wgPageContentModel !== void(0)) { if (window.wgPageContentModel !== 'javascript') return; } else { /* fallback check */ if (((wgNamespaceNumber !== 2) && (wgNamespaceNumber !== 8)) || !/\.js$/.test(wgPageName)) return; }

var wpSave = document.getElementById('wpSave');

if (!wpSave || !wpTextbox1) return;

var dirty = true;

// is Function(code) equivalent to eval("function {\n"+code+"\n}")? if yes, we are in trouble /* Testing results: * IE10                  : secure, throws a SyntaxError * KJS (Konqueror)       : secure, throws a SyntaxError * SpiderMonkey (Mozilla) : secure, throws a SyntaxError * Carakan (Opera 12)    : secure, throws a SyntaxError * Rhino                 : secure, although allows some invalid inputs, including the first one tested below; silently ignores + and - and any following expression; throws a SyntaxError otherwise (no browser uses Rhino, though) * V8 (Chrom*)           : insecure, throws an error if the expression does not evaluate to a function (which is avoided easily) * JavaScriptCore        : crashes spectacularly ("WTFCrash"); does not seem to be a security vulnerability otherwise */ try { new Function('){/*', '*///');

// if we got here, this is a buggy JavaScriptCore, and the tests that follow may crash the browser. // the immediate culprit is at  // but it seems the fix should be somewhere earlier.

// even the flawed (see below) versions of V8 will reject the above // see  if (!confirm('Your browser has a buggy implementation of the Function constructor; a specially crafted invalid script may crash your browser. Perform syntax checks?')) return; } catch (e) { // OK, an error is expected here.

try { // code.google.com/p/v8/issues/detail?id=2470 new Function("return false; } && void(window._insecure = true) || function { return true;"); } catch (ee) { // OK, an error is expected here. }

if (window._insecure) if (!confirm('Your browser has an insecure implementation of the Function constructor; a specially crafted invalid script may trick your browser into executing arbitrary JavaScript. Perform syntax checks?')) return; }

mw.hook("codeEditor.configure").add(function (sess) {	sess.on("change", function { dirty = true; }); });

wpTextbox1.addEventListener('input', function (ev) {	dirty = true; }, false);

wpTextbox1.addEventListener('change', function (ev) {	dirty = true; }, false);

function checkSyntax { var fixup = 0; // DO NOT prettify. this HAS TO to be on one line (minifiers should be okay). try { eval("(") /* xkcd.com/859/ */ } catch (e) { fixup = e.lineNumber }; try { (function($, $, mw, mediaWiki, jQuery, top, self, parent, window, document, navigator, location) { new Function(wpTextbox1.value); }).call({});	} catch (e) {		// SpiderMonkey workaround: it sums the line number of the evaluated script		// with the line number of the Function call in the containing script.		// this may make sense in some contrived situations, but not here.		if (fixup)			e.lineNumber -= fixup - 1;		else if (!e.line && !e.lineNumber && window.opera) {			// Carakan reports the line and column when calling eval, but not Function.			// since we already know the code is invalid, we can safely eval it again			// and extract the error info from there.			try {				// the \n at the start is necessary to have the message in the format below				// otherwise we get "at index n"; we can parse that format too, but why bother?

// also appending an unmatched (, in case for some reason it starts parsing correctly here;				// the error location will be bogus, but at least we avoided executing a potentially				// malicious script.				eval('\n' + wpTextbox1.value + '\n('); } catch (ee) { var m = /^at line (\d+), column (\d+)/.exec(ee.message); if (m) { e.lineNumber = parseInt(m[1], 10) - 1; e.columnNumber = parseInt(m[2], 10); }			}		}

return e;	} return null; }

function scrollEditor(line, col) { // line is 1-based, while col is 0-based var codeEditor = $(wpTextbox1).data('wikiEditorContext').codeEditor;

if (codeEditor) { codeEditor.gotoLine(line, col || 0, true); codeEditor.focus; return; }

var position = 0; for (var i = 1; i < line; ++i) { position = wpTextbox1.value.indexOf('\n', position) + 1; }

var lastcol = wpTextbox1.value.indexOf('\n', position) - position; position += col ? (col < lastcol ? col : lastcol) : 0;

if (wpTextbox1.createTextRange) { // MSIE var range = textBox.createTextRange; range.collapse(true); range.moveEnd('character', position); range.moveStart('character', position); range.select; } else if (wpTextbox1.setSelectionRange) { // browsers wpTextbox1.focus; wpTextbox1.setSelectionRange(position, position); var phantom = document.createElement('div'); var style = window.getComputedStyle(wpTextbox1, ''); phantom.style.padding = '0'; phantom.style.lineHeight = style.lineHeight; phantom.style.fontFamily = style.fontFamily; phantom.style.fontSize = style.fontSize; phantom.style.fontStyle = style.fontStyle; phantom.style.fontVariant = style.fontVariant; phantom.style.letterSpacing = style.letterSpacing; phantom.style.border = style.border; phantom.style.outline = style.outline; try { phantom.style.whiteSpace = "-moz-pre-wrap" } catch(e) {} try { phantom.style.whiteSpace = "-o-pre-wrap" } catch(e) {} try { phantom.style.whiteSpace = "-pre-wrap" } catch(e) {} try { phantom.style.whiteSpace = "pre-wrap" } catch(e) {} phantom.textContent = wpTextbox1.value.substr(0, position); document.body.appendChild(phantom); // XXX: do I need this? wpTextbox1.scrollTop = phantom.scrollHeight - (wpTextbox1.clientHeight / 2); document.body.removeChild(phantom); } }

function scrollToError(e) { if ((typeof e.lineNumber === 'number') && (typeof e.columnNumber === 'number')) { // SpiderMonkey (Firefox) and Opera scrollEditor(e.lineNumber, e.columnNumber); return "Line: " + e.lineNumber + ", column: " + (e.columnNumber + 1); } else if ((typeof e.line === 'number')) { // JavaScriptCore (WebKit) scrollEditor(e.line, 1 / 0); // most errors happen on the end of the line return "Line: " + e.line; }	// nothing for V8 (Chrom*) // unknown whether we can do anything on Trident (MSIE) return "Clicking \"Show changes\" may help you locate the error."; }

wpSave.addEventListener('click', function (ev) {	if (dirty) {		$(mw).trigger('LivePreviewPrepare'); // XXX: force CodeEditor (and everything else) to update wpTextbox1.value		if (inputStrip.checked) {			wpTextbox1.value = wpTextbox1.value.replace(/[ \t]+$/mg, '');		}		var err;

dirty = false; if (err = checkSyntax) { mw.util.jsMessage("There is an error in the script you were trying to save: " + err.toString + 				"  " + scrollToError(err) + "  If you click the \"Save\" button again, this error will be ignored."); ev.preventDefault; ev.stopPropagation; return false; }	} }, false);

$(wpTextbox1).wikiEditor('addToToolbar', {	section: 'main',	group: 'format',	tools: {		cksyntax: {			label: "Check syntax",			type: 'button',			icon: '//upload.wikimedia.org/wikipedia/commons/thumb/1/15/Tick-red.png/22px-Tick-red.png',			action: {				type: 'callback',				execute: function {					var err;					$(mw).trigger('LivePreviewPrepare'); // XXX: force CodeEditor (and everything else) to update wpTextbox1.value					if (err = checkSyntax) {						mw.util.jsMessage("" + err.toString + "  " + scrollToError(err));						scrollToError(err);					} else {						mw.util.jsMessage("No syntax errors found");					}				}			}		}	} });

});