build: Replace jshint/jscs with eslint, introduce stylelint

Change-Id: I9b5afb39f5a0d32e722b2a1f56c219b8c85b451c
This commit is contained in:
Ed Sanders 2017-03-21 10:13:18 +00:00 committed by Jforrester
parent a6427ea4c9
commit 66ba217ba2
10 changed files with 396 additions and 377 deletions

15
.eslintrc.json Normal file
View file

@ -0,0 +1,15 @@
{
"extends": "wikimedia",
"env": {
"browser": true,
"jquery": true,
"qunit": true
},
"globals": {
"mediaWiki": false,
"CodeMirror": false
},
"rules": {
"dot-notation": [ "error", { "allowKeywords": true } ]
}
}

View file

@ -1,6 +0,0 @@
{
"preset": "wikimedia",
"requireVarDeclFirst": null,
"requireMultipleVarDecl": null,
"disallowEmptyBlocks": null
}

View file

@ -1,4 +0,0 @@
node_modules
vendor
# upstream libs
resources/lib/

View file

@ -1,25 +0,0 @@
{
// Enforcing
"bitwise": true,
"eqeqeq": true,
"freeze": true,
"latedef": true,
"noarg": true,
"nonew": true,
"undef": true,
"unused": true,
"strict": false,
// Relaxing
"es5": false,
// Environment
"browser": true,
"jquery": true,
"globals": {
"mediaWiki": false,
"CodeMirror": false,
"mod": false
}
}

3
.stylelintrc Normal file
View file

@ -0,0 +1,3 @@
{
"extends": "stylelint-config-wikimedia"
}

View file

@ -1,23 +1,24 @@
/*jshint node:true */
/* eslint-env node, es6 */
module.exports = function ( grunt ) {
grunt.loadNpmTasks( 'grunt-contrib-jshint' );
grunt.loadNpmTasks( 'grunt-jsonlint' );
grunt.loadNpmTasks( 'grunt-banana-checker' );
grunt.loadNpmTasks( 'grunt-jscs' );
grunt.loadNpmTasks( 'grunt-eslint' );
grunt.loadNpmTasks( 'grunt-jsonlint' );
grunt.loadNpmTasks( 'grunt-stylelint' );
grunt.initConfig( {
jshint: {
options: {
jshintrc: true
},
eslint: {
all: [
'**/*.js',
'!resources/lib/**',
'!node_modules/**'
]
},
jscs: {
src: '<%= jshint.all %>'
stylelint: {
all: [
'**/*.css',
'!resources/lib/**',
'!node_modules/**'
]
},
banana: {
all: 'i18n/'
@ -30,6 +31,6 @@ module.exports = function ( grunt ) {
}
} );
grunt.registerTask( 'test', [ 'jshint', 'jscs', 'jsonlint', 'banana' ] );
grunt.registerTask( 'test', [ 'eslint', 'stylelint', 'jsonlint', 'banana' ] );
grunt.registerTask( 'default', 'test' );
};

View file

@ -3,11 +3,12 @@
"test": "grunt test"
},
"devDependencies": {
"grunt": "0.4.5",
"grunt-cli": "0.1.13",
"grunt-contrib-jshint": "0.11.3",
"grunt-banana-checker": "0.4.0",
"grunt-jscs": "2.5.0",
"grunt-jsonlint": "1.0.7"
"eslint-config-wikimedia": "0.3.0",
"grunt": "1.0.1",
"grunt-banana-checker": "0.5.0",
"grunt-eslint": "19.0.0",
"grunt-jsonlint": "1.1.0",
"grunt-stylelint": "0.7.0",
"stylelint-config-wikimedia": "0.4.1"
}
}

View file

@ -1,23 +1,30 @@
( function ( mw, $ ) {
var origTextSelection, codeMirror, api, originHooksTextarea;
if ( mw.config.get( 'wgCodeEditorCurrentLanguage' ) ) { // If the CodeEditor is used then just exit;
return;
}
// codeMirror needs a special textselection jQuery function to work, save the current one to restore when
// CodeMirror get's disabled.
var origTextSelection = $.fn.textSelection,
codeMirror = mw.user.options.get( 'usecodemirror' ) === '1' || mw.user.options.get( 'usecodemirror' ) === 1,
api = new mw.Api(),
origTextSelection = $.fn.textSelection;
codeMirror = mw.user.options.get( 'usecodemirror' ) === '1' || mw.user.options.get( 'usecodemirror' ) === 1;
api = new mw.Api();
originHooksTextarea = $.valHooks.textarea;
// function for a textselection function for CodeMirror
cmTextSelection = function ( command, options ) {
function cmTextSelection( command, options ) {
var fn, retval;
if ( !codeMirror || codeMirror.getTextArea() !== this[ 0 ] ) {
return origTextSelection.call( this, command, options );
}
var fn, retval;
fn = {
/**
* Get the contents of the textarea
*
* @return {string}
*/
getContents: function () {
return codeMirror.doc.getValue();
@ -30,6 +37,8 @@
/**
* Get the currently selected text in this textarea. Will focus the textarea
* in some browsers (IE/Opera)
*
* @return {string}
*/
getSelection: function () {
return codeMirror.doc.getSelection();
@ -38,6 +47,9 @@
/**
* Inserts text at the beginning and end of a text selection, optionally
* inserting text at the caret when selection is empty.
*
* @param {Object} options
* @return {jQuery}
*/
encapsulateSelection: function ( options ) {
return this.each( function () {
@ -78,6 +90,11 @@
* Do the splitlines stuff.
*
* Wrap each line of the selected text with pre and post
*
* @param {string} selText
* @param {string} pre
* @param {string} post
* @return {string}
*/
function doSplitLines( selText, pre, post ) {
var i,
@ -126,6 +143,9 @@
/**
* Get the position (in resolution of bytes not necessarily characters)
* in a textarea
*
* @param {Object} options
* @return {number}
*/
getCaretPosition: function ( options ) {
var caretPos = codeMirror.doc.indexFromPos( codeMirror.doc.getCursor( true ) ),
@ -145,6 +165,8 @@
/**
* Scroll a textarea to the current cursor position. You can set the cursor
* position with setSelection()
*
* @return {jQuery}
*/
scrollToCaretPosition: function () {
return this.each( function () {
@ -208,13 +230,15 @@
codeMirror.focus();
return retval;
},
}
/**
* Adds the CodeMirror button to WikiEditor
*/
addCodeMirrorToWikiEditor = function () {
function addCodeMirrorToWikiEditor() {
var msg;
if ( $( '#wikiEditor-section-main' ).length > 0 ) {
var msg = codeMirror ? 'codemirror-disable-label' : 'codemirror-enable-label';
msg = codeMirror ? 'codemirror-disable-label' : 'codemirror-enable-label';
$( '#wpTextbox1' ).wikiEditor(
'addToToolbar',
@ -231,6 +255,7 @@
action: {
type: 'callback',
execute: function ( context ) {
// eslint-disable-next-line no-use-before-define
switchCodeMirror( context );
}
}
@ -241,8 +266,7 @@
}
);
}
},
originHooksTextarea = $.valHooks.textarea;
}
// define JQuery hook for searching and replacing text using JS if CodeMirror is enabled, see Bug: T108711
$.valHooks.textarea = {
@ -308,6 +332,7 @@
.attr( 'src', $src )
.attr( 'title', mw.msg( 'codemirror-enable-label' ) );
} else {
// eslint-disable-next-line no-use-before-define
enableCodeMirror();
$src = mw.config.get( 'wgExtensionAssetsPath' ) + '/CodeMirror/resources/images/' + ( context ? 'cm-on.png' : 'old-cm-on.png' );
$img
@ -356,6 +381,7 @@
if ( $.inArray( mw.config.get( 'wgAction' ), [ 'edit', 'submit' ] ) !== -1 ) {
// This function shouldn't be called without user.options is loaded, but it's not guaranteed
mw.loader.using( 'user.options', function () {
var $image;
// This can be the string "0" if the user disabled the preference - Bug T54542#555387
if ( mw.user.options.get( 'usebetatoolbar' ) === 1 || mw.user.options.get( 'usebetatoolbar' ) === '1' ) {
// load wikiEditor's toolbar (if not already) and add our button
@ -364,7 +390,7 @@
).then( addCodeMirrorToWikiEditor );
} else {
// If WikiEditor isn't enabled, add CodeMirror button to the default wiki editor toolbar
var $image = $( '<img>' ).attr( {
$image = $( '<img>' ).attr( {
width: 23,
height: 22,
src: mw.config.get( 'wgExtensionAssetsPath' ) + '/CodeMirror/resources/images/old-cm-' + ( codeMirror ? 'on.png' : 'off.png' ),

View file

@ -1,5 +1,5 @@
.CodeMirror {
border: 1px solid #CCC;
border: 1px solid #ccc;
font-size: medium;
line-height: 1.5em;
}
@ -8,14 +8,17 @@
font-size: small;
}
/* stylelint-disable block-opening-brace-newline-before, block-opening-brace-newline-after,
block-closing-brace-space-after, declaration-block-single-line-max-declarations,
declaration-block-semicolon-newline-after, selector-list-comma-newline-after */
.cm-mw-pagename { text-decoration: underline; }
.cm-mw-matching {background-color: gold;}
.cm-mw-matching { background-color: #ffd700; }
.cm-mw-skipformatting { background-color: #adf; }
.cm-mw-list { color: #08f; font-weight: bold; background-color: #eee; }
.cm-mw-signature,
.cm-mw-hr { color: #08f; font-weight: bold; background-color: #eee; }
.cm-mw-signature, .cm-mw-hr { color: #08f; font-weight: bold; background-color: #eee; }
.cm-mw-indenting { color: #08f; font-weight: bold; background-color: #ddd; }
.cm-mw-mnemonic { color: #090; }
.cm-mw-comment { color: #aaa; font-weight: normal; }
@ -62,12 +65,12 @@ pre.cm-mw-tag-nowiki, .cm-mw-tag-nowiki {background-image: url(img/black4.png);}
.cm-mw-link-pagename { color: #219; font-weight: normal; }
.cm-mw-link-tosection { color: #08f; font-weight: normal; }
.cm-mw-link-bracket { color: #219; font-weight: normal; }
.cm-mw-link-text {}
/* .cm-mw-link-text { } */
.cm-mw-link-delimiter { color: #219; font-weight: normal; }
.cm-mw-extlink, .cm-mw-free-extlink { color: #219; font-weight: normal; }
.cm-mw-extlink-protocol, .cm-mw-free-extlink-protocol { color: #219; font-weight: bold; }
.cm-mw-extlink-text {}
/* .cm-mw-extlink-text { } */
.cm-mw-extlink-bracket { color: #219; font-weight: bold; }
.cm-mw-table-bracket { color: #e0e; font-weight: bold; }
@ -75,7 +78,7 @@ pre.cm-mw-tag-nowiki, .cm-mw-tag-nowiki {background-image: url(img/black4.png);}
.cm-mw-table-definition { color: #e0e; font-weight: normal; }
.cm-mw-table-caption { font-weight: bold; }
.cm-mw-template-ground {}
/* .cm-mw-template-ground {} */
.cm-mw-template2-ground { background-image: url( img/template4.png ); }
.cm-mw-template3-ground { background-image: url( img/template8.png ); }
.cm-mw-template-ext-ground { background-image: url( img/ext4.png ); }
@ -99,7 +102,7 @@ pre.cm-mw-tag-nowiki, .cm-mw-tag-nowiki {background-image: url(img/black4.png);}
.cm-mw-template3-ext-link-ground { background-image: url( img/template8.png ), url( img/ext4.png ), url( img/link4.png ); }
.cm-mw-template3-ext2-link-ground { background-image: url( img/template8.png ), url( img/ext4.png ), url( img/ext4.png ), url( img/link4.png ); }
.cm-mw-template3-ext3-link-ground { background-image: url( img/template8.png ), url( img/ext4.png ), url( img/ext4.png ), url( img/ext4.png ), url( img/link4.png ); }
.cm-mw-ext-ground {background-image: url(img/ext4.png)}
.cm-mw-ext-ground { background-image: url( img/ext4.png ); }
.cm-mw-ext2-ground { background-image: url( img/ext4.png ), url( img/ext4.png ); }
.cm-mw-ext3-ground { background-image: url( img/ext4.png ), url( img/ext4.png ), url( img/ext4.png ); }
.cm-mw-ext-link-ground { background-image: url( img/link4.png ); }

View file

@ -1,3 +1,4 @@
/* eslint-disable no-use-before-define */
( function ( CodeMirror ) {
'use strict';
@ -354,19 +355,20 @@
function eatLinkText() {
var linkIsBold, linkIsItalic;
return function ( stream, state ) {
var tmpstyle;
if ( stream.match( ']]' ) ) {
state.tokenize = state.stack.pop();
return makeLocalStyle( 'mw-link-bracket', state, 'nLink' );
}
if ( stream.match( '\'\'\'' ) ) {
linkIsBold = ( linkIsBold ? false : true );
linkIsBold = !linkIsBold;
return makeLocalStyle( 'mw-link-text mw-apostrophes', state );
}
if ( stream.match( '\'\'' ) ) {
linkIsItalic = ( linkIsItalic ? false : true );
linkIsItalic = !linkIsItalic;
return makeLocalStyle( 'mw-link-text mw-apostrophes', state );
}
var tmpstyle = 'mw-link-text';
tmpstyle = 'mw-link-text';
if ( linkIsBold ) {
tmpstyle += ' strong';
}
@ -627,13 +629,14 @@
function eatWikiText( style, mnemonicStyle ) {
return function ( stream, state ) {
var ch, tmp, mt, name, isCloseTag, tagname,
sol = stream.sol();
function chain( parser ) {
state.stack.push( state.tokenize );
state.tokenize = parser;
return parser( stream, state );
}
var ch,
sol = stream.sol();
if ( sol ) {
if ( stream.match( urlProtocols ) ) { // highlight free external links, bug T108448
@ -649,7 +652,7 @@
}
break;
case '=':
var tmp = stream.match( /(={0,5})(.+?(=\1\s*))$/ );
tmp = stream.match( /(={0,5})(.+?(=\1\s*))$/ );
if ( tmp ) { // Title
stream.backUp( tmp[ 2 ].length );
state.stack.push( state.tokenize );
@ -685,7 +688,7 @@
return 'mw-skipformatting';
}
// break is not necessary here
/*falls through*/
// falls through
case '{':
if ( stream.eat( '|' ) ) {
stream.eatSpace();
@ -709,10 +712,10 @@
if ( !( firstsingleletterword || stream.match( '\'\'', false ) ) ) {
prepareItalicForCorrection( stream );
}
isBold = isBold ? false : true;
isBold = !isBold;
return makeLocalStyle( 'mw-apostrophes-bold', state );
} else if ( stream.eat( '\'' ) ) { // italic
isItalic = isItalic ? false : true;
isItalic = !isItalic;
return makeLocalStyle( 'mw-apostrophes-italic', state );
}
break;
@ -726,7 +729,7 @@
return makeLocalStyle( 'mw-link-bracket', state );
}
} else {
var mt = stream.match( urlProtocols );
mt = stream.match( urlProtocols );
if ( mt ) {
state.nLink++;
stream.backUp( mt[ 0 ].length );
@ -750,7 +753,7 @@
return makeLocalStyle( 'mw-parserfunction-bracket', state );
}
// Check for parser function without '#'
var name = stream.match( /([^\s\u00a0\}\[\]<\{\'\|\&\:]+)(\:|[\s\u00a0]*)(\}\}?)?(.)?/ );
name = stream.match( /([^\s\u00a0\}\[\]<\{\'\|\&\:]+)(\:|[\s\u00a0]*)(\}\}?)?(.)?/ );
if ( name ) {
stream.backUp( name[ 0 ].length );
if ( ( name[ 2 ] === ':' || name[ 4 ] === undefined || name[ 3 ] === '}}' ) && ( name[ 1 ].toLowerCase() in config.mwextFunctionSynonyms[ 0 ] || name[ 1 ] in config.mwextFunctionSynonyms[ 1 ] ) ) {
@ -768,8 +771,8 @@
}
break;
case '<':
var isCloseTag = ( stream.eat( '/' ) ? true : false );
var tagname = stream.match( /[^>\/\s\u00a0\.\*\,\[\]\{\}\$\^\+\?\|\/\\'`~<=!@#%&\(\)-]+/ );
isCloseTag = !!stream.eat( '/' );
tagname = stream.match( /[^>\/\s\u00a0\.\*\,\[\]\{\}\$\^\+\?\|\/\\'`~<=!@#%&\(\)-]+/ );
if ( stream.match( '!--' ) ) { // coment
return chain( eatBlock( 'mw-comment', '-->' ) );
}
@ -937,9 +940,10 @@
return t.style;
},
blankLine: function ( state ) {
var ret;
if ( state.extName ) {
if ( state.extMode ) {
var ret = '';
ret = '';
if ( state.extMode.blankLine ) {
ret = ' ' + state.extMode.blankLine( state.extState );
}
@ -955,12 +959,13 @@
function eatNowiki( style, lineStyle ) {
return function ( stream, state, ownLine ) {
var s;
if ( ownLine && stream.sol() ) {
state.ownLine = true;
} else if ( ownLine === false && state.ownLine ) {
state.ownLine = false;
}
var s = ( state.ownLine ? lineStyle : style );
s = ( state.ownLine ? lineStyle : style );
if ( stream.match( /[^&]+/ ) ) {
return s;
}