Arlolra has uploaded a new change for review. ( https://gerrit.wikimedia.org/r/326062 )
Change subject: WIP: [linter] Use ext api for linter ...................................................................... WIP: [linter] Use ext api for linter Change-Id: Ib0ff28c14c953751f0c20985c790a2c7faa9dedb --- M lib/config/MWParserEnvironment.js M lib/config/ParsoidConfig.js M lib/config/WikiConfig.js M lib/config/extapi.js M lib/ext/Cite/index.js R lib/ext/Linter/index.js M lib/index.js M lib/logger/ParsoidLogger.js D lib/logger/linter.js M lib/wt2html/DOMPostProcessor.js R tests/mocha/linter.js 11 files changed, 168 insertions(+), 188 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/services/parsoid refs/changes/62/326062/1 diff --git a/lib/config/MWParserEnvironment.js b/lib/config/MWParserEnvironment.js index 6c90362..ed359e1 100644 --- a/lib/config/MWParserEnvironment.js +++ b/lib/config/MWParserEnvironment.js @@ -9,7 +9,6 @@ var Batcher = require('../mw/Batcher.js').Batcher; var Util = require('../utils/Util.js').Util; var ParserPipelineFactory = require('../wt2html/parser.js').ParserPipelineFactory; -var Linter = require('../logger/linter.js').Linter; var ParsoidLogger = require('../logger/ParsoidLogger.js').ParsoidLogger; /** @@ -121,7 +120,6 @@ // Sets ids on nodes and stores data-* attributes in a JSON blob this.pageBundle = false; - this.linter = new Linter(this); this.configureLogging(); this.initializeForPageName(options.pageName || this.defaultPageName); @@ -259,12 +257,8 @@ "fatal", "error", "warn", "info", ]; - if (this.conf.parsoid.linting && !this.conf.parsoid.linterSendAPI) { - defaultLogLevels.push("lint"); - } - // Configure backends - logger.registerLoggingBackends(defaultLogLevels, this.conf.parsoid, this.linter); + logger.registerLoggingBackends(defaultLogLevels, this.conf.parsoid); }; // The default page name (true name, without wikitext url encoding) diff --git a/lib/config/ParsoidConfig.js b/lib/config/ParsoidConfig.js index ba87fcd..7569e96 100644 --- a/lib/config/ParsoidConfig.js +++ b/lib/config/ParsoidConfig.js @@ -222,16 +222,16 @@ ParsoidConfig.prototype.addHTMLTemplateParameters = false; /** - * @property {boolean|Array} linting Whether to enable linter Backend. - * Or an array of enabled lint types + * @property {boolean|Array} linting + * Whether to enable linting. Or, an array of enabled lint types. */ ParsoidConfig.prototype.linting = false; /** * @property {boolean} linterSendAPI * Whether to send lint errors to the MW API - * Requires the MW Linter extension to be installed - * and configured. + * Requires the MW Linter extension to be installed and configured. + * Otherwise, logs at level "info/lint" when linting is enabled. */ ParsoidConfig.prototype.linterSendAPI = false; diff --git a/lib/config/WikiConfig.js b/lib/config/WikiConfig.js index f266f53..c51b10d 100644 --- a/lib/config/WikiConfig.js +++ b/lib/config/WikiConfig.js @@ -524,10 +524,7 @@ this.extensionTags.set(tag.name.toLowerCase(), tag); }, this); if (ext.config.hasOwnProperty('domPostProcessor')) { - this.extensionPostProcessors.push({ - tag: tags.map(function(t) { return t.name; }).join('+'), - domPP: ext.config.domPostProcessor, - }); + this.extensionPostProcessors.push(ext.config.domPostProcessor); } if (ext.config.hasOwnProperty('styles')) { ext.config.styles.forEach(function(s) { diff --git a/lib/config/extapi.js b/lib/config/extapi.js index 193e30e..15c3aee 100644 --- a/lib/config/extapi.js +++ b/lib/config/extapi.js @@ -31,8 +31,10 @@ Util: require('../utils/Util.js').Util, DOMUtils: require('../utils/DOMUtils.js').DOMUtils, JSUtils: require('../utils/jsutils.js').JSUtils, + DOMTraverser: require('../utils/DOMTraverser.js').DOMTraverser, addMetaData: require('../wt2html/DOMPostProcessor.js').DOMPostProcessor.addMetaData, defines: require('../wt2html/parser.defines.js'), + ApiRequest: require('../mw/ApiRequest.js'), }; }, }; diff --git a/lib/ext/Cite/index.js b/lib/ext/Cite/index.js index f523d80..abd0968 100644 --- a/lib/ext/Cite/index.js +++ b/lib/ext/Cite/index.js @@ -692,7 +692,10 @@ this.ref = new Ref(this); this.references = new References(this); this.config = { - domPostProcessor: this.domPostProcessor.bind(this), + domPostProcessor: { + name: 'cite', + proc: this.domPostProcessor.bind(this), + }, tags: [ { name: 'ref', diff --git a/lib/wt2html/pp/handlers/linter.js b/lib/ext/Linter/index.js similarity index 61% rename from lib/wt2html/pp/handlers/linter.js rename to lib/ext/Linter/index.js index e5cf66d..fb07a6c 100644 --- a/lib/wt2html/pp/handlers/linter.js +++ b/lib/ext/Linter/index.js @@ -1,3 +1,95 @@ +'use strict'; + +var ParsoidExtApi = module.parent.require('./extapi.js').versionCheck('^0.6.1'); + +var DU = ParsoidExtApi.DOMUtils; +var Util = ParsoidExtApi.Util; +var DOMTraverser = ParsoidExtApi.DOMTraverser; +var LintRequest = ParsoidExtApi.ApiRequest.LintRequest; + +var Linter = function() { + this.buffer = []; + this.config = { + domPostProcessor: { + name: 'linter', + proc: this.domPostProcessor.bind(this), + }, + }; +}; + +Linter.prototype.domPostProcessor = function(node, env, options, atTopLevel) { + if (env.conf.parsoid.linting && atTopLevel) { + var domVisitor = new DOMTraverser(env); + domVisitor.addHandler(null, this.logWikitextFixups.bind(this)); + domVisitor.traverse(node, env, options, atTopLevel); + if (env.conf.parsoid.linterSendAPI) { + this.sendToApi(env); + } + } +}; + +Linter.prototype.lint = function(env, type, lintObj) { + console.assert(env.conf.parsoid.linting, 'How did we get here?'); + + if (!env.conf.parsoid.linterSendAPI) { + env.log('info/lint/' + type, lintObj); + return; + } + + var msg = { + type: type, + params: lintObj.params || {}, + }; + + var dsr = lintObj.dsr; + if (dsr) { + msg.dsr = dsr; + } + + if (lintObj.templateInfo) { + msg.templateInfo = lintObj.templateInfo; + } + + var src = lintObj.src; + if (type === 'fostered' || type === 'multi-template' || type === 'mixed-content') { + msg.src = src; + } else if (dsr) { + msg.src = src.substring(dsr[0], dsr[1]); + } + + this.buffer.push(msg); +}; + +Linter.prototype.sendToApi = function(env) { + console.assert(env.conf.parsoid.linterSendAPI, 'How did we get here?'); + + var enabledBuffer; + if (env.conf.parsoid.linting === true) { + enabledBuffer = this.buffer; // Everything is enabled + } else if (Array.isArray(env.conf.parsoid.linting)) { + enabledBuffer = this.buffer.filter(function(item) { + return env.conf.parsoid.linting.indexOf(item.type) !== -1; + }); + } else { + console.assert(false, 'Why are we here? Linting is disabled.'); + } + + if (env.page.id % env.conf.parsoid.linterAPISampling !== 0) { + return; + } + + // Only send the request if it the latest revision + if (env.page.meta.revision.revid === env.page.latest) { + LintRequest.promise(env, JSON.stringify(enabledBuffer)) + .then(function(data) { + if (data.error) { env.log('error/lint-api', data.error); } + }) + .catch(function(e) { + env.log('error/lint-api', e); + }); + } +}; + /* * DOM pass that walks the DOM tree and places a call to logger * with logtype 'lint/*' to log the following scenarios: @@ -10,10 +102,39 @@ * 6. Obsolete HTML tags * 7. Self-closed HTML tags */ -'use strict'; +Linter.prototype.logWikitextFixups = function(node, env, atTopLevel, tplInfo) { + // For now, don't run linter in subpipelines. + // Only on the final DOM for the top-level page. + if (!atTopLevel || !DU.isElt(node)) { + return true; + } -var DU = require('../../../utils/DOMUtils.js').DOMUtils; -var Util = require('../../../utils/Util.js').Util; + var dp = DU.getDataParsoid(node); + + if (tplInfo && tplInfo.first === node) { + // Log transclusions with more than one part + this.logTransclusions(env, node, dp, tplInfo); + } + + // Log Tree Builder fixups + this.logTreeBuilderFixup(env, node, dp, tplInfo); + + // Log Ignored Table Attributes + this.logIgnoredTableAttr(env, node, dp, tplInfo); + + // Log obsolete HTML tags + this.logObsoleteHTMLTags(env, node, dp, tplInfo); + + // Log bogus image options + this.logBogusImageOptions(env, node, dp, tplInfo); + + if (dp.fostered) { + // Log Fostered content + return this.logfFosteredContent(env, node, dp, tplInfo); + } else { + return true; + } +}; /* * Log Transclusion with more than one parts @@ -27,7 +148,7 @@ * https://www.mediawiki.org/wiki/Parsoid/MediaWiki_DOM_spec#Transclusion_content */ -function logTransclusions(env, node, dp, tplInfo) { +Linter.prototype.logTransclusions = function(env, node, dp, tplInfo) { var dmw = DU.getDataMw(node); if (dmw) { var dsr = tplInfo.dsr; @@ -36,7 +157,7 @@ var lintObj; if (typeof parts[0] === 'string' || typeof parts[parts.length - 1] === 'string') { lintObj = {src: env.page.src, dsr: dsr }; - env.log('lint/mixed-content', lintObj); + this.lint(env, 'mixed-content', lintObj); } else if (parts.length > 1) { var targets = []; dmw.parts.forEach(function(a) { @@ -46,12 +167,12 @@ }); if (targets.length > 1) { lintObj = { src: targets, dsr: dsr }; - env.log('lint/multi-template', lintObj); + this.lint(env, 'multi-template', lintObj); } } } } -} +}; /* * Log Treebuilder fixups marked by dom.markTreeBuilderFixup.js @@ -61,7 +182,7 @@ * 2. Unclosed start tags * 3. Stripped tags */ -function logTreeBuilderFixup(env, c, dp, tplInfo) { +Linter.prototype.logTreeBuilderFixup = function(env, c, dp, tplInfo) { var cNodeName = c.nodeName.toLowerCase(); var dsr = dp.dsr; var lintObj; @@ -78,7 +199,7 @@ var type = c.getAttribute('typeof'); if (type === 'mw:Placeholder/StrippedTag') { lintObj = { src: env.page.src, dsr: dsr, templateInfo: templateInfo }; - env.log('lint/stripped-tag', lintObj); + this.lint(env, 'stripped-tag', lintObj); } } @@ -98,7 +219,7 @@ templateInfo: templateInfo, params: { name: cNodeName }, }; - env.log('lint/self-closed-tag', lintObj); + this.lint(env, 'self-closed-tag', lintObj); // The other checks won't pass - no need to test them. return; } @@ -110,7 +231,7 @@ templateInfo: templateInfo, params: { name: cNodeName }, }; - env.log('lint/missing-end-tag', lintObj); + this.lint(env, 'missing-end-tag', lintObj); } if (dp.autoInsertedStart === true && (tplInfo || dsr[3] > 0)) { @@ -119,10 +240,10 @@ dsr: dsr, templateInfo: templateInfo, }; - env.log('lint/missing-start-tag', lintObj); + this.lint(env, 'missing-start-tag', lintObj); } } -} +}; /* * Log ignored table attributes. @@ -135,7 +256,7 @@ * * Here foo gets ignored and is found in the data-parsoid of <tr> tags. */ -function logIgnoredTableAttr(env, c, dp, tplInfo) { +Linter.prototype.logIgnoredTableAttr = function(env, c, dp, tplInfo) { var dsr; var templateInfo; if (DU.hasNodeName(c, "table")) { @@ -165,7 +286,7 @@ dsr = dp.dsr; } var lintObj = { src: env.page.src, dsr: dsr, templateInfo: templateInfo }; - env.log('lint/ignored-table-attr', lintObj); + this.lint(env, 'ignored-table-attr', lintObj); } } } @@ -175,7 +296,7 @@ fc = fc.nextSibling; } } -} +}; /* * Log fostered content marked by markFosteredContent.js @@ -189,7 +310,7 @@ * * Here 'foo' gets fostered out. */ -function logFosteredContent(env, node, dp, tplInfo) { +Linter.prototype.logFosteredContent = function(env, node, dp, tplInfo) { var fosteredSRC = node.innerHTML; var nextSibling = node.nextSibling; while (nextSibling && !DU.hasNodeName(nextSibling, 'table')) { @@ -208,9 +329,9 @@ dsr = DU.getDataParsoid(nextSibling).dsr; } var lintObj = { src: fosteredSRC, dsr: dsr, templateInfo: templateInfo }; - env.log('lint/fostered', lintObj); + this.lint(env, 'fostered', lintObj); return nextSibling; -} +}; /* * @@ -218,7 +339,7 @@ * See - http://www.w3.org/TR/html5/obsolete.html#non-conforming-features * */ -function logObsoleteHTMLTags(env, c, dp, tplInfo) { +Linter.prototype.logObsoleteHTMLTags = function(env, c, dp, tplInfo) { var re = /^(BIG|CENTER|FONT|STRIKE|TT)$/; if (!(dp.autoInsertedStart && dp.autoInsertedEnd) && re.test(c.nodeName)) { @@ -232,17 +353,17 @@ templateInfo: templateInfo, params: { name: c.nodeName.toLowerCase() }, }; - env.log('lint/obsolete-tag', lintObj); + this.lint(env, 'obsolete-tag', lintObj); } -} +}; /* * * Log bogus (=unrecognized) image options -* See - https://www.mediawiki.org/wiki/Help:Images#Syntax +* See - https://www.mediawiki.org/wiki/Help:Images#Syntax * */ -function logBogusImageOptions(env, c, dp, tplInfo) { +Linter.prototype.logBogusImageOptions = function(env, c, dp, tplInfo) { if (DU.isGeneratedFigure(c)) { var items = []; dp.optList.forEach(function(item) { @@ -255,7 +376,7 @@ if (tplInfo) { templateInfo = { name: DU.findEnclosingTemplateName(tplInfo) }; } - env.log('lint/bogus-image-options', { + this.lint(env, 'bogus-image-options', { src: env.page.src, dsr: tplInfo ? tplInfo.dsr : dp.dsr, templateInfo: templateInfo, @@ -263,42 +384,8 @@ }); } } -} +}; -function logWikitextFixups(node, env, atTopLevel, tplInfo) { - // For now, don't run linter in subpipelines. - // Only on the final DOM for the top-level page. - if (!atTopLevel || !DU.isElt(node)) { - return true; - } - - var dp = DU.getDataParsoid(node); - - if (tplInfo && tplInfo.first === node) { - // Log transclusions with more than one part - logTransclusions(env, node, dp, tplInfo); - } - - // Log Tree Builder fixups - logTreeBuilderFixup(env, node, dp, tplInfo); - - // Log Ignored Table Attributes - logIgnoredTableAttr(env, node, dp, tplInfo); - - // Log obsolete HTML tags - logObsoleteHTMLTags(env, node, dp, tplInfo); - - // Log bogus image options - logBogusImageOptions(env, node, dp, tplInfo); - - if (dp.fostered) { - // Log Fostered content - return logFosteredContent(env, node, dp, tplInfo); - } else { - return true; - } -} - -if (typeof module === "object") { - module.exports.logWikitextFixups = logWikitextFixups; +if (typeof module === 'object') { + module.exports = Linter; } diff --git a/lib/index.js b/lib/index.js index 5238bb0..80c2dd9 100644 --- a/lib/index.js +++ b/lib/index.js @@ -174,8 +174,7 @@ // By default, set the loggerBackend and metrics to service-runner's. var parsoidOptions = { loggerBackend: function(logData, cb) { - var type = logData.logType.replace(/^lint/, 'info/lint'); - options.logger.log(type, prepareLog(logData)); + options.logger.log(logData.logType, prepareLog(logData)); cb(); }, metrics: options.metrics, diff --git a/lib/logger/ParsoidLogger.js b/lib/logger/ParsoidLogger.js index 15973b2..54b358c 100644 --- a/lib/logger/ParsoidLogger.js +++ b/lib/logger/ParsoidLogger.js @@ -58,7 +58,7 @@ return this._defaultTracerBackend.bind(this); }; -ParsoidLogger.prototype.registerLoggingBackends = function(defaultLogLevels, parsoidConfig, linter) { +ParsoidLogger.prototype.registerLoggingBackends = function(defaultLogLevels, parsoidConfig) { // Register a default backend based on default logTypes. // DEFAULT: Combine all regexp-escaped default logTypes into a single regexp. var fixLogType = function(logType) { return Util.escapeRegExp(logType) + "(\\/|$)"; }; @@ -112,10 +112,6 @@ } else if (parsoidConfig.debugFlags) { this.registerBackend(buildTraceOrDebugFlag(parsoidConfig.debugFlags, "debug"), tracerBackend); - } - if (linter && parsoidConfig.linting) { - this.registerBackend(/^lint(\/.*)?/, linter.linterBackend.bind(linter)); - this.registerBackend(/^end(\/.*)/, linter.logLintOutput.bind(linter)); } }; diff --git a/lib/logger/linter.js b/lib/logger/linter.js deleted file mode 100644 index dd8d74b..0000000 --- a/lib/logger/linter.js +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Logger backend for linter. - * This backend filters out logging messages with Logtype "lint/*" and - * logs them (console, external service). - */ - -'use strict'; - -var LintRequest = require('../mw/ApiRequest.js').LintRequest; - -var Linter = function(env) { - this._env = env; - this.buffer = []; -}; - -Linter.prototype.logLintOutput = function(logData, cb) { - var env = this._env; - var enabledBuffer; - try { - // Everything is enabled - if (env.conf.parsoid.linting === true) { - enabledBuffer = this.buffer; - } else if (Array.isArray(env.conf.parsoid.linting)) { - enabledBuffer = this.buffer.filter(function(item) { - return env.conf.parsoid.linting.indexOf(item.type) !== -1; - }); - } - - this.buffer = []; - - if (env.page.id % env.conf.parsoid.linterAPISampling !== 0) { - return; - } - - if (env.conf.parsoid.linterSendAPI && - // Only send the request if it the latest revision - env.page.meta.revision.revid === env.page.latest) { - LintRequest.promise(env, JSON.stringify(enabledBuffer)) - .then(function(data) { - if (data.error) { env.log('error/lint-api', data.error); } - }) - .catch(function(e) { - env.log('error/lint-api', e); - }); - } - } catch (e) { - env.log('error/lint-api', "Error in logLintOutput: " + e); - } finally { - cb(); - } -}; - -Linter.prototype.linterBackend = function(logData, cb) { - // Wrap in try-catch-finally so we can more accurately - // pin errors to specific logging backends - try { - var lintObj = logData.logObject[0]; - - var msg = { - type: logData.logType.match(/lint\/(.*)/)[1], - params: lintObj.params || {}, - }; - - var dsr = lintObj.dsr; - if (dsr) { - msg.dsr = dsr; - } - - if (lintObj.templateInfo) { - msg.templateInfo = lintObj.templateInfo; - } - - var src = lintObj.src; - if (msg.type === 'fostered' || - msg.type === 'multi-template' || - msg.type === 'mixed-content') { - msg.src = src; - } else if (dsr) { - msg.src = src.substring(dsr[0], dsr[1]); - } - - this.buffer.push(msg); - } catch (e) { - this._env.log("error/linter", "Error in linterBackend: " + e); - } finally { - cb(); - } -}; - -if (typeof module === "object") { - module.exports.Linter = Linter; -} diff --git a/lib/wt2html/DOMPostProcessor.js b/lib/wt2html/DOMPostProcessor.js index 308cdb1..93648cd 100644 --- a/lib/wt2html/DOMPostProcessor.js +++ b/lib/wt2html/DOMPostProcessor.js @@ -24,7 +24,6 @@ var wrapTemplates = require('./pp/processors/wrapTemplates.js').wrapTemplates; // handlers -var logWikitextFixup = require('./pp/handlers/linter.js').logWikitextFixups; var CleanUp = require('./pp/handlers/cleanup.js'); var headings = require('./pp/handlers/headings.js'); var unpackDOMFragments = require('./pp/handlers/unpackDOMFragments.js').unpackDOMFragments; @@ -137,19 +136,13 @@ // A pure DOM transformation env.conf.wiki.extensionPostProcessors.forEach(function(extPP) { - addPP('tag:' + extPP.tag, extPP.domPP); - }, this); + addPP(extPP.name, extPP.proc); + }); // Strip empty elements from template content domVisitor = new DOMTraverser(env); domVisitor.addHandler(null, CleanUp.stripEmptyElements); addPP('stripEmptyElts', domVisitor.traverse.bind(domVisitor)); - - if (env.conf.parsoid.linting) { - domVisitor = new DOMTraverser(env); - domVisitor.addHandler(null, logWikitextFixup); - addPP('linter', domVisitor.traverse.bind(domVisitor)); - } domVisitor = new DOMTraverser(env); var tableFixer = new TableFixups(env); diff --git a/tests/mocha/lintertest.js b/tests/mocha/linter.js similarity index 98% rename from tests/mocha/lintertest.js rename to tests/mocha/linter.js index 05871ff..dc94743 100644 --- a/tests/mocha/lintertest.js +++ b/tests/mocha/linter.js @@ -1,9 +1,10 @@ /** Test cases for the linter */ 'use strict'; -require('../../core-upgrade.js'); /*global describe, it*/ -var should = require("chai").should(); /*jshint unused:false*/ +require('../../core-upgrade.js'); +require('chai').should(); + var ParsoidConfig = require('../../lib/config/ParsoidConfig.js').ParsoidConfig; var helpers = require('./test.helpers.js'); -- To view, visit https://gerrit.wikimedia.org/r/326062 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Ib0ff28c14c953751f0c20985c790a2c7faa9dedb Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/services/parsoid Gerrit-Branch: master Gerrit-Owner: Arlolra <abrea...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits